@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
@@ -19,6 +19,7 @@ import type { AuthUser } from '@bevel-software/platform-shared';
19
19
  import '../auth/index.js'; // Express Request augmentation (req.userId / req.userEmail)
20
20
  import '../tool-auth/index.js'; // Express Request augmentation (req.toolAuth)
21
21
  import { hasGitInternalsSegment } from '../../shared/git-internals.js';
22
+ import { NOT_AVAILABLE_TO_CONNECTION, resolveConnectionScope, type IAgentScopeResolver } from '../agent-scope/index.js';
22
23
 
23
24
  /** Resolve a connection-key/internal-token user id to its email (per-caller ACL). */
24
25
  export type ResolveUserEmail = (userId: string) => Promise<string | undefined>;
@@ -64,6 +65,11 @@ export function createToolManualsAgentRoutes(
64
65
  */
65
66
  pluginIndex?: Pick<IPluginIndexService, 'catalog'>;
66
67
  },
68
+ /**
69
+ * Resolves the scope of a connection made as an agent configuration, so
70
+ * `all-tools` lists only the connectors it loads (`modules/agent-scope`).
71
+ */
72
+ agentScope?: Pick<IAgentScopeResolver, 'resolve'>,
67
73
  ): express.Router {
68
74
  const router = express.Router();
69
75
 
@@ -83,9 +89,18 @@ export function createToolManualsAgentRoutes(
83
89
  // Default remote-safe: exclude local-only manuals unless the caller (a locally
84
90
  // installed MCP server) explicitly opts in with `?remote=false`.
85
91
  const remoteOnly = req.query.remote !== 'false';
86
- const userManuals = email ? await toolManualService.toManualCallTemplates(email, { remoteOnly }) : [];
92
+ // A connection made as an agent configuration lists only the connectors that
93
+ // file loads, and says which agent it is — the MCP proxy words its
94
+ // refusals and its handshake from that. Everything lists as it always
95
+ // did, with no `connection` at all.
96
+ const scope = await resolveConnectionScope(agentScope, email, req.toolAuth?.agentPath);
97
+ const within = scope.connectorFilter();
98
+ const userManuals = email
99
+ ? await toolManualService.toManualCallTemplates(email, within ? { remoteOnly, within } : { remoteOnly })
100
+ : [];
87
101
  const manuals: CallTemplate[] = [kbManualTemplate(), ...userManuals];
88
- res.json({ manuals });
102
+ const connection = scope.toWire();
103
+ res.json(connection ? { manuals, connection } : { manuals });
89
104
  } catch (err) {
90
105
  log.error('all-tools failed:', { err });
91
106
  res.status(500).json({ error: 'Failed to list tools' });
@@ -171,6 +186,24 @@ export function createToolManualsAgentRoutes(
171
186
  email,
172
187
  rels.map((r) => `${pluginRel}/${r}`),
173
188
  );
189
+ // A folder with nothing the person may read is absent to them, whatever
190
+ // the connection — so that 404 comes first, and the connection's own
191
+ // refusal below never confirms a folder the person could not see.
192
+ if (![...verdicts.values()].some((v) => v === true)) {
193
+ return void res.status(404).json({ error: 'Not found' });
194
+ }
195
+ // A connection made as an agent configuration materializes only a plugin
196
+ // that defines a connector the file loads — the archive is how the
197
+ // local MCP server runs one, and nothing else asks for it. Checked
198
+ // before any file is opened.
199
+ const scope = await resolveConnectionScope(agentScope, email, req.toolAuth?.agentPath);
200
+ if (!scope.loadsConnectorIn(pluginRel)) {
201
+ const why = scope.describe();
202
+ return void res.status(403).json({
203
+ error: `The plugin "${folder}" is not available to this connection.${why ? ` ${why}` : ''}`,
204
+ code: NOT_AVAILABLE_TO_CONNECTION,
205
+ });
206
+ }
174
207
  const zip = new AdmZip();
175
208
  let included = 0;
176
209
  let bytes = 0;
@@ -269,7 +302,11 @@ export function createToolManualsAgentRoutes(
269
302
  const email = await callerEmail(req);
270
303
  if (!email) return void res.status(403).json({ error: 'Forbidden' });
271
304
  const slug = Array.isArray(req.params.slug) ? req.params.slug[0] : req.params.slug;
272
- const manual = await toolManualService.resolveInlineManual(email, String(slug));
305
+ // A connection made as an agent configuration reads only the manuals of the
306
+ // connectors it loads; another is as absent as one the person cannot read.
307
+ const scope = await resolveConnectionScope(agentScope, email, req.toolAuth?.agentPath);
308
+ const within = scope.connectorFilter();
309
+ const manual = await toolManualService.resolveInlineManual(email, String(slug), within ? { within } : undefined);
273
310
  if (!manual) return void res.status(404).json({ error: 'Not found' });
274
311
  res.json(manual);
275
312
  } catch (err) {
@@ -43,6 +43,7 @@ import {
43
43
  EXTERNAL_KB_MANUAL_NAME,
44
44
  MAX_CAPABILITIES,
45
45
  type IToolManualService,
46
+ type ConnectorScope,
46
47
  type ToolManualDescriptor,
47
48
  type ToolManualDescriptorBase,
48
49
  type ToolManualSummary,
@@ -304,8 +305,8 @@ export class ToolManualService implements IToolManualService {
304
305
  return (await this.accessibleManuals(userEmail)).map(toSummary);
305
306
  }
306
307
 
307
- async listAccessibleCatalog(userEmail: string): Promise<AccessibleCatalog> {
308
- const { manuals, invalid } = await this.accessibleScan(userEmail);
308
+ async listAccessibleCatalog(userEmail: string, within?: ConnectorScope): Promise<AccessibleCatalog> {
309
+ const { manuals, invalid } = await this.accessibleScan(userEmail, within);
309
310
  return { tools: manuals.map(toSummary), invalid };
310
311
  }
311
312
 
@@ -313,8 +314,8 @@ export class ToolManualService implements IToolManualService {
313
314
  return (await this.scan()).manuals.map(toSummary);
314
315
  }
315
316
 
316
- async catalogFingerprints(userEmail: string): Promise<string[]> {
317
- return (await this.accessibleManuals(userEmail)).map(manualFingerprint);
317
+ async catalogFingerprints(userEmail: string, within?: ConnectorScope): Promise<string[]> {
318
+ return (await this.accessibleManuals(userEmail, within)).map(manualFingerprint);
318
319
  }
319
320
 
320
321
  async getDetail(userEmail: string, slug: string): Promise<ToolManualDetail | null> {
@@ -341,14 +342,15 @@ export class ToolManualService implements IToolManualService {
341
342
  };
342
343
  }
343
344
 
344
- async listLocalOnly(userEmail: string): Promise<{ slug: string; name: string; path: string }[]> {
345
- const manuals = await this.accessibleManuals(userEmail);
345
+ async listLocalOnly(userEmail: string, within?: ConnectorScope): Promise<{ slug: string; name: string; path: string }[]> {
346
+ const manuals = await this.accessibleManuals(userEmail, within);
346
347
  return manuals.filter((m) => m.remote === false).map((m) => ({ slug: m.slug, name: m.name, path: m.path }));
347
348
  }
348
349
 
349
350
  async listDeclaredOnlyOnBranch(
350
351
  userEmail: string,
351
352
  branch: string,
353
+ within?: ConnectorScope,
352
354
  ): Promise<{ name: string; path: string; type: ToolManualType }[]> {
353
355
  if (!branch || branch === this.kb.defaultBranch) return [];
354
356
  // Only a draft this process already holds a clone of — the one the caller
@@ -377,7 +379,7 @@ export class ToolManualService implements IToolManualService {
377
379
  pending.map((m) => m.path),
378
380
  );
379
381
  return pending
380
- .filter((m) => allowed.get(m.path) === true)
382
+ .filter((m) => allowed.get(m.path) === true && (within === undefined || within(m.path)))
381
383
  .map((m) => ({ name: m.name, path: m.path, type: m.type }));
382
384
  }
383
385
 
@@ -456,8 +458,11 @@ export class ToolManualService implements IToolManualService {
456
458
  return { name: m.name, type: m.type, remote: m.remote, healthCheck: m.healthCheck, callTemplate };
457
459
  }
458
460
 
459
- async toManualCallTemplates(userEmail: string, opts?: { remoteOnly?: boolean }): Promise<CallTemplate[]> {
460
- const manuals = await this.accessibleManuals(userEmail);
461
+ async toManualCallTemplates(
462
+ userEmail: string,
463
+ opts?: { remoteOnly?: boolean; within?: ConnectorScope },
464
+ ): Promise<CallTemplate[]> {
465
+ const manuals = await this.accessibleManuals(userEmail, opts?.within);
461
466
  const out: CallTemplate[] = [];
462
467
  for (const m of manuals) {
463
468
  // Remote consumers (the hosted MCP proxy) don't get local-only manuals —
@@ -474,11 +479,17 @@ export class ToolManualService implements IToolManualService {
474
479
  return out;
475
480
  }
476
481
 
477
- async resolveInlineManual(userEmail: string, slug: string): Promise<UtcpManualDict | null> {
482
+ async resolveInlineManual(
483
+ userEmail: string,
484
+ slug: string,
485
+ opts?: { within?: ConnectorScope },
486
+ ): Promise<UtcpManualDict | null> {
478
487
  const found = (await this.scan()).manuals.find((m) => m.slug === slug);
479
488
  if (!found || found.type !== 'inline') return null;
480
489
  const wsId = this.kb.defaultWorkspaceId();
481
490
  if (!(await this.accessControl.canRead(wsId, userEmail, found.path))) return null;
491
+ // After the read gate, as every narrowing is: a connection's scope can only take away.
492
+ if (opts?.within && !opts.within(found.path)) return null;
482
493
  try {
483
494
  return manualSerializer.validateDict({
484
495
  utcp_version: '1.1.0',
@@ -590,8 +601,8 @@ export class ToolManualService implements IToolManualService {
590
601
  };
591
602
  }
592
603
 
593
- private async accessibleManuals(userEmail: string): Promise<ToolManualDescriptor[]> {
594
- return (await this.accessibleScan(userEmail)).manuals;
604
+ private async accessibleManuals(userEmail: string, within?: ConnectorScope): Promise<ToolManualDescriptor[]> {
605
+ return (await this.accessibleScan(userEmail, within)).manuals;
595
606
  }
596
607
 
597
608
  /**
@@ -599,8 +610,12 @@ export class ToolManualService implements IToolManualService {
599
610
  * ONE `canReadBatch`. Both halves are paths in the same workspace judged by
600
611
  * the same rule, and splitting them into two calls would pay a second
601
612
  * round-trip to ask the same question.
613
+ *
614
+ * `within` narrows the answer to a connection's connectors, AFTER the read
615
+ * gate: it can take a manual out of what the caller may read, never put one
616
+ * in. Absent, the answer is the caller's whole readable catalog.
602
617
  */
603
- private async accessibleScan(userEmail: string): Promise<ScanResult> {
618
+ private async accessibleScan(userEmail: string, within?: ConnectorScope): Promise<ScanResult> {
604
619
  const { manuals, invalid } = await this.scan();
605
620
  if (manuals.length === 0 && invalid.length === 0) return emptyScan();
606
621
  const wsId = this.kb.defaultWorkspaceId();
@@ -612,9 +627,10 @@ export class ToolManualService implements IToolManualService {
612
627
  // A REFUSED file is gated the same way a listed one is: its path is a fact
613
628
  // about the knowledge base, and "there is a broken tool at this path" is
614
629
  // not something to tell someone who may not read that path.
630
+ const inScope = (p: string) => allowed.get(p) === true && (within === undefined || within(p));
615
631
  return {
616
- manuals: manuals.filter((m) => allowed.get(m.path) === true),
617
- invalid: invalid.filter((i) => allowed.get(i.path) === true),
632
+ manuals: manuals.filter((m) => inScope(m.path)),
633
+ invalid: invalid.filter((i) => inScope(i.path)),
618
634
  };
619
635
  }
620
636
 
@@ -5,7 +5,7 @@ import type { ToolContext, ToolHandlerFactory } from '../tool-helpers/index.js';
5
5
  import { toolDef, declareRouteTool } from '../tool-helpers/index.js';
6
6
  import type { IAccessControl } from '../access/index.js';
7
7
  import { utcpNamespacedKey } from '../../shared/utcp-namespace.js';
8
- import type { IToolManualService } from './tool-manuals.contract.js';
8
+ import type { ConnectorScope, IToolManualService } from './tool-manuals.contract.js';
9
9
  import type { HiddenToolSource } from '../../shared/hidden-tools.js';
10
10
 
11
11
  /**
@@ -51,7 +51,9 @@ export function registerToolManualsTools(
51
51
  hiddenTools?: HiddenToolSource;
52
52
  },
53
53
  ): void {
54
- registry.registerExternalTool((ctx) => buildListLocalToolsDef(toolManualService, ctx.userEmail));
54
+ registry.registerExternalTool((ctx) =>
55
+ buildListLocalToolsDef(toolManualService, ctx.userEmail, ctx.connectionScope?.connectorFilter()),
56
+ );
55
57
  registry.registerInternalTool((ctx) => buildListLocalToolsDef(toolManualService, ctx.userEmail));
56
58
  // The def above is built per catalog listing (its description names the
57
59
  // tools configured for THIS caller), so declare the arguments here as well:
@@ -59,12 +61,22 @@ export function registerToolManualsTools(
59
61
  // against them too.
60
62
  declareRouteTool('list_local_tools', LIST_LOCAL_TOOLS_INPUTS);
61
63
 
64
+ // The local server's discovery read (`discovery`): it asks this route at
65
+ // startup for the connectors it must materialize, whatever tools the
66
+ // connection is offered — a connection made as an agent configuration that omits
67
+ // the tool is answered its connectors, not refused, or the local server
68
+ // could not start for that agent at all.
62
69
  router.post(
63
70
  '/agent/tools/list_local_tools',
64
71
  toolAuth,
65
- toolHandler(async (_args, ctx: ToolContext) => ({
66
- tools: await toolManualService.listLocalOnly(ctx.user.email),
67
- })),
72
+ toolHandler(async (_args, ctx: ToolContext) => {
73
+ const within = ctx.connectionScope?.connectorFilter();
74
+ return {
75
+ tools: await (within
76
+ ? toolManualService.listLocalOnly(ctx.user.email, within)
77
+ : toolManualService.listLocalOnly(ctx.user.email)),
78
+ };
79
+ }, { discovery: true }),
68
80
  );
69
81
 
70
82
  // A FUNCTION, not a constant — this factory runs at boot, and on a
@@ -243,12 +255,17 @@ export function registerToolManualsTools(
243
255
  toolHandler(async (args, ctx: ToolContext) => {
244
256
  // The in-app agent is focused on its own draft; an external caller names it.
245
257
  const branch = typeof args.branch === 'string' && args.branch ? args.branch : ctx.focusedBranch;
258
+ const within = ctx.connectionScope?.connectorFilter();
246
259
  // The catalog comes back whole — what parsed AND what was refused — from
247
260
  // ONE scan, so the refused files cost no second walk of the workspace and
248
261
  // the two halves cannot disagree about which files made it.
249
262
  const [{ tools: manuals, invalid }, pending] = await Promise.all([
250
- toolManualService.listAccessibleCatalog(ctx.user.email),
251
- branch ? toolManualService.listDeclaredOnlyOnBranch(ctx.user.email, branch) : Promise.resolve([]),
263
+ // Only the connectors the caller's connection loads: setup is counted
264
+ // for the tools this connection can call, not every one the person can.
265
+ within
266
+ ? toolManualService.listAccessibleCatalog(ctx.user.email, within)
267
+ : toolManualService.listAccessibleCatalog(ctx.user.email),
268
+ branch ? toolManualService.listDeclaredOnlyOnBranch(ctx.user.email, branch, within) : Promise.resolve([]),
252
269
  ]);
253
270
  const allKeys = manuals.flatMap((m) => (m.variables ?? []).map((v) => varKey(m.name, v.name)));
254
271
  const status = await deps.variableStatus.statusFor(ctx.user.id, allKeys);
@@ -301,9 +318,9 @@ export function registerToolManualsTools(
301
318
  }
302
319
 
303
320
  /** "Local-only tools configured for you: `a`, `b`." (or a none note), filtered to what the caller may read. */
304
- async function localToolsLine(svc: IToolManualService, userEmail?: string): Promise<string> {
321
+ async function localToolsLine(svc: IToolManualService, userEmail?: string, within?: ConnectorScope): Promise<string> {
305
322
  if (!userEmail) return 'No local-only tools are currently configured.';
306
- const tools = await svc.listLocalOnly(userEmail);
323
+ const tools = await (within ? svc.listLocalOnly(userEmail, within) : svc.listLocalOnly(userEmail));
307
324
  if (tools.length === 0) return 'No local-only tools are currently configured.';
308
325
  return `Local-only tools configured for you: ${tools.map((t) => `\`${t.name}\``).join(', ')}.`;
309
326
  }
@@ -311,7 +328,7 @@ async function localToolsLine(svc: IToolManualService, userEmail?: string): Prom
311
328
  /** `list_local_tools` takes nothing; a const so the route and the def share one declaration. */
312
329
  const LIST_LOCAL_TOOLS_INPUTS = { type: 'object' as const, properties: {}, additionalProperties: false };
313
330
 
314
- async function buildListLocalToolsDef(svc: IToolManualService, userEmail?: string): Promise<UtcpTool> {
331
+ async function buildListLocalToolsDef(svc: IToolManualService, userEmail?: string, within?: ConnectorScope): Promise<UtcpTool> {
315
332
  return toolDef({
316
333
  name: 'list_local_tools',
317
334
  description:
@@ -324,7 +341,7 @@ async function buildListLocalToolsDef(svc: IToolManualService, userEmail?: strin
324
341
  'machine. Otherwise each entry gives the tool’s name and its `.tool` file ' +
325
342
  'path in the knowledge base — read that file with `read_file` and wire the tool into your local ' +
326
343
  'setup by hand. ' +
327
- (await localToolsLine(svc, userEmail)),
344
+ (await localToolsLine(svc, userEmail, within)),
328
345
  path: '/api/agent/tools/list_local_tools',
329
346
  inputs: LIST_LOCAL_TOOLS_INPUTS,
330
347
  outputs: {
@@ -15,7 +15,7 @@ import type { UtcpTool } from '../tool.contract.js';
15
15
  import { createToolHandlerFactory } from '../../tool-helpers/tool-handler.js';
16
16
  import type { ToolAuth } from '../../tool-auth/tool-auth.middleware.js';
17
17
  import { registerWorkspaceTools } from '../../workspace/workspace.tools.js';
18
- import { WorkerPatternMatcher } from '../../workspace/bounded-matcher.js';
18
+ import { RipgrepPatternMatcher } from '../../workspace/index.js';
19
19
  import { registerWorkflowTools } from '../../workflow/agent-tools/workflow.tools.js';
20
20
  import { registerPluginsTools } from '../../plugins/plugins.tools.js';
21
21
  import { registerSkillsTools } from '../../skills/skills.tools.js';
@@ -115,7 +115,7 @@ async function hexisTools(skills: Parameters<typeof registerSkillsTools>[4] = em
115
115
  // mounted on the same terms (it spawns no worker until a search runs).
116
116
  undefined,
117
117
  undefined,
118
- new WorkerPatternMatcher(),
118
+ new RipgrepPatternMatcher(),
119
119
  );
120
120
  registerWorkflowTools(registry, router, toolAuth, toolHandler, kb, async () => true, { canRead: async () => true, canReadAtRef: async () => true, canReadBatch: async () => new Map(), canReadBatchAtRef: async () => new Map(), canWrite: async () => true } as never, { recoveryBotEmail: 'recovery@x', hooks: { runAgentRead: async () => undefined }, notes: { onChange: () => undefined } } as never);
121
121
  registerPluginsTools(registry, kb);
@@ -2,6 +2,7 @@ import express, { type Request, type RequestHandler } from 'express';
2
2
  import { UtcpManualSerializer } from '@utcp/sdk';
3
3
  import { declaresWrite, type IToolRegistry, type ToolManualContext, type UtcpTool } from './tool.contract.js';
4
4
  import { requireInternalSource } from '../tool-auth/index.js';
5
+ import { resolveConnectionScope, type IAgentScopeResolver } from '../agent-scope/index.js';
5
6
  import type { IToolReadOnlyHints } from './read-only-hints.contract.js';
6
7
 
7
8
  /** Resolve a connection-key/internal-token user id to its email (for per-caller provider defs). */
@@ -62,6 +63,12 @@ export function createManualRoutes(
62
63
  * Absent, the manual carries the tools' own tags only.
63
64
  */
64
65
  readOnlyHints?: Pick<IToolReadOnlyHints, 'apply'>,
66
+ /**
67
+ * Resolves the scope of a connection made as an agent configuration: the external
68
+ * manual then lists only the platform tools it loads, and the providers word
69
+ * their descriptions for it (`modules/agent-scope`).
70
+ */
71
+ agentScope?: Pick<IAgentScopeResolver, 'resolve'>,
65
72
  ): express.Router {
66
73
  const router = express.Router();
67
74
 
@@ -71,17 +78,31 @@ export function createManualRoutes(
71
78
  async function context(req: Request): Promise<ToolManualContext> {
72
79
  const userId = req.toolAuth?.userId;
73
80
  if (!userId) return {};
74
- if (!resolveUserEmail) return { userId };
81
+ let userEmail: string | undefined;
75
82
  try {
76
- return { userId, userEmail: await resolveUserEmail(userId) };
83
+ userEmail = resolveUserEmail ? await resolveUserEmail(userId) : undefined;
77
84
  } catch {
78
- return { userId };
85
+ userEmail = undefined;
79
86
  }
87
+ // Everything adds nothing to the context, so its providers are asked
88
+ // exactly what they were asked before connections could be narrowed.
89
+ const agentPath = req.toolAuth?.agentPath;
90
+ const connectionScope = agentPath ? await resolveConnectionScope(agentScope, userEmail, agentPath) : undefined;
91
+ return {
92
+ userId,
93
+ ...(userEmail !== undefined ? { userEmail } : {}),
94
+ ...(connectionScope ? { connectionScope } : {}),
95
+ };
80
96
  }
81
97
 
82
98
  router.get('/agent/utcp', manualAuth, async (req, res) => {
83
- const tools = await registry.listExternal(await context(req));
84
- res.json(toManual(readOnlyHints ? readOnlyHints.apply(tools) : tools));
99
+ const ctx = await context(req);
100
+ const tools = await registry.listExternal(ctx);
101
+ // A connection made as an agent configuration is offered only the platform
102
+ // tools that file loads; a call to another is refused at its route.
103
+ const scope = ctx.connectionScope;
104
+ const offered = scope ? tools.filter((t) => scope.allowsPlatformTool(t.name)) : tools;
105
+ res.json(toManual(readOnlyHints ? readOnlyHints.apply(offered) : offered));
85
106
  });
86
107
  router.get('/agent/internal/utcp', manualAuth, requireInternalSource, async (req, res) => {
87
108
  const internal = await registry.listInternal(await context(req));
@@ -1,4 +1,5 @@
1
1
  import type { JsonSchema, Tool as UtcpTool } from '@utcp/sdk';
2
+ import type { ConnectionScope } from '../agent-scope/index.js';
2
3
 
3
4
  export type { JsonSchema, UtcpTool };
4
5
 
@@ -23,6 +24,12 @@ export interface ToolManualContext {
23
24
  userEmail?: string;
24
25
  /** The requesting user's id, when it could be resolved (for per-user gating). */
25
26
  userId?: string;
27
+ /**
28
+ * What the requesting connection may load, when it was made as an agent configuration
29
+ * file (`modules/agent-scope`): a provider that names skills or connectors
30
+ * in its description names only those. Absent is Everything.
31
+ */
32
+ connectionScope?: ConnectionScope;
26
33
  }
27
34
 
28
35
  /**
@@ -250,7 +250,7 @@ function makeReviewWorkflow(): IReviewWorkflowService {
250
250
  describe('WorkflowService — branch delegation', () => {
251
251
  it('listBranches delegates to git.listBranches', async () => {
252
252
  const git = makeGit();
253
- const branches = [{ name: 'main', isCurrent: true, isProtected: true, ahead: 0, behind: 0, hasRemote: true }];
253
+ const branches = [{ name: 'main', isCurrent: true, isProtected: true, hasRemote: true }];
254
254
  (git.listBranches as ReturnType<typeof vi.fn>).mockResolvedValue(branches);
255
255
 
256
256
  const svc = new WorkflowService(makeDb(), git, makePrs(), makeReviewWorkflow(), makeWorkspaceService(), makeAccessControl(), makeFileLockService(), makePendingCommits(), testKbContext(), openChangeGate());
@@ -344,7 +344,7 @@ describe('WorkflowService.retireRemoteGoneClone — what can bring the branch ba
344
344
  }),
345
345
  createBranch: vi.fn(async (_ws: string, name: string) => {
346
346
  order.push('create');
347
- return { name, isProtected: false, ahead: 0, behind: 0, hasRemote: true };
347
+ return { name, isProtected: false, hasRemote: true };
348
348
  }),
349
349
  } as unknown as GitService;
350
350
  const workspaceService = {
@@ -104,6 +104,9 @@ const workspaceService = {
104
104
  const workflowService = {
105
105
  listBranches: async (ws: string) => {
106
106
  calls.push(['listBranches', ws]);
107
+ // A real listing fetches first; mark when that fetch has landed.
108
+ await new Promise((r) => setTimeout(r, 0));
109
+ calls.push(['listBranches:fetched', ws]);
107
110
  return [{ name: 'main', isProtected: true }];
108
111
  },
109
112
  commitChange: async (ws: string, user: { id: string }, body: unknown) => {
@@ -117,7 +120,7 @@ const workflowService = {
117
120
  },
118
121
  createBranch: async (ws: string, name: string) => {
119
122
  calls.push(['createBranch', ws, name]);
120
- return { name, isProtected: false, ahead: 0, behind: 0, hasRemote: true };
123
+ return { name, isProtected: false, hasRemote: true };
121
124
  },
122
125
  acquireLock: async (ws: string, branch: string, path: string) => {
123
126
  calls.push(['acquireLock', ws, branch, path]);
@@ -206,6 +209,10 @@ const workflowService = {
206
209
  calls.push(['branchStatus', ws]);
207
210
  return { branch: ws, hasUpstream: true, unmergedFromUpstream: true };
208
211
  },
212
+ branchCounts: async (ws: string) => {
213
+ calls.push(['branchCounts', ws]);
214
+ return { ahead: 3, behind: 1 };
215
+ },
209
216
  listPendingChangePaths: async (ws: string) => {
210
217
  calls.push(['listPendingChangePaths', ws]);
211
218
  return ['knowledge-base/KnowledgeBase/Pending.md'];
@@ -518,6 +525,24 @@ describe('registerWorkflowTools', () => {
518
525
  expect(calls).toContainEqual(['listBranches', 'existing-ws']);
519
526
  });
520
527
 
528
+ it('list_branches declares names, protection and remote presence, and no commit counts', async () => {
529
+ await start();
530
+ for (const tools of [await registryRef!.listInternal(), await registryRef!.listExternal()]) {
531
+ const listBranches = tools.find((t) => t.name === 'list_branches')!;
532
+ const branch = (listBranches.outputs as { properties: { branches: { items: { properties: object; required: string[] } } } })
533
+ .properties.branches.items;
534
+ expect(Object.keys(branch.properties).sort()).toEqual(['hasRemote', 'isProtected', 'name']);
535
+ expect([...branch.required].sort()).toEqual(['hasRemote', 'isProtected', 'name']);
536
+ expect(listBranches.description).toContain('{ name, isProtected, hasRemote }');
537
+ expect(listBranches.description).not.toMatch(/\bahead\b,|\bbehind\b,/);
538
+ // The counts are still `branch_status`'s answer, for its one branch.
539
+ const branchStatus = tools.find((t) => t.name === 'branch_status')!;
540
+ expect((branchStatus.outputs as { required: string[] }).required).toEqual(
541
+ expect.arrayContaining(['ahead', 'behind', 'behindRemote']),
542
+ );
543
+ }
544
+ });
545
+
521
546
  it('open_change_request acts on the SOURCE branch\'s workspace', async () => {
522
547
  const base = await start();
523
548
  const res = await post(`${base}/api/agent/tools/open_change_request`, writeTok(), {
@@ -1008,6 +1033,26 @@ describe('save_file and the repository folder', () => {
1008
1033
  expect(calls.some((c) => c[0] === 'acquireLock')).toBe(false);
1009
1034
  });
1010
1035
 
1036
+ it('refuses a tmp ref in every path input, before any lock — save_file, the history tools and update_branch\'s resolutions alike', async () => {
1037
+ const base = await start();
1038
+ const calls_: Array<[string, Record<string, unknown>]> = [
1039
+ ['save_file', { path: '__tmp__/chat-uploads/plan.xlsx' }],
1040
+ ['read_file_at', { path: '__tmp__/chat-uploads/plan.xlsx', at: 'HEAD' }],
1041
+ ['compare_file', { path: '__tmp__/chat-uploads/plan.xlsx' }],
1042
+ ['list_file_changes', { path: '__tmp__/chat-uploads/plan.xlsx' }],
1043
+ ['update_branch', { resolutions: [{ path: '__tmp__/chat-uploads/plan.xlsx', choose: 'ours' }] }],
1044
+ ];
1045
+ for (const [tool, args] of calls_) {
1046
+ const res = await post(`${base}/api/agent/tools/${tool}`, writeTok(), { ...args, branch: WS });
1047
+ expect(res.status, tool).toBe(400);
1048
+ const body = (await res.json()) as { error: string; kind: string };
1049
+ expect(body.kind, tool).toBe('tmp-ref-read-only');
1050
+ expect(body.error).toContain(`${tool} does not take one there`);
1051
+ expect(body.error).toContain('copy_file');
1052
+ }
1053
+ expect(calls.some((c) => c[0] === 'acquireLock')).toBe(false);
1054
+ });
1055
+
1011
1056
  it('answers a missing path with a 400, not a crash', async () => {
1012
1057
  const base = await start();
1013
1058
  const res = await post(`${base}/api/agent/tools/save_file`, writeTok(), { branch: WS });
@@ -1165,8 +1210,9 @@ describe('history and syncing tools', () => {
1165
1210
  // The listing double names only `main`, so the draft's own entry is unknown.
1166
1211
  isProtected: false,
1167
1212
  hasRemote: true,
1168
- ahead: null,
1169
- behind: null,
1213
+ // Counted for the draft alone, by its own workspace — not read off a listing.
1214
+ ahead: 3,
1215
+ behind: 1,
1170
1216
  behindRemote: true,
1171
1217
  uncommittedPaths: ['KnowledgeBase/Pending.md'],
1172
1218
  forkBase: 'main',
@@ -1175,6 +1221,11 @@ describe('history and syncing tools', () => {
1175
1221
  withheldPaths: 0,
1176
1222
  });
1177
1223
  expect(calls).toContainEqual(['changedPathsBetweenBranches', DRAFT_WS, 'main', DRAFT]);
1224
+ expect(calls).toContainEqual(['branchCounts', DRAFT_WS]);
1225
+ // Counted only once the listing's fetch has refreshed the remote-tracking refs.
1226
+ const fetched = calls.findIndex((c) => c[0] === 'listBranches:fetched' && c[1] === DRAFT_WS);
1227
+ expect(fetched).toBeGreaterThanOrEqual(0);
1228
+ expect(calls.findIndex((c) => c[0] === 'branchCounts')).toBeGreaterThan(fetched);
1178
1229
 
1179
1230
  calls = [];
1180
1231
  const named = await post(tool(base, 'branch_status'), writeTok(), { branch: DRAFT, base: 'release' });
@@ -36,11 +36,9 @@ const branchSchema: JsonSchema = {
36
36
  properties: {
37
37
  name: { type: 'string' },
38
38
  isProtected: { type: 'boolean' },
39
- ahead: { type: ['integer', 'null'], description: 'Commits ahead of upstream / nearest protected base; null if unresolved.' },
40
- behind: { type: ['integer', 'null'], description: 'Commits behind; null if unresolved.' },
41
39
  hasRemote: { type: 'boolean', description: 'True iff `origin/<name>` exists after the last fetch.' },
42
40
  },
43
- required: ['name', 'isProtected', 'ahead', 'behind', 'hasRemote'],
41
+ required: ['name', 'isProtected', 'hasRemote'],
44
42
  };
45
43
 
46
44
  const commitSchema: JsonSchema = {
@@ -239,7 +237,9 @@ export function registerWorkflowTools(
239
237
  // `getFilesystem` — so the branch-less call the tool handler refuses
240
238
  // before this runs would otherwise commit to, or fork from, a
241
239
  // workspace literally named "undefined".
242
- (args, ctx) => spec.handler(normalizePathArgs(args, kbDirName), ctx),
240
+ // The normaliser also refuses a tmp ref (`__tmp__/…`) in every path
241
+ // input: none of these tools takes one (see `kb-fs/tmp-ref.ts`).
242
+ (args, ctx) => spec.handler(normalizePathArgs(args, kbDirName, { tool: spec.name }), ctx),
243
243
  { write: spec.write },
244
244
  ),
245
245
  );
@@ -258,7 +258,8 @@ export function registerWorkflowTools(
258
258
  name: 'list_branches',
259
259
  description:
260
260
  'List every draft known to the repository (protected versions + unprotected drafts, ' +
261
- 'local and on the shared remote). Returns `{ branches: [{ name, isProtected, ahead, behind, hasRemote }] }`. ' +
261
+ 'local and on the shared remote). Returns `{ branches: [{ name, isProtected, hasRemote }] }`; ' +
262
+ 'how far one branch is ahead or behind is `branch_status`\'s answer. ' +
262
263
  'Takes no arguments — branch listing is repo-global, not scoped to any one draft.',
263
264
  // Repo-global: the branch set is the same from any clone, so don't require
264
265
  // (and risk cloning) a model-named branch — resolve any existing workspace.
@@ -978,9 +979,13 @@ export function registerWorkflowTools(
978
979
  // and a save or sync landing between two of them is tolerated — the
979
980
  // next call reports it. (The one answer that must be exact, what a
980
981
  // merge is of, is taken under one reservation by `update_branch`.)
981
- const [status, branches, pending, forkBase] = await Promise.all([
982
+ // The counts alone wait for the listing: its fetch refreshes the
983
+ // remote-tracking refs they are measured against.
984
+ const [status, [branches, counts], pending, forkBase] = await Promise.all([
982
985
  ctx.workflowService.branchStatus(workspaceId),
983
- ctx.workflowService.listBranches(workspaceId),
986
+ ctx.workflowService
987
+ .listBranches(workspaceId)
988
+ .then(async (listed) => [listed, await ctx.workflowService.branchCounts(workspaceId)] as const),
984
989
  ctx.workflowService.listPendingChangePaths(workspaceId),
985
990
  ctx.workflowService.resolveForkBase(workspaceId, branch),
986
991
  ]);
@@ -1000,8 +1005,8 @@ export function registerWorkflowTools(
1000
1005
  branch,
1001
1006
  isProtected: info?.isProtected ?? kb.protectedBranches.has(branch),
1002
1007
  hasRemote: info?.hasRemote ?? status.hasUpstream,
1003
- ahead: info?.ahead ?? null,
1004
- behind: info?.behind ?? null,
1008
+ ahead: counts.ahead,
1009
+ behind: counts.behind,
1005
1010
  behindRemote: status.unmergedFromUpstream,
1006
1011
  uncommittedPaths,
1007
1012
  forkBase,