@bevel-software/platform-core-backend 0.26.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent-guide/access-control.md +17 -0
- package/agent-guide/conventions.md +8 -0
- package/agent-guide/html-views.md +83 -0
- package/agent-guide/new-knowledge-base.md +24 -0
- package/agent-guide/tool-manuals.md +10 -0
- package/dist/assets.d.ts +19 -0
- package/dist/assets.d.ts.map +1 -1
- package/dist/assets.js +26 -2
- package/dist/assets.js.map +1 -1
- package/dist/core/catalog-revision.d.ts +19 -1
- package/dist/core/catalog-revision.d.ts.map +1 -1
- package/dist/core/catalog-revision.js +14 -3
- package/dist/core/catalog-revision.js.map +1 -1
- package/dist/core/create-core-server.d.ts +5 -0
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +54 -3
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +26 -0
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +100 -8
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/core/lifecycle.d.ts +16 -1
- package/dist/core/lifecycle.d.ts.map +1 -1
- package/dist/core/lifecycle.js +18 -1
- package/dist/core/lifecycle.js.map +1 -1
- package/dist/core-config.d.ts +21 -0
- package/dist/core-config.d.ts.map +1 -1
- package/dist/core-config.js +22 -1
- package/dist/core-config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +8 -0
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +1 -0
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +11 -0
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/directory-sync-bot.js +1 -1
- package/dist/modules/access/directory-sync-bot.js.map +1 -1
- package/dist/modules/agent-guide/agent-guide.d.ts +6 -0
- package/dist/modules/agent-guide/agent-guide.d.ts.map +1 -1
- package/dist/modules/agent-guide/agent-guide.js +8 -0
- package/dist/modules/agent-guide/agent-guide.js.map +1 -1
- package/dist/modules/agent-guide/index.d.ts +1 -1
- package/dist/modules/agent-guide/index.d.ts.map +1 -1
- package/dist/modules/agent-guide/index.js +1 -1
- package/dist/modules/agent-guide/index.js.map +1 -1
- package/dist/modules/agent-instructions/compose.d.ts +5 -0
- package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
- package/dist/modules/agent-instructions/compose.js +19 -2
- package/dist/modules/agent-instructions/compose.js.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.js +19 -19
- package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
- package/dist/modules/audit/agent-audit.service.d.ts +21 -2
- package/dist/modules/audit/agent-audit.service.d.ts.map +1 -1
- package/dist/modules/audit/agent-audit.service.js +43 -2
- package/dist/modules/audit/agent-audit.service.js.map +1 -1
- package/dist/modules/audit/agent-connection.routes.d.ts +34 -0
- package/dist/modules/audit/agent-connection.routes.d.ts.map +1 -0
- package/dist/modules/audit/agent-connection.routes.js +38 -0
- package/dist/modules/audit/agent-connection.routes.js.map +1 -0
- package/dist/modules/audit/audit.contract.d.ts +24 -0
- package/dist/modules/audit/audit.contract.d.ts.map +1 -1
- package/dist/modules/audit/audit.contract.js.map +1 -1
- package/dist/modules/audit/index.d.ts +1 -0
- package/dist/modules/audit/index.d.ts.map +1 -1
- package/dist/modules/audit/index.js +1 -0
- package/dist/modules/audit/index.js.map +1 -1
- package/dist/modules/auth/account.routes.d.ts.map +1 -1
- package/dist/modules/auth/account.routes.js +10 -3
- package/dist/modules/auth/account.routes.js.map +1 -1
- package/dist/modules/auth/auth.middleware.d.ts +7 -0
- package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
- package/dist/modules/auth/auth.middleware.js +1 -1
- package/dist/modules/auth/auth.middleware.js.map +1 -1
- package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
- package/dist/modules/code-mode/code-mode.tool.js +11 -3
- package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
- package/dist/modules/database/core-schema.d.ts +96 -0
- package/dist/modules/database/core-schema.d.ts.map +1 -1
- package/dist/modules/database/core-schema.js +32 -3
- package/dist/modules/database/core-schema.js.map +1 -1
- package/dist/modules/embed/account-link.service.d.ts +42 -0
- package/dist/modules/embed/account-link.service.d.ts.map +1 -0
- package/dist/modules/embed/account-link.service.js +50 -0
- package/dist/modules/embed/account-link.service.js.map +1 -0
- package/dist/modules/embed/embed-link.d.ts +58 -0
- package/dist/modules/embed/embed-link.d.ts.map +1 -0
- package/dist/modules/embed/embed-link.js +160 -0
- package/dist/modules/embed/embed-link.js.map +1 -0
- package/dist/modules/embed/embed.errors.d.ts +30 -0
- package/dist/modules/embed/embed.errors.d.ts.map +1 -0
- package/dist/modules/embed/embed.errors.js +42 -0
- package/dist/modules/embed/embed.errors.js.map +1 -0
- package/dist/modules/embed/embed.interface.d.ts +204 -0
- package/dist/modules/embed/embed.interface.d.ts.map +1 -0
- package/dist/modules/embed/embed.interface.js +2 -0
- package/dist/modules/embed/embed.interface.js.map +1 -0
- package/dist/modules/embed/embed.routes.d.ts +59 -0
- package/dist/modules/embed/embed.routes.d.ts.map +1 -0
- package/dist/modules/embed/embed.routes.js +343 -0
- package/dist/modules/embed/embed.routes.js.map +1 -0
- package/dist/modules/embed/embed.service.d.ts +169 -0
- package/dist/modules/embed/embed.service.d.ts.map +1 -0
- package/dist/modules/embed/embed.service.js +627 -0
- package/dist/modules/embed/embed.service.js.map +1 -0
- package/dist/modules/embed/embed.tools.d.ts +51 -0
- package/dist/modules/embed/embed.tools.d.ts.map +1 -0
- package/dist/modules/embed/embed.tools.js +170 -0
- package/dist/modules/embed/embed.tools.js.map +1 -0
- package/dist/modules/embed/mcp-app.d.ts +64 -0
- package/dist/modules/embed/mcp-app.d.ts.map +1 -0
- package/dist/modules/embed/mcp-app.js +147 -0
- package/dist/modules/embed/mcp-app.js.map +1 -0
- package/dist/modules/embed/mcp-app.routes.d.ts +21 -0
- package/dist/modules/embed/mcp-app.routes.d.ts.map +1 -0
- package/dist/modules/embed/mcp-app.routes.js +36 -0
- package/dist/modules/embed/mcp-app.routes.js.map +1 -0
- package/dist/modules/github-app/github-app.connection.d.ts +15 -0
- package/dist/modules/github-app/github-app.connection.d.ts.map +1 -1
- package/dist/modules/github-app/github-app.connection.js +34 -3
- package/dist/modules/github-app/github-app.connection.js.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.d.ts +65 -0
- package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.js +300 -0
- package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
- package/dist/modules/kb-sync/kb-sync.interface.d.ts +5 -0
- package/dist/modules/kb-sync/kb-sync.interface.d.ts.map +1 -1
- package/dist/modules/kb-sync/kb-sync.service.d.ts.map +1 -1
- package/dist/modules/kb-sync/kb-sync.service.js +6 -0
- package/dist/modules/kb-sync/kb-sync.service.js.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.js +2 -1
- package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
- package/dist/modules/mcp/mcp.service.d.ts +28 -1
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +64 -8
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +24 -0
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js +32 -4
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
- package/dist/modules/onboarding/onboarding.contract.d.ts +80 -0
- package/dist/modules/onboarding/onboarding.contract.d.ts.map +1 -0
- package/dist/modules/onboarding/onboarding.contract.js +7 -0
- package/dist/modules/onboarding/onboarding.contract.js.map +1 -0
- package/dist/modules/onboarding/onboarding.routes.d.ts +18 -0
- package/dist/modules/onboarding/onboarding.routes.d.ts.map +1 -0
- package/dist/modules/onboarding/onboarding.routes.js +81 -0
- package/dist/modules/onboarding/onboarding.routes.js.map +1 -0
- package/dist/modules/onboarding/starter-pack.service.d.ts +213 -0
- package/dist/modules/onboarding/starter-pack.service.d.ts.map +1 -0
- package/dist/modules/onboarding/starter-pack.service.js +492 -0
- package/dist/modules/onboarding/starter-pack.service.js.map +1 -0
- package/dist/modules/onboarding/starter-packs.d.ts +78 -0
- package/dist/modules/onboarding/starter-packs.d.ts.map +1 -0
- package/dist/modules/onboarding/starter-packs.js +156 -0
- package/dist/modules/onboarding/starter-packs.js.map +1 -0
- package/dist/modules/plugins/index.d.ts +1 -1
- package/dist/modules/plugins/index.d.ts.map +1 -1
- package/dist/modules/plugins/index.js +1 -1
- package/dist/modules/plugins/index.js.map +1 -1
- package/dist/modules/plugins/plugin-provision.service.d.ts +19 -0
- package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-provision.service.js +18 -1
- package/dist/modules/plugins/plugin-provision.service.js.map +1 -1
- package/dist/modules/plugins/plugins.routes.d.ts +0 -36
- package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.js +31 -0
- package/dist/modules/plugins/plugins.routes.js.map +1 -1
- package/dist/modules/settings/deployment-settings.service.d.ts +34 -0
- package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
- package/dist/modules/settings/deployment-settings.service.js +134 -1
- package/dist/modules/settings/deployment-settings.service.js.map +1 -1
- package/dist/modules/settings/repository-source.d.ts +1 -0
- package/dist/modules/settings/repository-source.d.ts.map +1 -1
- package/dist/modules/settings/repository-source.js +3 -3
- package/dist/modules/settings/repository-source.js.map +1 -1
- package/dist/modules/skills/skills.tools.d.ts.map +1 -1
- package/dist/modules/skills/skills.tools.js +50 -32
- package/dist/modules/skills/skills.tools.js.map +1 -1
- package/dist/modules/tool-auth/connection-key-rejection.d.ts +10 -1
- package/dist/modules/tool-auth/connection-key-rejection.d.ts.map +1 -1
- package/dist/modules/tool-auth/connection-key-rejection.js +11 -3
- package/dist/modules/tool-auth/connection-key-rejection.js.map +1 -1
- package/dist/modules/tool-auth/external-api-key.interface.d.ts +8 -0
- package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.d.ts +24 -2
- package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.js +59 -2
- package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.js +9 -4
- package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
- package/dist/modules/tool-helpers/index.d.ts +28 -1
- package/dist/modules/tool-helpers/index.d.ts.map +1 -1
- package/dist/modules/tool-helpers/index.js +28 -1
- package/dist/modules/tool-helpers/index.js.map +1 -1
- package/dist/modules/tool-helpers/route-argument-check.d.ts +10 -0
- package/dist/modules/tool-helpers/route-argument-check.d.ts.map +1 -0
- package/dist/modules/tool-helpers/route-argument-check.js +132 -0
- package/dist/modules/tool-helpers/route-argument-check.js.map +1 -0
- package/dist/modules/tool-helpers/route-tool-schemas.d.ts +89 -0
- package/dist/modules/tool-helpers/route-tool-schemas.d.ts.map +1 -0
- package/dist/modules/tool-helpers/route-tool-schemas.js +113 -0
- package/dist/modules/tool-helpers/route-tool-schemas.js.map +1 -0
- package/dist/modules/tool-helpers/tool-def.d.ts +71 -0
- package/dist/modules/tool-helpers/tool-def.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-def.js +70 -6
- package/dist/modules/tool-helpers/tool-def.js.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.d.ts +46 -6
- package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.js +157 -8
- package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.d.ts +9 -0
- package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.js +14 -5
- package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
- package/dist/modules/tool-registry/description-length.d.ts +6 -2
- package/dist/modules/tool-registry/description-length.d.ts.map +1 -1
- package/dist/modules/tool-registry/description-length.js +27 -5
- package/dist/modules/tool-registry/description-length.js.map +1 -1
- package/dist/modules/tool-registry/guide-first.d.ts +4 -0
- package/dist/modules/tool-registry/guide-first.d.ts.map +1 -1
- package/dist/modules/tool-registry/guide-first.js +9 -1
- package/dist/modules/tool-registry/guide-first.js.map +1 -1
- package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts +53 -0
- package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/change-request-read.tools.js +97 -68
- package/dist/modules/workflow/agent-tools/change-request-read.tools.js.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts +8 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.js +124 -37
- package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
- package/dist/modules/workflow/file-lock.service.d.ts +29 -5
- package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
- package/dist/modules/workflow/file-lock.service.js +62 -5
- package/dist/modules/workflow/file-lock.service.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +72 -1
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +172 -36
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/git/node-git-runner.d.ts +15 -0
- package/dist/modules/workflow/git/node-git-runner.d.ts.map +1 -1
- package/dist/modules/workflow/git/node-git-runner.js +78 -6
- package/dist/modules/workflow/git/node-git-runner.js.map +1 -1
- package/dist/modules/workflow/pending-commits.service.d.ts +15 -0
- package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.service.js +25 -1
- package/dist/modules/workflow/pending-commits.service.js.map +1 -1
- package/dist/modules/workflow/pending-commits.worker.d.ts +6 -1
- package/dist/modules/workflow/pending-commits.worker.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.worker.js +23 -6
- package/dist/modules/workflow/pending-commits.worker.js.map +1 -1
- package/dist/modules/workflow/recovery-bot.js +1 -1
- package/dist/modules/workflow/recovery-bot.js.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.js +2 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
- package/dist/modules/workflow/sanitize-error.d.ts +16 -0
- package/dist/modules/workflow/sanitize-error.d.ts.map +1 -1
- package/dist/modules/workflow/sanitize-error.js +45 -0
- package/dist/modules/workflow/sanitize-error.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +183 -15
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +805 -106
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/agent-download.builder.d.ts +105 -0
- package/dist/modules/workspace/agent-download.builder.d.ts.map +1 -0
- package/dist/modules/workspace/agent-download.builder.js +238 -0
- package/dist/modules/workspace/agent-download.builder.js.map +1 -0
- package/dist/modules/workspace/agent-download.routes.d.ts +77 -0
- package/dist/modules/workspace/agent-download.routes.d.ts.map +1 -0
- package/dist/modules/workspace/agent-download.routes.js +156 -0
- package/dist/modules/workspace/agent-download.routes.js.map +1 -0
- package/dist/modules/workspace/agent-download.store.d.ts +225 -0
- package/dist/modules/workspace/agent-download.store.d.ts.map +1 -0
- package/dist/modules/workspace/agent-download.store.js +423 -0
- package/dist/modules/workspace/agent-download.store.js.map +1 -0
- package/dist/modules/workspace/agent-upload.store.d.ts +10 -1
- package/dist/modules/workspace/agent-upload.store.d.ts.map +1 -1
- package/dist/modules/workspace/agent-upload.store.js +16 -6
- package/dist/modules/workspace/agent-upload.store.js.map +1 -1
- package/dist/modules/workspace/file-readers/raw-mime.d.ts +6 -0
- package/dist/modules/workspace/file-readers/raw-mime.d.ts.map +1 -0
- package/dist/modules/workspace/file-readers/raw-mime.js +38 -0
- package/dist/modules/workspace/file-readers/raw-mime.js.map +1 -0
- package/dist/modules/workspace/file-readers/text-reader.d.ts +5 -5
- package/dist/modules/workspace/file-readers/text-reader.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/text-reader.js +5 -8
- package/dist/modules/workspace/file-readers/text-reader.js.map +1 -1
- package/dist/modules/workspace/first-run.d.ts +111 -0
- package/dist/modules/workspace/first-run.d.ts.map +1 -0
- package/dist/modules/workspace/first-run.js +181 -0
- package/dist/modules/workspace/first-run.js.map +1 -0
- package/dist/modules/workspace/move-links.d.ts +92 -0
- package/dist/modules/workspace/move-links.d.ts.map +1 -0
- package/dist/modules/workspace/move-links.js +173 -0
- package/dist/modules/workspace/move-links.js.map +1 -0
- package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.js +3 -13
- package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
- package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.routes.js +63 -22
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +75 -2
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +197 -24
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts +59 -1
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +443 -65
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/modules/write-access/write-access.d.ts.map +1 -1
- package/dist/modules/write-access/write-access.js +9 -0
- package/dist/modules/write-access/write-access.js.map +1 -1
- package/dist/shared/domain-errors.d.ts +73 -1
- package/dist/shared/domain-errors.d.ts.map +1 -1
- package/dist/shared/domain-errors.js +86 -12
- package/dist/shared/domain-errors.js.map +1 -1
- package/dist/shared/git.contract.d.ts +10 -1
- package/dist/shared/git.contract.d.ts.map +1 -1
- package/dist/shared/git.contract.js.map +1 -1
- package/dist/shared/utf8-text.d.ts +15 -0
- package/dist/shared/utf8-text.d.ts.map +1 -0
- package/dist/shared/utf8-text.js +20 -0
- package/dist/shared/utf8-text.js.map +1 -0
- package/dist/tenancy/static-tenant-source.d.ts +2 -1
- package/dist/tenancy/static-tenant-source.d.ts.map +1 -1
- package/dist/tenancy/static-tenant-source.js +9 -2
- package/dist/tenancy/static-tenant-source.js.map +1 -1
- package/mcp-app/page.html +349 -0
- package/migrations/0017_atlassian_account_links.sql +37 -0
- package/migrations/meta/0017_snapshot.json +2396 -0
- package/migrations/meta/_journal.json +8 -1
- package/package.json +5 -3
- package/src/__tests__/packaged-assets-ship.test.ts +16 -4
- package/src/assets.ts +28 -2
- package/src/core/__tests__/catalog-revision.test.ts +40 -0
- package/src/core/__tests__/gated-boot.test.ts +1 -0
- package/src/core/__tests__/lifecycle.test.ts +14 -0
- package/src/core/catalog-revision.ts +15 -2
- package/src/core/create-core-server.ts +67 -3
- package/src/core/create-core-services.ts +140 -6
- package/src/core/lifecycle.ts +28 -2
- package/src/core-config.ts +24 -1
- package/src/index.ts +2 -2
- package/src/modules/access/__tests__/access-control.service.test.ts +19 -0
- package/src/modules/access/access-control.interface.ts +13 -0
- package/src/modules/access/access-control.service.ts +16 -0
- package/src/modules/access/directory-sync-bot.ts +1 -1
- package/src/modules/agent-guide/__tests__/agent-guide.test.ts +58 -0
- package/src/modules/agent-guide/__tests__/agent-guide.tools.test.ts +19 -1
- package/src/modules/agent-guide/agent-guide.ts +9 -0
- package/src/modules/agent-guide/index.ts +1 -0
- package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +14 -0
- package/src/modules/agent-instructions/compose.ts +17 -2
- package/src/modules/agent-instructions/shared-file-rules.ts +19 -19
- package/src/modules/audit/__tests__/agent-audit.service.test.ts +64 -4
- package/src/modules/audit/__tests__/agent-connection.routes.test.ts +102 -0
- package/src/modules/audit/agent-audit.service.ts +58 -3
- package/src/modules/audit/agent-connection.routes.ts +56 -0
- package/src/modules/audit/audit.contract.ts +26 -0
- package/src/modules/audit/index.ts +1 -0
- package/src/modules/auth/__tests__/account.routes.test.ts +5 -3
- package/src/modules/auth/account.routes.ts +10 -3
- package/src/modules/auth/auth.middleware.ts +1 -1
- package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +18 -2
- package/src/modules/code-mode/code-mode.tool.ts +13 -2
- package/src/modules/database/core-schema.ts +33 -3
- package/src/modules/embed/__tests__/account-links.migration.test.ts +98 -0
- package/src/modules/embed/__tests__/embed-link.test.ts +122 -0
- package/src/modules/embed/__tests__/embed.routes.test.ts +362 -0
- package/src/modules/embed/__tests__/embed.service.lock.test.ts +193 -0
- package/src/modules/embed/__tests__/embed.service.test.ts +805 -0
- package/src/modules/embed/__tests__/embed.tools.test.ts +304 -0
- package/src/modules/embed/__tests__/mcp-app.test.ts +207 -0
- package/src/modules/embed/account-link.service.ts +76 -0
- package/src/modules/embed/embed-link.ts +180 -0
- package/src/modules/embed/embed.errors.ts +43 -0
- package/src/modules/embed/embed.interface.ts +203 -0
- package/src/modules/embed/embed.routes.ts +363 -0
- package/src/modules/embed/embed.service.ts +705 -0
- package/src/modules/embed/embed.tools.ts +206 -0
- package/src/modules/embed/mcp-app.routes.ts +41 -0
- package/src/modules/embed/mcp-app.ts +173 -0
- package/src/modules/github-app/__tests__/github-app.test.ts +76 -0
- package/src/modules/github-app/github-app.connection.ts +38 -4
- package/src/modules/kb-fs/__tests__/move-with-edits.test.ts +304 -0
- package/src/modules/kb-fs/locking-filesystem.ts +287 -0
- package/src/modules/kb-sync/__tests__/kb-sync.service.test.ts +35 -0
- package/src/modules/kb-sync/kb-sync.interface.ts +2 -0
- package/src/modules/kb-sync/kb-sync.service.ts +6 -0
- package/src/modules/mcp/__tests__/bevel-oauth-provider.test.ts +68 -11
- package/src/modules/mcp/__tests__/mcp-proxy-helpers.test.ts +37 -2
- package/src/modules/mcp/__tests__/mcp.app.e2e.test.ts +200 -0
- package/src/modules/mcp/__tests__/mcp.e2e.test.ts +3 -1
- package/src/modules/mcp/__tests__/mcp.service.test.ts +23 -13
- package/src/modules/mcp/__tests__/tool-call-interface.e2e.test.ts +499 -0
- package/src/modules/mcp/mcp-auth.middleware.ts +2 -1
- package/src/modules/mcp/mcp.service.ts +81 -12
- package/src/modules/mcp/oauth/bevel-oauth-provider.ts +42 -4
- package/src/modules/onboarding/__tests__/onboarding.routes.test.ts +163 -0
- package/src/modules/onboarding/__tests__/starter-pack.service.test.ts +635 -0
- package/src/modules/onboarding/__tests__/starter-packs.test.ts +169 -0
- package/src/modules/onboarding/onboarding.contract.ts +85 -0
- package/src/modules/onboarding/onboarding.routes.ts +85 -0
- package/src/modules/onboarding/starter-pack.service.ts +580 -0
- package/src/modules/onboarding/starter-packs.ts +182 -0
- package/src/modules/plugins/__tests__/plugins.tools.test.ts +25 -2
- package/src/modules/plugins/index.ts +1 -0
- package/src/modules/plugins/plugin-provision.service.ts +22 -1
- package/src/modules/plugins/plugins.routes.ts +29 -0
- package/src/modules/settings/__tests__/deployment-settings.service.test.ts +150 -1
- package/src/modules/settings/__tests__/repository-source.test.ts +9 -0
- package/src/modules/settings/deployment-settings.service.ts +131 -1
- package/src/modules/settings/repository-source.ts +4 -4
- package/src/modules/skills/__tests__/skills.tools.description.test.ts +5 -1
- package/src/modules/skills/skills.tools.ts +55 -35
- package/src/modules/tool-auth/__tests__/connection-key-rejection.test.ts +32 -0
- package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +65 -0
- package/src/modules/tool-auth/connection-key-rejection.ts +13 -3
- package/src/modules/tool-auth/external-api-key.interface.ts +9 -0
- package/src/modules/tool-auth/external-api-key.service.ts +61 -4
- package/src/modules/tool-auth/tool-auth.middleware.ts +13 -4
- package/src/modules/tool-helpers/__tests__/branch-declaration.test.ts +502 -0
- package/src/modules/tool-helpers/__tests__/route-argument-check.test.ts +337 -0
- package/src/modules/tool-helpers/index.ts +34 -1
- package/src/modules/tool-helpers/route-argument-check.ts +149 -0
- package/src/modules/tool-helpers/route-tool-schemas.ts +171 -0
- package/src/modules/tool-helpers/tool-def.ts +132 -7
- package/src/modules/tool-helpers/tool-handler.ts +196 -8
- package/src/modules/tool-helpers/tool.contract.ts +9 -0
- package/src/modules/tool-manuals/tool-manuals.tools.ts +15 -5
- package/src/modules/tool-registry/__tests__/own-tool-schemas.test.ts +14 -1
- package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +23 -10
- package/src/modules/tool-registry/description-length.ts +34 -7
- package/src/modules/tool-registry/guide-first.ts +8 -1
- package/src/modules/workflow/__tests__/file-lock.service.deletion-gate.test.ts +104 -0
- package/src/modules/workflow/__tests__/pending-commits.repository-replaced.test.ts +66 -0
- package/src/modules/workflow/__tests__/pending-commits.worker.test.ts +31 -2
- package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +110 -6
- package/src/modules/workflow/__tests__/sanitize-error.test.ts +89 -1
- package/src/modules/workflow/__tests__/workflow.routes.push-refused.test.ts +202 -0
- package/src/modules/workflow/__tests__/workflow.service.branch-in-use.test.ts +6 -6
- package/src/modules/workflow/__tests__/workflow.service.delete-branch.test.ts +1026 -0
- package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +468 -10
- package/src/modules/workflow/__tests__/workflow.service.folder-proposals.test.ts +4 -0
- package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +5 -4
- package/src/modules/workflow/__tests__/workflow.service.update-from-target.test.ts +54 -2
- package/src/modules/workflow/agent-tools/__tests__/change-request-read.tools.test.ts +41 -5
- package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +127 -2
- package/src/modules/workflow/agent-tools/change-request-read.tools.ts +157 -122
- package/src/modules/workflow/agent-tools/workflow.tools.ts +129 -35
- package/src/modules/workflow/file-lock.service.ts +67 -5
- package/src/modules/workflow/git/__tests__/git.service.deleteBranch.test.ts +164 -0
- package/src/modules/workflow/git/__tests__/git.service.pull.test.ts +48 -1
- package/src/modules/workflow/git/__tests__/node-git-runner.test.ts +231 -2
- package/src/modules/workflow/git/git.service.ts +176 -38
- package/src/modules/workflow/git/node-git-runner.ts +82 -7
- package/src/modules/workflow/pending-commits.service.ts +29 -1
- package/src/modules/workflow/pending-commits.worker.ts +24 -6
- package/src/modules/workflow/recovery-bot.ts +1 -1
- package/src/modules/workflow/review-workflow/review-workflow.service.ts +2 -1
- package/src/modules/workflow/sanitize-error.ts +50 -0
- package/src/modules/workflow/workflow.service.ts +879 -112
- package/src/modules/workspace/__tests__/agent-downloads.test.ts +823 -0
- package/src/modules/workspace/__tests__/first-run.test.ts +234 -0
- package/src/modules/workspace/__tests__/git-internals.security.test.ts +5 -2
- package/src/modules/workspace/__tests__/md-links.test.ts +353 -0
- package/src/modules/workspace/__tests__/move-links.test.ts +261 -0
- package/src/modules/workspace/__tests__/rest-tool-arguments.test.ts +219 -0
- package/src/modules/workspace/__tests__/workspace-paths-inside-the-repo.test.ts +3 -0
- package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +102 -0
- package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +52 -5
- package/src/modules/workspace/__tests__/workspace.routes.folder-zip-access.test.ts +190 -0
- package/src/modules/workspace/__tests__/workspace.service.forget-clone-races.test.ts +8 -4
- package/src/modules/workspace/__tests__/workspace.service.test.ts +50 -8
- package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +44 -1
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +409 -12
- package/src/modules/workspace/agent-download.builder.ts +308 -0
- package/src/modules/workspace/agent-download.routes.ts +183 -0
- package/src/modules/workspace/agent-download.store.ts +533 -0
- package/src/modules/workspace/agent-upload.store.ts +23 -7
- package/src/modules/workspace/file-readers/raw-mime.ts +40 -0
- package/src/modules/workspace/file-readers/text-reader.ts +5 -8
- package/src/modules/workspace/first-run.ts +222 -0
- package/src/modules/workspace/move-links.ts +237 -0
- package/src/modules/workspace/startup/steps/seed-tree.ts +3 -12
- package/src/modules/workspace/workspace.routes.ts +64 -22
- package/src/modules/workspace/workspace.service.ts +202 -25
- package/src/modules/workspace/workspace.tools.ts +492 -63
- package/src/modules/write-access/__tests__/write-access.test.ts +13 -0
- package/src/modules/write-access/write-access.ts +9 -0
- package/src/shared/domain-errors.ts +108 -11
- package/src/shared/git.contract.ts +8 -1
- package/src/shared/utf8-text.ts +21 -0
- package/src/tenancy/__tests__/static-tenant-source.test.ts +1 -0
- package/src/tenancy/static-tenant-source.ts +10 -2
- package/starter-packs/README.md +103 -0
- package/starter-packs/engineering/KnowledgeBase/About us.md +9 -0
- package/starter-packs/engineering/KnowledgeBase/Architecture.md +9 -0
- package/starter-packs/engineering/KnowledgeBase/Glossary.md +9 -0
- package/starter-packs/engineering/KnowledgeBase/How we ship.md +9 -0
- package/starter-packs/engineering/Plugins/engineering-starter/LICENSE +21 -0
- package/starter-packs/engineering/Plugins/engineering-starter/SOURCE.md +20 -0
- package/starter-packs/engineering/Plugins/engineering-starter/access.md +19 -0
- package/starter-packs/engineering/Plugins/engineering-starter/plugin.json +10 -0
- package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/SKILL.md +171 -0
- package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/sections/phase-2a-startup-diagnostic.md +157 -0
- package/starter-packs/engineering/Plugins/engineering-starter/skills/office-hours/sections/phase-2b-builder-brainstorm.md +39 -0
- package/starter-packs/engineering/Plugins/engineering-starter/skills/plan-ceo-review/SKILL.md +144 -0
- package/starter-packs/engineering/Plugins/engineering-starter/skills/plan-eng-review/SKILL.md +144 -0
- package/starter-packs/engineering/Plugins/engineering-starter/skills/review/SKILL.md +80 -0
- package/starter-packs/engineering/Plugins/engineering-starter/skills/review/checklist.md +184 -0
- package/starter-packs/engineering/pack.yaml +11 -0
- package/starter-packs/general/KnowledgeBase/About us.md +9 -0
- package/starter-packs/general/KnowledgeBase/Glossary.md +9 -0
- package/starter-packs/general/KnowledgeBase/How we work.md +9 -0
- package/starter-packs/general/Plugins/general-starter/LICENSE +202 -0
- package/starter-packs/general/Plugins/general-starter/SOURCE.md +10 -0
- package/starter-packs/general/Plugins/general-starter/access.md +19 -0
- package/starter-packs/general/Plugins/general-starter/plugin.json +10 -0
- package/starter-packs/general/Plugins/general-starter/skills/memory-management/SKILL.md +332 -0
- package/starter-packs/general/Plugins/general-starter/skills/productivity-start/SKILL.md +163 -0
- package/starter-packs/general/Plugins/general-starter/skills/productivity-update/SKILL.md +167 -0
- package/starter-packs/general/Plugins/general-starter/skills/task-management/SKILL.md +83 -0
- package/starter-packs/general/pack.yaml +10 -0
- package/starter-packs/operations/KnowledgeBase/Glossary.md +9 -0
- package/starter-packs/operations/KnowledgeBase/How we work.md +9 -0
- package/starter-packs/operations/KnowledgeBase/Processes.md +9 -0
- package/starter-packs/operations/KnowledgeBase/Tools we use.md +9 -0
- package/starter-packs/operations/KnowledgeBase/Vendors.md +9 -0
- package/starter-packs/operations/Plugins/operations-starter/LICENSE +202 -0
- package/starter-packs/operations/Plugins/operations-starter/SOURCE.md +10 -0
- package/starter-packs/operations/Plugins/operations-starter/access.md +19 -0
- package/starter-packs/operations/Plugins/operations-starter/plugin.json +10 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/capacity-plan/SKILL.md +111 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/change-request/SKILL.md +119 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/compliance-tracking/SKILL.md +44 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/process-doc/SKILL.md +80 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/process-optimization/SKILL.md +39 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/risk-assessment/SKILL.md +40 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/runbook/SKILL.md +85 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/status-report/SKILL.md +75 -0
- package/starter-packs/operations/Plugins/operations-starter/skills/vendor-review/SKILL.md +102 -0
- package/starter-packs/operations/pack.yaml +12 -0
- package/starter-packs/sales/KnowledgeBase/About us.md +9 -0
- package/starter-packs/sales/KnowledgeBase/Customers.md +9 -0
- package/starter-packs/sales/KnowledgeBase/Glossary.md +9 -0
- package/starter-packs/sales/KnowledgeBase/How we sell.md +9 -0
- package/starter-packs/sales/KnowledgeBase/Objections.md +9 -0
- package/starter-packs/sales/KnowledgeBase/Pricing.md +9 -0
- package/starter-packs/sales/Plugins/sales-starter/LICENSE +202 -0
- package/starter-packs/sales/Plugins/sales-starter/SOURCE.md +10 -0
- package/starter-packs/sales/Plugins/sales-starter/access.md +19 -0
- package/starter-packs/sales/Plugins/sales-starter/plugin.json +10 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/account-context/SKILL.md +94 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/account-plan/SKILL.md +95 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/account-research/SKILL.md +99 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/account-tiering/SKILL.md +101 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/call-prep/SKILL.md +111 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/call-summary/SKILL.md +111 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/close-plan/SKILL.md +106 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/competitive-intelligence/SKILL.md +102 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/create-an-asset/SKILL.md +105 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/crm-hygiene-check/SKILL.md +103 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/customer-health/SKILL.md +108 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/customer-voice/SKILL.md +94 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/daily-briefing/SKILL.md +61 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/deal-advance-gap/SKILL.md +102 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/deal-review/SKILL.md +105 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/deal-signals/SKILL.md +96 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/deal-slip-scenario/SKILL.md +104 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/draft-outreach/SKILL.md +116 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/end-of-day/SKILL.md +114 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/expansion-whitespace/SKILL.md +97 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/forecast/SKILL.md +100 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/handle-objection/SKILL.md +93 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/inbox-sweep/SKILL.md +114 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/lead-triage/SKILL.md +106 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/log-activity/SKILL.md +111 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/pipeline-review/SKILL.md +97 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/renewal-radar/SKILL.md +95 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/rep-context/SKILL.md +90 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/route-lead/SKILL.md +111 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/sales-setup/SKILL.md +200 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/schedule-meeting/SKILL.md +99 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/stakeholder-map/SKILL.md +107 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/team-pipeline/SKILL.md +105 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/update-opportunity/SKILL.md +112 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/weekly-wrap/SKILL.md +90 -0
- package/starter-packs/sales/Plugins/sales-starter/skills/win-loss-review/SKILL.md +88 -0
- package/starter-packs/sales/pack.yaml +13 -0
- package/starter-packs/support/KnowledgeBase/Escalations.md +9 -0
- package/starter-packs/support/KnowledgeBase/Glossary.md +9 -0
- package/starter-packs/support/KnowledgeBase/Known issues.md +9 -0
- package/starter-packs/support/KnowledgeBase/Products.md +9 -0
- package/starter-packs/support/KnowledgeBase/Tone of voice.md +9 -0
- package/starter-packs/support/Plugins/support-starter/LICENSE +202 -0
- package/starter-packs/support/Plugins/support-starter/SOURCE.md +10 -0
- package/starter-packs/support/Plugins/support-starter/access.md +19 -0
- package/starter-packs/support/Plugins/support-starter/plugin.json +10 -0
- package/starter-packs/support/Plugins/support-starter/skills/customer-escalation/SKILL.md +246 -0
- package/starter-packs/support/Plugins/support-starter/skills/customer-research/SKILL.md +250 -0
- package/starter-packs/support/Plugins/support-starter/skills/draft-response/SKILL.md +416 -0
- package/starter-packs/support/Plugins/support-starter/skills/kb-article/SKILL.md +350 -0
- package/starter-packs/support/Plugins/support-starter/skills/ticket-triage/SKILL.md +273 -0
- package/starter-packs/support/pack.yaml +12 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The "first run" note `start_session` adds on a knowledge base nobody has
|
|
3
|
+
* written in yet.
|
|
4
|
+
*
|
|
5
|
+
* Someone who has just set up Hexis and connected their agent has an empty
|
|
6
|
+
* knowledge base and an agent that could fill it. The agent cannot know the
|
|
7
|
+
* first from a static instruction — the same text reaches a knowledge base of
|
|
8
|
+
* ten thousand pages — so the signal is computed when the session starts:
|
|
9
|
+
* while the knowledge folder of the default branch holds nothing but the
|
|
10
|
+
* starter guide the template seeds, `start_session` answers with a short note
|
|
11
|
+
* telling the agent to offer to draft the first pages, and the guide's
|
|
12
|
+
* {@link FIRST_RUN_SECTION_ID} section says how. Once a page of anyone's
|
|
13
|
+
* exists, the note stops on its own: there is no flag to clear.
|
|
14
|
+
*
|
|
15
|
+
* The question is asked AS THE CALLER, gated like a read. The note goes only
|
|
16
|
+
* to someone who may read the knowledge folder, and it judges the folder as
|
|
17
|
+
* they may see it: a page they may not read is passed over as if it were not
|
|
18
|
+
* there (`mayRead` in {@link knowledgeFolderIsNew}), so a note that does or
|
|
19
|
+
* does not come tells them nothing about pages beyond them. The answer names
|
|
20
|
+
* no file but the starter guide, which every knowledge base is seeded with,
|
|
21
|
+
* and a chosen starter pack's pages — and when one of those is beyond the
|
|
22
|
+
* caller, no note at all, since a list with a gap would say where the gap is.
|
|
23
|
+
*
|
|
24
|
+
* A STARTER PACK does not end it. The pages a pack adds are tasks with a
|
|
25
|
+
* heading and a line or two — "ask your agent to draft this" — so while one
|
|
26
|
+
* still holds exactly what the pack wrote it is not a page anyone wrote, and
|
|
27
|
+
* the note goes on, naming the pages the pack suggests drafting first. The
|
|
28
|
+
* first edit to any of them makes the knowledge base established.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import nodeFs from 'node:fs/promises';
|
|
32
|
+
import { join } from 'node:path';
|
|
33
|
+
import type { ITreeWalker } from '../../shared/fs.contract.js';
|
|
34
|
+
import { NEW_KNOWLEDGE_BASE_SECTION_ID } from '../agent-guide/index.js';
|
|
35
|
+
import { BevelIgnoreStack } from '../kb-fs/bevel-ignore.js';
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The starter guide the template seeds at the top of the knowledge folder
|
|
39
|
+
* (`kb-template/KnowledgeBase/How to get started.md`). The one page that does
|
|
40
|
+
* not make a knowledge base established. A test holds this to the template, so
|
|
41
|
+
* renaming one without the other fails there.
|
|
42
|
+
*/
|
|
43
|
+
export const STARTER_GUIDE_FILE = 'How to get started.md';
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The guide section the note points at (`agent-guide/new-knowledge-base.md`):
|
|
47
|
+
* the guide's own id, so the note cannot name a section the guide renamed.
|
|
48
|
+
*/
|
|
49
|
+
export const FIRST_RUN_SECTION_ID = NEW_KNOWLEDGE_BASE_SECTION_ID;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The starter pack a knowledge base was filled from, as far as the first-run
|
|
53
|
+
* check cares: what the team chose, the pages it suggests drafting, and the
|
|
54
|
+
* pages it wrote — by path below the knowledge folder, with the text each was
|
|
55
|
+
* written with.
|
|
56
|
+
*/
|
|
57
|
+
export interface FirstRunStarter {
|
|
58
|
+
name: string;
|
|
59
|
+
suggestedPages: readonly string[];
|
|
60
|
+
pages: ReadonlyMap<string, string>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Where `start_session` asks which starter pack was chosen (see `modules/onboarding`). */
|
|
64
|
+
export interface FirstRunStarterSource {
|
|
65
|
+
firstRunStarter(): Promise<FirstRunStarter | null>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Files that are configuration, not content, wherever they sit: a folder's
|
|
70
|
+
* access rules. Dot-entries (the folder placeholder `.gitkeep`, `.bevelignore`,
|
|
71
|
+
* an OS's `.DS_Store`) the knowledge-base walk never shows at all.
|
|
72
|
+
*/
|
|
73
|
+
const NOT_CONTENT = new Set(['access.md']);
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* How many folders the check lists, the knowledge folder included, before it
|
|
77
|
+
* calls the knowledge base established. A knowledge base with this many
|
|
78
|
+
* folders and no page among them is not one an agent should greet as new, and
|
|
79
|
+
* `start_session` must stay cheap whatever the tree looks like.
|
|
80
|
+
*/
|
|
81
|
+
export const FOLDER_BUDGET = 200;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Which of some pages (paths below the knowledge folder) the caller may
|
|
85
|
+
* read — `canReadBatch` for them, with the folder's prefix put on and taken
|
|
86
|
+
* off again.
|
|
87
|
+
*/
|
|
88
|
+
export type MayRead = (relPaths: string[]) => Promise<ReadonlyMap<string, boolean>>;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Whether the knowledge folder `knowledgeDir` of the checkout at `repoRoot`
|
|
92
|
+
* holds nothing but the starter guide: no file in it or below it other than
|
|
93
|
+
* that guide at its top, folder placeholders, dot-files and access rules. A
|
|
94
|
+
* folder that is not there answers false: that is a knowledge base in a shape
|
|
95
|
+
* the template never made, and greeting it as new would be a guess.
|
|
96
|
+
*
|
|
97
|
+
* `starterPages` are a starter pack's pages (path below the folder → the text
|
|
98
|
+
* the pack wrote): one still holding exactly that text is not a page either.
|
|
99
|
+
*
|
|
100
|
+
* THE knowledge-base walk does the walking ({@link ITreeWalker.walkKb}, see
|
|
101
|
+
* `shared/fs.contract.ts`), the way the explorer's does: dot-entries never
|
|
102
|
+
* shown, a link or a socket never entered nor counted as a file, and
|
|
103
|
+
* `.bevelignore` honoured — the repository root's rules and every folder's on
|
|
104
|
+
* the way down — so what the explorer hides this check does not see either,
|
|
105
|
+
* and a knowledge folder those rules hide is not greeted. A folder that
|
|
106
|
+
* cannot be listed is the answer: not new, since it cannot say.
|
|
107
|
+
*
|
|
108
|
+
* With `mayRead`, the answer is the CALLER'S: a file they may not read is not
|
|
109
|
+
* theirs to know of, so it does not make the folder old for them. Every
|
|
110
|
+
* folder is entered, as the agent's own listing shows every folder and
|
|
111
|
+
* filters its files — a deeper rule may open a page below a folder its
|
|
112
|
+
* parent's rules close — and a folder's pages are judged in chunks of
|
|
113
|
+
* {@link JUDGE_CHUNK}; the first the caller may read ends the walk: not new.
|
|
114
|
+
* Without `mayRead` the first page ends it. Either way {@link FOLDER_BUDGET}
|
|
115
|
+
* folders end it too: that many and no page is not a new knowledge base.
|
|
116
|
+
*/
|
|
117
|
+
export async function knowledgeFolderIsNew(
|
|
118
|
+
disk: ITreeWalker,
|
|
119
|
+
repoRoot: string,
|
|
120
|
+
knowledgeDir: string,
|
|
121
|
+
starterPages?: ReadonlyMap<string, string>,
|
|
122
|
+
mayRead?: MayRead,
|
|
123
|
+
): Promise<boolean> {
|
|
124
|
+
let sawRoot = false;
|
|
125
|
+
let established = false;
|
|
126
|
+
let listed = 0;
|
|
127
|
+
/** Whether any of `pages` (paths below the knowledge folder) is one the caller may read. */
|
|
128
|
+
const anyReadable = async (pages: string[]): Promise<boolean> => {
|
|
129
|
+
const verdicts = await mayRead!(pages);
|
|
130
|
+
return pages.some((p) => verdicts.get(p) === true);
|
|
131
|
+
};
|
|
132
|
+
try {
|
|
133
|
+
// The rules in force above the knowledge folder — the repository root's
|
|
134
|
+
// file — which a walk starting below it would not see on its own.
|
|
135
|
+
const above = await BevelIgnoreStack.empty().extendedWith(repoRoot);
|
|
136
|
+
const dir = join(repoRoot, knowledgeDir);
|
|
137
|
+
if (above.isIgnored(dir, true)) return false;
|
|
138
|
+
await disk.walkKb(
|
|
139
|
+
dir,
|
|
140
|
+
[
|
|
141
|
+
{
|
|
142
|
+
async onDir(rel, entries, folder) {
|
|
143
|
+
if (rel === '') sawRoot = true;
|
|
144
|
+
if (++listed > FOLDER_BUDGET) {
|
|
145
|
+
established = true;
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
let pages: string[] = [];
|
|
149
|
+
for (const entry of entries) {
|
|
150
|
+
if (!entry.isFile() || NOT_CONTENT.has(entry.name)) continue;
|
|
151
|
+
if (rel === '' && entry.name === STARTER_GUIDE_FILE) continue;
|
|
152
|
+
const childRel = rel ? `${rel}/${entry.name}` : entry.name;
|
|
153
|
+
if (await isUntouchedStarterPage(join(folder.abs, entry.name), starterPages?.get(childRel))) continue;
|
|
154
|
+
// A page, with nobody to ask: something someone put there.
|
|
155
|
+
if (!mayRead) {
|
|
156
|
+
established = true;
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
pages.push(childRel);
|
|
160
|
+
if (pages.length >= JUDGE_CHUNK) {
|
|
161
|
+
if (await anyReadable(pages)) {
|
|
162
|
+
established = true;
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
pages = [];
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (pages.length > 0 && (await anyReadable(pages))) established = true;
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
{ ignore: above, until: () => established, unreadable: 'throw' },
|
|
173
|
+
);
|
|
174
|
+
} catch {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
return sawRoot && !established;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** How many of a folder's pages are judged in one ask of `mayRead`. */
|
|
181
|
+
const JUDGE_CHUNK = 200;
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Whether the file at `file` still holds exactly `written`, the text a starter
|
|
185
|
+
* pack put there (line endings aside: a CRLF checkout of the same text is the
|
|
186
|
+
* same page). No `written` means no pack wrote it.
|
|
187
|
+
*/
|
|
188
|
+
export async function isUntouchedStarterPage(file: string, written: string | undefined): Promise<boolean> {
|
|
189
|
+
if (written === undefined) return false;
|
|
190
|
+
const norm = (text: string) => text.replace(/\r\n?/g, '\n');
|
|
191
|
+
try {
|
|
192
|
+
return norm(await nodeFs.readFile(file, 'utf8')) === norm(written);
|
|
193
|
+
} catch {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* The note itself, as `start_session` returns it under `firstRun`. Complete on
|
|
200
|
+
* its own, for the agent that reads no further: what the state is, what to
|
|
201
|
+
* offer, and that the person's own request comes first.
|
|
202
|
+
*/
|
|
203
|
+
export function firstRunNote(
|
|
204
|
+
knowledgeFolder: string,
|
|
205
|
+
starter?: Pick<FirstRunStarter, 'name' | 'suggestedPages'>,
|
|
206
|
+
): string {
|
|
207
|
+
if (starter) {
|
|
208
|
+
const pages = starter.suggestedPages.length > 0 ? ` (${starter.suggestedPages.join(', ')})` : '';
|
|
209
|
+
return (
|
|
210
|
+
`This knowledge base is new: \`${knowledgeFolder}/\` holds the starter guide and the ${starter.name} starter pages, which are short placeholders to fill in. ` +
|
|
211
|
+
`Once in this conversation, offer to draft its first pages${pages} from the person's website, repository or a few sentences of theirs, for them to review. ` +
|
|
212
|
+
'If they asked for something else, answer that first and make the offer in one line. ' +
|
|
213
|
+
`The guide's \`${FIRST_RUN_SECTION_ID}\` section says how.`
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
return (
|
|
217
|
+
`This knowledge base is new: \`${knowledgeFolder}/\` has no pages yet, the starter guide aside. ` +
|
|
218
|
+
"Once in this conversation, offer to draft its first pages (what the organisation does, its customers, its products, a glossary, how it works) from the person's website or a few sentences of theirs, for them to review. " +
|
|
219
|
+
'If they asked for something else, answer that first and make the offer in one line. ' +
|
|
220
|
+
`The guide's \`${FIRST_RUN_SECTION_ID}\` section says how.`
|
|
221
|
+
);
|
|
222
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The link half of `move_file`: which pages a move edits, and what it reports.
|
|
3
|
+
*
|
|
4
|
+
* A move breaks two sets of links — those inside the moved files that point
|
|
5
|
+
* elsewhere, and those elsewhere that point at the moved files. The plan reads
|
|
6
|
+
* both through the shared grammar (`md-links` in platform-shared) and answers
|
|
7
|
+
* the edits to make plus the report the agent gets, the same for a dry run and
|
|
8
|
+
* for the move itself.
|
|
9
|
+
*
|
|
10
|
+
* Who sees what:
|
|
11
|
+
* - a file the caller cannot read is never opened; the report then carries
|
|
12
|
+
* one sentence saying such files may still point at the old path, with no
|
|
13
|
+
* name and no count;
|
|
14
|
+
* - a file the caller can read but may not change (a protected branch's
|
|
15
|
+
* write rules, or the write hook's refusal) is named with its links, and
|
|
16
|
+
* left;
|
|
17
|
+
* - a file the read hook refuses counts as one the caller cannot read: not
|
|
18
|
+
* named, only covered by the same sentence — the hook is asked for every
|
|
19
|
+
* file the answer would name, before it is named, and for no other;
|
|
20
|
+
* - an HTML page is named with its links, and left — and so is a markdown
|
|
21
|
+
* page for the raw HTML it carries (`<a href>`, `<img src>`), which the
|
|
22
|
+
* grammar reads but never rewrites.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import {
|
|
26
|
+
htmlLinksAffectedByMove,
|
|
27
|
+
rewriteMdLinks,
|
|
28
|
+
scanMarkdownHtmlLinks,
|
|
29
|
+
type MdLinkEdit,
|
|
30
|
+
} from '@bevel-software/platform-shared';
|
|
31
|
+
|
|
32
|
+
/** More edited files than this and the move is refused. */
|
|
33
|
+
export const MOVE_LINK_EDIT_CAP = 200;
|
|
34
|
+
/** How many edits the report lists. */
|
|
35
|
+
export const MOVE_LINK_REPORT_EDITS = 100;
|
|
36
|
+
|
|
37
|
+
/** The sentence about files the caller cannot read. */
|
|
38
|
+
export const UNSEARCHED_SENTENCE =
|
|
39
|
+
'Links in files you cannot read were not searched, and may still point at the old path.';
|
|
40
|
+
|
|
41
|
+
/** A folder segment whose files are records of the past, never searched or rewritten. */
|
|
42
|
+
const UNSEARCHED_SEGMENTS = new Set(['transcripts', 'probes']);
|
|
43
|
+
|
|
44
|
+
export interface MoveLinksReport {
|
|
45
|
+
filesEdited: number;
|
|
46
|
+
linksRewritten: number;
|
|
47
|
+
/** The first {@link MOVE_LINK_REPORT_EDITS} edits; `path` is where the file sits after the move. */
|
|
48
|
+
edits: { path: string; from: string; to: string }[];
|
|
49
|
+
notRewritten: { path: string; reason: string; links: string[] }[];
|
|
50
|
+
unsearched?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface PlannedEdit {
|
|
54
|
+
/** Where the file sits after the move. */
|
|
55
|
+
path: string;
|
|
56
|
+
/** Where it sits now. */
|
|
57
|
+
lockAt: string;
|
|
58
|
+
/** The bytes the edit was computed from — compared again under the lock. */
|
|
59
|
+
original: string;
|
|
60
|
+
content: string;
|
|
61
|
+
links: MdLinkEdit[];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface MoveLinksPlan {
|
|
65
|
+
edits: PlannedEdit[];
|
|
66
|
+
report: MoveLinksReport;
|
|
67
|
+
/** Set when the move would edit more than the cap; the move is refused with it. */
|
|
68
|
+
overCap?: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface MoveLinksInput {
|
|
72
|
+
src: string;
|
|
73
|
+
dest: string;
|
|
74
|
+
branch: string;
|
|
75
|
+
kbDirName: string;
|
|
76
|
+
/** Every file on the branch inside the clone folder, workspace-relative. */
|
|
77
|
+
allFiles: string[];
|
|
78
|
+
/** Read verdicts for workspace-relative paths. */
|
|
79
|
+
canRead: (paths: string[]) => Promise<Map<string, boolean>>;
|
|
80
|
+
/** The paths among these the caller may not change (protected-branch rules). */
|
|
81
|
+
writeBlocked: (paths: string[]) => Promise<string[]>;
|
|
82
|
+
readText: (path: string) => Promise<string>;
|
|
83
|
+
/**
|
|
84
|
+
* The deployment's read hook on one page, at its current path: true when
|
|
85
|
+
* it refuses. Asked for every page the answer would NAME — edited, or left
|
|
86
|
+
* with its links listed — and for no page merely searched: naming is the
|
|
87
|
+
* disclosure. A refused page is one the caller cannot read, covered by the
|
|
88
|
+
* one sentence and never named.
|
|
89
|
+
*/
|
|
90
|
+
readRefused: (path: string) => Promise<boolean>;
|
|
91
|
+
/**
|
|
92
|
+
* The deployment's write hook on a page the move would edit, at its
|
|
93
|
+
* post-move path: why it may not be, or null. Asked for edited pages only.
|
|
94
|
+
*/
|
|
95
|
+
writeRefusal: (lockAt: string, path: string) => Promise<string | null>;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Whether `path` lies in a `transcripts/` or `probes/` folder. */
|
|
99
|
+
export function isUnsearchedRecord(path: string): boolean {
|
|
100
|
+
return path.split('/').slice(0, -1).some((s) => UNSEARCHED_SEGMENTS.has(s));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const isMarkdown = (p: string) => /\.md$/i.test(p);
|
|
104
|
+
const isHtml = (p: string) => /\.html?$/i.test(p);
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The search's cheap first cut: a file linking at `name` names it — raw, or
|
|
108
|
+
* spelled with markdown escapes or percent-encoding, which are undone here
|
|
109
|
+
* the way the resolver undoes them (any case, any subset of characters).
|
|
110
|
+
*/
|
|
111
|
+
function mayMention(text: string, name: string): boolean {
|
|
112
|
+
if (text.includes(name)) return true;
|
|
113
|
+
const unescaped = text.replace(/\\([!-/:-@[-`{-~])/g, '$1');
|
|
114
|
+
const decoded = unescaped.replace(/(?:%[0-9a-f]{2})+/gi, (run) => {
|
|
115
|
+
try {
|
|
116
|
+
return decodeURIComponent(run);
|
|
117
|
+
} catch {
|
|
118
|
+
return run;
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
return decoded.includes(name);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export async function planMoveLinks(input: MoveLinksInput): Promise<MoveLinksPlan> {
|
|
125
|
+
const { src, dest, branch, kbDirName } = input;
|
|
126
|
+
const mapPath = (p: string): string | null =>
|
|
127
|
+
p === src ? dest : p.startsWith(`${src}/`) ? dest + p.slice(src.length) : null;
|
|
128
|
+
const srcName = src.slice(src.lastIndexOf('/') + 1);
|
|
129
|
+
|
|
130
|
+
const candidates = input.allFiles.filter((p) => (isMarkdown(p) || isHtml(p)) && !isUnsearchedRecord(p)).sort();
|
|
131
|
+
const readable = await input.canRead(candidates);
|
|
132
|
+
let unsearched = candidates.some((p) => readable.get(p) !== true);
|
|
133
|
+
|
|
134
|
+
const edits: PlannedEdit[] = [];
|
|
135
|
+
/** Pages the answer would name for raw HTML, held back until the read hook allows the page. */
|
|
136
|
+
const htmlReports: { lockAt: string; entry: MoveLinksReport['notRewritten'][number] }[] = [];
|
|
137
|
+
for (const oldPath of candidates) {
|
|
138
|
+
if (readable.get(oldPath) !== true) continue;
|
|
139
|
+
const newPath = mapPath(oldPath) ?? oldPath;
|
|
140
|
+
// The sidebar moves and deletes without this tool's locks, so a page can
|
|
141
|
+
// go between the listing and this read. A page gone has no links to fix;
|
|
142
|
+
// one that cannot be opened is one more the search did not cover. Gone is
|
|
143
|
+
// the disk's own definition of absence: ENOENT, and ENOTDIR for a page
|
|
144
|
+
// whose folder became a file in the meantime.
|
|
145
|
+
let text: string;
|
|
146
|
+
try {
|
|
147
|
+
text = await input.readText(oldPath);
|
|
148
|
+
} catch (err) {
|
|
149
|
+
const code = (err as NodeJS.ErrnoException | null)?.code;
|
|
150
|
+
if (code !== 'ENOENT' && code !== 'ENOTDIR') unsearched = true;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
// A file that stays put is only touched by a link that names the moved
|
|
154
|
+
// path, so a page that does not mention it is skipped unparsed.
|
|
155
|
+
if (newPath === oldPath && !mayMention(text, srcName)) continue;
|
|
156
|
+
const opts = { oldPath, newPath, mapPath, kbDirName, branch };
|
|
157
|
+
if (isHtml(oldPath)) {
|
|
158
|
+
const links = htmlLinksAffectedByMove(text, opts);
|
|
159
|
+
if (links.length > 0) htmlReports.push({ lockAt: oldPath, entry: { path: newPath, reason: 'html page', links } });
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
// Raw HTML inside a markdown page (`<img src>`, `<a href>`) renders like
|
|
163
|
+
// a link but is not one the grammar rewrites: named with those links and
|
|
164
|
+
// left, as an HTML page is, so a stale target is never silent. Only a
|
|
165
|
+
// live tag: one inside code or behind a `\<` escape is an example.
|
|
166
|
+
const html = htmlLinksAffectedByMove(text, opts, scanMarkdownHtmlLinks(text));
|
|
167
|
+
if (html.length > 0) {
|
|
168
|
+
htmlReports.push({ lockAt: oldPath, entry: { path: newPath, reason: 'html in markdown', links: html } });
|
|
169
|
+
}
|
|
170
|
+
const rewritten = rewriteMdLinks(text, opts);
|
|
171
|
+
if (rewritten.edits.length === 0) continue;
|
|
172
|
+
edits.push({ path: newPath, lockAt: oldPath, original: text, content: rewritten.text, links: rewritten.edits });
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ONE gate before anything is named: the read hook, asked once per page the
|
|
176
|
+
// answer would name, whether as an edit or as a page left with its links
|
|
177
|
+
// listed. A page it refuses is one the caller cannot read — covered by the
|
|
178
|
+
// one sentence, named nowhere, its links quoted nowhere. A page merely
|
|
179
|
+
// searched is never asked about: nothing of it reaches the answer.
|
|
180
|
+
const readAllowed = new Map<string, boolean>();
|
|
181
|
+
const mayName = async (lockAt: string): Promise<boolean> => {
|
|
182
|
+
let allowed = readAllowed.get(lockAt);
|
|
183
|
+
if (allowed === undefined) {
|
|
184
|
+
allowed = !(await input.readRefused(lockAt));
|
|
185
|
+
readAllowed.set(lockAt, allowed);
|
|
186
|
+
if (!allowed) unsearched = true;
|
|
187
|
+
}
|
|
188
|
+
return allowed;
|
|
189
|
+
};
|
|
190
|
+
const notRewritten: MoveLinksReport['notRewritten'] = [];
|
|
191
|
+
for (const r of htmlReports) if (await mayName(r.lockAt)) notRewritten.push(r.entry);
|
|
192
|
+
const named: PlannedEdit[] = [];
|
|
193
|
+
for (const e of edits) if (await mayName(e.lockAt)) named.push(e);
|
|
194
|
+
|
|
195
|
+
// Only a file that stays put can be one the caller may not change: every
|
|
196
|
+
// moved file was already judged writable at both ends by the move itself.
|
|
197
|
+
const blocked = new Set(await input.writeBlocked(named.filter((e) => e.path === e.lockAt).map((e) => e.path)));
|
|
198
|
+
const kept = named.filter((e) => {
|
|
199
|
+
if (!blocked.has(e.path)) return true;
|
|
200
|
+
notRewritten.push({ path: e.path, reason: 'no write access', links: e.links.map((l) => l.from) });
|
|
201
|
+
return false;
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
if (kept.length > MOVE_LINK_EDIT_CAP) {
|
|
205
|
+
return {
|
|
206
|
+
edits: [],
|
|
207
|
+
report: reportOf([], notRewritten, unsearched),
|
|
208
|
+
overCap:
|
|
209
|
+
`This move would edit links in ${kept.length} files, more than the ${MOVE_LINK_EDIT_CAP} one move may edit, so nothing was moved. ` +
|
|
210
|
+
'Move fewer files at a time (a subfolder, then the rest), or pass `rewriteLinks: false` to move without rewriting links.',
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// The write hook hears about edited files only.
|
|
215
|
+
const allowed: PlannedEdit[] = [];
|
|
216
|
+
for (const e of kept) {
|
|
217
|
+
const refusal = await input.writeRefusal(e.lockAt, e.path);
|
|
218
|
+
if (refusal === null) allowed.push(e);
|
|
219
|
+
else notRewritten.push({ path: e.path, reason: refusal, links: e.links.map((l) => l.from) });
|
|
220
|
+
}
|
|
221
|
+
return { edits: allowed, report: reportOf(allowed, notRewritten, unsearched) };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function reportOf(
|
|
225
|
+
edits: PlannedEdit[],
|
|
226
|
+
notRewritten: MoveLinksReport['notRewritten'],
|
|
227
|
+
unsearched: boolean,
|
|
228
|
+
): MoveLinksReport {
|
|
229
|
+
const flat = edits.flatMap((e) => e.links.map((l) => ({ path: e.path, from: l.from, to: l.to })));
|
|
230
|
+
return {
|
|
231
|
+
filesEdited: edits.length,
|
|
232
|
+
linksRewritten: flat.length,
|
|
233
|
+
edits: flat.slice(0, MOVE_LINK_REPORT_EDITS),
|
|
234
|
+
notRewritten,
|
|
235
|
+
...(unsearched ? { unsearched: UNSEARCHED_SENTENCE } : {}),
|
|
236
|
+
};
|
|
237
|
+
}
|
|
@@ -8,6 +8,7 @@ import { TEMPLATE_SOURCE_FALLBACKS, TemplateSource, renderTemplateText } from '.
|
|
|
8
8
|
import { assertNotGitInternals, hasGitInternalsSegment } from '../../../../shared/git-internals.js';
|
|
9
9
|
import { GitInternalsError } from '../../../../shared/domain-errors.js';
|
|
10
10
|
import { isManagedGuide } from '../../../agent-guide/agent-guide.js';
|
|
11
|
+
import { utf8Text } from '../../../../shared/utf8-text.js';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* The empty-remote seed builder the runner takes as `buildSeedTree`: the full
|
|
@@ -155,7 +156,7 @@ class KbSeedTree {
|
|
|
155
156
|
// Judged the way the copy below judges: a binary under the name is
|
|
156
157
|
// spotted from its first bytes and never read whole.
|
|
157
158
|
const source = await this.templates.pathOf(name);
|
|
158
|
-
const text = (await headHasNul(source)) ? null :
|
|
159
|
+
const text = (await headHasNul(source)) ? null : utf8Text(await fs.readFile(source));
|
|
159
160
|
if (text !== null && isManagedGuide(text)) return;
|
|
160
161
|
}
|
|
161
162
|
await this.copyTemplateFile(relDir ? path.join(relDir, name) : name, dest);
|
|
@@ -179,7 +180,7 @@ class KbSeedTree {
|
|
|
179
180
|
// A binary is spotted from its first bytes (a NUL turns up early in any
|
|
180
181
|
// real one) and streamed across without ever being read whole; only what
|
|
181
182
|
// may be text is read in full, and the full decode is still the judge.
|
|
182
|
-
const text = (await headHasNul(from)) ? null :
|
|
183
|
+
const text = (await headHasNul(from)) ? null : utf8Text(await fs.readFile(from));
|
|
183
184
|
if (text === null) {
|
|
184
185
|
await fs.copyFile(from, to);
|
|
185
186
|
} else {
|
|
@@ -204,16 +205,6 @@ async function headHasNul(file: string): Promise<boolean> {
|
|
|
204
205
|
}
|
|
205
206
|
}
|
|
206
207
|
|
|
207
|
-
/** The bytes as text when they ARE text — strict UTF-8, BOM kept, no NUL — else null. */
|
|
208
|
-
function asText(bytes: Buffer): string | null {
|
|
209
|
-
if (bytes.includes(0)) return null;
|
|
210
|
-
try {
|
|
211
|
-
return new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
212
|
-
} catch {
|
|
213
|
-
return null;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
208
|
/**
|
|
218
209
|
* Whether `rel` under `templateRoot` resolves inside a git folder — the
|
|
219
210
|
* template entry is a link, and following it would read the repository's own
|
|
@@ -13,9 +13,13 @@ import {
|
|
|
13
13
|
canonicalRelativePath,
|
|
14
14
|
folderPlaceholderPath,
|
|
15
15
|
isPlatformFile,
|
|
16
|
+
isPlatformFolder,
|
|
16
17
|
isPlatformRestoreShape,
|
|
18
|
+
isRepositoryOwnFile,
|
|
19
|
+
repositoryOwnFileDeleteRefusal,
|
|
17
20
|
platformFileCreationRefusal,
|
|
18
21
|
platformFileRefusal,
|
|
22
|
+
platformFolderRefusal,
|
|
19
23
|
reservedRootDirNames,
|
|
20
24
|
} from '@bevel-software/platform-shared';
|
|
21
25
|
import { FolderTooLargeError, type ReadTreeFilter } from './workspace.service.js';
|
|
@@ -39,6 +43,7 @@ import { removeEmptyDirs } from './empty-dirs.js';
|
|
|
39
43
|
import '../auth/auth.middleware.js'; // Express Request augmentation
|
|
40
44
|
import type { SkillSaveCheck } from './workspace.tools.js';
|
|
41
45
|
import { MAX_UPLOAD_BYTES } from './upload-limits.js';
|
|
46
|
+
import { OCTET_STREAM, rawExtensionOf, rawMimeFor } from './file-readers/raw-mime.js';
|
|
42
47
|
|
|
43
48
|
/**
|
|
44
49
|
* One file identity from one request field, or `null` when the caller sent
|
|
@@ -571,8 +576,11 @@ export function createWorkspaceRoutes(
|
|
|
571
576
|
res: express.Response,
|
|
572
577
|
workspaceId: string,
|
|
573
578
|
relativePath: string,
|
|
579
|
+
// A caller that has already resolved the user hands it over, so one
|
|
580
|
+
// request is not looked up twice.
|
|
581
|
+
resolvedUser?: { email: string },
|
|
574
582
|
): Promise<boolean> {
|
|
575
|
-
const user = await requireUser(req, res);
|
|
583
|
+
const user = resolvedUser ?? (await requireUser(req, res));
|
|
576
584
|
if (!user) return false;
|
|
577
585
|
let allowed: boolean;
|
|
578
586
|
try {
|
|
@@ -852,27 +860,15 @@ export function createWorkspaceRoutes(
|
|
|
852
860
|
// its own weak ETag and answers a matching If-None-Match with a 304.
|
|
853
861
|
res.setHeader('ETag', `"${at.blobId}"`);
|
|
854
862
|
}
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
'.jpeg': 'image/jpeg',
|
|
860
|
-
'.gif': 'image/gif',
|
|
861
|
-
'.webp': 'image/webp',
|
|
862
|
-
'.svg': 'image/svg+xml',
|
|
863
|
-
'.bmp': 'image/bmp',
|
|
864
|
-
'.ico': 'image/x-icon',
|
|
865
|
-
'.pdf': 'application/pdf',
|
|
866
|
-
'.docx':
|
|
867
|
-
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
868
|
-
'.xlsx':
|
|
869
|
-
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
870
|
-
};
|
|
863
|
+
// The type from the extension, the one table the embed's raw route
|
|
864
|
+
// serves from as well (`file-readers/raw-mime.ts`).
|
|
865
|
+
const ext = rawExtensionOf(filePath);
|
|
866
|
+
const inlineMime = rawMimeFor(filePath);
|
|
871
867
|
// SVG is active web content (it can carry <script>), and it is active in
|
|
872
868
|
// BOTH directions: a saved-to-disk SVG re-opened later runs its scripts
|
|
873
869
|
// under the file:// origin, so a download is forced to octet-stream.
|
|
874
|
-
const downloadMime = ext === '.svg' ?
|
|
875
|
-
res.setHeader('Content-Type', wantsDownload ? downloadMime :
|
|
870
|
+
const downloadMime = ext === '.svg' ? OCTET_STREAM : inlineMime;
|
|
871
|
+
res.setHeader('Content-Type', wantsDownload ? downloadMime : inlineMime);
|
|
876
872
|
// Block MIME-sniffing so a misdeclared file can't be promoted to
|
|
877
873
|
// active content by the browser.
|
|
878
874
|
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
@@ -978,9 +974,33 @@ export function createWorkspaceRoutes(
|
|
|
978
974
|
res.status(400).json({ error: 'download=1 is required for folder zip downloads' });
|
|
979
975
|
return;
|
|
980
976
|
}
|
|
981
|
-
|
|
977
|
+
const user = await requireUser(req, res);
|
|
978
|
+
if (!user) return;
|
|
979
|
+
if (!(await requireDownloadPermission(req, res, id, folderPath, user))) return;
|
|
980
|
+
// `download` on the folder lets the caller ASK for the zip. What goes in
|
|
981
|
+
// it is judged file by file, through the two gates the per-file route
|
|
982
|
+
// runs in the same order: a file the caller may not read is left out, and
|
|
983
|
+
// so is one they may read but not download. Nothing in the answer names a
|
|
984
|
+
// left-out file; the count below covers only files the caller could
|
|
985
|
+
// already see in the tree, so it discloses nothing the tree does not.
|
|
986
|
+
let withheld = 0;
|
|
987
|
+
const include = async (paths: string[]): Promise<ReadonlySet<string>> => {
|
|
988
|
+
const readable = await accessControl.canReadBatch(id, user.email, paths);
|
|
989
|
+
const visible = paths.filter((p) => readable.get(p) === true);
|
|
990
|
+
const downloadable = await accessControl.canDownloadBatch(id, user.email, visible);
|
|
991
|
+
const kept = new Set(visible.filter((p) => downloadable.get(p) === true));
|
|
992
|
+
withheld = visible.length - kept.size;
|
|
993
|
+
return kept;
|
|
994
|
+
};
|
|
982
995
|
try {
|
|
983
|
-
const buffer = await workspaceService.createFolderZip(id, folderPath);
|
|
996
|
+
const buffer = await workspaceService.createFolderZip(id, folderPath, include);
|
|
997
|
+
res.setHeader('X-Withheld-Files', String(withheld));
|
|
998
|
+
// One caller's archive: which files it holds, and the count beside it,
|
|
999
|
+
// are that caller's verdicts. `private` keeps a shared cache from
|
|
1000
|
+
// handing it to the next caller; `no-store` because, unlike the single
|
|
1001
|
+
// file, nothing here can be revalidated (no ETag), so a copy is never
|
|
1002
|
+
// worth keeping.
|
|
1003
|
+
res.setHeader('Cache-Control', 'private, no-store');
|
|
984
1004
|
// `|| 'folder'` covers the edge case where `folderPath` itself was
|
|
985
1005
|
// a single bare slash (`/`) that survived the trim — the service
|
|
986
1006
|
// would still reject it as path traversal, but the basename
|
|
@@ -999,7 +1019,11 @@ export function createWorkspaceRoutes(
|
|
|
999
1019
|
res.status(413).json({ error: error.message });
|
|
1000
1020
|
return;
|
|
1001
1021
|
}
|
|
1002
|
-
|
|
1022
|
+
// A traversal refusal (`PathTraversalError`), or the access tree failing
|
|
1023
|
+
// to load while the entries were judged (`AccessConfigError`): both are
|
|
1024
|
+
// domain errors carrying their own status and payload, the same ones
|
|
1025
|
+
// the single-file gate answers with.
|
|
1026
|
+
if (error instanceof WorkflowDomainError) {
|
|
1003
1027
|
sendError(res, error);
|
|
1004
1028
|
return;
|
|
1005
1029
|
}
|
|
@@ -1098,6 +1122,16 @@ export function createWorkspaceRoutes(
|
|
|
1098
1122
|
// Not on disk — let workspaceService.deleteFile return its own 404.
|
|
1099
1123
|
}
|
|
1100
1124
|
if (stat?.isDirectory()) {
|
|
1125
|
+
// A platform folder — the repository root, whose sweep would take the
|
|
1126
|
+
// root's `access.md` and `roles.yaml` with it, or one of the reserved
|
|
1127
|
+
// top-level folders that hold a whole section of the knowledge base —
|
|
1128
|
+
// is refused here as `delete_folder` refuses it, by the same rule.
|
|
1129
|
+
const trimmedFolder = filePath.replace(/\/+$/, '');
|
|
1130
|
+
const folderRel = trimmedFolder === kbDirName ? '' : toKbRelative(trimmedFolder, kbDirName);
|
|
1131
|
+
if (folderRel !== null && isPlatformFolder(folderRel, kb.layout)) {
|
|
1132
|
+
res.status(409).json({ error: platformFolderRefusal(folderRel) });
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1101
1135
|
const branch = branchForWorkspaceId(id);
|
|
1102
1136
|
// In the folder's turn: keeping a folder under this one (a file delete
|
|
1103
1137
|
// racing this one) waits until the sweep is done, and then finds the
|
|
@@ -1166,6 +1200,14 @@ export function createWorkspaceRoutes(
|
|
|
1166
1200
|
res.json({ status: 'deleted', count: filesInDir.length });
|
|
1167
1201
|
return;
|
|
1168
1202
|
}
|
|
1203
|
+
// The root's `access.md` and `roles.yaml` govern the whole repository:
|
|
1204
|
+
// nobody deletes them, here or through the agent tools. A nested
|
|
1205
|
+
// `access.md` is deleted like any file its caller may write.
|
|
1206
|
+
const rel = toKbRelative(filePath, kbDirName);
|
|
1207
|
+
if (rel !== null && isRepositoryOwnFile(rel, kb.layout)) {
|
|
1208
|
+
res.status(409).json({ error: repositoryOwnFileDeleteRefusal(rel) });
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1169
1211
|
await withLock(id, user, filePath, () => workspaceService.deleteFile(id, filePath));
|
|
1170
1212
|
// Deleting content is not deleting structure: an emptied folder stays.
|
|
1171
1213
|
await keepFolderOf(id, user, filePath);
|