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