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