@bevel-software/platform-core-backend 0.26.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent-guide/access-control.md +17 -0
- package/agent-guide/conventions.md +8 -0
- package/agent-guide/html-views.md +83 -0
- package/agent-guide/new-knowledge-base.md +24 -0
- package/agent-guide/tool-manuals.md +10 -0
- package/dist/assets.d.ts +19 -0
- package/dist/assets.d.ts.map +1 -1
- package/dist/assets.js +26 -2
- package/dist/assets.js.map +1 -1
- package/dist/core/catalog-revision.d.ts +19 -1
- package/dist/core/catalog-revision.d.ts.map +1 -1
- package/dist/core/catalog-revision.js +14 -3
- package/dist/core/catalog-revision.js.map +1 -1
- package/dist/core/create-core-server.d.ts +5 -0
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +54 -3
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +26 -0
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +100 -8
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/core/lifecycle.d.ts +16 -1
- package/dist/core/lifecycle.d.ts.map +1 -1
- package/dist/core/lifecycle.js +18 -1
- package/dist/core/lifecycle.js.map +1 -1
- package/dist/core-config.d.ts +21 -0
- package/dist/core-config.d.ts.map +1 -1
- package/dist/core-config.js +22 -1
- package/dist/core-config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +8 -0
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +1 -0
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +11 -0
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/directory-sync-bot.js +1 -1
- package/dist/modules/access/directory-sync-bot.js.map +1 -1
- package/dist/modules/agent-guide/agent-guide.d.ts +6 -0
- package/dist/modules/agent-guide/agent-guide.d.ts.map +1 -1
- package/dist/modules/agent-guide/agent-guide.js +8 -0
- package/dist/modules/agent-guide/agent-guide.js.map +1 -1
- package/dist/modules/agent-guide/index.d.ts +1 -1
- package/dist/modules/agent-guide/index.d.ts.map +1 -1
- package/dist/modules/agent-guide/index.js +1 -1
- package/dist/modules/agent-guide/index.js.map +1 -1
- package/dist/modules/agent-instructions/compose.d.ts +5 -0
- package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
- package/dist/modules/agent-instructions/compose.js +19 -2
- package/dist/modules/agent-instructions/compose.js.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.js +19 -19
- package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
- package/dist/modules/audit/agent-audit.service.d.ts +21 -2
- package/dist/modules/audit/agent-audit.service.d.ts.map +1 -1
- package/dist/modules/audit/agent-audit.service.js +43 -2
- package/dist/modules/audit/agent-audit.service.js.map +1 -1
- package/dist/modules/audit/agent-connection.routes.d.ts +34 -0
- package/dist/modules/audit/agent-connection.routes.d.ts.map +1 -0
- package/dist/modules/audit/agent-connection.routes.js +38 -0
- package/dist/modules/audit/agent-connection.routes.js.map +1 -0
- package/dist/modules/audit/audit.contract.d.ts +24 -0
- package/dist/modules/audit/audit.contract.d.ts.map +1 -1
- package/dist/modules/audit/audit.contract.js.map +1 -1
- package/dist/modules/audit/index.d.ts +1 -0
- package/dist/modules/audit/index.d.ts.map +1 -1
- package/dist/modules/audit/index.js +1 -0
- package/dist/modules/audit/index.js.map +1 -1
- package/dist/modules/auth/account.routes.d.ts.map +1 -1
- package/dist/modules/auth/account.routes.js +10 -3
- package/dist/modules/auth/account.routes.js.map +1 -1
- package/dist/modules/auth/auth.middleware.d.ts +7 -0
- package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
- package/dist/modules/auth/auth.middleware.js +1 -1
- package/dist/modules/auth/auth.middleware.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 +11 -3
- package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
- package/dist/modules/database/core-schema.d.ts +96 -0
- package/dist/modules/database/core-schema.d.ts.map +1 -1
- package/dist/modules/database/core-schema.js +32 -3
- package/dist/modules/database/core-schema.js.map +1 -1
- package/dist/modules/embed/account-link.service.d.ts +42 -0
- package/dist/modules/embed/account-link.service.d.ts.map +1 -0
- package/dist/modules/embed/account-link.service.js +50 -0
- package/dist/modules/embed/account-link.service.js.map +1 -0
- package/dist/modules/embed/embed-link.d.ts +58 -0
- package/dist/modules/embed/embed-link.d.ts.map +1 -0
- package/dist/modules/embed/embed-link.js +160 -0
- package/dist/modules/embed/embed-link.js.map +1 -0
- package/dist/modules/embed/embed.errors.d.ts +30 -0
- package/dist/modules/embed/embed.errors.d.ts.map +1 -0
- package/dist/modules/embed/embed.errors.js +42 -0
- package/dist/modules/embed/embed.errors.js.map +1 -0
- package/dist/modules/embed/embed.interface.d.ts +204 -0
- package/dist/modules/embed/embed.interface.d.ts.map +1 -0
- package/dist/modules/embed/embed.interface.js +2 -0
- package/dist/modules/embed/embed.interface.js.map +1 -0
- package/dist/modules/embed/embed.routes.d.ts +59 -0
- package/dist/modules/embed/embed.routes.d.ts.map +1 -0
- package/dist/modules/embed/embed.routes.js +343 -0
- package/dist/modules/embed/embed.routes.js.map +1 -0
- package/dist/modules/embed/embed.service.d.ts +169 -0
- package/dist/modules/embed/embed.service.d.ts.map +1 -0
- package/dist/modules/embed/embed.service.js +627 -0
- package/dist/modules/embed/embed.service.js.map +1 -0
- package/dist/modules/embed/embed.tools.d.ts +51 -0
- package/dist/modules/embed/embed.tools.d.ts.map +1 -0
- package/dist/modules/embed/embed.tools.js +170 -0
- package/dist/modules/embed/embed.tools.js.map +1 -0
- package/dist/modules/embed/mcp-app.d.ts +64 -0
- package/dist/modules/embed/mcp-app.d.ts.map +1 -0
- package/dist/modules/embed/mcp-app.js +147 -0
- package/dist/modules/embed/mcp-app.js.map +1 -0
- package/dist/modules/embed/mcp-app.routes.d.ts +21 -0
- package/dist/modules/embed/mcp-app.routes.d.ts.map +1 -0
- package/dist/modules/embed/mcp-app.routes.js +36 -0
- package/dist/modules/embed/mcp-app.routes.js.map +1 -0
- package/dist/modules/github-app/github-app.connection.d.ts +15 -0
- package/dist/modules/github-app/github-app.connection.d.ts.map +1 -1
- package/dist/modules/github-app/github-app.connection.js +34 -3
- package/dist/modules/github-app/github-app.connection.js.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.d.ts +65 -0
- package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.js +300 -0
- package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
- package/dist/modules/kb-sync/kb-sync.interface.d.ts +5 -0
- package/dist/modules/kb-sync/kb-sync.interface.d.ts.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 +6 -0
- package/dist/modules/kb-sync/kb-sync.service.js.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.js +2 -1
- package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
- package/dist/modules/mcp/mcp.service.d.ts +28 -1
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +64 -8
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +24 -0
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js +32 -4
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
- package/dist/modules/onboarding/onboarding.contract.d.ts +80 -0
- package/dist/modules/onboarding/onboarding.contract.d.ts.map +1 -0
- package/dist/modules/onboarding/onboarding.contract.js +7 -0
- package/dist/modules/onboarding/onboarding.contract.js.map +1 -0
- package/dist/modules/onboarding/onboarding.routes.d.ts +18 -0
- package/dist/modules/onboarding/onboarding.routes.d.ts.map +1 -0
- package/dist/modules/onboarding/onboarding.routes.js +81 -0
- package/dist/modules/onboarding/onboarding.routes.js.map +1 -0
- package/dist/modules/onboarding/starter-pack.service.d.ts +213 -0
- package/dist/modules/onboarding/starter-pack.service.d.ts.map +1 -0
- package/dist/modules/onboarding/starter-pack.service.js +492 -0
- package/dist/modules/onboarding/starter-pack.service.js.map +1 -0
- package/dist/modules/onboarding/starter-packs.d.ts +78 -0
- package/dist/modules/onboarding/starter-packs.d.ts.map +1 -0
- package/dist/modules/onboarding/starter-packs.js +156 -0
- package/dist/modules/onboarding/starter-packs.js.map +1 -0
- package/dist/modules/plugins/index.d.ts +1 -1
- package/dist/modules/plugins/index.d.ts.map +1 -1
- package/dist/modules/plugins/index.js +1 -1
- package/dist/modules/plugins/index.js.map +1 -1
- package/dist/modules/plugins/plugin-provision.service.d.ts +19 -0
- package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-provision.service.js +18 -1
- package/dist/modules/plugins/plugin-provision.service.js.map +1 -1
- package/dist/modules/plugins/plugins.routes.d.ts +0 -36
- package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.js +31 -0
- package/dist/modules/plugins/plugins.routes.js.map +1 -1
- package/dist/modules/settings/deployment-settings.service.d.ts +34 -0
- package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
- package/dist/modules/settings/deployment-settings.service.js +134 -1
- package/dist/modules/settings/deployment-settings.service.js.map +1 -1
- package/dist/modules/settings/repository-source.d.ts +1 -0
- package/dist/modules/settings/repository-source.d.ts.map +1 -1
- package/dist/modules/settings/repository-source.js +3 -3
- package/dist/modules/settings/repository-source.js.map +1 -1
- package/dist/modules/skills/skills.tools.d.ts.map +1 -1
- package/dist/modules/skills/skills.tools.js +50 -32
- package/dist/modules/skills/skills.tools.js.map +1 -1
- package/dist/modules/tool-auth/connection-key-rejection.d.ts +10 -1
- package/dist/modules/tool-auth/connection-key-rejection.d.ts.map +1 -1
- package/dist/modules/tool-auth/connection-key-rejection.js +11 -3
- package/dist/modules/tool-auth/connection-key-rejection.js.map +1 -1
- package/dist/modules/tool-auth/external-api-key.interface.d.ts +8 -0
- package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.d.ts +24 -2
- package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.js +59 -2
- package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.js +9 -4
- package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
- package/dist/modules/tool-helpers/index.d.ts +28 -1
- package/dist/modules/tool-helpers/index.d.ts.map +1 -1
- package/dist/modules/tool-helpers/index.js +28 -1
- package/dist/modules/tool-helpers/index.js.map +1 -1
- package/dist/modules/tool-helpers/route-argument-check.d.ts +10 -0
- package/dist/modules/tool-helpers/route-argument-check.d.ts.map +1 -0
- package/dist/modules/tool-helpers/route-argument-check.js +132 -0
- package/dist/modules/tool-helpers/route-argument-check.js.map +1 -0
- package/dist/modules/tool-helpers/route-tool-schemas.d.ts +89 -0
- package/dist/modules/tool-helpers/route-tool-schemas.d.ts.map +1 -0
- package/dist/modules/tool-helpers/route-tool-schemas.js +113 -0
- package/dist/modules/tool-helpers/route-tool-schemas.js.map +1 -0
- package/dist/modules/tool-helpers/tool-def.d.ts +71 -0
- package/dist/modules/tool-helpers/tool-def.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-def.js +70 -6
- package/dist/modules/tool-helpers/tool-def.js.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.d.ts +46 -6
- package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.js +157 -8
- package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.d.ts +9 -0
- package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.js +14 -5
- package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
- package/dist/modules/tool-registry/description-length.d.ts +6 -2
- package/dist/modules/tool-registry/description-length.d.ts.map +1 -1
- package/dist/modules/tool-registry/description-length.js +27 -5
- package/dist/modules/tool-registry/description-length.js.map +1 -1
- package/dist/modules/tool-registry/guide-first.d.ts +4 -0
- package/dist/modules/tool-registry/guide-first.d.ts.map +1 -1
- package/dist/modules/tool-registry/guide-first.js +9 -1
- package/dist/modules/tool-registry/guide-first.js.map +1 -1
- package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts +53 -0
- package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/change-request-read.tools.js +97 -68
- package/dist/modules/workflow/agent-tools/change-request-read.tools.js.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts +8 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.js +124 -37
- package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
- package/dist/modules/workflow/file-lock.service.d.ts +29 -5
- package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
- package/dist/modules/workflow/file-lock.service.js +62 -5
- package/dist/modules/workflow/file-lock.service.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +72 -1
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +172 -36
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/git/node-git-runner.d.ts +15 -0
- package/dist/modules/workflow/git/node-git-runner.d.ts.map +1 -1
- package/dist/modules/workflow/git/node-git-runner.js +78 -6
- package/dist/modules/workflow/git/node-git-runner.js.map +1 -1
- package/dist/modules/workflow/pending-commits.service.d.ts +15 -0
- package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.service.js +25 -1
- package/dist/modules/workflow/pending-commits.service.js.map +1 -1
- package/dist/modules/workflow/pending-commits.worker.d.ts +6 -1
- package/dist/modules/workflow/pending-commits.worker.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.worker.js +23 -6
- package/dist/modules/workflow/pending-commits.worker.js.map +1 -1
- package/dist/modules/workflow/recovery-bot.js +1 -1
- package/dist/modules/workflow/recovery-bot.js.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.js +2 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
- package/dist/modules/workflow/sanitize-error.d.ts +16 -0
- package/dist/modules/workflow/sanitize-error.d.ts.map +1 -1
- package/dist/modules/workflow/sanitize-error.js +45 -0
- package/dist/modules/workflow/sanitize-error.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +183 -15
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +805 -106
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/agent-download.builder.d.ts +105 -0
- package/dist/modules/workspace/agent-download.builder.d.ts.map +1 -0
- package/dist/modules/workspace/agent-download.builder.js +238 -0
- package/dist/modules/workspace/agent-download.builder.js.map +1 -0
- package/dist/modules/workspace/agent-download.routes.d.ts +77 -0
- package/dist/modules/workspace/agent-download.routes.d.ts.map +1 -0
- package/dist/modules/workspace/agent-download.routes.js +156 -0
- package/dist/modules/workspace/agent-download.routes.js.map +1 -0
- package/dist/modules/workspace/agent-download.store.d.ts +225 -0
- package/dist/modules/workspace/agent-download.store.d.ts.map +1 -0
- package/dist/modules/workspace/agent-download.store.js +423 -0
- package/dist/modules/workspace/agent-download.store.js.map +1 -0
- package/dist/modules/workspace/agent-upload.store.d.ts +10 -1
- package/dist/modules/workspace/agent-upload.store.d.ts.map +1 -1
- package/dist/modules/workspace/agent-upload.store.js +16 -6
- package/dist/modules/workspace/agent-upload.store.js.map +1 -1
- package/dist/modules/workspace/file-readers/raw-mime.d.ts +6 -0
- package/dist/modules/workspace/file-readers/raw-mime.d.ts.map +1 -0
- package/dist/modules/workspace/file-readers/raw-mime.js +38 -0
- package/dist/modules/workspace/file-readers/raw-mime.js.map +1 -0
- package/dist/modules/workspace/file-readers/text-reader.d.ts +5 -5
- package/dist/modules/workspace/file-readers/text-reader.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/text-reader.js +5 -8
- package/dist/modules/workspace/file-readers/text-reader.js.map +1 -1
- package/dist/modules/workspace/first-run.d.ts +111 -0
- package/dist/modules/workspace/first-run.d.ts.map +1 -0
- package/dist/modules/workspace/first-run.js +181 -0
- package/dist/modules/workspace/first-run.js.map +1 -0
- package/dist/modules/workspace/move-links.d.ts +92 -0
- package/dist/modules/workspace/move-links.d.ts.map +1 -0
- package/dist/modules/workspace/move-links.js +173 -0
- package/dist/modules/workspace/move-links.js.map +1 -0
- package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.js +3 -13
- package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
- package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.routes.js +63 -22
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +75 -2
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +197 -24
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts +59 -1
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +443 -65
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/modules/write-access/write-access.d.ts.map +1 -1
- package/dist/modules/write-access/write-access.js +9 -0
- package/dist/modules/write-access/write-access.js.map +1 -1
- package/dist/shared/domain-errors.d.ts +73 -1
- package/dist/shared/domain-errors.d.ts.map +1 -1
- package/dist/shared/domain-errors.js +86 -12
- package/dist/shared/domain-errors.js.map +1 -1
- package/dist/shared/git.contract.d.ts +10 -1
- package/dist/shared/git.contract.d.ts.map +1 -1
- package/dist/shared/git.contract.js.map +1 -1
- package/dist/shared/utf8-text.d.ts +15 -0
- package/dist/shared/utf8-text.d.ts.map +1 -0
- package/dist/shared/utf8-text.js +20 -0
- package/dist/shared/utf8-text.js.map +1 -0
- package/dist/tenancy/static-tenant-source.d.ts +2 -1
- package/dist/tenancy/static-tenant-source.d.ts.map +1 -1
- package/dist/tenancy/static-tenant-source.js +9 -2
- package/dist/tenancy/static-tenant-source.js.map +1 -1
- package/mcp-app/page.html +349 -0
- package/migrations/0017_atlassian_account_links.sql +37 -0
- package/migrations/meta/0017_snapshot.json +2396 -0
- package/migrations/meta/_journal.json +8 -1
- package/package.json +5 -3
- package/src/__tests__/packaged-assets-ship.test.ts +16 -4
- package/src/assets.ts +28 -2
- package/src/core/__tests__/catalog-revision.test.ts +40 -0
- package/src/core/__tests__/gated-boot.test.ts +1 -0
- package/src/core/__tests__/lifecycle.test.ts +14 -0
- package/src/core/catalog-revision.ts +15 -2
- package/src/core/create-core-server.ts +67 -3
- package/src/core/create-core-services.ts +140 -6
- package/src/core/lifecycle.ts +28 -2
- package/src/core-config.ts +24 -1
- package/src/index.ts +2 -2
- package/src/modules/access/__tests__/access-control.service.test.ts +19 -0
- package/src/modules/access/access-control.interface.ts +13 -0
- package/src/modules/access/access-control.service.ts +16 -0
- package/src/modules/access/directory-sync-bot.ts +1 -1
- package/src/modules/agent-guide/__tests__/agent-guide.test.ts +58 -0
- package/src/modules/agent-guide/__tests__/agent-guide.tools.test.ts +19 -1
- package/src/modules/agent-guide/agent-guide.ts +9 -0
- package/src/modules/agent-guide/index.ts +1 -0
- package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +14 -0
- package/src/modules/agent-instructions/compose.ts +17 -2
- package/src/modules/agent-instructions/shared-file-rules.ts +19 -19
- package/src/modules/audit/__tests__/agent-audit.service.test.ts +64 -4
- package/src/modules/audit/__tests__/agent-connection.routes.test.ts +102 -0
- package/src/modules/audit/agent-audit.service.ts +58 -3
- package/src/modules/audit/agent-connection.routes.ts +56 -0
- package/src/modules/audit/audit.contract.ts +26 -0
- package/src/modules/audit/index.ts +1 -0
- package/src/modules/auth/__tests__/account.routes.test.ts +5 -3
- package/src/modules/auth/account.routes.ts +10 -3
- package/src/modules/auth/auth.middleware.ts +1 -1
- package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +18 -2
- package/src/modules/code-mode/code-mode.tool.ts +13 -2
- package/src/modules/database/core-schema.ts +33 -3
- package/src/modules/embed/__tests__/account-links.migration.test.ts +98 -0
- package/src/modules/embed/__tests__/embed-link.test.ts +122 -0
- package/src/modules/embed/__tests__/embed.routes.test.ts +362 -0
- package/src/modules/embed/__tests__/embed.service.lock.test.ts +193 -0
- package/src/modules/embed/__tests__/embed.service.test.ts +805 -0
- package/src/modules/embed/__tests__/embed.tools.test.ts +304 -0
- package/src/modules/embed/__tests__/mcp-app.test.ts +207 -0
- package/src/modules/embed/account-link.service.ts +76 -0
- package/src/modules/embed/embed-link.ts +180 -0
- package/src/modules/embed/embed.errors.ts +43 -0
- package/src/modules/embed/embed.interface.ts +203 -0
- package/src/modules/embed/embed.routes.ts +363 -0
- package/src/modules/embed/embed.service.ts +705 -0
- package/src/modules/embed/embed.tools.ts +206 -0
- package/src/modules/embed/mcp-app.routes.ts +41 -0
- package/src/modules/embed/mcp-app.ts +173 -0
- package/src/modules/github-app/__tests__/github-app.test.ts +76 -0
- package/src/modules/github-app/github-app.connection.ts +38 -4
- package/src/modules/kb-fs/__tests__/move-with-edits.test.ts +304 -0
- package/src/modules/kb-fs/locking-filesystem.ts +287 -0
- package/src/modules/kb-sync/__tests__/kb-sync.service.test.ts +35 -0
- package/src/modules/kb-sync/kb-sync.interface.ts +2 -0
- package/src/modules/kb-sync/kb-sync.service.ts +6 -0
- package/src/modules/mcp/__tests__/bevel-oauth-provider.test.ts +68 -11
- package/src/modules/mcp/__tests__/mcp-proxy-helpers.test.ts +37 -2
- package/src/modules/mcp/__tests__/mcp.app.e2e.test.ts +200 -0
- package/src/modules/mcp/__tests__/mcp.e2e.test.ts +3 -1
- package/src/modules/mcp/__tests__/mcp.service.test.ts +23 -13
- package/src/modules/mcp/__tests__/tool-call-interface.e2e.test.ts +499 -0
- package/src/modules/mcp/mcp-auth.middleware.ts +2 -1
- package/src/modules/mcp/mcp.service.ts +81 -12
- package/src/modules/mcp/oauth/bevel-oauth-provider.ts +42 -4
- package/src/modules/onboarding/__tests__/onboarding.routes.test.ts +163 -0
- package/src/modules/onboarding/__tests__/starter-pack.service.test.ts +635 -0
- package/src/modules/onboarding/__tests__/starter-packs.test.ts +169 -0
- package/src/modules/onboarding/onboarding.contract.ts +85 -0
- package/src/modules/onboarding/onboarding.routes.ts +85 -0
- package/src/modules/onboarding/starter-pack.service.ts +580 -0
- package/src/modules/onboarding/starter-packs.ts +182 -0
- package/src/modules/plugins/__tests__/plugins.tools.test.ts +25 -2
- package/src/modules/plugins/index.ts +1 -0
- package/src/modules/plugins/plugin-provision.service.ts +22 -1
- package/src/modules/plugins/plugins.routes.ts +29 -0
- package/src/modules/settings/__tests__/deployment-settings.service.test.ts +150 -1
- package/src/modules/settings/__tests__/repository-source.test.ts +9 -0
- package/src/modules/settings/deployment-settings.service.ts +131 -1
- package/src/modules/settings/repository-source.ts +4 -4
- package/src/modules/skills/__tests__/skills.tools.description.test.ts +5 -1
- package/src/modules/skills/skills.tools.ts +55 -35
- package/src/modules/tool-auth/__tests__/connection-key-rejection.test.ts +32 -0
- package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +65 -0
- package/src/modules/tool-auth/connection-key-rejection.ts +13 -3
- package/src/modules/tool-auth/external-api-key.interface.ts +9 -0
- package/src/modules/tool-auth/external-api-key.service.ts +61 -4
- package/src/modules/tool-auth/tool-auth.middleware.ts +13 -4
- package/src/modules/tool-helpers/__tests__/branch-declaration.test.ts +502 -0
- package/src/modules/tool-helpers/__tests__/route-argument-check.test.ts +337 -0
- package/src/modules/tool-helpers/index.ts +34 -1
- package/src/modules/tool-helpers/route-argument-check.ts +149 -0
- package/src/modules/tool-helpers/route-tool-schemas.ts +171 -0
- package/src/modules/tool-helpers/tool-def.ts +132 -7
- package/src/modules/tool-helpers/tool-handler.ts +196 -8
- package/src/modules/tool-helpers/tool.contract.ts +9 -0
- package/src/modules/tool-manuals/tool-manuals.tools.ts +15 -5
- package/src/modules/tool-registry/__tests__/own-tool-schemas.test.ts +14 -1
- package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +23 -10
- package/src/modules/tool-registry/description-length.ts +34 -7
- package/src/modules/tool-registry/guide-first.ts +8 -1
- package/src/modules/workflow/__tests__/file-lock.service.deletion-gate.test.ts +104 -0
- package/src/modules/workflow/__tests__/pending-commits.repository-replaced.test.ts +66 -0
- package/src/modules/workflow/__tests__/pending-commits.worker.test.ts +31 -2
- package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +110 -6
- package/src/modules/workflow/__tests__/sanitize-error.test.ts +89 -1
- package/src/modules/workflow/__tests__/workflow.routes.push-refused.test.ts +202 -0
- package/src/modules/workflow/__tests__/workflow.service.branch-in-use.test.ts +6 -6
- package/src/modules/workflow/__tests__/workflow.service.delete-branch.test.ts +1026 -0
- package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +468 -10
- package/src/modules/workflow/__tests__/workflow.service.folder-proposals.test.ts +4 -0
- package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +5 -4
- package/src/modules/workflow/__tests__/workflow.service.update-from-target.test.ts +54 -2
- package/src/modules/workflow/agent-tools/__tests__/change-request-read.tools.test.ts +41 -5
- package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +127 -2
- package/src/modules/workflow/agent-tools/change-request-read.tools.ts +157 -122
- package/src/modules/workflow/agent-tools/workflow.tools.ts +129 -35
- package/src/modules/workflow/file-lock.service.ts +67 -5
- package/src/modules/workflow/git/__tests__/git.service.deleteBranch.test.ts +164 -0
- package/src/modules/workflow/git/__tests__/git.service.pull.test.ts +48 -1
- package/src/modules/workflow/git/__tests__/node-git-runner.test.ts +231 -2
- package/src/modules/workflow/git/git.service.ts +176 -38
- package/src/modules/workflow/git/node-git-runner.ts +82 -7
- package/src/modules/workflow/pending-commits.service.ts +29 -1
- package/src/modules/workflow/pending-commits.worker.ts +24 -6
- package/src/modules/workflow/recovery-bot.ts +1 -1
- package/src/modules/workflow/review-workflow/review-workflow.service.ts +2 -1
- package/src/modules/workflow/sanitize-error.ts +50 -0
- package/src/modules/workflow/workflow.service.ts +879 -112
- package/src/modules/workspace/__tests__/agent-downloads.test.ts +823 -0
- package/src/modules/workspace/__tests__/first-run.test.ts +234 -0
- package/src/modules/workspace/__tests__/git-internals.security.test.ts +5 -2
- package/src/modules/workspace/__tests__/md-links.test.ts +353 -0
- package/src/modules/workspace/__tests__/move-links.test.ts +261 -0
- package/src/modules/workspace/__tests__/rest-tool-arguments.test.ts +219 -0
- package/src/modules/workspace/__tests__/workspace-paths-inside-the-repo.test.ts +3 -0
- package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +102 -0
- package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +52 -5
- package/src/modules/workspace/__tests__/workspace.routes.folder-zip-access.test.ts +190 -0
- package/src/modules/workspace/__tests__/workspace.service.forget-clone-races.test.ts +8 -4
- package/src/modules/workspace/__tests__/workspace.service.test.ts +50 -8
- package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +44 -1
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +409 -12
- package/src/modules/workspace/agent-download.builder.ts +308 -0
- package/src/modules/workspace/agent-download.routes.ts +183 -0
- package/src/modules/workspace/agent-download.store.ts +533 -0
- package/src/modules/workspace/agent-upload.store.ts +23 -7
- package/src/modules/workspace/file-readers/raw-mime.ts +40 -0
- package/src/modules/workspace/file-readers/text-reader.ts +5 -8
- package/src/modules/workspace/first-run.ts +222 -0
- package/src/modules/workspace/move-links.ts +237 -0
- package/src/modules/workspace/startup/steps/seed-tree.ts +3 -12
- package/src/modules/workspace/workspace.routes.ts +64 -22
- package/src/modules/workspace/workspace.service.ts +202 -25
- package/src/modules/workspace/workspace.tools.ts +492 -63
- package/src/modules/write-access/__tests__/write-access.test.ts +13 -0
- package/src/modules/write-access/write-access.ts +9 -0
- package/src/shared/domain-errors.ts +108 -11
- package/src/shared/git.contract.ts +8 -1
- package/src/shared/utf8-text.ts +21 -0
- package/src/tenancy/__tests__/static-tenant-source.test.ts +1 -0
- package/src/tenancy/static-tenant-source.ts +10 -2
- package/starter-packs/README.md +103 -0
- package/starter-packs/engineering/KnowledgeBase/About us.md +9 -0
- package/starter-packs/engineering/KnowledgeBase/Architecture.md +9 -0
- package/starter-packs/engineering/KnowledgeBase/Glossary.md +9 -0
- package/starter-packs/engineering/KnowledgeBase/How we ship.md +9 -0
- package/starter-packs/engineering/Plugins/engineering-starter/LICENSE +21 -0
- package/starter-packs/engineering/Plugins/engineering-starter/SOURCE.md +20 -0
- package/starter-packs/engineering/Plugins/engineering-starter/access.md +19 -0
- package/starter-packs/engineering/Plugins/engineering-starter/plugin.json +10 -0
- package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/SKILL.md +171 -0
- package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/sections/phase-2a-startup-diagnostic.md +157 -0
- package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/sections/phase-2b-builder-brainstorm.md +39 -0
- package/starter-packs/engineering/Plugins/engineering-starter/skills/plan-ceo-review/SKILL.md +144 -0
- package/starter-packs/engineering/Plugins/engineering-starter/skills/plan-eng-review/SKILL.md +144 -0
- package/starter-packs/engineering/Plugins/engineering-starter/skills/review/SKILL.md +80 -0
- package/starter-packs/engineering/Plugins/engineering-starter/skills/review/checklist.md +184 -0
- package/starter-packs/engineering/pack.yaml +11 -0
- package/starter-packs/general/KnowledgeBase/About us.md +9 -0
- package/starter-packs/general/KnowledgeBase/Glossary.md +9 -0
- package/starter-packs/general/KnowledgeBase/How we work.md +9 -0
- package/starter-packs/general/Plugins/general-starter/LICENSE +202 -0
- package/starter-packs/general/Plugins/general-starter/SOURCE.md +10 -0
- package/starter-packs/general/Plugins/general-starter/access.md +19 -0
- package/starter-packs/general/Plugins/general-starter/plugin.json +10 -0
- package/starter-packs/general/Plugins/general-starter/skills/memory-management/SKILL.md +332 -0
- package/starter-packs/general/Plugins/general-starter/skills/productivity-start/SKILL.md +163 -0
- package/starter-packs/general/Plugins/general-starter/skills/productivity-update/SKILL.md +167 -0
- package/starter-packs/general/Plugins/general-starter/skills/task-management/SKILL.md +83 -0
- package/starter-packs/general/pack.yaml +10 -0
- package/starter-packs/operations/KnowledgeBase/Glossary.md +9 -0
- package/starter-packs/operations/KnowledgeBase/How we work.md +9 -0
- package/starter-packs/operations/KnowledgeBase/Processes.md +9 -0
- package/starter-packs/operations/KnowledgeBase/Tools we use.md +9 -0
- package/starter-packs/operations/KnowledgeBase/Vendors.md +9 -0
- package/starter-packs/operations/Plugins/operations-starter/LICENSE +202 -0
- package/starter-packs/operations/Plugins/operations-starter/SOURCE.md +10 -0
- package/starter-packs/operations/Plugins/operations-starter/access.md +19 -0
- package/starter-packs/operations/Plugins/operations-starter/plugin.json +10 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/capacity-plan/SKILL.md +111 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/change-request/SKILL.md +119 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/compliance-tracking/SKILL.md +44 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/process-doc/SKILL.md +80 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/process-optimization/SKILL.md +39 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/risk-assessment/SKILL.md +40 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/runbook/SKILL.md +85 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/status-report/SKILL.md +75 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/vendor-review/SKILL.md +102 -0
- package/starter-packs/operations/pack.yaml +12 -0
- package/starter-packs/sales/KnowledgeBase/About us.md +9 -0
- package/starter-packs/sales/KnowledgeBase/Customers.md +9 -0
- package/starter-packs/sales/KnowledgeBase/Glossary.md +9 -0
- package/starter-packs/sales/KnowledgeBase/How we sell.md +9 -0
- package/starter-packs/sales/KnowledgeBase/Objections.md +9 -0
- package/starter-packs/sales/KnowledgeBase/Pricing.md +9 -0
- package/starter-packs/sales/Plugins/sales-starter/LICENSE +202 -0
- package/starter-packs/sales/Plugins/sales-starter/SOURCE.md +10 -0
- package/starter-packs/sales/Plugins/sales-starter/access.md +19 -0
- package/starter-packs/sales/Plugins/sales-starter/plugin.json +10 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/account-context/SKILL.md +94 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/account-plan/SKILL.md +95 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/account-research/SKILL.md +99 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/account-tiering/SKILL.md +101 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/call-prep/SKILL.md +111 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/call-summary/SKILL.md +111 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/close-plan/SKILL.md +106 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/competitive-intelligence/SKILL.md +102 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/create-an-asset/SKILL.md +105 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/crm-hygiene-check/SKILL.md +103 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/customer-health/SKILL.md +108 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/customer-voice/SKILL.md +94 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/daily-briefing/SKILL.md +61 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/deal-advance-gap/SKILL.md +102 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/deal-review/SKILL.md +105 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/deal-signals/SKILL.md +96 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/deal-slip-scenario/SKILL.md +104 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/draft-outreach/SKILL.md +116 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/end-of-day/SKILL.md +114 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/expansion-whitespace/SKILL.md +97 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/forecast/SKILL.md +100 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/handle-objection/SKILL.md +93 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/inbox-sweep/SKILL.md +114 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/lead-triage/SKILL.md +106 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/log-activity/SKILL.md +111 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/pipeline-review/SKILL.md +97 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/renewal-radar/SKILL.md +95 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/rep-context/SKILL.md +90 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/route-lead/SKILL.md +111 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/sales-setup/SKILL.md +200 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/schedule-meeting/SKILL.md +99 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/stakeholder-map/SKILL.md +107 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/team-pipeline/SKILL.md +105 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/update-opportunity/SKILL.md +112 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/weekly-wrap/SKILL.md +90 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/win-loss-review/SKILL.md +88 -0
- package/starter-packs/sales/pack.yaml +13 -0
- package/starter-packs/support/KnowledgeBase/Escalations.md +9 -0
- package/starter-packs/support/KnowledgeBase/Glossary.md +9 -0
- package/starter-packs/support/KnowledgeBase/Known issues.md +9 -0
- package/starter-packs/support/KnowledgeBase/Products.md +9 -0
- package/starter-packs/support/KnowledgeBase/Tone of voice.md +9 -0
- package/starter-packs/support/Plugins/support-starter/LICENSE +202 -0
- package/starter-packs/support/Plugins/support-starter/SOURCE.md +10 -0
- package/starter-packs/support/Plugins/support-starter/access.md +19 -0
- package/starter-packs/support/Plugins/support-starter/plugin.json +10 -0
- package/starter-packs/support/Plugins/support-starter/skills/customer-escalation/SKILL.md +246 -0
- package/starter-packs/support/Plugins/support-starter/skills/customer-research/SKILL.md +250 -0
- package/starter-packs/support/Plugins/support-starter/skills/draft-response/SKILL.md +416 -0
- package/starter-packs/support/Plugins/support-starter/skills/kb-article/SKILL.md +350 -0
- package/starter-packs/support/Plugins/support-starter/skills/ticket-triage/SKILL.md +273 -0
- package/starter-packs/support/pack.yaml +12 -0
|
@@ -22,10 +22,32 @@ function updated(branch: string): BranchSyncOutcome {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
describe('KbSyncService', () => {
|
|
25
|
+
it('answers the sync without waiting for the tidy-up after it', async () => {
|
|
26
|
+
let finish = (): void => undefined;
|
|
27
|
+
const workflow: SyncWorkflowPort = {
|
|
28
|
+
syncWorkspaceFromRemote: vi.fn(async (id: string) => updated(decodeURIComponent(id))),
|
|
29
|
+
closeChangeRequestsWithDeletedBranches: vi.fn(async () => 0),
|
|
30
|
+
// A tidy-up that never ends unless told to — and one that fails.
|
|
31
|
+
tidyAfterSweep: vi
|
|
32
|
+
.fn()
|
|
33
|
+
.mockImplementationOnce(
|
|
34
|
+
() => new Promise((resolve) => (finish = () => resolve({ closedEmpty: 0, removedLeftovers: 0 }))),
|
|
35
|
+
)
|
|
36
|
+
.mockRejectedValueOnce(new Error('boom')),
|
|
37
|
+
retireRemoteGoneClone: vi.fn(async () => true),
|
|
38
|
+
};
|
|
39
|
+
const svc = new KbSyncService(workflow, workspaces(['main']));
|
|
40
|
+
await expect(svc.sync({ branches: 'all' })).resolves.toMatchObject({ status: 'synced' });
|
|
41
|
+
expect(workflow.tidyAfterSweep).toHaveBeenCalledTimes(1);
|
|
42
|
+
await expect(svc.sync({ branches: 'all' })).resolves.toMatchObject({ status: 'synced' });
|
|
43
|
+
finish();
|
|
44
|
+
});
|
|
45
|
+
|
|
25
46
|
it('syncs every known clone for an "all" request and reports unknown branches as not cloned', async () => {
|
|
26
47
|
const workflow: SyncWorkflowPort = {
|
|
27
48
|
syncWorkspaceFromRemote: vi.fn(async (id: string) => updated(decodeURIComponent(id))),
|
|
28
49
|
closeChangeRequestsWithDeletedBranches: vi.fn(async () => 1),
|
|
50
|
+
tidyAfterSweep: vi.fn(async () => ({ closedEmpty: 0, removedLeftovers: 0 })),
|
|
29
51
|
retireRemoteGoneClone: vi.fn(async () => true),
|
|
30
52
|
};
|
|
31
53
|
const svc = new KbSyncService(workflow, workspaces(['main', 'ali/x']));
|
|
@@ -34,6 +56,8 @@ describe('KbSyncService', () => {
|
|
|
34
56
|
expect(all.status).toBe('synced');
|
|
35
57
|
expect(all.results.map((r) => r.branch)).toEqual(['main', 'ali/x']);
|
|
36
58
|
expect(all.changeRequests.closedDeletedBranch).toBe(1);
|
|
59
|
+
// Every sweep for deleted branches is followed by the leftover cleanup.
|
|
60
|
+
expect(workflow.tidyAfterSweep).toHaveBeenCalledTimes(1);
|
|
37
61
|
expect(workflow.syncWorkspaceFromRemote).toHaveBeenCalledWith('ali%2Fx');
|
|
38
62
|
|
|
39
63
|
const some = await svc.sync({ branches: ['ali/x', 'juan/other'] });
|
|
@@ -48,6 +72,7 @@ describe('KbSyncService', () => {
|
|
|
48
72
|
: updated(decodeURIComponent(id)),
|
|
49
73
|
),
|
|
50
74
|
closeChangeRequestsWithDeletedBranches: vi.fn(async () => 0),
|
|
75
|
+
tidyAfterSweep: vi.fn(async () => ({ closedEmpty: 0, removedLeftovers: 0 })),
|
|
51
76
|
retireRemoteGoneClone: vi.fn(async () => true),
|
|
52
77
|
};
|
|
53
78
|
const svc = new KbSyncService(workflow, workspaces(['main', 'ali/x']));
|
|
@@ -62,6 +87,7 @@ describe('KbSyncService', () => {
|
|
|
62
87
|
closeChangeRequestsWithDeletedBranches: vi.fn(async () => {
|
|
63
88
|
throw new Error('origin down');
|
|
64
89
|
}),
|
|
90
|
+
tidyAfterSweep: vi.fn(async () => ({ closedEmpty: 0, removedLeftovers: 0 })),
|
|
65
91
|
retireRemoteGoneClone: vi.fn(async () => true),
|
|
66
92
|
};
|
|
67
93
|
const svc = new KbSyncService(workflow, workspaces(['main']));
|
|
@@ -91,6 +117,7 @@ describe('KbSyncService', () => {
|
|
|
91
117
|
return updated(branch);
|
|
92
118
|
}),
|
|
93
119
|
closeChangeRequestsWithDeletedBranches: vi.fn(async () => 0),
|
|
120
|
+
tidyAfterSweep: vi.fn(async () => ({ closedEmpty: 0, removedLeftovers: 0 })),
|
|
94
121
|
retireRemoteGoneClone: vi.fn(async () => true),
|
|
95
122
|
};
|
|
96
123
|
const svc = new KbSyncService(workflow, workspaces(['main', 'ali/x', 'juan/y']));
|
|
@@ -121,6 +148,7 @@ describe('KbSyncService', () => {
|
|
|
121
148
|
return updated(decodeURIComponent(id));
|
|
122
149
|
}),
|
|
123
150
|
closeChangeRequestsWithDeletedBranches: vi.fn(async () => 0),
|
|
151
|
+
tidyAfterSweep: vi.fn(async () => ({ closedEmpty: 0, removedLeftovers: 0 })),
|
|
124
152
|
retireRemoteGoneClone: vi.fn(async () => true),
|
|
125
153
|
};
|
|
126
154
|
const svc = new KbSyncService(workflow, workspaces(['main', 'ali/x', 'juan/y']));
|
|
@@ -141,6 +169,7 @@ describe('KbSyncService.lastSync', () => {
|
|
|
141
169
|
const workflow: SyncWorkflowPort = {
|
|
142
170
|
syncWorkspaceFromRemote: vi.fn(async (id: string) => updated(decodeURIComponent(id))),
|
|
143
171
|
closeChangeRequestsWithDeletedBranches: vi.fn(async () => 0),
|
|
172
|
+
tidyAfterSweep: vi.fn(async () => ({ closedEmpty: 0, removedLeftovers: 0 })),
|
|
144
173
|
retireRemoteGoneClone: vi.fn(async () => true),
|
|
145
174
|
};
|
|
146
175
|
let clock = 1_000;
|
|
@@ -168,6 +197,7 @@ describe('KbSyncService.lastSync', () => {
|
|
|
168
197
|
return updated(decodeURIComponent(id));
|
|
169
198
|
}),
|
|
170
199
|
closeChangeRequestsWithDeletedBranches: vi.fn(async () => 0),
|
|
200
|
+
tidyAfterSweep: vi.fn(async () => ({ closedEmpty: 0, removedLeftovers: 0 })),
|
|
171
201
|
retireRemoteGoneClone: vi.fn(async () => true),
|
|
172
202
|
};
|
|
173
203
|
const svc = new KbSyncService(workflow, workspaces(['main', 'ali/x']));
|
|
@@ -188,6 +218,7 @@ describe('KbSyncService — a throwing branch step', () => {
|
|
|
188
218
|
return updated(decodeURIComponent(id));
|
|
189
219
|
}),
|
|
190
220
|
closeChangeRequestsWithDeletedBranches: vi.fn(async () => 1),
|
|
221
|
+
tidyAfterSweep: vi.fn(async () => ({ closedEmpty: 0, removedLeftovers: 0 })),
|
|
191
222
|
retireRemoteGoneClone: vi.fn(async () => true),
|
|
192
223
|
};
|
|
193
224
|
const svc = new KbSyncService(workflow, workspaces(['main', 'ali/x']));
|
|
@@ -208,6 +239,7 @@ describe('KbSyncService — clones the host no longer has, and clones that canno
|
|
|
208
239
|
id === 'ali%2Fx' ? { branch: 'ali/x', outcome: 'remote-gone' } : updated(decodeURIComponent(id)),
|
|
209
240
|
),
|
|
210
241
|
closeChangeRequestsWithDeletedBranches: vi.fn(async () => 1),
|
|
242
|
+
tidyAfterSweep: vi.fn(async () => ({ closedEmpty: 0, removedLeftovers: 0 })),
|
|
211
243
|
retireRemoteGoneClone: vi.fn(async () => true),
|
|
212
244
|
};
|
|
213
245
|
const svc = new KbSyncService(workflow, workspaces(['ali/x', 'main']));
|
|
@@ -222,6 +254,7 @@ describe('KbSyncService — clones the host no longer has, and clones that canno
|
|
|
222
254
|
const workflow: SyncWorkflowPort = {
|
|
223
255
|
syncWorkspaceFromRemote: vi.fn(async (id: string) => updated(decodeURIComponent(id))),
|
|
224
256
|
closeChangeRequestsWithDeletedBranches: vi.fn(async () => 0),
|
|
257
|
+
tidyAfterSweep: vi.fn(async () => ({ closedEmpty: 0, removedLeftovers: 0 })),
|
|
225
258
|
retireRemoteGoneClone: vi.fn(async () => true),
|
|
226
259
|
};
|
|
227
260
|
const port: SyncWorkspacePort = {
|
|
@@ -251,6 +284,7 @@ describe('KbSyncService — a coalesced caller is judged by its own branches', (
|
|
|
251
284
|
return updated(decodeURIComponent(id));
|
|
252
285
|
}),
|
|
253
286
|
closeChangeRequestsWithDeletedBranches: vi.fn(async () => 0),
|
|
287
|
+
tidyAfterSweep: vi.fn(async () => ({ closedEmpty: 0, removedLeftovers: 0 })),
|
|
254
288
|
retireRemoteGoneClone: vi.fn(async () => true),
|
|
255
289
|
};
|
|
256
290
|
const svc = new KbSyncService(workflow, workspaces(['main', 'ali/x', 'juan/y']));
|
|
@@ -278,6 +312,7 @@ describe('KbSyncService — a waiter keeps the branches it asked for', () => {
|
|
|
278
312
|
return updated(decodeURIComponent(id));
|
|
279
313
|
}),
|
|
280
314
|
closeChangeRequestsWithDeletedBranches: vi.fn(async () => 0),
|
|
315
|
+
tidyAfterSweep: vi.fn(async () => ({ closedEmpty: 0, removedLeftovers: 0 })),
|
|
281
316
|
retireRemoteGoneClone: vi.fn(async () => true),
|
|
282
317
|
};
|
|
283
318
|
const svc = new KbSyncService(workflow, workspaces(['main', 'ali/x', 'juan/y']));
|
|
@@ -61,6 +61,8 @@ export interface SyncWorkflowPort {
|
|
|
61
61
|
retireRemoteGoneClone(workspaceId: string): Promise<boolean>;
|
|
62
62
|
/** The existing sweep; returns how many requests it closed. */
|
|
63
63
|
closeChangeRequestsWithDeletedBranches(): Promise<number>;
|
|
64
|
+
/** The tidy-up after the sweep — empty open requests closed, leftover branches removed; never throws. */
|
|
65
|
+
tidyAfterSweep(): Promise<{ closedEmpty: number; removedLeftovers: number }>;
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
/**
|
|
@@ -187,6 +187,12 @@ export class KbSyncService implements IKbSyncService {
|
|
|
187
187
|
} catch (err) {
|
|
188
188
|
log.warn('deleted-branch sweep failed:', { err });
|
|
189
189
|
}
|
|
190
|
+
// Then the tidy-up boot runs too: empty open requests closed, leftover
|
|
191
|
+
// branches of merged requests removed. Detached: it can take a while on a
|
|
192
|
+
// large repository, and the sync (often a webhook with a short timeout)
|
|
193
|
+
// must not wait on it. It never throws; the catch is for a mock or a
|
|
194
|
+
// future change that does.
|
|
195
|
+
void this.workflow.tidyAfterSweep().catch((err: unknown) => log.warn('post-sync tidy-up failed:', { err }));
|
|
190
196
|
|
|
191
197
|
const result: SyncResult = {
|
|
192
198
|
status: KbSyncService.statusOf(results),
|
|
@@ -70,15 +70,19 @@ function render(fragment: SQL): { sql: string; params: unknown[] } {
|
|
|
70
70
|
return { sql: q.sql, params: q.params };
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
function
|
|
74
|
-
|
|
75
|
-
const provider = new BevelOAuthProvider({
|
|
73
|
+
function providerDeps(db: Database) {
|
|
74
|
+
return {
|
|
76
75
|
db,
|
|
77
76
|
crypto: null,
|
|
78
77
|
stateSecret: STATE_SECRET,
|
|
79
78
|
publicFrontendUrl: FRONTEND,
|
|
80
79
|
tokenPrefix: PREFIX,
|
|
81
|
-
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function makeProvider(queue: unknown[] = []) {
|
|
84
|
+
const { db, captured } = makeFakeDb(queue);
|
|
85
|
+
const provider = new BevelOAuthProvider(providerDeps(db));
|
|
82
86
|
return { provider, captured, db };
|
|
83
87
|
}
|
|
84
88
|
|
|
@@ -292,9 +296,10 @@ describe('BevelOAuthProvider', () => {
|
|
|
292
296
|
const { provider, captured, db } = makeProvider([
|
|
293
297
|
[oldRow] /* revoke.returning */,
|
|
294
298
|
[{ id: 'conn-1' }] /* the token's connection, still live */,
|
|
295
|
-
|
|
299
|
+
[] /* the (throttled) use stamp's first-use claim: stamped before */,
|
|
296
300
|
undefined, undefined, /* prunes */
|
|
297
301
|
undefined /* token insert */,
|
|
302
|
+
undefined /* the ordinary use stamp */,
|
|
298
303
|
]);
|
|
299
304
|
const tokens = await provider.exchangeRefreshToken(CLIENT, 'bevel-mcp_r_old', ['mcp']);
|
|
300
305
|
expect(tokens.scope).toBe('mcp');
|
|
@@ -307,8 +312,10 @@ describe('BevelOAuthProvider', () => {
|
|
|
307
312
|
expect((db as any).select).toHaveBeenCalledTimes(1);
|
|
308
313
|
expect(render(captured.where[1])).toMatchObject({ params: ['conn-1'] });
|
|
309
314
|
expect(render(captured.where[1]).sql).toMatch(/"agent_connections"."revoked_at" is null/);
|
|
310
|
-
// …and the use is stamped separately, off the refresh's own path
|
|
311
|
-
|
|
315
|
+
// …and the use is stamped separately, off the refresh's own path: the
|
|
316
|
+
// first-use claim, then (stamped before) the ordinary stamp.
|
|
317
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
318
|
+
expect(captured.updateTargets).toEqual([oauthTokens, agentConnections, agentConnections]);
|
|
312
319
|
expect(Object.keys(captured.set[1])).toEqual(['lastUsedAt']);
|
|
313
320
|
|
|
314
321
|
// Widening is refused.
|
|
@@ -363,6 +370,7 @@ describe('BevelOAuthProvider', () => {
|
|
|
363
370
|
const { provider, captured } = makeProvider([
|
|
364
371
|
[row] /* select */,
|
|
365
372
|
undefined /* token lastUsedAt touch */,
|
|
373
|
+
[] /* the connection's first-use claim: stamped before */,
|
|
366
374
|
undefined /* connection lastUsedAt touch */,
|
|
367
375
|
[row] /* a second verify a moment later */,
|
|
368
376
|
undefined /* its token touch — and NO second connection touch */,
|
|
@@ -381,16 +389,65 @@ describe('BevelOAuthProvider', () => {
|
|
|
381
389
|
// Both "last used" marks are touched: the token's row, and THIS token's
|
|
382
390
|
// agent connection.
|
|
383
391
|
await new Promise((r) => setTimeout(r, 0));
|
|
384
|
-
|
|
385
|
-
|
|
392
|
+
// The connection's stamp is two statements: the first-use claim (its
|
|
393
|
+
// id, and never stamped), then — stamped before — the ordinary stamp.
|
|
394
|
+
expect(captured.updateTargets).toEqual([oauthTokens, agentConnections, agentConnections]);
|
|
395
|
+
expect(captured.set.map((s) => Object.keys(s))).toEqual([['lastUsedAt'], ['lastUsedAt'], ['lastUsedAt']]);
|
|
386
396
|
expect(render(captured.where[1])).toMatchObject({ params: ['tok-row-1'] });
|
|
387
397
|
expect(render(captured.where[2])).toMatchObject({ params: ['conn-1'] });
|
|
388
|
-
expect(render(captured.where[2]).sql).toMatch(/"agent_connections"."id" = \$1/);
|
|
398
|
+
expect(render(captured.where[2]).sql).toMatch(/"agent_connections"."id" = \$1 and "agent_connections"."last_used_at" is null/);
|
|
399
|
+
expect(render(captured.where[3])).toMatchObject({ params: ['conn-1'] });
|
|
400
|
+
expect(render(captured.where[3]).sql).toMatch(/"agent_connections"."id" = \$1/);
|
|
389
401
|
// The connection's stamp is throttled: a verify a moment later touches
|
|
390
402
|
// the token row again but not the connection — a minute has not passed.
|
|
391
403
|
await provider.verifyAccessToken('bevel-mcp_live');
|
|
392
404
|
await new Promise((r) => setTimeout(r, 0));
|
|
393
|
-
expect(captured.updateTargets).toEqual([oauthTokens, agentConnections, oauthTokens]);
|
|
405
|
+
expect(captured.updateTargets).toEqual([oauthTokens, agentConnections, agentConnections, oauthTokens]);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* The FIRST stamp of a connection is the agent arriving — what the
|
|
410
|
+
* onboarding waits for — so the owner is told, once: the claim that took
|
|
411
|
+
* the row from never-used to now is the one that announces, and it is
|
|
412
|
+
* the only statement that run makes. A connection stamped before loses
|
|
413
|
+
* the claim, is stamped the ordinary way, and nobody is told.
|
|
414
|
+
*/
|
|
415
|
+
it('announces an agent connection to its owner on its first use, and never again', async () => {
|
|
416
|
+
const row = {
|
|
417
|
+
id: 'tok-row-1', clientId: 'client-1', connectionId: 'conn-1', scope: 'mcp', resource: null,
|
|
418
|
+
expiresAt: new Date(Date.now() + 60_000), userId: 'user-1', userEmail: 'alice@example.com',
|
|
419
|
+
};
|
|
420
|
+
const events = { emit: vi.fn() };
|
|
421
|
+
const fresh = makeProvider([
|
|
422
|
+
[row],
|
|
423
|
+
undefined /* token touch */,
|
|
424
|
+
[{ userId: 'user-1', client: 'Claude' }] /* the claim: this request took the first stamp */,
|
|
425
|
+
]);
|
|
426
|
+
const freshProvider = new BevelOAuthProvider({ ...providerDeps(fresh.db), events });
|
|
427
|
+
await freshProvider.verifyAccessToken('bevel-mcp_live');
|
|
428
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
429
|
+
expect(events.emit).toHaveBeenCalledTimes(1);
|
|
430
|
+
expect(events.emit).toHaveBeenCalledWith({
|
|
431
|
+
kind: 'agent-connected',
|
|
432
|
+
forUserId: 'user-1',
|
|
433
|
+
client: 'Claude',
|
|
434
|
+
agentKind: 'agent',
|
|
435
|
+
at: expect.stringMatching(/^\d{4}-\d{2}-\d{2}T/),
|
|
436
|
+
});
|
|
437
|
+
// The claim WAS the stamp: no second write for the winner.
|
|
438
|
+
expect(fresh.captured.updateTargets).toEqual([oauthTokens, agentConnections]);
|
|
439
|
+
|
|
440
|
+
const used = makeProvider([
|
|
441
|
+
[row],
|
|
442
|
+
undefined,
|
|
443
|
+
[] /* the claim lost: stamped before (or by a racing request) */,
|
|
444
|
+
undefined /* the ordinary stamp */,
|
|
445
|
+
]);
|
|
446
|
+
const usedEvents = { emit: vi.fn() };
|
|
447
|
+
await new BevelOAuthProvider({ ...providerDeps(used.db), events: usedEvents }).verifyAccessToken('bevel-mcp_live');
|
|
448
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
449
|
+
expect(usedEvents.emit).not.toHaveBeenCalled();
|
|
450
|
+
expect(used.captured.updateTargets).toEqual([oauthTokens, agentConnections, agentConnections]);
|
|
394
451
|
});
|
|
395
452
|
|
|
396
453
|
it('revokeToken (RFC 7009) ends the agent connection once no live token remains on it, as the owner\'s doing', async () => {
|
|
@@ -68,15 +68,50 @@ describe('toListedTool', () => {
|
|
|
68
68
|
...over,
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
-
it('passes a conforming tool through', () => {
|
|
71
|
+
it('passes a conforming tool through, its call example first', () => {
|
|
72
72
|
const listed = toListedTool(proxied({ mcpName: 'gmail_send_email' }));
|
|
73
73
|
expect(listed).toEqual({
|
|
74
74
|
name: 'gmail_send_email',
|
|
75
|
-
description: '
|
|
75
|
+
description: 'Call: m.t({})\n\na tool',
|
|
76
76
|
inputSchema: { type: 'object', properties: {} },
|
|
77
77
|
});
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
+
it('generates the example from the tool\'s own schema, in the shape it really takes', () => {
|
|
81
|
+
const platform = toListedTool(
|
|
82
|
+
proxied({
|
|
83
|
+
utcpName: 'KNOWLEDGE_BASE.read_file',
|
|
84
|
+
mcpName: 'read_file',
|
|
85
|
+
inputSchema: {
|
|
86
|
+
type: 'object',
|
|
87
|
+
properties: {
|
|
88
|
+
body: {
|
|
89
|
+
type: 'object',
|
|
90
|
+
properties: { branch: { type: 'string' }, path: { type: 'string' }, limit: { type: 'integer' } },
|
|
91
|
+
required: ['branch', 'path'],
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
required: ['body'],
|
|
95
|
+
} as never,
|
|
96
|
+
}),
|
|
97
|
+
)!;
|
|
98
|
+
expect(platform.description.split('\n')[0]).toBe(
|
|
99
|
+
'Call: KNOWLEDGE_BASE.read_file({ body: { branch: "...", path: "..." } })',
|
|
100
|
+
);
|
|
101
|
+
const flat = toListedTool(
|
|
102
|
+
proxied({
|
|
103
|
+
utcpName: 'NS.search',
|
|
104
|
+
mcpName: 'NS_search',
|
|
105
|
+
inputSchema: {
|
|
106
|
+
type: 'object',
|
|
107
|
+
properties: { query: { type: 'string' }, limit: { type: 'integer' } },
|
|
108
|
+
required: ['query'],
|
|
109
|
+
} as never,
|
|
110
|
+
}),
|
|
111
|
+
)!;
|
|
112
|
+
expect(flat.description.split('\n')[0]).toBe('Call: NS.search({ query: "..." })');
|
|
113
|
+
});
|
|
114
|
+
|
|
80
115
|
it('drops a tool whose name is too long or has illegal chars (so it cannot blank the whole list)', () => {
|
|
81
116
|
expect(toListedTool(proxied({ mcpName: 'has spaces' }))).toBeNull();
|
|
82
117
|
expect(toListedTool(proxied({ mcpName: 'a'.repeat(129) }))).toBeNull();
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import type { Server as HttpServer } from 'node:http';
|
|
2
|
+
import type { AddressInfo } from 'node:net';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import express, { type RequestHandler } from 'express';
|
|
6
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
7
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
8
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
9
|
+
import { MCP_APP_MIME_TYPE, MCP_APP_UI_META_KEY } from '@bevel-software/platform-mcp-core';
|
|
10
|
+
import { createMcpRoutes } from '../mcp.routes.js';
|
|
11
|
+
import { McpService } from '../mcp.service.js';
|
|
12
|
+
import { SpillStore } from '../../workspace/spill-store.js';
|
|
13
|
+
import { createManualRoutes } from '../../tool-registry/manual.routes.js';
|
|
14
|
+
import { ToolRegistry } from '../../tool-registry/tool-registry.js';
|
|
15
|
+
import { toolDef } from '../../tool-helpers/tool-def.js';
|
|
16
|
+
import { McpAppService, OPEN_PAGE_VIEW_URI_PREFIX } from '../../embed/mcp-app.js';
|
|
17
|
+
import { OPEN_PAGE_TOOL } from '../../embed/embed.tools.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The MCP App, over the REAL Streamable-HTTP transport with the official SDK
|
|
21
|
+
* client — the criterion as a host actually observes it: `tools/list` carries
|
|
22
|
+
* the view on `open_page`, `resources/list` offers it under the MCP App media
|
|
23
|
+
* type, and `resources/read` hands back the view with the framing metadata.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const KEY = 'bevel_key_user_a';
|
|
27
|
+
const PUBLIC = 'https://hexis.example';
|
|
28
|
+
|
|
29
|
+
const bearerOf = (req: express.Request) => (req.headers.authorization ?? '').replace(/^Bearer\s+/i, '');
|
|
30
|
+
const fakeAuth: RequestHandler = (req, res, next) => {
|
|
31
|
+
if (bearerOf(req) !== KEY) {
|
|
32
|
+
res.status(401).json({ error: 'unauthorized' });
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
req.userId = 'user-A';
|
|
36
|
+
req.externalApiKeyId = 'tok-A';
|
|
37
|
+
next();
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
let httpServer: HttpServer | null = null;
|
|
41
|
+
let client: Client | null = null;
|
|
42
|
+
|
|
43
|
+
afterEach(async () => {
|
|
44
|
+
await client?.close().catch(() => undefined);
|
|
45
|
+
client = null;
|
|
46
|
+
if (httpServer) {
|
|
47
|
+
httpServer.closeAllConnections();
|
|
48
|
+
await new Promise<void>((r) => httpServer!.close(() => r()));
|
|
49
|
+
}
|
|
50
|
+
httpServer = null;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
/** The platform: the real proxy, the real routes, and `open_page` in the catalog. */
|
|
54
|
+
async function connect(opts: { serveApps?: boolean } = {}) {
|
|
55
|
+
const app = express();
|
|
56
|
+
app.use(express.json());
|
|
57
|
+
|
|
58
|
+
const registry = new ToolRegistry();
|
|
59
|
+
for (const name of [OPEN_PAGE_TOOL, 'read_file']) {
|
|
60
|
+
registry.registerExternalTool(
|
|
61
|
+
toolDef({
|
|
62
|
+
name,
|
|
63
|
+
description: `the ${name} tool`,
|
|
64
|
+
path: `/api/agent/tools/${name}`,
|
|
65
|
+
inputs: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'], additionalProperties: false },
|
|
66
|
+
}),
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
const toolRoutes = express.Router();
|
|
70
|
+
toolRoutes.use(createManualRoutes(registry, fakeAuth, async () => 'a@x.io'));
|
|
71
|
+
toolRoutes.post('/agent/tools/:name', (req, res) =>
|
|
72
|
+
res.json(
|
|
73
|
+
req.params.name === OPEN_PAGE_TOOL
|
|
74
|
+
? { path: 'Data/Thing.md', content: '# Thing', embedUrl: `${PUBLIC}/embed?token=t`, appUrl: PUBLIC, branch: 'main' }
|
|
75
|
+
: { ok: true },
|
|
76
|
+
),
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
httpServer = app.listen(0);
|
|
80
|
+
await new Promise<void>((r) => httpServer!.once('listening', () => r()));
|
|
81
|
+
const port = (httpServer.address() as AddressInfo).port;
|
|
82
|
+
const baseUrl = `http://127.0.0.1:${port}`;
|
|
83
|
+
|
|
84
|
+
const service = new McpService(
|
|
85
|
+
{
|
|
86
|
+
loopbackBaseUrl: baseUrl,
|
|
87
|
+
manualName: 'KNOWLEDGE_BASE',
|
|
88
|
+
spillStore: new SpillStore(join(tmpdir(), 'bevel-test-spills')),
|
|
89
|
+
publicFrontendUrl: PUBLIC,
|
|
90
|
+
},
|
|
91
|
+
undefined,
|
|
92
|
+
undefined,
|
|
93
|
+
undefined,
|
|
94
|
+
undefined,
|
|
95
|
+
undefined,
|
|
96
|
+
// A deployment with no apps is the degraded shape: no capability, no view.
|
|
97
|
+
opts.serveApps === false ? undefined : new McpAppService({ publicFrontendUrl: PUBLIC }),
|
|
98
|
+
);
|
|
99
|
+
const stub = {} as never;
|
|
100
|
+
app.use('/api', createMcpRoutes(service, stub, fakeAuth, fakeAuth, stub, stub, stub, ''));
|
|
101
|
+
app.use('/api', toolRoutes);
|
|
102
|
+
|
|
103
|
+
client = new Client({ name: 'test-host', version: '1.0.0' });
|
|
104
|
+
await client.connect(
|
|
105
|
+
new StreamableHTTPClientTransport(new URL(`${baseUrl}/api/mcp`), {
|
|
106
|
+
requestInit: { headers: { Authorization: `Bearer ${KEY}` } },
|
|
107
|
+
}),
|
|
108
|
+
);
|
|
109
|
+
return client;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** The URI the packaged view is served under — named by its content, so read off the service. */
|
|
113
|
+
async function viewUri(): Promise<string> {
|
|
114
|
+
return (await new McpAppService({ publicFrontendUrl: PUBLIC }).manifest()).resources[0].uri;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
describe('the hosted MCP endpoint serves the open_page view', () => {
|
|
118
|
+
it('lists open_page carrying UI metadata that names a ui:// resource', async () => {
|
|
119
|
+
const host = await connect();
|
|
120
|
+
const { tools } = await host.listTools();
|
|
121
|
+
const openPage = tools.find((t) => t.name === OPEN_PAGE_TOOL);
|
|
122
|
+
expect(openPage).toBeDefined();
|
|
123
|
+
expect(openPage!._meta).toEqual({ [MCP_APP_UI_META_KEY]: { resourceUri: await viewUri() } });
|
|
124
|
+
expect((await viewUri()).startsWith(OPEN_PAGE_VIEW_URI_PREFIX)).toBe(true);
|
|
125
|
+
// And only that tool: `read_file` is deliberately untouched.
|
|
126
|
+
expect(tools.find((t) => t.name === 'read_file')!._meta).toBeUndefined();
|
|
127
|
+
}, 30_000);
|
|
128
|
+
|
|
129
|
+
it('lists the view with the MCP App media type', async () => {
|
|
130
|
+
const host = await connect();
|
|
131
|
+
const { resources } = await host.listResources();
|
|
132
|
+
const uri = await viewUri();
|
|
133
|
+
const view = resources.find((r) => r.uri === uri);
|
|
134
|
+
expect(view).toBeDefined();
|
|
135
|
+
expect(view!.mimeType).toBe(MCP_APP_MIME_TYPE);
|
|
136
|
+
expect(view!._meta).toMatchObject({
|
|
137
|
+
[MCP_APP_UI_META_KEY]: { csp: { connectDomains: [PUBLIC], resourceDomains: [PUBLIC] } },
|
|
138
|
+
});
|
|
139
|
+
}, 30_000);
|
|
140
|
+
|
|
141
|
+
it('reads the view, reaching exactly the deployment own origin and asking for no sandbox domain', async () => {
|
|
142
|
+
const host = await connect();
|
|
143
|
+
const read = await host.readResource({ uri: await viewUri() });
|
|
144
|
+
expect(read.contents).toHaveLength(1);
|
|
145
|
+
const [content] = read.contents as Array<{ mimeType: string; text: string; _meta: Record<string, unknown> }>;
|
|
146
|
+
expect(content.mimeType).toBe(MCP_APP_MIME_TYPE);
|
|
147
|
+
expect(content.text).toContain('ui/initialize');
|
|
148
|
+
expect(content._meta[MCP_APP_UI_META_KEY]).toMatchObject({
|
|
149
|
+
csp: { connectDomains: [PUBLIC], resourceDomains: [PUBLIC] },
|
|
150
|
+
prefersBorder: false,
|
|
151
|
+
});
|
|
152
|
+
// No frames: a chat host's sandbox blocks them, so the view does not ask.
|
|
153
|
+
expect((content._meta[MCP_APP_UI_META_KEY] as { csp: Record<string, unknown> }).csp).not.toHaveProperty(
|
|
154
|
+
'frameDomains',
|
|
155
|
+
);
|
|
156
|
+
// The host's default sandbox: the field's format is each host's own, and
|
|
157
|
+
// a value a host rejects is a view that never renders.
|
|
158
|
+
expect(content._meta[MCP_APP_UI_META_KEY]).not.toHaveProperty('domain');
|
|
159
|
+
}, 30_000);
|
|
160
|
+
|
|
161
|
+
it('refuses a resource it does not serve, naming the ones it does', async () => {
|
|
162
|
+
const host = await connect();
|
|
163
|
+
await expect(host.readResource({ uri: 'ui://hexis/not-a-view.html' })).rejects.toThrow(
|
|
164
|
+
/No resource at ui:\/\/hexis\/not-a-view\.html/,
|
|
165
|
+
);
|
|
166
|
+
}, 30_000);
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* A deployment that serves no view must not declare the capability: a
|
|
170
|
+
* client would list resources and be told the method does not exist, which
|
|
171
|
+
* reads as a broken endpoint rather than as "this one has no apps".
|
|
172
|
+
*/
|
|
173
|
+
it('declares no resources capability when the deployment serves no app', async () => {
|
|
174
|
+
const host = await connect({ serveApps: false });
|
|
175
|
+
expect(host.getServerCapabilities()?.resources).toBeUndefined();
|
|
176
|
+
const { tools } = await host.listTools();
|
|
177
|
+
// Every tool is still listed, and still carries no view.
|
|
178
|
+
expect(tools.find((t) => t.name === OPEN_PAGE_TOOL)).toBeDefined();
|
|
179
|
+
expect(tools.find((t) => t.name === OPEN_PAGE_TOOL)!._meta).toBeUndefined();
|
|
180
|
+
}, 30_000);
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* The view frames `structuredContent.embedUrl` — it has nowhere else to
|
|
184
|
+
* read it — so a tool with a view must answer structured content as well
|
|
185
|
+
* as the text the model reads. A tool without one answers text alone.
|
|
186
|
+
*/
|
|
187
|
+
it('answers open_page with structured content the view can read, and read_file without', async () => {
|
|
188
|
+
const host = await connect();
|
|
189
|
+
const opened = await host.callTool({ name: OPEN_PAGE_TOOL, arguments: { path: 'Data/Thing.md' } });
|
|
190
|
+
expect(opened.structuredContent).toMatchObject({ embedUrl: `${PUBLIC}/embed?token=t`, branch: 'main' });
|
|
191
|
+
expect((opened.content as Array<{ type: string; text: string }>)[0].text).toContain('embedUrl');
|
|
192
|
+
const read = await host.callTool({ name: 'read_file', arguments: { path: 'Data/Thing.md' } });
|
|
193
|
+
expect(read.structuredContent).toBeUndefined();
|
|
194
|
+
}, 30_000);
|
|
195
|
+
|
|
196
|
+
it('declares the resources capability when it does', async () => {
|
|
197
|
+
const host = await connect();
|
|
198
|
+
expect(host.getServerCapabilities()?.resources).toBeDefined();
|
|
199
|
+
}, 30_000);
|
|
200
|
+
});
|
|
@@ -1296,9 +1296,11 @@ describe('a connected tool whose schema is invalid is not offered to agents', ()
|
|
|
1296
1296
|
// The same chain, same namespace, on the sibling: bound and callable. This
|
|
1297
1297
|
// is what makes the line above a statement about the HIDDEN tool rather
|
|
1298
1298
|
// than about a namespace the chain could not reach at all.
|
|
1299
|
+
// Called the way its `Call:` line shows: a connected tool takes its
|
|
1300
|
+
// arguments flat, and the same call wrapped in `body` is refused.
|
|
1299
1301
|
const sibling = await client.callTool({
|
|
1300
1302
|
name: 'call_tool_chain',
|
|
1301
|
-
arguments: { code: 'return notion.srv_fine({
|
|
1303
|
+
arguments: { code: 'return notion.srv_fine({});' },
|
|
1302
1304
|
});
|
|
1303
1305
|
expect(sibling.isError).toBeFalsy();
|
|
1304
1306
|
});
|
|
@@ -14,7 +14,7 @@ import { toolDef } from '../../tool-helpers/tool-def.js';
|
|
|
14
14
|
import { DEFAULT_KB_LAYOUT } from '@bevel-software/platform-shared';
|
|
15
15
|
import { TOOL_PREFIX_LINE, platformInstructions } from '../../agent-instructions/index.js';
|
|
16
16
|
import { GUIDE_FIRST_SENTENCE } from '../../tool-registry/guide-first.js';
|
|
17
|
-
import {
|
|
17
|
+
import { splitCallLine } from '@bevel-software/platform-mcp-core';
|
|
18
18
|
import type { AgentEventInput, IAgentEventRecorder } from '../../audit/audit.contract.js';
|
|
19
19
|
|
|
20
20
|
/** The platform-owned part of the handshake text: the header plus the shared file rules. */
|
|
@@ -279,7 +279,9 @@ describe('McpService (UTCP→MCP proxy)', () => {
|
|
|
279
279
|
const { tools } = await client.listTools();
|
|
280
280
|
for (const name of ['call_tool_chain', 'list_tools', 'tools_info']) {
|
|
281
281
|
const served = tools.find((t) => t.name === name)!;
|
|
282
|
-
|
|
282
|
+
// Behind its `Call:` line, the first line of every description.
|
|
283
|
+
expect(served.description!.startsWith(`Call: ${name}(`), name).toBe(true);
|
|
284
|
+
expect(splitCallLine(served.description!).rest.startsWith(`${GUIDE_FIRST_SENTENCE} `), name).toBe(true);
|
|
283
285
|
expect(served.description!.split(GUIDE_FIRST_SENTENCE), name).toHaveLength(2);
|
|
284
286
|
}
|
|
285
287
|
const chain = tools.find((t) => t.name === 'call_tool_chain')!;
|
|
@@ -614,33 +616,40 @@ describe('McpService — agent instructions', () => {
|
|
|
614
616
|
expect.stringContaining('mcp-description.md'),
|
|
615
617
|
expect.objectContaining({ message: 'disk' }),
|
|
616
618
|
);
|
|
617
|
-
// And the four tools carry the fixed line alone
|
|
619
|
+
// And the four tools carry the fixed line alone — behind the call line,
|
|
620
|
+
// which stays first whatever is prepended, and ahead of the guide-first
|
|
618
621
|
// opening every listed tool has.
|
|
619
622
|
const { tools } = await client.listTools();
|
|
620
623
|
for (const name of KB_TOOLS) {
|
|
621
624
|
expect(tools.find((t) => t.name === name)?.description).toBe(
|
|
622
|
-
|
|
625
|
+
`Call: KNOWLEDGE_BASE.${name}({})\n\n${TOOL_PREFIX_LINE}\n\n${GUIDE_FIRST_SENTENCE} original ${name} description`,
|
|
623
626
|
);
|
|
624
627
|
}
|
|
625
628
|
});
|
|
626
629
|
|
|
627
|
-
it('prefixes exactly the four knowledge-base tools; every other description
|
|
630
|
+
it('prefixes exactly the four knowledge-base tools; every other description gains only its call line and the guide-first opening', async () => {
|
|
628
631
|
const client = await setup({ readAgentPreamble: async () => 'Acme builds solar farms.', extraTools: KB_TOOLS });
|
|
629
632
|
const { tools } = await client.listTools();
|
|
630
633
|
const byName = Object.fromEntries(tools.map((t) => [t.name, t.description]));
|
|
631
634
|
const prefix = `${TOOL_PREFIX_LINE} Acme builds solar farms.`;
|
|
632
635
|
for (const name of KB_TOOLS) {
|
|
633
|
-
expect(byName[name], name).toBe(
|
|
636
|
+
expect(byName[name], name).toBe(
|
|
637
|
+
`Call: KNOWLEDGE_BASE.${name}({})\n\n${prefix}\n\n${GUIDE_FIRST_SENTENCE} original ${name} description`,
|
|
638
|
+
);
|
|
634
639
|
}
|
|
635
640
|
// Regression: the rest carries only what the catalog gives every tool —
|
|
636
|
-
// the guide-first opening — and never the purpose
|
|
637
|
-
// open
|
|
638
|
-
expect(byName.ask).toBe(
|
|
639
|
-
expect(byName.boom).toBe(
|
|
640
|
-
|
|
641
|
+
// its own call line, then the guide-first opening — and never the purpose
|
|
642
|
+
// prefix; the meta-tools open the same way.
|
|
643
|
+
expect(byName.ask).toBe(`Call: KNOWLEDGE_BASE.ask({ body: { prompt: "..." } })\n\n${GUIDE_FIRST_SENTENCE} echo the prompt`);
|
|
644
|
+
expect(byName.boom).toBe(`Call: KNOWLEDGE_BASE.boom({})\n\n${GUIDE_FIRST_SENTENCE} always errors`);
|
|
645
|
+
// Its `to` is an array of a `$ref`'d shape: the example takes the type at
|
|
646
|
+
// the top and says nothing about what is inside — the interface does that.
|
|
647
|
+
expect(byName.refy).toBe(
|
|
648
|
+
`Call: KNOWLEDGE_BASE.refy({ body: { to: [] } })\n\n${GUIDE_FIRST_SENTENCE} has $defs/$ref in its schema`,
|
|
649
|
+
);
|
|
641
650
|
for (const meta of ['call_tool_chain', 'list_tools', 'tools_info']) {
|
|
642
651
|
expect(byName[meta], meta).not.toContain(TOOL_PREFIX_LINE);
|
|
643
|
-
expect(byName[meta]
|
|
652
|
+
expect(splitCallLine(byName[meta]!).rest.startsWith(`${GUIDE_FIRST_SENTENCE} `), meta).toBe(true);
|
|
644
653
|
}
|
|
645
654
|
});
|
|
646
655
|
|
|
@@ -658,7 +667,8 @@ describe('McpService — agent instructions', () => {
|
|
|
658
667
|
});
|
|
659
668
|
const { tools } = await client.listTools();
|
|
660
669
|
for (const name of KB_TOOLS) {
|
|
661
|
-
expect(tools.find((t) => t.name === name)?.description.
|
|
670
|
+
expect(tools.find((t) => t.name === name)?.description).toContain(`\n\n${TOOL_PREFIX_LINE} Acme.`);
|
|
671
|
+
expect(tools.find((t) => t.name === name)?.description.startsWith('Call: ')).toBe(true);
|
|
662
672
|
}
|
|
663
673
|
});
|
|
664
674
|
});
|