@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.
- package/agent-guide/access-control.md +19 -8
- package/agent-guide/html-views.md +231 -5
- package/agent-guide/tool-manuals.md +1 -1
- package/dist/core/catalog-revision.d.ts +12 -1
- package/dist/core/catalog-revision.d.ts.map +1 -1
- package/dist/core/catalog-revision.js +23 -3
- package/dist/core/catalog-revision.js.map +1 -1
- package/dist/core/core-ports.d.ts +41 -0
- package/dist/core/core-ports.d.ts.map +1 -1
- package/dist/core/core-ports.js.map +1 -1
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +14 -7
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +16 -5
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +37 -12
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/core/lifecycle.d.ts +16 -0
- package/dist/core/lifecycle.d.ts.map +1 -1
- package/dist/core/lifecycle.js +12 -2
- package/dist/core/lifecycle.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +17 -9
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +12 -2
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +164 -75
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/access-mutation.service.d.ts +22 -24
- package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
- package/dist/modules/access/access-mutation.service.js +48 -51
- package/dist/modules/access/access-mutation.service.js.map +1 -1
- package/dist/modules/access/access-view.d.ts.map +1 -1
- package/dist/modules/access/access-view.js +12 -11
- package/dist/modules/access/access-view.js.map +1 -1
- package/dist/modules/access/access.routes.d.ts +3 -0
- package/dist/modules/access/access.routes.d.ts.map +1 -1
- package/dist/modules/access/access.routes.js +35 -29
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/access/groups-admin.routes.d.ts.map +1 -1
- package/dist/modules/access/groups-admin.routes.js +4 -2
- package/dist/modules/access/groups-admin.routes.js.map +1 -1
- package/dist/modules/access/groups-admin.service.d.ts +44 -13
- package/dist/modules/access/groups-admin.service.d.ts.map +1 -1
- package/dist/modules/access/groups-admin.service.js +80 -38
- package/dist/modules/access/groups-admin.service.js.map +1 -1
- package/dist/modules/access/groups-edit.d.ts +1 -1
- package/dist/modules/access/groups-edit.d.ts.map +1 -1
- package/dist/modules/access/groups-edit.js +1 -1
- package/dist/modules/access/groups-edit.js.map +1 -1
- package/dist/modules/access/reference-scan.d.ts +116 -24
- package/dist/modules/access/reference-scan.d.ts.map +1 -1
- package/dist/modules/access/reference-scan.js +305 -44
- package/dist/modules/access/reference-scan.js.map +1 -1
- package/dist/modules/access/roles-admin.service.d.ts +17 -8
- package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
- package/dist/modules/access/roles-admin.service.js +32 -23
- package/dist/modules/access/roles-admin.service.js.map +1 -1
- package/dist/modules/access-model/access-grammar.d.ts +35 -10
- package/dist/modules/access-model/access-grammar.d.ts.map +1 -1
- package/dist/modules/access-model/access-grammar.js +60 -20
- package/dist/modules/access-model/access-grammar.js.map +1 -1
- package/dist/modules/access-model/access-splice.d.ts +22 -18
- package/dist/modules/access-model/access-splice.d.ts.map +1 -1
- package/dist/modules/access-model/access-splice.js +20 -14
- package/dist/modules/access-model/access-splice.js.map +1 -1
- package/dist/modules/access-model/group-files.d.ts +1 -0
- package/dist/modules/access-model/group-files.d.ts.map +1 -1
- package/dist/modules/access-model/group-files.js +5 -1
- package/dist/modules/access-model/group-files.js.map +1 -1
- package/dist/modules/agent-guide/agent-guide.tools.d.ts.map +1 -1
- package/dist/modules/agent-guide/agent-guide.tools.js +13 -3
- package/dist/modules/agent-guide/agent-guide.tools.js.map +1 -1
- package/dist/modules/agent-instructions/compose.d.ts +3 -1
- package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
- package/dist/modules/agent-instructions/compose.js +3 -1
- package/dist/modules/agent-instructions/compose.js.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.d.ts +5 -1
- package/dist/modules/agent-instructions/shared-file-rules.d.ts.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.js +16 -1
- package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
- package/dist/modules/agent-scope/agent-scope.contract.d.ts +67 -0
- package/dist/modules/agent-scope/agent-scope.contract.d.ts.map +1 -0
- package/dist/modules/agent-scope/agent-scope.contract.js +27 -0
- package/dist/modules/agent-scope/agent-scope.contract.js.map +1 -0
- package/dist/modules/agent-scope/connection-scope.d.ts +111 -0
- package/dist/modules/agent-scope/connection-scope.d.ts.map +1 -0
- package/dist/modules/agent-scope/connection-scope.js +165 -0
- package/dist/modules/agent-scope/connection-scope.js.map +1 -0
- package/dist/modules/agent-scope/index.d.ts +3 -0
- package/dist/modules/agent-scope/index.d.ts.map +1 -0
- package/dist/modules/agent-scope/index.js +3 -0
- package/dist/modules/agent-scope/index.js.map +1 -0
- package/dist/modules/audit/agent-audit.service.d.ts.map +1 -1
- package/dist/modules/audit/agent-audit.service.js +2 -0
- package/dist/modules/audit/agent-audit.service.js.map +1 -1
- package/dist/modules/audit/audit.contract.d.ts +6 -0
- package/dist/modules/audit/audit.contract.d.ts.map +1 -1
- package/dist/modules/audit/audit.contract.js.map +1 -1
- package/dist/modules/auth/account-admission.d.ts +12 -0
- package/dist/modules/auth/account-admission.d.ts.map +1 -1
- package/dist/modules/auth/account-admission.js +15 -0
- package/dist/modules/auth/account-admission.js.map +1 -1
- package/dist/modules/auth/auth.middleware.d.ts +27 -1
- package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
- package/dist/modules/auth/auth.middleware.js +42 -4
- package/dist/modules/auth/auth.middleware.js.map +1 -1
- package/dist/modules/auth/auth.routes.d.ts +13 -9
- package/dist/modules/auth/auth.routes.d.ts.map +1 -1
- package/dist/modules/auth/auth.routes.js +35 -29
- package/dist/modules/auth/auth.routes.js.map +1 -1
- package/dist/modules/auth/auth.service.d.ts +55 -6
- package/dist/modules/auth/auth.service.d.ts.map +1 -1
- package/dist/modules/auth/auth.service.js +128 -34
- package/dist/modules/auth/auth.service.js.map +1 -1
- package/dist/modules/auth/index.d.ts +1 -1
- package/dist/modules/auth/index.d.ts.map +1 -1
- package/dist/modules/auth/index.js +1 -1
- package/dist/modules/auth/index.js.map +1 -1
- package/dist/modules/auth/oidc-auth-provider.d.ts.map +1 -1
- package/dist/modules/auth/oidc-auth-provider.js +2 -9
- package/dist/modules/auth/oidc-auth-provider.js.map +1 -1
- package/dist/modules/database/core-schema.d.ts +68 -0
- package/dist/modules/database/core-schema.d.ts.map +1 -1
- package/dist/modules/database/core-schema.js +33 -0
- package/dist/modules/database/core-schema.js.map +1 -1
- package/dist/modules/declared-variables/declared-variables.routes.d.ts +8 -1
- package/dist/modules/declared-variables/declared-variables.routes.d.ts.map +1 -1
- package/dist/modules/declared-variables/declared-variables.routes.js +13 -2
- package/dist/modules/declared-variables/declared-variables.routes.js.map +1 -1
- package/dist/modules/embed/embed.tools.d.ts.map +1 -1
- package/dist/modules/embed/embed.tools.js +5 -0
- package/dist/modules/embed/embed.tools.js.map +1 -1
- package/dist/modules/kb-fs/index.d.ts +2 -0
- package/dist/modules/kb-fs/index.d.ts.map +1 -1
- package/dist/modules/kb-fs/index.js +1 -0
- package/dist/modules/kb-fs/index.js.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.js +7 -1
- package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
- package/dist/modules/kb-fs/repo-path.d.ts +26 -6
- package/dist/modules/kb-fs/repo-path.d.ts.map +1 -1
- package/dist/modules/kb-fs/repo-path.js +22 -7
- package/dist/modules/kb-fs/repo-path.js.map +1 -1
- package/dist/modules/kb-fs/tmp-ref.d.ts +36 -0
- package/dist/modules/kb-fs/tmp-ref.d.ts.map +1 -0
- package/dist/modules/kb-fs/tmp-ref.js +76 -0
- package/dist/modules/kb-fs/tmp-ref.js.map +1 -0
- package/dist/modules/mcp/mcp-auth.middleware.d.ts +7 -0
- package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.js +14 -1
- package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
- package/dist/modules/mcp/mcp.routes.d.ts +7 -1
- package/dist/modules/mcp/mcp.routes.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.routes.js +39 -4
- package/dist/modules/mcp/mcp.routes.js.map +1 -1
- package/dist/modules/mcp/mcp.service.d.ts +11 -0
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +68 -13
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +24 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js +45 -10
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts +9 -0
- package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.js +34 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.js.map +1 -1
- package/dist/modules/plugins/index.d.ts +1 -1
- package/dist/modules/plugins/index.d.ts.map +1 -1
- package/dist/modules/plugins/index.js.map +1 -1
- package/dist/modules/plugins/plugin-links.d.ts +18 -2
- package/dist/modules/plugins/plugin-links.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-links.js.map +1 -1
- package/dist/modules/plugins/plugins.routes.d.ts +5 -1
- package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.js +32 -5
- package/dist/modules/plugins/plugins.routes.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.d.ts +9 -0
- package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.js +15 -3
- package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
- package/dist/modules/skills/index.d.ts +3 -3
- package/dist/modules/skills/index.d.ts.map +1 -1
- package/dist/modules/skills/index.js +0 -2
- package/dist/modules/skills/index.js.map +1 -1
- package/dist/modules/skills/skills.contract.d.ts +16 -0
- package/dist/modules/skills/skills.contract.d.ts.map +1 -1
- package/dist/modules/skills/skills.service.d.ts.map +1 -1
- package/dist/modules/skills/skills.service.js +6 -1
- package/dist/modules/skills/skills.service.js.map +1 -1
- package/dist/modules/skills/skills.tools.d.ts.map +1 -1
- package/dist/modules/skills/skills.tools.js +24 -12
- package/dist/modules/skills/skills.tools.js.map +1 -1
- package/dist/modules/tool-auth/external-api-key.interface.d.ts +11 -0
- package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.interface.js.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.d.ts +1 -0
- package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.js +4 -1
- package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
- package/dist/modules/tool-auth/index.d.ts +1 -1
- package/dist/modules/tool-auth/index.d.ts.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.d.ts +9 -0
- package/dist/modules/tool-auth/internal-token.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.js +1 -0
- package/dist/modules/tool-auth/internal-token.service.js.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts +27 -3
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.js +32 -6
- package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
- package/dist/modules/tool-helpers/tool-context.d.ts +7 -0
- package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-context.js +3 -0
- package/dist/modules/tool-helpers/tool-context.js.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.d.ts +15 -0
- package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.js +19 -0
- package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.d.ts +10 -0
- package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
- package/dist/modules/tool-helpers/validate-token.d.ts +3 -1
- package/dist/modules/tool-helpers/validate-token.d.ts.map +1 -1
- package/dist/modules/tool-helpers/validate-token.js +1 -1
- package/dist/modules/tool-helpers/validate-token.js.map +1 -1
- package/dist/modules/tool-manuals/index.d.ts +3 -3
- package/dist/modules/tool-manuals/index.d.ts.map +1 -1
- package/dist/modules/tool-manuals/index.js +0 -2
- package/dist/modules/tool-manuals/index.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +19 -6
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +7 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.js +41 -4
- package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts +13 -6
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +23 -15
- package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.js +25 -10
- package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
- package/dist/modules/tool-registry/manual.routes.d.ts +8 -1
- package/dist/modules/tool-registry/manual.routes.d.ts.map +1 -1
- package/dist/modules/tool-registry/manual.routes.js +27 -7
- package/dist/modules/tool-registry/manual.routes.js.map +1 -1
- package/dist/modules/tool-registry/tool.contract.d.ts +7 -0
- package/dist/modules/tool-registry/tool.contract.d.ts.map +1 -1
- package/dist/modules/tool-registry/tool.contract.js.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.js +14 -9
- package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +2 -2
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +20 -35
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +2 -1
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +3 -0
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/file-readers/doc-extract.service.d.ts +28 -4
- package/dist/modules/workspace/file-readers/doc-extract.service.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/doc-extract.service.js +61 -40
- package/dist/modules/workspace/file-readers/doc-extract.service.js.map +1 -1
- package/dist/modules/workspace/index.d.ts +8 -0
- package/dist/modules/workspace/index.d.ts.map +1 -1
- package/dist/modules/workspace/index.js +8 -0
- package/dist/modules/workspace/index.js.map +1 -1
- package/dist/modules/workspace/nul-scan.d.ts +19 -0
- package/dist/modules/workspace/nul-scan.d.ts.map +1 -0
- package/dist/modules/workspace/nul-scan.js +55 -0
- package/dist/modules/workspace/nul-scan.js.map +1 -0
- package/dist/modules/workspace/pattern-matcher.contract.d.ts +110 -27
- package/dist/modules/workspace/pattern-matcher.contract.d.ts.map +1 -1
- package/dist/modules/workspace/pattern-matcher.contract.js +30 -13
- package/dist/modules/workspace/pattern-matcher.contract.js.map +1 -1
- package/dist/modules/workspace/ripgrep-search.d.ts +54 -0
- package/dist/modules/workspace/ripgrep-search.d.ts.map +1 -0
- package/dist/modules/workspace/ripgrep-search.js +657 -0
- package/dist/modules/workspace/ripgrep-search.js.map +1 -0
- package/dist/modules/workspace/ripgrep-version.d.ts +15 -0
- package/dist/modules/workspace/ripgrep-version.d.ts.map +1 -0
- package/dist/modules/workspace/ripgrep-version.js +31 -0
- package/dist/modules/workspace/ripgrep-version.js.map +1 -0
- package/dist/modules/workspace/startup/beside-checkout.d.ts +8 -5
- package/dist/modules/workspace/startup/beside-checkout.d.ts.map +1 -1
- package/dist/modules/workspace/startup/beside-checkout.js +14 -5
- package/dist/modules/workspace/startup/beside-checkout.js.map +1 -1
- package/dist/modules/workspace/worker-pool.d.ts +3 -4
- package/dist/modules/workspace/worker-pool.d.ts.map +1 -1
- package/dist/modules/workspace/worker-pool.js.map +1 -1
- package/dist/modules/workspace/workspace-tmp.d.ts +146 -0
- package/dist/modules/workspace/workspace-tmp.d.ts.map +1 -0
- package/dist/modules/workspace/workspace-tmp.js +435 -0
- package/dist/modules/workspace/workspace-tmp.js.map +1 -0
- package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.routes.js +5 -26
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +47 -6
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +86 -6
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +460 -116
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/shared/path-order.d.ts +28 -8
- package/dist/shared/path-order.d.ts.map +1 -1
- package/dist/shared/path-order.js +37 -8
- package/dist/shared/path-order.js.map +1 -1
- package/migrations/0022_session_version.sql +6 -0
- package/migrations/0023_agent_path.sql +3 -0
- package/migrations/meta/0022_snapshot.json +2360 -0
- package/migrations/meta/0023_snapshot.json +2378 -0
- package/migrations/meta/_journal.json +14 -0
- package/package.json +3 -3
- package/src/__tests__/pg-scratch.test.ts +69 -0
- package/src/__tests__/pg-scratch.ts +31 -0
- package/src/__tests__/slow-ripgrep.ts +65 -0
- package/src/core/__tests__/boot-without-ripgrep.test.ts +34 -0
- package/src/core/__tests__/catalog-revision.test.ts +89 -0
- package/src/core/__tests__/damaged-document.tools.test.ts +2 -2
- package/src/core/__tests__/gated-boot.test.ts +8 -1
- package/src/core/__tests__/lifecycle.test.ts +46 -13
- package/src/core/catalog-revision.ts +24 -2
- package/src/core/core-ports.ts +42 -0
- package/src/core/create-core-server.ts +13 -0
- package/src/core/create-core-services.ts +49 -12
- package/src/core/lifecycle.ts +26 -2
- package/src/index.ts +1 -1
- package/src/modules/access/__tests__/access-control.admin-root-floor.test.ts +12 -0
- package/src/modules/access/__tests__/access-control.colliding-name-cases.test.ts +113 -0
- package/src/modules/access/__tests__/access-control.frontmatter-head.test.ts +117 -0
- package/src/modules/access/__tests__/access-control.group-named-admin.test.ts +122 -0
- package/src/modules/access/__tests__/access-control.preview-relocation.test.ts +50 -0
- package/src/modules/access/__tests__/access-declarations.test.ts +19 -0
- package/src/modules/access/__tests__/access-groups.test.ts +55 -22
- package/src/modules/access/__tests__/access-mutation.service.test.ts +85 -59
- package/src/modules/access/__tests__/access-restrict-here.test.ts +7 -5
- package/src/modules/access/__tests__/access.routes.admins.test.ts +1 -0
- package/src/modules/access/__tests__/access.routes.batch.test.ts +2 -0
- package/src/modules/access/__tests__/access.routes.folder-governs.test.ts +2 -0
- package/src/modules/access/__tests__/access.routes.group-grant.test.ts +50 -14
- package/src/modules/access/__tests__/access.routes.overrides.test.ts +2 -1
- package/src/modules/access/__tests__/access.routes.proposal-branch.test.ts +1 -0
- package/src/modules/access/__tests__/access.routes.prospective.test.ts +2 -0
- package/src/modules/access/__tests__/access.routes.revoke.test.ts +18 -13
- package/src/modules/access/__tests__/access.routes.unknown-email.test.ts +2 -0
- package/src/modules/access/__tests__/grant-sources.test.ts +20 -15
- package/src/modules/access/__tests__/groups-admin.routes.test.ts +61 -0
- package/src/modules/access/__tests__/groups-admin.service.test.ts +211 -18
- package/src/modules/access/__tests__/reference-scan.test.ts +366 -14
- package/src/modules/access/__tests__/roles-admin.security.test.ts +1 -1
- package/src/modules/access/__tests__/roles-admin.service.test.ts +43 -22
- package/src/modules/access/__tests__/roles-capabilities.test.ts +6 -2
- package/src/modules/access/__tests__/roles.routes.test.ts +1 -1
- package/src/modules/access/access-control.interface.ts +17 -10
- package/src/modules/access/access-control.service.ts +158 -72
- package/src/modules/access/access-mutation.service.ts +48 -53
- package/src/modules/access/access-view.ts +12 -10
- package/src/modules/access/access.routes.ts +43 -33
- package/src/modules/access/groups-admin.routes.ts +4 -2
- package/src/modules/access/groups-admin.service.ts +114 -44
- package/src/modules/access/groups-edit.ts +1 -1
- package/src/modules/access/reference-scan.ts +319 -46
- package/src/modules/access/roles-admin.service.ts +43 -26
- package/src/modules/access/synced-groups-writer.ts +1 -1
- package/src/modules/access-model/__tests__/access-grammar.test.ts +38 -1
- package/src/modules/access-model/__tests__/access-splice.test.ts +50 -8
- package/src/modules/access-model/access-grammar.ts +65 -20
- package/src/modules/access-model/access-splice.ts +31 -23
- package/src/modules/access-model/group-files.ts +5 -0
- package/src/modules/agent-guide/__tests__/agent-guide.test.ts +31 -0
- package/src/modules/agent-guide/agent-guide.tools.ts +13 -2
- package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +1 -0
- package/src/modules/agent-instructions/compose.ts +3 -1
- package/src/modules/agent-instructions/shared-file-rules.ts +17 -1
- package/src/modules/agent-scope/__tests__/agent-surfaces.connect-as.test.ts +421 -0
- package/src/modules/agent-scope/__tests__/connection-scope.test.ts +46 -0
- package/src/modules/agent-scope/__tests__/in-memory-kb.ts +148 -0
- package/src/modules/agent-scope/agent-scope.contract.ts +83 -0
- package/src/modules/agent-scope/connection-scope.ts +199 -0
- package/src/modules/agent-scope/index.ts +15 -0
- package/src/modules/audit/agent-audit.service.ts +2 -0
- package/src/modules/audit/audit.contract.ts +6 -0
- package/src/modules/auth/__tests__/auth.middleware.test.ts +30 -2
- package/src/modules/auth/__tests__/auth.routes.test.ts +71 -5
- package/src/modules/auth/__tests__/auth.service.test.ts +90 -9
- package/src/modules/auth/__tests__/password-change-sessions.pg.test.ts +269 -0
- package/src/modules/auth/account-admission.ts +19 -0
- package/src/modules/auth/auth.middleware.ts +45 -4
- package/src/modules/auth/auth.routes.ts +46 -29
- package/src/modules/auth/auth.service.ts +135 -34
- package/src/modules/auth/index.ts +2 -0
- package/src/modules/auth/oidc-auth-provider.ts +2 -9
- package/src/modules/database/__tests__/session-version-migration.pg.test.ts +133 -0
- package/src/modules/database/core-schema.ts +33 -0
- package/src/modules/declared-variables/__tests__/declared-variables.route.test.ts +44 -1
- package/src/modules/declared-variables/declared-variables.routes.ts +12 -1
- package/src/modules/embed/__tests__/embed.tools.test.ts +12 -0
- package/src/modules/embed/embed.tools.ts +5 -0
- package/src/modules/kb-fs/__tests__/locking-filesystem.test.ts +25 -0
- package/src/modules/kb-fs/__tests__/repo-path.test.ts +53 -8
- package/src/modules/kb-fs/index.ts +2 -0
- package/src/modules/kb-fs/locking-filesystem.ts +7 -1
- package/src/modules/kb-fs/repo-path.ts +33 -7
- package/src/modules/kb-fs/tmp-ref.ts +81 -0
- package/src/modules/mcp/__tests__/connect-as.pg.test.ts +137 -0
- package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +29 -5
- package/src/modules/mcp/__tests__/mcp-routes-harness.ts +8 -0
- package/src/modules/mcp/__tests__/mcp.connect-as.test.ts +195 -0
- package/src/modules/mcp/__tests__/mcp.routes.connect-as.test.ts +192 -0
- package/src/modules/mcp/__tests__/oauth-consent.connect-as.test.ts +146 -0
- package/src/modules/mcp/mcp-auth.middleware.ts +19 -2
- package/src/modules/mcp/mcp.routes.ts +39 -3
- package/src/modules/mcp/mcp.service.ts +107 -17
- package/src/modules/mcp/oauth/bevel-oauth-provider.ts +46 -8
- package/src/modules/mcp/oauth/oauth-consent.routes.ts +41 -1
- package/src/modules/plugins/__tests__/plugins.tools.test.ts +38 -2
- package/src/modules/plugins/index.ts +1 -1
- package/src/modules/plugins/plugin-links.ts +19 -2
- package/src/modules/plugins/plugins.routes.ts +35 -4
- package/src/modules/secrets-vault/__tests__/connect-pending.route.test.ts +113 -0
- package/src/modules/secrets-vault/secrets-vault.routes.ts +23 -3
- package/src/modules/skills/__tests__/skills.service.test.ts +34 -0
- package/src/modules/skills/index.ts +7 -2
- package/src/modules/skills/skills.contract.ts +17 -0
- package/src/modules/skills/skills.service.ts +5 -1
- package/src/modules/skills/skills.tools.ts +44 -13
- package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +4 -0
- package/src/modules/tool-auth/external-api-key.interface.ts +11 -1
- package/src/modules/tool-auth/external-api-key.service.ts +5 -2
- package/src/modules/tool-auth/index.ts +1 -1
- package/src/modules/tool-auth/internal-token.service.ts +10 -0
- package/src/modules/tool-auth/tool-auth.middleware.ts +45 -3
- package/src/modules/tool-helpers/__tests__/can-write.tools.test.ts +3 -1
- package/src/modules/tool-helpers/tool-context.ts +10 -0
- package/src/modules/tool-helpers/tool-handler.ts +34 -0
- package/src/modules/tool-helpers/tool.contract.ts +10 -0
- package/src/modules/tool-helpers/validate-token.ts +4 -2
- package/src/modules/tool-manuals/__tests__/tool-manuals.archive.route.test.ts +80 -4
- package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +42 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +3 -3
- package/src/modules/tool-manuals/index.ts +6 -2
- package/src/modules/tool-manuals/tool-manuals.contract.ts +18 -6
- package/src/modules/tool-manuals/tool-manuals.routes.ts +40 -3
- package/src/modules/tool-manuals/tool-manuals.service.ts +31 -15
- package/src/modules/tool-manuals/tool-manuals.tools.ts +28 -11
- package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +2 -2
- package/src/modules/tool-registry/manual.routes.ts +26 -5
- package/src/modules/tool-registry/tool.contract.ts +7 -0
- package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +1 -1
- package/src/modules/workflow/__tests__/workflow.service.sync.test.ts +1 -1
- package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +54 -3
- package/src/modules/workflow/agent-tools/workflow.tools.ts +14 -9
- package/src/modules/workflow/git/__tests__/git.service.list-branches.test.ts +130 -3
- package/src/modules/workflow/git/git.service.ts +29 -48
- package/src/modules/workflow/workflow.service.ts +5 -0
- package/src/modules/workspace/__tests__/git-internals.security.test.ts +2 -2
- package/src/modules/workspace/__tests__/holds-nul.test.ts +66 -0
- package/src/modules/workspace/__tests__/rest-tool-arguments.test.ts +3 -3
- package/src/modules/workspace/__tests__/ripgrep-search.test.ts +570 -0
- package/src/modules/workspace/__tests__/ripgrep-version.test.ts +29 -0
- package/src/modules/workspace/__tests__/workspace-tmp.tools.test.ts +928 -0
- package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +17 -37
- package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +20 -8
- package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +2 -2
- package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +22 -10
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +393 -113
- package/src/modules/workspace/file-readers/__tests__/doc-extract.service.test.ts +10 -0
- package/src/modules/workspace/file-readers/doc-extract.service.ts +71 -38
- package/src/modules/workspace/index.ts +20 -0
- package/src/modules/workspace/nul-scan.ts +50 -0
- package/src/modules/workspace/pattern-matcher.contract.ts +123 -32
- package/src/modules/workspace/ripgrep-search.ts +715 -0
- package/src/modules/workspace/ripgrep-version.ts +35 -0
- package/src/modules/workspace/startup/__tests__/beside-checkout.test.ts +11 -0
- package/src/modules/workspace/startup/beside-checkout.ts +13 -5
- package/src/modules/workspace/worker-pool.ts +3 -4
- package/src/modules/workspace/workspace-tmp.ts +457 -0
- package/src/modules/workspace/workspace.routes.ts +6 -27
- package/src/modules/workspace/workspace.service.ts +92 -15
- package/src/modules/workspace/workspace.tools.ts +500 -155
- package/src/shared/path-order.ts +37 -8
- package/starter-packs/engineering/Plugins/engineering-starter/access.md +1 -1
- package/starter-packs/general/Plugins/general-starter/access.md +1 -1
- package/starter-packs/operations/Plugins/operations-starter/access.md +1 -1
- package/starter-packs/sales/Plugins/sales-starter/access.md +1 -1
- package/starter-packs/support/Plugins/support-starter/access.md +1 -1
- package/dist/modules/workspace/bounded-matcher.d.ts +0 -73
- package/dist/modules/workspace/bounded-matcher.d.ts.map +0 -1
- package/dist/modules/workspace/bounded-matcher.js +0 -200
- package/dist/modules/workspace/bounded-matcher.js.map +0 -1
- package/src/modules/workspace/__tests__/bounded-matcher.test.ts +0 -213
- package/src/modules/workspace/bounded-matcher.ts +0 -271
|
@@ -16,6 +16,8 @@ import type { BevelOAuthProvider } from './oauth/bevel-oauth-provider.js';
|
|
|
16
16
|
import '../tool-auth/index.js'; // req.externalApiKeyId augmentation
|
|
17
17
|
import './mcp-auth.middleware.js'; // req.mcpCallerKind augmentation
|
|
18
18
|
import { isPagePath } from '../audit/index.js';
|
|
19
|
+
import type { IAgentScopeResolver } from '../agent-scope/index.js';
|
|
20
|
+
import { NOT_AN_AGENT_CHOICE } from './oauth/oauth-consent.routes.js';
|
|
19
21
|
|
|
20
22
|
/** Pull the raw bearer token off an already-authenticated request. */
|
|
21
23
|
function extractBearer(req: Request): string {
|
|
@@ -104,6 +106,11 @@ export function createMcpRoutes(
|
|
|
104
106
|
// RFC 9728 pointer carried on this router's own 401 challenges (the
|
|
105
107
|
// local-token exchange), same value McpAuthMiddleware advertises.
|
|
106
108
|
resourceMetadataUrl: string,
|
|
109
|
+
/**
|
|
110
|
+
* The "Connect as" choices a key may be minted as (`modules/agent-scope`).
|
|
111
|
+
* Absent: keys are minted as Everything only, and no choices are listed.
|
|
112
|
+
*/
|
|
113
|
+
agents?: Pick<IAgentScopeResolver, 'listChoices' | 'choosable'>,
|
|
107
114
|
): express.Router {
|
|
108
115
|
const router = express.Router();
|
|
109
116
|
|
|
@@ -153,6 +160,10 @@ export function createMcpRoutes(
|
|
|
153
160
|
// exchanged grant; undefined for keys/JWTs) — the Audit log's other
|
|
154
161
|
// principal, resolved per request like the key id.
|
|
155
162
|
connectionId: req.agentConnectionId ?? null,
|
|
163
|
+
// The agent configuration the credential connects as (a key's, an agent
|
|
164
|
+
// connection's, or the one an exchanged grant carries); null is
|
|
165
|
+
// Everything. Resolved per request like the ids above.
|
|
166
|
+
...(req.mcpAgentPath ? { agentPath: req.mcpAgentPath } : {}),
|
|
156
167
|
// The proxy authenticates its loopback calls with the SAME bearer the
|
|
157
168
|
// client used here, so it acts on the request exactly as the caller would.
|
|
158
169
|
bearer: extractBearer(req),
|
|
@@ -233,6 +244,17 @@ export function createMcpRoutes(
|
|
|
233
244
|
|
|
234
245
|
// ── Connection-key management (JWT-only) ───────────────────────────────
|
|
235
246
|
|
|
247
|
+
// The agents a key may be minted as — the same choices the OAuth consent
|
|
248
|
+
// step offers below Everything, for the key-mint dialog.
|
|
249
|
+
router.get('/mcp/agent-choices', jwtAuthMiddleware, async (req, res) => {
|
|
250
|
+
try {
|
|
251
|
+
res.json({ agents: agents && req.userEmail ? await agents.listChoices(req.userEmail) : [] });
|
|
252
|
+
} catch (err) {
|
|
253
|
+
log.error('listing the agents to connect as failed:', { err });
|
|
254
|
+
res.status(500).json({ error: 'Failed to list the agents' });
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
|
|
236
258
|
router.get('/mcp/external-api-keys', jwtAuthMiddleware, async (req, res) => {
|
|
237
259
|
try {
|
|
238
260
|
const tokens = await externalApiKeyService.listForUser(req.userId!);
|
|
@@ -264,12 +286,23 @@ export function createMcpRoutes(
|
|
|
264
286
|
res.status(400).json({ error: 'JSON body required' });
|
|
265
287
|
return;
|
|
266
288
|
}
|
|
267
|
-
const { label } = req.body as { label?: string };
|
|
289
|
+
const { label, agentPath: asked } = req.body as { label?: string; agentPath?: unknown };
|
|
268
290
|
if (!label) {
|
|
269
291
|
res.status(400).json({ error: 'label is required' });
|
|
270
292
|
return;
|
|
271
293
|
}
|
|
272
|
-
|
|
294
|
+
// "Connect as", exactly as on the consent step: absent, null or empty is
|
|
295
|
+
// Everything; anything else must be an agent the owner may choose now.
|
|
296
|
+
let agentPath: string | null = null;
|
|
297
|
+
if (asked !== undefined && asked !== null && asked !== '') {
|
|
298
|
+
agentPath =
|
|
299
|
+
typeof asked === 'string' && agents && req.userEmail ? await agents.choosable(req.userEmail, asked) : null;
|
|
300
|
+
if (agentPath === null) {
|
|
301
|
+
res.status(400).json({ error: NOT_AN_AGENT_CHOICE });
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
const minted = await externalApiKeyService.mint(req.userId!, label, agentPath ? { agentPath } : {});
|
|
273
306
|
// The plaintext field is the *only* read path for the raw key. The
|
|
274
307
|
// frontend must store it nowhere — it shows the dialog once and then
|
|
275
308
|
// discards. Subsequent fetches return only `summary`-shaped rows.
|
|
@@ -428,8 +461,11 @@ export function createMcpRoutes(
|
|
|
428
461
|
// The grant's agent connection rides along, so the local server's
|
|
429
462
|
// calls with this token stay attributed to the agent in the Audit log.
|
|
430
463
|
const connectionId = typeof info.extra?.connectionId === 'string' ? info.extra.connectionId : undefined;
|
|
464
|
+
// So does the agent the connection was made as: the local server's
|
|
465
|
+
// calls see the same narrowed surface as the hosted endpoint's.
|
|
466
|
+
const agentPath = typeof info.extra?.agentPath === 'string' && info.extra.agentPath ? info.extra.agentPath : undefined;
|
|
431
467
|
const minted = internalTokens.mint(
|
|
432
|
-
{ userId, externalProxy: true, ...(connectionId ? { connectionId } : {}) },
|
|
468
|
+
{ userId, externalProxy: true, ...(connectionId ? { connectionId } : {}), ...(agentPath ? { agentPath } : {}) },
|
|
433
469
|
ttlMs,
|
|
434
470
|
);
|
|
435
471
|
// The ACTUAL lifetime, not the constant — the caller schedules its
|
|
@@ -75,6 +75,16 @@ import {
|
|
|
75
75
|
type ComposedAgentInstructions,
|
|
76
76
|
} from '../agent-instructions/index.js';
|
|
77
77
|
import { guideFirstDescription } from '../tool-registry/index.js';
|
|
78
|
+
import {
|
|
79
|
+
CONNECTION_META_KEY,
|
|
80
|
+
connectionRefusal,
|
|
81
|
+
connectionWireOf,
|
|
82
|
+
describeConnection,
|
|
83
|
+
type ConnectionScopeWire,
|
|
84
|
+
} from '@bevel-software/platform-mcp-core';
|
|
85
|
+
|
|
86
|
+
// Still offered from here, as this module's tests and callers learnt it.
|
|
87
|
+
export { CONNECTION_META_KEY };
|
|
78
88
|
|
|
79
89
|
/**
|
|
80
90
|
* Configuration for the loopback proxy. `loopbackBaseUrl` is the backend's own
|
|
@@ -142,6 +152,14 @@ export interface McpCaller {
|
|
|
142
152
|
* attributed in the Audit log; a request with neither records nothing.
|
|
143
153
|
*/
|
|
144
154
|
connectionId?: string | null;
|
|
155
|
+
/**
|
|
156
|
+
* The agent configuration the caller's credential connects as (a connection
|
|
157
|
+
* key's, or an agent connection's), or null for Everything. It rides the
|
|
158
|
+
* request's loopback identity, so every agent surface behind the loopback
|
|
159
|
+
* narrows to that agent's scope (`modules/agent-scope`); here it only keys
|
|
160
|
+
* the surface and words the handshake.
|
|
161
|
+
*/
|
|
162
|
+
agentPath?: string | null;
|
|
145
163
|
/** The raw bearer the request carried. */
|
|
146
164
|
bearer: string;
|
|
147
165
|
/**
|
|
@@ -222,6 +240,24 @@ interface RequestSurface {
|
|
|
222
240
|
* build, at the latest after an hour unused.
|
|
223
241
|
*/
|
|
224
242
|
degraded: boolean;
|
|
243
|
+
/**
|
|
244
|
+
* The agent the catalog was narrowed to, as `all-tools` reported it —
|
|
245
|
+
* absent for Everything. A call to a tool not on a narrowed surface is
|
|
246
|
+
* refused by name and agent rather than as unknown, and a connection whose
|
|
247
|
+
* agent configuration cannot be used is served nothing, with the sentence that says
|
|
248
|
+
* why.
|
|
249
|
+
*/
|
|
250
|
+
connection?: ConnectionScopeWire;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* The `connection` a loopback answer (`all-tools`, `catalog-revision`)
|
|
255
|
+
* carries, as a field to spread: validated at the boundary by the MCP
|
|
256
|
+
* core's reader (HTTP loses the type), and absent for Everything.
|
|
257
|
+
*/
|
|
258
|
+
function connectionOf(body: unknown): { connection?: ConnectionScopeWire } {
|
|
259
|
+
const connection = connectionWireOf(body);
|
|
260
|
+
return connection ? { connection } : {};
|
|
225
261
|
}
|
|
226
262
|
|
|
227
263
|
/**
|
|
@@ -490,13 +526,26 @@ export class McpService {
|
|
|
490
526
|
// verifier resolves it to `source: 'external'`: the caller IS an external
|
|
491
527
|
// agent and must be treated like one (admitted to `start_session`/`ask`,
|
|
492
528
|
// refused from internal-only tools).
|
|
529
|
+
// The agent the connection was made as rides that token too, so the
|
|
530
|
+
// surfaces behind the loopback narrow to its scope; a connection key
|
|
531
|
+
// carries its own.
|
|
532
|
+
const agentPath = caller.agentPath || null;
|
|
493
533
|
const loopbackBearer =
|
|
494
534
|
tokenId == null && this.internalTokens
|
|
495
|
-
? this.internalTokens.mint(
|
|
535
|
+
? this.internalTokens.mint(
|
|
536
|
+
{ userId, externalProxy: true, ...(agentPath ? { agentPath } : {}) },
|
|
537
|
+
MCP_LOOPBACK_TOKEN_TTL_MS,
|
|
538
|
+
)
|
|
496
539
|
: bearer;
|
|
497
540
|
|
|
498
541
|
let instructions: Promise<ComposedAgentInstructions> | undefined;
|
|
499
542
|
const agentInstructions = () => (instructions ??= this.composeAgentInstructions());
|
|
543
|
+
// What a narrowed connection is told about itself at the handshake: the
|
|
544
|
+
// agent it is connected as, or why it loads nothing. Read off the catalog
|
|
545
|
+
// revision (cheap), not the surface (a full discovery). Everything is
|
|
546
|
+
// told nothing new, so its handshake is exactly what it was.
|
|
547
|
+
const connectionLine = async (): Promise<string | null> =>
|
|
548
|
+
agentPath ? describeConnection((await this.fetchCatalogRevision(loopbackBearer)).connection) : null;
|
|
500
549
|
// The Audit log's view of this request — only when the request can be
|
|
501
550
|
// attributed to a key, an agent connection or a page. The catalog names
|
|
502
551
|
// it classifies pooled tools by are the surface's, which is looked up
|
|
@@ -534,6 +583,10 @@ export class McpService {
|
|
|
534
583
|
}));
|
|
535
584
|
|
|
536
585
|
const initializing = (Array.isArray(messages) ? messages : [messages]).some((m) => isInitializeRequest(m));
|
|
586
|
+
const handshakeInstructions = async (): Promise<string> => {
|
|
587
|
+
const [{ instructions: text }, line] = await Promise.all([agentInstructions(), connectionLine()]);
|
|
588
|
+
return line ? `${text}\n\n${line}` : text;
|
|
589
|
+
};
|
|
537
590
|
// The deployment's MCP Apps, read once per request. Declaring the
|
|
538
591
|
// `resources` capability is what permits the two handlers below, and a
|
|
539
592
|
// deployment with no view must not declare it — a client would then list
|
|
@@ -554,12 +607,12 @@ export class McpService {
|
|
|
554
607
|
},
|
|
555
608
|
// `instructions` rides the initialize result; clients that honour it
|
|
556
609
|
// place the text in the model's system prompt without the model acting.
|
|
557
|
-
...(initializing ? { instructions:
|
|
610
|
+
...(initializing ? { instructions: await handshakeInstructions() } : {}),
|
|
558
611
|
},
|
|
559
612
|
);
|
|
560
613
|
|
|
561
614
|
server.setRequestHandler(ListToolsRequestSchema, async () => inRequest(async () => {
|
|
562
|
-
const [{ tools, catalogNames }, { toolPrefix }, readOnly] = await Promise.all([
|
|
615
|
+
const [{ tools, catalogNames, connection }, { toolPrefix }, readOnly] = await Promise.all([
|
|
563
616
|
requestSurface(),
|
|
564
617
|
agentInstructions(),
|
|
565
618
|
// A failed read lists the built-in hints rather than no listing at all.
|
|
@@ -568,6 +621,12 @@ export class McpService {
|
|
|
568
621
|
return undefined;
|
|
569
622
|
}),
|
|
570
623
|
]);
|
|
624
|
+
// A connection whose agent configuration cannot be used is served NOTHING — not
|
|
625
|
+
// Everything — and the one sentence saying why rides the listing's
|
|
626
|
+
// `_meta` (the handshake carries it too).
|
|
627
|
+
if (connection?.unavailable) {
|
|
628
|
+
return { tools: [], _meta: { [CONNECTION_META_KEY]: connection.unavailable } };
|
|
629
|
+
}
|
|
571
630
|
// A discovered tool whose name collides with a meta-tool would be
|
|
572
631
|
// listed but never callable (the dispatcher routes the name to the
|
|
573
632
|
// meta-tool first), so drop it from the listing entirely.
|
|
@@ -694,7 +753,10 @@ export class McpService {
|
|
|
694
753
|
}
|
|
695
754
|
|
|
696
755
|
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => inRequest(async () => {
|
|
697
|
-
const { client: shared, tools, unavailable, catalogNames, hidden: hiddenTools } = await requestSurface();
|
|
756
|
+
const { client: shared, tools, unavailable, catalogNames, hidden: hiddenTools, connection } = await requestSurface();
|
|
757
|
+
// Nothing is callable on a connection that loads nothing, meta-tools
|
|
758
|
+
// included: every call is answered with the reason.
|
|
759
|
+
if (connection?.unavailable) return toolError(connection.unavailable);
|
|
698
760
|
// The person's client, as this request uses it: every call it makes —
|
|
699
761
|
// a chain's too, which comes back from the isolate outside any async
|
|
700
762
|
// context — carries this request's bearer and audit.
|
|
@@ -769,7 +831,16 @@ export class McpService {
|
|
|
769
831
|
if (answer && owner && audit) {
|
|
770
832
|
await audit.denied(`${owner.utcpName}.${toolName.slice(owner.utcpName.length + 1)}`, args);
|
|
771
833
|
}
|
|
772
|
-
|
|
834
|
+
// On a narrowed connection, a tool off the surface is one its agent
|
|
835
|
+
// does not load: refused by name and agent, never "unknown" — the tool
|
|
836
|
+
// may well exist, for this person, on another connection.
|
|
837
|
+
return (
|
|
838
|
+
answer ??
|
|
839
|
+
toolError(
|
|
840
|
+
retiredToolMessage(toolName) ??
|
|
841
|
+
(connection ? connectionRefusal(toolName, connection) : `Unknown tool "${toolName}".`),
|
|
842
|
+
)
|
|
843
|
+
);
|
|
773
844
|
}
|
|
774
845
|
const needsAuth = await needsAuthorization(proxied.manualName);
|
|
775
846
|
if (needsAuth) {
|
|
@@ -879,9 +950,20 @@ export class McpService {
|
|
|
879
950
|
*/
|
|
880
951
|
private async surfaceFor(caller: McpCaller, loopbackBearer: string): Promise<RequestSurface> {
|
|
881
952
|
const { userId, tokenId } = caller;
|
|
882
|
-
const revision = await this.fetchCatalogRevision(loopbackBearer);
|
|
953
|
+
const { revision } = await this.fetchCatalogRevision(loopbackBearer);
|
|
883
954
|
if (revision === null) return this.buildSurface(userId, tokenId, loopbackBearer, 'uncached');
|
|
884
|
-
|
|
955
|
+
// The agent the connection was made as is part of the key: two
|
|
956
|
+
// connections of one person made as different agents are two surfaces.
|
|
957
|
+
// (The revision already moves when the agent's FILE changes — it carries
|
|
958
|
+
// the file's digest — so an edit is a new build at the next request.)
|
|
959
|
+
// Everything adds nothing, and keeps the key it always had.
|
|
960
|
+
const key = [
|
|
961
|
+
userId,
|
|
962
|
+
tokenId != null ? 'key' : 'person',
|
|
963
|
+
caller.scope ?? 'write',
|
|
964
|
+
revision,
|
|
965
|
+
...(caller.agentPath ? [caller.agentPath] : []),
|
|
966
|
+
].join(POOL_KEY_SEPARATOR);
|
|
885
967
|
const { value } = await this.surfaces.get(
|
|
886
968
|
key,
|
|
887
969
|
() => this.buildSurface(userId, tokenId, loopbackBearer, 'miss'),
|
|
@@ -893,10 +975,13 @@ export class McpService {
|
|
|
893
975
|
/**
|
|
894
976
|
* The caller's catalog revision (`GET /api/agent/catalog-revision`, the one
|
|
895
977
|
* `hexis-mcp` polls), read with their bearer; null when it cannot be read.
|
|
978
|
+
* With it, the agent the caller's connection is narrowed to, when it is.
|
|
896
979
|
* A deployment or a test that does not serve the route answers 404, which
|
|
897
980
|
* is not worth a line in the log; anything else is.
|
|
898
981
|
*/
|
|
899
|
-
private async fetchCatalogRevision(
|
|
982
|
+
private async fetchCatalogRevision(
|
|
983
|
+
bearer: string,
|
|
984
|
+
): Promise<{ revision: string | null; connection?: ConnectionScopeWire }> {
|
|
900
985
|
try {
|
|
901
986
|
const res = await fetch(`${this.opts.loopbackBaseUrl}/api/agent/catalog-revision`, {
|
|
902
987
|
headers: { Authorization: `Bearer ${bearer}` },
|
|
@@ -904,13 +989,16 @@ export class McpService {
|
|
|
904
989
|
});
|
|
905
990
|
if (!res.ok) {
|
|
906
991
|
if (res.status !== 404) log.warn(`catalog-revision loopback failed: HTTP ${res.status}; building this request's surface uncached`);
|
|
907
|
-
return null;
|
|
992
|
+
return { revision: null };
|
|
908
993
|
}
|
|
909
|
-
const body = (await res.json()) as { revision?: unknown };
|
|
910
|
-
return
|
|
994
|
+
const body = (await res.json()) as { revision?: unknown; connection?: unknown };
|
|
995
|
+
return {
|
|
996
|
+
revision: typeof body.revision === 'string' && body.revision ? body.revision : null,
|
|
997
|
+
...connectionOf(body),
|
|
998
|
+
};
|
|
911
999
|
} catch (err) {
|
|
912
1000
|
log.warn("catalog-revision loopback threw; building this request's surface uncached:", { err });
|
|
913
|
-
return null;
|
|
1001
|
+
return { revision: null };
|
|
914
1002
|
}
|
|
915
1003
|
}
|
|
916
1004
|
|
|
@@ -935,7 +1023,7 @@ export class McpService {
|
|
|
935
1023
|
// The list of manuals this caller gets: the KB manual + each `.tool` they
|
|
936
1024
|
// can read. Fetched over loopback with the caller's key, so ACL filtering
|
|
937
1025
|
// lives ONCE behind the REST surface.
|
|
938
|
-
const manuals = await this.fetchManualTemplates(loopbackBearer);
|
|
1026
|
+
const { manuals, connection } = await this.fetchManualTemplates(loopbackBearer);
|
|
939
1027
|
const catalogMs = performance.now() - started;
|
|
940
1028
|
const client = await this.buildClient(userId, manuals);
|
|
941
1029
|
const discovered = await this.discoverTools(client, manuals, userId);
|
|
@@ -960,7 +1048,7 @@ export class McpService {
|
|
|
960
1048
|
(decision.suppressed > 0 ? ` [+${decision.suppressed} identical rebuild(s) since last line]` : ''),
|
|
961
1049
|
);
|
|
962
1050
|
}
|
|
963
|
-
return { ...discovered, client, tools };
|
|
1051
|
+
return { ...discovered, client, tools, ...(connection ? { connection } : {}) };
|
|
964
1052
|
}
|
|
965
1053
|
|
|
966
1054
|
/**
|
|
@@ -1039,11 +1127,13 @@ export class McpService {
|
|
|
1039
1127
|
* request. HTTP transport loses the `CallTemplate` type, so we re-validate the
|
|
1040
1128
|
* received JSON even though the producing endpoint already validated it.
|
|
1041
1129
|
*/
|
|
1042
|
-
private async fetchManualTemplates(
|
|
1130
|
+
private async fetchManualTemplates(
|
|
1131
|
+
bearer: string,
|
|
1132
|
+
): Promise<{ manuals: CallTemplate[]; connection?: ConnectionScopeWire }> {
|
|
1043
1133
|
// This is the REMOTE proxy, so ask for remote-capable manuals only — local-only
|
|
1044
1134
|
// `.tool`s are surfaced instead via the `list_local_tools` tool in the KB manual.
|
|
1045
1135
|
const body = (await this.loopbackJson(bearer, '/api/agent/all-tools?remote=true', 'all-tools')) as
|
|
1046
|
-
| { manuals?: unknown }
|
|
1136
|
+
| { manuals?: unknown; connection?: unknown }
|
|
1047
1137
|
| null;
|
|
1048
1138
|
const rawManuals: unknown[] = Array.isArray(body?.manuals) ? body.manuals : [];
|
|
1049
1139
|
|
|
@@ -1062,7 +1152,7 @@ export class McpService {
|
|
|
1062
1152
|
}
|
|
1063
1153
|
// Always include the KB manual, even if `all-tools` was unavailable/regressed.
|
|
1064
1154
|
if (!hasKb) out.unshift(this.kbManualTemplate());
|
|
1065
|
-
return out;
|
|
1155
|
+
return { manuals: out, ...connectionOf(body) };
|
|
1066
1156
|
}
|
|
1067
1157
|
|
|
1068
1158
|
/** The KB manual's discovery template — the fallback if `all-tools` is unavailable. */
|
|
@@ -199,10 +199,16 @@ export class BevelOAuthProvider implements OAuthServerProvider {
|
|
|
199
199
|
* Called by the Finish endpoint once an authenticated user completes the
|
|
200
200
|
* /connect step: bind a one-time code to (user, client, redirect_uri, PKCE
|
|
201
201
|
* challenge) and hand back the client redirect the browser should follow.
|
|
202
|
+
*
|
|
203
|
+
* @param agentPath The "Connect as" choice — an agent configuration the consent
|
|
204
|
+
* route has already checked the person may choose — or null for
|
|
205
|
+
* Everything. Carried on the code to the exchange, which records it on
|
|
206
|
+
* the agent connection.
|
|
202
207
|
*/
|
|
203
208
|
async issueAuthCode(
|
|
204
209
|
userId: string,
|
|
205
210
|
st: McpAuthRequestState,
|
|
211
|
+
agentPath: string | null = null,
|
|
206
212
|
): Promise<{ redirectTo: string }> {
|
|
207
213
|
// A state without a PKCE challenge is a Claude-link request, which the
|
|
208
214
|
// marketplace bridge finishes; it must never reach the SDK's code store.
|
|
@@ -217,6 +223,7 @@ export class BevelOAuthProvider implements OAuthServerProvider {
|
|
|
217
223
|
scope: st.sc ?? null,
|
|
218
224
|
resource: st.rs ?? null,
|
|
219
225
|
expiresAt: new Date(Date.now() + CODE_TTL_MS),
|
|
226
|
+
...(agentPath ? { agentPath } : {}),
|
|
220
227
|
});
|
|
221
228
|
const redirect = new URL(st.r);
|
|
222
229
|
redirect.searchParams.set('code', code);
|
|
@@ -269,7 +276,7 @@ export class BevelOAuthProvider implements OAuthServerProvider {
|
|
|
269
276
|
if (redirectUri && redirectUri !== row.redirectUri) {
|
|
270
277
|
throw new InvalidGrantError('redirect_uri does not match the authorization request');
|
|
271
278
|
}
|
|
272
|
-
return this.mintTokens(row.userId, row.clientId, row.scope, row.resource);
|
|
279
|
+
return this.mintTokens(row.userId, row.clientId, row.scope, row.resource, undefined, undefined, row.agentPath ?? null);
|
|
273
280
|
}
|
|
274
281
|
|
|
275
282
|
async exchangeRefreshToken(
|
|
@@ -463,6 +470,7 @@ export class BevelOAuthProvider implements OAuthServerProvider {
|
|
|
463
470
|
id: oauthTokens.id,
|
|
464
471
|
clientId: oauthTokens.clientId,
|
|
465
472
|
connectionId: oauthTokens.connectionId,
|
|
473
|
+
agentPath: agentConnections.agentPath,
|
|
466
474
|
scope: oauthTokens.scope,
|
|
467
475
|
resource: oauthTokens.resource,
|
|
468
476
|
expiresAt: oauthTokens.expiresAt,
|
|
@@ -507,6 +515,10 @@ export class BevelOAuthProvider implements OAuthServerProvider {
|
|
|
507
515
|
// Null on a token minted before connections existed; the auth
|
|
508
516
|
// middleware then binds no agent and the call goes unrecorded.
|
|
509
517
|
connectionId: row.connectionId,
|
|
518
|
+
// The connection's "Connect as" choice, read off the row on every
|
|
519
|
+
// verification, so a re-consent with another choice reaches the
|
|
520
|
+
// agent's very next request. Null is Everything.
|
|
521
|
+
agentPath: row.agentPath ?? null,
|
|
510
522
|
},
|
|
511
523
|
};
|
|
512
524
|
}
|
|
@@ -575,6 +587,9 @@ export class BevelOAuthProvider implements OAuthServerProvider {
|
|
|
575
587
|
* first mint, which finds or makes the live connection for (user, client).
|
|
576
588
|
* @param tx The transaction the insert belongs to, when the mint must land
|
|
577
589
|
* with the statement that consumed the token it replaces (a refresh).
|
|
590
|
+
* @param agentPath The consent's "Connect as" choice, recorded on the
|
|
591
|
+
* connection the pair is bound to (null: Everything). Only a first mint —
|
|
592
|
+
* a consent — passes it; a refresh leaves the connection's choice alone.
|
|
578
593
|
* The prune is then the caller's, before the transaction opens: a
|
|
579
594
|
* best-effort statement that fails inside a transaction would abort the
|
|
580
595
|
* mint it is meant to spare, and one on the pool from inside it would
|
|
@@ -587,6 +602,7 @@ export class BevelOAuthProvider implements OAuthServerProvider {
|
|
|
587
602
|
resource: string | null,
|
|
588
603
|
connectionId?: string,
|
|
589
604
|
tx?: Tx,
|
|
605
|
+
agentPath?: string | null,
|
|
590
606
|
): Promise<OAuthTokens> {
|
|
591
607
|
const accessToken = this.deps.tokenPrefix + randomBytes(TOKEN_BYTES).toString('base64url');
|
|
592
608
|
const refreshToken = this.refreshPrefix + randomBytes(TOKEN_BYTES).toString('base64url');
|
|
@@ -594,7 +610,7 @@ export class BevelOAuthProvider implements OAuthServerProvider {
|
|
|
594
610
|
// A mint of its own (a sign-in) prunes here; a refresh pruned before its
|
|
595
611
|
// transaction opened.
|
|
596
612
|
if (!tx) await this.pruneTokenTables(now);
|
|
597
|
-
const boundTo = connectionId ?? (await this.ensureConnection(userId, clientId));
|
|
613
|
+
const boundTo = connectionId ?? (await this.ensureConnection(userId, clientId, agentPath));
|
|
598
614
|
const sink: Tx | Database = tx ?? this.deps.db;
|
|
599
615
|
await sink.insert(oauthTokens).values({
|
|
600
616
|
accessTokenHash: hashToken(accessToken),
|
|
@@ -665,12 +681,23 @@ export class BevelOAuthProvider implements OAuthServerProvider {
|
|
|
665
681
|
* revoke. A read, not a write: the use is stamped separately, throttled.
|
|
666
682
|
*/
|
|
667
683
|
async isConnectionLive(connectionId: string): Promise<boolean> {
|
|
684
|
+
return (await this.liveConnectionChoice(connectionId)) !== null;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* A live agent connection's current "Connect as" choice (`agentPath` null
|
|
689
|
+
* = Everything), or null when the connection is gone or revoked. Read on
|
|
690
|
+
* every request through the local server's exchanged grant: that token is
|
|
691
|
+
* stateless and outlives a re-consent, so the choice it was minted with is
|
|
692
|
+
* not the one it is served as — the row's is.
|
|
693
|
+
*/
|
|
694
|
+
async liveConnectionChoice(connectionId: string): Promise<{ agentPath: string | null } | null> {
|
|
668
695
|
const [row] = await this.deps.db
|
|
669
|
-
.select({
|
|
696
|
+
.select({ agentPath: agentConnections.agentPath })
|
|
670
697
|
.from(agentConnections)
|
|
671
698
|
.where(and(eq(agentConnections.id, connectionId), isNull(agentConnections.revokedAt)))
|
|
672
699
|
.limit(1);
|
|
673
|
-
return row
|
|
700
|
+
return row ? { agentPath: row.agentPath ?? null } : null;
|
|
674
701
|
}
|
|
675
702
|
|
|
676
703
|
/**
|
|
@@ -737,8 +764,13 @@ export class BevelOAuthProvider implements OAuthServerProvider {
|
|
|
737
764
|
* the other's `ON CONFLICT DO NOTHING` returns no row, and it re-reads the
|
|
738
765
|
* winner's. The client's display name is snapshotted from its registration
|
|
739
766
|
* at that moment.
|
|
767
|
+
*
|
|
768
|
+
* `agentPath` is a consent's "Connect as" choice: written on the row made,
|
|
769
|
+
* and on the live row found — the person's latest consent is what the agent
|
|
770
|
+
* connects as. `undefined` (a refresh of a token from before connections
|
|
771
|
+
* existed) leaves a found row's choice as it is.
|
|
740
772
|
*/
|
|
741
|
-
private async ensureConnection(userId: string, clientId: string): Promise<string> {
|
|
773
|
+
private async ensureConnection(userId: string, clientId: string, agentPath?: string | null): Promise<string> {
|
|
742
774
|
const [client] = await this.deps.db
|
|
743
775
|
.select({ clientName: oauthClients.clientName })
|
|
744
776
|
.from(oauthClients)
|
|
@@ -758,11 +790,17 @@ export class BevelOAuthProvider implements OAuthServerProvider {
|
|
|
758
790
|
),
|
|
759
791
|
)
|
|
760
792
|
.limit(1);
|
|
793
|
+
const choose = async (id: string): Promise<string> => {
|
|
794
|
+
if (agentPath !== undefined) {
|
|
795
|
+
await this.deps.db.update(agentConnections).set({ agentPath }).where(eq(agentConnections.id, id));
|
|
796
|
+
}
|
|
797
|
+
return id;
|
|
798
|
+
};
|
|
761
799
|
const [existing] = await live();
|
|
762
|
-
if (existing) return existing.id;
|
|
800
|
+
if (existing) return choose(existing.id);
|
|
763
801
|
const [inserted] = await this.deps.db
|
|
764
802
|
.insert(agentConnections)
|
|
765
|
-
.values({ userId, clientId, clientName, agentKey })
|
|
803
|
+
.values({ userId, clientId, clientName, agentKey, ...(agentPath ? { agentPath } : {}) })
|
|
766
804
|
.onConflictDoNothing({
|
|
767
805
|
target: [agentConnections.userId, agentConnections.agentKey],
|
|
768
806
|
where: sql`${agentConnections.revokedAt} is null`,
|
|
@@ -771,7 +809,7 @@ export class BevelOAuthProvider implements OAuthServerProvider {
|
|
|
771
809
|
if (inserted) return inserted.id;
|
|
772
810
|
const [winner] = await live();
|
|
773
811
|
if (!winner) throw new Error('agent connection vanished between insert and re-read');
|
|
774
|
-
return winner.id;
|
|
812
|
+
return choose(winner.id);
|
|
775
813
|
}
|
|
776
814
|
}
|
|
777
815
|
|
|
@@ -4,6 +4,7 @@ import { logger } from '../../../shared/logging.js';
|
|
|
4
4
|
const log = logger('mcp-oauth');
|
|
5
5
|
import type { BevelOAuthProvider } from './bevel-oauth-provider.js';
|
|
6
6
|
import { verifyAuthRequest, type McpAuthRequestState } from './oauth-state.js';
|
|
7
|
+
import type { IAgentScopeResolver } from '../../agent-scope/index.js';
|
|
7
8
|
import '../../auth/index.js'; // Express Request augmentation (req.userId / req.userEmail)
|
|
8
9
|
|
|
9
10
|
export interface OAuthConsentRoutesDeps {
|
|
@@ -21,8 +22,17 @@ export interface OAuthConsentRoutesDeps {
|
|
|
21
22
|
clientNameFor(st: McpAuthRequestState): string;
|
|
22
23
|
completeConsent(userId: string, st: McpAuthRequestState): Promise<{ redirectTo: string }>;
|
|
23
24
|
};
|
|
25
|
+
/**
|
|
26
|
+
* The "Connect as" choices: the agent configurations the person may connect an
|
|
27
|
+
* MCP client as, offered below Everything. Absent, the consent offers
|
|
28
|
+
* Everything alone and Finish refuses any other choice.
|
|
29
|
+
*/
|
|
30
|
+
agents?: Pick<IAgentScopeResolver, 'listChoices' | 'choosable'>;
|
|
24
31
|
}
|
|
25
32
|
|
|
33
|
+
/** The refusal of a choice that is not one of the person's — one answer for missing and unreadable. */
|
|
34
|
+
export const NOT_AN_AGENT_CHOICE = 'That agent is not one you can connect as. Choose Everything or one of the agents listed.';
|
|
35
|
+
|
|
26
36
|
/**
|
|
27
37
|
* The authenticated tail of the MCP OAuth flow. `/authorize` (SDK-owned)
|
|
28
38
|
* validated the client's request, packed it into a signed state, and sent the
|
|
@@ -49,10 +59,25 @@ export function createOAuthConsentRoutes(deps: OAuthConsentRoutesDeps): express.
|
|
|
49
59
|
return;
|
|
50
60
|
}
|
|
51
61
|
const client = await deps.provider.clientsStore.getClient(st.c);
|
|
62
|
+
// Everything is not in the list: it is the choice every consent has, and
|
|
63
|
+
// the one the page selects. A failure to read the agents costs the choice
|
|
64
|
+
// of an agent, never the consent itself — but it is SAID
|
|
65
|
+
// (`agentsUnavailable`), so the page does not pass an empty list off as
|
|
66
|
+
// "you have no agents" and approve Everything unasked.
|
|
67
|
+
let agentsUnavailable = false;
|
|
68
|
+
const agents = deps.agents && req.userEmail
|
|
69
|
+
? await deps.agents.listChoices(req.userEmail).catch((err: unknown) => {
|
|
70
|
+
log.warn('listing the agents to connect as failed:', { detail: err instanceof Error ? err.message : String(err) });
|
|
71
|
+
agentsUnavailable = true;
|
|
72
|
+
return [];
|
|
73
|
+
})
|
|
74
|
+
: [];
|
|
52
75
|
res.json({
|
|
53
76
|
clientName: client?.client_name ?? null,
|
|
54
77
|
scope: st.sc ?? null,
|
|
55
78
|
resource: st.rs ?? null,
|
|
79
|
+
agents,
|
|
80
|
+
...(agentsUnavailable ? { agentsUnavailable: true } : {}),
|
|
56
81
|
});
|
|
57
82
|
});
|
|
58
83
|
|
|
@@ -65,10 +90,25 @@ export function createOAuthConsentRoutes(deps: OAuthConsentRoutesDeps): express.
|
|
|
65
90
|
const st = raw ? verifyAuthRequest(deps.stateSecret, raw) : null;
|
|
66
91
|
if (!st) return void res.status(400).json({ error: 'Invalid or expired authorization request. Restart the connection from your agent.' });
|
|
67
92
|
if (st.gh && !deps.facade) return void res.status(400).json({ error: NO_FACADE });
|
|
93
|
+
// "Connect as": absent, null or empty is Everything. Anything else must be
|
|
94
|
+
// an agent the person may choose right now — checked here, at the only
|
|
95
|
+
// place a choice is made, so the token exchange records it as given.
|
|
96
|
+
const asked: unknown = (req.body ?? {}).agentPath;
|
|
97
|
+
if (asked !== undefined && asked !== null && typeof asked !== 'string') {
|
|
98
|
+
return void res.status(400).json({ error: NOT_AN_AGENT_CHOICE });
|
|
99
|
+
}
|
|
100
|
+
const choosing = typeof asked === 'string' && asked.trim() !== '';
|
|
101
|
+
// An account linked through the GitHub facade has no agent connection to
|
|
102
|
+
// record a choice on.
|
|
103
|
+
if (choosing && (st.gh || !deps.agents || !req.userEmail)) {
|
|
104
|
+
return void res.status(400).json({ error: NOT_AN_AGENT_CHOICE });
|
|
105
|
+
}
|
|
68
106
|
try {
|
|
107
|
+
const agentPath = choosing && deps.agents && req.userEmail ? await deps.agents.choosable(req.userEmail, asked as string) : null;
|
|
108
|
+
if (choosing && agentPath === null) return void res.status(400).json({ error: NOT_AN_AGENT_CHOICE });
|
|
69
109
|
const { redirectTo } = st.gh && deps.facade
|
|
70
110
|
? await deps.facade.completeConsent(userId, st)
|
|
71
|
-
: await deps.provider.issueAuthCode(userId, st);
|
|
111
|
+
: await deps.provider.issueAuthCode(userId, st, agentPath);
|
|
72
112
|
res.json({ redirectTo });
|
|
73
113
|
} catch (err) {
|
|
74
114
|
// Message only — the raw error object can carry sensitive context
|
|
@@ -13,6 +13,8 @@ import { GUIDE_FIRST_SENTENCE } from '../../tool-registry/guide-first.js';
|
|
|
13
13
|
import { registerPluginsTools, CREATE_PLUGIN, myPluginDef, myPluginDescription } from '../plugins.tools.js';
|
|
14
14
|
import { createPluginCreationRoutes } from '../plugins.routes.js';
|
|
15
15
|
import { PluginProvisionError } from '../plugin-provision.service.js';
|
|
16
|
+
import type { IAgentScopeResolver } from '../../agent-scope/index.js';
|
|
17
|
+
import { agentFile, agentScopeOver, inMemoryKb } from '../../agent-scope/__tests__/in-memory-kb.js';
|
|
16
18
|
|
|
17
19
|
/**
|
|
18
20
|
* The plugin tools are DESCRIPTIONS of the app's own creation endpoints.
|
|
@@ -200,7 +202,12 @@ describe('the creation endpoints behind the key-or-session gate', () => {
|
|
|
200
202
|
|
|
201
203
|
const externalApiKeyService = {
|
|
202
204
|
looksLikeExternalApiKey: (t: string) => typeof t === 'string' && t.startsWith('bevel_'),
|
|
203
|
-
verifyAndLoadToken: async (t: string) =>
|
|
205
|
+
verifyAndLoadToken: async (t: string) =>
|
|
206
|
+
t === 'bevel_alice'
|
|
207
|
+
? { user: ALICE, tokenId: 'tok-a' }
|
|
208
|
+
: t === 'bevel_alice_agent'
|
|
209
|
+
? { user: ALICE, tokenId: 'tok-b', agentPath: 'Agents/plugin-maker.agent' }
|
|
210
|
+
: null,
|
|
204
211
|
} as never;
|
|
205
212
|
const authService = {
|
|
206
213
|
getUserById: async (id: string) => (id === ALICE.id ? ALICE : null),
|
|
@@ -211,7 +218,7 @@ describe('the creation endpoints behind the key-or-session gate', () => {
|
|
|
211
218
|
},
|
|
212
219
|
} as never;
|
|
213
220
|
|
|
214
|
-
async function start(): Promise<string> {
|
|
221
|
+
async function start(agentScope?: Pick<IAgentScopeResolver, 'resolve'>): Promise<string> {
|
|
215
222
|
const internalToken = new InternalTokenService({ secret: 'test-secret' });
|
|
216
223
|
const app = express();
|
|
217
224
|
app.use(express.json());
|
|
@@ -224,6 +231,7 @@ describe('the creation endpoints behind the key-or-session gate', () => {
|
|
|
224
231
|
}),
|
|
225
232
|
createPluginCreationRoutes({ ensurePersonalPlugin, createPlugin } as never, async (req) =>
|
|
226
233
|
req.userId ? ((await (authService as { getUserById(id: string): Promise<unknown> }).getUserById(req.userId)) as never) : null,
|
|
234
|
+
agentScope,
|
|
227
235
|
),
|
|
228
236
|
);
|
|
229
237
|
httpServer = await new Promise<HttpServer>((r) => {
|
|
@@ -302,4 +310,32 @@ describe('the creation endpoints behind the key-or-session gate', () => {
|
|
|
302
310
|
);
|
|
303
311
|
expect(createPlugin).not.toHaveBeenCalled();
|
|
304
312
|
});
|
|
313
|
+
|
|
314
|
+
it("refuses a connection made as an .agent that does not list the tool, by name, before anything is provisioned", async () => {
|
|
315
|
+
// The agent lists `my_plugin` only. These routes are not hosted by
|
|
316
|
+
// `toolHandler`, so they make its connection-scope check themselves.
|
|
317
|
+
const kb = inMemoryKb(
|
|
318
|
+
{ 'Agents/plugin-maker.agent': agentFile('name: Plugin maker\nplatformTools: [my_plugin]') },
|
|
319
|
+
{ reader: ALICE.email },
|
|
320
|
+
);
|
|
321
|
+
const base = await start(agentScopeOver(kb));
|
|
322
|
+
const refused = await post(base, '/api/plugins', { name: 'GTM' }, 'bevel_alice_agent');
|
|
323
|
+
expect(refused.status).toBe(403);
|
|
324
|
+
expect(await refused.json()).toEqual({
|
|
325
|
+
error: expect.stringContaining('The tool "create_plugin" is not available to this connection'),
|
|
326
|
+
code: 'not_available_to_connection',
|
|
327
|
+
});
|
|
328
|
+
expect(createPlugin).not.toHaveBeenCalled();
|
|
329
|
+
|
|
330
|
+
// The tool it lists is served; a key made as Everything and a session are untouched.
|
|
331
|
+
expect((await post(base, '/api/plugins/personal', {}, 'bevel_alice_agent')).status).toBe(200);
|
|
332
|
+
expect((await post(base, '/api/plugins', { name: 'Ops' }, 'bevel_alice')).status).toBe(201);
|
|
333
|
+
expect((await post(base, '/api/plugins', { name: 'Fin' }, 'session-alice')).status).toBe(201);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it('a narrowed connection with no scope resolver wired loads nothing, never Everything', async () => {
|
|
337
|
+
const base = await start();
|
|
338
|
+
expect((await post(base, '/api/plugins/personal', {}, 'bevel_alice_agent')).status).toBe(403);
|
|
339
|
+
expect(ensurePersonalPlugin).not.toHaveBeenCalled();
|
|
340
|
+
});
|
|
305
341
|
});
|
|
@@ -19,7 +19,7 @@ export {
|
|
|
19
19
|
type JoinRequestStatus,
|
|
20
20
|
type JoinRequestStore,
|
|
21
21
|
} from './join-request-records.store.js';
|
|
22
|
-
export { PluginLinkIndex, type LinkMembership, type PluginLinks } from './plugin-links.js';
|
|
22
|
+
export { PluginLinkIndex, type IPluginLinkIndex, type LinkMembership, type PluginLinks } from './plugin-links.js';
|
|
23
23
|
export { PluginLinksService, PluginLinkError } from './plugin-links.service.js';
|
|
24
24
|
export { PluginRenameService, PluginRenameError, renamePluginPrincipalInText } from './plugin-rename.service.js';
|
|
25
25
|
export { compileMarketplace, type VirtualTree, type CompileInput, type MarketplaceOptions } from './compile/compile-marketplace.js';
|