@bevel-software/platform-core-backend 0.25.2 → 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 +251 -0
- package/agent-guide/conventions.md +35 -0
- package/agent-guide/directory-structure.md +145 -0
- package/agent-guide/finding-things.md +7 -0
- package/agent-guide/html-views.md +83 -0
- package/agent-guide/introduction.md +27 -0
- package/agent-guide/new-knowledge-base.md +24 -0
- package/agent-guide/skills.md +47 -0
- package/agent-guide/tool-manuals.md +227 -0
- package/agent-guide/where-a-new-file-goes.md +36 -0
- package/dist/assets.d.ts +26 -0
- package/dist/assets.d.ts.map +1 -1
- package/dist/assets.js +35 -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/core-ports.d.ts +11 -0
- package/dist/core/core-ports.d.ts.map +1 -1
- package/dist/core/core-ports.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 +65 -3
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +35 -0
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +114 -12
- 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 +27 -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 +145 -0
- package/dist/modules/agent-guide/agent-guide.d.ts.map +1 -0
- package/dist/modules/agent-guide/agent-guide.js +199 -0
- package/dist/modules/agent-guide/agent-guide.js.map +1 -0
- package/dist/modules/agent-guide/agent-guide.tools.d.ts +24 -0
- package/dist/modules/agent-guide/agent-guide.tools.d.ts.map +1 -0
- package/dist/modules/agent-guide/agent-guide.tools.js +100 -0
- package/dist/modules/agent-guide/agent-guide.tools.js.map +1 -0
- package/dist/modules/agent-guide/index.d.ts +4 -0
- package/dist/modules/agent-guide/index.d.ts.map +1 -0
- package/dist/modules/agent-guide/index.js +4 -0
- package/dist/modules/agent-guide/index.js.map +1 -0
- package/dist/modules/agent-instructions/agent-instructions.routes.d.ts +3 -2
- package/dist/modules/agent-instructions/agent-instructions.routes.d.ts.map +1 -1
- package/dist/modules/agent-instructions/agent-instructions.routes.js +3 -2
- package/dist/modules/agent-instructions/agent-instructions.routes.js.map +1 -1
- package/dist/modules/agent-instructions/compose.d.ts +14 -6
- package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
- package/dist/modules/agent-instructions/compose.js +28 -8
- package/dist/modules/agent-instructions/compose.js.map +1 -1
- package/dist/modules/agent-instructions/index.d.ts +1 -1
- package/dist/modules/agent-instructions/index.d.ts.map +1 -1
- package/dist/modules/agent-instructions/index.js +1 -1
- package/dist/modules/agent-instructions/index.js.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.d.ts +10 -50
- package/dist/modules/agent-instructions/shared-file-rules.d.ts.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.js +51 -104
- 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 +57 -3
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +177 -24
- 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/mcp/tool-schema-guard.d.ts +105 -0
- package/dist/modules/mcp/tool-schema-guard.d.ts.map +1 -0
- package/dist/modules/mcp/tool-schema-guard.js +171 -0
- package/dist/modules/mcp/tool-schema-guard.js.map +1 -0
- 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/plugins/plugins.tools.d.ts +36 -2
- package/dist/modules/plugins/plugins.tools.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.tools.js +71 -14
- package/dist/modules/plugins/plugins.tools.js.map +1 -1
- package/dist/modules/settings/deployment-settings.service.d.ts +34 -7
- package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
- package/dist/modules/settings/deployment-settings.service.js +148 -54
- 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/settings/setup.routes.d.ts.map +1 -1
- package/dist/modules/settings/setup.routes.js +3 -6
- package/dist/modules/settings/setup.routes.js.map +1 -1
- package/dist/modules/skills/skills.tools.d.ts.map +1 -1
- package/dist/modules/skills/skills.tools.js +103 -43
- 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.contract.d.ts +23 -4
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.contract.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts +4 -0
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +14 -0
- package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts +7 -0
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.js +77 -38
- package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
- package/dist/modules/tool-registry/description-length.d.ts +20 -16
- package/dist/modules/tool-registry/description-length.d.ts.map +1 -1
- package/dist/modules/tool-registry/description-length.js +50 -30
- package/dist/modules/tool-registry/description-length.js.map +1 -1
- package/dist/modules/tool-registry/guide-first.d.ts +27 -0
- package/dist/modules/tool-registry/guide-first.d.ts.map +1 -0
- package/dist/modules/tool-registry/guide-first.js +40 -0
- package/dist/modules/tool-registry/guide-first.js.map +1 -0
- package/dist/modules/tool-registry/tool-registry.d.ts +6 -0
- package/dist/modules/tool-registry/tool-registry.d.ts.map +1 -1
- package/dist/modules/tool-registry/tool-registry.js +9 -2
- package/dist/modules/tool-registry/tool-registry.js.map +1 -1
- package/dist/modules/workflow/agent-tools/change-request-read-shape.d.ts +449 -0
- package/dist/modules/workflow/agent-tools/change-request-read-shape.d.ts.map +1 -0
- package/dist/modules/workflow/agent-tools/change-request-read-shape.js +481 -0
- package/dist/modules/workflow/agent-tools/change-request-read-shape.js.map +1 -0
- package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts +126 -0
- package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts.map +1 -0
- package/dist/modules/workflow/agent-tools/change-request-read.tools.js +611 -0
- package/dist/modules/workflow/agent-tools/change-request-read.tools.js.map +1 -0
- package/dist/modules/workflow/agent-tools/change-request-summary.d.ts +12 -1
- package/dist/modules/workflow/agent-tools/change-request-summary.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/change-request-summary.js +5 -1
- package/dist/modules/workflow/agent-tools/change-request-summary.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 +133 -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 +282 -14
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +627 -126
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/git/merge-commit.d.ts +73 -0
- package/dist/modules/workflow/git/merge-commit.d.ts.map +1 -0
- package/dist/modules/workflow/git/merge-commit.js +89 -0
- package/dist/modules/workflow/git/merge-commit.js.map +1 -0
- 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/git/pull-request.service.d.ts +94 -1
- package/dist/modules/workflow/git/pull-request.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/pull-request.service.js +332 -37
- package/dist/modules/workflow/git/pull-request.service.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 +35 -0
- package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.js +180 -13
- 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.routes.d.ts +6 -2
- package/dist/modules/workflow/workflow.routes.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.routes.js +7 -2
- package/dist/modules/workflow/workflow.routes.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +187 -15
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +808 -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 +24 -39
- package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
- package/dist/modules/workspace/startup/steps/template-files.step.d.ts +58 -52
- package/dist/modules/workspace/startup/steps/template-files.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/template-files.step.js +209 -223
- package/dist/modules/workspace/startup/steps/template-files.step.js.map +1 -1
- package/dist/modules/workspace/startup/steps/template-source.d.ts +5 -3
- package/dist/modules/workspace/startup/steps/template-source.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/template-source.js +5 -3
- package/dist/modules/workspace/startup/steps/template-source.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 +68 -1
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +642 -71
- 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 +84 -1
- package/dist/shared/domain-errors.d.ts.map +1 -1
- package/dist/shared/domain-errors.js +100 -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/hidden-tools.d.ts +44 -0
- package/dist/shared/hidden-tools.d.ts.map +1 -0
- package/dist/shared/hidden-tools.js +13 -0
- package/dist/shared/hidden-tools.js.map +1 -0
- 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/kb-template/.bevelignore +0 -5
- 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 +6 -3
- package/src/__tests__/kb-layout-config.test.ts +10 -100
- package/src/__tests__/packaged-assets-ship.test.ts +66 -0
- package/src/assets.ts +38 -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/core-ports.ts +11 -0
- package/src/core/create-core-server.ts +78 -3
- package/src/core/create-core-services.ts +168 -10
- 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.atref-batch.test.ts +58 -0
- package/src/modules/access/__tests__/access-control.platform-restore.test.ts +8 -7
- package/src/modules/access/__tests__/access-control.service.test.ts +19 -0
- package/src/modules/access/__tests__/access-personal-plugin.test.ts +1 -18
- package/src/modules/access/access-control.interface.ts +28 -0
- package/src/modules/access/access-control.service.ts +37 -0
- package/src/modules/access/directory-sync-bot.ts +1 -1
- package/src/modules/agent-guide/__tests__/agent-guide.test.ts +386 -0
- package/src/modules/agent-guide/__tests__/agent-guide.tools.test.ts +207 -0
- package/src/modules/agent-guide/agent-guide.tools.ts +122 -0
- package/src/modules/agent-guide/agent-guide.ts +300 -0
- package/src/modules/agent-guide/index.ts +22 -0
- package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +39 -118
- package/src/modules/agent-instructions/agent-instructions.routes.ts +3 -2
- package/src/modules/agent-instructions/compose.ts +26 -8
- package/src/modules/agent-instructions/index.ts +0 -3
- package/src/modules/agent-instructions/shared-file-rules.ts +50 -112
- 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__/fake-downstream-mcp-server.ts +14 -3
- 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 +252 -0
- package/src/modules/mcp/__tests__/mcp.service.test.ts +42 -24
- package/src/modules/mcp/__tests__/tool-call-interface.e2e.test.ts +499 -0
- package/src/modules/mcp/__tests__/tool-schema-guard.test.ts +266 -0
- package/src/modules/mcp/mcp-auth.middleware.ts +2 -1
- package/src/modules/mcp/mcp.service.ts +218 -31
- package/src/modules/mcp/oauth/bevel-oauth-provider.ts +42 -4
- package/src/modules/mcp/tool-schema-guard.ts +196 -0
- 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 +177 -4
- 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/plugins/plugins.tools.ts +75 -15
- package/src/modules/settings/__tests__/deployment-settings.service.test.ts +176 -56
- package/src/modules/settings/__tests__/repository-source.test.ts +9 -0
- package/src/modules/settings/deployment-settings.service.ts +144 -55
- package/src/modules/settings/repository-source.ts +4 -4
- package/src/modules/settings/setup.routes.ts +3 -6
- package/src/modules/skills/__tests__/skills.tools.description.test.ts +95 -0
- package/src/modules/skills/skills.tools.ts +112 -46
- 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/__tests__/tool-manuals.detail.route.test.ts +57 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +73 -4
- package/src/modules/tool-manuals/tool-manuals.contract.ts +24 -4
- package/src/modules/tool-manuals/tool-manuals.service.ts +17 -0
- package/src/modules/tool-manuals/tool-manuals.tools.ts +86 -38
- package/src/modules/tool-registry/__tests__/own-tool-schemas.test.ts +173 -0
- package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +81 -66
- package/src/modules/tool-registry/description-length.ts +57 -32
- package/src/modules/tool-registry/guide-first.ts +41 -0
- package/src/modules/tool-registry/tool-registry.ts +9 -2
- package/src/modules/workflow/__tests__/apply-failure.test.ts +6 -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-shape.test.ts +705 -0
- package/src/modules/workflow/agent-tools/__tests__/change-request-read.tools.test.ts +1554 -0
- package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +150 -4
- package/src/modules/workflow/agent-tools/change-request-read-shape.ts +712 -0
- package/src/modules/workflow/agent-tools/change-request-read.tools.ts +759 -0
- package/src/modules/workflow/agent-tools/change-request-summary.ts +5 -1
- package/src/modules/workflow/agent-tools/workflow.tools.ts +137 -35
- package/src/modules/workflow/file-lock.service.ts +67 -5
- package/src/modules/workflow/git/__tests__/git.service.appliedChange.test.ts +285 -0
- package/src/modules/workflow/git/__tests__/git.service.changedFilesForPr.test.ts +124 -0
- package/src/modules/workflow/git/__tests__/git.service.deleteBranch.test.ts +164 -0
- package/src/modules/workflow/git/__tests__/git.service.mergeChangeRequest.test.ts +334 -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/__tests__/pull-request.service.list-fetch.test.ts +72 -2
- package/src/modules/workflow/git/__tests__/pull-request.service.placeholder.test.ts +24 -2
- package/src/modules/workflow/git/__tests__/pull-request.service.test.ts +620 -1
- package/src/modules/workflow/git/git.service.ts +714 -133
- package/src/modules/workflow/git/merge-commit.ts +88 -0
- package/src/modules/workflow/git/node-git-runner.ts +82 -7
- package/src/modules/workflow/git/pull-request.service.ts +380 -54
- 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/__tests__/approval-states.test.ts +7 -1
- package/src/modules/workflow/review-workflow/__tests__/merge-records-own-commit.test.ts +407 -0
- package/src/modules/workflow/review-workflow/review-workflow.service.ts +191 -12
- package/src/modules/workflow/sanitize-error.ts +50 -0
- package/src/modules/workflow/workflow.routes.ts +7 -2
- package/src/modules/workflow/workflow.service.ts +886 -112
- package/src/modules/workspace/__tests__/agent-downloads.test.ts +823 -0
- package/src/modules/workspace/__tests__/escape-sequences.routes.test.ts +4 -3
- 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.routes.move-platform-files.test.ts +21 -10
- 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.agents-file.test.ts +33 -55
- package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +44 -1
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +664 -34
- 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/__tests__/steps.test.ts +191 -489
- package/src/modules/workspace/startup/steps/seed-tree.ts +23 -38
- package/src/modules/workspace/startup/steps/template-files.step.ts +217 -249
- package/src/modules/workspace/startup/steps/template-source.ts +5 -3
- 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 +707 -68
- 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 +123 -11
- package/src/shared/git.contract.ts +8 -1
- package/src/shared/hidden-tools.ts +45 -0
- 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
- package/kb-template/AGENTS.md +0 -730
|
@@ -14,9 +14,12 @@ import { createToolHandlerFactory } from '../../tool-helpers/tool-handler.js';
|
|
|
14
14
|
import { ToolError, type ToolContext } from '../../tool-helpers/tool.contract.js';
|
|
15
15
|
import type { ToolAuth } from '../../tool-auth/tool-auth.middleware.js';
|
|
16
16
|
import { registerWorkspaceTools } from '../workspace.tools.js';
|
|
17
|
-
import { sharedFileRules, sharedFileRulesSection
|
|
17
|
+
import { sharedFileRules, sharedFileRulesSection } from '../../agent-instructions/shared-file-rules.js';
|
|
18
|
+
import { composeAgentGuide } from '../../agent-guide/agent-guide.js';
|
|
19
|
+
import { GUIDE_FIRST_SENTENCE } from '../../tool-registry/guide-first.js';
|
|
18
20
|
import { RoutineWritePolicyService } from '../routine-write-policy.js';
|
|
19
21
|
import { UuidSessionSink, type ISessionSink } from '../session-sink.js';
|
|
22
|
+
import { FIRST_RUN_SECTION_ID, STARTER_GUIDE_FILE, firstRunNote, type FirstRunStarterSource } from '../first-run.js';
|
|
20
23
|
import { WorkflowHooks, type AgentOperationContext } from '../../workflow/workflow-hooks.js';
|
|
21
24
|
import { SESSION_ID_DESCRIPTION, ToolDescriptionNotes } from '../agent-access.gate.js';
|
|
22
25
|
import { SpillStore } from '../spill-store.js';
|
|
@@ -26,13 +29,16 @@ import type { IAccessControl } from '../../access/access-control.interface.js';
|
|
|
26
29
|
import { AccessControlService } from '../../access/access-control.service.js';
|
|
27
30
|
import { NodeFs } from '../../kb-fs/node-fs.js';
|
|
28
31
|
import type { WorkspaceService } from '../workspace.service.js';
|
|
29
|
-
import { isBranchAuthoredBy, isOwnSuggestionsBranch } from '@bevel-software/platform-shared';
|
|
32
|
+
import { DEFAULT_KB_LAYOUT, isBranchAuthoredBy, isOwnSuggestionsBranch } from '@bevel-software/platform-shared';
|
|
30
33
|
import { assertValidBranchName } from '../../kb-fs/branch-name.js';
|
|
31
34
|
import { normalizeWorkspacePath } from '../../kb-fs/repo-path.js';
|
|
32
35
|
import { GIT_INTERNALS_MESSAGE, PathNotFoundError } from '../../../shared/domain-errors.js';
|
|
33
36
|
import { AccessDeniedError } from '../../access-model/access-errors.js';
|
|
34
37
|
import { proposalTitleFor } from '../write-denial.js';
|
|
35
38
|
import { NOT_FOUND_NEXT_STEP } from '../not-found.js';
|
|
39
|
+
import { compileCheck, exampleArguments } from '@bevel-software/platform-mcp-core';
|
|
40
|
+
import { routeToolSchemas } from '../../tool-helpers/route-tool-schemas.js';
|
|
41
|
+
import { TOOL_DESCRIPTION_CAP, clientVisibleLength } from '../../tool-registry/description-length.js';
|
|
36
42
|
|
|
37
43
|
const KB_DIR = 'knowledge-base';
|
|
38
44
|
|
|
@@ -98,6 +104,8 @@ let unzipEntries: string[] = [];
|
|
|
98
104
|
let folderTurns: string[] = [];
|
|
99
105
|
/** The policy instance the tools were mounted with, so a test can restrict a session. */
|
|
100
106
|
let writePolicy: RoutineWritePolicyService;
|
|
107
|
+
/** What the platform's guide reads as, for the tests about the guide's name. */
|
|
108
|
+
let guideText = 'THE PLATFORM GUIDE\n';
|
|
101
109
|
/**
|
|
102
110
|
* The focused branch the resolved `ToolContext` carries — mirrors the branch an
|
|
103
111
|
* internal token bakes for the in-process agent. A test sets it to prove a
|
|
@@ -297,7 +305,7 @@ async function start(
|
|
|
297
305
|
recoveryBotEmail: RECOVERY_BOT,
|
|
298
306
|
hooks,
|
|
299
307
|
notes,
|
|
300
|
-
}, writePolicy, {} as never /* sessionSink — start_session not exercised here
|
|
308
|
+
}, writePolicy, {} as never /* sessionSink — start_session not exercised here */, undefined, undefined, undefined, async () => guideText);
|
|
301
309
|
app.use('/api', router);
|
|
302
310
|
httpServer = await new Promise<HttpServer>((r) => {
|
|
303
311
|
const s = app.listen(0, () => r(s));
|
|
@@ -313,7 +321,9 @@ const postRaw = (url: string, body: unknown = {}) =>
|
|
|
313
321
|
* POST a well-formed call. Every KB tool requires `branch` — a call that names
|
|
314
322
|
* none is refused at the mount with 400 `branch-required` — so this names one
|
|
315
323
|
* unless the test already did. A test ABOUT the missing input uses `postRaw`,
|
|
316
|
-
* so the thing under test is never papered over by the helper.
|
|
324
|
+
* so the thing under test is never papered over by the helper. So does
|
|
325
|
+
* `start_session`, which declares no arguments at all and forbids extras: its
|
|
326
|
+
* route refuses a `branch` as an argument it does not have.
|
|
317
327
|
*/
|
|
318
328
|
const post = (url: string, body: unknown = {}) =>
|
|
319
329
|
postRaw(
|
|
@@ -325,6 +335,7 @@ const post = (url: string, body: unknown = {}) =>
|
|
|
325
335
|
|
|
326
336
|
beforeEach(() => {
|
|
327
337
|
/* fresh per test via start() */
|
|
338
|
+
guideText = 'THE PLATFORM GUIDE\n';
|
|
328
339
|
});
|
|
329
340
|
afterEach(async () => {
|
|
330
341
|
if (httpServer) await new Promise<void>((r) => httpServer!.close(() => r()));
|
|
@@ -963,6 +974,8 @@ describe('write modes and per-path outcomes', () => {
|
|
|
963
974
|
for (const res of [
|
|
964
975
|
await writeFile(base, { path: `${KB_DIR}/a.md`, content: 'x', mode: 'replace' }),
|
|
965
976
|
await writeFiles(base, { files: [{ path: `${KB_DIR}/a.md`, content: 'x' }], mode: 'replace' }),
|
|
977
|
+
// An empty batch is no way round it: the mode is judged before the batch is.
|
|
978
|
+
await writeFiles(base, { files: [], mode: 'bogus' }),
|
|
966
979
|
]) {
|
|
967
980
|
expect(res.status).toBe(400);
|
|
968
981
|
const body = (await res.json()) as { error: string; code: string };
|
|
@@ -1096,6 +1109,202 @@ describe('write modes and per-path outcomes', () => {
|
|
|
1096
1109
|
});
|
|
1097
1110
|
});
|
|
1098
1111
|
|
|
1112
|
+
/**
|
|
1113
|
+
* The agent guide at the repository root. It is not a file: `read_file` of
|
|
1114
|
+
* its name answers with the platform's guide, after the knowledge base's own
|
|
1115
|
+
* file of that name when it has one, and `file_stat` says what is there.
|
|
1116
|
+
*/
|
|
1117
|
+
describe("the agent guide at the guide's name", () => {
|
|
1118
|
+
const GUIDE = `${KB_DIR}/AGENTS.md`;
|
|
1119
|
+
const read = (base: string, p = GUIDE, extra: Record<string, unknown> = {}) =>
|
|
1120
|
+
post(`${base}/api/agent/tools/read_file`, { path: p, ...extra }).then((r) => r.json() as Promise<{ path: string; content: string }>);
|
|
1121
|
+
const statOf = (base: string, p = GUIDE) =>
|
|
1122
|
+
post(`${base}/api/agent/tools/file_stat`, { path: p }).then((r) => r.json() as Promise<Record<string, unknown>>);
|
|
1123
|
+
|
|
1124
|
+
it('answers with the guide when the knowledge base has no file of that name', async () => {
|
|
1125
|
+
guideText = 'THE PLATFORM GUIDE\n';
|
|
1126
|
+
const base = await start();
|
|
1127
|
+
expect(await read(base)).toEqual({ path: GUIDE, content: 'THE PLATFORM GUIDE\n' });
|
|
1128
|
+
// By the root-anchored and the prefix-less spellings too, like any path.
|
|
1129
|
+
expect((await read(base, `/${GUIDE}`)).content).toBe('THE PLATFORM GUIDE\n');
|
|
1130
|
+
expect((await read(base, 'AGENTS.md')).content).toBe('THE PLATFORM GUIDE\n');
|
|
1131
|
+
// Sliced like any content.
|
|
1132
|
+
expect((await read(base, GUIDE, { offset: 4, limit: 8 })).content).toBe('PLATFORM');
|
|
1133
|
+
});
|
|
1134
|
+
|
|
1135
|
+
it('answers with the platform\'s composed guide, HTML views section and all', async () => {
|
|
1136
|
+
guideText = await composeAgentGuide(DEFAULT_KB_LAYOUT);
|
|
1137
|
+
const base = await start();
|
|
1138
|
+
const { content } = await read(base, 'AGENTS.md');
|
|
1139
|
+
expect(content).toBe(guideText);
|
|
1140
|
+
expect(content).toContain('## HTML views');
|
|
1141
|
+
expect(content).toContain('**A bare fragment scrolls the page.**');
|
|
1142
|
+
});
|
|
1143
|
+
|
|
1144
|
+
it("puts the knowledge base's own AGENTS.md first, then the separator, then the guide", async () => {
|
|
1145
|
+
guideText = 'THE PLATFORM GUIDE\n';
|
|
1146
|
+
const base = await start();
|
|
1147
|
+
await fs.writeFile(`${KB_DIR}/AGENTS.md`, '# Acme\n\nWrite tickets in the present tense.\n');
|
|
1148
|
+
const { content } = await read(base);
|
|
1149
|
+
expect(content.startsWith('# Acme\n\nWrite tickets in the present tense.\n\n---\n')).toBe(true);
|
|
1150
|
+
expect(content.endsWith('\n\nTHE PLATFORM GUIDE\n')).toBe(true);
|
|
1151
|
+
expect(content).toContain("The text above is this knowledge base's own conventions file.");
|
|
1152
|
+
});
|
|
1153
|
+
|
|
1154
|
+
it('never serves a copy of the guide an earlier release left on disk a second time', async () => {
|
|
1155
|
+
guideText = 'THE PLATFORM GUIDE\n';
|
|
1156
|
+
const base = await start();
|
|
1157
|
+
await fs.writeFile(`${KB_DIR}/AGENTS.md`, '# Knowledge base\n\n> **This file is managed by the platform.** Stale.\n');
|
|
1158
|
+
expect((await read(base)).content).toBe('THE PLATFORM GUIDE\n');
|
|
1159
|
+
});
|
|
1160
|
+
|
|
1161
|
+
it("never tells a caller who may not read the knowledge base's own file that it exists", async () => {
|
|
1162
|
+
guideText = 'THE PLATFORM GUIDE\n';
|
|
1163
|
+
const denied = await start('read', denyReads(new Set(['AGENTS.md'])));
|
|
1164
|
+
// Nothing of theirs there: the guide is everyone's.
|
|
1165
|
+
const absent = await read(denied);
|
|
1166
|
+
const absentStat = await statOf(denied);
|
|
1167
|
+
expect(absent.content).toBe('THE PLATFORM GUIDE\n');
|
|
1168
|
+
// A file they may not read answers EXACTLY as no file does — a refusal
|
|
1169
|
+
// would be the one thing the platform never says about a restricted
|
|
1170
|
+
// file, which is that it is there.
|
|
1171
|
+
await fs.writeFile(`${KB_DIR}/AGENTS.md`, '# Acme\n\nThe secret conventions.\n');
|
|
1172
|
+
const closed = await post(`${denied}/api/agent/tools/read_file`, { path: GUIDE });
|
|
1173
|
+
expect(closed.status).toBe(200);
|
|
1174
|
+
expect(await closed.json()).toEqual(absent);
|
|
1175
|
+
expect(await statOf(denied)).toEqual(absentStat);
|
|
1176
|
+
});
|
|
1177
|
+
|
|
1178
|
+
it("serves the knowledge base's own file to a caller who may read it", async () => {
|
|
1179
|
+
guideText = 'THE PLATFORM GUIDE\n';
|
|
1180
|
+
const allowed = await start('read');
|
|
1181
|
+
await fs.writeFile(`${KB_DIR}/AGENTS.md`, '# Acme\n\nThe conventions.\n');
|
|
1182
|
+
expect((await read(allowed)).content).toContain('The conventions.');
|
|
1183
|
+
});
|
|
1184
|
+
|
|
1185
|
+
it("answers the guide alone when the knowledge base's own file vanishes between the probe and the read", async () => {
|
|
1186
|
+
guideText = 'THE PLATFORM GUIDE\n';
|
|
1187
|
+
const base = await start();
|
|
1188
|
+
// The file is there when it is probed and gone when it is read: a
|
|
1189
|
+
// concurrent delete, which is the absent case and never a failure.
|
|
1190
|
+
const probed = fs.stat.bind(fs);
|
|
1191
|
+
let vanish = false;
|
|
1192
|
+
(fs as unknown as Record<string, unknown>).stat = async (p: string) => {
|
|
1193
|
+
const st = await probed(p);
|
|
1194
|
+
if (vanish && p.endsWith('AGENTS.md')) {
|
|
1195
|
+
vanish = false;
|
|
1196
|
+
await fs.deleteFile(p);
|
|
1197
|
+
}
|
|
1198
|
+
return st;
|
|
1199
|
+
};
|
|
1200
|
+
await fs.writeFile(`${KB_DIR}/AGENTS.md`, '# Acme\n');
|
|
1201
|
+
vanish = true;
|
|
1202
|
+
expect(await read(base)).toEqual({ path: GUIDE, content: 'THE PLATFORM GUIDE\n' });
|
|
1203
|
+
await fs.writeFile(`${KB_DIR}/AGENTS.md`, '# Acme\n');
|
|
1204
|
+
vanish = true;
|
|
1205
|
+
expect(await statOf(base)).toMatchObject({ platformGuide: true });
|
|
1206
|
+
});
|
|
1207
|
+
|
|
1208
|
+
it('leaves a folder at the guide\'s name to the ordinary stat, and never reads it as a copy', async () => {
|
|
1209
|
+
guideText = 'THE PLATFORM GUIDE\n';
|
|
1210
|
+
const base = await start();
|
|
1211
|
+
await fs.mkdir(`${KB_DIR}/AGENTS.md`);
|
|
1212
|
+
const folder = await statOf(base);
|
|
1213
|
+
expect(folder).toMatchObject({ type: 'directory' });
|
|
1214
|
+
expect(folder.platformGuide).toBeUndefined();
|
|
1215
|
+
});
|
|
1216
|
+
|
|
1217
|
+
it('is a nested AGENTS.md no concern of: that is a file like any other', async () => {
|
|
1218
|
+
guideText = 'THE PLATFORM GUIDE\n';
|
|
1219
|
+
const base = await start();
|
|
1220
|
+
await fs.writeFile(`${KB_DIR}/Handbook/AGENTS.md`, '# Handbook\n');
|
|
1221
|
+
expect((await read(base, `${KB_DIR}/Handbook/AGENTS.md`)).content).toBe('# Handbook\n');
|
|
1222
|
+
expect((await post(`${base}/api/agent/tools/read_file`, { path: `${KB_DIR}/Handbook/HEXIS.md` })).status).toBe(404);
|
|
1223
|
+
});
|
|
1224
|
+
|
|
1225
|
+
it('grep finds the guide where read_file serves it: from the root and at its own path, own file first, denied file absent', async () => {
|
|
1226
|
+
guideText = '# Guide\n\nName every needle you plant.\n';
|
|
1227
|
+
const grep = (base: string, pattern: string, path?: string) =>
|
|
1228
|
+
post(`${base}/api/agent/tools/grep`, path === undefined ? { pattern } : { pattern, path }).then(
|
|
1229
|
+
(r) => r.json() as Promise<{ matches: { path: string; line: number; text: string }[] }>,
|
|
1230
|
+
);
|
|
1231
|
+
const base = await start();
|
|
1232
|
+
await fs.writeFile(`${KB_DIR}/Handbook/a.md`, 'a needle on disk\n');
|
|
1233
|
+
// A search of the whole knowledge base reaches the guide, under the path
|
|
1234
|
+
// a read of it answers to, with the line number that read gives.
|
|
1235
|
+
const fromRoot = await grep(base, 'needle');
|
|
1236
|
+
expect(fromRoot.matches).toContainEqual({ path: GUIDE, line: 3, text: 'Name every needle you plant.' });
|
|
1237
|
+
expect(fromRoot.matches).toContainEqual({ path: `${KB_DIR}/Handbook/a.md`, line: 1, text: 'a needle on disk' });
|
|
1238
|
+
// A search of the guide's own path is a search of what read_file answers.
|
|
1239
|
+
expect((await grep(base, 'needle', GUIDE)).matches).toEqual([{ path: GUIDE, line: 3, text: 'Name every needle you plant.' }]);
|
|
1240
|
+
expect((await grep(base, 'needle', 'AGENTS.md')).matches).toEqual([{ path: GUIDE, line: 3, text: 'Name every needle you plant.' }]);
|
|
1241
|
+
// A search under a folder does not reach a file that is not under it.
|
|
1242
|
+
expect((await grep(base, 'needle', `${KB_DIR}/Handbook`)).matches.map((m) => m.path)).toEqual([`${KB_DIR}/Handbook/a.md`]);
|
|
1243
|
+
|
|
1244
|
+
// With the knowledge base's own AGENTS.md, the search covers the composed
|
|
1245
|
+
// text — the own file first, then the guide — and once: the walk's own
|
|
1246
|
+
// matches in that file are the same lines, so they are not repeated.
|
|
1247
|
+
await fs.writeFile(`${KB_DIR}/AGENTS.md`, '# Acme\n\nOur needle rule.\n');
|
|
1248
|
+
const { content } = await read(base);
|
|
1249
|
+
const guideLine = content.split('\n').indexOf('Name every needle you plant.') + 1;
|
|
1250
|
+
const composed = (await grep(base, 'needle')).matches.filter((m) => m.path === GUIDE);
|
|
1251
|
+
expect(composed).toEqual([
|
|
1252
|
+
{ path: GUIDE, line: 3, text: 'Our needle rule.' },
|
|
1253
|
+
{ path: GUIDE, line: guideLine, text: 'Name every needle you plant.' },
|
|
1254
|
+
]);
|
|
1255
|
+
expect((await grep(base, 'needle', GUIDE)).matches).toEqual(composed);
|
|
1256
|
+
|
|
1257
|
+
// The own file does not use up `max_results` twice: the walk leaves it to
|
|
1258
|
+
// the composed search, so a file later in the tree is still reached when
|
|
1259
|
+
// the own file alone has more matches than the cap. Without that, the
|
|
1260
|
+
// walk filled the cap from AGENTS.md, those matches were dropped as
|
|
1261
|
+
// duplicates, and Handbook/ was never searched.
|
|
1262
|
+
await fs.writeFile(`${KB_DIR}/AGENTS.md`, `# Acme\n\n${'needle\n'.repeat(5)}`);
|
|
1263
|
+
const capped = (await (await post(`${base}/api/agent/tools/grep`, { pattern: 'needle', max_results: 3 })).json()) as {
|
|
1264
|
+
matches: { path: string }[];
|
|
1265
|
+
};
|
|
1266
|
+
expect(capped.matches.map((m) => m.path)).toContain(`${KB_DIR}/Handbook/a.md`);
|
|
1267
|
+
expect(capped.matches).toHaveLength(3);
|
|
1268
|
+
await fs.writeFile(`${KB_DIR}/AGENTS.md`, '# Acme\n\nOur needle rule.\n');
|
|
1269
|
+
|
|
1270
|
+
// A caller who may not read the own file searches the guide alone, from
|
|
1271
|
+
// the root and at the path — as read_file answers them, with no sign that
|
|
1272
|
+
// anything of the organisation's is there.
|
|
1273
|
+
const denied = await start('read', denyReads(new Set(['AGENTS.md'])));
|
|
1274
|
+
await fs.writeFile(`${KB_DIR}/AGENTS.md`, '# Acme\n\nOur needle rule.\n');
|
|
1275
|
+
for (const found of [await grep(denied, 'needle'), await grep(denied, 'needle', GUIDE)]) {
|
|
1276
|
+
expect(found.matches.filter((m) => m.path === GUIDE)).toEqual([{ path: GUIDE, line: 3, text: 'Name every needle you plant.' }]);
|
|
1277
|
+
expect(JSON.stringify(found)).not.toContain('Acme');
|
|
1278
|
+
}
|
|
1279
|
+
});
|
|
1280
|
+
|
|
1281
|
+
it('file_stat says a text file is there to read, and that nothing can be written, moved or deleted at it', async () => {
|
|
1282
|
+
guideText = 'THE PLATFORM GUIDE\n';
|
|
1283
|
+
const base = await start();
|
|
1284
|
+
expect(await statOf(base)).toMatchObject({
|
|
1285
|
+
name: 'AGENTS.md',
|
|
1286
|
+
type: 'file',
|
|
1287
|
+
size: Buffer.byteLength('THE PLATFORM GUIDE\n'),
|
|
1288
|
+
platformGuide: true,
|
|
1289
|
+
managed: true,
|
|
1290
|
+
movable: false,
|
|
1291
|
+
deletable: false,
|
|
1292
|
+
contentMode: 'text',
|
|
1293
|
+
textEditable: false,
|
|
1294
|
+
access: { read: true, write: false },
|
|
1295
|
+
});
|
|
1296
|
+
// With a file of the knowledge base's own there, stat describes THAT file.
|
|
1297
|
+
await fs.writeFile(`${KB_DIR}/AGENTS.md`, '# Acme\n');
|
|
1298
|
+
const own = await statOf(base);
|
|
1299
|
+
expect(own).toMatchObject({ name: 'AGENTS.md', type: 'file', managed: false, movable: true, textEditable: true });
|
|
1300
|
+
expect(own.platformGuide).toBeUndefined();
|
|
1301
|
+
// A copy an earlier release wrote is what read_file does not serve, so
|
|
1302
|
+
// stat says the same thing it says for no file at all.
|
|
1303
|
+
await fs.writeFile(`${KB_DIR}/AGENTS.md`, '# Knowledge base\n\n> **This file is managed by the platform.** Stale.\n');
|
|
1304
|
+
expect(await statOf(base)).toMatchObject({ platformGuide: true, managed: true, movable: false });
|
|
1305
|
+
});
|
|
1306
|
+
});
|
|
1307
|
+
|
|
1099
1308
|
describe('read-permission gating', () => {
|
|
1100
1309
|
// Seed two KB nodes; the access stub denies read on the "secret" one.
|
|
1101
1310
|
async function startGated(): Promise<string> {
|
|
@@ -1714,19 +1923,18 @@ describe('office documents and PDFs', () => {
|
|
|
1714
1923
|
expect(await readContent(base, `${KB_DIR}/deck.pptx`)).toContain('Original');
|
|
1715
1924
|
});
|
|
1716
1925
|
|
|
1717
|
-
it('every mounted tool
|
|
1926
|
+
it('every mounted tool opens with the one sentence sending the agent to the guide, and repeats none of the rules', async () => {
|
|
1718
1927
|
await start();
|
|
1719
1928
|
const tools = await toolRegistry.listInternal();
|
|
1720
|
-
const pointer = sharedRulesPointer(testKbContext().layout);
|
|
1721
1929
|
// The shell is in the list too: it carried the agent-guide reminder before,
|
|
1722
1930
|
// and that reminder is one of the rules that moved.
|
|
1723
1931
|
const mounted = ['read_file', 'list_files', 'file_stat', 'grep', 'write_file', 'write_files', 'edit_file', 'delete_file', 'delete_folder', 'mkdir', 'move_file', 'copy_file', 'unzip', 'execute_command'];
|
|
1724
1932
|
for (const name of mounted) {
|
|
1725
1933
|
const def = tools.find((t) => t.name === name);
|
|
1726
1934
|
expect(def, name).toBeDefined();
|
|
1727
|
-
expect(def!.description!.
|
|
1728
|
-
// Once, at the
|
|
1729
|
-
expect(def!.description!.split(
|
|
1935
|
+
expect(def!.description!.startsWith(`${GUIDE_FIRST_SENTENCE} `), name).toBe(true);
|
|
1936
|
+
// Once, at the front — not once per paragraph that used to be appended.
|
|
1937
|
+
expect(def!.description!.split(GUIDE_FIRST_SENTENCE), name).toHaveLength(2);
|
|
1730
1938
|
// EVERY shared rule, in full, is in the two shared places now (see
|
|
1731
1939
|
// agent-instructions/__tests__/shared-file-rules.test.ts) and in no
|
|
1732
1940
|
// description. Checked on the whole body rather than on a phrase: the
|
|
@@ -1740,10 +1948,57 @@ describe('office documents and PDFs', () => {
|
|
|
1740
1948
|
expect(def!.description, name).not.toContain('Content rule (the same on every file tool)');
|
|
1741
1949
|
expect(def!.description, name).not.toContain('Before your first read or change in a workspace');
|
|
1742
1950
|
}
|
|
1743
|
-
// start_session
|
|
1744
|
-
//
|
|
1951
|
+
// start_session touches no file, yet it opens with the same sentence: the
|
|
1952
|
+
// guide is read before ANYTHING in the platform, a session included.
|
|
1953
|
+
// External-only, so it is looked up on that surface.
|
|
1745
1954
|
const external = await toolRegistry.listExternal();
|
|
1746
|
-
expect(external.find((t) => t.name === 'start_session')!.description).
|
|
1955
|
+
expect(external.find((t) => t.name === 'start_session')!.description!.startsWith(`${GUIDE_FIRST_SENTENCE} `)).toBe(true);
|
|
1956
|
+
});
|
|
1957
|
+
|
|
1958
|
+
/**
|
|
1959
|
+
* The call example at the top of every description is generated from the
|
|
1960
|
+
* tool's input schema, and the same schema is what the argument check reads.
|
|
1961
|
+
* If the two could disagree, the platform would publish an example its own
|
|
1962
|
+
* check refuses — so every declared tool is called with its own example here.
|
|
1963
|
+
*/
|
|
1964
|
+
it('every declared tool can be called with its own generated example', async () => {
|
|
1965
|
+
await start();
|
|
1966
|
+
const tools = await toolRegistry.listInternal();
|
|
1967
|
+
// The whole family this harness declares, the four the scenarios name included.
|
|
1968
|
+
for (const name of ['read_file', 'write_file', 'list_files', 'grep']) {
|
|
1969
|
+
expect(tools.some((t) => t.name === name), name).toBe(true);
|
|
1970
|
+
}
|
|
1971
|
+
expect(tools.length).toBeGreaterThan(12);
|
|
1972
|
+
for (const def of tools) {
|
|
1973
|
+
const compiled = compileCheck(def.inputs);
|
|
1974
|
+
expect(compiled.checkable, `${def.name}: ${compiled.checkable ? '' : compiled.reason}`).toBe(true);
|
|
1975
|
+
// Narrowed by hand: the assertion above already failed the test if not.
|
|
1976
|
+
if (!compiled.checkable) throw new Error(compiled.reason);
|
|
1977
|
+
expect(compiled.check(exampleArguments(def.inputs)), def.name).toEqual([]);
|
|
1978
|
+
// And the FLAT schema, which is what the tool's own route checks the
|
|
1979
|
+
// call against: the two must agree, or a call the example produced would
|
|
1980
|
+
// be refused one layer in.
|
|
1981
|
+
const flat = routeToolSchemas(def.name)?.flat;
|
|
1982
|
+
expect(flat, def.name).toBeDefined();
|
|
1983
|
+
const flatCheck = compileCheck(flat);
|
|
1984
|
+
expect(flatCheck.checkable, `${def.name} (flat): ${flatCheck.checkable ? '' : flatCheck.reason}`).toBe(true);
|
|
1985
|
+
if (!flatCheck.checkable) throw new Error(flatCheck.reason);
|
|
1986
|
+
expect(flatCheck.check(exampleArguments(flat)), `${def.name} (flat)`).toEqual([]);
|
|
1987
|
+
}
|
|
1988
|
+
});
|
|
1989
|
+
|
|
1990
|
+
it('every description, call example and purpose prefix included, stays inside the cap a client shows', async () => {
|
|
1991
|
+
await start();
|
|
1992
|
+
const tools = await toolRegistry.listInternal();
|
|
1993
|
+
for (const def of tools) {
|
|
1994
|
+
// Measured as a CLIENT receives it: the call line, the purpose prefix at
|
|
1995
|
+
// its own cap (the four knowledge-base tools carry one), and the
|
|
1996
|
+
// description — the three things that ride one tool's entry.
|
|
1997
|
+
const received = clientVisibleLength(def);
|
|
1998
|
+
expect(received, `${def.name} is ${received} characters (cap ${TOOL_DESCRIPTION_CAP})`).toBeLessThanOrEqual(
|
|
1999
|
+
TOOL_DESCRIPTION_CAP,
|
|
2000
|
+
);
|
|
2001
|
+
}
|
|
1747
2002
|
});
|
|
1748
2003
|
|
|
1749
2004
|
describe('binary capability contract: a text file, a document, an image and a zip', () => {
|
|
@@ -2011,6 +2266,15 @@ describe('start_session', () => {
|
|
|
2011
2266
|
// `UuidSessionSink`, because "did fifty first calls collide?" is a question
|
|
2012
2267
|
// only the real minting can answer.
|
|
2013
2268
|
sink?: ISessionSink,
|
|
2269
|
+
// The default branch's workspace directory, for the tests about the
|
|
2270
|
+
// `firstRun` note. Without one the workspace service is a bare stand-in
|
|
2271
|
+
// the note's check cannot use, which is what every other test here wants:
|
|
2272
|
+
// the call answers with the id alone.
|
|
2273
|
+
defaultWorkspaceDir?: string,
|
|
2274
|
+
// The starter pack the knowledge base was filled from, for the note.
|
|
2275
|
+
starterPacks?: FirstRunStarterSource,
|
|
2276
|
+
// Who may read what: everything, unless a test about the note's gate says otherwise.
|
|
2277
|
+
access: IAccessControl = allowAll,
|
|
2014
2278
|
): Promise<string> {
|
|
2015
2279
|
created = [];
|
|
2016
2280
|
const registry = new ToolRegistry();
|
|
@@ -2019,7 +2283,12 @@ describe('start_session', () => {
|
|
|
2019
2283
|
scope: auth.scope,
|
|
2020
2284
|
source: auth.source,
|
|
2021
2285
|
abortSignal: signal,
|
|
2022
|
-
workspaceService:
|
|
2286
|
+
workspaceService: (defaultWorkspaceDir
|
|
2287
|
+
? {
|
|
2288
|
+
hasBootstrappedWorkspace: async () => true,
|
|
2289
|
+
getWorkspacePath: async () => defaultWorkspaceDir,
|
|
2290
|
+
}
|
|
2291
|
+
: {}) as never,
|
|
2023
2292
|
workflowService: {} as never,
|
|
2024
2293
|
events: {} as never,
|
|
2025
2294
|
getFilesystem: async () => ({}) as never,
|
|
@@ -2042,10 +2311,17 @@ describe('start_session', () => {
|
|
|
2042
2311
|
const router = express.Router();
|
|
2043
2312
|
registerWorkspaceTools(
|
|
2044
2313
|
registry, router, auth, toolHandler,
|
|
2045
|
-
new SpillStore(join(tmpdir(), 'bevel-test-spills')), new DocExtractService(join(tmpdir(), 'bevel-test-doc-extract')),
|
|
2314
|
+
new SpillStore(join(tmpdir(), 'bevel-test-spills')), new DocExtractService(join(tmpdir(), 'bevel-test-doc-extract')), access, testKbContext({ kbDirName: KB_DIR }),
|
|
2046
2315
|
{ recoveryBotEmail: RECOVERY_BOT, hooks: new WorkflowHooks(), notes: new ToolDescriptionNotes() },
|
|
2047
2316
|
new RoutineWritePolicyService(),
|
|
2048
2317
|
sink ?? fakeSessionSink,
|
|
2318
|
+
undefined,
|
|
2319
|
+
undefined,
|
|
2320
|
+
undefined,
|
|
2321
|
+
undefined,
|
|
2322
|
+
undefined /* downloads */,
|
|
2323
|
+
starterPacks,
|
|
2324
|
+
new NodeFs(),
|
|
2049
2325
|
);
|
|
2050
2326
|
app.use('/api', router);
|
|
2051
2327
|
server = await new Promise<HttpServer>((r) => {
|
|
@@ -2061,13 +2337,13 @@ describe('start_session', () => {
|
|
|
2061
2337
|
|
|
2062
2338
|
it('returns the sink-minted id as sessionId', async () => {
|
|
2063
2339
|
const base = await startSessionApp();
|
|
2064
|
-
const res = (await (await
|
|
2340
|
+
const res = (await (await postRaw(`${base}/api/agent/tools/start_session`)).json()) as { sessionId: string };
|
|
2065
2341
|
expect(res.sessionId).toBe('thread-xyz');
|
|
2066
2342
|
});
|
|
2067
2343
|
|
|
2068
2344
|
it('mints the session for the authenticated user', async () => {
|
|
2069
2345
|
const base = await startSessionApp();
|
|
2070
|
-
await
|
|
2346
|
+
await postRaw(`${base}/api/agent/tools/start_session`);
|
|
2071
2347
|
expect(created).toHaveLength(1);
|
|
2072
2348
|
expect(created[0].userId).toBe('user-42');
|
|
2073
2349
|
expect(created[0].startedAt).toBeInstanceOf(Date);
|
|
@@ -2078,7 +2354,7 @@ describe('start_session', () => {
|
|
|
2078
2354
|
// loopback token resolves to source 'external' at the verifier (see
|
|
2079
2355
|
// tool-auth), so it is admitted here like any external agent.
|
|
2080
2356
|
const base = await startSessionApp('internal');
|
|
2081
|
-
const res = await
|
|
2357
|
+
const res = await postRaw(`${base}/api/agent/tools/start_session`);
|
|
2082
2358
|
expect(res.status).toBe(403);
|
|
2083
2359
|
expect(created).toHaveLength(0);
|
|
2084
2360
|
});
|
|
@@ -2102,7 +2378,7 @@ describe('start_session', () => {
|
|
|
2102
2378
|
it('answers every one of them with a session id of its own', async () => {
|
|
2103
2379
|
const base = await startSessionApp('external', new UuidSessionSink());
|
|
2104
2380
|
|
|
2105
|
-
const responses = await Promise.all(Array.from({ length: 50 }, () =>
|
|
2381
|
+
const responses = await Promise.all(Array.from({ length: 50 }, () => postRaw(`${base}/api/agent/tools/start_session`)));
|
|
2106
2382
|
const bodies = (await Promise.all(responses.map((r) => r.json()))) as Array<{ sessionId?: string }>;
|
|
2107
2383
|
|
|
2108
2384
|
expect(responses.map((r) => r.status)).toEqual(Array.from({ length: 50 }, () => 200));
|
|
@@ -2135,13 +2411,13 @@ describe('start_session', () => {
|
|
|
2135
2411
|
const errorLog = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
2136
2412
|
let failed: Awaited<ReturnType<typeof post>>;
|
|
2137
2413
|
try {
|
|
2138
|
-
failed = await
|
|
2414
|
+
failed = await postRaw(`${base}/api/agent/tools/start_session`);
|
|
2139
2415
|
} finally {
|
|
2140
2416
|
errorLog.mockRestore();
|
|
2141
2417
|
}
|
|
2142
2418
|
expect(failed.status).toBeGreaterThanOrEqual(500);
|
|
2143
2419
|
|
|
2144
|
-
const retried = (await (await
|
|
2420
|
+
const retried = (await (await postRaw(`${base}/api/agent/tools/start_session`)).json()) as { sessionId: string };
|
|
2145
2421
|
expect(retried.sessionId).toBe('session-2');
|
|
2146
2422
|
expect(calls).toBe(2);
|
|
2147
2423
|
});
|
|
@@ -2153,8 +2429,8 @@ describe('start_session', () => {
|
|
|
2153
2429
|
// to other tools, and the spare is simply never mentioned again.
|
|
2154
2430
|
const base = await startSessionApp('external', new UuidSessionSink());
|
|
2155
2431
|
|
|
2156
|
-
const first = (await (await
|
|
2157
|
-
const retry = (await (await
|
|
2432
|
+
const first = (await (await postRaw(`${base}/api/agent/tools/start_session`)).json()) as { sessionId: string };
|
|
2433
|
+
const retry = (await (await postRaw(`${base}/api/agent/tools/start_session`)).json()) as { sessionId: string };
|
|
2158
2434
|
|
|
2159
2435
|
expect(retry.sessionId).not.toBe(first.sessionId);
|
|
2160
2436
|
expect(first.sessionId).toBeTruthy();
|
|
@@ -2182,6 +2458,141 @@ describe('start_session', () => {
|
|
|
2182
2458
|
expect(description).toMatch(/retry/i);
|
|
2183
2459
|
expect(description).toMatch(/created nothing/i);
|
|
2184
2460
|
expect(description).toMatch(/harmless/i);
|
|
2461
|
+
// And that the answer may carry a note to act on, so an agent reading
|
|
2462
|
+
// only the catalog knows the field is not noise.
|
|
2463
|
+
expect(description).toContain('`firstRun`');
|
|
2464
|
+
});
|
|
2465
|
+
|
|
2466
|
+
/**
|
|
2467
|
+
* "The agent is the onboarding guide": on a knowledge base nobody has
|
|
2468
|
+
* written in yet, the first call of a conversation says so, and the note
|
|
2469
|
+
* stops on its own once a page exists.
|
|
2470
|
+
*/
|
|
2471
|
+
describe('the firstRun note', () => {
|
|
2472
|
+
let wsDir = '';
|
|
2473
|
+
const knowledge = () => join(wsDir, KB_DIR, 'KnowledgeBase');
|
|
2474
|
+
|
|
2475
|
+
beforeEach(async () => {
|
|
2476
|
+
wsDir = await mkdtemp(join(tmpdir(), 'bevel-first-run-'));
|
|
2477
|
+
await mkdir(knowledge(), { recursive: true });
|
|
2478
|
+
await writeFile(join(knowledge(), STARTER_GUIDE_FILE), '# How to get started\n');
|
|
2479
|
+
});
|
|
2480
|
+
|
|
2481
|
+
afterEach(async () => {
|
|
2482
|
+
await rm(wsDir, { recursive: true, force: true });
|
|
2483
|
+
});
|
|
2484
|
+
|
|
2485
|
+
const startSession = async () => {
|
|
2486
|
+
const base = await startSessionApp('external', undefined, wsDir);
|
|
2487
|
+
// `postRaw`: the tool takes no arguments, and `post` adds a branch.
|
|
2488
|
+
return (await (await postRaw(`${base}/api/agent/tools/start_session`)).json()) as { sessionId: string; firstRun?: string };
|
|
2489
|
+
};
|
|
2490
|
+
|
|
2491
|
+
it('is there while the knowledge folder holds only the starter guide', async () => {
|
|
2492
|
+
// Folder placeholders and access rules are not pages either.
|
|
2493
|
+
await mkdir(join(knowledge(), 'Empty'), { recursive: true });
|
|
2494
|
+
await writeFile(join(knowledge(), 'Empty', '.gitkeep'), '');
|
|
2495
|
+
await writeFile(join(knowledge(), 'access.md'), '# Access\n');
|
|
2496
|
+
|
|
2497
|
+
const res = await startSession();
|
|
2498
|
+
|
|
2499
|
+
expect(res.sessionId).toBe('thread-xyz');
|
|
2500
|
+
expect(res.firstRun).toBe(firstRunNote(`${KB_DIR}/KnowledgeBase`));
|
|
2501
|
+
expect(res.firstRun).toContain(`\`${FIRST_RUN_SECTION_ID}\``);
|
|
2502
|
+
});
|
|
2503
|
+
|
|
2504
|
+
it('is gone once another page exists beside the starter guide', async () => {
|
|
2505
|
+
await writeFile(join(knowledge(), 'Glossary.md'), '# Glossary\n');
|
|
2506
|
+
|
|
2507
|
+
const res = await startSession();
|
|
2508
|
+
|
|
2509
|
+
expect(res.sessionId).toBe('thread-xyz');
|
|
2510
|
+
expect(res).not.toHaveProperty('firstRun');
|
|
2511
|
+
});
|
|
2512
|
+
|
|
2513
|
+
it('is gone once another page exists in a folder', async () => {
|
|
2514
|
+
await mkdir(join(knowledge(), 'Company'), { recursive: true });
|
|
2515
|
+
await writeFile(join(knowledge(), 'Company', 'About.md'), '# About us\n');
|
|
2516
|
+
|
|
2517
|
+
const res = await startSession();
|
|
2518
|
+
|
|
2519
|
+
expect(res.sessionId).toBe('thread-xyz');
|
|
2520
|
+
expect(res).not.toHaveProperty('firstRun');
|
|
2521
|
+
});
|
|
2522
|
+
|
|
2523
|
+
it("after a starter pack, stays while the pack's pages are untouched and names its suggestions", async () => {
|
|
2524
|
+
await writeFile(join(knowledge(), 'Customers.md'), '# Customers\n');
|
|
2525
|
+
const starter: FirstRunStarterSource = {
|
|
2526
|
+
firstRunStarter: async () => ({
|
|
2527
|
+
name: 'Sales',
|
|
2528
|
+
suggestedPages: ['Customers', 'Pricing'],
|
|
2529
|
+
pages: new Map([['Customers.md', '# Customers\n']]),
|
|
2530
|
+
}),
|
|
2531
|
+
};
|
|
2532
|
+
const base = await startSessionApp('external', undefined, wsDir, starter);
|
|
2533
|
+
const first = (await (await postRaw(`${base}/api/agent/tools/start_session`)).json()) as { firstRun?: string };
|
|
2534
|
+
expect(first.firstRun).toBe(
|
|
2535
|
+
firstRunNote(`${KB_DIR}/KnowledgeBase`, { name: 'Sales', suggestedPages: ['Customers', 'Pricing'] }),
|
|
2536
|
+
);
|
|
2537
|
+
|
|
2538
|
+
await writeFile(join(knowledge(), 'Customers.md'), '# Customers\n\nAcme.\n');
|
|
2539
|
+
const second = (await (await postRaw(`${base}/api/agent/tools/start_session`)).json()) as { firstRun?: string };
|
|
2540
|
+
expect(second).not.toHaveProperty('firstRun');
|
|
2541
|
+
});
|
|
2542
|
+
|
|
2543
|
+
/**
|
|
2544
|
+
* The note is a read: it says what the knowledge folder holds. A caller
|
|
2545
|
+
* who may not read the folder gets the id alone, and a pack page the
|
|
2546
|
+
* caller may not read is judged as anybody's page, so the note never
|
|
2547
|
+
* tells them it is still a placeholder.
|
|
2548
|
+
*/
|
|
2549
|
+
it('is withheld from a caller who may not read the knowledge folder', async () => {
|
|
2550
|
+
const closed = { ...allowAll, canRead: async () => false } as unknown as IAccessControl;
|
|
2551
|
+
const base = await startSessionApp('external', undefined, wsDir, undefined, closed);
|
|
2552
|
+
const res = (await (await postRaw(`${base}/api/agent/tools/start_session`)).json()) as { firstRun?: string };
|
|
2553
|
+
expect(res).toEqual({ sessionId: 'thread-xyz' });
|
|
2554
|
+
});
|
|
2555
|
+
|
|
2556
|
+
it('judges the folder as the caller may see it: a page they may not read leaves the note standing', async () => {
|
|
2557
|
+
await mkdir(join(knowledge(), 'Leadership'), { recursive: true });
|
|
2558
|
+
await writeFile(join(knowledge(), 'Leadership', 'Plan.md'), '# Plan\n');
|
|
2559
|
+
const planClosed = {
|
|
2560
|
+
...allowAll,
|
|
2561
|
+
canReadBatch: async (_w: string, _u: string, paths: string[]) =>
|
|
2562
|
+
new Map(paths.map((p) => [p, !p.endsWith('Plan.md')])),
|
|
2563
|
+
} as unknown as IAccessControl;
|
|
2564
|
+
const base = await startSessionApp('external', undefined, wsDir, undefined, planClosed);
|
|
2565
|
+
const res = (await (await postRaw(`${base}/api/agent/tools/start_session`)).json()) as { firstRun?: string };
|
|
2566
|
+
expect(res.firstRun).toBe(firstRunNote(`${KB_DIR}/KnowledgeBase`));
|
|
2567
|
+
});
|
|
2568
|
+
|
|
2569
|
+
it('says nothing of a pack whose page the caller may not read, in the checkout or gone from it', async () => {
|
|
2570
|
+
// Absent from the checkout, so the folder reads as new — and the note
|
|
2571
|
+
// would still name the pack and its suggestions.
|
|
2572
|
+
const starter: FirstRunStarterSource = {
|
|
2573
|
+
firstRunStarter: async () => ({
|
|
2574
|
+
name: 'Sales',
|
|
2575
|
+
suggestedPages: ['Customers'],
|
|
2576
|
+
pages: new Map([['Customers.md', '# Customers\n']]),
|
|
2577
|
+
}),
|
|
2578
|
+
};
|
|
2579
|
+
const pageClosed = {
|
|
2580
|
+
...allowAll,
|
|
2581
|
+
canReadBatch: async (_w: string, _u: string, paths: string[]) =>
|
|
2582
|
+
new Map(paths.map((p) => [p, !p.endsWith('Customers.md')])),
|
|
2583
|
+
} as unknown as IAccessControl;
|
|
2584
|
+
const base = await startSessionApp('external', undefined, wsDir, starter, pageClosed);
|
|
2585
|
+
const res = (await (await postRaw(`${base}/api/agent/tools/start_session`)).json()) as { firstRun?: string };
|
|
2586
|
+
expect(res).toEqual({ sessionId: 'thread-xyz' });
|
|
2587
|
+
});
|
|
2588
|
+
|
|
2589
|
+
it('never costs the session id: a workspace it cannot read answers with the id alone', async () => {
|
|
2590
|
+
await rm(wsDir, { recursive: true, force: true });
|
|
2591
|
+
|
|
2592
|
+
const res = await startSession();
|
|
2593
|
+
|
|
2594
|
+
expect(res).toEqual({ sessionId: 'thread-xyz' });
|
|
2595
|
+
});
|
|
2185
2596
|
});
|
|
2186
2597
|
});
|
|
2187
2598
|
|
|
@@ -2755,6 +3166,108 @@ describe('preflight for moves and deletes', () => {
|
|
|
2755
3166
|
});
|
|
2756
3167
|
|
|
2757
3168
|
describe('move_file', () => {
|
|
3169
|
+
describe('rewrites links', () => {
|
|
3170
|
+
/** A node at Sales/deal.md, linked from a page beside it, a read-only page, and a page the caller cannot read. */
|
|
3171
|
+
async function linked(): Promise<string> {
|
|
3172
|
+
const base = await seeded();
|
|
3173
|
+
await fs.writeFile(KB('NodeTypes/Deal.md'), '# Deal\n');
|
|
3174
|
+
await fs.writeFile(KB('Sales/deal.md'), '---\nnodeType: "[Deal](../NodeTypes/Deal.md)"\nid: deal-a\n---\n\n# Deal\nSee [policy](../HR/policy.md).\n');
|
|
3175
|
+
await fs.writeFile(KB('Sales/index.md'), `[d](deal.md "Deal") [r](/${KB_DIR}/Sales/deal.md#terms) [id](deal-a)\n\n\`deal.md\`\n`);
|
|
3176
|
+
await fs.writeFile(KB('Locked/refs.md'), '[d](../Sales/deal.md)\n');
|
|
3177
|
+
await fs.writeFile(KB('Secret/refs.md'), '[d](../Sales/deal.md)\n');
|
|
3178
|
+
await fs.writeFile(KB('Sales/transcripts/01.md'), '[d](../deal.md)\n');
|
|
3179
|
+
return base;
|
|
3180
|
+
}
|
|
3181
|
+
const args = { src: KB('Sales/deal.md'), dest: KB('Sales/2026/deal.md') };
|
|
3182
|
+
|
|
3183
|
+
it('by default: links in and out are rewritten, the dry run answering the plan the move carries out', async () => {
|
|
3184
|
+
const base = await linked();
|
|
3185
|
+
const dry = await call(base, 'move_file', { ...args, dryRun: true });
|
|
3186
|
+
expect(dry.status).toBe(200);
|
|
3187
|
+
expect(dry.body.links).toEqual({
|
|
3188
|
+
filesEdited: 2,
|
|
3189
|
+
linksRewritten: 4,
|
|
3190
|
+
edits: [
|
|
3191
|
+
{ path: KB('Sales/2026/deal.md'), from: '../NodeTypes/Deal.md', to: '../../NodeTypes/Deal.md' },
|
|
3192
|
+
{ path: KB('Sales/2026/deal.md'), from: '../HR/policy.md', to: '../../HR/policy.md' },
|
|
3193
|
+
{ path: KB('Sales/index.md'), from: 'deal.md', to: '2026/deal.md' },
|
|
3194
|
+
{ path: KB('Sales/index.md'), from: `/${KB_DIR}/Sales/deal.md#terms`, to: `/${KB_DIR}/Sales/2026/deal.md#terms` },
|
|
3195
|
+
],
|
|
3196
|
+
notRewritten: [{ path: KB('Locked/refs.md'), reason: 'no write access', links: ['../Sales/deal.md'] }],
|
|
3197
|
+
unsearched: 'Links in files you cannot read were not searched, and may still point at the old path.',
|
|
3198
|
+
});
|
|
3199
|
+
// Nothing about the unreadable page leaks.
|
|
3200
|
+
expect(JSON.stringify(dry.body)).not.toContain('Secret');
|
|
3201
|
+
expect(await fs.readFile(KB('Sales/index.md'), { encoding: 'utf8' })).toContain('[d](deal.md "Deal")');
|
|
3202
|
+
|
|
3203
|
+
const run = await call(base, 'move_file', args);
|
|
3204
|
+
expect(run.status).toBe(200);
|
|
3205
|
+
expect(run.body).toMatchObject({ moved: true });
|
|
3206
|
+
expect(run.body.links).toEqual(dry.body.links);
|
|
3207
|
+
expect(await fs.readFile(KB('Sales/2026/deal.md'), { encoding: 'utf8' })).toBe(
|
|
3208
|
+
'---\nnodeType: "[Deal](../../NodeTypes/Deal.md)"\nid: deal-a\n---\n\n# Deal\nSee [policy](../../HR/policy.md).\n',
|
|
3209
|
+
);
|
|
3210
|
+
expect(await fs.readFile(KB('Sales/index.md'), { encoding: 'utf8' })).toBe(
|
|
3211
|
+
`[d](2026/deal.md "Deal") [r](/${KB_DIR}/Sales/2026/deal.md#terms) [id](deal-a)\n\n\`deal.md\`\n`,
|
|
3212
|
+
);
|
|
3213
|
+
// The read-only page, the unreadable page and the transcript keep the old path.
|
|
3214
|
+
expect(await fs.readFile(KB('Locked/refs.md'), { encoding: 'utf8' })).toBe('[d](../Sales/deal.md)\n');
|
|
3215
|
+
expect(await fs.readFile(KB('Secret/refs.md'), { encoding: 'utf8' })).toBe('[d](../Sales/deal.md)\n');
|
|
3216
|
+
expect(await fs.readFile(KB('Sales/transcripts/01.md'), { encoding: 'utf8' })).toBe('[d](../deal.md)\n');
|
|
3217
|
+
});
|
|
3218
|
+
|
|
3219
|
+
it('`rewriteLinks: false` moves exactly as before: no link touched, no `links` block', async () => {
|
|
3220
|
+
const base = await linked();
|
|
3221
|
+
const dry = await call(base, 'move_file', { ...args, dryRun: true, rewriteLinks: false });
|
|
3222
|
+
expect(dry.body.links).toBeUndefined();
|
|
3223
|
+
const run = await call(base, 'move_file', { ...args, rewriteLinks: false });
|
|
3224
|
+
expect(run.body).toMatchObject({ moved: true });
|
|
3225
|
+
expect(run.body.links).toBeUndefined();
|
|
3226
|
+
expect(await fs.readFile(KB('Sales/index.md'), { encoding: 'utf8' })).toContain('[d](deal.md "Deal")');
|
|
3227
|
+
expect(await fs.readFile(KB('Sales/2026/deal.md'), { encoding: 'utf8' })).toContain('../NodeTypes/Deal.md');
|
|
3228
|
+
});
|
|
3229
|
+
|
|
3230
|
+
it('the pages the answer names reach the read hook, the edited ones the write hook, and a page merely searched neither', async () => {
|
|
3231
|
+
const base = await linked();
|
|
3232
|
+
const reads: string[] = [];
|
|
3233
|
+
const writes: string[] = [];
|
|
3234
|
+
hooks.onAgentRead(async (op) => {
|
|
3235
|
+
reads.push(op.wsPath ?? '');
|
|
3236
|
+
});
|
|
3237
|
+
hooks.onPreWrite(async (op) => {
|
|
3238
|
+
writes.push(op.wsPath ?? '');
|
|
3239
|
+
});
|
|
3240
|
+
expect((await call(base, 'move_file', args)).body).toMatchObject({ moved: true });
|
|
3241
|
+
// `Locked/refs.md` is named (left for want of write access, its links
|
|
3242
|
+
// listed), so the read hook hears of it; the unreadable page and the
|
|
3243
|
+
// transcript are never named, so it never hears of them.
|
|
3244
|
+
expect(reads.sort()).toEqual([KB('Locked/refs.md'), KB('Sales/deal.md'), KB('Sales/index.md')]);
|
|
3245
|
+
expect([...new Set(writes)].sort()).toEqual([KB('Sales/2026/deal.md'), KB('Sales/deal.md'), KB('Sales/index.md')]);
|
|
3246
|
+
});
|
|
3247
|
+
|
|
3248
|
+
it('a move that would edit more than 200 files is refused, and nothing changes', async () => {
|
|
3249
|
+
const base = await seeded();
|
|
3250
|
+
for (let i = 0; i < 201; i++) await fs.writeFile(KB(`Sales/pages/p${i}.md`), '[d](../deal.md)\n');
|
|
3251
|
+
const dry = await call(base, 'move_file', { ...args, dryRun: true });
|
|
3252
|
+
expect(dry.body).toMatchObject({ allowed: false });
|
|
3253
|
+
expect(dry.body.reason).toMatch(/201 files.*rewriteLinks: false/s);
|
|
3254
|
+
const run = await call(base, 'move_file', args);
|
|
3255
|
+
expect(run.status).toBe(400);
|
|
3256
|
+
expect(JSON.stringify(run.body)).toContain('rewriteLinks: false');
|
|
3257
|
+
expect(await exists(args.src)).toBe(true);
|
|
3258
|
+
expect(await fs.readFile(KB('Sales/pages/p0.md'), { encoding: 'utf8' })).toBe('[d](../deal.md)\n');
|
|
3259
|
+
});
|
|
3260
|
+
|
|
3261
|
+
it('the description states the default, the opt-out, and that prose and code are not changed', async () => {
|
|
3262
|
+
await start();
|
|
3263
|
+
const def = (await toolRegistry.listInternal()).find((t) => t.name === 'move_file');
|
|
3264
|
+
const description = typeof def?.description === 'string' ? def.description : '';
|
|
3265
|
+
expect(description).toMatch(/rewritten by default/);
|
|
3266
|
+
expect(description).toContain('`rewriteLinks: false`');
|
|
3267
|
+
expect(description).toMatch(/plain prose or code are never changed/);
|
|
3268
|
+
});
|
|
3269
|
+
});
|
|
3270
|
+
|
|
2758
3271
|
it('a same-access move: the dry run changes nothing, the real call runs without confirm', async () => {
|
|
2759
3272
|
const base = await seeded();
|
|
2760
3273
|
const args = { src: KB('Sales/deal.md'), dest: KB('Sales/2026/deal.md') };
|
|
@@ -2998,19 +3511,17 @@ describe('preflight for moves and deletes', () => {
|
|
|
2998
3511
|
expect(await exists(args.src)).toBe(true);
|
|
2999
3512
|
});
|
|
3000
3513
|
|
|
3001
|
-
it('every one of the
|
|
3514
|
+
it('every one of the three platform files gets the same sentence, and the agent never gets the admin restore', async () => {
|
|
3002
3515
|
// The agent move tool has no exception: the recovery move is a person's,
|
|
3003
3516
|
// made as an admin, and an agent is neither.
|
|
3004
3517
|
const base = await seeded();
|
|
3005
3518
|
await fs.writeFile(KB('roles.yaml'), 'roles: {}\n');
|
|
3006
|
-
await fs.writeFile(KB('AGENTS.md'), 'agents\n');
|
|
3007
3519
|
await fs.writeFile(KB('.bevelignore'), '*.tmp\n');
|
|
3008
3520
|
await fs.writeFile(KB('Misplaced/access.md'), '---\nread: everyone\n---\n');
|
|
3009
3521
|
const cases: [string, string][] = [
|
|
3010
3522
|
[KB('access.md'), KB('Sales/access.md')],
|
|
3011
3523
|
[KB('roles.yaml'), KB('Sales/roles.yaml')],
|
|
3012
3524
|
[KB('.bevelignore'), KB('Sales/.bevelignore')],
|
|
3013
|
-
[KB('AGENTS.md'), KB('Sales/AGENTS.md')],
|
|
3014
3525
|
// Including the shape of the admin's recovery move: a misplaced
|
|
3015
3526
|
// access.md into a folder that has none. A person holding the Admin
|
|
3016
3527
|
// role is allowed exactly this move from the UI; the agent is not.
|
|
@@ -3115,20 +3626,98 @@ describe('preflight for moves and deletes', () => {
|
|
|
3115
3626
|
expect(run.status).toBe(403);
|
|
3116
3627
|
expect(run.body).toMatchObject({ kind: 'write-denied', path: KB('Sales/deal.md'), canPropose: true });
|
|
3117
3628
|
});
|
|
3629
|
+
|
|
3630
|
+
// A nested access.md is deleted the way a person deletes it in the app:
|
|
3631
|
+
// by whoever may write it, through the same one-file delete (the locking
|
|
3632
|
+
// filesystem's, which commits and pushes as the caller) as any file.
|
|
3633
|
+
it('a nested access.md is deleted by a caller who may write it, like any file', async () => {
|
|
3634
|
+
const base = await seeded();
|
|
3635
|
+
await fs.writeFile(KB('Sales/access.md'), '---\nread: Admin\n---\n');
|
|
3636
|
+
expect((await call(base, 'file_stat', { path: KB('Sales/access.md') })).body).toMatchObject({
|
|
3637
|
+
managed: true, movable: false, deletable: true,
|
|
3638
|
+
});
|
|
3639
|
+
const deleted: string[] = [];
|
|
3640
|
+
const deleteFile = fs.deleteFile.bind(fs);
|
|
3641
|
+
fs.deleteFile = async (p: string) => { deleted.push(p); return deleteFile(p); };
|
|
3642
|
+
|
|
3643
|
+
const run = await call(base, 'delete_file', { path: KB('Sales/access.md') });
|
|
3644
|
+
|
|
3645
|
+
expect(run.status).toBe(200);
|
|
3646
|
+
expect(run.body).toEqual({ path: KB('Sales/access.md'), deleted: true });
|
|
3647
|
+
expect(deleted).toEqual([KB('Sales/access.md')]);
|
|
3648
|
+
expect(await exists(KB('Sales/access.md'))).toBe(false);
|
|
3649
|
+
expect(await exists(KB('Sales/deal.md'))).toBe(true);
|
|
3650
|
+
});
|
|
3651
|
+
|
|
3652
|
+
it('a nested access.md the caller may not write is the ordinary write refusal, and stays', async () => {
|
|
3653
|
+
const base = await seeded();
|
|
3654
|
+
await fs.writeFile(KB('Locked/access.md'), '---\nread: everyone\n---\n');
|
|
3655
|
+
expect((await call(base, 'file_stat', { path: KB('Locked/access.md') })).body).toMatchObject({
|
|
3656
|
+
managed: true, deletable: false,
|
|
3657
|
+
});
|
|
3658
|
+
|
|
3659
|
+
const run = await call(base, 'delete_file', { path: KB('Locked/access.md') });
|
|
3660
|
+
|
|
3661
|
+
expect(run.status).toBe(403);
|
|
3662
|
+
expect(run.body).toMatchObject({ kind: 'write-denied', path: KB('Locked/access.md'), canPropose: true });
|
|
3663
|
+
expect(JSON.stringify(run.body)).not.toContain('platform file');
|
|
3664
|
+
expect(await exists(KB('Locked/access.md'))).toBe(true);
|
|
3665
|
+
});
|
|
3666
|
+
|
|
3667
|
+
it('the root access.md and roles.yaml are the repository\'s own: refused for everyone, and they stay', async () => {
|
|
3668
|
+
const base = await seeded();
|
|
3669
|
+
await fs.writeFile(KB('roles.yaml'), 'roles: {}\n');
|
|
3670
|
+
for (const name of ['access.md', 'roles.yaml']) {
|
|
3671
|
+
// The caller here may write and own everything: the refusal is not about who asks.
|
|
3672
|
+
expect((await call(base, 'file_stat', { path: KB(name) })).body).toMatchObject({
|
|
3673
|
+
managed: true, movable: false, deletable: false,
|
|
3674
|
+
access: { read: true, write: true, download: true, owner: true },
|
|
3675
|
+
});
|
|
3676
|
+
const run = await call(base, 'delete_file', { path: KB(name) });
|
|
3677
|
+
expect(run.status).toBe(400);
|
|
3678
|
+
expect(run.body.error).toBe(`${name} is the repository's own file and cannot be deleted.`);
|
|
3679
|
+
expect(await exists(KB(name))).toBe(true);
|
|
3680
|
+
}
|
|
3681
|
+
});
|
|
3682
|
+
|
|
3683
|
+
it('a nested roles.yaml is content, and .bevelignore stays refused as before', async () => {
|
|
3684
|
+
const base = await seeded();
|
|
3685
|
+
await fs.writeFile(KB('Sales/roles.yaml'), 'content');
|
|
3686
|
+
await fs.writeFile(KB('Sales/.bevelignore'), '*.tmp\n');
|
|
3687
|
+
expect((await call(base, 'delete_file', { path: KB('Sales/roles.yaml') })).status).toBe(200);
|
|
3688
|
+
const ignore = await call(base, 'delete_file', { path: KB('Sales/.bevelignore') });
|
|
3689
|
+
expect(ignore.status).toBe(400);
|
|
3690
|
+
expect(ignore.body.error).toBe('.bevelignore is a platform file and cannot be deleted through the agent tools.');
|
|
3691
|
+
expect((await call(base, 'file_stat', { path: KB('Sales/.bevelignore') })).body).toMatchObject({ deletable: false });
|
|
3692
|
+
});
|
|
3693
|
+
|
|
3694
|
+
it('delete_folder still refuses the repository root, which keeps its access.md', async () => {
|
|
3695
|
+
const base = await seeded();
|
|
3696
|
+
for (const args of [{ dryRun: true }, { confirm: true }]) {
|
|
3697
|
+
const run = await call(base, 'delete_folder', { path: KB_DIR, ...args });
|
|
3698
|
+
expect(run.body.allowed === false || run.status === 400).toBe(true);
|
|
3699
|
+
expect(JSON.stringify(run.body)).toContain('The repository root is a platform folder and cannot be moved or deleted.');
|
|
3700
|
+
}
|
|
3701
|
+
expect(await exists(KB('access.md'))).toBe(true);
|
|
3702
|
+
});
|
|
3118
3703
|
});
|
|
3119
3704
|
|
|
3120
3705
|
describe('what counts as the platform\'s own, and what a move or delete may reach', () => {
|
|
3121
3706
|
const caseSensitiveDisk = process.platform === 'linux';
|
|
3122
3707
|
|
|
3123
|
-
it('roles.yaml
|
|
3708
|
+
it('roles.yaml is a platform file only at the root; access.md and .bevelignore at any depth; AGENTS.md nowhere', async () => {
|
|
3124
3709
|
const base = await seeded();
|
|
3125
3710
|
await fs.writeFile(KB('roles.yaml'), 'roles: {}\n');
|
|
3126
3711
|
await fs.writeFile(KB('Sales/roles.yaml'), 'content');
|
|
3712
|
+
await fs.writeFile(KB('AGENTS.md'), 'content');
|
|
3127
3713
|
await fs.writeFile(KB('Sales/AGENTS.md'), 'content');
|
|
3128
3714
|
await fs.writeFile(KB('Sales/.bevelignore'), '*.tmp\n');
|
|
3129
3715
|
const managed = async (p: string) => (await call(base, 'file_stat', { path: KB(p) })).body.managed;
|
|
3130
3716
|
expect(await managed('roles.yaml')).toBe(true);
|
|
3131
3717
|
expect(await managed('Sales/roles.yaml')).toBe(false);
|
|
3718
|
+
// The organisation's own conventions file: the guide is served from
|
|
3719
|
+
// code, so nothing under this name is the platform's.
|
|
3720
|
+
expect(await managed('AGENTS.md')).toBe(false);
|
|
3132
3721
|
expect(await managed('Sales/AGENTS.md')).toBe(false);
|
|
3133
3722
|
expect(await managed('Sales/.bevelignore')).toBe(true);
|
|
3134
3723
|
expect((await call(base, 'move_file', { src: KB('Sales/roles.yaml'), dest: KB('Sales/old-roles.yaml') })).body).toMatchObject({ moved: true });
|
|
@@ -3497,7 +4086,7 @@ describe('preflight for moves and deletes', () => {
|
|
|
3497
4086
|
// is stated once in the shared rules rather than on each of them.
|
|
3498
4087
|
expect(sharedFileRulesSection(testKbContext().layout)).toContain('`write-denied`');
|
|
3499
4088
|
for (const name of ['move_file', 'delete_file', 'delete_folder']) {
|
|
3500
|
-
expect((await def(name)).description, name).
|
|
4089
|
+
expect((await def(name)).description.startsWith(GUIDE_FIRST_SENTENCE), name).toBe(true);
|
|
3501
4090
|
}
|
|
3502
4091
|
});
|
|
3503
4092
|
});
|
|
@@ -4015,6 +4604,47 @@ describe('agent read/write hooks', () => {
|
|
|
4015
4604
|
expect(writes).toEqual([]);
|
|
4016
4605
|
});
|
|
4017
4606
|
|
|
4607
|
+
it("file_stat at the guide's name tells the read hook once when it reads the organisation's own file, and never for the guide alone", async () => {
|
|
4608
|
+
guideText = 'THE PLATFORM GUIDE\n';
|
|
4609
|
+
const base = await start();
|
|
4610
|
+
record();
|
|
4611
|
+
// Nothing of the organisation's there: the guide is everyone's and no
|
|
4612
|
+
// file is read, so the hook hears nothing.
|
|
4613
|
+
expect((await post(`${base}/api/agent/tools/file_stat`, { path: `${KB_DIR}/AGENTS.md`, sessionId: 's1' })).status).toBe(200);
|
|
4614
|
+
expect(reads).toEqual([]);
|
|
4615
|
+
// The organisation's own file: telling it from a stale copy reads it,
|
|
4616
|
+
// and the hook hears of that read exactly once — as it does of a read_file.
|
|
4617
|
+
await fs.writeFile(`${KB_DIR}/AGENTS.md`, '# Acme\n');
|
|
4618
|
+
expect((await post(`${base}/api/agent/tools/file_stat`, { path: `${KB_DIR}/AGENTS.md`, sessionId: 's1' })).status).toBe(200);
|
|
4619
|
+
expect(reads.map((op) => op.wsPath)).toEqual([`${KB_DIR}/AGENTS.md`]);
|
|
4620
|
+
// A stale copy of the guide is read to be recognised, so it is heard of too.
|
|
4621
|
+
await fs.writeFile(`${KB_DIR}/AGENTS.md`, '# Knowledge base\n\n> **This file is managed by the platform.** Stale.\n');
|
|
4622
|
+
expect((await post(`${base}/api/agent/tools/file_stat`, { path: `${KB_DIR}/AGENTS.md`, sessionId: 's1' })).status).toBe(200);
|
|
4623
|
+
expect(reads.map((op) => op.wsPath)).toEqual([`${KB_DIR}/AGENTS.md`, `${KB_DIR}/AGENTS.md`]);
|
|
4624
|
+
});
|
|
4625
|
+
|
|
4626
|
+
it("grep at the guide's name tells the read hook the same way: never for the guide alone, once for the organisation's own file", async () => {
|
|
4627
|
+
guideText = 'THE PLATFORM GUIDE\n';
|
|
4628
|
+
const base = await start();
|
|
4629
|
+
record();
|
|
4630
|
+
// The guide alone: nothing of the organisation's is read, so the hook
|
|
4631
|
+
// hears nothing — a search of the guide's path is not a read of a file.
|
|
4632
|
+
expect((await post(`${base}/api/agent/tools/grep`, { pattern: 'GUIDE', path: `${KB_DIR}/AGENTS.md`, sessionId: 's1' })).status).toBe(200);
|
|
4633
|
+
expect(reads).toEqual([]);
|
|
4634
|
+
// The organisation's own file: once, as read_file tells it — not once
|
|
4635
|
+
// for the search root and again for the file.
|
|
4636
|
+
await fs.writeFile(`${KB_DIR}/AGENTS.md`, '# Acme GUIDE\n');
|
|
4637
|
+
expect((await post(`${base}/api/agent/tools/grep`, { pattern: 'GUIDE', path: `${KB_DIR}/AGENTS.md`, sessionId: 's1' })).status).toBe(200);
|
|
4638
|
+
expect(reads.map((op) => op.wsPath)).toEqual([`${KB_DIR}/AGENTS.md`]);
|
|
4639
|
+
// A search of the whole knowledge base: the root once, then each file the
|
|
4640
|
+
// walk opens once — the own AGENTS.md among them exactly once, from the
|
|
4641
|
+
// composed search, never again from the walk.
|
|
4642
|
+
reads = [];
|
|
4643
|
+
expect((await post(`${base}/api/agent/tools/grep`, { pattern: 'GUIDE', sessionId: 's1' })).status).toBe(200);
|
|
4644
|
+
expect(reads[0]?.wsPath).toBe(KB_DIR);
|
|
4645
|
+
expect(reads.filter((op) => op.wsPath === `${KB_DIR}/AGENTS.md`)).toHaveLength(1);
|
|
4646
|
+
});
|
|
4647
|
+
|
|
4018
4648
|
it('the read hook covers list_files, file_stat, grep, delete_file and delete_folder', async () => {
|
|
4019
4649
|
const base = await start();
|
|
4020
4650
|
record();
|
|
@@ -4275,22 +4905,22 @@ describe('tool descriptions and the deployment note', () => {
|
|
|
4275
4905
|
expect(all.get('read_file')!.description).not.toContain('Stay within one');
|
|
4276
4906
|
});
|
|
4277
4907
|
|
|
4278
|
-
it("a registered note lands after every gated tool's own text,
|
|
4908
|
+
it("a registered note lands after every gated tool's own text, behind the guide-first opening, and on the sessionId input", async () => {
|
|
4279
4909
|
await start();
|
|
4280
4910
|
notes.registerGatedToolNote(' One folder per conversation.');
|
|
4281
4911
|
notes.registerSessionIdNote(' It also pins that folder.');
|
|
4282
4912
|
const all = await defs();
|
|
4283
|
-
// The
|
|
4284
|
-
//
|
|
4285
|
-
// deployment's note
|
|
4286
|
-
const pointer = sharedRulesPointer(testKbContext().layout);
|
|
4913
|
+
// The guide-first sentence is FIRST, always: that is the one instruction
|
|
4914
|
+
// an agent needs, and a description cut short from the end must not lose
|
|
4915
|
+
// it. The deployment's note is last, after the tool's own text.
|
|
4287
4916
|
for (const name of ['read_file', 'list_files', 'file_stat', 'grep', 'write_file', 'write_files', 'edit_file', 'delete_file', 'delete_folder', 'mkdir', 'move_file', 'copy_file', 'unzip']) {
|
|
4288
|
-
expect(all.get(name)!.description.
|
|
4917
|
+
expect(all.get(name)!.description.startsWith(`${GUIDE_FIRST_SENTENCE} `), name).toBe(true);
|
|
4918
|
+
expect(all.get(name)!.description.endsWith(' One folder per conversation.'), name).toBe(true);
|
|
4289
4919
|
expect(sessionIdDescriptionOf(all.get(name)!), name).toBe(`${SESSION_ID_DESCRIPTION} It also pins that folder.`);
|
|
4290
4920
|
}
|
|
4291
4921
|
// `execute_command` is internal-only, so it is checked on that surface.
|
|
4292
4922
|
const internal = new Map((await toolRegistry.listInternal()).map((t) => [t.name, t]));
|
|
4293
|
-
expect(internal.get('execute_command')!.description.endsWith(
|
|
4923
|
+
expect(internal.get('execute_command')!.description.endsWith(' One folder per conversation.')).toBe(true);
|
|
4294
4924
|
});
|
|
4295
4925
|
|
|
4296
4926
|
it('a tool that is not gated carries no note', async () => {
|