@bevel-software/platform-core-backend 0.14.0 → 0.15.1
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 +6 -6
- package/dist/core/catalog-cache-invalidation.d.ts +29 -0
- package/dist/core/catalog-cache-invalidation.d.ts.map +1 -0
- package/dist/core/catalog-cache-invalidation.js +71 -0
- package/dist/core/catalog-cache-invalidation.js.map +1 -0
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +99 -8
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +18 -1
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +96 -43
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +18 -6
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +26 -1
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +213 -71
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/access.routes.d.ts.map +1 -1
- package/dist/modules/access/access.routes.js +59 -7
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/access-model/access-grammar.d.ts +51 -1
- package/dist/modules/access-model/access-grammar.d.ts.map +1 -1
- package/dist/modules/access-model/access-grammar.js +95 -1
- package/dist/modules/access-model/access-grammar.js.map +1 -1
- package/dist/modules/access-model/group-files.d.ts.map +1 -1
- package/dist/modules/access-model/group-files.js +4 -1
- package/dist/modules/access-model/group-files.js.map +1 -1
- package/dist/modules/access-model/plugin-principals.d.ts +49 -0
- package/dist/modules/access-model/plugin-principals.d.ts.map +1 -0
- package/dist/modules/access-model/plugin-principals.js +125 -0
- package/dist/modules/access-model/plugin-principals.js.map +1 -0
- package/dist/modules/database/core-schema.d.ts +326 -0
- package/dist/modules/database/core-schema.d.ts.map +1 -1
- package/dist/modules/database/core-schema.js +52 -0
- package/dist/modules/database/core-schema.js.map +1 -1
- package/dist/modules/kb-sync/index.d.ts +6 -0
- package/dist/modules/kb-sync/index.d.ts.map +1 -0
- package/dist/modules/kb-sync/index.js +5 -0
- package/dist/modules/kb-sync/index.js.map +1 -0
- package/dist/modules/kb-sync/kb-sync.interface.d.ts +77 -0
- package/dist/modules/kb-sync/kb-sync.interface.d.ts.map +1 -0
- package/dist/modules/kb-sync/kb-sync.interface.js +2 -0
- package/dist/modules/kb-sync/kb-sync.interface.js.map +1 -0
- package/dist/modules/kb-sync/kb-sync.routes.d.ts +70 -0
- package/dist/modules/kb-sync/kb-sync.routes.d.ts.map +1 -0
- package/dist/modules/kb-sync/kb-sync.routes.js +135 -0
- package/dist/modules/kb-sync/kb-sync.routes.js.map +1 -0
- package/dist/modules/kb-sync/kb-sync.service.d.ts +45 -0
- package/dist/modules/kb-sync/kb-sync.service.d.ts.map +1 -0
- package/dist/modules/kb-sync/kb-sync.service.js +183 -0
- package/dist/modules/kb-sync/kb-sync.service.js.map +1 -0
- package/dist/modules/kb-sync/sync-auth.d.ts +59 -0
- package/dist/modules/kb-sync/sync-auth.d.ts.map +1 -0
- package/dist/modules/kb-sync/sync-auth.js +62 -0
- package/dist/modules/kb-sync/sync-auth.js.map +1 -0
- package/dist/modules/kb-sync/sync-payload.d.ts +32 -0
- package/dist/modules/kb-sync/sync-payload.d.ts.map +1 -0
- package/dist/modules/kb-sync/sync-payload.js +105 -0
- package/dist/modules/kb-sync/sync-payload.js.map +1 -0
- package/dist/modules/marketplace/git-http.routes.d.ts +42 -0
- package/dist/modules/marketplace/git-http.routes.d.ts.map +1 -0
- package/dist/modules/marketplace/git-http.routes.js +190 -0
- package/dist/modules/marketplace/git-http.routes.js.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.d.ts +23 -0
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.d.ts.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.js +59 -0
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.js.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade-codes.store.d.ts +59 -0
- package/dist/modules/marketplace/github-facade/github-facade-codes.store.d.ts.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade-codes.store.js +96 -0
- package/dist/modules/marketplace/github-facade/github-facade-codes.store.js.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade-credentials.service.d.ts +99 -0
- package/dist/modules/marketplace/github-facade/github-facade-credentials.service.d.ts.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade-credentials.service.js +147 -0
- package/dist/modules/marketplace/github-facade/github-facade-credentials.service.js.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade.routes.d.ts +47 -0
- package/dist/modules/marketplace/github-facade/github-facade.routes.d.ts.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade.routes.js +228 -0
- package/dist/modules/marketplace/github-facade/github-facade.routes.js.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade.service.d.ts +109 -0
- package/dist/modules/marketplace/github-facade/github-facade.service.d.ts.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade.service.js +172 -0
- package/dist/modules/marketplace/github-facade/github-facade.service.js.map +1 -0
- package/dist/modules/marketplace/github-facade/index.d.ts +6 -0
- package/dist/modules/marketplace/github-facade/index.d.ts.map +1 -0
- package/dist/modules/marketplace/github-facade/index.js +6 -0
- package/dist/modules/marketplace/github-facade/index.js.map +1 -0
- package/dist/modules/marketplace/index.d.ts +4 -0
- package/dist/modules/marketplace/index.d.ts.map +1 -0
- package/dist/modules/marketplace/index.js +4 -0
- package/dist/modules/marketplace/index.js.map +1 -0
- package/dist/modules/marketplace/marketplace-repo.service.d.ts +122 -0
- package/dist/modules/marketplace/marketplace-repo.service.d.ts.map +1 -0
- package/dist/modules/marketplace/marketplace-repo.service.js +300 -0
- package/dist/modules/marketplace/marketplace-repo.service.js.map +1 -0
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js +4 -0
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts +14 -8
- package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.js +13 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.js.map +1 -1
- package/dist/modules/mcp/oauth/oauth-state.d.ts +13 -2
- package/dist/modules/mcp/oauth/oauth-state.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/oauth-state.js +3 -1
- package/dist/modules/mcp/oauth/oauth-state.js.map +1 -1
- package/dist/modules/plugins/compile/compile-marketplace.d.ts +97 -0
- package/dist/modules/plugins/compile/compile-marketplace.d.ts.map +1 -0
- package/dist/modules/plugins/compile/compile-marketplace.js +240 -0
- package/dist/modules/plugins/compile/compile-marketplace.js.map +1 -0
- package/dist/modules/plugins/compile/marketplace-compiler.service.d.ts +66 -0
- package/dist/modules/plugins/compile/marketplace-compiler.service.d.ts.map +1 -0
- package/dist/modules/plugins/compile/marketplace-compiler.service.js +132 -0
- package/dist/modules/plugins/compile/marketplace-compiler.service.js.map +1 -0
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.d.ts +37 -0
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.d.ts.map +1 -0
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.js +135 -0
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.js.map +1 -0
- package/dist/modules/plugins/discovery/bundle-dialect/registry.d.ts +45 -0
- package/dist/modules/plugins/discovery/bundle-dialect/registry.d.ts.map +1 -0
- package/dist/modules/plugins/discovery/bundle-dialect/registry.js +147 -0
- package/dist/modules/plugins/discovery/bundle-dialect/registry.js.map +1 -0
- package/dist/modules/plugins/discovery/kb-plugin-source.d.ts +37 -0
- package/dist/modules/plugins/discovery/kb-plugin-source.d.ts.map +1 -0
- package/dist/modules/plugins/discovery/kb-plugin-source.js +107 -0
- package/dist/modules/plugins/discovery/kb-plugin-source.js.map +1 -0
- package/dist/modules/plugins/discovery/native.source.d.ts +15 -0
- package/dist/modules/plugins/discovery/native.source.d.ts.map +1 -0
- package/dist/modules/plugins/discovery/native.source.js +102 -0
- package/dist/modules/plugins/discovery/native.source.js.map +1 -0
- package/dist/modules/plugins/discovery/plugin-source.d.ts +96 -0
- package/dist/modules/plugins/discovery/plugin-source.d.ts.map +1 -0
- package/dist/modules/plugins/discovery/plugin-source.js +2 -0
- package/dist/modules/plugins/discovery/plugin-source.js.map +1 -0
- package/dist/modules/plugins/index.d.ts +10 -1
- package/dist/modules/plugins/index.d.ts.map +1 -1
- package/dist/modules/plugins/index.js +8 -0
- package/dist/modules/plugins/index.js.map +1 -1
- package/dist/modules/plugins/plugin-links.d.ts +66 -0
- package/dist/modules/plugins/plugin-links.d.ts.map +1 -0
- package/dist/modules/plugins/plugin-links.js +160 -0
- package/dist/modules/plugins/plugin-links.js.map +1 -0
- package/dist/modules/plugins/plugin-links.service.d.ts +114 -0
- package/dist/modules/plugins/plugin-links.service.d.ts.map +1 -0
- package/dist/modules/plugins/plugin-links.service.js +304 -0
- package/dist/modules/plugins/plugin-links.service.js.map +1 -0
- package/dist/modules/plugins/plugin-provision.service.d.ts +39 -3
- package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-provision.service.js +152 -52
- package/dist/modules/plugins/plugin-provision.service.js.map +1 -1
- package/dist/modules/plugins/plugin-rename.service.d.ts +69 -0
- package/dist/modules/plugins/plugin-rename.service.d.ts.map +1 -0
- package/dist/modules/plugins/plugin-rename.service.js +245 -0
- package/dist/modules/plugins/plugin-rename.service.js.map +1 -0
- package/dist/modules/plugins/plugins.contract.d.ts +27 -1
- package/dist/modules/plugins/plugins.contract.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.d.ts +7 -1
- package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.js +120 -12
- package/dist/modules/plugins/plugins.routes.js.map +1 -1
- package/dist/modules/plugins/plugins.service.d.ts +50 -3
- package/dist/modules/plugins/plugins.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.service.js +108 -47
- package/dist/modules/plugins/plugins.service.js.map +1 -1
- package/dist/modules/settings/deployment-settings.service.d.ts +9 -0
- package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
- package/dist/modules/settings/deployment-settings.service.js +82 -2
- package/dist/modules/settings/deployment-settings.service.js.map +1 -1
- package/dist/modules/settings/setup.routes.d.ts +26 -0
- package/dist/modules/settings/setup.routes.d.ts.map +1 -1
- package/dist/modules/settings/setup.routes.js +9 -1
- package/dist/modules/settings/setup.routes.js.map +1 -1
- package/dist/modules/skills/index.d.ts +1 -0
- 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.map +1 -1
- package/dist/modules/skills/pending-skills.service.js +9 -4
- package/dist/modules/skills/pending-skills.service.js.map +1 -1
- package/dist/modules/skills/skill-access-requests.routes.d.ts +38 -0
- package/dist/modules/skills/skill-access-requests.routes.d.ts.map +1 -0
- package/dist/modules/skills/skill-access-requests.routes.js +168 -0
- package/dist/modules/skills/skill-access-requests.routes.js.map +1 -0
- package/dist/modules/skills/skills.contract.d.ts +31 -0
- package/dist/modules/skills/skills.contract.d.ts.map +1 -1
- package/dist/modules/skills/skills.routes.d.ts +28 -3
- package/dist/modules/skills/skills.routes.d.ts.map +1 -1
- package/dist/modules/skills/skills.routes.js +42 -3
- package/dist/modules/skills/skills.routes.js.map +1 -1
- package/dist/modules/skills/skills.service.d.ts +4 -0
- package/dist/modules/skills/skills.service.d.ts.map +1 -1
- package/dist/modules/skills/skills.service.js +83 -22
- package/dist/modules/skills/skills.service.js.map +1 -1
- package/dist/modules/tool-auth/external-api-key.interface.d.ts +18 -1
- package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.interface.js +2 -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 +13 -4
- package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.js +26 -9
- package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.d.ts +36 -0
- package/dist/modules/tool-manuals/mcp-json-discovery.d.ts.map +1 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.js +130 -92
- package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts +6 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +18 -25
- package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +40 -2
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +227 -54
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/git/pull-request.service.d.ts +6 -0
- package/dist/modules/workflow/git/pull-request.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/pull-request.service.js +8 -0
- package/dist/modules/workflow/git/pull-request.service.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +52 -1
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +242 -9
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.d.ts +35 -0
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.d.ts.map +1 -0
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.js +121 -0
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.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 +46 -2
- package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
- package/dist/modules/workspace/startup/steps/template-files.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/template-files.step.js +112 -25
- package/dist/modules/workspace/startup/steps/template-files.step.js.map +1 -1
- package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.routes.js +9 -9
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +33 -0
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +110 -16
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/shared/domain-errors.d.ts +20 -0
- package/dist/shared/domain-errors.d.ts.map +1 -1
- package/dist/shared/domain-errors.js +29 -0
- package/dist/shared/domain-errors.js.map +1 -1
- package/dist/shared/fs-errors.d.ts +8 -0
- package/dist/shared/fs-errors.d.ts.map +1 -0
- package/dist/shared/fs-errors.js +11 -0
- package/dist/shared/fs-errors.js.map +1 -0
- package/dist/shared/fs-walk.d.ts +20 -6
- package/dist/shared/fs-walk.d.ts.map +1 -1
- package/dist/shared/fs-walk.js +35 -27
- package/dist/shared/fs-walk.js.map +1 -1
- package/dist/shared/kb-walk.d.ts +52 -0
- package/dist/shared/kb-walk.d.ts.map +1 -0
- package/dist/shared/kb-walk.js +76 -0
- package/dist/shared/kb-walk.js.map +1 -0
- package/dist/shared/path-order.d.ts +18 -0
- package/dist/shared/path-order.d.ts.map +1 -0
- package/dist/shared/path-order.js +35 -0
- package/dist/shared/path-order.js.map +1 -0
- package/dist/shared/ttl-cache.d.ts +28 -1
- package/dist/shared/ttl-cache.d.ts.map +1 -1
- package/dist/shared/ttl-cache.js +33 -1
- package/dist/shared/ttl-cache.js.map +1 -1
- package/kb-template/.bevelignore +1 -1
- package/kb-template/AGENTS.md +95 -30
- package/kb-template/Skills/.gitkeep +0 -0
- package/migrations/0005_github_facade.sql +25 -0
- package/migrations/meta/0005_snapshot.json +1717 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +6 -6
- package/src/__tests__/kb-layout-config.test.ts +110 -0
- package/src/core/__tests__/catalog-cache-invalidation.test.ts +146 -0
- package/src/core/catalog-cache-invalidation.ts +92 -0
- package/src/core/create-core-server.ts +123 -7
- package/src/core/create-core-services.ts +162 -41
- package/src/modules/access/__tests__/access-control.invalidate-race.test.ts +86 -0
- package/src/modules/access/__tests__/access-control.service.test.ts +19 -3
- package/src/modules/access/__tests__/access-plugin-principals.test.ts +368 -0
- package/src/modules/access/__tests__/groups-admin.service.test.ts +8 -0
- package/src/modules/access/access-control.interface.ts +20 -9
- package/src/modules/access/access-control.service.ts +240 -78
- package/src/modules/access/access.routes.ts +96 -14
- package/src/modules/access-model/__tests__/access-grammar.test.ts +18 -0
- package/src/modules/access-model/__tests__/plugin-token-canonical.test.ts +40 -0
- package/src/modules/access-model/access-grammar.ts +114 -2
- package/src/modules/access-model/group-files.ts +4 -0
- package/src/modules/access-model/plugin-principals.ts +137 -0
- package/src/modules/database/core-schema.ts +54 -0
- package/src/modules/kb-sync/__tests__/kb-sync.routes.test.ts +297 -0
- package/src/modules/kb-sync/__tests__/kb-sync.service.test.ts +295 -0
- package/src/modules/kb-sync/__tests__/sync-auth.test.ts +118 -0
- package/src/modules/kb-sync/__tests__/sync-payload.test.ts +148 -0
- package/src/modules/kb-sync/index.ts +19 -0
- package/src/modules/kb-sync/kb-sync.interface.ts +79 -0
- package/src/modules/kb-sync/kb-sync.routes.ts +197 -0
- package/src/modules/kb-sync/kb-sync.service.ts +201 -0
- package/src/modules/kb-sync/sync-auth.ts +118 -0
- package/src/modules/kb-sync/sync-payload.ts +143 -0
- package/src/modules/marketplace/__tests__/github-facade.test.ts +443 -0
- package/src/modules/marketplace/__tests__/marketplace-git.test.ts +179 -0
- package/src/modules/marketplace/git-http.routes.ts +193 -0
- package/src/modules/marketplace/github-facade/github-facade-admin.routes.ts +78 -0
- package/src/modules/marketplace/github-facade/github-facade-codes.store.ts +145 -0
- package/src/modules/marketplace/github-facade/github-facade-credentials.service.ts +208 -0
- package/src/modules/marketplace/github-facade/github-facade.routes.ts +268 -0
- package/src/modules/marketplace/github-facade/github-facade.service.ts +221 -0
- package/src/modules/marketplace/github-facade/index.ts +25 -0
- package/src/modules/marketplace/index.ts +3 -0
- package/src/modules/marketplace/marketplace-repo.service.ts +337 -0
- package/src/modules/mcp/oauth/bevel-oauth-provider.ts +3 -0
- package/src/modules/mcp/oauth/oauth-consent.routes.ts +24 -2
- package/src/modules/mcp/oauth/oauth-state.ts +15 -3
- package/src/modules/plugins/__tests__/bundle-dialect.e2e.test.ts +163 -0
- package/src/modules/plugins/__tests__/compile-marketplace.test.ts +294 -0
- package/src/modules/plugins/__tests__/plugin-index.service.test.ts +56 -12
- package/src/modules/plugins/__tests__/plugin-links.service.test.ts +287 -0
- package/src/modules/plugins/__tests__/plugin-provision.service.test.ts +205 -5
- package/src/modules/plugins/__tests__/plugin-rename.service.test.ts +394 -0
- package/src/modules/plugins/__tests__/plugins.routes.test.ts +80 -33
- package/src/modules/plugins/compile/compile-marketplace.ts +403 -0
- package/src/modules/plugins/compile/marketplace-compiler.service.ts +150 -0
- package/src/modules/plugins/discovery/__tests__/kb-plugin-source.test.ts +329 -0
- package/src/modules/plugins/discovery/bundle-dialect/bundle.source.ts +140 -0
- package/src/modules/plugins/discovery/bundle-dialect/registry.ts +175 -0
- package/src/modules/plugins/discovery/kb-plugin-source.ts +114 -0
- package/src/modules/plugins/discovery/native.source.ts +122 -0
- package/src/modules/plugins/discovery/plugin-source.ts +99 -0
- package/src/modules/plugins/index.ts +10 -0
- package/src/modules/plugins/plugin-links.service.ts +363 -0
- package/src/modules/plugins/plugin-links.ts +184 -0
- package/src/modules/plugins/plugin-provision.service.ts +160 -54
- package/src/modules/plugins/plugin-rename.service.ts +298 -0
- package/src/modules/plugins/plugins.contract.ts +28 -1
- package/src/modules/plugins/plugins.routes.ts +128 -11
- package/src/modules/plugins/plugins.service.ts +106 -53
- package/src/modules/settings/__tests__/deployment-settings.service.test.ts +57 -0
- package/src/modules/settings/__tests__/setup.routes.sync-status.test.ts +77 -0
- package/src/modules/settings/deployment-settings.service.ts +95 -2
- package/src/modules/settings/setup.routes.ts +24 -0
- package/src/modules/skills/__tests__/skill-access-requests.routes.test.ts +128 -0
- package/src/modules/skills/__tests__/skills.service.test.ts +85 -0
- package/src/modules/skills/index.ts +1 -0
- package/src/modules/skills/pending-skills.service.ts +7 -5
- package/src/modules/skills/skill-access-requests.routes.ts +199 -0
- package/src/modules/skills/skills.contract.ts +32 -0
- package/src/modules/skills/skills.routes.ts +59 -3
- package/src/modules/skills/skills.service.ts +84 -22
- package/src/modules/tool-auth/external-api-key.interface.ts +20 -1
- package/src/modules/tool-auth/external-api-key.service.ts +31 -13
- package/src/modules/tool-manuals/__tests__/mcp-json-discovery.test.ts +24 -1
- package/src/modules/tool-manuals/__tests__/tool-manuals.cli.test.ts +2 -0
- package/src/modules/tool-manuals/mcp-json-discovery.ts +154 -88
- package/src/modules/tool-manuals/tool-manuals.service.ts +14 -23
- package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +1 -1
- package/src/modules/workflow/__tests__/workflow.service.commitFileWhileLocked.test.ts +1 -0
- package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +2 -2
- package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +119 -2
- package/src/modules/workflow/__tests__/workflow.service.sync.test.ts +437 -0
- package/src/modules/workflow/git/__tests__/git.service.postMergePull.test.ts +3 -3
- package/src/modules/workflow/git/__tests__/git.service.pull.test.ts +47 -4
- package/src/modules/workflow/git/__tests__/git.service.syncFromRemote.test.ts +253 -0
- package/src/modules/workflow/git/git.service.ts +239 -56
- package/src/modules/workflow/git/pull-request.service.ts +9 -0
- package/src/modules/workflow/workflow.service.ts +260 -9
- package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +6 -7
- package/src/modules/workspace/__tests__/workspace.service.list-cloned.test.ts +93 -0
- package/src/modules/workspace/__tests__/workspace.service.missing-branch.test.ts +28 -0
- package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +42 -12
- package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +219 -4
- package/src/modules/workspace/startup/steps/plugin-manifests.step.ts +129 -0
- package/src/modules/workspace/startup/steps/seed-tree.ts +44 -2
- package/src/modules/workspace/startup/steps/template-files.step.ts +131 -27
- package/src/modules/workspace/workspace.routes.ts +9 -17
- package/src/modules/workspace/workspace.service.ts +107 -16
- package/src/shared/__tests__/filename.test.ts +0 -0
- package/src/shared/__tests__/fs-walk.test.ts +22 -1
- package/src/shared/__tests__/kb-walk.test.ts +107 -0
- package/src/shared/__tests__/path-order.test.ts +24 -0
- package/src/shared/__tests__/ttl-cache.test.ts +55 -0
- package/src/shared/domain-errors.ts +29 -0
- package/src/shared/fs-errors.ts +10 -0
- package/src/shared/fs-walk.ts +38 -23
- package/src/shared/kb-walk.ts +95 -0
- package/src/shared/path-order.ts +33 -0
- package/src/shared/ttl-cache.ts +33 -1
|
@@ -195,6 +195,121 @@ export function validatedVariables(raw: unknown): ToolVariable[] | null {
|
|
|
195
195
|
* materialization is a later phase — so a stdio server relying on them will
|
|
196
196
|
* fail to spawn until then; bare-command servers (`npx …`) work today.
|
|
197
197
|
*/
|
|
198
|
+
/**
|
|
199
|
+
* The ONE judgement of an `mcp.json` server entry — its name and its
|
|
200
|
+
* transport — shared by the manual scanner (which layers the platform's
|
|
201
|
+
* extension data and its reachability gate on top), the bundle dialect's
|
|
202
|
+
* registry (whose configs become entries) and the marketplace compiler
|
|
203
|
+
* (which ships only what a client could run). An entry that fails here fails
|
|
204
|
+
* everywhere, for one reason; an entry that passes is returned NORMALISED to
|
|
205
|
+
* the portable shape every consumer emits.
|
|
206
|
+
*/
|
|
207
|
+
export type McpEntryVerdict =
|
|
208
|
+
| { ok: true; transport: 'stdio'; entry: PortableStdioEntry }
|
|
209
|
+
| { ok: true; transport: 'streamable-http'; entry: PortableHttpEntry }
|
|
210
|
+
| { ok: false; reason: string };
|
|
211
|
+
|
|
212
|
+
/** A stdio server as a client launches it — every field already the type the launcher needs. */
|
|
213
|
+
export interface PortableStdioEntry {
|
|
214
|
+
type: 'stdio';
|
|
215
|
+
command: string;
|
|
216
|
+
args: string[];
|
|
217
|
+
env?: Record<string, string>;
|
|
218
|
+
cwd?: string;
|
|
219
|
+
}
|
|
220
|
+
/** An http server as a client dials it — an endpoint, never a credential. */
|
|
221
|
+
export interface PortableHttpEntry {
|
|
222
|
+
type: 'streamable-http';
|
|
223
|
+
url: string;
|
|
224
|
+
headers?: Record<string, string>;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* A record whose every value is a string, or null — no coercion, no cast.
|
|
229
|
+
* Built without a prototype: the keys are HTTP header and environment
|
|
230
|
+
* variable names, and a plain object would silently swallow one spelled
|
|
231
|
+
* `__proto__` (a prototype assignment, not a property) — every accepted key
|
|
232
|
+
* must come out the other side.
|
|
233
|
+
*/
|
|
234
|
+
function stringMap(v: unknown): Record<string, string> | null {
|
|
235
|
+
if (!isRecord(v)) return null;
|
|
236
|
+
const out: Record<string, string> = Object.create(null) as Record<string, string>;
|
|
237
|
+
for (const [k, val] of Object.entries(v)) {
|
|
238
|
+
if (typeof val !== 'string') return null;
|
|
239
|
+
out[k] = val;
|
|
240
|
+
}
|
|
241
|
+
return out;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function judgeMcpServerEntry(name: string, raw: unknown): McpEntryVerdict {
|
|
245
|
+
// The name rule (lowercase, alphanumeric first) also keeps every object
|
|
246
|
+
// prototype key — `__proto__`, `constructor` — out of the plain-object
|
|
247
|
+
// maps consumers build from these names.
|
|
248
|
+
if (!SERVER_NAME_RE.test(name)) {
|
|
249
|
+
return {
|
|
250
|
+
ok: false,
|
|
251
|
+
reason: 'the name is the secret namespace and route slug, so it must be lowercase alphanumeric with `_`/`-`',
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
if (!isRecord(raw) || typeof raw.type !== 'string') return { ok: false, reason: 'no transport type' };
|
|
255
|
+
if (raw.type === 'stdio') {
|
|
256
|
+
if (typeof raw.command !== 'string' || raw.command.length === 0) return { ok: false, reason: 'no command' };
|
|
257
|
+
const entry: PortableStdioEntry = { type: 'stdio', command: raw.command, args: [] };
|
|
258
|
+
if (raw.args !== undefined) {
|
|
259
|
+
// A launcher gets the strings it was given, never a stringified object.
|
|
260
|
+
if (!Array.isArray(raw.args) || !raw.args.every((a) => typeof a === 'string')) {
|
|
261
|
+
return { ok: false, reason: 'args must be a list of strings' };
|
|
262
|
+
}
|
|
263
|
+
entry.args = raw.args;
|
|
264
|
+
}
|
|
265
|
+
if (raw.env !== undefined) {
|
|
266
|
+
const env = stringMap(raw.env);
|
|
267
|
+
if (env === null) return { ok: false, reason: 'env must be a map of strings' };
|
|
268
|
+
entry.env = env;
|
|
269
|
+
}
|
|
270
|
+
if (raw.cwd !== undefined) {
|
|
271
|
+
if (typeof raw.cwd !== 'string') return { ok: false, reason: 'cwd must be a string' };
|
|
272
|
+
entry.cwd = raw.cwd;
|
|
273
|
+
}
|
|
274
|
+
return { ok: true, transport: 'stdio', entry };
|
|
275
|
+
}
|
|
276
|
+
if (raw.type === 'sse') {
|
|
277
|
+
// The pinned `@utcp/mcp` speaks `stdio` and streamable `http` — there
|
|
278
|
+
// is no sse transport in its schema, so a declaration claiming one either
|
|
279
|
+
// fails validation or, worse, dials a handshake the server does not
|
|
280
|
+
// speak. Refusing names the fix; silently rebuilding as http used to
|
|
281
|
+
// configure exactly that wrong handshake.
|
|
282
|
+
return {
|
|
283
|
+
ok: false,
|
|
284
|
+
reason: 'the MCP client has no `sse` transport — declare the server as `streamable-http` if it supports it',
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
if (raw.type === 'streamable-http') {
|
|
288
|
+
if (typeof raw.url !== 'string' || raw.url.length === 0) return { ok: false, reason: 'no url' };
|
|
289
|
+
let parsed: URL;
|
|
290
|
+
try {
|
|
291
|
+
parsed = new URL(raw.url);
|
|
292
|
+
} catch {
|
|
293
|
+
return { ok: false, reason: 'url must be http(s)' };
|
|
294
|
+
}
|
|
295
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') return { ok: false, reason: 'url must be http(s)' };
|
|
296
|
+
// A portable entry is an endpoint. A credential belongs in the vault and
|
|
297
|
+
// reaches a client through the platform, never verbatim in a config that
|
|
298
|
+
// is compiled into a marketplace and cloned onto every member's machine.
|
|
299
|
+
if (parsed.username || parsed.password) return { ok: false, reason: 'url must not carry credentials' };
|
|
300
|
+
const entry: PortableHttpEntry = { type: 'streamable-http', url: raw.url };
|
|
301
|
+
if (raw.headers !== undefined) {
|
|
302
|
+
const headers = stringMap(raw.headers);
|
|
303
|
+
if (headers === null) return { ok: false, reason: 'headers must be a map of strings' };
|
|
304
|
+
entry.headers = headers;
|
|
305
|
+
}
|
|
306
|
+
return { ok: true, transport: 'streamable-http', entry };
|
|
307
|
+
}
|
|
308
|
+
// Unknown transport: the spec says an unknown `type` invalidates the
|
|
309
|
+
// ENTRY, not the file — the caller skips it and keeps its siblings.
|
|
310
|
+
return { ok: false, reason: `unknown type "${raw.type}"` };
|
|
311
|
+
}
|
|
312
|
+
|
|
198
313
|
export function descriptorsFromMcpJson(
|
|
199
314
|
pluginFolder: string,
|
|
200
315
|
mcpJsonText: string,
|
|
@@ -227,18 +342,14 @@ export function descriptorsFromMcpJson(
|
|
|
227
342
|
const mcpJsonPath = `${PLUGINS_DIR}/${pluginFolder}/${PLUGIN_MCP_FILE}`;
|
|
228
343
|
|
|
229
344
|
const out: ToolManualDescriptor[] = [];
|
|
230
|
-
for (const [name,
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
'namespace and route slug, so it must be lowercase alphanumeric with `_`/`-`.',
|
|
235
|
-
);
|
|
236
|
-
continue;
|
|
237
|
-
}
|
|
238
|
-
if (!isRecord(raw) || typeof raw.type !== 'string') {
|
|
239
|
-
console.warn(`[tool-manuals] skipping mcp server "${name}" in ${mcpJsonPath}: no transport type.`);
|
|
345
|
+
for (const [name, rawEntry] of Object.entries(mcp.mcpServers)) {
|
|
346
|
+
const verdict = judgeMcpServerEntry(name, rawEntry);
|
|
347
|
+
if (!verdict.ok) {
|
|
348
|
+
console.warn(`[tool-manuals] skipping mcp server "${name}" in ${mcpJsonPath}: ${verdict.reason}.`);
|
|
240
349
|
continue;
|
|
241
350
|
}
|
|
351
|
+
// Narrowed by the verdict: an entry that passed is a record.
|
|
352
|
+
const raw = rawEntry as Record<string, unknown>;
|
|
242
353
|
// The extension entry is knowledge-base content too — same zero-trust
|
|
243
354
|
// parse as the rest: a non-object entry reads as "no extension data".
|
|
244
355
|
const ext: HexisMcpServerExtension = isRecord(extensions[name])
|
|
@@ -274,91 +385,46 @@ export function descriptorsFromMcpJson(
|
|
|
274
385
|
...(variables.length > 0 ? { variables } : {}),
|
|
275
386
|
};
|
|
276
387
|
|
|
277
|
-
if (
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
continue;
|
|
281
|
-
}
|
|
282
|
-
out.push({
|
|
283
|
-
...shared,
|
|
284
|
-
remote: false,
|
|
285
|
-
stdio: {
|
|
286
|
-
command: raw.command,
|
|
287
|
-
args: Array.isArray(raw.args) ? raw.args.map(String) : [],
|
|
288
|
-
env: isRecord(raw.env) ? (raw.env as Record<string, string>) : undefined,
|
|
289
|
-
cwd: typeof raw.cwd === 'string' ? raw.cwd : undefined,
|
|
290
|
-
},
|
|
291
|
-
});
|
|
292
|
-
continue;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
if (raw.type === 'sse') {
|
|
296
|
-
// The pinned `@utcp/mcp` speaks `stdio` and streamable `http` — there
|
|
297
|
-
// is no sse transport in its schema, so a template claiming one either
|
|
298
|
-
// fails validation or, worse, dials a handshake the server does not
|
|
299
|
-
// speak. Refusing here names the fix; silently rebuilding as http used
|
|
300
|
-
// to configure exactly that wrong handshake.
|
|
301
|
-
console.warn(
|
|
302
|
-
`[tool-manuals] skipping mcp server "${name}" in ${mcpJsonPath}: the MCP client has no \`sse\` transport — declare the server as \`streamable-http\` if it supports it.`,
|
|
303
|
-
);
|
|
388
|
+
if (verdict.transport === 'stdio') {
|
|
389
|
+
const { command, args, env, cwd } = verdict.entry;
|
|
390
|
+
out.push({ ...shared, remote: false, stdio: { command, args, env, cwd } });
|
|
304
391
|
continue;
|
|
305
392
|
}
|
|
306
393
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
// user's own machine ever dials it.
|
|
317
|
-
// Scheme first, for EVERYONE: `local: true` exempts a server from the
|
|
318
|
-
// private-network reachability policy, not from being http(s) at all.
|
|
319
|
-
let schemeOk = false;
|
|
394
|
+
const url = verdict.entry.url;
|
|
395
|
+
// Remote-capable servers get the same SSRF gate `.tool` urls pass —
|
|
396
|
+
// otherwise mcp.json becomes the way to point the backend at loopback,
|
|
397
|
+
// private ranges, or the cloud metadata endpoint. A `local: true` entry
|
|
398
|
+
// is exempt because loopback is exactly what local MEANS, and only the
|
|
399
|
+
// user's own machine ever dials it. (The scheme itself was judged for
|
|
400
|
+
// everyone above: `local: true` exempts a server from the reachability
|
|
401
|
+
// policy, not from being http(s) at all.)
|
|
402
|
+
if (ext.local !== true) {
|
|
320
403
|
try {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
console.warn(`[tool-manuals] skipping mcp server "${name}" in ${mcpJsonPath}: url must be http(s).`);
|
|
404
|
+
assertSafeFetchUrl(url, { label: `mcp server "${name}" url` });
|
|
405
|
+
} catch (err) {
|
|
406
|
+
console.warn(
|
|
407
|
+
`[tool-manuals] skipping mcp server "${name}" in ${mcpJsonPath}: ` +
|
|
408
|
+
`${err instanceof Error ? err.message : String(err)} (declare it \`local: true\` if it is deliberately private).`,
|
|
409
|
+
);
|
|
328
410
|
continue;
|
|
329
411
|
}
|
|
330
|
-
if (ext.local !== true) {
|
|
331
|
-
try {
|
|
332
|
-
assertSafeFetchUrl(raw.url, { label: `mcp server "${name}" url` });
|
|
333
|
-
} catch (err) {
|
|
334
|
-
console.warn(
|
|
335
|
-
`[tool-manuals] skipping mcp server "${name}" in ${mcpJsonPath}: ` +
|
|
336
|
-
`${err instanceof Error ? err.message : String(err)} (declare it \`local: true\` if it is deliberately private).`,
|
|
337
|
-
);
|
|
338
|
-
continue;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
// Extension headers (auth, `${VAR}` refs) win over mcp.json's literal
|
|
342
|
-
// ones on a key collision: the portable file cannot carry a credential,
|
|
343
|
-
// so when both name the same header the extension is the operative one.
|
|
344
|
-
// Both sides pass the isRecord gate — spreading a malformed non-object
|
|
345
|
-
// value (a string, say) would scatter its indices into header keys.
|
|
346
|
-
const headers = {
|
|
347
|
-
...(isRecord(raw.headers) ? (raw.headers as Record<string, string>) : {}),
|
|
348
|
-
...(isRecord(ext.headers) ? (ext.headers as Record<string, string>) : {}),
|
|
349
|
-
};
|
|
350
|
-
out.push({
|
|
351
|
-
...shared,
|
|
352
|
-
url: raw.url,
|
|
353
|
-
...(Object.keys(headers).length > 0 ? { headers } : {}),
|
|
354
|
-
...(ext.local === true ? { remote: false } : {}),
|
|
355
|
-
});
|
|
356
|
-
continue;
|
|
357
412
|
}
|
|
358
|
-
|
|
359
|
-
//
|
|
360
|
-
//
|
|
361
|
-
|
|
413
|
+
// Extension headers (auth, `${VAR}` refs) win over mcp.json's literal
|
|
414
|
+
// ones on a key collision: the portable file cannot carry a credential,
|
|
415
|
+
// so when both name the same header the extension is the operative one.
|
|
416
|
+
// Both sides pass the isRecord gate — spreading a malformed non-object
|
|
417
|
+
// value (a string, say) would scatter its indices into header keys.
|
|
418
|
+
const headers = {
|
|
419
|
+
...(verdict.entry.headers ?? {}),
|
|
420
|
+
...(isRecord(ext.headers) ? (ext.headers as Record<string, string>) : {}),
|
|
421
|
+
};
|
|
422
|
+
out.push({
|
|
423
|
+
...shared,
|
|
424
|
+
url,
|
|
425
|
+
...(Object.keys(headers).length > 0 ? { headers } : {}),
|
|
426
|
+
...(ext.local === true ? { remote: false } : {}),
|
|
427
|
+
});
|
|
362
428
|
}
|
|
363
429
|
return out;
|
|
364
430
|
}
|
|
@@ -15,10 +15,10 @@ import {
|
|
|
15
15
|
import {
|
|
16
16
|
DEFAULT_BRANCH,
|
|
17
17
|
PLUGINS_DIR,
|
|
18
|
-
PLUGIN_MANIFEST_FILE,
|
|
19
|
-
PLUGIN_MCP_FILE,
|
|
20
18
|
} from '@bevel-software/platform-shared';
|
|
21
19
|
import { descriptorsFromMcpJson } from './mcp-json-discovery.js';
|
|
20
|
+
import type { PluginSource } from '../plugins/discovery/plugin-source.js';
|
|
21
|
+
import { KbPluginSource } from '../plugins/discovery/kb-plugin-source.js';
|
|
22
22
|
import type { WorkspaceService } from '../workspace/workspace.service.js';
|
|
23
23
|
import { workspaceIdForBranch } from '../../shared/workspace-id.js';
|
|
24
24
|
import type { IAccessControl } from '../access/access-control.interface.js';
|
|
@@ -150,6 +150,8 @@ export class ToolManualService implements IToolManualService {
|
|
|
150
150
|
private readonly accessControl: IAccessControl,
|
|
151
151
|
private readonly kbDirName: string,
|
|
152
152
|
now: () => number = Date.now,
|
|
153
|
+
/** Where plugins (and their MCP servers) come from — native manifests unless a dialect is configured. */
|
|
154
|
+
private readonly source: PluginSource = new KbPluginSource(),
|
|
153
155
|
) {
|
|
154
156
|
this.cache = new TtlCache(CACHE_TTL_MS, now);
|
|
155
157
|
}
|
|
@@ -425,12 +427,15 @@ export class ToolManualService implements IToolManualService {
|
|
|
425
427
|
private async scan(): Promise<ToolManualDescriptor[]> {
|
|
426
428
|
const cached = this.cache.get();
|
|
427
429
|
if (cached) return cached;
|
|
430
|
+
// See `TtlCache.begin`: taken before the read so an `invalidate()` that
|
|
431
|
+
// lands mid-scan discards this result instead of being overwritten by it.
|
|
432
|
+
const token = this.cache.begin();
|
|
428
433
|
const manuals = await this.scanDisk();
|
|
429
434
|
await this.decorateMcpOAuth(manuals);
|
|
430
435
|
// AFTER the oauth decoration, so an injected `${MCP_OAUTH}` header ref is
|
|
431
436
|
// already declared and isn't re-surfaced as a bare admin key.
|
|
432
437
|
for (const m of manuals) this.surfaceReferencedVariables(m);
|
|
433
|
-
this.cache.set(manuals);
|
|
438
|
+
this.cache.set(manuals, token);
|
|
434
439
|
return manuals;
|
|
435
440
|
}
|
|
436
441
|
|
|
@@ -654,27 +659,13 @@ export class ToolManualService implements IToolManualService {
|
|
|
654
659
|
// shape. Listed BEFORE the `.tool` files: on a name collision (a legacy
|
|
655
660
|
// mcp `.tool` the migration has not converted yet), the shared dedup keeps
|
|
656
661
|
// the first occurrence, and the authoritative source must be the one kept.
|
|
662
|
+
// The plugins (and their mcp.json text) come from the configured source, so
|
|
663
|
+
// a dialect that expands servers from a registry lands here unchanged.
|
|
657
664
|
const parsed: ToolManualDescriptor[] = [];
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
.map((e) => e.name)
|
|
663
|
-
.sort();
|
|
664
|
-
} catch {
|
|
665
|
-
/* no Plugins/ root — nothing to scan */
|
|
666
|
-
}
|
|
667
|
-
for (const folder of pluginFolders) {
|
|
668
|
-
let mcpJson: string;
|
|
669
|
-
try {
|
|
670
|
-
mcpJson = await fs.readFile(path.join(root, folder, PLUGIN_MCP_FILE), 'utf-8');
|
|
671
|
-
} catch {
|
|
672
|
-
continue; // no mcp.json is the common case, not an error
|
|
673
|
-
}
|
|
674
|
-
const pluginJson = await fs
|
|
675
|
-
.readFile(path.join(root, folder, PLUGIN_MANIFEST_FILE), 'utf-8')
|
|
676
|
-
.catch(() => null);
|
|
677
|
-
parsed.push(...descriptorsFromMcpJson(folder, mcpJson, pluginJson));
|
|
665
|
+
const discovered = await this.source.discover(kbRoot);
|
|
666
|
+
for (const plugin of discovered.plugins) {
|
|
667
|
+
if (plugin.mcpJsonText === null) continue; // no servers is the common case, not an error
|
|
668
|
+
parsed.push(...descriptorsFromMcpJson(plugin.relFolder, plugin.mcpJsonText, plugin.manifestText));
|
|
678
669
|
}
|
|
679
670
|
|
|
680
671
|
for (const f of files) {
|
|
@@ -80,7 +80,7 @@ describe('mergeChangeRequest — roles.yaml preservation guard', () => {
|
|
|
80
80
|
|
|
81
81
|
const git = {
|
|
82
82
|
fetch: vi.fn().mockResolvedValue(undefined),
|
|
83
|
-
pull: vi.fn().mockResolvedValue(
|
|
83
|
+
pull: vi.fn().mockResolvedValue({ treeChanged: true }),
|
|
84
84
|
// Resolve roles.yaml at origin/<ref> from the per-test content.
|
|
85
85
|
readFileAtRef: vi.fn(async (_ws: string, ref: string) =>
|
|
86
86
|
ref === `origin/${BASE}` ? opts.baseRoles : ref === `origin/${HEAD}` ? opts.headRoles : null,
|
|
@@ -182,7 +182,7 @@ function makeGit(): GitService {
|
|
|
182
182
|
commit: vi.fn(),
|
|
183
183
|
push: vi.fn(),
|
|
184
184
|
fetch: vi.fn(),
|
|
185
|
-
pull: vi.fn(),
|
|
185
|
+
pull: vi.fn().mockResolvedValue({ treeChanged: true }),
|
|
186
186
|
diffStat: vi.fn(),
|
|
187
187
|
pendingChanges: vi.fn(),
|
|
188
188
|
resolveForkBase: vi.fn(),
|
|
@@ -559,7 +559,7 @@ describe('WorkflowService — revertChangeRequestFile / closeEmptyChangeRequest'
|
|
|
559
559
|
|
|
560
560
|
function makeRevertGit(overrides: Record<string, unknown> = {}): GitService {
|
|
561
561
|
return Object.assign(makeGit(), {
|
|
562
|
-
pull: vi.fn().mockResolvedValue(
|
|
562
|
+
pull: vi.fn().mockResolvedValue({ treeChanged: true }),
|
|
563
563
|
changedPathsForPr: vi.fn().mockResolvedValue(['Docs/a.md', 'Docs/b.md']),
|
|
564
564
|
mergeBaseForPr: vi.fn().mockResolvedValue('mb-sha'),
|
|
565
565
|
restorePathFromRef: vi.fn().mockResolvedValue(undefined),
|
|
@@ -8,9 +8,10 @@ import type { IAccessControl } from '../../access/access-control.interface.js';
|
|
|
8
8
|
import { FileLockService } from '../file-lock.service.js';
|
|
9
9
|
import { PendingCommitsService } from '../pending-commits.service.js';
|
|
10
10
|
import { WorkflowEventBus } from '../event-bus.js';
|
|
11
|
-
import { WorkflowService } from '../workflow.service.js';
|
|
11
|
+
import { WorkflowService, syncConflictMessage } from '../workflow.service.js';
|
|
12
12
|
import type { Database } from '../../database/connection.js';
|
|
13
13
|
import {
|
|
14
|
+
PullRebaseConflictError,
|
|
14
15
|
PullRebaseConflictError,
|
|
15
16
|
PushNeedsAgentResolutionError,
|
|
16
17
|
WorkflowValidationError,
|
|
@@ -87,7 +88,7 @@ function makePending(): PendingCommitsService {
|
|
|
87
88
|
function makeGit(overrides: Partial<{
|
|
88
89
|
commitFile: Change | null;
|
|
89
90
|
pushBehavior: 'ok' | 'nff' | 'auth-fail';
|
|
90
|
-
pullBehavior: 'ok' | 'fail';
|
|
91
|
+
pullBehavior: 'ok' | 'fail' | 'conflict';
|
|
91
92
|
pushAfterPullBehavior: 'ok' | 'fail';
|
|
92
93
|
hasUnpushedCommits: boolean;
|
|
93
94
|
}> = {}): GitService {
|
|
@@ -120,6 +121,8 @@ function makeGit(overrides: Partial<{
|
|
|
120
121
|
}),
|
|
121
122
|
pull: vi.fn().mockImplementation(async () => {
|
|
122
123
|
if (pullBehavior === 'fail') throw new Error('git pull failed: merge conflict');
|
|
124
|
+
if (pullBehavior === 'conflict') throw new PullRebaseConflictError('feat/x', ['foo.md'], 'CONFLICT (content)');
|
|
125
|
+
return { treeChanged: true };
|
|
123
126
|
}),
|
|
124
127
|
} as unknown as GitService;
|
|
125
128
|
}
|
|
@@ -460,6 +463,29 @@ describe('WorkflowService.runPendingCommit — worker entry point', () => {
|
|
|
460
463
|
]);
|
|
461
464
|
});
|
|
462
465
|
|
|
466
|
+
it('a rebase CONFLICT on the recovery path raises the banner WITH its files, like the remote sync does', async () => {
|
|
467
|
+
// The banner keeps the latest failure per branch. The remote sync raises
|
|
468
|
+
// the conflict variant (files as links); if this path then raised the
|
|
469
|
+
// generic variant for the same conflict, the retry would take the links
|
|
470
|
+
// away — seen on staging. Every path names the files.
|
|
471
|
+
const git = makeGit({
|
|
472
|
+
commitFile: null,
|
|
473
|
+
hasUnpushedCommits: true,
|
|
474
|
+
pushBehavior: 'nff',
|
|
475
|
+
pullBehavior: 'conflict',
|
|
476
|
+
});
|
|
477
|
+
const svc = makeFacade(git, makeFileLocks(USER.id), makePending(), events);
|
|
478
|
+
await expect(svc.runPendingCommit('ws-1', 'feat/x', 'foo.md', USER)).rejects.toBeInstanceOf(
|
|
479
|
+
PushNeedsAgentResolutionError,
|
|
480
|
+
);
|
|
481
|
+
const failed = emitSpy.mock.calls.map((c) => c[0] as Record<string, unknown>).find((e) => e.kind === 'git-sync-failed');
|
|
482
|
+
expect(failed).toMatchObject({
|
|
483
|
+
branch: 'feat/x',
|
|
484
|
+
conflictedPaths: ['foo.md'],
|
|
485
|
+
reason: syncConflictMessage('feat/x', ['foo.md']),
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
|
|
463
489
|
it('recovers from non-fast-forward push via pull --rebase + retry, emits file-changed exactly once', async () => {
|
|
464
490
|
// Teammate pushed to the same branch between our commit and our
|
|
465
491
|
// push. The cooperative path pulls + retries — this is the case
|
|
@@ -750,3 +776,94 @@ describe('WorkflowService.updateFromRemote — pull-conflict recovery dispatch',
|
|
|
750
776
|
await expect(svc.updateFromRemote('main', USER)).rejects.toBe(CONFLICT);
|
|
751
777
|
});
|
|
752
778
|
});
|
|
779
|
+
|
|
780
|
+
describe('WorkflowService.updateFromRemote — the tree-change announcement', () => {
|
|
781
|
+
/**
|
|
782
|
+
* The announcement under test is what keeps the DEFAULT branch's catalog
|
|
783
|
+
* caches (skills, tool manuals, plugin index) honest: a pull is the one way
|
|
784
|
+
* a tree changes without passing the write routes, so `pullWorkspace` owes
|
|
785
|
+
* the process an `fs-tree-changed` — and owes it ONLY when the pull moved
|
|
786
|
+
* HEAD, on the default branch, after the pull resolved. Asserted through
|
|
787
|
+
* `updateFromRemote`, the public verb that wraps `pullWorkspace`.
|
|
788
|
+
*/
|
|
789
|
+
const DEFAULT_WS = 'target-company-state'; // == the test env's DEFAULT_BRANCH
|
|
790
|
+
|
|
791
|
+
function makePullingGit(result: { treeChanged: boolean } | Error): {
|
|
792
|
+
git: GitService;
|
|
793
|
+
pullSettled: () => boolean;
|
|
794
|
+
} {
|
|
795
|
+
let settled = false;
|
|
796
|
+
const git = {
|
|
797
|
+
pull: vi.fn().mockImplementation(async () => {
|
|
798
|
+
// A microtask gap, so an emit issued before the await would be
|
|
799
|
+
// observably premature rather than coincidentally ordered.
|
|
800
|
+
await Promise.resolve();
|
|
801
|
+
settled = true;
|
|
802
|
+
if (result instanceof Error) throw result;
|
|
803
|
+
return result;
|
|
804
|
+
}),
|
|
805
|
+
} as unknown as GitService;
|
|
806
|
+
return { git, pullSettled: () => settled };
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
function treeChangedEvents(emitSpy: MockInstance): unknown[] {
|
|
810
|
+
return emitSpy.mock.calls
|
|
811
|
+
.map((c) => c[0] as { kind: string })
|
|
812
|
+
.filter((e) => e.kind === 'fs-tree-changed');
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
it('emits fs-tree-changed for the default workspace once the pull has changed the tree', async () => {
|
|
816
|
+
const events = new WorkflowEventBus();
|
|
817
|
+
const emitSpy = vi.spyOn(events, 'emit');
|
|
818
|
+
const { git, pullSettled } = makePullingGit({ treeChanged: true });
|
|
819
|
+
let settledAtEmit: boolean | undefined;
|
|
820
|
+
emitSpy.mockImplementation(() => {
|
|
821
|
+
settledAtEmit = pullSettled();
|
|
822
|
+
});
|
|
823
|
+
const svc = makeFacade(git, makeFileLocks(USER.id), makePending(), events);
|
|
824
|
+
|
|
825
|
+
await svc.updateFromRemote(DEFAULT_WS, USER);
|
|
826
|
+
|
|
827
|
+
expect(treeChangedEvents(emitSpy)).toEqual([
|
|
828
|
+
{ kind: 'fs-tree-changed', workspaceId: DEFAULT_WS, branch: DEFAULT_WS },
|
|
829
|
+
]);
|
|
830
|
+
// After the pull RESOLVED — an emit for a pull that later failed would
|
|
831
|
+
// make other clients refetch a tree that never changed.
|
|
832
|
+
expect(settledAtEmit).toBe(true);
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
it('stays silent when the pull found nothing new (treeChanged: false)', async () => {
|
|
836
|
+
const events = new WorkflowEventBus();
|
|
837
|
+
const emitSpy = vi.spyOn(events, 'emit');
|
|
838
|
+
const { git } = makePullingGit({ treeChanged: false });
|
|
839
|
+
const svc = makeFacade(git, makeFileLocks(USER.id), makePending(), events);
|
|
840
|
+
|
|
841
|
+
await svc.updateFromRemote(DEFAULT_WS, USER);
|
|
842
|
+
|
|
843
|
+
// An "already up to date" sync must not drop the catalogs or make every
|
|
844
|
+
// attached browser refetch its file tree.
|
|
845
|
+
expect(treeChangedEvents(emitSpy)).toEqual([]);
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
it('stays silent for a non-default workspace even when the tree changed', async () => {
|
|
849
|
+
const events = new WorkflowEventBus();
|
|
850
|
+
const emitSpy = vi.spyOn(events, 'emit');
|
|
851
|
+
const { git } = makePullingGit({ treeChanged: true });
|
|
852
|
+
const svc = makeFacade(git, makeFileLocks(USER.id), makePending(), events);
|
|
853
|
+
|
|
854
|
+
await svc.updateFromRemote(encodeURIComponent('alice/draft'), USER);
|
|
855
|
+
|
|
856
|
+
expect(treeChangedEvents(emitSpy)).toEqual([]);
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
it('stays silent when the pull throws', async () => {
|
|
860
|
+
const events = new WorkflowEventBus();
|
|
861
|
+
const emitSpy = vi.spyOn(events, 'emit');
|
|
862
|
+
const { git } = makePullingGit(new Error('git fetch failed: could not resolve host'));
|
|
863
|
+
const svc = makeFacade(git, makeFileLocks(USER.id), makePending(), events);
|
|
864
|
+
|
|
865
|
+
await expect(svc.updateFromRemote(DEFAULT_WS, USER)).rejects.toThrow();
|
|
866
|
+
|
|
867
|
+
expect(treeChangedEvents(emitSpy)).toEqual([]);
|
|
868
|
+
});
|
|
869
|
+
});
|