@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
@@ -0,0 +1,171 @@
1
+ import { logger } from '../../shared/logging.js';
2
+ import type { JsonSchema } from '../tool-registry/tool.contract.js';
3
+
4
+ const log = logger('tools');
5
+
6
+ /**
7
+ * What a route-hosted tool declared about its own arguments, so the route that
8
+ * hosts it can check a call against it.
9
+ *
10
+ * Written by {@link declareRouteTool} — which `toolDef` calls for every tool it
11
+ * builds, the platform's own and a deployment's alike — and read by the tool
12
+ * handler. That is what makes the check free for a deployment: it declares its
13
+ * tool exactly as it does today and its route refuses a call that does not
14
+ * match, with no code of its own.
15
+ *
16
+ * Keyed by the ROUTE the tool is hosted at (its `toolDef` `path`), so the
17
+ * handler finds the schema from the request path alone, without every module
18
+ * having to hand it over a second time — and a tool whose route does not end
19
+ * in its name (`my_plugin` is `POST /api/plugins/personal`) is found all
20
+ * the same. Also readable by tool name, for the tests.
21
+ */
22
+ export interface RouteToolSchemas {
23
+ /** The tool's name: what the refusal calls it. */
24
+ name: string;
25
+ /** The route it is hosted at, e.g. `/api/agent/tools/grep`. */
26
+ path: string;
27
+ /**
28
+ * The LOGICAL (flat) schema: what the handler receives as `args`, and what a
29
+ * script calling `POST /api/agent/tools/<name>` sends as its JSON body. The
30
+ * arguments are checked against this one.
31
+ */
32
+ flat: JsonSchema;
33
+ /**
34
+ * The WIRE schema: the flat one inside the `{ body: … }` envelope, which is
35
+ * what an agent passes over MCP or inside a chain. Only the call example in a
36
+ * refusal is generated from it — so the line the agent is invited to copy is
37
+ * the line it would actually type.
38
+ */
39
+ wire: JsonSchema;
40
+ /**
41
+ * Arguments this tool refuses BY NAME ITSELF, with a message of its own — so
42
+ * the generic check says nothing about them and that message is what the
43
+ * caller reads. `create_branch`'s `name` is one: it answers
44
+ * `name-required`, which says what the name is for.
45
+ */
46
+ refusesItself: ReadonlySet<string>;
47
+ }
48
+
49
+ /**
50
+ * What every route-hosted tool declared, by the last segment of its route and
51
+ * then by the whole route. Process-wide, because the declarations are made by
52
+ * CODE — a module's or a deployment's `toolDef` — which is the same for every
53
+ * tenant a process serves: two tenants mount the same tools with the same
54
+ * arguments. A route re-declared with DIFFERENT arguments would break that, so
55
+ * it is logged (once per route) rather than passing silently.
56
+ */
57
+ const byLastSegment = new Map<string, RouteToolSchemas[]>();
58
+ /** Tool name → its declaration. */
59
+ const byName = new Map<string, RouteToolSchemas>();
60
+ /** Routes already reported as re-declared with other arguments. */
61
+ const conflicting = new Set<string>();
62
+
63
+ /** A route path as declarations and requests are compared: no query, no trailing slash. */
64
+ function normalizePath(path: string): string {
65
+ const withoutQuery = path.split('?')[0] ?? '';
66
+ return withoutQuery.length > 1 ? withoutQuery.replace(/\/+$/, '') : withoutQuery;
67
+ }
68
+
69
+ /** Does this flat input schema require any argument at all? */
70
+ function requiresAnything(inputs: JsonSchema): boolean {
71
+ const required = (inputs as { required?: unknown }).required;
72
+ return Array.isArray(required) && required.length > 0;
73
+ }
74
+
75
+ /**
76
+ * The flat inputs inside the `body` envelope every module-hosted tool advertises
77
+ * — the only standard-http way to ride multiple fields in a JSON body.
78
+ *
79
+ * The envelope is required only when something inside it is. A tool whose flat
80
+ * inputs are all optional is legitimately called as `Bevel.<name>({})` — agents
81
+ * have always called `list_branches` and `start_session` that way, and the
82
+ * endpoint reads `req.body` as `{}` either way — so declaring `body` required
83
+ * would make the schema disagree with the tool, and a check must never refuse a
84
+ * call the tool accepts.
85
+ */
86
+ export function bodyEnvelope(inputs: JsonSchema): JsonSchema {
87
+ return {
88
+ type: 'object',
89
+ properties: { body: inputs },
90
+ ...(requiresAnything(inputs) ? { required: ['body'] } : {}),
91
+ additionalProperties: false,
92
+ } as JsonSchema;
93
+ }
94
+
95
+ /**
96
+ * Record what the route-hosted tool `name` takes, and answer with the wire
97
+ * schema its def must advertise.
98
+ *
99
+ * `toolDef` calls this for every tool it builds, so declaring a tool IS
100
+ * declaring its arguments to the check. A module that builds its def lazily
101
+ * (the skill tools rebuild theirs per catalog listing, to name the skills that
102
+ * exist now) calls it once at registration as well, so a direct REST call that
103
+ * lands before the first catalog listing is checked too.
104
+ *
105
+ * Idempotent in effect: a tool whose def is rebuilt re-declares the same
106
+ * arguments, and the last declaration for a name is the one in force.
107
+ */
108
+ export function declareRouteTool(
109
+ name: string,
110
+ inputs: JsonSchema,
111
+ refusesItself: readonly string[] = [],
112
+ path = `/api/agent/tools/${name}`,
113
+ ): JsonSchema {
114
+ const wire = bodyEnvelope(inputs);
115
+ if (!name) return wire;
116
+ const route = normalizePath(path);
117
+ const entry: RouteToolSchemas = { name, path: route, flat: inputs, wire, refusesItself: new Set(refusesItself) };
118
+ const segment = routeToolName(route);
119
+ const same = (byLastSegment.get(segment) ?? []).filter((e) => e.path !== route);
120
+ const previous = (byLastSegment.get(segment) ?? []).find((e) => e.path === route);
121
+ if (previous && !conflicting.has(route) && JSON.stringify(previous.flat) !== JSON.stringify(inputs)) {
122
+ conflicting.add(route);
123
+ log.warn(`the route "${route}" was declared again with other arguments; calls are checked against the latest.`);
124
+ }
125
+ byLastSegment.set(segment, [...same, entry]);
126
+ byName.set(name, entry);
127
+ return wire;
128
+ }
129
+
130
+ /** What the tool named `name` takes, or `undefined` when nothing declared it. */
131
+ export function routeToolSchemas(name: string): RouteToolSchemas | undefined {
132
+ return byName.get(name);
133
+ }
134
+
135
+ /**
136
+ * What the tool hosted at the route a request arrived on takes, or
137
+ * `undefined` when no tool is declared there. `requestPath` is the request's
138
+ * WHOLE path (`req.originalUrl`): it ends with the declared route whatever
139
+ * the app is mounted under — a tenant's loopback prefix included.
140
+ */
141
+ export function routeToolSchemasForRequest(requestPath: string): RouteToolSchemas | undefined {
142
+ const path = normalizePath(requestPath);
143
+ const candidates = byLastSegment.get(routeToolName(path)) ?? [];
144
+ // The route that fits the request BEST wins, not the first one declared: an
145
+ // exact match, then the longest declared route the path ends with, so
146
+ // `/api/foo` is never checked against a `/foo` declared before it. A path
147
+ // shorter than every declaration (a test mounting a route bare) comes last.
148
+ const exact = candidates.find((e) => path === e.path);
149
+ if (exact) return exact;
150
+ const longest = (list: RouteToolSchemas[]): RouteToolSchemas | undefined =>
151
+ [...list].sort((a, b) => b.path.length - a.path.length)[0];
152
+ return (
153
+ longest(candidates.filter((e) => path.endsWith(e.path))) ??
154
+ longest(candidates.filter((e) => e.path.endsWith(path)))
155
+ );
156
+ }
157
+
158
+ /**
159
+ * The tool a request to `/api/agent/tools/<name>` is for: the last segment of
160
+ * the path, query and trailing slash left out.
161
+ *
162
+ * Read off the path rather than taken as a parameter, because the path is the
163
+ * only thing every mounting style agrees on — the modules mount their routes on
164
+ * a router at `/api`, the tests mount theirs wherever they like, and a
165
+ * deployment mounts its own.
166
+ */
167
+ export function routeToolName(path: string): string {
168
+ const withoutQuery = path.split('?')[0] ?? '';
169
+ const segments = withoutQuery.split('/').filter((s) => s.length > 0);
170
+ return segments[segments.length - 1] ?? '';
171
+ }
@@ -2,6 +2,7 @@ import '@utcp/http';
2
2
  import { HttpCallTemplateSerializer } from '@utcp/http';
3
3
  import { ToolSerializer } from '@utcp/sdk';
4
4
  import type { JsonSchema, UtcpTool } from '../tool-registry/tool.contract.js';
5
+ import { declareRouteTool } from './route-tool-schemas.js';
5
6
 
6
7
  const httpTemplate = new HttpCallTemplateSerializer();
7
8
  const toolSerializer = new ToolSerializer();
@@ -18,9 +19,99 @@ export const BRANCH_INPUT: JsonSchema = {
18
19
  description: 'The branch (draft) whose workspace this operates on — pass the branch you are currently working on.',
19
20
  };
20
21
 
22
+ /**
23
+ * The optional `branch` of a tool that declares `defaults-to-default-branch`:
24
+ * the same input, saying what happens without it.
25
+ */
26
+ export const DEFAULTED_BRANCH_INPUT: JsonSchema = {
27
+ type: 'string',
28
+ minLength: 1,
29
+ description:
30
+ "Optional: the branch (draft) to read. Without it the call runs on the deployment's default branch, " +
31
+ "and the answer's `branch` field names that branch.",
32
+ };
33
+
34
+ /**
35
+ * How a tool treats its `branch` input — the declaration the tool handler
36
+ * resolves the branch from, once, before the tool runs:
37
+ *
38
+ * - `required`: the call must name a branch. A call without one, or with an
39
+ * empty or non-string one, is refused with the `branch-required` refusal
40
+ * and the tool does not run. The default for a tool whose inputs require
41
+ * `branch`.
42
+ * - `defaults-to-default-branch`: a call without a branch runs on the
43
+ * deployment's default branch, and an object answer without a `branch`
44
+ * field gets one naming it. An empty or non-string branch is still refused:
45
+ * only an ABSENT one is defaulted. Read-only tools only — a writing tool
46
+ * declaring it fails at startup, since its write would land on the default
47
+ * branch without the caller having named it.
48
+ *
49
+ * A tool that declares neither, and whose inputs have no `branch`, takes no
50
+ * branch.
51
+ *
52
+ * Under both declarations a branch that is given must exist: one that does not
53
+ * is answered 404 naming it, before the tool runs. The tool reads the resolved
54
+ * name from `ctx.branch` — or from `args.branch`, which the handler overwrites
55
+ * with it — and never sees a missing, empty or non-string one.
56
+ */
57
+ export type BranchDeclaration = 'required' | 'defaults-to-default-branch';
58
+
59
+ /**
60
+ * What the tool handler does with a call's `branch`: a {@link BranchDeclaration},
61
+ * `none` for a tool that takes no branch, or `own` for a tool with an OPTIONAL
62
+ * `branch` and no declaration (the platform's skill tools, `list_tool_setup`)
63
+ * — such a tool keeps its own handling of an absent branch, and gets only the
64
+ * 404 for a named branch that does not exist.
65
+ */
66
+ export type BranchHandling = BranchDeclaration | 'none' | 'own';
67
+
68
+ /**
69
+ * Every route path a `toolDef` was built for, with how its branch is handled.
70
+ * Kept here rather than on the registry so the tool handler finds a tool's
71
+ * declaration from its route alone: a deployment's tool is covered by building
72
+ * its def with `toolDef` and mounting its route with `toolHandler`, as it
73
+ * already does, with nothing in between to keep in step.
74
+ */
75
+ const branchHandlingByPath = new Map<string, BranchHandling>();
76
+
77
+ /** How the tool hosted at `path` treats its branch; `undefined` for a route no `toolDef` described. */
78
+ export function branchHandlingFor(path: string): BranchHandling | undefined {
79
+ return branchHandlingByPath.get(path);
80
+ }
81
+
82
+ type ObjectSchema = { properties?: Record<string, JsonSchema>; required?: string[] };
83
+
84
+ /** A tool's branch handling read off inputs that carry no declaration. */
85
+ function inferBranchHandling(inputs: JsonSchema): BranchHandling {
86
+ const o = inputs as ObjectSchema;
87
+ if ((o.required ?? []).includes('branch')) return 'required';
88
+ return o.properties && 'branch' in o.properties ? 'own' : 'none';
89
+ }
90
+
91
+ /**
92
+ * The input schema with `branch` as `declaration` says: required, or optional
93
+ * with the sentence about the default. A tool that declares its own `branch`
94
+ * property with its own meaning (a fork base, a switch target) keeps that
95
+ * property under `required`.
96
+ */
97
+ export function withBranchDeclaration(inputs: JsonSchema, declaration: BranchDeclaration): JsonSchema {
98
+ const o = inputs as ObjectSchema;
99
+ const properties = { ...(o.properties ?? {}) };
100
+ const others = (o.required ?? []).filter((r) => r !== 'branch');
101
+ if (declaration === 'required') {
102
+ properties.branch = properties.branch ?? BRANCH_INPUT;
103
+ return { ...inputs, properties, required: [...others, 'branch'] } as JsonSchema;
104
+ }
105
+ properties.branch = DEFAULTED_BRANCH_INPUT;
106
+ const out = { ...inputs, properties } as ObjectSchema;
107
+ if (others.length > 0) out.required = others;
108
+ else delete out.required;
109
+ return out as JsonSchema;
110
+ }
111
+
21
112
  /** Add a required `branch` property to an object input schema (KB tools). */
22
113
  export function withBranchInput(inputs: JsonSchema): JsonSchema {
23
- const o = inputs as { properties?: Record<string, JsonSchema>; required?: string[] };
114
+ const o = inputs as ObjectSchema;
24
115
  return {
25
116
  ...inputs,
26
117
  properties: { ...(o.properties ?? {}), branch: BRANCH_INPUT },
@@ -38,6 +129,26 @@ export interface ToolDefSpec {
38
129
  tags?: string[];
39
130
  /** The route the owning module hosts, e.g. `/agent/tools/list_branches`. */
40
131
  path: string;
132
+ /**
133
+ * Arguments this tool's own handler refuses by name, with a message of its
134
+ * own. The generic argument check its route runs then says nothing about
135
+ * them, so that message is what the caller reads — which is the point of
136
+ * having written it. A missing `branch` needs no entry here: the route
137
+ * check leaves a call that names no branch to `branch-required`.
138
+ */
139
+ refusesItself?: string[];
140
+ /**
141
+ * How the tool treats `branch` — see {@link BranchDeclaration}. Given, the
142
+ * `branch` input is added to `inputs` to match. Omitted, a tool whose
143
+ * `inputs` require `branch` is `required`, and one without `branch` takes
144
+ * no branch.
145
+ */
146
+ branch?: BranchDeclaration;
147
+ /**
148
+ * The tool writes. A `write` tag says the same. A writing tool cannot
149
+ * declare `defaults-to-default-branch`.
150
+ */
151
+ write?: boolean;
41
152
  }
42
153
 
43
154
  /**
@@ -48,17 +159,31 @@ export interface ToolDefSpec {
48
159
  * as the flat args. The URL + bearer are `${API_URL}` / `${CONNECTION_KEY}`
49
160
  * placeholders the consumer resolves (public URL + key for external; loopback +
50
161
  * internal token for our agent), so one def serves both surfaces.
162
+ *
163
+ * Also records the tool's branch handling for its route, which is what the
164
+ * tool handler mounted on that route resolves the branch from. Throws — at
165
+ * startup, where defs are built — for a writing tool that declares
166
+ * `defaults-to-default-branch`.
51
167
  */
52
168
  export function toolDef(spec: ToolDefSpec): UtcpTool {
169
+ if (spec.branch === 'defaults-to-default-branch' && (spec.write || (spec.tags ?? []).includes('write'))) {
170
+ throw new Error(
171
+ `Tool "${spec.name}" writes, so it cannot declare branch: 'defaults-to-default-branch' — a write must ` +
172
+ "name its branch. Declare branch: 'required' instead.",
173
+ );
174
+ }
175
+ const inputs = spec.branch ? withBranchDeclaration(spec.inputs, spec.branch) : spec.inputs;
176
+ branchHandlingByPath.set(spec.path, spec.branch ?? inferBranchHandling(inputs));
177
+ // Declaring the tool IS declaring its arguments to the check that its route
178
+ // runs, which is why the envelope comes back from the declaration rather than
179
+ // being built here: a tool declared with this helper — the platform's own and
180
+ // a deployment's alike — refuses a call whose arguments do not match it on
181
+ // every way in, the REST route included, with no code of its own.
182
+ const wire = declareRouteTool(spec.name, inputs, spec.refusesItself, spec.path);
53
183
  return toolSerializer.validateDict({
54
184
  name: spec.name,
55
185
  description: spec.description,
56
- inputs: {
57
- type: 'object',
58
- properties: { body: spec.inputs },
59
- required: ['body'],
60
- additionalProperties: false,
61
- },
186
+ inputs: wire,
62
187
  outputs: spec.outputs ?? { type: 'object', properties: {} },
63
188
  tags: spec.tags ?? [],
64
189
  tool_call_template: httpTemplate.validateDict({
@@ -3,9 +3,16 @@ import { logger } from '../../shared/logging.js';
3
3
 
4
4
  const log = logger('tools');
5
5
  import { hasHttpStatus, ToolError, type ToolHandler } from './tool.contract.js';
6
- import { WorkflowDomainError } from '../../shared/domain-errors.js';
6
+ import {
7
+ assertBranchProvided,
8
+ BranchNotFoundError,
9
+ DefaultBranchUnsetError,
10
+ WorkflowDomainError,
11
+ } from '../../shared/domain-errors.js';
12
+ import { branchHandlingFor, type BranchHandling } from './tool-def.js';
7
13
  import { domainErrorBody } from '../../shared/http-errors.js';
8
14
  import type { ResolveToolContext } from './tool-context.js';
15
+ import { argumentsRefusal } from './route-argument-check.js';
9
16
  import { alwaysWritable, READ_ONLY_CODE, refuseWriteTool, type IWriteAccess } from '../write-access/write-access.js';
10
17
  import '../tool-auth/tool-auth.middleware.js'; // Express Request.toolAuth augmentation
11
18
 
@@ -20,18 +27,130 @@ function isAsyncIterable(v: unknown): v is AsyncIterable<unknown> {
20
27
  export interface ToolHandlerOptions {
21
28
  /** Mutating tool — refuse read-scoped callers up front (defense in depth). */
22
29
  write?: boolean;
30
+ /**
31
+ * Refuse read-scoped callers up front WITHOUT making the tool a write: the
32
+ * read-only-deployment gate below is not applied. For a tool a read-only
33
+ * credential may not use that is nonetheless a read — `request_file_download`
34
+ * takes bytes out, so a read-only deployment must still serve it.
35
+ */
36
+ writeScope?: boolean;
37
+ /**
38
+ * How this route treats `branch`, when it must differ from what its
39
+ * `toolDef` recorded. Only `execute_command` sets it (`own`): it falls back
40
+ * to an internal caller's focused branch rather than being refused.
41
+ */
42
+ branch?: BranchHandling;
43
+ }
44
+
45
+ /**
46
+ * What the tool handler needs to know about branches, asked at call time so a
47
+ * default branch renamed in the settings is the next call's default.
48
+ */
49
+ export interface ToolBranchPort {
50
+ /** The deployment's default branch; `''` until one is configured. */
51
+ defaultBranch(): string;
52
+ /**
53
+ * True only when `branch` certainly does not exist. Creates no workspace
54
+ * and clones nothing. A name it cannot judge — malformed, or a remote it
55
+ * could not ask — is `false`: opening the branch then answers for it.
56
+ */
57
+ isMissing(branch: string): Promise<boolean>;
58
+ }
59
+
60
+ /**
61
+ * Refuse a branch that certainly does not exist. A fault while finding out —
62
+ * a storage error reading the workspaces root, say — is logged here in full
63
+ * and answered as a 500 that names the branch and nothing else: its message
64
+ * would carry the deployment's paths to the caller.
65
+ */
66
+ async function assertBranchExists(branch: string, branches: ToolBranchPort | undefined): Promise<void> {
67
+ let missing: boolean | undefined;
68
+ try {
69
+ missing = await branches?.isMissing(branch);
70
+ } catch (err) {
71
+ log.error('branch existence check failed:', { branch, err });
72
+ throw new ToolError(`Could not check whether branch ${branch} exists.`, 500);
73
+ }
74
+ if (missing) throw new BranchNotFoundError(branch);
75
+ }
76
+
77
+ /**
78
+ * The branch a call runs on, from the tool's handling and the call's
79
+ * arguments — or the refusal. `defaulted` says the caller named none.
80
+ */
81
+ async function resolveBranch(
82
+ handling: BranchHandling,
83
+ args: Record<string, unknown>,
84
+ branches: ToolBranchPort | undefined,
85
+ ): Promise<{ branch: string; defaulted: boolean } | null> {
86
+ if (handling === 'none') return null;
87
+ const given = args.branch;
88
+ if (handling === 'own') {
89
+ // The tool reads its optional branch itself, absence and all; only a
90
+ // name it was given is ours to check, and only one that names something —
91
+ // what the tool makes of a value that does not is the tool's to answer.
92
+ if (namesSomething(given)) await assertBranchExists(given, branches);
93
+ return null;
94
+ }
95
+ // ABSENT, and only absent, is defaulted. An empty or non-string value is a
96
+ // caller that meant to name a branch and got it wrong: refused by name
97
+ // under both declarations rather than quietly read as the default.
98
+ if (handling === 'defaults-to-default-branch' && given === undefined) {
99
+ const fallback = branches?.defaultBranch() ?? '';
100
+ if (fallback.length === 0) throw new DefaultBranchUnsetError();
101
+ return { branch: fallback, defaulted: true };
102
+ }
103
+ assertBranchProvided(given);
104
+ await assertBranchExists(given, branches);
105
+ return { branch: given, defaulted: false };
106
+ }
107
+
108
+ function namesSomething(v: unknown): v is string {
109
+ try {
110
+ assertBranchProvided(v);
111
+ return true;
112
+ } catch {
113
+ return false;
114
+ }
115
+ }
116
+
117
+ /**
118
+ * A plain JSON object answer — the only kind a `branch` field can be added to.
119
+ * A `Date`, a class instance or an array serializes as something else, and
120
+ * spreading it into `{ branch }` would lose that answer.
121
+ */
122
+ function isPlainObject(v: unknown): v is Record<string, unknown> {
123
+ if (typeof v !== 'object' || v === null) return false;
124
+ const prototype: unknown = Object.getPrototypeOf(v);
125
+ return prototype === Object.prototype || prototype === null;
23
126
  }
24
127
 
25
128
  /**
26
129
  * Wrap a pure tool handler into an Express handler. Resolves the `ToolContext`
27
- * from `req.toolAuth` (set by `toolAuth`), enforces write-scope, runs the
28
- * handler, and serializes the result — JSON for a value, SSE for an
29
- * async-iterable (streaming). `ToolError`/`hasHttpStatus` map to HTTP status.
30
- * The handler receives `req.body` as the flat args (UTCP's `body_field` already
31
- * delivered the inner body as the request body).
130
+ * from `req.toolAuth` (set by `toolAuth`), enforces write-scope, CHECKS THE
131
+ * ARGUMENTS against what the tool declared, runs the handler, and serializes the
132
+ * result — JSON for a value, SSE for an async-iterable (streaming).
133
+ * `ToolError`/`hasHttpStatus` map to HTTP status. The handler receives
134
+ * `req.body` as the flat args (UTCP's `body_field` already delivered the inner
135
+ * body as the request body).
136
+ *
137
+ * The argument check is HERE, in the route, because the route is where every
138
+ * caller of a route-hosted tool arrives: an agent over MCP, a `call_tool_chain`
139
+ * chain, the in-process agent over loopback, and a script calling
140
+ * `POST /api/agent/tools/<name>` with a connection key. Checked one layer up,
141
+ * in the MCP dispatch, the last of those was left answering a 500 for a missing
142
+ * argument — or running: `grep` without a `pattern` matched every file. Every
143
+ * tool mounted through this factory gets it, a deployment's included, without
144
+ * saying so (see `route-argument-check.ts`).
32
145
  */
33
- export function createToolHandlerFactory(resolve: ResolveToolContext, writeAccess: IWriteAccess = alwaysWritable) {
146
+ export function createToolHandlerFactory(
147
+ resolve: ResolveToolContext,
148
+ writeAccess: IWriteAccess = alwaysWritable,
149
+ /** Absent: no default branch to fall back on, and no existence check before the tool runs. */
150
+ branches?: ToolBranchPort,
151
+ ) {
34
152
  return function toolHandler(handler: ToolHandler, opts: ToolHandlerOptions = {}) {
153
+ let warnedHeldToRequired = false;
35
154
  return async (req: Request, res: Response): Promise<void> => {
36
155
  const auth = req.toolAuth;
37
156
  if (!auth) {
@@ -42,6 +161,12 @@ export function createToolHandlerFactory(resolve: ResolveToolContext, writeAcces
42
161
  res.status(403).json({ error: 'This tool requires write access.' });
43
162
  return;
44
163
  }
164
+ // About the CREDENTIAL, not the deployment: the tool is a read, and a
165
+ // read-only deployment serves it — the key used here is what may not.
166
+ if (opts.writeScope && auth.scope === 'read') {
167
+ res.status(403).json({ error: 'This tool cannot be used with a read-only credential; use a full-scope key.' });
168
+ return;
169
+ }
45
170
  // Before anything is awaited: a client that goes away during the
46
171
  // write-access check below must still abort the call.
47
172
  //
@@ -71,6 +196,18 @@ export function createToolHandlerFactory(resolve: ResolveToolContext, writeAcces
71
196
  return;
72
197
  }
73
198
  const args = (body ?? {}) as Record<string, unknown>;
199
+ // Before the context is resolved and before the handler runs: nothing is
200
+ // read, written or sent on a call that does not match its tool. The more
201
+ // specific refusals each tool makes for itself (a missing `branch`,
202
+ // above all) are left to the handler and keep their own wording — this
203
+ // reports what the SCHEMA alone can settle.
204
+ // The WHOLE path: a module mounts its routes on a router, which sees only
205
+ // the part below its mount point.
206
+ const mismatch = argumentsRefusal(req.originalUrl, args, req.query);
207
+ if (mismatch) {
208
+ res.status(mismatch.status).json({ ...mismatch.details, error: mismatch.message });
209
+ return;
210
+ }
74
211
  // `sessionId` rides the tool body like `branch` does: the external MCP
75
212
  // proxy injects it (ask-tool continuity convention) and the in-process
76
213
  // agent passes its thread id. Surfaced on the context so the
@@ -79,8 +216,52 @@ export function createToolHandlerFactory(resolve: ResolveToolContext, writeAcces
79
216
  ? args.sessionId
80
217
  : undefined;
81
218
  try {
219
+ // The branch is resolved HERE, once, before the tool runs: refused,
220
+ // defaulted or checked to exist, by what the tool declared — so no
221
+ // tool, the platform's or a deployment's, carries a guard of its own,
222
+ // and none can be handed a branch that is missing, empty or not a
223
+ // string. Its declaration comes from the `toolDef` built for this
224
+ // route; a route no `toolDef` described is read like an optional
225
+ // `branch` the tool handles itself.
226
+ //
227
+ // A writing route never defaults, whatever was recorded for it:
228
+ // `toolDef` refuses the declaration at startup for a tool it knows
229
+ // writes, and one only mounted as writing is held to `required` here,
230
+ // so its write cannot land on the default branch unasked. Its
231
+ // published schema still calls `branch` optional — the tool's
232
+ // declaration is wrong, not the call — so the mismatch is logged,
233
+ // once per route, for the tool's author to fix by declaring both
234
+ // `write: true` and `branch: 'required'` (`write` alone with the
235
+ // defaulting declaration makes `toolDef` throw at startup).
236
+ const declared =
237
+ opts.branch ?? branchHandlingFor(req.baseUrl + (req.route?.path ?? req.path)) ?? 'own';
238
+ const heldToRequired = declared === 'defaults-to-default-branch' && opts.write === true;
239
+ const handling: BranchHandling = heldToRequired ? 'required' : declared;
240
+ if (heldToRequired && !warnedHeldToRequired) {
241
+ warnedHeldToRequired = true;
242
+ log.warn(
243
+ `Tool route ${req.baseUrl + (req.route?.path ?? req.path)} is mounted as writing but declared ` +
244
+ `branch: 'defaults-to-default-branch'; it is held to 'required'. ` +
245
+ `Declare both \`write: true\` and \`branch: 'required'\` to its toolDef.`,
246
+ );
247
+ }
248
+ const resolved = await resolveBranch(handling, args, branches);
249
+ // The tool can read no other branch than the resolved one: `args`
250
+ // carries it under the same name, and a tool that takes no branch
251
+ // gets none at all.
252
+ const toolArgs = resolved
253
+ ? { ...args, branch: resolved.branch }
254
+ : handling === 'none'
255
+ ? withoutBranch(args)
256
+ : args;
82
257
  const ctx = await resolve(auth, abort.signal, sessionId);
83
- const out = await handler(args, ctx);
258
+ if (resolved) ctx.branch = resolved.branch;
259
+ let out = await handler(toolArgs, ctx);
260
+ // A defaulted call's answer says which branch it ran on, so a caller
261
+ // that forgot its branch can see it was answered about another.
262
+ if (resolved?.defaulted && isPlainObject(out) && out.branch === undefined) {
263
+ out = { ...out, branch: resolved.branch };
264
+ }
84
265
  if (isAsyncIterable(out)) {
85
266
  res.setHeader('Content-Type', 'text/event-stream');
86
267
  res.setHeader('Cache-Control', 'no-cache');
@@ -126,4 +307,11 @@ export function createToolHandlerFactory(resolve: ResolveToolContext, writeAcces
126
307
  };
127
308
  }
128
309
 
310
+ function withoutBranch(args: Record<string, unknown>): Record<string, unknown> {
311
+ if (!('branch' in args)) return args;
312
+ const rest = { ...args };
313
+ delete rest.branch;
314
+ return rest;
315
+ }
316
+
129
317
  export type ToolHandlerFactory = ReturnType<typeof createToolHandlerFactory>;
@@ -48,6 +48,15 @@ export interface ToolContext {
48
48
  * still fails closed regardless of this).
49
49
  */
50
50
  focusedBranch?: string;
51
+ /**
52
+ * The branch this call runs on, resolved by the tool handler before the tool
53
+ * ran from the tool's branch declaration (see `BranchDeclaration`): the one
54
+ * the caller named, or the deployment's default branch for a
55
+ * `defaults-to-default-branch` tool called without one. Set for every tool
56
+ * that declares a branch, and always a non-empty string then; absent for a
57
+ * tool that takes no branch.
58
+ */
59
+ branch?: string;
51
60
  abortSignal: AbortSignal;
52
61
  workspaceService: WorkspaceService;
53
62
  workflowService: IWorkflowService;