@bevel-software/platform-core-backend 0.22.0 → 0.24.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/dist/core/core-ports.d.ts +11 -1
- 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 +3 -3
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +46 -13
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +12 -2
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +137 -24
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/core/lifecycle.d.ts +46 -1
- package/dist/core/lifecycle.d.ts.map +1 -1
- package/dist/core/lifecycle.js +99 -13
- package/dist/core/lifecycle.js.map +1 -1
- package/dist/core-config.d.ts +16 -12
- package/dist/core-config.d.ts.map +1 -1
- package/dist/core-config.js +28 -13
- package/dist/core-config.js.map +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -3
- package/dist/index.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +42 -12
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +67 -10
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +214 -35
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/access.routes.d.ts.map +1 -1
- package/dist/modules/access/access.routes.js +17 -16
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/access/directory-sync-bot.d.ts.map +1 -1
- package/dist/modules/access/directory-sync-bot.js +7 -3
- package/dist/modules/access/directory-sync-bot.js.map +1 -1
- package/dist/modules/agent-instructions/agent-instructions.routes.d.ts +8 -1
- package/dist/modules/agent-instructions/agent-instructions.routes.d.ts.map +1 -1
- package/dist/modules/agent-instructions/agent-instructions.routes.js +8 -2
- package/dist/modules/agent-instructions/agent-instructions.routes.js.map +1 -1
- package/dist/modules/agent-instructions/compose.d.ts +38 -6
- package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
- package/dist/modules/agent-instructions/compose.js +39 -6
- package/dist/modules/agent-instructions/compose.js.map +1 -1
- package/dist/modules/agent-instructions/index.d.ts +2 -1
- package/dist/modules/agent-instructions/index.d.ts.map +1 -1
- package/dist/modules/agent-instructions/index.js +2 -1
- package/dist/modules/agent-instructions/index.js.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.d.ts +115 -0
- package/dist/modules/agent-instructions/shared-file-rules.d.ts.map +1 -0
- package/dist/modules/agent-instructions/shared-file-rules.js +272 -0
- package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -0
- package/dist/modules/audit/agent-audit.service.d.ts.map +1 -1
- package/dist/modules/audit/agent-audit.service.js +4 -2
- package/dist/modules/audit/agent-audit.service.js.map +1 -1
- package/dist/modules/auth/account-admission.d.ts +58 -7
- package/dist/modules/auth/account-admission.d.ts.map +1 -1
- package/dist/modules/auth/account-admission.js +44 -1
- package/dist/modules/auth/account-admission.js.map +1 -1
- package/dist/modules/auth/account-erasure.service.d.ts.map +1 -1
- package/dist/modules/auth/account-erasure.service.js +57 -16
- package/dist/modules/auth/account-erasure.service.js.map +1 -1
- package/dist/modules/auth/account.routes.d.ts +1 -1
- package/dist/modules/auth/account.routes.d.ts.map +1 -1
- package/dist/modules/auth/account.routes.js +61 -5
- package/dist/modules/auth/account.routes.js.map +1 -1
- package/dist/modules/auth/auth.middleware.d.ts +1 -1
- package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
- package/dist/modules/auth/auth.middleware.js +18 -7
- package/dist/modules/auth/auth.middleware.js.map +1 -1
- package/dist/modules/auth/auth.routes.d.ts.map +1 -1
- package/dist/modules/auth/auth.routes.js +8 -0
- package/dist/modules/auth/auth.routes.js.map +1 -1
- package/dist/modules/auth/auth.service.d.ts +70 -2
- package/dist/modules/auth/auth.service.d.ts.map +1 -1
- package/dist/modules/auth/auth.service.js +197 -18
- package/dist/modules/auth/auth.service.js.map +1 -1
- package/dist/modules/auth/oidc-auth-provider.d.ts.map +1 -1
- package/dist/modules/auth/oidc-auth-provider.js +7 -2
- package/dist/modules/auth/oidc-auth-provider.js.map +1 -1
- package/dist/modules/code-mode/code-mode.tool.d.ts +20 -2
- package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
- package/dist/modules/code-mode/code-mode.tool.js +66 -35
- package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
- package/dist/modules/database/connection.d.ts +16 -0
- package/dist/modules/database/connection.d.ts.map +1 -1
- package/dist/modules/database/connection.js +117 -0
- package/dist/modules/database/connection.js.map +1 -1
- package/dist/modules/database/core-schema.d.ts +337 -120
- package/dist/modules/database/core-schema.d.ts.map +1 -1
- package/dist/modules/database/core-schema.js +116 -58
- package/dist/modules/database/core-schema.js.map +1 -1
- package/dist/modules/database/migrate.d.ts +8 -0
- package/dist/modules/database/migrate.d.ts.map +1 -1
- package/dist/modules/database/migrate.js +271 -1
- package/dist/modules/database/migrate.js.map +1 -1
- package/dist/modules/kb-fs/branch-name.d.ts.map +1 -1
- package/dist/modules/kb-fs/branch-name.js +12 -2
- package/dist/modules/kb-fs/branch-name.js.map +1 -1
- package/dist/modules/kb-fs/repo-path.d.ts +11 -16
- package/dist/modules/kb-fs/repo-path.d.ts.map +1 -1
- package/dist/modules/kb-fs/repo-path.js +79 -0
- package/dist/modules/kb-fs/repo-path.js.map +1 -1
- package/dist/modules/kb-sync/kb-sync.routes.d.ts +2 -2
- package/dist/modules/kb-sync/kb-sync.routes.d.ts.map +1 -1
- package/dist/modules/kb-sync/kb-sync.routes.js +14 -3
- package/dist/modules/kb-sync/kb-sync.routes.js.map +1 -1
- package/dist/modules/kb-sync/sync-auth.d.ts +3 -1
- package/dist/modules/kb-sync/sync-auth.d.ts.map +1 -1
- package/dist/modules/kb-sync/sync-auth.js +1 -1
- package/dist/modules/kb-sync/sync-auth.js.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.js +21 -6
- package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
- package/dist/modules/mcp/mcp.service.d.ts +8 -0
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +38 -8
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js +3 -2
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
- package/dist/modules/plugins/join-request-records.store.d.ts.map +1 -1
- package/dist/modules/plugins/join-request-records.store.js +7 -4
- package/dist/modules/plugins/join-request-records.store.js.map +1 -1
- package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
- package/dist/modules/settings/deployment-settings.service.js +8 -3
- package/dist/modules/settings/deployment-settings.service.js.map +1 -1
- package/dist/modules/settings/setup.routes.d.ts +52 -1
- package/dist/modules/settings/setup.routes.d.ts.map +1 -1
- package/dist/modules/settings/setup.routes.js +207 -19
- package/dist/modules/settings/setup.routes.js.map +1 -1
- package/dist/modules/skills/skills.contract.d.ts +90 -18
- package/dist/modules/skills/skills.contract.d.ts.map +1 -1
- package/dist/modules/skills/skills.contract.js +4 -1
- package/dist/modules/skills/skills.contract.js.map +1 -1
- package/dist/modules/skills/skills.service.d.ts +98 -9
- package/dist/modules/skills/skills.service.d.ts.map +1 -1
- package/dist/modules/skills/skills.service.js +239 -37
- package/dist/modules/skills/skills.service.js.map +1 -1
- package/dist/modules/skills/skills.tools.d.ts +7 -0
- package/dist/modules/skills/skills.tools.d.ts.map +1 -1
- package/dist/modules/skills/skills.tools.js +71 -7
- package/dist/modules/skills/skills.tools.js.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.js +12 -4
- package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.d.ts +3 -3
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts +16 -7
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.js +34 -12
- package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.d.ts +2 -1
- package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.js +25 -4
- package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.d.ts +3 -2
- 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-helpers/validate-token.js +1 -1
- package/dist/modules/tool-helpers/validate-token.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.js +42 -31
- package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
- package/dist/modules/tool-registry/description-length.d.ts +80 -0
- package/dist/modules/tool-registry/description-length.d.ts.map +1 -0
- package/dist/modules/tool-registry/description-length.js +108 -0
- package/dist/modules/tool-registry/description-length.js.map +1 -0
- package/dist/modules/workflow/agent-tools/change-request-summary.d.ts +98 -0
- package/dist/modules/workflow/agent-tools/change-request-summary.d.ts.map +1 -0
- package/dist/modules/workflow/agent-tools/change-request-summary.js +81 -0
- package/dist/modules/workflow/agent-tools/change-request-summary.js.map +1 -0
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.js +75 -35
- package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
- package/dist/modules/workflow/file-lock.service.d.ts +24 -0
- package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
- package/dist/modules/workflow/file-lock.service.js +30 -0
- package/dist/modules/workflow/file-lock.service.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +94 -0
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +193 -3
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/pending-commits.service.d.ts +38 -0
- package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.service.js +60 -1
- package/dist/modules/workflow/pending-commits.service.js.map +1 -1
- package/dist/modules/workflow/recovery-bot.d.ts.map +1 -1
- package/dist/modules/workflow/recovery-bot.js +7 -3
- package/dist/modules/workflow/recovery-bot.js.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.js +12 -3
- package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
- package/dist/modules/workflow/workflow-hooks.d.ts +54 -32
- package/dist/modules/workflow/workflow-hooks.d.ts.map +1 -1
- package/dist/modules/workflow/workflow-hooks.js +16 -1
- package/dist/modules/workflow/workflow-hooks.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +60 -6
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +115 -5
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/agent-access.gate.d.ts +94 -0
- package/dist/modules/workspace/agent-access.gate.d.ts.map +1 -0
- package/dist/modules/workspace/agent-access.gate.js +123 -0
- package/dist/modules/workspace/agent-access.gate.js.map +1 -0
- package/dist/modules/workspace/agent-upload.routes.d.ts +77 -0
- package/dist/modules/workspace/agent-upload.routes.d.ts.map +1 -0
- package/dist/modules/workspace/agent-upload.routes.js +210 -0
- package/dist/modules/workspace/agent-upload.routes.js.map +1 -0
- package/dist/modules/workspace/agent-upload.store.d.ts +284 -0
- package/dist/modules/workspace/agent-upload.store.d.ts.map +1 -0
- package/dist/modules/workspace/agent-upload.store.js +553 -0
- package/dist/modules/workspace/agent-upload.store.js.map +1 -0
- package/dist/modules/workspace/routine-write-policy.d.ts +5 -6
- package/dist/modules/workspace/routine-write-policy.d.ts.map +1 -1
- package/dist/modules/workspace/routine-write-policy.js +5 -6
- package/dist/modules/workspace/routine-write-policy.js.map +1 -1
- package/dist/modules/workspace/session-sink.d.ts +5 -5
- package/dist/modules/workspace/set-aside-clone.d.ts +46 -0
- package/dist/modules/workspace/set-aside-clone.d.ts.map +1 -0
- package/dist/modules/workspace/set-aside-clone.js +92 -0
- package/dist/modules/workspace/set-aside-clone.js.map +1 -0
- package/dist/modules/workspace/startup/kb-startup-runner.d.ts +59 -9
- package/dist/modules/workspace/startup/kb-startup-runner.d.ts.map +1 -1
- package/dist/modules/workspace/startup/kb-startup-runner.js +65 -24
- package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.js +3 -3
- package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
- package/dist/modules/workspace/startup/steps/template-source.d.ts +40 -0
- package/dist/modules/workspace/startup/steps/template-source.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/template-source.js +46 -4
- package/dist/modules/workspace/startup/steps/template-source.js.map +1 -1
- package/dist/modules/workspace/upload-limits.d.ts +13 -0
- package/dist/modules/workspace/upload-limits.d.ts.map +1 -0
- package/dist/modules/workspace/upload-limits.js +13 -0
- package/dist/modules/workspace/upload-limits.js.map +1 -0
- package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.routes.js +93 -4
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +128 -5
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +316 -58
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts +13 -11
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +791 -198
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/modules/workspace/write-denial.d.ts +0 -6
- package/dist/modules/workspace/write-denial.d.ts.map +1 -1
- package/dist/modules/workspace/write-denial.js +0 -6
- package/dist/modules/workspace/write-denial.js.map +1 -1
- package/dist/modules/workspace/zip-entry-rules.d.ts +114 -0
- package/dist/modules/workspace/zip-entry-rules.d.ts.map +1 -0
- package/dist/modules/workspace/zip-entry-rules.js +154 -0
- package/dist/modules/workspace/zip-entry-rules.js.map +1 -0
- package/dist/modules/write-access/write-access.d.ts +60 -0
- package/dist/modules/write-access/write-access.d.ts.map +1 -0
- package/dist/modules/write-access/write-access.js +129 -0
- package/dist/modules/write-access/write-access.js.map +1 -0
- package/dist/shared/column-crypto.d.ts +194 -0
- package/dist/shared/column-crypto.d.ts.map +1 -0
- package/dist/shared/column-crypto.js +144 -0
- package/dist/shared/column-crypto.js.map +1 -0
- package/dist/shared/domain-errors.d.ts +25 -0
- package/dist/shared/domain-errors.d.ts.map +1 -1
- package/dist/shared/domain-errors.js +28 -0
- package/dist/shared/domain-errors.js.map +1 -1
- package/dist/shared/git.contract.d.ts +20 -0
- package/dist/shared/git.contract.d.ts.map +1 -1
- package/dist/shared/git.contract.js +26 -0
- package/dist/shared/git.contract.js.map +1 -1
- package/dist/shared/token-crypto.d.ts.map +1 -1
- package/dist/shared/token-crypto.js +25 -1
- package/dist/shared/token-crypto.js.map +1 -1
- package/dist/tenancy/static-tenant-source.d.ts +0 -1
- package/dist/tenancy/static-tenant-source.d.ts.map +1 -1
- package/dist/tenancy/static-tenant-source.js +1 -2
- package/dist/tenancy/static-tenant-source.js.map +1 -1
- package/dist/tenancy/tenant-secrets.d.ts +5 -1
- package/dist/tenancy/tenant-secrets.d.ts.map +1 -1
- package/dist/tenancy/tenant-secrets.js +4 -0
- package/dist/tenancy/tenant-secrets.js.map +1 -1
- package/kb-template/AGENTS.md +2 -0
- package/migrations/0014_change_request_closed_reason.sql +1 -0
- package/migrations/0015_account_deactivation.sql +3 -0
- package/migrations/0016_pii_encryption.sql +20 -0
- package/migrations/meta/0014_snapshot.json +2265 -0
- package/migrations/meta/0015_snapshot.json +2271 -0
- package/migrations/meta/0016_snapshot.json +2327 -0
- package/migrations/meta/_journal.json +21 -0
- package/package.json +3 -3
- package/src/__tests__/kb-layout-config.test.ts +0 -2
- package/src/__tests__/retired-settings.test.ts +96 -0
- package/src/core/__tests__/gated-boot.test.ts +132 -0
- package/src/core/__tests__/lifecycle.test.ts +241 -5
- package/src/core/__tests__/set-aside-root-is-one-place.test.ts +63 -0
- package/src/core/core-ports.ts +11 -1
- package/src/core/create-core-server.ts +52 -16
- package/src/core/create-core-services.ts +156 -24
- package/src/core/lifecycle.ts +117 -13
- package/src/core-config.ts +31 -14
- package/src/index.ts +30 -1
- package/src/modules/access/__tests__/access-control.preview-relocation.test.ts +385 -0
- package/src/modules/access/__tests__/access-control.prospective.test.ts +94 -18
- package/src/modules/access/__tests__/access.routes.prospective.test.ts +6 -7
- package/src/modules/access/__tests__/users-db-double.ts +21 -12
- package/src/modules/access/access-control.interface.ts +43 -12
- package/src/modules/access/access-control.service.ts +234 -36
- package/src/modules/access/access.routes.ts +17 -16
- package/src/modules/access/directory-sync-bot.ts +7 -3
- package/src/modules/agent-instructions/__tests__/agent-instructions.route.test.ts +11 -5
- package/src/modules/agent-instructions/__tests__/compose.test.ts +19 -10
- package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +238 -0
- package/src/modules/agent-instructions/agent-instructions.routes.ts +12 -2
- package/src/modules/agent-instructions/compose.ts +50 -7
- package/src/modules/agent-instructions/index.ts +12 -0
- package/src/modules/agent-instructions/shared-file-rules.ts +314 -0
- package/src/modules/audit/agent-audit.service.ts +4 -2
- package/src/modules/auth/__tests__/account-deactivation.test.ts +253 -0
- package/src/modules/auth/__tests__/account-erasure.approval-gate.test.ts +6 -2
- package/src/modules/auth/__tests__/account.routes.test.ts +87 -6
- package/src/modules/auth/__tests__/auth.middleware.test.ts +45 -22
- package/src/modules/auth/__tests__/auth.service.test.ts +4 -2
- package/src/modules/auth/account-admission.ts +78 -9
- package/src/modules/auth/account-erasure.service.ts +69 -18
- package/src/modules/auth/account.routes.ts +63 -7
- package/src/modules/auth/auth.middleware.ts +19 -8
- package/src/modules/auth/auth.routes.ts +8 -0
- package/src/modules/auth/auth.service.ts +206 -23
- package/src/modules/auth/oidc-auth-provider.ts +7 -2
- package/src/modules/code-mode/__tests__/chain-runtime.e2e.test.ts +335 -0
- package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +46 -2
- package/src/modules/code-mode/code-mode.tool.ts +80 -34
- package/src/modules/database/__tests__/connection.test.ts +12 -0
- package/src/modules/database/__tests__/pii-backfill.pg.test.ts +561 -0
- package/src/modules/database/connection.ts +117 -0
- package/src/modules/database/core-schema.ts +116 -58
- package/src/modules/database/migrate.ts +353 -1
- package/src/modules/kb-fs/__tests__/branch-name.test.ts +10 -0
- package/src/modules/kb-fs/__tests__/repo-path.test.ts +123 -0
- package/src/modules/kb-fs/branch-name.ts +14 -1
- package/src/modules/kb-fs/repo-path.ts +85 -0
- package/src/modules/kb-sync/__tests__/kb-sync.routes.test.ts +26 -1
- package/src/modules/kb-sync/kb-sync.routes.ts +14 -4
- package/src/modules/kb-sync/sync-auth.ts +2 -2
- package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +60 -3
- package/src/modules/mcp/__tests__/mcp.e2e.test.ts +4 -3
- package/src/modules/mcp/__tests__/mcp.service.test.ts +115 -13
- package/src/modules/mcp/mcp-auth.middleware.ts +20 -6
- package/src/modules/mcp/mcp.service.ts +46 -7
- package/src/modules/mcp/oauth/bevel-oauth-provider.ts +3 -1
- package/src/modules/plugins/__tests__/plugins.tools.test.ts +2 -1
- package/src/modules/plugins/join-request-records.store.ts +7 -4
- package/src/modules/settings/__tests__/deployment-settings.service.test.ts +16 -0
- package/src/modules/settings/__tests__/setup.routes.git-mode.test.ts +91 -58
- package/src/modules/settings/__tests__/setup.routes.github-app.test.ts +25 -8
- package/src/modules/settings/__tests__/setup.routes.managed-phase.test.ts +14 -11
- package/src/modules/settings/__tests__/setup.routes.repository-change.test.ts +390 -0
- package/src/modules/settings/__tests__/setup.routes.test.ts +3 -0
- package/src/modules/settings/deployment-settings.service.ts +8 -3
- package/src/modules/settings/setup.routes.ts +263 -19
- package/src/modules/skills/__tests__/allowed-tools-warn.tools.test.ts +2 -1
- package/src/modules/skills/__tests__/branch-skills.tools.test.ts +218 -0
- package/src/modules/skills/__tests__/skills.service.test.ts +229 -5
- package/src/modules/skills/skills.contract.ts +91 -18
- package/src/modules/skills/skills.service.ts +278 -41
- package/src/modules/skills/skills.tools.ts +80 -8
- package/src/modules/tool-auth/__tests__/manual-auth.middleware.test.ts +14 -1
- package/src/modules/tool-auth/external-api-key.service.ts +12 -4
- package/src/modules/tool-auth/internal-token.service.ts +3 -3
- package/src/modules/tool-auth/tool-auth.middleware.ts +34 -11
- package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +5 -3
- package/src/modules/tool-helpers/__tests__/phase4-tools.test.ts +3 -3
- package/src/modules/tool-helpers/__tests__/validate-token.test.ts +11 -1
- package/src/modules/tool-helpers/tool-handler.ts +24 -4
- package/src/modules/tool-helpers/tool.contract.ts +3 -2
- package/src/modules/tool-helpers/validate-token.ts +1 -1
- package/src/modules/tool-manuals/tool-manuals.tools.ts +44 -31
- package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +378 -0
- package/src/modules/tool-registry/description-length.ts +111 -0
- package/src/modules/workflow/__tests__/pending-commits.repository-replaced.test.ts +111 -0
- package/src/modules/workflow/__tests__/workflow.routes.history-read-gate.test.ts +25 -0
- package/src/modules/workflow/__tests__/workflow.service.repository-replaced.test.ts +249 -0
- package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +264 -6
- package/src/modules/workflow/agent-tools/change-request-summary.ts +182 -0
- package/src/modules/workflow/agent-tools/workflow.tools.ts +86 -35
- package/src/modules/workflow/file-lock.service.ts +31 -0
- package/src/modules/workflow/git/__tests__/git.service.fileBytesAtCommit.test.ts +260 -0
- package/src/modules/workflow/git/__tests__/git.service.prFetchFailure.test.ts +170 -0
- package/src/modules/workflow/git/git.service.ts +215 -1
- package/src/modules/workflow/pending-commits.service.ts +64 -2
- package/src/modules/workflow/recovery-bot.ts +7 -3
- package/src/modules/workflow/review-workflow/__tests__/carry-approvals-forward.test.ts +4 -0
- package/src/modules/workflow/review-workflow/__tests__/erase-approver.test.ts +25 -3
- package/src/modules/workflow/review-workflow/review-workflow.service.ts +12 -3
- package/src/modules/workflow/workflow-hooks.ts +64 -26
- package/src/modules/workflow/workflow.service.ts +125 -5
- package/src/modules/workspace/__tests__/agent-access.gate.test.ts +208 -0
- package/src/modules/workspace/__tests__/agent-uploads.test.ts +1604 -0
- package/src/modules/workspace/__tests__/escape-sequences.routes.test.ts +394 -0
- package/src/modules/workspace/__tests__/file-stat-access.test.ts +2 -1
- package/src/modules/workspace/__tests__/git-internals.security.test.ts +2 -1
- package/src/modules/workspace/__tests__/set-aside-clone.test.ts +52 -0
- package/src/modules/workspace/__tests__/workspace.routes.at-ref.test.ts +305 -0
- package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +2 -0
- package/src/modules/workspace/__tests__/workspace.service.any-workspace-credentials.test.ts +156 -0
- package/src/modules/workspace/__tests__/workspace.service.forget-clone-races.test.ts +147 -0
- package/src/modules/workspace/__tests__/workspace.service.replaced-repository.test.ts +402 -0
- package/src/modules/workspace/__tests__/workspace.service.test.ts +77 -9
- package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +41 -39
- package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +2 -3
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +766 -56
- package/src/modules/workspace/agent-access.gate.ts +164 -0
- package/src/modules/workspace/agent-upload.routes.ts +214 -0
- package/src/modules/workspace/agent-upload.store.ts +668 -0
- package/src/modules/workspace/routine-write-policy.ts +5 -6
- package/src/modules/workspace/session-sink.ts +5 -5
- package/src/modules/workspace/set-aside-clone.ts +96 -0
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +86 -0
- package/src/modules/workspace/startup/kb-startup-runner.ts +115 -34
- package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +9 -9
- package/src/modules/workspace/startup/steps/seed-tree.ts +3 -6
- package/src/modules/workspace/startup/steps/template-source.ts +53 -5
- package/src/modules/workspace/upload-limits.ts +12 -0
- package/src/modules/workspace/workspace.routes.ts +96 -5
- package/src/modules/workspace/workspace.service.ts +319 -61
- package/src/modules/workspace/workspace.tools.ts +894 -214
- package/src/modules/workspace/write-denial.ts +0 -8
- package/src/modules/workspace/zip-entry-rules.ts +173 -0
- package/src/modules/write-access/__tests__/write-access.test.ts +248 -0
- package/src/modules/write-access/write-access.ts +153 -0
- package/src/shared/__tests__/column-crypto.test.ts +217 -0
- package/src/shared/column-crypto.ts +218 -0
- package/src/shared/domain-errors.ts +31 -0
- package/src/shared/git.contract.ts +27 -0
- package/src/shared/token-crypto.ts +28 -1
- package/src/tenancy/__tests__/static-tenant-source.test.ts +4 -1
- package/src/tenancy/static-tenant-source.ts +1 -3
- package/src/tenancy/tenant-secrets.ts +5 -1
- package/dist/modules/workflow/session-ontology.policy.d.ts +0 -52
- package/dist/modules/workflow/session-ontology.policy.d.ts.map +0 -1
- package/dist/modules/workflow/session-ontology.policy.js +0 -62
- package/dist/modules/workflow/session-ontology.policy.js.map +0 -1
- package/dist/modules/workflow/session-ontology.service.d.ts +0 -105
- package/dist/modules/workflow/session-ontology.service.d.ts.map +0 -1
- package/dist/modules/workflow/session-ontology.service.js +0 -147
- package/dist/modules/workflow/session-ontology.service.js.map +0 -1
- package/dist/modules/workspace/session-ontology.gate.d.ts +0 -114
- package/dist/modules/workspace/session-ontology.gate.d.ts.map +0 -1
- package/dist/modules/workspace/session-ontology.gate.js +0 -161
- package/dist/modules/workspace/session-ontology.gate.js.map +0 -1
- package/dist/shared/kb-layout.d.ts +0 -39
- package/dist/shared/kb-layout.d.ts.map +0 -1
- package/dist/shared/kb-layout.js +0 -103
- package/dist/shared/kb-layout.js.map +0 -1
- package/dist/shared/kb-layout.test.d.ts +0 -2
- package/dist/shared/kb-layout.test.d.ts.map +0 -1
- package/dist/shared/kb-layout.test.js +0 -75
- package/dist/shared/kb-layout.test.js.map +0 -1
- package/src/modules/workflow/__tests__/session-ontology.policy.test.ts +0 -62
- package/src/modules/workflow/__tests__/session-ontology.service.test.ts +0 -201
- package/src/modules/workflow/session-ontology.policy.ts +0 -70
- package/src/modules/workflow/session-ontology.service.ts +0 -183
- package/src/modules/workspace/__tests__/session-ontology.gate.test.ts +0 -239
- package/src/modules/workspace/session-ontology.gate.ts +0 -191
- package/src/shared/kb-layout.test.ts +0 -98
- package/src/shared/kb-layout.ts +0 -102
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
import type { Server as HttpServer } from 'node:http';
|
|
2
|
+
import type { AddressInfo } from 'node:net';
|
|
3
|
+
import { mkdtemp, readFile, rm } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import express, { type RequestHandler } from 'express';
|
|
7
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
8
|
+
import { LocalFilesystem } from '@mastra/core/workspace';
|
|
9
|
+
import { ToolRegistry } from '../../tool-registry/tool-registry.js';
|
|
10
|
+
import { createManualRoutes } from '../../tool-registry/manual.routes.js';
|
|
11
|
+
import { createToolHandlerFactory } from '../../tool-helpers/tool-handler.js';
|
|
12
|
+
import type { ToolContext } from '../../tool-helpers/tool.contract.js';
|
|
13
|
+
import type { ToolAuth } from '../../tool-auth/tool-auth.middleware.js';
|
|
14
|
+
import { registerWorkspaceTools } from '../workspace.tools.js';
|
|
15
|
+
import { RoutineWritePolicyService } from '../routine-write-policy.js';
|
|
16
|
+
import { WorkflowHooks } from '../../workflow/workflow-hooks.js';
|
|
17
|
+
import { ToolDescriptionNotes } from '../agent-access.gate.js';
|
|
18
|
+
import { SpillStore } from '../spill-store.js';
|
|
19
|
+
import { DocExtractService } from '../file-readers/doc-extract.service.js';
|
|
20
|
+
import type { IAccessControl } from '../../access/access-control.interface.js';
|
|
21
|
+
import { createMcpRoutes } from '../../mcp/mcp.routes.js';
|
|
22
|
+
import { McpService } from '../../mcp/mcp.service.js';
|
|
23
|
+
import { testKbContext } from '../../../__tests__/kb-context.js';
|
|
24
|
+
import { sharedFileRules, sharedFileRulesSection, sharedRulesPointer } from '../../agent-instructions/shared-file-rules.js';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Who decodes escape sequences in written content a second time — settled on
|
|
28
|
+
* all three routes an agent can write through, with RAW request bodies and a
|
|
29
|
+
* BYTE-level read of what landed:
|
|
30
|
+
*
|
|
31
|
+
* 1. the tool route POST /api/agent/tools/<name>
|
|
32
|
+
* 2. the MCP endpoint POST /api/mcp (JSON-RPC `tools/call`)
|
|
33
|
+
* 3. `call_tool_chain` the same endpoint, content as a JavaScript
|
|
34
|
+
* string literal inside the chain's code
|
|
35
|
+
*
|
|
36
|
+
* Route 2 and 3 run the real `McpService` proxy over the real Streamable-HTTP
|
|
37
|
+
* transport, and dispatch back to route 1 over loopback through UTCP — so the
|
|
38
|
+
* three cases exercise one, two and three serialization hops respectively.
|
|
39
|
+
*
|
|
40
|
+
* Nothing in this file is written as an escape sequence. Every character of
|
|
41
|
+
* the payload is built from its code point, so neither an editor nor the tool
|
|
42
|
+
* that wrote this file can decode the thing under test and leave a broken
|
|
43
|
+
* assertion looking green. What goes on the wire is asserted too
|
|
44
|
+
* ({@link WIRE_ESC_A}), so a payload that was already decoded before it was
|
|
45
|
+
* sent cannot pass as a payload that survived the trip.
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
const BACKSLASH = String.fromCharCode(92);
|
|
49
|
+
const QUOTE = String.fromCharCode(34);
|
|
50
|
+
const KB_DIR = 'knowledge-base';
|
|
51
|
+
const BRANCH = 'main';
|
|
52
|
+
|
|
53
|
+
/** The six characters backslash, `u`, `0`, `0`, `4`, `1` — the ticket's ESC-A. */
|
|
54
|
+
const ESC_A = `${BACKSLASH}u0041`;
|
|
55
|
+
/** ESC-A as it must appear in the JSON *text* of a request: the backslash doubled. */
|
|
56
|
+
const WIRE_ESC_A = `${BACKSLASH}${BACKSLASH}u0041`;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* One payload carrying an escape for a letter (ESC-A), a quote, a backslash
|
|
60
|
+
* and a line break — each meant to stay TEXT — plus a real em dash, which is
|
|
61
|
+
* meant to stay the character it is. Nine distinct characters of interest and
|
|
62
|
+
* no accident: if any hop decodes once more than it should, this string is
|
|
63
|
+
* stored shorter than it was sent.
|
|
64
|
+
*/
|
|
65
|
+
const PAYLOAD = [
|
|
66
|
+
ESC_A,
|
|
67
|
+
`${BACKSLASH}${QUOTE}`,
|
|
68
|
+
`${BACKSLASH}${BACKSLASH}`,
|
|
69
|
+
`${BACKSLASH}n`,
|
|
70
|
+
String.fromCharCode(0x2014),
|
|
71
|
+
].join('|');
|
|
72
|
+
|
|
73
|
+
/** What `PAYLOAD` must be on disk, as bytes. */
|
|
74
|
+
const EXPECTED = Buffer.from(PAYLOAD, 'utf8');
|
|
75
|
+
|
|
76
|
+
const BEARER = 'bevel_key_user_a';
|
|
77
|
+
|
|
78
|
+
/** Sets both surfaces' auth stamps: `toolAuth` for the tool routes, `userId` for MCP. */
|
|
79
|
+
const fakeAuth: RequestHandler = (req, _res, next) => {
|
|
80
|
+
req.toolAuth = { source: 'internal', userId: 'u', scope: 'write' };
|
|
81
|
+
req.userId = 'u';
|
|
82
|
+
req.externalApiKeyId = 'tok-a';
|
|
83
|
+
next();
|
|
84
|
+
};
|
|
85
|
+
const passthrough: RequestHandler = (_req, _res, next) => next();
|
|
86
|
+
|
|
87
|
+
interface Platform {
|
|
88
|
+
baseUrl: string;
|
|
89
|
+
/** The bytes of a stored workspace file, read straight off disk. */
|
|
90
|
+
bytesOf(path: string): Promise<Buffer>;
|
|
91
|
+
/** Tool name → the description an agent reads, off the served catalog. */
|
|
92
|
+
descriptions(): Promise<Record<string, string>>;
|
|
93
|
+
stop(): Promise<void>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const running: Platform[] = [];
|
|
97
|
+
|
|
98
|
+
async function startPlatform(): Promise<Platform> {
|
|
99
|
+
const tempDir = await mkdtemp(join(tmpdir(), 'escape-seq-'));
|
|
100
|
+
const fs = new LocalFilesystem({ basePath: tempDir, contained: true });
|
|
101
|
+
// `write_files` lands its batch through the locking filesystem's `writeFiles`
|
|
102
|
+
// (re-judge every path under the lock, then write what is kept). This plain
|
|
103
|
+
// filesystem has none, so the same contract is honoured by hand.
|
|
104
|
+
Object.assign(fs, {
|
|
105
|
+
writeFiles: async (
|
|
106
|
+
writes: { path: string; content: string }[],
|
|
107
|
+
_summary: string,
|
|
108
|
+
_deletes: string[],
|
|
109
|
+
check: (pending: readonly { path: string; content: string }[]) => Promise<{ path: string; content: string }[]>,
|
|
110
|
+
) => {
|
|
111
|
+
for (const w of await check(writes)) await fs.writeFile(w.path, w.content);
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const registry = new ToolRegistry();
|
|
116
|
+
const resolve = async (auth: ToolAuth, signal: AbortSignal, sessionId?: string): Promise<ToolContext> => ({
|
|
117
|
+
user: { id: 'u', email: 'e@x', name: 'N' },
|
|
118
|
+
scope: auth.scope,
|
|
119
|
+
source: auth.source,
|
|
120
|
+
sessionId,
|
|
121
|
+
abortSignal: signal,
|
|
122
|
+
workspaceService: {} as never,
|
|
123
|
+
workflowService: {} as never,
|
|
124
|
+
events: {} as never,
|
|
125
|
+
getFilesystem: async () => fs,
|
|
126
|
+
});
|
|
127
|
+
const allowAll = { canRead: async () => true } as unknown as IAccessControl;
|
|
128
|
+
|
|
129
|
+
const app = express();
|
|
130
|
+
app.use(express.json());
|
|
131
|
+
const router = express.Router();
|
|
132
|
+
registerWorkspaceTools(
|
|
133
|
+
registry,
|
|
134
|
+
router,
|
|
135
|
+
fakeAuth,
|
|
136
|
+
createToolHandlerFactory(resolve),
|
|
137
|
+
new SpillStore(join(tempDir, 'spills')),
|
|
138
|
+
new DocExtractService(join(tempDir, 'doc-extract')),
|
|
139
|
+
allowAll,
|
|
140
|
+
testKbContext({ kbDirName: KB_DIR }),
|
|
141
|
+
{ recoveryBotEmail: 'recovery-bot@bevel.local', hooks: new WorkflowHooks(), notes: new ToolDescriptionNotes() },
|
|
142
|
+
new RoutineWritePolicyService(),
|
|
143
|
+
{} as never,
|
|
144
|
+
);
|
|
145
|
+
app.use('/api', router);
|
|
146
|
+
|
|
147
|
+
const httpServer = await new Promise<HttpServer>((res) => {
|
|
148
|
+
const s = app.listen(0, '127.0.0.1', () => res(s));
|
|
149
|
+
});
|
|
150
|
+
const { port } = httpServer.address() as AddressInfo;
|
|
151
|
+
const baseUrl = `http://127.0.0.1:${port}`;
|
|
152
|
+
|
|
153
|
+
// The catalog the MCP proxy discovers: this process's own tool manual.
|
|
154
|
+
app.get('/api/agent/all-tools', (_req, res) => {
|
|
155
|
+
res.json({
|
|
156
|
+
manuals: [
|
|
157
|
+
{
|
|
158
|
+
name: 'KNOWLEDGE_BASE',
|
|
159
|
+
call_template_type: 'http',
|
|
160
|
+
http_method: 'GET',
|
|
161
|
+
url: '${API_URL}/api/agent/utcp',
|
|
162
|
+
content_type: 'application/json',
|
|
163
|
+
headers: { Authorization: 'Bearer ${CONNECTION_KEY}' },
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
const service = new McpService({
|
|
169
|
+
loopbackBaseUrl: baseUrl,
|
|
170
|
+
manualName: 'KNOWLEDGE_BASE',
|
|
171
|
+
spillStore: new SpillStore(join(tempDir, 'spills')),
|
|
172
|
+
publicFrontendUrl: 'http://localhost:5173',
|
|
173
|
+
});
|
|
174
|
+
const stub = {} as never;
|
|
175
|
+
app.use('/api', createMcpRoutes(service, stub, fakeAuth, fakeAuth, stub, stub, stub, ''));
|
|
176
|
+
app.use('/api', createManualRoutes(registry, passthrough));
|
|
177
|
+
|
|
178
|
+
let stopped = false;
|
|
179
|
+
const platform: Platform = {
|
|
180
|
+
baseUrl,
|
|
181
|
+
bytesOf: (path) => readFile(join(tempDir, path)),
|
|
182
|
+
async descriptions() {
|
|
183
|
+
const res = await fetch(`${baseUrl}/api/agent/utcp`, { headers: { Authorization: `Bearer ${BEARER}` } });
|
|
184
|
+
const manual = (await res.json()) as { tools: Array<{ name: string; description: string }> };
|
|
185
|
+
return Object.fromEntries(manual.tools.map((t) => [t.name, t.description]));
|
|
186
|
+
},
|
|
187
|
+
async stop() {
|
|
188
|
+
if (stopped) return;
|
|
189
|
+
stopped = true;
|
|
190
|
+
service.onSecretsChanged(null);
|
|
191
|
+
httpServer.closeAllConnections();
|
|
192
|
+
await new Promise<void>((r) => httpServer.close(() => r()));
|
|
193
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
running.push(platform);
|
|
197
|
+
return platform;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** POST a raw body string — no object is handed to `fetch`, only text. */
|
|
201
|
+
async function postRaw(url: string, body: string, accept = 'application/json'): Promise<string> {
|
|
202
|
+
const res = await fetch(url, {
|
|
203
|
+
method: 'POST',
|
|
204
|
+
headers: {
|
|
205
|
+
'Content-Type': 'application/json',
|
|
206
|
+
Accept: `${accept}, text/event-stream`,
|
|
207
|
+
Authorization: `Bearer ${BEARER}`,
|
|
208
|
+
},
|
|
209
|
+
body,
|
|
210
|
+
});
|
|
211
|
+
return res.text();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** The JSON-RPC messages out of a `/api/mcp` answer, JSON or SSE alike. */
|
|
215
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- raw JSON-RPC, read by shape
|
|
216
|
+
function messagesOf(text: string): Array<{ result?: any; error?: any }> {
|
|
217
|
+
if (!text.trim()) return [];
|
|
218
|
+
if (!text.startsWith('event:') && !text.startsWith('data:')) return [JSON.parse(text)];
|
|
219
|
+
return text
|
|
220
|
+
.split('\n')
|
|
221
|
+
.filter((line) => line.startsWith('data:'))
|
|
222
|
+
.map((line) => JSON.parse(line.slice('data:'.length)));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** `tools/call` over the MCP endpoint, from raw JSON text. */
|
|
226
|
+
async function callTool(baseUrl: string, name: string, args: unknown): Promise<string> {
|
|
227
|
+
const body = JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/call', params: { name, arguments: args } });
|
|
228
|
+
// What goes on the wire is the six characters, escaped once for JSON.
|
|
229
|
+
expect(body).toContain(WIRE_ESC_A);
|
|
230
|
+
const out = messagesOf(await postRaw(`${baseUrl}/api/mcp`, body));
|
|
231
|
+
const result = out[out.length - 1]?.result;
|
|
232
|
+
expect(out[out.length - 1]?.error, `tools/call ${name} failed`).toBeUndefined();
|
|
233
|
+
expect(result?.isError, JSON.stringify(result)).toBeFalsy();
|
|
234
|
+
return (result.content as Array<{ text: string }>)[0].text;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
afterEach(async () => {
|
|
238
|
+
for (const p of running.splice(0)) await p.stop().catch(() => {});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
describe('escape sequences survive a write — the tool route', () => {
|
|
242
|
+
it('stores `write_file` content byte for byte', async () => {
|
|
243
|
+
const { baseUrl, bytesOf } = await startPlatform();
|
|
244
|
+
const path = `${KB_DIR}/esc/route-rest.md`;
|
|
245
|
+
const body = JSON.stringify({ branch: BRANCH, path, content: PAYLOAD });
|
|
246
|
+
expect(body).toContain(WIRE_ESC_A);
|
|
247
|
+
|
|
248
|
+
const answer = JSON.parse(await postRaw(`${baseUrl}/api/agent/tools/write_file`, body));
|
|
249
|
+
expect(answer.outcome).toBe('created');
|
|
250
|
+
expect(await bytesOf(path)).toEqual(EXPECTED);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('stores `write_files` content byte for byte', async () => {
|
|
254
|
+
const { baseUrl, bytesOf } = await startPlatform();
|
|
255
|
+
const path = `${KB_DIR}/esc/route-rest-batch.md`;
|
|
256
|
+
const body = JSON.stringify({ branch: BRANCH, files: [{ path, content: PAYLOAD }] });
|
|
257
|
+
expect(body).toContain(WIRE_ESC_A);
|
|
258
|
+
|
|
259
|
+
const answer = JSON.parse(await postRaw(`${baseUrl}/api/agent/tools/write_files`, body));
|
|
260
|
+
expect(answer.count).toBe(1);
|
|
261
|
+
expect(await bytesOf(path)).toEqual(EXPECTED);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('stores an `edit_file` `new_string` byte for byte', async () => {
|
|
265
|
+
const { baseUrl, bytesOf } = await startPlatform();
|
|
266
|
+
const path = `${KB_DIR}/esc/route-rest-edit.md`;
|
|
267
|
+
await postRaw(
|
|
268
|
+
`${baseUrl}/api/agent/tools/write_file`,
|
|
269
|
+
JSON.stringify({ branch: BRANCH, path, content: 'PLACEHOLDER' }),
|
|
270
|
+
);
|
|
271
|
+
const body = JSON.stringify({ branch: BRANCH, path, old_string: 'PLACEHOLDER', new_string: PAYLOAD });
|
|
272
|
+
expect(body).toContain(WIRE_ESC_A);
|
|
273
|
+
|
|
274
|
+
const answer = JSON.parse(await postRaw(`${baseUrl}/api/agent/tools/edit_file`, body));
|
|
275
|
+
expect(answer.replaced).toBe(1);
|
|
276
|
+
expect(await bytesOf(path)).toEqual(EXPECTED);
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
describe('escape sequences survive a write — the MCP endpoint', () => {
|
|
281
|
+
it('stores `write_file` content byte for byte', async () => {
|
|
282
|
+
const { baseUrl, bytesOf } = await startPlatform();
|
|
283
|
+
const path = `${KB_DIR}/esc/route-mcp.md`;
|
|
284
|
+
await callTool(baseUrl, 'write_file', { body: { branch: BRANCH, path, content: PAYLOAD } });
|
|
285
|
+
expect(await bytesOf(path)).toEqual(EXPECTED);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it('stores `write_files` content byte for byte', async () => {
|
|
289
|
+
const { baseUrl, bytesOf } = await startPlatform();
|
|
290
|
+
const path = `${KB_DIR}/esc/route-mcp-batch.md`;
|
|
291
|
+
await callTool(baseUrl, 'write_files', { body: { branch: BRANCH, files: [{ path, content: PAYLOAD }] } });
|
|
292
|
+
expect(await bytesOf(path)).toEqual(EXPECTED);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('stores an `edit_file` `new_string` byte for byte', async () => {
|
|
296
|
+
const { baseUrl, bytesOf } = await startPlatform();
|
|
297
|
+
const path = `${KB_DIR}/esc/route-mcp-edit.md`;
|
|
298
|
+
await postRaw(
|
|
299
|
+
`${baseUrl}/api/agent/tools/write_file`,
|
|
300
|
+
JSON.stringify({ branch: BRANCH, path, content: 'PLACEHOLDER' }),
|
|
301
|
+
);
|
|
302
|
+
await callTool(baseUrl, 'edit_file', {
|
|
303
|
+
body: { branch: BRANCH, path, old_string: 'PLACEHOLDER', new_string: PAYLOAD },
|
|
304
|
+
});
|
|
305
|
+
expect(await bytesOf(path)).toEqual(EXPECTED);
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
describe('escape sequences survive a write — call_tool_chain', () => {
|
|
310
|
+
/**
|
|
311
|
+
* The chain's code with `PAYLOAD` as a JavaScript string LITERAL — the shape
|
|
312
|
+
* the ticket describes, and the one that crosses the isolate boundary twice
|
|
313
|
+
* (source in, arguments out). `JSON.stringify` of the payload is a valid JS
|
|
314
|
+
* literal for it, so the literal is produced rather than hand-escaped.
|
|
315
|
+
*/
|
|
316
|
+
const chainCode = (expr: string) => `return KNOWLEDGE_BASE.${expr};`;
|
|
317
|
+
|
|
318
|
+
it('stores `write_file` content byte for byte', async () => {
|
|
319
|
+
const { baseUrl, bytesOf } = await startPlatform();
|
|
320
|
+
const path = `${KB_DIR}/esc/route-chain.md`;
|
|
321
|
+
const code = chainCode(
|
|
322
|
+
`write_file({ body: { branch: ${JSON.stringify(BRANCH)}, path: ${JSON.stringify(path)}, content: ${JSON.stringify(PAYLOAD)} } })`,
|
|
323
|
+
);
|
|
324
|
+
// The literal inside the chain's own source is the six characters, written
|
|
325
|
+
// as a JS escape — two backslashes, exactly as on the JSON wire.
|
|
326
|
+
expect(code).toContain(WIRE_ESC_A);
|
|
327
|
+
|
|
328
|
+
const text = await callTool(baseUrl, 'call_tool_chain', { code });
|
|
329
|
+
expect(JSON.parse(text).success, text).toBe(true);
|
|
330
|
+
expect(await bytesOf(path)).toEqual(EXPECTED);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('stores `write_files` content byte for byte', async () => {
|
|
334
|
+
const { baseUrl, bytesOf } = await startPlatform();
|
|
335
|
+
const path = `${KB_DIR}/esc/route-chain-batch.md`;
|
|
336
|
+
const code = chainCode(
|
|
337
|
+
`write_files({ body: { branch: ${JSON.stringify(BRANCH)}, files: [{ path: ${JSON.stringify(path)}, content: ${JSON.stringify(PAYLOAD)} }] } })`,
|
|
338
|
+
);
|
|
339
|
+
expect(code).toContain(WIRE_ESC_A);
|
|
340
|
+
|
|
341
|
+
const text = await callTool(baseUrl, 'call_tool_chain', { code });
|
|
342
|
+
expect(JSON.parse(text).success, text).toBe(true);
|
|
343
|
+
expect(await bytesOf(path)).toEqual(EXPECTED);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it('stores an `edit_file` `new_string` byte for byte', async () => {
|
|
347
|
+
const { baseUrl, bytesOf } = await startPlatform();
|
|
348
|
+
const path = `${KB_DIR}/esc/route-chain-edit.md`;
|
|
349
|
+
await postRaw(
|
|
350
|
+
`${baseUrl}/api/agent/tools/write_file`,
|
|
351
|
+
JSON.stringify({ branch: BRANCH, path, content: 'PLACEHOLDER' }),
|
|
352
|
+
);
|
|
353
|
+
const code = chainCode(
|
|
354
|
+
`edit_file({ body: { branch: ${JSON.stringify(BRANCH)}, path: ${JSON.stringify(path)}, old_string: ${JSON.stringify('PLACEHOLDER')}, new_string: ${JSON.stringify(PAYLOAD)} } })`,
|
|
355
|
+
);
|
|
356
|
+
expect(code).toContain(WIRE_ESC_A);
|
|
357
|
+
|
|
358
|
+
const text = await callTool(baseUrl, 'call_tool_chain', { code });
|
|
359
|
+
expect(JSON.parse(text).success, text).toBe(true);
|
|
360
|
+
expect(await bytesOf(path)).toEqual(EXPECTED);
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
describe('what the write tools tell an agent about escape sequences', () => {
|
|
365
|
+
it('states the warning once, in the shared rules, and in no description', async () => {
|
|
366
|
+
const { descriptions } = await startPlatform();
|
|
367
|
+
const served = await descriptions();
|
|
368
|
+
// It applies to the three tools that take content as a JSON string, so it is
|
|
369
|
+
// a SHARED rule: stated in the handshake instructions and in the managed
|
|
370
|
+
// agent guide, and repeated in no tool description — a description that
|
|
371
|
+
// carried it was long enough for a client to cut the end off.
|
|
372
|
+
const rule = sharedFileRules(testKbContext().layout).find((r) => r.id === 'escape-sequences')!;
|
|
373
|
+
expect(rule.body).toContain('some clients decode escape sequences in arguments before sending');
|
|
374
|
+
// The way out is the upload route, which the rule after it spells out by
|
|
375
|
+
// the names of the tools this deployment serves.
|
|
376
|
+
expect(rule.body).toContain('by upload (see below), which lands it unchanged');
|
|
377
|
+
const upload = sharedFileRules(testKbContext().layout).find((r) => r.id === 'upload-route')!;
|
|
378
|
+
expect(upload.body).toContain('`request_file_upload`');
|
|
379
|
+
expect(upload.body).toContain('`apply_file_upload`');
|
|
380
|
+
// It names the three tools it is about, so an agent reading the section
|
|
381
|
+
// knows where it applies.
|
|
382
|
+
for (const name of ['write_file', 'write_files', 'edit_file']) expect(rule.body, name).toContain(name);
|
|
383
|
+
expect(sharedFileRulesSection(testKbContext().layout)).toContain(rule.body);
|
|
384
|
+
|
|
385
|
+
const carrying = Object.entries(served)
|
|
386
|
+
.filter(([, d]) => d.includes('decode escape sequences in arguments before sending') || d.includes('some clients decode them in arguments'))
|
|
387
|
+
.map(([n]) => n);
|
|
388
|
+
expect(carrying).toEqual([]);
|
|
389
|
+
// Each of the three still points at where the warning is.
|
|
390
|
+
for (const name of ['write_file', 'write_files', 'edit_file']) {
|
|
391
|
+
expect(served[name], name).toContain(sharedRulesPointer(testKbContext().layout).trim());
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
});
|
|
@@ -13,6 +13,7 @@ import type { ToolAuth } from '../../tool-auth/tool-auth.middleware.js';
|
|
|
13
13
|
import { registerWorkspaceTools } from '../workspace.tools.js';
|
|
14
14
|
import { RoutineWritePolicyService } from '../routine-write-policy.js';
|
|
15
15
|
import { WorkflowHooks } from '../../workflow/workflow-hooks.js';
|
|
16
|
+
import { ToolDescriptionNotes } from '../../workspace/agent-access.gate.js';
|
|
16
17
|
import { SpillStore } from '../spill-store.js';
|
|
17
18
|
import { DocExtractService } from '../file-readers/doc-extract.service.js';
|
|
18
19
|
import { AccessControlService } from '../../access/access-control.service.js';
|
|
@@ -88,7 +89,7 @@ beforeAll(async () => {
|
|
|
88
89
|
new DocExtractService(docCache),
|
|
89
90
|
service,
|
|
90
91
|
testKbContext({ kbDirName: KB }),
|
|
91
|
-
{
|
|
92
|
+
{ recoveryBotEmail: 'recovery-bot@bevel.local', hooks: new WorkflowHooks(), notes: new ToolDescriptionNotes() },
|
|
92
93
|
new RoutineWritePolicyService(),
|
|
93
94
|
{} as never,
|
|
94
95
|
);
|
|
@@ -22,6 +22,7 @@ import type { ICreatorAccess } from '../../access-model/creator.js';
|
|
|
22
22
|
import type { IAdminAccessService } from '../../admin/admin.interface.js';
|
|
23
23
|
import type { WorkflowEventBus } from '../../workflow/event-bus.js';
|
|
24
24
|
import { WorkflowHooks } from '../../workflow/workflow-hooks.js';
|
|
25
|
+
import { ToolDescriptionNotes } from '../../workspace/agent-access.gate.js';
|
|
25
26
|
import type { AuthService } from '../../auth/auth.service.js';
|
|
26
27
|
import { registerWorkspaceTools } from '../workspace.tools.js';
|
|
27
28
|
import { RoutineWritePolicyService } from '../routine-write-policy.js';
|
|
@@ -277,7 +278,7 @@ describe('workspace tools refuse the git folder', () => {
|
|
|
277
278
|
new DocExtractService(join(root, 'doc-cache')),
|
|
278
279
|
allowAll,
|
|
279
280
|
testKbContext({ kbDirName: KB }),
|
|
280
|
-
{
|
|
281
|
+
{ recoveryBotEmail: 'recovery-bot@bevel.local', hooks: new WorkflowHooks(), notes: new ToolDescriptionNotes() },
|
|
281
282
|
new RoutineWritePolicyService(),
|
|
282
283
|
{} as never,
|
|
283
284
|
);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { setAsideClone } from '../set-aside-clone.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Setting a working copy aside is the one thing between a replaced repository
|
|
9
|
+
* and the loss of whatever was only ever committed in that copy. So the
|
|
10
|
+
* destination is never somebody else's: a folder already there holds an
|
|
11
|
+
* EARLIER set-aside, and this one must neither land in it nor clean it up.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
let root: string;
|
|
15
|
+
|
|
16
|
+
beforeEach(async () => {
|
|
17
|
+
root = await fs.mkdtemp(path.join(os.tmpdir(), 'set-aside-clone-'));
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
afterEach(async () => {
|
|
21
|
+
await fs.rm(root, { recursive: true, force: true });
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
async function folderWith(name: string, file: string, content: string): Promise<string> {
|
|
25
|
+
const dir = path.join(root, name);
|
|
26
|
+
await fs.mkdir(dir, { recursive: true });
|
|
27
|
+
await fs.writeFile(path.join(dir, file), content, 'utf8');
|
|
28
|
+
return dir;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe('setAsideClone', () => {
|
|
32
|
+
it('moves the working copy, whole, to a destination that is not there yet', async () => {
|
|
33
|
+
const copy = await folderWith('workspaces/main/knowledge-base', 'unpushed.md', 'local work');
|
|
34
|
+
const dest = path.join(root, 'replaced-working-copies', 'stamp', 'main');
|
|
35
|
+
|
|
36
|
+
await setAsideClone(copy, dest);
|
|
37
|
+
|
|
38
|
+
expect(await fs.readFile(path.join(dest, 'unpushed.md'), 'utf8')).toBe('local work');
|
|
39
|
+
expect(await fs.access(copy).then(() => true, () => false)).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('refuses a destination that already holds a set-aside copy, and touches neither', async () => {
|
|
43
|
+
const copy = await folderWith('workspaces/main/knowledge-base', 'unpushed.md', 'this copy');
|
|
44
|
+
const dest = await folderWith('replaced-working-copies/stamp/main', 'unpushed.md', 'an earlier copy');
|
|
45
|
+
|
|
46
|
+
await expect(setAsideClone(copy, dest)).rejects.toThrow(/already holds one/);
|
|
47
|
+
|
|
48
|
+
// The earlier one is whole, and so is the one that was to be moved.
|
|
49
|
+
expect(await fs.readFile(path.join(dest, 'unpushed.md'), 'utf8')).toBe('an earlier copy');
|
|
50
|
+
expect(await fs.readFile(path.join(copy, 'unpushed.md'), 'utf8')).toBe('this copy');
|
|
51
|
+
});
|
|
52
|
+
});
|