@bevel-software/platform-core-backend 0.33.0 → 0.34.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 (497) hide show
  1. package/agent-guide/access-control.md +19 -8
  2. package/agent-guide/html-views.md +231 -5
  3. package/agent-guide/tool-manuals.md +1 -1
  4. package/dist/core/catalog-revision.d.ts +12 -1
  5. package/dist/core/catalog-revision.d.ts.map +1 -1
  6. package/dist/core/catalog-revision.js +23 -3
  7. package/dist/core/catalog-revision.js.map +1 -1
  8. package/dist/core/core-ports.d.ts +41 -0
  9. package/dist/core/core-ports.d.ts.map +1 -1
  10. package/dist/core/core-ports.js.map +1 -1
  11. package/dist/core/create-core-server.d.ts.map +1 -1
  12. package/dist/core/create-core-server.js +14 -7
  13. package/dist/core/create-core-server.js.map +1 -1
  14. package/dist/core/create-core-services.d.ts +16 -5
  15. package/dist/core/create-core-services.d.ts.map +1 -1
  16. package/dist/core/create-core-services.js +37 -12
  17. package/dist/core/create-core-services.js.map +1 -1
  18. package/dist/core/lifecycle.d.ts +16 -0
  19. package/dist/core/lifecycle.d.ts.map +1 -1
  20. package/dist/core/lifecycle.js +12 -2
  21. package/dist/core/lifecycle.js.map +1 -1
  22. package/dist/index.d.ts +1 -1
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/modules/access/access-control.interface.d.ts +17 -9
  26. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  27. package/dist/modules/access/access-control.service.d.ts +12 -2
  28. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  29. package/dist/modules/access/access-control.service.js +164 -75
  30. package/dist/modules/access/access-control.service.js.map +1 -1
  31. package/dist/modules/access/access-mutation.service.d.ts +22 -24
  32. package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
  33. package/dist/modules/access/access-mutation.service.js +48 -51
  34. package/dist/modules/access/access-mutation.service.js.map +1 -1
  35. package/dist/modules/access/access-view.d.ts.map +1 -1
  36. package/dist/modules/access/access-view.js +12 -11
  37. package/dist/modules/access/access-view.js.map +1 -1
  38. package/dist/modules/access/access.routes.d.ts +3 -0
  39. package/dist/modules/access/access.routes.d.ts.map +1 -1
  40. package/dist/modules/access/access.routes.js +35 -29
  41. package/dist/modules/access/access.routes.js.map +1 -1
  42. package/dist/modules/access/groups-admin.routes.d.ts.map +1 -1
  43. package/dist/modules/access/groups-admin.routes.js +4 -2
  44. package/dist/modules/access/groups-admin.routes.js.map +1 -1
  45. package/dist/modules/access/groups-admin.service.d.ts +44 -13
  46. package/dist/modules/access/groups-admin.service.d.ts.map +1 -1
  47. package/dist/modules/access/groups-admin.service.js +80 -38
  48. package/dist/modules/access/groups-admin.service.js.map +1 -1
  49. package/dist/modules/access/groups-edit.d.ts +1 -1
  50. package/dist/modules/access/groups-edit.d.ts.map +1 -1
  51. package/dist/modules/access/groups-edit.js +1 -1
  52. package/dist/modules/access/groups-edit.js.map +1 -1
  53. package/dist/modules/access/reference-scan.d.ts +116 -24
  54. package/dist/modules/access/reference-scan.d.ts.map +1 -1
  55. package/dist/modules/access/reference-scan.js +305 -44
  56. package/dist/modules/access/reference-scan.js.map +1 -1
  57. package/dist/modules/access/roles-admin.service.d.ts +17 -8
  58. package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
  59. package/dist/modules/access/roles-admin.service.js +32 -23
  60. package/dist/modules/access/roles-admin.service.js.map +1 -1
  61. package/dist/modules/access-model/access-grammar.d.ts +35 -10
  62. package/dist/modules/access-model/access-grammar.d.ts.map +1 -1
  63. package/dist/modules/access-model/access-grammar.js +60 -20
  64. package/dist/modules/access-model/access-grammar.js.map +1 -1
  65. package/dist/modules/access-model/access-splice.d.ts +22 -18
  66. package/dist/modules/access-model/access-splice.d.ts.map +1 -1
  67. package/dist/modules/access-model/access-splice.js +20 -14
  68. package/dist/modules/access-model/access-splice.js.map +1 -1
  69. package/dist/modules/access-model/group-files.d.ts +1 -0
  70. package/dist/modules/access-model/group-files.d.ts.map +1 -1
  71. package/dist/modules/access-model/group-files.js +5 -1
  72. package/dist/modules/access-model/group-files.js.map +1 -1
  73. package/dist/modules/agent-guide/agent-guide.tools.d.ts.map +1 -1
  74. package/dist/modules/agent-guide/agent-guide.tools.js +13 -3
  75. package/dist/modules/agent-guide/agent-guide.tools.js.map +1 -1
  76. package/dist/modules/agent-instructions/compose.d.ts +3 -1
  77. package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
  78. package/dist/modules/agent-instructions/compose.js +3 -1
  79. package/dist/modules/agent-instructions/compose.js.map +1 -1
  80. package/dist/modules/agent-instructions/shared-file-rules.d.ts +5 -1
  81. package/dist/modules/agent-instructions/shared-file-rules.d.ts.map +1 -1
  82. package/dist/modules/agent-instructions/shared-file-rules.js +16 -1
  83. package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
  84. package/dist/modules/agent-scope/agent-scope.contract.d.ts +67 -0
  85. package/dist/modules/agent-scope/agent-scope.contract.d.ts.map +1 -0
  86. package/dist/modules/agent-scope/agent-scope.contract.js +27 -0
  87. package/dist/modules/agent-scope/agent-scope.contract.js.map +1 -0
  88. package/dist/modules/agent-scope/connection-scope.d.ts +111 -0
  89. package/dist/modules/agent-scope/connection-scope.d.ts.map +1 -0
  90. package/dist/modules/agent-scope/connection-scope.js +165 -0
  91. package/dist/modules/agent-scope/connection-scope.js.map +1 -0
  92. package/dist/modules/agent-scope/index.d.ts +3 -0
  93. package/dist/modules/agent-scope/index.d.ts.map +1 -0
  94. package/dist/modules/agent-scope/index.js +3 -0
  95. package/dist/modules/agent-scope/index.js.map +1 -0
  96. package/dist/modules/audit/agent-audit.service.d.ts.map +1 -1
  97. package/dist/modules/audit/agent-audit.service.js +2 -0
  98. package/dist/modules/audit/agent-audit.service.js.map +1 -1
  99. package/dist/modules/audit/audit.contract.d.ts +6 -0
  100. package/dist/modules/audit/audit.contract.d.ts.map +1 -1
  101. package/dist/modules/audit/audit.contract.js.map +1 -1
  102. package/dist/modules/auth/account-admission.d.ts +12 -0
  103. package/dist/modules/auth/account-admission.d.ts.map +1 -1
  104. package/dist/modules/auth/account-admission.js +15 -0
  105. package/dist/modules/auth/account-admission.js.map +1 -1
  106. package/dist/modules/auth/auth.middleware.d.ts +27 -1
  107. package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
  108. package/dist/modules/auth/auth.middleware.js +42 -4
  109. package/dist/modules/auth/auth.middleware.js.map +1 -1
  110. package/dist/modules/auth/auth.routes.d.ts +13 -9
  111. package/dist/modules/auth/auth.routes.d.ts.map +1 -1
  112. package/dist/modules/auth/auth.routes.js +35 -29
  113. package/dist/modules/auth/auth.routes.js.map +1 -1
  114. package/dist/modules/auth/auth.service.d.ts +55 -6
  115. package/dist/modules/auth/auth.service.d.ts.map +1 -1
  116. package/dist/modules/auth/auth.service.js +128 -34
  117. package/dist/modules/auth/auth.service.js.map +1 -1
  118. package/dist/modules/auth/index.d.ts +1 -1
  119. package/dist/modules/auth/index.d.ts.map +1 -1
  120. package/dist/modules/auth/index.js +1 -1
  121. package/dist/modules/auth/index.js.map +1 -1
  122. package/dist/modules/auth/oidc-auth-provider.d.ts.map +1 -1
  123. package/dist/modules/auth/oidc-auth-provider.js +2 -9
  124. package/dist/modules/auth/oidc-auth-provider.js.map +1 -1
  125. package/dist/modules/database/core-schema.d.ts +68 -0
  126. package/dist/modules/database/core-schema.d.ts.map +1 -1
  127. package/dist/modules/database/core-schema.js +33 -0
  128. package/dist/modules/database/core-schema.js.map +1 -1
  129. package/dist/modules/declared-variables/declared-variables.routes.d.ts +8 -1
  130. package/dist/modules/declared-variables/declared-variables.routes.d.ts.map +1 -1
  131. package/dist/modules/declared-variables/declared-variables.routes.js +13 -2
  132. package/dist/modules/declared-variables/declared-variables.routes.js.map +1 -1
  133. package/dist/modules/embed/embed.tools.d.ts.map +1 -1
  134. package/dist/modules/embed/embed.tools.js +5 -0
  135. package/dist/modules/embed/embed.tools.js.map +1 -1
  136. package/dist/modules/kb-fs/index.d.ts +2 -0
  137. package/dist/modules/kb-fs/index.d.ts.map +1 -1
  138. package/dist/modules/kb-fs/index.js +1 -0
  139. package/dist/modules/kb-fs/index.js.map +1 -1
  140. package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
  141. package/dist/modules/kb-fs/locking-filesystem.js +7 -1
  142. package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
  143. package/dist/modules/kb-fs/repo-path.d.ts +26 -6
  144. package/dist/modules/kb-fs/repo-path.d.ts.map +1 -1
  145. package/dist/modules/kb-fs/repo-path.js +22 -7
  146. package/dist/modules/kb-fs/repo-path.js.map +1 -1
  147. package/dist/modules/kb-fs/tmp-ref.d.ts +36 -0
  148. package/dist/modules/kb-fs/tmp-ref.d.ts.map +1 -0
  149. package/dist/modules/kb-fs/tmp-ref.js +76 -0
  150. package/dist/modules/kb-fs/tmp-ref.js.map +1 -0
  151. package/dist/modules/mcp/mcp-auth.middleware.d.ts +7 -0
  152. package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
  153. package/dist/modules/mcp/mcp-auth.middleware.js +14 -1
  154. package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
  155. package/dist/modules/mcp/mcp.routes.d.ts +7 -1
  156. package/dist/modules/mcp/mcp.routes.d.ts.map +1 -1
  157. package/dist/modules/mcp/mcp.routes.js +39 -4
  158. package/dist/modules/mcp/mcp.routes.js.map +1 -1
  159. package/dist/modules/mcp/mcp.service.d.ts +11 -0
  160. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  161. package/dist/modules/mcp/mcp.service.js +68 -13
  162. package/dist/modules/mcp/mcp.service.js.map +1 -1
  163. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +24 -1
  164. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
  165. package/dist/modules/mcp/oauth/bevel-oauth-provider.js +45 -10
  166. package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
  167. package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts +9 -0
  168. package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts.map +1 -1
  169. package/dist/modules/mcp/oauth/oauth-consent.routes.js +34 -1
  170. package/dist/modules/mcp/oauth/oauth-consent.routes.js.map +1 -1
  171. package/dist/modules/plugins/index.d.ts +1 -1
  172. package/dist/modules/plugins/index.d.ts.map +1 -1
  173. package/dist/modules/plugins/index.js.map +1 -1
  174. package/dist/modules/plugins/plugin-links.d.ts +18 -2
  175. package/dist/modules/plugins/plugin-links.d.ts.map +1 -1
  176. package/dist/modules/plugins/plugin-links.js.map +1 -1
  177. package/dist/modules/plugins/plugins.routes.d.ts +5 -1
  178. package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
  179. package/dist/modules/plugins/plugins.routes.js +32 -5
  180. package/dist/modules/plugins/plugins.routes.js.map +1 -1
  181. package/dist/modules/secrets-vault/secrets-vault.routes.d.ts +9 -0
  182. package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
  183. package/dist/modules/secrets-vault/secrets-vault.routes.js +15 -3
  184. package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
  185. package/dist/modules/skills/index.d.ts +3 -3
  186. package/dist/modules/skills/index.d.ts.map +1 -1
  187. package/dist/modules/skills/index.js +0 -2
  188. package/dist/modules/skills/index.js.map +1 -1
  189. package/dist/modules/skills/skills.contract.d.ts +16 -0
  190. package/dist/modules/skills/skills.contract.d.ts.map +1 -1
  191. package/dist/modules/skills/skills.service.d.ts.map +1 -1
  192. package/dist/modules/skills/skills.service.js +6 -1
  193. package/dist/modules/skills/skills.service.js.map +1 -1
  194. package/dist/modules/skills/skills.tools.d.ts.map +1 -1
  195. package/dist/modules/skills/skills.tools.js +24 -12
  196. package/dist/modules/skills/skills.tools.js.map +1 -1
  197. package/dist/modules/tool-auth/external-api-key.interface.d.ts +11 -0
  198. package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
  199. package/dist/modules/tool-auth/external-api-key.interface.js.map +1 -1
  200. package/dist/modules/tool-auth/external-api-key.service.d.ts +1 -0
  201. package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
  202. package/dist/modules/tool-auth/external-api-key.service.js +4 -1
  203. package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
  204. package/dist/modules/tool-auth/index.d.ts +1 -1
  205. package/dist/modules/tool-auth/index.d.ts.map +1 -1
  206. package/dist/modules/tool-auth/internal-token.service.d.ts +9 -0
  207. package/dist/modules/tool-auth/internal-token.service.d.ts.map +1 -1
  208. package/dist/modules/tool-auth/internal-token.service.js +1 -0
  209. package/dist/modules/tool-auth/internal-token.service.js.map +1 -1
  210. package/dist/modules/tool-auth/tool-auth.middleware.d.ts +27 -3
  211. package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
  212. package/dist/modules/tool-auth/tool-auth.middleware.js +32 -6
  213. package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
  214. package/dist/modules/tool-helpers/tool-context.d.ts +7 -0
  215. package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
  216. package/dist/modules/tool-helpers/tool-context.js +3 -0
  217. package/dist/modules/tool-helpers/tool-context.js.map +1 -1
  218. package/dist/modules/tool-helpers/tool-handler.d.ts +15 -0
  219. package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
  220. package/dist/modules/tool-helpers/tool-handler.js +19 -0
  221. package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
  222. package/dist/modules/tool-helpers/tool.contract.d.ts +10 -0
  223. package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
  224. package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
  225. package/dist/modules/tool-helpers/validate-token.d.ts +3 -1
  226. package/dist/modules/tool-helpers/validate-token.d.ts.map +1 -1
  227. package/dist/modules/tool-helpers/validate-token.js +1 -1
  228. package/dist/modules/tool-helpers/validate-token.js.map +1 -1
  229. package/dist/modules/tool-manuals/index.d.ts +3 -3
  230. package/dist/modules/tool-manuals/index.d.ts.map +1 -1
  231. package/dist/modules/tool-manuals/index.js +0 -2
  232. package/dist/modules/tool-manuals/index.js.map +1 -1
  233. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +19 -6
  234. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
  235. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +7 -1
  236. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
  237. package/dist/modules/tool-manuals/tool-manuals.routes.js +41 -4
  238. package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
  239. package/dist/modules/tool-manuals/tool-manuals.service.d.ts +13 -6
  240. package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
  241. package/dist/modules/tool-manuals/tool-manuals.service.js +23 -15
  242. package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
  243. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
  244. package/dist/modules/tool-manuals/tool-manuals.tools.js +25 -10
  245. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  246. package/dist/modules/tool-registry/manual.routes.d.ts +8 -1
  247. package/dist/modules/tool-registry/manual.routes.d.ts.map +1 -1
  248. package/dist/modules/tool-registry/manual.routes.js +27 -7
  249. package/dist/modules/tool-registry/manual.routes.js.map +1 -1
  250. package/dist/modules/tool-registry/tool.contract.d.ts +7 -0
  251. package/dist/modules/tool-registry/tool.contract.d.ts.map +1 -1
  252. package/dist/modules/tool-registry/tool.contract.js.map +1 -1
  253. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
  254. package/dist/modules/workflow/agent-tools/workflow.tools.js +14 -9
  255. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
  256. package/dist/modules/workflow/git/git.service.d.ts +2 -2
  257. package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
  258. package/dist/modules/workflow/git/git.service.js +20 -35
  259. package/dist/modules/workflow/git/git.service.js.map +1 -1
  260. package/dist/modules/workflow/workflow.service.d.ts +2 -1
  261. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  262. package/dist/modules/workflow/workflow.service.js +3 -0
  263. package/dist/modules/workflow/workflow.service.js.map +1 -1
  264. package/dist/modules/workspace/file-readers/doc-extract.service.d.ts +28 -4
  265. package/dist/modules/workspace/file-readers/doc-extract.service.d.ts.map +1 -1
  266. package/dist/modules/workspace/file-readers/doc-extract.service.js +61 -40
  267. package/dist/modules/workspace/file-readers/doc-extract.service.js.map +1 -1
  268. package/dist/modules/workspace/index.d.ts +8 -0
  269. package/dist/modules/workspace/index.d.ts.map +1 -1
  270. package/dist/modules/workspace/index.js +8 -0
  271. package/dist/modules/workspace/index.js.map +1 -1
  272. package/dist/modules/workspace/nul-scan.d.ts +19 -0
  273. package/dist/modules/workspace/nul-scan.d.ts.map +1 -0
  274. package/dist/modules/workspace/nul-scan.js +55 -0
  275. package/dist/modules/workspace/nul-scan.js.map +1 -0
  276. package/dist/modules/workspace/pattern-matcher.contract.d.ts +110 -27
  277. package/dist/modules/workspace/pattern-matcher.contract.d.ts.map +1 -1
  278. package/dist/modules/workspace/pattern-matcher.contract.js +30 -13
  279. package/dist/modules/workspace/pattern-matcher.contract.js.map +1 -1
  280. package/dist/modules/workspace/ripgrep-search.d.ts +54 -0
  281. package/dist/modules/workspace/ripgrep-search.d.ts.map +1 -0
  282. package/dist/modules/workspace/ripgrep-search.js +657 -0
  283. package/dist/modules/workspace/ripgrep-search.js.map +1 -0
  284. package/dist/modules/workspace/ripgrep-version.d.ts +15 -0
  285. package/dist/modules/workspace/ripgrep-version.d.ts.map +1 -0
  286. package/dist/modules/workspace/ripgrep-version.js +31 -0
  287. package/dist/modules/workspace/ripgrep-version.js.map +1 -0
  288. package/dist/modules/workspace/startup/beside-checkout.d.ts +8 -5
  289. package/dist/modules/workspace/startup/beside-checkout.d.ts.map +1 -1
  290. package/dist/modules/workspace/startup/beside-checkout.js +14 -5
  291. package/dist/modules/workspace/startup/beside-checkout.js.map +1 -1
  292. package/dist/modules/workspace/worker-pool.d.ts +3 -4
  293. package/dist/modules/workspace/worker-pool.d.ts.map +1 -1
  294. package/dist/modules/workspace/worker-pool.js.map +1 -1
  295. package/dist/modules/workspace/workspace-tmp.d.ts +146 -0
  296. package/dist/modules/workspace/workspace-tmp.d.ts.map +1 -0
  297. package/dist/modules/workspace/workspace-tmp.js +435 -0
  298. package/dist/modules/workspace/workspace-tmp.js.map +1 -0
  299. package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
  300. package/dist/modules/workspace/workspace.routes.js +5 -26
  301. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  302. package/dist/modules/workspace/workspace.service.d.ts +47 -6
  303. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  304. package/dist/modules/workspace/workspace.service.js +86 -6
  305. package/dist/modules/workspace/workspace.service.js.map +1 -1
  306. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  307. package/dist/modules/workspace/workspace.tools.js +460 -116
  308. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  309. package/dist/shared/path-order.d.ts +28 -8
  310. package/dist/shared/path-order.d.ts.map +1 -1
  311. package/dist/shared/path-order.js +37 -8
  312. package/dist/shared/path-order.js.map +1 -1
  313. package/migrations/0022_session_version.sql +6 -0
  314. package/migrations/0023_agent_path.sql +3 -0
  315. package/migrations/meta/0022_snapshot.json +2360 -0
  316. package/migrations/meta/0023_snapshot.json +2378 -0
  317. package/migrations/meta/_journal.json +14 -0
  318. package/package.json +3 -3
  319. package/src/__tests__/pg-scratch.test.ts +69 -0
  320. package/src/__tests__/pg-scratch.ts +31 -0
  321. package/src/__tests__/slow-ripgrep.ts +65 -0
  322. package/src/core/__tests__/boot-without-ripgrep.test.ts +34 -0
  323. package/src/core/__tests__/catalog-revision.test.ts +89 -0
  324. package/src/core/__tests__/damaged-document.tools.test.ts +2 -2
  325. package/src/core/__tests__/gated-boot.test.ts +8 -1
  326. package/src/core/__tests__/lifecycle.test.ts +46 -13
  327. package/src/core/catalog-revision.ts +24 -2
  328. package/src/core/core-ports.ts +42 -0
  329. package/src/core/create-core-server.ts +13 -0
  330. package/src/core/create-core-services.ts +49 -12
  331. package/src/core/lifecycle.ts +26 -2
  332. package/src/index.ts +1 -1
  333. package/src/modules/access/__tests__/access-control.admin-root-floor.test.ts +12 -0
  334. package/src/modules/access/__tests__/access-control.colliding-name-cases.test.ts +113 -0
  335. package/src/modules/access/__tests__/access-control.frontmatter-head.test.ts +117 -0
  336. package/src/modules/access/__tests__/access-control.group-named-admin.test.ts +122 -0
  337. package/src/modules/access/__tests__/access-control.preview-relocation.test.ts +50 -0
  338. package/src/modules/access/__tests__/access-declarations.test.ts +19 -0
  339. package/src/modules/access/__tests__/access-groups.test.ts +55 -22
  340. package/src/modules/access/__tests__/access-mutation.service.test.ts +85 -59
  341. package/src/modules/access/__tests__/access-restrict-here.test.ts +7 -5
  342. package/src/modules/access/__tests__/access.routes.admins.test.ts +1 -0
  343. package/src/modules/access/__tests__/access.routes.batch.test.ts +2 -0
  344. package/src/modules/access/__tests__/access.routes.folder-governs.test.ts +2 -0
  345. package/src/modules/access/__tests__/access.routes.group-grant.test.ts +50 -14
  346. package/src/modules/access/__tests__/access.routes.overrides.test.ts +2 -1
  347. package/src/modules/access/__tests__/access.routes.proposal-branch.test.ts +1 -0
  348. package/src/modules/access/__tests__/access.routes.prospective.test.ts +2 -0
  349. package/src/modules/access/__tests__/access.routes.revoke.test.ts +18 -13
  350. package/src/modules/access/__tests__/access.routes.unknown-email.test.ts +2 -0
  351. package/src/modules/access/__tests__/grant-sources.test.ts +20 -15
  352. package/src/modules/access/__tests__/groups-admin.routes.test.ts +61 -0
  353. package/src/modules/access/__tests__/groups-admin.service.test.ts +211 -18
  354. package/src/modules/access/__tests__/reference-scan.test.ts +366 -14
  355. package/src/modules/access/__tests__/roles-admin.security.test.ts +1 -1
  356. package/src/modules/access/__tests__/roles-admin.service.test.ts +43 -22
  357. package/src/modules/access/__tests__/roles-capabilities.test.ts +6 -2
  358. package/src/modules/access/__tests__/roles.routes.test.ts +1 -1
  359. package/src/modules/access/access-control.interface.ts +17 -10
  360. package/src/modules/access/access-control.service.ts +158 -72
  361. package/src/modules/access/access-mutation.service.ts +48 -53
  362. package/src/modules/access/access-view.ts +12 -10
  363. package/src/modules/access/access.routes.ts +43 -33
  364. package/src/modules/access/groups-admin.routes.ts +4 -2
  365. package/src/modules/access/groups-admin.service.ts +114 -44
  366. package/src/modules/access/groups-edit.ts +1 -1
  367. package/src/modules/access/reference-scan.ts +319 -46
  368. package/src/modules/access/roles-admin.service.ts +43 -26
  369. package/src/modules/access/synced-groups-writer.ts +1 -1
  370. package/src/modules/access-model/__tests__/access-grammar.test.ts +38 -1
  371. package/src/modules/access-model/__tests__/access-splice.test.ts +50 -8
  372. package/src/modules/access-model/access-grammar.ts +65 -20
  373. package/src/modules/access-model/access-splice.ts +31 -23
  374. package/src/modules/access-model/group-files.ts +5 -0
  375. package/src/modules/agent-guide/__tests__/agent-guide.test.ts +31 -0
  376. package/src/modules/agent-guide/agent-guide.tools.ts +13 -2
  377. package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +1 -0
  378. package/src/modules/agent-instructions/compose.ts +3 -1
  379. package/src/modules/agent-instructions/shared-file-rules.ts +17 -1
  380. package/src/modules/agent-scope/__tests__/agent-surfaces.connect-as.test.ts +421 -0
  381. package/src/modules/agent-scope/__tests__/connection-scope.test.ts +46 -0
  382. package/src/modules/agent-scope/__tests__/in-memory-kb.ts +148 -0
  383. package/src/modules/agent-scope/agent-scope.contract.ts +83 -0
  384. package/src/modules/agent-scope/connection-scope.ts +199 -0
  385. package/src/modules/agent-scope/index.ts +15 -0
  386. package/src/modules/audit/agent-audit.service.ts +2 -0
  387. package/src/modules/audit/audit.contract.ts +6 -0
  388. package/src/modules/auth/__tests__/auth.middleware.test.ts +30 -2
  389. package/src/modules/auth/__tests__/auth.routes.test.ts +71 -5
  390. package/src/modules/auth/__tests__/auth.service.test.ts +90 -9
  391. package/src/modules/auth/__tests__/password-change-sessions.pg.test.ts +269 -0
  392. package/src/modules/auth/account-admission.ts +19 -0
  393. package/src/modules/auth/auth.middleware.ts +45 -4
  394. package/src/modules/auth/auth.routes.ts +46 -29
  395. package/src/modules/auth/auth.service.ts +135 -34
  396. package/src/modules/auth/index.ts +2 -0
  397. package/src/modules/auth/oidc-auth-provider.ts +2 -9
  398. package/src/modules/database/__tests__/session-version-migration.pg.test.ts +133 -0
  399. package/src/modules/database/core-schema.ts +33 -0
  400. package/src/modules/declared-variables/__tests__/declared-variables.route.test.ts +44 -1
  401. package/src/modules/declared-variables/declared-variables.routes.ts +12 -1
  402. package/src/modules/embed/__tests__/embed.tools.test.ts +12 -0
  403. package/src/modules/embed/embed.tools.ts +5 -0
  404. package/src/modules/kb-fs/__tests__/locking-filesystem.test.ts +25 -0
  405. package/src/modules/kb-fs/__tests__/repo-path.test.ts +53 -8
  406. package/src/modules/kb-fs/index.ts +2 -0
  407. package/src/modules/kb-fs/locking-filesystem.ts +7 -1
  408. package/src/modules/kb-fs/repo-path.ts +33 -7
  409. package/src/modules/kb-fs/tmp-ref.ts +81 -0
  410. package/src/modules/mcp/__tests__/connect-as.pg.test.ts +137 -0
  411. package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +29 -5
  412. package/src/modules/mcp/__tests__/mcp-routes-harness.ts +8 -0
  413. package/src/modules/mcp/__tests__/mcp.connect-as.test.ts +195 -0
  414. package/src/modules/mcp/__tests__/mcp.routes.connect-as.test.ts +192 -0
  415. package/src/modules/mcp/__tests__/oauth-consent.connect-as.test.ts +146 -0
  416. package/src/modules/mcp/mcp-auth.middleware.ts +19 -2
  417. package/src/modules/mcp/mcp.routes.ts +39 -3
  418. package/src/modules/mcp/mcp.service.ts +107 -17
  419. package/src/modules/mcp/oauth/bevel-oauth-provider.ts +46 -8
  420. package/src/modules/mcp/oauth/oauth-consent.routes.ts +41 -1
  421. package/src/modules/plugins/__tests__/plugins.tools.test.ts +38 -2
  422. package/src/modules/plugins/index.ts +1 -1
  423. package/src/modules/plugins/plugin-links.ts +19 -2
  424. package/src/modules/plugins/plugins.routes.ts +35 -4
  425. package/src/modules/secrets-vault/__tests__/connect-pending.route.test.ts +113 -0
  426. package/src/modules/secrets-vault/secrets-vault.routes.ts +23 -3
  427. package/src/modules/skills/__tests__/skills.service.test.ts +34 -0
  428. package/src/modules/skills/index.ts +7 -2
  429. package/src/modules/skills/skills.contract.ts +17 -0
  430. package/src/modules/skills/skills.service.ts +5 -1
  431. package/src/modules/skills/skills.tools.ts +44 -13
  432. package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +4 -0
  433. package/src/modules/tool-auth/external-api-key.interface.ts +11 -1
  434. package/src/modules/tool-auth/external-api-key.service.ts +5 -2
  435. package/src/modules/tool-auth/index.ts +1 -1
  436. package/src/modules/tool-auth/internal-token.service.ts +10 -0
  437. package/src/modules/tool-auth/tool-auth.middleware.ts +45 -3
  438. package/src/modules/tool-helpers/__tests__/can-write.tools.test.ts +3 -1
  439. package/src/modules/tool-helpers/tool-context.ts +10 -0
  440. package/src/modules/tool-helpers/tool-handler.ts +34 -0
  441. package/src/modules/tool-helpers/tool.contract.ts +10 -0
  442. package/src/modules/tool-helpers/validate-token.ts +4 -2
  443. package/src/modules/tool-manuals/__tests__/tool-manuals.archive.route.test.ts +80 -4
  444. package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +42 -0
  445. package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +3 -3
  446. package/src/modules/tool-manuals/index.ts +6 -2
  447. package/src/modules/tool-manuals/tool-manuals.contract.ts +18 -6
  448. package/src/modules/tool-manuals/tool-manuals.routes.ts +40 -3
  449. package/src/modules/tool-manuals/tool-manuals.service.ts +31 -15
  450. package/src/modules/tool-manuals/tool-manuals.tools.ts +28 -11
  451. package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +2 -2
  452. package/src/modules/tool-registry/manual.routes.ts +26 -5
  453. package/src/modules/tool-registry/tool.contract.ts +7 -0
  454. package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +1 -1
  455. package/src/modules/workflow/__tests__/workflow.service.sync.test.ts +1 -1
  456. package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +54 -3
  457. package/src/modules/workflow/agent-tools/workflow.tools.ts +14 -9
  458. package/src/modules/workflow/git/__tests__/git.service.list-branches.test.ts +130 -3
  459. package/src/modules/workflow/git/git.service.ts +29 -48
  460. package/src/modules/workflow/workflow.service.ts +5 -0
  461. package/src/modules/workspace/__tests__/git-internals.security.test.ts +2 -2
  462. package/src/modules/workspace/__tests__/holds-nul.test.ts +66 -0
  463. package/src/modules/workspace/__tests__/rest-tool-arguments.test.ts +3 -3
  464. package/src/modules/workspace/__tests__/ripgrep-search.test.ts +570 -0
  465. package/src/modules/workspace/__tests__/ripgrep-version.test.ts +29 -0
  466. package/src/modules/workspace/__tests__/workspace-tmp.tools.test.ts +928 -0
  467. package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +17 -37
  468. package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +20 -8
  469. package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +2 -2
  470. package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +22 -10
  471. package/src/modules/workspace/__tests__/workspace.tools.test.ts +393 -113
  472. package/src/modules/workspace/file-readers/__tests__/doc-extract.service.test.ts +10 -0
  473. package/src/modules/workspace/file-readers/doc-extract.service.ts +71 -38
  474. package/src/modules/workspace/index.ts +20 -0
  475. package/src/modules/workspace/nul-scan.ts +50 -0
  476. package/src/modules/workspace/pattern-matcher.contract.ts +123 -32
  477. package/src/modules/workspace/ripgrep-search.ts +715 -0
  478. package/src/modules/workspace/ripgrep-version.ts +35 -0
  479. package/src/modules/workspace/startup/__tests__/beside-checkout.test.ts +11 -0
  480. package/src/modules/workspace/startup/beside-checkout.ts +13 -5
  481. package/src/modules/workspace/worker-pool.ts +3 -4
  482. package/src/modules/workspace/workspace-tmp.ts +457 -0
  483. package/src/modules/workspace/workspace.routes.ts +6 -27
  484. package/src/modules/workspace/workspace.service.ts +92 -15
  485. package/src/modules/workspace/workspace.tools.ts +500 -155
  486. package/src/shared/path-order.ts +37 -8
  487. package/starter-packs/engineering/Plugins/engineering-starter/access.md +1 -1
  488. package/starter-packs/general/Plugins/general-starter/access.md +1 -1
  489. package/starter-packs/operations/Plugins/operations-starter/access.md +1 -1
  490. package/starter-packs/sales/Plugins/sales-starter/access.md +1 -1
  491. package/starter-packs/support/Plugins/support-starter/access.md +1 -1
  492. package/dist/modules/workspace/bounded-matcher.d.ts +0 -73
  493. package/dist/modules/workspace/bounded-matcher.d.ts.map +0 -1
  494. package/dist/modules/workspace/bounded-matcher.js +0 -200
  495. package/dist/modules/workspace/bounded-matcher.js.map +0 -1
  496. package/src/modules/workspace/__tests__/bounded-matcher.test.ts +0 -213
  497. package/src/modules/workspace/bounded-matcher.ts +0 -271
@@ -58,10 +58,27 @@ export const LINK_GRANT_VERBS = ['read', 'write'] as const;
58
58
  export interface LinkMembership {
59
59
  /** skill path → every plugin that holds it, inline or by link. */
60
60
  bySkill: Map<string, PluginMembership[]>;
61
- /** plugin folder → its links. Every plugin folder appears, linked or not. */
61
+ /**
62
+ * plugin identity (`PluginLinks.name`) → its links. Every plugin appears,
63
+ * linked or not. A name and a folder may differ, so a repository path is
64
+ * read from the value's `folder`, never from the key.
65
+ */
62
66
  byPlugin: Map<string, PluginLinks>;
63
67
  }
64
68
 
69
+ /**
70
+ * What a reader of the link index asks it — the contract a consumer outside
71
+ * this module (a distribution's agent-scope resolver, through
72
+ * `CorePorts.agentScope`) is written against. Dropping the cache is the
73
+ * index owner's affair, not a reader's, so it is not here.
74
+ */
75
+ export interface IPluginLinkIndex {
76
+ /** Every plugin's links and every skill's memberships, from the released catalog. */
77
+ membership(): Promise<LinkMembership>;
78
+ /** The memberships of one skill (empty when it is in no plugin). */
79
+ pluginsOf(skillPath: string): Promise<PluginMembership[]>;
80
+ }
81
+
65
82
  /**
66
83
  * The link index: which shared skills each plugin links, and — the other way
67
84
  * round — which plugins each skill belongs to, whether by sitting inside the
@@ -88,7 +105,7 @@ export interface LinkMembership {
88
105
  * subscriber that drops the plugin index, so a link committed on the default
89
106
  * branch shows within one round-trip.
90
107
  */
91
- export class PluginLinkIndex {
108
+ export class PluginLinkIndex implements IPluginLinkIndex {
92
109
  private readonly cache: TtlCache<LinkMembership>;
93
110
 
94
111
  constructor(
@@ -17,6 +17,8 @@ import { pluginFolderBelowRoot } from './plugins.service.js';
17
17
  import type { KbContext } from '../../shared/kb-context.js';
18
18
  import { PluginProvisionError, type PluginProvisionService } from './plugin-provision.service.js';
19
19
  import { argumentsRefusal } from '../tool-helpers/index.js';
20
+ import { resolveConnectionScope, type IAgentScopeResolver } from '../agent-scope/index.js';
21
+ import '../tool-auth/index.js'; // Express Request.toolAuth augmentation
20
22
  import { PluginLinkError, type PluginLinksService } from './plugin-links.service.js';
21
23
  import { PluginRenameError, type PluginRenameService } from './plugin-rename.service.js';
22
24
  import type { JoinRequestsService } from './join-requests.service.js';
@@ -83,6 +85,29 @@ function refuseMismatchedArguments(req: express.Request, res: express.Response):
83
85
  return true;
84
86
  }
85
87
 
88
+ /**
89
+ * Answer the connection-scope refusal when the caller's connection was made
90
+ * as an agent configuration that does not list `toolName`; `true` when it answered.
91
+ * These routes ARE the `create_plugin` and `my_plugin` tools but are not
92
+ * hosted by `toolHandler`, so they ask the one policy it asks —
93
+ * `ConnectionScope.platformToolRefusal` — and answer what it answers. A
94
+ * session, or a key made as Everything, carries no `agentPath` and is never
95
+ * refused.
96
+ */
97
+ async function refuseOutsideConnectionScope(
98
+ req: express.Request,
99
+ res: express.Response,
100
+ user: AuthUser,
101
+ toolName: string,
102
+ agentScope: Pick<IAgentScopeResolver, 'resolve'> | undefined,
103
+ ): Promise<boolean> {
104
+ const scope = await resolveConnectionScope(agentScope, user.email, req.toolAuth?.agentPath);
105
+ const refusal = scope.platformToolRefusal(toolName);
106
+ if (!refusal) return false;
107
+ res.status(403).json(refusal);
108
+ return true;
109
+ }
110
+
86
111
  /**
87
112
  * The two doors through which plugin folders come to exist — ONE
88
113
  * implementation each, for the app and for agents alike:
@@ -106,6 +131,8 @@ function refuseMismatchedArguments(req: express.Request, res: express.Response):
106
131
  export function createPluginCreationRoutes(
107
132
  provision: Pick<PluginProvisionService, 'createPlugin' | 'ensurePersonalPlugin'>,
108
133
  resolveUser: (req: express.Request) => Promise<AuthUser | null>,
134
+ /** Resolves a connection's agent choice to its scope; absent, a narrowed connection loads nothing. */
135
+ agentScope?: Pick<IAgentScopeResolver, 'resolve'>,
109
136
  ): express.Router {
110
137
  const router = express.Router();
111
138
 
@@ -117,8 +144,10 @@ export function createPluginCreationRoutes(
117
144
  }
118
145
  // These two routes ARE the `create_plugin` and `my_plugin` tools, so an
119
146
  // agent's call is checked against what those tools declare, exactly as a
120
- // route-hosted tool's is in `toolHandler`: a call that does not match is
121
- // refused before anything is provisioned.
147
+ // route-hosted tool's is in `toolHandler`: a call that does not match, or
148
+ // a connection whose agent does not list the tool, is refused before
149
+ // anything is provisioned.
150
+ if (await refuseOutsideConnectionScope(req, res, user, 'create_plugin', agentScope)) return;
122
151
  if (refuseMismatchedArguments(req, res)) return;
123
152
  // `req.body` is undefined when no JSON body was sent at all — that is a
124
153
  // 400, not a destructuring crash.
@@ -155,8 +184,10 @@ export function createPluginCreationRoutes(
155
184
  }
156
185
  // These two routes ARE the `create_plugin` and `my_plugin` tools, so an
157
186
  // agent's call is checked against what those tools declare, exactly as a
158
- // route-hosted tool's is in `toolHandler`: a call that does not match is
159
- // refused before anything is provisioned.
187
+ // route-hosted tool's is in `toolHandler`: a call that does not match, or
188
+ // a connection whose agent does not list the tool, is refused before
189
+ // anything is provisioned.
190
+ if (await refuseOutsideConnectionScope(req, res, user, 'my_plugin', agentScope)) return;
160
191
  if (refuseMismatchedArguments(req, res)) return;
161
192
  try {
162
193
  res.json(await provision.ensurePersonalPlugin(user));
@@ -3,6 +3,7 @@ import express from 'express';
3
3
  import { afterEach, describe, expect, it } from 'vitest';
4
4
  import { createSecretsVaultRoutes } from '../secrets-vault.routes.js';
5
5
  import { testKbContext } from '../../../__tests__/kb-context.js';
6
+ import { ConnectionScope } from '../../agent-scope/index.js';
6
7
 
7
8
  /**
8
9
  * The aggregated `/connect/pending` surface: everything standing between the
@@ -336,6 +337,118 @@ describe('GET /api/connect/pending — readability', () => {
336
337
  });
337
338
  });
338
339
 
340
+ describe('GET /api/connect/pending — narrowed to the agent the connection is made as', () => {
341
+ /**
342
+ * The consent step's "Connect as" choice: a connection made as an `.agent`
343
+ * file loads only the connectors the file lists, so the page that asks for
344
+ * sign-ins and keys lists only those. Two readable tools, one of which the
345
+ * agent loads; the agent is answered by a stub resolver over the REAL
346
+ * `ConnectionScope`, the one answer every agent surface asks.
347
+ */
348
+ const AGENT = { path: 'Agents/ops.agent', name: 'ops' };
349
+ const resolver = {
350
+ resolve: async (_email: string | undefined, agentPath: string | null | undefined) => {
351
+ if (!agentPath) return ConnectionScope.everything();
352
+ if (agentPath === AGENT.path) {
353
+ return ConnectionScope.narrowed(AGENT, { skills: [], connectors: ['Tools/weather.tool'], platformTools: null }, 'fp');
354
+ }
355
+ return ConnectionScope.gone(agentPath);
356
+ },
357
+ };
358
+ const twoTools = {
359
+ listAccessible: async () => [
360
+ {
361
+ slug: 'weather',
362
+ name: 'weather',
363
+ path: 'Tools/weather.tool',
364
+ type: 'http' as const,
365
+ variables: [{ name: 'API_KEY', scope: 'user' as const, label: null }],
366
+ },
367
+ {
368
+ slug: 'notion',
369
+ name: 'notion',
370
+ path: 'Tools/notion.tool',
371
+ type: 'mcp' as const,
372
+ variables: [
373
+ {
374
+ name: 'MCP_OAUTH',
375
+ scope: 'user' as const,
376
+ label: 'notion sign-in',
377
+ oauth: { authorizationUrl: 'https://a.example/auth', tokenUrl: 'https://a.example/token', clientId: 'cid' },
378
+ },
379
+ ],
380
+ },
381
+ ],
382
+ } as unknown as Parameters<typeof createSecretsVaultRoutes>[0]['toolManualService'];
383
+ const vault = {
384
+ statusFor: async (_u: string, keys: string[]) =>
385
+ keys.map((key) => ({
386
+ key,
387
+ adminConfigured: key === 'notion_MCP_OAUTH',
388
+ adminKind: key === 'notion_MCP_OAUTH' ? ('oauth' as const) : null,
389
+ userConfigured: false,
390
+ userKind: null,
391
+ })),
392
+ // A sign-in registered directly on the Secrets page: nobody's connector.
393
+ list: async () => [
394
+ { id: 's-own', key: 'MY_TOKEN', kind: 'oauth' as const, label: 'Mine', authorized: false, createdAt: new Date(), updatedAt: new Date() },
395
+ ],
396
+ } as unknown as Parameters<typeof createSecretsVaultRoutes>[0]['secretsVault'];
397
+
398
+ async function pendingAs(agent: string | null, withResolver = true) {
399
+ const app = express();
400
+ app.use(express.json());
401
+ app.use((req, _res, next) => {
402
+ req.userId = 'u1';
403
+ req.userEmail = 'a@x.com';
404
+ next();
405
+ });
406
+ app.use(
407
+ '/api',
408
+ createSecretsVaultRoutes({
409
+ kb: testKbContext(),
410
+ secretsVault: vault,
411
+ toolManualService: twoTools,
412
+ accessControl,
413
+ ...(withResolver ? { agents: resolver } : {}),
414
+ connectionProbe: {
415
+ probe: async () => ({ status: 'unverifiable' as const, detail: null, checkedAt: new Date() }),
416
+ },
417
+ stateSecret: 'test-secret',
418
+ publicBackendUrl: 'http://localhost:3000',
419
+ publicFrontendUrl: 'http://localhost:5173',
420
+ }),
421
+ );
422
+ httpServer = await new Promise<HttpServer>((resolve) => {
423
+ const s = app.listen(0, () => resolve(s));
424
+ });
425
+ const port = (httpServer.address() as { port: number }).port;
426
+ const query = agent === null ? '' : `?agent=${encodeURIComponent(agent)}`;
427
+ const res = await fetch(`http://127.0.0.1:${port}/api/connect/pending${query}`);
428
+ expect(res.status).toBe(200);
429
+ const body = (await res.json()) as PendingBody & { toolOAuth: { slug: string }[] };
430
+ return { tools: body.tools.map((t) => t.slug), signIns: body.toolOAuth.map((o) => o.slug), standalone: body.oauth.map((o) => o.id) };
431
+ }
432
+
433
+ it('lists everything without a choice, as before', async () => {
434
+ expect(await pendingAs(null)).toEqual({ tools: ['weather'], signIns: ['notion'], standalone: ['s-own'] });
435
+ });
436
+
437
+ it('lists only the connectors the agent loads, and no standalone sign-in', async () => {
438
+ expect(await pendingAs(AGENT.path)).toEqual({ tools: ['weather'], signIns: [], standalone: [] });
439
+ });
440
+
441
+ it('lists nothing for an agent the person cannot use: a narrowing that cannot be read is never Everything', async () => {
442
+ expect(await pendingAs('Agents/gone.agent')).toEqual({ tools: [], signIns: [], standalone: [] });
443
+ // …and the same with no resolver wired at all.
444
+ expect(await pendingAs(AGENT.path, false)).toEqual({ tools: [], signIns: [], standalone: [] });
445
+ });
446
+
447
+ it('reads an empty or blank choice as Everything', async () => {
448
+ expect(await pendingAs(' ')).toEqual({ tools: ['weather'], signIns: ['notion'], standalone: ['s-own'] });
449
+ });
450
+ });
451
+
339
452
  describe('GET /api/connect/pending — OAuth scope coverage', () => {
340
453
  // A tool with an OAuth-backed var that now requires two scopes.
341
454
  const oauthTool = {
@@ -17,10 +17,19 @@ import type { IConnectionProbeService } from '../connection-probe/index.js';
17
17
  import { utcpNamespacedKey } from '../../shared/utcp-namespace.js';
18
18
  import type { IAccessControl } from '../access/index.js';
19
19
  import type { KbContext } from '../../shared/kb-context.js';
20
+ import { resolveConnectionScope, type IAgentScopeResolver } from '../agent-scope/index.js';
20
21
  import '../auth/index.js'; // Express Request augmentation (req.userId / req.userEmail)
21
22
 
22
23
  export interface SecretsVaultRoutesDeps {
23
24
  secretsVault: ISecretsVaultService;
25
+ /**
26
+ * Resolves what a connection made as an agent configuration loads, so the connect
27
+ * page can list only the tools the chosen agent would reach (`?agent=`).
28
+ * Optional: a construction without one answers such a request as a
29
+ * connection that loads nothing — a narrowing that cannot be read is never
30
+ * read as Everything.
31
+ */
32
+ agents?: Pick<IAgentScopeResolver, 'resolve'>;
24
33
  /** The released branch's clone — where the `.tool` files the access verdicts are read from live. */
25
34
  kb: Pick<KbContext, 'defaultWorkspaceId'>;
26
35
  /** Source of `.tool` manuals + their declared variable scopes (default-branch catalog). */
@@ -267,12 +276,21 @@ export function createSecretsVaultRoutes(deps: SecretsVaultRoutesDeps): express.
267
276
  // that is already set is nobody's outstanding work, so it is dropped. What is
268
277
  // not relaxed is readability: the listing is still built from `listAccessible`,
269
278
  // so a tool the caller may not read is named here in no state at all.
279
+ //
280
+ // `?agent=<path>` — the consent step's "Connect as" choice — narrows the
281
+ // listing to what a connection made as that agent configuration would load, by the
282
+ // ONE answer every agent surface asks (`ConnectionScope.allowsConnector`,
283
+ // applied after the read gate exactly as the tool listing applies it). A
284
+ // sign-in the connection will never reach is not on a page about what the
285
+ // connection needs. Absent, the listing is Everything's, as it always was.
270
286
  router.get('/connect/pending', async (req, res) => {
271
287
  const userId = req.userId;
272
288
  const email = req.userEmail;
273
289
  if (!userId || !email) return void res.status(401).json({ error: 'Not authenticated' });
274
290
  try {
275
- const manuals = await toolManualService.listAccessible(email);
291
+ const agentPath = typeof req.query.agent === 'string' && req.query.agent.trim() !== '' ? req.query.agent : null;
292
+ const scope = await resolveConnectionScope(deps.agents, email, agentPath);
293
+ const manuals = (await toolManualService.listAccessible(email)).filter((m) => scope.allowsConnector(m.path));
276
294
  // BOTH tiers now — the workspace values are reported as well, so their
277
295
  // stored state has to be asked for.
278
296
  const allKeys = manuals.flatMap((m) => (m.variables ?? []).map((v) => varKey(m.name, v.name)));
@@ -371,9 +389,11 @@ export function createSecretsVaultRoutes(deps: SecretsVaultRoutesDeps): express.
371
389
  // Secrets page. A TOOL sign-in provisions a per-user row in the same
372
390
  // table (keyed `<manual>_<VAR>`), so exclude every tool-var key here or
373
391
  // an authorized tool sign-in would render twice — once as a toolOAuth
374
- // row and again as a "standalone" one.
392
+ // row and again as a "standalone" one. They belong to no connector, so
393
+ // a connection narrowed to an agent's connectors has nothing to do with
394
+ // them: listed for Everything only.
375
395
  const toolOAuthKeys = new Set(toolOAuth.map((o) => o.key));
376
- const secrets = await secretsVault.list(userId);
396
+ const secrets = scope.isEverything() ? await secretsVault.list(userId) : [];
377
397
  const oauth = secrets
378
398
  .filter((s) => s.kind === 'oauth' && !toolOAuthKeys.has(s.key))
379
399
  .map((s) => ({ id: s.id, key: s.key, label: s.label, authorized: s.authorized ?? false }));
@@ -280,6 +280,40 @@ describe('SkillService', () => {
280
280
  expect(await svc(denyRfi).getSkill('user@x.eu', 'rfi')).toEqual({ ok: false, error: 'forbidden' });
281
281
  });
282
282
 
283
+ /**
284
+ * A connection made as an `.agent` file narrows the skills (`within`) —
285
+ * AFTER the person's read gate, so it can take a skill away and never give
286
+ * one: a skill its lists name that the person may not read stays out.
287
+ */
288
+ describe('narrowed to a connection’s skills', () => {
289
+ const denyGuidelines = {
290
+ canRead: async () => true,
291
+ canReadBatch: async (_w: string, _e: string, paths: string[]) =>
292
+ new Map(paths.map((p) => [p, !p.startsWith('Plugins/Development/')])),
293
+ } as unknown as IAccessControl;
294
+ const everyPluginSkill = (folder: string) => folder.startsWith('Plugins/');
295
+
296
+ test('lists only what is both readable and within, and loads nothing else', async () => {
297
+ const list = await svc(denyGuidelines).listSkills('user@x.eu', { within: everyPluginSkill });
298
+ expect(list.map((s) => s.name)).toEqual(['rfi']);
299
+ expect(await svc().listSkills('user@x.eu', { within: (p) => p === 'Plugins/Development/coding-guidelines' })).toEqual([
300
+ expect.objectContaining({ name: 'coding-guidelines' }),
301
+ ]);
302
+ });
303
+
304
+ test('getSkill outside it is not there for the connection; inside it, the read gate still answers', async () => {
305
+ expect(await svc().getSkill('user@x.eu', 'rfi', undefined, { within: () => false })).toEqual({
306
+ ok: false,
307
+ error: 'not_found',
308
+ });
309
+ const inside = await svc().getSkill('user@x.eu', 'rfi', undefined, { within: everyPluginSkill });
310
+ expect(inside.ok).toBe(true);
311
+ expect(
312
+ await svc(denyGuidelines).getSkill('user@x.eu', 'coding-guidelines', undefined, { within: everyPluginSkill }),
313
+ ).toEqual({ ok: false, error: 'forbidden' });
314
+ });
315
+ });
316
+
283
317
  /**
284
318
  * The listing and the loader must never disagree about the same skill at the
285
319
  * same instant.
@@ -1,5 +1,8 @@
1
- export { SkillService } from './skills.service.js';
2
- export { PendingSkillsService } from './pending-skills.service.js';
1
+ // The services are types here: the composition root constructs them from
2
+ // their files, so a module that imports this entry for a helper or a type
3
+ // does not load the implementations.
4
+ export type { SkillService } from './skills.service.js';
5
+ export type { PendingSkillsService } from './pending-skills.service.js';
3
6
  export { registerSkillsTools } from './skills.tools.js';
4
7
  export { createSkillsRoutes } from './skills.routes.js';
5
8
  export { AllowedToolsChecker } from './allowed-tools-check.js';
@@ -14,7 +17,9 @@ export type {
14
17
  PendingSkill,
15
18
  GetSkillResult,
16
19
  GetSkillOptions,
20
+ ListSkillsOptions,
17
21
  SkillHistorySource,
22
+ SkillScope,
18
23
  } from './skills.contract.js';
19
24
  // Reached by other modules; exported here so they stop importing the files.
20
25
  export type { PluginMembership } from './skills.contract.js';
@@ -119,8 +119,19 @@ export interface ListSkillsOptions {
119
119
  * carries `unmerged: true` and this branch name: nobody approved it.
120
120
  */
121
121
  branch?: string;
122
+ /** Narrow the listing to a connection's skills — see {@link SkillScope}. */
123
+ within?: SkillScope;
122
124
  }
123
125
 
126
+ /**
127
+ * A connection's narrowing of the skills: whether the skill in `skillFolder`
128
+ * (repo-relative) is one the connection loads. Applied AFTER the caller's read
129
+ * gate, so it can only take skills away. The agent surfaces pass the
130
+ * connection scope's `allowsSkill` (`modules/agent-scope`); absent is every
131
+ * skill the caller may read.
132
+ */
133
+ export type SkillScope = (skillFolder: string) => boolean;
134
+
124
135
  /** What `getSkill` may be asked beyond a name and a file. */
125
136
  export interface GetSkillOptions {
126
137
  /**
@@ -152,6 +163,12 @@ export interface GetSkillOptions {
152
163
  * combination has exactly one sensible meaning.
153
164
  */
154
165
  branch?: string;
166
+ /**
167
+ * Narrow to a connection's skills — see {@link SkillScope}. A skill outside
168
+ * it answers `not_found`, as a name nobody used does: to this connection it
169
+ * is not there.
170
+ */
171
+ within?: SkillScope;
155
172
  }
156
173
 
157
174
  /**
@@ -108,7 +108,9 @@ export class SkillService implements ISkillService {
108
108
 
109
109
  async listSkills(userEmail?: string, options: ListSkillsOptions = {}): Promise<SkillSummary[]> {
110
110
  const { catalog, branch } = await this.catalogFor(options.branch);
111
- const visible = userEmail ? await this.readableSkills(catalog, userEmail) : catalog.skills;
111
+ const readable = userEmail ? await this.readableSkills(catalog, userEmail) : catalog.skills;
112
+ const within = options.within;
113
+ const visible = within ? readable.filter((s) => within(s.summary.path)) : readable;
112
114
  if (branch === undefined) return visible.map((s) => s.summary);
113
115
  // Marking reads both copies of a folder, so it happens only here, on a
114
116
  // branch read that asked for it — the released catalog every other surface
@@ -135,6 +137,8 @@ export class SkillService implements ISkillService {
135
137
  // A skill the branch deleted is not a skill as far as this answer goes:
136
138
  // the same `not_found` a name nobody ever used gets.
137
139
  if (!found) return { ok: false, error: 'not_found' };
140
+ // Outside the connection's skills: not there, for this connection.
141
+ if (options.within && !options.within(found.summary.path)) return { ok: false, error: 'not_found' };
138
142
 
139
143
  // Through `readable`, not a bare `canRead`: the two gates must never
140
144
  // disagree. `canRead` reads a file's own frontmatter rules off disk on
@@ -4,7 +4,7 @@ import type { IToolRegistry, JsonSchema, UtcpTool } from '../tool-registry/index
4
4
  import { TOOL_DESCRIPTION_CAP, GUIDE_FIRST_SENTENCE } from '../tool-registry/index.js';
5
5
  import { ToolError, type ToolContext, toolDef, bodyEnvelope, declareRouteTool, CAN_WRITE_CLAUSE, defaultBranchWriteVerdicts, type DefaultBranchWriteDeps } from '../tool-helpers/index.js';
6
6
  import type { ToolHandlerFactory } from '../tool-helpers/index.js';
7
- import type { ISkillService } from './skills.contract.js';
7
+ import type { ISkillService, SkillScope } from './skills.contract.js';
8
8
  import { EXTERNAL_KB_MANUAL_NAME } from '../tool-manuals/index.js';
9
9
  import type { IAllowedToolsChecker } from './allowed-tools-check.js';
10
10
 
@@ -130,9 +130,13 @@ export function registerSkillsTools(
130
130
  return { paths: committed.includes(skillMdOf(loaded)) ? committed : [skillMdOf(loaded), ...loaded.files], at };
131
131
  };
132
132
 
133
- registry.registerExternalTool((ctx) => buildListSkillsDef(skillService, ctx.userEmail));
133
+ registry.registerExternalTool((ctx) =>
134
+ buildListSkillsDef(skillService, ctx.userEmail, ctx.connectionScope?.skillFilter()),
135
+ );
134
136
  registry.registerInternalTool((ctx) => buildListSkillsDef(skillService, ctx.userEmail));
135
- registry.registerExternalTool((ctx) => buildGetSkillDef(skillService, ctx.userEmail));
137
+ registry.registerExternalTool((ctx) =>
138
+ buildGetSkillDef(skillService, ctx.userEmail, ctx.connectionScope?.skillFilter()),
139
+ );
136
140
  registry.registerInternalTool((ctx) => buildGetSkillDef(skillService, ctx.userEmail));
137
141
  // Both defs are built per catalog listing (their descriptions name the skills
138
142
  // THIS caller may read), so declare the arguments here as well: a direct REST
@@ -140,17 +144,24 @@ export function registerSkillsTools(
140
144
  declareRouteTool('list_skills', LIST_SKILLS_INPUTS);
141
145
  declareRouteTool('get_skill', GET_SKILL_INPUTS);
142
146
 
147
+ // Both skill routes are also how prompts are read (`discovery`): a
148
+ // connection made as an agent configuration that names skills has them as
149
+ // prompts even when its `platformTools` does not offer these two tools.
143
150
  router.post(
144
151
  '/agent/tools/list_skills',
145
152
  toolAuth,
146
153
  toolHandler(async (args, ctx: ToolContext) => {
147
- const skills = await skillService.listSkills(ctx.user.email, { branch: branchArg(args) });
154
+ const within = ctx.connectionScope?.skillFilter();
155
+ const skills = await skillService.listSkills(ctx.user.email, {
156
+ branch: branchArg(args),
157
+ ...(within ? { within } : {}),
158
+ });
148
159
  // Judged on each skill's SKILL.md, by the default branch's rules — a
149
160
  // skill only on a draft is judged where it would land. Each verdict goes
150
161
  // onto a COPY: the summaries may be the catalog shared across users.
151
162
  const verdicts = await verdictsFor(ctx, skills.map(skillMdOf));
152
163
  return { skills: skills.map((s) => ({ ...s, canWrite: verdicts.get(skillMdOf(s)) === true })) };
153
- }),
164
+ }, { discovery: true }),
154
165
  );
155
166
 
156
167
  router.post(
@@ -174,7 +185,12 @@ export function registerSkillsTools(
174
185
  400,
175
186
  );
176
187
  }
177
- const result = await skillService.getSkill(ctx.user.email, name, file, { version, branch });
188
+ const within = ctx.connectionScope?.skillFilter();
189
+ const result = await skillService.getSkill(ctx.user.email, name, file, {
190
+ version,
191
+ branch,
192
+ ...(within ? { within } : {}),
193
+ });
178
194
  if (!result.ok) return result;
179
195
  if (result.kind === 'file') {
180
196
  const verdicts = await verdictsFor(ctx, [result.file.path]);
@@ -193,7 +209,7 @@ export function registerSkillsTools(
193
209
  };
194
210
  if (!allowedTools) return { ...result, skill };
195
211
  return { ...result, skill, warnings: await allowedTools.check(ctx.user.email, result.skill.allowedTools) };
196
- }),
212
+ }, { discovery: true }),
197
213
  );
198
214
  }
199
215
 
@@ -223,8 +239,13 @@ function branchArg(args: Record<string, unknown>): string | undefined {
223
239
  * the tool's fixed text leaves under {@link TOOL_DESCRIPTION_CAP} once the
224
240
  * guide-first sentence the registry puts in front is counted.
225
241
  */
226
- async function availableSkillsLine(skillService: ISkillService, userEmail: string | undefined, budget: number): Promise<string> {
227
- const skills = await skillService.listSkills(userEmail);
242
+ async function availableSkillsLine(
243
+ skillService: ISkillService,
244
+ userEmail: string | undefined,
245
+ budget: number,
246
+ within?: SkillScope,
247
+ ): Promise<string> {
248
+ const skills = await skillService.listSkills(userEmail, within ? { within } : {});
228
249
  if (skills.length === 0) return 'No skills are currently available.';
229
250
  const names = skills.map((s) => `\`${s.name}\``);
230
251
  const rest = (shown: number): string =>
@@ -276,12 +297,17 @@ const LIST_SKILLS_DESCRIPTION =
276
297
  'Each skill carries `canWrite`: whether you may change its SKILL.md directly on the default branch. ' +
277
298
  `${CAN_WRITE_CLAUSE} `;
278
299
 
279
- async function buildListSkillsDef(skillService: ISkillService, userEmail?: string): Promise<UtcpTool> {
300
+ async function buildListSkillsDef(skillService: ISkillService, userEmail?: string, within?: SkillScope): Promise<UtcpTool> {
280
301
  return toolDef({
281
302
  name: 'list_skills',
282
303
  description:
283
304
  LIST_SKILLS_DESCRIPTION +
284
- (await availableSkillsLine(skillService, userEmail, skillsLineBudget('list_skills', LIST_SKILLS_INPUTS, LIST_SKILLS_DESCRIPTION))),
305
+ (await availableSkillsLine(
306
+ skillService,
307
+ userEmail,
308
+ skillsLineBudget('list_skills', LIST_SKILLS_INPUTS, LIST_SKILLS_DESCRIPTION),
309
+ within,
310
+ )),
285
311
  path: '/api/agent/tools/list_skills',
286
312
  inputs: LIST_SKILLS_INPUTS,
287
313
  outputs: {
@@ -334,12 +360,17 @@ const GET_SKILL_DESCRIPTION =
334
360
  'The skill (or `file`) carries `canWrite`: whether you may change all of it directly on the default branch. ' +
335
361
  `${CAN_WRITE_CLAUSE} `;
336
362
 
337
- async function buildGetSkillDef(skillService: ISkillService, userEmail?: string): Promise<UtcpTool> {
363
+ async function buildGetSkillDef(skillService: ISkillService, userEmail?: string, within?: SkillScope): Promise<UtcpTool> {
338
364
  return toolDef({
339
365
  name: 'get_skill',
340
366
  description:
341
367
  GET_SKILL_DESCRIPTION +
342
- (await availableSkillsLine(skillService, userEmail, skillsLineBudget('get_skill', GET_SKILL_INPUTS, GET_SKILL_DESCRIPTION))),
368
+ (await availableSkillsLine(
369
+ skillService,
370
+ userEmail,
371
+ skillsLineBudget('get_skill', GET_SKILL_INPUTS, GET_SKILL_DESCRIPTION),
372
+ within,
373
+ )),
343
374
  path: '/api/agent/tools/get_skill',
344
375
  inputs: GET_SKILL_INPUTS,
345
376
  outputs: {
@@ -150,6 +150,7 @@ describe('ExternalApiKeyService', () => {
150
150
  revokedAt: null,
151
151
  revokedBy: null,
152
152
  deletedAt: null,
153
+ agentPath: null,
153
154
  });
154
155
  });
155
156
 
@@ -370,6 +371,7 @@ describe('ExternalApiKeyService', () => {
370
371
  revokedAt: null,
371
372
  revokedBy: null,
372
373
  deletedAt: null,
374
+ agentPath: null,
373
375
  });
374
376
  expect(summaries[1].revokedAt).toBe(rows[1].revokedAt!.getTime());
375
377
  // sanity-check that ordering was requested (we can't introspect the
@@ -401,6 +403,7 @@ describe('ExternalApiKeyService', () => {
401
403
  revokedAt: null,
402
404
  revokedBy: null,
403
405
  deletedAt: null,
406
+ agentPath: null,
404
407
  user: { id: 'u-alice', email: 'alice@example.com', name: 'Alice' },
405
408
  },
406
409
  {
@@ -412,6 +415,7 @@ describe('ExternalApiKeyService', () => {
412
415
  revokedAt: bobKey.revokedAt!.getTime(),
413
416
  revokedBy: null,
414
417
  deletedAt: null,
418
+ agentPath: null,
415
419
  user: { id: 'u-bob', email: 'bob@example.com', name: 'Bob' },
416
420
  },
417
421
  ]);
@@ -43,6 +43,11 @@ export interface ExternalApiKeySummary {
43
43
  * its row stays so the Audit log's events keep their principal.
44
44
  */
45
45
  deletedAt: number | null;
46
+ /**
47
+ * The agent configuration the key connects as (a repo-relative knowledge-base
48
+ * path), or null for Everything — chosen when the key was minted.
49
+ */
50
+ agentPath: string | null;
46
51
  }
47
52
 
48
53
  /** Who revoked a key: its owner, or an admin acting across the deployment. */
@@ -97,6 +102,11 @@ export interface KeyKindSpec {
97
102
  */
98
103
  export interface MintOptions {
99
104
  kind?: string;
105
+ /**
106
+ * The agent configuration the key connects as, already checked to be one the
107
+ * owner may choose (`IAgentScopeResolver.choosable`). Absent: Everything.
108
+ */
109
+ agentPath?: string;
100
110
  }
101
111
 
102
112
  /**
@@ -138,7 +148,7 @@ export interface IExternalApiKeyService {
138
148
  */
139
149
  verifyAndLoadToken(
140
150
  plaintext: string,
141
- ): Promise<{ tokenId: string; user: AuthUser } | null>;
151
+ ): Promise<{ tokenId: string; user: AuthUser; agentPath?: string | null } | null>;
142
152
 
143
153
  /**
144
154
  * For a key {@link verifyAndLoadToken} refused: whether it is a live key
@@ -89,7 +89,7 @@ export class ExternalApiKeyService implements IExternalApiKeyService {
89
89
 
90
90
  const [row] = await this.db
91
91
  .insert(externalApiKeys)
92
- .values({ userId, tokenHash, label: trimmed, kind })
92
+ .values({ userId, tokenHash, label: trimmed, kind, ...(options.agentPath ? { agentPath: options.agentPath } : {}) })
93
93
  .returning();
94
94
 
95
95
  return {
@@ -118,7 +118,7 @@ export class ExternalApiKeyService implements IExternalApiKeyService {
118
118
 
119
119
  async verifyAndLoadToken(
120
120
  plaintext: string,
121
- ): Promise<{ tokenId: string; user: AuthUser } | null> {
121
+ ): Promise<{ tokenId: string; user: AuthUser; agentPath: string | null } | null> {
122
122
  if (!this.looksLikeExternalApiKey(plaintext)) {
123
123
  return null;
124
124
  }
@@ -135,6 +135,7 @@ export class ExternalApiKeyService implements IExternalApiKeyService {
135
135
  tokenId: externalApiKeys.id,
136
136
  label: externalApiKeys.label,
137
137
  lastUsedAt: externalApiKeys.lastUsedAt,
138
+ agentPath: externalApiKeys.agentPath,
138
139
  userId: users.id,
139
140
  email: users.email,
140
141
  name: users.name,
@@ -176,6 +177,7 @@ export class ExternalApiKeyService implements IExternalApiKeyService {
176
177
 
177
178
  return {
178
179
  tokenId: row.tokenId,
180
+ agentPath: row.agentPath,
179
181
  user: {
180
182
  id: row.userId,
181
183
  email: row.email,
@@ -353,5 +355,6 @@ function toSummary(row: typeof externalApiKeys.$inferSelect): ExternalApiKeySumm
353
355
  revokedAt: row.revokedAt ? row.revokedAt.getTime() : null,
354
356
  revokedBy: (row.revokedBy as RevokedBy | null) ?? null,
355
357
  deletedAt: row.deletedAt ? row.deletedAt.getTime() : null,
358
+ agentPath: row.agentPath ?? null,
356
359
  };
357
360
  }
@@ -10,5 +10,5 @@ import './external-api-key.interface.js';
10
10
  export type { InternalTokenService } from './internal-token.service.js';
11
11
  export type { ILlmUsageMeter } from './llm-usage-meter.js';
12
12
  export { createTokenVerifier, requireExternalSource, requireInternalSource } from './tool-auth.middleware.js';
13
- export type { ToolAuth } from './tool-auth.middleware.js';
13
+ export type { ConnectionChoices, ToolAuth } from './tool-auth.middleware.js';
14
14
  import './tool-auth.middleware.js';