@bevel-software/platform-core-backend 0.26.0 → 0.27.0

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 (613) hide show
  1. package/agent-guide/access-control.md +17 -0
  2. package/agent-guide/conventions.md +8 -0
  3. package/agent-guide/html-views.md +83 -0
  4. package/agent-guide/new-knowledge-base.md +24 -0
  5. package/agent-guide/tool-manuals.md +10 -0
  6. package/dist/assets.d.ts +19 -0
  7. package/dist/assets.d.ts.map +1 -1
  8. package/dist/assets.js +26 -2
  9. package/dist/assets.js.map +1 -1
  10. package/dist/core/catalog-revision.d.ts +19 -1
  11. package/dist/core/catalog-revision.d.ts.map +1 -1
  12. package/dist/core/catalog-revision.js +14 -3
  13. package/dist/core/catalog-revision.js.map +1 -1
  14. package/dist/core/create-core-server.d.ts +5 -0
  15. package/dist/core/create-core-server.d.ts.map +1 -1
  16. package/dist/core/create-core-server.js +54 -3
  17. package/dist/core/create-core-server.js.map +1 -1
  18. package/dist/core/create-core-services.d.ts +26 -0
  19. package/dist/core/create-core-services.d.ts.map +1 -1
  20. package/dist/core/create-core-services.js +100 -8
  21. package/dist/core/create-core-services.js.map +1 -1
  22. package/dist/core/lifecycle.d.ts +16 -1
  23. package/dist/core/lifecycle.d.ts.map +1 -1
  24. package/dist/core/lifecycle.js +18 -1
  25. package/dist/core/lifecycle.js.map +1 -1
  26. package/dist/core-config.d.ts +21 -0
  27. package/dist/core-config.d.ts.map +1 -1
  28. package/dist/core-config.js +22 -1
  29. package/dist/core-config.js.map +1 -1
  30. package/dist/index.d.ts +1 -1
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +2 -2
  33. package/dist/index.js.map +1 -1
  34. package/dist/modules/access/access-control.interface.d.ts +8 -0
  35. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  36. package/dist/modules/access/access-control.service.d.ts +1 -0
  37. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  38. package/dist/modules/access/access-control.service.js +11 -0
  39. package/dist/modules/access/access-control.service.js.map +1 -1
  40. package/dist/modules/access/directory-sync-bot.js +1 -1
  41. package/dist/modules/access/directory-sync-bot.js.map +1 -1
  42. package/dist/modules/agent-guide/agent-guide.d.ts +6 -0
  43. package/dist/modules/agent-guide/agent-guide.d.ts.map +1 -1
  44. package/dist/modules/agent-guide/agent-guide.js +8 -0
  45. package/dist/modules/agent-guide/agent-guide.js.map +1 -1
  46. package/dist/modules/agent-guide/index.d.ts +1 -1
  47. package/dist/modules/agent-guide/index.d.ts.map +1 -1
  48. package/dist/modules/agent-guide/index.js +1 -1
  49. package/dist/modules/agent-guide/index.js.map +1 -1
  50. package/dist/modules/agent-instructions/compose.d.ts +5 -0
  51. package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
  52. package/dist/modules/agent-instructions/compose.js +19 -2
  53. package/dist/modules/agent-instructions/compose.js.map +1 -1
  54. package/dist/modules/agent-instructions/shared-file-rules.js +19 -19
  55. package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
  56. package/dist/modules/audit/agent-audit.service.d.ts +21 -2
  57. package/dist/modules/audit/agent-audit.service.d.ts.map +1 -1
  58. package/dist/modules/audit/agent-audit.service.js +43 -2
  59. package/dist/modules/audit/agent-audit.service.js.map +1 -1
  60. package/dist/modules/audit/agent-connection.routes.d.ts +34 -0
  61. package/dist/modules/audit/agent-connection.routes.d.ts.map +1 -0
  62. package/dist/modules/audit/agent-connection.routes.js +38 -0
  63. package/dist/modules/audit/agent-connection.routes.js.map +1 -0
  64. package/dist/modules/audit/audit.contract.d.ts +24 -0
  65. package/dist/modules/audit/audit.contract.d.ts.map +1 -1
  66. package/dist/modules/audit/audit.contract.js.map +1 -1
  67. package/dist/modules/audit/index.d.ts +1 -0
  68. package/dist/modules/audit/index.d.ts.map +1 -1
  69. package/dist/modules/audit/index.js +1 -0
  70. package/dist/modules/audit/index.js.map +1 -1
  71. package/dist/modules/auth/account.routes.d.ts.map +1 -1
  72. package/dist/modules/auth/account.routes.js +10 -3
  73. package/dist/modules/auth/account.routes.js.map +1 -1
  74. package/dist/modules/auth/auth.middleware.d.ts +7 -0
  75. package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
  76. package/dist/modules/auth/auth.middleware.js +1 -1
  77. package/dist/modules/auth/auth.middleware.js.map +1 -1
  78. package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
  79. package/dist/modules/code-mode/code-mode.tool.js +11 -3
  80. package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
  81. package/dist/modules/database/core-schema.d.ts +96 -0
  82. package/dist/modules/database/core-schema.d.ts.map +1 -1
  83. package/dist/modules/database/core-schema.js +32 -3
  84. package/dist/modules/database/core-schema.js.map +1 -1
  85. package/dist/modules/embed/account-link.service.d.ts +42 -0
  86. package/dist/modules/embed/account-link.service.d.ts.map +1 -0
  87. package/dist/modules/embed/account-link.service.js +50 -0
  88. package/dist/modules/embed/account-link.service.js.map +1 -0
  89. package/dist/modules/embed/embed-link.d.ts +58 -0
  90. package/dist/modules/embed/embed-link.d.ts.map +1 -0
  91. package/dist/modules/embed/embed-link.js +160 -0
  92. package/dist/modules/embed/embed-link.js.map +1 -0
  93. package/dist/modules/embed/embed.errors.d.ts +30 -0
  94. package/dist/modules/embed/embed.errors.d.ts.map +1 -0
  95. package/dist/modules/embed/embed.errors.js +42 -0
  96. package/dist/modules/embed/embed.errors.js.map +1 -0
  97. package/dist/modules/embed/embed.interface.d.ts +204 -0
  98. package/dist/modules/embed/embed.interface.d.ts.map +1 -0
  99. package/dist/modules/embed/embed.interface.js +2 -0
  100. package/dist/modules/embed/embed.interface.js.map +1 -0
  101. package/dist/modules/embed/embed.routes.d.ts +59 -0
  102. package/dist/modules/embed/embed.routes.d.ts.map +1 -0
  103. package/dist/modules/embed/embed.routes.js +343 -0
  104. package/dist/modules/embed/embed.routes.js.map +1 -0
  105. package/dist/modules/embed/embed.service.d.ts +169 -0
  106. package/dist/modules/embed/embed.service.d.ts.map +1 -0
  107. package/dist/modules/embed/embed.service.js +627 -0
  108. package/dist/modules/embed/embed.service.js.map +1 -0
  109. package/dist/modules/embed/embed.tools.d.ts +51 -0
  110. package/dist/modules/embed/embed.tools.d.ts.map +1 -0
  111. package/dist/modules/embed/embed.tools.js +170 -0
  112. package/dist/modules/embed/embed.tools.js.map +1 -0
  113. package/dist/modules/embed/mcp-app.d.ts +64 -0
  114. package/dist/modules/embed/mcp-app.d.ts.map +1 -0
  115. package/dist/modules/embed/mcp-app.js +147 -0
  116. package/dist/modules/embed/mcp-app.js.map +1 -0
  117. package/dist/modules/embed/mcp-app.routes.d.ts +21 -0
  118. package/dist/modules/embed/mcp-app.routes.d.ts.map +1 -0
  119. package/dist/modules/embed/mcp-app.routes.js +36 -0
  120. package/dist/modules/embed/mcp-app.routes.js.map +1 -0
  121. package/dist/modules/github-app/github-app.connection.d.ts +15 -0
  122. package/dist/modules/github-app/github-app.connection.d.ts.map +1 -1
  123. package/dist/modules/github-app/github-app.connection.js +34 -3
  124. package/dist/modules/github-app/github-app.connection.js.map +1 -1
  125. package/dist/modules/kb-fs/locking-filesystem.d.ts +65 -0
  126. package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
  127. package/dist/modules/kb-fs/locking-filesystem.js +300 -0
  128. package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
  129. package/dist/modules/kb-sync/kb-sync.interface.d.ts +5 -0
  130. package/dist/modules/kb-sync/kb-sync.interface.d.ts.map +1 -1
  131. package/dist/modules/kb-sync/kb-sync.service.d.ts.map +1 -1
  132. package/dist/modules/kb-sync/kb-sync.service.js +6 -0
  133. package/dist/modules/kb-sync/kb-sync.service.js.map +1 -1
  134. package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
  135. package/dist/modules/mcp/mcp-auth.middleware.js +2 -1
  136. package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
  137. package/dist/modules/mcp/mcp.service.d.ts +28 -1
  138. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  139. package/dist/modules/mcp/mcp.service.js +64 -8
  140. package/dist/modules/mcp/mcp.service.js.map +1 -1
  141. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +24 -0
  142. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
  143. package/dist/modules/mcp/oauth/bevel-oauth-provider.js +32 -4
  144. package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
  145. package/dist/modules/onboarding/onboarding.contract.d.ts +80 -0
  146. package/dist/modules/onboarding/onboarding.contract.d.ts.map +1 -0
  147. package/dist/modules/onboarding/onboarding.contract.js +7 -0
  148. package/dist/modules/onboarding/onboarding.contract.js.map +1 -0
  149. package/dist/modules/onboarding/onboarding.routes.d.ts +18 -0
  150. package/dist/modules/onboarding/onboarding.routes.d.ts.map +1 -0
  151. package/dist/modules/onboarding/onboarding.routes.js +81 -0
  152. package/dist/modules/onboarding/onboarding.routes.js.map +1 -0
  153. package/dist/modules/onboarding/starter-pack.service.d.ts +213 -0
  154. package/dist/modules/onboarding/starter-pack.service.d.ts.map +1 -0
  155. package/dist/modules/onboarding/starter-pack.service.js +492 -0
  156. package/dist/modules/onboarding/starter-pack.service.js.map +1 -0
  157. package/dist/modules/onboarding/starter-packs.d.ts +78 -0
  158. package/dist/modules/onboarding/starter-packs.d.ts.map +1 -0
  159. package/dist/modules/onboarding/starter-packs.js +156 -0
  160. package/dist/modules/onboarding/starter-packs.js.map +1 -0
  161. package/dist/modules/plugins/index.d.ts +1 -1
  162. package/dist/modules/plugins/index.d.ts.map +1 -1
  163. package/dist/modules/plugins/index.js +1 -1
  164. package/dist/modules/plugins/index.js.map +1 -1
  165. package/dist/modules/plugins/plugin-provision.service.d.ts +19 -0
  166. package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -1
  167. package/dist/modules/plugins/plugin-provision.service.js +18 -1
  168. package/dist/modules/plugins/plugin-provision.service.js.map +1 -1
  169. package/dist/modules/plugins/plugins.routes.d.ts +0 -36
  170. package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
  171. package/dist/modules/plugins/plugins.routes.js +31 -0
  172. package/dist/modules/plugins/plugins.routes.js.map +1 -1
  173. package/dist/modules/settings/deployment-settings.service.d.ts +34 -0
  174. package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
  175. package/dist/modules/settings/deployment-settings.service.js +134 -1
  176. package/dist/modules/settings/deployment-settings.service.js.map +1 -1
  177. package/dist/modules/settings/repository-source.d.ts +1 -0
  178. package/dist/modules/settings/repository-source.d.ts.map +1 -1
  179. package/dist/modules/settings/repository-source.js +3 -3
  180. package/dist/modules/settings/repository-source.js.map +1 -1
  181. package/dist/modules/skills/skills.tools.d.ts.map +1 -1
  182. package/dist/modules/skills/skills.tools.js +50 -32
  183. package/dist/modules/skills/skills.tools.js.map +1 -1
  184. package/dist/modules/tool-auth/connection-key-rejection.d.ts +10 -1
  185. package/dist/modules/tool-auth/connection-key-rejection.d.ts.map +1 -1
  186. package/dist/modules/tool-auth/connection-key-rejection.js +11 -3
  187. package/dist/modules/tool-auth/connection-key-rejection.js.map +1 -1
  188. package/dist/modules/tool-auth/external-api-key.interface.d.ts +8 -0
  189. package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
  190. package/dist/modules/tool-auth/external-api-key.service.d.ts +24 -2
  191. package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
  192. package/dist/modules/tool-auth/external-api-key.service.js +59 -2
  193. package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
  194. package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
  195. package/dist/modules/tool-auth/tool-auth.middleware.js +9 -4
  196. package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
  197. package/dist/modules/tool-helpers/index.d.ts +28 -1
  198. package/dist/modules/tool-helpers/index.d.ts.map +1 -1
  199. package/dist/modules/tool-helpers/index.js +28 -1
  200. package/dist/modules/tool-helpers/index.js.map +1 -1
  201. package/dist/modules/tool-helpers/route-argument-check.d.ts +10 -0
  202. package/dist/modules/tool-helpers/route-argument-check.d.ts.map +1 -0
  203. package/dist/modules/tool-helpers/route-argument-check.js +132 -0
  204. package/dist/modules/tool-helpers/route-argument-check.js.map +1 -0
  205. package/dist/modules/tool-helpers/route-tool-schemas.d.ts +89 -0
  206. package/dist/modules/tool-helpers/route-tool-schemas.d.ts.map +1 -0
  207. package/dist/modules/tool-helpers/route-tool-schemas.js +113 -0
  208. package/dist/modules/tool-helpers/route-tool-schemas.js.map +1 -0
  209. package/dist/modules/tool-helpers/tool-def.d.ts +71 -0
  210. package/dist/modules/tool-helpers/tool-def.d.ts.map +1 -1
  211. package/dist/modules/tool-helpers/tool-def.js +70 -6
  212. package/dist/modules/tool-helpers/tool-def.js.map +1 -1
  213. package/dist/modules/tool-helpers/tool-handler.d.ts +46 -6
  214. package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
  215. package/dist/modules/tool-helpers/tool-handler.js +157 -8
  216. package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
  217. package/dist/modules/tool-helpers/tool.contract.d.ts +9 -0
  218. package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
  219. package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
  220. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
  221. package/dist/modules/tool-manuals/tool-manuals.tools.js +14 -5
  222. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  223. package/dist/modules/tool-registry/description-length.d.ts +6 -2
  224. package/dist/modules/tool-registry/description-length.d.ts.map +1 -1
  225. package/dist/modules/tool-registry/description-length.js +27 -5
  226. package/dist/modules/tool-registry/description-length.js.map +1 -1
  227. package/dist/modules/tool-registry/guide-first.d.ts +4 -0
  228. package/dist/modules/tool-registry/guide-first.d.ts.map +1 -1
  229. package/dist/modules/tool-registry/guide-first.js +9 -1
  230. package/dist/modules/tool-registry/guide-first.js.map +1 -1
  231. package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts +53 -0
  232. package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts.map +1 -1
  233. package/dist/modules/workflow/agent-tools/change-request-read.tools.js +97 -68
  234. package/dist/modules/workflow/agent-tools/change-request-read.tools.js.map +1 -1
  235. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts +8 -1
  236. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
  237. package/dist/modules/workflow/agent-tools/workflow.tools.js +124 -37
  238. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
  239. package/dist/modules/workflow/file-lock.service.d.ts +29 -5
  240. package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
  241. package/dist/modules/workflow/file-lock.service.js +62 -5
  242. package/dist/modules/workflow/file-lock.service.js.map +1 -1
  243. package/dist/modules/workflow/git/git.service.d.ts +72 -1
  244. package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
  245. package/dist/modules/workflow/git/git.service.js +172 -36
  246. package/dist/modules/workflow/git/git.service.js.map +1 -1
  247. package/dist/modules/workflow/git/node-git-runner.d.ts +15 -0
  248. package/dist/modules/workflow/git/node-git-runner.d.ts.map +1 -1
  249. package/dist/modules/workflow/git/node-git-runner.js +78 -6
  250. package/dist/modules/workflow/git/node-git-runner.js.map +1 -1
  251. package/dist/modules/workflow/pending-commits.service.d.ts +15 -0
  252. package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
  253. package/dist/modules/workflow/pending-commits.service.js +25 -1
  254. package/dist/modules/workflow/pending-commits.service.js.map +1 -1
  255. package/dist/modules/workflow/pending-commits.worker.d.ts +6 -1
  256. package/dist/modules/workflow/pending-commits.worker.d.ts.map +1 -1
  257. package/dist/modules/workflow/pending-commits.worker.js +23 -6
  258. package/dist/modules/workflow/pending-commits.worker.js.map +1 -1
  259. package/dist/modules/workflow/recovery-bot.js +1 -1
  260. package/dist/modules/workflow/recovery-bot.js.map +1 -1
  261. package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
  262. package/dist/modules/workflow/review-workflow/review-workflow.service.js +2 -1
  263. package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
  264. package/dist/modules/workflow/sanitize-error.d.ts +16 -0
  265. package/dist/modules/workflow/sanitize-error.d.ts.map +1 -1
  266. package/dist/modules/workflow/sanitize-error.js +45 -0
  267. package/dist/modules/workflow/sanitize-error.js.map +1 -1
  268. package/dist/modules/workflow/workflow.service.d.ts +183 -15
  269. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  270. package/dist/modules/workflow/workflow.service.js +805 -106
  271. package/dist/modules/workflow/workflow.service.js.map +1 -1
  272. package/dist/modules/workspace/agent-download.builder.d.ts +105 -0
  273. package/dist/modules/workspace/agent-download.builder.d.ts.map +1 -0
  274. package/dist/modules/workspace/agent-download.builder.js +238 -0
  275. package/dist/modules/workspace/agent-download.builder.js.map +1 -0
  276. package/dist/modules/workspace/agent-download.routes.d.ts +77 -0
  277. package/dist/modules/workspace/agent-download.routes.d.ts.map +1 -0
  278. package/dist/modules/workspace/agent-download.routes.js +156 -0
  279. package/dist/modules/workspace/agent-download.routes.js.map +1 -0
  280. package/dist/modules/workspace/agent-download.store.d.ts +225 -0
  281. package/dist/modules/workspace/agent-download.store.d.ts.map +1 -0
  282. package/dist/modules/workspace/agent-download.store.js +423 -0
  283. package/dist/modules/workspace/agent-download.store.js.map +1 -0
  284. package/dist/modules/workspace/agent-upload.store.d.ts +10 -1
  285. package/dist/modules/workspace/agent-upload.store.d.ts.map +1 -1
  286. package/dist/modules/workspace/agent-upload.store.js +16 -6
  287. package/dist/modules/workspace/agent-upload.store.js.map +1 -1
  288. package/dist/modules/workspace/file-readers/raw-mime.d.ts +6 -0
  289. package/dist/modules/workspace/file-readers/raw-mime.d.ts.map +1 -0
  290. package/dist/modules/workspace/file-readers/raw-mime.js +38 -0
  291. package/dist/modules/workspace/file-readers/raw-mime.js.map +1 -0
  292. package/dist/modules/workspace/file-readers/text-reader.d.ts +5 -5
  293. package/dist/modules/workspace/file-readers/text-reader.d.ts.map +1 -1
  294. package/dist/modules/workspace/file-readers/text-reader.js +5 -8
  295. package/dist/modules/workspace/file-readers/text-reader.js.map +1 -1
  296. package/dist/modules/workspace/first-run.d.ts +111 -0
  297. package/dist/modules/workspace/first-run.d.ts.map +1 -0
  298. package/dist/modules/workspace/first-run.js +181 -0
  299. package/dist/modules/workspace/first-run.js.map +1 -0
  300. package/dist/modules/workspace/move-links.d.ts +92 -0
  301. package/dist/modules/workspace/move-links.d.ts.map +1 -0
  302. package/dist/modules/workspace/move-links.js +173 -0
  303. package/dist/modules/workspace/move-links.js.map +1 -0
  304. package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
  305. package/dist/modules/workspace/startup/steps/seed-tree.js +3 -13
  306. package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
  307. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
  308. package/dist/modules/workspace/workspace.routes.js +63 -22
  309. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  310. package/dist/modules/workspace/workspace.service.d.ts +75 -2
  311. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  312. package/dist/modules/workspace/workspace.service.js +197 -24
  313. package/dist/modules/workspace/workspace.service.js.map +1 -1
  314. package/dist/modules/workspace/workspace.tools.d.ts +59 -1
  315. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  316. package/dist/modules/workspace/workspace.tools.js +443 -65
  317. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  318. package/dist/modules/write-access/write-access.d.ts.map +1 -1
  319. package/dist/modules/write-access/write-access.js +9 -0
  320. package/dist/modules/write-access/write-access.js.map +1 -1
  321. package/dist/shared/domain-errors.d.ts +73 -1
  322. package/dist/shared/domain-errors.d.ts.map +1 -1
  323. package/dist/shared/domain-errors.js +86 -12
  324. package/dist/shared/domain-errors.js.map +1 -1
  325. package/dist/shared/git.contract.d.ts +10 -1
  326. package/dist/shared/git.contract.d.ts.map +1 -1
  327. package/dist/shared/git.contract.js.map +1 -1
  328. package/dist/shared/utf8-text.d.ts +15 -0
  329. package/dist/shared/utf8-text.d.ts.map +1 -0
  330. package/dist/shared/utf8-text.js +20 -0
  331. package/dist/shared/utf8-text.js.map +1 -0
  332. package/dist/tenancy/static-tenant-source.d.ts +2 -1
  333. package/dist/tenancy/static-tenant-source.d.ts.map +1 -1
  334. package/dist/tenancy/static-tenant-source.js +9 -2
  335. package/dist/tenancy/static-tenant-source.js.map +1 -1
  336. package/mcp-app/page.html +349 -0
  337. package/migrations/0017_atlassian_account_links.sql +37 -0
  338. package/migrations/meta/0017_snapshot.json +2396 -0
  339. package/migrations/meta/_journal.json +8 -1
  340. package/package.json +5 -3
  341. package/src/__tests__/packaged-assets-ship.test.ts +16 -4
  342. package/src/assets.ts +28 -2
  343. package/src/core/__tests__/catalog-revision.test.ts +40 -0
  344. package/src/core/__tests__/gated-boot.test.ts +1 -0
  345. package/src/core/__tests__/lifecycle.test.ts +14 -0
  346. package/src/core/catalog-revision.ts +15 -2
  347. package/src/core/create-core-server.ts +67 -3
  348. package/src/core/create-core-services.ts +140 -6
  349. package/src/core/lifecycle.ts +28 -2
  350. package/src/core-config.ts +24 -1
  351. package/src/index.ts +2 -2
  352. package/src/modules/access/__tests__/access-control.service.test.ts +19 -0
  353. package/src/modules/access/access-control.interface.ts +13 -0
  354. package/src/modules/access/access-control.service.ts +16 -0
  355. package/src/modules/access/directory-sync-bot.ts +1 -1
  356. package/src/modules/agent-guide/__tests__/agent-guide.test.ts +58 -0
  357. package/src/modules/agent-guide/__tests__/agent-guide.tools.test.ts +19 -1
  358. package/src/modules/agent-guide/agent-guide.ts +9 -0
  359. package/src/modules/agent-guide/index.ts +1 -0
  360. package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +14 -0
  361. package/src/modules/agent-instructions/compose.ts +17 -2
  362. package/src/modules/agent-instructions/shared-file-rules.ts +19 -19
  363. package/src/modules/audit/__tests__/agent-audit.service.test.ts +64 -4
  364. package/src/modules/audit/__tests__/agent-connection.routes.test.ts +102 -0
  365. package/src/modules/audit/agent-audit.service.ts +58 -3
  366. package/src/modules/audit/agent-connection.routes.ts +56 -0
  367. package/src/modules/audit/audit.contract.ts +26 -0
  368. package/src/modules/audit/index.ts +1 -0
  369. package/src/modules/auth/__tests__/account.routes.test.ts +5 -3
  370. package/src/modules/auth/account.routes.ts +10 -3
  371. package/src/modules/auth/auth.middleware.ts +1 -1
  372. package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +18 -2
  373. package/src/modules/code-mode/code-mode.tool.ts +13 -2
  374. package/src/modules/database/core-schema.ts +33 -3
  375. package/src/modules/embed/__tests__/account-links.migration.test.ts +98 -0
  376. package/src/modules/embed/__tests__/embed-link.test.ts +122 -0
  377. package/src/modules/embed/__tests__/embed.routes.test.ts +362 -0
  378. package/src/modules/embed/__tests__/embed.service.lock.test.ts +193 -0
  379. package/src/modules/embed/__tests__/embed.service.test.ts +805 -0
  380. package/src/modules/embed/__tests__/embed.tools.test.ts +304 -0
  381. package/src/modules/embed/__tests__/mcp-app.test.ts +207 -0
  382. package/src/modules/embed/account-link.service.ts +76 -0
  383. package/src/modules/embed/embed-link.ts +180 -0
  384. package/src/modules/embed/embed.errors.ts +43 -0
  385. package/src/modules/embed/embed.interface.ts +203 -0
  386. package/src/modules/embed/embed.routes.ts +363 -0
  387. package/src/modules/embed/embed.service.ts +705 -0
  388. package/src/modules/embed/embed.tools.ts +206 -0
  389. package/src/modules/embed/mcp-app.routes.ts +41 -0
  390. package/src/modules/embed/mcp-app.ts +173 -0
  391. package/src/modules/github-app/__tests__/github-app.test.ts +76 -0
  392. package/src/modules/github-app/github-app.connection.ts +38 -4
  393. package/src/modules/kb-fs/__tests__/move-with-edits.test.ts +304 -0
  394. package/src/modules/kb-fs/locking-filesystem.ts +287 -0
  395. package/src/modules/kb-sync/__tests__/kb-sync.service.test.ts +35 -0
  396. package/src/modules/kb-sync/kb-sync.interface.ts +2 -0
  397. package/src/modules/kb-sync/kb-sync.service.ts +6 -0
  398. package/src/modules/mcp/__tests__/bevel-oauth-provider.test.ts +68 -11
  399. package/src/modules/mcp/__tests__/mcp-proxy-helpers.test.ts +37 -2
  400. package/src/modules/mcp/__tests__/mcp.app.e2e.test.ts +200 -0
  401. package/src/modules/mcp/__tests__/mcp.e2e.test.ts +3 -1
  402. package/src/modules/mcp/__tests__/mcp.service.test.ts +23 -13
  403. package/src/modules/mcp/__tests__/tool-call-interface.e2e.test.ts +499 -0
  404. package/src/modules/mcp/mcp-auth.middleware.ts +2 -1
  405. package/src/modules/mcp/mcp.service.ts +81 -12
  406. package/src/modules/mcp/oauth/bevel-oauth-provider.ts +42 -4
  407. package/src/modules/onboarding/__tests__/onboarding.routes.test.ts +163 -0
  408. package/src/modules/onboarding/__tests__/starter-pack.service.test.ts +635 -0
  409. package/src/modules/onboarding/__tests__/starter-packs.test.ts +169 -0
  410. package/src/modules/onboarding/onboarding.contract.ts +85 -0
  411. package/src/modules/onboarding/onboarding.routes.ts +85 -0
  412. package/src/modules/onboarding/starter-pack.service.ts +580 -0
  413. package/src/modules/onboarding/starter-packs.ts +182 -0
  414. package/src/modules/plugins/__tests__/plugins.tools.test.ts +25 -2
  415. package/src/modules/plugins/index.ts +1 -0
  416. package/src/modules/plugins/plugin-provision.service.ts +22 -1
  417. package/src/modules/plugins/plugins.routes.ts +29 -0
  418. package/src/modules/settings/__tests__/deployment-settings.service.test.ts +150 -1
  419. package/src/modules/settings/__tests__/repository-source.test.ts +9 -0
  420. package/src/modules/settings/deployment-settings.service.ts +131 -1
  421. package/src/modules/settings/repository-source.ts +4 -4
  422. package/src/modules/skills/__tests__/skills.tools.description.test.ts +5 -1
  423. package/src/modules/skills/skills.tools.ts +55 -35
  424. package/src/modules/tool-auth/__tests__/connection-key-rejection.test.ts +32 -0
  425. package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +65 -0
  426. package/src/modules/tool-auth/connection-key-rejection.ts +13 -3
  427. package/src/modules/tool-auth/external-api-key.interface.ts +9 -0
  428. package/src/modules/tool-auth/external-api-key.service.ts +61 -4
  429. package/src/modules/tool-auth/tool-auth.middleware.ts +13 -4
  430. package/src/modules/tool-helpers/__tests__/branch-declaration.test.ts +502 -0
  431. package/src/modules/tool-helpers/__tests__/route-argument-check.test.ts +337 -0
  432. package/src/modules/tool-helpers/index.ts +34 -1
  433. package/src/modules/tool-helpers/route-argument-check.ts +149 -0
  434. package/src/modules/tool-helpers/route-tool-schemas.ts +171 -0
  435. package/src/modules/tool-helpers/tool-def.ts +132 -7
  436. package/src/modules/tool-helpers/tool-handler.ts +196 -8
  437. package/src/modules/tool-helpers/tool.contract.ts +9 -0
  438. package/src/modules/tool-manuals/tool-manuals.tools.ts +15 -5
  439. package/src/modules/tool-registry/__tests__/own-tool-schemas.test.ts +14 -1
  440. package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +23 -10
  441. package/src/modules/tool-registry/description-length.ts +34 -7
  442. package/src/modules/tool-registry/guide-first.ts +8 -1
  443. package/src/modules/workflow/__tests__/file-lock.service.deletion-gate.test.ts +104 -0
  444. package/src/modules/workflow/__tests__/pending-commits.repository-replaced.test.ts +66 -0
  445. package/src/modules/workflow/__tests__/pending-commits.worker.test.ts +31 -2
  446. package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +110 -6
  447. package/src/modules/workflow/__tests__/sanitize-error.test.ts +89 -1
  448. package/src/modules/workflow/__tests__/workflow.routes.push-refused.test.ts +202 -0
  449. package/src/modules/workflow/__tests__/workflow.service.branch-in-use.test.ts +6 -6
  450. package/src/modules/workflow/__tests__/workflow.service.delete-branch.test.ts +1026 -0
  451. package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +468 -10
  452. package/src/modules/workflow/__tests__/workflow.service.folder-proposals.test.ts +4 -0
  453. package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +5 -4
  454. package/src/modules/workflow/__tests__/workflow.service.update-from-target.test.ts +54 -2
  455. package/src/modules/workflow/agent-tools/__tests__/change-request-read.tools.test.ts +41 -5
  456. package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +127 -2
  457. package/src/modules/workflow/agent-tools/change-request-read.tools.ts +157 -122
  458. package/src/modules/workflow/agent-tools/workflow.tools.ts +129 -35
  459. package/src/modules/workflow/file-lock.service.ts +67 -5
  460. package/src/modules/workflow/git/__tests__/git.service.deleteBranch.test.ts +164 -0
  461. package/src/modules/workflow/git/__tests__/git.service.pull.test.ts +48 -1
  462. package/src/modules/workflow/git/__tests__/node-git-runner.test.ts +231 -2
  463. package/src/modules/workflow/git/git.service.ts +176 -38
  464. package/src/modules/workflow/git/node-git-runner.ts +82 -7
  465. package/src/modules/workflow/pending-commits.service.ts +29 -1
  466. package/src/modules/workflow/pending-commits.worker.ts +24 -6
  467. package/src/modules/workflow/recovery-bot.ts +1 -1
  468. package/src/modules/workflow/review-workflow/review-workflow.service.ts +2 -1
  469. package/src/modules/workflow/sanitize-error.ts +50 -0
  470. package/src/modules/workflow/workflow.service.ts +879 -112
  471. package/src/modules/workspace/__tests__/agent-downloads.test.ts +823 -0
  472. package/src/modules/workspace/__tests__/first-run.test.ts +234 -0
  473. package/src/modules/workspace/__tests__/git-internals.security.test.ts +5 -2
  474. package/src/modules/workspace/__tests__/md-links.test.ts +353 -0
  475. package/src/modules/workspace/__tests__/move-links.test.ts +261 -0
  476. package/src/modules/workspace/__tests__/rest-tool-arguments.test.ts +219 -0
  477. package/src/modules/workspace/__tests__/workspace-paths-inside-the-repo.test.ts +3 -0
  478. package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +102 -0
  479. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +52 -5
  480. package/src/modules/workspace/__tests__/workspace.routes.folder-zip-access.test.ts +190 -0
  481. package/src/modules/workspace/__tests__/workspace.service.forget-clone-races.test.ts +8 -4
  482. package/src/modules/workspace/__tests__/workspace.service.test.ts +50 -8
  483. package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +44 -1
  484. package/src/modules/workspace/__tests__/workspace.tools.test.ts +409 -12
  485. package/src/modules/workspace/agent-download.builder.ts +308 -0
  486. package/src/modules/workspace/agent-download.routes.ts +183 -0
  487. package/src/modules/workspace/agent-download.store.ts +533 -0
  488. package/src/modules/workspace/agent-upload.store.ts +23 -7
  489. package/src/modules/workspace/file-readers/raw-mime.ts +40 -0
  490. package/src/modules/workspace/file-readers/text-reader.ts +5 -8
  491. package/src/modules/workspace/first-run.ts +222 -0
  492. package/src/modules/workspace/move-links.ts +237 -0
  493. package/src/modules/workspace/startup/steps/seed-tree.ts +3 -12
  494. package/src/modules/workspace/workspace.routes.ts +64 -22
  495. package/src/modules/workspace/workspace.service.ts +202 -25
  496. package/src/modules/workspace/workspace.tools.ts +492 -63
  497. package/src/modules/write-access/__tests__/write-access.test.ts +13 -0
  498. package/src/modules/write-access/write-access.ts +9 -0
  499. package/src/shared/domain-errors.ts +108 -11
  500. package/src/shared/git.contract.ts +8 -1
  501. package/src/shared/utf8-text.ts +21 -0
  502. package/src/tenancy/__tests__/static-tenant-source.test.ts +1 -0
  503. package/src/tenancy/static-tenant-source.ts +10 -2
  504. package/starter-packs/README.md +103 -0
  505. package/starter-packs/engineering/KnowledgeBase/About us.md +9 -0
  506. package/starter-packs/engineering/KnowledgeBase/Architecture.md +9 -0
  507. package/starter-packs/engineering/KnowledgeBase/Glossary.md +9 -0
  508. package/starter-packs/engineering/KnowledgeBase/How we ship.md +9 -0
  509. package/starter-packs/engineering/Plugins/engineering-starter/LICENSE +21 -0
  510. package/starter-packs/engineering/Plugins/engineering-starter/SOURCE.md +20 -0
  511. package/starter-packs/engineering/Plugins/engineering-starter/access.md +19 -0
  512. package/starter-packs/engineering/Plugins/engineering-starter/plugin.json +10 -0
  513. package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/SKILL.md +171 -0
  514. package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/sections/phase-2a-startup-diagnostic.md +157 -0
  515. package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/sections/phase-2b-builder-brainstorm.md +39 -0
  516. package/starter-packs/engineering/Plugins/engineering-starter/skills/plan-ceo-review/SKILL.md +144 -0
  517. package/starter-packs/engineering/Plugins/engineering-starter/skills/plan-eng-review/SKILL.md +144 -0
  518. package/starter-packs/engineering/Plugins/engineering-starter/skills/review/SKILL.md +80 -0
  519. package/starter-packs/engineering/Plugins/engineering-starter/skills/review/checklist.md +184 -0
  520. package/starter-packs/engineering/pack.yaml +11 -0
  521. package/starter-packs/general/KnowledgeBase/About us.md +9 -0
  522. package/starter-packs/general/KnowledgeBase/Glossary.md +9 -0
  523. package/starter-packs/general/KnowledgeBase/How we work.md +9 -0
  524. package/starter-packs/general/Plugins/general-starter/LICENSE +202 -0
  525. package/starter-packs/general/Plugins/general-starter/SOURCE.md +10 -0
  526. package/starter-packs/general/Plugins/general-starter/access.md +19 -0
  527. package/starter-packs/general/Plugins/general-starter/plugin.json +10 -0
  528. package/starter-packs/general/Plugins/general-starter/skills/memory-management/SKILL.md +332 -0
  529. package/starter-packs/general/Plugins/general-starter/skills/productivity-start/SKILL.md +163 -0
  530. package/starter-packs/general/Plugins/general-starter/skills/productivity-update/SKILL.md +167 -0
  531. package/starter-packs/general/Plugins/general-starter/skills/task-management/SKILL.md +83 -0
  532. package/starter-packs/general/pack.yaml +10 -0
  533. package/starter-packs/operations/KnowledgeBase/Glossary.md +9 -0
  534. package/starter-packs/operations/KnowledgeBase/How we work.md +9 -0
  535. package/starter-packs/operations/KnowledgeBase/Processes.md +9 -0
  536. package/starter-packs/operations/KnowledgeBase/Tools we use.md +9 -0
  537. package/starter-packs/operations/KnowledgeBase/Vendors.md +9 -0
  538. package/starter-packs/operations/Plugins/operations-starter/LICENSE +202 -0
  539. package/starter-packs/operations/Plugins/operations-starter/SOURCE.md +10 -0
  540. package/starter-packs/operations/Plugins/operations-starter/access.md +19 -0
  541. package/starter-packs/operations/Plugins/operations-starter/plugin.json +10 -0
  542. package/starter-packs/operations/Plugins/operations-starter/skills/capacity-plan/SKILL.md +111 -0
  543. package/starter-packs/operations/Plugins/operations-starter/skills/change-request/SKILL.md +119 -0
  544. package/starter-packs/operations/Plugins/operations-starter/skills/compliance-tracking/SKILL.md +44 -0
  545. package/starter-packs/operations/Plugins/operations-starter/skills/process-doc/SKILL.md +80 -0
  546. package/starter-packs/operations/Plugins/operations-starter/skills/process-optimization/SKILL.md +39 -0
  547. package/starter-packs/operations/Plugins/operations-starter/skills/risk-assessment/SKILL.md +40 -0
  548. package/starter-packs/operations/Plugins/operations-starter/skills/runbook/SKILL.md +85 -0
  549. package/starter-packs/operations/Plugins/operations-starter/skills/status-report/SKILL.md +75 -0
  550. package/starter-packs/operations/Plugins/operations-starter/skills/vendor-review/SKILL.md +102 -0
  551. package/starter-packs/operations/pack.yaml +12 -0
  552. package/starter-packs/sales/KnowledgeBase/About us.md +9 -0
  553. package/starter-packs/sales/KnowledgeBase/Customers.md +9 -0
  554. package/starter-packs/sales/KnowledgeBase/Glossary.md +9 -0
  555. package/starter-packs/sales/KnowledgeBase/How we sell.md +9 -0
  556. package/starter-packs/sales/KnowledgeBase/Objections.md +9 -0
  557. package/starter-packs/sales/KnowledgeBase/Pricing.md +9 -0
  558. package/starter-packs/sales/Plugins/sales-starter/LICENSE +202 -0
  559. package/starter-packs/sales/Plugins/sales-starter/SOURCE.md +10 -0
  560. package/starter-packs/sales/Plugins/sales-starter/access.md +19 -0
  561. package/starter-packs/sales/Plugins/sales-starter/plugin.json +10 -0
  562. package/starter-packs/sales/Plugins/sales-starter/skills/account-context/SKILL.md +94 -0
  563. package/starter-packs/sales/Plugins/sales-starter/skills/account-plan/SKILL.md +95 -0
  564. package/starter-packs/sales/Plugins/sales-starter/skills/account-research/SKILL.md +99 -0
  565. package/starter-packs/sales/Plugins/sales-starter/skills/account-tiering/SKILL.md +101 -0
  566. package/starter-packs/sales/Plugins/sales-starter/skills/call-prep/SKILL.md +111 -0
  567. package/starter-packs/sales/Plugins/sales-starter/skills/call-summary/SKILL.md +111 -0
  568. package/starter-packs/sales/Plugins/sales-starter/skills/close-plan/SKILL.md +106 -0
  569. package/starter-packs/sales/Plugins/sales-starter/skills/competitive-intelligence/SKILL.md +102 -0
  570. package/starter-packs/sales/Plugins/sales-starter/skills/create-an-asset/SKILL.md +105 -0
  571. package/starter-packs/sales/Plugins/sales-starter/skills/crm-hygiene-check/SKILL.md +103 -0
  572. package/starter-packs/sales/Plugins/sales-starter/skills/customer-health/SKILL.md +108 -0
  573. package/starter-packs/sales/Plugins/sales-starter/skills/customer-voice/SKILL.md +94 -0
  574. package/starter-packs/sales/Plugins/sales-starter/skills/daily-briefing/SKILL.md +61 -0
  575. package/starter-packs/sales/Plugins/sales-starter/skills/deal-advance-gap/SKILL.md +102 -0
  576. package/starter-packs/sales/Plugins/sales-starter/skills/deal-review/SKILL.md +105 -0
  577. package/starter-packs/sales/Plugins/sales-starter/skills/deal-signals/SKILL.md +96 -0
  578. package/starter-packs/sales/Plugins/sales-starter/skills/deal-slip-scenario/SKILL.md +104 -0
  579. package/starter-packs/sales/Plugins/sales-starter/skills/draft-outreach/SKILL.md +116 -0
  580. package/starter-packs/sales/Plugins/sales-starter/skills/end-of-day/SKILL.md +114 -0
  581. package/starter-packs/sales/Plugins/sales-starter/skills/expansion-whitespace/SKILL.md +97 -0
  582. package/starter-packs/sales/Plugins/sales-starter/skills/forecast/SKILL.md +100 -0
  583. package/starter-packs/sales/Plugins/sales-starter/skills/handle-objection/SKILL.md +93 -0
  584. package/starter-packs/sales/Plugins/sales-starter/skills/inbox-sweep/SKILL.md +114 -0
  585. package/starter-packs/sales/Plugins/sales-starter/skills/lead-triage/SKILL.md +106 -0
  586. package/starter-packs/sales/Plugins/sales-starter/skills/log-activity/SKILL.md +111 -0
  587. package/starter-packs/sales/Plugins/sales-starter/skills/pipeline-review/SKILL.md +97 -0
  588. package/starter-packs/sales/Plugins/sales-starter/skills/renewal-radar/SKILL.md +95 -0
  589. package/starter-packs/sales/Plugins/sales-starter/skills/rep-context/SKILL.md +90 -0
  590. package/starter-packs/sales/Plugins/sales-starter/skills/route-lead/SKILL.md +111 -0
  591. package/starter-packs/sales/Plugins/sales-starter/skills/sales-setup/SKILL.md +200 -0
  592. package/starter-packs/sales/Plugins/sales-starter/skills/schedule-meeting/SKILL.md +99 -0
  593. package/starter-packs/sales/Plugins/sales-starter/skills/stakeholder-map/SKILL.md +107 -0
  594. package/starter-packs/sales/Plugins/sales-starter/skills/team-pipeline/SKILL.md +105 -0
  595. package/starter-packs/sales/Plugins/sales-starter/skills/update-opportunity/SKILL.md +112 -0
  596. package/starter-packs/sales/Plugins/sales-starter/skills/weekly-wrap/SKILL.md +90 -0
  597. package/starter-packs/sales/Plugins/sales-starter/skills/win-loss-review/SKILL.md +88 -0
  598. package/starter-packs/sales/pack.yaml +13 -0
  599. package/starter-packs/support/KnowledgeBase/Escalations.md +9 -0
  600. package/starter-packs/support/KnowledgeBase/Glossary.md +9 -0
  601. package/starter-packs/support/KnowledgeBase/Known issues.md +9 -0
  602. package/starter-packs/support/KnowledgeBase/Products.md +9 -0
  603. package/starter-packs/support/KnowledgeBase/Tone of voice.md +9 -0
  604. package/starter-packs/support/Plugins/support-starter/LICENSE +202 -0
  605. package/starter-packs/support/Plugins/support-starter/SOURCE.md +10 -0
  606. package/starter-packs/support/Plugins/support-starter/access.md +19 -0
  607. package/starter-packs/support/Plugins/support-starter/plugin.json +10 -0
  608. package/starter-packs/support/Plugins/support-starter/skills/customer-escalation/SKILL.md +246 -0
  609. package/starter-packs/support/Plugins/support-starter/skills/customer-research/SKILL.md +250 -0
  610. package/starter-packs/support/Plugins/support-starter/skills/draft-response/SKILL.md +416 -0
  611. package/starter-packs/support/Plugins/support-starter/skills/kb-article/SKILL.md +350 -0
  612. package/starter-packs/support/Plugins/support-starter/skills/ticket-triage/SKILL.md +273 -0
  613. package/starter-packs/support/pack.yaml +12 -0
@@ -22,10 +22,32 @@ function updated(branch: string): BranchSyncOutcome {
22
22
  }
23
23
 
24
24
  describe('KbSyncService', () => {
25
+ it('answers the sync without waiting for the tidy-up after it', async () => {
26
+ let finish = (): void => undefined;
27
+ const workflow: SyncWorkflowPort = {
28
+ syncWorkspaceFromRemote: vi.fn(async (id: string) => updated(decodeURIComponent(id))),
29
+ closeChangeRequestsWithDeletedBranches: vi.fn(async () => 0),
30
+ // A tidy-up that never ends unless told to — and one that fails.
31
+ tidyAfterSweep: vi
32
+ .fn()
33
+ .mockImplementationOnce(
34
+ () => new Promise((resolve) => (finish = () => resolve({ closedEmpty: 0, removedLeftovers: 0 }))),
35
+ )
36
+ .mockRejectedValueOnce(new Error('boom')),
37
+ retireRemoteGoneClone: vi.fn(async () => true),
38
+ };
39
+ const svc = new KbSyncService(workflow, workspaces(['main']));
40
+ await expect(svc.sync({ branches: 'all' })).resolves.toMatchObject({ status: 'synced' });
41
+ expect(workflow.tidyAfterSweep).toHaveBeenCalledTimes(1);
42
+ await expect(svc.sync({ branches: 'all' })).resolves.toMatchObject({ status: 'synced' });
43
+ finish();
44
+ });
45
+
25
46
  it('syncs every known clone for an "all" request and reports unknown branches as not cloned', async () => {
26
47
  const workflow: SyncWorkflowPort = {
27
48
  syncWorkspaceFromRemote: vi.fn(async (id: string) => updated(decodeURIComponent(id))),
28
49
  closeChangeRequestsWithDeletedBranches: vi.fn(async () => 1),
50
+ tidyAfterSweep: vi.fn(async () => ({ closedEmpty: 0, removedLeftovers: 0 })),
29
51
  retireRemoteGoneClone: vi.fn(async () => true),
30
52
  };
31
53
  const svc = new KbSyncService(workflow, workspaces(['main', 'ali/x']));
@@ -34,6 +56,8 @@ describe('KbSyncService', () => {
34
56
  expect(all.status).toBe('synced');
35
57
  expect(all.results.map((r) => r.branch)).toEqual(['main', 'ali/x']);
36
58
  expect(all.changeRequests.closedDeletedBranch).toBe(1);
59
+ // Every sweep for deleted branches is followed by the leftover cleanup.
60
+ expect(workflow.tidyAfterSweep).toHaveBeenCalledTimes(1);
37
61
  expect(workflow.syncWorkspaceFromRemote).toHaveBeenCalledWith('ali%2Fx');
38
62
 
39
63
  const some = await svc.sync({ branches: ['ali/x', 'juan/other'] });
@@ -48,6 +72,7 @@ describe('KbSyncService', () => {
48
72
  : updated(decodeURIComponent(id)),
49
73
  ),
50
74
  closeChangeRequestsWithDeletedBranches: vi.fn(async () => 0),
75
+ tidyAfterSweep: vi.fn(async () => ({ closedEmpty: 0, removedLeftovers: 0 })),
51
76
  retireRemoteGoneClone: vi.fn(async () => true),
52
77
  };
53
78
  const svc = new KbSyncService(workflow, workspaces(['main', 'ali/x']));
@@ -62,6 +87,7 @@ describe('KbSyncService', () => {
62
87
  closeChangeRequestsWithDeletedBranches: vi.fn(async () => {
63
88
  throw new Error('origin down');
64
89
  }),
90
+ tidyAfterSweep: vi.fn(async () => ({ closedEmpty: 0, removedLeftovers: 0 })),
65
91
  retireRemoteGoneClone: vi.fn(async () => true),
66
92
  };
67
93
  const svc = new KbSyncService(workflow, workspaces(['main']));
@@ -91,6 +117,7 @@ describe('KbSyncService', () => {
91
117
  return updated(branch);
92
118
  }),
93
119
  closeChangeRequestsWithDeletedBranches: vi.fn(async () => 0),
120
+ tidyAfterSweep: vi.fn(async () => ({ closedEmpty: 0, removedLeftovers: 0 })),
94
121
  retireRemoteGoneClone: vi.fn(async () => true),
95
122
  };
96
123
  const svc = new KbSyncService(workflow, workspaces(['main', 'ali/x', 'juan/y']));
@@ -121,6 +148,7 @@ describe('KbSyncService', () => {
121
148
  return updated(decodeURIComponent(id));
122
149
  }),
123
150
  closeChangeRequestsWithDeletedBranches: vi.fn(async () => 0),
151
+ tidyAfterSweep: vi.fn(async () => ({ closedEmpty: 0, removedLeftovers: 0 })),
124
152
  retireRemoteGoneClone: vi.fn(async () => true),
125
153
  };
126
154
  const svc = new KbSyncService(workflow, workspaces(['main', 'ali/x', 'juan/y']));
@@ -141,6 +169,7 @@ describe('KbSyncService.lastSync', () => {
141
169
  const workflow: SyncWorkflowPort = {
142
170
  syncWorkspaceFromRemote: vi.fn(async (id: string) => updated(decodeURIComponent(id))),
143
171
  closeChangeRequestsWithDeletedBranches: vi.fn(async () => 0),
172
+ tidyAfterSweep: vi.fn(async () => ({ closedEmpty: 0, removedLeftovers: 0 })),
144
173
  retireRemoteGoneClone: vi.fn(async () => true),
145
174
  };
146
175
  let clock = 1_000;
@@ -168,6 +197,7 @@ describe('KbSyncService.lastSync', () => {
168
197
  return updated(decodeURIComponent(id));
169
198
  }),
170
199
  closeChangeRequestsWithDeletedBranches: vi.fn(async () => 0),
200
+ tidyAfterSweep: vi.fn(async () => ({ closedEmpty: 0, removedLeftovers: 0 })),
171
201
  retireRemoteGoneClone: vi.fn(async () => true),
172
202
  };
173
203
  const svc = new KbSyncService(workflow, workspaces(['main', 'ali/x']));
@@ -188,6 +218,7 @@ describe('KbSyncService — a throwing branch step', () => {
188
218
  return updated(decodeURIComponent(id));
189
219
  }),
190
220
  closeChangeRequestsWithDeletedBranches: vi.fn(async () => 1),
221
+ tidyAfterSweep: vi.fn(async () => ({ closedEmpty: 0, removedLeftovers: 0 })),
191
222
  retireRemoteGoneClone: vi.fn(async () => true),
192
223
  };
193
224
  const svc = new KbSyncService(workflow, workspaces(['main', 'ali/x']));
@@ -208,6 +239,7 @@ describe('KbSyncService — clones the host no longer has, and clones that canno
208
239
  id === 'ali%2Fx' ? { branch: 'ali/x', outcome: 'remote-gone' } : updated(decodeURIComponent(id)),
209
240
  ),
210
241
  closeChangeRequestsWithDeletedBranches: vi.fn(async () => 1),
242
+ tidyAfterSweep: vi.fn(async () => ({ closedEmpty: 0, removedLeftovers: 0 })),
211
243
  retireRemoteGoneClone: vi.fn(async () => true),
212
244
  };
213
245
  const svc = new KbSyncService(workflow, workspaces(['ali/x', 'main']));
@@ -222,6 +254,7 @@ describe('KbSyncService — clones the host no longer has, and clones that canno
222
254
  const workflow: SyncWorkflowPort = {
223
255
  syncWorkspaceFromRemote: vi.fn(async (id: string) => updated(decodeURIComponent(id))),
224
256
  closeChangeRequestsWithDeletedBranches: vi.fn(async () => 0),
257
+ tidyAfterSweep: vi.fn(async () => ({ closedEmpty: 0, removedLeftovers: 0 })),
225
258
  retireRemoteGoneClone: vi.fn(async () => true),
226
259
  };
227
260
  const port: SyncWorkspacePort = {
@@ -251,6 +284,7 @@ describe('KbSyncService — a coalesced caller is judged by its own branches', (
251
284
  return updated(decodeURIComponent(id));
252
285
  }),
253
286
  closeChangeRequestsWithDeletedBranches: vi.fn(async () => 0),
287
+ tidyAfterSweep: vi.fn(async () => ({ closedEmpty: 0, removedLeftovers: 0 })),
254
288
  retireRemoteGoneClone: vi.fn(async () => true),
255
289
  };
256
290
  const svc = new KbSyncService(workflow, workspaces(['main', 'ali/x', 'juan/y']));
@@ -278,6 +312,7 @@ describe('KbSyncService — a waiter keeps the branches it asked for', () => {
278
312
  return updated(decodeURIComponent(id));
279
313
  }),
280
314
  closeChangeRequestsWithDeletedBranches: vi.fn(async () => 0),
315
+ tidyAfterSweep: vi.fn(async () => ({ closedEmpty: 0, removedLeftovers: 0 })),
281
316
  retireRemoteGoneClone: vi.fn(async () => true),
282
317
  };
283
318
  const svc = new KbSyncService(workflow, workspaces(['main', 'ali/x', 'juan/y']));
@@ -61,6 +61,8 @@ export interface SyncWorkflowPort {
61
61
  retireRemoteGoneClone(workspaceId: string): Promise<boolean>;
62
62
  /** The existing sweep; returns how many requests it closed. */
63
63
  closeChangeRequestsWithDeletedBranches(): Promise<number>;
64
+ /** The tidy-up after the sweep — empty open requests closed, leftover branches removed; never throws. */
65
+ tidyAfterSweep(): Promise<{ closedEmpty: number; removedLeftovers: number }>;
64
66
  }
65
67
 
66
68
  /**
@@ -187,6 +187,12 @@ export class KbSyncService implements IKbSyncService {
187
187
  } catch (err) {
188
188
  log.warn('deleted-branch sweep failed:', { err });
189
189
  }
190
+ // Then the tidy-up boot runs too: empty open requests closed, leftover
191
+ // branches of merged requests removed. Detached: it can take a while on a
192
+ // large repository, and the sync (often a webhook with a short timeout)
193
+ // must not wait on it. It never throws; the catch is for a mock or a
194
+ // future change that does.
195
+ void this.workflow.tidyAfterSweep().catch((err: unknown) => log.warn('post-sync tidy-up failed:', { err }));
190
196
 
191
197
  const result: SyncResult = {
192
198
  status: KbSyncService.statusOf(results),
@@ -70,15 +70,19 @@ function render(fragment: SQL): { sql: string; params: unknown[] } {
70
70
  return { sql: q.sql, params: q.params };
71
71
  }
72
72
 
73
- function makeProvider(queue: any[] = []) {
74
- const { db, captured } = makeFakeDb(queue);
75
- const provider = new BevelOAuthProvider({
73
+ function providerDeps(db: Database) {
74
+ return {
76
75
  db,
77
76
  crypto: null,
78
77
  stateSecret: STATE_SECRET,
79
78
  publicFrontendUrl: FRONTEND,
80
79
  tokenPrefix: PREFIX,
81
- });
80
+ };
81
+ }
82
+
83
+ function makeProvider(queue: unknown[] = []) {
84
+ const { db, captured } = makeFakeDb(queue);
85
+ const provider = new BevelOAuthProvider(providerDeps(db));
82
86
  return { provider, captured, db };
83
87
  }
84
88
 
@@ -292,9 +296,10 @@ describe('BevelOAuthProvider', () => {
292
296
  const { provider, captured, db } = makeProvider([
293
297
  [oldRow] /* revoke.returning */,
294
298
  [{ id: 'conn-1' }] /* the token's connection, still live */,
295
- undefined /* the (throttled) use stamp */,
299
+ [] /* the (throttled) use stamp's first-use claim: stamped before */,
296
300
  undefined, undefined, /* prunes */
297
301
  undefined /* token insert */,
302
+ undefined /* the ordinary use stamp */,
298
303
  ]);
299
304
  const tokens = await provider.exchangeRefreshToken(CLIENT, 'bevel-mcp_r_old', ['mcp']);
300
305
  expect(tokens.scope).toBe('mcp');
@@ -307,8 +312,10 @@ describe('BevelOAuthProvider', () => {
307
312
  expect((db as any).select).toHaveBeenCalledTimes(1);
308
313
  expect(render(captured.where[1])).toMatchObject({ params: ['conn-1'] });
309
314
  expect(render(captured.where[1]).sql).toMatch(/"agent_connections"."revoked_at" is null/);
310
- // …and the use is stamped separately, off the refresh's own path.
311
- expect(captured.updateTargets).toEqual([oauthTokens, agentConnections]);
315
+ // …and the use is stamped separately, off the refresh's own path: the
316
+ // first-use claim, then (stamped before) the ordinary stamp.
317
+ await new Promise((r) => setTimeout(r, 0));
318
+ expect(captured.updateTargets).toEqual([oauthTokens, agentConnections, agentConnections]);
312
319
  expect(Object.keys(captured.set[1])).toEqual(['lastUsedAt']);
313
320
 
314
321
  // Widening is refused.
@@ -363,6 +370,7 @@ describe('BevelOAuthProvider', () => {
363
370
  const { provider, captured } = makeProvider([
364
371
  [row] /* select */,
365
372
  undefined /* token lastUsedAt touch */,
373
+ [] /* the connection's first-use claim: stamped before */,
366
374
  undefined /* connection lastUsedAt touch */,
367
375
  [row] /* a second verify a moment later */,
368
376
  undefined /* its token touch — and NO second connection touch */,
@@ -381,16 +389,65 @@ describe('BevelOAuthProvider', () => {
381
389
  // Both "last used" marks are touched: the token's row, and THIS token's
382
390
  // agent connection.
383
391
  await new Promise((r) => setTimeout(r, 0));
384
- expect(captured.updateTargets).toEqual([oauthTokens, agentConnections]);
385
- expect(captured.set.map((s) => Object.keys(s))).toEqual([['lastUsedAt'], ['lastUsedAt']]);
392
+ // The connection's stamp is two statements: the first-use claim (its
393
+ // id, and never stamped), then — stamped before — the ordinary stamp.
394
+ expect(captured.updateTargets).toEqual([oauthTokens, agentConnections, agentConnections]);
395
+ expect(captured.set.map((s) => Object.keys(s))).toEqual([['lastUsedAt'], ['lastUsedAt'], ['lastUsedAt']]);
386
396
  expect(render(captured.where[1])).toMatchObject({ params: ['tok-row-1'] });
387
397
  expect(render(captured.where[2])).toMatchObject({ params: ['conn-1'] });
388
- expect(render(captured.where[2]).sql).toMatch(/"agent_connections"."id" = \$1/);
398
+ expect(render(captured.where[2]).sql).toMatch(/"agent_connections"."id" = \$1 and "agent_connections"."last_used_at" is null/);
399
+ expect(render(captured.where[3])).toMatchObject({ params: ['conn-1'] });
400
+ expect(render(captured.where[3]).sql).toMatch(/"agent_connections"."id" = \$1/);
389
401
  // The connection's stamp is throttled: a verify a moment later touches
390
402
  // the token row again but not the connection — a minute has not passed.
391
403
  await provider.verifyAccessToken('bevel-mcp_live');
392
404
  await new Promise((r) => setTimeout(r, 0));
393
- expect(captured.updateTargets).toEqual([oauthTokens, agentConnections, oauthTokens]);
405
+ expect(captured.updateTargets).toEqual([oauthTokens, agentConnections, agentConnections, oauthTokens]);
406
+ });
407
+
408
+ /**
409
+ * The FIRST stamp of a connection is the agent arriving — what the
410
+ * onboarding waits for — so the owner is told, once: the claim that took
411
+ * the row from never-used to now is the one that announces, and it is
412
+ * the only statement that run makes. A connection stamped before loses
413
+ * the claim, is stamped the ordinary way, and nobody is told.
414
+ */
415
+ it('announces an agent connection to its owner on its first use, and never again', async () => {
416
+ const row = {
417
+ id: 'tok-row-1', clientId: 'client-1', connectionId: 'conn-1', scope: 'mcp', resource: null,
418
+ expiresAt: new Date(Date.now() + 60_000), userId: 'user-1', userEmail: 'alice@example.com',
419
+ };
420
+ const events = { emit: vi.fn() };
421
+ const fresh = makeProvider([
422
+ [row],
423
+ undefined /* token touch */,
424
+ [{ userId: 'user-1', client: 'Claude' }] /* the claim: this request took the first stamp */,
425
+ ]);
426
+ const freshProvider = new BevelOAuthProvider({ ...providerDeps(fresh.db), events });
427
+ await freshProvider.verifyAccessToken('bevel-mcp_live');
428
+ await new Promise((r) => setTimeout(r, 0));
429
+ expect(events.emit).toHaveBeenCalledTimes(1);
430
+ expect(events.emit).toHaveBeenCalledWith({
431
+ kind: 'agent-connected',
432
+ forUserId: 'user-1',
433
+ client: 'Claude',
434
+ agentKind: 'agent',
435
+ at: expect.stringMatching(/^\d{4}-\d{2}-\d{2}T/),
436
+ });
437
+ // The claim WAS the stamp: no second write for the winner.
438
+ expect(fresh.captured.updateTargets).toEqual([oauthTokens, agentConnections]);
439
+
440
+ const used = makeProvider([
441
+ [row],
442
+ undefined,
443
+ [] /* the claim lost: stamped before (or by a racing request) */,
444
+ undefined /* the ordinary stamp */,
445
+ ]);
446
+ const usedEvents = { emit: vi.fn() };
447
+ await new BevelOAuthProvider({ ...providerDeps(used.db), events: usedEvents }).verifyAccessToken('bevel-mcp_live');
448
+ await new Promise((r) => setTimeout(r, 0));
449
+ expect(usedEvents.emit).not.toHaveBeenCalled();
450
+ expect(used.captured.updateTargets).toEqual([oauthTokens, agentConnections, agentConnections]);
394
451
  });
395
452
 
396
453
  it('revokeToken (RFC 7009) ends the agent connection once no live token remains on it, as the owner\'s doing', async () => {
@@ -68,15 +68,50 @@ describe('toListedTool', () => {
68
68
  ...over,
69
69
  });
70
70
 
71
- it('passes a conforming tool through', () => {
71
+ it('passes a conforming tool through, its call example first', () => {
72
72
  const listed = toListedTool(proxied({ mcpName: 'gmail_send_email' }));
73
73
  expect(listed).toEqual({
74
74
  name: 'gmail_send_email',
75
- description: 'a tool',
75
+ description: 'Call: m.t({})\n\na tool',
76
76
  inputSchema: { type: 'object', properties: {} },
77
77
  });
78
78
  });
79
79
 
80
+ it('generates the example from the tool\'s own schema, in the shape it really takes', () => {
81
+ const platform = toListedTool(
82
+ proxied({
83
+ utcpName: 'KNOWLEDGE_BASE.read_file',
84
+ mcpName: 'read_file',
85
+ inputSchema: {
86
+ type: 'object',
87
+ properties: {
88
+ body: {
89
+ type: 'object',
90
+ properties: { branch: { type: 'string' }, path: { type: 'string' }, limit: { type: 'integer' } },
91
+ required: ['branch', 'path'],
92
+ },
93
+ },
94
+ required: ['body'],
95
+ } as never,
96
+ }),
97
+ )!;
98
+ expect(platform.description.split('\n')[0]).toBe(
99
+ 'Call: KNOWLEDGE_BASE.read_file({ body: { branch: "...", path: "..." } })',
100
+ );
101
+ const flat = toListedTool(
102
+ proxied({
103
+ utcpName: 'NS.search',
104
+ mcpName: 'NS_search',
105
+ inputSchema: {
106
+ type: 'object',
107
+ properties: { query: { type: 'string' }, limit: { type: 'integer' } },
108
+ required: ['query'],
109
+ } as never,
110
+ }),
111
+ )!;
112
+ expect(flat.description.split('\n')[0]).toBe('Call: NS.search({ query: "..." })');
113
+ });
114
+
80
115
  it('drops a tool whose name is too long or has illegal chars (so it cannot blank the whole list)', () => {
81
116
  expect(toListedTool(proxied({ mcpName: 'has spaces' }))).toBeNull();
82
117
  expect(toListedTool(proxied({ mcpName: 'a'.repeat(129) }))).toBeNull();
@@ -0,0 +1,200 @@
1
+ import type { Server as HttpServer } from 'node:http';
2
+ import type { AddressInfo } from 'node:net';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import express, { type RequestHandler } from 'express';
6
+ import { afterEach, describe, expect, it } from 'vitest';
7
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
8
+ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
9
+ import { MCP_APP_MIME_TYPE, MCP_APP_UI_META_KEY } from '@bevel-software/platform-mcp-core';
10
+ import { createMcpRoutes } from '../mcp.routes.js';
11
+ import { McpService } from '../mcp.service.js';
12
+ import { SpillStore } from '../../workspace/spill-store.js';
13
+ import { createManualRoutes } from '../../tool-registry/manual.routes.js';
14
+ import { ToolRegistry } from '../../tool-registry/tool-registry.js';
15
+ import { toolDef } from '../../tool-helpers/tool-def.js';
16
+ import { McpAppService, OPEN_PAGE_VIEW_URI_PREFIX } from '../../embed/mcp-app.js';
17
+ import { OPEN_PAGE_TOOL } from '../../embed/embed.tools.js';
18
+
19
+ /**
20
+ * The MCP App, over the REAL Streamable-HTTP transport with the official SDK
21
+ * client — the criterion as a host actually observes it: `tools/list` carries
22
+ * the view on `open_page`, `resources/list` offers it under the MCP App media
23
+ * type, and `resources/read` hands back the view with the framing metadata.
24
+ */
25
+
26
+ const KEY = 'bevel_key_user_a';
27
+ const PUBLIC = 'https://hexis.example';
28
+
29
+ const bearerOf = (req: express.Request) => (req.headers.authorization ?? '').replace(/^Bearer\s+/i, '');
30
+ const fakeAuth: RequestHandler = (req, res, next) => {
31
+ if (bearerOf(req) !== KEY) {
32
+ res.status(401).json({ error: 'unauthorized' });
33
+ return;
34
+ }
35
+ req.userId = 'user-A';
36
+ req.externalApiKeyId = 'tok-A';
37
+ next();
38
+ };
39
+
40
+ let httpServer: HttpServer | null = null;
41
+ let client: Client | null = null;
42
+
43
+ afterEach(async () => {
44
+ await client?.close().catch(() => undefined);
45
+ client = null;
46
+ if (httpServer) {
47
+ httpServer.closeAllConnections();
48
+ await new Promise<void>((r) => httpServer!.close(() => r()));
49
+ }
50
+ httpServer = null;
51
+ });
52
+
53
+ /** The platform: the real proxy, the real routes, and `open_page` in the catalog. */
54
+ async function connect(opts: { serveApps?: boolean } = {}) {
55
+ const app = express();
56
+ app.use(express.json());
57
+
58
+ const registry = new ToolRegistry();
59
+ for (const name of [OPEN_PAGE_TOOL, 'read_file']) {
60
+ registry.registerExternalTool(
61
+ toolDef({
62
+ name,
63
+ description: `the ${name} tool`,
64
+ path: `/api/agent/tools/${name}`,
65
+ inputs: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'], additionalProperties: false },
66
+ }),
67
+ );
68
+ }
69
+ const toolRoutes = express.Router();
70
+ toolRoutes.use(createManualRoutes(registry, fakeAuth, async () => 'a@x.io'));
71
+ toolRoutes.post('/agent/tools/:name', (req, res) =>
72
+ res.json(
73
+ req.params.name === OPEN_PAGE_TOOL
74
+ ? { path: 'Data/Thing.md', content: '# Thing', embedUrl: `${PUBLIC}/embed?token=t`, appUrl: PUBLIC, branch: 'main' }
75
+ : { ok: true },
76
+ ),
77
+ );
78
+
79
+ httpServer = app.listen(0);
80
+ await new Promise<void>((r) => httpServer!.once('listening', () => r()));
81
+ const port = (httpServer.address() as AddressInfo).port;
82
+ const baseUrl = `http://127.0.0.1:${port}`;
83
+
84
+ const service = new McpService(
85
+ {
86
+ loopbackBaseUrl: baseUrl,
87
+ manualName: 'KNOWLEDGE_BASE',
88
+ spillStore: new SpillStore(join(tmpdir(), 'bevel-test-spills')),
89
+ publicFrontendUrl: PUBLIC,
90
+ },
91
+ undefined,
92
+ undefined,
93
+ undefined,
94
+ undefined,
95
+ undefined,
96
+ // A deployment with no apps is the degraded shape: no capability, no view.
97
+ opts.serveApps === false ? undefined : new McpAppService({ publicFrontendUrl: PUBLIC }),
98
+ );
99
+ const stub = {} as never;
100
+ app.use('/api', createMcpRoutes(service, stub, fakeAuth, fakeAuth, stub, stub, stub, ''));
101
+ app.use('/api', toolRoutes);
102
+
103
+ client = new Client({ name: 'test-host', version: '1.0.0' });
104
+ await client.connect(
105
+ new StreamableHTTPClientTransport(new URL(`${baseUrl}/api/mcp`), {
106
+ requestInit: { headers: { Authorization: `Bearer ${KEY}` } },
107
+ }),
108
+ );
109
+ return client;
110
+ }
111
+
112
+ /** The URI the packaged view is served under — named by its content, so read off the service. */
113
+ async function viewUri(): Promise<string> {
114
+ return (await new McpAppService({ publicFrontendUrl: PUBLIC }).manifest()).resources[0].uri;
115
+ }
116
+
117
+ describe('the hosted MCP endpoint serves the open_page view', () => {
118
+ it('lists open_page carrying UI metadata that names a ui:// resource', async () => {
119
+ const host = await connect();
120
+ const { tools } = await host.listTools();
121
+ const openPage = tools.find((t) => t.name === OPEN_PAGE_TOOL);
122
+ expect(openPage).toBeDefined();
123
+ expect(openPage!._meta).toEqual({ [MCP_APP_UI_META_KEY]: { resourceUri: await viewUri() } });
124
+ expect((await viewUri()).startsWith(OPEN_PAGE_VIEW_URI_PREFIX)).toBe(true);
125
+ // And only that tool: `read_file` is deliberately untouched.
126
+ expect(tools.find((t) => t.name === 'read_file')!._meta).toBeUndefined();
127
+ }, 30_000);
128
+
129
+ it('lists the view with the MCP App media type', async () => {
130
+ const host = await connect();
131
+ const { resources } = await host.listResources();
132
+ const uri = await viewUri();
133
+ const view = resources.find((r) => r.uri === uri);
134
+ expect(view).toBeDefined();
135
+ expect(view!.mimeType).toBe(MCP_APP_MIME_TYPE);
136
+ expect(view!._meta).toMatchObject({
137
+ [MCP_APP_UI_META_KEY]: { csp: { connectDomains: [PUBLIC], resourceDomains: [PUBLIC] } },
138
+ });
139
+ }, 30_000);
140
+
141
+ it('reads the view, reaching exactly the deployment own origin and asking for no sandbox domain', async () => {
142
+ const host = await connect();
143
+ const read = await host.readResource({ uri: await viewUri() });
144
+ expect(read.contents).toHaveLength(1);
145
+ const [content] = read.contents as Array<{ mimeType: string; text: string; _meta: Record<string, unknown> }>;
146
+ expect(content.mimeType).toBe(MCP_APP_MIME_TYPE);
147
+ expect(content.text).toContain('ui/initialize');
148
+ expect(content._meta[MCP_APP_UI_META_KEY]).toMatchObject({
149
+ csp: { connectDomains: [PUBLIC], resourceDomains: [PUBLIC] },
150
+ prefersBorder: false,
151
+ });
152
+ // No frames: a chat host's sandbox blocks them, so the view does not ask.
153
+ expect((content._meta[MCP_APP_UI_META_KEY] as { csp: Record<string, unknown> }).csp).not.toHaveProperty(
154
+ 'frameDomains',
155
+ );
156
+ // The host's default sandbox: the field's format is each host's own, and
157
+ // a value a host rejects is a view that never renders.
158
+ expect(content._meta[MCP_APP_UI_META_KEY]).not.toHaveProperty('domain');
159
+ }, 30_000);
160
+
161
+ it('refuses a resource it does not serve, naming the ones it does', async () => {
162
+ const host = await connect();
163
+ await expect(host.readResource({ uri: 'ui://hexis/not-a-view.html' })).rejects.toThrow(
164
+ /No resource at ui:\/\/hexis\/not-a-view\.html/,
165
+ );
166
+ }, 30_000);
167
+
168
+ /**
169
+ * A deployment that serves no view must not declare the capability: a
170
+ * client would list resources and be told the method does not exist, which
171
+ * reads as a broken endpoint rather than as "this one has no apps".
172
+ */
173
+ it('declares no resources capability when the deployment serves no app', async () => {
174
+ const host = await connect({ serveApps: false });
175
+ expect(host.getServerCapabilities()?.resources).toBeUndefined();
176
+ const { tools } = await host.listTools();
177
+ // Every tool is still listed, and still carries no view.
178
+ expect(tools.find((t) => t.name === OPEN_PAGE_TOOL)).toBeDefined();
179
+ expect(tools.find((t) => t.name === OPEN_PAGE_TOOL)!._meta).toBeUndefined();
180
+ }, 30_000);
181
+
182
+ /**
183
+ * The view frames `structuredContent.embedUrl` — it has nowhere else to
184
+ * read it — so a tool with a view must answer structured content as well
185
+ * as the text the model reads. A tool without one answers text alone.
186
+ */
187
+ it('answers open_page with structured content the view can read, and read_file without', async () => {
188
+ const host = await connect();
189
+ const opened = await host.callTool({ name: OPEN_PAGE_TOOL, arguments: { path: 'Data/Thing.md' } });
190
+ expect(opened.structuredContent).toMatchObject({ embedUrl: `${PUBLIC}/embed?token=t`, branch: 'main' });
191
+ expect((opened.content as Array<{ type: string; text: string }>)[0].text).toContain('embedUrl');
192
+ const read = await host.callTool({ name: 'read_file', arguments: { path: 'Data/Thing.md' } });
193
+ expect(read.structuredContent).toBeUndefined();
194
+ }, 30_000);
195
+
196
+ it('declares the resources capability when it does', async () => {
197
+ const host = await connect();
198
+ expect(host.getServerCapabilities()?.resources).toBeDefined();
199
+ }, 30_000);
200
+ });
@@ -1296,9 +1296,11 @@ describe('a connected tool whose schema is invalid is not offered to agents', ()
1296
1296
  // The same chain, same namespace, on the sibling: bound and callable. This
1297
1297
  // is what makes the line above a statement about the HIDDEN tool rather
1298
1298
  // than about a namespace the chain could not reach at all.
1299
+ // Called the way its `Call:` line shows: a connected tool takes its
1300
+ // arguments flat, and the same call wrapped in `body` is refused.
1299
1301
  const sibling = await client.callTool({
1300
1302
  name: 'call_tool_chain',
1301
- arguments: { code: 'return notion.srv_fine({ body: {} });' },
1303
+ arguments: { code: 'return notion.srv_fine({});' },
1302
1304
  });
1303
1305
  expect(sibling.isError).toBeFalsy();
1304
1306
  });
@@ -14,7 +14,7 @@ import { toolDef } from '../../tool-helpers/tool-def.js';
14
14
  import { DEFAULT_KB_LAYOUT } from '@bevel-software/platform-shared';
15
15
  import { TOOL_PREFIX_LINE, platformInstructions } from '../../agent-instructions/index.js';
16
16
  import { GUIDE_FIRST_SENTENCE } from '../../tool-registry/guide-first.js';
17
- import { GUIDE_FIRST_SENTENCE } from '../../tool-registry/guide-first.js';
17
+ import { splitCallLine } from '@bevel-software/platform-mcp-core';
18
18
  import type { AgentEventInput, IAgentEventRecorder } from '../../audit/audit.contract.js';
19
19
 
20
20
  /** The platform-owned part of the handshake text: the header plus the shared file rules. */
@@ -279,7 +279,9 @@ describe('McpService (UTCP→MCP proxy)', () => {
279
279
  const { tools } = await client.listTools();
280
280
  for (const name of ['call_tool_chain', 'list_tools', 'tools_info']) {
281
281
  const served = tools.find((t) => t.name === name)!;
282
- expect(served.description!.startsWith(`${GUIDE_FIRST_SENTENCE} `), name).toBe(true);
282
+ // Behind its `Call:` line, the first line of every description.
283
+ expect(served.description!.startsWith(`Call: ${name}(`), name).toBe(true);
284
+ expect(splitCallLine(served.description!).rest.startsWith(`${GUIDE_FIRST_SENTENCE} `), name).toBe(true);
283
285
  expect(served.description!.split(GUIDE_FIRST_SENTENCE), name).toHaveLength(2);
284
286
  }
285
287
  const chain = tools.find((t) => t.name === 'call_tool_chain')!;
@@ -614,33 +616,40 @@ describe('McpService — agent instructions', () => {
614
616
  expect.stringContaining('mcp-description.md'),
615
617
  expect.objectContaining({ message: 'disk' }),
616
618
  );
617
- // And the four tools carry the fixed line alone, ahead of the guide-first
619
+ // And the four tools carry the fixed line alone — behind the call line,
620
+ // which stays first whatever is prepended, and ahead of the guide-first
618
621
  // opening every listed tool has.
619
622
  const { tools } = await client.listTools();
620
623
  for (const name of KB_TOOLS) {
621
624
  expect(tools.find((t) => t.name === name)?.description).toBe(
622
- `${TOOL_PREFIX_LINE}\n\n${GUIDE_FIRST_SENTENCE} original ${name} description`,
625
+ `Call: KNOWLEDGE_BASE.${name}({})\n\n${TOOL_PREFIX_LINE}\n\n${GUIDE_FIRST_SENTENCE} original ${name} description`,
623
626
  );
624
627
  }
625
628
  });
626
629
 
627
- it('prefixes exactly the four knowledge-base tools; every other description is as the catalog lists it', async () => {
630
+ it('prefixes exactly the four knowledge-base tools; every other description gains only its call line and the guide-first opening', async () => {
628
631
  const client = await setup({ readAgentPreamble: async () => 'Acme builds solar farms.', extraTools: KB_TOOLS });
629
632
  const { tools } = await client.listTools();
630
633
  const byName = Object.fromEntries(tools.map((t) => [t.name, t.description]));
631
634
  const prefix = `${TOOL_PREFIX_LINE} Acme builds solar farms.`;
632
635
  for (const name of KB_TOOLS) {
633
- expect(byName[name], name).toBe(`${prefix}\n\n${GUIDE_FIRST_SENTENCE} original ${name} description`);
636
+ expect(byName[name], name).toBe(
637
+ `Call: KNOWLEDGE_BASE.${name}({})\n\n${prefix}\n\n${GUIDE_FIRST_SENTENCE} original ${name} description`,
638
+ );
634
639
  }
635
640
  // Regression: the rest carries only what the catalog gives every tool —
636
- // the guide-first opening — and never the purpose prefix; the meta-tools
637
- // open with that sentence too.
638
- expect(byName.ask).toBe(`${GUIDE_FIRST_SENTENCE} echo the prompt`);
639
- expect(byName.boom).toBe(`${GUIDE_FIRST_SENTENCE} always errors`);
640
- expect(byName.refy).toBe(`${GUIDE_FIRST_SENTENCE} has $defs/$ref in its schema`);
641
+ // its own call line, then the guide-first opening — and never the purpose
642
+ // prefix; the meta-tools open the same way.
643
+ expect(byName.ask).toBe(`Call: KNOWLEDGE_BASE.ask({ body: { prompt: "..." } })\n\n${GUIDE_FIRST_SENTENCE} echo the prompt`);
644
+ expect(byName.boom).toBe(`Call: KNOWLEDGE_BASE.boom({})\n\n${GUIDE_FIRST_SENTENCE} always errors`);
645
+ // Its `to` is an array of a `$ref`'d shape: the example takes the type at
646
+ // the top and says nothing about what is inside — the interface does that.
647
+ expect(byName.refy).toBe(
648
+ `Call: KNOWLEDGE_BASE.refy({ body: { to: [] } })\n\n${GUIDE_FIRST_SENTENCE} has $defs/$ref in its schema`,
649
+ );
641
650
  for (const meta of ['call_tool_chain', 'list_tools', 'tools_info']) {
642
651
  expect(byName[meta], meta).not.toContain(TOOL_PREFIX_LINE);
643
- expect(byName[meta]!.startsWith(`${GUIDE_FIRST_SENTENCE} `), meta).toBe(true);
652
+ expect(splitCallLine(byName[meta]!).rest.startsWith(`${GUIDE_FIRST_SENTENCE} `), meta).toBe(true);
644
653
  }
645
654
  });
646
655
 
@@ -658,7 +667,8 @@ describe('McpService — agent instructions', () => {
658
667
  });
659
668
  const { tools } = await client.listTools();
660
669
  for (const name of KB_TOOLS) {
661
- expect(tools.find((t) => t.name === name)?.description.startsWith(`${TOOL_PREFIX_LINE} Acme.`)).toBe(true);
670
+ expect(tools.find((t) => t.name === name)?.description).toContain(`\n\n${TOOL_PREFIX_LINE} Acme.`);
671
+ expect(tools.find((t) => t.name === name)?.description.startsWith('Call: ')).toBe(true);
662
672
  }
663
673
  });
664
674
  });