@bevel-software/platform-core-backend 0.15.1 → 0.20.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 (1385) hide show
  1. package/THIRD-PARTY-NOTICES.md +4 -4
  2. package/dist/core/catalog-cache-invalidation.d.ts +16 -0
  3. package/dist/core/catalog-cache-invalidation.d.ts.map +1 -1
  4. package/dist/core/catalog-cache-invalidation.js +6 -1
  5. package/dist/core/catalog-cache-invalidation.js.map +1 -1
  6. package/dist/core/catalog-revision.d.ts +39 -0
  7. package/dist/core/catalog-revision.d.ts.map +1 -0
  8. package/dist/core/catalog-revision.js +94 -0
  9. package/dist/core/catalog-revision.js.map +1 -0
  10. package/dist/core/create-core-server.d.ts +7 -0
  11. package/dist/core/create-core-server.d.ts.map +1 -1
  12. package/dist/core/create-core-server.js +221 -75
  13. package/dist/core/create-core-server.js.map +1 -1
  14. package/dist/core/create-core-services.d.ts +52 -2
  15. package/dist/core/create-core-services.d.ts.map +1 -1
  16. package/dist/core/create-core-services.js +222 -87
  17. package/dist/core/create-core-services.js.map +1 -1
  18. package/dist/core/lifecycle.d.ts +138 -0
  19. package/dist/core/lifecycle.d.ts.map +1 -0
  20. package/dist/core/lifecycle.js +218 -0
  21. package/dist/core/lifecycle.js.map +1 -0
  22. package/dist/core/public-config.d.ts +82 -0
  23. package/dist/core/public-config.d.ts.map +1 -0
  24. package/dist/core/public-config.js +78 -0
  25. package/dist/core/public-config.js.map +1 -0
  26. package/dist/core/readiness.d.ts +86 -0
  27. package/dist/core/readiness.d.ts.map +1 -0
  28. package/dist/core/readiness.js +56 -0
  29. package/dist/core/readiness.js.map +1 -0
  30. package/dist/core-config.d.ts +30 -0
  31. package/dist/core-config.d.ts.map +1 -1
  32. package/dist/core-config.js +64 -2
  33. package/dist/core-config.js.map +1 -1
  34. package/dist/index.d.ts +4 -1
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +6 -0
  37. package/dist/index.js.map +1 -1
  38. package/dist/modules/access/access-control.interface.d.ts +215 -11
  39. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  40. package/dist/modules/access/access-control.service.d.ts +57 -11
  41. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  42. package/dist/modules/access/access-control.service.js +616 -155
  43. package/dist/modules/access/access-control.service.js.map +1 -1
  44. package/dist/modules/access/access-mutation.service.d.ts +71 -5
  45. package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
  46. package/dist/modules/access/access-mutation.service.js +161 -13
  47. package/dist/modules/access/access-mutation.service.js.map +1 -1
  48. package/dist/modules/access/access-view.d.ts +147 -0
  49. package/dist/modules/access/access-view.d.ts.map +1 -0
  50. package/dist/modules/access/access-view.js +211 -0
  51. package/dist/modules/access/access-view.js.map +1 -0
  52. package/dist/modules/access/access.routes.d.ts.map +1 -1
  53. package/dist/modules/access/access.routes.js +255 -79
  54. package/dist/modules/access/access.routes.js.map +1 -1
  55. package/dist/modules/access/admin-locked-commit.d.ts +2 -3
  56. package/dist/modules/access/admin-locked-commit.d.ts.map +1 -1
  57. package/dist/modules/access/admin-locked-commit.js +9 -8
  58. package/dist/modules/access/admin-locked-commit.js.map +1 -1
  59. package/dist/modules/access/admin-route-helpers.d.ts.map +1 -1
  60. package/dist/modules/access/admin-route-helpers.js +4 -4
  61. package/dist/modules/access/admin-route-helpers.js.map +1 -1
  62. package/dist/modules/access/change-read-gate.d.ts +53 -0
  63. package/dist/modules/access/change-read-gate.d.ts.map +1 -0
  64. package/dist/modules/access/change-read-gate.js +134 -0
  65. package/dist/modules/access/change-read-gate.js.map +1 -0
  66. package/dist/modules/access/creator-access.d.ts +33 -39
  67. package/dist/modules/access/creator-access.d.ts.map +1 -1
  68. package/dist/modules/access/creator-access.js +92 -119
  69. package/dist/modules/access/creator-access.js.map +1 -1
  70. package/dist/modules/access/directory-sync-bot.d.ts.map +1 -1
  71. package/dist/modules/access/directory-sync-bot.js +3 -1
  72. package/dist/modules/access/directory-sync-bot.js.map +1 -1
  73. package/dist/modules/access/groups-admin.service.d.ts +3 -3
  74. package/dist/modules/access/groups-admin.service.js +2 -2
  75. package/dist/modules/access/reference-scan.d.ts +1 -1
  76. package/dist/modules/access/reference-scan.d.ts.map +1 -1
  77. package/dist/modules/access/reference-scan.js +5 -4
  78. package/dist/modules/access/reference-scan.js.map +1 -1
  79. package/dist/modules/access/roles-admin.service.d.ts +72 -13
  80. package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
  81. package/dist/modules/access/roles-admin.service.js +71 -13
  82. package/dist/modules/access/roles-admin.service.js.map +1 -1
  83. package/dist/modules/access/synced-groups-committer.d.ts.map +1 -1
  84. package/dist/modules/access/synced-groups-committer.js +8 -6
  85. package/dist/modules/access/synced-groups-committer.js.map +1 -1
  86. package/dist/modules/access/synced-groups-writer.d.ts.map +1 -1
  87. package/dist/modules/access/synced-groups-writer.js +3 -8
  88. package/dist/modules/access/synced-groups-writer.js.map +1 -1
  89. package/dist/modules/access/user-access-removal.service.d.ts +209 -0
  90. package/dist/modules/access/user-access-removal.service.d.ts.map +1 -0
  91. package/dist/modules/access/user-access-removal.service.js +662 -0
  92. package/dist/modules/access/user-access-removal.service.js.map +1 -0
  93. package/dist/modules/access-model/access-errors.d.ts +32 -1
  94. package/dist/modules/access-model/access-errors.d.ts.map +1 -1
  95. package/dist/modules/access-model/access-errors.js +38 -11
  96. package/dist/modules/access-model/access-errors.js.map +1 -1
  97. package/dist/modules/access-model/access-grammar.d.ts +49 -24
  98. package/dist/modules/access-model/access-grammar.d.ts.map +1 -1
  99. package/dist/modules/access-model/access-grammar.js +68 -48
  100. package/dist/modules/access-model/access-grammar.js.map +1 -1
  101. package/dist/modules/access-model/access-splice.d.ts.map +1 -1
  102. package/dist/modules/access-model/access-splice.js +18 -18
  103. package/dist/modules/access-model/access-splice.js.map +1 -1
  104. package/dist/modules/access-model/access-template.d.ts +22 -0
  105. package/dist/modules/access-model/access-template.d.ts.map +1 -0
  106. package/dist/modules/access-model/access-template.js +42 -0
  107. package/dist/modules/access-model/access-template.js.map +1 -0
  108. package/dist/modules/access-model/change-gate.d.ts +65 -0
  109. package/dist/modules/access-model/change-gate.d.ts.map +1 -0
  110. package/dist/modules/access-model/change-gate.js +25 -0
  111. package/dist/modules/access-model/change-gate.js.map +1 -0
  112. package/dist/modules/access-model/creator.d.ts +13 -16
  113. package/dist/modules/access-model/creator.d.ts.map +1 -1
  114. package/dist/modules/access-model/creator.js.map +1 -1
  115. package/dist/modules/access-model/frontmatter-lines.d.ts +27 -0
  116. package/dist/modules/access-model/frontmatter-lines.d.ts.map +1 -0
  117. package/dist/modules/access-model/frontmatter-lines.js +74 -0
  118. package/dist/modules/access-model/frontmatter-lines.js.map +1 -0
  119. package/dist/modules/access-model/plugin-principals.d.ts +5 -3
  120. package/dist/modules/access-model/plugin-principals.d.ts.map +1 -1
  121. package/dist/modules/access-model/plugin-principals.js +11 -17
  122. package/dist/modules/access-model/plugin-principals.js.map +1 -1
  123. package/dist/modules/access-model/roles-yaml-guard.d.ts +73 -2
  124. package/dist/modules/access-model/roles-yaml-guard.d.ts.map +1 -1
  125. package/dist/modules/access-model/roles-yaml-guard.js +153 -3
  126. package/dist/modules/access-model/roles-yaml-guard.js.map +1 -1
  127. package/dist/modules/admin/admin-access.service.d.ts.map +1 -1
  128. package/dist/modules/admin/admin-access.service.js +5 -2
  129. package/dist/modules/admin/admin-access.service.js.map +1 -1
  130. package/dist/modules/agent-instructions/agent-instructions.routes.d.ts +21 -0
  131. package/dist/modules/agent-instructions/agent-instructions.routes.d.ts.map +1 -0
  132. package/dist/modules/agent-instructions/agent-instructions.routes.js +38 -0
  133. package/dist/modules/agent-instructions/agent-instructions.routes.js.map +1 -0
  134. package/dist/modules/agent-instructions/compose.d.ts +71 -0
  135. package/dist/modules/agent-instructions/compose.d.ts.map +1 -0
  136. package/dist/modules/agent-instructions/compose.js +234 -0
  137. package/dist/modules/agent-instructions/compose.js.map +1 -0
  138. package/dist/modules/agent-instructions/index.d.ts +4 -0
  139. package/dist/modules/agent-instructions/index.d.ts.map +1 -0
  140. package/dist/modules/agent-instructions/index.js +4 -0
  141. package/dist/modules/agent-instructions/index.js.map +1 -0
  142. package/dist/modules/agent-instructions/read-preamble.d.ts +37 -0
  143. package/dist/modules/agent-instructions/read-preamble.d.ts.map +1 -0
  144. package/dist/modules/agent-instructions/read-preamble.js +81 -0
  145. package/dist/modules/agent-instructions/read-preamble.js.map +1 -0
  146. package/dist/modules/audit/agent-audit.service.d.ts +54 -0
  147. package/dist/modules/audit/agent-audit.service.d.ts.map +1 -0
  148. package/dist/modules/audit/agent-audit.service.js +290 -0
  149. package/dist/modules/audit/agent-audit.service.js.map +1 -0
  150. package/dist/modules/audit/agent-rest-audit.middleware.d.ts +40 -0
  151. package/dist/modules/audit/agent-rest-audit.middleware.d.ts.map +1 -0
  152. package/dist/modules/audit/agent-rest-audit.middleware.js +75 -0
  153. package/dist/modules/audit/agent-rest-audit.middleware.js.map +1 -0
  154. package/dist/modules/audit/audit.contract.d.ts +145 -0
  155. package/dist/modules/audit/audit.contract.d.ts.map +1 -0
  156. package/dist/modules/audit/audit.contract.js +35 -0
  157. package/dist/modules/audit/audit.contract.js.map +1 -0
  158. package/dist/modules/audit/audit.routes.d.ts +21 -0
  159. package/dist/modules/audit/audit.routes.d.ts.map +1 -0
  160. package/dist/modules/audit/audit.routes.js +157 -0
  161. package/dist/modules/audit/audit.routes.js.map +1 -0
  162. package/dist/modules/audit/event-classifier.d.ts +52 -0
  163. package/dist/modules/audit/event-classifier.d.ts.map +1 -0
  164. package/dist/modules/audit/event-classifier.js +123 -0
  165. package/dist/modules/audit/event-classifier.js.map +1 -0
  166. package/dist/modules/audit/index.d.ts +7 -0
  167. package/dist/modules/audit/index.d.ts.map +1 -0
  168. package/dist/modules/audit/index.js +7 -0
  169. package/dist/modules/audit/index.js.map +1 -0
  170. package/dist/modules/audit/request-audit.d.ts +57 -0
  171. package/dist/modules/audit/request-audit.d.ts.map +1 -0
  172. package/dist/modules/audit/request-audit.js +117 -0
  173. package/dist/modules/audit/request-audit.js.map +1 -0
  174. package/dist/modules/auth/account-erasure.service.d.ts +30 -4
  175. package/dist/modules/auth/account-erasure.service.d.ts.map +1 -1
  176. package/dist/modules/auth/account-erasure.service.js +96 -10
  177. package/dist/modules/auth/account-erasure.service.js.map +1 -1
  178. package/dist/modules/auth/account.routes.d.ts +6 -3
  179. package/dist/modules/auth/account.routes.d.ts.map +1 -1
  180. package/dist/modules/auth/account.routes.js +109 -10
  181. package/dist/modules/auth/account.routes.js.map +1 -1
  182. package/dist/modules/auth/auth.routes.d.ts +10 -3
  183. package/dist/modules/auth/auth.routes.d.ts.map +1 -1
  184. package/dist/modules/auth/auth.routes.js +9 -4
  185. package/dist/modules/auth/auth.routes.js.map +1 -1
  186. package/dist/modules/auth/auth.service.d.ts +89 -5
  187. package/dist/modules/auth/auth.service.d.ts.map +1 -1
  188. package/dist/modules/auth/auth.service.js +160 -25
  189. package/dist/modules/auth/auth.service.js.map +1 -1
  190. package/dist/modules/auth/oidc-auth-provider.d.ts +46 -3
  191. package/dist/modules/auth/oidc-auth-provider.d.ts.map +1 -1
  192. package/dist/modules/auth/oidc-auth-provider.js +99 -21
  193. package/dist/modules/auth/oidc-auth-provider.js.map +1 -1
  194. package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
  195. package/dist/modules/code-mode/code-mode.tool.js +14 -2
  196. package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
  197. package/dist/modules/connection-probe/connection-probe.service.d.ts.map +1 -1
  198. package/dist/modules/connection-probe/connection-probe.service.js +56 -7
  199. package/dist/modules/connection-probe/connection-probe.service.js.map +1 -1
  200. package/dist/modules/database/advisory-lock.d.ts +103 -0
  201. package/dist/modules/database/advisory-lock.d.ts.map +1 -0
  202. package/dist/modules/database/advisory-lock.js +320 -0
  203. package/dist/modules/database/advisory-lock.js.map +1 -0
  204. package/dist/modules/database/connection.d.ts.map +1 -1
  205. package/dist/modules/database/connection.js +9 -1
  206. package/dist/modules/database/connection.js.map +1 -1
  207. package/dist/modules/database/core-schema.d.ts +859 -99
  208. package/dist/modules/database/core-schema.d.ts.map +1 -1
  209. package/dist/modules/database/core-schema.js +218 -0
  210. package/dist/modules/database/core-schema.js.map +1 -1
  211. package/dist/modules/database/migrate.d.ts.map +1 -1
  212. package/dist/modules/database/migrate.js +26 -6
  213. package/dist/modules/database/migrate.js.map +1 -1
  214. package/dist/modules/declared-variables/declared-variables.routes.d.ts.map +1 -1
  215. package/dist/modules/declared-variables/declared-variables.routes.js +4 -2
  216. package/dist/modules/declared-variables/declared-variables.routes.js.map +1 -1
  217. package/dist/modules/diff/diff.routes.d.ts.map +1 -1
  218. package/dist/modules/diff/diff.routes.js +2 -4
  219. package/dist/modules/diff/diff.routes.js.map +1 -1
  220. package/dist/modules/diff/diff.service.d.ts +4 -1
  221. package/dist/modules/diff/diff.service.d.ts.map +1 -1
  222. package/dist/modules/diff/diff.service.js +84 -81
  223. package/dist/modules/diff/diff.service.js.map +1 -1
  224. package/dist/modules/{workspace → kb-fs}/bevel-ignore.d.ts +14 -9
  225. package/dist/modules/kb-fs/bevel-ignore.d.ts.map +1 -0
  226. package/dist/modules/{workspace → kb-fs}/bevel-ignore.js +24 -14
  227. package/dist/modules/kb-fs/bevel-ignore.js.map +1 -0
  228. package/dist/modules/kb-fs/file-change-notifier.d.ts.map +1 -1
  229. package/dist/modules/kb-fs/file-change-notifier.js +3 -1
  230. package/dist/modules/kb-fs/file-change-notifier.js.map +1 -1
  231. package/dist/modules/kb-fs/git-guarded-filesystem.d.ts +45 -0
  232. package/dist/modules/kb-fs/git-guarded-filesystem.d.ts.map +1 -0
  233. package/dist/modules/kb-fs/git-guarded-filesystem.js +117 -0
  234. package/dist/modules/kb-fs/git-guarded-filesystem.js.map +1 -0
  235. package/dist/modules/kb-fs/locking-filesystem.d.ts +113 -18
  236. package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
  237. package/dist/modules/kb-fs/locking-filesystem.js +353 -76
  238. package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
  239. package/dist/modules/kb-fs/node-fs.d.ts +23 -0
  240. package/dist/modules/kb-fs/node-fs.d.ts.map +1 -0
  241. package/dist/modules/kb-fs/node-fs.js +158 -0
  242. package/dist/modules/kb-fs/node-fs.js.map +1 -0
  243. package/dist/modules/kb-fs/read-only-filesystem.d.ts +10 -9
  244. package/dist/modules/kb-fs/read-only-filesystem.d.ts.map +1 -1
  245. package/dist/modules/kb-fs/read-only-filesystem.js +18 -9
  246. package/dist/modules/kb-fs/read-only-filesystem.js.map +1 -1
  247. package/dist/modules/kb-fs/repo-path.d.ts +100 -3
  248. package/dist/modules/kb-fs/repo-path.d.ts.map +1 -1
  249. package/dist/modules/kb-fs/repo-path.js +195 -3
  250. package/dist/modules/kb-fs/repo-path.js.map +1 -1
  251. package/dist/modules/kb-sync/kb-sync.routes.d.ts.map +1 -1
  252. package/dist/modules/kb-sync/kb-sync.routes.js +4 -2
  253. package/dist/modules/kb-sync/kb-sync.routes.js.map +1 -1
  254. package/dist/modules/kb-sync/kb-sync.service.d.ts.map +1 -1
  255. package/dist/modules/kb-sync/kb-sync.service.js +7 -5
  256. package/dist/modules/kb-sync/kb-sync.service.js.map +1 -1
  257. package/dist/modules/marketplace/git-http.routes.d.ts.map +1 -1
  258. package/dist/modules/marketplace/git-http.routes.js +6 -4
  259. package/dist/modules/marketplace/git-http.routes.js.map +1 -1
  260. package/dist/modules/marketplace/github-facade/github-facade-admin.routes.d.ts +12 -2
  261. package/dist/modules/marketplace/github-facade/github-facade-admin.routes.d.ts.map +1 -1
  262. package/dist/modules/marketplace/github-facade/github-facade-admin.routes.js +32 -3
  263. package/dist/modules/marketplace/github-facade/github-facade-admin.routes.js.map +1 -1
  264. package/dist/modules/marketplace/github-facade/github-facade-credentials.service.d.ts +27 -0
  265. package/dist/modules/marketplace/github-facade/github-facade-credentials.service.d.ts.map +1 -1
  266. package/dist/modules/marketplace/github-facade/github-facade-credentials.service.js +45 -0
  267. package/dist/modules/marketplace/github-facade/github-facade-credentials.service.js.map +1 -1
  268. package/dist/modules/marketplace/github-facade/github-facade.routes.d.ts.map +1 -1
  269. package/dist/modules/marketplace/github-facade/github-facade.routes.js +30 -6
  270. package/dist/modules/marketplace/github-facade/github-facade.routes.js.map +1 -1
  271. package/dist/modules/marketplace/github-facade/github-facade.service.d.ts +21 -9
  272. package/dist/modules/marketplace/github-facade/github-facade.service.d.ts.map +1 -1
  273. package/dist/modules/marketplace/github-facade/github-facade.service.js +60 -19
  274. package/dist/modules/marketplace/github-facade/github-facade.service.js.map +1 -1
  275. package/dist/modules/marketplace/github-facade/index.d.ts +1 -1
  276. package/dist/modules/marketplace/github-facade/index.d.ts.map +1 -1
  277. package/dist/modules/marketplace/github-facade/index.js +1 -1
  278. package/dist/modules/marketplace/github-facade/index.js.map +1 -1
  279. package/dist/modules/marketplace/marketplace-repo.service.d.ts +14 -1
  280. package/dist/modules/marketplace/marketplace-repo.service.d.ts.map +1 -1
  281. package/dist/modules/marketplace/marketplace-repo.service.js +26 -8
  282. package/dist/modules/marketplace/marketplace-repo.service.js.map +1 -1
  283. package/dist/modules/mcp/downstream-pool.d.ts +107 -0
  284. package/dist/modules/mcp/downstream-pool.d.ts.map +1 -0
  285. package/dist/modules/mcp/downstream-pool.js +221 -0
  286. package/dist/modules/mcp/downstream-pool.js.map +1 -0
  287. package/dist/modules/mcp/downstream-token-refresh.d.ts +77 -0
  288. package/dist/modules/mcp/downstream-token-refresh.d.ts.map +1 -0
  289. package/dist/modules/mcp/downstream-token-refresh.js +166 -0
  290. package/dist/modules/mcp/downstream-token-refresh.js.map +1 -0
  291. package/dist/modules/mcp/first-call-probe.cli-args.d.ts +78 -0
  292. package/dist/modules/mcp/first-call-probe.cli-args.d.ts.map +1 -0
  293. package/dist/modules/mcp/first-call-probe.cli-args.js +151 -0
  294. package/dist/modules/mcp/first-call-probe.cli-args.js.map +1 -0
  295. package/dist/modules/mcp/first-call-probe.cli.d.ts +2 -0
  296. package/dist/modules/mcp/first-call-probe.cli.d.ts.map +1 -0
  297. package/dist/modules/mcp/first-call-probe.cli.js +62 -0
  298. package/dist/modules/mcp/first-call-probe.cli.js.map +1 -0
  299. package/dist/modules/mcp/first-call-probe.d.ts +165 -0
  300. package/dist/modules/mcp/first-call-probe.d.ts.map +1 -0
  301. package/dist/modules/mcp/first-call-probe.js +298 -0
  302. package/dist/modules/mcp/first-call-probe.js.map +1 -0
  303. package/dist/modules/mcp/manual-failure-memo.d.ts +42 -17
  304. package/dist/modules/mcp/manual-failure-memo.d.ts.map +1 -1
  305. package/dist/modules/mcp/manual-failure-memo.js +79 -23
  306. package/dist/modules/mcp/manual-failure-memo.js.map +1 -1
  307. package/dist/modules/mcp/mcp-auth.middleware.d.ts +10 -5
  308. package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
  309. package/dist/modules/mcp/mcp-auth.middleware.js +56 -16
  310. package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
  311. package/dist/modules/mcp/mcp.routes.d.ts +4 -4
  312. package/dist/modules/mcp/mcp.routes.d.ts.map +1 -1
  313. package/dist/modules/mcp/mcp.routes.js +91 -180
  314. package/dist/modules/mcp/mcp.routes.js.map +1 -1
  315. package/dist/modules/mcp/mcp.service.d.ts +226 -61
  316. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  317. package/dist/modules/mcp/mcp.service.js +758 -161
  318. package/dist/modules/mcp/mcp.service.js.map +1 -1
  319. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +59 -0
  320. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
  321. package/dist/modules/mcp/oauth/bevel-oauth-provider.js +184 -10
  322. package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
  323. package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts.map +1 -1
  324. package/dist/modules/mcp/oauth/oauth-consent.routes.js +3 -1
  325. package/dist/modules/mcp/oauth/oauth-consent.routes.js.map +1 -1
  326. package/dist/modules/mcp/surface-log-throttle.d.ts +59 -0
  327. package/dist/modules/mcp/surface-log-throttle.d.ts.map +1 -0
  328. package/dist/modules/mcp/surface-log-throttle.js +94 -0
  329. package/dist/modules/mcp/surface-log-throttle.js.map +1 -0
  330. package/dist/modules/plugins/compile/compile-marketplace.d.ts +13 -6
  331. package/dist/modules/plugins/compile/compile-marketplace.d.ts.map +1 -1
  332. package/dist/modules/plugins/compile/compile-marketplace.js +119 -36
  333. package/dist/modules/plugins/compile/compile-marketplace.js.map +1 -1
  334. package/dist/modules/plugins/compile/marketplace-compiler.service.d.ts +8 -1
  335. package/dist/modules/plugins/compile/marketplace-compiler.service.d.ts.map +1 -1
  336. package/dist/modules/plugins/compile/marketplace-compiler.service.js +9 -6
  337. package/dist/modules/plugins/compile/marketplace-compiler.service.js.map +1 -1
  338. package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.d.ts.map +1 -1
  339. package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.js +32 -9
  340. package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.js.map +1 -1
  341. package/dist/modules/plugins/discovery/bundle-dialect/registry.d.ts +26 -6
  342. package/dist/modules/plugins/discovery/bundle-dialect/registry.d.ts.map +1 -1
  343. package/dist/modules/plugins/discovery/bundle-dialect/registry.js +71 -14
  344. package/dist/modules/plugins/discovery/bundle-dialect/registry.js.map +1 -1
  345. package/dist/modules/plugins/discovery/kb-plugin-source.d.ts +4 -2
  346. package/dist/modules/plugins/discovery/kb-plugin-source.d.ts.map +1 -1
  347. package/dist/modules/plugins/discovery/kb-plugin-source.js +9 -6
  348. package/dist/modules/plugins/discovery/kb-plugin-source.js.map +1 -1
  349. package/dist/modules/plugins/discovery/native.source.d.ts +2 -1
  350. package/dist/modules/plugins/discovery/native.source.d.ts.map +1 -1
  351. package/dist/modules/plugins/discovery/native.source.js +22 -4
  352. package/dist/modules/plugins/discovery/native.source.js.map +1 -1
  353. package/dist/modules/plugins/discovery/plugin-source.d.ts +17 -3
  354. package/dist/modules/plugins/discovery/plugin-source.d.ts.map +1 -1
  355. package/dist/modules/plugins/index.d.ts +6 -3
  356. package/dist/modules/plugins/index.d.ts.map +1 -1
  357. package/dist/modules/plugins/index.js +6 -3
  358. package/dist/modules/plugins/index.js.map +1 -1
  359. package/dist/modules/plugins/join-request-jobs.service.d.ts +250 -0
  360. package/dist/modules/plugins/join-request-jobs.service.d.ts.map +1 -0
  361. package/dist/modules/plugins/join-request-jobs.service.js +602 -0
  362. package/dist/modules/plugins/join-request-jobs.service.js.map +1 -0
  363. package/dist/modules/plugins/join-request-records.store.d.ts +161 -0
  364. package/dist/modules/plugins/join-request-records.store.d.ts.map +1 -0
  365. package/dist/modules/plugins/join-request-records.store.js +179 -0
  366. package/dist/modules/plugins/join-request-records.store.js.map +1 -0
  367. package/dist/modules/plugins/join-requests.service.d.ts.map +1 -1
  368. package/dist/modules/plugins/join-requests.service.js +4 -2
  369. package/dist/modules/plugins/join-requests.service.js.map +1 -1
  370. package/dist/modules/plugins/plugin-links.d.ts +4 -1
  371. package/dist/modules/plugins/plugin-links.d.ts.map +1 -1
  372. package/dist/modules/plugins/plugin-links.js +6 -3
  373. package/dist/modules/plugins/plugin-links.js.map +1 -1
  374. package/dist/modules/plugins/plugin-links.service.d.ts +2 -5
  375. package/dist/modules/plugins/plugin-links.service.d.ts.map +1 -1
  376. package/dist/modules/plugins/plugin-links.service.js +4 -13
  377. package/dist/modules/plugins/plugin-links.service.js.map +1 -1
  378. package/dist/modules/plugins/plugin-provision.service.d.ts +91 -19
  379. package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -1
  380. package/dist/modules/plugins/plugin-provision.service.js +183 -38
  381. package/dist/modules/plugins/plugin-provision.service.js.map +1 -1
  382. package/dist/modules/plugins/plugin-rename.service.d.ts +3 -1
  383. package/dist/modules/plugins/plugin-rename.service.d.ts.map +1 -1
  384. package/dist/modules/plugins/plugin-rename.service.js +20 -11
  385. package/dist/modules/plugins/plugin-rename.service.js.map +1 -1
  386. package/dist/modules/plugins/plugins.contract.d.ts +52 -4
  387. package/dist/modules/plugins/plugins.contract.d.ts.map +1 -1
  388. package/dist/modules/plugins/plugins.routes.d.ts +38 -5
  389. package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
  390. package/dist/modules/plugins/plugins.routes.js +190 -116
  391. package/dist/modules/plugins/plugins.routes.js.map +1 -1
  392. package/dist/modules/plugins/plugins.service.d.ts +38 -6
  393. package/dist/modules/plugins/plugins.service.d.ts.map +1 -1
  394. package/dist/modules/plugins/plugins.service.js +86 -17
  395. package/dist/modules/plugins/plugins.service.js.map +1 -1
  396. package/dist/modules/plugins/plugins.tools.d.ts +25 -0
  397. package/dist/modules/plugins/plugins.tools.d.ts.map +1 -0
  398. package/dist/modules/plugins/plugins.tools.js +82 -0
  399. package/dist/modules/plugins/plugins.tools.js.map +1 -0
  400. package/dist/modules/plugins/teams.routes.d.ts +42 -0
  401. package/dist/modules/plugins/teams.routes.d.ts.map +1 -0
  402. package/dist/modules/plugins/teams.routes.js +79 -0
  403. package/dist/modules/plugins/teams.routes.js.map +1 -0
  404. package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts +40 -1
  405. package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts.map +1 -1
  406. package/dist/modules/secrets-vault/db-secrets-vault.service.js +202 -37
  407. package/dist/modules/secrets-vault/db-secrets-vault.service.js.map +1 -1
  408. package/dist/modules/secrets-vault/index.d.ts +1 -1
  409. package/dist/modules/secrets-vault/index.d.ts.map +1 -1
  410. package/dist/modules/secrets-vault/index.js.map +1 -1
  411. package/dist/modules/secrets-vault/mcp-oauth-discovery.service.d.ts.map +1 -1
  412. package/dist/modules/secrets-vault/mcp-oauth-discovery.service.js +5 -3
  413. package/dist/modules/secrets-vault/mcp-oauth-discovery.service.js.map +1 -1
  414. package/dist/modules/secrets-vault/secrets-variable-loader.d.ts.map +1 -1
  415. package/dist/modules/secrets-vault/secrets-variable-loader.js +3 -1
  416. package/dist/modules/secrets-vault/secrets-variable-loader.js.map +1 -1
  417. package/dist/modules/secrets-vault/secrets-vault.contract.d.ts +78 -0
  418. package/dist/modules/secrets-vault/secrets-vault.contract.d.ts.map +1 -1
  419. package/dist/modules/secrets-vault/secrets-vault.contract.js +40 -0
  420. package/dist/modules/secrets-vault/secrets-vault.contract.js.map +1 -1
  421. package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
  422. package/dist/modules/secrets-vault/secrets-vault.routes.js +106 -52
  423. package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
  424. package/dist/modules/settings/connection-check.d.ts +118 -0
  425. package/dist/modules/settings/connection-check.d.ts.map +1 -0
  426. package/dist/modules/settings/connection-check.js +287 -0
  427. package/dist/modules/settings/connection-check.js.map +1 -0
  428. package/dist/modules/settings/deployment-settings.service.d.ts +162 -8
  429. package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
  430. package/dist/modules/settings/deployment-settings.service.js +375 -52
  431. package/dist/modules/settings/deployment-settings.service.js.map +1 -1
  432. package/dist/modules/settings/git-root-folders.d.ts +54 -0
  433. package/dist/modules/settings/git-root-folders.d.ts.map +1 -0
  434. package/dist/modules/settings/git-root-folders.js +130 -0
  435. package/dist/modules/settings/git-root-folders.js.map +1 -0
  436. package/dist/modules/settings/oidc-check.d.ts +85 -0
  437. package/dist/modules/settings/oidc-check.d.ts.map +1 -0
  438. package/dist/modules/settings/oidc-check.js +172 -0
  439. package/dist/modules/settings/oidc-check.js.map +1 -0
  440. package/dist/modules/settings/setup.routes.d.ts +31 -1
  441. package/dist/modules/settings/setup.routes.d.ts.map +1 -1
  442. package/dist/modules/settings/setup.routes.js +400 -108
  443. package/dist/modules/settings/setup.routes.js.map +1 -1
  444. package/dist/modules/skills/allowed-tools-check.d.ts +132 -0
  445. package/dist/modules/skills/allowed-tools-check.d.ts.map +1 -0
  446. package/dist/modules/skills/allowed-tools-check.js +260 -0
  447. package/dist/modules/skills/allowed-tools-check.js.map +1 -0
  448. package/dist/modules/skills/index.d.ts +3 -1
  449. package/dist/modules/skills/index.d.ts.map +1 -1
  450. package/dist/modules/skills/index.js +1 -0
  451. package/dist/modules/skills/index.js.map +1 -1
  452. package/dist/modules/skills/pending-skills.service.d.ts +6 -11
  453. package/dist/modules/skills/pending-skills.service.d.ts.map +1 -1
  454. package/dist/modules/skills/pending-skills.service.js +18 -70
  455. package/dist/modules/skills/pending-skills.service.js.map +1 -1
  456. package/dist/modules/skills/skill-access-requests.routes.d.ts.map +1 -1
  457. package/dist/modules/skills/skill-access-requests.routes.js +9 -6
  458. package/dist/modules/skills/skill-access-requests.routes.js.map +1 -1
  459. package/dist/modules/skills/skills.contract.d.ts +44 -10
  460. package/dist/modules/skills/skills.contract.d.ts.map +1 -1
  461. package/dist/modules/skills/skills.routes.d.ts +8 -1
  462. package/dist/modules/skills/skills.routes.d.ts.map +1 -1
  463. package/dist/modules/skills/skills.routes.js +16 -2
  464. package/dist/modules/skills/skills.routes.js.map +1 -1
  465. package/dist/modules/skills/skills.service.d.ts +31 -7
  466. package/dist/modules/skills/skills.service.d.ts.map +1 -1
  467. package/dist/modules/skills/skills.service.js +191 -81
  468. package/dist/modules/skills/skills.service.js.map +1 -1
  469. package/dist/modules/skills/skills.tools.d.ts +4 -1
  470. package/dist/modules/skills/skills.tools.d.ts.map +1 -1
  471. package/dist/modules/skills/skills.tools.js +41 -6
  472. package/dist/modules/skills/skills.tools.js.map +1 -1
  473. package/dist/modules/tool-auth/connection-key-rejection.d.ts +18 -0
  474. package/dist/modules/tool-auth/connection-key-rejection.d.ts.map +1 -0
  475. package/dist/modules/tool-auth/connection-key-rejection.js +20 -0
  476. package/dist/modules/tool-auth/connection-key-rejection.js.map +1 -0
  477. package/dist/modules/tool-auth/connection-keys-admin.routes.d.ts +14 -0
  478. package/dist/modules/tool-auth/connection-keys-admin.routes.d.ts.map +1 -0
  479. package/dist/modules/tool-auth/connection-keys-admin.routes.js +67 -0
  480. package/dist/modules/tool-auth/connection-keys-admin.routes.js.map +1 -0
  481. package/dist/modules/tool-auth/external-api-key.interface.d.ts +72 -8
  482. package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
  483. package/dist/modules/tool-auth/external-api-key.interface.js.map +1 -1
  484. package/dist/modules/tool-auth/external-api-key.service.d.ts +17 -6
  485. package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
  486. package/dist/modules/tool-auth/external-api-key.service.js +83 -27
  487. package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
  488. package/dist/modules/tool-auth/internal-token.service.d.ts +15 -2
  489. package/dist/modules/tool-auth/internal-token.service.d.ts.map +1 -1
  490. package/dist/modules/tool-auth/internal-token.service.js +6 -2
  491. package/dist/modules/tool-auth/internal-token.service.js.map +1 -1
  492. package/dist/modules/tool-auth/key-or-session.middleware.d.ts +23 -0
  493. package/dist/modules/tool-auth/key-or-session.middleware.d.ts.map +1 -0
  494. package/dist/modules/tool-auth/key-or-session.middleware.js +39 -0
  495. package/dist/modules/tool-auth/key-or-session.middleware.js.map +1 -0
  496. package/dist/modules/tool-auth/tool-auth.middleware.d.ts +15 -1
  497. package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
  498. package/dist/modules/tool-auth/tool-auth.middleware.js +17 -6
  499. package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
  500. package/dist/modules/tool-helpers/tool-context.d.ts +14 -0
  501. package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
  502. package/dist/modules/tool-helpers/tool-context.js +28 -6
  503. package/dist/modules/tool-helpers/tool-context.js.map +1 -1
  504. package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
  505. package/dist/modules/tool-helpers/tool-handler.js +22 -5
  506. package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
  507. package/dist/modules/tool-helpers/tool.contract.d.ts +6 -1
  508. package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
  509. package/dist/modules/tool-helpers/tool.contract.js +7 -1
  510. package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
  511. package/dist/modules/tool-manuals/index.d.ts +2 -1
  512. package/dist/modules/tool-manuals/index.d.ts.map +1 -1
  513. package/dist/modules/tool-manuals/index.js +1 -0
  514. package/dist/modules/tool-manuals/index.js.map +1 -1
  515. package/dist/modules/tool-manuals/mcp-json-discovery.d.ts.map +1 -1
  516. package/dist/modules/tool-manuals/mcp-json-discovery.js +8 -6
  517. package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -1
  518. package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts +10 -2
  519. package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts.map +1 -1
  520. package/dist/modules/tool-manuals/mcp-server-edit.service.js +12 -15
  521. package/dist/modules/tool-manuals/mcp-server-edit.service.js.map +1 -1
  522. package/dist/modules/tool-manuals/pending-tools.service.d.ts +50 -0
  523. package/dist/modules/tool-manuals/pending-tools.service.d.ts.map +1 -0
  524. package/dist/modules/tool-manuals/pending-tools.service.js +149 -0
  525. package/dist/modules/tool-manuals/pending-tools.service.js.map +1 -0
  526. package/dist/modules/tool-manuals/tool-delete.service.d.ts +135 -0
  527. package/dist/modules/tool-manuals/tool-delete.service.d.ts.map +1 -0
  528. package/dist/modules/tool-manuals/tool-delete.service.js +303 -0
  529. package/dist/modules/tool-manuals/tool-delete.service.js.map +1 -0
  530. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +161 -0
  531. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
  532. package/dist/modules/tool-manuals/tool-manuals.contract.js +9 -0
  533. package/dist/modules/tool-manuals/tool-manuals.contract.js.map +1 -1
  534. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +19 -2
  535. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
  536. package/dist/modules/tool-manuals/tool-manuals.routes.js +127 -57
  537. package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
  538. package/dist/modules/tool-manuals/tool-manuals.service.d.ts +119 -5
  539. package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
  540. package/dist/modules/tool-manuals/tool-manuals.service.js +404 -74
  541. package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
  542. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
  543. package/dist/modules/tool-manuals/tool-manuals.tools.js +78 -6
  544. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  545. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
  546. package/dist/modules/workflow/agent-tools/workflow.tools.js +88 -54
  547. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
  548. package/dist/modules/workflow/event-bus.d.ts +10 -4
  549. package/dist/modules/workflow/event-bus.d.ts.map +1 -1
  550. package/dist/modules/workflow/event-bus.js +14 -11
  551. package/dist/modules/workflow/event-bus.js.map +1 -1
  552. package/dist/modules/workflow/events.routes.d.ts +17 -2
  553. package/dist/modules/workflow/events.routes.d.ts.map +1 -1
  554. package/dist/modules/workflow/events.routes.js +31 -12
  555. package/dist/modules/workflow/events.routes.js.map +1 -1
  556. package/dist/modules/workflow/file-lock.service.d.ts +22 -4
  557. package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
  558. package/dist/modules/workflow/file-lock.service.js +27 -4
  559. package/dist/modules/workflow/file-lock.service.js.map +1 -1
  560. package/dist/modules/workflow/git/change-request-link.d.ts +20 -0
  561. package/dist/modules/workflow/git/change-request-link.d.ts.map +1 -0
  562. package/dist/modules/workflow/git/change-request-link.js +34 -0
  563. package/dist/modules/workflow/git/change-request-link.js.map +1 -0
  564. package/dist/modules/workflow/git/git-version.d.ts.map +1 -1
  565. package/dist/modules/workflow/git/git-version.js +3 -1
  566. package/dist/modules/workflow/git/git-version.js.map +1 -1
  567. package/dist/modules/workflow/git/git.service.d.ts +263 -4
  568. package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
  569. package/dist/modules/workflow/git/git.service.js +528 -118
  570. package/dist/modules/workflow/git/git.service.js.map +1 -1
  571. package/dist/modules/workflow/git/node-git-runner.d.ts +36 -0
  572. package/dist/modules/workflow/git/node-git-runner.d.ts.map +1 -0
  573. package/dist/modules/workflow/git/node-git-runner.js +273 -0
  574. package/dist/modules/workflow/git/node-git-runner.js.map +1 -0
  575. package/dist/modules/workflow/git/pull-request.service.d.ts +51 -2
  576. package/dist/modules/workflow/git/pull-request.service.d.ts.map +1 -1
  577. package/dist/modules/workflow/git/pull-request.service.js +157 -25
  578. package/dist/modules/workflow/git/pull-request.service.js.map +1 -1
  579. package/dist/modules/workflow/pending-commits.service.d.ts +11 -0
  580. package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
  581. package/dist/modules/workflow/pending-commits.service.js +24 -4
  582. package/dist/modules/workflow/pending-commits.service.js.map +1 -1
  583. package/dist/modules/workflow/pending-commits.worker.d.ts +15 -8
  584. package/dist/modules/workflow/pending-commits.worker.d.ts.map +1 -1
  585. package/dist/modules/workflow/pending-commits.worker.js +107 -58
  586. package/dist/modules/workflow/pending-commits.worker.js.map +1 -1
  587. package/dist/modules/workflow/recovery-bot.d.ts.map +1 -1
  588. package/dist/modules/workflow/recovery-bot.js +3 -1
  589. package/dist/modules/workflow/recovery-bot.js.map +1 -1
  590. package/dist/modules/workflow/review-workflow/approval-lock.d.ts +60 -0
  591. package/dist/modules/workflow/review-workflow/approval-lock.d.ts.map +1 -0
  592. package/dist/modules/workflow/review-workflow/approval-lock.js +116 -0
  593. package/dist/modules/workflow/review-workflow/approval-lock.js.map +1 -0
  594. package/dist/modules/workflow/review-workflow/review-workflow.interface.d.ts +54 -7
  595. package/dist/modules/workflow/review-workflow/review-workflow.interface.d.ts.map +1 -1
  596. package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts +64 -0
  597. package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
  598. package/dist/modules/workflow/review-workflow/review-workflow.service.js +269 -90
  599. package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
  600. package/dist/modules/workflow/sanitize-error.d.ts +5 -2
  601. package/dist/modules/workflow/sanitize-error.d.ts.map +1 -1
  602. package/dist/modules/workflow/sanitize-error.js +8 -4
  603. package/dist/modules/workflow/sanitize-error.js.map +1 -1
  604. package/dist/modules/workflow/workflow.routes.d.ts +2 -0
  605. package/dist/modules/workflow/workflow.routes.d.ts.map +1 -1
  606. package/dist/modules/workflow/workflow.routes.js +259 -29
  607. package/dist/modules/workflow/workflow.routes.js.map +1 -1
  608. package/dist/modules/workflow/workflow.service.d.ts +166 -14
  609. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  610. package/dist/modules/workflow/workflow.service.js +906 -106
  611. package/dist/modules/workflow/workflow.service.js.map +1 -1
  612. package/dist/modules/workspace/empty-dirs.d.ts +15 -0
  613. package/dist/modules/workspace/empty-dirs.d.ts.map +1 -0
  614. package/dist/modules/workspace/empty-dirs.js +90 -0
  615. package/dist/modules/workspace/empty-dirs.js.map +1 -0
  616. package/dist/modules/workspace/file-readers/content-mode.d.ts +50 -0
  617. package/dist/modules/workspace/file-readers/content-mode.d.ts.map +1 -0
  618. package/dist/modules/workspace/file-readers/content-mode.js +55 -0
  619. package/dist/modules/workspace/file-readers/content-mode.js.map +1 -0
  620. package/dist/modules/workspace/file-readers/doc-extract.service.d.ts +4 -1
  621. package/dist/modules/workspace/file-readers/doc-extract.service.d.ts.map +1 -1
  622. package/dist/modules/workspace/file-readers/doc-extract.service.js +4 -1
  623. package/dist/modules/workspace/file-readers/doc-extract.service.js.map +1 -1
  624. package/dist/modules/workspace/file-readers/doc-extract.types.d.ts +5 -2
  625. package/dist/modules/workspace/file-readers/doc-extract.types.d.ts.map +1 -1
  626. package/dist/modules/workspace/file-readers/doc-extract.types.js +7 -4
  627. package/dist/modules/workspace/file-readers/doc-extract.types.js.map +1 -1
  628. package/dist/modules/workspace/file-readers/document-reader.d.ts +2 -0
  629. package/dist/modules/workspace/file-readers/document-reader.d.ts.map +1 -1
  630. package/dist/modules/workspace/file-readers/document-reader.js +5 -0
  631. package/dist/modules/workspace/file-readers/document-reader.js.map +1 -1
  632. package/dist/modules/workspace/file-readers/extract-docx.d.ts +6 -0
  633. package/dist/modules/workspace/file-readers/extract-docx.d.ts.map +1 -1
  634. package/dist/modules/workspace/file-readers/extract-docx.js +6 -0
  635. package/dist/modules/workspace/file-readers/extract-docx.js.map +1 -1
  636. package/dist/modules/workspace/file-readers/file-reader.d.ts +24 -5
  637. package/dist/modules/workspace/file-readers/file-reader.d.ts.map +1 -1
  638. package/dist/modules/workspace/file-readers/file-reader.js +11 -6
  639. package/dist/modules/workspace/file-readers/file-reader.js.map +1 -1
  640. package/dist/modules/workspace/file-readers/file-reader.registry.d.ts.map +1 -1
  641. package/dist/modules/workspace/file-readers/file-reader.registry.js +6 -1
  642. package/dist/modules/workspace/file-readers/file-reader.registry.js.map +1 -1
  643. package/dist/modules/workspace/file-readers/image-reader.d.ts +7 -5
  644. package/dist/modules/workspace/file-readers/image-reader.d.ts.map +1 -1
  645. package/dist/modules/workspace/file-readers/image-reader.js +9 -5
  646. package/dist/modules/workspace/file-readers/image-reader.js.map +1 -1
  647. package/dist/modules/workspace/file-readers/ooxml-text.d.ts +3 -0
  648. package/dist/modules/workspace/file-readers/ooxml-text.d.ts.map +1 -1
  649. package/dist/modules/workspace/file-readers/ooxml-text.js +39 -2
  650. package/dist/modules/workspace/file-readers/ooxml-text.js.map +1 -1
  651. package/dist/modules/workspace/file-readers/text-reader.d.ts +29 -1
  652. package/dist/modules/workspace/file-readers/text-reader.d.ts.map +1 -1
  653. package/dist/modules/workspace/file-readers/text-reader.js +57 -3
  654. package/dist/modules/workspace/file-readers/text-reader.js.map +1 -1
  655. package/dist/modules/workspace/git-internals.middleware.d.ts +28 -0
  656. package/dist/modules/workspace/git-internals.middleware.d.ts.map +1 -0
  657. package/dist/modules/workspace/git-internals.middleware.js +93 -0
  658. package/dist/modules/workspace/git-internals.middleware.js.map +1 -0
  659. package/dist/modules/workspace/not-found.d.ts +80 -0
  660. package/dist/modules/workspace/not-found.d.ts.map +1 -0
  661. package/dist/modules/workspace/not-found.js +112 -0
  662. package/dist/modules/workspace/not-found.js.map +1 -0
  663. package/dist/modules/workspace/session-ontology.gate.d.ts +1 -1
  664. package/dist/modules/workspace/session-ontology.gate.d.ts.map +1 -1
  665. package/dist/modules/workspace/session-ontology.gate.js +1 -1
  666. package/dist/modules/workspace/session-ontology.gate.js.map +1 -1
  667. package/dist/modules/workspace/startup/beside-checkout.d.ts +42 -0
  668. package/dist/modules/workspace/startup/beside-checkout.d.ts.map +1 -0
  669. package/dist/modules/workspace/startup/beside-checkout.js +110 -0
  670. package/dist/modules/workspace/startup/beside-checkout.js.map +1 -0
  671. package/dist/modules/workspace/startup/kb-git.d.ts +7 -12
  672. package/dist/modules/workspace/startup/kb-git.d.ts.map +1 -1
  673. package/dist/modules/workspace/startup/kb-git.js +34 -35
  674. package/dist/modules/workspace/startup/kb-git.js.map +1 -1
  675. package/dist/modules/workspace/startup/kb-startup-runner.d.ts +95 -0
  676. package/dist/modules/workspace/startup/kb-startup-runner.d.ts.map +1 -1
  677. package/dist/modules/workspace/startup/kb-startup-runner.js +257 -55
  678. package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -1
  679. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.d.ts +45 -0
  680. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.d.ts.map +1 -1
  681. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js +417 -385
  682. package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js.map +1 -1
  683. package/dist/modules/workspace/startup/steps/personal-spaces.step.d.ts +48 -0
  684. package/dist/modules/workspace/startup/steps/personal-spaces.step.d.ts.map +1 -0
  685. package/dist/modules/workspace/startup/steps/personal-spaces.step.js +183 -0
  686. package/dist/modules/workspace/startup/steps/personal-spaces.step.js.map +1 -0
  687. package/dist/modules/workspace/startup/steps/plugin-display-names.step.d.ts +51 -0
  688. package/dist/modules/workspace/startup/steps/plugin-display-names.step.d.ts.map +1 -0
  689. package/dist/modules/workspace/startup/steps/plugin-display-names.step.js +158 -0
  690. package/dist/modules/workspace/startup/steps/plugin-display-names.step.js.map +1 -0
  691. package/dist/modules/workspace/startup/steps/plugin-layout.d.ts +64 -0
  692. package/dist/modules/workspace/startup/steps/plugin-layout.d.ts.map +1 -0
  693. package/dist/modules/workspace/startup/steps/plugin-layout.js +97 -0
  694. package/dist/modules/workspace/startup/steps/plugin-layout.js.map +1 -0
  695. package/dist/modules/workspace/startup/steps/plugin-manifests.step.d.ts +7 -1
  696. package/dist/modules/workspace/startup/steps/plugin-manifests.step.d.ts.map +1 -1
  697. package/dist/modules/workspace/startup/steps/plugin-manifests.step.js +45 -81
  698. package/dist/modules/workspace/startup/steps/plugin-manifests.step.js.map +1 -1
  699. package/dist/modules/workspace/startup/steps/roles-yaml.step.d.ts +3 -1
  700. package/dist/modules/workspace/startup/steps/roles-yaml.step.d.ts.map +1 -1
  701. package/dist/modules/workspace/startup/steps/roles-yaml.step.js +4 -12
  702. package/dist/modules/workspace/startup/steps/roles-yaml.step.js.map +1 -1
  703. package/dist/modules/workspace/startup/steps/seed-tree.d.ts +6 -1
  704. package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
  705. package/dist/modules/workspace/startup/steps/seed-tree.js +154 -78
  706. package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
  707. package/dist/modules/workspace/startup/steps/template-files.step.d.ts +96 -24
  708. package/dist/modules/workspace/startup/steps/template-files.step.d.ts.map +1 -1
  709. package/dist/modules/workspace/startup/steps/template-files.step.js +446 -138
  710. package/dist/modules/workspace/startup/steps/template-files.step.js.map +1 -1
  711. package/dist/modules/workspace/startup/steps/template-source.d.ts +94 -0
  712. package/dist/modules/workspace/startup/steps/template-source.d.ts.map +1 -0
  713. package/dist/modules/workspace/startup/steps/template-source.js +160 -0
  714. package/dist/modules/workspace/startup/steps/template-source.js.map +1 -0
  715. package/dist/modules/workspace/workspace.routes.d.ts +14 -2
  716. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
  717. package/dist/modules/workspace/workspace.routes.js +649 -238
  718. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  719. package/dist/modules/workspace/workspace.service.d.ts +298 -12
  720. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  721. package/dist/modules/workspace/workspace.service.js +933 -269
  722. package/dist/modules/workspace/workspace.service.js.map +1 -1
  723. package/dist/modules/workspace/workspace.tools.d.ts +42 -1
  724. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  725. package/dist/modules/workspace/workspace.tools.js +1626 -146
  726. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  727. package/dist/modules/workspace/write-denial.d.ts +76 -0
  728. package/dist/modules/workspace/write-denial.d.ts.map +1 -0
  729. package/dist/modules/workspace/write-denial.js +138 -0
  730. package/dist/modules/workspace/write-denial.js.map +1 -0
  731. package/dist/shared/canonical-file-identity.d.ts +64 -0
  732. package/dist/shared/canonical-file-identity.d.ts.map +1 -0
  733. package/dist/shared/canonical-file-identity.js +92 -0
  734. package/dist/shared/canonical-file-identity.js.map +1 -0
  735. package/dist/shared/domain-errors.d.ts +108 -0
  736. package/dist/shared/domain-errors.d.ts.map +1 -1
  737. package/dist/shared/domain-errors.js +137 -0
  738. package/dist/shared/domain-errors.js.map +1 -1
  739. package/dist/shared/email-identity.d.ts +32 -0
  740. package/dist/shared/email-identity.d.ts.map +1 -0
  741. package/dist/shared/email-identity.js +37 -0
  742. package/dist/shared/email-identity.js.map +1 -0
  743. package/dist/shared/fs.contract.d.ts +206 -0
  744. package/dist/shared/fs.contract.d.ts.map +1 -0
  745. package/dist/shared/fs.contract.js +79 -0
  746. package/dist/shared/fs.contract.js.map +1 -0
  747. package/dist/shared/git-failure.d.ts +80 -0
  748. package/dist/shared/git-failure.d.ts.map +1 -0
  749. package/dist/shared/git-failure.js +139 -0
  750. package/dist/shared/git-failure.js.map +1 -0
  751. package/dist/shared/git-internals.d.ts +23 -0
  752. package/dist/shared/git-internals.d.ts.map +1 -0
  753. package/dist/shared/git-internals.js +181 -0
  754. package/dist/shared/git-internals.js.map +1 -0
  755. package/dist/shared/git.contract.d.ts +157 -0
  756. package/dist/shared/git.contract.d.ts.map +1 -0
  757. package/dist/shared/git.contract.js +103 -0
  758. package/dist/shared/git.contract.js.map +1 -0
  759. package/dist/shared/http-errors.d.ts +33 -0
  760. package/dist/shared/http-errors.d.ts.map +1 -0
  761. package/dist/shared/http-errors.js +35 -0
  762. package/dist/shared/http-errors.js.map +1 -0
  763. package/dist/shared/logger.contract.d.ts +63 -0
  764. package/dist/shared/logger.contract.d.ts.map +1 -0
  765. package/dist/shared/logger.contract.js +126 -0
  766. package/dist/shared/logger.contract.js.map +1 -0
  767. package/dist/shared/logging.d.ts +31 -0
  768. package/dist/shared/logging.d.ts.map +1 -0
  769. package/dist/shared/logging.js +92 -0
  770. package/dist/shared/logging.js.map +1 -0
  771. package/dist/shared/path-containment.d.ts +21 -0
  772. package/dist/shared/path-containment.d.ts.map +1 -0
  773. package/dist/shared/path-containment.js +34 -0
  774. package/dist/shared/path-containment.js.map +1 -0
  775. package/dist/shared/pending-proposals.d.ts +71 -0
  776. package/dist/shared/pending-proposals.d.ts.map +1 -0
  777. package/dist/shared/pending-proposals.js +98 -0
  778. package/dist/shared/pending-proposals.js.map +1 -0
  779. package/dist/shared/printable.d.ts +22 -0
  780. package/dist/shared/printable.d.ts.map +1 -0
  781. package/dist/shared/printable.js +65 -0
  782. package/dist/shared/printable.js.map +1 -0
  783. package/dist/shared/redact-secret.d.ts +30 -0
  784. package/dist/shared/redact-secret.d.ts.map +1 -0
  785. package/dist/shared/redact-secret.js +114 -0
  786. package/dist/shared/redact-secret.js.map +1 -0
  787. package/dist/shared/rename-no-replace.d.ts +119 -0
  788. package/dist/shared/rename-no-replace.d.ts.map +1 -0
  789. package/dist/shared/rename-no-replace.js +323 -0
  790. package/dist/shared/rename-no-replace.js.map +1 -0
  791. package/dist/shared/ssrf.d.ts.map +1 -1
  792. package/dist/shared/ssrf.js +49 -18
  793. package/dist/shared/ssrf.js.map +1 -1
  794. package/dist/version.d.ts +10 -6
  795. package/dist/version.d.ts.map +1 -1
  796. package/dist/version.js +10 -6
  797. package/dist/version.js.map +1 -1
  798. package/kb-template/.bevelignore +4 -3
  799. package/kb-template/AGENTS.md +281 -36
  800. package/kb-template/KnowledgeBase/How to get started.md +1 -1
  801. package/kb-template/access.md +5 -3
  802. package/kb-template/mcp-description.md +27 -0
  803. package/migrations/0006_api_tokens_revoked_by.sql +1 -0
  804. package/migrations/0007_github_facade_registered.sql +1 -0
  805. package/migrations/0008_change_request_apply_failure.sql +4 -0
  806. package/migrations/0009_change_request_apply_failure_kind.sql +13 -0
  807. package/migrations/0010_plugin_join_requests.sql +15 -0
  808. package/migrations/0011_plugin_join_request_claim.sql +11 -0
  809. package/migrations/0012_agent_audit.sql +41 -0
  810. package/migrations/0013_agent_identity_and_key_soft_delete.sql +29 -0
  811. package/migrations/meta/0006_snapshot.json +1723 -0
  812. package/migrations/meta/0007_snapshot.json +1729 -0
  813. package/migrations/meta/0008_snapshot.json +1753 -0
  814. package/migrations/meta/0009_snapshot.json +1759 -0
  815. package/migrations/meta/0010_snapshot.json +1872 -0
  816. package/migrations/meta/0011_snapshot.json +1884 -0
  817. package/migrations/meta/0012_snapshot.json +2247 -0
  818. package/migrations/meta/0013_snapshot.json +2259 -0
  819. package/migrations/meta/_journal.json +56 -0
  820. package/package.json +7 -6
  821. package/src/__tests__/core-config.domain.test.ts +67 -2
  822. package/src/__tests__/kb-layout-config.test.ts +186 -1
  823. package/src/__tests__/open-change-gate.ts +15 -0
  824. package/src/core/__tests__/catalog-cache-invalidation.test.ts +83 -3
  825. package/src/core/__tests__/catalog-revision.test.ts +190 -0
  826. package/src/core/__tests__/lifecycle.test.ts +369 -0
  827. package/src/core/__tests__/public-config.test.ts +30 -0
  828. package/src/core/__tests__/readiness.test.ts +109 -0
  829. package/src/core/catalog-cache-invalidation.ts +20 -1
  830. package/src/core/catalog-revision.ts +110 -0
  831. package/src/core/create-core-server.ts +286 -79
  832. package/src/core/create-core-services.ts +315 -81
  833. package/src/core/lifecycle.ts +312 -0
  834. package/src/core/public-config.ts +86 -0
  835. package/src/core/readiness.ts +123 -0
  836. package/src/core-config.ts +70 -4
  837. package/src/index.ts +15 -0
  838. package/src/modules/access/__tests__/access-control.admin-root-floor.test.ts +362 -0
  839. package/src/modules/access/__tests__/access-control.atref-batch.test.ts +2 -1
  840. package/src/modules/access/__tests__/access-control.atref-cache.test.ts +46 -22
  841. package/src/modules/access/__tests__/access-control.deny-dependents.test.ts +215 -0
  842. package/src/modules/access/__tests__/access-control.download-read.test.ts +268 -0
  843. package/src/modules/access/__tests__/access-control.everyone-read.test.ts +145 -0
  844. package/src/modules/access/__tests__/access-control.group-read.test.ts +163 -0
  845. package/src/modules/access/__tests__/access-control.invalidate-race.test.ts +2 -0
  846. package/src/modules/access/__tests__/access-control.platform-restore.test.ts +152 -0
  847. package/src/modules/access/__tests__/access-control.prospective.test.ts +189 -0
  848. package/src/modules/access/__tests__/access-control.read-batch.test.ts +2 -1
  849. package/src/modules/access/__tests__/access-control.service.test.ts +123 -78
  850. package/src/modules/access/__tests__/access-groups.test.ts +47 -14
  851. package/src/modules/access/__tests__/access-md-format.test.ts +2 -1
  852. package/src/modules/access/__tests__/access-mutation.service.test.ts +52 -4
  853. package/src/modules/access/__tests__/access-own-read-grant.test.ts +2 -0
  854. package/src/modules/access/__tests__/access-personal-plugin.test.ts +153 -0
  855. package/src/modules/access/__tests__/access-plugin-principals.test.ts +18 -1
  856. package/src/modules/access/__tests__/access-restrict-here.test.ts +450 -0
  857. package/src/modules/access/__tests__/access.routes.batch.test.ts +139 -0
  858. package/src/modules/access/__tests__/access.routes.folder-governs.test.ts +394 -0
  859. package/src/modules/access/__tests__/access.routes.group-grant.test.ts +6 -2
  860. package/src/modules/access/__tests__/access.routes.overrides.test.ts +3 -2
  861. package/src/modules/access/__tests__/access.routes.proposal-branch.test.ts +234 -0
  862. package/src/modules/access/__tests__/access.routes.prospective.test.ts +216 -0
  863. package/src/modules/access/__tests__/access.routes.revoke.test.ts +7 -2
  864. package/src/modules/access/__tests__/access.routes.unknown-email.test.ts +360 -0
  865. package/src/modules/access/__tests__/change-read-gate.test.ts +272 -0
  866. package/src/modules/access/__tests__/creator-access.test.ts +81 -83
  867. package/src/modules/access/__tests__/grant-sources.test.ts +3 -2
  868. package/src/modules/access/__tests__/groups-admin.service.test.ts +6 -5
  869. package/src/modules/access/__tests__/roles-admin.security.test.ts +196 -0
  870. package/src/modules/access/__tests__/roles-admin.service.test.ts +127 -2
  871. package/src/modules/access/__tests__/roles-capabilities.test.ts +5 -4
  872. package/src/modules/access/__tests__/roles.routes.test.ts +5 -2
  873. package/src/modules/access/__tests__/user-access-removal.service.test.ts +402 -0
  874. package/src/modules/access/__tests__/users-db-double.ts +63 -0
  875. package/src/modules/access/access-control.interface.ts +251 -11
  876. package/src/modules/access/access-control.service.ts +719 -164
  877. package/src/modules/access/access-mutation.service.ts +182 -13
  878. package/src/modules/access/access-view.ts +316 -0
  879. package/src/modules/access/access.routes.ts +293 -95
  880. package/src/modules/access/admin-locked-commit.ts +12 -14
  881. package/src/modules/access/admin-route-helpers.ts +4 -4
  882. package/src/modules/access/change-read-gate.ts +148 -0
  883. package/src/modules/access/creator-access.ts +89 -124
  884. package/src/modules/access/directory-sync-bot.ts +4 -1
  885. package/src/modules/access/groups-admin.service.ts +3 -3
  886. package/src/modules/access/reference-scan.ts +5 -5
  887. package/src/modules/access/roles-admin.service.ts +94 -15
  888. package/src/modules/access/synced-groups-committer.ts +10 -15
  889. package/src/modules/access/synced-groups-writer.ts +3 -12
  890. package/src/modules/access/user-access-removal.service.ts +711 -0
  891. package/src/modules/access-model/__tests__/access-splice.test.ts +63 -1
  892. package/src/modules/access-model/__tests__/frontmatter-fences.test.ts +71 -0
  893. package/src/modules/access-model/__tests__/roles-yaml-guard.test.ts +131 -0
  894. package/src/modules/access-model/access-errors.ts +56 -15
  895. package/src/modules/access-model/access-grammar.ts +85 -48
  896. package/src/modules/access-model/access-splice.ts +18 -18
  897. package/src/modules/access-model/access-template.ts +44 -0
  898. package/src/modules/access-model/change-gate.ts +80 -0
  899. package/src/modules/access-model/creator.ts +18 -21
  900. package/src/modules/access-model/frontmatter-lines.ts +98 -0
  901. package/src/modules/access-model/plugin-principals.ts +12 -19
  902. package/src/modules/access-model/roles-yaml-guard.ts +182 -4
  903. package/src/modules/admin/admin-access.service.ts +6 -2
  904. package/src/modules/agent-instructions/__tests__/agent-instructions.route.test.ts +100 -0
  905. package/src/modules/agent-instructions/__tests__/compose.test.ts +256 -0
  906. package/src/modules/agent-instructions/__tests__/read-preamble.test.ts +135 -0
  907. package/src/modules/agent-instructions/agent-instructions.routes.ts +41 -0
  908. package/src/modules/agent-instructions/compose.ts +271 -0
  909. package/src/modules/agent-instructions/index.ts +14 -0
  910. package/src/modules/agent-instructions/read-preamble.ts +88 -0
  911. package/src/modules/audit/__tests__/agent-audit.service.test.ts +370 -0
  912. package/src/modules/audit/__tests__/agent-rest-audit.middleware.test.ts +113 -0
  913. package/src/modules/audit/__tests__/audit.routes.test.ts +201 -0
  914. package/src/modules/audit/__tests__/event-classifier.test.ts +121 -0
  915. package/src/modules/audit/agent-audit.service.ts +316 -0
  916. package/src/modules/audit/agent-rest-audit.middleware.ts +83 -0
  917. package/src/modules/audit/audit.contract.ts +164 -0
  918. package/src/modules/audit/audit.routes.ts +168 -0
  919. package/src/modules/audit/event-classifier.ts +155 -0
  920. package/src/modules/audit/index.ts +14 -0
  921. package/src/modules/audit/request-audit.ts +141 -0
  922. package/src/modules/auth/__tests__/account-erasure.approval-gate.test.ts +209 -0
  923. package/src/modules/auth/__tests__/account.routes.test.ts +178 -2
  924. package/src/modules/auth/__tests__/auth.middleware.test.ts +78 -0
  925. package/src/modules/auth/__tests__/auth.service.test.ts +338 -5
  926. package/src/modules/auth/__tests__/oidc-auth-provider.test.ts +352 -12
  927. package/src/modules/auth/__tests__/onboarding-done.route.test.ts +3 -1
  928. package/src/modules/auth/account-erasure.service.ts +117 -10
  929. package/src/modules/auth/account.routes.ts +115 -14
  930. package/src/modules/auth/auth.routes.ts +20 -7
  931. package/src/modules/auth/auth.service.ts +187 -28
  932. package/src/modules/auth/oidc-auth-provider.ts +120 -22
  933. package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +109 -1
  934. package/src/modules/code-mode/code-mode.tool.ts +13 -2
  935. package/src/modules/connection-probe/__tests__/connection-probe.service.test.ts +78 -0
  936. package/src/modules/connection-probe/connection-probe.service.ts +59 -7
  937. package/src/modules/database/__tests__/advisory-lease.test.ts +152 -0
  938. package/src/modules/database/__tests__/advisory-lock.test.ts +147 -0
  939. package/src/modules/database/advisory-lock.ts +346 -0
  940. package/src/modules/database/connection.ts +9 -1
  941. package/src/modules/database/core-schema.ts +227 -0
  942. package/src/modules/database/migrate.ts +27 -6
  943. package/src/modules/declared-variables/declared-variables.routes.ts +6 -3
  944. package/src/modules/diff/__tests__/diff.service.seed-atomicity.test.ts +3 -0
  945. package/src/modules/diff/__tests__/diff.service.test.ts +3 -0
  946. package/src/modules/diff/diff.routes.ts +2 -4
  947. package/src/modules/diff/diff.service.ts +80 -79
  948. package/src/modules/kb-fs/__tests__/locking-filesystem.test.ts +472 -9
  949. package/src/modules/kb-fs/__tests__/node-fs.test.ts +409 -0
  950. package/src/modules/kb-fs/__tests__/repo-path.test.ts +210 -1
  951. package/src/modules/{workspace → kb-fs}/bevel-ignore.ts +22 -15
  952. package/src/modules/kb-fs/file-change-notifier.ts +4 -1
  953. package/src/modules/kb-fs/git-guarded-filesystem.ts +136 -0
  954. package/src/modules/kb-fs/locking-filesystem.ts +399 -109
  955. package/src/modules/kb-fs/node-fs.ts +165 -0
  956. package/src/modules/kb-fs/read-only-filesystem.ts +18 -9
  957. package/src/modules/kb-fs/repo-path.ts +213 -3
  958. package/src/modules/kb-sync/kb-sync.routes.ts +5 -2
  959. package/src/modules/kb-sync/kb-sync.service.ts +8 -5
  960. package/src/modules/marketplace/__tests__/github-facade.test.ts +134 -2
  961. package/src/modules/marketplace/__tests__/marketplace-git.test.ts +2 -1
  962. package/src/modules/marketplace/__tests__/marketplace-repo.head.test.ts +47 -0
  963. package/src/modules/marketplace/git-http.routes.ts +7 -4
  964. package/src/modules/marketplace/github-facade/github-facade-admin.routes.ts +35 -3
  965. package/src/modules/marketplace/github-facade/github-facade-credentials.service.ts +55 -0
  966. package/src/modules/marketplace/github-facade/github-facade.routes.ts +32 -6
  967. package/src/modules/marketplace/github-facade/github-facade.service.ts +80 -19
  968. package/src/modules/marketplace/github-facade/index.ts +1 -0
  969. package/src/modules/marketplace/marketplace-repo.service.ts +23 -8
  970. package/src/modules/mcp/__tests__/bevel-oauth-provider.test.ts +183 -13
  971. package/src/modules/mcp/__tests__/downstream-pool.test.ts +278 -0
  972. package/src/modules/mcp/__tests__/downstream-token-refresh.test.ts +178 -0
  973. package/src/modules/mcp/__tests__/fake-downstream-mcp-server.ts +156 -0
  974. package/src/modules/mcp/__tests__/first-call-probe.cli-args.test.ts +178 -0
  975. package/src/modules/mcp/__tests__/first-call-probe.test.ts +414 -0
  976. package/src/modules/mcp/__tests__/manual-failure-memo.test.ts +98 -6
  977. package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +68 -7
  978. package/src/modules/mcp/__tests__/mcp-routes-harness.ts +1 -3
  979. package/src/modules/mcp/__tests__/mcp.e2e.test.ts +1055 -59
  980. package/src/modules/mcp/__tests__/mcp.routes.local-token.test.ts +24 -4
  981. package/src/modules/mcp/__tests__/mcp.routes.stateless.test.ts +182 -0
  982. package/src/modules/mcp/__tests__/mcp.service.test.ts +391 -6
  983. package/src/modules/mcp/__tests__/surface-log-throttle.test.ts +169 -0
  984. package/src/modules/mcp/downstream-pool.ts +256 -0
  985. package/src/modules/mcp/downstream-token-refresh.ts +170 -0
  986. package/src/modules/mcp/first-call-probe.cli-args.ts +159 -0
  987. package/src/modules/mcp/first-call-probe.cli.ts +60 -0
  988. package/src/modules/mcp/first-call-probe.ts +418 -0
  989. package/src/modules/mcp/manual-failure-memo.ts +81 -23
  990. package/src/modules/mcp/mcp-auth.middleware.ts +56 -16
  991. package/src/modules/mcp/mcp.routes.ts +386 -482
  992. package/src/modules/mcp/mcp.service.ts +905 -177
  993. package/src/modules/mcp/oauth/bevel-oauth-provider.ts +189 -8
  994. package/src/modules/mcp/oauth/oauth-consent.routes.ts +4 -4
  995. package/src/modules/mcp/surface-log-throttle.ts +120 -0
  996. package/src/modules/plugins/__tests__/bundle-dialect.e2e.test.ts +10 -7
  997. package/src/modules/plugins/__tests__/bundle-dialect.test.ts +165 -0
  998. package/src/modules/plugins/__tests__/compile-marketplace.test.ts +139 -10
  999. package/src/modules/plugins/__tests__/fake-join-request-store.ts +189 -0
  1000. package/src/modules/plugins/__tests__/join-request-jobs.service.test.ts +608 -0
  1001. package/src/modules/plugins/__tests__/plugin-creation-display-name.test.ts +241 -0
  1002. package/src/modules/plugins/__tests__/plugin-index.service.test.ts +148 -7
  1003. package/src/modules/plugins/__tests__/plugin-links.service.test.ts +10 -13
  1004. package/src/modules/plugins/__tests__/plugin-names.test.ts +77 -0
  1005. package/src/modules/plugins/__tests__/plugin-provision.service.test.ts +129 -19
  1006. package/src/modules/plugins/__tests__/plugin-rename.service.test.ts +91 -29
  1007. package/src/modules/plugins/__tests__/plugins.routes.test.ts +370 -19
  1008. package/src/modules/plugins/__tests__/plugins.tools.test.ts +131 -0
  1009. package/src/modules/plugins/__tests__/teams.routes.test.ts +248 -0
  1010. package/src/modules/plugins/compile/compile-marketplace.ts +143 -46
  1011. package/src/modules/plugins/compile/marketplace-compiler.service.ts +8 -7
  1012. package/src/modules/plugins/discovery/__tests__/kb-plugin-source.test.ts +56 -20
  1013. package/src/modules/plugins/discovery/bundle-dialect/bundle.source.ts +30 -9
  1014. package/src/modules/plugins/discovery/bundle-dialect/registry.ts +93 -18
  1015. package/src/modules/plugins/discovery/kb-plugin-source.ts +10 -7
  1016. package/src/modules/plugins/discovery/native.source.ts +21 -3
  1017. package/src/modules/plugins/discovery/plugin-source.ts +17 -3
  1018. package/src/modules/plugins/index.ts +16 -3
  1019. package/src/modules/plugins/join-request-jobs.service.ts +675 -0
  1020. package/src/modules/plugins/join-request-records.store.ts +353 -0
  1021. package/src/modules/plugins/join-requests.service.ts +7 -4
  1022. package/src/modules/plugins/plugin-links.service.ts +4 -17
  1023. package/src/modules/plugins/plugin-links.ts +6 -3
  1024. package/src/modules/plugins/plugin-provision.service.ts +209 -37
  1025. package/src/modules/plugins/plugin-rename.service.ts +21 -9
  1026. package/src/modules/plugins/plugins.contract.ts +52 -4
  1027. package/src/modules/plugins/plugins.routes.ts +204 -124
  1028. package/src/modules/plugins/plugins.service.ts +92 -18
  1029. package/src/modules/plugins/plugins.tools.ts +88 -0
  1030. package/src/modules/plugins/teams.routes.ts +129 -0
  1031. package/src/modules/secrets-vault/__tests__/connect-pending.route.test.ts +359 -15
  1032. package/src/modules/secrets-vault/__tests__/db-secrets-vault.oauth.test.ts +248 -0
  1033. package/src/modules/secrets-vault/__tests__/namespace-secrets.test.ts +202 -0
  1034. package/src/modules/secrets-vault/db-secrets-vault.service.ts +217 -35
  1035. package/src/modules/secrets-vault/index.ts +1 -0
  1036. package/src/modules/secrets-vault/mcp-oauth-discovery.service.ts +6 -3
  1037. package/src/modules/secrets-vault/secrets-variable-loader.ts +4 -4
  1038. package/src/modules/secrets-vault/secrets-vault.contract.ts +93 -0
  1039. package/src/modules/secrets-vault/secrets-vault.routes.ts +99 -38
  1040. package/src/modules/settings/__tests__/connection-check.test.ts +233 -0
  1041. package/src/modules/settings/__tests__/deployment-settings.service.test.ts +223 -6
  1042. package/src/modules/settings/__tests__/git-root-folders.test.ts +247 -0
  1043. package/src/modules/settings/__tests__/oidc-check.test.ts +185 -0
  1044. package/src/modules/settings/__tests__/setup.routes.boot-failure.test.ts +119 -0
  1045. package/src/modules/settings/__tests__/setup.routes.layout-phase.test.ts +170 -0
  1046. package/src/modules/settings/__tests__/setup.routes.oidc.test.ts +555 -0
  1047. package/src/modules/settings/__tests__/setup.routes.test.ts +667 -16
  1048. package/src/modules/settings/connection-check.ts +351 -0
  1049. package/src/modules/settings/deployment-settings.service.ts +444 -59
  1050. package/src/modules/settings/git-root-folders.ts +171 -0
  1051. package/src/modules/settings/oidc-check.ts +229 -0
  1052. package/src/modules/settings/setup.routes.ts +456 -110
  1053. package/src/modules/skills/__tests__/allowed-tools-check.test.ts +224 -0
  1054. package/src/modules/skills/__tests__/allowed-tools-warn.tools.test.ts +210 -0
  1055. package/src/modules/skills/__tests__/pending-skills.service.test.ts +3 -3
  1056. package/src/modules/skills/__tests__/skills.routes.warnings.test.ts +91 -0
  1057. package/src/modules/skills/__tests__/skills.service.test.ts +223 -2
  1058. package/src/modules/skills/allowed-tools-check.ts +352 -0
  1059. package/src/modules/skills/index.ts +4 -0
  1060. package/src/modules/skills/pending-skills.service.ts +21 -82
  1061. package/src/modules/skills/skill-access-requests.routes.ts +10 -6
  1062. package/src/modules/skills/skills.contract.ts +43 -11
  1063. package/src/modules/skills/skills.routes.ts +16 -1
  1064. package/src/modules/skills/skills.service.ts +214 -84
  1065. package/src/modules/skills/skills.tools.ts +43 -5
  1066. package/src/modules/tool-auth/__tests__/connection-key-rejection.test.ts +192 -0
  1067. package/src/modules/tool-auth/__tests__/connection-keys-admin.routes.test.ts +116 -0
  1068. package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +153 -8
  1069. package/src/modules/tool-auth/__tests__/internal-token.service.test.ts +6 -0
  1070. package/src/modules/tool-auth/connection-key-rejection.ts +25 -0
  1071. package/src/modules/tool-auth/connection-keys-admin.routes.ts +76 -0
  1072. package/src/modules/tool-auth/external-api-key.interface.ts +78 -8
  1073. package/src/modules/tool-auth/external-api-key.service.ts +98 -33
  1074. package/src/modules/tool-auth/internal-token.service.ts +16 -2
  1075. package/src/modules/tool-auth/key-or-session.middleware.ts +45 -0
  1076. package/src/modules/tool-auth/tool-auth.middleware.ts +33 -8
  1077. package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +228 -0
  1078. package/src/modules/tool-helpers/__tests__/phase4-tools.test.ts +33 -4
  1079. package/src/modules/tool-helpers/tool-context.ts +46 -7
  1080. package/src/modules/tool-helpers/tool-handler.ts +23 -5
  1081. package/src/modules/tool-helpers/tool.contract.ts +5 -0
  1082. package/src/modules/tool-manuals/__tests__/mcp-server-edit.service.test.ts +2 -1
  1083. package/src/modules/tool-manuals/__tests__/pending-tools.service.test.ts +411 -0
  1084. package/src/modules/tool-manuals/__tests__/tool-delete.route.test.ts +133 -0
  1085. package/src/modules/tool-manuals/__tests__/tool-delete.service.test.ts +469 -0
  1086. package/src/modules/tool-manuals/__tests__/tool-manuals.archive.route.test.ts +21 -2
  1087. package/src/modules/tool-manuals/__tests__/tool-manuals.cli.test.ts +9 -4
  1088. package/src/modules/tool-manuals/__tests__/tool-manuals.detail.route.test.ts +6 -1
  1089. package/src/modules/tool-manuals/__tests__/tool-manuals.invalid.routes.test.ts +254 -0
  1090. package/src/modules/tool-manuals/__tests__/tool-manuals.invalid.test.ts +577 -0
  1091. package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +6 -1
  1092. package/src/modules/tool-manuals/__tests__/tool-manuals.pending.route.test.ts +102 -0
  1093. package/src/modules/tool-manuals/__tests__/tool-manuals.restart.test.ts +285 -0
  1094. package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +178 -2
  1095. package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +114 -2
  1096. package/src/modules/tool-manuals/index.ts +3 -0
  1097. package/src/modules/tool-manuals/mcp-json-discovery.ts +13 -10
  1098. package/src/modules/tool-manuals/mcp-server-edit.service.ts +11 -14
  1099. package/src/modules/tool-manuals/pending-tools.service.ts +179 -0
  1100. package/src/modules/tool-manuals/tool-delete.service.ts +401 -0
  1101. package/src/modules/tool-manuals/tool-manuals.contract.ts +165 -0
  1102. package/src/modules/tool-manuals/tool-manuals.routes.ts +138 -54
  1103. package/src/modules/tool-manuals/tool-manuals.service.ts +432 -87
  1104. package/src/modules/tool-manuals/tool-manuals.tools.ts +81 -6
  1105. package/src/modules/workflow/__tests__/apply-failure.test.ts +1038 -0
  1106. package/src/modules/workflow/__tests__/event-bus.test.ts +61 -9
  1107. package/src/modules/workflow/__tests__/fake-file-lock-db.ts +199 -0
  1108. package/src/modules/workflow/__tests__/file-lock.service.path-identity.test.ts +237 -0
  1109. package/src/modules/workflow/__tests__/format-affected-owners.test.ts +18 -0
  1110. package/src/modules/workflow/__tests__/merge-announces-tree.test.ts +220 -0
  1111. package/src/modules/workflow/__tests__/pending-commits.worker.concurrency.test.ts +187 -0
  1112. package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +2 -0
  1113. package/src/modules/workflow/__tests__/sanitize-error.test.ts +10 -0
  1114. package/src/modules/workflow/__tests__/workflow.routes.folder-proposals.test.ts +128 -0
  1115. package/src/modules/workflow/__tests__/workflow.routes.fork-point-file.test.ts +149 -0
  1116. package/src/modules/workflow/__tests__/workflow.routes.lock-path.test.ts +305 -0
  1117. package/src/modules/workflow/__tests__/workflow.service.branch-in-use.test.ts +2 -0
  1118. package/src/modules/workflow/__tests__/workflow.service.commitFileWhileLocked.test.ts +2 -0
  1119. package/src/modules/workflow/__tests__/workflow.service.deleted-branch-sweep.test.ts +2 -0
  1120. package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +294 -25
  1121. package/src/modules/workflow/__tests__/workflow.service.folder-proposals.test.ts +564 -0
  1122. package/src/modules/workflow/__tests__/workflow.service.platform-restore.test.ts +135 -0
  1123. package/src/modules/workflow/__tests__/workflow.service.read-gate.test.ts +172 -0
  1124. package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +14 -0
  1125. package/src/modules/workflow/__tests__/workflow.service.sync.test.ts +5 -0
  1126. package/src/modules/workflow/__tests__/workflow.service.update-from-target.test.ts +281 -0
  1127. package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +158 -7
  1128. package/src/modules/workflow/agent-tools/workflow.tools.ts +93 -64
  1129. package/src/modules/workflow/event-bus.ts +26 -28
  1130. package/src/modules/workflow/events.routes.ts +56 -21
  1131. package/src/modules/workflow/file-lock.service.ts +27 -4
  1132. package/src/modules/workflow/git/__tests__/change-request-link.test.ts +43 -0
  1133. package/src/modules/workflow/git/__tests__/git.service.accessGating.test.ts +1 -0
  1134. package/src/modules/workflow/git/__tests__/git.service.changedFilesForPr.test.ts +238 -1
  1135. package/src/modules/workflow/git/__tests__/git.service.forkPoint.test.ts +245 -0
  1136. package/src/modules/workflow/git/__tests__/git.service.list-branches.test.ts +35 -0
  1137. package/src/modules/workflow/git/__tests__/git.service.mergeChangeRequest.test.ts +149 -0
  1138. package/src/modules/workflow/git/__tests__/git.service.revertPathsAndPush.test.ts +177 -0
  1139. package/src/modules/workflow/git/__tests__/node-git-runner.test.ts +114 -0
  1140. package/src/modules/workflow/git/__tests__/pull-request.service.getPrDetail.test.ts +69 -2
  1141. package/src/modules/workflow/git/__tests__/pull-request.service.list-fetch.test.ts +121 -0
  1142. package/src/modules/workflow/git/__tests__/pull-request.service.placeholder.test.ts +79 -0
  1143. package/src/modules/workflow/git/__tests__/pull-request.service.test.ts +1 -1
  1144. package/src/modules/workflow/git/__tests__/pull-request.service.viewer-can-cancel.test.ts +1 -1
  1145. package/src/modules/workflow/git/__tests__/pull-request.service.viewer-can-update.test.ts +111 -0
  1146. package/src/modules/workflow/git/change-request-link.ts +35 -0
  1147. package/src/modules/workflow/git/git-version.ts +5 -2
  1148. package/src/modules/workflow/git/git.service.ts +684 -144
  1149. package/src/modules/workflow/git/node-git-runner.ts +315 -0
  1150. package/src/modules/workflow/git/pull-request.service.ts +179 -27
  1151. package/src/modules/workflow/pending-commits.service.ts +26 -7
  1152. package/src/modules/workflow/pending-commits.worker.ts +114 -69
  1153. package/src/modules/workflow/recovery-bot.ts +4 -1
  1154. package/src/modules/workflow/review-workflow/__tests__/approval-states.test.ts +180 -1
  1155. package/src/modules/workflow/review-workflow/__tests__/cancel-pr.test.ts +1 -1
  1156. package/src/modules/workflow/review-workflow/__tests__/carry-approvals-forward.test.ts +520 -0
  1157. package/src/modules/workflow/review-workflow/__tests__/erase-approver.test.ts +84 -0
  1158. package/src/modules/workflow/review-workflow/__tests__/merge-gate.test.ts +89 -77
  1159. package/src/modules/workflow/review-workflow/approval-lock.ts +122 -0
  1160. package/src/modules/workflow/review-workflow/review-workflow.interface.ts +59 -7
  1161. package/src/modules/workflow/review-workflow/review-workflow.service.ts +309 -105
  1162. package/src/modules/workflow/sanitize-error.ts +9 -4
  1163. package/src/modules/workflow/workflow.routes.ts +273 -29
  1164. package/src/modules/workflow/workflow.service.ts +1087 -171
  1165. package/src/modules/workspace/__tests__/empty-dirs.test.ts +44 -0
  1166. package/src/modules/workspace/__tests__/file-stat-access.test.ts +301 -0
  1167. package/src/modules/workspace/__tests__/git-internals.security.test.ts +883 -0
  1168. package/src/modules/workspace/__tests__/workspace-paths-inside-the-repo.test.ts +464 -0
  1169. package/src/modules/workspace/__tests__/workspace.routes.branch-errors.test.ts +139 -0
  1170. package/src/modules/workspace/__tests__/workspace.routes.create-grant.test.ts +189 -37
  1171. package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +51 -11
  1172. package/src/modules/workspace/__tests__/workspace.routes.download-implies-read.test.ts +161 -0
  1173. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +109 -10
  1174. package/src/modules/workspace/__tests__/workspace.routes.file-writes.test.ts +624 -0
  1175. package/src/modules/workspace/__tests__/workspace.routes.folders.test.ts +409 -0
  1176. package/src/modules/workspace/__tests__/workspace.routes.move-platform-files.test.ts +327 -0
  1177. package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +25 -6
  1178. package/src/modules/workspace/__tests__/workspace.service.ensure-remotes-fetched.test.ts +109 -0
  1179. package/src/modules/workspace/__tests__/workspace.service.list-cloned.test.ts +6 -5
  1180. package/src/modules/workspace/__tests__/workspace.service.move-never-overwrites.test.ts +218 -0
  1181. package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +56 -1
  1182. package/src/modules/workspace/__tests__/workspace.service.test.ts +811 -42
  1183. package/src/modules/workspace/__tests__/workspace.service.unknown-branch.test.ts +197 -0
  1184. package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +128 -0
  1185. package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +212 -0
  1186. package/src/modules/workspace/__tests__/workspace.tools.test.ts +2632 -110
  1187. package/src/modules/workspace/__tests__/write-denial.test.ts +75 -0
  1188. package/src/modules/workspace/empty-dirs.ts +79 -0
  1189. package/src/modules/workspace/file-readers/README.md +32 -0
  1190. package/src/modules/workspace/file-readers/__tests__/doc-extract.test.ts +59 -2
  1191. package/src/modules/workspace/file-readers/__tests__/file-reader.registry.test.ts +133 -4
  1192. package/src/modules/workspace/file-readers/__tests__/fixtures/soft-break.docx +0 -0
  1193. package/src/modules/workspace/file-readers/__tests__/fixtures/tab.docx +0 -0
  1194. package/src/modules/workspace/file-readers/content-mode.ts +87 -0
  1195. package/src/modules/workspace/file-readers/doc-extract.service.ts +4 -1
  1196. package/src/modules/workspace/file-readers/doc-extract.types.ts +8 -4
  1197. package/src/modules/workspace/file-readers/document-reader.ts +6 -0
  1198. package/src/modules/workspace/file-readers/extract-docx.ts +6 -0
  1199. package/src/modules/workspace/file-readers/file-reader.registry.ts +9 -1
  1200. package/src/modules/workspace/file-readers/file-reader.ts +27 -6
  1201. package/src/modules/workspace/file-readers/image-reader.ts +10 -5
  1202. package/src/modules/workspace/file-readers/ooxml-text.ts +38 -2
  1203. package/src/modules/workspace/file-readers/text-reader.ts +61 -4
  1204. package/src/modules/workspace/git-internals.middleware.ts +86 -0
  1205. package/src/modules/workspace/not-found.ts +119 -0
  1206. package/src/modules/workspace/session-ontology.gate.ts +1 -1
  1207. package/src/modules/workspace/startup/__tests__/beside-checkout.test.ts +181 -0
  1208. package/src/modules/workspace/startup/__tests__/kb-startup-runner.degraded.test.ts +361 -0
  1209. package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +53 -16
  1210. package/src/modules/workspace/startup/beside-checkout.ts +112 -0
  1211. package/src/modules/workspace/startup/kb-git.ts +43 -36
  1212. package/src/modules/workspace/startup/kb-startup-runner.ts +303 -63
  1213. package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +1215 -73
  1214. package/src/modules/workspace/startup/steps/groups-to-plugins.step.ts +460 -417
  1215. package/src/modules/workspace/startup/steps/personal-spaces.step.ts +191 -0
  1216. package/src/modules/workspace/startup/steps/plugin-display-names.step.ts +169 -0
  1217. package/src/modules/workspace/startup/steps/plugin-layout.ts +110 -0
  1218. package/src/modules/workspace/startup/steps/plugin-manifests.step.ts +46 -85
  1219. package/src/modules/workspace/startup/steps/roles-yaml.step.ts +6 -12
  1220. package/src/modules/workspace/startup/steps/seed-tree.ts +159 -78
  1221. package/src/modules/workspace/startup/steps/template-files.step.ts +492 -141
  1222. package/src/modules/workspace/startup/steps/template-source.ts +168 -0
  1223. package/src/modules/workspace/workspace.routes.ts +687 -257
  1224. package/src/modules/workspace/workspace.service.ts +1000 -303
  1225. package/src/modules/workspace/workspace.tools.ts +1823 -180
  1226. package/src/modules/workspace/write-denial.ts +189 -0
  1227. package/src/shared/__tests__/canonical-file-identity.test.ts +154 -0
  1228. package/src/shared/__tests__/email-identity.test.ts +35 -0
  1229. package/src/shared/__tests__/filename.test.ts +0 -0
  1230. package/src/shared/__tests__/frontmatter-id.test.ts +5 -0
  1231. package/src/shared/__tests__/git-failure.test.ts +198 -0
  1232. package/src/shared/__tests__/git-internals.test.ts +156 -0
  1233. package/src/shared/__tests__/http-errors.test.ts +42 -0
  1234. package/src/shared/__tests__/logging.test.ts +185 -0
  1235. package/src/shared/__tests__/path-containment.test.ts +60 -0
  1236. package/src/shared/__tests__/pending-proposals.readAt.test.ts +102 -0
  1237. package/src/shared/__tests__/printable.test.ts +64 -0
  1238. package/src/shared/__tests__/redact-secret.test.ts +122 -0
  1239. package/src/shared/__tests__/rename-no-replace.test.ts +466 -0
  1240. package/src/shared/__tests__/ssrf.test.ts +20 -0
  1241. package/src/shared/canonical-file-identity.ts +92 -0
  1242. package/src/shared/domain-errors.ts +149 -0
  1243. package/src/shared/email-identity.ts +38 -0
  1244. package/src/shared/fs.contract.ts +225 -0
  1245. package/src/shared/git-failure.ts +196 -0
  1246. package/src/shared/git-internals.ts +179 -0
  1247. package/src/shared/git.contract.ts +170 -0
  1248. package/src/shared/http-errors.ts +36 -0
  1249. package/src/shared/logger.contract.ts +141 -0
  1250. package/src/shared/logging.ts +96 -0
  1251. package/src/shared/path-containment.ts +34 -0
  1252. package/src/shared/pending-proposals.ts +161 -0
  1253. package/src/shared/printable.ts +68 -0
  1254. package/src/shared/redact-secret.ts +118 -0
  1255. package/src/shared/rename-no-replace.ts +386 -0
  1256. package/src/shared/ssrf.ts +44 -16
  1257. package/src/version.ts +10 -6
  1258. package/dist/modules/access/access-errors.d.ts +0 -34
  1259. package/dist/modules/access/access-errors.d.ts.map +0 -1
  1260. package/dist/modules/access/access-errors.js +0 -40
  1261. package/dist/modules/access/access-errors.js.map +0 -1
  1262. package/dist/modules/access/access-splice.d.ts +0 -140
  1263. package/dist/modules/access/access-splice.d.ts.map +0 -1
  1264. package/dist/modules/access/access-splice.js +0 -389
  1265. package/dist/modules/access/access-splice.js.map +0 -1
  1266. package/dist/modules/access/group-files.d.ts +0 -83
  1267. package/dist/modules/access/group-files.d.ts.map +0 -1
  1268. package/dist/modules/access/group-files.js +0 -167
  1269. package/dist/modules/access/group-files.js.map +0 -1
  1270. package/dist/modules/access/kb-read-filter.d.ts +0 -41
  1271. package/dist/modules/access/kb-read-filter.d.ts.map +0 -1
  1272. package/dist/modules/access/kb-read-filter.js +0 -60
  1273. package/dist/modules/access/kb-read-filter.js.map +0 -1
  1274. package/dist/modules/access/render-roles-yaml.d.ts +0 -22
  1275. package/dist/modules/access/render-roles-yaml.d.ts.map +0 -1
  1276. package/dist/modules/access/render-roles-yaml.js +0 -56
  1277. package/dist/modules/access/render-roles-yaml.js.map +0 -1
  1278. package/dist/modules/access/roles-yaml-guard.d.ts +0 -56
  1279. package/dist/modules/access/roles-yaml-guard.d.ts.map +0 -1
  1280. package/dist/modules/access/roles-yaml-guard.js +0 -79
  1281. package/dist/modules/access/roles-yaml-guard.js.map +0 -1
  1282. package/dist/modules/diff/diff-paths.d.ts +0 -2
  1283. package/dist/modules/diff/diff-paths.d.ts.map +0 -1
  1284. package/dist/modules/diff/diff-paths.js +0 -9
  1285. package/dist/modules/diff/diff-paths.js.map +0 -1
  1286. package/dist/modules/groups/group-provision.service.d.ts +0 -130
  1287. package/dist/modules/groups/group-provision.service.d.ts.map +0 -1
  1288. package/dist/modules/groups/group-provision.service.js +0 -288
  1289. package/dist/modules/groups/group-provision.service.js.map +0 -1
  1290. package/dist/modules/groups/groups.contract.d.ts +0 -106
  1291. package/dist/modules/groups/groups.contract.d.ts.map +0 -1
  1292. package/dist/modules/groups/groups.contract.js +0 -36
  1293. package/dist/modules/groups/groups.contract.js.map +0 -1
  1294. package/dist/modules/groups/groups.routes.d.ts +0 -42
  1295. package/dist/modules/groups/groups.routes.d.ts.map +0 -1
  1296. package/dist/modules/groups/groups.routes.js +0 -379
  1297. package/dist/modules/groups/groups.routes.js.map +0 -1
  1298. package/dist/modules/groups/groups.service.d.ts +0 -60
  1299. package/dist/modules/groups/groups.service.d.ts.map +0 -1
  1300. package/dist/modules/groups/groups.service.js +0 -172
  1301. package/dist/modules/groups/groups.service.js.map +0 -1
  1302. package/dist/modules/groups/index.d.ts +0 -7
  1303. package/dist/modules/groups/index.d.ts.map +0 -1
  1304. package/dist/modules/groups/index.js +0 -6
  1305. package/dist/modules/groups/index.js.map +0 -1
  1306. package/dist/modules/groups/join-proposals.d.ts +0 -53
  1307. package/dist/modules/groups/join-proposals.d.ts.map +0 -1
  1308. package/dist/modules/groups/join-proposals.js +0 -67
  1309. package/dist/modules/groups/join-proposals.js.map +0 -1
  1310. package/dist/modules/groups/join-requests.service.d.ts +0 -81
  1311. package/dist/modules/groups/join-requests.service.d.ts.map +0 -1
  1312. package/dist/modules/groups/join-requests.service.js +0 -135
  1313. package/dist/modules/groups/join-requests.service.js.map +0 -1
  1314. package/dist/modules/mcp/mcp-session-store.d.ts +0 -74
  1315. package/dist/modules/mcp/mcp-session-store.d.ts.map +0 -1
  1316. package/dist/modules/mcp/mcp-session-store.js +0 -131
  1317. package/dist/modules/mcp/mcp-session-store.js.map +0 -1
  1318. package/dist/modules/workflow/file-change-notifier.d.ts +0 -38
  1319. package/dist/modules/workflow/file-change-notifier.d.ts.map +0 -1
  1320. package/dist/modules/workflow/file-change-notifier.js +0 -22
  1321. package/dist/modules/workflow/file-change-notifier.js.map +0 -1
  1322. package/dist/modules/workflow/git/branch-name.d.ts +0 -10
  1323. package/dist/modules/workflow/git/branch-name.d.ts.map +0 -1
  1324. package/dist/modules/workflow/git/branch-name.js +0 -76
  1325. package/dist/modules/workflow/git/branch-name.js.map +0 -1
  1326. package/dist/modules/workflow/git/clone-config.d.ts +0 -61
  1327. package/dist/modules/workflow/git/clone-config.d.ts.map +0 -1
  1328. package/dist/modules/workflow/git/clone-config.js +0 -69
  1329. package/dist/modules/workflow/git/clone-config.js.map +0 -1
  1330. package/dist/modules/workflow/git/mutex.d.ts +0 -11
  1331. package/dist/modules/workflow/git/mutex.d.ts.map +0 -1
  1332. package/dist/modules/workflow/git/mutex.js +0 -23
  1333. package/dist/modules/workflow/git/mutex.js.map +0 -1
  1334. package/dist/modules/workflow/locking-filesystem.d.ts +0 -137
  1335. package/dist/modules/workflow/locking-filesystem.d.ts.map +0 -1
  1336. package/dist/modules/workflow/locking-filesystem.js +0 -553
  1337. package/dist/modules/workflow/locking-filesystem.js.map +0 -1
  1338. package/dist/modules/workflow/read-only-filesystem.d.ts +0 -24
  1339. package/dist/modules/workflow/read-only-filesystem.d.ts.map +0 -1
  1340. package/dist/modules/workflow/read-only-filesystem.js +0 -39
  1341. package/dist/modules/workflow/read-only-filesystem.js.map +0 -1
  1342. package/dist/modules/workflow/workflow.errors.d.ts +0 -197
  1343. package/dist/modules/workflow/workflow.errors.d.ts.map +0 -1
  1344. package/dist/modules/workflow/workflow.errors.js +0 -298
  1345. package/dist/modules/workflow/workflow.errors.js.map +0 -1
  1346. package/dist/modules/workspace/bevel-ignore.d.ts.map +0 -1
  1347. package/dist/modules/workspace/bevel-ignore.js.map +0 -1
  1348. package/dist/modules/workspace/kb-seed.interface.d.ts +0 -36
  1349. package/dist/modules/workspace/kb-seed.interface.d.ts.map +0 -1
  1350. package/dist/modules/workspace/kb-seed.interface.js +0 -2
  1351. package/dist/modules/workspace/kb-seed.interface.js.map +0 -1
  1352. package/dist/modules/workspace/kb-seed.service.d.ts +0 -125
  1353. package/dist/modules/workspace/kb-seed.service.d.ts.map +0 -1
  1354. package/dist/modules/workspace/kb-seed.service.js +0 -535
  1355. package/dist/modules/workspace/kb-seed.service.js.map +0 -1
  1356. package/dist/modules/workspace/plugins-migration.d.ts +0 -50
  1357. package/dist/modules/workspace/plugins-migration.d.ts.map +0 -1
  1358. package/dist/modules/workspace/plugins-migration.js +0 -379
  1359. package/dist/modules/workspace/plugins-migration.js.map +0 -1
  1360. package/dist/shared/fs-errors.d.ts +0 -8
  1361. package/dist/shared/fs-errors.d.ts.map +0 -1
  1362. package/dist/shared/fs-errors.js +0 -11
  1363. package/dist/shared/fs-errors.js.map +0 -1
  1364. package/dist/shared/fs-walk.d.ts +0 -23
  1365. package/dist/shared/fs-walk.d.ts.map +0 -1
  1366. package/dist/shared/fs-walk.js +0 -42
  1367. package/dist/shared/fs-walk.js.map +0 -1
  1368. package/dist/shared/hash-email.d.ts +0 -11
  1369. package/dist/shared/hash-email.d.ts.map +0 -1
  1370. package/dist/shared/hash-email.js +0 -14
  1371. package/dist/shared/hash-email.js.map +0 -1
  1372. package/dist/shared/kb-walk.d.ts +0 -52
  1373. package/dist/shared/kb-walk.d.ts.map +0 -1
  1374. package/dist/shared/kb-walk.js +0 -76
  1375. package/dist/shared/kb-walk.js.map +0 -1
  1376. package/src/modules/diff/diff-paths.ts +0 -9
  1377. package/src/modules/mcp/__tests__/mcp-session-store.test.ts +0 -151
  1378. package/src/modules/mcp/__tests__/mcp.routes.session.test.ts +0 -226
  1379. package/src/modules/mcp/mcp-session-store.ts +0 -162
  1380. package/src/shared/__tests__/fs-walk.test.ts +0 -63
  1381. package/src/shared/__tests__/kb-walk.test.ts +0 -107
  1382. package/src/shared/fs-errors.ts +0 -10
  1383. package/src/shared/fs-walk.ts +0 -45
  1384. package/src/shared/hash-email.ts +0 -14
  1385. package/src/shared/kb-walk.ts +0 -95
@@ -1,6 +1,8 @@
1
- import { randomUUID } from 'node:crypto';
1
+ import { createHash } from 'node:crypto';
2
+ import { logger } from '../../shared/logging.js';
3
+
4
+ const log = logger('mcp');
2
5
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
- import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
4
6
  import {
5
7
  CallToolRequestSchema,
6
8
  ListToolsRequestSchema,
@@ -8,6 +10,7 @@ import {
8
10
  GetPromptRequestSchema,
9
11
  McpError,
10
12
  ErrorCode,
13
+ isInitializeRequest,
11
14
  type CallToolResult,
12
15
  type Tool as McpTool,
13
16
  type Prompt,
@@ -18,6 +21,7 @@ import '@utcp/mcp'; // side effect: registers the 'mcp' protocol (native MCP-ser
18
21
  import {
19
22
  UtcpClientConfigSerializer,
20
23
  CallTemplateSerializer,
24
+ UtcpManualSchema,
21
25
  type CallTemplate,
22
26
  type Tool as UtcpTool,
23
27
  } from '@utcp/sdk';
@@ -28,9 +32,11 @@ import {
28
32
  dispatchMetaTool,
29
33
  dispatchToolCall,
30
34
  registerManual,
35
+ installSessionRecovery,
31
36
  flattenManualTool,
32
37
  toListedTool,
33
38
  toolError,
39
+ retiredToolMessage,
34
40
  needsAuthorizationResult,
35
41
  skillPromptText,
36
42
  type ProxiedTool,
@@ -38,19 +44,35 @@ import {
38
44
  type LoadedSkill,
39
45
  } from '@bevel-software/platform-mcp-core';
40
46
  import { bevelSecretsLoaderConfig } from '../secrets-vault/index.js';
41
- import { scopesCovered, type ISecretsVaultService } from '../secrets-vault/secrets-vault.contract.js';
47
+ import {
48
+ scopesCovered,
49
+ type ForcedRefreshOutcome,
50
+ type ISecretsVaultService,
51
+ } from '../secrets-vault/secrets-vault.contract.js';
42
52
  import { EXTERNAL_KB_MANUAL_NAME } from '../tool-manuals/tool-manuals.contract.js';
43
53
  import type { IToolManualService } from '../tool-manuals/tool-manuals.contract.js';
44
54
  import type { SpillStore } from '../workspace/spill-store.js';
45
55
  import { seedBevelHostedManualVars } from '../../shared/utcp-namespace.js';
46
- import type { McpSessionStore } from './mcp-session-store.js';
47
56
  import type { InternalTokenService } from '../tool-auth/internal-token.service.js';
57
+ import type { IAgentEventRecorder } from '../audit/audit.contract.js';
58
+ import { RequestAudit } from '../audit/request-audit.js';
48
59
  import { ManualFailureMemo } from './manual-failure-memo.js';
60
+ import { DownstreamPool, POOL_KEY_SEPARATOR, type DownstreamPoolOptions, type Lease } from './downstream-pool.js';
61
+ import { SurfaceLogThrottle } from './surface-log-throttle.js';
62
+ import { DownstreamRefreshGuard, isDownstreamTokenRejection } from './downstream-token-refresh.js';
63
+ import { printable } from '../../shared/printable.js';
64
+ import {
65
+ composeAgentInstructions,
66
+ prefixToolDescription,
67
+ PREFIXED_TOOLS,
68
+ type AgentPreambleReader,
69
+ type ComposedAgentInstructions,
70
+ } from '../agent-instructions/index.js';
49
71
 
50
72
  /**
51
73
  * Configuration for the loopback proxy. `loopbackBaseUrl` is the backend's own
52
74
  * address (`http://127.0.0.1:<port>`) and `manualName` is BOTH the UTCP manual
53
- * namespace the per-session client registers under AND the prefix its `${VAR}`
75
+ * namespace the per-request client registers under AND the prefix its `${VAR}`
54
76
  * placeholders resolve through (`<manualName>_API_URL` / `_CONNECTION_KEY`).
55
77
  */
56
78
  export interface McpProxyOptions {
@@ -60,22 +82,46 @@ export interface McpProxyOptions {
60
82
  spillStore: SpillStore;
61
83
  /** Public web address of the frontend, for the needs-authorization setup link. */
62
84
  publicFrontendUrl: string;
85
+ /**
86
+ * Reads `mcp-description.md` on the default branch with platform rights
87
+ * (see modules/agent-instructions). Called on every request that needs it
88
+ * (an `initialize`, or a `tools/list` for the tool prefix), so an edit
89
+ * reaches the next request without a restart. Optional so constructions
90
+ * that never exercise it (tests) keep working: without it every request
91
+ * carries the platform header alone.
92
+ */
93
+ readAgentPreamble?: AgentPreambleReader;
94
+ /** Bounds of the downstream (`mcp.json`) connection pool; defaults are 4h idle / 5000 entries. */
95
+ downstreamPool?: Pick<DownstreamPoolOptions<unknown>, 'idleTtlMs' | 'maxEntries' | 'now'>;
96
+ }
97
+
98
+ /** Who a request is from — everything the proxy needs, resolved from that request's own bearer. */
99
+ export interface McpCaller {
100
+ userId: string;
101
+ /** The connection-key id (per-key metering rides on it), or null for an OAuth/JWT bearer. */
102
+ tokenId: string | null;
103
+ /**
104
+ * The agent connection behind an OAuth bearer (or the local server's
105
+ * exchanged grant), or null. With `tokenId`, the two ways a call is
106
+ * attributed in the Audit log; a request with neither records nothing.
107
+ */
108
+ connectionId?: string | null;
109
+ /** The raw bearer the request carried. */
110
+ bearer: string;
63
111
  }
64
112
 
65
113
  /**
66
114
  * Upper bound on one `loopbackJson` round-trip (manual list, skill fetch) so a
67
- * hung loopback can't stall `createSession`. Generous: these endpoints answer
68
- * in milliseconds; only a wedged process ever comes near it.
115
+ * hung loopback can't stall a request. Generous: these endpoints answer in
116
+ * milliseconds; only a wedged process ever comes near it.
69
117
  */
70
118
  const LOOPBACK_TIMEOUT_MS = 15_000;
71
119
 
72
120
  /**
73
- * Lifetime of the internal token minted as an OAuth/JWT session's loopback
74
- * bearer. Longer than the session store's 4h idle eviction (see
75
- * `DEFAULT_IDLE_TTL_MS` in mcp-session-store.ts) so the credential is not the
76
- * first thing to die under a session's normal lifecycle. A continuously-active
77
- * session CAN outlive it — its tool calls then fail at the loopback and the
78
- * client recovers by re-initializing, which mints a fresh token.
121
+ * Lifetime of the internal token minted as an OAuth/JWT request's loopback
122
+ * bearer. A request only needs it for as long as it runs, but one `tools/call`
123
+ * can legitimately run for a long time (`ask`, a long `call_tool_chain`), so
124
+ * the lifetime is generous rather than tight.
79
125
  *
80
126
  * Exported because `POST /api/mcp/local-token` (mcp.routes.ts) performs the
81
127
  * same OAuth-access-token → internal-token exchange for the LOCAL MCP server,
@@ -86,142 +132,287 @@ export const MCP_LOOPBACK_TOKEN_TTL_MS = 5 * 60 * 60 * 1000;
86
132
 
87
133
  const callTemplateSerializer = new CallTemplateSerializer();
88
134
 
135
+ /** What one request's tool calls run against: its UTCP client and the flattened catalog. */
136
+ interface RequestSurface {
137
+ client: CodeModeUtcpClient;
138
+ tools: ProxiedTool[];
139
+ /**
140
+ * Pooled manuals the caller may use but whose tools could not be put on the
141
+ * surface because the caller's sign-in for them is missing or broken AND no
142
+ * tool definitions were ever discovered for them in this process. A call to
143
+ * one of their tools is answered with the /connect link, not "Unknown tool".
144
+ */
145
+ unavailable: UnavailableManual[];
146
+ /**
147
+ * Registered (rewritten, `utcpManualName`) name → catalog name, for every
148
+ * pooled manual of the caller's. A tool carries the registered name; the
149
+ * credential catalog (`userScopedKeysForManual`) keys by the catalog name.
150
+ * They differ only when the name holds a non-word character (`my-server`).
151
+ */
152
+ catalogNames: ReadonlyMap<string, string>;
153
+ }
154
+
155
+ /** A manual off the surface for want of a sign-in — see {@link RequestSurface.unavailable}. */
156
+ interface UnavailableManual {
157
+ /** The name as registered (`utcpManualName`): tools of it are `<utcpName>_<tool>` on the wire. */
158
+ utcpName: string;
159
+ }
160
+
89
161
  /**
90
- * The MCP server is a GENERIC proxy over the UTCP tool surface. It owns no tool
91
- * logic: per MCP session it stands up a `UtcpClient` pointed at the backend's
92
- * own `GET /api/agent/utcp` manual (over loopback, authenticated with the
93
- * caller's connection key), discovers every tool, and re-exposes each one to
94
- * the MCP client. A tool call is dispatched straight back through the same REST
95
- * endpoint the tool's UTCP `tool_call_template` names — so the agent logic,
162
+ * How a pooled manual's calls are served: lease its pooled connection, and —
163
+ * when a call fails — decide whether a token refresh warrants one retry
164
+ * ({@link RETRY_WITH_REFRESHED_TOKEN}) or which error the caller gets.
165
+ */
166
+ interface DownstreamRoute {
167
+ acquire: () => Promise<Lease<PooledDownstream>>;
168
+ afterFailure: (err: unknown) => Promise<unknown>;
169
+ }
170
+
171
+ /**
172
+ * What a failed downstream operation turned out to be, for the code that has
173
+ * to decide more than "retry or throw" — see {@link McpService.attachDownstream}.
174
+ */
175
+ type DownstreamFailure =
176
+ | { retry: true }
177
+ /** `credentialTransient`: a token rejection whose refresh could not be settled (network, 5xx, or the once-a-minute guard), so the very next call should try again rather than being written off. */
178
+ | { retry: false; error: unknown; credentialTransient: boolean };
179
+
180
+ /** The {@link DownstreamRoute.afterFailure} answer that means "refreshed — retry once". */
181
+ const RETRY_WITH_REFRESHED_TOKEN = Symbol('retry-with-refreshed-token');
182
+
183
+ /**
184
+ * What attaching one pooled manual to a request came to. `retryable`: do not
185
+ * remember this failure (it may clear on its own within the minute).
186
+ * `signInMissing`: the caller has no usable sign-in for the manual and this
187
+ * process has never seen its tools — the manual goes on the request's
188
+ * {@link RequestSurface.unavailable} list so a call still gets the sign-in link.
189
+ */
190
+ type DownstreamAttachResult =
191
+ | { ok: true }
192
+ | { ok: false; error: string; retryable?: boolean; signInMissing?: boolean };
193
+
194
+ /** One pooled downstream connection: a client holding one `mcp` manual, named so it can be deregistered. */
195
+ interface PooledDownstream {
196
+ /** Owns its own `mcp` protocol instance, so its sessions are its alone and `close()` ends exactly them. */
197
+ client: CodeModeUtcpClient;
198
+ }
199
+
200
+ /**
201
+ * The MCP server is a GENERIC, STATELESS proxy over the UTCP tool surface. It
202
+ * owns no tool logic and no session: every HTTP request gets its own MCP
203
+ * `Server`, built from that request's bearer. When the request needs tools it
204
+ * stands up a `UtcpClient` pointed at the backend's own `GET /api/agent/all-tools`
205
+ * catalog (over loopback, authenticated as the caller, so ACL filtering lives
206
+ * ONCE behind the REST surface), discovers every tool, and re-exposes each one
207
+ * to the MCP client. A tool call is dispatched straight back through the same
208
+ * REST endpoint the tool's UTCP `tool_call_template` names — so the agent logic,
96
209
  * thread continuity, and per-key token metering all live ONCE behind that REST
97
210
  * endpoint (`AgentAskService` for `ask`), never duplicated here.
98
211
  *
212
+ * Because nothing is held between requests, a platform restart between two
213
+ * requests of one client conversation is undetectable by the client.
214
+ *
215
+ * There is NO catalog cache at this layer, deliberately: the catalog is
216
+ * re-derived per request, and if that ever proves too slow the fix belongs in
217
+ * the source path (all-tools/ACL), which serves every other caller too.
218
+ *
219
+ * The one exception to "nothing between requests" is proxied third-party MCP
220
+ * servers (`mcp` manuals): those are session-ful by nature, so their
221
+ * connections live in a {@link DownstreamPool} keyed by (user, manual) — lazy,
222
+ * single-flight, idle-evicted, bounded, and healed by session recovery. Losing
223
+ * any of it costs a reconnect, never correctness.
224
+ *
99
225
  * Dispatch always uses `callToolStreaming`, which is uniform across tool kinds:
100
226
  * a plain `http` tool yields exactly one chunk (its final result, emitted as
101
227
  * the tool result with no progress), while a `streamable_http` tool yields many
102
- * (the all-but-last become `notifications/progress`, the last is the result).
103
- * Because of that, adding — or later upgrading a tool to streaming — never
104
- * touches this file.
228
+ * (the all-but-last become `notifications/progress` on the request's own
229
+ * response stream, the last is the result).
105
230
  *
106
231
  * Auth note: `/api/agent/*` accepts connection keys and internal tokens only.
107
- * Connection-key sessions pass the caller's own key through to the loopback;
108
- * OAuth/JWT sessions can't (their bearer would 401 there), so `createSession`
109
- * mints a least-privilege internal token for the resolved user and uses THAT
110
- * as the session's loopback bearer instead.
232
+ * Connection-key requests pass the caller's own key through to the loopback;
233
+ * OAuth/JWT requests can't (their bearer would 401 there), so the proxy mints a
234
+ * least-privilege internal token for the resolved user and uses THAT as the
235
+ * request's loopback bearer instead.
111
236
  */
112
237
  export class McpService {
113
238
  // Circuit breaker for manuals whose credentials just failed — see the memo.
239
+ // Retry policy, not session state: it survives the move to statelessness.
114
240
  private readonly manualFailures = new ManualFailureMemo();
115
-
241
+ private readonly surfaceLog = new SurfaceLogThrottle();
242
+ // One forced token refresh per (user, manual) per minute — see downstream-token-refresh.
243
+ private readonly tokenRefreshes: DownstreamRefreshGuard<ForcedRefreshOutcome>;
116
244
  /**
117
- * Invalidate remembered manual failures — wired to the secrets vault's
118
- * mutation listener, so a just-repaired credential is retried on the very
119
- * next session build. `null` = a shared secret changed (affects everyone).
245
+ * The tools each pooled manual last advertised, by manual definition. A
246
+ * downstream can only be asked what its tools are over a connection, and a
247
+ * caller whose sign-in is missing or broken has no connection — so their
248
+ * tools would silently vanish from the surface, and a call would answer
249
+ * "Unknown tool" instead of "sign in again on /connect". These definitions
250
+ * keep the manual on the surface for such a caller; the call-time credential
251
+ * check then answers with the link, and an interactive client is sent back
252
+ * through authorization. Definitions, never credentials or results. Keyed
253
+ * by catalog name — one entry per manual, replaced when the manual is next
254
+ * discovered — and used only while the manual's definition is the one the
255
+ * tools were discovered from, so an edited `mcp.json` never serves the old
256
+ * server's tools.
120
257
  */
121
- clearManualFailures(userId: string | null): void {
122
- if (userId === null) this.manualFailures.clearAll();
123
- else this.manualFailures.clearUser(userId);
124
- }
258
+ private readonly knownDownstreamTools = new Map<string, { definition: string; tools: UtcpTool[] }>();
259
+
260
+ // The downstream connection pool for `mcp` manuals — see the class doc.
261
+ private readonly downstream: DownstreamPool<PooledDownstream>;
125
262
 
126
263
  constructor(
127
- private readonly sessionStore: McpSessionStore,
128
264
  private readonly opts: McpProxyOptions,
129
265
  // The vault + manual catalog, used to check a caller's per-user credentials
130
266
  // before dispatching a tool. Optional so existing constructions/tests that
131
267
  // don't exercise the check keep working (the check is skipped when absent).
132
268
  private readonly secretsVault?: ISecretsVaultService,
133
269
  private readonly toolManuals?: IToolManualService,
134
- // Mints the loopback bearer for OAuth/JWT sessions (see class doc).
135
- // Optional for the same test-compat reason; without it those sessions
270
+ // Mints the loopback bearer for OAuth/JWT requests (see class doc).
271
+ // Optional for the same test-compat reason; without it those requests
136
272
  // fall back to the old pass-through (and 401 at the loopback hop).
137
273
  private readonly internalTokens?: InternalTokenService,
138
274
  // Revokes an MCP OAuth access token (BevelOAuthProvider.revokeByAccessToken)
139
- // — the reset that sends an interactive session back through the browser
275
+ // — the reset that sends an interactive client back through the browser
140
276
  // authorization when one of its TOOL sign-ins breaks. Optional: without
141
277
  // it, broken sign-ins surface only as the /connect link in the result.
142
278
  private readonly revokeOAuthAccess?: (bearer: string) => Promise<void>,
143
- ) {}
279
+ // The Audit log's recorder: every tool call, skill read and meta-tool run
280
+ // of a request that arrived with a key or an agent connection goes
281
+ // through it. Optional like the others; without it nothing is recorded.
282
+ private readonly auditRecorder?: IAgentEventRecorder,
283
+ ) {
284
+ this.downstream = new DownstreamPool<PooledDownstream>({
285
+ ...opts.downstreamPool,
286
+ // Since @utcp/sdk 1.2.0 a client closes only the protocol instances it
287
+ // created, and @utcp/mcp registers as a factory — so this closes exactly
288
+ // this entry's MCP sessions and touches no other client's.
289
+ dispose: (entry) => entry.client.close(),
290
+ });
291
+ // Paced on the pool's clock, so a test that moves one moves both.
292
+ this.tokenRefreshes = new DownstreamRefreshGuard(undefined, opts.downstreamPool?.now);
293
+ }
144
294
 
145
295
  /**
146
- * Subscribe to store-driven evictions (idle-TTL sweep, size-cap eviction) so
147
- * the route layer can drop its transport mirror. Returns an unsubscribe fn.
296
+ * A secret changed — wired to the secrets vault's mutation listener.
297
+ * `null` = a shared secret changed (affects everyone).
298
+ *
299
+ * Two things were built from the old value: remembered manual failures (so a
300
+ * just-repaired credential is retried on the very next request) and pooled
301
+ * downstream connections (dialed and discovered with the old credential).
302
+ * Both are dropped; the next request rebuilds whatever it needs.
148
303
  */
149
- onSessionEvicted(listener: (sessionId: string) => void): () => void {
150
- return this.sessionStore.onEvict(listener);
304
+ onSecretsChanged(userId: string | null): void {
305
+ this.forgetStaleRefreshWindows(userId);
306
+ if (userId === null) {
307
+ this.manualFailures.clearAll();
308
+ this.downstream.closeAll();
309
+ return;
310
+ }
311
+ this.manualFailures.clearUser(userId);
312
+ this.downstream.evictWhere((key) => key.startsWith(`${userId}${POOL_KEY_SEPARATOR}`));
151
313
  }
152
314
 
153
315
  /**
154
- * Build a fresh per-session (transport, server) pair. Async because tool
155
- * discovery (a loopback round-trip authenticated with `bearer`) happens here,
156
- * before the server is returned, so `tools/list` is ready the moment the
157
- * client asks. The route awaits this, then `server.connect(transport)`.
316
+ * A credential changed, so a refresh window that FAILED to produce one no
317
+ * longer describes anything: a token the user has just re-authorized (or an
318
+ * admin has just re-keyed) must be tried on the very next rejection, not sit
319
+ * out the remainder of a minute earned by the credential it replaced.
320
+ *
321
+ * Only settled, non-`refreshed` windows are forgotten. A refresh of our own
322
+ * is itself a secrets mutation, and clearing on that would hand a provider
323
+ * that keeps minting tokens the downstream keeps refusing one refresh per
324
+ * call — precisely the loop the once-a-minute guard exists to bound. An
325
+ * in-flight attempt (no outcome yet) is left alone for the same reason: it
326
+ * may be the very refresh that raised this notification.
158
327
  */
159
- async createSession(
160
- userId: string,
161
- tokenId: string | null,
162
- bearer: string,
163
- onSessionInitialized: (sessionId: string) => void,
164
- ): Promise<{ transport: StreamableHTTPServerTransport; server: Server }> {
328
+ private forgetStaleRefreshWindows(userId: string | null): void {
329
+ const mine = (key: string) => userId === null || key.startsWith(`${userId}${POOL_KEY_SEPARATOR}`);
330
+ this.tokenRefreshes.clearWhere(
331
+ (key, outcome) => mine(key) && outcome !== undefined && outcome !== 'refreshed',
332
+ );
333
+ }
334
+
335
+ /**
336
+ * Build the MCP `Server` for ONE request. The route connects it to a fresh
337
+ * stateless transport, lets it answer, and closes both with the response.
338
+ *
339
+ * Cheap by construction: nothing is fetched up front except what the
340
+ * request's own messages need. The agent instructions are read only when the
341
+ * request carries an `initialize` (the only place they ride); the tool
342
+ * catalog is built on the first handler that needs it and shared by every
343
+ * message of the request (a JSON-RPC batch), never beyond it.
344
+ */
345
+ async createRequestServer(caller: McpCaller, messages: unknown): Promise<Server> {
346
+ const { userId, tokenId, bearer } = caller;
347
+ const connectionId = caller.connectionId ?? null;
165
348
  // The loopback surface (`/api/agent/*`) accepts connection keys and
166
- // internal tokens only. A connection-key session passes the caller's own
167
- // key through (per-key metering rides on it); an OAuth/JWT session's
349
+ // internal tokens only. A connection-key request passes the caller's own
350
+ // key through (per-key metering rides on it); an OAuth/JWT request's
168
351
  // bearer would 401 there, so mint a least-privilege internal token for
169
352
  // the resolved user instead — flagged `externalProxy` so the tool-auth
170
353
  // verifier resolves it to `source: 'external'`: the caller IS an external
171
354
  // agent and must be treated like one (admitted to `start_session`/`ask`,
172
- // refused from internal-only tools). TTL covers the session store's 4h
173
- // idle eviction — an evicted session re-initializes and re-mints.
355
+ // refused from internal-only tools).
174
356
  const loopbackBearer =
175
357
  tokenId == null && this.internalTokens
176
358
  ? this.internalTokens.mint({ userId, externalProxy: true }, MCP_LOOPBACK_TOKEN_TTL_MS)
177
359
  : bearer;
178
360
 
179
- // The list of manuals this caller gets: the KB manual + each `.tool` they
180
- // can read. Fetched over loopback with the caller's key, so ACL filtering
181
- // lives ONCE behind the REST surface.
182
- const manuals = await this.fetchManualTemplates(loopbackBearer);
183
- const client = await this.buildClient(loopbackBearer, userId, manuals);
184
- const tools = await this.discoverTools(client, manuals, userId);
185
- // Confirms the MCP session was built for a connecting client (vs. an
186
- // in-process agent code-mode client, which never runs createSession) and
187
- // how many tools discovery yielded before listing/filtering.
188
- console.log(
189
- `[mcp] createSession: user=${userId} tokenId=${tokenId ?? 'none'} — ` +
190
- `discovered ${tools.length} tool(s) across ${manuals.length} manual(s)`,
191
- );
192
-
193
- const transport = new StreamableHTTPServerTransport({
194
- sessionIdGenerator: () => randomUUID(),
195
- onsessioninitialized: (sessionId) => {
196
- this.sessionStore.create(sessionId, userId, tokenId);
197
- onSessionInitialized(sessionId);
198
- },
199
- onsessionclosed: (sessionId) => {
200
- this.sessionStore.delete(sessionId);
201
- },
202
- });
361
+ let instructions: Promise<ComposedAgentInstructions> | undefined;
362
+ const agentInstructions = () => (instructions ??= this.composeAgentInstructions());
363
+ // The Audit log's view of this request — only when the request can be
364
+ // attributed to a key or an agent connection. The catalog names it
365
+ // classifies pooled tools by are the surface's, which is built lazily
366
+ // below, so it reads them through a holder the surface fills in.
367
+ const catalogNamesHolder: { current: ReadonlyMap<string, string> } = { current: new Map() };
368
+ const principal = tokenId != null ? { kind: 'key' as const, id: tokenId } : connectionId ? { kind: 'agent' as const, id: connectionId } : null;
369
+ const audit =
370
+ this.auditRecorder && principal
371
+ ? new RequestAudit(this.auditRecorder, userId, principal, {
372
+ kbManualName: EXTERNAL_KB_MANUAL_NAME,
373
+ catalogNames: () => catalogNamesHolder.current,
374
+ loadSkills: () =>
375
+ this.fetchSkillList(loopbackBearer).then((skills) =>
376
+ skills.flatMap((s) => (s.path ? [{ name: s.name, path: s.path }] : [])),
377
+ ),
378
+ })
379
+ : null;
380
+ let surface: Promise<RequestSurface> | undefined;
381
+ const requestSurface = () =>
382
+ (surface ??= this.buildSurface(userId, tokenId, loopbackBearer).then((built) => {
383
+ catalogNamesHolder.current = built.catalogNames;
384
+ audit?.instrumentChainCalls(built.client);
385
+ return built;
386
+ }));
203
387
 
388
+ const initializing = (Array.isArray(messages) ? messages : [messages]).some((m) => isInitializeRequest(m));
204
389
  const server = new Server(
205
390
  { name: 'bevel-mcp', version: '0.1.0' },
206
- { capabilities: { tools: {}, prompts: {} } },
391
+ {
392
+ capabilities: { tools: {}, prompts: {} },
393
+ // `instructions` rides the initialize result; clients that honour it
394
+ // place the text in the model's system prompt without the model acting.
395
+ ...(initializing ? { instructions: (await agentInstructions()).instructions } : {}),
396
+ },
207
397
  );
208
398
 
209
399
  server.setRequestHandler(ListToolsRequestSchema, async () => {
400
+ const [{ tools, catalogNames }, { toolPrefix }] = await Promise.all([requestSurface(), agentInstructions()]);
210
401
  // A discovered tool whose name collides with a meta-tool would be
211
402
  // listed but never callable (the dispatcher routes the name to the
212
403
  // meta-tool first), so drop it from the listing entirely.
213
404
  let listed = tools.filter((t) => !META_TOOL_NAMES.has(t.mcpName));
214
- // Connection-key sessions are autonomous pipelines — nobody is present
405
+ // Connection-key callers are autonomous pipelines — nobody is present
215
406
  // to complete a sign-in mid-run, so register ONLY the tools whose
216
407
  // per-user credentials are already satisfied. Interactive (OAuth/JWT)
217
- // sessions keep the full listing: their caller can configure a tool on
408
+ // callers keep the full listing: they can configure a tool on
218
409
  // /connect when the call-time check points them there.
219
410
  if (tokenId != null) {
220
411
  const ready = await Promise.all(
221
412
  // A per-tool check that throws must NOT reject the whole list (which
222
413
  // would blank every tool) — fail that one tool closed and move on.
223
414
  listed.map((t) =>
224
- this.missingUserSecrets(userId, t).then(
415
+ this.missingUserSecrets(userId, catalogNames.get(t.manualName) ?? t.manualName).then(
225
416
  (missing) => missing.length === 0,
226
417
  () => false,
227
418
  ),
@@ -247,15 +438,23 @@ export class McpService {
247
438
  continue;
248
439
  }
249
440
  seen.add(entry.name);
250
- direct.push(entry);
441
+ // The four knowledge-base tools carry the purpose prefix: the one
442
+ // pre-call channel every client shows the model, for the clients that
443
+ // drop the handshake's `instructions`. Applied AFTER the credential
444
+ // filter above, so a connection-key caller's listing carries it too.
445
+ // Every other tool, meta-tools included, keeps its description as is.
446
+ direct.push(
447
+ PREFIXED_TOOLS.has(entry.name)
448
+ ? { ...entry, description: prefixToolDescription(toolPrefix, entry.description) }
449
+ : entry,
450
+ );
251
451
  }
252
452
  // Log only when a tool was dropped (name/schema/duplicate) — that's the
253
453
  // anomaly worth surfacing, since a downstream client would otherwise hide
254
- // it by rejecting the whole response. The steady-state served count is
255
- // already visible once per session in the createSession log above.
454
+ // it by rejecting the whole response.
256
455
  if (dropped.length) {
257
- console.warn(
258
- `[mcp] tools/list: serving ${CODE_MODE_META_TOOLS.length + direct.length} tool(s); ` +
456
+ log.warn(
457
+ `tools/list: serving ${CODE_MODE_META_TOOLS.length + direct.length} tool(s); ` +
259
458
  `dropped ${dropped.length} non-listable: ${dropped.join(', ')}`,
260
459
  );
261
460
  }
@@ -266,44 +465,75 @@ export class McpService {
266
465
  });
267
466
 
268
467
  server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
269
- if (META_TOOL_NAMES.has(request.params.name)) {
270
- return this.dispatchMetaTool(client, request.params.name, request.params.arguments ?? {});
468
+ const { client, tools, unavailable, catalogNames } = await requestSurface();
469
+ const toolName = request.params.name;
470
+ const args = request.params.arguments ?? {};
471
+ if (META_TOOL_NAMES.has(toolName)) {
472
+ // A meta-tool is the platform's own; its inner tool calls (a chain's)
473
+ // are recorded one by one through the instrumented client.
474
+ const run = () => this.dispatchMetaTool(client, toolName, args);
475
+ return audit ? audit.call(toolName, args, run, isErrorResult) : run();
271
476
  }
272
- const proxied = tools.find((t) => t.mcpName === request.params.name);
273
- if (!proxied) {
274
- return toolError(`Unknown tool "${request.params.name}".`);
275
- }
276
- // Stop before running a tool whose personal (user-scoped) credentials the
277
- // caller hasn't provided — return a setup link instead of a blank-credential
278
- // request that would fail opaquely at the provider.
279
- const needsAuth = await this.checkUserSecrets(userId, proxied);
280
- if (needsAuth) {
477
+ /**
478
+ * The needs-authorization answer, when the caller's sign-in for the
479
+ * manual registered as `utcpName` is missing or broken; `null` to
480
+ * proceed. Stops before running a tool whose personal (user-scoped)
481
+ * credentials the caller hasn't provided — a setup link instead of a
482
+ * blank-credential request that would fail opaquely at the provider.
483
+ * The credential catalog keys manuals by their CATALOG name; a tool
484
+ * carries the registered (rewritten) one, so it is translated back.
485
+ */
486
+ const needsAuthorization = async (utcpName: string): Promise<CallToolResult | null> => {
487
+ const needsAuth = await this.checkUserSecrets(userId, toolName, catalogNames.get(utcpName) ?? utcpName);
488
+ if (!needsAuth) return null;
281
489
  // A sign-in that EXISTS but is broken (expired grant, abandoned
282
- // consent, missing scopes) on an interactive OAuth session: revoke the
490
+ // consent, missing scopes) on an interactive OAuth caller: revoke the
283
491
  // agent's own grant too. Its next request then 401s, its refresh
284
492
  // fails, and it re-runs the browser authorization — landing the user
285
493
  // on /connect where the broken sign-in shows as not-connected, to
286
494
  // re-authorize or deselect. Never-configured tools keep the plain
287
495
  // link (revoking for those would loop on every poke at a tool the
288
- // user simply hasn't set up). Connection-key sessions have no grant
496
+ // user simply hasn't set up). Connection-key callers have no grant
289
497
  // to reset; the revoke no-ops on non-OAuth bearers anyway.
290
498
  if (needsAuth.brokenSignIn && tokenId == null && this.revokeOAuthAccess) {
291
499
  try {
292
500
  await this.revokeOAuthAccess(bearer);
293
501
  } catch (err) {
294
- console.warn('[mcp] failed to reset the session grant for re-auth:', err);
502
+ log.warn('failed to reset the caller grant for re-auth:', { err });
295
503
  }
296
504
  }
297
505
  return needsAuth.result;
506
+ };
507
+ const proxied = tools.find((t) => t.mcpName === toolName);
508
+ if (!proxied) {
509
+ // Not on the surface — but if the name belongs to a manual that is off
510
+ // it only because this caller's sign-in is gone (and nothing in this
511
+ // process has seen its tools yet), the honest answer is the sign-in
512
+ // link, exactly as if the tool were listed. Longest matching prefix:
513
+ // manual names may themselves contain underscores.
514
+ const owner = unavailable
515
+ .filter((m) => toolName.startsWith(`${m.utcpName}_`))
516
+ .sort((a, b) => b.utcpName.length - a.utcpName.length)[0];
517
+ const answer = owner ? await needsAuthorization(owner.utcpName) : null;
518
+ if (answer && owner && audit) {
519
+ await audit.denied(`${owner.utcpName}.${toolName.slice(owner.utcpName.length + 1)}`, args);
520
+ }
521
+ return answer ?? toolError(retiredToolMessage(toolName) ?? `Unknown tool "${toolName}".`);
298
522
  }
299
- return this.dispatch(client, proxied, request, extra);
523
+ const needsAuth = await needsAuthorization(proxied.manualName);
524
+ if (needsAuth) {
525
+ if (audit) await audit.denied(proxied.utcpName, args);
526
+ return needsAuth;
527
+ }
528
+ const run = () => this.dispatch(client, proxied, request, extra);
529
+ return audit ? audit.call(proxied.utcpName, args, run, isErrorResult) : run();
300
530
  });
301
531
 
302
532
  // Prompts = skills. Each skill becomes a user-callable prompt (slash command
303
533
  // in MCP clients). Backed by the same skill endpoints the tools use, over
304
534
  // loopback with the caller's key — so the default-branch catalog, access
305
535
  // filtering, and progressive disclosure all live ONCE behind that REST
306
- // surface, never duplicated here.
536
+ // surface, never duplicated here. Neither handler needs the tool catalog.
307
537
  server.setRequestHandler(ListPromptsRequestSchema, async () => {
308
538
  const skills = await this.fetchSkillList(loopbackBearer);
309
539
  const prompts: Prompt[] = skills.map((s) => ({
@@ -315,7 +545,14 @@ export class McpService {
315
545
  });
316
546
 
317
547
  server.setRequestHandler(GetPromptRequestSchema, async (request): Promise<GetPromptResult> => {
548
+ const started = performance.now();
318
549
  const skill = await this.fetchSkill(loopbackBearer, request.params.name);
550
+ // A prompt IS a skill read — the same event `get_skill` produces.
551
+ audit?.emit(
552
+ { kind: 'skill', manual: skill?.path ?? null, name: request.params.name },
553
+ skill ? 'ok' : 'error',
554
+ Math.round(performance.now() - started),
555
+ );
319
556
  if (!skill) {
320
557
  throw new McpError(ErrorCode.InvalidParams, `Unknown skill "${request.params.name}".`);
321
558
  }
@@ -325,7 +562,58 @@ export class McpService {
325
562
  };
326
563
  });
327
564
 
328
- return { transport, server };
565
+ return server;
566
+ }
567
+
568
+ /**
569
+ * The request's tool surface: catalog over loopback, a fresh UTCP client,
570
+ * every manual registered (pooled for `mcp` ones), tools flattened.
571
+ *
572
+ * Logs one line with the time each half took — the per-request overhead
573
+ * the stateless design trades for restart-invisibility, kept visible so it
574
+ * is measured rather than assumed.
575
+ */
576
+ private async buildSurface(userId: string, tokenId: string | null, loopbackBearer: string): Promise<RequestSurface> {
577
+ const started = performance.now();
578
+ // The list of manuals this caller gets: the KB manual + each `.tool` they
579
+ // can read. Fetched over loopback with the caller's key, so ACL filtering
580
+ // lives ONCE behind the REST surface.
581
+ const manuals = await this.fetchManualTemplates(loopbackBearer);
582
+ const catalogMs = performance.now() - started;
583
+ const client = await this.buildClient(loopbackBearer, userId, manuals);
584
+ const { tools, unavailable, catalogNames } = await this.discoverTools(client, manuals, userId);
585
+ const totalMs = performance.now() - started;
586
+ // Per user: on a shape change or once per interval, never per request —
587
+ // see SurfaceLogThrottle for why both halves matter.
588
+ const decision = this.surfaceLog.decide(userId, { tools: tools.length, manuals: manuals.length });
589
+ if (decision.log) {
590
+ log.info(
591
+ // Both ids come from the request (an identity provider's subject, a
592
+ // connection key's id), so neither is interpolated raw — see printable.
593
+ `request surface: user=${printable(userId)} tokenId=${tokenId ? printable(tokenId) : 'none'} — ` +
594
+ `${tools.length} tool(s) across ${manuals.length} manual(s) in ${totalMs.toFixed(0)}ms ` +
595
+ `(catalog ${catalogMs.toFixed(0)}ms, registration ${(totalMs - catalogMs).toFixed(0)}ms)` +
596
+ (decision.suppressed > 0 ? ` [+${decision.suppressed} identical rebuild(s) since last line]` : ''),
597
+ );
598
+ }
599
+ return { client, tools, unavailable, catalogNames };
600
+ }
601
+
602
+ /**
603
+ * The instructions and tool prefix. A reader failure (a disk fault; ENOENT
604
+ * is not one, the reader answers null for that) falls back to the header
605
+ * alone and the fixed prefix line with a logged warning: a request never
606
+ * fails over its preamble.
607
+ */
608
+ private async composeAgentInstructions(): Promise<ComposedAgentInstructions> {
609
+ const read = this.opts.readAgentPreamble;
610
+ if (!read) return composeAgentInstructions(null);
611
+ try {
612
+ return composeAgentInstructions(await read());
613
+ } catch (err) {
614
+ log.warn('could not read mcp-description.md; this request gets the platform header alone:', { err });
615
+ return composeAgentInstructions(null);
616
+ }
329
617
  }
330
618
 
331
619
  /** Loopback GET of the default-branch skill catalog (via the `list_skills` tool). */
@@ -348,10 +636,9 @@ export class McpService {
348
636
  * The ONE loopback round-trip: fetch `path` on our own REST surface with the
349
637
  * caller's bearer, parse JSON. Failures are logged under `label` (never the
350
638
  * bearer) and degrade to null — a loopback hiccup must not throw into the
351
- * MCP session. GET when `json` is absent, POST with a JSON body when present.
352
- * Bounded: a hung loopback (e.g. mid-restart) must not block `createSession`
353
- * or a prompts handler indefinitely; a timeout aborts and degrades to null
354
- * like any other failure.
639
+ * MCP request. GET when `json` is absent, POST with a JSON body when present.
640
+ * Bounded: a hung loopback (e.g. mid-restart) must not block a request
641
+ * indefinitely; a timeout aborts and degrades to null like any other failure.
355
642
  */
356
643
  private async loopbackJson(bearer: string, path: string, label: string, json?: unknown): Promise<unknown> {
357
644
  try {
@@ -364,12 +651,12 @@ export class McpService {
364
651
  signal: AbortSignal.timeout(LOOPBACK_TIMEOUT_MS),
365
652
  });
366
653
  if (!res.ok) {
367
- console.error(`[mcp] ${label} loopback failed: HTTP ${res.status} ${await res.text().catch(() => '')}`);
654
+ log.error(`${label} loopback failed: HTTP ${res.status} ${await res.text().catch(() => '')}`);
368
655
  return null;
369
656
  }
370
657
  return await res.json();
371
658
  } catch (err) {
372
- console.error(`[mcp] ${label} loopback threw:`, err instanceof Error ? err.message : err);
659
+ log.error(`${label} loopback threw:`, { err });
373
660
  return null;
374
661
  }
375
662
  }
@@ -384,7 +671,7 @@ export class McpService {
384
671
  * validated into `CallTemplate`s at the boundary. The KB manual is guaranteed
385
672
  * present (a fallback covers an unavailable endpoint); a user manual that
386
673
  * fails validation is dropped + logged so one bad `.tool` can't break the
387
- * session. HTTP transport loses the `CallTemplate` type, so we re-validate the
674
+ * request. HTTP transport loses the `CallTemplate` type, so we re-validate the
388
675
  * received JSON even though the producing endpoint already validated it.
389
676
  */
390
677
  private async fetchManualTemplates(bearer: string): Promise<CallTemplate[]> {
@@ -405,7 +692,7 @@ export class McpService {
405
692
  } catch (err) {
406
693
  const name = String((raw as { name?: unknown })?.name ?? '');
407
694
  if (isKb) throw err; // the KB manual must be valid — the core toolset depends on it
408
- console.warn(`[mcp] skipping manual "${name}": ${err instanceof Error ? err.message : String(err)}`);
695
+ log.warn(`skipping manual "${name}": ${err instanceof Error ? err.message : String(err)}`);
409
696
  }
410
697
  }
411
698
  // Always include the KB manual, even if `all-tools` was unavailable/regressed.
@@ -426,83 +713,385 @@ export class McpService {
426
713
  }
427
714
 
428
715
  /**
429
- * One `CodeModeUtcpClient` per session. Reserved `API_URL`/`CONNECTION_KEY` are
716
+ * One `CodeModeUtcpClient` per request. Reserved `API_URL`/`CONNECTION_KEY` are
430
717
  * seeded (namespaced) ONLY for Bevel-hosted manuals — the KB manual and inline
431
718
  * `.tool` sub-manuals, whose discovery template targets the loopback (`${API_URL}`).
432
719
  * Third-party http/mcp `.tool`s point at arbitrary user URLs, so we must NOT seed
433
720
  * them the caller's bearer: a malicious `.tool` referencing `${CONNECTION_KEY}`
434
721
  * would otherwise exfiltrate the caller's token to its own endpoint. Tool
435
722
  * `${SECRET}` refs resolve lazily via the per-user `bevel-secrets` loader.
723
+ *
724
+ * Not `close()`d: a request's client holds no connection of its own (its
725
+ * `mcp` manuals route to the pool, so its own `mcp` protocol instance never
726
+ * opens a session), and dropping the reference is the whole teardown.
436
727
  */
437
- private async buildClient(
438
- bearer: string,
439
- userId: string,
440
- manuals: CallTemplate[],
441
- ): Promise<CodeModeUtcpClient> {
442
- // Bevel-hosted manuals (KB + inline `.tool` sub-manuals, discovery url
443
- // `${API_URL}/…`) get the loopback creds; third-party `.tool` URLs are never
444
- // seeded so the bearer can't leak. Same rule the in-process agent factory uses.
728
+ private async buildClient(bearer: string, userId: string, manuals: CallTemplate[]): Promise<CodeModeUtcpClient> {
445
729
  const variables = seedBevelHostedManualVars(manuals, this.opts.loopbackBaseUrl, bearer);
446
- const config = new UtcpClientConfigSerializer().validateDict({
447
- variables,
448
- load_variables_from: [bevelSecretsLoaderConfig(userId)],
449
- });
450
- return CodeModeUtcpClient.create(process.cwd(), config);
730
+ return CodeModeUtcpClient.create(process.cwd(), utcpClientConfig(userId, variables));
451
731
  }
452
732
 
453
733
  /**
454
- * Register EVERY manual on the client, then flatten each discovered tool into
455
- * the proxy's advertised shape. A failure registering a user `.tool` is
456
- * isolated (logged + skipped) so one broken manual never breaks the session;
457
- * the KB manual failing is fatal (the core toolset is unusable).
734
+ * Register EVERY manual for this request, then flatten each discovered tool
735
+ * into the proxy's advertised shape. Manuals register concurrently — they are
736
+ * independent, and a request pays for the slowest one rather than the sum. A
737
+ * failure registering a user `.tool` is isolated (logged + skipped) so one
738
+ * broken manual never breaks the request; the KB manual failing is fatal (the
739
+ * core toolset is unusable).
740
+ *
741
+ * `mcp` manuals never dial from here: they are attached from the downstream
742
+ * pool (see {@link attachDownstream}).
458
743
  *
459
- * Failures are memoized per (user, manual) for a few minutes (see
460
- * {@link ManualFailureMemo}): sessions rebuild constantly, and a manual with
461
- * a broken credential (expired OAuth, revoked key) would otherwise re-dial
462
- * its provider on every single build.
744
+ * Failures are memoized per (user, manual definition) for a few minutes (see
745
+ * {@link ManualFailureMemo}): the catalog is rebuilt on every request, and a
746
+ * manual with a broken credential (expired OAuth, revoked key) would
747
+ * otherwise re-dial its provider on every single one. The definition's
748
+ * fingerprint is part of the key, so a manual edited after a failure (a
749
+ * corrected `mcp.json` URL) is tried on the very next request.
463
750
  */
464
751
  private async discoverTools(
465
752
  client: CodeModeUtcpClient,
466
753
  manuals: CallTemplate[],
467
754
  userId: string,
468
- ): Promise<ProxiedTool[]> {
755
+ ): Promise<{ tools: ProxiedTool[]; unavailable: UnavailableManual[]; catalogNames: Map<string, string> }> {
756
+ const routes = new Map<string, DownstreamRoute>();
757
+ const unavailable: UnavailableManual[] = [];
758
+ const catalogNames = new Map<string, string>();
469
759
  for (const m of manuals) {
470
- const isKb = m.name === EXTERNAL_KB_MANUAL_NAME;
471
- const name = String(m.name);
472
- if (!isKb) {
473
- const recent = this.manualFailures.recentFailure(userId, name);
474
- if (recent !== undefined) {
475
- console.warn(`[mcp] skipping manual "${name}" (recent failure, not retried): ${recent}`);
476
- continue;
760
+ if (m.call_template_type === 'mcp') catalogNames.set(utcpManualName(m), String(m.name));
761
+ }
762
+ // The shared layer rewrites every manual name (`[^\w]` → `_`) and tools
763
+ // route by the rewritten prefix, so two manuals whose names rewrite to one
764
+ // identifier would silently share it. Sequential registration used to
765
+ // throw "already registered" for the second; concurrent registration
766
+ // (below) would let both pass the shared layer's pre-check, one
767
+ // overwriting the other in the repository while `routes` kept whichever
768
+ // resolved last — tools/list from one server, tools/call to the other.
769
+ // Decided here, over the whole list, before anything is registered: every
770
+ // manual in a colliding group fails with a message naming the others. The
771
+ // KB manual always keeps its name — a `.tool` colliding with it fails,
772
+ // the KB manual does not.
773
+ const byRewrittenName = new Map<string, string[]>();
774
+ for (const m of manuals) {
775
+ const rewritten = utcpManualName(m);
776
+ byRewrittenName.set(rewritten, [...(byRewrittenName.get(rewritten) ?? []), String(m.name)]);
777
+ }
778
+ const outcomes = await Promise.all(
779
+ manuals.map(async (m) => {
780
+ const isKb = m.name === EXTERNAL_KB_MANUAL_NAME;
781
+ const name = String(m.name);
782
+ // Both taken before registration, which renames the template in place.
783
+ const rewritten = utcpManualName(m);
784
+ const siblings = (byRewrittenName.get(rewritten) ?? []).filter((s) => s !== name);
785
+ // A collision is a property of the LIST, not of this manual's
786
+ // definition, so the memo key carries the siblings: the moment one is
787
+ // renamed away, the key changes and the survivor is tried on the very
788
+ // next request instead of sitting out the memo's TTL.
789
+ const memoKey =
790
+ `${name}${POOL_KEY_SEPARATOR}${templateFingerprint(m)}` +
791
+ (siblings.length > 0 ? `${POOL_KEY_SEPARATOR}collides:${[...siblings].sort().join(',')}` : '');
792
+ if (!isKb) {
793
+ const recent = this.manualFailures.recentFailure(userId, memoKey);
794
+ if (recent !== undefined) {
795
+ log.warn(`skipping manual "${name}" (recent failure, not retried): ${recent}`);
796
+ return { isKb, ok: true as const };
797
+ }
798
+ }
799
+ // Begun BEFORE the awaited attempt: if a secrets change (or a
800
+ // concurrent success of this same manual) clears the memo while
801
+ // registration is in flight, the stale failure must not resurrect an
802
+ // entry the clear removed.
803
+ const generation = this.manualFailures.beginAttempt();
804
+ try {
805
+ // Neither path throws: a discovery/network failure and a validation
806
+ // failure both come back as `{ ok: false }`, because the retry
807
+ // policy — this memo — is ours, not the shared layer's.
808
+ const result: DownstreamAttachResult =
809
+ !isKb && siblings.length > 0
810
+ ? {
811
+ ok: false,
812
+ error:
813
+ `manual name "${name}" rewrites to "${rewritten}", the same identifier as ` +
814
+ `${siblings.map((s) => `"${s}"`).join(', ')} — rename one; a colliding manual is not registered`,
815
+ }
816
+ : m.call_template_type === 'mcp'
817
+ ? await this.attachDownstream(client, m, userId, routes)
818
+ : await registerManual(client, m);
819
+ if (!result.ok) {
820
+ if (isKb) return { isKb, ok: false as const, error: result.error };
821
+ // A failure the manual may recover from on its own schedule (a
822
+ // token refresh that hasn't settled) is reported but NOT
823
+ // remembered: the memo's five minutes would outlast the refresh
824
+ // policy's one and hold the manual down after the provider is back.
825
+ if (!result.retryable) this.manualFailures.recordFailure(userId, memoKey, result.error, generation);
826
+ if (result.signInMissing) unavailable.push({ utcpName: rewritten });
827
+ log.warn(`skipping manual "${name}": ${result.error}`);
828
+ } else if (!isKb) {
829
+ this.manualFailures.clear(userId, memoKey);
830
+ }
831
+ } finally {
832
+ this.manualFailures.endAttempt(generation);
833
+ }
834
+ return { isKb, ok: true as const };
835
+ }),
836
+ );
837
+ const kbFailure = outcomes.find((o) => o.isKb && !o.ok);
838
+ if (kbFailure && !kbFailure.ok) throw new Error(`Bevel tool discovery failed: ${kbFailure.error}`);
839
+ if (routes.size > 0) routeToDownstream(client, routes);
840
+ const utcpTools = await client.getTools();
841
+ return {
842
+ tools: utcpTools.map((tool: UtcpTool) => flattenManualTool(tool, EXTERNAL_KB_MANUAL_NAME)),
843
+ unavailable,
844
+ catalogNames,
845
+ };
846
+ }
847
+
848
+ /**
849
+ * Make one `mcp` manual's tools part of this request's client, backed by the
850
+ * pooled connection for (user, manual).
851
+ *
852
+ * The pooled client's discovered tools are copied into the request client's
853
+ * repository — so `tools/list`, `list_tools`/`tools_info` and the TypeScript
854
+ * interfaces `call_tool_chain` generates all see them exactly as if they had
855
+ * been registered here — and `routes` records that calls to this manual go to
856
+ * the pool. The route re-acquires at CALL time rather than holding the pooled
857
+ * client, so an entry evicted between discovery and the call is transparently
858
+ * re-created instead of used after its connection was closed. Each use holds
859
+ * a pool lease only for its own duration, so the pool never closes a
860
+ * connection an operation is still using.
861
+ */
862
+ private async attachDownstream(
863
+ client: CodeModeUtcpClient,
864
+ template: CallTemplate,
865
+ userId: string,
866
+ routes: Map<string, DownstreamRoute>,
867
+ ): Promise<DownstreamAttachResult> {
868
+ const key = downstreamPoolKey(userId, template);
869
+ // The catalog name, which is what the manual's per-user variables are keyed by.
870
+ const catalogName = String(template.name ?? '');
871
+ const manualName = utcpManualName(template);
872
+ const definition = templateFingerprint(template);
873
+ const acquire = () => this.downstream.acquire(key, () => this.connectDownstream(userId, template));
874
+ const afterFailure = (err: unknown) => this.afterDownstreamFailure(userId, catalogName, key, err);
875
+ const attach = async (tools: UtcpTool[]) => {
876
+ await client.config.tool_repository.saveManual({ ...template, name: manualName }, UtcpManualSchema.parse({ tools }));
877
+ routes.set(manualName, { acquire, afterFailure });
878
+ };
879
+ // Set when the handshake failed on a credential the provider may yet
880
+ // renew: the caller must NOT remember that as a dead manual (see below).
881
+ let credentialTransient = false;
882
+ try {
883
+ let lease: Lease<PooledDownstream>;
884
+ try {
885
+ lease = await acquire();
886
+ } catch (err) {
887
+ // A connection dialed with a token the server refuses fails right here,
888
+ // at the handshake — the same refresh-and-retry applies.
889
+ const verdict = await this.classifyDownstreamFailure(userId, catalogName, key, err);
890
+ if (!verdict.retry) {
891
+ credentialTransient = verdict.credentialTransient;
892
+ throw verdict.error;
477
893
  }
894
+ lease = await acquire();
478
895
  }
479
- // Captured BEFORE the awaited attempt: if a secrets change clears the
480
- // memo while registration is in flight, the stale failure from the OLD
481
- // credential must not resurrect an entry the clear removed.
482
- const generation = this.manualFailures.currentGeneration;
483
- // `registerManual` never throws: a discovery/network failure and a
484
- // validation failure both come back as `{ ok: false }`, because the retry
485
- // policy — this memo — is ours, not the shared layer's.
486
- const result = await registerManual(client, m);
487
- if (!result.ok) {
488
- if (isKb) throw new Error(`Bevel tool discovery failed: ${result.error}`);
489
- this.manualFailures.recordFailure(userId, name, result.error, generation);
490
- console.warn(`[mcp] skipping manual "${name}": ${result.error}`);
491
- } else if (!isKb) {
492
- this.manualFailures.clear(userId, name);
896
+ let tools: UtcpTool[];
897
+ try {
898
+ tools = (await lease.value.client.getTools()).filter((t) => t.name.startsWith(`${manualName}.`));
899
+ } finally {
900
+ lease.release();
493
901
  }
902
+ this.knownDownstreamTools.set(catalogName, { definition, tools });
903
+ await attach(tools);
904
+ return { ok: true };
905
+ } catch (err) {
906
+ const error = err instanceof Error ? err.message : String(err);
907
+ // No connection because this CALLER has no usable sign-in for the manual
908
+ // (never signed in, or the grant was just found dead and wiped): the
909
+ // manual is not broken, the sign-in is. Keep its tools on the surface
910
+ // from what the downstream last advertised, so a call answers with the
911
+ // sign-in link — and never remember the failure, since re-authorizing is
912
+ // one visit to /connect away and the next attempt costs no network.
913
+ if (await this.signInMissing(userId, catalogName)) {
914
+ const known = this.knownDownstreamTools.get(catalogName);
915
+ if (known && known.definition === definition) {
916
+ await attach(known.tools);
917
+ return { ok: true };
918
+ }
919
+ return { ok: false, error, retryable: true, signInMissing: true };
920
+ }
921
+ // `retryable` keeps a transient credential failure out of the caller's
922
+ // five-minute failure memo: the refresh-and-retry policy for a rejected
923
+ // token is one minute, and a memo entry would silently outlast it.
924
+ return { ok: false, error, retryable: credentialTransient };
925
+ }
926
+ }
927
+
928
+ /** Whether the caller's sign-in for the manual named `catalogName` is missing or broken — a check that must never throw here. */
929
+ private async signInMissing(userId: string, catalogName: string): Promise<boolean> {
930
+ try {
931
+ return (await this.missingUserSecrets(userId, catalogName)).length > 0;
932
+ } catch {
933
+ return false;
494
934
  }
495
- const utcpTools = await client.getTools();
496
- return utcpTools.map((tool: UtcpTool) => flattenManualTool(tool, EXTERNAL_KB_MANUAL_NAME));
497
935
  }
498
936
 
499
937
  /**
500
- * Run one tool call through `callToolStreaming` with a one-chunk lookahead:
501
- * every chunk except the last becomes a progress notification, the last is
502
- * the result. Continuity is the caller's: a tool that supports it (e.g.
503
- * `ask`) returns its `sessionId` in the result verbatim, and the caller
504
- * echoes it back per the tool's own schema — the proxy never rewrites args.
938
+ * Create one pooled downstream connection: a client registering just this
939
+ * manual.
940
+ *
941
+ * `@utcp/mcp` (1.2.0+) registers its protocol as a FACTORY, so this client
942
+ * gets an `mcp` protocol instance of its own: the session it opens lives in
943
+ * that instance, is reachable from no other client, and ends with
944
+ * `client.close()`. That is what makes a pool entry per (user, server) an
945
+ * actual isolation boundary — two users with byte-identical server
946
+ * definitions still never share a connection — with nothing reached into.
947
+ *
948
+ * SESSION RECOVERY is installed on the pooled client: a downstream server
949
+ * that restarts (or expires a session) answers our next call with the spec's
950
+ * 404/`-32001`, and recovery re-registers in place and retries once. It is
951
+ * deliberately clear of {@link ManualFailureMemo}: that memo is a circuit
952
+ * breaker for manuals whose REGISTRATION failed, so a recovery neither
953
+ * consults it nor records into it.
954
+ *
955
+ * A registration that fails closes the client it built (ending whatever the
956
+ * attempt opened) and throws, so the pool caches nothing and the caller's
957
+ * memo records the failure.
505
958
  */
959
+ private async connectDownstream(userId: string, template: CallTemplate): Promise<PooledDownstream> {
960
+ // Third-party manuals are never seeded loopback credentials (see
961
+ // `buildClient`), so a pooled client carries no request-scoped bearer.
962
+ const client = await CodeModeUtcpClient.create(process.cwd(), utcpClientConfig(userId, {}));
963
+ // Our own copy: registration renames the template in place, and recovery
964
+ // re-registers from exactly what discovery used.
965
+ const own = structuredClone(template);
966
+ const manualName = utcpManualName(own);
967
+ installSessionRecovery(client, { manualTemplate: (name) => (name === manualName ? own : undefined) });
968
+ const result = await registerManual(client, own);
969
+ if (!result.ok) {
970
+ await client.close().catch(() => {});
971
+ throw new Error(result.error);
972
+ }
973
+ return { client };
974
+ }
975
+
976
+ /**
977
+ * A pooled manual's operation failed: may it be retried with a refreshed
978
+ * token? Answers {@link RETRY_WITH_REFRESHED_TOKEN} when so, otherwise the
979
+ * error the caller should see.
980
+ *
981
+ * Only a token rejection (401 / `invalid_token`) qualifies. Then the
982
+ * caller's OAuth token for this manual is refreshed once, ignoring its
983
+ * stored expiry, and:
984
+ * - refreshed → the pooled connection (dialed with the dead token) is
985
+ * dropped and the operation retried once, on a fresh one;
986
+ * - rejected → the grant is gone and the vault has wiped it; the caller is
987
+ * told to re-authorize on /connect, where it now shows as not connected;
988
+ * - transient, or no refresh allowed / possible → the original error.
989
+ */
990
+ private async afterDownstreamFailure(
991
+ userId: string,
992
+ manualName: string,
993
+ poolKey: string,
994
+ err: unknown,
995
+ ): Promise<unknown> {
996
+ const verdict = await this.classifyDownstreamFailure(userId, manualName, poolKey, err);
997
+ return verdict.retry ? RETRY_WITH_REFRESHED_TOKEN : verdict.error;
998
+ }
999
+
1000
+ /** {@link afterDownstreamFailure}, keeping the one distinction its symbol-or-error answer drops. */
1001
+ private async classifyDownstreamFailure(
1002
+ userId: string,
1003
+ manualName: string,
1004
+ poolKey: string,
1005
+ err: unknown,
1006
+ ): Promise<DownstreamFailure> {
1007
+ if (!isDownstreamTokenRejection(err)) return { retry: false, error: err, credentialTransient: false };
1008
+ const outcome = await this.refreshDownstreamToken(userId, manualName);
1009
+ if (outcome === 'refreshed') {
1010
+ // The vault's mutation signal evicts this user's connections too; doing it
1011
+ // here as well keeps the retry correct whether or not that is wired.
1012
+ this.downstream.evictWhere((k) => k === poolKey);
1013
+ return { retry: true };
1014
+ }
1015
+ if (outcome === 'rejected') {
1016
+ // The connection was dialed with a grant that no longer exists; the
1017
+ // vault's mutation signal drops it too, this keeps the next request
1018
+ // honest whether or not that is wired.
1019
+ this.downstream.evictWhere((k) => k === poolKey);
1020
+ return {
1021
+ retry: false,
1022
+ credentialTransient: false,
1023
+ error: new Error(
1024
+ `Your sign-in for "${manualName}" was rejected and has been disconnected. ` +
1025
+ `Re-authorize it on ${this.opts.publicFrontendUrl}/connect, then run the tool again.`,
1026
+ ),
1027
+ };
1028
+ }
1029
+ // The token stands and so does the call's own error. Whether the NEXT
1030
+ // call may try again depends on why: a refresh that failed transiently, or
1031
+ // one the window refused, will be worth attempting shortly — while
1032
+ // "nothing here to refresh" (no vault, no OAuth variable, no stored token)
1033
+ // is a standing condition, and the failure memo should hold the manual
1034
+ // down exactly as it does for any other broken credential.
1035
+ const mayRecoverSoon = outcome === 'transient' || outcome === 'guarded';
1036
+ return { retry: false, error: err, credentialTransient: mayRecoverSoon };
1037
+ }
1038
+
1039
+ /**
1040
+ * Force-refresh the caller's OAuth token(s) for `manualName`, at most once per
1041
+ * (user, manual) per minute. Logs ONE line per attempt — manual, user,
1042
+ * outcome, never token material.
1043
+ *
1044
+ * `'guarded'` when the window refused this one (a refresh just ran, or is
1045
+ * running, for this pair); `undefined` when there was nothing to refresh at
1046
+ * all: no vault wired, or no OAuth variable on the manual. The two are not
1047
+ * the same thing to the caller — see {@link classifyDownstreamFailure}.
1048
+ */
1049
+ private async refreshDownstreamToken(
1050
+ userId: string,
1051
+ manualName: string,
1052
+ ): Promise<ForcedRefreshOutcome | 'guarded' | undefined> {
1053
+ const vault = this.secretsVault;
1054
+ if (!vault || !this.toolManuals || !manualName) return undefined;
1055
+ let keys: string[];
1056
+ try {
1057
+ keys = (await this.toolManuals.userScopedKeysForManual(manualName)).filter((v) => v.oauth).map((v) => v.key);
1058
+ } catch (err) {
1059
+ // The error's own message is caller-controlled as well (it quotes the
1060
+ // manual, and may quote a provider's reply), and a raw Error handed to
1061
+ // the logger renders its message and stack verbatim — so it goes through
1062
+ // the same escaper rather than travelling as an object.
1063
+ log.warn(
1064
+ `downstream token refresh: could not read the variables of manual=${printable(manualName)}: ` +
1065
+ printable(err instanceof Error ? err.message : String(err)),
1066
+ );
1067
+ return undefined;
1068
+ }
1069
+ if (keys.length === 0) return undefined;
1070
+ const attempt = this.tokenRefreshes.run(`${userId}${POOL_KEY_SEPARATOR}${manualName}`, async () => {
1071
+ const outcomes: ForcedRefreshOutcome[] = [];
1072
+ for (const key of keys) {
1073
+ // A vault fault is not a verdict on the grant: keep the token, try later.
1074
+ outcomes.push(await vault.forceRefresh(userId, key).catch((): ForcedRefreshOutcome => 'transient'));
1075
+ }
1076
+ const outcome: ForcedRefreshOutcome = outcomes.includes('rejected')
1077
+ ? 'rejected'
1078
+ : outcomes.includes('transient')
1079
+ ? 'transient'
1080
+ : outcomes.includes('refreshed')
1081
+ ? 'refreshed'
1082
+ : 'skipped';
1083
+ if (outcome !== 'skipped') {
1084
+ // Both values reach the log from outside (a catalog name, an identity
1085
+ // provider's id), so both are escaped: one event stays one line.
1086
+ log.info(
1087
+ `downstream token refresh: manual=${printable(manualName)} user=${printable(userId)} outcome=${outcome}`,
1088
+ );
1089
+ }
1090
+ return outcome;
1091
+ });
1092
+ return attempt ? await attempt : 'guarded';
1093
+ }
1094
+
506
1095
  /**
507
1096
  * If the tool declares per-user credentials the caller hasn't set, return a
508
1097
  * needs-authorization result (naming the tool + a setup link); otherwise null
@@ -514,19 +1103,20 @@ export class McpService {
514
1103
  */
515
1104
  private async checkUserSecrets(
516
1105
  userId: string,
517
- tool: ProxiedTool,
1106
+ toolName: string,
1107
+ manualName: string,
518
1108
  ): Promise<{ result: CallToolResult; brokenSignIn: boolean } | null> {
519
- const missing = await this.missingUserSecrets(userId, tool);
1109
+ const missing = await this.missingUserSecrets(userId, manualName);
520
1110
  if (missing.length === 0) return null;
521
1111
  return {
522
1112
  result: needsAuthorizationResult(
523
- tool.mcpName,
1113
+ toolName,
524
1114
  missing.map((v) => v.label ?? v.name),
525
1115
  `${this.opts.publicFrontendUrl}/connect`,
526
1116
  ),
527
1117
  // At least one missing item is a sign-in the caller already HAS a row
528
1118
  // for (dead grant, abandoned consent, or missing scopes) — the state
529
- // that warrants resetting an interactive session for re-auth, as
1119
+ // that warrants resetting an interactive client for re-auth, as
530
1120
  // opposed to a tool the caller never set up at all.
531
1121
  brokenSignIn: missing.some((v) => v.brokenSignIn),
532
1122
  };
@@ -535,16 +1125,16 @@ export class McpService {
535
1125
  /**
536
1126
  * The per-user variables of `tool`'s manual the caller has NOT satisfied.
537
1127
  * Shared by the call-time needs-authorization check above and the
538
- * listing-time filter for connection-key sessions (which registers only
1128
+ * listing-time filter for connection-key callers (which registers only
539
1129
  * ready tools). Empty when the vault/manual services aren't wired or the
540
1130
  * manual declares no per-user credential.
541
1131
  */
542
1132
  private async missingUserSecrets(
543
1133
  userId: string,
544
- tool: ProxiedTool,
1134
+ manualName: string,
545
1135
  ): Promise<{ name: string; label?: string | null; brokenSignIn: boolean }[]> {
546
- if (!this.secretsVault || !this.toolManuals || !tool.manualName) return [];
547
- const userVars = await this.toolManuals.userScopedKeysForManual(tool.manualName);
1136
+ if (!this.secretsVault || !this.toolManuals || !manualName) return [];
1137
+ const userVars = await this.toolManuals.userScopedKeysForManual(manualName);
548
1138
  if (userVars.length === 0) return [];
549
1139
  const status = await this.secretsVault.statusFor(
550
1140
  userId,
@@ -561,6 +1151,17 @@ export class McpService {
561
1151
  missing.push({ name: v.name, label: v.label, brokenSignIn }); // no row at all → needs a value / sign-in
562
1152
  continue;
563
1153
  }
1154
+ // A row of the OTHER kind is not this credential: a `.tool` edited from
1155
+ // an OAuth sign-in to a plain key (or back) leaves the old row behind,
1156
+ // and serving a token where a key is declared — or a key where a
1157
+ // sign-in is — fails opaquely at the provider. Not a broken sign-in
1158
+ // either: nothing to reset, the user has to set the credential up as
1159
+ // the manual now declares it.
1160
+ const declaredKind = v.oauth ? 'oauth' : 'static';
1161
+ if (st.userKind && st.userKind !== declaredKind) {
1162
+ missing.push({ name: v.name, label: v.label, brokenSignIn: false });
1163
+ continue;
1164
+ }
564
1165
  // An OAuth-backed var whose row exists but has no token yet is NOT ready —
565
1166
  // the user has registered but not completed sign-in. Fail closed: anything
566
1167
  // other than a confirmed `true` (including a non-oauth row → undefined)
@@ -581,6 +1182,14 @@ export class McpService {
581
1182
  return missing;
582
1183
  }
583
1184
 
1185
+ /**
1186
+ * Run one tool call through `callToolStreaming` with a one-chunk lookahead:
1187
+ * every chunk except the last becomes a progress notification — sent on THIS
1188
+ * request's own response stream — and the last is the result. Continuity is
1189
+ * the caller's: a tool that supports it (e.g. `ask`) returns its `sessionId`
1190
+ * in the result verbatim, and the caller echoes it back per the tool's own
1191
+ * schema — the proxy never rewrites args.
1192
+ */
584
1193
  private async dispatch(
585
1194
  client: CodeModeUtcpClient,
586
1195
  tool: ProxiedTool,
@@ -588,7 +1197,7 @@ export class McpService {
588
1197
  // `sendNotification` typed loosely (`any`) so a `notifications/progress`
589
1198
  // payload without a `progressToken` is accepted — same approach the prior
590
1199
  // handler used; the strict ServerNotification type requires the token.
591
- extra: { sessionId?: string; sendNotification: (n: any) => Promise<void> },
1200
+ extra: { sendNotification: (n: any) => Promise<void> },
592
1201
  ): Promise<CallToolResult> {
593
1202
  const progressToken = request.params._meta?.progressToken;
594
1203
  return dispatchToolCall(client, tool, request.params.arguments ?? {}, (progress, message) =>
@@ -605,8 +1214,8 @@ export class McpService {
605
1214
 
606
1215
  /**
607
1216
  * Handle a code-mode meta-tool. The shared implementation reflects on this
608
- * session's client, so `list_tools`/`tools_info` describe exactly the catalog
609
- * this session discovered and `call_tool_chain` runs in that client's
1217
+ * request's client, so `list_tools`/`tools_info` describe exactly the catalog
1218
+ * this request discovered and `call_tool_chain` runs in that client's
610
1219
  * isolated-vm — resolving over loopback with the caller's key. The workspace
611
1220
  * spill store is passed in so an oversized chain result comes back as a
612
1221
  * `read_file`-able ref rather than a wall of JSON.
@@ -620,6 +1229,125 @@ export class McpService {
620
1229
  }
621
1230
  }
622
1231
 
1232
+ /** Whether a tool result is the tool saying it failed — what the Audit log records as `error`. */
1233
+ function isErrorResult(result: CallToolResult): boolean {
1234
+ return result.isError === true;
1235
+ }
1236
+
1237
+ /**
1238
+ * The UTCP client config for `userId`: the given variables, plus the per-user
1239
+ * `bevel-secrets` loader that resolves every `${SECRET}` ref lazily. Shared by
1240
+ * request clients and pooled downstream clients, so both resolve a manual's
1241
+ * variables through exactly the same tiers.
1242
+ */
1243
+ function utcpClientConfig(userId: string, variables: Record<string, string>) {
1244
+ return new UtcpClientConfigSerializer().validateDict({
1245
+ variables,
1246
+ load_variables_from: [bevelSecretsLoaderConfig(userId)],
1247
+ });
1248
+ }
1249
+
1250
+ /**
1251
+ * The name a manual is registered under. `UtcpClient.registerManual` rewrites
1252
+ * every non-word character to `_`, and tool names are prefixed with the result,
1253
+ * so routing has to key on the same rewrite.
1254
+ */
1255
+ function utcpManualName(template: CallTemplate): string {
1256
+ return String(template.name ?? '').replace(/[^\w]/g, '_');
1257
+ }
1258
+
1259
+ /**
1260
+ * The pool key for (user, manual). The template's fingerprint is part of it, so
1261
+ * an edited `mcp.json` (new URL, new headers) gets a new connection rather than
1262
+ * the one dialed for the old definition, which simply idles out. Hashed: the
1263
+ * template can carry credentials and the key must stay log-safe.
1264
+ */
1265
+ function downstreamPoolKey(userId: string, template: CallTemplate): string {
1266
+ return [userId, utcpManualName(template), templateFingerprint(template)].join(POOL_KEY_SEPARATOR);
1267
+ }
1268
+
1269
+ /** A short, log-safe hash of a manual's whole definition: changes whenever the definition does. */
1270
+ function templateFingerprint(template: CallTemplate): string {
1271
+ return createHash('sha256').update(JSON.stringify(template)).digest('hex').slice(0, 16);
1272
+ }
1273
+
1274
+ /**
1275
+ * Route tool calls for pooled manuals to their pooled client, in place on the
1276
+ * request client. Both entry points are wrapped — `callToolStreaming` is the
1277
+ * MCP dispatch path, `callTool` is what `call_tool_chain` bridges every
1278
+ * in-isolate tool function to — so the two can never disagree. Every other
1279
+ * manual's calls go through the request client unchanged.
1280
+ *
1281
+ * Each routed call holds its pool lease until the call ends — for a stream,
1282
+ * until the consumer finishes or abandons it (`for await` returns the
1283
+ * generator, which runs the `finally`).
1284
+ *
1285
+ * A call the downstream refuses for its token (401 / `invalid_token`) goes to
1286
+ * the route's `afterFailure`, which may refresh the token and ask for ONE retry
1287
+ * on a fresh connection. Retrying is safe for the same reason session recovery
1288
+ * is: authentication is decided before the request reaches a tool, so the
1289
+ * refused attempt ran nothing.
1290
+ */
1291
+ function routeToDownstream(client: CodeModeUtcpClient, routes: ReadonlyMap<string, DownstreamRoute>): void {
1292
+ const callTool = client.callTool.bind(client);
1293
+ const callToolStreaming = client.callToolStreaming.bind(client);
1294
+ const routeOf = (toolName: string) => routes.get(toolName.split('.')[0] ?? '');
1295
+
1296
+ client.callTool = async function routedCallTool(toolName: string, toolArgs: Record<string, unknown>) {
1297
+ const route = routeOf(toolName);
1298
+ if (!route) return callTool(toolName, toolArgs);
1299
+ const once = async () => {
1300
+ const lease = await route.acquire();
1301
+ try {
1302
+ return await lease.value.client.callTool(toolName, toolArgs);
1303
+ } finally {
1304
+ lease.release();
1305
+ }
1306
+ };
1307
+ try {
1308
+ return await once();
1309
+ } catch (err) {
1310
+ const next = await route.afterFailure(err);
1311
+ if (next !== RETRY_WITH_REFRESHED_TOKEN) throw next;
1312
+ // Exactly one retry: whatever it produces is what the caller sees.
1313
+ return await once();
1314
+ }
1315
+ };
1316
+ client.callToolStreaming = async function* routedCallToolStreaming(
1317
+ toolName: string,
1318
+ toolArgs: Record<string, unknown>,
1319
+ ): AsyncGenerator<unknown, void, unknown> {
1320
+ const route = routeOf(toolName);
1321
+ if (!route) {
1322
+ yield* callToolStreaming(toolName, toolArgs);
1323
+ return;
1324
+ }
1325
+ const once = async function* () {
1326
+ const lease = await route.acquire();
1327
+ try {
1328
+ yield* lease.value.client.callToolStreaming(toolName, toolArgs);
1329
+ } finally {
1330
+ lease.release();
1331
+ }
1332
+ };
1333
+ let yielded = false;
1334
+ try {
1335
+ for await (const chunk of once()) {
1336
+ yielded = true;
1337
+ yield chunk;
1338
+ }
1339
+ return;
1340
+ } catch (err) {
1341
+ // A stream that already produced output was accepted — a rejection can
1342
+ // only come before the first chunk, and replaying would duplicate output.
1343
+ if (yielded) throw err;
1344
+ const next = await route.afterFailure(err);
1345
+ if (next !== RETRY_WITH_REFRESHED_TOKEN) throw next;
1346
+ }
1347
+ yield* once();
1348
+ };
1349
+ }
1350
+
623
1351
  /**
624
1352
  * The pieces of the proxy that are shared with the local MCP server now live in
625
1353
  * `@bevel-software/platform-mcp-core`. Re-exported here because this module is