@bevel-software/platform-core-backend 0.26.0 → 0.28.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 +56 -5
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +33 -0
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +105 -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 +17 -0
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +2 -0
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +31 -0
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/access.routes.d.ts +4 -1
- package/dist/modules/access/access.routes.d.ts.map +1 -1
- package/dist/modules/access/access.routes.js +66 -7
- package/dist/modules/access/access.routes.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/access/roles-admin.service.d.ts +35 -6
- package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
- package/dist/modules/access/roles-admin.service.js +36 -9
- package/dist/modules/access/roles-admin.service.js.map +1 -1
- package/dist/modules/access/user-access-removal.service.js +1 -1
- package/dist/modules/access/user-access-removal.service.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-hooks.d.ts +46 -0
- package/dist/modules/auth/account-hooks.d.ts.map +1 -0
- package/dist/modules/auth/account-hooks.js +25 -0
- package/dist/modules/auth/account-hooks.js.map +1 -0
- package/dist/modules/auth/account-switch-guard.d.ts +15 -0
- package/dist/modules/auth/account-switch-guard.d.ts.map +1 -0
- package/dist/modules/auth/account-switch-guard.js +2 -0
- package/dist/modules/auth/account-switch-guard.js.map +1 -0
- package/dist/modules/auth/account-switch-lock.d.ts +34 -0
- package/dist/modules/auth/account-switch-lock.d.ts.map +1 -0
- package/dist/modules/auth/account-switch-lock.js +48 -0
- package/dist/modules/auth/account-switch-lock.js.map +1 -0
- package/dist/modules/auth/account.routes.d.ts +4 -1
- package/dist/modules/auth/account.routes.d.ts.map +1 -1
- package/dist/modules/auth/account.routes.js +110 -13
- 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/auth/auth.service.d.ts +101 -6
- package/dist/modules/auth/auth.service.d.ts.map +1 -1
- package/dist/modules/auth/auth.service.js +246 -51
- package/dist/modules/auth/auth.service.js.map +1 -1
- package/dist/modules/auth/password-hash.d.ts +5 -2
- package/dist/modules/auth/password-hash.d.ts.map +1 -1
- package/dist/modules/auth/password-hash.js +5 -2
- package/dist/modules/auth/password-hash.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/database/pg-error.d.ts +9 -0
- package/dist/modules/database/pg-error.d.ts.map +1 -0
- package/dist/modules/database/pg-error.js +17 -0
- package/dist/modules/database/pg-error.js.map +1 -0
- 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 +807 -107
- 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/__tests__/pg-scratch.ts +48 -0
- 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 +69 -3
- package/src/core/create-core-services.ts +151 -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.admin-root-floor.test.ts +1 -0
- package/src/modules/access/__tests__/access-control.service.test.ts +19 -0
- package/src/modules/access/__tests__/access.routes.admins.test.ts +203 -0
- package/src/modules/access/__tests__/access.routes.batch.test.ts +25 -5
- package/src/modules/access/__tests__/access.routes.folder-governs.test.ts +1 -0
- package/src/modules/access/__tests__/access.routes.group-grant.test.ts +1 -1
- package/src/modules/access/__tests__/access.routes.overrides.test.ts +1 -1
- package/src/modules/access/__tests__/access.routes.proposal-branch.test.ts +1 -0
- package/src/modules/access/__tests__/access.routes.prospective.test.ts +1 -0
- package/src/modules/access/__tests__/access.routes.revoke.test.ts +1 -1
- package/src/modules/access/__tests__/access.routes.unknown-email.test.ts +1 -0
- package/src/modules/access/__tests__/roles-admin.security.test.ts +1 -1
- package/src/modules/access/__tests__/roles-admin.service.test.ts +15 -2
- package/src/modules/access/__tests__/roles.routes.test.ts +132 -7
- package/src/modules/access/__tests__/user-access-removal.service.test.ts +2 -2
- package/src/modules/access/__tests__/users-db-double.ts +12 -3
- package/src/modules/access/access-control.interface.ts +23 -0
- package/src/modules/access/access-control.service.ts +35 -0
- package/src/modules/access/access.routes.ts +67 -6
- package/src/modules/access/directory-sync-bot.ts +1 -1
- package/src/modules/access/roles-admin.service.ts +51 -11
- package/src/modules/access/user-access-removal.service.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-deactivation.test.ts +145 -16
- package/src/modules/auth/__tests__/account-hooks.test.ts +57 -0
- package/src/modules/auth/__tests__/account-switch-lock.pg.test.ts +78 -0
- package/src/modules/auth/__tests__/account-switch-lock.test.ts +43 -0
- package/src/modules/auth/__tests__/account.routes.test.ts +387 -9
- package/src/modules/auth/__tests__/auth.service.created.pg.test.ts +69 -0
- package/src/modules/auth/__tests__/auth.service.test.ts +159 -1
- package/src/modules/auth/account-hooks.ts +69 -0
- package/src/modules/auth/account-switch-guard.ts +14 -0
- package/src/modules/auth/account-switch-lock.ts +58 -0
- package/src/modules/auth/account.routes.ts +122 -13
- package/src/modules/auth/auth.middleware.ts +1 -1
- package/src/modules/auth/auth.service.ts +268 -52
- package/src/modules/auth/password-hash.ts +5 -2
- 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/__tests__/pg-error.test.ts +21 -0
- package/src/modules/database/__tests__/pii-backfill.pg.test.ts +6 -27
- package/src/modules/database/core-schema.ts +33 -3
- package/src/modules/database/pg-error.ts +15 -0
- 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 +881 -113
- 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
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
import type { Server } from 'node:http';
|
|
2
|
+
import type { AddressInfo } from 'node:net';
|
|
3
|
+
import express from 'express';
|
|
4
|
+
import cors from 'cors';
|
|
5
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
6
|
+
import { createEmbedLinkRoutes, createEmbedRoutes, TOKEN_REQUIRED } from '../embed.routes.js';
|
|
7
|
+
import {
|
|
8
|
+
EmbedAccessError,
|
|
9
|
+
EmbedLockedError,
|
|
10
|
+
EmbedNodeNotFoundError,
|
|
11
|
+
EmbedTokenError,
|
|
12
|
+
} from '../embed.errors.js';
|
|
13
|
+
|
|
14
|
+
const TOKEN = 'a-valid-token';
|
|
15
|
+
|
|
16
|
+
let server: Server | null = null;
|
|
17
|
+
afterEach(async () => {
|
|
18
|
+
if (server) await new Promise<void>((resolve) => server!.close(() => resolve()));
|
|
19
|
+
server = null;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
function stubService(overrides: Record<string, unknown> = {}) {
|
|
23
|
+
return {
|
|
24
|
+
sharedSecretConfigured: vi.fn(() => true),
|
|
25
|
+
verifySharedSecret: vi.fn((s?: string) => s === 'shh'),
|
|
26
|
+
mintForUser: vi.fn(async () => ({ token: TOKEN, embedUrl: 'https://hexis.example/embed?token=' + TOKEN })),
|
|
27
|
+
mintToken: vi.fn(async () => ({ token: TOKEN, embedUrl: 'https://hexis.example/embed?token=' + TOKEN })),
|
|
28
|
+
loadFile: vi.fn(async (token: string) => {
|
|
29
|
+
if (token !== TOKEN) throw new EmbedTokenError();
|
|
30
|
+
return { nodeName: 'Thing', content: '# Thing', canWrite: true };
|
|
31
|
+
}),
|
|
32
|
+
readBytes: vi.fn(async () => ({ bytes: Buffer.from([1, 2, 3]), path: 'Data/x.png' })),
|
|
33
|
+
acquireLock: vi.fn(async () => ({ acquired: true })),
|
|
34
|
+
heartbeat: vi.fn(async () => undefined),
|
|
35
|
+
cancel: vi.fn(async () => undefined),
|
|
36
|
+
save: vi.fn(async () => undefined),
|
|
37
|
+
propose: vi.fn(async () => ({ branch: 'suggestions/a-1/knowledge', number: 7, url: '/change-requests/7' })),
|
|
38
|
+
linkAccount: vi.fn(async () => undefined),
|
|
39
|
+
listLinkedAccounts: vi.fn(async () => [{ atlassianAccountId: 'acc-1', createdAt: 0 }]),
|
|
40
|
+
unlinkAccount: vi.fn(async () => true),
|
|
41
|
+
...overrides,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The app as the server mounts it: the embed routes ahead of the JWT
|
|
47
|
+
* middleware, the link routes behind it, and a static catch-all last — the
|
|
48
|
+
* same order `create-core-server` uses, because the order is what makes the
|
|
49
|
+
* framing headers land before `index.html` is served.
|
|
50
|
+
*/
|
|
51
|
+
async function serve(
|
|
52
|
+
service: ReturnType<typeof stubService>,
|
|
53
|
+
opts: { session?: boolean; restrictFraming?: boolean; appWideCors?: boolean } = {},
|
|
54
|
+
) {
|
|
55
|
+
const app = express();
|
|
56
|
+
// The server's own CORS, as `create-core-server` mounts it ahead of every
|
|
57
|
+
// route: any origin reflected, credentials allowed, preflights answered.
|
|
58
|
+
if (opts.appWideCors) app.use(cors({ origin: true, credentials: true }));
|
|
59
|
+
app.use(express.json());
|
|
60
|
+
// A deployment (or a later change to this app) that stamps a framing
|
|
61
|
+
// restriction on EVERY response, ahead of the embed routes — the case the
|
|
62
|
+
// embed page's own header removal exists for.
|
|
63
|
+
if (opts.restrictFraming) {
|
|
64
|
+
app.use((_req, res, next) => {
|
|
65
|
+
res.setHeader('X-Frame-Options', 'SAMEORIGIN');
|
|
66
|
+
res.setHeader('Content-Security-Policy', "frame-ancestors 'self'");
|
|
67
|
+
next();
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
app.use(createEmbedRoutes(service as never));
|
|
71
|
+
const authMiddleware: express.RequestHandler = (req, res, next) => {
|
|
72
|
+
if (!opts.session) {
|
|
73
|
+
res.status(401).json({ error: 'Unauthenticated' });
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
req.userId = 'u-1';
|
|
77
|
+
next();
|
|
78
|
+
};
|
|
79
|
+
app.use('/api', authMiddleware, createEmbedLinkRoutes(service as never));
|
|
80
|
+
app.get('{*path}', (_req, res) => res.type('html').send('<!doctype html><title>SPA</title>'));
|
|
81
|
+
|
|
82
|
+
server = app.listen(0);
|
|
83
|
+
await new Promise<void>((r) => server!.once('listening', () => r()));
|
|
84
|
+
const base = `http://127.0.0.1:${(server!.address() as AddressInfo).port}`;
|
|
85
|
+
return {
|
|
86
|
+
base,
|
|
87
|
+
get: (path: string, headers: Record<string, string> = {}) => fetch(`${base}${path}`, { headers }),
|
|
88
|
+
post: (path: string, body: unknown, headers: Record<string, string> = {}) =>
|
|
89
|
+
fetch(`${base}${path}`, {
|
|
90
|
+
method: 'POST',
|
|
91
|
+
headers: { 'content-type': 'application/json', ...headers },
|
|
92
|
+
body: JSON.stringify(body),
|
|
93
|
+
}),
|
|
94
|
+
del: (path: string) => fetch(`${base}${path}`, { method: 'DELETE' }),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Every route that serves FILE CONTENT or changes a file. The framing rule
|
|
100
|
+
* makes the embed page reachable from any site, so the token is the whole of
|
|
101
|
+
* the security — a session fallback would let a hostile frame read and write
|
|
102
|
+
* as whoever happened to be signed in.
|
|
103
|
+
*/
|
|
104
|
+
const DATA_ROUTES = [
|
|
105
|
+
['GET', '/api/embed/load'],
|
|
106
|
+
['GET', '/api/embed/raw'],
|
|
107
|
+
['POST', '/api/embed/lock'],
|
|
108
|
+
['POST', '/api/embed/heartbeat'],
|
|
109
|
+
['POST', '/api/embed/cancel'],
|
|
110
|
+
['POST', '/api/embed/save'],
|
|
111
|
+
['POST', '/api/embed/propose'],
|
|
112
|
+
] as const;
|
|
113
|
+
|
|
114
|
+
describe('the embed data routes take the token and nothing else', () => {
|
|
115
|
+
it.each(DATA_ROUTES)('%s %s refuses a request with a valid session and no token', async (method, path) => {
|
|
116
|
+
const client = await serve(stubService(), { session: true });
|
|
117
|
+
const res =
|
|
118
|
+
method === 'GET'
|
|
119
|
+
? await client.get(path, { cookie: 'bevel_token=a-real-session' })
|
|
120
|
+
: await client.post(path, { content: 'x' }, { cookie: 'bevel_token=a-real-session' });
|
|
121
|
+
expect(res.status).toBe(401);
|
|
122
|
+
expect((await res.json()).error).toBe(TOKEN_REQUIRED);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it.each(DATA_ROUTES)('%s %s answers a request with a token and no session', async (method, path) => {
|
|
126
|
+
const client = await serve(stubService());
|
|
127
|
+
const res =
|
|
128
|
+
method === 'GET'
|
|
129
|
+
? await client.get(`${path}?token=${TOKEN}`)
|
|
130
|
+
: await client.post(path, { token: TOKEN, content: 'x' });
|
|
131
|
+
expect(res.status).toBeLessThan(400);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('refuses an expired or rejected token with 401, so the view can say it expired', async () => {
|
|
135
|
+
const client = await serve(stubService());
|
|
136
|
+
const res = await client.get('/api/embed/load?token=stale');
|
|
137
|
+
expect(res.status).toBe(401);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe('framing', () => {
|
|
142
|
+
/**
|
|
143
|
+
* Any host may frame the embed page — that is what makes it work in every
|
|
144
|
+
* host's sandbox without an admin entry per host, and the token is what
|
|
145
|
+
* secures it. `X-Frame-Options` is REMOVED rather than merely unset, so a
|
|
146
|
+
* header added globally later cannot silently break every host's embed.
|
|
147
|
+
*/
|
|
148
|
+
it('sends no framing restriction on the embed page, even under a global one', async () => {
|
|
149
|
+
const client = await serve(stubService(), { restrictFraming: true });
|
|
150
|
+
// The restriction is really there on every other page…
|
|
151
|
+
const other = await client.get('/workspace/main');
|
|
152
|
+
expect(other.headers.get('x-frame-options')).toBe('SAMEORIGIN');
|
|
153
|
+
// …and the embed page clears it.
|
|
154
|
+
const res = await client.get('/embed?token=' + TOKEN);
|
|
155
|
+
expect(res.status).toBe(200);
|
|
156
|
+
expect(res.headers.get('x-frame-options')).toBeNull();
|
|
157
|
+
expect(res.headers.get('content-security-policy')).toBeNull();
|
|
158
|
+
// And it still falls through to the SPA.
|
|
159
|
+
expect(await res.text()).toContain('<!doctype html>');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* The account-link page acts under the SIGNED-IN SESSION, so a page that
|
|
164
|
+
* could frame it could link a foreign account to a victim.
|
|
165
|
+
*/
|
|
166
|
+
it('refuses every framing ancestor on the account-link page', async () => {
|
|
167
|
+
const client = await serve(stubService(), { restrictFraming: true });
|
|
168
|
+
const res = await client.get('/embed/link?token=' + TOKEN);
|
|
169
|
+
expect(res.status).toBe(200);
|
|
170
|
+
expect(res.headers.get('content-security-policy')).toBe("frame-ancestors 'none'");
|
|
171
|
+
expect(res.headers.get('x-frame-options')).toBe('DENY');
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
describe('the connector mint', () => {
|
|
176
|
+
it('mints from the shared secret, an account id and a reference', async () => {
|
|
177
|
+
const service = stubService();
|
|
178
|
+
const client = await serve(service);
|
|
179
|
+
const res = await client.post(
|
|
180
|
+
'/api/embed/token',
|
|
181
|
+
{ accountId: 'acc-1', email: 'a@x.io', reference: 'Data/Thing.md' },
|
|
182
|
+
{ 'x-embed-secret': 'shh' },
|
|
183
|
+
);
|
|
184
|
+
expect(res.status).toBe(200);
|
|
185
|
+
expect(await res.json()).toMatchObject({ token: TOKEN });
|
|
186
|
+
expect(service.mintToken).toHaveBeenCalledWith({
|
|
187
|
+
accountId: 'acc-1',
|
|
188
|
+
email: 'a@x.io',
|
|
189
|
+
reference: 'Data/Thing.md',
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('refuses a wrong secret and a missing one', async () => {
|
|
194
|
+
const client = await serve(stubService());
|
|
195
|
+
expect((await client.post('/api/embed/token', { accountId: 'a', reference: 'b' }, { 'x-embed-secret': 'no' })).status).toBe(401);
|
|
196
|
+
expect((await client.post('/api/embed/token', { accountId: 'a', reference: 'b' })).status).toBe(401);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('refuses an incomplete body', async () => {
|
|
200
|
+
const client = await serve(stubService());
|
|
201
|
+
const res = await client.post('/api/embed/token', { accountId: 'acc-1' }, { 'x-embed-secret': 'shh' });
|
|
202
|
+
expect(res.status).toBe(400);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
/** A deployment with no connector pointed at it has no such route at all. */
|
|
206
|
+
it('is absent when no shared secret is configured', async () => {
|
|
207
|
+
const service = stubService({ sharedSecretConfigured: vi.fn(() => false) });
|
|
208
|
+
const client = await serve(service);
|
|
209
|
+
const res = await client.post('/api/embed/token', { accountId: 'a', reference: 'b' }, { 'x-embed-secret': 'shh' });
|
|
210
|
+
expect(res.status).toBe(404);
|
|
211
|
+
expect(service.verifySharedSecret).not.toHaveBeenCalled();
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
describe('the data routes', () => {
|
|
216
|
+
it('serves the page to render', async () => {
|
|
217
|
+
const client = await serve(stubService());
|
|
218
|
+
const res = await client.get(`/api/embed/load?token=${TOKEN}`);
|
|
219
|
+
expect(await res.json()).toMatchObject({ nodeName: 'Thing' });
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('serves bytes inline, never as a download, and never from a shared cache', async () => {
|
|
223
|
+
const client = await serve(stubService());
|
|
224
|
+
const res = await client.get(`/api/embed/raw?token=${TOKEN}&path=assets/x.png`);
|
|
225
|
+
expect(res.status).toBe(200);
|
|
226
|
+
expect(res.headers.get('content-disposition')).toBe('inline');
|
|
227
|
+
expect(res.headers.get('cache-control')).toContain('no-store');
|
|
228
|
+
expect(res.headers.get('x-content-type-options')).toBe('nosniff');
|
|
229
|
+
expect([...new Uint8Array(await res.arrayBuffer())]).toEqual([1, 2, 3]);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('saves, and proposes, through the service', async () => {
|
|
233
|
+
const service = stubService();
|
|
234
|
+
const client = await serve(service);
|
|
235
|
+
expect((await client.post('/api/embed/save', { token: TOKEN, content: 'new' })).status).toBe(204);
|
|
236
|
+
expect(service.save).toHaveBeenCalledWith(TOKEN, 'new');
|
|
237
|
+
const proposed = await client.post('/api/embed/propose', { token: TOKEN, content: 'draft' });
|
|
238
|
+
expect(await proposed.json()).toMatchObject({ number: 7 });
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('says who holds the lock when a save is refused for it', async () => {
|
|
242
|
+
const service = stubService({
|
|
243
|
+
save: vi.fn(async () => {
|
|
244
|
+
throw new EmbedLockedError('Bob');
|
|
245
|
+
}),
|
|
246
|
+
});
|
|
247
|
+
const client = await serve(service);
|
|
248
|
+
const res = await client.post('/api/embed/save', { token: TOKEN, content: 'x' });
|
|
249
|
+
expect(res.status).toBe(409);
|
|
250
|
+
expect((await res.json()).error).toContain('Bob');
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
/** Protected text and a token-bearing link: nothing may keep the answer. */
|
|
254
|
+
it('forbids caching the loaded page, as it does the raw bytes', async () => {
|
|
255
|
+
const client = await serve(stubService());
|
|
256
|
+
const load = await client.get(`/api/embed/load?token=${TOKEN}`);
|
|
257
|
+
expect(load.headers.get('cache-control')).toBe('no-store, private');
|
|
258
|
+
const refused = await client.get('/api/embed/load?token=wrong');
|
|
259
|
+
expect(refused.headers.get('cache-control')).toBe('no-store, private');
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('refuses a save with no content', async () => {
|
|
263
|
+
const client = await serve(stubService());
|
|
264
|
+
expect((await client.post('/api/embed/save', { token: TOKEN })).status).toBe(400);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it.each([
|
|
268
|
+
[new EmbedTokenError(), 401],
|
|
269
|
+
[new EmbedAccessError(), 403],
|
|
270
|
+
[new EmbedNodeNotFoundError('gone'), 404],
|
|
271
|
+
[new EmbedLockedError('Bob'), 409],
|
|
272
|
+
])('maps %s to its status', async (error, status) => {
|
|
273
|
+
const service = stubService({
|
|
274
|
+
loadFile: vi.fn(async () => {
|
|
275
|
+
throw error;
|
|
276
|
+
}),
|
|
277
|
+
});
|
|
278
|
+
const client = await serve(service);
|
|
279
|
+
expect((await client.get(`/api/embed/load?token=${TOKEN}`)).status).toBe(status);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('does not leak an unexpected failure message', async () => {
|
|
283
|
+
const service = stubService({
|
|
284
|
+
loadFile: vi.fn(async () => {
|
|
285
|
+
throw new Error('postgres://user:pw@host down');
|
|
286
|
+
}),
|
|
287
|
+
});
|
|
288
|
+
const client = await serve(service);
|
|
289
|
+
const res = await client.get(`/api/embed/load?token=${TOKEN}`);
|
|
290
|
+
expect(res.status).toBe(500);
|
|
291
|
+
expect((await res.json()).error).toBe('Something went wrong.');
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
describe('the account-link routes', () => {
|
|
296
|
+
it('link, list and unlink need a session', async () => {
|
|
297
|
+
const client = await serve(stubService(), { session: false });
|
|
298
|
+
expect((await client.post('/api/embed/link', { token: TOKEN })).status).toBe(401);
|
|
299
|
+
expect((await client.get('/api/embed/links')).status).toBe(401);
|
|
300
|
+
expect((await client.del('/api/embed/links/acc-1')).status).toBe(401);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('links the token account to the signed-in user', async () => {
|
|
304
|
+
const service = stubService();
|
|
305
|
+
const client = await serve(service, { session: true });
|
|
306
|
+
expect((await client.post('/api/embed/link', { token: TOKEN })).status).toBe(204);
|
|
307
|
+
expect(service.linkAccount).toHaveBeenCalledWith(TOKEN, 'u-1');
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('answers 404 for a link that is not this user own, so it cannot probe others', async () => {
|
|
311
|
+
const service = stubService({ unlinkAccount: vi.fn(async () => false) });
|
|
312
|
+
const client = await serve(service, { session: true });
|
|
313
|
+
expect((await client.del('/api/embed/links/someone-elses')).status).toBe(404);
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* The MCP App view runs the embed inside a chat host's SANDBOX — another
|
|
319
|
+
* origin, and one no deployment can know in advance — and calls the token
|
|
320
|
+
* routes from there. The server's app-wide CORS is what lets it: mounted
|
|
321
|
+
* here as `create-core-server` mounts it, so what is pinned is the behaviour
|
|
322
|
+
* a sandbox actually meets, not a header these routes set themselves. Safe
|
|
323
|
+
* on the token routes for the reason the token-only rule exists: CORS guards
|
|
324
|
+
* a browser's ambient credentials, these routes take none, and the token in
|
|
325
|
+
* the request is the whole credential.
|
|
326
|
+
*/
|
|
327
|
+
describe('a cross-origin caller reaches the token routes through the app-wide CORS', () => {
|
|
328
|
+
const SANDBOX = 'https://sandbox.example';
|
|
329
|
+
const FOREIGN = { origin: SANDBOX };
|
|
330
|
+
|
|
331
|
+
it.each(DATA_ROUTES)('%s %s answers the sandbox origin', async (method, path) => {
|
|
332
|
+
const client = await serve(stubService(), { appWideCors: true });
|
|
333
|
+
const res =
|
|
334
|
+
method === 'GET'
|
|
335
|
+
? await client.get(`${path}?token=${TOKEN}`, FOREIGN)
|
|
336
|
+
: await client.post(path, { token: TOKEN, content: 'x' }, FOREIGN);
|
|
337
|
+
expect(res.status).toBeLessThan(400);
|
|
338
|
+
expect(res.headers.get('access-control-allow-origin')).toBe(SANDBOX);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it('answers the preflight a JSON POST from the sandbox triggers', async () => {
|
|
342
|
+
const client = await serve(stubService(), { appWideCors: true });
|
|
343
|
+
const res = await fetch(`${client.base}/api/embed/save`, {
|
|
344
|
+
method: 'OPTIONS',
|
|
345
|
+
headers: { ...FOREIGN, 'access-control-request-method': 'POST', 'access-control-request-headers': 'content-type' },
|
|
346
|
+
});
|
|
347
|
+
expect(res.status).toBe(204);
|
|
348
|
+
expect(res.headers.get('access-control-allow-origin')).toBe(SANDBOX);
|
|
349
|
+
expect(res.headers.get('access-control-allow-methods')).toContain('POST');
|
|
350
|
+
expect(res.headers.get('access-control-allow-headers')?.toLowerCase()).toContain('content-type');
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it('still refuses a sandbox caller that brings no token', async () => {
|
|
354
|
+
// Being reachable from another origin changes nothing about who may
|
|
355
|
+
// read: the token is the credential, and a request without it is 401
|
|
356
|
+
// from the sandbox exactly as from a tab.
|
|
357
|
+
const client = await serve(stubService(), { appWideCors: true });
|
|
358
|
+
const res = await client.get('/api/embed/load', FOREIGN);
|
|
359
|
+
expect(res.status).toBe(401);
|
|
360
|
+
expect((await res.json()).error).toBe(TOKEN_REQUIRED);
|
|
361
|
+
});
|
|
362
|
+
});
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import type { AuthUser } from '@bevel-software/platform-shared';
|
|
3
|
+
import { FileLockService } from '../../workflow/file-lock.service.js';
|
|
4
|
+
import { makeFakeLockDb, type FakeLockDb } from '../../workflow/__tests__/fake-file-lock-db.js';
|
|
5
|
+
import { createFileReaderRegistry } from '../../workspace/file-readers/file-reader.registry.js';
|
|
6
|
+
import type { DocExtractOutcome, DocExtractService } from '../../workspace/file-readers/doc-extract.service.js';
|
|
7
|
+
import { makeRolesYamlWriteValidator } from '../../access-model/roles-yaml-guard.js';
|
|
8
|
+
import { testKbContext, TEST_BRANCH_MODEL } from '../../../__tests__/kb-context.js';
|
|
9
|
+
import { EmbedService } from '../embed.service.js';
|
|
10
|
+
import { EmbedLockedError } from '../embed.errors.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Edit and Save against the REAL lock.
|
|
14
|
+
*
|
|
15
|
+
* The rest of the service suite stubs `acquireLock`, and a stub answers
|
|
16
|
+
* whatever it was told to. That is how a Save that re-acquired the writer's
|
|
17
|
+
* own lock shipped: the stub said yes, while `FileLockService.acquire` is
|
|
18
|
+
* strict on purpose and refuses a live lock even to its own holder (an agent
|
|
19
|
+
* shares the human's user id, and must not steal their edit). On a real boot
|
|
20
|
+
* every Save from the embed answered 409 against the writer's own Edit lock.
|
|
21
|
+
*
|
|
22
|
+
* So the lock half of the workflow service here is the real `FileLockService`
|
|
23
|
+
* over the in-memory `file_locks` table the workflow suites use — the
|
|
24
|
+
* contention rules come from the code that enforces them.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
const KB = 'knowledge-base';
|
|
28
|
+
const BRANCH = TEST_BRANCH_MODEL.defaultBranch;
|
|
29
|
+
const WS_ID = encodeURIComponent(BRANCH);
|
|
30
|
+
const REPO = 'Data/Thing.md';
|
|
31
|
+
const WS = `${KB}/${REPO}`;
|
|
32
|
+
|
|
33
|
+
const ALICE: AuthUser = { id: '11111111-1111-4111-8111-111111111111', email: 'alice@example.com', name: 'Alice' };
|
|
34
|
+
const BOB: AuthUser = { id: '22222222-2222-4222-8222-222222222222', email: 'bob@example.com', name: 'Bob' };
|
|
35
|
+
|
|
36
|
+
let fake: FakeLockDb;
|
|
37
|
+
let locks: FileLockService;
|
|
38
|
+
|
|
39
|
+
/** The extraction service the reader registry takes, typed against its contract; no document is read here. */
|
|
40
|
+
function noDocExtract(): DocExtractService {
|
|
41
|
+
const extract = async (): Promise<DocExtractOutcome> => ({
|
|
42
|
+
ok: false,
|
|
43
|
+
message: 'this suite reads no document-format file',
|
|
44
|
+
});
|
|
45
|
+
return { extract } satisfies Pick<DocExtractService, 'extract'> as DocExtractService;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function build() {
|
|
49
|
+
const workspaceService = {
|
|
50
|
+
getOrCreateForBranch: vi.fn(async (branch: string) => ({ id: encodeURIComponent(branch), kbDirName: KB })),
|
|
51
|
+
readFileBinary: vi.fn(async () => Buffer.from('# Thing\n', 'utf8')),
|
|
52
|
+
isFile: vi.fn(async () => true),
|
|
53
|
+
writeFile: vi.fn(async () => undefined),
|
|
54
|
+
withPathTurn: vi.fn(async (_id: string, _wsPath: string, op: () => Promise<unknown>) => op()),
|
|
55
|
+
};
|
|
56
|
+
// The lock verbs go straight to the real service; release drops the row as
|
|
57
|
+
// the real `releaseLock` does once it has enqueued the commit.
|
|
58
|
+
const workflowService = {
|
|
59
|
+
getLock: vi.fn((id: string, branch: string, path: string) => locks.get(id, branch, path)),
|
|
60
|
+
acquireLock: vi.fn((id: string, branch: string, path: string, user: AuthUser) =>
|
|
61
|
+
locks.acquire(id, branch, path, user),
|
|
62
|
+
),
|
|
63
|
+
heartbeatLock: vi.fn((id: string, branch: string, path: string, user: AuthUser) =>
|
|
64
|
+
locks.heartbeat(id, branch, path, user),
|
|
65
|
+
),
|
|
66
|
+
releaseLock: vi.fn((id: string, branch: string, path: string, user: AuthUser) =>
|
|
67
|
+
locks.release(id, branch, path, user),
|
|
68
|
+
),
|
|
69
|
+
releaseLockNoCommit: vi.fn((id: string, branch: string, path: string, user: AuthUser) =>
|
|
70
|
+
locks.release(id, branch, path, user),
|
|
71
|
+
),
|
|
72
|
+
};
|
|
73
|
+
const service = new EmbedService(
|
|
74
|
+
{ jwtSecret: 's', embedSharedSecret: '', publicFrontendUrl: 'https://hexis.example', kbDirName: KB },
|
|
75
|
+
testKbContext({ kbDirName: KB }),
|
|
76
|
+
workspaceService as never,
|
|
77
|
+
{ canRead: async () => true, canWrite: async () => true } as never,
|
|
78
|
+
{
|
|
79
|
+
getUserById: async (id: string) => (id === ALICE.id ? ALICE : BOB),
|
|
80
|
+
isEmailDomainAllowed: () => true,
|
|
81
|
+
isActive: async () => true,
|
|
82
|
+
} as never,
|
|
83
|
+
workflowService as never,
|
|
84
|
+
{ remoteBranchExists: async () => false } as never,
|
|
85
|
+
{ getUserId: async () => null } as never,
|
|
86
|
+
createFileReaderRegistry(noDocExtract()),
|
|
87
|
+
makeRolesYamlWriteValidator(KB),
|
|
88
|
+
);
|
|
89
|
+
return { service, workspaceService, workflowService };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
beforeEach(() => {
|
|
93
|
+
fake = makeFakeLockDb();
|
|
94
|
+
locks = new FileLockService(fake.db);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe('the real lock is strict', () => {
|
|
98
|
+
/** The premise the regression broke on — pinned, so nobody "relies" on the opposite again. */
|
|
99
|
+
it('refuses a live lock even to its own holder', async () => {
|
|
100
|
+
expect((await locks.acquire(WS_ID, BRANCH, WS, ALICE)).acquired).toBe(true);
|
|
101
|
+
expect((await locks.acquire(WS_ID, BRANCH, WS, ALICE)).acquired).toBe(false);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe('Edit then Save, as the writer does it', () => {
|
|
106
|
+
it('saves under the lock Edit took, then releases it', async () => {
|
|
107
|
+
const { service, workspaceService } = build();
|
|
108
|
+
const { token } = await service.mintForUser({ userId: ALICE.id, reference: REPO });
|
|
109
|
+
|
|
110
|
+
expect(await service.acquireLock(token)).toEqual({ acquired: true });
|
|
111
|
+
await service.heartbeat(token);
|
|
112
|
+
await expect(service.save(token, 'new text')).resolves.toBeUndefined();
|
|
113
|
+
|
|
114
|
+
expect(workspaceService.writeFile).toHaveBeenCalledWith(WS_ID, WS, 'new text');
|
|
115
|
+
// Released, so the file is free for the next editor.
|
|
116
|
+
expect(await locks.get(WS_ID, BRANCH, WS)).toBeNull();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('can Edit and Save again straight after', async () => {
|
|
120
|
+
const { service, workspaceService } = build();
|
|
121
|
+
const { token } = await service.mintForUser({ userId: ALICE.id, reference: REPO });
|
|
122
|
+
await service.acquireLock(token);
|
|
123
|
+
await service.save(token, 'first');
|
|
124
|
+
await service.acquireLock(token);
|
|
125
|
+
await service.save(token, 'second');
|
|
126
|
+
expect(workspaceService.writeFile).toHaveBeenLastCalledWith(WS_ID, WS, 'second');
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe('a Save whose lock is not the viewer own', () => {
|
|
131
|
+
it('is refused, names the holder, writes nothing and leaves their lock alone', async () => {
|
|
132
|
+
await locks.acquire(WS_ID, BRANCH, WS, BOB);
|
|
133
|
+
const { service, workspaceService } = build();
|
|
134
|
+
const { token } = await service.mintForUser({ userId: ALICE.id, reference: REPO });
|
|
135
|
+
|
|
136
|
+
const save = service.save(token, 'x');
|
|
137
|
+
await expect(save).rejects.toThrow(EmbedLockedError);
|
|
138
|
+
await expect(service.save(token, 'x')).rejects.toThrow('Bob');
|
|
139
|
+
expect(workspaceService.writeFile).not.toHaveBeenCalled();
|
|
140
|
+
expect((await locks.get(WS_ID, BRANCH, WS))?.holderUserId).toBe(BOB.id);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
/** A frame hidden past the TTL comes back to a free file: it takes the lock again and saves. */
|
|
144
|
+
it('takes a lapsed lock back when nobody else took the file', async () => {
|
|
145
|
+
const { service, workspaceService } = build();
|
|
146
|
+
const { token } = await service.mintForUser({ userId: ALICE.id, reference: REPO });
|
|
147
|
+
const past = new Date(Date.now() - 60_000);
|
|
148
|
+
fake.seed({
|
|
149
|
+
workspaceId: WS_ID,
|
|
150
|
+
branch: BRANCH,
|
|
151
|
+
path: WS,
|
|
152
|
+
holderUserId: ALICE.id,
|
|
153
|
+
holderName: ALICE.name,
|
|
154
|
+
mode: 'edit',
|
|
155
|
+
acquiredAt: past,
|
|
156
|
+
lastHeartbeatAt: past,
|
|
157
|
+
expiresAt: past,
|
|
158
|
+
});
|
|
159
|
+
await expect(service.save(token, 'back')).resolves.toBeUndefined();
|
|
160
|
+
expect(workspaceService.writeFile).toHaveBeenCalledWith(WS_ID, WS, 'back');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('is refused when the lapsed lock was taken by somebody else', async () => {
|
|
164
|
+
const { service, workspaceService } = build();
|
|
165
|
+
const { token } = await service.mintForUser({ userId: ALICE.id, reference: REPO });
|
|
166
|
+
await service.acquireLock(token);
|
|
167
|
+
// Alice's lock lapses, and Bob takes the file.
|
|
168
|
+
const past = new Date(Date.now() - 60_000);
|
|
169
|
+
fake.seed({
|
|
170
|
+
workspaceId: WS_ID,
|
|
171
|
+
branch: BRANCH,
|
|
172
|
+
path: WS,
|
|
173
|
+
holderUserId: ALICE.id,
|
|
174
|
+
holderName: ALICE.name,
|
|
175
|
+
mode: 'edit',
|
|
176
|
+
acquiredAt: past,
|
|
177
|
+
lastHeartbeatAt: past,
|
|
178
|
+
expiresAt: past,
|
|
179
|
+
});
|
|
180
|
+
expect((await locks.acquire(WS_ID, BRANCH, WS, BOB)).acquired).toBe(true);
|
|
181
|
+
|
|
182
|
+
await expect(service.save(token, 'stale')).rejects.toThrow(EmbedLockedError);
|
|
183
|
+
expect(workspaceService.writeFile).not.toHaveBeenCalled();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('does not treat a coordination hold under the same id as write possession', async () => {
|
|
187
|
+
await locks.acquire(WS_ID, BRANCH, WS, ALICE, { coordination: true });
|
|
188
|
+
const { service, workspaceService } = build();
|
|
189
|
+
const { token } = await service.mintForUser({ userId: ALICE.id, reference: REPO });
|
|
190
|
+
await expect(service.save(token, 'x')).rejects.toThrow(EmbedLockedError);
|
|
191
|
+
expect(workspaceService.writeFile).not.toHaveBeenCalled();
|
|
192
|
+
});
|
|
193
|
+
});
|