@bevel-software/platform-core-backend 0.26.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent-guide/access-control.md +17 -0
- package/agent-guide/conventions.md +8 -0
- package/agent-guide/html-views.md +83 -0
- package/agent-guide/new-knowledge-base.md +24 -0
- package/agent-guide/tool-manuals.md +10 -0
- package/dist/assets.d.ts +19 -0
- package/dist/assets.d.ts.map +1 -1
- package/dist/assets.js +26 -2
- package/dist/assets.js.map +1 -1
- package/dist/core/catalog-revision.d.ts +19 -1
- package/dist/core/catalog-revision.d.ts.map +1 -1
- package/dist/core/catalog-revision.js +14 -3
- package/dist/core/catalog-revision.js.map +1 -1
- package/dist/core/create-core-server.d.ts +5 -0
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +54 -3
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +26 -0
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +100 -8
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/core/lifecycle.d.ts +16 -1
- package/dist/core/lifecycle.d.ts.map +1 -1
- package/dist/core/lifecycle.js +18 -1
- package/dist/core/lifecycle.js.map +1 -1
- package/dist/core-config.d.ts +21 -0
- package/dist/core-config.d.ts.map +1 -1
- package/dist/core-config.js +22 -1
- package/dist/core-config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +8 -0
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +1 -0
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +11 -0
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/directory-sync-bot.js +1 -1
- package/dist/modules/access/directory-sync-bot.js.map +1 -1
- package/dist/modules/agent-guide/agent-guide.d.ts +6 -0
- package/dist/modules/agent-guide/agent-guide.d.ts.map +1 -1
- package/dist/modules/agent-guide/agent-guide.js +8 -0
- package/dist/modules/agent-guide/agent-guide.js.map +1 -1
- package/dist/modules/agent-guide/index.d.ts +1 -1
- package/dist/modules/agent-guide/index.d.ts.map +1 -1
- package/dist/modules/agent-guide/index.js +1 -1
- package/dist/modules/agent-guide/index.js.map +1 -1
- package/dist/modules/agent-instructions/compose.d.ts +5 -0
- package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
- package/dist/modules/agent-instructions/compose.js +19 -2
- package/dist/modules/agent-instructions/compose.js.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.js +19 -19
- package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
- package/dist/modules/audit/agent-audit.service.d.ts +21 -2
- package/dist/modules/audit/agent-audit.service.d.ts.map +1 -1
- package/dist/modules/audit/agent-audit.service.js +43 -2
- package/dist/modules/audit/agent-audit.service.js.map +1 -1
- package/dist/modules/audit/agent-connection.routes.d.ts +34 -0
- package/dist/modules/audit/agent-connection.routes.d.ts.map +1 -0
- package/dist/modules/audit/agent-connection.routes.js +38 -0
- package/dist/modules/audit/agent-connection.routes.js.map +1 -0
- package/dist/modules/audit/audit.contract.d.ts +24 -0
- package/dist/modules/audit/audit.contract.d.ts.map +1 -1
- package/dist/modules/audit/audit.contract.js.map +1 -1
- package/dist/modules/audit/index.d.ts +1 -0
- package/dist/modules/audit/index.d.ts.map +1 -1
- package/dist/modules/audit/index.js +1 -0
- package/dist/modules/audit/index.js.map +1 -1
- package/dist/modules/auth/account.routes.d.ts.map +1 -1
- package/dist/modules/auth/account.routes.js +10 -3
- package/dist/modules/auth/account.routes.js.map +1 -1
- package/dist/modules/auth/auth.middleware.d.ts +7 -0
- package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
- package/dist/modules/auth/auth.middleware.js +1 -1
- package/dist/modules/auth/auth.middleware.js.map +1 -1
- package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
- package/dist/modules/code-mode/code-mode.tool.js +11 -3
- package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
- package/dist/modules/database/core-schema.d.ts +96 -0
- package/dist/modules/database/core-schema.d.ts.map +1 -1
- package/dist/modules/database/core-schema.js +32 -3
- package/dist/modules/database/core-schema.js.map +1 -1
- package/dist/modules/embed/account-link.service.d.ts +42 -0
- package/dist/modules/embed/account-link.service.d.ts.map +1 -0
- package/dist/modules/embed/account-link.service.js +50 -0
- package/dist/modules/embed/account-link.service.js.map +1 -0
- package/dist/modules/embed/embed-link.d.ts +58 -0
- package/dist/modules/embed/embed-link.d.ts.map +1 -0
- package/dist/modules/embed/embed-link.js +160 -0
- package/dist/modules/embed/embed-link.js.map +1 -0
- package/dist/modules/embed/embed.errors.d.ts +30 -0
- package/dist/modules/embed/embed.errors.d.ts.map +1 -0
- package/dist/modules/embed/embed.errors.js +42 -0
- package/dist/modules/embed/embed.errors.js.map +1 -0
- package/dist/modules/embed/embed.interface.d.ts +204 -0
- package/dist/modules/embed/embed.interface.d.ts.map +1 -0
- package/dist/modules/embed/embed.interface.js +2 -0
- package/dist/modules/embed/embed.interface.js.map +1 -0
- package/dist/modules/embed/embed.routes.d.ts +59 -0
- package/dist/modules/embed/embed.routes.d.ts.map +1 -0
- package/dist/modules/embed/embed.routes.js +343 -0
- package/dist/modules/embed/embed.routes.js.map +1 -0
- package/dist/modules/embed/embed.service.d.ts +169 -0
- package/dist/modules/embed/embed.service.d.ts.map +1 -0
- package/dist/modules/embed/embed.service.js +627 -0
- package/dist/modules/embed/embed.service.js.map +1 -0
- package/dist/modules/embed/embed.tools.d.ts +51 -0
- package/dist/modules/embed/embed.tools.d.ts.map +1 -0
- package/dist/modules/embed/embed.tools.js +170 -0
- package/dist/modules/embed/embed.tools.js.map +1 -0
- package/dist/modules/embed/mcp-app.d.ts +64 -0
- package/dist/modules/embed/mcp-app.d.ts.map +1 -0
- package/dist/modules/embed/mcp-app.js +147 -0
- package/dist/modules/embed/mcp-app.js.map +1 -0
- package/dist/modules/embed/mcp-app.routes.d.ts +21 -0
- package/dist/modules/embed/mcp-app.routes.d.ts.map +1 -0
- package/dist/modules/embed/mcp-app.routes.js +36 -0
- package/dist/modules/embed/mcp-app.routes.js.map +1 -0
- package/dist/modules/github-app/github-app.connection.d.ts +15 -0
- package/dist/modules/github-app/github-app.connection.d.ts.map +1 -1
- package/dist/modules/github-app/github-app.connection.js +34 -3
- package/dist/modules/github-app/github-app.connection.js.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.d.ts +65 -0
- package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.js +300 -0
- package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
- package/dist/modules/kb-sync/kb-sync.interface.d.ts +5 -0
- package/dist/modules/kb-sync/kb-sync.interface.d.ts.map +1 -1
- package/dist/modules/kb-sync/kb-sync.service.d.ts.map +1 -1
- package/dist/modules/kb-sync/kb-sync.service.js +6 -0
- package/dist/modules/kb-sync/kb-sync.service.js.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.js +2 -1
- package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
- package/dist/modules/mcp/mcp.service.d.ts +28 -1
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +64 -8
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +24 -0
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js +32 -4
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
- package/dist/modules/onboarding/onboarding.contract.d.ts +80 -0
- package/dist/modules/onboarding/onboarding.contract.d.ts.map +1 -0
- package/dist/modules/onboarding/onboarding.contract.js +7 -0
- package/dist/modules/onboarding/onboarding.contract.js.map +1 -0
- package/dist/modules/onboarding/onboarding.routes.d.ts +18 -0
- package/dist/modules/onboarding/onboarding.routes.d.ts.map +1 -0
- package/dist/modules/onboarding/onboarding.routes.js +81 -0
- package/dist/modules/onboarding/onboarding.routes.js.map +1 -0
- package/dist/modules/onboarding/starter-pack.service.d.ts +213 -0
- package/dist/modules/onboarding/starter-pack.service.d.ts.map +1 -0
- package/dist/modules/onboarding/starter-pack.service.js +492 -0
- package/dist/modules/onboarding/starter-pack.service.js.map +1 -0
- package/dist/modules/onboarding/starter-packs.d.ts +78 -0
- package/dist/modules/onboarding/starter-packs.d.ts.map +1 -0
- package/dist/modules/onboarding/starter-packs.js +156 -0
- package/dist/modules/onboarding/starter-packs.js.map +1 -0
- package/dist/modules/plugins/index.d.ts +1 -1
- package/dist/modules/plugins/index.d.ts.map +1 -1
- package/dist/modules/plugins/index.js +1 -1
- package/dist/modules/plugins/index.js.map +1 -1
- package/dist/modules/plugins/plugin-provision.service.d.ts +19 -0
- package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-provision.service.js +18 -1
- package/dist/modules/plugins/plugin-provision.service.js.map +1 -1
- package/dist/modules/plugins/plugins.routes.d.ts +0 -36
- package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.js +31 -0
- package/dist/modules/plugins/plugins.routes.js.map +1 -1
- package/dist/modules/settings/deployment-settings.service.d.ts +34 -0
- package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
- package/dist/modules/settings/deployment-settings.service.js +134 -1
- package/dist/modules/settings/deployment-settings.service.js.map +1 -1
- package/dist/modules/settings/repository-source.d.ts +1 -0
- package/dist/modules/settings/repository-source.d.ts.map +1 -1
- package/dist/modules/settings/repository-source.js +3 -3
- package/dist/modules/settings/repository-source.js.map +1 -1
- package/dist/modules/skills/skills.tools.d.ts.map +1 -1
- package/dist/modules/skills/skills.tools.js +50 -32
- package/dist/modules/skills/skills.tools.js.map +1 -1
- package/dist/modules/tool-auth/connection-key-rejection.d.ts +10 -1
- package/dist/modules/tool-auth/connection-key-rejection.d.ts.map +1 -1
- package/dist/modules/tool-auth/connection-key-rejection.js +11 -3
- package/dist/modules/tool-auth/connection-key-rejection.js.map +1 -1
- package/dist/modules/tool-auth/external-api-key.interface.d.ts +8 -0
- package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.d.ts +24 -2
- package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.js +59 -2
- package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.js +9 -4
- package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
- package/dist/modules/tool-helpers/index.d.ts +28 -1
- package/dist/modules/tool-helpers/index.d.ts.map +1 -1
- package/dist/modules/tool-helpers/index.js +28 -1
- package/dist/modules/tool-helpers/index.js.map +1 -1
- package/dist/modules/tool-helpers/route-argument-check.d.ts +10 -0
- package/dist/modules/tool-helpers/route-argument-check.d.ts.map +1 -0
- package/dist/modules/tool-helpers/route-argument-check.js +132 -0
- package/dist/modules/tool-helpers/route-argument-check.js.map +1 -0
- package/dist/modules/tool-helpers/route-tool-schemas.d.ts +89 -0
- package/dist/modules/tool-helpers/route-tool-schemas.d.ts.map +1 -0
- package/dist/modules/tool-helpers/route-tool-schemas.js +113 -0
- package/dist/modules/tool-helpers/route-tool-schemas.js.map +1 -0
- package/dist/modules/tool-helpers/tool-def.d.ts +71 -0
- package/dist/modules/tool-helpers/tool-def.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-def.js +70 -6
- package/dist/modules/tool-helpers/tool-def.js.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.d.ts +46 -6
- package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.js +157 -8
- package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.d.ts +9 -0
- package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.js +14 -5
- package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
- package/dist/modules/tool-registry/description-length.d.ts +6 -2
- package/dist/modules/tool-registry/description-length.d.ts.map +1 -1
- package/dist/modules/tool-registry/description-length.js +27 -5
- package/dist/modules/tool-registry/description-length.js.map +1 -1
- package/dist/modules/tool-registry/guide-first.d.ts +4 -0
- package/dist/modules/tool-registry/guide-first.d.ts.map +1 -1
- package/dist/modules/tool-registry/guide-first.js +9 -1
- package/dist/modules/tool-registry/guide-first.js.map +1 -1
- package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts +53 -0
- package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/change-request-read.tools.js +97 -68
- package/dist/modules/workflow/agent-tools/change-request-read.tools.js.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts +8 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.js +124 -37
- package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
- package/dist/modules/workflow/file-lock.service.d.ts +29 -5
- package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
- package/dist/modules/workflow/file-lock.service.js +62 -5
- package/dist/modules/workflow/file-lock.service.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +72 -1
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +172 -36
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/git/node-git-runner.d.ts +15 -0
- package/dist/modules/workflow/git/node-git-runner.d.ts.map +1 -1
- package/dist/modules/workflow/git/node-git-runner.js +78 -6
- package/dist/modules/workflow/git/node-git-runner.js.map +1 -1
- package/dist/modules/workflow/pending-commits.service.d.ts +15 -0
- package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.service.js +25 -1
- package/dist/modules/workflow/pending-commits.service.js.map +1 -1
- package/dist/modules/workflow/pending-commits.worker.d.ts +6 -1
- package/dist/modules/workflow/pending-commits.worker.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.worker.js +23 -6
- package/dist/modules/workflow/pending-commits.worker.js.map +1 -1
- package/dist/modules/workflow/recovery-bot.js +1 -1
- package/dist/modules/workflow/recovery-bot.js.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.js +2 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
- package/dist/modules/workflow/sanitize-error.d.ts +16 -0
- package/dist/modules/workflow/sanitize-error.d.ts.map +1 -1
- package/dist/modules/workflow/sanitize-error.js +45 -0
- package/dist/modules/workflow/sanitize-error.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +183 -15
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +805 -106
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/agent-download.builder.d.ts +105 -0
- package/dist/modules/workspace/agent-download.builder.d.ts.map +1 -0
- package/dist/modules/workspace/agent-download.builder.js +238 -0
- package/dist/modules/workspace/agent-download.builder.js.map +1 -0
- package/dist/modules/workspace/agent-download.routes.d.ts +77 -0
- package/dist/modules/workspace/agent-download.routes.d.ts.map +1 -0
- package/dist/modules/workspace/agent-download.routes.js +156 -0
- package/dist/modules/workspace/agent-download.routes.js.map +1 -0
- package/dist/modules/workspace/agent-download.store.d.ts +225 -0
- package/dist/modules/workspace/agent-download.store.d.ts.map +1 -0
- package/dist/modules/workspace/agent-download.store.js +423 -0
- package/dist/modules/workspace/agent-download.store.js.map +1 -0
- package/dist/modules/workspace/agent-upload.store.d.ts +10 -1
- package/dist/modules/workspace/agent-upload.store.d.ts.map +1 -1
- package/dist/modules/workspace/agent-upload.store.js +16 -6
- package/dist/modules/workspace/agent-upload.store.js.map +1 -1
- package/dist/modules/workspace/file-readers/raw-mime.d.ts +6 -0
- package/dist/modules/workspace/file-readers/raw-mime.d.ts.map +1 -0
- package/dist/modules/workspace/file-readers/raw-mime.js +38 -0
- package/dist/modules/workspace/file-readers/raw-mime.js.map +1 -0
- package/dist/modules/workspace/file-readers/text-reader.d.ts +5 -5
- package/dist/modules/workspace/file-readers/text-reader.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/text-reader.js +5 -8
- package/dist/modules/workspace/file-readers/text-reader.js.map +1 -1
- package/dist/modules/workspace/first-run.d.ts +111 -0
- package/dist/modules/workspace/first-run.d.ts.map +1 -0
- package/dist/modules/workspace/first-run.js +181 -0
- package/dist/modules/workspace/first-run.js.map +1 -0
- package/dist/modules/workspace/move-links.d.ts +92 -0
- package/dist/modules/workspace/move-links.d.ts.map +1 -0
- package/dist/modules/workspace/move-links.js +173 -0
- package/dist/modules/workspace/move-links.js.map +1 -0
- package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.js +3 -13
- package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
- package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.routes.js +63 -22
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +75 -2
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +197 -24
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts +59 -1
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +443 -65
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/modules/write-access/write-access.d.ts.map +1 -1
- package/dist/modules/write-access/write-access.js +9 -0
- package/dist/modules/write-access/write-access.js.map +1 -1
- package/dist/shared/domain-errors.d.ts +73 -1
- package/dist/shared/domain-errors.d.ts.map +1 -1
- package/dist/shared/domain-errors.js +86 -12
- package/dist/shared/domain-errors.js.map +1 -1
- package/dist/shared/git.contract.d.ts +10 -1
- package/dist/shared/git.contract.d.ts.map +1 -1
- package/dist/shared/git.contract.js.map +1 -1
- package/dist/shared/utf8-text.d.ts +15 -0
- package/dist/shared/utf8-text.d.ts.map +1 -0
- package/dist/shared/utf8-text.js +20 -0
- package/dist/shared/utf8-text.js.map +1 -0
- package/dist/tenancy/static-tenant-source.d.ts +2 -1
- package/dist/tenancy/static-tenant-source.d.ts.map +1 -1
- package/dist/tenancy/static-tenant-source.js +9 -2
- package/dist/tenancy/static-tenant-source.js.map +1 -1
- package/mcp-app/page.html +349 -0
- package/migrations/0017_atlassian_account_links.sql +37 -0
- package/migrations/meta/0017_snapshot.json +2396 -0
- package/migrations/meta/_journal.json +8 -1
- package/package.json +5 -3
- package/src/__tests__/packaged-assets-ship.test.ts +16 -4
- package/src/assets.ts +28 -2
- package/src/core/__tests__/catalog-revision.test.ts +40 -0
- package/src/core/__tests__/gated-boot.test.ts +1 -0
- package/src/core/__tests__/lifecycle.test.ts +14 -0
- package/src/core/catalog-revision.ts +15 -2
- package/src/core/create-core-server.ts +67 -3
- package/src/core/create-core-services.ts +140 -6
- package/src/core/lifecycle.ts +28 -2
- package/src/core-config.ts +24 -1
- package/src/index.ts +2 -2
- package/src/modules/access/__tests__/access-control.service.test.ts +19 -0
- package/src/modules/access/access-control.interface.ts +13 -0
- package/src/modules/access/access-control.service.ts +16 -0
- package/src/modules/access/directory-sync-bot.ts +1 -1
- package/src/modules/agent-guide/__tests__/agent-guide.test.ts +58 -0
- package/src/modules/agent-guide/__tests__/agent-guide.tools.test.ts +19 -1
- package/src/modules/agent-guide/agent-guide.ts +9 -0
- package/src/modules/agent-guide/index.ts +1 -0
- package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +14 -0
- package/src/modules/agent-instructions/compose.ts +17 -2
- package/src/modules/agent-instructions/shared-file-rules.ts +19 -19
- package/src/modules/audit/__tests__/agent-audit.service.test.ts +64 -4
- package/src/modules/audit/__tests__/agent-connection.routes.test.ts +102 -0
- package/src/modules/audit/agent-audit.service.ts +58 -3
- package/src/modules/audit/agent-connection.routes.ts +56 -0
- package/src/modules/audit/audit.contract.ts +26 -0
- package/src/modules/audit/index.ts +1 -0
- package/src/modules/auth/__tests__/account.routes.test.ts +5 -3
- package/src/modules/auth/account.routes.ts +10 -3
- package/src/modules/auth/auth.middleware.ts +1 -1
- package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +18 -2
- package/src/modules/code-mode/code-mode.tool.ts +13 -2
- package/src/modules/database/core-schema.ts +33 -3
- package/src/modules/embed/__tests__/account-links.migration.test.ts +98 -0
- package/src/modules/embed/__tests__/embed-link.test.ts +122 -0
- package/src/modules/embed/__tests__/embed.routes.test.ts +362 -0
- package/src/modules/embed/__tests__/embed.service.lock.test.ts +193 -0
- package/src/modules/embed/__tests__/embed.service.test.ts +805 -0
- package/src/modules/embed/__tests__/embed.tools.test.ts +304 -0
- package/src/modules/embed/__tests__/mcp-app.test.ts +207 -0
- package/src/modules/embed/account-link.service.ts +76 -0
- package/src/modules/embed/embed-link.ts +180 -0
- package/src/modules/embed/embed.errors.ts +43 -0
- package/src/modules/embed/embed.interface.ts +203 -0
- package/src/modules/embed/embed.routes.ts +363 -0
- package/src/modules/embed/embed.service.ts +705 -0
- package/src/modules/embed/embed.tools.ts +206 -0
- package/src/modules/embed/mcp-app.routes.ts +41 -0
- package/src/modules/embed/mcp-app.ts +173 -0
- package/src/modules/github-app/__tests__/github-app.test.ts +76 -0
- package/src/modules/github-app/github-app.connection.ts +38 -4
- package/src/modules/kb-fs/__tests__/move-with-edits.test.ts +304 -0
- package/src/modules/kb-fs/locking-filesystem.ts +287 -0
- package/src/modules/kb-sync/__tests__/kb-sync.service.test.ts +35 -0
- package/src/modules/kb-sync/kb-sync.interface.ts +2 -0
- package/src/modules/kb-sync/kb-sync.service.ts +6 -0
- package/src/modules/mcp/__tests__/bevel-oauth-provider.test.ts +68 -11
- package/src/modules/mcp/__tests__/mcp-proxy-helpers.test.ts +37 -2
- package/src/modules/mcp/__tests__/mcp.app.e2e.test.ts +200 -0
- package/src/modules/mcp/__tests__/mcp.e2e.test.ts +3 -1
- package/src/modules/mcp/__tests__/mcp.service.test.ts +23 -13
- package/src/modules/mcp/__tests__/tool-call-interface.e2e.test.ts +499 -0
- package/src/modules/mcp/mcp-auth.middleware.ts +2 -1
- package/src/modules/mcp/mcp.service.ts +81 -12
- package/src/modules/mcp/oauth/bevel-oauth-provider.ts +42 -4
- package/src/modules/onboarding/__tests__/onboarding.routes.test.ts +163 -0
- package/src/modules/onboarding/__tests__/starter-pack.service.test.ts +635 -0
- package/src/modules/onboarding/__tests__/starter-packs.test.ts +169 -0
- package/src/modules/onboarding/onboarding.contract.ts +85 -0
- package/src/modules/onboarding/onboarding.routes.ts +85 -0
- package/src/modules/onboarding/starter-pack.service.ts +580 -0
- package/src/modules/onboarding/starter-packs.ts +182 -0
- package/src/modules/plugins/__tests__/plugins.tools.test.ts +25 -2
- package/src/modules/plugins/index.ts +1 -0
- package/src/modules/plugins/plugin-provision.service.ts +22 -1
- package/src/modules/plugins/plugins.routes.ts +29 -0
- package/src/modules/settings/__tests__/deployment-settings.service.test.ts +150 -1
- package/src/modules/settings/__tests__/repository-source.test.ts +9 -0
- package/src/modules/settings/deployment-settings.service.ts +131 -1
- package/src/modules/settings/repository-source.ts +4 -4
- package/src/modules/skills/__tests__/skills.tools.description.test.ts +5 -1
- package/src/modules/skills/skills.tools.ts +55 -35
- package/src/modules/tool-auth/__tests__/connection-key-rejection.test.ts +32 -0
- package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +65 -0
- package/src/modules/tool-auth/connection-key-rejection.ts +13 -3
- package/src/modules/tool-auth/external-api-key.interface.ts +9 -0
- package/src/modules/tool-auth/external-api-key.service.ts +61 -4
- package/src/modules/tool-auth/tool-auth.middleware.ts +13 -4
- package/src/modules/tool-helpers/__tests__/branch-declaration.test.ts +502 -0
- package/src/modules/tool-helpers/__tests__/route-argument-check.test.ts +337 -0
- package/src/modules/tool-helpers/index.ts +34 -1
- package/src/modules/tool-helpers/route-argument-check.ts +149 -0
- package/src/modules/tool-helpers/route-tool-schemas.ts +171 -0
- package/src/modules/tool-helpers/tool-def.ts +132 -7
- package/src/modules/tool-helpers/tool-handler.ts +196 -8
- package/src/modules/tool-helpers/tool.contract.ts +9 -0
- package/src/modules/tool-manuals/tool-manuals.tools.ts +15 -5
- package/src/modules/tool-registry/__tests__/own-tool-schemas.test.ts +14 -1
- package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +23 -10
- package/src/modules/tool-registry/description-length.ts +34 -7
- package/src/modules/tool-registry/guide-first.ts +8 -1
- package/src/modules/workflow/__tests__/file-lock.service.deletion-gate.test.ts +104 -0
- package/src/modules/workflow/__tests__/pending-commits.repository-replaced.test.ts +66 -0
- package/src/modules/workflow/__tests__/pending-commits.worker.test.ts +31 -2
- package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +110 -6
- package/src/modules/workflow/__tests__/sanitize-error.test.ts +89 -1
- package/src/modules/workflow/__tests__/workflow.routes.push-refused.test.ts +202 -0
- package/src/modules/workflow/__tests__/workflow.service.branch-in-use.test.ts +6 -6
- package/src/modules/workflow/__tests__/workflow.service.delete-branch.test.ts +1026 -0
- package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +468 -10
- package/src/modules/workflow/__tests__/workflow.service.folder-proposals.test.ts +4 -0
- package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +5 -4
- package/src/modules/workflow/__tests__/workflow.service.update-from-target.test.ts +54 -2
- package/src/modules/workflow/agent-tools/__tests__/change-request-read.tools.test.ts +41 -5
- package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +127 -2
- package/src/modules/workflow/agent-tools/change-request-read.tools.ts +157 -122
- package/src/modules/workflow/agent-tools/workflow.tools.ts +129 -35
- package/src/modules/workflow/file-lock.service.ts +67 -5
- package/src/modules/workflow/git/__tests__/git.service.deleteBranch.test.ts +164 -0
- package/src/modules/workflow/git/__tests__/git.service.pull.test.ts +48 -1
- package/src/modules/workflow/git/__tests__/node-git-runner.test.ts +231 -2
- package/src/modules/workflow/git/git.service.ts +176 -38
- package/src/modules/workflow/git/node-git-runner.ts +82 -7
- package/src/modules/workflow/pending-commits.service.ts +29 -1
- package/src/modules/workflow/pending-commits.worker.ts +24 -6
- package/src/modules/workflow/recovery-bot.ts +1 -1
- package/src/modules/workflow/review-workflow/review-workflow.service.ts +2 -1
- package/src/modules/workflow/sanitize-error.ts +50 -0
- package/src/modules/workflow/workflow.service.ts +879 -112
- package/src/modules/workspace/__tests__/agent-downloads.test.ts +823 -0
- package/src/modules/workspace/__tests__/first-run.test.ts +234 -0
- package/src/modules/workspace/__tests__/git-internals.security.test.ts +5 -2
- package/src/modules/workspace/__tests__/md-links.test.ts +353 -0
- package/src/modules/workspace/__tests__/move-links.test.ts +261 -0
- package/src/modules/workspace/__tests__/rest-tool-arguments.test.ts +219 -0
- package/src/modules/workspace/__tests__/workspace-paths-inside-the-repo.test.ts +3 -0
- package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +102 -0
- package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +52 -5
- package/src/modules/workspace/__tests__/workspace.routes.folder-zip-access.test.ts +190 -0
- package/src/modules/workspace/__tests__/workspace.service.forget-clone-races.test.ts +8 -4
- package/src/modules/workspace/__tests__/workspace.service.test.ts +50 -8
- package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +44 -1
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +409 -12
- package/src/modules/workspace/agent-download.builder.ts +308 -0
- package/src/modules/workspace/agent-download.routes.ts +183 -0
- package/src/modules/workspace/agent-download.store.ts +533 -0
- package/src/modules/workspace/agent-upload.store.ts +23 -7
- package/src/modules/workspace/file-readers/raw-mime.ts +40 -0
- package/src/modules/workspace/file-readers/text-reader.ts +5 -8
- package/src/modules/workspace/first-run.ts +222 -0
- package/src/modules/workspace/move-links.ts +237 -0
- package/src/modules/workspace/startup/steps/seed-tree.ts +3 -12
- package/src/modules/workspace/workspace.routes.ts +64 -22
- package/src/modules/workspace/workspace.service.ts +202 -25
- package/src/modules/workspace/workspace.tools.ts +492 -63
- package/src/modules/write-access/__tests__/write-access.test.ts +13 -0
- package/src/modules/write-access/write-access.ts +9 -0
- package/src/shared/domain-errors.ts +108 -11
- package/src/shared/git.contract.ts +8 -1
- package/src/shared/utf8-text.ts +21 -0
- package/src/tenancy/__tests__/static-tenant-source.test.ts +1 -0
- package/src/tenancy/static-tenant-source.ts +10 -2
- package/starter-packs/README.md +103 -0
- package/starter-packs/engineering/KnowledgeBase/About us.md +9 -0
- package/starter-packs/engineering/KnowledgeBase/Architecture.md +9 -0
- package/starter-packs/engineering/KnowledgeBase/Glossary.md +9 -0
- package/starter-packs/engineering/KnowledgeBase/How we ship.md +9 -0
- package/starter-packs/engineering/Plugins/engineering-starter/LICENSE +21 -0
- package/starter-packs/engineering/Plugins/engineering-starter/SOURCE.md +20 -0
- package/starter-packs/engineering/Plugins/engineering-starter/access.md +19 -0
- package/starter-packs/engineering/Plugins/engineering-starter/plugin.json +10 -0
- package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/SKILL.md +171 -0
- package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/sections/phase-2a-startup-diagnostic.md +157 -0
- package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/sections/phase-2b-builder-brainstorm.md +39 -0
- package/starter-packs/engineering/Plugins/engineering-starter/skills/plan-ceo-review/SKILL.md +144 -0
- package/starter-packs/engineering/Plugins/engineering-starter/skills/plan-eng-review/SKILL.md +144 -0
- package/starter-packs/engineering/Plugins/engineering-starter/skills/review/SKILL.md +80 -0
- package/starter-packs/engineering/Plugins/engineering-starter/skills/review/checklist.md +184 -0
- package/starter-packs/engineering/pack.yaml +11 -0
- package/starter-packs/general/KnowledgeBase/About us.md +9 -0
- package/starter-packs/general/KnowledgeBase/Glossary.md +9 -0
- package/starter-packs/general/KnowledgeBase/How we work.md +9 -0
- package/starter-packs/general/Plugins/general-starter/LICENSE +202 -0
- package/starter-packs/general/Plugins/general-starter/SOURCE.md +10 -0
- package/starter-packs/general/Plugins/general-starter/access.md +19 -0
- package/starter-packs/general/Plugins/general-starter/plugin.json +10 -0
- package/starter-packs/general/Plugins/general-starter/skills/memory-management/SKILL.md +332 -0
- package/starter-packs/general/Plugins/general-starter/skills/productivity-start/SKILL.md +163 -0
- package/starter-packs/general/Plugins/general-starter/skills/productivity-update/SKILL.md +167 -0
- package/starter-packs/general/Plugins/general-starter/skills/task-management/SKILL.md +83 -0
- package/starter-packs/general/pack.yaml +10 -0
- package/starter-packs/operations/KnowledgeBase/Glossary.md +9 -0
- package/starter-packs/operations/KnowledgeBase/How we work.md +9 -0
- package/starter-packs/operations/KnowledgeBase/Processes.md +9 -0
- package/starter-packs/operations/KnowledgeBase/Tools we use.md +9 -0
- package/starter-packs/operations/KnowledgeBase/Vendors.md +9 -0
- package/starter-packs/operations/Plugins/operations-starter/LICENSE +202 -0
- package/starter-packs/operations/Plugins/operations-starter/SOURCE.md +10 -0
- package/starter-packs/operations/Plugins/operations-starter/access.md +19 -0
- package/starter-packs/operations/Plugins/operations-starter/plugin.json +10 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/capacity-plan/SKILL.md +111 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/change-request/SKILL.md +119 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/compliance-tracking/SKILL.md +44 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/process-doc/SKILL.md +80 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/process-optimization/SKILL.md +39 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/risk-assessment/SKILL.md +40 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/runbook/SKILL.md +85 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/status-report/SKILL.md +75 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/vendor-review/SKILL.md +102 -0
- package/starter-packs/operations/pack.yaml +12 -0
- package/starter-packs/sales/KnowledgeBase/About us.md +9 -0
- package/starter-packs/sales/KnowledgeBase/Customers.md +9 -0
- package/starter-packs/sales/KnowledgeBase/Glossary.md +9 -0
- package/starter-packs/sales/KnowledgeBase/How we sell.md +9 -0
- package/starter-packs/sales/KnowledgeBase/Objections.md +9 -0
- package/starter-packs/sales/KnowledgeBase/Pricing.md +9 -0
- package/starter-packs/sales/Plugins/sales-starter/LICENSE +202 -0
- package/starter-packs/sales/Plugins/sales-starter/SOURCE.md +10 -0
- package/starter-packs/sales/Plugins/sales-starter/access.md +19 -0
- package/starter-packs/sales/Plugins/sales-starter/plugin.json +10 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/account-context/SKILL.md +94 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/account-plan/SKILL.md +95 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/account-research/SKILL.md +99 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/account-tiering/SKILL.md +101 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/call-prep/SKILL.md +111 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/call-summary/SKILL.md +111 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/close-plan/SKILL.md +106 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/competitive-intelligence/SKILL.md +102 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/create-an-asset/SKILL.md +105 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/crm-hygiene-check/SKILL.md +103 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/customer-health/SKILL.md +108 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/customer-voice/SKILL.md +94 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/daily-briefing/SKILL.md +61 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/deal-advance-gap/SKILL.md +102 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/deal-review/SKILL.md +105 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/deal-signals/SKILL.md +96 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/deal-slip-scenario/SKILL.md +104 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/draft-outreach/SKILL.md +116 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/end-of-day/SKILL.md +114 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/expansion-whitespace/SKILL.md +97 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/forecast/SKILL.md +100 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/handle-objection/SKILL.md +93 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/inbox-sweep/SKILL.md +114 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/lead-triage/SKILL.md +106 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/log-activity/SKILL.md +111 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/pipeline-review/SKILL.md +97 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/renewal-radar/SKILL.md +95 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/rep-context/SKILL.md +90 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/route-lead/SKILL.md +111 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/sales-setup/SKILL.md +200 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/schedule-meeting/SKILL.md +99 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/stakeholder-map/SKILL.md +107 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/team-pipeline/SKILL.md +105 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/update-opportunity/SKILL.md +112 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/weekly-wrap/SKILL.md +90 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/win-loss-review/SKILL.md +88 -0
- package/starter-packs/sales/pack.yaml +13 -0
- package/starter-packs/support/KnowledgeBase/Escalations.md +9 -0
- package/starter-packs/support/KnowledgeBase/Glossary.md +9 -0
- package/starter-packs/support/KnowledgeBase/Known issues.md +9 -0
- package/starter-packs/support/KnowledgeBase/Products.md +9 -0
- package/starter-packs/support/KnowledgeBase/Tone of voice.md +9 -0
- package/starter-packs/support/Plugins/support-starter/LICENSE +202 -0
- package/starter-packs/support/Plugins/support-starter/SOURCE.md +10 -0
- package/starter-packs/support/Plugins/support-starter/access.md +19 -0
- package/starter-packs/support/Plugins/support-starter/plugin.json +10 -0
- package/starter-packs/support/Plugins/support-starter/skills/customer-escalation/SKILL.md +246 -0
- package/starter-packs/support/Plugins/support-starter/skills/customer-research/SKILL.md +250 -0
- package/starter-packs/support/Plugins/support-starter/skills/draft-response/SKILL.md +416 -0
- package/starter-packs/support/Plugins/support-starter/skills/kb-article/SKILL.md +350 -0
- package/starter-packs/support/Plugins/support-starter/skills/ticket-triage/SKILL.md +273 -0
- package/starter-packs/support/pack.yaml +12 -0
|
@@ -11,9 +11,11 @@ import type { PendingCommitsService } from '../pending-commits.service.js';
|
|
|
11
11
|
import type { Database } from '../../database/connection.js';
|
|
12
12
|
import { WorkflowService } from '../workflow.service.js';
|
|
13
13
|
import { openChangeGate } from '../../../__tests__/open-change-gate.js';
|
|
14
|
+
import type { WorkflowEventBus } from '../event-bus.js';
|
|
14
15
|
import {
|
|
15
16
|
ChangeRequestConflictsError,
|
|
16
17
|
PullRebaseConflictError,
|
|
18
|
+
PushNeedsAgentResolutionError,
|
|
17
19
|
WorkflowDomainError,
|
|
18
20
|
} from '../../../shared/domain-errors.js';
|
|
19
21
|
|
|
@@ -76,6 +78,8 @@ function harness(opts: {
|
|
|
76
78
|
/** Does the clone hold commits origin has not seen? */
|
|
77
79
|
unpushed?: boolean;
|
|
78
80
|
unpushedError?: Error;
|
|
81
|
+
/** Each push answers the next of these in turn: an Error rejects, undefined lands. */
|
|
82
|
+
pushes?: Array<Error | undefined>;
|
|
79
83
|
}) {
|
|
80
84
|
// Two resolves per update: once after the pull (where the branch IS), once
|
|
81
85
|
// after the merge (where it ended up). One mock that answers in order, so a
|
|
@@ -93,7 +97,10 @@ function harness(opts: {
|
|
|
93
97
|
? vi.fn().mockRejectedValue(opts.pullError)
|
|
94
98
|
: vi.fn().mockResolvedValue({ treeChanged: false }),
|
|
95
99
|
mergeFromOrigin: vi.fn().mockResolvedValue(opts.merge ?? { kind: 'clean', alreadyUpToDate: false }),
|
|
96
|
-
push:
|
|
100
|
+
push: (opts.pushes ?? []).reduce(
|
|
101
|
+
(fn, outcome) => (outcome ? fn.mockRejectedValueOnce(outcome) : fn.mockResolvedValueOnce(undefined)),
|
|
102
|
+
vi.fn().mockResolvedValue(undefined),
|
|
103
|
+
),
|
|
97
104
|
hasUnpushedCommits: opts.unpushedError
|
|
98
105
|
? vi.fn().mockRejectedValue(opts.unpushedError)
|
|
99
106
|
: vi.fn().mockResolvedValue(opts.unpushed ?? true),
|
|
@@ -114,6 +121,7 @@ function harness(opts: {
|
|
|
114
121
|
const reviewWorkflow = {
|
|
115
122
|
carryApprovalsForward: vi.fn().mockResolvedValue(opts.carried ?? 0),
|
|
116
123
|
};
|
|
124
|
+
const emit = vi.fn();
|
|
117
125
|
const svc = new WorkflowService(
|
|
118
126
|
{} as unknown as Database,
|
|
119
127
|
git as unknown as GitService,
|
|
@@ -125,8 +133,9 @@ function harness(opts: {
|
|
|
125
133
|
pendingCommits as unknown as PendingCommitsService,
|
|
126
134
|
testKbContext(),
|
|
127
135
|
openChangeGate(),
|
|
136
|
+
{ emit } as unknown as WorkflowEventBus,
|
|
128
137
|
);
|
|
129
|
-
return { svc, git, prs, pendingCommits, reviewWorkflow, refreshed };
|
|
138
|
+
return { svc, git, prs, pendingCommits, reviewWorkflow, refreshed, emit };
|
|
130
139
|
}
|
|
131
140
|
|
|
132
141
|
describe('WorkflowService.updateFromTarget', () => {
|
|
@@ -385,4 +394,47 @@ describe('WorkflowService.updateFromTarget: the approvals the merge did not touc
|
|
|
385
394
|
// slow beats a dialog that goes on presenting pre-merge text as current.
|
|
386
395
|
expect(result.updatedPaths).toEqual(['Sales/Deal.md']);
|
|
387
396
|
});
|
|
397
|
+
|
|
398
|
+
describe('when the repository host refuses the push', () => {
|
|
399
|
+
const REFUSED = new Error('git push failed: remote: Internal Server Error');
|
|
400
|
+
|
|
401
|
+
it('keeps the merge applied locally and answers 409 with the saved-locally sentence and the banner', async () => {
|
|
402
|
+
const h = harness({ changedPaths: ['Sales/Deal.md'], pushes: [REFUSED] });
|
|
403
|
+
const err = await h.svc.updateFromTarget(WS, USER, 7).catch((e: unknown) => e);
|
|
404
|
+
|
|
405
|
+
expect(err).toBeInstanceOf(PushNeedsAgentResolutionError);
|
|
406
|
+
expect((err as PushNeedsAgentResolutionError).status).toBe(409);
|
|
407
|
+
expect((err as Error).message).toContain('Saved locally on "alice/deal"');
|
|
408
|
+
expect(JSON.stringify((err as PushNeedsAgentResolutionError).payload)).not.toContain('Internal Server Error');
|
|
409
|
+
// One attempt: a refusal is not a divergence, so nothing is retried.
|
|
410
|
+
expect(h.git.push).toHaveBeenCalledTimes(1);
|
|
411
|
+
// The merge ran and stays in the clone — the next push carries it.
|
|
412
|
+
expect(h.git.mergeFromOrigin).toHaveBeenCalledTimes(1);
|
|
413
|
+
expect(h.emit).toHaveBeenCalledWith(
|
|
414
|
+
expect.objectContaining({ kind: 'git-sync-failed', workspaceId: 'alice/deal', branch: 'alice/deal' }),
|
|
415
|
+
);
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
it('the next update pushes what is still unpushed, and the banner clears', async () => {
|
|
419
|
+
const h = harness({
|
|
420
|
+
changedPaths: ['Sales/Deal.md'],
|
|
421
|
+
unpushed: true,
|
|
422
|
+
pushes: [REFUSED, undefined],
|
|
423
|
+
});
|
|
424
|
+
await expect(h.svc.updateFromTarget(WS, USER, 7)).rejects.toBeInstanceOf(PushNeedsAgentResolutionError);
|
|
425
|
+
await h.svc.updateFromTarget(WS, USER, 7);
|
|
426
|
+
const kinds = h.emit.mock.calls.map((c) => (c[0] as { kind: string }).kind).filter((k) => k.startsWith('git-sync-'));
|
|
427
|
+
expect(kinds).toEqual(['git-sync-failed', 'git-sync-recovered']);
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
it('a non-fast-forward recovers with a merge-preserving pull, so the merge stays a merge', async () => {
|
|
431
|
+
const h = harness({
|
|
432
|
+
changedPaths: ['Sales/Deal.md'],
|
|
433
|
+
pushes: [new Error('! [rejected] alice/deal -> alice/deal (non-fast-forward)'), undefined],
|
|
434
|
+
});
|
|
435
|
+
await h.svc.updateFromTarget(WS, USER, 7);
|
|
436
|
+
expect(h.git.push).toHaveBeenCalledTimes(2);
|
|
437
|
+
expect(h.git.pull).toHaveBeenLastCalledWith(WS, { preserveMerges: true });
|
|
438
|
+
});
|
|
439
|
+
});
|
|
388
440
|
});
|
|
@@ -383,6 +383,25 @@ describe('list_change_requests', () => {
|
|
|
383
383
|
expect(calls).toContainEqual(['listChangeRequestsByState', ['closed', 'merged', 'open']]);
|
|
384
384
|
});
|
|
385
385
|
|
|
386
|
+
it('refuses a `state` it does not know instead of answering the open ones', async () => {
|
|
387
|
+
const base = await start();
|
|
388
|
+
// `merged` is what the ANSWER says for an applied request, so it is the
|
|
389
|
+
// word an agent is most likely to feed back in; a typo is refused too.
|
|
390
|
+
// The route's argument check answers first — the schema pins `state` to
|
|
391
|
+
// its three values — and names them in its refusal; the handler's own
|
|
392
|
+
// check beneath it is the floor for a caller that reaches it another way.
|
|
393
|
+
for (const state of ['merged', 'applied', 'OPEN', '', 7, null]) {
|
|
394
|
+
calls = [];
|
|
395
|
+
const { status, json } = await call(base, 'list_change_requests', { state });
|
|
396
|
+
expect(status, JSON.stringify(state)).toBe(400);
|
|
397
|
+
expect((json as { kind?: string }).kind, JSON.stringify(state)).toBe('arguments-do-not-match');
|
|
398
|
+
// The three values are named either in the mismatch line (a wrong
|
|
399
|
+
// string) or in the interface the refusal carries (a wrong type).
|
|
400
|
+
expect(JSON.stringify(json), JSON.stringify(state)).toMatch(/open.*closed.*all/);
|
|
401
|
+
expect(calls, JSON.stringify(state)).toEqual([]);
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
|
|
386
405
|
it('filters by target branch and by author', async () => {
|
|
387
406
|
const base = await start();
|
|
388
407
|
summaries = [
|
|
@@ -619,6 +638,19 @@ describe('get_change_request', () => {
|
|
|
619
638
|
expect(absent.json.error).toBe('Change request #99 not found.');
|
|
620
639
|
});
|
|
621
640
|
|
|
641
|
+
// An OPEN request that proposes nothing, before the background sweep has
|
|
642
|
+
// closed it: still "not found" to an agent that is not its author. Closing
|
|
643
|
+
// empty requests changed nothing about what agents may see.
|
|
644
|
+
it('answers not found for an open request with no files to an agent that is not its author', async () => {
|
|
645
|
+
const base = await start();
|
|
646
|
+
details.set(12, detail({ state: 'open', files: [], approvals: [] }));
|
|
647
|
+
const { status, json } = await call(base, 'get_change_request', { number: 12 });
|
|
648
|
+
expect(status).toBe(404);
|
|
649
|
+
expect(json.error).toBe('Change request #12 not found.');
|
|
650
|
+
callerEmail = AUTHOR;
|
|
651
|
+
expect((await call(base, 'get_change_request', { number: 12 })).status).toBe(200);
|
|
652
|
+
});
|
|
653
|
+
|
|
622
654
|
it('answers not found when the access tree at the target cannot be resolved', async () => {
|
|
623
655
|
const base = await start();
|
|
624
656
|
readable = null;
|
|
@@ -1033,11 +1065,15 @@ describe('a mixed-access caller is never handed a path they may not read', () =>
|
|
|
1033
1065
|
mixedAccessRequest();
|
|
1034
1066
|
summaries = [details.get(1)!];
|
|
1035
1067
|
for (const tool of TOOLS) {
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
tool === 'list_change_requests'
|
|
1040
|
-
|
|
1068
|
+
// `include` only where the tool declares it: a call naming an argument
|
|
1069
|
+
// its tool does not have is refused before it runs.
|
|
1070
|
+
const args =
|
|
1071
|
+
tool === 'list_change_requests'
|
|
1072
|
+
? {}
|
|
1073
|
+
: tool === 'list_change_request_files'
|
|
1074
|
+
? { number: 1, include: ['patches'] }
|
|
1075
|
+
: { number: 1 };
|
|
1076
|
+
const { status, json } = await call(base, tool, args);
|
|
1041
1077
|
expect(status, tool).toBe(200);
|
|
1042
1078
|
const whole = JSON.stringify(json);
|
|
1043
1079
|
expect(whole, tool).not.toContain('Avi-Checkin');
|
|
@@ -9,7 +9,13 @@ import { createToolContextResolver } from '../../../tool-helpers/tool-context.js
|
|
|
9
9
|
import { createToolHandlerFactory } from '../../../tool-helpers/tool-handler.js';
|
|
10
10
|
import { createManualRoutes } from '../../../tool-registry/manual.routes.js';
|
|
11
11
|
import { registerWorkflowTools } from '../workflow.tools.js';
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
BranchAuthorshipError,
|
|
14
|
+
BranchDeleteBlockedError,
|
|
15
|
+
BranchNotFoundError,
|
|
16
|
+
ChangeRequestConflictsError,
|
|
17
|
+
OpenChangeRequestBlocksMergeError,
|
|
18
|
+
} from '../../../../shared/domain-errors.js';
|
|
13
19
|
|
|
14
20
|
const WS = 'target-company-state';
|
|
15
21
|
|
|
@@ -144,6 +150,34 @@ const workflowService = {
|
|
|
144
150
|
}
|
|
145
151
|
return { kind: 'merged', sha: 'merge-sha' };
|
|
146
152
|
},
|
|
153
|
+
deleteBranchChecked: async (
|
|
154
|
+
user: { id: string },
|
|
155
|
+
name: string,
|
|
156
|
+
{ maySee, ...opts }: { dryRun?: boolean; discardUnmerged?: boolean; maySee?: (n: number) => Promise<boolean> },
|
|
157
|
+
) => {
|
|
158
|
+
calls.push(['deleteBranchChecked', user.id, name, opts]);
|
|
159
|
+
if (maySee) calls.push(['maySee', 12, await maySee(12)], ['maySee', 13, await maySee(13)]);
|
|
160
|
+
if (name === 'me/missing') throw new BranchNotFoundError(name);
|
|
161
|
+
if (name === 'ali/theirs') throw new BranchAuthorshipError(name);
|
|
162
|
+
if (name === 'me/proposed') {
|
|
163
|
+
throw new BranchDeleteBlockedError(
|
|
164
|
+
'open-change-request',
|
|
165
|
+
name,
|
|
166
|
+
'Change request #12 is open from "me/proposed". Ask the user to withdraw or decline it in the app; then the branch can be deleted.',
|
|
167
|
+
{ number: 12 },
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
if (name === 'me/unmerged' && !(opts as { discardUnmerged?: boolean }).discardUnmerged) {
|
|
171
|
+
throw new BranchDeleteBlockedError('unmerged-commits', name, '"me/unmerged" holds 3 commits…', { unmergedCommits: 3 });
|
|
172
|
+
}
|
|
173
|
+
if (name === 'me/offline') {
|
|
174
|
+
throw new BranchDeleteBlockedError('state-unconfirmed', name, 'Could not reach the shared repository…');
|
|
175
|
+
}
|
|
176
|
+
if ((opts as { dryRun?: boolean }).dryRun) {
|
|
177
|
+
return { kind: 'preview', branch: name, exists: true, canDelete: true, refusals: [], unmergedCommits: 0, openChangeRequests: [], lastCommit: 'tip-1' };
|
|
178
|
+
}
|
|
179
|
+
return { kind: 'deleted', branch: name, lastCommit: 'tip-1', discardedCommits: name === 'me/unmerged' ? 3 : 0 };
|
|
180
|
+
},
|
|
147
181
|
postComment: async (number: number, _user: unknown, input: { path?: string }) => {
|
|
148
182
|
calls.push(['postComment', number, input.path]);
|
|
149
183
|
return { id: 'c-1' };
|
|
@@ -169,7 +203,11 @@ async function start(): Promise<string> {
|
|
|
169
203
|
const toolHandler = createToolHandlerFactory(resolve);
|
|
170
204
|
|
|
171
205
|
const router = express.Router();
|
|
172
|
-
|
|
206
|
+
// Request #13 is one the caller may not see.
|
|
207
|
+
registerWorkflowTools(registry, router, toolAuth, toolHandler, testKbContext(), async (ctx, number) => {
|
|
208
|
+
calls.push(['maySeeAs', ctx.user.id, number]);
|
|
209
|
+
return number !== 13;
|
|
210
|
+
});
|
|
173
211
|
router.use(createManualRoutes(registry, toolAuth));
|
|
174
212
|
|
|
175
213
|
const app = express();
|
|
@@ -250,6 +288,80 @@ describe('registerWorkflowTools', () => {
|
|
|
250
288
|
expect(calls.some((c) => c[0] === 'postComment')).toBe(false);
|
|
251
289
|
});
|
|
252
290
|
|
|
291
|
+
describe('delete_branch', () => {
|
|
292
|
+
it('is on both catalogs, takes name, dryRun and discardUnmerged, and is a write tool', async () => {
|
|
293
|
+
await start();
|
|
294
|
+
for (const tools of [await registryRef!.listInternal(), await registryRef!.listExternal()]) {
|
|
295
|
+
const tool = tools.find((t) => t.name === 'delete_branch');
|
|
296
|
+
expect(tool).toBeDefined();
|
|
297
|
+
// `toolDef` wraps a tool's own inputs under `body`.
|
|
298
|
+
const inputs = (tool!.inputs as { properties: { body: { properties: Record<string, unknown>; required: string[] } } })
|
|
299
|
+
.properties.body;
|
|
300
|
+
expect(Object.keys(inputs.properties).sort()).toEqual(['discardUnmerged', 'dryRun', 'name']);
|
|
301
|
+
expect(inputs.required).toEqual(['name']);
|
|
302
|
+
expect(tool!.tags).toContain('write');
|
|
303
|
+
// An empty request is closed by the delete; a live one refuses it, linked.
|
|
304
|
+
expect(tool!.description).toContain('proposes nothing is closed');
|
|
305
|
+
expect(tool!.description).toContain('its author withdraws it or an Admin declines it');
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('deletes as the caller and answers the last commit', async () => {
|
|
310
|
+
const base = await start();
|
|
311
|
+
const res = await post(`${base}/api/agent/tools/delete_branch`, writeTok(), { name: 'me/draft' });
|
|
312
|
+
expect(res.status).toBe(200);
|
|
313
|
+
expect(await res.json()).toEqual({ kind: 'deleted', branch: 'me/draft', lastCommit: 'tip-1', discardedCommits: 0 });
|
|
314
|
+
expect(calls).toContainEqual(['deleteBranchChecked', 'user-A', 'me/draft', { dryRun: false, discardUnmerged: false }]);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('passes dryRun and discardUnmerged through', async () => {
|
|
318
|
+
const base = await start();
|
|
319
|
+
const preview = await post(`${base}/api/agent/tools/delete_branch`, writeTok(), { name: 'me/draft', dryRun: true });
|
|
320
|
+
expect(await preview.json()).toMatchObject({ kind: 'preview', canDelete: true, lastCommit: 'tip-1' });
|
|
321
|
+
const discarded = await post(`${base}/api/agent/tools/delete_branch`, writeTok(), { name: 'me/unmerged', discardUnmerged: true });
|
|
322
|
+
expect(await discarded.json()).toMatchObject({ kind: 'deleted', discardedCommits: 3 });
|
|
323
|
+
expect(calls).toContainEqual(['deleteBranchChecked', 'user-A', 'me/draft', { dryRun: true, discardUnmerged: false }]);
|
|
324
|
+
expect(calls).toContainEqual(['deleteBranchChecked', 'user-A', 'me/unmerged', { dryRun: false, discardUnmerged: true }]);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it('answers each refusal with its status and message', async () => {
|
|
328
|
+
const base = await start();
|
|
329
|
+
const ask = async (name: string) => {
|
|
330
|
+
const res = await post(`${base}/api/agent/tools/delete_branch`, writeTok(), { name });
|
|
331
|
+
return { status: res.status, body: (await res.json()) as Record<string, unknown> };
|
|
332
|
+
};
|
|
333
|
+
const missing = await ask('me/missing');
|
|
334
|
+
expect(missing.status).toBe(404);
|
|
335
|
+
expect(missing.body.error).toMatch(/no branch named me\/missing/);
|
|
336
|
+
const theirs = await ask('ali/theirs');
|
|
337
|
+
expect(theirs.status).toBe(403);
|
|
338
|
+
expect(theirs.body.error).toBe('Only the author of "ali/theirs" can delete it.');
|
|
339
|
+
const proposed = await ask('me/proposed');
|
|
340
|
+
expect(proposed.status).toBe(409);
|
|
341
|
+
expect(proposed.body).toMatchObject({ reason: 'open-change-request', number: 12 });
|
|
342
|
+
expect(proposed.body.error).toMatch(/#12.*withdraw or decline it in the app/);
|
|
343
|
+
const unmerged = await ask('me/unmerged');
|
|
344
|
+
expect(unmerged.status).toBe(409);
|
|
345
|
+
expect(unmerged.body).toMatchObject({ reason: 'unmerged-commits', unmergedCommits: 3 });
|
|
346
|
+
expect((await ask('me/offline')).status).toBe(503);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it('requires a name', async () => {
|
|
350
|
+
const base = await start();
|
|
351
|
+
const res = await post(`${base}/api/agent/tools/delete_branch`, writeTok(), {});
|
|
352
|
+
expect(res.status).toBe(400);
|
|
353
|
+
expect(calls.some((c) => c[0] === 'deleteBranchChecked')).toBe(false);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it("hands the service the caller's view of change requests, as get_change_request gates it", async () => {
|
|
357
|
+
const base = await start();
|
|
358
|
+
await post(`${base}/api/agent/tools/delete_branch`, writeTok(), { name: 'me/draft', dryRun: true });
|
|
359
|
+
expect(calls).toContainEqual(['maySeeAs', 'user-A', 12]);
|
|
360
|
+
expect(calls).toContainEqual(['maySee', 12, true]);
|
|
361
|
+
expect(calls).toContainEqual(['maySee', 13, false]);
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
|
|
253
365
|
// An agent proposes and syncs; a person merges.
|
|
254
366
|
describe('merging', () => {
|
|
255
367
|
it('merge_change_request is gone: its old path answers who merges now, and never merges', async () => {
|
|
@@ -304,6 +416,19 @@ describe('registerWorkflowTools', () => {
|
|
|
304
416
|
const mergeBranch = tools.find((t) => t.name === 'merge_branch')!;
|
|
305
417
|
expect(mergeBranch.description).toMatch(/a person merges/);
|
|
306
418
|
expect(mergeBranch.description).toMatch(/SYNC/);
|
|
419
|
+
// Every outcome is named, and the asynchronous commit the merge waits for.
|
|
420
|
+
for (const kind of ['merged', 'nothing-to-merge', 'pending-commits', 'conflicts-need-resolution']) {
|
|
421
|
+
expect(mergeBranch.description, kind).toContain(`\`${kind}\``);
|
|
422
|
+
}
|
|
423
|
+
expect(mergeBranch.description).toMatch(/committed asynchronously/);
|
|
424
|
+
expect(mergeBranch.description).toMatch(/waits up to 20s/);
|
|
425
|
+
const outcome = (mergeBranch.outputs as { properties: { outcome: { description: string; properties: Record<string, { enum?: string[] }> } } })
|
|
426
|
+
.properties.outcome;
|
|
427
|
+
expect(outcome.properties.kind.enum).toEqual(['merged', 'nothing-to-merge', 'pending-commits', 'conflicts-need-resolution']);
|
|
428
|
+
expect(Object.keys(outcome.properties)).toEqual(
|
|
429
|
+
expect.arrayContaining(['sha', 'conflictedPaths', 'branch', 'pending', 'needsAttention', 'message']),
|
|
430
|
+
);
|
|
431
|
+
expect(outcome.description).toMatch(/committed asynchronously and the merge waits/);
|
|
307
432
|
}
|
|
308
433
|
});
|
|
309
434
|
});
|
|
@@ -133,14 +133,6 @@ export function registerChangeRequestReadTools(
|
|
|
133
133
|
router.post(path.slice('/api'.length), toolAuth, toolHandler(spec.handler, { write: false }));
|
|
134
134
|
};
|
|
135
135
|
|
|
136
|
-
/**
|
|
137
|
-
* A clone to resolve access verdicts in. All clones track the same origin and
|
|
138
|
-
* every verdict here is read at `origin/<base>`, so any existing one answers
|
|
139
|
-
* identically — reusing one avoids cloning a branch just to read a ref.
|
|
140
|
-
*/
|
|
141
|
-
const repoGlobalWorkspaceId = async (ctx: ToolContext): Promise<string> =>
|
|
142
|
-
(await ctx.workspaceService.findAnyWorkspaceId()) ?? kb.defaultWorkspaceId();
|
|
143
|
-
|
|
144
136
|
/** A change-request number as the tools accept it. */
|
|
145
137
|
const numberArg = (args: Record<string, unknown>): number => {
|
|
146
138
|
const n = args.number;
|
|
@@ -153,119 +145,7 @@ export function registerChangeRequestReadTools(
|
|
|
153
145
|
return n;
|
|
154
146
|
};
|
|
155
147
|
|
|
156
|
-
|
|
157
|
-
* Read verdicts for `paths` at `origin/<base>`, as a membership set of what
|
|
158
|
-
* the caller MAY read. A null answer (the ref or its `roles.yaml` does not
|
|
159
|
-
* resolve) withholds everything, which is the same reading the app's
|
|
160
|
-
* fork-point route gives it.
|
|
161
|
-
*/
|
|
162
|
-
const readablePaths = async (
|
|
163
|
-
ctx: ToolContext,
|
|
164
|
-
workspaceId: string,
|
|
165
|
-
base: string,
|
|
166
|
-
paths: string[],
|
|
167
|
-
): Promise<Set<string>> => {
|
|
168
|
-
if (paths.length === 0) return new Set();
|
|
169
|
-
const verdicts = await accessControl.canReadBatchAtRef(
|
|
170
|
-
workspaceId,
|
|
171
|
-
`origin/${base}`,
|
|
172
|
-
ctx.user.email,
|
|
173
|
-
[...new Set(paths)],
|
|
174
|
-
);
|
|
175
|
-
if (!verdicts) return new Set();
|
|
176
|
-
return new Set([...verdicts].filter(([, allowed]) => allowed).map(([p]) => p));
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* The detail of request `number` with the access filter applied, or a 404 —
|
|
181
|
-
* the one place the four by-number tools get their data, so they cannot
|
|
182
|
-
* drift on who may see what.
|
|
183
|
-
*/
|
|
184
|
-
interface ScopedDetail {
|
|
185
|
-
detail: ChangeRequestDetail;
|
|
186
|
-
/**
|
|
187
|
-
* Whether the caller may read one path of this request — the raw per-path
|
|
188
|
-
* verdict, as the access tree gives it at `origin/<base>`.
|
|
189
|
-
*/
|
|
190
|
-
mayRead: (path: string) => boolean;
|
|
191
|
-
/**
|
|
192
|
-
* Whether this request may NAME `path` to the caller — `mayRead`, minus
|
|
193
|
-
* every name a withheld file goes by. The ONE verdict the tools below ask
|
|
194
|
-
* about a path, so none of them can decide it a second way.
|
|
195
|
-
*
|
|
196
|
-
* The two differ on exactly one shape, and it is the shape that leaks: a
|
|
197
|
-
* file moved out of a folder the caller may not read is readable under
|
|
198
|
-
* its NEW name, yet `fileIsReadable` withholds it whole (its diff shows the
|
|
199
|
-
* old path's content). Anything keyed on that new path — an approval, a
|
|
200
|
-
* comment anchored to it — would otherwise say the request touches a file
|
|
201
|
-
* the file tool refuses to list, under a name it refuses to print.
|
|
202
|
-
*/
|
|
203
|
-
mayShow: (path: string) => boolean;
|
|
204
|
-
/** The request's files the caller may read, in the request's own order. */
|
|
205
|
-
readableFiles: ChangeRequestDetail['files'];
|
|
206
|
-
/**
|
|
207
|
-
* Every name the withheld files go by — for filtering the gate blockers and
|
|
208
|
-
* for deciding `mayShow`. NEVER answered: use `withheldFileCount` to report
|
|
209
|
-
* them.
|
|
210
|
-
*/
|
|
211
|
-
withheldFilePaths: string[];
|
|
212
|
-
/** How many files are withheld. One per file, whatever its move names. */
|
|
213
|
-
withheldFileCount: number;
|
|
214
|
-
viewerIsAuthor: boolean;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const scopedDetail = async (
|
|
218
|
-
ctx: ToolContext,
|
|
219
|
-
number: number,
|
|
220
|
-
opts: { patches: boolean },
|
|
221
|
-
): Promise<ScopedDetail> => {
|
|
222
|
-
const workspaceId = await repoGlobalWorkspaceId(ctx);
|
|
223
|
-
const detail = await ctx.workflowService.getChangeRequestDetail(number, {
|
|
224
|
-
workspaceId,
|
|
225
|
-
viewerEmail: ctx.user.email,
|
|
226
|
-
patches: opts.patches,
|
|
227
|
-
});
|
|
228
|
-
if (!detail) throw notFound(number);
|
|
229
|
-
const viewerIsAuthor = isAuthor(detail, ctx.user.email);
|
|
230
|
-
// Comment paths join the batch: a comment may name a file the request no
|
|
231
|
-
// longer changes, and without a verdict of its own it would be withheld
|
|
232
|
-
// from a caller who can read it perfectly well.
|
|
233
|
-
const commentPaths = detail.comments.map((c) => c.path).filter((p): p is string => !!p);
|
|
234
|
-
// BOTH names of every file: a move is judged on its old path as well as
|
|
235
|
-
// its new one, because the diff of a move shows what was at the old one.
|
|
236
|
-
const readable = await readablePaths(ctx, workspaceId, detail.base, [
|
|
237
|
-
...detail.files.flatMap(pathsOf),
|
|
238
|
-
...commentPaths,
|
|
239
|
-
]);
|
|
240
|
-
const mayRead = (path: string) => readable.has(path);
|
|
241
|
-
const readableFiles = detail.files.filter((f) => fileIsReadable(f, mayRead));
|
|
242
|
-
if (!maySeeChangeRequest({ readableFiles: readableFiles.length, isAuthor: viewerIsAuthor })) {
|
|
243
|
-
throw notFound(number);
|
|
244
|
-
}
|
|
245
|
-
// Every name a withheld file goes by, so a gate warning quoting either
|
|
246
|
-
// spelling is caught by the blocker filter — and so `mayShow` catches
|
|
247
|
-
// whichever spelling an approval or a comment happens to use.
|
|
248
|
-
const withheldFilePaths = detail.files
|
|
249
|
-
.filter((f) => !fileIsReadable(f, mayRead))
|
|
250
|
-
.flatMap(pathsOf);
|
|
251
|
-
const withheldNames = new Set(withheldFilePaths);
|
|
252
|
-
return {
|
|
253
|
-
detail,
|
|
254
|
-
mayRead,
|
|
255
|
-
mayShow: (path: string) => mayRead(path) && !withheldNames.has(path),
|
|
256
|
-
readableFiles,
|
|
257
|
-
withheldFilePaths,
|
|
258
|
-
withheldFileCount: detail.files.length - readableFiles.length,
|
|
259
|
-
viewerIsAuthor,
|
|
260
|
-
};
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* The same answer for a number that was never issued and for one the caller
|
|
265
|
-
* may not see — requirement 7. Says nothing a probe could tell apart.
|
|
266
|
-
*/
|
|
267
|
-
const notFound = (number: number): ToolError =>
|
|
268
|
-
new ToolError(`Change request #${number} not found.`, 404);
|
|
148
|
+
const { repoGlobalWorkspaceId, readablePaths, scopedDetail } = changeRequestScope(accessControl, kb);
|
|
269
149
|
|
|
270
150
|
/**
|
|
271
151
|
* The changed files of a SUMMARY, as the pairs a read gate decides over.
|
|
@@ -374,7 +254,18 @@ export function registerChangeRequestReadTools(
|
|
|
374
254
|
required: ['changeRequests', ...pagingRequired],
|
|
375
255
|
},
|
|
376
256
|
handler: async (args, ctx: ToolContext) => {
|
|
377
|
-
|
|
257
|
+
// The answer spells a merged request `state: merged`, so an agent that
|
|
258
|
+
// copies that word back into the filter is asking for the applied
|
|
259
|
+
// ones. Quietly answering the open ones instead reads as "there are no
|
|
260
|
+
// merged requests" — refuse, and name the filter's own vocabulary.
|
|
261
|
+
const state = args.state === undefined ? 'open' : args.state;
|
|
262
|
+
if (state !== 'open' && state !== 'closed' && state !== 'all') {
|
|
263
|
+
throw new ToolError(
|
|
264
|
+
`Unknown \`state\` ${JSON.stringify(state)}. Use \`open\` (the default), \`closed\` ` +
|
|
265
|
+
'(applied and declined alike) or `all`; the answer then says `merged` or `closed` for each.',
|
|
266
|
+
400,
|
|
267
|
+
);
|
|
268
|
+
}
|
|
378
269
|
const head = typeof args.head === 'string' ? args.head : undefined;
|
|
379
270
|
const base = typeof args.base === 'string' ? args.base : undefined;
|
|
380
271
|
const author = typeof args.author === 'string' ? args.author : undefined;
|
|
@@ -722,3 +613,147 @@ export function registerChangeRequestReadTools(
|
|
|
722
613
|
});
|
|
723
614
|
}
|
|
724
615
|
|
|
616
|
+
/**
|
|
617
|
+
* The detail of request `number` with the access filter applied, or a 404 —
|
|
618
|
+
* the one place the four by-number tools get their data, so they cannot
|
|
619
|
+
* drift on who may see what.
|
|
620
|
+
*/
|
|
621
|
+
export interface ScopedDetail {
|
|
622
|
+
detail: ChangeRequestDetail;
|
|
623
|
+
/**
|
|
624
|
+
* Whether the caller may read one path of this request — the raw per-path
|
|
625
|
+
* verdict, as the access tree gives it at `origin/<base>`.
|
|
626
|
+
*/
|
|
627
|
+
mayRead: (path: string) => boolean;
|
|
628
|
+
/**
|
|
629
|
+
* Whether this request may NAME `path` to the caller — `mayRead`, minus
|
|
630
|
+
* every name a withheld file goes by. The ONE verdict the tools below ask
|
|
631
|
+
* about a path, so none of them can decide it a second way.
|
|
632
|
+
*
|
|
633
|
+
* The two differ on exactly one shape, and it is the shape that leaks: a
|
|
634
|
+
* file moved out of a folder the caller may not read is readable under
|
|
635
|
+
* its NEW name, yet `fileIsReadable` withholds it whole (its diff shows the
|
|
636
|
+
* old path's content). Anything keyed on that new path — an approval, a
|
|
637
|
+
* comment anchored to it — would otherwise say the request touches a file
|
|
638
|
+
* the file tool refuses to list, under a name it refuses to print.
|
|
639
|
+
*/
|
|
640
|
+
mayShow: (path: string) => boolean;
|
|
641
|
+
/** The request's files the caller may read, in the request's own order. */
|
|
642
|
+
readableFiles: ChangeRequestDetail['files'];
|
|
643
|
+
/**
|
|
644
|
+
* Every name the withheld files go by — for filtering the gate blockers and
|
|
645
|
+
* for deciding `mayShow`. NEVER answered: use `withheldFileCount` to report
|
|
646
|
+
* them.
|
|
647
|
+
*/
|
|
648
|
+
withheldFilePaths: string[];
|
|
649
|
+
/** How many files are withheld. One per file, whatever its move names. */
|
|
650
|
+
withheldFileCount: number;
|
|
651
|
+
viewerIsAuthor: boolean;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* The access scope of one change request, as every by-number read tool sees
|
|
656
|
+
* it — and as `delete_branch` does, so a deletion's preview or refusal never
|
|
657
|
+
* names a request `get_change_request` answers as not found.
|
|
658
|
+
*/
|
|
659
|
+
export function changeRequestScope(
|
|
660
|
+
accessControl: Pick<IAccessControl, 'canReadBatchAtRef'>,
|
|
661
|
+
kb: Pick<KbContext, 'defaultWorkspaceId'>,
|
|
662
|
+
) {
|
|
663
|
+
/**
|
|
664
|
+
* A clone to resolve access verdicts in. All clones track the same origin and
|
|
665
|
+
* every verdict here is read at `origin/<base>`, so any existing one answers
|
|
666
|
+
* identically — reusing one avoids cloning a branch just to read a ref.
|
|
667
|
+
*/
|
|
668
|
+
const repoGlobalWorkspaceId = async (ctx: ToolContext): Promise<string> =>
|
|
669
|
+
(await ctx.workspaceService.findAnyWorkspaceId()) ?? kb.defaultWorkspaceId();
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Read verdicts for `paths` at `origin/<base>`, as a membership set of what
|
|
673
|
+
* the caller MAY read. A null answer (the ref or its `roles.yaml` does not
|
|
674
|
+
* resolve) withholds everything, which is the same reading the app's
|
|
675
|
+
* fork-point route gives it.
|
|
676
|
+
*/
|
|
677
|
+
const readablePaths = async (
|
|
678
|
+
ctx: ToolContext,
|
|
679
|
+
workspaceId: string,
|
|
680
|
+
base: string,
|
|
681
|
+
paths: string[],
|
|
682
|
+
): Promise<Set<string>> => {
|
|
683
|
+
if (paths.length === 0) return new Set();
|
|
684
|
+
const verdicts = await accessControl.canReadBatchAtRef(
|
|
685
|
+
workspaceId,
|
|
686
|
+
`origin/${base}`,
|
|
687
|
+
ctx.user.email,
|
|
688
|
+
[...new Set(paths)],
|
|
689
|
+
);
|
|
690
|
+
if (!verdicts) return new Set();
|
|
691
|
+
return new Set([...verdicts].filter(([, allowed]) => allowed).map(([p]) => p));
|
|
692
|
+
};
|
|
693
|
+
|
|
694
|
+
const scopedDetail = async (
|
|
695
|
+
ctx: ToolContext,
|
|
696
|
+
number: number,
|
|
697
|
+
opts: { patches: boolean },
|
|
698
|
+
): Promise<ScopedDetail> => {
|
|
699
|
+
const workspaceId = await repoGlobalWorkspaceId(ctx);
|
|
700
|
+
const detail = await ctx.workflowService.getChangeRequestDetail(number, {
|
|
701
|
+
workspaceId,
|
|
702
|
+
viewerEmail: ctx.user.email,
|
|
703
|
+
patches: opts.patches,
|
|
704
|
+
});
|
|
705
|
+
if (!detail) throw notFound(number);
|
|
706
|
+
const viewerIsAuthor = isAuthor(detail, ctx.user.email);
|
|
707
|
+
// Comment paths join the batch: a comment may name a file the request no
|
|
708
|
+
// longer changes, and without a verdict of its own it would be withheld
|
|
709
|
+
// from a caller who can read it perfectly well.
|
|
710
|
+
const commentPaths = detail.comments.map((c) => c.path).filter((p): p is string => !!p);
|
|
711
|
+
// BOTH names of every file: a move is judged on its old path as well as
|
|
712
|
+
// its new one, because the diff of a move shows what was at the old one.
|
|
713
|
+
const readable = await readablePaths(ctx, workspaceId, detail.base, [
|
|
714
|
+
...detail.files.flatMap(pathsOf),
|
|
715
|
+
...commentPaths,
|
|
716
|
+
]);
|
|
717
|
+
const mayRead = (path: string) => readable.has(path);
|
|
718
|
+
const readableFiles = detail.files.filter((f) => fileIsReadable(f, mayRead));
|
|
719
|
+
if (!maySeeChangeRequest({ readableFiles: readableFiles.length, isAuthor: viewerIsAuthor })) {
|
|
720
|
+
throw notFound(number);
|
|
721
|
+
}
|
|
722
|
+
// Every name a withheld file goes by, so a gate warning quoting either
|
|
723
|
+
// spelling is caught by the blocker filter — and so `mayShow` catches
|
|
724
|
+
// whichever spelling an approval or a comment happens to use.
|
|
725
|
+
const withheldFilePaths = detail.files
|
|
726
|
+
.filter((f) => !fileIsReadable(f, mayRead))
|
|
727
|
+
.flatMap(pathsOf);
|
|
728
|
+
const withheldNames = new Set(withheldFilePaths);
|
|
729
|
+
return {
|
|
730
|
+
detail,
|
|
731
|
+
mayRead,
|
|
732
|
+
mayShow: (path: string) => mayRead(path) && !withheldNames.has(path),
|
|
733
|
+
readableFiles,
|
|
734
|
+
withheldFilePaths,
|
|
735
|
+
withheldFileCount: detail.files.length - readableFiles.length,
|
|
736
|
+
viewerIsAuthor,
|
|
737
|
+
};
|
|
738
|
+
};
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* The same answer for a number that was never issued and for one the caller
|
|
742
|
+
* may not see — requirement 7. Says nothing a probe could tell apart.
|
|
743
|
+
*/
|
|
744
|
+
const notFound = (number: number): ToolError =>
|
|
745
|
+
new ToolError(`Change request #${number} not found.`, 404);
|
|
746
|
+
|
|
747
|
+
/** Whether the caller may see request `number` at all: what `scopedDetail` answers 404 for, as a no. */
|
|
748
|
+
const maySee = async (ctx: ToolContext, number: number): Promise<boolean> => {
|
|
749
|
+
try {
|
|
750
|
+
await scopedDetail(ctx, number, { patches: false });
|
|
751
|
+
return true;
|
|
752
|
+
} catch (err) {
|
|
753
|
+
if (err instanceof ToolError && err.status === 404) return false;
|
|
754
|
+
throw err;
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
|
|
758
|
+
return { repoGlobalWorkspaceId, readablePaths, scopedDetail, notFound, maySee };
|
|
759
|
+
}
|