@bevel-software/platform-core-backend 0.15.1 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD-PARTY-NOTICES.md +4 -4
- package/dist/core/catalog-cache-invalidation.d.ts +16 -0
- package/dist/core/catalog-cache-invalidation.d.ts.map +1 -1
- package/dist/core/catalog-cache-invalidation.js +6 -1
- package/dist/core/catalog-cache-invalidation.js.map +1 -1
- package/dist/core/catalog-revision.d.ts +39 -0
- package/dist/core/catalog-revision.d.ts.map +1 -0
- package/dist/core/catalog-revision.js +94 -0
- package/dist/core/catalog-revision.js.map +1 -0
- package/dist/core/create-core-server.d.ts +7 -0
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +199 -75
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +49 -2
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +212 -86
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/core/lifecycle.d.ts +138 -0
- package/dist/core/lifecycle.d.ts.map +1 -0
- package/dist/core/lifecycle.js +218 -0
- package/dist/core/lifecycle.js.map +1 -0
- package/dist/core/public-config.d.ts +82 -0
- package/dist/core/public-config.d.ts.map +1 -0
- package/dist/core/public-config.js +78 -0
- package/dist/core/public-config.js.map +1 -0
- package/dist/core/readiness.d.ts +86 -0
- package/dist/core/readiness.d.ts.map +1 -0
- package/dist/core/readiness.js +56 -0
- package/dist/core/readiness.js.map +1 -0
- package/dist/core-config.d.ts +30 -0
- package/dist/core-config.d.ts.map +1 -1
- package/dist/core-config.js +64 -2
- package/dist/core-config.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +215 -11
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +57 -11
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +616 -155
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/access-mutation.service.d.ts +71 -5
- package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
- package/dist/modules/access/access-mutation.service.js +161 -13
- package/dist/modules/access/access-mutation.service.js.map +1 -1
- package/dist/modules/access/access-view.d.ts +147 -0
- package/dist/modules/access/access-view.d.ts.map +1 -0
- package/dist/modules/access/access-view.js +211 -0
- package/dist/modules/access/access-view.js.map +1 -0
- package/dist/modules/access/access.routes.d.ts.map +1 -1
- package/dist/modules/access/access.routes.js +255 -79
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/access/admin-locked-commit.d.ts +2 -3
- package/dist/modules/access/admin-locked-commit.d.ts.map +1 -1
- package/dist/modules/access/admin-locked-commit.js +9 -8
- package/dist/modules/access/admin-locked-commit.js.map +1 -1
- package/dist/modules/access/admin-route-helpers.d.ts.map +1 -1
- package/dist/modules/access/admin-route-helpers.js +4 -4
- package/dist/modules/access/admin-route-helpers.js.map +1 -1
- package/dist/modules/access/change-read-gate.d.ts +53 -0
- package/dist/modules/access/change-read-gate.d.ts.map +1 -0
- package/dist/modules/access/change-read-gate.js +134 -0
- package/dist/modules/access/change-read-gate.js.map +1 -0
- package/dist/modules/access/creator-access.d.ts +33 -39
- package/dist/modules/access/creator-access.d.ts.map +1 -1
- package/dist/modules/access/creator-access.js +92 -119
- package/dist/modules/access/creator-access.js.map +1 -1
- package/dist/modules/access/directory-sync-bot.d.ts.map +1 -1
- package/dist/modules/access/directory-sync-bot.js +3 -1
- package/dist/modules/access/directory-sync-bot.js.map +1 -1
- package/dist/modules/access/groups-admin.service.d.ts +3 -3
- package/dist/modules/access/groups-admin.service.js +2 -2
- package/dist/modules/access/reference-scan.d.ts +1 -1
- package/dist/modules/access/reference-scan.d.ts.map +1 -1
- package/dist/modules/access/reference-scan.js +5 -4
- package/dist/modules/access/reference-scan.js.map +1 -1
- package/dist/modules/access/roles-admin.service.d.ts +72 -13
- package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
- package/dist/modules/access/roles-admin.service.js +71 -13
- package/dist/modules/access/roles-admin.service.js.map +1 -1
- package/dist/modules/access/synced-groups-committer.d.ts.map +1 -1
- package/dist/modules/access/synced-groups-committer.js +8 -6
- package/dist/modules/access/synced-groups-committer.js.map +1 -1
- package/dist/modules/access/synced-groups-writer.d.ts.map +1 -1
- package/dist/modules/access/synced-groups-writer.js +3 -8
- package/dist/modules/access/synced-groups-writer.js.map +1 -1
- package/dist/modules/access/user-access-removal.service.d.ts +209 -0
- package/dist/modules/access/user-access-removal.service.d.ts.map +1 -0
- package/dist/modules/access/user-access-removal.service.js +662 -0
- package/dist/modules/access/user-access-removal.service.js.map +1 -0
- package/dist/modules/access-model/access-errors.d.ts +32 -1
- package/dist/modules/access-model/access-errors.d.ts.map +1 -1
- package/dist/modules/access-model/access-errors.js +38 -11
- package/dist/modules/access-model/access-errors.js.map +1 -1
- package/dist/modules/access-model/access-grammar.d.ts +49 -24
- package/dist/modules/access-model/access-grammar.d.ts.map +1 -1
- package/dist/modules/access-model/access-grammar.js +68 -48
- package/dist/modules/access-model/access-grammar.js.map +1 -1
- package/dist/modules/access-model/access-splice.d.ts.map +1 -1
- package/dist/modules/access-model/access-splice.js +18 -18
- package/dist/modules/access-model/access-splice.js.map +1 -1
- package/dist/modules/access-model/access-template.d.ts +22 -0
- package/dist/modules/access-model/access-template.d.ts.map +1 -0
- package/dist/modules/access-model/access-template.js +42 -0
- package/dist/modules/access-model/access-template.js.map +1 -0
- package/dist/modules/access-model/change-gate.d.ts +65 -0
- package/dist/modules/access-model/change-gate.d.ts.map +1 -0
- package/dist/modules/access-model/change-gate.js +25 -0
- package/dist/modules/access-model/change-gate.js.map +1 -0
- package/dist/modules/access-model/creator.d.ts +13 -16
- package/dist/modules/access-model/creator.d.ts.map +1 -1
- package/dist/modules/access-model/creator.js.map +1 -1
- package/dist/modules/access-model/frontmatter-lines.d.ts +27 -0
- package/dist/modules/access-model/frontmatter-lines.d.ts.map +1 -0
- package/dist/modules/access-model/frontmatter-lines.js +74 -0
- package/dist/modules/access-model/frontmatter-lines.js.map +1 -0
- package/dist/modules/access-model/plugin-principals.d.ts +5 -3
- package/dist/modules/access-model/plugin-principals.d.ts.map +1 -1
- package/dist/modules/access-model/plugin-principals.js +11 -17
- package/dist/modules/access-model/plugin-principals.js.map +1 -1
- package/dist/modules/access-model/roles-yaml-guard.d.ts +73 -2
- package/dist/modules/access-model/roles-yaml-guard.d.ts.map +1 -1
- package/dist/modules/access-model/roles-yaml-guard.js +153 -3
- package/dist/modules/access-model/roles-yaml-guard.js.map +1 -1
- package/dist/modules/admin/admin-access.service.d.ts.map +1 -1
- package/dist/modules/admin/admin-access.service.js +5 -2
- package/dist/modules/admin/admin-access.service.js.map +1 -1
- package/dist/modules/agent-instructions/agent-instructions.routes.d.ts +21 -0
- package/dist/modules/agent-instructions/agent-instructions.routes.d.ts.map +1 -0
- package/dist/modules/agent-instructions/agent-instructions.routes.js +38 -0
- package/dist/modules/agent-instructions/agent-instructions.routes.js.map +1 -0
- package/dist/modules/agent-instructions/compose.d.ts +71 -0
- package/dist/modules/agent-instructions/compose.d.ts.map +1 -0
- package/dist/modules/agent-instructions/compose.js +234 -0
- package/dist/modules/agent-instructions/compose.js.map +1 -0
- package/dist/modules/agent-instructions/index.d.ts +4 -0
- package/dist/modules/agent-instructions/index.d.ts.map +1 -0
- package/dist/modules/agent-instructions/index.js +4 -0
- package/dist/modules/agent-instructions/index.js.map +1 -0
- package/dist/modules/agent-instructions/read-preamble.d.ts +37 -0
- package/dist/modules/agent-instructions/read-preamble.d.ts.map +1 -0
- package/dist/modules/agent-instructions/read-preamble.js +81 -0
- package/dist/modules/agent-instructions/read-preamble.js.map +1 -0
- package/dist/modules/auth/account-erasure.service.d.ts +30 -4
- package/dist/modules/auth/account-erasure.service.d.ts.map +1 -1
- package/dist/modules/auth/account-erasure.service.js +96 -10
- package/dist/modules/auth/account-erasure.service.js.map +1 -1
- package/dist/modules/auth/account.routes.d.ts +6 -3
- package/dist/modules/auth/account.routes.d.ts.map +1 -1
- package/dist/modules/auth/account.routes.js +109 -10
- package/dist/modules/auth/account.routes.js.map +1 -1
- package/dist/modules/auth/auth.routes.d.ts +10 -3
- package/dist/modules/auth/auth.routes.d.ts.map +1 -1
- package/dist/modules/auth/auth.routes.js +9 -4
- package/dist/modules/auth/auth.routes.js.map +1 -1
- package/dist/modules/auth/auth.service.d.ts +89 -5
- package/dist/modules/auth/auth.service.d.ts.map +1 -1
- package/dist/modules/auth/auth.service.js +160 -25
- package/dist/modules/auth/auth.service.js.map +1 -1
- package/dist/modules/auth/oidc-auth-provider.d.ts +46 -3
- package/dist/modules/auth/oidc-auth-provider.d.ts.map +1 -1
- package/dist/modules/auth/oidc-auth-provider.js +99 -21
- package/dist/modules/auth/oidc-auth-provider.js.map +1 -1
- package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
- package/dist/modules/code-mode/code-mode.tool.js +14 -2
- package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
- package/dist/modules/connection-probe/connection-probe.service.d.ts.map +1 -1
- package/dist/modules/connection-probe/connection-probe.service.js +56 -7
- package/dist/modules/connection-probe/connection-probe.service.js.map +1 -1
- package/dist/modules/database/advisory-lock.d.ts +103 -0
- package/dist/modules/database/advisory-lock.d.ts.map +1 -0
- package/dist/modules/database/advisory-lock.js +320 -0
- package/dist/modules/database/advisory-lock.js.map +1 -0
- package/dist/modules/database/connection.d.ts.map +1 -1
- package/dist/modules/database/connection.js +9 -1
- package/dist/modules/database/connection.js.map +1 -1
- package/dist/modules/database/core-schema.d.ts +346 -0
- package/dist/modules/database/core-schema.d.ts.map +1 -1
- package/dist/modules/database/core-schema.js +119 -0
- package/dist/modules/database/core-schema.js.map +1 -1
- package/dist/modules/database/migrate.d.ts.map +1 -1
- package/dist/modules/database/migrate.js +26 -6
- package/dist/modules/database/migrate.js.map +1 -1
- package/dist/modules/declared-variables/declared-variables.routes.d.ts.map +1 -1
- package/dist/modules/declared-variables/declared-variables.routes.js +4 -2
- package/dist/modules/declared-variables/declared-variables.routes.js.map +1 -1
- package/dist/modules/diff/diff.routes.d.ts.map +1 -1
- package/dist/modules/diff/diff.routes.js +2 -4
- package/dist/modules/diff/diff.routes.js.map +1 -1
- package/dist/modules/diff/diff.service.d.ts +4 -1
- package/dist/modules/diff/diff.service.d.ts.map +1 -1
- package/dist/modules/diff/diff.service.js +79 -81
- package/dist/modules/diff/diff.service.js.map +1 -1
- package/dist/modules/kb-fs/bevel-ignore.d.ts +35 -0
- package/dist/modules/kb-fs/bevel-ignore.d.ts.map +1 -0
- package/dist/modules/kb-fs/bevel-ignore.js +71 -0
- package/dist/modules/kb-fs/bevel-ignore.js.map +1 -0
- package/dist/modules/kb-fs/file-change-notifier.d.ts.map +1 -1
- package/dist/modules/kb-fs/file-change-notifier.js +3 -1
- package/dist/modules/kb-fs/file-change-notifier.js.map +1 -1
- package/dist/modules/kb-fs/git-guarded-filesystem.d.ts +37 -0
- package/dist/modules/kb-fs/git-guarded-filesystem.d.ts.map +1 -0
- package/dist/modules/kb-fs/git-guarded-filesystem.js +104 -0
- package/dist/modules/kb-fs/git-guarded-filesystem.js.map +1 -0
- package/dist/modules/kb-fs/locking-filesystem.d.ts +113 -18
- package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.js +353 -76
- package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
- package/dist/modules/kb-fs/node-fs.d.ts +23 -0
- package/dist/modules/kb-fs/node-fs.d.ts.map +1 -0
- package/dist/modules/kb-fs/node-fs.js +158 -0
- package/dist/modules/kb-fs/node-fs.js.map +1 -0
- package/dist/modules/kb-fs/read-only-filesystem.d.ts +10 -9
- package/dist/modules/kb-fs/read-only-filesystem.d.ts.map +1 -1
- package/dist/modules/kb-fs/read-only-filesystem.js +18 -9
- package/dist/modules/kb-fs/read-only-filesystem.js.map +1 -1
- package/dist/modules/kb-fs/repo-path.d.ts +100 -3
- package/dist/modules/kb-fs/repo-path.d.ts.map +1 -1
- package/dist/modules/kb-fs/repo-path.js +195 -3
- package/dist/modules/kb-fs/repo-path.js.map +1 -1
- package/dist/modules/kb-sync/kb-sync.routes.d.ts.map +1 -1
- package/dist/modules/kb-sync/kb-sync.routes.js +4 -2
- package/dist/modules/kb-sync/kb-sync.routes.js.map +1 -1
- package/dist/modules/kb-sync/kb-sync.service.d.ts.map +1 -1
- package/dist/modules/kb-sync/kb-sync.service.js +7 -5
- package/dist/modules/kb-sync/kb-sync.service.js.map +1 -1
- package/dist/modules/marketplace/git-http.routes.d.ts.map +1 -1
- package/dist/modules/marketplace/git-http.routes.js +6 -4
- package/dist/modules/marketplace/git-http.routes.js.map +1 -1
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.d.ts +12 -2
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.d.ts.map +1 -1
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.js +32 -3
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.js.map +1 -1
- package/dist/modules/marketplace/github-facade/github-facade-credentials.service.d.ts +27 -0
- package/dist/modules/marketplace/github-facade/github-facade-credentials.service.d.ts.map +1 -1
- package/dist/modules/marketplace/github-facade/github-facade-credentials.service.js +45 -0
- package/dist/modules/marketplace/github-facade/github-facade-credentials.service.js.map +1 -1
- package/dist/modules/marketplace/github-facade/github-facade.routes.d.ts.map +1 -1
- package/dist/modules/marketplace/github-facade/github-facade.routes.js +30 -6
- package/dist/modules/marketplace/github-facade/github-facade.routes.js.map +1 -1
- package/dist/modules/marketplace/github-facade/github-facade.service.d.ts +21 -9
- package/dist/modules/marketplace/github-facade/github-facade.service.d.ts.map +1 -1
- package/dist/modules/marketplace/github-facade/github-facade.service.js +60 -19
- package/dist/modules/marketplace/github-facade/github-facade.service.js.map +1 -1
- package/dist/modules/marketplace/github-facade/index.d.ts +1 -1
- package/dist/modules/marketplace/github-facade/index.d.ts.map +1 -1
- package/dist/modules/marketplace/github-facade/index.js +1 -1
- package/dist/modules/marketplace/github-facade/index.js.map +1 -1
- package/dist/modules/marketplace/marketplace-repo.service.d.ts +14 -1
- package/dist/modules/marketplace/marketplace-repo.service.d.ts.map +1 -1
- package/dist/modules/marketplace/marketplace-repo.service.js +26 -8
- package/dist/modules/marketplace/marketplace-repo.service.js.map +1 -1
- package/dist/modules/mcp/downstream-pool.d.ts +107 -0
- package/dist/modules/mcp/downstream-pool.d.ts.map +1 -0
- package/dist/modules/mcp/downstream-pool.js +221 -0
- package/dist/modules/mcp/downstream-pool.js.map +1 -0
- package/dist/modules/mcp/downstream-token-refresh.d.ts +77 -0
- package/dist/modules/mcp/downstream-token-refresh.d.ts.map +1 -0
- package/dist/modules/mcp/downstream-token-refresh.js +166 -0
- package/dist/modules/mcp/downstream-token-refresh.js.map +1 -0
- package/dist/modules/mcp/first-call-probe.cli-args.d.ts +78 -0
- package/dist/modules/mcp/first-call-probe.cli-args.d.ts.map +1 -0
- package/dist/modules/mcp/first-call-probe.cli-args.js +151 -0
- package/dist/modules/mcp/first-call-probe.cli-args.js.map +1 -0
- package/dist/modules/mcp/first-call-probe.cli.d.ts +2 -0
- package/dist/modules/mcp/first-call-probe.cli.d.ts.map +1 -0
- package/dist/modules/mcp/first-call-probe.cli.js +62 -0
- package/dist/modules/mcp/first-call-probe.cli.js.map +1 -0
- package/dist/modules/mcp/first-call-probe.d.ts +165 -0
- package/dist/modules/mcp/first-call-probe.d.ts.map +1 -0
- package/dist/modules/mcp/first-call-probe.js +298 -0
- package/dist/modules/mcp/first-call-probe.js.map +1 -0
- package/dist/modules/mcp/manual-failure-memo.d.ts +42 -17
- package/dist/modules/mcp/manual-failure-memo.d.ts.map +1 -1
- package/dist/modules/mcp/manual-failure-memo.js +79 -23
- package/dist/modules/mcp/manual-failure-memo.js.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.d.ts +9 -5
- package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.js +24 -16
- package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
- package/dist/modules/mcp/mcp.routes.d.ts +4 -4
- package/dist/modules/mcp/mcp.routes.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.routes.js +83 -179
- package/dist/modules/mcp/mcp.routes.js.map +1 -1
- package/dist/modules/mcp/mcp.service.d.ts +218 -61
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +713 -159
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js +4 -2
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.js +3 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.js.map +1 -1
- package/dist/modules/mcp/surface-log-throttle.d.ts +59 -0
- package/dist/modules/mcp/surface-log-throttle.d.ts.map +1 -0
- package/dist/modules/mcp/surface-log-throttle.js +94 -0
- package/dist/modules/mcp/surface-log-throttle.js.map +1 -0
- package/dist/modules/plugins/compile/compile-marketplace.d.ts +13 -6
- package/dist/modules/plugins/compile/compile-marketplace.d.ts.map +1 -1
- package/dist/modules/plugins/compile/compile-marketplace.js +119 -36
- package/dist/modules/plugins/compile/compile-marketplace.js.map +1 -1
- package/dist/modules/plugins/compile/marketplace-compiler.service.d.ts +8 -1
- package/dist/modules/plugins/compile/marketplace-compiler.service.d.ts.map +1 -1
- package/dist/modules/plugins/compile/marketplace-compiler.service.js +9 -6
- package/dist/modules/plugins/compile/marketplace-compiler.service.js.map +1 -1
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.d.ts.map +1 -1
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.js +32 -9
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.js.map +1 -1
- package/dist/modules/plugins/discovery/bundle-dialect/registry.d.ts +26 -6
- package/dist/modules/plugins/discovery/bundle-dialect/registry.d.ts.map +1 -1
- package/dist/modules/plugins/discovery/bundle-dialect/registry.js +71 -14
- package/dist/modules/plugins/discovery/bundle-dialect/registry.js.map +1 -1
- package/dist/modules/plugins/discovery/kb-plugin-source.d.ts +4 -2
- package/dist/modules/plugins/discovery/kb-plugin-source.d.ts.map +1 -1
- package/dist/modules/plugins/discovery/kb-plugin-source.js +9 -6
- package/dist/modules/plugins/discovery/kb-plugin-source.js.map +1 -1
- package/dist/modules/plugins/discovery/native.source.d.ts +2 -1
- package/dist/modules/plugins/discovery/native.source.d.ts.map +1 -1
- package/dist/modules/plugins/discovery/native.source.js +22 -4
- package/dist/modules/plugins/discovery/native.source.js.map +1 -1
- package/dist/modules/plugins/discovery/plugin-source.d.ts +17 -3
- package/dist/modules/plugins/discovery/plugin-source.d.ts.map +1 -1
- package/dist/modules/plugins/index.d.ts +6 -3
- package/dist/modules/plugins/index.d.ts.map +1 -1
- package/dist/modules/plugins/index.js +6 -3
- package/dist/modules/plugins/index.js.map +1 -1
- package/dist/modules/plugins/join-request-jobs.service.d.ts +250 -0
- package/dist/modules/plugins/join-request-jobs.service.d.ts.map +1 -0
- package/dist/modules/plugins/join-request-jobs.service.js +602 -0
- package/dist/modules/plugins/join-request-jobs.service.js.map +1 -0
- package/dist/modules/plugins/join-request-records.store.d.ts +161 -0
- package/dist/modules/plugins/join-request-records.store.d.ts.map +1 -0
- package/dist/modules/plugins/join-request-records.store.js +179 -0
- package/dist/modules/plugins/join-request-records.store.js.map +1 -0
- package/dist/modules/plugins/join-requests.service.d.ts.map +1 -1
- package/dist/modules/plugins/join-requests.service.js +4 -2
- package/dist/modules/plugins/join-requests.service.js.map +1 -1
- package/dist/modules/plugins/plugin-links.d.ts +4 -1
- package/dist/modules/plugins/plugin-links.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-links.js +6 -3
- package/dist/modules/plugins/plugin-links.js.map +1 -1
- package/dist/modules/plugins/plugin-links.service.d.ts +2 -5
- package/dist/modules/plugins/plugin-links.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-links.service.js +4 -13
- package/dist/modules/plugins/plugin-links.service.js.map +1 -1
- package/dist/modules/plugins/plugin-provision.service.d.ts +91 -19
- package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-provision.service.js +183 -38
- package/dist/modules/plugins/plugin-provision.service.js.map +1 -1
- package/dist/modules/plugins/plugin-rename.service.d.ts +3 -1
- package/dist/modules/plugins/plugin-rename.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-rename.service.js +20 -11
- package/dist/modules/plugins/plugin-rename.service.js.map +1 -1
- package/dist/modules/plugins/plugins.contract.d.ts +52 -4
- package/dist/modules/plugins/plugins.contract.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.d.ts +38 -5
- package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.js +190 -116
- package/dist/modules/plugins/plugins.routes.js.map +1 -1
- package/dist/modules/plugins/plugins.service.d.ts +38 -6
- package/dist/modules/plugins/plugins.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.service.js +86 -17
- package/dist/modules/plugins/plugins.service.js.map +1 -1
- package/dist/modules/plugins/plugins.tools.d.ts +25 -0
- package/dist/modules/plugins/plugins.tools.d.ts.map +1 -0
- package/dist/modules/plugins/plugins.tools.js +82 -0
- package/dist/modules/plugins/plugins.tools.js.map +1 -0
- package/dist/modules/plugins/teams.routes.d.ts +42 -0
- package/dist/modules/plugins/teams.routes.d.ts.map +1 -0
- package/dist/modules/plugins/teams.routes.js +79 -0
- package/dist/modules/plugins/teams.routes.js.map +1 -0
- package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts +40 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts.map +1 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.js +202 -37
- package/dist/modules/secrets-vault/db-secrets-vault.service.js.map +1 -1
- package/dist/modules/secrets-vault/index.d.ts +1 -1
- package/dist/modules/secrets-vault/index.d.ts.map +1 -1
- package/dist/modules/secrets-vault/index.js.map +1 -1
- package/dist/modules/secrets-vault/mcp-oauth-discovery.service.d.ts.map +1 -1
- package/dist/modules/secrets-vault/mcp-oauth-discovery.service.js +5 -3
- package/dist/modules/secrets-vault/mcp-oauth-discovery.service.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-variable-loader.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-variable-loader.js +3 -1
- package/dist/modules/secrets-vault/secrets-variable-loader.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.contract.d.ts +78 -0
- package/dist/modules/secrets-vault/secrets-vault.contract.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.contract.js +40 -0
- package/dist/modules/secrets-vault/secrets-vault.contract.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.js +106 -52
- package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
- package/dist/modules/settings/connection-check.d.ts +118 -0
- package/dist/modules/settings/connection-check.d.ts.map +1 -0
- package/dist/modules/settings/connection-check.js +287 -0
- package/dist/modules/settings/connection-check.js.map +1 -0
- package/dist/modules/settings/deployment-settings.service.d.ts +153 -7
- package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
- package/dist/modules/settings/deployment-settings.service.js +348 -51
- package/dist/modules/settings/deployment-settings.service.js.map +1 -1
- package/dist/modules/settings/git-root-folders.d.ts +54 -0
- package/dist/modules/settings/git-root-folders.d.ts.map +1 -0
- package/dist/modules/settings/git-root-folders.js +130 -0
- package/dist/modules/settings/git-root-folders.js.map +1 -0
- package/dist/modules/settings/oidc-check.d.ts +85 -0
- package/dist/modules/settings/oidc-check.d.ts.map +1 -0
- package/dist/modules/settings/oidc-check.js +172 -0
- package/dist/modules/settings/oidc-check.js.map +1 -0
- package/dist/modules/settings/setup.routes.d.ts +31 -1
- package/dist/modules/settings/setup.routes.d.ts.map +1 -1
- package/dist/modules/settings/setup.routes.js +400 -108
- package/dist/modules/settings/setup.routes.js.map +1 -1
- package/dist/modules/skills/allowed-tools-check.d.ts +132 -0
- package/dist/modules/skills/allowed-tools-check.d.ts.map +1 -0
- package/dist/modules/skills/allowed-tools-check.js +260 -0
- package/dist/modules/skills/allowed-tools-check.js.map +1 -0
- package/dist/modules/skills/index.d.ts +3 -1
- package/dist/modules/skills/index.d.ts.map +1 -1
- package/dist/modules/skills/index.js +1 -0
- package/dist/modules/skills/index.js.map +1 -1
- package/dist/modules/skills/pending-skills.service.d.ts +6 -11
- package/dist/modules/skills/pending-skills.service.d.ts.map +1 -1
- package/dist/modules/skills/pending-skills.service.js +18 -70
- package/dist/modules/skills/pending-skills.service.js.map +1 -1
- package/dist/modules/skills/skill-access-requests.routes.d.ts.map +1 -1
- package/dist/modules/skills/skill-access-requests.routes.js +9 -6
- package/dist/modules/skills/skill-access-requests.routes.js.map +1 -1
- package/dist/modules/skills/skills.contract.d.ts +44 -10
- package/dist/modules/skills/skills.contract.d.ts.map +1 -1
- package/dist/modules/skills/skills.routes.d.ts +8 -1
- package/dist/modules/skills/skills.routes.d.ts.map +1 -1
- package/dist/modules/skills/skills.routes.js +16 -2
- package/dist/modules/skills/skills.routes.js.map +1 -1
- package/dist/modules/skills/skills.service.d.ts +31 -7
- package/dist/modules/skills/skills.service.d.ts.map +1 -1
- package/dist/modules/skills/skills.service.js +191 -81
- package/dist/modules/skills/skills.service.js.map +1 -1
- package/dist/modules/skills/skills.tools.d.ts +4 -1
- package/dist/modules/skills/skills.tools.d.ts.map +1 -1
- package/dist/modules/skills/skills.tools.js +41 -6
- package/dist/modules/skills/skills.tools.js.map +1 -1
- package/dist/modules/tool-auth/connection-key-rejection.d.ts +18 -0
- package/dist/modules/tool-auth/connection-key-rejection.d.ts.map +1 -0
- package/dist/modules/tool-auth/connection-key-rejection.js +20 -0
- package/dist/modules/tool-auth/connection-key-rejection.js.map +1 -0
- package/dist/modules/tool-auth/connection-keys-admin.routes.d.ts +14 -0
- package/dist/modules/tool-auth/connection-keys-admin.routes.d.ts.map +1 -0
- package/dist/modules/tool-auth/connection-keys-admin.routes.js +67 -0
- package/dist/modules/tool-auth/connection-keys-admin.routes.js.map +1 -0
- package/dist/modules/tool-auth/external-api-key.interface.d.ts +51 -3
- 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 +16 -6
- package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.js +61 -18
- package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.d.ts +5 -2
- package/dist/modules/tool-auth/internal-token.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.js +5 -2
- package/dist/modules/tool-auth/internal-token.service.js.map +1 -1
- package/dist/modules/tool-auth/key-or-session.middleware.d.ts +23 -0
- package/dist/modules/tool-auth/key-or-session.middleware.d.ts.map +1 -0
- package/dist/modules/tool-auth/key-or-session.middleware.js +39 -0
- package/dist/modules/tool-auth/key-or-session.middleware.js.map +1 -0
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts +7 -1
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.js +8 -5
- package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
- package/dist/modules/tool-helpers/tool-context.d.ts +14 -0
- package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-context.js +28 -6
- package/dist/modules/tool-helpers/tool-context.js.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.js +22 -5
- package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.d.ts +6 -1
- package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.js +7 -1
- package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
- package/dist/modules/tool-manuals/index.d.ts +2 -1
- package/dist/modules/tool-manuals/index.d.ts.map +1 -1
- package/dist/modules/tool-manuals/index.js +1 -0
- package/dist/modules/tool-manuals/index.js.map +1 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.d.ts.map +1 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.js +8 -6
- package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -1
- package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts +10 -2
- package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/mcp-server-edit.service.js +12 -15
- package/dist/modules/tool-manuals/mcp-server-edit.service.js.map +1 -1
- package/dist/modules/tool-manuals/pending-tools.service.d.ts +50 -0
- package/dist/modules/tool-manuals/pending-tools.service.d.ts.map +1 -0
- package/dist/modules/tool-manuals/pending-tools.service.js +149 -0
- package/dist/modules/tool-manuals/pending-tools.service.js.map +1 -0
- package/dist/modules/tool-manuals/tool-delete.service.d.ts +135 -0
- package/dist/modules/tool-manuals/tool-delete.service.d.ts.map +1 -0
- package/dist/modules/tool-manuals/tool-delete.service.js +303 -0
- package/dist/modules/tool-manuals/tool-delete.service.js.map +1 -0
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +161 -0
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.contract.js +9 -0
- package/dist/modules/tool-manuals/tool-manuals.contract.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +19 -2
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.js +127 -57
- package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts +119 -5
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +404 -74
- 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 +78 -6
- package/dist/modules/tool-manuals/tool-manuals.tools.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 +88 -54
- package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
- package/dist/modules/workflow/event-bus.d.ts +10 -4
- package/dist/modules/workflow/event-bus.d.ts.map +1 -1
- package/dist/modules/workflow/event-bus.js +14 -11
- package/dist/modules/workflow/event-bus.js.map +1 -1
- package/dist/modules/workflow/events.routes.d.ts +17 -2
- package/dist/modules/workflow/events.routes.d.ts.map +1 -1
- package/dist/modules/workflow/events.routes.js +31 -12
- package/dist/modules/workflow/events.routes.js.map +1 -1
- package/dist/modules/workflow/file-lock.service.d.ts +22 -4
- package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
- package/dist/modules/workflow/file-lock.service.js +27 -4
- package/dist/modules/workflow/file-lock.service.js.map +1 -1
- package/dist/modules/workflow/git/change-request-link.d.ts +20 -0
- package/dist/modules/workflow/git/change-request-link.d.ts.map +1 -0
- package/dist/modules/workflow/git/change-request-link.js +34 -0
- package/dist/modules/workflow/git/change-request-link.js.map +1 -0
- package/dist/modules/workflow/git/git-version.d.ts.map +1 -1
- package/dist/modules/workflow/git/git-version.js +3 -1
- package/dist/modules/workflow/git/git-version.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +263 -4
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +528 -118
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/git/node-git-runner.d.ts +36 -0
- package/dist/modules/workflow/git/node-git-runner.d.ts.map +1 -0
- package/dist/modules/workflow/git/node-git-runner.js +273 -0
- package/dist/modules/workflow/git/node-git-runner.js.map +1 -0
- package/dist/modules/workflow/git/pull-request.service.d.ts +51 -2
- package/dist/modules/workflow/git/pull-request.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/pull-request.service.js +157 -25
- package/dist/modules/workflow/git/pull-request.service.js.map +1 -1
- package/dist/modules/workflow/pending-commits.service.d.ts +11 -0
- package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.service.js +24 -4
- package/dist/modules/workflow/pending-commits.service.js.map +1 -1
- package/dist/modules/workflow/pending-commits.worker.d.ts +15 -8
- package/dist/modules/workflow/pending-commits.worker.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.worker.js +107 -58
- package/dist/modules/workflow/pending-commits.worker.js.map +1 -1
- package/dist/modules/workflow/recovery-bot.d.ts.map +1 -1
- package/dist/modules/workflow/recovery-bot.js +3 -1
- package/dist/modules/workflow/recovery-bot.js.map +1 -1
- package/dist/modules/workflow/review-workflow/approval-lock.d.ts +60 -0
- package/dist/modules/workflow/review-workflow/approval-lock.d.ts.map +1 -0
- package/dist/modules/workflow/review-workflow/approval-lock.js +116 -0
- package/dist/modules/workflow/review-workflow/approval-lock.js.map +1 -0
- package/dist/modules/workflow/review-workflow/review-workflow.interface.d.ts +54 -7
- package/dist/modules/workflow/review-workflow/review-workflow.interface.d.ts.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts +64 -0
- package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.js +269 -90
- package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
- package/dist/modules/workflow/sanitize-error.d.ts +5 -2
- package/dist/modules/workflow/sanitize-error.d.ts.map +1 -1
- package/dist/modules/workflow/sanitize-error.js +8 -4
- package/dist/modules/workflow/sanitize-error.js.map +1 -1
- package/dist/modules/workflow/workflow.routes.d.ts +2 -0
- package/dist/modules/workflow/workflow.routes.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.routes.js +259 -29
- package/dist/modules/workflow/workflow.routes.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +166 -14
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +906 -106
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/empty-dirs.d.ts +15 -0
- package/dist/modules/workspace/empty-dirs.d.ts.map +1 -0
- package/dist/modules/workspace/empty-dirs.js +90 -0
- package/dist/modules/workspace/empty-dirs.js.map +1 -0
- package/dist/modules/workspace/file-readers/content-mode.d.ts +50 -0
- package/dist/modules/workspace/file-readers/content-mode.d.ts.map +1 -0
- package/dist/modules/workspace/file-readers/content-mode.js +55 -0
- package/dist/modules/workspace/file-readers/content-mode.js.map +1 -0
- package/dist/modules/workspace/file-readers/doc-extract.service.d.ts +4 -1
- package/dist/modules/workspace/file-readers/doc-extract.service.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/doc-extract.service.js +4 -1
- package/dist/modules/workspace/file-readers/doc-extract.service.js.map +1 -1
- package/dist/modules/workspace/file-readers/doc-extract.types.d.ts +5 -2
- package/dist/modules/workspace/file-readers/doc-extract.types.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/doc-extract.types.js +7 -4
- package/dist/modules/workspace/file-readers/doc-extract.types.js.map +1 -1
- package/dist/modules/workspace/file-readers/document-reader.d.ts +2 -0
- package/dist/modules/workspace/file-readers/document-reader.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/document-reader.js +5 -0
- package/dist/modules/workspace/file-readers/document-reader.js.map +1 -1
- package/dist/modules/workspace/file-readers/extract-docx.d.ts +6 -0
- package/dist/modules/workspace/file-readers/extract-docx.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/extract-docx.js +6 -0
- package/dist/modules/workspace/file-readers/extract-docx.js.map +1 -1
- package/dist/modules/workspace/file-readers/file-reader.d.ts +24 -5
- package/dist/modules/workspace/file-readers/file-reader.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/file-reader.js +11 -6
- package/dist/modules/workspace/file-readers/file-reader.js.map +1 -1
- package/dist/modules/workspace/file-readers/file-reader.registry.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/file-reader.registry.js +6 -1
- package/dist/modules/workspace/file-readers/file-reader.registry.js.map +1 -1
- package/dist/modules/workspace/file-readers/image-reader.d.ts +7 -5
- package/dist/modules/workspace/file-readers/image-reader.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/image-reader.js +9 -5
- package/dist/modules/workspace/file-readers/image-reader.js.map +1 -1
- package/dist/modules/workspace/file-readers/ooxml-text.d.ts +3 -0
- package/dist/modules/workspace/file-readers/ooxml-text.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/ooxml-text.js +39 -2
- package/dist/modules/workspace/file-readers/ooxml-text.js.map +1 -1
- package/dist/modules/workspace/file-readers/text-reader.d.ts +29 -1
- package/dist/modules/workspace/file-readers/text-reader.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/text-reader.js +57 -3
- package/dist/modules/workspace/file-readers/text-reader.js.map +1 -1
- package/dist/modules/workspace/git-internals.middleware.d.ts +28 -0
- package/dist/modules/workspace/git-internals.middleware.d.ts.map +1 -0
- package/dist/modules/workspace/git-internals.middleware.js +93 -0
- package/dist/modules/workspace/git-internals.middleware.js.map +1 -0
- package/dist/modules/workspace/not-found.d.ts +80 -0
- package/dist/modules/workspace/not-found.d.ts.map +1 -0
- package/dist/modules/workspace/not-found.js +112 -0
- package/dist/modules/workspace/not-found.js.map +1 -0
- package/dist/modules/workspace/session-ontology.gate.d.ts +1 -1
- package/dist/modules/workspace/session-ontology.gate.d.ts.map +1 -1
- package/dist/modules/workspace/session-ontology.gate.js +1 -1
- package/dist/modules/workspace/session-ontology.gate.js.map +1 -1
- package/dist/modules/workspace/startup/beside-checkout.d.ts +42 -0
- package/dist/modules/workspace/startup/beside-checkout.d.ts.map +1 -0
- package/dist/modules/workspace/startup/beside-checkout.js +110 -0
- package/dist/modules/workspace/startup/beside-checkout.js.map +1 -0
- package/dist/modules/workspace/startup/kb-git.d.ts +7 -12
- package/dist/modules/workspace/startup/kb-git.d.ts.map +1 -1
- package/dist/modules/workspace/startup/kb-git.js +34 -35
- package/dist/modules/workspace/startup/kb-git.js.map +1 -1
- package/dist/modules/workspace/startup/kb-startup-runner.d.ts +95 -0
- package/dist/modules/workspace/startup/kb-startup-runner.d.ts.map +1 -1
- package/dist/modules/workspace/startup/kb-startup-runner.js +257 -55
- package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -1
- package/dist/modules/workspace/startup/steps/groups-to-plugins.step.d.ts +45 -0
- package/dist/modules/workspace/startup/steps/groups-to-plugins.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js +417 -385
- package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js.map +1 -1
- package/dist/modules/workspace/startup/steps/personal-spaces.step.d.ts +48 -0
- package/dist/modules/workspace/startup/steps/personal-spaces.step.d.ts.map +1 -0
- package/dist/modules/workspace/startup/steps/personal-spaces.step.js +183 -0
- package/dist/modules/workspace/startup/steps/personal-spaces.step.js.map +1 -0
- package/dist/modules/workspace/startup/steps/plugin-display-names.step.d.ts +51 -0
- package/dist/modules/workspace/startup/steps/plugin-display-names.step.d.ts.map +1 -0
- package/dist/modules/workspace/startup/steps/plugin-display-names.step.js +158 -0
- package/dist/modules/workspace/startup/steps/plugin-display-names.step.js.map +1 -0
- package/dist/modules/workspace/startup/steps/plugin-layout.d.ts +64 -0
- package/dist/modules/workspace/startup/steps/plugin-layout.d.ts.map +1 -0
- package/dist/modules/workspace/startup/steps/plugin-layout.js +97 -0
- package/dist/modules/workspace/startup/steps/plugin-layout.js.map +1 -0
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.d.ts +7 -1
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.js +45 -81
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.js.map +1 -1
- package/dist/modules/workspace/startup/steps/roles-yaml.step.d.ts +3 -1
- package/dist/modules/workspace/startup/steps/roles-yaml.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/roles-yaml.step.js +4 -12
- package/dist/modules/workspace/startup/steps/roles-yaml.step.js.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.d.ts +6 -1
- package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.js +154 -78
- package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
- package/dist/modules/workspace/startup/steps/template-files.step.d.ts +96 -24
- package/dist/modules/workspace/startup/steps/template-files.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/template-files.step.js +446 -138
- package/dist/modules/workspace/startup/steps/template-files.step.js.map +1 -1
- package/dist/modules/workspace/startup/steps/template-source.d.ts +94 -0
- package/dist/modules/workspace/startup/steps/template-source.d.ts.map +1 -0
- package/dist/modules/workspace/startup/steps/template-source.js +160 -0
- package/dist/modules/workspace/startup/steps/template-source.js.map +1 -0
- package/dist/modules/workspace/workspace.routes.d.ts +14 -2
- package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.routes.js +649 -238
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +290 -12
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +924 -269
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts +42 -1
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +1590 -146
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/modules/workspace/write-denial.d.ts +76 -0
- package/dist/modules/workspace/write-denial.d.ts.map +1 -0
- package/dist/modules/workspace/write-denial.js +138 -0
- package/dist/modules/workspace/write-denial.js.map +1 -0
- package/dist/shared/canonical-file-identity.d.ts +64 -0
- package/dist/shared/canonical-file-identity.d.ts.map +1 -0
- package/dist/shared/canonical-file-identity.js +92 -0
- package/dist/shared/canonical-file-identity.js.map +1 -0
- package/dist/shared/domain-errors.d.ts +108 -0
- package/dist/shared/domain-errors.d.ts.map +1 -1
- package/dist/shared/domain-errors.js +137 -0
- package/dist/shared/domain-errors.js.map +1 -1
- package/dist/shared/email-identity.d.ts +32 -0
- package/dist/shared/email-identity.d.ts.map +1 -0
- package/dist/shared/email-identity.js +37 -0
- package/dist/shared/email-identity.js.map +1 -0
- package/dist/shared/fs.contract.d.ts +206 -0
- package/dist/shared/fs.contract.d.ts.map +1 -0
- package/dist/shared/fs.contract.js +79 -0
- package/dist/shared/fs.contract.js.map +1 -0
- package/dist/shared/git-failure.d.ts +80 -0
- package/dist/shared/git-failure.d.ts.map +1 -0
- package/dist/shared/git-failure.js +139 -0
- package/dist/shared/git-failure.js.map +1 -0
- package/dist/shared/git-internals.d.ts +17 -0
- package/dist/shared/git-internals.d.ts.map +1 -0
- package/dist/shared/git-internals.js +123 -0
- package/dist/shared/git-internals.js.map +1 -0
- package/dist/shared/git.contract.d.ts +157 -0
- package/dist/shared/git.contract.d.ts.map +1 -0
- package/dist/shared/git.contract.js +103 -0
- package/dist/shared/git.contract.js.map +1 -0
- package/dist/shared/http-errors.d.ts +33 -0
- package/dist/shared/http-errors.d.ts.map +1 -0
- package/dist/shared/http-errors.js +35 -0
- package/dist/shared/http-errors.js.map +1 -0
- package/dist/shared/logger.contract.d.ts +63 -0
- package/dist/shared/logger.contract.d.ts.map +1 -0
- package/dist/shared/logger.contract.js +126 -0
- package/dist/shared/logger.contract.js.map +1 -0
- package/dist/shared/logging.d.ts +31 -0
- package/dist/shared/logging.d.ts.map +1 -0
- package/dist/shared/logging.js +92 -0
- package/dist/shared/logging.js.map +1 -0
- package/dist/shared/path-containment.d.ts +21 -0
- package/dist/shared/path-containment.d.ts.map +1 -0
- package/dist/shared/path-containment.js +34 -0
- package/dist/shared/path-containment.js.map +1 -0
- package/dist/shared/pending-proposals.d.ts +71 -0
- package/dist/shared/pending-proposals.d.ts.map +1 -0
- package/dist/shared/pending-proposals.js +98 -0
- package/dist/shared/pending-proposals.js.map +1 -0
- package/dist/shared/printable.d.ts +22 -0
- package/dist/shared/printable.d.ts.map +1 -0
- package/dist/shared/printable.js +65 -0
- package/dist/shared/printable.js.map +1 -0
- package/dist/shared/redact-secret.d.ts +30 -0
- package/dist/shared/redact-secret.d.ts.map +1 -0
- package/dist/shared/redact-secret.js +114 -0
- package/dist/shared/redact-secret.js.map +1 -0
- package/dist/shared/rename-no-replace.d.ts +119 -0
- package/dist/shared/rename-no-replace.d.ts.map +1 -0
- package/dist/shared/rename-no-replace.js +323 -0
- package/dist/shared/rename-no-replace.js.map +1 -0
- package/dist/shared/ssrf.d.ts.map +1 -1
- package/dist/shared/ssrf.js +49 -18
- package/dist/shared/ssrf.js.map +1 -1
- package/dist/version.d.ts +10 -6
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +10 -6
- package/dist/version.js.map +1 -1
- package/kb-template/.bevelignore +4 -3
- package/kb-template/AGENTS.md +281 -36
- package/kb-template/KnowledgeBase/How to get started.md +1 -1
- package/kb-template/access.md +5 -3
- package/kb-template/mcp-description.md +27 -0
- package/migrations/0006_api_tokens_revoked_by.sql +1 -0
- package/migrations/0007_github_facade_registered.sql +1 -0
- package/migrations/0008_change_request_apply_failure.sql +4 -0
- package/migrations/0009_change_request_apply_failure_kind.sql +13 -0
- package/migrations/0010_plugin_join_requests.sql +15 -0
- package/migrations/0011_plugin_join_request_claim.sql +11 -0
- package/migrations/meta/0006_snapshot.json +1723 -0
- package/migrations/meta/0007_snapshot.json +1729 -0
- package/migrations/meta/0008_snapshot.json +1753 -0
- package/migrations/meta/0009_snapshot.json +1759 -0
- package/migrations/meta/0010_snapshot.json +1872 -0
- package/migrations/meta/0011_snapshot.json +1884 -0
- package/migrations/meta/_journal.json +42 -0
- package/package.json +6 -5
- package/src/__tests__/core-config.domain.test.ts +67 -2
- package/src/__tests__/kb-layout-config.test.ts +186 -1
- package/src/__tests__/open-change-gate.ts +15 -0
- package/src/core/__tests__/catalog-cache-invalidation.test.ts +83 -3
- package/src/core/__tests__/catalog-revision.test.ts +190 -0
- package/src/core/__tests__/lifecycle.test.ts +369 -0
- package/src/core/__tests__/public-config.test.ts +30 -0
- package/src/core/__tests__/readiness.test.ts +109 -0
- package/src/core/catalog-cache-invalidation.ts +20 -1
- package/src/core/catalog-revision.ts +110 -0
- package/src/core/create-core-server.ts +258 -79
- package/src/core/create-core-services.ts +302 -81
- package/src/core/lifecycle.ts +312 -0
- package/src/core/public-config.ts +86 -0
- package/src/core/readiness.ts +123 -0
- package/src/core-config.ts +70 -4
- package/src/index.ts +15 -0
- package/src/modules/access/__tests__/access-control.admin-root-floor.test.ts +362 -0
- package/src/modules/access/__tests__/access-control.atref-batch.test.ts +2 -1
- package/src/modules/access/__tests__/access-control.atref-cache.test.ts +46 -22
- package/src/modules/access/__tests__/access-control.deny-dependents.test.ts +215 -0
- package/src/modules/access/__tests__/access-control.download-read.test.ts +268 -0
- package/src/modules/access/__tests__/access-control.everyone-read.test.ts +145 -0
- package/src/modules/access/__tests__/access-control.group-read.test.ts +163 -0
- package/src/modules/access/__tests__/access-control.invalidate-race.test.ts +2 -0
- package/src/modules/access/__tests__/access-control.platform-restore.test.ts +152 -0
- package/src/modules/access/__tests__/access-control.prospective.test.ts +189 -0
- package/src/modules/access/__tests__/access-control.read-batch.test.ts +2 -1
- package/src/modules/access/__tests__/access-control.service.test.ts +123 -78
- package/src/modules/access/__tests__/access-groups.test.ts +47 -14
- package/src/modules/access/__tests__/access-md-format.test.ts +2 -1
- package/src/modules/access/__tests__/access-mutation.service.test.ts +52 -4
- package/src/modules/access/__tests__/access-own-read-grant.test.ts +2 -0
- package/src/modules/access/__tests__/access-personal-plugin.test.ts +153 -0
- package/src/modules/access/__tests__/access-plugin-principals.test.ts +18 -1
- package/src/modules/access/__tests__/access-restrict-here.test.ts +450 -0
- package/src/modules/access/__tests__/access.routes.batch.test.ts +139 -0
- package/src/modules/access/__tests__/access.routes.folder-governs.test.ts +394 -0
- package/src/modules/access/__tests__/access.routes.group-grant.test.ts +6 -2
- package/src/modules/access/__tests__/access.routes.overrides.test.ts +3 -2
- package/src/modules/access/__tests__/access.routes.proposal-branch.test.ts +234 -0
- package/src/modules/access/__tests__/access.routes.prospective.test.ts +216 -0
- package/src/modules/access/__tests__/access.routes.revoke.test.ts +7 -2
- package/src/modules/access/__tests__/access.routes.unknown-email.test.ts +360 -0
- package/src/modules/access/__tests__/change-read-gate.test.ts +272 -0
- package/src/modules/access/__tests__/creator-access.test.ts +81 -83
- package/src/modules/access/__tests__/grant-sources.test.ts +3 -2
- package/src/modules/access/__tests__/groups-admin.service.test.ts +6 -5
- package/src/modules/access/__tests__/roles-admin.security.test.ts +196 -0
- package/src/modules/access/__tests__/roles-admin.service.test.ts +127 -2
- package/src/modules/access/__tests__/roles-capabilities.test.ts +5 -4
- package/src/modules/access/__tests__/roles.routes.test.ts +5 -2
- package/src/modules/access/__tests__/user-access-removal.service.test.ts +402 -0
- package/src/modules/access/__tests__/users-db-double.ts +63 -0
- package/src/modules/access/access-control.interface.ts +251 -11
- package/src/modules/access/access-control.service.ts +719 -164
- package/src/modules/access/access-mutation.service.ts +182 -13
- package/src/modules/access/access-view.ts +316 -0
- package/src/modules/access/access.routes.ts +293 -95
- package/src/modules/access/admin-locked-commit.ts +12 -14
- package/src/modules/access/admin-route-helpers.ts +4 -4
- package/src/modules/access/change-read-gate.ts +148 -0
- package/src/modules/access/creator-access.ts +89 -124
- package/src/modules/access/directory-sync-bot.ts +4 -1
- package/src/modules/access/groups-admin.service.ts +3 -3
- package/src/modules/access/reference-scan.ts +5 -5
- package/src/modules/access/roles-admin.service.ts +94 -15
- package/src/modules/access/synced-groups-committer.ts +10 -15
- package/src/modules/access/synced-groups-writer.ts +3 -12
- package/src/modules/access/user-access-removal.service.ts +711 -0
- package/src/modules/access-model/__tests__/access-splice.test.ts +63 -1
- package/src/modules/access-model/__tests__/frontmatter-fences.test.ts +71 -0
- package/src/modules/access-model/__tests__/roles-yaml-guard.test.ts +131 -0
- package/src/modules/access-model/access-errors.ts +56 -15
- package/src/modules/access-model/access-grammar.ts +85 -48
- package/src/modules/access-model/access-splice.ts +18 -18
- package/src/modules/access-model/access-template.ts +44 -0
- package/src/modules/access-model/change-gate.ts +80 -0
- package/src/modules/access-model/creator.ts +18 -21
- package/src/modules/access-model/frontmatter-lines.ts +98 -0
- package/src/modules/access-model/plugin-principals.ts +12 -19
- package/src/modules/access-model/roles-yaml-guard.ts +182 -4
- package/src/modules/admin/admin-access.service.ts +6 -2
- package/src/modules/agent-instructions/__tests__/agent-instructions.route.test.ts +100 -0
- package/src/modules/agent-instructions/__tests__/compose.test.ts +256 -0
- package/src/modules/agent-instructions/__tests__/read-preamble.test.ts +135 -0
- package/src/modules/agent-instructions/agent-instructions.routes.ts +41 -0
- package/src/modules/agent-instructions/compose.ts +271 -0
- package/src/modules/agent-instructions/index.ts +14 -0
- package/src/modules/agent-instructions/read-preamble.ts +88 -0
- package/src/modules/auth/__tests__/account-erasure.approval-gate.test.ts +209 -0
- package/src/modules/auth/__tests__/account.routes.test.ts +178 -2
- package/src/modules/auth/__tests__/auth.middleware.test.ts +78 -0
- package/src/modules/auth/__tests__/auth.service.test.ts +338 -5
- package/src/modules/auth/__tests__/oidc-auth-provider.test.ts +352 -12
- package/src/modules/auth/__tests__/onboarding-done.route.test.ts +3 -1
- package/src/modules/auth/account-erasure.service.ts +117 -10
- package/src/modules/auth/account.routes.ts +115 -14
- package/src/modules/auth/auth.routes.ts +20 -7
- package/src/modules/auth/auth.service.ts +187 -28
- package/src/modules/auth/oidc-auth-provider.ts +120 -22
- package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +109 -1
- package/src/modules/code-mode/code-mode.tool.ts +13 -2
- package/src/modules/connection-probe/__tests__/connection-probe.service.test.ts +78 -0
- package/src/modules/connection-probe/connection-probe.service.ts +59 -7
- package/src/modules/database/__tests__/advisory-lease.test.ts +152 -0
- package/src/modules/database/__tests__/advisory-lock.test.ts +147 -0
- package/src/modules/database/advisory-lock.ts +346 -0
- package/src/modules/database/connection.ts +9 -1
- package/src/modules/database/core-schema.ts +123 -0
- package/src/modules/database/migrate.ts +27 -6
- package/src/modules/declared-variables/declared-variables.routes.ts +6 -3
- package/src/modules/diff/__tests__/diff.service.seed-atomicity.test.ts +3 -0
- package/src/modules/diff/__tests__/diff.service.test.ts +3 -0
- package/src/modules/diff/diff.routes.ts +2 -4
- package/src/modules/diff/diff.service.ts +75 -79
- package/src/modules/kb-fs/__tests__/locking-filesystem.test.ts +472 -9
- package/src/modules/kb-fs/__tests__/node-fs.test.ts +409 -0
- package/src/modules/kb-fs/__tests__/repo-path.test.ts +210 -1
- package/src/modules/{workspace → kb-fs}/bevel-ignore.ts +22 -15
- package/src/modules/kb-fs/file-change-notifier.ts +4 -1
- package/src/modules/kb-fs/git-guarded-filesystem.ts +123 -0
- package/src/modules/kb-fs/locking-filesystem.ts +399 -109
- package/src/modules/kb-fs/node-fs.ts +165 -0
- package/src/modules/kb-fs/read-only-filesystem.ts +18 -9
- package/src/modules/kb-fs/repo-path.ts +213 -3
- package/src/modules/kb-sync/kb-sync.routes.ts +5 -2
- package/src/modules/kb-sync/kb-sync.service.ts +8 -5
- package/src/modules/marketplace/__tests__/github-facade.test.ts +134 -2
- package/src/modules/marketplace/__tests__/marketplace-git.test.ts +2 -1
- package/src/modules/marketplace/__tests__/marketplace-repo.head.test.ts +47 -0
- package/src/modules/marketplace/git-http.routes.ts +7 -4
- package/src/modules/marketplace/github-facade/github-facade-admin.routes.ts +35 -3
- package/src/modules/marketplace/github-facade/github-facade-credentials.service.ts +55 -0
- package/src/modules/marketplace/github-facade/github-facade.routes.ts +32 -6
- package/src/modules/marketplace/github-facade/github-facade.service.ts +80 -19
- package/src/modules/marketplace/github-facade/index.ts +1 -0
- package/src/modules/marketplace/marketplace-repo.service.ts +23 -8
- package/src/modules/mcp/__tests__/downstream-pool.test.ts +278 -0
- package/src/modules/mcp/__tests__/downstream-token-refresh.test.ts +178 -0
- package/src/modules/mcp/__tests__/fake-downstream-mcp-server.ts +156 -0
- package/src/modules/mcp/__tests__/first-call-probe.cli-args.test.ts +178 -0
- package/src/modules/mcp/__tests__/first-call-probe.test.ts +414 -0
- package/src/modules/mcp/__tests__/manual-failure-memo.test.ts +98 -6
- package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +8 -5
- package/src/modules/mcp/__tests__/mcp-routes-harness.ts +1 -3
- package/src/modules/mcp/__tests__/mcp.e2e.test.ts +1055 -59
- package/src/modules/mcp/__tests__/mcp.routes.stateless.test.ts +182 -0
- package/src/modules/mcp/__tests__/mcp.service.test.ts +274 -6
- package/src/modules/mcp/__tests__/surface-log-throttle.test.ts +169 -0
- package/src/modules/mcp/downstream-pool.ts +256 -0
- package/src/modules/mcp/downstream-token-refresh.ts +170 -0
- package/src/modules/mcp/first-call-probe.cli-args.ts +159 -0
- package/src/modules/mcp/first-call-probe.cli.ts +60 -0
- package/src/modules/mcp/first-call-probe.ts +418 -0
- package/src/modules/mcp/manual-failure-memo.ts +81 -23
- package/src/modules/mcp/mcp-auth.middleware.ts +25 -16
- package/src/modules/mcp/mcp.routes.ts +376 -482
- package/src/modules/mcp/mcp.service.ts +846 -176
- package/src/modules/mcp/oauth/bevel-oauth-provider.ts +5 -2
- package/src/modules/mcp/oauth/oauth-consent.routes.ts +4 -4
- package/src/modules/mcp/surface-log-throttle.ts +120 -0
- package/src/modules/plugins/__tests__/bundle-dialect.e2e.test.ts +10 -7
- package/src/modules/plugins/__tests__/bundle-dialect.test.ts +165 -0
- package/src/modules/plugins/__tests__/compile-marketplace.test.ts +139 -10
- package/src/modules/plugins/__tests__/fake-join-request-store.ts +189 -0
- package/src/modules/plugins/__tests__/join-request-jobs.service.test.ts +608 -0
- package/src/modules/plugins/__tests__/plugin-creation-display-name.test.ts +241 -0
- package/src/modules/plugins/__tests__/plugin-index.service.test.ts +148 -7
- package/src/modules/plugins/__tests__/plugin-links.service.test.ts +10 -13
- package/src/modules/plugins/__tests__/plugin-names.test.ts +77 -0
- package/src/modules/plugins/__tests__/plugin-provision.service.test.ts +129 -19
- package/src/modules/plugins/__tests__/plugin-rename.service.test.ts +91 -29
- package/src/modules/plugins/__tests__/plugins.routes.test.ts +370 -19
- package/src/modules/plugins/__tests__/plugins.tools.test.ts +131 -0
- package/src/modules/plugins/__tests__/teams.routes.test.ts +248 -0
- package/src/modules/plugins/compile/compile-marketplace.ts +143 -46
- package/src/modules/plugins/compile/marketplace-compiler.service.ts +8 -7
- package/src/modules/plugins/discovery/__tests__/kb-plugin-source.test.ts +56 -20
- package/src/modules/plugins/discovery/bundle-dialect/bundle.source.ts +30 -9
- package/src/modules/plugins/discovery/bundle-dialect/registry.ts +93 -18
- package/src/modules/plugins/discovery/kb-plugin-source.ts +10 -7
- package/src/modules/plugins/discovery/native.source.ts +21 -3
- package/src/modules/plugins/discovery/plugin-source.ts +17 -3
- package/src/modules/plugins/index.ts +16 -3
- package/src/modules/plugins/join-request-jobs.service.ts +675 -0
- package/src/modules/plugins/join-request-records.store.ts +353 -0
- package/src/modules/plugins/join-requests.service.ts +7 -4
- package/src/modules/plugins/plugin-links.service.ts +4 -17
- package/src/modules/plugins/plugin-links.ts +6 -3
- package/src/modules/plugins/plugin-provision.service.ts +209 -37
- package/src/modules/plugins/plugin-rename.service.ts +21 -9
- package/src/modules/plugins/plugins.contract.ts +52 -4
- package/src/modules/plugins/plugins.routes.ts +204 -124
- package/src/modules/plugins/plugins.service.ts +92 -18
- package/src/modules/plugins/plugins.tools.ts +88 -0
- package/src/modules/plugins/teams.routes.ts +129 -0
- package/src/modules/secrets-vault/__tests__/connect-pending.route.test.ts +359 -15
- package/src/modules/secrets-vault/__tests__/db-secrets-vault.oauth.test.ts +248 -0
- package/src/modules/secrets-vault/__tests__/namespace-secrets.test.ts +202 -0
- package/src/modules/secrets-vault/db-secrets-vault.service.ts +217 -35
- package/src/modules/secrets-vault/index.ts +1 -0
- package/src/modules/secrets-vault/mcp-oauth-discovery.service.ts +6 -3
- package/src/modules/secrets-vault/secrets-variable-loader.ts +4 -4
- package/src/modules/secrets-vault/secrets-vault.contract.ts +93 -0
- package/src/modules/secrets-vault/secrets-vault.routes.ts +99 -38
- package/src/modules/settings/__tests__/connection-check.test.ts +233 -0
- package/src/modules/settings/__tests__/deployment-settings.service.test.ts +188 -6
- package/src/modules/settings/__tests__/git-root-folders.test.ts +247 -0
- package/src/modules/settings/__tests__/oidc-check.test.ts +185 -0
- package/src/modules/settings/__tests__/setup.routes.boot-failure.test.ts +119 -0
- package/src/modules/settings/__tests__/setup.routes.layout-phase.test.ts +170 -0
- package/src/modules/settings/__tests__/setup.routes.oidc.test.ts +555 -0
- package/src/modules/settings/__tests__/setup.routes.test.ts +667 -16
- package/src/modules/settings/connection-check.ts +351 -0
- package/src/modules/settings/deployment-settings.service.ts +403 -57
- package/src/modules/settings/git-root-folders.ts +171 -0
- package/src/modules/settings/oidc-check.ts +229 -0
- package/src/modules/settings/setup.routes.ts +456 -110
- package/src/modules/skills/__tests__/allowed-tools-check.test.ts +224 -0
- package/src/modules/skills/__tests__/allowed-tools-warn.tools.test.ts +210 -0
- package/src/modules/skills/__tests__/pending-skills.service.test.ts +3 -3
- package/src/modules/skills/__tests__/skills.routes.warnings.test.ts +91 -0
- package/src/modules/skills/__tests__/skills.service.test.ts +223 -2
- package/src/modules/skills/allowed-tools-check.ts +352 -0
- package/src/modules/skills/index.ts +4 -0
- package/src/modules/skills/pending-skills.service.ts +21 -82
- package/src/modules/skills/skill-access-requests.routes.ts +10 -6
- package/src/modules/skills/skills.contract.ts +43 -11
- package/src/modules/skills/skills.routes.ts +16 -1
- package/src/modules/skills/skills.service.ts +214 -84
- package/src/modules/skills/skills.tools.ts +43 -5
- package/src/modules/tool-auth/__tests__/connection-key-rejection.test.ts +192 -0
- package/src/modules/tool-auth/__tests__/connection-keys-admin.routes.test.ts +116 -0
- package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +124 -0
- package/src/modules/tool-auth/connection-key-rejection.ts +25 -0
- package/src/modules/tool-auth/connection-keys-admin.routes.ts +76 -0
- package/src/modules/tool-auth/external-api-key.interface.ts +56 -3
- package/src/modules/tool-auth/external-api-key.service.ts +73 -25
- package/src/modules/tool-auth/internal-token.service.ts +5 -2
- package/src/modules/tool-auth/key-or-session.middleware.ts +45 -0
- package/src/modules/tool-auth/tool-auth.middleware.ts +16 -7
- package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +228 -0
- package/src/modules/tool-helpers/__tests__/phase4-tools.test.ts +33 -4
- package/src/modules/tool-helpers/tool-context.ts +46 -7
- package/src/modules/tool-helpers/tool-handler.ts +23 -5
- package/src/modules/tool-helpers/tool.contract.ts +5 -0
- package/src/modules/tool-manuals/__tests__/mcp-server-edit.service.test.ts +2 -1
- package/src/modules/tool-manuals/__tests__/pending-tools.service.test.ts +411 -0
- package/src/modules/tool-manuals/__tests__/tool-delete.route.test.ts +133 -0
- package/src/modules/tool-manuals/__tests__/tool-delete.service.test.ts +469 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.archive.route.test.ts +21 -2
- package/src/modules/tool-manuals/__tests__/tool-manuals.cli.test.ts +9 -4
- package/src/modules/tool-manuals/__tests__/tool-manuals.detail.route.test.ts +6 -1
- package/src/modules/tool-manuals/__tests__/tool-manuals.invalid.routes.test.ts +254 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.invalid.test.ts +577 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +6 -1
- package/src/modules/tool-manuals/__tests__/tool-manuals.pending.route.test.ts +102 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.restart.test.ts +285 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +178 -2
- package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +114 -2
- package/src/modules/tool-manuals/index.ts +3 -0
- package/src/modules/tool-manuals/mcp-json-discovery.ts +13 -10
- package/src/modules/tool-manuals/mcp-server-edit.service.ts +11 -14
- package/src/modules/tool-manuals/pending-tools.service.ts +179 -0
- package/src/modules/tool-manuals/tool-delete.service.ts +401 -0
- package/src/modules/tool-manuals/tool-manuals.contract.ts +165 -0
- package/src/modules/tool-manuals/tool-manuals.routes.ts +138 -54
- package/src/modules/tool-manuals/tool-manuals.service.ts +432 -87
- package/src/modules/tool-manuals/tool-manuals.tools.ts +81 -6
- package/src/modules/workflow/__tests__/apply-failure.test.ts +1038 -0
- package/src/modules/workflow/__tests__/event-bus.test.ts +61 -9
- package/src/modules/workflow/__tests__/fake-file-lock-db.ts +199 -0
- package/src/modules/workflow/__tests__/file-lock.service.path-identity.test.ts +237 -0
- package/src/modules/workflow/__tests__/format-affected-owners.test.ts +18 -0
- package/src/modules/workflow/__tests__/merge-announces-tree.test.ts +220 -0
- package/src/modules/workflow/__tests__/pending-commits.worker.concurrency.test.ts +187 -0
- package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +2 -0
- package/src/modules/workflow/__tests__/sanitize-error.test.ts +10 -0
- package/src/modules/workflow/__tests__/workflow.routes.folder-proposals.test.ts +128 -0
- package/src/modules/workflow/__tests__/workflow.routes.fork-point-file.test.ts +149 -0
- package/src/modules/workflow/__tests__/workflow.routes.lock-path.test.ts +305 -0
- package/src/modules/workflow/__tests__/workflow.service.branch-in-use.test.ts +2 -0
- package/src/modules/workflow/__tests__/workflow.service.commitFileWhileLocked.test.ts +2 -0
- package/src/modules/workflow/__tests__/workflow.service.deleted-branch-sweep.test.ts +2 -0
- package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +294 -25
- package/src/modules/workflow/__tests__/workflow.service.folder-proposals.test.ts +564 -0
- package/src/modules/workflow/__tests__/workflow.service.platform-restore.test.ts +135 -0
- package/src/modules/workflow/__tests__/workflow.service.read-gate.test.ts +172 -0
- package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +14 -0
- package/src/modules/workflow/__tests__/workflow.service.sync.test.ts +5 -0
- package/src/modules/workflow/__tests__/workflow.service.update-from-target.test.ts +281 -0
- package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +158 -7
- package/src/modules/workflow/agent-tools/workflow.tools.ts +93 -64
- package/src/modules/workflow/event-bus.ts +26 -28
- package/src/modules/workflow/events.routes.ts +56 -21
- package/src/modules/workflow/file-lock.service.ts +27 -4
- package/src/modules/workflow/git/__tests__/change-request-link.test.ts +43 -0
- package/src/modules/workflow/git/__tests__/git.service.accessGating.test.ts +1 -0
- package/src/modules/workflow/git/__tests__/git.service.changedFilesForPr.test.ts +238 -1
- package/src/modules/workflow/git/__tests__/git.service.forkPoint.test.ts +245 -0
- package/src/modules/workflow/git/__tests__/git.service.list-branches.test.ts +35 -0
- package/src/modules/workflow/git/__tests__/git.service.mergeChangeRequest.test.ts +149 -0
- package/src/modules/workflow/git/__tests__/git.service.revertPathsAndPush.test.ts +177 -0
- package/src/modules/workflow/git/__tests__/node-git-runner.test.ts +114 -0
- package/src/modules/workflow/git/__tests__/pull-request.service.getPrDetail.test.ts +69 -2
- package/src/modules/workflow/git/__tests__/pull-request.service.list-fetch.test.ts +121 -0
- package/src/modules/workflow/git/__tests__/pull-request.service.placeholder.test.ts +79 -0
- package/src/modules/workflow/git/__tests__/pull-request.service.test.ts +1 -1
- package/src/modules/workflow/git/__tests__/pull-request.service.viewer-can-cancel.test.ts +1 -1
- package/src/modules/workflow/git/__tests__/pull-request.service.viewer-can-update.test.ts +111 -0
- package/src/modules/workflow/git/change-request-link.ts +35 -0
- package/src/modules/workflow/git/git-version.ts +5 -2
- package/src/modules/workflow/git/git.service.ts +684 -144
- package/src/modules/workflow/git/node-git-runner.ts +315 -0
- package/src/modules/workflow/git/pull-request.service.ts +179 -27
- package/src/modules/workflow/pending-commits.service.ts +26 -7
- package/src/modules/workflow/pending-commits.worker.ts +114 -69
- package/src/modules/workflow/recovery-bot.ts +4 -1
- package/src/modules/workflow/review-workflow/__tests__/approval-states.test.ts +180 -1
- package/src/modules/workflow/review-workflow/__tests__/cancel-pr.test.ts +1 -1
- package/src/modules/workflow/review-workflow/__tests__/carry-approvals-forward.test.ts +520 -0
- package/src/modules/workflow/review-workflow/__tests__/erase-approver.test.ts +84 -0
- package/src/modules/workflow/review-workflow/__tests__/merge-gate.test.ts +89 -77
- package/src/modules/workflow/review-workflow/approval-lock.ts +122 -0
- package/src/modules/workflow/review-workflow/review-workflow.interface.ts +59 -7
- package/src/modules/workflow/review-workflow/review-workflow.service.ts +309 -105
- package/src/modules/workflow/sanitize-error.ts +9 -4
- package/src/modules/workflow/workflow.routes.ts +273 -29
- package/src/modules/workflow/workflow.service.ts +1087 -171
- package/src/modules/workspace/__tests__/empty-dirs.test.ts +44 -0
- package/src/modules/workspace/__tests__/file-stat-access.test.ts +301 -0
- package/src/modules/workspace/__tests__/git-internals.security.test.ts +792 -0
- package/src/modules/workspace/__tests__/workspace-paths-inside-the-repo.test.ts +464 -0
- package/src/modules/workspace/__tests__/workspace.routes.branch-errors.test.ts +139 -0
- package/src/modules/workspace/__tests__/workspace.routes.create-grant.test.ts +189 -37
- package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +51 -11
- package/src/modules/workspace/__tests__/workspace.routes.download-implies-read.test.ts +161 -0
- package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +109 -10
- package/src/modules/workspace/__tests__/workspace.routes.file-writes.test.ts +624 -0
- package/src/modules/workspace/__tests__/workspace.routes.folders.test.ts +409 -0
- package/src/modules/workspace/__tests__/workspace.routes.move-platform-files.test.ts +327 -0
- package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +25 -6
- package/src/modules/workspace/__tests__/workspace.service.ensure-remotes-fetched.test.ts +109 -0
- package/src/modules/workspace/__tests__/workspace.service.list-cloned.test.ts +6 -5
- package/src/modules/workspace/__tests__/workspace.service.move-never-overwrites.test.ts +218 -0
- package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +56 -1
- package/src/modules/workspace/__tests__/workspace.service.test.ts +811 -42
- package/src/modules/workspace/__tests__/workspace.service.unknown-branch.test.ts +197 -0
- package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +128 -0
- package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +212 -0
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +2632 -110
- package/src/modules/workspace/__tests__/write-denial.test.ts +75 -0
- package/src/modules/workspace/empty-dirs.ts +79 -0
- package/src/modules/workspace/file-readers/README.md +32 -0
- package/src/modules/workspace/file-readers/__tests__/doc-extract.test.ts +59 -2
- package/src/modules/workspace/file-readers/__tests__/file-reader.registry.test.ts +133 -4
- package/src/modules/workspace/file-readers/__tests__/fixtures/soft-break.docx +0 -0
- package/src/modules/workspace/file-readers/__tests__/fixtures/tab.docx +0 -0
- package/src/modules/workspace/file-readers/content-mode.ts +87 -0
- package/src/modules/workspace/file-readers/doc-extract.service.ts +4 -1
- package/src/modules/workspace/file-readers/doc-extract.types.ts +8 -4
- package/src/modules/workspace/file-readers/document-reader.ts +6 -0
- package/src/modules/workspace/file-readers/extract-docx.ts +6 -0
- package/src/modules/workspace/file-readers/file-reader.registry.ts +9 -1
- package/src/modules/workspace/file-readers/file-reader.ts +27 -6
- package/src/modules/workspace/file-readers/image-reader.ts +10 -5
- package/src/modules/workspace/file-readers/ooxml-text.ts +38 -2
- package/src/modules/workspace/file-readers/text-reader.ts +61 -4
- package/src/modules/workspace/git-internals.middleware.ts +86 -0
- package/src/modules/workspace/not-found.ts +119 -0
- package/src/modules/workspace/session-ontology.gate.ts +1 -1
- package/src/modules/workspace/startup/__tests__/beside-checkout.test.ts +181 -0
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.degraded.test.ts +361 -0
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +53 -16
- package/src/modules/workspace/startup/beside-checkout.ts +112 -0
- package/src/modules/workspace/startup/kb-git.ts +43 -36
- package/src/modules/workspace/startup/kb-startup-runner.ts +303 -63
- package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +1215 -73
- package/src/modules/workspace/startup/steps/groups-to-plugins.step.ts +460 -417
- package/src/modules/workspace/startup/steps/personal-spaces.step.ts +191 -0
- package/src/modules/workspace/startup/steps/plugin-display-names.step.ts +169 -0
- package/src/modules/workspace/startup/steps/plugin-layout.ts +110 -0
- package/src/modules/workspace/startup/steps/plugin-manifests.step.ts +46 -85
- package/src/modules/workspace/startup/steps/roles-yaml.step.ts +6 -12
- package/src/modules/workspace/startup/steps/seed-tree.ts +159 -78
- package/src/modules/workspace/startup/steps/template-files.step.ts +492 -141
- package/src/modules/workspace/startup/steps/template-source.ts +168 -0
- package/src/modules/workspace/workspace.routes.ts +687 -257
- package/src/modules/workspace/workspace.service.ts +991 -303
- package/src/modules/workspace/workspace.tools.ts +1787 -180
- package/src/modules/workspace/write-denial.ts +189 -0
- package/src/shared/__tests__/canonical-file-identity.test.ts +154 -0
- package/src/shared/__tests__/email-identity.test.ts +35 -0
- package/src/shared/__tests__/filename.test.ts +0 -0
- package/src/shared/__tests__/frontmatter-id.test.ts +5 -0
- package/src/shared/__tests__/git-failure.test.ts +198 -0
- package/src/shared/__tests__/git-internals.test.ts +115 -0
- package/src/shared/__tests__/http-errors.test.ts +42 -0
- package/src/shared/__tests__/logging.test.ts +185 -0
- package/src/shared/__tests__/path-containment.test.ts +60 -0
- package/src/shared/__tests__/pending-proposals.readAt.test.ts +102 -0
- package/src/shared/__tests__/printable.test.ts +64 -0
- package/src/shared/__tests__/redact-secret.test.ts +122 -0
- package/src/shared/__tests__/rename-no-replace.test.ts +466 -0
- package/src/shared/__tests__/ssrf.test.ts +20 -0
- package/src/shared/canonical-file-identity.ts +92 -0
- package/src/shared/domain-errors.ts +149 -0
- package/src/shared/email-identity.ts +38 -0
- package/src/shared/fs.contract.ts +225 -0
- package/src/shared/git-failure.ts +196 -0
- package/src/shared/git-internals.ts +124 -0
- package/src/shared/git.contract.ts +170 -0
- package/src/shared/http-errors.ts +36 -0
- package/src/shared/logger.contract.ts +141 -0
- package/src/shared/logging.ts +96 -0
- package/src/shared/path-containment.ts +34 -0
- package/src/shared/pending-proposals.ts +161 -0
- package/src/shared/printable.ts +68 -0
- package/src/shared/redact-secret.ts +118 -0
- package/src/shared/rename-no-replace.ts +386 -0
- package/src/shared/ssrf.ts +44 -16
- package/src/version.ts +10 -6
- package/src/modules/diff/diff-paths.ts +0 -9
- package/src/modules/mcp/__tests__/mcp-session-store.test.ts +0 -151
- package/src/modules/mcp/__tests__/mcp.routes.session.test.ts +0 -226
- package/src/modules/mcp/mcp-session-store.ts +0 -162
- package/src/shared/__tests__/fs-walk.test.ts +0 -63
- package/src/shared/__tests__/kb-walk.test.ts +0 -107
- package/src/shared/fs-errors.ts +0 -10
- package/src/shared/fs-walk.ts +0 -45
- package/src/shared/hash-email.ts +0 -14
- package/src/shared/kb-walk.ts +0 -95
|
@@ -1,47 +1,85 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
1
2
|
import type { Server as HttpServer } from 'node:http';
|
|
3
|
+
import type { AddressInfo } from 'node:net';
|
|
2
4
|
import type { RequestHandler } from 'express';
|
|
3
5
|
import express from 'express';
|
|
4
|
-
import { afterEach, describe, expect, it } from 'vitest';
|
|
6
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
5
7
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
6
8
|
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
9
|
+
import { CallTemplateSerializer, UtcpClientConfigSerializer } from '@utcp/sdk';
|
|
10
|
+
import { CodeModeUtcpClient } from '@utcp/code-mode';
|
|
7
11
|
import { tmpdir } from 'node:os';
|
|
8
12
|
import { join } from 'node:path';
|
|
9
13
|
import { createMcpRoutes } from '../mcp.routes.js';
|
|
10
14
|
import { McpService } from '../mcp.service.js';
|
|
11
|
-
import { McpSessionStore } from '../mcp-session-store.js';
|
|
12
15
|
import { SpillStore } from '../../workspace/spill-store.js';
|
|
13
16
|
import { createManualRoutes } from '../../tool-registry/manual.routes.js';
|
|
14
17
|
import { ToolRegistry } from '../../tool-registry/tool-registry.js';
|
|
15
18
|
import { toolDef } from '../../tool-helpers/tool-def.js';
|
|
19
|
+
import { PLATFORM_HEADER } from '../../agent-instructions/index.js';
|
|
20
|
+
import { startFakeDownstreamMcpServer, type FakeDownstreamMcpServer } from './fake-downstream-mcp-server.js';
|
|
21
|
+
import { registerBevelSecretsVariableLoader } from '../../secrets-vault/secrets-variable-loader.js';
|
|
22
|
+
import type { ForcedRefreshOutcome, ISecretsVaultService } from '../../secrets-vault/secrets-vault.contract.js';
|
|
23
|
+
import type { IToolManualService } from '../../tool-manuals/tool-manuals.contract.js';
|
|
24
|
+
import { setLogger } from '../../../shared/logging.js';
|
|
25
|
+
import type { ILogger } from '../../../shared/logger.contract.js';
|
|
26
|
+
import { formatProbeReport, probeFirstCall } from '../first-call-probe.js';
|
|
27
|
+
import { printable } from '../../../shared/printable.js';
|
|
16
28
|
|
|
17
29
|
/**
|
|
18
|
-
* True end-to-end test over the REAL Streamable-HTTP MCP transport.
|
|
19
|
-
* `Client`
|
|
20
|
-
* real `
|
|
21
|
-
* `
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
30
|
+
* True end-to-end test over the REAL Streamable-HTTP MCP transport. Real MCP
|
|
31
|
+
* clients (the official SDK `Client`, and `@utcp/mcp` — the client `hexis-mcp`
|
|
32
|
+
* pins) talk to the real `createMcpRoutes` HTTP surface, which drives the real
|
|
33
|
+
* `McpService` proxy, which discovers and dispatches against real tool routes
|
|
34
|
+
* over loopback — all on one express app per "platform process". Nothing is
|
|
35
|
+
* faked except credential verification (a bearer → user map).
|
|
36
|
+
*
|
|
37
|
+
* The endpoint is STATELESS, so the suite's centre of gravity is what that
|
|
38
|
+
* buys: no session id ever issued, every request served on its own, and a
|
|
39
|
+
* platform restart between two requests of one client invisible to it.
|
|
25
40
|
*/
|
|
26
41
|
|
|
27
|
-
const
|
|
42
|
+
const KEY_A = 'bevel_key_user_a';
|
|
43
|
+
const KEY_B = 'bevel_key_user_b';
|
|
44
|
+
const KEY_FORGER = 'bevel_key_user_forger';
|
|
45
|
+
/**
|
|
46
|
+
* A user id carrying a whole forged log line — and one character from every
|
|
47
|
+
* class `printable` escapes: a C0 newline, a C1 control (U+009B, the one-byte
|
|
48
|
+
* CSI that opens an ANSI sequence), and a Unicode line separator (U+2028).
|
|
49
|
+
* `JSON.stringify` escapes only the first, so the last two are what keep the
|
|
50
|
+
* escaping test honest about what it pins.
|
|
51
|
+
*/
|
|
52
|
+
const FORGER_ID =
|
|
53
|
+
'user-C\ninfo: downstream token refresh: manual=notion user=root outcome=refreshed\u009b31m\u2028tail';
|
|
54
|
+
/** An INTERACTIVE caller (OAuth/JWT): no connection-key id, so it gets the full tool listing. */
|
|
55
|
+
const KEY_INTERACTIVE = 'bevel_key_user_interactive';
|
|
56
|
+
const USERS: Record<string, { userId: string; tokenId?: string }> = {
|
|
57
|
+
[KEY_A]: { userId: 'user-A', tokenId: 'tok-A' },
|
|
58
|
+
[KEY_B]: { userId: 'user-B', tokenId: 'tok-B' },
|
|
59
|
+
[KEY_FORGER]: { userId: FORGER_ID, tokenId: 'tok-C' },
|
|
60
|
+
[KEY_INTERACTIVE]: { userId: 'user-I' },
|
|
61
|
+
};
|
|
28
62
|
|
|
29
|
-
|
|
30
|
-
const cleanups: Array<() => Promise<void>> = [];
|
|
63
|
+
const bearerOf = (req: express.Request) => (req.headers.authorization ?? '').replace(/^Bearer\s+/i, '');
|
|
31
64
|
|
|
32
|
-
// Stand-in auth: bind
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
65
|
+
// Stand-in auth: bind the user + connection-key id for the bearer, as the real
|
|
66
|
+
// middleware does — per request, from that request's own header.
|
|
67
|
+
const fakeAuth: RequestHandler = (req, res, next) => {
|
|
68
|
+
const user = USERS[bearerOf(req)];
|
|
69
|
+
if (!user) {
|
|
70
|
+
res.status(401).json({ error: 'unauthorized' });
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
req.userId = user.userId;
|
|
74
|
+
req.externalApiKeyId = user.tokenId;
|
|
38
75
|
next();
|
|
39
76
|
};
|
|
40
77
|
|
|
41
|
-
|
|
78
|
+
const passthrough: RequestHandler = (_req, _res, next) => next();
|
|
79
|
+
|
|
80
|
+
/** A tool registry: the echo `ask` + erroring `boom`, plus `extra` tools only this catalog has. */
|
|
81
|
+
function registryWith(extra: string[]): ToolRegistry {
|
|
42
82
|
const registry = new ToolRegistry();
|
|
43
|
-
// Echo `ask` (reflects the received sessionId so continuity is observable) +
|
|
44
|
-
// an erroring `boom`, registered as defs and hosted as endpoints.
|
|
45
83
|
registry.registerExternalTool(
|
|
46
84
|
toolDef({
|
|
47
85
|
name: 'ask',
|
|
@@ -57,96 +95,1054 @@ async function connectClient(): Promise<Client> {
|
|
|
57
95
|
registry.registerExternalTool(
|
|
58
96
|
toolDef({ name: 'boom', description: 'always errors', path: '/api/agent/tools/boom', inputs: { type: 'object', properties: {} } }),
|
|
59
97
|
);
|
|
98
|
+
for (const name of extra) {
|
|
99
|
+
registry.registerExternalTool(
|
|
100
|
+
toolDef({ name, description: name, path: `/api/agent/tools/${name}`, inputs: { type: 'object', properties: {} } }),
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
return registry;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
interface Platform {
|
|
107
|
+
baseUrl: string;
|
|
108
|
+
port: number;
|
|
109
|
+
service: McpService;
|
|
110
|
+
/** Loopback `ask` calls, with the bearer each one carried. */
|
|
111
|
+
askCalls: Array<{ bearer: string; prompt?: string; sessionId?: string }>;
|
|
112
|
+
stop(): Promise<void>;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
interface PlatformOptions {
|
|
116
|
+
/** Bind this port — how a "restarted" platform comes back at the same address. */
|
|
117
|
+
port?: number;
|
|
118
|
+
readAgentPreamble?: () => Promise<string | null>;
|
|
119
|
+
/** Extra manuals the catalog serves each caller (beyond the KB manual). */
|
|
120
|
+
manualsFor?: (bearer: string) => unknown[];
|
|
121
|
+
poolNow?: () => number;
|
|
122
|
+
/** The vault + manual catalog behind per-user credentials; absent ⇒ the proxy skips credential handling. */
|
|
123
|
+
secretsVault?: ConstructorParameters<typeof McpService>[1];
|
|
124
|
+
toolManuals?: ConstructorParameters<typeof McpService>[2];
|
|
125
|
+
/**
|
|
126
|
+
* Extra KB-manual tools in this platform's catalog. Empty for every suite
|
|
127
|
+
* that asserts on the catalog's exact contents; the first-call reproduction
|
|
128
|
+
* adds `start_session` so it can call the tool the ticket is about.
|
|
129
|
+
*/
|
|
130
|
+
extraTools?: string[];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const platforms: Platform[] = [];
|
|
134
|
+
const cleanups: Array<() => Promise<void>> = [];
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* One "platform process": its own express app, its own `McpService`, nothing
|
|
138
|
+
* shared with any earlier instance — which is exactly what a restart leaves.
|
|
139
|
+
*/
|
|
140
|
+
async function startPlatform(opts: PlatformOptions = {}): Promise<Platform> {
|
|
141
|
+
const askCalls: Platform['askCalls'] = [];
|
|
142
|
+
// Per-user catalogs: user B can see `b_only`, user A cannot.
|
|
143
|
+
const routesA = createManualRoutes(registryWith(opts.extraTools ?? []), passthrough);
|
|
144
|
+
const routesB = createManualRoutes(registryWith(['b_only']), passthrough);
|
|
60
145
|
|
|
61
146
|
const app = express();
|
|
62
147
|
app.use(express.json());
|
|
63
148
|
app.post('/api/agent/tools/ask', (req, res) => {
|
|
64
149
|
const b = (req.body ?? {}) as { prompt?: string; sessionId?: string };
|
|
150
|
+
askCalls.push({ bearer: bearerOf(req), prompt: b.prompt, sessionId: b.sessionId });
|
|
65
151
|
const incoming = typeof b.sessionId === 'string' ? b.sessionId : undefined;
|
|
66
152
|
res.json({ text: `echo: ${b.prompt} sid=${incoming ?? 'NONE'}`, sessionId: incoming ?? 'sess-1' });
|
|
67
153
|
});
|
|
68
154
|
app.post('/api/agent/tools/boom', (_req, res) => res.status(500).json({ error: 'kaboom' }));
|
|
155
|
+
// The real `start_session` behind the core `UuidSessionSink`: a fresh id and
|
|
156
|
+
// no I/O whatsoever. Mounted on every platform, but reachable only through a
|
|
157
|
+
// catalog that lists the tool (see `extraTools`).
|
|
158
|
+
app.post('/api/agent/tools/start_session', (_req, res) => res.json({ sessionId: randomUUID() }));
|
|
159
|
+
app.get('/api/agent/all-tools', (req, res) => {
|
|
160
|
+
res.json({
|
|
161
|
+
manuals: [
|
|
162
|
+
{
|
|
163
|
+
name: 'KNOWLEDGE_BASE',
|
|
164
|
+
call_template_type: 'http',
|
|
165
|
+
http_method: 'GET',
|
|
166
|
+
url: '${API_URL}/api/agent/utcp',
|
|
167
|
+
content_type: 'application/json',
|
|
168
|
+
headers: { Authorization: 'Bearer ${CONNECTION_KEY}' },
|
|
169
|
+
},
|
|
170
|
+
...(opts.manualsFor?.(bearerOf(req)) ?? []),
|
|
171
|
+
],
|
|
172
|
+
});
|
|
173
|
+
});
|
|
69
174
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
httpServer = await new Promise<HttpServer>((resolve) => {
|
|
73
|
-
const s = app.listen(0, () => resolve(s));
|
|
175
|
+
const httpServer = await new Promise<HttpServer>((resolve) => {
|
|
176
|
+
const s = app.listen(opts.port ?? 0, '127.0.0.1', () => resolve(s));
|
|
74
177
|
});
|
|
75
|
-
const port =
|
|
178
|
+
const { port } = httpServer.address() as AddressInfo;
|
|
76
179
|
const baseUrl = `http://127.0.0.1:${port}`;
|
|
77
180
|
|
|
78
|
-
const
|
|
181
|
+
const service = new McpService({
|
|
79
182
|
loopbackBaseUrl: baseUrl,
|
|
80
183
|
manualName: 'KNOWLEDGE_BASE',
|
|
81
184
|
spillStore: new SpillStore(join(tmpdir(), 'bevel-test-spills')),
|
|
82
185
|
publicFrontendUrl: 'http://localhost:5173',
|
|
83
|
-
|
|
186
|
+
readAgentPreamble: opts.readAgentPreamble,
|
|
187
|
+
downstreamPool: opts.poolNow ? { now: opts.poolNow } : undefined,
|
|
188
|
+
}, opts.secretsVault, opts.toolManuals);
|
|
84
189
|
const stub = {} as never;
|
|
85
|
-
|
|
86
|
-
//
|
|
87
|
-
app.use('/api',
|
|
88
|
-
app.use('/api', createManualRoutes(registry, fakeAuth));
|
|
190
|
+
app.use('/api', createMcpRoutes(service, stub, fakeAuth, fakeAuth, stub, stub, stub, ''));
|
|
191
|
+
// The loopback catalog is served per caller, from the bearer it carries.
|
|
192
|
+
app.use('/api', (req, res, next) => (bearerOf(req) === KEY_B ? routesB : routesA)(req, res, next));
|
|
89
193
|
|
|
194
|
+
let stopped = false;
|
|
195
|
+
const platform: Platform = {
|
|
196
|
+
baseUrl,
|
|
197
|
+
port,
|
|
198
|
+
service,
|
|
199
|
+
askCalls,
|
|
200
|
+
async stop() {
|
|
201
|
+
if (stopped) return;
|
|
202
|
+
stopped = true;
|
|
203
|
+
// A pooled downstream connection belongs to this process; a restart ends it.
|
|
204
|
+
service.onSecretsChanged(null);
|
|
205
|
+
httpServer.closeAllConnections();
|
|
206
|
+
await new Promise<void>((r) => httpServer.close(() => r()));
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
platforms.push(platform);
|
|
210
|
+
return platform;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Stop `platform` and bring a brand-new one up at the same address. */
|
|
214
|
+
async function restart(platform: Platform, opts: PlatformOptions = {}): Promise<Platform> {
|
|
215
|
+
await platform.stop();
|
|
216
|
+
// A real restart has downtime, during which a client's idle keep-alive
|
|
217
|
+
// sockets see the close. Without this pause the test would instead measure a
|
|
218
|
+
// same-tick race — a pooled socket reused before its FIN was processed —
|
|
219
|
+
// that no restart produces and that has nothing to do with sessions.
|
|
220
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
221
|
+
return startPlatform({ ...opts, port: platform.port });
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function connectSdkClient(baseUrl: string, bearer = KEY_A): Promise<{ client: Client; transport: StreamableHTTPClientTransport }> {
|
|
90
225
|
const transport = new StreamableHTTPClientTransport(new URL(`${baseUrl}/api/mcp`), {
|
|
91
|
-
requestInit: { headers: { Authorization: `Bearer ${
|
|
226
|
+
requestInit: { headers: { Authorization: `Bearer ${bearer}` } },
|
|
92
227
|
});
|
|
93
228
|
const client = new Client({ name: 'e2e-client', version: '0.0.0' }, { capabilities: {} });
|
|
94
229
|
await client.connect(transport);
|
|
95
230
|
cleanups.push(async () => {
|
|
96
231
|
await client.close();
|
|
97
232
|
});
|
|
98
|
-
return client;
|
|
233
|
+
return { client, transport };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** One raw JSON-RPC POST; the SSE or JSON body decoded into its messages. */
|
|
237
|
+
async function rpc(
|
|
238
|
+
baseUrl: string,
|
|
239
|
+
body: unknown,
|
|
240
|
+
headers: Record<string, string> = {},
|
|
241
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- raw JSON-RPC bodies, read by shape
|
|
242
|
+
): Promise<{ res: Response; messages: Array<{ id?: unknown; result?: any; error?: any }> }> {
|
|
243
|
+
const res = await fetch(`${baseUrl}/api/mcp`, {
|
|
244
|
+
method: 'POST',
|
|
245
|
+
headers: {
|
|
246
|
+
'Content-Type': 'application/json',
|
|
247
|
+
Accept: 'application/json, text/event-stream',
|
|
248
|
+
Authorization: `Bearer ${KEY_A}`,
|
|
249
|
+
...headers,
|
|
250
|
+
},
|
|
251
|
+
body: JSON.stringify(body),
|
|
252
|
+
});
|
|
253
|
+
const text = await res.text();
|
|
254
|
+
const messages = (res.headers.get('content-type') ?? '').includes('text/event-stream')
|
|
255
|
+
? text
|
|
256
|
+
.split('\n')
|
|
257
|
+
.filter((line) => line.startsWith('data:'))
|
|
258
|
+
.map((line) => JSON.parse(line.slice('data:'.length)))
|
|
259
|
+
: text
|
|
260
|
+
? [JSON.parse(text)]
|
|
261
|
+
: [];
|
|
262
|
+
return { res, messages };
|
|
99
263
|
}
|
|
100
264
|
|
|
265
|
+
const INITIALIZE = {
|
|
266
|
+
jsonrpc: '2.0',
|
|
267
|
+
id: 0,
|
|
268
|
+
method: 'initialize',
|
|
269
|
+
params: { protocolVersion: '2025-06-18', capabilities: {}, clientInfo: { name: 'raw', version: '0' } },
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const toolText = (res: { content?: unknown }) => (res.content as Array<{ text: string }>)[0].text;
|
|
273
|
+
const toolNames = async (client: Client) => (await client.listTools()).tools.map((t) => t.name).sort();
|
|
274
|
+
|
|
101
275
|
afterEach(async () => {
|
|
102
276
|
for (const c of cleanups.splice(0)) await c().catch(() => {});
|
|
103
|
-
|
|
104
|
-
|
|
277
|
+
for (const p of platforms.splice(0)) await p.stop().catch(() => {});
|
|
278
|
+
vi.restoreAllMocks();
|
|
105
279
|
});
|
|
106
280
|
|
|
107
281
|
describe('MCP over real Streamable-HTTP transport', () => {
|
|
108
282
|
it('initializes, discovers, and lists the proxied tools with their schemas verbatim', async () => {
|
|
109
|
-
const
|
|
283
|
+
const { baseUrl } = await startPlatform();
|
|
284
|
+
const { client } = await connectSdkClient(baseUrl);
|
|
110
285
|
const { tools } = await client.listTools();
|
|
111
286
|
const byName = Object.fromEntries(tools.map((t) => [t.name, t]));
|
|
112
287
|
|
|
113
288
|
expect(Object.keys(byName).sort()).toEqual(['ask', 'boom', 'call_tool_chain', 'list_tools', 'tools_info']);
|
|
114
289
|
// The {body} envelope the manual carries reaches the client untouched.
|
|
115
|
-
|
|
290
|
+
const askSchema = byName.ask.inputSchema as { properties: { body?: { properties?: Record<string, unknown> } } };
|
|
291
|
+
expect(askSchema.properties.body?.properties?.prompt).toBeDefined();
|
|
116
292
|
});
|
|
117
293
|
|
|
118
294
|
it('calls a tool end-to-end and returns the result text', async () => {
|
|
119
|
-
const
|
|
295
|
+
const { baseUrl } = await startPlatform();
|
|
296
|
+
const { client } = await connectSdkClient(baseUrl);
|
|
120
297
|
const res = await client.callTool({ name: 'ask', arguments: { body: { prompt: 'hello' } } });
|
|
121
|
-
|
|
122
|
-
// returned sessionId both survive for a client without proxy auto-inject.
|
|
123
|
-
const payload = JSON.parse((res.content as Array<{ text: string }>)[0].text);
|
|
298
|
+
const payload = JSON.parse(toolText(res));
|
|
124
299
|
expect(payload.text).toBe('echo: hello sid=NONE');
|
|
125
300
|
expect(payload.sessionId).toBe('sess-1');
|
|
126
301
|
expect(res.isError).toBeFalsy();
|
|
127
302
|
});
|
|
128
303
|
|
|
129
|
-
it('
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
304
|
+
it('surfaces a downstream tool failure as an MCP isError result', async () => {
|
|
305
|
+
const { baseUrl } = await startPlatform();
|
|
306
|
+
const { client } = await connectSdkClient(baseUrl);
|
|
307
|
+
const res = await client.callTool({ name: 'boom', arguments: {} });
|
|
308
|
+
expect(res.isError).toBe(true);
|
|
309
|
+
expect(toolText(res)).toMatch(/kaboom/i);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('call_tool_chain runs against the request-built client', async () => {
|
|
313
|
+
const { baseUrl } = await startPlatform();
|
|
314
|
+
const { client } = await connectSdkClient(baseUrl);
|
|
315
|
+
const res = await client.callTool({
|
|
316
|
+
name: 'call_tool_chain',
|
|
317
|
+
arguments: { code: "return KNOWLEDGE_BASE.ask({ body: { prompt: 'chain' } });" },
|
|
318
|
+
});
|
|
319
|
+
expect(JSON.parse(toolText(res)).result).toMatchObject({ text: 'echo: chain sid=NONE' });
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
describe('stateless: no session, every request on its own', () => {
|
|
324
|
+
it('initialize succeeds without ever issuing an Mcp-Session-Id', async () => {
|
|
325
|
+
const { baseUrl } = await startPlatform();
|
|
326
|
+
const { res, messages } = await rpc(baseUrl, INITIALIZE);
|
|
327
|
+
expect(res.status).toBe(200);
|
|
328
|
+
expect(res.headers.get('mcp-session-id')).toBeNull();
|
|
329
|
+
expect(messages[0].result.serverInfo.name).toBe('bevel-mcp');
|
|
330
|
+
// And the official client, following the server's lead, holds no session id.
|
|
331
|
+
const { transport } = await connectSdkClient(baseUrl);
|
|
332
|
+
expect(transport.sessionId).toBeUndefined();
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it('serves tools/list and tools/call with no prior initialize at all', async () => {
|
|
336
|
+
const { baseUrl } = await startPlatform();
|
|
337
|
+
const list = await rpc(baseUrl, { jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} });
|
|
338
|
+
expect(list.res.status).toBe(200);
|
|
339
|
+
expect(list.res.headers.get('mcp-session-id')).toBeNull();
|
|
340
|
+
expect(list.messages[0].result.tools.map((t: { name: string }) => t.name)).toContain('ask');
|
|
341
|
+
|
|
342
|
+
const call = await rpc(baseUrl, {
|
|
343
|
+
jsonrpc: '2.0',
|
|
344
|
+
id: 2,
|
|
345
|
+
method: 'tools/call',
|
|
346
|
+
params: { name: 'ask', arguments: { body: { prompt: 'cold' } } },
|
|
347
|
+
});
|
|
348
|
+
expect(call.res.status).toBe(200);
|
|
349
|
+
expect(JSON.parse(call.messages[0].result.content[0].text).text).toBe('echo: cold sid=NONE');
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it('never answers "Session not found": a stale session id from before is simply ignored', async () => {
|
|
353
|
+
const { baseUrl } = await startPlatform();
|
|
354
|
+
const { res, messages } = await rpc(
|
|
355
|
+
baseUrl,
|
|
356
|
+
{ jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} },
|
|
357
|
+
{ 'mcp-session-id': 'sess-from-before-the-restart' },
|
|
358
|
+
);
|
|
359
|
+
expect(res.status).toBe(200);
|
|
360
|
+
expect(messages[0].error).toBeUndefined();
|
|
361
|
+
expect(messages[0].result.tools.length).toBeGreaterThan(0);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it.each(['GET', 'DELETE'])('%s /mcp is retired with 405', async (method) => {
|
|
365
|
+
const { baseUrl } = await startPlatform();
|
|
366
|
+
const res = await fetch(`${baseUrl}/api/mcp`, { method, headers: { Authorization: `Bearer ${KEY_A}` } });
|
|
367
|
+
expect(res.status).toBe(405);
|
|
368
|
+
expect(res.headers.get('allow')).toBe('POST');
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
describe('a platform restart is invisible to a connected client', () => {
|
|
373
|
+
it('official SDK client: the next tools/list and tools/call after a restart just work', async () => {
|
|
374
|
+
const first = await startPlatform();
|
|
375
|
+
const { client } = await connectSdkClient(first.baseUrl);
|
|
376
|
+
expect(await toolNames(client)).toContain('ask');
|
|
377
|
+
|
|
378
|
+
// A brand-new process at the same address: new app, new McpService, nothing carried over.
|
|
379
|
+
const second = await restart(first);
|
|
380
|
+
|
|
381
|
+
// Same client object, no re-initialize, no reconnect.
|
|
382
|
+
expect(await toolNames(client)).toContain('ask');
|
|
383
|
+
const res = await client.callTool({ name: 'ask', arguments: { body: { prompt: 'after' } } });
|
|
384
|
+
expect(res.isError).toBeFalsy();
|
|
385
|
+
expect(JSON.parse(toolText(res)).text).toBe('echo: after sid=NONE');
|
|
386
|
+
// Served by the new process, which never saw an initialize from this client.
|
|
387
|
+
expect(second.askCalls).toHaveLength(1);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it("`@utcp/mcp` (hexis-mcp's client): a registered manual keeps calling across a restart", async () => {
|
|
391
|
+
const first = await startPlatform();
|
|
392
|
+
const client = await CodeModeUtcpClient.create(process.cwd(), new UtcpClientConfigSerializer().validateDict({}));
|
|
393
|
+
cleanups.push(() => client.close());
|
|
394
|
+
const registered = await client.registerManual(
|
|
395
|
+
new CallTemplateSerializer().validateDict({
|
|
396
|
+
name: 'hexis',
|
|
397
|
+
call_template_type: 'mcp',
|
|
398
|
+
config: {
|
|
399
|
+
mcpServers: {
|
|
400
|
+
platform: {
|
|
401
|
+
transport: 'http',
|
|
402
|
+
url: `${first.baseUrl}/api/mcp`,
|
|
403
|
+
headers: { Authorization: `Bearer ${KEY_A}` },
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
},
|
|
407
|
+
}),
|
|
408
|
+
);
|
|
409
|
+
expect(registered.success).toBe(true);
|
|
410
|
+
const names = (await client.getTools()).map((t) => t.name);
|
|
411
|
+
expect(names).toContain('hexis.platform.ask');
|
|
412
|
+
|
|
413
|
+
const before = await client.callTool('hexis.platform.ask', { body: { prompt: 'one' } });
|
|
414
|
+
expect(JSON.stringify(before)).toContain('echo: one');
|
|
415
|
+
|
|
416
|
+
const second = await restart(first);
|
|
417
|
+
const after = await client.callTool('hexis.platform.ask', { body: { prompt: 'two' } });
|
|
418
|
+
expect(JSON.stringify(after)).toContain('echo: two');
|
|
419
|
+
expect(second.askCalls.map((c) => c.prompt)).toEqual(['two']);
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
describe('per-request identity: catalog, metering and continuity', () => {
|
|
424
|
+
it("two users' catalogs never bleed into each other, even interleaved on one process", async () => {
|
|
425
|
+
const { baseUrl } = await startPlatform();
|
|
426
|
+
const { client: a } = await connectSdkClient(baseUrl, KEY_A);
|
|
427
|
+
const { client: b } = await connectSdkClient(baseUrl, KEY_B);
|
|
428
|
+
const [listA1, listB1] = await Promise.all([toolNames(a), toolNames(b)]);
|
|
429
|
+
const [listB2, listA2] = await Promise.all([toolNames(b), toolNames(a)]);
|
|
430
|
+
expect(listA1).not.toContain('b_only');
|
|
431
|
+
expect(listA2).not.toContain('b_only');
|
|
432
|
+
expect(listB1).toContain('b_only');
|
|
433
|
+
expect(listB2).toContain('b_only');
|
|
434
|
+
// A cannot call what it was not given, however recently B listed it.
|
|
435
|
+
const denied = await a.callTool({ name: 'b_only', arguments: {} });
|
|
436
|
+
expect(denied.isError).toBe(true);
|
|
437
|
+
expect(toolText(denied)).toMatch(/Unknown tool/);
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
it('answers the retired merge_change_request with who merges now, not "Unknown tool"', async () => {
|
|
441
|
+
const { baseUrl } = await startPlatform();
|
|
442
|
+
const { client } = await connectSdkClient(baseUrl, KEY_A);
|
|
443
|
+
expect(await toolNames(client)).not.toContain('merge_change_request');
|
|
444
|
+
const res = await client.callTool({ name: 'merge_change_request', arguments: { body: { number: 4 } } });
|
|
445
|
+
expect(res.isError).toBe(true);
|
|
446
|
+
expect(toolText(res)).toMatch(/a change request is merged by a person in the app/);
|
|
447
|
+
// And from inside a code-mode chain, where the runner reports the failure
|
|
448
|
+
// in its logs instead of throwing.
|
|
449
|
+
const chained = await client.callTool({
|
|
450
|
+
name: 'call_tool_chain',
|
|
451
|
+
arguments: { code: 'return KNOWLEDGE_BASE.merge_change_request({ body: { number: 4 } })' },
|
|
452
|
+
});
|
|
453
|
+
expect(chained.isError).toBe(true);
|
|
454
|
+
expect(toolText(chained)).toMatch(/a change request is merged by a person in the app/);
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
it('metering: every loopback call carries THAT request\'s connection key', async () => {
|
|
458
|
+
const platform = await startPlatform();
|
|
459
|
+
const { client: a } = await connectSdkClient(platform.baseUrl, KEY_A);
|
|
460
|
+
const { client: b } = await connectSdkClient(platform.baseUrl, KEY_B);
|
|
461
|
+
await a.callTool({ name: 'ask', arguments: { body: { prompt: 'from-a' } } });
|
|
462
|
+
await b.callTool({ name: 'ask', arguments: { body: { prompt: 'from-b' } } });
|
|
463
|
+
await a.callTool({ name: 'ask', arguments: { body: { prompt: 'from-a-again' } } });
|
|
464
|
+
expect(platform.askCalls.map((c) => [c.prompt, c.bearer])).toEqual([
|
|
465
|
+
['from-a', KEY_A],
|
|
466
|
+
['from-b', KEY_B],
|
|
467
|
+
['from-a-again', KEY_A],
|
|
468
|
+
]);
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
it("`ask` continuity rides the tool's own sessionId — across a restart too", async () => {
|
|
472
|
+
const first = await startPlatform();
|
|
473
|
+
const { client } = await connectSdkClient(first.baseUrl);
|
|
133
474
|
const res1 = await client.callTool({ name: 'ask', arguments: { body: { prompt: 'one' } } });
|
|
134
|
-
const
|
|
135
|
-
expect(
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
475
|
+
const { sessionId } = JSON.parse(toolText(res1));
|
|
476
|
+
expect(sessionId).toBe('sess-1');
|
|
477
|
+
|
|
478
|
+
const second = await restart(first);
|
|
479
|
+
const res2 = await client.callTool({ name: 'ask', arguments: { body: { prompt: 'two', sessionId } } });
|
|
480
|
+
expect(JSON.parse(toolText(res2)).text).toBe('echo: two sid=sess-1');
|
|
481
|
+
expect(second.askCalls[0].sessionId).toBe('sess-1');
|
|
482
|
+
});
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
describe('agent instructions over the real transport', () => {
|
|
486
|
+
it('the initialize result carries the header and the preamble body inline', async () => {
|
|
487
|
+
const { baseUrl } = await startPlatform({
|
|
488
|
+
readAgentPreamble: async () => 'Acme builds solar farms.\n\n<!-- private -->Look in Projects/ first.',
|
|
142
489
|
});
|
|
143
|
-
|
|
490
|
+
const { client } = await connectSdkClient(baseUrl);
|
|
491
|
+
const instructions = client.getInstructions();
|
|
492
|
+
expect(instructions).toBe(`${PLATFORM_HEADER}\n\nAcme builds solar farms.\n\nLook in Projects/ first.`);
|
|
493
|
+
expect(instructions).not.toContain('private');
|
|
144
494
|
});
|
|
145
495
|
|
|
146
|
-
it('
|
|
147
|
-
|
|
148
|
-
const
|
|
496
|
+
it('a later initialize on the SAME process after the file changed carries the new text — read per request', async () => {
|
|
497
|
+
let content = 'Version one.';
|
|
498
|
+
const { baseUrl } = await startPlatform({ readAgentPreamble: async () => content });
|
|
499
|
+
const { client: first } = await connectSdkClient(baseUrl);
|
|
500
|
+
expect(first.getInstructions()).toContain('Version one.');
|
|
501
|
+
|
|
502
|
+
content = 'Version two.';
|
|
503
|
+
const { client: second } = await connectSdkClient(baseUrl);
|
|
504
|
+
expect(second.getInstructions()).toContain('Version two.');
|
|
505
|
+
expect(second.getInstructions()).not.toContain('Version one.');
|
|
506
|
+
// The first client keeps the instructions it was initialised with.
|
|
507
|
+
expect(first.getInstructions()).toContain('Version one.');
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
describe('proxied third-party MCP servers: the downstream pool', () => {
|
|
512
|
+
let downstream: FakeDownstreamMcpServer | undefined;
|
|
513
|
+
afterEach(async () => {
|
|
514
|
+
await downstream?.stop();
|
|
515
|
+
downstream = undefined;
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
/** A catalog that gives every caller one `mcp.json`-style manual at the fake server. */
|
|
519
|
+
const withDownstream = (server: FakeDownstreamMcpServer) => () => [
|
|
520
|
+
{ name: 'notion', call_template_type: 'mcp', config: { mcpServers: { srv: { transport: 'http', url: server.url } } } },
|
|
521
|
+
];
|
|
522
|
+
|
|
523
|
+
const echoName = async (client: Client) => {
|
|
524
|
+
const name = (await client.listTools()).tools.map((t) => t.name).find((n) => n.endsWith('echo'));
|
|
525
|
+
expect(name).toBeDefined();
|
|
526
|
+
return name!;
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
it('lists and calls a proxied tool, reusing ONE downstream connection across requests', async () => {
|
|
530
|
+
downstream = await startFakeDownstreamMcpServer();
|
|
531
|
+
const { baseUrl } = await startPlatform({ manualsFor: withDownstream(downstream) });
|
|
532
|
+
const { client } = await connectSdkClient(baseUrl);
|
|
533
|
+
const name = await echoName(client);
|
|
534
|
+
for (const text of ['one', 'two', 'three']) {
|
|
535
|
+
const res = await client.callTool({ name, arguments: { text } });
|
|
536
|
+
expect(res.isError).toBeFalsy();
|
|
537
|
+
expect(toolText(res)).toContain(text);
|
|
538
|
+
}
|
|
539
|
+
// Four requests (list + three calls), one handshake.
|
|
540
|
+
expect(downstream.initializations()).toBe(1);
|
|
541
|
+
expect(downstream.executions()).toBe(3);
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
it('single-flight: concurrent first calls to one server produce one connection', async () => {
|
|
545
|
+
downstream = await startFakeDownstreamMcpServer();
|
|
546
|
+
const { baseUrl } = await startPlatform({ manualsFor: withDownstream(downstream) });
|
|
547
|
+
const results = await Promise.all(
|
|
548
|
+
Array.from({ length: 5 }, (_, i) =>
|
|
549
|
+
rpc(baseUrl, { jsonrpc: '2.0', id: i, method: 'tools/call', params: { name: 'notion_srv_echo', arguments: { text: `c${i}` } } }),
|
|
550
|
+
),
|
|
551
|
+
);
|
|
552
|
+
for (const { res } of results) expect(res.status).toBe(200);
|
|
553
|
+
expect(downstream.initializations()).toBe(1);
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
it('pools per (user, server): another user gets a connection of their own, even for an identical definition', async () => {
|
|
557
|
+
downstream = await startFakeDownstreamMcpServer();
|
|
558
|
+
// The same server definition for both users, with no per-user credential
|
|
559
|
+
// in it — the case where nothing but the pool boundary keeps them apart.
|
|
560
|
+
const { baseUrl } = await startPlatform({ manualsFor: withDownstream(downstream) });
|
|
561
|
+
const { client: a } = await connectSdkClient(baseUrl, KEY_A);
|
|
562
|
+
const { client: b } = await connectSdkClient(baseUrl, KEY_B);
|
|
563
|
+
await a.callTool({ name: await echoName(a), arguments: { text: 'a' } });
|
|
564
|
+
await b.callTool({ name: await echoName(b), arguments: { text: 'b' } });
|
|
565
|
+
// Each pooled client owns its own `mcp` protocol instance, so each user's
|
|
566
|
+
// entry dials its own session: a second initialization, not a shared one.
|
|
567
|
+
expect(downstream.initializations()).toBe(2);
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
it('recovery pass-through: a downstream restart is healed on the next call', async () => {
|
|
571
|
+
downstream = await startFakeDownstreamMcpServer();
|
|
572
|
+
vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
573
|
+
const { baseUrl } = await startPlatform({ manualsFor: withDownstream(downstream) });
|
|
574
|
+
const { client } = await connectSdkClient(baseUrl);
|
|
575
|
+
const name = await echoName(client);
|
|
576
|
+
await client.callTool({ name, arguments: { text: 'before' } });
|
|
577
|
+
|
|
578
|
+
await downstream.restart(); // the downstream forgets our session
|
|
579
|
+
|
|
580
|
+
const res = await client.callTool({ name, arguments: { text: 'after' } });
|
|
581
|
+
expect(res.isError).toBeFalsy();
|
|
582
|
+
expect(toolText(res)).toContain('after');
|
|
583
|
+
expect(downstream.initializations()).toBe(2);
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
it('an mcp.json edited after a failure is tried on the next request, not after the failure memo expires', async () => {
|
|
587
|
+
downstream = await startFakeDownstreamMcpServer();
|
|
588
|
+
vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
589
|
+
vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
590
|
+
let url = 'http://127.0.0.1:1/mcp'; // nothing listens: registration fails and is memoized
|
|
591
|
+
const { baseUrl } = await startPlatform({
|
|
592
|
+
manualsFor: () => [
|
|
593
|
+
{ name: 'notion', call_template_type: 'mcp', config: { mcpServers: { srv: { transport: 'http', url } } } },
|
|
594
|
+
],
|
|
595
|
+
});
|
|
596
|
+
const { client } = await connectSdkClient(baseUrl);
|
|
597
|
+
expect((await client.listTools()).tools.some((t) => t.name.endsWith('echo'))).toBe(false);
|
|
598
|
+
|
|
599
|
+
url = downstream.url; // the user corrects the definition
|
|
600
|
+
await echoName(client);
|
|
601
|
+
expect(downstream.initializations()).toBe(1);
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
it('idle eviction: an entry unused past the TTL is closed and re-dialed lazily', async () => {
|
|
605
|
+
downstream = await startFakeDownstreamMcpServer();
|
|
606
|
+
let now = 1_000_000;
|
|
607
|
+
const { baseUrl } = await startPlatform({ manualsFor: withDownstream(downstream), poolNow: () => now });
|
|
608
|
+
const { client } = await connectSdkClient(baseUrl);
|
|
609
|
+
const name = await echoName(client);
|
|
610
|
+
await client.callTool({ name, arguments: { text: 'x' } });
|
|
611
|
+
expect(downstream.initializations()).toBe(1);
|
|
612
|
+
|
|
613
|
+
now += 4 * 60 * 60 * 1000 + 1; // past the default 4h idle TTL
|
|
614
|
+
const res = await client.callTool({ name, arguments: { text: 'y' } });
|
|
615
|
+
expect(res.isError).toBeFalsy();
|
|
616
|
+
expect(downstream.initializations()).toBe(2);
|
|
617
|
+
});
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
describe('proxied third-party MCP servers: a rejected token gets one refresh and a retry', () => {
|
|
621
|
+
let downstream: FakeDownstreamMcpServer | undefined;
|
|
622
|
+
let restoreLogger: ILogger | undefined;
|
|
623
|
+
afterEach(async () => {
|
|
624
|
+
await downstream?.stop();
|
|
625
|
+
downstream = undefined;
|
|
626
|
+
registerBevelSecretsVariableLoader(null as unknown as ISecretsVaultService);
|
|
627
|
+
if (restoreLogger) setLogger(restoreLogger);
|
|
628
|
+
restoreLogger = undefined;
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
const KEY = 'notion_ACCESS_TOKEN';
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* One user's stored OAuth sign-in for the `notion` manual, as the proxy sees
|
|
635
|
+
* it through the vault contract: `resolve` hands out the current token, and
|
|
636
|
+
* `forceRefresh` does what the test scripts — rotate it, wipe it (a dead
|
|
637
|
+
* grant), or leave it (a provider outage).
|
|
638
|
+
*/
|
|
639
|
+
function fakeSignIn(initialToken: string, onRefresh: (s: { token: string | null }) => ForcedRefreshOutcome) {
|
|
640
|
+
const state = { token: initialToken as string | null, refreshes: 0 };
|
|
641
|
+
const vault = {
|
|
642
|
+
resolve: async (_userId: string, key: string) => (key === KEY ? state.token : null),
|
|
643
|
+
statusFor: async (_userId: string, keys: string[]) =>
|
|
644
|
+
keys.map((key) => ({ key, adminConfigured: false, userConfigured: true, userAuthorized: state.token !== null })),
|
|
645
|
+
forceRefresh: async (_userId: string, key: string) => {
|
|
646
|
+
expect(key).toBe(KEY);
|
|
647
|
+
state.refreshes += 1;
|
|
648
|
+
return onRefresh(state);
|
|
649
|
+
},
|
|
650
|
+
} as unknown as ISecretsVaultService;
|
|
651
|
+
registerBevelSecretsVariableLoader(vault);
|
|
652
|
+
const toolManuals = {
|
|
653
|
+
userScopedKeysForManual: async (manual: string) =>
|
|
654
|
+
manual === 'notion' ? [{ key: KEY, name: 'ACCESS_TOKEN', label: 'Notion sign-in', oauth: true }] : [],
|
|
655
|
+
} as unknown as IToolManualService;
|
|
656
|
+
return { state, vault, toolManuals };
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const notionManual = (server: FakeDownstreamMcpServer) => () => [
|
|
660
|
+
{
|
|
661
|
+
name: 'notion',
|
|
662
|
+
call_template_type: 'mcp',
|
|
663
|
+
config: {
|
|
664
|
+
mcpServers: { srv: { transport: 'http', url: server.url, headers: { Authorization: 'Bearer ${ACCESS_TOKEN}' } } },
|
|
665
|
+
},
|
|
666
|
+
},
|
|
667
|
+
];
|
|
668
|
+
|
|
669
|
+
/** Every log line, flattened to text, so a test can assert what was — and was not — written. */
|
|
670
|
+
function captureLogs(): string[] {
|
|
671
|
+
const lines: string[] = [];
|
|
672
|
+
const capture = (bindings: Record<string, unknown>): ILogger => {
|
|
673
|
+
const write = (message: string, fields?: Record<string, unknown>) =>
|
|
674
|
+
lines.push(JSON.stringify({ ...bindings, message, fields }, (_k, v) => (v instanceof Error ? v.message : v)));
|
|
675
|
+
return { debug: write, info: write, warn: write, error: write, child: (more) => capture({ ...bindings, ...more }) };
|
|
676
|
+
};
|
|
677
|
+
restoreLogger = setLogger(capture({}));
|
|
678
|
+
return lines;
|
|
679
|
+
}
|
|
680
|
+
const refreshLines = (lines: string[]) => lines.filter((l) => l.includes('downstream token refresh'));
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* Does this text carry anything `printable` escapes? Everything it escapes,
|
|
684
|
+
* not just the C0 controls: DEL and the C1 block (U+009B is a one-byte CSI —
|
|
685
|
+
* an ANSI sequence on its own) and the line separators U+2028/U+2029 all
|
|
686
|
+
* survive `captureLogs`' JSON round trip raw, so a C0-only check would let a
|
|
687
|
+
* regression that dropped their escaping through while the line still breaks
|
|
688
|
+
* or colours an operator's terminal.
|
|
689
|
+
*/
|
|
690
|
+
const unprintable = (text: string) =>
|
|
691
|
+
[...text].some((c) => {
|
|
692
|
+
const code = c.codePointAt(0)!;
|
|
693
|
+
return code < 0x20 || (code >= 0x7f && code <= 0x9f) || code === 0x2028 || code === 0x2029;
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
const quiet = () => {
|
|
697
|
+
vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
698
|
+
vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
699
|
+
vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
700
|
+
};
|
|
701
|
+
|
|
702
|
+
it('reproduction: a deliberately invalid stored token is refreshed at the handshake and the tool works', async () => {
|
|
703
|
+
quiet();
|
|
704
|
+
let valid = 'fresh-token';
|
|
705
|
+
downstream = await startFakeDownstreamMcpServer({ acceptsToken: (t) => t === valid });
|
|
706
|
+
const { state, vault, toolManuals } = fakeSignIn('deliberately-invalid-token', (s) => {
|
|
707
|
+
s.token = valid;
|
|
708
|
+
return 'refreshed';
|
|
709
|
+
});
|
|
710
|
+
const logs = captureLogs();
|
|
711
|
+
const { baseUrl } = await startPlatform({ manualsFor: notionManual(downstream), secretsVault: vault, toolManuals });
|
|
712
|
+
const { client } = await connectSdkClient(baseUrl);
|
|
713
|
+
|
|
714
|
+
const res = await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'hello' } });
|
|
715
|
+
|
|
716
|
+
expect(res.isError).toBeFalsy();
|
|
717
|
+
expect(toolText(res)).toContain('hello');
|
|
718
|
+
expect(state.refreshes).toBe(1);
|
|
719
|
+
expect(downstream.rejections()).toBeGreaterThan(0);
|
|
720
|
+
expect(refreshLines(logs)).toHaveLength(1);
|
|
721
|
+
expect(refreshLines(logs)[0]).toContain('manual=\\"notion\\" user=\\"user-A\\" outcome=refreshed');
|
|
722
|
+
valid = 'unused';
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
it('401 → refresh → retry: a token revoked mid-life is refreshed and the call succeeds; the caller sees only that', async () => {
|
|
726
|
+
quiet();
|
|
727
|
+
let valid = 'token-1';
|
|
728
|
+
downstream = await startFakeDownstreamMcpServer({ acceptsToken: (t) => t === valid });
|
|
729
|
+
const { state, vault, toolManuals } = fakeSignIn('token-1', (s) => {
|
|
730
|
+
s.token = 'token-2';
|
|
731
|
+
return 'refreshed';
|
|
732
|
+
});
|
|
733
|
+
const { baseUrl } = await startPlatform({ manualsFor: notionManual(downstream), secretsVault: vault, toolManuals });
|
|
734
|
+
const { client } = await connectSdkClient(baseUrl);
|
|
735
|
+
expect(toolText(await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'one' } }))).toContain('one');
|
|
736
|
+
expect(downstream.initializations()).toBe(1);
|
|
737
|
+
|
|
738
|
+
valid = 'token-2'; // the provider revokes token-1 before its stated expiry
|
|
739
|
+
|
|
740
|
+
const res = await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'two' } });
|
|
741
|
+
expect(res.isError).toBeFalsy();
|
|
742
|
+
expect(toolText(res)).toContain('two');
|
|
743
|
+
expect(state.refreshes).toBe(1);
|
|
744
|
+
expect(downstream.executions()).toBe(2); // the refused attempt ran nothing
|
|
745
|
+
expect(downstream.initializations()).toBe(2); // the retry dialed with the fresh token
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
it('refresh rejected → token wiped → status not connected → the answer says to re-authorize on /connect', async () => {
|
|
749
|
+
quiet();
|
|
750
|
+
let valid = 'token-1';
|
|
751
|
+
downstream = await startFakeDownstreamMcpServer({ acceptsToken: (t) => t === valid });
|
|
752
|
+
const { state, vault, toolManuals } = fakeSignIn('token-1', (s) => {
|
|
753
|
+
s.token = null; // the vault wipes a dead grant
|
|
754
|
+
return 'rejected';
|
|
755
|
+
});
|
|
756
|
+
const logs = captureLogs();
|
|
757
|
+
const { baseUrl } = await startPlatform({ manualsFor: notionManual(downstream), secretsVault: vault, toolManuals });
|
|
758
|
+
const { client } = await connectSdkClient(baseUrl);
|
|
759
|
+
await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'one' } });
|
|
760
|
+
|
|
761
|
+
valid = 'nothing-valid-any-more';
|
|
762
|
+
|
|
763
|
+
const res = await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'two' } });
|
|
764
|
+
expect(res.isError).toBe(true);
|
|
765
|
+
expect(toolText(res)).toContain('Your sign-in for "notion" was rejected and has been disconnected');
|
|
766
|
+
expect(toolText(res)).toContain('Re-authorize it on http://localhost:5173/connect');
|
|
767
|
+
expect(state.refreshes).toBe(1);
|
|
768
|
+
expect((await vault.statusFor('user-A', [KEY]))[0]?.userAuthorized).toBe(false);
|
|
769
|
+
expect(refreshLines(logs)[0]).toContain('manual=\\"notion\\" user=\\"user-A\\" outcome=rejected');
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
it('transient refresh failure: the original error comes back, the token is kept, and a later call tries again', async () => {
|
|
773
|
+
quiet();
|
|
774
|
+
let now = 1_000_000;
|
|
775
|
+
let valid = 'token-1';
|
|
776
|
+
downstream = await startFakeDownstreamMcpServer({ acceptsToken: (t) => t === valid });
|
|
777
|
+
let providerUp = false;
|
|
778
|
+
const { state, vault, toolManuals } = fakeSignIn('token-1', (s) => {
|
|
779
|
+
if (!providerUp) return 'transient';
|
|
780
|
+
s.token = 'token-2';
|
|
781
|
+
return 'refreshed';
|
|
782
|
+
});
|
|
783
|
+
const logs = captureLogs();
|
|
784
|
+
const { baseUrl } = await startPlatform({
|
|
785
|
+
manualsFor: notionManual(downstream),
|
|
786
|
+
secretsVault: vault,
|
|
787
|
+
toolManuals,
|
|
788
|
+
poolNow: () => now,
|
|
789
|
+
});
|
|
790
|
+
const { client } = await connectSdkClient(baseUrl);
|
|
791
|
+
await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'one' } });
|
|
792
|
+
|
|
793
|
+
valid = 'token-2';
|
|
794
|
+
|
|
795
|
+
const failed = await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'two' } });
|
|
796
|
+
expect(failed.isError).toBe(true);
|
|
797
|
+
expect(toolText(failed)).toContain('invalid_token'); // the downstream's own refusal, unchanged
|
|
798
|
+
expect(toolText(failed)).not.toContain('Re-authorize');
|
|
799
|
+
expect(state.token).toBe('token-1'); // kept
|
|
800
|
+
expect(refreshLines(logs)[0]).toContain('outcome=transient');
|
|
801
|
+
|
|
802
|
+
providerUp = true;
|
|
803
|
+
now += 60_000;
|
|
804
|
+
const later = await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'three' } });
|
|
805
|
+
expect(later.isError).toBeFalsy();
|
|
806
|
+
expect(state.refreshes).toBe(2);
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
it('once per minute: a grant the downstream keeps refusing cannot loop the provider', async () => {
|
|
810
|
+
quiet();
|
|
811
|
+
let now = 1_000_000;
|
|
812
|
+
let valid = 'token-1';
|
|
813
|
+
downstream = await startFakeDownstreamMcpServer({ acceptsToken: (t) => t === valid });
|
|
814
|
+
// The provider happily refreshes; the downstream refuses whatever comes back.
|
|
815
|
+
const { state, vault, toolManuals } = fakeSignIn('token-1', (s) => {
|
|
816
|
+
s.token = `token-${s.token}-next`;
|
|
817
|
+
return 'refreshed';
|
|
818
|
+
});
|
|
819
|
+
const { baseUrl } = await startPlatform({
|
|
820
|
+
manualsFor: notionManual(downstream),
|
|
821
|
+
secretsVault: vault,
|
|
822
|
+
toolManuals,
|
|
823
|
+
poolNow: () => now,
|
|
824
|
+
});
|
|
825
|
+
const { client } = await connectSdkClient(baseUrl);
|
|
826
|
+
await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'one' } });
|
|
827
|
+
|
|
828
|
+
valid = 'never';
|
|
829
|
+
for (let i = 0; i < 4; i++) {
|
|
830
|
+
const res = await client.callTool({ name: 'notion_srv_echo', arguments: { text: `x${i}` } });
|
|
831
|
+
expect(res.isError).toBe(true);
|
|
832
|
+
now += 10_000;
|
|
833
|
+
}
|
|
834
|
+
// Four refused calls inside the minute, one refresh. (The window reopening
|
|
835
|
+
// is the guard's own test; here the manual-failure memo additionally keeps
|
|
836
|
+
// a manual that failed to register from being re-dialed for a while.)
|
|
837
|
+
expect(state.refreshes).toBe(1);
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
it('logs one line per refresh attempt — manual, user, outcome — and never the token', async () => {
|
|
841
|
+
quiet();
|
|
842
|
+
let valid = 'secret-token-OLD';
|
|
843
|
+
downstream = await startFakeDownstreamMcpServer({ acceptsToken: (t) => t === valid });
|
|
844
|
+
const { vault, toolManuals } = fakeSignIn('secret-token-OLD', (s) => {
|
|
845
|
+
s.token = 'secret-token-NEW';
|
|
846
|
+
return 'refreshed';
|
|
847
|
+
});
|
|
848
|
+
const logs = captureLogs();
|
|
849
|
+
const { baseUrl } = await startPlatform({ manualsFor: notionManual(downstream), secretsVault: vault, toolManuals });
|
|
850
|
+
const { client } = await connectSdkClient(baseUrl);
|
|
851
|
+
await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'one' } });
|
|
852
|
+
valid = 'secret-token-NEW';
|
|
853
|
+
await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'two' } });
|
|
854
|
+
|
|
855
|
+
expect(refreshLines(logs)).toEqual([
|
|
856
|
+
expect.stringContaining('manual=\\"notion\\" user=\\"user-A\\" outcome=refreshed'),
|
|
857
|
+
]);
|
|
858
|
+
for (const line of logs) {
|
|
859
|
+
expect(line).not.toContain('secret-token-OLD');
|
|
860
|
+
expect(line).not.toContain('secret-token-NEW');
|
|
861
|
+
}
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
it('the refresh line cannot be forged: caller-controlled text is escaped, so one attempt is one line', async () => {
|
|
865
|
+
quiet();
|
|
866
|
+
let valid = 'token-1';
|
|
867
|
+
downstream = await startFakeDownstreamMcpServer({ acceptsToken: (t) => t === valid });
|
|
868
|
+
const { vault, toolManuals } = fakeSignIn('token-1', (s) => {
|
|
869
|
+
s.token = 'token-2';
|
|
870
|
+
return 'refreshed';
|
|
871
|
+
});
|
|
872
|
+
const logs = captureLogs();
|
|
873
|
+
const { baseUrl } = await startPlatform({ manualsFor: notionManual(downstream), secretsVault: vault, toolManuals });
|
|
874
|
+
// This caller's user id is a newline plus a plausible-looking refresh line.
|
|
875
|
+
const { client } = await connectSdkClient(baseUrl, KEY_FORGER);
|
|
876
|
+
await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'one' } });
|
|
877
|
+
valid = 'token-2';
|
|
878
|
+
await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'two' } });
|
|
879
|
+
|
|
880
|
+
// One attempt, one EVENT. The forged text is carried inside the line it
|
|
881
|
+
// belongs to, escaped, rather than starting a line of its own — here and
|
|
882
|
+
// in every other line that names the caller.
|
|
883
|
+
const messages = logs.map((entry) => JSON.parse(entry).message as string);
|
|
884
|
+
const events = messages.filter((m) => m.startsWith('downstream token refresh:'));
|
|
885
|
+
expect(events).toEqual([
|
|
886
|
+
`downstream token refresh: manual="notion" user=${printable(FORGER_ID)} outcome=refreshed`,
|
|
887
|
+
]);
|
|
888
|
+
for (const message of messages) expect(unprintable(message)).toBe(false);
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
it('a transient refresh at the HANDSHAKE is not remembered as a dead manual: the next call tries again', async () => {
|
|
892
|
+
quiet();
|
|
893
|
+
let now = 1_000_000;
|
|
894
|
+
// Rejects the stored token outright, so the very first connection — the
|
|
895
|
+
// handshake, before any tool call — is what gets the 401.
|
|
896
|
+
downstream = await startFakeDownstreamMcpServer({ acceptsToken: (t) => t === 'token-2' });
|
|
897
|
+
let providerUp = false;
|
|
898
|
+
const { state, vault, toolManuals } = fakeSignIn('token-1', (s) => {
|
|
899
|
+
if (!providerUp) return 'transient';
|
|
900
|
+
s.token = 'token-2';
|
|
901
|
+
return 'refreshed';
|
|
902
|
+
});
|
|
903
|
+
const { baseUrl } = await startPlatform({
|
|
904
|
+
manualsFor: notionManual(downstream),
|
|
905
|
+
secretsVault: vault,
|
|
906
|
+
toolManuals,
|
|
907
|
+
poolNow: () => now,
|
|
908
|
+
});
|
|
909
|
+
const { client } = await connectSdkClient(baseUrl);
|
|
910
|
+
|
|
911
|
+
// The provider is down: the manual cannot register, and the catalog says so.
|
|
912
|
+
expect((await client.listTools()).tools.some((t) => t.name === 'notion_srv_echo')).toBe(false);
|
|
913
|
+
expect(state.refreshes).toBe(1);
|
|
914
|
+
|
|
915
|
+
// The provider comes back inside the manual-failure memo's five minutes.
|
|
916
|
+
// The refresh policy's window is one minute, and it is the one that governs:
|
|
917
|
+
// a transient credential failure is never written into that memo.
|
|
918
|
+
providerUp = true;
|
|
919
|
+
now += 60_000;
|
|
920
|
+
expect((await client.listTools()).tools.some((t) => t.name === 'notion_srv_echo')).toBe(true);
|
|
921
|
+
expect(state.refreshes).toBe(2);
|
|
922
|
+
expect(toolText(await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'hi' } }))).toContain('hi');
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
it('a manual catalog that fails mid-refresh is reported on one line, escaped, and refreshes nothing', async () => {
|
|
926
|
+
quiet();
|
|
927
|
+
// The catalog breaks only BETWEEN the pre-call secrets check and the
|
|
928
|
+
// refresh: the fake downstream flips it the moment it answers 401, which
|
|
929
|
+
// is exactly the window `refreshDownstreamToken` reads it in.
|
|
930
|
+
let broken = false;
|
|
931
|
+
let valid = 'token-1';
|
|
932
|
+
downstream = await startFakeDownstreamMcpServer({
|
|
933
|
+
acceptsToken: (t) => {
|
|
934
|
+
const ok = t === valid;
|
|
935
|
+
if (!ok) broken = true;
|
|
936
|
+
return ok;
|
|
937
|
+
},
|
|
938
|
+
});
|
|
939
|
+
const { state, vault } = fakeSignIn('token-1', () => 'refreshed');
|
|
940
|
+
const boom = new Error('catalog read failed\u009b31m\nerror: forged by the manual name\u2028tail');
|
|
941
|
+
const toolManuals = {
|
|
942
|
+
userScopedKeysForManual: async (manual: string) => {
|
|
943
|
+
if (broken) throw boom;
|
|
944
|
+
return manual === 'notion' ? [{ key: KEY, name: 'ACCESS_TOKEN', label: 'Notion sign-in', oauth: true }] : [];
|
|
945
|
+
},
|
|
946
|
+
} as unknown as IToolManualService;
|
|
947
|
+
const logs = captureLogs();
|
|
948
|
+
const { baseUrl } = await startPlatform({ manualsFor: notionManual(downstream), secretsVault: vault, toolManuals });
|
|
949
|
+
const { client } = await connectSdkClient(baseUrl);
|
|
950
|
+
await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'one' } });
|
|
951
|
+
|
|
952
|
+
valid = 'token-2'; // the provider revokes the stored token
|
|
953
|
+
|
|
954
|
+
// The token is now refused, so the refresh path runs — and finds the
|
|
955
|
+
// catalog broken.
|
|
956
|
+
const res = await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'two' } });
|
|
957
|
+
|
|
958
|
+
expect(res.isError).toBe(true);
|
|
959
|
+
expect(state.refreshes).toBe(0); // nothing to refresh against: the token stands
|
|
960
|
+
const messages = logs.map((entry) => JSON.parse(entry).message as string);
|
|
961
|
+
const warnings = messages.filter((m) => m.startsWith('downstream token refresh: could not read'));
|
|
962
|
+
expect(warnings).toEqual([
|
|
963
|
+
`downstream token refresh: could not read the variables of manual=${printable('notion')}: ${printable(boom.message)}`,
|
|
964
|
+
]);
|
|
965
|
+
// The error's own text cannot start a line either — it travels escaped,
|
|
966
|
+
// not as a raw Error the logger would render verbatim.
|
|
967
|
+
for (const message of messages) expect(unprintable(message)).toBe(false);
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
/** The connect link every not-signed-in answer carries. */
|
|
971
|
+
const CONNECT = 'http://localhost:5173/connect';
|
|
972
|
+
|
|
973
|
+
it('after the wipe the manual stays on the surface: an interactive caller still lists the tool, and a call answers with the sign-in link', async () => {
|
|
974
|
+
quiet();
|
|
975
|
+
let valid = 'token-1';
|
|
976
|
+
downstream = await startFakeDownstreamMcpServer({ acceptsToken: (t) => t === valid });
|
|
977
|
+
const { state, vault, toolManuals } = fakeSignIn('token-1', (s) => {
|
|
978
|
+
s.token = null; // the vault wipes the dead grant
|
|
979
|
+
return 'rejected';
|
|
980
|
+
});
|
|
981
|
+
const { baseUrl } = await startPlatform({ manualsFor: notionManual(downstream), secretsVault: vault, toolManuals });
|
|
982
|
+
const { client } = await connectSdkClient(baseUrl, KEY_INTERACTIVE);
|
|
983
|
+
expect(toolText(await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'one' } }))).toContain('one');
|
|
984
|
+
|
|
985
|
+
valid = 'nothing-valid-any-more';
|
|
986
|
+
const wiped = await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'two' } });
|
|
987
|
+
expect(toolText(wiped)).toContain('has been disconnected');
|
|
988
|
+
expect(state.token).toBeNull();
|
|
989
|
+
|
|
990
|
+
// The sign-in is gone; the manual is not. Its tools stay listed from what
|
|
991
|
+
// the downstream last advertised, and calling one says what to do —
|
|
992
|
+
// rather than the tool vanishing and the call answering "Unknown tool".
|
|
993
|
+
expect((await client.listTools()).tools.some((t) => t.name === 'notion_srv_echo')).toBe(true);
|
|
994
|
+
const res = await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'three' } });
|
|
995
|
+
expect(res.isError).toBe(true);
|
|
996
|
+
expect(toolText(res)).toContain('needs credentials you haven\'t set up yet');
|
|
997
|
+
expect(toolText(res)).toContain(CONNECT);
|
|
998
|
+
expect(toolText(res)).not.toContain('Unknown tool');
|
|
999
|
+
// One refresh in total. The wipe's own attempt is the one; whatever the
|
|
1000
|
+
// listing and the third call ran into afterwards (a token that no longer
|
|
1001
|
+
// substitutes, or a rejection inside the same minute) stayed inside the
|
|
1002
|
+
// once-a-minute window and did not reach the provider again.
|
|
1003
|
+
expect(state.refreshes).toBe(1);
|
|
1004
|
+
});
|
|
1005
|
+
|
|
1006
|
+
it('after the wipe a connection-key caller does not list the tool, but a call to it still gets the sign-in link', async () => {
|
|
1007
|
+
quiet();
|
|
1008
|
+
let valid = 'token-1';
|
|
1009
|
+
downstream = await startFakeDownstreamMcpServer({ acceptsToken: (t) => t === valid });
|
|
1010
|
+
const { vault, toolManuals } = fakeSignIn('token-1', (s) => {
|
|
1011
|
+
s.token = null;
|
|
1012
|
+
return 'rejected';
|
|
1013
|
+
});
|
|
1014
|
+
const { baseUrl } = await startPlatform({ manualsFor: notionManual(downstream), secretsVault: vault, toolManuals });
|
|
1015
|
+
const { client } = await connectSdkClient(baseUrl);
|
|
1016
|
+
await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'one' } });
|
|
1017
|
+
valid = 'nothing-valid-any-more';
|
|
1018
|
+
await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'two' } });
|
|
1019
|
+
|
|
1020
|
+
// Autonomous callers are shown only what is ready to run…
|
|
1021
|
+
expect((await client.listTools()).tools.some((t) => t.name === 'notion_srv_echo')).toBe(false);
|
|
1022
|
+
// …but one holding the name from before is told why, and where to go.
|
|
1023
|
+
const res = await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'three' } });
|
|
1024
|
+
expect(res.isError).toBe(true);
|
|
1025
|
+
expect(toolText(res)).toContain(CONNECT);
|
|
1026
|
+
expect(toolText(res)).not.toContain('Unknown tool');
|
|
1027
|
+
});
|
|
1028
|
+
|
|
1029
|
+
it('a manual nobody has connected to since the process started still answers a call with the sign-in link', async () => {
|
|
1030
|
+
quiet();
|
|
1031
|
+
// Fresh process (a deploy restarted it), and this caller's sign-in is
|
|
1032
|
+
// already gone: no connection was ever made, so no tool definitions exist
|
|
1033
|
+
// to put on the surface — the call must still not answer "Unknown tool".
|
|
1034
|
+
downstream = await startFakeDownstreamMcpServer({ acceptsToken: () => false });
|
|
1035
|
+
const { state, vault, toolManuals } = fakeSignIn('token-1', () => 'rejected');
|
|
1036
|
+
state.token = null;
|
|
1037
|
+
const { baseUrl } = await startPlatform({ manualsFor: notionManual(downstream), secretsVault: vault, toolManuals });
|
|
1038
|
+
const { client } = await connectSdkClient(baseUrl);
|
|
1039
|
+
|
|
1040
|
+
expect((await client.listTools()).tools.some((t) => t.name === 'notion_srv_echo')).toBe(false);
|
|
1041
|
+
const res = await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'one' } });
|
|
1042
|
+
expect(res.isError).toBe(true);
|
|
1043
|
+
expect(toolText(res)).toContain('needs credentials you haven\'t set up yet');
|
|
1044
|
+
expect(toolText(res)).toContain(CONNECT);
|
|
1045
|
+
expect(toolText(res)).not.toContain('Unknown tool');
|
|
1046
|
+
// A name that belongs to no manual of the caller's is still unknown.
|
|
1047
|
+
expect(toolText(await client.callTool({ name: 'linear_srv_echo', arguments: {} }))).toContain('Unknown tool');
|
|
1048
|
+
});
|
|
1049
|
+
|
|
1050
|
+
it('a leftover sign-in row does not stand in for a plain key the manual now declares: the call asks for setup', async () => {
|
|
1051
|
+
quiet();
|
|
1052
|
+
// The `.tool` was edited from an OAuth sign-in to a static key; the user's
|
|
1053
|
+
// old OAuth row is still there and its token is even accepted downstream.
|
|
1054
|
+
downstream = await startFakeDownstreamMcpServer({ acceptsToken: (t) => t === 'old-oauth-token' });
|
|
1055
|
+
const { vault } = fakeSignIn('old-oauth-token', () => 'refreshed');
|
|
1056
|
+
const withKind = {
|
|
1057
|
+
...vault,
|
|
1058
|
+
statusFor: async (_userId: string, keys: string[]) =>
|
|
1059
|
+
keys.map((key) => ({ key, adminConfigured: false, userConfigured: true, userAuthorized: true, userKind: 'oauth' })),
|
|
1060
|
+
} as unknown as ISecretsVaultService;
|
|
1061
|
+
registerBevelSecretsVariableLoader(withKind);
|
|
1062
|
+
const toolManuals = {
|
|
1063
|
+
userScopedKeysForManual: async (manual: string) =>
|
|
1064
|
+
manual === 'notion' ? [{ key: KEY, name: 'ACCESS_TOKEN', label: 'Notion key', oauth: false }] : [],
|
|
1065
|
+
} as unknown as IToolManualService;
|
|
1066
|
+
const { baseUrl } = await startPlatform({ manualsFor: notionManual(downstream), secretsVault: withKind, toolManuals });
|
|
1067
|
+
const { client } = await connectSdkClient(baseUrl, KEY_INTERACTIVE);
|
|
1068
|
+
|
|
1069
|
+
const res = await client.callTool({ name: 'notion_srv_echo', arguments: { text: 'one' } });
|
|
149
1070
|
expect(res.isError).toBe(true);
|
|
150
|
-
expect((res.
|
|
1071
|
+
expect(toolText(res)).toContain('needs credentials you haven\'t set up yet');
|
|
1072
|
+
expect(toolText(res)).toContain(CONNECT);
|
|
1073
|
+
expect(downstream.executions()).toBe(0);
|
|
1074
|
+
});
|
|
1075
|
+
});
|
|
1076
|
+
|
|
1077
|
+
// A measurement, not a regression gate: it asserts nothing about latency, so it
|
|
1078
|
+
// runs only on request (`MCP_PERF=1`) instead of spending ~40 loopback calls on
|
|
1079
|
+
// every CI run.
|
|
1080
|
+
describe.runIf(process.env.MCP_PERF === '1')('per-request overhead (reported, not asserted)', () => {
|
|
1081
|
+
it('measures tools/list and tools/call latency against a warm process', async () => {
|
|
1082
|
+
const { baseUrl } = await startPlatform();
|
|
1083
|
+
const log = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
1084
|
+
const { client } = await connectSdkClient(baseUrl);
|
|
1085
|
+
await client.listTools(); // warm-up
|
|
1086
|
+
|
|
1087
|
+
const time = async (fn: () => Promise<unknown>, runs = 20) => {
|
|
1088
|
+
const samples: number[] = [];
|
|
1089
|
+
for (let i = 0; i < runs; i++) {
|
|
1090
|
+
const t0 = performance.now();
|
|
1091
|
+
await fn();
|
|
1092
|
+
samples.push(performance.now() - t0);
|
|
1093
|
+
}
|
|
1094
|
+
samples.sort((x, y) => x - y);
|
|
1095
|
+
// Nearest-rank percentile: the smallest sample with at least p% of samples at or below it.
|
|
1096
|
+
const pct = (p: number) => samples[Math.min(runs - 1, Math.max(0, Math.ceil(runs * p) - 1))]!;
|
|
1097
|
+
return { p50: pct(0.5), p95: pct(0.95) };
|
|
1098
|
+
};
|
|
1099
|
+
const list = await time(() => client.listTools());
|
|
1100
|
+
const call = await time(() => client.callTool({ name: 'ask', arguments: { body: { prompt: 'perf' } } }));
|
|
1101
|
+
log.mockRestore();
|
|
1102
|
+
console.log(
|
|
1103
|
+
`[mcp perf] per-request (in-process loopback, test catalog): ` +
|
|
1104
|
+
`tools/list p50=${list.p50.toFixed(1)}ms p95=${list.p95.toFixed(1)}ms; ` +
|
|
1105
|
+
`tools/call p50=${call.p50.toFixed(1)}ms p95=${call.p95.toFixed(1)}ms`,
|
|
1106
|
+
);
|
|
1107
|
+
expect(list.p50).toBeGreaterThan(0);
|
|
151
1108
|
});
|
|
152
1109
|
});
|
|
1110
|
+
|
|
1111
|
+
/**
|
|
1112
|
+
* The ticket's reproduction, run against the whole platform: real transport,
|
|
1113
|
+
* real `McpService` proxy, real catalog discovery, real tool route.
|
|
1114
|
+
*
|
|
1115
|
+
* The report was one first `start_session` failing on a fresh connection with
|
|
1116
|
+
* a generic error, and an immediate retry working. Fifty fresh connections,
|
|
1117
|
+
* one first call each, all at once — an agent tester opens them in bursts, and
|
|
1118
|
+
* a first-call failure that only shows under contention would never appear one
|
|
1119
|
+
* connection at a time. Every connection pays for its own `initialize` and its
|
|
1120
|
+
* own catalog discovery here, which is the expensive part of a first call and
|
|
1121
|
+
* the part a reused connection never repeats.
|
|
1122
|
+
*
|
|
1123
|
+
* The suite asserts no timing: what it pins is that the burst produces fifty
|
|
1124
|
+
* ids and no failure, so a regression that makes the first call flaky under
|
|
1125
|
+
* load fails here instead of in an agent's transcript.
|
|
1126
|
+
*/
|
|
1127
|
+
describe('the first call on a fresh connection, fifty at once', () => {
|
|
1128
|
+
it('serves every one of fifty fresh connections its own session id', async () => {
|
|
1129
|
+
const { baseUrl } = await startPlatform({ extraTools: ['start_session'] });
|
|
1130
|
+
|
|
1131
|
+
// `timeoutMs` well under the vitest budget on purpose. The probe charges it
|
|
1132
|
+
// per phase, so the default 30s would let one hung attempt burn ~60s and
|
|
1133
|
+
// vitest would abort the test before `probeFirstCall` resolved — in exactly
|
|
1134
|
+
// the flaky-first-call scenario this test exists to catch, the failure list
|
|
1135
|
+
// and the report below would never be printed, and the run would show a
|
|
1136
|
+
// bare timeout instead of which attempt died where.
|
|
1137
|
+
const report = await probeFirstCall({ baseUrl, bearer: KEY_A, connections: 50, timeoutMs: 20_000 });
|
|
1138
|
+
|
|
1139
|
+
// Printed as well as asserted: this block is what the ticket's log records,
|
|
1140
|
+
// and a run whose numbers were never shown proves nothing to a reader.
|
|
1141
|
+
console.log(`[first-call probe]\n${formatProbeReport(report)}`);
|
|
1142
|
+
// Not `toHaveLength(0)`: on a failure the assertion should show WHICH
|
|
1143
|
+
// attempt died, at which stage, with the platform's own error text.
|
|
1144
|
+
expect(report.failures).toEqual([]);
|
|
1145
|
+
expect(report.ok).toBe(50);
|
|
1146
|
+
expect(report.distinctSessionIds).toBe(50);
|
|
1147
|
+
}, 120_000);
|
|
1148
|
+
});
|