@bevel-software/platform-core-backend 0.15.1 → 0.20.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/THIRD-PARTY-NOTICES.md +4 -4
- package/dist/core/catalog-cache-invalidation.d.ts +16 -0
- package/dist/core/catalog-cache-invalidation.d.ts.map +1 -1
- package/dist/core/catalog-cache-invalidation.js +6 -1
- package/dist/core/catalog-cache-invalidation.js.map +1 -1
- package/dist/core/catalog-revision.d.ts +39 -0
- package/dist/core/catalog-revision.d.ts.map +1 -0
- package/dist/core/catalog-revision.js +94 -0
- package/dist/core/catalog-revision.js.map +1 -0
- package/dist/core/create-core-server.d.ts +7 -0
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +221 -75
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +52 -2
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +222 -87
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/core/lifecycle.d.ts +138 -0
- package/dist/core/lifecycle.d.ts.map +1 -0
- package/dist/core/lifecycle.js +218 -0
- package/dist/core/lifecycle.js.map +1 -0
- package/dist/core/public-config.d.ts +82 -0
- package/dist/core/public-config.d.ts.map +1 -0
- package/dist/core/public-config.js +78 -0
- package/dist/core/public-config.js.map +1 -0
- package/dist/core/readiness.d.ts +86 -0
- package/dist/core/readiness.d.ts.map +1 -0
- package/dist/core/readiness.js +56 -0
- package/dist/core/readiness.js.map +1 -0
- package/dist/core-config.d.ts +30 -0
- package/dist/core-config.d.ts.map +1 -1
- package/dist/core-config.js +64 -2
- package/dist/core-config.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +215 -11
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +57 -11
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +616 -155
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/access-mutation.service.d.ts +71 -5
- package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
- package/dist/modules/access/access-mutation.service.js +161 -13
- package/dist/modules/access/access-mutation.service.js.map +1 -1
- package/dist/modules/access/access-view.d.ts +147 -0
- package/dist/modules/access/access-view.d.ts.map +1 -0
- package/dist/modules/access/access-view.js +211 -0
- package/dist/modules/access/access-view.js.map +1 -0
- package/dist/modules/access/access.routes.d.ts.map +1 -1
- package/dist/modules/access/access.routes.js +255 -79
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/access/admin-locked-commit.d.ts +2 -3
- package/dist/modules/access/admin-locked-commit.d.ts.map +1 -1
- package/dist/modules/access/admin-locked-commit.js +9 -8
- package/dist/modules/access/admin-locked-commit.js.map +1 -1
- package/dist/modules/access/admin-route-helpers.d.ts.map +1 -1
- package/dist/modules/access/admin-route-helpers.js +4 -4
- package/dist/modules/access/admin-route-helpers.js.map +1 -1
- package/dist/modules/access/change-read-gate.d.ts +53 -0
- package/dist/modules/access/change-read-gate.d.ts.map +1 -0
- package/dist/modules/access/change-read-gate.js +134 -0
- package/dist/modules/access/change-read-gate.js.map +1 -0
- package/dist/modules/access/creator-access.d.ts +33 -39
- package/dist/modules/access/creator-access.d.ts.map +1 -1
- package/dist/modules/access/creator-access.js +92 -119
- package/dist/modules/access/creator-access.js.map +1 -1
- package/dist/modules/access/directory-sync-bot.d.ts.map +1 -1
- package/dist/modules/access/directory-sync-bot.js +3 -1
- package/dist/modules/access/directory-sync-bot.js.map +1 -1
- package/dist/modules/access/groups-admin.service.d.ts +3 -3
- package/dist/modules/access/groups-admin.service.js +2 -2
- package/dist/modules/access/reference-scan.d.ts +1 -1
- package/dist/modules/access/reference-scan.d.ts.map +1 -1
- package/dist/modules/access/reference-scan.js +5 -4
- package/dist/modules/access/reference-scan.js.map +1 -1
- package/dist/modules/access/roles-admin.service.d.ts +72 -13
- package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
- package/dist/modules/access/roles-admin.service.js +71 -13
- package/dist/modules/access/roles-admin.service.js.map +1 -1
- package/dist/modules/access/synced-groups-committer.d.ts.map +1 -1
- package/dist/modules/access/synced-groups-committer.js +8 -6
- package/dist/modules/access/synced-groups-committer.js.map +1 -1
- package/dist/modules/access/synced-groups-writer.d.ts.map +1 -1
- package/dist/modules/access/synced-groups-writer.js +3 -8
- package/dist/modules/access/synced-groups-writer.js.map +1 -1
- package/dist/modules/access/user-access-removal.service.d.ts +209 -0
- package/dist/modules/access/user-access-removal.service.d.ts.map +1 -0
- package/dist/modules/access/user-access-removal.service.js +662 -0
- package/dist/modules/access/user-access-removal.service.js.map +1 -0
- package/dist/modules/access-model/access-errors.d.ts +32 -1
- package/dist/modules/access-model/access-errors.d.ts.map +1 -1
- package/dist/modules/access-model/access-errors.js +38 -11
- package/dist/modules/access-model/access-errors.js.map +1 -1
- package/dist/modules/access-model/access-grammar.d.ts +49 -24
- package/dist/modules/access-model/access-grammar.d.ts.map +1 -1
- package/dist/modules/access-model/access-grammar.js +68 -48
- package/dist/modules/access-model/access-grammar.js.map +1 -1
- package/dist/modules/access-model/access-splice.d.ts.map +1 -1
- package/dist/modules/access-model/access-splice.js +18 -18
- package/dist/modules/access-model/access-splice.js.map +1 -1
- package/dist/modules/access-model/access-template.d.ts +22 -0
- package/dist/modules/access-model/access-template.d.ts.map +1 -0
- package/dist/modules/access-model/access-template.js +42 -0
- package/dist/modules/access-model/access-template.js.map +1 -0
- package/dist/modules/access-model/change-gate.d.ts +65 -0
- package/dist/modules/access-model/change-gate.d.ts.map +1 -0
- package/dist/modules/access-model/change-gate.js +25 -0
- package/dist/modules/access-model/change-gate.js.map +1 -0
- package/dist/modules/access-model/creator.d.ts +13 -16
- package/dist/modules/access-model/creator.d.ts.map +1 -1
- package/dist/modules/access-model/creator.js.map +1 -1
- package/dist/modules/access-model/frontmatter-lines.d.ts +27 -0
- package/dist/modules/access-model/frontmatter-lines.d.ts.map +1 -0
- package/dist/modules/access-model/frontmatter-lines.js +74 -0
- package/dist/modules/access-model/frontmatter-lines.js.map +1 -0
- package/dist/modules/access-model/plugin-principals.d.ts +5 -3
- package/dist/modules/access-model/plugin-principals.d.ts.map +1 -1
- package/dist/modules/access-model/plugin-principals.js +11 -17
- package/dist/modules/access-model/plugin-principals.js.map +1 -1
- package/dist/modules/access-model/roles-yaml-guard.d.ts +73 -2
- package/dist/modules/access-model/roles-yaml-guard.d.ts.map +1 -1
- package/dist/modules/access-model/roles-yaml-guard.js +153 -3
- package/dist/modules/access-model/roles-yaml-guard.js.map +1 -1
- package/dist/modules/admin/admin-access.service.d.ts.map +1 -1
- package/dist/modules/admin/admin-access.service.js +5 -2
- package/dist/modules/admin/admin-access.service.js.map +1 -1
- package/dist/modules/agent-instructions/agent-instructions.routes.d.ts +21 -0
- package/dist/modules/agent-instructions/agent-instructions.routes.d.ts.map +1 -0
- package/dist/modules/agent-instructions/agent-instructions.routes.js +38 -0
- package/dist/modules/agent-instructions/agent-instructions.routes.js.map +1 -0
- package/dist/modules/agent-instructions/compose.d.ts +71 -0
- package/dist/modules/agent-instructions/compose.d.ts.map +1 -0
- package/dist/modules/agent-instructions/compose.js +234 -0
- package/dist/modules/agent-instructions/compose.js.map +1 -0
- package/dist/modules/agent-instructions/index.d.ts +4 -0
- package/dist/modules/agent-instructions/index.d.ts.map +1 -0
- package/dist/modules/agent-instructions/index.js +4 -0
- package/dist/modules/agent-instructions/index.js.map +1 -0
- package/dist/modules/agent-instructions/read-preamble.d.ts +37 -0
- package/dist/modules/agent-instructions/read-preamble.d.ts.map +1 -0
- package/dist/modules/agent-instructions/read-preamble.js +81 -0
- package/dist/modules/agent-instructions/read-preamble.js.map +1 -0
- package/dist/modules/audit/agent-audit.service.d.ts +54 -0
- package/dist/modules/audit/agent-audit.service.d.ts.map +1 -0
- package/dist/modules/audit/agent-audit.service.js +290 -0
- package/dist/modules/audit/agent-audit.service.js.map +1 -0
- package/dist/modules/audit/agent-rest-audit.middleware.d.ts +40 -0
- package/dist/modules/audit/agent-rest-audit.middleware.d.ts.map +1 -0
- package/dist/modules/audit/agent-rest-audit.middleware.js +75 -0
- package/dist/modules/audit/agent-rest-audit.middleware.js.map +1 -0
- package/dist/modules/audit/audit.contract.d.ts +145 -0
- package/dist/modules/audit/audit.contract.d.ts.map +1 -0
- package/dist/modules/audit/audit.contract.js +35 -0
- package/dist/modules/audit/audit.contract.js.map +1 -0
- package/dist/modules/audit/audit.routes.d.ts +21 -0
- package/dist/modules/audit/audit.routes.d.ts.map +1 -0
- package/dist/modules/audit/audit.routes.js +157 -0
- package/dist/modules/audit/audit.routes.js.map +1 -0
- package/dist/modules/audit/event-classifier.d.ts +52 -0
- package/dist/modules/audit/event-classifier.d.ts.map +1 -0
- package/dist/modules/audit/event-classifier.js +123 -0
- package/dist/modules/audit/event-classifier.js.map +1 -0
- package/dist/modules/audit/index.d.ts +7 -0
- package/dist/modules/audit/index.d.ts.map +1 -0
- package/dist/modules/audit/index.js +7 -0
- package/dist/modules/audit/index.js.map +1 -0
- package/dist/modules/audit/request-audit.d.ts +57 -0
- package/dist/modules/audit/request-audit.d.ts.map +1 -0
- package/dist/modules/audit/request-audit.js +117 -0
- package/dist/modules/audit/request-audit.js.map +1 -0
- package/dist/modules/auth/account-erasure.service.d.ts +30 -4
- package/dist/modules/auth/account-erasure.service.d.ts.map +1 -1
- package/dist/modules/auth/account-erasure.service.js +96 -10
- package/dist/modules/auth/account-erasure.service.js.map +1 -1
- package/dist/modules/auth/account.routes.d.ts +6 -3
- package/dist/modules/auth/account.routes.d.ts.map +1 -1
- package/dist/modules/auth/account.routes.js +109 -10
- package/dist/modules/auth/account.routes.js.map +1 -1
- package/dist/modules/auth/auth.routes.d.ts +10 -3
- package/dist/modules/auth/auth.routes.d.ts.map +1 -1
- package/dist/modules/auth/auth.routes.js +9 -4
- package/dist/modules/auth/auth.routes.js.map +1 -1
- package/dist/modules/auth/auth.service.d.ts +89 -5
- package/dist/modules/auth/auth.service.d.ts.map +1 -1
- package/dist/modules/auth/auth.service.js +160 -25
- package/dist/modules/auth/auth.service.js.map +1 -1
- package/dist/modules/auth/oidc-auth-provider.d.ts +46 -3
- package/dist/modules/auth/oidc-auth-provider.d.ts.map +1 -1
- package/dist/modules/auth/oidc-auth-provider.js +99 -21
- package/dist/modules/auth/oidc-auth-provider.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 +14 -2
- package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
- package/dist/modules/connection-probe/connection-probe.service.d.ts.map +1 -1
- package/dist/modules/connection-probe/connection-probe.service.js +56 -7
- package/dist/modules/connection-probe/connection-probe.service.js.map +1 -1
- package/dist/modules/database/advisory-lock.d.ts +103 -0
- package/dist/modules/database/advisory-lock.d.ts.map +1 -0
- package/dist/modules/database/advisory-lock.js +320 -0
- package/dist/modules/database/advisory-lock.js.map +1 -0
- package/dist/modules/database/connection.d.ts.map +1 -1
- package/dist/modules/database/connection.js +9 -1
- package/dist/modules/database/connection.js.map +1 -1
- package/dist/modules/database/core-schema.d.ts +859 -99
- package/dist/modules/database/core-schema.d.ts.map +1 -1
- package/dist/modules/database/core-schema.js +218 -0
- package/dist/modules/database/core-schema.js.map +1 -1
- package/dist/modules/database/migrate.d.ts.map +1 -1
- package/dist/modules/database/migrate.js +26 -6
- package/dist/modules/database/migrate.js.map +1 -1
- package/dist/modules/declared-variables/declared-variables.routes.d.ts.map +1 -1
- package/dist/modules/declared-variables/declared-variables.routes.js +4 -2
- package/dist/modules/declared-variables/declared-variables.routes.js.map +1 -1
- package/dist/modules/diff/diff.routes.d.ts.map +1 -1
- package/dist/modules/diff/diff.routes.js +2 -4
- package/dist/modules/diff/diff.routes.js.map +1 -1
- package/dist/modules/diff/diff.service.d.ts +4 -1
- package/dist/modules/diff/diff.service.d.ts.map +1 -1
- package/dist/modules/diff/diff.service.js +84 -81
- package/dist/modules/diff/diff.service.js.map +1 -1
- package/dist/modules/{workspace → kb-fs}/bevel-ignore.d.ts +14 -9
- package/dist/modules/kb-fs/bevel-ignore.d.ts.map +1 -0
- package/dist/modules/{workspace → kb-fs}/bevel-ignore.js +24 -14
- package/dist/modules/kb-fs/bevel-ignore.js.map +1 -0
- package/dist/modules/kb-fs/file-change-notifier.d.ts.map +1 -1
- package/dist/modules/kb-fs/file-change-notifier.js +3 -1
- package/dist/modules/kb-fs/file-change-notifier.js.map +1 -1
- package/dist/modules/kb-fs/git-guarded-filesystem.d.ts +45 -0
- package/dist/modules/kb-fs/git-guarded-filesystem.d.ts.map +1 -0
- package/dist/modules/kb-fs/git-guarded-filesystem.js +117 -0
- package/dist/modules/kb-fs/git-guarded-filesystem.js.map +1 -0
- package/dist/modules/kb-fs/locking-filesystem.d.ts +113 -18
- package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.js +353 -76
- package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
- package/dist/modules/kb-fs/node-fs.d.ts +23 -0
- package/dist/modules/kb-fs/node-fs.d.ts.map +1 -0
- package/dist/modules/kb-fs/node-fs.js +158 -0
- package/dist/modules/kb-fs/node-fs.js.map +1 -0
- package/dist/modules/kb-fs/read-only-filesystem.d.ts +10 -9
- package/dist/modules/kb-fs/read-only-filesystem.d.ts.map +1 -1
- package/dist/modules/kb-fs/read-only-filesystem.js +18 -9
- package/dist/modules/kb-fs/read-only-filesystem.js.map +1 -1
- package/dist/modules/kb-fs/repo-path.d.ts +100 -3
- package/dist/modules/kb-fs/repo-path.d.ts.map +1 -1
- package/dist/modules/kb-fs/repo-path.js +195 -3
- package/dist/modules/kb-fs/repo-path.js.map +1 -1
- package/dist/modules/kb-sync/kb-sync.routes.d.ts.map +1 -1
- package/dist/modules/kb-sync/kb-sync.routes.js +4 -2
- package/dist/modules/kb-sync/kb-sync.routes.js.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 +7 -5
- package/dist/modules/kb-sync/kb-sync.service.js.map +1 -1
- package/dist/modules/marketplace/git-http.routes.d.ts.map +1 -1
- package/dist/modules/marketplace/git-http.routes.js +6 -4
- package/dist/modules/marketplace/git-http.routes.js.map +1 -1
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.d.ts +12 -2
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.d.ts.map +1 -1
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.js +32 -3
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.js.map +1 -1
- package/dist/modules/marketplace/github-facade/github-facade-credentials.service.d.ts +27 -0
- package/dist/modules/marketplace/github-facade/github-facade-credentials.service.d.ts.map +1 -1
- package/dist/modules/marketplace/github-facade/github-facade-credentials.service.js +45 -0
- package/dist/modules/marketplace/github-facade/github-facade-credentials.service.js.map +1 -1
- package/dist/modules/marketplace/github-facade/github-facade.routes.d.ts.map +1 -1
- package/dist/modules/marketplace/github-facade/github-facade.routes.js +30 -6
- package/dist/modules/marketplace/github-facade/github-facade.routes.js.map +1 -1
- package/dist/modules/marketplace/github-facade/github-facade.service.d.ts +21 -9
- package/dist/modules/marketplace/github-facade/github-facade.service.d.ts.map +1 -1
- package/dist/modules/marketplace/github-facade/github-facade.service.js +60 -19
- package/dist/modules/marketplace/github-facade/github-facade.service.js.map +1 -1
- package/dist/modules/marketplace/github-facade/index.d.ts +1 -1
- package/dist/modules/marketplace/github-facade/index.d.ts.map +1 -1
- package/dist/modules/marketplace/github-facade/index.js +1 -1
- package/dist/modules/marketplace/github-facade/index.js.map +1 -1
- package/dist/modules/marketplace/marketplace-repo.service.d.ts +14 -1
- package/dist/modules/marketplace/marketplace-repo.service.d.ts.map +1 -1
- package/dist/modules/marketplace/marketplace-repo.service.js +26 -8
- package/dist/modules/marketplace/marketplace-repo.service.js.map +1 -1
- package/dist/modules/mcp/downstream-pool.d.ts +107 -0
- package/dist/modules/mcp/downstream-pool.d.ts.map +1 -0
- package/dist/modules/mcp/downstream-pool.js +221 -0
- package/dist/modules/mcp/downstream-pool.js.map +1 -0
- package/dist/modules/mcp/downstream-token-refresh.d.ts +77 -0
- package/dist/modules/mcp/downstream-token-refresh.d.ts.map +1 -0
- package/dist/modules/mcp/downstream-token-refresh.js +166 -0
- package/dist/modules/mcp/downstream-token-refresh.js.map +1 -0
- package/dist/modules/mcp/first-call-probe.cli-args.d.ts +78 -0
- package/dist/modules/mcp/first-call-probe.cli-args.d.ts.map +1 -0
- package/dist/modules/mcp/first-call-probe.cli-args.js +151 -0
- package/dist/modules/mcp/first-call-probe.cli-args.js.map +1 -0
- package/dist/modules/mcp/first-call-probe.cli.d.ts +2 -0
- package/dist/modules/mcp/first-call-probe.cli.d.ts.map +1 -0
- package/dist/modules/mcp/first-call-probe.cli.js +62 -0
- package/dist/modules/mcp/first-call-probe.cli.js.map +1 -0
- package/dist/modules/mcp/first-call-probe.d.ts +165 -0
- package/dist/modules/mcp/first-call-probe.d.ts.map +1 -0
- package/dist/modules/mcp/first-call-probe.js +298 -0
- package/dist/modules/mcp/first-call-probe.js.map +1 -0
- package/dist/modules/mcp/manual-failure-memo.d.ts +42 -17
- package/dist/modules/mcp/manual-failure-memo.d.ts.map +1 -1
- package/dist/modules/mcp/manual-failure-memo.js +79 -23
- package/dist/modules/mcp/manual-failure-memo.js.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.d.ts +10 -5
- package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.js +56 -16
- package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
- package/dist/modules/mcp/mcp.routes.d.ts +4 -4
- package/dist/modules/mcp/mcp.routes.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.routes.js +91 -180
- package/dist/modules/mcp/mcp.routes.js.map +1 -1
- package/dist/modules/mcp/mcp.service.d.ts +226 -61
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +758 -161
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +59 -0
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js +184 -10
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.js +3 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.js.map +1 -1
- package/dist/modules/mcp/surface-log-throttle.d.ts +59 -0
- package/dist/modules/mcp/surface-log-throttle.d.ts.map +1 -0
- package/dist/modules/mcp/surface-log-throttle.js +94 -0
- package/dist/modules/mcp/surface-log-throttle.js.map +1 -0
- package/dist/modules/plugins/compile/compile-marketplace.d.ts +13 -6
- package/dist/modules/plugins/compile/compile-marketplace.d.ts.map +1 -1
- package/dist/modules/plugins/compile/compile-marketplace.js +119 -36
- package/dist/modules/plugins/compile/compile-marketplace.js.map +1 -1
- package/dist/modules/plugins/compile/marketplace-compiler.service.d.ts +8 -1
- package/dist/modules/plugins/compile/marketplace-compiler.service.d.ts.map +1 -1
- package/dist/modules/plugins/compile/marketplace-compiler.service.js +9 -6
- package/dist/modules/plugins/compile/marketplace-compiler.service.js.map +1 -1
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.d.ts.map +1 -1
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.js +32 -9
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.js.map +1 -1
- package/dist/modules/plugins/discovery/bundle-dialect/registry.d.ts +26 -6
- package/dist/modules/plugins/discovery/bundle-dialect/registry.d.ts.map +1 -1
- package/dist/modules/plugins/discovery/bundle-dialect/registry.js +71 -14
- package/dist/modules/plugins/discovery/bundle-dialect/registry.js.map +1 -1
- package/dist/modules/plugins/discovery/kb-plugin-source.d.ts +4 -2
- package/dist/modules/plugins/discovery/kb-plugin-source.d.ts.map +1 -1
- package/dist/modules/plugins/discovery/kb-plugin-source.js +9 -6
- package/dist/modules/plugins/discovery/kb-plugin-source.js.map +1 -1
- package/dist/modules/plugins/discovery/native.source.d.ts +2 -1
- package/dist/modules/plugins/discovery/native.source.d.ts.map +1 -1
- package/dist/modules/plugins/discovery/native.source.js +22 -4
- package/dist/modules/plugins/discovery/native.source.js.map +1 -1
- package/dist/modules/plugins/discovery/plugin-source.d.ts +17 -3
- package/dist/modules/plugins/discovery/plugin-source.d.ts.map +1 -1
- package/dist/modules/plugins/index.d.ts +6 -3
- package/dist/modules/plugins/index.d.ts.map +1 -1
- package/dist/modules/plugins/index.js +6 -3
- package/dist/modules/plugins/index.js.map +1 -1
- package/dist/modules/plugins/join-request-jobs.service.d.ts +250 -0
- package/dist/modules/plugins/join-request-jobs.service.d.ts.map +1 -0
- package/dist/modules/plugins/join-request-jobs.service.js +602 -0
- package/dist/modules/plugins/join-request-jobs.service.js.map +1 -0
- package/dist/modules/plugins/join-request-records.store.d.ts +161 -0
- package/dist/modules/plugins/join-request-records.store.d.ts.map +1 -0
- package/dist/modules/plugins/join-request-records.store.js +179 -0
- package/dist/modules/plugins/join-request-records.store.js.map +1 -0
- package/dist/modules/plugins/join-requests.service.d.ts.map +1 -1
- package/dist/modules/plugins/join-requests.service.js +4 -2
- package/dist/modules/plugins/join-requests.service.js.map +1 -1
- package/dist/modules/plugins/plugin-links.d.ts +4 -1
- package/dist/modules/plugins/plugin-links.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-links.js +6 -3
- package/dist/modules/plugins/plugin-links.js.map +1 -1
- package/dist/modules/plugins/plugin-links.service.d.ts +2 -5
- package/dist/modules/plugins/plugin-links.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-links.service.js +4 -13
- package/dist/modules/plugins/plugin-links.service.js.map +1 -1
- package/dist/modules/plugins/plugin-provision.service.d.ts +91 -19
- package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-provision.service.js +183 -38
- package/dist/modules/plugins/plugin-provision.service.js.map +1 -1
- package/dist/modules/plugins/plugin-rename.service.d.ts +3 -1
- package/dist/modules/plugins/plugin-rename.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-rename.service.js +20 -11
- package/dist/modules/plugins/plugin-rename.service.js.map +1 -1
- package/dist/modules/plugins/plugins.contract.d.ts +52 -4
- package/dist/modules/plugins/plugins.contract.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.d.ts +38 -5
- package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.js +190 -116
- package/dist/modules/plugins/plugins.routes.js.map +1 -1
- package/dist/modules/plugins/plugins.service.d.ts +38 -6
- package/dist/modules/plugins/plugins.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.service.js +86 -17
- package/dist/modules/plugins/plugins.service.js.map +1 -1
- package/dist/modules/plugins/plugins.tools.d.ts +25 -0
- package/dist/modules/plugins/plugins.tools.d.ts.map +1 -0
- package/dist/modules/plugins/plugins.tools.js +82 -0
- package/dist/modules/plugins/plugins.tools.js.map +1 -0
- package/dist/modules/plugins/teams.routes.d.ts +42 -0
- package/dist/modules/plugins/teams.routes.d.ts.map +1 -0
- package/dist/modules/plugins/teams.routes.js +79 -0
- package/dist/modules/plugins/teams.routes.js.map +1 -0
- package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts +40 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts.map +1 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.js +202 -37
- package/dist/modules/secrets-vault/db-secrets-vault.service.js.map +1 -1
- package/dist/modules/secrets-vault/index.d.ts +1 -1
- package/dist/modules/secrets-vault/index.d.ts.map +1 -1
- package/dist/modules/secrets-vault/index.js.map +1 -1
- package/dist/modules/secrets-vault/mcp-oauth-discovery.service.d.ts.map +1 -1
- package/dist/modules/secrets-vault/mcp-oauth-discovery.service.js +5 -3
- package/dist/modules/secrets-vault/mcp-oauth-discovery.service.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-variable-loader.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-variable-loader.js +3 -1
- package/dist/modules/secrets-vault/secrets-variable-loader.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.contract.d.ts +78 -0
- package/dist/modules/secrets-vault/secrets-vault.contract.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.contract.js +40 -0
- package/dist/modules/secrets-vault/secrets-vault.contract.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.js +106 -52
- package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
- package/dist/modules/settings/connection-check.d.ts +118 -0
- package/dist/modules/settings/connection-check.d.ts.map +1 -0
- package/dist/modules/settings/connection-check.js +287 -0
- package/dist/modules/settings/connection-check.js.map +1 -0
- package/dist/modules/settings/deployment-settings.service.d.ts +162 -8
- package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
- package/dist/modules/settings/deployment-settings.service.js +375 -52
- package/dist/modules/settings/deployment-settings.service.js.map +1 -1
- package/dist/modules/settings/git-root-folders.d.ts +54 -0
- package/dist/modules/settings/git-root-folders.d.ts.map +1 -0
- package/dist/modules/settings/git-root-folders.js +130 -0
- package/dist/modules/settings/git-root-folders.js.map +1 -0
- package/dist/modules/settings/oidc-check.d.ts +85 -0
- package/dist/modules/settings/oidc-check.d.ts.map +1 -0
- package/dist/modules/settings/oidc-check.js +172 -0
- package/dist/modules/settings/oidc-check.js.map +1 -0
- package/dist/modules/settings/setup.routes.d.ts +31 -1
- package/dist/modules/settings/setup.routes.d.ts.map +1 -1
- package/dist/modules/settings/setup.routes.js +400 -108
- package/dist/modules/settings/setup.routes.js.map +1 -1
- package/dist/modules/skills/allowed-tools-check.d.ts +132 -0
- package/dist/modules/skills/allowed-tools-check.d.ts.map +1 -0
- package/dist/modules/skills/allowed-tools-check.js +260 -0
- package/dist/modules/skills/allowed-tools-check.js.map +1 -0
- package/dist/modules/skills/index.d.ts +3 -1
- package/dist/modules/skills/index.d.ts.map +1 -1
- package/dist/modules/skills/index.js +1 -0
- package/dist/modules/skills/index.js.map +1 -1
- package/dist/modules/skills/pending-skills.service.d.ts +6 -11
- package/dist/modules/skills/pending-skills.service.d.ts.map +1 -1
- package/dist/modules/skills/pending-skills.service.js +18 -70
- package/dist/modules/skills/pending-skills.service.js.map +1 -1
- package/dist/modules/skills/skill-access-requests.routes.d.ts.map +1 -1
- package/dist/modules/skills/skill-access-requests.routes.js +9 -6
- package/dist/modules/skills/skill-access-requests.routes.js.map +1 -1
- package/dist/modules/skills/skills.contract.d.ts +44 -10
- package/dist/modules/skills/skills.contract.d.ts.map +1 -1
- package/dist/modules/skills/skills.routes.d.ts +8 -1
- package/dist/modules/skills/skills.routes.d.ts.map +1 -1
- package/dist/modules/skills/skills.routes.js +16 -2
- package/dist/modules/skills/skills.routes.js.map +1 -1
- package/dist/modules/skills/skills.service.d.ts +31 -7
- package/dist/modules/skills/skills.service.d.ts.map +1 -1
- package/dist/modules/skills/skills.service.js +191 -81
- package/dist/modules/skills/skills.service.js.map +1 -1
- package/dist/modules/skills/skills.tools.d.ts +4 -1
- package/dist/modules/skills/skills.tools.d.ts.map +1 -1
- package/dist/modules/skills/skills.tools.js +41 -6
- package/dist/modules/skills/skills.tools.js.map +1 -1
- package/dist/modules/tool-auth/connection-key-rejection.d.ts +18 -0
- package/dist/modules/tool-auth/connection-key-rejection.d.ts.map +1 -0
- package/dist/modules/tool-auth/connection-key-rejection.js +20 -0
- package/dist/modules/tool-auth/connection-key-rejection.js.map +1 -0
- package/dist/modules/tool-auth/connection-keys-admin.routes.d.ts +14 -0
- package/dist/modules/tool-auth/connection-keys-admin.routes.d.ts.map +1 -0
- package/dist/modules/tool-auth/connection-keys-admin.routes.js +67 -0
- package/dist/modules/tool-auth/connection-keys-admin.routes.js.map +1 -0
- package/dist/modules/tool-auth/external-api-key.interface.d.ts +72 -8
- package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.interface.js.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.d.ts +17 -6
- package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.js +83 -27
- package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.d.ts +15 -2
- package/dist/modules/tool-auth/internal-token.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.js +6 -2
- package/dist/modules/tool-auth/internal-token.service.js.map +1 -1
- package/dist/modules/tool-auth/key-or-session.middleware.d.ts +23 -0
- package/dist/modules/tool-auth/key-or-session.middleware.d.ts.map +1 -0
- package/dist/modules/tool-auth/key-or-session.middleware.js +39 -0
- package/dist/modules/tool-auth/key-or-session.middleware.js.map +1 -0
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts +15 -1
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.js +17 -6
- package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
- package/dist/modules/tool-helpers/tool-context.d.ts +14 -0
- package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-context.js +28 -6
- package/dist/modules/tool-helpers/tool-context.js.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.js +22 -5
- package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.d.ts +6 -1
- package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.js +7 -1
- package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
- package/dist/modules/tool-manuals/index.d.ts +2 -1
- package/dist/modules/tool-manuals/index.d.ts.map +1 -1
- package/dist/modules/tool-manuals/index.js +1 -0
- package/dist/modules/tool-manuals/index.js.map +1 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.d.ts.map +1 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.js +8 -6
- package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -1
- package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts +10 -2
- package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/mcp-server-edit.service.js +12 -15
- package/dist/modules/tool-manuals/mcp-server-edit.service.js.map +1 -1
- package/dist/modules/tool-manuals/pending-tools.service.d.ts +50 -0
- package/dist/modules/tool-manuals/pending-tools.service.d.ts.map +1 -0
- package/dist/modules/tool-manuals/pending-tools.service.js +149 -0
- package/dist/modules/tool-manuals/pending-tools.service.js.map +1 -0
- package/dist/modules/tool-manuals/tool-delete.service.d.ts +135 -0
- package/dist/modules/tool-manuals/tool-delete.service.d.ts.map +1 -0
- package/dist/modules/tool-manuals/tool-delete.service.js +303 -0
- package/dist/modules/tool-manuals/tool-delete.service.js.map +1 -0
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +161 -0
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.contract.js +9 -0
- package/dist/modules/tool-manuals/tool-manuals.contract.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +19 -2
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.js +127 -57
- package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts +119 -5
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +404 -74
- package/dist/modules/tool-manuals/tool-manuals.service.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 +78 -6
- package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.js +88 -54
- package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
- package/dist/modules/workflow/event-bus.d.ts +10 -4
- package/dist/modules/workflow/event-bus.d.ts.map +1 -1
- package/dist/modules/workflow/event-bus.js +14 -11
- package/dist/modules/workflow/event-bus.js.map +1 -1
- package/dist/modules/workflow/events.routes.d.ts +17 -2
- package/dist/modules/workflow/events.routes.d.ts.map +1 -1
- package/dist/modules/workflow/events.routes.js +31 -12
- package/dist/modules/workflow/events.routes.js.map +1 -1
- package/dist/modules/workflow/file-lock.service.d.ts +22 -4
- package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
- package/dist/modules/workflow/file-lock.service.js +27 -4
- package/dist/modules/workflow/file-lock.service.js.map +1 -1
- package/dist/modules/workflow/git/change-request-link.d.ts +20 -0
- package/dist/modules/workflow/git/change-request-link.d.ts.map +1 -0
- package/dist/modules/workflow/git/change-request-link.js +34 -0
- package/dist/modules/workflow/git/change-request-link.js.map +1 -0
- package/dist/modules/workflow/git/git-version.d.ts.map +1 -1
- package/dist/modules/workflow/git/git-version.js +3 -1
- package/dist/modules/workflow/git/git-version.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +263 -4
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +528 -118
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/git/node-git-runner.d.ts +36 -0
- package/dist/modules/workflow/git/node-git-runner.d.ts.map +1 -0
- package/dist/modules/workflow/git/node-git-runner.js +273 -0
- package/dist/modules/workflow/git/node-git-runner.js.map +1 -0
- package/dist/modules/workflow/git/pull-request.service.d.ts +51 -2
- package/dist/modules/workflow/git/pull-request.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/pull-request.service.js +157 -25
- package/dist/modules/workflow/git/pull-request.service.js.map +1 -1
- package/dist/modules/workflow/pending-commits.service.d.ts +11 -0
- package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.service.js +24 -4
- package/dist/modules/workflow/pending-commits.service.js.map +1 -1
- package/dist/modules/workflow/pending-commits.worker.d.ts +15 -8
- package/dist/modules/workflow/pending-commits.worker.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.worker.js +107 -58
- package/dist/modules/workflow/pending-commits.worker.js.map +1 -1
- package/dist/modules/workflow/recovery-bot.d.ts.map +1 -1
- package/dist/modules/workflow/recovery-bot.js +3 -1
- package/dist/modules/workflow/recovery-bot.js.map +1 -1
- package/dist/modules/workflow/review-workflow/approval-lock.d.ts +60 -0
- package/dist/modules/workflow/review-workflow/approval-lock.d.ts.map +1 -0
- package/dist/modules/workflow/review-workflow/approval-lock.js +116 -0
- package/dist/modules/workflow/review-workflow/approval-lock.js.map +1 -0
- package/dist/modules/workflow/review-workflow/review-workflow.interface.d.ts +54 -7
- package/dist/modules/workflow/review-workflow/review-workflow.interface.d.ts.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts +64 -0
- package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.js +269 -90
- package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
- package/dist/modules/workflow/sanitize-error.d.ts +5 -2
- package/dist/modules/workflow/sanitize-error.d.ts.map +1 -1
- package/dist/modules/workflow/sanitize-error.js +8 -4
- package/dist/modules/workflow/sanitize-error.js.map +1 -1
- package/dist/modules/workflow/workflow.routes.d.ts +2 -0
- package/dist/modules/workflow/workflow.routes.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.routes.js +259 -29
- package/dist/modules/workflow/workflow.routes.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +166 -14
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +906 -106
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/empty-dirs.d.ts +15 -0
- package/dist/modules/workspace/empty-dirs.d.ts.map +1 -0
- package/dist/modules/workspace/empty-dirs.js +90 -0
- package/dist/modules/workspace/empty-dirs.js.map +1 -0
- package/dist/modules/workspace/file-readers/content-mode.d.ts +50 -0
- package/dist/modules/workspace/file-readers/content-mode.d.ts.map +1 -0
- package/dist/modules/workspace/file-readers/content-mode.js +55 -0
- package/dist/modules/workspace/file-readers/content-mode.js.map +1 -0
- package/dist/modules/workspace/file-readers/doc-extract.service.d.ts +4 -1
- package/dist/modules/workspace/file-readers/doc-extract.service.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/doc-extract.service.js +4 -1
- package/dist/modules/workspace/file-readers/doc-extract.service.js.map +1 -1
- package/dist/modules/workspace/file-readers/doc-extract.types.d.ts +5 -2
- package/dist/modules/workspace/file-readers/doc-extract.types.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/doc-extract.types.js +7 -4
- package/dist/modules/workspace/file-readers/doc-extract.types.js.map +1 -1
- package/dist/modules/workspace/file-readers/document-reader.d.ts +2 -0
- package/dist/modules/workspace/file-readers/document-reader.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/document-reader.js +5 -0
- package/dist/modules/workspace/file-readers/document-reader.js.map +1 -1
- package/dist/modules/workspace/file-readers/extract-docx.d.ts +6 -0
- package/dist/modules/workspace/file-readers/extract-docx.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/extract-docx.js +6 -0
- package/dist/modules/workspace/file-readers/extract-docx.js.map +1 -1
- package/dist/modules/workspace/file-readers/file-reader.d.ts +24 -5
- package/dist/modules/workspace/file-readers/file-reader.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/file-reader.js +11 -6
- package/dist/modules/workspace/file-readers/file-reader.js.map +1 -1
- package/dist/modules/workspace/file-readers/file-reader.registry.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/file-reader.registry.js +6 -1
- package/dist/modules/workspace/file-readers/file-reader.registry.js.map +1 -1
- package/dist/modules/workspace/file-readers/image-reader.d.ts +7 -5
- package/dist/modules/workspace/file-readers/image-reader.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/image-reader.js +9 -5
- package/dist/modules/workspace/file-readers/image-reader.js.map +1 -1
- package/dist/modules/workspace/file-readers/ooxml-text.d.ts +3 -0
- package/dist/modules/workspace/file-readers/ooxml-text.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/ooxml-text.js +39 -2
- package/dist/modules/workspace/file-readers/ooxml-text.js.map +1 -1
- package/dist/modules/workspace/file-readers/text-reader.d.ts +29 -1
- package/dist/modules/workspace/file-readers/text-reader.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/text-reader.js +57 -3
- package/dist/modules/workspace/file-readers/text-reader.js.map +1 -1
- package/dist/modules/workspace/git-internals.middleware.d.ts +28 -0
- package/dist/modules/workspace/git-internals.middleware.d.ts.map +1 -0
- package/dist/modules/workspace/git-internals.middleware.js +93 -0
- package/dist/modules/workspace/git-internals.middleware.js.map +1 -0
- package/dist/modules/workspace/not-found.d.ts +80 -0
- package/dist/modules/workspace/not-found.d.ts.map +1 -0
- package/dist/modules/workspace/not-found.js +112 -0
- package/dist/modules/workspace/not-found.js.map +1 -0
- package/dist/modules/workspace/session-ontology.gate.d.ts +1 -1
- package/dist/modules/workspace/session-ontology.gate.d.ts.map +1 -1
- package/dist/modules/workspace/session-ontology.gate.js +1 -1
- package/dist/modules/workspace/session-ontology.gate.js.map +1 -1
- package/dist/modules/workspace/startup/beside-checkout.d.ts +42 -0
- package/dist/modules/workspace/startup/beside-checkout.d.ts.map +1 -0
- package/dist/modules/workspace/startup/beside-checkout.js +110 -0
- package/dist/modules/workspace/startup/beside-checkout.js.map +1 -0
- package/dist/modules/workspace/startup/kb-git.d.ts +7 -12
- package/dist/modules/workspace/startup/kb-git.d.ts.map +1 -1
- package/dist/modules/workspace/startup/kb-git.js +34 -35
- package/dist/modules/workspace/startup/kb-git.js.map +1 -1
- package/dist/modules/workspace/startup/kb-startup-runner.d.ts +95 -0
- package/dist/modules/workspace/startup/kb-startup-runner.d.ts.map +1 -1
- package/dist/modules/workspace/startup/kb-startup-runner.js +257 -55
- package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -1
- package/dist/modules/workspace/startup/steps/groups-to-plugins.step.d.ts +45 -0
- package/dist/modules/workspace/startup/steps/groups-to-plugins.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js +417 -385
- package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js.map +1 -1
- package/dist/modules/workspace/startup/steps/personal-spaces.step.d.ts +48 -0
- package/dist/modules/workspace/startup/steps/personal-spaces.step.d.ts.map +1 -0
- package/dist/modules/workspace/startup/steps/personal-spaces.step.js +183 -0
- package/dist/modules/workspace/startup/steps/personal-spaces.step.js.map +1 -0
- package/dist/modules/workspace/startup/steps/plugin-display-names.step.d.ts +51 -0
- package/dist/modules/workspace/startup/steps/plugin-display-names.step.d.ts.map +1 -0
- package/dist/modules/workspace/startup/steps/plugin-display-names.step.js +158 -0
- package/dist/modules/workspace/startup/steps/plugin-display-names.step.js.map +1 -0
- package/dist/modules/workspace/startup/steps/plugin-layout.d.ts +64 -0
- package/dist/modules/workspace/startup/steps/plugin-layout.d.ts.map +1 -0
- package/dist/modules/workspace/startup/steps/plugin-layout.js +97 -0
- package/dist/modules/workspace/startup/steps/plugin-layout.js.map +1 -0
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.d.ts +7 -1
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.js +45 -81
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.js.map +1 -1
- package/dist/modules/workspace/startup/steps/roles-yaml.step.d.ts +3 -1
- package/dist/modules/workspace/startup/steps/roles-yaml.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/roles-yaml.step.js +4 -12
- package/dist/modules/workspace/startup/steps/roles-yaml.step.js.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.d.ts +6 -1
- package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.js +154 -78
- package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
- package/dist/modules/workspace/startup/steps/template-files.step.d.ts +96 -24
- package/dist/modules/workspace/startup/steps/template-files.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/template-files.step.js +446 -138
- package/dist/modules/workspace/startup/steps/template-files.step.js.map +1 -1
- package/dist/modules/workspace/startup/steps/template-source.d.ts +94 -0
- package/dist/modules/workspace/startup/steps/template-source.d.ts.map +1 -0
- package/dist/modules/workspace/startup/steps/template-source.js +160 -0
- package/dist/modules/workspace/startup/steps/template-source.js.map +1 -0
- package/dist/modules/workspace/workspace.routes.d.ts +14 -2
- package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.routes.js +649 -238
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +298 -12
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +933 -269
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts +42 -1
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +1626 -146
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/modules/workspace/write-denial.d.ts +76 -0
- package/dist/modules/workspace/write-denial.d.ts.map +1 -0
- package/dist/modules/workspace/write-denial.js +138 -0
- package/dist/modules/workspace/write-denial.js.map +1 -0
- package/dist/shared/canonical-file-identity.d.ts +64 -0
- package/dist/shared/canonical-file-identity.d.ts.map +1 -0
- package/dist/shared/canonical-file-identity.js +92 -0
- package/dist/shared/canonical-file-identity.js.map +1 -0
- package/dist/shared/domain-errors.d.ts +108 -0
- package/dist/shared/domain-errors.d.ts.map +1 -1
- package/dist/shared/domain-errors.js +137 -0
- package/dist/shared/domain-errors.js.map +1 -1
- package/dist/shared/email-identity.d.ts +32 -0
- package/dist/shared/email-identity.d.ts.map +1 -0
- package/dist/shared/email-identity.js +37 -0
- package/dist/shared/email-identity.js.map +1 -0
- package/dist/shared/fs.contract.d.ts +206 -0
- package/dist/shared/fs.contract.d.ts.map +1 -0
- package/dist/shared/fs.contract.js +79 -0
- package/dist/shared/fs.contract.js.map +1 -0
- package/dist/shared/git-failure.d.ts +80 -0
- package/dist/shared/git-failure.d.ts.map +1 -0
- package/dist/shared/git-failure.js +139 -0
- package/dist/shared/git-failure.js.map +1 -0
- package/dist/shared/git-internals.d.ts +23 -0
- package/dist/shared/git-internals.d.ts.map +1 -0
- package/dist/shared/git-internals.js +181 -0
- package/dist/shared/git-internals.js.map +1 -0
- package/dist/shared/git.contract.d.ts +157 -0
- package/dist/shared/git.contract.d.ts.map +1 -0
- package/dist/shared/git.contract.js +103 -0
- package/dist/shared/git.contract.js.map +1 -0
- package/dist/shared/http-errors.d.ts +33 -0
- package/dist/shared/http-errors.d.ts.map +1 -0
- package/dist/shared/http-errors.js +35 -0
- package/dist/shared/http-errors.js.map +1 -0
- package/dist/shared/logger.contract.d.ts +63 -0
- package/dist/shared/logger.contract.d.ts.map +1 -0
- package/dist/shared/logger.contract.js +126 -0
- package/dist/shared/logger.contract.js.map +1 -0
- package/dist/shared/logging.d.ts +31 -0
- package/dist/shared/logging.d.ts.map +1 -0
- package/dist/shared/logging.js +92 -0
- package/dist/shared/logging.js.map +1 -0
- package/dist/shared/path-containment.d.ts +21 -0
- package/dist/shared/path-containment.d.ts.map +1 -0
- package/dist/shared/path-containment.js +34 -0
- package/dist/shared/path-containment.js.map +1 -0
- package/dist/shared/pending-proposals.d.ts +71 -0
- package/dist/shared/pending-proposals.d.ts.map +1 -0
- package/dist/shared/pending-proposals.js +98 -0
- package/dist/shared/pending-proposals.js.map +1 -0
- package/dist/shared/printable.d.ts +22 -0
- package/dist/shared/printable.d.ts.map +1 -0
- package/dist/shared/printable.js +65 -0
- package/dist/shared/printable.js.map +1 -0
- package/dist/shared/redact-secret.d.ts +30 -0
- package/dist/shared/redact-secret.d.ts.map +1 -0
- package/dist/shared/redact-secret.js +114 -0
- package/dist/shared/redact-secret.js.map +1 -0
- package/dist/shared/rename-no-replace.d.ts +119 -0
- package/dist/shared/rename-no-replace.d.ts.map +1 -0
- package/dist/shared/rename-no-replace.js +323 -0
- package/dist/shared/rename-no-replace.js.map +1 -0
- package/dist/shared/ssrf.d.ts.map +1 -1
- package/dist/shared/ssrf.js +49 -18
- package/dist/shared/ssrf.js.map +1 -1
- package/dist/version.d.ts +10 -6
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +10 -6
- package/dist/version.js.map +1 -1
- package/kb-template/.bevelignore +4 -3
- package/kb-template/AGENTS.md +281 -36
- package/kb-template/KnowledgeBase/How to get started.md +1 -1
- package/kb-template/access.md +5 -3
- package/kb-template/mcp-description.md +27 -0
- package/migrations/0006_api_tokens_revoked_by.sql +1 -0
- package/migrations/0007_github_facade_registered.sql +1 -0
- package/migrations/0008_change_request_apply_failure.sql +4 -0
- package/migrations/0009_change_request_apply_failure_kind.sql +13 -0
- package/migrations/0010_plugin_join_requests.sql +15 -0
- package/migrations/0011_plugin_join_request_claim.sql +11 -0
- package/migrations/0012_agent_audit.sql +41 -0
- package/migrations/0013_agent_identity_and_key_soft_delete.sql +29 -0
- package/migrations/meta/0006_snapshot.json +1723 -0
- package/migrations/meta/0007_snapshot.json +1729 -0
- package/migrations/meta/0008_snapshot.json +1753 -0
- package/migrations/meta/0009_snapshot.json +1759 -0
- package/migrations/meta/0010_snapshot.json +1872 -0
- package/migrations/meta/0011_snapshot.json +1884 -0
- package/migrations/meta/0012_snapshot.json +2247 -0
- package/migrations/meta/0013_snapshot.json +2259 -0
- package/migrations/meta/_journal.json +56 -0
- package/package.json +7 -6
- package/src/__tests__/core-config.domain.test.ts +67 -2
- package/src/__tests__/kb-layout-config.test.ts +186 -1
- package/src/__tests__/open-change-gate.ts +15 -0
- package/src/core/__tests__/catalog-cache-invalidation.test.ts +83 -3
- package/src/core/__tests__/catalog-revision.test.ts +190 -0
- package/src/core/__tests__/lifecycle.test.ts +369 -0
- package/src/core/__tests__/public-config.test.ts +30 -0
- package/src/core/__tests__/readiness.test.ts +109 -0
- package/src/core/catalog-cache-invalidation.ts +20 -1
- package/src/core/catalog-revision.ts +110 -0
- package/src/core/create-core-server.ts +286 -79
- package/src/core/create-core-services.ts +315 -81
- package/src/core/lifecycle.ts +312 -0
- package/src/core/public-config.ts +86 -0
- package/src/core/readiness.ts +123 -0
- package/src/core-config.ts +70 -4
- package/src/index.ts +15 -0
- package/src/modules/access/__tests__/access-control.admin-root-floor.test.ts +362 -0
- package/src/modules/access/__tests__/access-control.atref-batch.test.ts +2 -1
- package/src/modules/access/__tests__/access-control.atref-cache.test.ts +46 -22
- package/src/modules/access/__tests__/access-control.deny-dependents.test.ts +215 -0
- package/src/modules/access/__tests__/access-control.download-read.test.ts +268 -0
- package/src/modules/access/__tests__/access-control.everyone-read.test.ts +145 -0
- package/src/modules/access/__tests__/access-control.group-read.test.ts +163 -0
- package/src/modules/access/__tests__/access-control.invalidate-race.test.ts +2 -0
- package/src/modules/access/__tests__/access-control.platform-restore.test.ts +152 -0
- package/src/modules/access/__tests__/access-control.prospective.test.ts +189 -0
- package/src/modules/access/__tests__/access-control.read-batch.test.ts +2 -1
- package/src/modules/access/__tests__/access-control.service.test.ts +123 -78
- package/src/modules/access/__tests__/access-groups.test.ts +47 -14
- package/src/modules/access/__tests__/access-md-format.test.ts +2 -1
- package/src/modules/access/__tests__/access-mutation.service.test.ts +52 -4
- package/src/modules/access/__tests__/access-own-read-grant.test.ts +2 -0
- package/src/modules/access/__tests__/access-personal-plugin.test.ts +153 -0
- package/src/modules/access/__tests__/access-plugin-principals.test.ts +18 -1
- package/src/modules/access/__tests__/access-restrict-here.test.ts +450 -0
- package/src/modules/access/__tests__/access.routes.batch.test.ts +139 -0
- package/src/modules/access/__tests__/access.routes.folder-governs.test.ts +394 -0
- package/src/modules/access/__tests__/access.routes.group-grant.test.ts +6 -2
- package/src/modules/access/__tests__/access.routes.overrides.test.ts +3 -2
- package/src/modules/access/__tests__/access.routes.proposal-branch.test.ts +234 -0
- package/src/modules/access/__tests__/access.routes.prospective.test.ts +216 -0
- package/src/modules/access/__tests__/access.routes.revoke.test.ts +7 -2
- package/src/modules/access/__tests__/access.routes.unknown-email.test.ts +360 -0
- package/src/modules/access/__tests__/change-read-gate.test.ts +272 -0
- package/src/modules/access/__tests__/creator-access.test.ts +81 -83
- package/src/modules/access/__tests__/grant-sources.test.ts +3 -2
- package/src/modules/access/__tests__/groups-admin.service.test.ts +6 -5
- package/src/modules/access/__tests__/roles-admin.security.test.ts +196 -0
- package/src/modules/access/__tests__/roles-admin.service.test.ts +127 -2
- package/src/modules/access/__tests__/roles-capabilities.test.ts +5 -4
- package/src/modules/access/__tests__/roles.routes.test.ts +5 -2
- package/src/modules/access/__tests__/user-access-removal.service.test.ts +402 -0
- package/src/modules/access/__tests__/users-db-double.ts +63 -0
- package/src/modules/access/access-control.interface.ts +251 -11
- package/src/modules/access/access-control.service.ts +719 -164
- package/src/modules/access/access-mutation.service.ts +182 -13
- package/src/modules/access/access-view.ts +316 -0
- package/src/modules/access/access.routes.ts +293 -95
- package/src/modules/access/admin-locked-commit.ts +12 -14
- package/src/modules/access/admin-route-helpers.ts +4 -4
- package/src/modules/access/change-read-gate.ts +148 -0
- package/src/modules/access/creator-access.ts +89 -124
- package/src/modules/access/directory-sync-bot.ts +4 -1
- package/src/modules/access/groups-admin.service.ts +3 -3
- package/src/modules/access/reference-scan.ts +5 -5
- package/src/modules/access/roles-admin.service.ts +94 -15
- package/src/modules/access/synced-groups-committer.ts +10 -15
- package/src/modules/access/synced-groups-writer.ts +3 -12
- package/src/modules/access/user-access-removal.service.ts +711 -0
- package/src/modules/access-model/__tests__/access-splice.test.ts +63 -1
- package/src/modules/access-model/__tests__/frontmatter-fences.test.ts +71 -0
- package/src/modules/access-model/__tests__/roles-yaml-guard.test.ts +131 -0
- package/src/modules/access-model/access-errors.ts +56 -15
- package/src/modules/access-model/access-grammar.ts +85 -48
- package/src/modules/access-model/access-splice.ts +18 -18
- package/src/modules/access-model/access-template.ts +44 -0
- package/src/modules/access-model/change-gate.ts +80 -0
- package/src/modules/access-model/creator.ts +18 -21
- package/src/modules/access-model/frontmatter-lines.ts +98 -0
- package/src/modules/access-model/plugin-principals.ts +12 -19
- package/src/modules/access-model/roles-yaml-guard.ts +182 -4
- package/src/modules/admin/admin-access.service.ts +6 -2
- package/src/modules/agent-instructions/__tests__/agent-instructions.route.test.ts +100 -0
- package/src/modules/agent-instructions/__tests__/compose.test.ts +256 -0
- package/src/modules/agent-instructions/__tests__/read-preamble.test.ts +135 -0
- package/src/modules/agent-instructions/agent-instructions.routes.ts +41 -0
- package/src/modules/agent-instructions/compose.ts +271 -0
- package/src/modules/agent-instructions/index.ts +14 -0
- package/src/modules/agent-instructions/read-preamble.ts +88 -0
- package/src/modules/audit/__tests__/agent-audit.service.test.ts +370 -0
- package/src/modules/audit/__tests__/agent-rest-audit.middleware.test.ts +113 -0
- package/src/modules/audit/__tests__/audit.routes.test.ts +201 -0
- package/src/modules/audit/__tests__/event-classifier.test.ts +121 -0
- package/src/modules/audit/agent-audit.service.ts +316 -0
- package/src/modules/audit/agent-rest-audit.middleware.ts +83 -0
- package/src/modules/audit/audit.contract.ts +164 -0
- package/src/modules/audit/audit.routes.ts +168 -0
- package/src/modules/audit/event-classifier.ts +155 -0
- package/src/modules/audit/index.ts +14 -0
- package/src/modules/audit/request-audit.ts +141 -0
- package/src/modules/auth/__tests__/account-erasure.approval-gate.test.ts +209 -0
- package/src/modules/auth/__tests__/account.routes.test.ts +178 -2
- package/src/modules/auth/__tests__/auth.middleware.test.ts +78 -0
- package/src/modules/auth/__tests__/auth.service.test.ts +338 -5
- package/src/modules/auth/__tests__/oidc-auth-provider.test.ts +352 -12
- package/src/modules/auth/__tests__/onboarding-done.route.test.ts +3 -1
- package/src/modules/auth/account-erasure.service.ts +117 -10
- package/src/modules/auth/account.routes.ts +115 -14
- package/src/modules/auth/auth.routes.ts +20 -7
- package/src/modules/auth/auth.service.ts +187 -28
- package/src/modules/auth/oidc-auth-provider.ts +120 -22
- package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +109 -1
- package/src/modules/code-mode/code-mode.tool.ts +13 -2
- package/src/modules/connection-probe/__tests__/connection-probe.service.test.ts +78 -0
- package/src/modules/connection-probe/connection-probe.service.ts +59 -7
- package/src/modules/database/__tests__/advisory-lease.test.ts +152 -0
- package/src/modules/database/__tests__/advisory-lock.test.ts +147 -0
- package/src/modules/database/advisory-lock.ts +346 -0
- package/src/modules/database/connection.ts +9 -1
- package/src/modules/database/core-schema.ts +227 -0
- package/src/modules/database/migrate.ts +27 -6
- package/src/modules/declared-variables/declared-variables.routes.ts +6 -3
- package/src/modules/diff/__tests__/diff.service.seed-atomicity.test.ts +3 -0
- package/src/modules/diff/__tests__/diff.service.test.ts +3 -0
- package/src/modules/diff/diff.routes.ts +2 -4
- package/src/modules/diff/diff.service.ts +80 -79
- package/src/modules/kb-fs/__tests__/locking-filesystem.test.ts +472 -9
- package/src/modules/kb-fs/__tests__/node-fs.test.ts +409 -0
- package/src/modules/kb-fs/__tests__/repo-path.test.ts +210 -1
- package/src/modules/{workspace → kb-fs}/bevel-ignore.ts +22 -15
- package/src/modules/kb-fs/file-change-notifier.ts +4 -1
- package/src/modules/kb-fs/git-guarded-filesystem.ts +136 -0
- package/src/modules/kb-fs/locking-filesystem.ts +399 -109
- package/src/modules/kb-fs/node-fs.ts +165 -0
- package/src/modules/kb-fs/read-only-filesystem.ts +18 -9
- package/src/modules/kb-fs/repo-path.ts +213 -3
- package/src/modules/kb-sync/kb-sync.routes.ts +5 -2
- package/src/modules/kb-sync/kb-sync.service.ts +8 -5
- package/src/modules/marketplace/__tests__/github-facade.test.ts +134 -2
- package/src/modules/marketplace/__tests__/marketplace-git.test.ts +2 -1
- package/src/modules/marketplace/__tests__/marketplace-repo.head.test.ts +47 -0
- package/src/modules/marketplace/git-http.routes.ts +7 -4
- package/src/modules/marketplace/github-facade/github-facade-admin.routes.ts +35 -3
- package/src/modules/marketplace/github-facade/github-facade-credentials.service.ts +55 -0
- package/src/modules/marketplace/github-facade/github-facade.routes.ts +32 -6
- package/src/modules/marketplace/github-facade/github-facade.service.ts +80 -19
- package/src/modules/marketplace/github-facade/index.ts +1 -0
- package/src/modules/marketplace/marketplace-repo.service.ts +23 -8
- package/src/modules/mcp/__tests__/bevel-oauth-provider.test.ts +183 -13
- package/src/modules/mcp/__tests__/downstream-pool.test.ts +278 -0
- package/src/modules/mcp/__tests__/downstream-token-refresh.test.ts +178 -0
- package/src/modules/mcp/__tests__/fake-downstream-mcp-server.ts +156 -0
- package/src/modules/mcp/__tests__/first-call-probe.cli-args.test.ts +178 -0
- package/src/modules/mcp/__tests__/first-call-probe.test.ts +414 -0
- package/src/modules/mcp/__tests__/manual-failure-memo.test.ts +98 -6
- package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +68 -7
- package/src/modules/mcp/__tests__/mcp-routes-harness.ts +1 -3
- package/src/modules/mcp/__tests__/mcp.e2e.test.ts +1055 -59
- package/src/modules/mcp/__tests__/mcp.routes.local-token.test.ts +24 -4
- package/src/modules/mcp/__tests__/mcp.routes.stateless.test.ts +182 -0
- package/src/modules/mcp/__tests__/mcp.service.test.ts +391 -6
- package/src/modules/mcp/__tests__/surface-log-throttle.test.ts +169 -0
- package/src/modules/mcp/downstream-pool.ts +256 -0
- package/src/modules/mcp/downstream-token-refresh.ts +170 -0
- package/src/modules/mcp/first-call-probe.cli-args.ts +159 -0
- package/src/modules/mcp/first-call-probe.cli.ts +60 -0
- package/src/modules/mcp/first-call-probe.ts +418 -0
- package/src/modules/mcp/manual-failure-memo.ts +81 -23
- package/src/modules/mcp/mcp-auth.middleware.ts +56 -16
- package/src/modules/mcp/mcp.routes.ts +386 -482
- package/src/modules/mcp/mcp.service.ts +905 -177
- package/src/modules/mcp/oauth/bevel-oauth-provider.ts +189 -8
- package/src/modules/mcp/oauth/oauth-consent.routes.ts +4 -4
- package/src/modules/mcp/surface-log-throttle.ts +120 -0
- package/src/modules/plugins/__tests__/bundle-dialect.e2e.test.ts +10 -7
- package/src/modules/plugins/__tests__/bundle-dialect.test.ts +165 -0
- package/src/modules/plugins/__tests__/compile-marketplace.test.ts +139 -10
- package/src/modules/plugins/__tests__/fake-join-request-store.ts +189 -0
- package/src/modules/plugins/__tests__/join-request-jobs.service.test.ts +608 -0
- package/src/modules/plugins/__tests__/plugin-creation-display-name.test.ts +241 -0
- package/src/modules/plugins/__tests__/plugin-index.service.test.ts +148 -7
- package/src/modules/plugins/__tests__/plugin-links.service.test.ts +10 -13
- package/src/modules/plugins/__tests__/plugin-names.test.ts +77 -0
- package/src/modules/plugins/__tests__/plugin-provision.service.test.ts +129 -19
- package/src/modules/plugins/__tests__/plugin-rename.service.test.ts +91 -29
- package/src/modules/plugins/__tests__/plugins.routes.test.ts +370 -19
- package/src/modules/plugins/__tests__/plugins.tools.test.ts +131 -0
- package/src/modules/plugins/__tests__/teams.routes.test.ts +248 -0
- package/src/modules/plugins/compile/compile-marketplace.ts +143 -46
- package/src/modules/plugins/compile/marketplace-compiler.service.ts +8 -7
- package/src/modules/plugins/discovery/__tests__/kb-plugin-source.test.ts +56 -20
- package/src/modules/plugins/discovery/bundle-dialect/bundle.source.ts +30 -9
- package/src/modules/plugins/discovery/bundle-dialect/registry.ts +93 -18
- package/src/modules/plugins/discovery/kb-plugin-source.ts +10 -7
- package/src/modules/plugins/discovery/native.source.ts +21 -3
- package/src/modules/plugins/discovery/plugin-source.ts +17 -3
- package/src/modules/plugins/index.ts +16 -3
- package/src/modules/plugins/join-request-jobs.service.ts +675 -0
- package/src/modules/plugins/join-request-records.store.ts +353 -0
- package/src/modules/plugins/join-requests.service.ts +7 -4
- package/src/modules/plugins/plugin-links.service.ts +4 -17
- package/src/modules/plugins/plugin-links.ts +6 -3
- package/src/modules/plugins/plugin-provision.service.ts +209 -37
- package/src/modules/plugins/plugin-rename.service.ts +21 -9
- package/src/modules/plugins/plugins.contract.ts +52 -4
- package/src/modules/plugins/plugins.routes.ts +204 -124
- package/src/modules/plugins/plugins.service.ts +92 -18
- package/src/modules/plugins/plugins.tools.ts +88 -0
- package/src/modules/plugins/teams.routes.ts +129 -0
- package/src/modules/secrets-vault/__tests__/connect-pending.route.test.ts +359 -15
- package/src/modules/secrets-vault/__tests__/db-secrets-vault.oauth.test.ts +248 -0
- package/src/modules/secrets-vault/__tests__/namespace-secrets.test.ts +202 -0
- package/src/modules/secrets-vault/db-secrets-vault.service.ts +217 -35
- package/src/modules/secrets-vault/index.ts +1 -0
- package/src/modules/secrets-vault/mcp-oauth-discovery.service.ts +6 -3
- package/src/modules/secrets-vault/secrets-variable-loader.ts +4 -4
- package/src/modules/secrets-vault/secrets-vault.contract.ts +93 -0
- package/src/modules/secrets-vault/secrets-vault.routes.ts +99 -38
- package/src/modules/settings/__tests__/connection-check.test.ts +233 -0
- package/src/modules/settings/__tests__/deployment-settings.service.test.ts +223 -6
- package/src/modules/settings/__tests__/git-root-folders.test.ts +247 -0
- package/src/modules/settings/__tests__/oidc-check.test.ts +185 -0
- package/src/modules/settings/__tests__/setup.routes.boot-failure.test.ts +119 -0
- package/src/modules/settings/__tests__/setup.routes.layout-phase.test.ts +170 -0
- package/src/modules/settings/__tests__/setup.routes.oidc.test.ts +555 -0
- package/src/modules/settings/__tests__/setup.routes.test.ts +667 -16
- package/src/modules/settings/connection-check.ts +351 -0
- package/src/modules/settings/deployment-settings.service.ts +444 -59
- package/src/modules/settings/git-root-folders.ts +171 -0
- package/src/modules/settings/oidc-check.ts +229 -0
- package/src/modules/settings/setup.routes.ts +456 -110
- package/src/modules/skills/__tests__/allowed-tools-check.test.ts +224 -0
- package/src/modules/skills/__tests__/allowed-tools-warn.tools.test.ts +210 -0
- package/src/modules/skills/__tests__/pending-skills.service.test.ts +3 -3
- package/src/modules/skills/__tests__/skills.routes.warnings.test.ts +91 -0
- package/src/modules/skills/__tests__/skills.service.test.ts +223 -2
- package/src/modules/skills/allowed-tools-check.ts +352 -0
- package/src/modules/skills/index.ts +4 -0
- package/src/modules/skills/pending-skills.service.ts +21 -82
- package/src/modules/skills/skill-access-requests.routes.ts +10 -6
- package/src/modules/skills/skills.contract.ts +43 -11
- package/src/modules/skills/skills.routes.ts +16 -1
- package/src/modules/skills/skills.service.ts +214 -84
- package/src/modules/skills/skills.tools.ts +43 -5
- package/src/modules/tool-auth/__tests__/connection-key-rejection.test.ts +192 -0
- package/src/modules/tool-auth/__tests__/connection-keys-admin.routes.test.ts +116 -0
- package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +153 -8
- package/src/modules/tool-auth/__tests__/internal-token.service.test.ts +6 -0
- package/src/modules/tool-auth/connection-key-rejection.ts +25 -0
- package/src/modules/tool-auth/connection-keys-admin.routes.ts +76 -0
- package/src/modules/tool-auth/external-api-key.interface.ts +78 -8
- package/src/modules/tool-auth/external-api-key.service.ts +98 -33
- package/src/modules/tool-auth/internal-token.service.ts +16 -2
- package/src/modules/tool-auth/key-or-session.middleware.ts +45 -0
- package/src/modules/tool-auth/tool-auth.middleware.ts +33 -8
- package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +228 -0
- package/src/modules/tool-helpers/__tests__/phase4-tools.test.ts +33 -4
- package/src/modules/tool-helpers/tool-context.ts +46 -7
- package/src/modules/tool-helpers/tool-handler.ts +23 -5
- package/src/modules/tool-helpers/tool.contract.ts +5 -0
- package/src/modules/tool-manuals/__tests__/mcp-server-edit.service.test.ts +2 -1
- package/src/modules/tool-manuals/__tests__/pending-tools.service.test.ts +411 -0
- package/src/modules/tool-manuals/__tests__/tool-delete.route.test.ts +133 -0
- package/src/modules/tool-manuals/__tests__/tool-delete.service.test.ts +469 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.archive.route.test.ts +21 -2
- package/src/modules/tool-manuals/__tests__/tool-manuals.cli.test.ts +9 -4
- package/src/modules/tool-manuals/__tests__/tool-manuals.detail.route.test.ts +6 -1
- package/src/modules/tool-manuals/__tests__/tool-manuals.invalid.routes.test.ts +254 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.invalid.test.ts +577 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +6 -1
- package/src/modules/tool-manuals/__tests__/tool-manuals.pending.route.test.ts +102 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.restart.test.ts +285 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +178 -2
- package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +114 -2
- package/src/modules/tool-manuals/index.ts +3 -0
- package/src/modules/tool-manuals/mcp-json-discovery.ts +13 -10
- package/src/modules/tool-manuals/mcp-server-edit.service.ts +11 -14
- package/src/modules/tool-manuals/pending-tools.service.ts +179 -0
- package/src/modules/tool-manuals/tool-delete.service.ts +401 -0
- package/src/modules/tool-manuals/tool-manuals.contract.ts +165 -0
- package/src/modules/tool-manuals/tool-manuals.routes.ts +138 -54
- package/src/modules/tool-manuals/tool-manuals.service.ts +432 -87
- package/src/modules/tool-manuals/tool-manuals.tools.ts +81 -6
- package/src/modules/workflow/__tests__/apply-failure.test.ts +1038 -0
- package/src/modules/workflow/__tests__/event-bus.test.ts +61 -9
- package/src/modules/workflow/__tests__/fake-file-lock-db.ts +199 -0
- package/src/modules/workflow/__tests__/file-lock.service.path-identity.test.ts +237 -0
- package/src/modules/workflow/__tests__/format-affected-owners.test.ts +18 -0
- package/src/modules/workflow/__tests__/merge-announces-tree.test.ts +220 -0
- package/src/modules/workflow/__tests__/pending-commits.worker.concurrency.test.ts +187 -0
- package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +2 -0
- package/src/modules/workflow/__tests__/sanitize-error.test.ts +10 -0
- package/src/modules/workflow/__tests__/workflow.routes.folder-proposals.test.ts +128 -0
- package/src/modules/workflow/__tests__/workflow.routes.fork-point-file.test.ts +149 -0
- package/src/modules/workflow/__tests__/workflow.routes.lock-path.test.ts +305 -0
- package/src/modules/workflow/__tests__/workflow.service.branch-in-use.test.ts +2 -0
- package/src/modules/workflow/__tests__/workflow.service.commitFileWhileLocked.test.ts +2 -0
- package/src/modules/workflow/__tests__/workflow.service.deleted-branch-sweep.test.ts +2 -0
- package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +294 -25
- package/src/modules/workflow/__tests__/workflow.service.folder-proposals.test.ts +564 -0
- package/src/modules/workflow/__tests__/workflow.service.platform-restore.test.ts +135 -0
- package/src/modules/workflow/__tests__/workflow.service.read-gate.test.ts +172 -0
- package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +14 -0
- package/src/modules/workflow/__tests__/workflow.service.sync.test.ts +5 -0
- package/src/modules/workflow/__tests__/workflow.service.update-from-target.test.ts +281 -0
- package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +158 -7
- package/src/modules/workflow/agent-tools/workflow.tools.ts +93 -64
- package/src/modules/workflow/event-bus.ts +26 -28
- package/src/modules/workflow/events.routes.ts +56 -21
- package/src/modules/workflow/file-lock.service.ts +27 -4
- package/src/modules/workflow/git/__tests__/change-request-link.test.ts +43 -0
- package/src/modules/workflow/git/__tests__/git.service.accessGating.test.ts +1 -0
- package/src/modules/workflow/git/__tests__/git.service.changedFilesForPr.test.ts +238 -1
- package/src/modules/workflow/git/__tests__/git.service.forkPoint.test.ts +245 -0
- package/src/modules/workflow/git/__tests__/git.service.list-branches.test.ts +35 -0
- package/src/modules/workflow/git/__tests__/git.service.mergeChangeRequest.test.ts +149 -0
- package/src/modules/workflow/git/__tests__/git.service.revertPathsAndPush.test.ts +177 -0
- package/src/modules/workflow/git/__tests__/node-git-runner.test.ts +114 -0
- package/src/modules/workflow/git/__tests__/pull-request.service.getPrDetail.test.ts +69 -2
- package/src/modules/workflow/git/__tests__/pull-request.service.list-fetch.test.ts +121 -0
- package/src/modules/workflow/git/__tests__/pull-request.service.placeholder.test.ts +79 -0
- package/src/modules/workflow/git/__tests__/pull-request.service.test.ts +1 -1
- package/src/modules/workflow/git/__tests__/pull-request.service.viewer-can-cancel.test.ts +1 -1
- package/src/modules/workflow/git/__tests__/pull-request.service.viewer-can-update.test.ts +111 -0
- package/src/modules/workflow/git/change-request-link.ts +35 -0
- package/src/modules/workflow/git/git-version.ts +5 -2
- package/src/modules/workflow/git/git.service.ts +684 -144
- package/src/modules/workflow/git/node-git-runner.ts +315 -0
- package/src/modules/workflow/git/pull-request.service.ts +179 -27
- package/src/modules/workflow/pending-commits.service.ts +26 -7
- package/src/modules/workflow/pending-commits.worker.ts +114 -69
- package/src/modules/workflow/recovery-bot.ts +4 -1
- package/src/modules/workflow/review-workflow/__tests__/approval-states.test.ts +180 -1
- package/src/modules/workflow/review-workflow/__tests__/cancel-pr.test.ts +1 -1
- package/src/modules/workflow/review-workflow/__tests__/carry-approvals-forward.test.ts +520 -0
- package/src/modules/workflow/review-workflow/__tests__/erase-approver.test.ts +84 -0
- package/src/modules/workflow/review-workflow/__tests__/merge-gate.test.ts +89 -77
- package/src/modules/workflow/review-workflow/approval-lock.ts +122 -0
- package/src/modules/workflow/review-workflow/review-workflow.interface.ts +59 -7
- package/src/modules/workflow/review-workflow/review-workflow.service.ts +309 -105
- package/src/modules/workflow/sanitize-error.ts +9 -4
- package/src/modules/workflow/workflow.routes.ts +273 -29
- package/src/modules/workflow/workflow.service.ts +1087 -171
- package/src/modules/workspace/__tests__/empty-dirs.test.ts +44 -0
- package/src/modules/workspace/__tests__/file-stat-access.test.ts +301 -0
- package/src/modules/workspace/__tests__/git-internals.security.test.ts +883 -0
- package/src/modules/workspace/__tests__/workspace-paths-inside-the-repo.test.ts +464 -0
- package/src/modules/workspace/__tests__/workspace.routes.branch-errors.test.ts +139 -0
- package/src/modules/workspace/__tests__/workspace.routes.create-grant.test.ts +189 -37
- package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +51 -11
- package/src/modules/workspace/__tests__/workspace.routes.download-implies-read.test.ts +161 -0
- package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +109 -10
- package/src/modules/workspace/__tests__/workspace.routes.file-writes.test.ts +624 -0
- package/src/modules/workspace/__tests__/workspace.routes.folders.test.ts +409 -0
- package/src/modules/workspace/__tests__/workspace.routes.move-platform-files.test.ts +327 -0
- package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +25 -6
- package/src/modules/workspace/__tests__/workspace.service.ensure-remotes-fetched.test.ts +109 -0
- package/src/modules/workspace/__tests__/workspace.service.list-cloned.test.ts +6 -5
- package/src/modules/workspace/__tests__/workspace.service.move-never-overwrites.test.ts +218 -0
- package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +56 -1
- package/src/modules/workspace/__tests__/workspace.service.test.ts +811 -42
- package/src/modules/workspace/__tests__/workspace.service.unknown-branch.test.ts +197 -0
- package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +128 -0
- package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +212 -0
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +2632 -110
- package/src/modules/workspace/__tests__/write-denial.test.ts +75 -0
- package/src/modules/workspace/empty-dirs.ts +79 -0
- package/src/modules/workspace/file-readers/README.md +32 -0
- package/src/modules/workspace/file-readers/__tests__/doc-extract.test.ts +59 -2
- package/src/modules/workspace/file-readers/__tests__/file-reader.registry.test.ts +133 -4
- package/src/modules/workspace/file-readers/__tests__/fixtures/soft-break.docx +0 -0
- package/src/modules/workspace/file-readers/__tests__/fixtures/tab.docx +0 -0
- package/src/modules/workspace/file-readers/content-mode.ts +87 -0
- package/src/modules/workspace/file-readers/doc-extract.service.ts +4 -1
- package/src/modules/workspace/file-readers/doc-extract.types.ts +8 -4
- package/src/modules/workspace/file-readers/document-reader.ts +6 -0
- package/src/modules/workspace/file-readers/extract-docx.ts +6 -0
- package/src/modules/workspace/file-readers/file-reader.registry.ts +9 -1
- package/src/modules/workspace/file-readers/file-reader.ts +27 -6
- package/src/modules/workspace/file-readers/image-reader.ts +10 -5
- package/src/modules/workspace/file-readers/ooxml-text.ts +38 -2
- package/src/modules/workspace/file-readers/text-reader.ts +61 -4
- package/src/modules/workspace/git-internals.middleware.ts +86 -0
- package/src/modules/workspace/not-found.ts +119 -0
- package/src/modules/workspace/session-ontology.gate.ts +1 -1
- package/src/modules/workspace/startup/__tests__/beside-checkout.test.ts +181 -0
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.degraded.test.ts +361 -0
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +53 -16
- package/src/modules/workspace/startup/beside-checkout.ts +112 -0
- package/src/modules/workspace/startup/kb-git.ts +43 -36
- package/src/modules/workspace/startup/kb-startup-runner.ts +303 -63
- package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +1215 -73
- package/src/modules/workspace/startup/steps/groups-to-plugins.step.ts +460 -417
- package/src/modules/workspace/startup/steps/personal-spaces.step.ts +191 -0
- package/src/modules/workspace/startup/steps/plugin-display-names.step.ts +169 -0
- package/src/modules/workspace/startup/steps/plugin-layout.ts +110 -0
- package/src/modules/workspace/startup/steps/plugin-manifests.step.ts +46 -85
- package/src/modules/workspace/startup/steps/roles-yaml.step.ts +6 -12
- package/src/modules/workspace/startup/steps/seed-tree.ts +159 -78
- package/src/modules/workspace/startup/steps/template-files.step.ts +492 -141
- package/src/modules/workspace/startup/steps/template-source.ts +168 -0
- package/src/modules/workspace/workspace.routes.ts +687 -257
- package/src/modules/workspace/workspace.service.ts +1000 -303
- package/src/modules/workspace/workspace.tools.ts +1823 -180
- package/src/modules/workspace/write-denial.ts +189 -0
- package/src/shared/__tests__/canonical-file-identity.test.ts +154 -0
- package/src/shared/__tests__/email-identity.test.ts +35 -0
- package/src/shared/__tests__/filename.test.ts +0 -0
- package/src/shared/__tests__/frontmatter-id.test.ts +5 -0
- package/src/shared/__tests__/git-failure.test.ts +198 -0
- package/src/shared/__tests__/git-internals.test.ts +156 -0
- package/src/shared/__tests__/http-errors.test.ts +42 -0
- package/src/shared/__tests__/logging.test.ts +185 -0
- package/src/shared/__tests__/path-containment.test.ts +60 -0
- package/src/shared/__tests__/pending-proposals.readAt.test.ts +102 -0
- package/src/shared/__tests__/printable.test.ts +64 -0
- package/src/shared/__tests__/redact-secret.test.ts +122 -0
- package/src/shared/__tests__/rename-no-replace.test.ts +466 -0
- package/src/shared/__tests__/ssrf.test.ts +20 -0
- package/src/shared/canonical-file-identity.ts +92 -0
- package/src/shared/domain-errors.ts +149 -0
- package/src/shared/email-identity.ts +38 -0
- package/src/shared/fs.contract.ts +225 -0
- package/src/shared/git-failure.ts +196 -0
- package/src/shared/git-internals.ts +179 -0
- package/src/shared/git.contract.ts +170 -0
- package/src/shared/http-errors.ts +36 -0
- package/src/shared/logger.contract.ts +141 -0
- package/src/shared/logging.ts +96 -0
- package/src/shared/path-containment.ts +34 -0
- package/src/shared/pending-proposals.ts +161 -0
- package/src/shared/printable.ts +68 -0
- package/src/shared/redact-secret.ts +118 -0
- package/src/shared/rename-no-replace.ts +386 -0
- package/src/shared/ssrf.ts +44 -16
- package/src/version.ts +10 -6
- package/dist/modules/access/access-errors.d.ts +0 -34
- package/dist/modules/access/access-errors.d.ts.map +0 -1
- package/dist/modules/access/access-errors.js +0 -40
- package/dist/modules/access/access-errors.js.map +0 -1
- package/dist/modules/access/access-splice.d.ts +0 -140
- package/dist/modules/access/access-splice.d.ts.map +0 -1
- package/dist/modules/access/access-splice.js +0 -389
- package/dist/modules/access/access-splice.js.map +0 -1
- package/dist/modules/access/group-files.d.ts +0 -83
- package/dist/modules/access/group-files.d.ts.map +0 -1
- package/dist/modules/access/group-files.js +0 -167
- package/dist/modules/access/group-files.js.map +0 -1
- package/dist/modules/access/kb-read-filter.d.ts +0 -41
- package/dist/modules/access/kb-read-filter.d.ts.map +0 -1
- package/dist/modules/access/kb-read-filter.js +0 -60
- package/dist/modules/access/kb-read-filter.js.map +0 -1
- package/dist/modules/access/render-roles-yaml.d.ts +0 -22
- package/dist/modules/access/render-roles-yaml.d.ts.map +0 -1
- package/dist/modules/access/render-roles-yaml.js +0 -56
- package/dist/modules/access/render-roles-yaml.js.map +0 -1
- package/dist/modules/access/roles-yaml-guard.d.ts +0 -56
- package/dist/modules/access/roles-yaml-guard.d.ts.map +0 -1
- package/dist/modules/access/roles-yaml-guard.js +0 -79
- package/dist/modules/access/roles-yaml-guard.js.map +0 -1
- package/dist/modules/diff/diff-paths.d.ts +0 -2
- package/dist/modules/diff/diff-paths.d.ts.map +0 -1
- package/dist/modules/diff/diff-paths.js +0 -9
- package/dist/modules/diff/diff-paths.js.map +0 -1
- package/dist/modules/groups/group-provision.service.d.ts +0 -130
- package/dist/modules/groups/group-provision.service.d.ts.map +0 -1
- package/dist/modules/groups/group-provision.service.js +0 -288
- package/dist/modules/groups/group-provision.service.js.map +0 -1
- package/dist/modules/groups/groups.contract.d.ts +0 -106
- package/dist/modules/groups/groups.contract.d.ts.map +0 -1
- package/dist/modules/groups/groups.contract.js +0 -36
- package/dist/modules/groups/groups.contract.js.map +0 -1
- package/dist/modules/groups/groups.routes.d.ts +0 -42
- package/dist/modules/groups/groups.routes.d.ts.map +0 -1
- package/dist/modules/groups/groups.routes.js +0 -379
- package/dist/modules/groups/groups.routes.js.map +0 -1
- package/dist/modules/groups/groups.service.d.ts +0 -60
- package/dist/modules/groups/groups.service.d.ts.map +0 -1
- package/dist/modules/groups/groups.service.js +0 -172
- package/dist/modules/groups/groups.service.js.map +0 -1
- package/dist/modules/groups/index.d.ts +0 -7
- package/dist/modules/groups/index.d.ts.map +0 -1
- package/dist/modules/groups/index.js +0 -6
- package/dist/modules/groups/index.js.map +0 -1
- package/dist/modules/groups/join-proposals.d.ts +0 -53
- package/dist/modules/groups/join-proposals.d.ts.map +0 -1
- package/dist/modules/groups/join-proposals.js +0 -67
- package/dist/modules/groups/join-proposals.js.map +0 -1
- package/dist/modules/groups/join-requests.service.d.ts +0 -81
- package/dist/modules/groups/join-requests.service.d.ts.map +0 -1
- package/dist/modules/groups/join-requests.service.js +0 -135
- package/dist/modules/groups/join-requests.service.js.map +0 -1
- package/dist/modules/mcp/mcp-session-store.d.ts +0 -74
- package/dist/modules/mcp/mcp-session-store.d.ts.map +0 -1
- package/dist/modules/mcp/mcp-session-store.js +0 -131
- package/dist/modules/mcp/mcp-session-store.js.map +0 -1
- package/dist/modules/workflow/file-change-notifier.d.ts +0 -38
- package/dist/modules/workflow/file-change-notifier.d.ts.map +0 -1
- package/dist/modules/workflow/file-change-notifier.js +0 -22
- package/dist/modules/workflow/file-change-notifier.js.map +0 -1
- package/dist/modules/workflow/git/branch-name.d.ts +0 -10
- package/dist/modules/workflow/git/branch-name.d.ts.map +0 -1
- package/dist/modules/workflow/git/branch-name.js +0 -76
- package/dist/modules/workflow/git/branch-name.js.map +0 -1
- package/dist/modules/workflow/git/clone-config.d.ts +0 -61
- package/dist/modules/workflow/git/clone-config.d.ts.map +0 -1
- package/dist/modules/workflow/git/clone-config.js +0 -69
- package/dist/modules/workflow/git/clone-config.js.map +0 -1
- package/dist/modules/workflow/git/mutex.d.ts +0 -11
- package/dist/modules/workflow/git/mutex.d.ts.map +0 -1
- package/dist/modules/workflow/git/mutex.js +0 -23
- package/dist/modules/workflow/git/mutex.js.map +0 -1
- package/dist/modules/workflow/locking-filesystem.d.ts +0 -137
- package/dist/modules/workflow/locking-filesystem.d.ts.map +0 -1
- package/dist/modules/workflow/locking-filesystem.js +0 -553
- package/dist/modules/workflow/locking-filesystem.js.map +0 -1
- package/dist/modules/workflow/read-only-filesystem.d.ts +0 -24
- package/dist/modules/workflow/read-only-filesystem.d.ts.map +0 -1
- package/dist/modules/workflow/read-only-filesystem.js +0 -39
- package/dist/modules/workflow/read-only-filesystem.js.map +0 -1
- package/dist/modules/workflow/workflow.errors.d.ts +0 -197
- package/dist/modules/workflow/workflow.errors.d.ts.map +0 -1
- package/dist/modules/workflow/workflow.errors.js +0 -298
- package/dist/modules/workflow/workflow.errors.js.map +0 -1
- package/dist/modules/workspace/bevel-ignore.d.ts.map +0 -1
- package/dist/modules/workspace/bevel-ignore.js.map +0 -1
- package/dist/modules/workspace/kb-seed.interface.d.ts +0 -36
- package/dist/modules/workspace/kb-seed.interface.d.ts.map +0 -1
- package/dist/modules/workspace/kb-seed.interface.js +0 -2
- package/dist/modules/workspace/kb-seed.interface.js.map +0 -1
- package/dist/modules/workspace/kb-seed.service.d.ts +0 -125
- package/dist/modules/workspace/kb-seed.service.d.ts.map +0 -1
- package/dist/modules/workspace/kb-seed.service.js +0 -535
- package/dist/modules/workspace/kb-seed.service.js.map +0 -1
- package/dist/modules/workspace/plugins-migration.d.ts +0 -50
- package/dist/modules/workspace/plugins-migration.d.ts.map +0 -1
- package/dist/modules/workspace/plugins-migration.js +0 -379
- package/dist/modules/workspace/plugins-migration.js.map +0 -1
- package/dist/shared/fs-errors.d.ts +0 -8
- package/dist/shared/fs-errors.d.ts.map +0 -1
- package/dist/shared/fs-errors.js +0 -11
- package/dist/shared/fs-errors.js.map +0 -1
- package/dist/shared/fs-walk.d.ts +0 -23
- package/dist/shared/fs-walk.d.ts.map +0 -1
- package/dist/shared/fs-walk.js +0 -42
- package/dist/shared/fs-walk.js.map +0 -1
- package/dist/shared/hash-email.d.ts +0 -11
- package/dist/shared/hash-email.d.ts.map +0 -1
- package/dist/shared/hash-email.js +0 -14
- package/dist/shared/hash-email.js.map +0 -1
- package/dist/shared/kb-walk.d.ts +0 -52
- package/dist/shared/kb-walk.d.ts.map +0 -1
- package/dist/shared/kb-walk.js +0 -76
- package/dist/shared/kb-walk.js.map +0 -1
- package/src/modules/diff/diff-paths.ts +0 -9
- package/src/modules/mcp/__tests__/mcp-session-store.test.ts +0 -151
- package/src/modules/mcp/__tests__/mcp.routes.session.test.ts +0 -226
- package/src/modules/mcp/mcp-session-store.ts +0 -162
- package/src/shared/__tests__/fs-walk.test.ts +0 -63
- package/src/shared/__tests__/kb-walk.test.ts +0 -107
- package/src/shared/fs-errors.ts +0 -10
- package/src/shared/fs-walk.ts +0 -45
- package/src/shared/hash-email.ts +0 -14
- package/src/shared/kb-walk.ts +0 -95
|
@@ -1,13 +1,41 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
|
+
import { logger } from '../../shared/logging.js';
|
|
3
|
+
|
|
4
|
+
const log = logger('workspace');
|
|
2
5
|
import path from 'node:path';
|
|
3
|
-
import { execFile } from 'node:child_process';
|
|
4
|
-
import { promisify } from 'node:util';
|
|
5
6
|
import AdmZip from 'adm-zip';
|
|
6
7
|
import type { AuthUser, IWorkspaceService, WorkspaceInfo, FileTreeEntry } from '@bevel-software/platform-shared';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
8
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
9
|
+
import {
|
|
10
|
+
validateRelativePath,
|
|
11
|
+
validateFilename,
|
|
12
|
+
DEFAULT_BRANCH,
|
|
13
|
+
FOLDER_PLACEHOLDER,
|
|
14
|
+
isFolderPlaceholder,
|
|
15
|
+
entryExistsMessage,
|
|
16
|
+
type ExistingEntryKind,
|
|
17
|
+
} from '@bevel-software/platform-shared';
|
|
18
|
+
import { isAbsence, type ITreeWalker, type TreeWalkOptions } from '../../shared/fs.contract.js';
|
|
19
|
+
import {
|
|
20
|
+
DestinationTakenError,
|
|
21
|
+
inspectDestination,
|
|
22
|
+
renameNoReplace,
|
|
23
|
+
} from '../../shared/rename-no-replace.js';
|
|
24
|
+
import type { IGitRunner } from '../../shared/git.contract.js';
|
|
25
|
+
import { NodeGitRunner } from '../workflow/git/node-git-runner.js';
|
|
26
|
+
import { assertWithinDirectory } from '../../shared/path-containment.js';
|
|
27
|
+
import { assertRepoRootNameFree, normalizeWorkspacePath } from '../kb-fs/repo-path.js';
|
|
28
|
+
import { assertNoGitInternalsSegment, assertNotGitInternals, hasGitInternalsSegment } from '../../shared/git-internals.js';
|
|
29
|
+
import {
|
|
30
|
+
GitInternalsError,
|
|
31
|
+
PathNotFoundError,
|
|
32
|
+
PathTraversalError,
|
|
33
|
+
UnreadableArchiveError,
|
|
34
|
+
WorkflowValidationError,
|
|
35
|
+
} from '../../shared/domain-errors.js';
|
|
9
36
|
import { workspaceIdForBranch, branchForWorkspaceId } from '../../shared/workspace-id.js';
|
|
10
37
|
import {
|
|
38
|
+
BranchNotFoundError,
|
|
11
39
|
RemoteBranchGoneError,
|
|
12
40
|
WorkflowDomainError,
|
|
13
41
|
isMissingRemoteBranchFailure,
|
|
@@ -21,6 +49,11 @@ import {
|
|
|
21
49
|
} from '../kb-fs/clone-config.js';
|
|
22
50
|
import type { IDiffService } from '../diff/diff.interface.js';
|
|
23
51
|
|
|
52
|
+
/** One held path turn: the settled tail of the nested mutations launched inside it. */
|
|
53
|
+
interface HeldTurn {
|
|
54
|
+
tail: Promise<void>;
|
|
55
|
+
}
|
|
56
|
+
|
|
24
57
|
/**
|
|
25
58
|
* Workspaces are per-branch, not per-user (PLAN §3). One on-disk clone per
|
|
26
59
|
* branch name, shared by every user editing that branch — coordination of
|
|
@@ -76,7 +109,36 @@ export class FolderTooLargeError extends Error {
|
|
|
76
109
|
}
|
|
77
110
|
}
|
|
78
111
|
|
|
79
|
-
|
|
112
|
+
/**
|
|
113
|
+
* A rename or move would land on a name something else already has. 409, like
|
|
114
|
+
* every other precondition the caller has to clear first: nothing was moved,
|
|
115
|
+
* and the entry already there is untouched.
|
|
116
|
+
*
|
|
117
|
+
* Lives here rather than in `shared/domain-errors.ts` because the sentence is
|
|
118
|
+
* built by `entryExistsMessage` and that file deliberately imports no VALUE
|
|
119
|
+
* from `@bevel-software/platform-shared`, only a type. It still extends
|
|
120
|
+
* `WorkflowDomainError`, so the routes' `sendError` maps it to 409 with
|
|
121
|
+
* `{ kind: 'entry-exists', … }` without knowing this class exists.
|
|
122
|
+
*/
|
|
123
|
+
export class EntryExistsError extends WorkflowDomainError {
|
|
124
|
+
readonly kind = 'entry-exists' as const;
|
|
125
|
+
constructor(readonly entryKind: ExistingEntryKind, readonly destination: string) {
|
|
126
|
+
super(entryExistsMessage(entryKind, destination), 409, {
|
|
127
|
+
kind: 'entry-exists',
|
|
128
|
+
entryKind,
|
|
129
|
+
destination,
|
|
130
|
+
});
|
|
131
|
+
this.name = 'EntryExistsError';
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* A floor under the git deadline for a clone: a first clone of a large
|
|
137
|
+
* knowledge base over a slow link legitimately takes minutes, and the port's
|
|
138
|
+
* default is sized for a running deployment's operations. A configured
|
|
139
|
+
* ceiling above this applies as configured.
|
|
140
|
+
*/
|
|
141
|
+
const CLONE_TIMEOUT_MS = 600_000;
|
|
80
142
|
|
|
81
143
|
/**
|
|
82
144
|
* Identity stamped on the per-branch clone's git config. Every workflow
|
|
@@ -96,14 +158,75 @@ function redactError(err: unknown): string {
|
|
|
96
158
|
|
|
97
159
|
const FETCH_CACHE_TTL_MS = 30_000;
|
|
98
160
|
|
|
161
|
+
/**
|
|
162
|
+
* The bytes a conditional write compares against. An absent file reads as the
|
|
163
|
+
* empty string, so `expectedContent: ''` means "expect nothing there yet".
|
|
164
|
+
*
|
|
165
|
+
* A directory at the path is a client error, not a 500 with a raw OS message:
|
|
166
|
+
* `GET /file` counts EISDIR as "no file at this path", so the editor that
|
|
167
|
+
* opened empty on that 404 would otherwise be told its save failed for an
|
|
168
|
+
* unexplained internal reason. Anything else (EACCES, EIO) is a real read
|
|
169
|
+
* failure and propagates: an unreadable file must not pass for an empty one.
|
|
170
|
+
*/
|
|
171
|
+
async function readForConditionalWrite(absolutePath: string, relativePath: string): Promise<string> {
|
|
172
|
+
try {
|
|
173
|
+
return await fs.readFile(absolutePath, 'utf-8');
|
|
174
|
+
} catch (err) {
|
|
175
|
+
if (isAbsence(err)) return '';
|
|
176
|
+
const code = (err as NodeJS.ErrnoException).code;
|
|
177
|
+
if (code === 'EISDIR') {
|
|
178
|
+
const notAFile: Error & { status?: number } = new Error(
|
|
179
|
+
`"${relativePath}" is a directory, not a file.`,
|
|
180
|
+
);
|
|
181
|
+
notAFile.status = 400;
|
|
182
|
+
throw notAFile;
|
|
183
|
+
}
|
|
184
|
+
throw err;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* The published library's path rule, refused as a TYPED 400.
|
|
190
|
+
*
|
|
191
|
+
* The library throws a bare `Error('Invalid path: …')` — it is shared with
|
|
192
|
+
* the frontend and cannot carry a backend domain type — and the routes used
|
|
193
|
+
* to recognise that by its message prefix. Converting it here, at the one
|
|
194
|
+
* layer that calls it, is what lets the route layer read a status off a
|
|
195
|
+
* class like it does for every other refusal.
|
|
196
|
+
*/
|
|
197
|
+
function assertValidPath(relativePath: string): void {
|
|
198
|
+
const reason = validateRelativePath(relativePath);
|
|
199
|
+
if (reason) throw new WorkflowValidationError(`Invalid path: ${reason}`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Throw the conditional write's 409 unless the file still holds `expectedContent`. */
|
|
203
|
+
async function assertConditionalWriteMatches(
|
|
204
|
+
absolutePath: string,
|
|
205
|
+
relativePath: string,
|
|
206
|
+
expectedContent: string,
|
|
207
|
+
): Promise<void> {
|
|
208
|
+
const current = await readForConditionalWrite(absolutePath, relativePath);
|
|
209
|
+
if (current === expectedContent) return;
|
|
210
|
+
const stale: Error & { status?: number } = new Error(
|
|
211
|
+
`"${relativePath}" changed since you opened it. Reload it and apply your edit again, ` +
|
|
212
|
+
'so the other change is not overwritten.',
|
|
213
|
+
);
|
|
214
|
+
stale.status = 409;
|
|
215
|
+
throw stale;
|
|
216
|
+
}
|
|
217
|
+
|
|
99
218
|
/**
|
|
100
219
|
* Per-directory read filter for the file tree. Given a batch of
|
|
101
220
|
* workspace-relative entry paths (e.g. `staging-repo/Product/Knowledge/x.md`),
|
|
102
221
|
* returns a verdict map keyed by those paths (`path → readable`). Injected by
|
|
103
222
|
* the route from the access service so `WorkspaceService` stays access-agnostic
|
|
104
223
|
* (it gets a function, not the access module). See `modules/access-model/kb-read-filter.ts`.
|
|
224
|
+
*
|
|
225
|
+
* `'unlisted'` hides an entry WITHOUT counting it as withheld: a listing
|
|
226
|
+
* decision (the admin-only `.bevelignore`) rather than content the caller's
|
|
227
|
+
* read rules keep from them, so it must not tell them something is being kept.
|
|
105
228
|
*/
|
|
106
|
-
export type ReadTreeFilter = (wsRelPaths: string[]) => Promise<Map<string, boolean>>;
|
|
229
|
+
export type ReadTreeFilter = (wsRelPaths: string[]) => Promise<Map<string, boolean | 'unlisted'>>;
|
|
107
230
|
|
|
108
231
|
export class WorkspaceService implements IWorkspaceService {
|
|
109
232
|
/** Maps branch → absolute directory path. Lazily populated. */
|
|
@@ -121,6 +244,32 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
121
244
|
/** Whether the last fetch per repo SUCCEEDED — read by strict callers of `ensureRemotesFetched`. */
|
|
122
245
|
private readonly lastFetchOk = new Map<string, boolean>();
|
|
123
246
|
private readonly inFlightFetches = new Map<string, Promise<void>>();
|
|
247
|
+
/**
|
|
248
|
+
* One mutation at a time per file, keyed by RESOLVED absolute path — so
|
|
249
|
+
* `x/a.md`, `./x/a.md` and `x//a.md` are one queue and not three. See
|
|
250
|
+
* {@link withPathTurn} for what takes a turn and why.
|
|
251
|
+
*/
|
|
252
|
+
private readonly writeTurns = new Map<string, Promise<void>>();
|
|
253
|
+
/**
|
|
254
|
+
* The folder structure changes in flight, keyed by RESOLVED absolute folder
|
|
255
|
+
* path — see {@link withFolderTurn}.
|
|
256
|
+
*/
|
|
257
|
+
private readonly folderTurns = new Map<string, Promise<void>>();
|
|
258
|
+
/**
|
|
259
|
+
* The turns the CURRENT async context already holds. Taking one it holds
|
|
260
|
+
* runs straight through instead of waiting for itself, which is what lets a
|
|
261
|
+
* caller wrap a whole read-decide-write sequence in a turn and still call
|
|
262
|
+
* the ordinary write inside it.
|
|
263
|
+
*/
|
|
264
|
+
/**
|
|
265
|
+
* The turns the current async context holds, each with the tail of the
|
|
266
|
+
* nested mutations already launched inside it — so a re-entrant call never
|
|
267
|
+
* waits for the outer turn (that would hang) while two nested calls on the
|
|
268
|
+
* same path, launched side by side inside one turn, still run one at a
|
|
269
|
+
* time. The map is inherited by every child context; the entry object is
|
|
270
|
+
* shared, which is what makes siblings queue on one tail.
|
|
271
|
+
*/
|
|
272
|
+
private readonly heldTurns = new AsyncLocalStorage<ReadonlyMap<string, HeldTurn>>();
|
|
124
273
|
|
|
125
274
|
/**
|
|
126
275
|
* Late-bound by the composition root — DiffService depends on
|
|
@@ -149,7 +298,15 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
149
298
|
*/
|
|
150
299
|
kbRepoUrl: string | (() => string),
|
|
151
300
|
private readonly kbDirName: string,
|
|
301
|
+
private readonly disk: ITreeWalker,
|
|
152
302
|
gitUsername: string | (() => string) = 'x-access-token',
|
|
303
|
+
/**
|
|
304
|
+
* How git is run — see `shared/git.contract.ts`. The composition root
|
|
305
|
+
* passes the one runner carrying the deployment's deadline; the default
|
|
306
|
+
* is the same runner on its default deadline, for a directly constructed
|
|
307
|
+
* service.
|
|
308
|
+
*/
|
|
309
|
+
private readonly gitRunner: IGitRunner = new NodeGitRunner(),
|
|
153
310
|
) {
|
|
154
311
|
this.kbRepoUrl = typeof kbRepoUrl === 'function' ? kbRepoUrl : () => kbRepoUrl;
|
|
155
312
|
this.gitUsername = typeof gitUsername === 'function' ? gitUsername : () => gitUsername;
|
|
@@ -166,6 +323,77 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
166
323
|
this.onWorkspaceCloned = listener;
|
|
167
324
|
}
|
|
168
325
|
|
|
326
|
+
/**
|
|
327
|
+
* Every branch name this process has been shown to be real: named by a
|
|
328
|
+
* listing of origin's branches, or registered as a clone of ours. Together
|
|
329
|
+
* with the clones still on disk this is the whole of what the platform
|
|
330
|
+
* KNOWS about branch names — and the difference between "your link is to a
|
|
331
|
+
* branch that was deleted" (410) and "there is no branch by that name"
|
|
332
|
+
* (404).
|
|
333
|
+
*
|
|
334
|
+
* Names ACCUMULATE and are never removed, and the two halves of that are
|
|
335
|
+
* the same rule: the evidence we need is evidence about a branch that is
|
|
336
|
+
* no longer there. Replacing the set on each listing would forget a
|
|
337
|
+
* deleted branch at the moment its deletion becomes the thing we have to
|
|
338
|
+
* report; evicting an old name would silently turn a long-dead branch back
|
|
339
|
+
* into "never existed"; dropping a clone's name when its workspace is swept
|
|
340
|
+
* would do that to a branch we cloned ourselves. So nothing here is ever
|
|
341
|
+
* dropped. Growth is one short string per distinct branch name origin has
|
|
342
|
+
* ever shown this process — kilobytes for any real repository, and reset
|
|
343
|
+
* by a restart.
|
|
344
|
+
*
|
|
345
|
+
* In memory only, by the spec's decision — no new persistence. A restart
|
|
346
|
+
* forgets a deleted branch whose clone was already retired, and that name
|
|
347
|
+
* then reads as unknown, which is exactly what the platform then knows.
|
|
348
|
+
*/
|
|
349
|
+
private readonly branchesEverHeardOf = new Set<string>();
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Record the branch names a listing returned. Called by the git layer after
|
|
353
|
+
* every `listBranches` (wired in the composition root), which is the one
|
|
354
|
+
* place the platform ever sees origin's set of branches.
|
|
355
|
+
*/
|
|
356
|
+
noteBranchesListed(names: readonly string[]): void {
|
|
357
|
+
for (const name of names) this.branchesEverHeardOf.add(name);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Register a branch's clone directory — the only writer of `branchDirs`, so
|
|
362
|
+
* that every clone this process opens (fresh, adopted off disk, or handed
|
|
363
|
+
* over by a concurrent bootstrap) also leaves its branch name in
|
|
364
|
+
* `branchesEverHeardOf`. `branchDirs` is a cache of live clones and loses the
|
|
365
|
+
* branch when its workspace is swept; the name left behind is the record
|
|
366
|
+
* that the branch was real, and outlives the clone.
|
|
367
|
+
*/
|
|
368
|
+
private registerBranchDir(branch: string, workspaceDir: string): void {
|
|
369
|
+
this.branchDirs.set(branch, workspaceDir);
|
|
370
|
+
this.branchesEverHeardOf.add(branch);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Has the platform ever heard of this branch? A clone of it — registered in
|
|
375
|
+
* this process, remembered from one, or sitting on disk from a previous one
|
|
376
|
+
* — or a listing that named it. Only asked on the failure path, where
|
|
377
|
+
* origin has just answered that it has no such ref: a yes means the branch
|
|
378
|
+
* was deleted (410), a no means it never existed as far as the platform is
|
|
379
|
+
* concerned (404).
|
|
380
|
+
*/
|
|
381
|
+
private async hasHeardOfBranch(branch: string): Promise<boolean> {
|
|
382
|
+
if (this.branchDirs.has(branch)) return true;
|
|
383
|
+
if (this.branchesEverHeardOf.has(branch)) return true;
|
|
384
|
+
try {
|
|
385
|
+
await fs.access(path.join(this.workspacesRoot, workspaceIdForBranch(branch), this.kbDirName, '.git'));
|
|
386
|
+
return true;
|
|
387
|
+
} catch (err) {
|
|
388
|
+
// Only "there is nothing there" answers the question. A permission or
|
|
389
|
+
// I/O fault means we could not look, and a storage failure of ours must
|
|
390
|
+
// not be reported to the caller as a branch that never existed: it
|
|
391
|
+
// stays the 500 the operator reads.
|
|
392
|
+
if (isAbsence(err)) return false;
|
|
393
|
+
throw err;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
169
397
|
/**
|
|
170
398
|
* Build git clone args. Credentials go in as `clone --config`, which both
|
|
171
399
|
* authenticates this clone and leaves the helper in the new repo's config for
|
|
@@ -279,8 +507,7 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
279
507
|
try {
|
|
280
508
|
entries = await fs.readdir(this.workspacesRoot, { withFileTypes: true });
|
|
281
509
|
} catch (err) {
|
|
282
|
-
|
|
283
|
-
if (code === 'ENOENT' || code === 'ENOTDIR') return [];
|
|
510
|
+
if (isAbsence(err)) return [];
|
|
284
511
|
throw err;
|
|
285
512
|
}
|
|
286
513
|
const cloned: Array<{ id: string; branch: string; unreadable?: string }> = [];
|
|
@@ -300,8 +527,7 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
300
527
|
try {
|
|
301
528
|
await fs.access(path.join(this.workspacesRoot, entry.name, this.kbDirName, '.git'));
|
|
302
529
|
} catch (err) {
|
|
303
|
-
|
|
304
|
-
if (code === 'ENOENT' || code === 'ENOTDIR') continue;
|
|
530
|
+
if (isAbsence(err)) continue;
|
|
305
531
|
cloned.push({
|
|
306
532
|
id: entry.name,
|
|
307
533
|
branch,
|
|
@@ -330,24 +556,23 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
330
556
|
branch: string,
|
|
331
557
|
referenceRepo: string | null,
|
|
332
558
|
): Promise<void> {
|
|
559
|
+
// Long enough for a first clone, whatever the configured ceiling says.
|
|
560
|
+
const timeoutMs = Math.max(this.gitRunner.defaultTimeoutMs, CLONE_TIMEOUT_MS);
|
|
333
561
|
if (referenceRepo) {
|
|
334
562
|
try {
|
|
335
|
-
await
|
|
336
|
-
|
|
563
|
+
await this.gitRunner.run(path.dirname(targetDir), this.gitCloneArgs(targetDir, branch, referenceRepo), {
|
|
564
|
+
timeoutMs,
|
|
337
565
|
});
|
|
338
566
|
return;
|
|
339
567
|
} catch (err) {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
);
|
|
568
|
+
log.warn(`referenced clone for "${branch}" failed, retrying without reference:`, {
|
|
569
|
+
detail: redactError(err),
|
|
570
|
+
});
|
|
344
571
|
// Clear any partial output so the retry clones into a clean dir.
|
|
345
572
|
await fs.rm(targetDir, { recursive: true, force: true }).catch(() => {});
|
|
346
573
|
}
|
|
347
574
|
}
|
|
348
|
-
await
|
|
349
|
-
env: { ...process.env },
|
|
350
|
-
});
|
|
575
|
+
await this.gitRunner.run(path.dirname(targetDir), this.gitCloneArgs(targetDir, branch), { timeoutMs });
|
|
351
576
|
}
|
|
352
577
|
|
|
353
578
|
/**
|
|
@@ -398,7 +623,7 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
398
623
|
// pulls it. Once per branch per process — the cached paths above return
|
|
399
624
|
// before reaching here.
|
|
400
625
|
await this.normalizeCloneConfig(repoDir, branch);
|
|
401
|
-
this.
|
|
626
|
+
this.registerBranchDir(branch, workspaceDir);
|
|
402
627
|
return this.buildWorkspaceInfo(branch, workspaceDir);
|
|
403
628
|
} catch {
|
|
404
629
|
// Not on disk — bootstrap below.
|
|
@@ -417,7 +642,7 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
417
642
|
const existingBootstrap = this.inFlightBootstraps.get(branch);
|
|
418
643
|
if (existingBootstrap) {
|
|
419
644
|
await existingBootstrap;
|
|
420
|
-
this.
|
|
645
|
+
this.registerBranchDir(branch, workspaceDir);
|
|
421
646
|
return this.buildWorkspaceInfo(branch, workspaceDir);
|
|
422
647
|
}
|
|
423
648
|
this.inFlightBootstraps.set(branch, bootstrap);
|
|
@@ -427,7 +652,7 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
427
652
|
// naturally; seeding the remote is the KB startup phase's job, at boot.
|
|
428
653
|
await fs.mkdir(workspaceDir, { recursive: true });
|
|
429
654
|
await this.cloneProcessMapForBranch(workspaceDir, branch);
|
|
430
|
-
this.
|
|
655
|
+
this.registerBranchDir(branch, workspaceDir);
|
|
431
656
|
resolveBootstrap();
|
|
432
657
|
} catch (err) {
|
|
433
658
|
this.branchDirs.delete(branch);
|
|
@@ -511,15 +736,12 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
511
736
|
private async normalizeCloneConfig(repoDir: string, branch: string): Promise<void> {
|
|
512
737
|
try {
|
|
513
738
|
for (const args of cloneTrackingConfigArgs(branch)) {
|
|
514
|
-
await
|
|
739
|
+
await this.gitRunner.run(repoDir, args);
|
|
515
740
|
}
|
|
516
741
|
} catch (err) {
|
|
517
742
|
// One line per branch, not per key: every key writes to the same
|
|
518
743
|
// `.git/config`, so what fails for one fails for all.
|
|
519
|
-
|
|
520
|
-
`[workspace] could not normalize the config of the "${branch}" clone:`,
|
|
521
|
-
redactError(err),
|
|
522
|
-
);
|
|
744
|
+
log.warn(`could not normalize the config of the "${branch}" clone:`, { detail: redactError(err) });
|
|
523
745
|
}
|
|
524
746
|
await this.stampCredentialHelper(repoDir, branch);
|
|
525
747
|
}
|
|
@@ -560,28 +782,24 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
560
782
|
// re-clone that fails on the same validation — masking the real cause.
|
|
561
783
|
// Contain it as a loud non-stamp instead; `normalizeCloneConfig`'s
|
|
562
784
|
// never-throws contract stays true.
|
|
563
|
-
|
|
564
|
-
`[workspace] refusing to stamp the credential helper of the "${branch}" clone:`,
|
|
565
|
-
redactError(err),
|
|
566
|
-
);
|
|
785
|
+
log.warn(`refusing to stamp the credential helper of the "${branch}" clone:`, { detail: redactError(err) });
|
|
567
786
|
this.stampedCredentialFingerprint.delete(branch);
|
|
568
787
|
return;
|
|
569
788
|
}
|
|
570
789
|
let stamped = true;
|
|
571
790
|
for (const args of argLists) {
|
|
572
791
|
try {
|
|
573
|
-
await
|
|
792
|
+
await this.gitRunner.run(repoDir, args);
|
|
574
793
|
} catch (err) {
|
|
575
794
|
// `--unset-all` with no matching value (exit 5) is the expected no-op
|
|
576
795
|
// for a clone that never carried an app helper; anything else is a
|
|
577
796
|
// real failure.
|
|
578
|
-
const code = (err as {
|
|
797
|
+
const code = (err as { exitCode?: number } | null)?.exitCode;
|
|
579
798
|
if (!(args.includes('--unset-all') && code === 5)) {
|
|
580
799
|
stamped = false;
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
);
|
|
800
|
+
log.warn(`could not stamp the credential helper of the "${branch}" clone:`, {
|
|
801
|
+
detail: redactError(err),
|
|
802
|
+
});
|
|
585
803
|
}
|
|
586
804
|
}
|
|
587
805
|
}
|
|
@@ -618,19 +836,25 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
618
836
|
// propagate to the caller instead of getting silently swallowed by an
|
|
619
837
|
// outer catch (the previous shape let an inner rethrow fall through
|
|
620
838
|
// to a clone-into-non-empty-dir, producing a confusing 500).
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
839
|
+
//
|
|
840
|
+
// A FILE squatting the clone's name is refused outright, never wiped:
|
|
841
|
+
// the recovery below exists for a crashed bootstrap's directory shell,
|
|
842
|
+
// and a regular file here is a state a human put the deployment in — so
|
|
843
|
+
// it is named and stopped, the same stance the startup phase takes on a
|
|
844
|
+
// squatted reserved root. (Links are followed: one clone mounted
|
|
845
|
+
// elsewhere is the operator's business, as it is at the workspace root.)
|
|
846
|
+
const target = await fs.stat(targetDir).catch((err: unknown) => {
|
|
847
|
+
if (isAbsence(err)) return null;
|
|
848
|
+
throw err;
|
|
849
|
+
});
|
|
850
|
+
if (target !== null && !target.isDirectory()) {
|
|
851
|
+
throw new Error(
|
|
852
|
+
`The clone path "${this.kbDirName}" in this workspace exists but is not a directory. ` +
|
|
853
|
+
'Remove or rename it — the platform requires this name to be the knowledge-base clone.',
|
|
854
|
+
);
|
|
631
855
|
}
|
|
632
856
|
|
|
633
|
-
if (
|
|
857
|
+
if (target !== null) {
|
|
634
858
|
// The directory exists, but a previous bootstrap may have crashed
|
|
635
859
|
// mid-clone — leaving a directory shell without `.git`. Treat
|
|
636
860
|
// anything missing `.git` as not-cloned and re-clone. (Can't be
|
|
@@ -647,10 +871,9 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
647
871
|
// re-clone". A transient EACCES / EIO / EBUSY must NOT delete
|
|
648
872
|
// what might be a perfectly valid repo whose `.git` we couldn't
|
|
649
873
|
// read this moment — surface the error so the caller can retry.
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
}
|
|
874
|
+
// (`targetDir` is known to be a directory here, so ENOTDIR can only
|
|
875
|
+
// mean `.git` itself vanished under us — an absence either way.)
|
|
876
|
+
if (!isAbsence(err)) throw err;
|
|
654
877
|
}
|
|
655
878
|
if (alreadyCloned) {
|
|
656
879
|
// We don't auto-pull because that could clobber another user's
|
|
@@ -671,21 +894,18 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
671
894
|
await this.runClone(targetDir, branch, reference);
|
|
672
895
|
// Persist longpaths in the cloned repo's config so subsequent
|
|
673
896
|
// checkouts also honor it.
|
|
674
|
-
await
|
|
897
|
+
await this.gitRunner.run(targetDir, ['config', 'core.longpaths', 'true']);
|
|
675
898
|
// Generic bot identity for the clone — every workflow commit
|
|
676
899
|
// overrides via `--author=…` so the real human shows up in
|
|
677
900
|
// `git log`. This is purely the fallback committer.
|
|
678
|
-
await
|
|
679
|
-
await
|
|
901
|
+
await this.gitRunner.run(targetDir, ['config', 'user.name', BOT_NAME]);
|
|
902
|
+
await this.gitRunner.run(targetDir, ['config', 'user.email', BOT_EMAIL]);
|
|
680
903
|
// Pin the clone to exactly one fetch refspec and one upstream ref for
|
|
681
904
|
// this branch. `git clone -b` already produces that shape; stamping it
|
|
682
905
|
// explicitly means the shape is asserted rather than assumed, and the
|
|
683
906
|
// same call is what repairs an existing clone that drifted.
|
|
684
907
|
await this.normalizeCloneConfig(targetDir, branch);
|
|
685
|
-
|
|
686
|
-
`[workspace] Cloned ${this.kbDirName} for branch "${branch}"` +
|
|
687
|
-
(reference ? ' (referenced a sibling clone)' : ''),
|
|
688
|
-
);
|
|
908
|
+
log.info(`Cloned ${this.kbDirName} for branch "${branch}"` + (reference ? ' (referenced a sibling clone)' : ''));
|
|
689
909
|
// A fresh clone has already downloaded every ref — tell the git layer
|
|
690
910
|
// so the first `listBranches` skips the redundant implicit `git fetch`.
|
|
691
911
|
// Isolated from the clone-rollback `catch` below: a misbehaving listener
|
|
@@ -693,25 +913,41 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
693
913
|
try {
|
|
694
914
|
this.onWorkspaceCloned?.(workspaceIdForBranch(branch));
|
|
695
915
|
} catch (listenerErr) {
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
);
|
|
916
|
+
log.error(`onWorkspaceCloned listener failed for branch "${branch}":`, {
|
|
917
|
+
detail: redactError(listenerErr),
|
|
918
|
+
});
|
|
700
919
|
}
|
|
701
920
|
} catch (err) {
|
|
702
921
|
const redacted = redactError(err);
|
|
703
922
|
// Roll back partial state so the next bootstrap retries cleanly.
|
|
704
923
|
await fs.rm(targetDir, { recursive: true, force: true }).catch(() => {});
|
|
705
924
|
// A branch origin does not have is a fact about the branch, not a
|
|
706
|
-
// failure of ours
|
|
707
|
-
//
|
|
708
|
-
//
|
|
709
|
-
//
|
|
925
|
+
// failure of ours — but WHICH fact depends on whether the platform ever
|
|
926
|
+
// knew the name. A branch it cloned or listed and origin no longer has
|
|
927
|
+
// was deleted (410, "this branch no longer exists"); a name it has
|
|
928
|
+
// never seen is simply not a branch (404), and saying "no longer exists
|
|
929
|
+
// on the remote" to a typo tells the reader it once did.
|
|
710
930
|
if (isMissingRemoteBranchFailure(redacted)) {
|
|
711
|
-
|
|
712
|
-
|
|
931
|
+
let everHeardOf: boolean;
|
|
932
|
+
try {
|
|
933
|
+
everHeardOf = await this.hasHeardOfBranch(branch);
|
|
934
|
+
} catch (probeErr) {
|
|
935
|
+
// We could not read our own storage, so we cannot say which of the
|
|
936
|
+
// two stories this is — and guessing either one states something
|
|
937
|
+
// about the branch that we do not know. Our failure, so: 500.
|
|
938
|
+
log.error(`Could not tell whether branch "${branch}" was ever known here:`, {
|
|
939
|
+
detail: redactError(probeErr),
|
|
940
|
+
});
|
|
941
|
+
throw new Error(`Failed to clone process map: ${redacted}`);
|
|
942
|
+
}
|
|
943
|
+
if (everHeardOf) {
|
|
944
|
+
log.info(`branch "${branch}" is gone from origin — nothing to clone`);
|
|
945
|
+
throw new RemoteBranchGoneError(branch);
|
|
946
|
+
}
|
|
947
|
+
log.info(`branch "${branch}" has never existed here — nothing to clone`);
|
|
948
|
+
throw new BranchNotFoundError(branch);
|
|
713
949
|
}
|
|
714
|
-
|
|
950
|
+
log.error(`Failed to clone for branch "${branch}":`, { detail: redacted });
|
|
715
951
|
throw new Error(`Failed to clone process map: ${redacted}`);
|
|
716
952
|
}
|
|
717
953
|
}
|
|
@@ -739,23 +975,126 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
739
975
|
|
|
740
976
|
async listFiles(workspaceId: string, readFilter?: ReadTreeFilter): Promise<FileTreeEntry> {
|
|
741
977
|
const workspaceDir = await this.resolveWorkspaceDir(workspaceId);
|
|
742
|
-
return this.buildFileTree(workspaceDir, workspaceDir,
|
|
978
|
+
return this.buildFileTree(workspaceDir, workspaceDir, readFilter);
|
|
743
979
|
}
|
|
744
980
|
|
|
745
|
-
|
|
981
|
+
/** The repository inside a workspace directory: `<workspaceDir>/<kbDirName>`. */
|
|
982
|
+
private repoRoot(workspaceDir: string): string {
|
|
983
|
+
return path.join(workspaceDir, this.kbDirName);
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
/**
|
|
987
|
+
* THE resolution — the only place in this service that turns a workspace
|
|
988
|
+
* path into a location on disk.
|
|
989
|
+
*
|
|
990
|
+
* Two steps, and neither is optional. `normalizeWorkspacePath` places the
|
|
991
|
+
* path inside the repository (`KnowledgeBase/Report.md` is the page of that
|
|
992
|
+
* name in the checkout, not a stray beside it) and refuses `.`/`..`,
|
|
993
|
+
* backslashes and absolute paths outright. Then the RESOLVED path is checked
|
|
994
|
+
* against `<workspaceDir>/<kbDirName>` — after normalisation, because that
|
|
995
|
+
* is the path the operation will use, and a spelling that normalises inside
|
|
996
|
+
* and resolves outside is exactly the miss this check exists to catch.
|
|
997
|
+
*
|
|
998
|
+
* The git rule reads the caller's RAW spelling before either step — the
|
|
999
|
+
* whole rule, links resolved, not just the spelling. Those two refuse
|
|
1000
|
+
* `.`/`..`, backslashes and absolute paths with a message that quotes the
|
|
1001
|
+
* path back and names a corrected one, and a git path spelled any of those
|
|
1002
|
+
* ways was answered by THAT rather than by the one sanitized refusal
|
|
1003
|
+
* (measured in production), as was a LINK into the folder spelled the same
|
|
1004
|
+
* ways. Which rule speaks is not the caller's to choose.
|
|
1005
|
+
*
|
|
1006
|
+
* Callers use the returned `relativePath` for everything downstream — the
|
|
1007
|
+
* git-internals check, the diff baseline, the lock-free path turns, their own
|
|
1008
|
+
* error messages — so what is checked is what is written. Nine inline
|
|
1009
|
+
* `path.resolve(workspaceDir, …)` calls used to do this, none of them
|
|
1010
|
+
* checking the repository root; a drift-guard test now fails if one returns.
|
|
1011
|
+
*/
|
|
1012
|
+
private async resolveInsideRepo(
|
|
1013
|
+
workspaceId: string,
|
|
1014
|
+
wsPath: string,
|
|
1015
|
+
): Promise<{ workspaceDir: string; relativePath: string; absolutePath: string }> {
|
|
746
1016
|
const workspaceDir = await this.resolveWorkspaceDir(workspaceId);
|
|
1017
|
+
await assertNotGitInternals(workspaceDir, wsPath); // The RAW spelling, first — see above.
|
|
1018
|
+
const relativePath = normalizeWorkspacePath(wsPath, this.kbDirName);
|
|
747
1019
|
const absolutePath = path.resolve(workspaceDir, relativePath);
|
|
748
|
-
|
|
1020
|
+
assertWithinDirectory(absolutePath, this.repoRoot(workspaceDir));
|
|
1021
|
+
return { workspaceDir, relativePath, absolutePath };
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
async readFile(workspaceId: string, wsPath: string): Promise<string> {
|
|
1025
|
+
const { workspaceDir, relativePath, absolutePath } = await this.resolveInsideRepo(workspaceId, wsPath);
|
|
1026
|
+
assertNoGitInternalsSegment(relativePath);
|
|
1027
|
+
await assertNotGitInternals(workspaceDir, relativePath, absolutePath);
|
|
1028
|
+
await this.assertNotThroughLink(absolutePath, workspaceDir);
|
|
749
1029
|
return fs.readFile(absolutePath, 'utf-8');
|
|
750
1030
|
}
|
|
751
1031
|
|
|
752
|
-
async readFileBinary(workspaceId: string,
|
|
753
|
-
const workspaceDir = await this.
|
|
754
|
-
|
|
755
|
-
|
|
1032
|
+
async readFileBinary(workspaceId: string, wsPath: string): Promise<Buffer> {
|
|
1033
|
+
const { workspaceDir, relativePath, absolutePath } = await this.resolveInsideRepo(workspaceId, wsPath);
|
|
1034
|
+
assertNoGitInternalsSegment(relativePath);
|
|
1035
|
+
await assertNotGitInternals(workspaceDir, relativePath, absolutePath);
|
|
1036
|
+
await this.assertNotThroughLink(absolutePath, workspaceDir);
|
|
756
1037
|
return fs.readFile(absolutePath);
|
|
757
1038
|
}
|
|
758
1039
|
|
|
1040
|
+
/**
|
|
1041
|
+
* Refuse a path that reaches its file through a symbolic link — as the
|
|
1042
|
+
* final component, or as any directory between the workspace root and it.
|
|
1043
|
+
* `resolveInsideRepo`'s containment is lexical: `knowledge-base/notes.md`
|
|
1044
|
+
* passes it however the name resolves, so a link the repository carries (they only
|
|
1045
|
+
* arrive by direct git push; the app's own writes never create one) would
|
|
1046
|
+
* let a read hand out bytes from anywhere the server process can read, and
|
|
1047
|
+
* a write replace whatever the link points at. The rule is the plugin
|
|
1048
|
+
* archive's: the deepest ancestor that exists must resolve to exactly its
|
|
1049
|
+
* own spelling under the workspace's resolved root — identity, not mere
|
|
1050
|
+
* containment — and the entry itself, when it exists, must not be a link.
|
|
1051
|
+
* The workspace root may sit behind a link of the operator's (a mounted
|
|
1052
|
+
* volume): both sides are resolved, so that is allowed.
|
|
1053
|
+
*
|
|
1054
|
+
* The same refusal as the lexical check on purpose: this IS a traversal,
|
|
1055
|
+
* reached through a link rather than through a spelling.
|
|
1056
|
+
*/
|
|
1057
|
+
private async assertNotThroughLink(absolutePath: string, workspaceDir: string): Promise<void> {
|
|
1058
|
+
const traversal = () => new PathTraversalError();
|
|
1059
|
+
const entry = await fs.lstat(absolutePath).catch((err: unknown) => {
|
|
1060
|
+
if (isAbsence(err)) return null;
|
|
1061
|
+
throw err;
|
|
1062
|
+
});
|
|
1063
|
+
if (entry?.isSymbolicLink()) throw traversal();
|
|
1064
|
+
// The deepest existing ancestor: a write creates the rest, under it. Each
|
|
1065
|
+
// ancestor is lstat-ed BEFORE its absence is taken as leave to climb: a
|
|
1066
|
+
// dangling link resolves to nothing too, and climbing past it would let
|
|
1067
|
+
// a target created in the meantime receive the write.
|
|
1068
|
+
let ancestor = path.dirname(absolutePath);
|
|
1069
|
+
let realAncestor: string | null = null;
|
|
1070
|
+
while (realAncestor === null) {
|
|
1071
|
+
const ancestorEntry = await fs.lstat(ancestor).catch((err: unknown) => {
|
|
1072
|
+
if (isAbsence(err)) return null;
|
|
1073
|
+
throw err;
|
|
1074
|
+
});
|
|
1075
|
+
// The workspace directory itself may be a link of the operator's (one
|
|
1076
|
+
// workspace mounted elsewhere), like the root above it; the identity
|
|
1077
|
+
// check below still holds everything beneath it to its own spelling.
|
|
1078
|
+
if (ancestorEntry?.isSymbolicLink() && path.resolve(ancestor) !== path.resolve(workspaceDir)) throw traversal();
|
|
1079
|
+
realAncestor = await fs.realpath(ancestor).catch((err: unknown) => {
|
|
1080
|
+
if (isAbsence(err)) return null;
|
|
1081
|
+
throw err;
|
|
1082
|
+
});
|
|
1083
|
+
if (realAncestor === null) {
|
|
1084
|
+
const up = path.dirname(ancestor);
|
|
1085
|
+
if (up === ancestor) throw traversal();
|
|
1086
|
+
ancestor = up;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
const realRoot = await fs.realpath(workspaceDir);
|
|
1090
|
+
const expected = path.join(realRoot, path.relative(path.resolve(workspaceDir), ancestor));
|
|
1091
|
+
const same =
|
|
1092
|
+
process.platform === 'win32'
|
|
1093
|
+
? realAncestor.toLowerCase() === expected.toLowerCase()
|
|
1094
|
+
: realAncestor === expected;
|
|
1095
|
+
if (!same) throw traversal();
|
|
1096
|
+
}
|
|
1097
|
+
|
|
759
1098
|
/**
|
|
760
1099
|
* Read every `.md` file under the KB repo in one disk pass, keyed by
|
|
761
1100
|
* repo-root-relative POSIX path (e.g. `Product/NodeTypes/ServiceCommitment.md`)
|
|
@@ -770,23 +1109,15 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
770
1109
|
const workspaceDir = await this.resolveWorkspaceDir(workspaceId);
|
|
771
1110
|
const repoRoot = path.join(workspaceDir, this.kbDirName);
|
|
772
1111
|
const files: Record<string, string> = {};
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
continue;
|
|
783
|
-
}
|
|
784
|
-
if (!entry.isFile() || !entry.name.endsWith('.md')) continue;
|
|
785
|
-
const repoPath = path.relative(repoRoot, childAbs).replace(/\\/g, '/');
|
|
786
|
-
files[repoPath] = await fs.readFile(childAbs, 'utf-8');
|
|
787
|
-
}
|
|
788
|
-
};
|
|
789
|
-
await walk(repoRoot, BevelIgnoreStack.empty());
|
|
1112
|
+
await this.disk.walk(repoRoot, explorerWalk(), [
|
|
1113
|
+
{
|
|
1114
|
+
async onFile(dir, name) {
|
|
1115
|
+
if (!name.endsWith('.md')) return;
|
|
1116
|
+
const repoPath = dir ? `${dir}/${name}` : name;
|
|
1117
|
+
files[repoPath] = await fs.readFile(path.join(repoRoot, dir, name), 'utf-8');
|
|
1118
|
+
},
|
|
1119
|
+
},
|
|
1120
|
+
]);
|
|
790
1121
|
return files;
|
|
791
1122
|
}
|
|
792
1123
|
|
|
@@ -803,10 +1134,10 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
803
1134
|
* size crosses `ZIP_DOWNLOAD_MAX_BYTES`. Buffered in memory (adm-zip has
|
|
804
1135
|
* no streaming API); the cap therefore doubles as a peak-heap bound.
|
|
805
1136
|
*/
|
|
806
|
-
async createFolderZip(workspaceId: string,
|
|
807
|
-
const workspaceDir = await this.
|
|
808
|
-
|
|
809
|
-
|
|
1137
|
+
async createFolderZip(workspaceId: string, wsPath: string): Promise<Buffer> {
|
|
1138
|
+
const { workspaceDir, relativePath, absolutePath: absoluteRoot } = await this.resolveInsideRepo(workspaceId, wsPath);
|
|
1139
|
+
assertNoGitInternalsSegment(relativePath);
|
|
1140
|
+
await assertNotGitInternals(workspaceDir, relativePath, absoluteRoot);
|
|
810
1141
|
const stat = await fs.stat(absoluteRoot);
|
|
811
1142
|
if (!stat.isDirectory()) {
|
|
812
1143
|
throw new Error('Not a directory');
|
|
@@ -816,45 +1147,31 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
816
1147
|
const zip = new AdmZip();
|
|
817
1148
|
let totalBytes = 0;
|
|
818
1149
|
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
throw new FolderTooLargeError(ZIP_DOWNLOAD_MAX_BYTES);
|
|
845
|
-
}
|
|
846
|
-
totalBytes += data.byteLength;
|
|
847
|
-
// Path inside the archive: <folderName>/<relPathUnderFolder>, POSIX
|
|
848
|
-
// separators regardless of host OS so the zip extracts cleanly
|
|
849
|
-
// on Windows/Mac/Linux alike.
|
|
850
|
-
const relInside = path
|
|
851
|
-
.relative(absoluteRoot, childAbs)
|
|
852
|
-
.replace(/\\/g, '/');
|
|
853
|
-
zip.addFile(`${zipRoot}/${relInside}`, data);
|
|
854
|
-
}
|
|
855
|
-
};
|
|
856
|
-
|
|
857
|
-
await walk(absoluteRoot, BevelIgnoreStack.empty());
|
|
1150
|
+
await this.disk.walk(absoluteRoot, explorerWalk(), [
|
|
1151
|
+
{
|
|
1152
|
+
async onFile(dir, name) {
|
|
1153
|
+
const childAbs = path.join(absoluteRoot, dir, name);
|
|
1154
|
+
// Check the size cap BEFORE reading the file into memory. Reading
|
|
1155
|
+
// first would let a single hostile 2 GB file allocate the whole
|
|
1156
|
+
// buffer before the throw — defeating the cap as a peak-heap
|
|
1157
|
+
// bound. `stat.size` is an upper bound that we re-verify after
|
|
1158
|
+
// the read in case the file grew between stat and readFile.
|
|
1159
|
+
const stat = await fs.stat(childAbs);
|
|
1160
|
+
if (totalBytes + stat.size > ZIP_DOWNLOAD_MAX_BYTES) {
|
|
1161
|
+
throw new FolderTooLargeError(ZIP_DOWNLOAD_MAX_BYTES);
|
|
1162
|
+
}
|
|
1163
|
+
const data = await fs.readFile(childAbs);
|
|
1164
|
+
if (totalBytes + data.byteLength > ZIP_DOWNLOAD_MAX_BYTES) {
|
|
1165
|
+
throw new FolderTooLargeError(ZIP_DOWNLOAD_MAX_BYTES);
|
|
1166
|
+
}
|
|
1167
|
+
totalBytes += data.byteLength;
|
|
1168
|
+
// Path inside the archive: <folderName>/<relPathUnderFolder>, POSIX
|
|
1169
|
+
// separators regardless of host OS so the zip extracts cleanly
|
|
1170
|
+
// on Windows/Mac/Linux alike.
|
|
1171
|
+
zip.addFile(`${zipRoot}/${dir ? `${dir}/${name}` : name}`, data);
|
|
1172
|
+
},
|
|
1173
|
+
},
|
|
1174
|
+
]);
|
|
858
1175
|
return zip.toBuffer();
|
|
859
1176
|
}
|
|
860
1177
|
|
|
@@ -870,6 +1187,7 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
870
1187
|
relativePath: string,
|
|
871
1188
|
): Promise<string | null> {
|
|
872
1189
|
this.assertValidGitRef(ref);
|
|
1190
|
+
assertNoGitInternalsSegment(relativePath);
|
|
873
1191
|
this.assertValidRepoRelativePath(relativePath);
|
|
874
1192
|
const workspaceDir = await this.resolveWorkspaceDir(workspaceId);
|
|
875
1193
|
const repoDir = path.join(workspaceDir, this.kbDirName);
|
|
@@ -881,11 +1199,7 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
881
1199
|
|
|
882
1200
|
for (const candidate of candidates) {
|
|
883
1201
|
try {
|
|
884
|
-
const { stdout } = await
|
|
885
|
-
'git',
|
|
886
|
-
['-C', repoDir, 'show', `${candidate}:${relativePath}`],
|
|
887
|
-
{ encoding: 'utf-8', maxBuffer: 16 * 1024 * 1024 },
|
|
888
|
-
);
|
|
1202
|
+
const { stdout } = await this.gitRunner.run(repoDir, ['show', `${candidate}:${relativePath}`]);
|
|
889
1203
|
return stdout;
|
|
890
1204
|
} catch {
|
|
891
1205
|
// Try next candidate.
|
|
@@ -898,15 +1212,25 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
898
1212
|
* Run `git fetch --prune origin` for this branch's clone. Results are
|
|
899
1213
|
* cached for `FETCH_CACHE_TTL_MS`; concurrent callers share the same
|
|
900
1214
|
* in-flight fetch to avoid fetch storms when e.g. the CR list poll fans out.
|
|
1215
|
+
*
|
|
1216
|
+
* `force` skips the TTL — for a caller that KNOWS the remote just moved
|
|
1217
|
+
* (an event-driven `?fresh=1` read, which bypasses its own cache for the
|
|
1218
|
+
* same reason) and would otherwise answer from refs older than the change
|
|
1219
|
+
* it is being asked about. It keeps the in-flight join, which is the half
|
|
1220
|
+
* that actually prevents storms: ten forced callers in the same tick still
|
|
1221
|
+
* share one fetch.
|
|
901
1222
|
*/
|
|
902
|
-
async ensureRemotesFetched(
|
|
1223
|
+
async ensureRemotesFetched(
|
|
1224
|
+
workspaceId: string,
|
|
1225
|
+
opts: { strict?: boolean; force?: boolean } = {},
|
|
1226
|
+
): Promise<void> {
|
|
903
1227
|
const workspaceDir = await this.resolveWorkspaceDir(workspaceId);
|
|
904
1228
|
const repoDir = path.join(workspaceDir, this.kbDirName);
|
|
905
1229
|
const now = Date.now();
|
|
906
1230
|
// The TTL is stamped only by a SUCCESSFUL fetch, so a fresh window is
|
|
907
1231
|
// itself the proof a strict caller wants.
|
|
908
1232
|
const last = this.lastFetchAt.get(repoDir) ?? 0;
|
|
909
|
-
if (now - last < FETCH_CACHE_TTL_MS) return;
|
|
1233
|
+
if (!opts.force && now - last < FETCH_CACHE_TTL_MS) return;
|
|
910
1234
|
|
|
911
1235
|
const inFlight = this.inFlightFetches.get(repoDir);
|
|
912
1236
|
if (inFlight) {
|
|
@@ -923,18 +1247,15 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
923
1247
|
// This driver runs outside the git layer's per-workspace mutex, so it may
|
|
924
1248
|
// only run the safe implicit-fetch shape — see `SAFE_IMPLICIT_FETCH_ARGS`
|
|
925
1249
|
// in `kb-fs/clone-config.ts` for the full rationale.
|
|
926
|
-
const promise =
|
|
927
|
-
|
|
928
|
-
['-C', repoDir, ...SAFE_IMPLICIT_FETCH_ARGS],
|
|
929
|
-
{ env: { ...process.env } },
|
|
930
|
-
)
|
|
1250
|
+
const promise = this.gitRunner
|
|
1251
|
+
.run(repoDir, [...SAFE_IMPLICIT_FETCH_ARGS])
|
|
931
1252
|
.then(() => {
|
|
932
1253
|
this.lastFetchAt.set(repoDir, Date.now());
|
|
933
1254
|
this.lastFetchOk.set(repoDir, true);
|
|
934
1255
|
})
|
|
935
1256
|
.catch((err) => {
|
|
936
1257
|
this.lastFetchOk.set(repoDir, false);
|
|
937
|
-
|
|
1258
|
+
log.warn('git fetch origin failed:', { detail: redactError(err) });
|
|
938
1259
|
})
|
|
939
1260
|
.finally(() => {
|
|
940
1261
|
if (this.inFlightFetches.get(repoDir) === promise) {
|
|
@@ -962,12 +1283,20 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
962
1283
|
}
|
|
963
1284
|
}
|
|
964
1285
|
|
|
965
|
-
async deleteFile(workspaceId: string,
|
|
966
|
-
const workspaceDir = await this.
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
1286
|
+
async deleteFile(workspaceId: string, wsPath: string): Promise<void> {
|
|
1287
|
+
const { workspaceDir, relativePath, absolutePath } = await this.resolveInsideRepo(workspaceId, wsPath);
|
|
1288
|
+
assertNoGitInternalsSegment(relativePath);
|
|
1289
|
+
// Under the path's turn like every other single-file mutation: a delete
|
|
1290
|
+
// landing between a conditional write's compare and its write would let
|
|
1291
|
+
// that write recreate the file the delete had just removed. The diff
|
|
1292
|
+
// baseline is updated inside the same turn, so two mutations of one path
|
|
1293
|
+
// update it in the order they landed on disk. The resolved git check runs
|
|
1294
|
+
// inside the turn too (see `withPathTurn`), so it cannot reorder callers.
|
|
1295
|
+
await this.withResolvedPathTurn(absolutePath, async () => {
|
|
1296
|
+
await assertNotGitInternals(workspaceDir, relativePath, absolutePath);
|
|
1297
|
+
await fs.rm(absolutePath, { recursive: true, force: true });
|
|
1298
|
+
await this.diffService?.markUserDeleted(workspaceId, relativePath);
|
|
1299
|
+
});
|
|
971
1300
|
}
|
|
972
1301
|
|
|
973
1302
|
/**
|
|
@@ -989,74 +1318,345 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
989
1318
|
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
990
1319
|
}
|
|
991
1320
|
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1321
|
+
/**
|
|
1322
|
+
* Rename or move one entry.
|
|
1323
|
+
*
|
|
1324
|
+
* Deliberately NOT under {@link withPathTurn}: a move mutates two paths, and
|
|
1325
|
+
* holding both turns would introduce the one shape that can deadlock here
|
|
1326
|
+
* (a move waiting on a target whose holder is waiting on the move's other
|
|
1327
|
+
* path). A rename onto a path being conditionally written is therefore
|
|
1328
|
+
* outside that guarantee; the workflow lock on the moved path is what
|
|
1329
|
+
* serializes it against the app's own editors.
|
|
1330
|
+
*/
|
|
1331
|
+
async moveEntry(workspaceId: string, oldWsPath: string, newWsPath: string): Promise<void> {
|
|
1332
|
+
const { workspaceDir, relativePath: oldRelativePath, absolutePath: oldAbsolute } =
|
|
1333
|
+
await this.resolveInsideRepo(workspaceId, oldWsPath);
|
|
1334
|
+
const { relativePath: newRelativePath, absolutePath: newAbsolute } =
|
|
1335
|
+
await this.resolveInsideRepo(workspaceId, newWsPath);
|
|
1336
|
+
// A move and a rename are the same call, so this is where the checkout's
|
|
1337
|
+
// own name is reserved at the repository root for both.
|
|
1338
|
+
assertRepoRootNameFree(newRelativePath, this.kbDirName);
|
|
1339
|
+
assertNoGitInternalsSegment(oldRelativePath);
|
|
1340
|
+
assertNoGitInternalsSegment(newRelativePath);
|
|
1341
|
+
assertValidPath(newRelativePath);
|
|
1342
|
+
await assertNotGitInternals(workspaceDir, oldRelativePath, oldAbsolute);
|
|
1343
|
+
await assertNotGitInternals(workspaceDir, newRelativePath, newAbsolute);
|
|
1344
|
+
// Both ends: a link on the way to either end would carry the rename
|
|
1345
|
+
// outside. A link used AS the source is refused too — a committed link
|
|
1346
|
+
// is not something the app moves around, any more than reads it.
|
|
1347
|
+
await this.assertNotThroughLink(oldAbsolute, workspaceDir);
|
|
1348
|
+
await this.assertNotThroughLink(newAbsolute, workspaceDir);
|
|
1349
|
+
// Before `mkdir`, so a refused move leaves no empty folder behind — and
|
|
1350
|
+
// it is the look that produces the sentence naming what is in the way.
|
|
1351
|
+
await this.assertDestinationFree(oldAbsolute, newAbsolute, newRelativePath);
|
|
999
1352
|
await fs.mkdir(path.dirname(newAbsolute), { recursive: true });
|
|
1000
|
-
|
|
1353
|
+
// The move itself cannot replace anything either, so a destination that
|
|
1354
|
+
// appears between the look above and this line is refused rather than
|
|
1355
|
+
// eaten (`shared/rename-no-replace.ts`). This path takes no lock on
|
|
1356
|
+
// purpose — see the note above — so the atomicity has to come from the
|
|
1357
|
+
// filesystem call.
|
|
1358
|
+
try {
|
|
1359
|
+
await renameNoReplace(oldAbsolute, newAbsolute, newRelativePath);
|
|
1360
|
+
} catch (err) {
|
|
1361
|
+
if (err instanceof DestinationTakenError) {
|
|
1362
|
+
throw new EntryExistsError(err.entryKind, newRelativePath);
|
|
1363
|
+
}
|
|
1364
|
+
throw err;
|
|
1365
|
+
}
|
|
1001
1366
|
if (this.diffService) {
|
|
1002
1367
|
await this.diffService.markUserDeleted(workspaceId, oldRelativePath);
|
|
1003
1368
|
await this.diffService.syncFromDisk(workspaceId, newRelativePath);
|
|
1004
1369
|
}
|
|
1005
1370
|
}
|
|
1006
1371
|
|
|
1372
|
+
/**
|
|
1373
|
+
* Refuse a move onto a name that is taken. `fs.rename` REPLACES an existing
|
|
1374
|
+
* file on every platform — that is how renaming a `.docx` onto an existing
|
|
1375
|
+
* `.md` handed the markdown file the Word bytes — so the destination is
|
|
1376
|
+
* looked at first, and a move never overwrites a file or merges into a
|
|
1377
|
+
* folder.
|
|
1378
|
+
*
|
|
1379
|
+
* Judged by `inspectDestination`, which reads a case-only rename on a
|
|
1380
|
+
* case-insensitive filesystem — where `notes.md` → `Notes.md` finds the
|
|
1381
|
+
* SOURCE at the destination — as the rename that was asked for rather than a
|
|
1382
|
+
* clash, and everything else as a clash. It is the same reading the move
|
|
1383
|
+
* itself uses, so the sentence and the refusal cannot drift apart.
|
|
1384
|
+
*/
|
|
1385
|
+
private async assertDestinationFree(
|
|
1386
|
+
oldAbsolute: string,
|
|
1387
|
+
newAbsolute: string,
|
|
1388
|
+
newRelativePath: string,
|
|
1389
|
+
): Promise<void> {
|
|
1390
|
+
const verdict = await inspectDestination(oldAbsolute, newAbsolute);
|
|
1391
|
+
if (verdict.state === 'taken') throw new EntryExistsError(verdict.kind, newRelativePath);
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
/**
|
|
1395
|
+
* Refuse a conditional write, with the same 409 {@link writeFile} throws,
|
|
1396
|
+
* without writing anything. The route calls this BEFORE the creator-access
|
|
1397
|
+
* plan: that plan can commit a seeded `access.md` under its own lock, and a
|
|
1398
|
+
* precondition failing after it would leave an authorization grant behind
|
|
1399
|
+
* for a save that never landed. The write's own compare, under its write
|
|
1400
|
+
* turn, stays the authoritative one; this only keeps the refusal free of
|
|
1401
|
+
* side effects.
|
|
1402
|
+
*/
|
|
1403
|
+
async assertContentMatches(
|
|
1404
|
+
workspaceId: string,
|
|
1405
|
+
wsPath: string,
|
|
1406
|
+
expectedContent: string,
|
|
1407
|
+
): Promise<void> {
|
|
1408
|
+
const { workspaceDir, relativePath, absolutePath } = await this.resolveInsideRepo(workspaceId, wsPath);
|
|
1409
|
+
assertNoGitInternalsSegment(relativePath);
|
|
1410
|
+
assertValidPath(relativePath);
|
|
1411
|
+
await assertNotGitInternals(workspaceDir, relativePath, absolutePath);
|
|
1412
|
+
await assertConditionalWriteMatches(absolutePath, relativePath,expectedContent);
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
/**
|
|
1416
|
+
* Run `op` as the only mutation in flight for `relativePath` in this
|
|
1417
|
+
* process, so a read-then-decide-then-write sequence inside it cannot have
|
|
1418
|
+
* another mutation land in the middle of it.
|
|
1419
|
+
*
|
|
1420
|
+
* The conditional write ({@link writeFile}'s `expectedContent`) needs that,
|
|
1421
|
+
* and so does a caller whose decision spans more than one call: `PUT /file`
|
|
1422
|
+
* checks the precondition, plans a creator-access grant that COMMITS, and
|
|
1423
|
+
* only then writes, and a save that lands in between would leave that grant
|
|
1424
|
+
* behind for a write about to be refused. Such a caller wraps the sequence
|
|
1425
|
+
* in a turn and the write inside it takes the same one, re-entrantly.
|
|
1426
|
+
*
|
|
1427
|
+
* The route's own per-path lock does not establish this: it deliberately
|
|
1428
|
+
* runs the op WITHOUT acquiring when the caller already holds the lock, so
|
|
1429
|
+
* two requests from that holder would interleave.
|
|
1430
|
+
*
|
|
1431
|
+
* The reach of the guarantee, exactly: within this process, one mutation at
|
|
1432
|
+
* a time per resolved path. ACROSS instances the workflow lock row is the
|
|
1433
|
+
* coordinator, and it agrees with this queue only because `PUT /file`
|
|
1434
|
+
* canonicalises the path before taking either (see `canonicalRelativePath`);
|
|
1435
|
+
* a caller that reaches the service directly with an odd spelling gets the
|
|
1436
|
+
* local queue and whatever lock it took itself. Case is not folded: on the
|
|
1437
|
+
* Linux deployment target `Foo.md` and `foo.md` are two files, and treating
|
|
1438
|
+
* them as one would be a worse bug than the race it would close.
|
|
1439
|
+
*/
|
|
1440
|
+
async withPathTurn<T>(workspaceId: string, wsPath: string, op: () => Promise<T>): Promise<T> {
|
|
1441
|
+
const { workspaceDir, relativePath, absolutePath } = await this.resolveInsideRepo(workspaceId, wsPath);
|
|
1442
|
+
assertNoGitInternalsSegment(relativePath);
|
|
1443
|
+
assertValidPath(relativePath);
|
|
1444
|
+
// The resolved check reads the disk, and how long that takes depends on
|
|
1445
|
+
// the spelling. Run before the queue, it let a later call overtake an
|
|
1446
|
+
// earlier one for the same file; inside the turn, callers keep their order
|
|
1447
|
+
// and `op` still never runs on a path that resolves into the git folder.
|
|
1448
|
+
return this.withResolvedPathTurn(absolutePath, async () => {
|
|
1449
|
+
await assertNotGitInternals(workspaceDir, relativePath, absolutePath);
|
|
1450
|
+
return op();
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
/**
|
|
1455
|
+
* One folder structure change at a time per SUBTREE: an explicit folder
|
|
1456
|
+
* delete and the placeholder that keeps an emptied folder. Two turns wait
|
|
1457
|
+
* for each other when one folder is, or sits inside, the other, so keeping
|
|
1458
|
+
* a folder can never run in the middle of deleting it (and write the
|
|
1459
|
+
* placeholder back into a folder whose delete already enumerated its
|
|
1460
|
+
* files), and a delete never starts while a folder under it is being kept.
|
|
1461
|
+
* Not re-entrant: take it once, and never around another folder's turn.
|
|
1462
|
+
*
|
|
1463
|
+
* The reach of the guarantee, like {@link withPathTurn}'s: within this
|
|
1464
|
+
* process, over this process's clones. Across instances the workflow lock
|
|
1465
|
+
* rows coordinate — the placeholder write takes its own path's lock, as the
|
|
1466
|
+
* folder delete takes each file's — and each clone's changes meet in git,
|
|
1467
|
+
* as any write into a folder another instance deletes already does.
|
|
1468
|
+
*/
|
|
1469
|
+
async withFolderTurn<T>(workspaceId: string, wsDir: string, op: () => Promise<T>): Promise<T> {
|
|
1470
|
+
const { relativePath: relativeDir, absolutePath: absoluteDir } = await this.resolveInsideRepo(workspaceId, wsDir);
|
|
1471
|
+
assertValidPath(relativeDir);
|
|
1472
|
+
const overlaps = (held: string) =>
|
|
1473
|
+
held === absoluteDir ||
|
|
1474
|
+
held.startsWith(absoluteDir + path.sep) ||
|
|
1475
|
+
absoluteDir.startsWith(held + path.sep);
|
|
1476
|
+
// Check-and-claim with no await in between, so two callers cannot both
|
|
1477
|
+
// find the subtree free.
|
|
1478
|
+
for (;;) {
|
|
1479
|
+
const waiting = [...this.folderTurns].filter(([held]) => overlaps(held)).map(([, turn]) => turn);
|
|
1480
|
+
if (waiting.length === 0) break;
|
|
1481
|
+
await Promise.all(waiting);
|
|
1482
|
+
}
|
|
1483
|
+
let release!: () => void;
|
|
1484
|
+
this.folderTurns.set(absoluteDir, new Promise<void>((resolve) => (release = resolve)));
|
|
1485
|
+
try {
|
|
1486
|
+
return await op();
|
|
1487
|
+
} finally {
|
|
1488
|
+
this.folderTurns.delete(absoluteDir);
|
|
1489
|
+
release();
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
/**
|
|
1494
|
+
* {@link withPathTurn} on an already-resolved path.
|
|
1495
|
+
*
|
|
1496
|
+
* A turn this context holds runs through: waiting for itself would hang.
|
|
1497
|
+
* What is stored is the SETTLED tail, never the caller's promise, so a
|
|
1498
|
+
* throwing mutation cannot wedge the queue behind it and no rejection is
|
|
1499
|
+
* left unhandled; the entry is dropped once it is still the tail, so the
|
|
1500
|
+
* map does not grow by an entry per file ever touched.
|
|
1501
|
+
*
|
|
1502
|
+
* Nesting a DIFFERENT file's turn inside one is safe here because the only
|
|
1503
|
+
* caller that does it is `PUT /file`, and the grant it seeds is always an
|
|
1504
|
+
* `access.md` at or above its target: two such sequences cannot each hold
|
|
1505
|
+
* what the other wants, since that would need each target to sit above the
|
|
1506
|
+
* other. Every other holder takes one turn and nests nothing.
|
|
1507
|
+
*/
|
|
1508
|
+
private async withResolvedPathTurn<T>(absolutePath: string, op: () => Promise<T>): Promise<T> {
|
|
1509
|
+
const held = this.heldTurns.getStore();
|
|
1510
|
+
const inner = held?.get(absolutePath);
|
|
1511
|
+
if (inner) {
|
|
1512
|
+
// Re-entrant: runs without waiting for the turn this context holds —
|
|
1513
|
+
// but behind any sibling launched inside that same turn, so a held
|
|
1514
|
+
// turn that fires two conditional writes at once cannot have both
|
|
1515
|
+
// compare the same old content and then overwrite each other.
|
|
1516
|
+
const turn = inner.tail.then(() => op());
|
|
1517
|
+
inner.tail = turn.then(
|
|
1518
|
+
() => undefined,
|
|
1519
|
+
() => undefined,
|
|
1520
|
+
);
|
|
1521
|
+
return turn;
|
|
1522
|
+
}
|
|
1523
|
+
const run = (): Promise<T> =>
|
|
1524
|
+
this.heldTurns.run(new Map([...(held ?? []), [absolutePath, { tail: Promise.resolve() }]]), op);
|
|
1525
|
+
const previous = this.writeTurns.get(absolutePath);
|
|
1526
|
+
const turn = previous ? previous.then(run) : run();
|
|
1527
|
+
const tail = turn.then(
|
|
1528
|
+
() => undefined,
|
|
1529
|
+
() => undefined,
|
|
1530
|
+
);
|
|
1531
|
+
this.writeTurns.set(absolutePath, tail);
|
|
1532
|
+
void tail.then(() => {
|
|
1533
|
+
if (this.writeTurns.get(absolutePath) === tail) this.writeTurns.delete(absolutePath);
|
|
1534
|
+
});
|
|
1535
|
+
return turn;
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
/**
|
|
1539
|
+
* Write one workspace file.
|
|
1540
|
+
*
|
|
1541
|
+
* - `failIfExists` is an exclusive create (see the `wx` note below).
|
|
1542
|
+
* - `expectedContent` is a conditional write: the file must still hold
|
|
1543
|
+
* exactly that text, or the write is refused with a 409. It is what lets
|
|
1544
|
+
* an editor that composed its save from a snapshot (the inline agent
|
|
1545
|
+
* description merges the private comments it read minutes earlier) refuse
|
|
1546
|
+
* rather than erase whatever landed in between. An absent file reads as
|
|
1547
|
+
* the empty string, so `expectedContent: ''` means "expect nothing there
|
|
1548
|
+
* yet" and creates it.
|
|
1549
|
+
*
|
|
1550
|
+
* The compare and the write run inside this path's write turn, so no other
|
|
1551
|
+
* write through this service can land between them, whatever locking the
|
|
1552
|
+
* caller did or skipped.
|
|
1553
|
+
*/
|
|
1007
1554
|
async writeFile(
|
|
1008
1555
|
workspaceId: string,
|
|
1009
|
-
|
|
1556
|
+
wsPath: string,
|
|
1010
1557
|
content: string,
|
|
1011
|
-
options?: { failIfExists?: boolean },
|
|
1558
|
+
options?: { failIfExists?: boolean; expectedContent?: string },
|
|
1012
1559
|
): Promise<void> {
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
//
|
|
1022
|
-
|
|
1023
|
-
await
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
);
|
|
1032
|
-
conflict.status = 409;
|
|
1033
|
-
throw conflict;
|
|
1560
|
+
const { workspaceDir, relativePath, absolutePath } = await this.resolveInsideRepo(workspaceId, wsPath);
|
|
1561
|
+
// A write creates the folders on the way to it, so the reserved root name
|
|
1562
|
+
// is refused here too — otherwise `knowledge-base/knowledge-base/x.md`
|
|
1563
|
+
// would bring the unreachable folder into existence around the file.
|
|
1564
|
+
assertRepoRootNameFree(relativePath, this.kbDirName);
|
|
1565
|
+
assertNoGitInternalsSegment(relativePath);
|
|
1566
|
+
assertValidPath(relativePath);
|
|
1567
|
+
await this.withResolvedPathTurn(absolutePath, async () => {
|
|
1568
|
+
// Inside the turn, beside the link check (see `withPathTurn`).
|
|
1569
|
+
await assertNotGitInternals(workspaceDir, relativePath, absolutePath);
|
|
1570
|
+
await this.assertNotThroughLink(absolutePath, workspaceDir);
|
|
1571
|
+
// Compare BEFORE creating anything. The parent chain used to be made
|
|
1572
|
+
// first, so a refused save at `x/new-folder/note.md` left an empty
|
|
1573
|
+
// `x/new-folder/` behind in everyone's tree — for a write that never
|
|
1574
|
+
// happened. The compare reads the file, and an absent file reads as
|
|
1575
|
+
// empty whether or not its directory exists.
|
|
1576
|
+
if (options?.expectedContent !== undefined) {
|
|
1577
|
+
await assertConditionalWriteMatches(absolutePath, relativePath,options.expectedContent);
|
|
1034
1578
|
}
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1579
|
+
await fs.mkdir(path.dirname(absolutePath), { recursive: true });
|
|
1580
|
+
try {
|
|
1581
|
+
// `wx` makes create-if-absent ATOMIC at the fs level — an exists-check
|
|
1582
|
+
// followed by a plain write would let two concurrent creators (or a
|
|
1583
|
+
// stale client whose file list predates the file) both pass the check
|
|
1584
|
+
// and silently replace each other's content.
|
|
1585
|
+
await fs.writeFile(absolutePath, content, {
|
|
1586
|
+
encoding: 'utf-8',
|
|
1587
|
+
flag: options?.failIfExists ? 'wx' : 'w',
|
|
1588
|
+
});
|
|
1589
|
+
} catch (err) {
|
|
1590
|
+
if ((err as NodeJS.ErrnoException).code === 'EEXIST') {
|
|
1591
|
+
const conflict: Error & { status?: number } = new Error(
|
|
1592
|
+
`"${relativePath}" already exists.`,
|
|
1593
|
+
);
|
|
1594
|
+
conflict.status = 409;
|
|
1595
|
+
throw conflict;
|
|
1596
|
+
}
|
|
1597
|
+
throw err;
|
|
1598
|
+
}
|
|
1599
|
+
// Inside the turn, so the baseline follows the order the writes landed in.
|
|
1600
|
+
await this.diffService?.syncFromDisk(workspaceId, relativePath);
|
|
1601
|
+
});
|
|
1038
1602
|
}
|
|
1039
1603
|
|
|
1040
|
-
async createDirectory(workspaceId: string,
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1604
|
+
async createDirectory(workspaceId: string, wsPath: string): Promise<void> {
|
|
1605
|
+
const { workspaceDir, relativePath, absolutePath } = await this.resolveInsideRepo(workspaceId, wsPath);
|
|
1606
|
+
assertRepoRootNameFree(relativePath, this.kbDirName);
|
|
1607
|
+
assertNoGitInternalsSegment(relativePath);
|
|
1608
|
+
assertValidPath(relativePath);
|
|
1609
|
+
await assertNotGitInternals(workspaceDir, relativePath, absolutePath);
|
|
1610
|
+
await this.assertNotThroughLink(absolutePath, workspaceDir);
|
|
1045
1611
|
await fs.mkdir(absolutePath, { recursive: true });
|
|
1046
1612
|
const entries = await fs.readdir(absolutePath);
|
|
1047
1613
|
if (entries.length === 0) {
|
|
1048
|
-
await fs.writeFile(path.join(absolutePath,
|
|
1614
|
+
await fs.writeFile(path.join(absolutePath, FOLDER_PLACEHOLDER), '', 'utf-8');
|
|
1049
1615
|
}
|
|
1050
1616
|
}
|
|
1051
1617
|
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1618
|
+
/**
|
|
1619
|
+
* Write the empty-folder placeholder into `relativeDir` when that folder
|
|
1620
|
+
* exists and holds nothing; true when it wrote one. Unlike
|
|
1621
|
+
* {@link createDirectory} it never creates the folder: one that is gone was
|
|
1622
|
+
* deleted explicitly and stays gone. A folder reached through a link is
|
|
1623
|
+
* refused like any write through one, so the placeholder cannot land
|
|
1624
|
+
* outside the workspace.
|
|
1625
|
+
*/
|
|
1626
|
+
async writeFolderPlaceholder(workspaceId: string, wsDir: string): Promise<boolean> {
|
|
1627
|
+
const { workspaceDir, relativePath: relativeDir, absolutePath: absoluteDir } =
|
|
1628
|
+
await this.resolveInsideRepo(workspaceId, wsDir);
|
|
1629
|
+
assertValidPath(relativeDir);
|
|
1630
|
+
const placeholder = path.join(absoluteDir, FOLDER_PLACEHOLDER);
|
|
1631
|
+
await this.assertNotThroughLink(placeholder, workspaceDir);
|
|
1632
|
+
let entries: string[];
|
|
1633
|
+
try {
|
|
1634
|
+
entries = await fs.readdir(absoluteDir);
|
|
1635
|
+
} catch (err) {
|
|
1636
|
+
if (isAbsence(err)) return false;
|
|
1637
|
+
throw err;
|
|
1638
|
+
}
|
|
1639
|
+
if (entries.length > 0) return false;
|
|
1640
|
+
await fs.writeFile(placeholder, '', { flag: 'wx' });
|
|
1641
|
+
return true;
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
async writeFileBinary(workspaceId: string, wsPath: string, data: Uint8Array): Promise<void> {
|
|
1645
|
+
const { workspaceDir, relativePath, absolutePath } = await this.resolveInsideRepo(workspaceId, wsPath);
|
|
1646
|
+
assertRepoRootNameFree(relativePath, this.kbDirName);
|
|
1647
|
+
assertNoGitInternalsSegment(relativePath);
|
|
1648
|
+
assertValidPath(relativePath);
|
|
1649
|
+
// An upload over a path is a mutation of that path, so it takes the same
|
|
1650
|
+
// turn as a text write: see {@link withPathTurn}. The whole of it — the
|
|
1651
|
+
// parent chain too, so a folder delete serialized before this turn
|
|
1652
|
+
// cannot remove the parent between its creation and the write.
|
|
1653
|
+
await this.withResolvedPathTurn(absolutePath, async () => {
|
|
1654
|
+
await assertNotGitInternals(workspaceDir, relativePath, absolutePath);
|
|
1655
|
+
await this.assertNotThroughLink(absolutePath, workspaceDir);
|
|
1656
|
+
await fs.mkdir(path.dirname(absolutePath), { recursive: true });
|
|
1657
|
+
await fs.writeFile(absolutePath, data);
|
|
1658
|
+
await this.diffService?.syncFromDisk(workspaceId, relativePath);
|
|
1659
|
+
});
|
|
1060
1660
|
}
|
|
1061
1661
|
|
|
1062
1662
|
/**
|
|
@@ -1067,8 +1667,8 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
1067
1667
|
*/
|
|
1068
1668
|
async unzipFile(
|
|
1069
1669
|
workspaceId: string,
|
|
1070
|
-
|
|
1071
|
-
|
|
1670
|
+
zipWsPath: string,
|
|
1671
|
+
destDirWsPath?: string,
|
|
1072
1672
|
/**
|
|
1073
1673
|
* Per-extracted-path write guard (ontology-session boundary). Called with
|
|
1074
1674
|
* each entry's workspace-relative target before it is written; if it throws,
|
|
@@ -1077,28 +1677,56 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
1077
1677
|
*/
|
|
1078
1678
|
guardWrite?: (wsRelativePath: string) => Promise<void>,
|
|
1079
1679
|
): Promise<UnzipResult> {
|
|
1080
|
-
const workspaceDir
|
|
1081
|
-
|
|
1082
|
-
|
|
1680
|
+
const { workspaceDir, relativePath: zipRelativePath, absolutePath: zipAbsolute } =
|
|
1681
|
+
await this.resolveInsideRepo(workspaceId, zipWsPath);
|
|
1682
|
+
assertNoGitInternalsSegment(zipRelativePath);
|
|
1683
|
+
await assertNotGitInternals(workspaceDir, zipRelativePath, zipAbsolute);
|
|
1083
1684
|
if (!zipRelativePath.toLowerCase().endsWith('.zip')) {
|
|
1084
|
-
throw new
|
|
1685
|
+
throw new WorkflowValidationError('Only .zip files can be extracted');
|
|
1085
1686
|
}
|
|
1086
1687
|
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
const destRel
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1688
|
+
// Inferred from the NORMALISED archive path, so an archive named without
|
|
1689
|
+
// the prefix extracts beside itself inside the repository rather than at
|
|
1690
|
+
// the workspace root.
|
|
1691
|
+
const inferredDest = path.posix.dirname(zipRelativePath);
|
|
1692
|
+
const { relativePath: destRel, absolutePath: destAbsolute } =
|
|
1693
|
+
await this.resolveInsideRepo(workspaceId, destDirWsPath ?? inferredDest);
|
|
1694
|
+
assertValidPath(destRel);
|
|
1695
|
+
assertNoGitInternalsSegment(destRel);
|
|
1696
|
+
await assertNotGitInternals(workspaceDir, destRel, destAbsolute);
|
|
1697
|
+
// Neither the archive nor the destination may sit behind a link; each
|
|
1698
|
+
// entry's own target is checked again below, once it is known.
|
|
1699
|
+
await this.assertNotThroughLink(zipAbsolute, workspaceDir);
|
|
1700
|
+
if (destAbsolute !== this.repoRoot(workspaceDir)) await this.assertNotThroughLink(destAbsolute, workspaceDir);
|
|
1701
|
+
|
|
1702
|
+
// The archive is READ ONCE, here, and the reader is handed those bytes —
|
|
1703
|
+
// it is never given the path to open for itself. Two reasons, and the
|
|
1704
|
+
// first is why this is not a `stat` followed by `new AdmZip(path)`:
|
|
1705
|
+
//
|
|
1706
|
+
// - The zip reader cannot tell absence from corruption. On a path with
|
|
1707
|
+
// nothing at it `AdmZip` throws "ADM-ZIP: Invalid filename", which would
|
|
1708
|
+
// be dressed up as an unreadable archive (422) and blame the bytes for
|
|
1709
|
+
// a name that never existed. Only the read knows which it was.
|
|
1710
|
+
// - A separate probe answers about a DIFFERENT moment than the open. An
|
|
1711
|
+
// archive deleted in between passed the probe and then failed the open,
|
|
1712
|
+
// and the caller got the 422 the probe existed to prevent. One read is
|
|
1713
|
+
// one moment, so there is no in-between to lose.
|
|
1714
|
+
//
|
|
1715
|
+
// `AdmZip` from a buffer costs nothing extra: given a path it reads the
|
|
1716
|
+
// whole file in anyway.
|
|
1717
|
+
let zipBytes: Buffer;
|
|
1718
|
+
try {
|
|
1719
|
+
zipBytes = await fs.readFile(zipAbsolute);
|
|
1720
|
+
} catch (err) {
|
|
1721
|
+
if (isAbsence(err)) throw new PathNotFoundError(zipRelativePath);
|
|
1722
|
+
throw err;
|
|
1723
|
+
}
|
|
1095
1724
|
|
|
1096
1725
|
let zip: AdmZip;
|
|
1097
1726
|
try {
|
|
1098
|
-
zip = new AdmZip(
|
|
1727
|
+
zip = new AdmZip(zipBytes);
|
|
1099
1728
|
} catch (err) {
|
|
1100
|
-
|
|
1101
|
-
throw new Error(`Could not read zip file: ${msg}`);
|
|
1729
|
+
throw new UnreadableArchiveError(err instanceof Error ? err.message : String(err));
|
|
1102
1730
|
}
|
|
1103
1731
|
|
|
1104
1732
|
// The destination directory is created on demand by the first allowed entry
|
|
@@ -1135,6 +1763,12 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
1135
1763
|
continue;
|
|
1136
1764
|
}
|
|
1137
1765
|
|
|
1766
|
+
// An archive never writes into the git folder, whatever the entry's spelling.
|
|
1767
|
+
if (hasGitInternalsSegment(rawName)) {
|
|
1768
|
+
skipped.push({ path: rawName, reason: new GitInternalsError().message });
|
|
1769
|
+
continue;
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1138
1772
|
const trimmed = rawName.replace(/\/+$/, '');
|
|
1139
1773
|
const segments = trimmed.split('/').filter((s) => s.length > 0);
|
|
1140
1774
|
let invalidReason: string | null = null;
|
|
@@ -1175,6 +1809,38 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
1175
1809
|
}
|
|
1176
1810
|
};
|
|
1177
1811
|
|
|
1812
|
+
// The symbolic-link rule, per entry: a link already on disk under the
|
|
1813
|
+
// destination must not redirect this entry's bytes. Reported like the
|
|
1814
|
+
// other per-entry refusals, so one such entry does not fail the rest.
|
|
1815
|
+
// The checkout's own name is reserved at the repository root here too: an
|
|
1816
|
+
// archive carrying it would otherwise create the one folder no workspace
|
|
1817
|
+
// path can name. Reported like the other per-entry refusals — one such
|
|
1818
|
+
// entry does not fail the rest — and asked on its own rather than inside
|
|
1819
|
+
// the catch below, which stays the closed list of types it was.
|
|
1820
|
+
const reservedName = (() => {
|
|
1821
|
+
try {
|
|
1822
|
+
assertRepoRootNameFree(relForReport, this.kbDirName);
|
|
1823
|
+
return null;
|
|
1824
|
+
} catch (err) {
|
|
1825
|
+
return err instanceof Error ? err.message : String(err);
|
|
1826
|
+
}
|
|
1827
|
+
})();
|
|
1828
|
+
if (reservedName !== null) {
|
|
1829
|
+
skipped.push({ path: rawName, reason: reservedName });
|
|
1830
|
+
continue;
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
try {
|
|
1834
|
+
await assertNotGitInternals(workspaceDir, trimmed, targetAbsolute);
|
|
1835
|
+
await this.assertNotThroughLink(targetAbsolute, workspaceDir);
|
|
1836
|
+
} catch (err) {
|
|
1837
|
+
if (err instanceof PathTraversalError || err instanceof GitInternalsError) {
|
|
1838
|
+
skipped.push({ path: rawName, reason: err.message });
|
|
1839
|
+
continue;
|
|
1840
|
+
}
|
|
1841
|
+
throw err;
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1178
1844
|
if (entry.isDirectory) {
|
|
1179
1845
|
if (!(await allowEntry())) continue;
|
|
1180
1846
|
await fs.mkdir(targetAbsolute, { recursive: true });
|
|
@@ -1313,15 +1979,9 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
1313
1979
|
}
|
|
1314
1980
|
let stdout: string;
|
|
1315
1981
|
try {
|
|
1316
|
-
({ stdout } = await
|
|
1317
|
-
encoding: 'utf-8',
|
|
1318
|
-
maxBuffer: 16 * 1024 * 1024,
|
|
1319
|
-
}));
|
|
1982
|
+
({ stdout } = await this.gitRunner.run(repoDir, ['status', '--porcelain=v1', '-z']));
|
|
1320
1983
|
} catch (err) {
|
|
1321
|
-
|
|
1322
|
-
`[workspace] orphan scan via git status failed for ${workspaceId}:`,
|
|
1323
|
-
err instanceof Error ? err.message : err,
|
|
1324
|
-
);
|
|
1984
|
+
log.warn(`orphan scan via git status failed for ${workspaceId}:`, { err });
|
|
1325
1985
|
return [];
|
|
1326
1986
|
}
|
|
1327
1987
|
if (!stdout.trim()) return [];
|
|
@@ -1390,10 +2050,7 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
1390
2050
|
this.branchDirs.delete(branchForWorkspaceId(entry.name));
|
|
1391
2051
|
removed.push(entry.name);
|
|
1392
2052
|
} catch (err) {
|
|
1393
|
-
|
|
1394
|
-
`[workspace] sweep failed for ${entry.name}:`,
|
|
1395
|
-
err instanceof Error ? err.message : err,
|
|
1396
|
-
);
|
|
2053
|
+
log.warn(`sweep failed for ${entry.name}:`, { err });
|
|
1397
2054
|
}
|
|
1398
2055
|
}
|
|
1399
2056
|
return { removed };
|
|
@@ -1459,7 +2116,7 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
1459
2116
|
const workspaceDir = this.workspaceDirWithinRoot(workspaceId);
|
|
1460
2117
|
try {
|
|
1461
2118
|
await fs.access(path.join(workspaceDir, this.kbDirName, '.git'));
|
|
1462
|
-
this.
|
|
2119
|
+
this.registerBranchDir(branch, workspaceDir);
|
|
1463
2120
|
return workspaceDir;
|
|
1464
2121
|
} catch {
|
|
1465
2122
|
// Fall through to lazy bootstrap.
|
|
@@ -1483,78 +2140,118 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
1483
2140
|
}
|
|
1484
2141
|
}
|
|
1485
2142
|
|
|
1486
|
-
private
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
2143
|
+
private async buildFileTree(root: string, workspaceRoot: string, readFilter?: ReadTreeFilter): Promise<FileTreeEntry> {
|
|
2144
|
+
/** A folder while the tree is built: whether the caller may read it decides, once its subtree is known, whether it stays. */
|
|
2145
|
+
interface DirNode {
|
|
2146
|
+
name: string;
|
|
2147
|
+
relativePath: string;
|
|
2148
|
+
readable: boolean;
|
|
2149
|
+
/** Hidden by a listing decision, not a read rule: dropping it withholds nothing. */
|
|
2150
|
+
unlisted: boolean;
|
|
2151
|
+
children: (FileTreeEntry | DirNode)[];
|
|
2152
|
+
/**
|
|
2153
|
+
* Files directly in it the caller's read rules kept out — counted, never
|
|
2154
|
+
* named. Only an entry the filter judged unreadable counts:
|
|
2155
|
+
* `.bevelignore`d and `.git` entries never reach it, and an `'unlisted'`
|
|
2156
|
+
* one is kept from no one. Folders dropped below it are added in `finish`.
|
|
2157
|
+
*/
|
|
2158
|
+
withheld: number;
|
|
1491
2159
|
}
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
2160
|
+
const relOf = (abs: string) => path.relative(workspaceRoot, abs).replace(/\\/g, '/');
|
|
2161
|
+
const top: DirNode = { name: path.basename(root), relativePath: relOf(root) || '.', readable: true, unlisted: false, children: [], withheld: 0 };
|
|
2162
|
+
const nodes = new Map<string, DirNode>([['', top]]);
|
|
2163
|
+
|
|
2164
|
+
await this.disk.walk(root, explorerWalk(), [
|
|
2165
|
+
{
|
|
2166
|
+
async onDir(rel, entries) {
|
|
2167
|
+
const node = nodes.get(rel)!;
|
|
2168
|
+
const abs = path.join(root, rel);
|
|
2169
|
+
const rels = entries.map((e) => relOf(path.join(abs, e.name)));
|
|
2170
|
+
// Read-permission filter: ONE batched check per directory. Drops files the
|
|
2171
|
+
// caller can't read. A directory is kept when the caller can read it — a
|
|
2172
|
+
// readable directory left empty after filtering stays visible, the folder
|
|
2173
|
+
// itself is readable — OR when something readable survives beneath it: a
|
|
2174
|
+
// grant below (a linked skill's folder opened to a plugin's readers, a
|
|
2175
|
+
// sub-folder shared on its own) makes the folders above it the way there,
|
|
2176
|
+
// shown as containers whose own contents stay filtered. So an unreadable
|
|
2177
|
+
// directory is walked, not skipped, and dropped only when the walk finds
|
|
2178
|
+
// nothing (see `finish`). Fail-closed: anything not explicitly readable is
|
|
2179
|
+
// dropped. No filter → identical to the pre-feature tree (regression-safe).
|
|
2180
|
+
const verdict = readFilter && entries.length > 0 ? await readFilter(rels) : null;
|
|
2181
|
+
const readable = (rel: string) => verdict === null || verdict.get(rel) === true;
|
|
2182
|
+
const unlisted = (rel: string) => verdict !== null && verdict.get(rel) === 'unlisted';
|
|
2183
|
+
entries.forEach((entry, i) => {
|
|
2184
|
+
const entryRel = rels[i]!;
|
|
2185
|
+
if (entry.isDirectory()) {
|
|
2186
|
+
const child: DirNode = {
|
|
2187
|
+
name: entry.name,
|
|
2188
|
+
relativePath: entryRel,
|
|
2189
|
+
readable: readable(entryRel),
|
|
2190
|
+
unlisted: unlisted(entryRel),
|
|
2191
|
+
children: [],
|
|
2192
|
+
withheld: 0,
|
|
2193
|
+
};
|
|
2194
|
+
nodes.set(rel ? `${rel}/${entry.name}` : entry.name, child);
|
|
2195
|
+
node.children.push(child);
|
|
2196
|
+
} else if (readable(entryRel)) {
|
|
2197
|
+
node.children.push({ name: entry.name, relativePath: entryRel, type: 'file' });
|
|
2198
|
+
} else if (!unlisted(entryRel)) {
|
|
2199
|
+
node.withheld++;
|
|
2200
|
+
}
|
|
2201
|
+
});
|
|
2202
|
+
},
|
|
2203
|
+
},
|
|
2204
|
+
]);
|
|
1503
2205
|
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
const
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
// caller can't read. A directory is kept when the caller can read it — a
|
|
1527
|
-
// readable directory left empty after filtering stays visible, the folder
|
|
1528
|
-
// itself is readable — OR when something readable survives beneath it: a
|
|
1529
|
-
// grant below (a linked skill's folder opened to a plugin's readers, a
|
|
1530
|
-
// sub-folder shared on its own) makes the folders above it the way there,
|
|
1531
|
-
// shown as containers whose own contents stay filtered. So an unreadable
|
|
1532
|
-
// directory is walked, not skipped, and dropped only when the walk finds
|
|
1533
|
-
// nothing. Fail-closed: anything not explicitly readable is dropped. No
|
|
1534
|
-
// filter → identical to the pre-feature tree (regression-safe).
|
|
1535
|
-
const verdict = readFilter && candidates.length > 0 ? await readFilter(candidates.map((c) => c.rel)) : null;
|
|
1536
|
-
const readable = (rel: string) => verdict === null || verdict.get(rel) === true;
|
|
1537
|
-
|
|
1538
|
-
const children: FileTreeEntry[] = [];
|
|
1539
|
-
for (const { entry, entryPath, rel } of candidates) {
|
|
1540
|
-
if (entry.isDirectory()) {
|
|
1541
|
-
const sub = await this.buildFileTree(entryPath, workspaceRoot, ignoreStack, readFilter);
|
|
1542
|
-
if (readable(rel) || (sub.children?.length ?? 0) > 0) children.push(sub);
|
|
1543
|
-
} else if (readable(rel)) {
|
|
1544
|
-
children.push({ name: entry.name, relativePath: rel, type: 'file' });
|
|
2206
|
+
/**
|
|
2207
|
+
* Bottom-up: drop a folder the caller may not read once nothing readable
|
|
2208
|
+
* turned up beneath it; sort each level. Each folder that stays carries
|
|
2209
|
+
* how many entries were withheld anywhere under it (a dropped folder
|
|
2210
|
+
* counts as one, its own subtree's count included), so a tree drawn from
|
|
2211
|
+
* ONE folder — the Library's, a delete's — can tell "empty" from "kept
|
|
2212
|
+
* from you" for that folder rather than for the listing. Only when
|
|
2213
|
+
* something was withheld, so an unfiltered — or all-readable — listing
|
|
2214
|
+
* stays exactly the tree it always was.
|
|
2215
|
+
*/
|
|
2216
|
+
const finish = (node: DirNode): FileTreeEntry => {
|
|
2217
|
+
const children: FileTreeEntry[] = [];
|
|
2218
|
+
let withheld = node.withheld;
|
|
2219
|
+
for (const child of node.children) {
|
|
2220
|
+
if ('type' in child) {
|
|
2221
|
+
children.push(child);
|
|
2222
|
+
continue;
|
|
2223
|
+
}
|
|
2224
|
+
const sub = finish(child);
|
|
2225
|
+
withheld += sub.withheld ?? 0;
|
|
2226
|
+
if (child.readable || (sub.children?.length ?? 0) > 0) children.push(sub);
|
|
2227
|
+
else if (!child.unlisted) withheld++;
|
|
1545
2228
|
}
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
return {
|
|
1554
|
-
name,
|
|
1555
|
-
relativePath: relativePath || '.',
|
|
1556
|
-
type: 'directory',
|
|
1557
|
-
children,
|
|
2229
|
+
children.sort((a, b) => {
|
|
2230
|
+
if (a.type !== b.type) return a.type === 'directory' ? -1 : 1;
|
|
2231
|
+
return a.name.localeCompare(b.name);
|
|
2232
|
+
});
|
|
2233
|
+
const entry: FileTreeEntry = { name: node.name, relativePath: node.relativePath, type: 'directory', children };
|
|
2234
|
+
return withheld > 0 ? { ...entry, withheld } : entry;
|
|
1558
2235
|
};
|
|
2236
|
+
return finish(top);
|
|
1559
2237
|
}
|
|
1560
2238
|
}
|
|
2239
|
+
|
|
2240
|
+
/**
|
|
2241
|
+
* The explorer's walk of a workspace: no git internals (never user content,
|
|
2242
|
+
* and listing it would blow up the tree), no `.gitkeep` (the empty-folder
|
|
2243
|
+
* placeholder, not worth showing), `.bevelignore` honoured on the way down —
|
|
2244
|
+
* and a folder that cannot be listed is the request's error, not a tree with
|
|
2245
|
+
* a silent gap. A folder download and the whole-KB read walk the same way,
|
|
2246
|
+
* so they carry exactly what the explorer shows.
|
|
2247
|
+
*/
|
|
2248
|
+
function explorerWalk(): TreeWalkOptions {
|
|
2249
|
+
return {
|
|
2250
|
+
// Any spelling of the git folder (`.GIT`, `.git.`) — the same rule the path
|
|
2251
|
+
// guards apply, so a download or listing never carries what they refuse —
|
|
2252
|
+
// and the empty-folder placeholder, which is never content.
|
|
2253
|
+
skip: (e) => hasGitInternalsSegment(e.name) || (isFolderPlaceholder(e.name) && e.isFile()),
|
|
2254
|
+
ignore: true,
|
|
2255
|
+
unreadable: 'throw',
|
|
2256
|
+
};
|
|
2257
|
+
}
|