@vantran-se/openchamber-web 2.1.1 → 2.2.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/README.md +32 -24
- package/bin/cli.js +1 -3
- package/bin/cli.test.js +40 -16
- package/bin/lib/DOCUMENTATION.md +6 -5
- package/bin/lib/cli-args.js +6 -1
- package/bin/lib/cli-args.test.js +34 -0
- package/bin/lib/cli-lifecycle.js +0 -1
- package/bin/lib/cli-network.js +8 -2
- package/bin/lib/cli-process.js +1 -1
- package/bin/lib/cli-settings-accessors.test.js +21 -13
- package/bin/lib/cli-startup.js +17 -11
- package/bin/lib/cli-startup.test.js +92 -2
- package/bin/lib/commands-connect-url.js +8 -1
- package/bin/lib/commands-schedule.js +8 -2
- package/bin/lib/commands-serve.js +15 -21
- package/bin/lib/commands-update.js +12 -24
- package/bin/lib/commands-update.test.js +14 -46
- package/dist/assets/DiffView-Cv9MSXl1.js +4 -0
- package/dist/assets/FilePreviewCommentMenu-D4kLseHj.js +4 -0
- package/dist/assets/FilesView-BVoD8I2x.js +14 -0
- package/dist/assets/{GitView-DzuzhIPx.js → GitView-Bzb8p-ew.js} +4 -4
- package/dist/assets/GuestSurfacePane-Crdbvsxo.js +1 -0
- package/dist/assets/InProgressOperationBanner-CwMjNQgk.js +2 -0
- package/dist/assets/{JsonTreeView-B_EPRjzy.js → JsonTreeView-DRKHZfsn.js} +1 -1
- package/dist/assets/MarkdownImageGallery-DZtqT1Zn.js +1 -0
- package/dist/assets/MarkdownRendererImpl-CqG7HCNd.js +10 -0
- package/dist/assets/OnboardingScreen-DMIxwYK5.js +1 -0
- package/dist/assets/{PatchDiff-6yh1csza.js → PatchDiff-CUcCg1jk.js} +1 -1
- package/dist/assets/PierreDiffViewer-CnnR-dnD.js +228 -0
- package/dist/assets/PlanView-RCBadcY7.js +5 -0
- package/dist/assets/SettingsView-CjBjZFRO.js +30 -0
- package/dist/assets/SettingsWindow-DyM7QuhI.js +1 -0
- package/dist/assets/SubmoduleDiffSummary-BWIfgMED.js +1 -0
- package/dist/assets/ToolOutputDialog-Q0P3FuAi.js +15 -0
- package/dist/assets/ToolPartDiffPreview-p7qR9FpU.js +8 -0
- package/dist/assets/{VirtualizedFileDiff-BTJVRkyo.js → VirtualizedFileDiff-CFpjL8Ns.js} +3 -3
- package/dist/assets/{Virtualizer-xCEYz34p.js → Virtualizer-C0IsclZn.js} +1 -1
- package/dist/assets/WalkthroughView-DI31zD02.js +4 -0
- package/dist/assets/appThemeRegistry-B9gkFq9i.js +1 -0
- package/dist/assets/{computeVirtualFileMetrics-CmPe29DM.js → computeVirtualFileMetrics-C2WM6JC2.js} +2 -2
- package/dist/assets/de-YKfk5MmC.js +14 -0
- package/dist/assets/es-_gwT0kr-.js +20 -0
- package/dist/assets/fileTreeStatus-D438yi8M.js +1 -0
- package/dist/assets/fr-DqzvXmIm.js +18 -0
- package/dist/assets/index-BLaHKdtx.js +1 -0
- package/dist/assets/index-BkMXRrVQ.css +1 -0
- package/dist/assets/index-DxNa7xYB.js +65 -0
- package/dist/assets/initializeSharedPreferences-Bck5EAWP.js +1 -0
- package/dist/assets/{isDiffPlainText-CQmDCnEv.js → isDiffPlainText-Dgbw8VKk.js} +1 -1
- package/dist/assets/{isFilePlainText-CYBUzW4I.js → isFilePlainText-_ZS5UYRj.js} +1 -1
- package/dist/assets/ja-B-GFYhQE.js +20 -0
- package/dist/assets/ko-mzksfqDC.js +20 -0
- package/dist/assets/main-C5Xx8dn_.js +2 -0
- package/dist/assets/main-CfBSQM1H.js +990 -0
- package/dist/assets/markdownCore-BmWwW1ll.js +7 -0
- package/dist/assets/miniChat-mnKwy4vQ.js +2 -0
- package/dist/assets/mobile-DeEQd0wZ.js +2 -0
- package/dist/assets/nl-CWQr6NSq.js +20 -0
- package/dist/assets/{parseDiffFromFile-Cy0PNxun.js → parseDiffFromFile-BUgn-T9p.js} +2 -2
- package/dist/assets/patchFileDiff-Dt8LhgBh.js +13 -0
- package/dist/assets/pl-UmRcDUX2.js +20 -0
- package/dist/assets/pt-BR-BSLybvVF.js +20 -0
- package/dist/assets/relocateRestoredSession-B5E9xb3T.js +1 -0
- package/dist/assets/renderElectronMiniChatApp-C0a9A5_J.js +2 -0
- package/dist/assets/renderMobileApp-BNncg3Pk.js +25 -0
- package/dist/assets/runtimeEndpointReset-CZ5aafpl.js +62 -0
- package/dist/assets/{selectionMarkdown-TkYk5h9l.js → selectionMarkdown-CUN3wUld.js} +9 -9
- package/dist/assets/{sessionOwnership-Bs1xbRfB.js → sessionOwnership-BSuxCEhb.js} +1 -1
- package/dist/assets/shikiHighlight-DHDx-6eM.js +1 -0
- package/dist/assets/space-apply-DM_2pRW7.js +1 -0
- package/dist/assets/space-close-Bpomt8z0.js +1 -0
- package/dist/assets/{surface-B6n7wJtt.js → surface-C-jQaGnL.js} +1 -1
- package/dist/assets/{textMateThemeFromAppTheme-CDlKffWz.js → textMateThemeFromAppTheme-DlsTphbY.js} +1 -1
- package/dist/assets/tr-gaTrX_HK.js +11 -0
- package/dist/assets/uk-CnJF6ykl.js +20 -0
- package/dist/assets/{useAppFontEffects-DCqCVGlT.js → useAppFontEffects-DGlvOqP_.js} +194 -233
- package/dist/assets/useInlineCommentController-kMN4Wg_D.js +2 -0
- package/dist/assets/{useRootScrollLock-BowwS8kq.js → useRootScrollLock-DMPpvpNP.js} +2 -2
- package/dist/assets/{vendor-base-ui-DX96QhTP.js → vendor-base-ui-Det7U81h.js} +1 -1
- package/dist/assets/{vendor-codemirror-autocomplete-DW1FQz9_.js → vendor-codemirror-autocomplete-BSHQHbKu.js} +1 -1
- package/dist/assets/{vendor-codemirror-commands-DpmhalwN.js → vendor-codemirror-commands-C4G_dPWe.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-angular-BqExbN1M.js → vendor-codemirror-lang-angular-B6LbBGHB.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-cpp-DZA36HfS.js → vendor-codemirror-lang-cpp-BWWXBx7W.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-css-B07blnnZ.js → vendor-codemirror-lang-css-Cayfb29q.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-go-BM8K9D1-.js → vendor-codemirror-lang-go-BoKlCNKw.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-html-DaH9adfE.js → vendor-codemirror-lang-html-BQWerHsJ.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-java-CsdC0viz.js → vendor-codemirror-lang-java-DWX7Fq_K.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-javascript-CpGYXGuo.js → vendor-codemirror-lang-javascript-DbpEcZM0.js} +2 -2
- package/dist/assets/{vendor-codemirror-lang-jinja-BBeGzXw5.js → vendor-codemirror-lang-jinja-D1JIJRA7.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-json-58T70Hqs.js → vendor-codemirror-lang-json-BzgckzmB.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-less-BI-xCA59.js → vendor-codemirror-lang-less-B9EpDXFi.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-liquid-zEcMa-zy.js → vendor-codemirror-lang-liquid-9U7kRbSf.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-markdown-BLKnlLP4.js → vendor-codemirror-lang-markdown-D2TEBIvZ.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-php-9f35M83-.js → vendor-codemirror-lang-php-BT6sDVzE.js} +1 -1
- package/dist/assets/vendor-codemirror-lang-python-jAMtYiA9.js +3 -0
- package/dist/assets/{vendor-codemirror-lang-rust-MNDL_jZQ.js → vendor-codemirror-lang-rust-CVO56EiL.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-sass-p9epsSNm.js → vendor-codemirror-lang-sass-DZlOoUWu.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-sql-Bqzh0EXX.js → vendor-codemirror-lang-sql-fZ6t1DPw.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-vue-BnNUWV-p.js → vendor-codemirror-lang-vue-DxbqrLiE.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-wast-C2a-oLmk.js → vendor-codemirror-lang-wast-DyU1Gt-I.js} +1 -1
- package/dist/assets/vendor-codemirror-lang-xml-C9a_K5_p.js +1 -0
- package/dist/assets/{vendor-codemirror-lang-yaml-C0sjswB0.js → vendor-codemirror-lang-yaml-Cptq8l0e.js} +1 -1
- package/dist/assets/{vendor-codemirror-language-ByBn5zIW.js → vendor-codemirror-language-DufXoQvE.js} +2 -2
- package/dist/assets/vendor-codemirror-language-data-CtwSWKtX.js +2 -0
- package/dist/assets/{vendor-codemirror-search-ib-sHD5W.js → vendor-codemirror-search-CvXM_pZg.js} +1 -1
- package/dist/assets/{vendor-codemirror-state-G3UuR-be.js → vendor-codemirror-state-Dek9sMtw.js} +1 -1
- package/dist/assets/{vendor-codemirror-view-Bniqp7p5.js → vendor-codemirror-view-1e0FQ6Wn.js} +1 -1
- package/dist/assets/vendor-framer-motion-DxDVhOLc.js +9 -0
- package/dist/assets/vendor-katex-CuQvj9-L.js +257 -0
- package/dist/assets/vendor-katex-DdZNUVNG.css +1 -0
- package/dist/assets/{vendor-motion-dom-DcCgeG69.js → vendor-motion-dom-HXPOMxb7.js} +1 -1
- package/dist/assets/{vendor-opencode-client-DHzhmLwV.js → vendor-opencode-client-BmFcUkbQ.js} +1 -1
- package/dist/assets/{vendor-replit-codemirror-vim-DkzqxN2H.js → vendor-replit-codemirror-vim-BlJRn-cW.js} +2 -2
- package/dist/assets/{vendor-tanstack-react-virtual-H_JmXOFV.js → vendor-tanstack-react-virtual-CvuLsk7D.js} +1 -1
- package/dist/assets/{vendor-tanstack-virtual-core-Dxc8hSTB.js → vendor-tanstack-virtual-core-DPaNcJcT.js} +1 -1
- package/dist/assets/zh-CN-t2vigSB1.js +20 -0
- package/dist/assets/zh-TW-jAgqBokB.js +20 -0
- package/dist/index.html +6 -6
- package/dist/mini-chat.html +6 -6
- package/dist/mobile.html +6 -6
- package/package.json +8 -5
- package/server/index.js +80 -36
- package/server/lib/agent-memory/actions.js +1 -6
- package/server/lib/agent-memory/routes.js +1 -2
- package/server/lib/agent-memory/runtime.js +1 -8
- package/server/lib/agent-tool/DOCUMENTATION.md +7 -3
- package/server/lib/agent-tool/runtime.js +3 -6
- package/server/lib/agent-tool/runtime.test.js +0 -2
- package/server/lib/browser-control/DOCUMENTATION.md +6 -0
- package/server/lib/changelog/update-notes.js +1 -1
- package/server/lib/context-obligatory/runtime.js +1 -2
- package/server/lib/dev-tunnel/DOCUMENTATION.md +8 -0
- package/server/lib/dev-tunnel/client.js +21 -10
- package/server/lib/dev-tunnel/tunnel.test.js +34 -0
- package/server/lib/dispatch-results/runtime.js +1 -1
- package/server/lib/enterprise-mode.d.ts +1 -0
- package/server/lib/enterprise-mode.js +13 -2
- package/server/lib/enterprise-mode.test.js +21 -0
- package/server/lib/event-stream/DOCUMENTATION.md +2 -2
- package/server/lib/event-stream/index.js +1 -3
- package/server/lib/event-stream/translate-v2.js +1 -9
- package/server/lib/event-stream/translate-v2.test.js +0 -11
- package/server/lib/fs/DOCUMENTATION.md +5 -2
- package/server/lib/fs/routes.js +62 -15
- package/server/lib/fs/routes.test.js +278 -48
- package/server/lib/fs/workspace-file-names.js +104 -0
- package/server/lib/fs/workspace-file-names.test.js +62 -0
- package/server/lib/git/DOCUMENTATION.md +17 -5
- package/server/lib/git/contributor-provenance-storage.js +9 -6
- package/server/lib/git/contributor-provenance-storage.test.js +29 -0
- package/server/lib/git/credential-broker.js +1 -1
- package/server/lib/git/credential-resolver.js +1 -4
- package/server/lib/git/credentials.js +1 -0
- package/server/lib/git/discovery-endpoint.js +1 -1
- package/server/lib/git/identity-provisioning.js +2 -1
- package/server/lib/git/identity-storage.js +3 -4
- package/server/lib/git/lfs-discovery.js +14 -14
- package/server/lib/git/lfs-discovery.test.js +16 -6
- package/server/lib/git/network-operation-plan.js +50 -62
- package/server/lib/git/network-operation-plan.test.js +103 -1
- package/server/lib/git/network-operation-registry.js +4 -6
- package/server/lib/git/network-operation-storage.js +4 -6
- package/server/lib/git/network-operations.js +12 -4
- package/server/lib/git/network-operations.test.js +19 -6
- package/server/lib/git/redaction.js +2 -1
- package/server/lib/git/repository-credential-runtime.js +5 -3
- package/server/lib/git/repository-credential-runtime.test.js +22 -0
- package/server/lib/git/routes.js +31 -137
- package/server/lib/git/routes.test.js +2 -2
- package/server/lib/git/service.js +143 -27
- package/server/lib/git/service.test.js +215 -1
- package/server/lib/git/ssh-credential-storage.js +1 -4
- package/server/lib/git/submodule-discovery.js +1 -1
- package/server/lib/git/worktree-bootstrap-storage.js +3 -4
- package/server/lib/git/worktree-bootstrap-storage.test.js +15 -0
- package/server/lib/git/worktree-creation.js +194 -0
- package/server/lib/git/worktree-creation.test.js +155 -0
- package/server/lib/github/DOCUMENTATION.md +11 -6
- package/server/lib/github/auth.js +2 -4
- package/server/lib/github/device-flow.js +2 -2
- package/server/lib/github/gh-cli-credential.js +1 -0
- package/server/lib/github/octokit.js +31 -0
- package/server/lib/github/pr-status.js +35 -18
- package/server/lib/github/pr-status.test.js +271 -3
- package/server/lib/github/reference-search.js +102 -13
- package/server/lib/github/reference-search.test.js +48 -7
- package/server/lib/github/routes.auth.test.js +18 -0
- package/server/lib/github/routes.js +360 -6
- package/server/lib/github/routes.mutations.test.js +239 -0
- package/server/lib/github/routes.test.js +134 -2
- package/server/lib/gitlab/DOCUMENTATION.md +5 -1
- package/server/lib/gitlab/auth-storage.test.js +1 -0
- package/server/lib/gitlab/mappers.js +30 -1
- package/server/lib/gitlab/resources.js +230 -6
- package/server/lib/gitlab/resources.test.js +179 -0
- package/server/lib/gitlab/routes.js +200 -6
- package/server/lib/gitlab/routes.test.js +186 -2
- package/server/lib/gitlab/validation.js +1 -5
- package/server/lib/guests/DOCUMENTATION.md +1 -1
- package/server/lib/guests/oauth.js +2 -2
- package/server/lib/guests/service.js +1 -1
- package/server/lib/guests/sockets.js +1 -1
- package/server/lib/injected-env.d.ts +8 -0
- package/server/lib/injected-env.js +50 -0
- package/server/lib/injected-env.test.js +56 -0
- package/server/lib/linear/DOCUMENTATION.md +5 -6
- package/server/lib/linear/issues.js +62 -4
- package/server/lib/linear/issues.test.js +68 -0
- package/server/lib/linear/oauth.js +5 -5
- package/server/lib/linear/parse.js +2 -9
- package/server/lib/message-queue/DOCUMENTATION.md +23 -1
- package/server/lib/message-queue/runtime.js +85 -12
- package/server/lib/message-queue/runtime.test.js +172 -2
- package/server/lib/notifications/apns-runtime.js +5 -6
- package/server/lib/notifications/apns-runtime.test.js +20 -4
- package/server/lib/openchamber-control/DOCUMENTATION.md +19 -1
- package/server/lib/openchamber-control/actions.js +2 -1
- package/server/lib/openchamber-control/service.js +119 -20
- package/server/lib/openchamber-control/service.test.js +166 -0
- package/server/lib/openchamber-sessions/archive-store.js +1 -6
- package/server/lib/openchamber-sessions/routes.js +47 -12
- package/server/lib/openchamber-sessions/routes.test.js +65 -2
- package/server/lib/openchamber-sessions/session-metadata-store.js +1 -6
- package/server/lib/opencode/DOCUMENTATION.md +22 -8
- package/server/lib/opencode/agents.js +62 -0
- package/server/lib/opencode/auth-state-runtime.js +3 -1
- package/server/lib/opencode/auth.d.ts +1 -1
- package/server/lib/opencode/auth.js +8 -0
- package/server/lib/opencode/auth.test.js +31 -0
- package/server/lib/opencode/config-entity-routes.js +16 -0
- package/server/lib/opencode/config-entity-v2.test.js +16 -1
- package/server/lib/opencode/config-paths.test.js +2 -2
- package/server/lib/opencode/config-v2.js +2 -4
- package/server/lib/opencode/feature-routes-runtime.js +89 -49
- package/server/lib/opencode/lifecycle.js +41 -21
- package/server/lib/opencode/lifecycle.test.js +50 -2
- package/server/lib/opencode/model-discovery.js +1 -1
- package/server/lib/opencode/model-discovery.test.js +17 -0
- package/server/lib/opencode/models-metadata.js +0 -2
- package/server/lib/opencode/openchamber-routes.test.js +3 -3
- package/server/lib/opencode/plugins.js +6 -9
- package/server/lib/opencode/provider-source-route.test.js +101 -3
- package/server/lib/opencode/providers-disabled.test.js +59 -0
- package/server/lib/opencode/providers.js +34 -0
- package/server/lib/opencode/proxy.js +11 -3
- package/server/lib/opencode/routes.js +40 -2
- package/server/lib/opencode/settings-helpers.js +27 -1
- package/server/lib/opencode/settings-helpers.test.js +19 -7
- package/server/lib/opencode/settings-registry.json +27 -16
- package/server/lib/opencode/settings-runtime.test.js +18 -0
- package/server/lib/opencode/shared-service-runtime.js +23 -104
- package/server/lib/opencode/shared.js +77 -46
- package/server/lib/opencode/shared.test.js +12 -12
- package/server/lib/opencode/shutdown-runtime.test.js +2 -3
- package/server/lib/opencode/skill-routes.js +123 -38
- package/server/lib/opencode/skill-routes.test.js +201 -0
- package/server/lib/opencode/skills.js +81 -34
- package/server/lib/opencode/skills.test.js +176 -21
- package/server/lib/opencode/startup-pipeline-runtime.js +6 -3
- package/server/lib/opencode/startup-pipeline-runtime.test.js +2 -1
- package/server/lib/opencode/watcher.test.js +0 -54
- package/server/lib/outbound-proxy.js +52 -0
- package/server/lib/outbound-proxy.test.js +55 -0
- package/server/lib/package-manager.js +23 -17
- package/server/lib/package-manager.test.js +5 -18
- package/server/lib/permission-auto-accept/DOCUMENTATION.md +1 -1
- package/server/lib/permission-auto-accept/runtime.js +19 -47
- package/server/lib/permission-auto-accept/runtime.test.js +2 -2
- package/server/lib/project-context/DOCUMENTATION.md +7 -1
- package/server/lib/project-context/routes.js +1 -2
- package/server/lib/project-context/runtime.js +12 -14
- package/server/lib/project-context/runtime.test.js +78 -2
- package/server/lib/projects/project-config.js +30 -15
- package/server/lib/projects/project-config.test.js +30 -0
- package/server/lib/projects/project-setup.js +1 -2
- package/server/lib/projects/routes.js +1 -2
- package/server/lib/quota/DOCUMENTATION.md +35 -7
- package/server/lib/quota/index.js +1 -0
- package/server/lib/quota/providers/claude/index.js +1 -1
- package/server/lib/quota/providers/cline-pass.js +1 -1
- package/server/lib/quota/providers/exe-dev.js +1 -1
- package/server/lib/quota/providers/google/index.js +1 -1
- package/server/lib/quota/providers/hyper.js +1 -1
- package/server/lib/quota/providers/kimi.js +116 -36
- package/server/lib/quota/providers/kimi.test.js +128 -3
- package/server/lib/quota/providers/opencode-go.js +144 -21
- package/server/lib/quota/providers/opencode-go.test.js +229 -6
- package/server/lib/quota/providers/openrouter.js +1 -1
- package/server/lib/quota/providers/zenmux.js +1 -1
- package/server/lib/quota/utils/auth.js +1 -0
- package/server/lib/quota/utils/transformers.js +2 -6
- package/server/lib/relay/DOCUMENTATION.md +18 -3
- package/server/lib/relay/e2ee.js +7 -7
- package/server/lib/relay/host-client.js +84 -13
- package/server/lib/relay/host-client.test.js +201 -1
- package/server/lib/relay/identity.js +6 -29
- package/server/lib/relay/identity.test.js +18 -28
- package/server/lib/relay/key-store.js +141 -0
- package/server/lib/relay/key-store.test.js +138 -0
- package/server/lib/relay/service.js +8 -8
- package/server/lib/relay/service.test.js +10 -5
- package/server/lib/relay/signing-key.js +13 -36
- package/server/lib/relay/tunnel-codec.js +10 -10
- package/server/lib/route-module-exports.test.js +22 -0
- package/server/lib/routing/DOCUMENTATION.md +23 -3
- package/server/lib/routing/cache.js +87 -0
- package/server/lib/routing/defaults.js +11 -0
- package/server/lib/routing/history.js +12 -4
- package/server/lib/routing/runtime.js +39 -3
- package/server/lib/routing/runtime.test.js +81 -2
- package/server/lib/routing/store.js +12 -1
- package/server/lib/routing/store.test.js +13 -0
- package/server/lib/scheduled-tasks/DOCUMENTATION.md +59 -1
- package/server/lib/scheduled-tasks/existing-session.js +119 -0
- package/server/lib/scheduled-tasks/existing-session.test.js +350 -0
- package/server/lib/scheduled-tasks/loops.js +1 -8
- package/server/lib/scheduled-tasks/routes.js +8 -8
- package/server/lib/scheduled-tasks/runtime.js +103 -13
- package/server/lib/scheduled-tasks/runtime.test.js +48 -0
- package/server/lib/scheduled-tasks/service.js +33 -11
- package/server/lib/scheduled-tasks/service.test.js +128 -2
- package/server/lib/scheduled-tasks/session-defaults.js +27 -0
- package/server/lib/scheduled-tasks/session-defaults.test.js +19 -0
- package/server/lib/session-assist/context.js +1 -0
- package/server/lib/session-folders/routes.js +2 -2
- package/server/lib/session-goal/objectives.js +1 -1
- package/server/lib/session-knowledge/routes.js +2 -2
- package/server/lib/session-knowledge/runtime.js +1 -2
- package/server/lib/session-work/DOCUMENTATION.md +39 -14
- package/server/lib/session-work/questions.js +31 -2
- package/server/lib/session-work/runtime.js +22 -13
- package/server/lib/session-work/runtime.test.js +43 -26
- package/server/lib/session-work/state.js +10 -4
- package/server/lib/session-work/state.test.js +8 -1
- package/server/lib/shared/DOCUMENTATION.md +89 -0
- package/server/lib/shared/guards.js +53 -0
- package/server/lib/shared/guards.test.js +112 -0
- package/server/lib/skills-catalog/DOCUMENTATION.md +10 -8
- package/server/lib/skills-catalog/cache.js +3 -2
- package/server/lib/skills-catalog/curated-sources.js +5 -1
- package/server/lib/skills-catalog/git.js +107 -20
- package/server/lib/skills-catalog/git.test.js +78 -0
- package/server/lib/skills-catalog/install.js +21 -12
- package/server/lib/skills-catalog/scan.js +25 -15
- package/server/lib/skills-catalog/source.js +26 -1
- package/server/lib/skills-catalog/source.test.js +27 -0
- package/server/lib/source-control/DOCUMENTATION.md +2 -1
- package/server/lib/source-control/audit-storage.js +7 -6
- package/server/lib/source-control/audit-storage.test.js +11 -0
- package/server/lib/source-control/binding-service.js +2 -5
- package/server/lib/source-control/change-request-worktree.js +90 -0
- package/server/lib/source-control/change-request-worktree.test.js +121 -0
- package/server/lib/source-control/mutation-executor.js +1 -1
- package/server/lib/source-control/mutation-storage.js +11 -5
- package/server/lib/source-control/provider-instance.js +1 -2
- package/server/lib/source-control/repository-identity.js +1 -1
- package/server/lib/source-control/routes.js +14 -2
- package/server/lib/spaces/DOCUMENTATION.md +42 -17
- package/server/lib/spaces/command-stream.test.js +15 -0
- package/server/lib/spaces/dispatcher.js +7 -0
- package/server/lib/spaces/dispatcher.test.js +17 -0
- package/server/lib/spaces/hardening.js +2 -2
- package/server/lib/spaces/hardening.test.js +1 -1
- package/server/lib/spaces/host.js +2 -0
- package/server/lib/spaces/journey.js +66 -5
- package/server/lib/spaces/journey.test.js +95 -4
- package/server/lib/spaces/layout.js +1 -1
- package/server/lib/spaces/places/docker-disk.js +38 -14
- package/server/lib/spaces/places/docker-disk.test.js +80 -1
- package/server/lib/spaces/places/docker.js +35 -11
- package/server/lib/spaces/places/fake-docker.js +22 -3
- package/server/lib/spaces/places/memory-place.js +3 -0
- package/server/lib/spaces/places/packed.docker.live.test.js +1 -1
- package/server/lib/spaces/routes.js +5 -0
- package/server/lib/spaces/routes.test.js +4 -2
- package/server/lib/spaces/run-command.js +9 -2
- package/server/lib/spaces/tools.js +4 -5
- package/server/lib/spaces/tools.test.js +2 -2
- package/server/lib/terminal/runtime.js +18 -6
- package/server/lib/terminal/runtime.test.js +66 -0
- package/server/lib/tts/index.js +3 -0
- package/server/lib/tts/service.js +4 -2
- package/server/lib/walkthrough/DOCUMENTATION.md +3 -1
- package/server/lib/walkthrough/index.js +2 -3
- package/server/lib/walkthrough/pr-source.test.js +22 -0
- package/server/lib/walkthrough/sources.js +6 -1
- package/server/opencode-proxy.test.js +127 -0
- package/bin/lib/commands-startup.test.js +0 -15
- package/dist/assets/CodeMirrorCommentWidgets-CVDSjcrP.js +0 -1
- package/dist/assets/DiffView-Ct7vL3fD.js +0 -4
- package/dist/assets/FilesView-DT0uEYgT.js +0 -17
- package/dist/assets/GuestSurfacePane-DVvk1VPu.js +0 -1
- package/dist/assets/LinearIssuesView-C10MwA9h.js +0 -1
- package/dist/assets/MarkdownImageGallery-BbN_K_3d.js +0 -1
- package/dist/assets/MarkdownRendererImpl-BLt2Tkk9.js +0 -10
- package/dist/assets/OnboardingScreen-Bgp5A3WB.js +0 -1
- package/dist/assets/PierreDiffViewer-CWJLj-S-.js +0 -210
- package/dist/assets/PlanView-DsRWnsOI.js +0 -5
- package/dist/assets/PublishDialog-CCeKloRu.js +0 -2
- package/dist/assets/SettingsView-BuPPsNPp.js +0 -30
- package/dist/assets/SettingsWindow-CM5fL8Ot.js +0 -1
- package/dist/assets/SubmoduleDiffSummary-CfsMcfiQ.js +0 -1
- package/dist/assets/ToolOutputDialog-mZrfSofz.js +0 -15
- package/dist/assets/ToolPartDiffPreview-BUGCRYEL.js +0 -8
- package/dist/assets/WalkthroughView-ClBo7PPd.js +0 -4
- package/dist/assets/appThemeRegistry-CxzMxR-H.js +0 -1
- package/dist/assets/de-CBla4hjd.js +0 -14
- package/dist/assets/es-BmnZtxTD.js +0 -20
- package/dist/assets/fileTreeStatus-BSHFIGEg.js +0 -1
- package/dist/assets/fr-OVKIBUZO.js +0 -18
- package/dist/assets/index-CKCFKmjF.js +0 -1
- package/dist/assets/index-Dbx4V4GN.css +0 -1
- package/dist/assets/index-vQ1Pg1cr.js +0 -61
- package/dist/assets/ja-6v-yr-gE.js +0 -20
- package/dist/assets/ko-I_ZLvgQ9.js +0 -20
- package/dist/assets/main-CmO4dRtR.js +0 -2
- package/dist/assets/main-b9vqhlYM.js +0 -999
- package/dist/assets/markdownCore-BPEZRQfP.js +0 -7
- package/dist/assets/miniChat-B5Oi7B-6.js +0 -2
- package/dist/assets/mobile-CVtShif9.js +0 -2
- package/dist/assets/nl-DcvIJf46.js +0 -20
- package/dist/assets/patchFileDiff-CW3wCH1O.js +0 -13
- package/dist/assets/pl-JxCUl0sd.js +0 -20
- package/dist/assets/pt-BR-L89mjja2.js +0 -20
- package/dist/assets/relocateRestoredSession-DYh_9oBa.js +0 -1
- package/dist/assets/renderElectronMiniChatApp-BD-1JHSg.js +0 -2
- package/dist/assets/renderMobileApp-7s5InXt1.js +0 -25
- package/dist/assets/runtimeEndpointReset-DqLJC6L1.js +0 -53
- package/dist/assets/shikiHighlight-CXgZnvHh.js +0 -1
- package/dist/assets/space-apply-1trGVIl8.js +0 -1
- package/dist/assets/space-close-y7j-NuBv.js +0 -1
- package/dist/assets/tr-ka5J1flt.js +0 -11
- package/dist/assets/uk-PyGe6Q_u.js +0 -20
- package/dist/assets/useInlineCommentController-CrjSGCpi.js +0 -2
- package/dist/assets/vendor-codemirror-lang-python-B7ClrMIT.js +0 -3
- package/dist/assets/vendor-codemirror-lang-xml-C8tGnS64.js +0 -1
- package/dist/assets/vendor-codemirror-language-data-q2bKO1EM.js +0 -2
- package/dist/assets/vendor-framer-motion-Bq3iTZ6r.js +0 -1
- package/dist/assets/vendor-katex-CHD9BdDO.js +0 -257
- package/dist/assets/vendor-katex-Cp579UoJ.css +0 -1
- package/dist/assets/zh-CN-B0P2tybJ.js +0 -20
- package/dist/assets/zh-TW-Bs9C5cN8.js +0 -20
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
getTrackingBranch,
|
|
34
34
|
getWorktrees,
|
|
35
35
|
isGitRepository,
|
|
36
|
+
isPatchEquivalentOfHead,
|
|
36
37
|
observeWorktreeTopology,
|
|
37
38
|
populateWorktreeWithLockRecovery,
|
|
38
39
|
previewWorktreeCreate,
|
|
@@ -49,6 +50,7 @@ import {
|
|
|
49
50
|
getGlobalIdentity,
|
|
50
51
|
stageFiles,
|
|
51
52
|
subscribeWorktreeTopologyChanges,
|
|
53
|
+
renameBranch,
|
|
52
54
|
unstageFiles,
|
|
53
55
|
applyHunk,
|
|
54
56
|
getDiff,
|
|
@@ -56,7 +58,6 @@ import {
|
|
|
56
58
|
revertFile,
|
|
57
59
|
getUntrackedDiffs,
|
|
58
60
|
getFileDiff,
|
|
59
|
-
commit,
|
|
60
61
|
hasLocalIdentity,
|
|
61
62
|
validateWorktreeCreate,
|
|
62
63
|
parseBranchCreationSource,
|
|
@@ -905,6 +906,28 @@ describe('getStatus', () => {
|
|
|
905
906
|
await expect(getStatus(repo)).resolves.toMatchObject({ current: 'main' });
|
|
906
907
|
});
|
|
907
908
|
|
|
909
|
+
it('never rewrites the index, so a terminal commit does not meet its lock (#2229)', async () => {
|
|
910
|
+
if (!canRunGit()) return;
|
|
911
|
+
|
|
912
|
+
const repo = createTempDir();
|
|
913
|
+
runGit(repo, ['init', '-b', 'main']);
|
|
914
|
+
runGit(repo, ['config', 'user.email', 'test@example.com']);
|
|
915
|
+
runGit(repo, ['config', 'user.name', 'Test User']);
|
|
916
|
+
fs.writeFileSync(path.join(repo, 'README.md'), '# Test\n');
|
|
917
|
+
runGit(repo, ['add', 'README.md']);
|
|
918
|
+
runGit(repo, ['commit', '-m', 'Initial commit']);
|
|
919
|
+
// Same content under an older timestamp: the index entry is stale, and a
|
|
920
|
+
// status that takes the optional lock rewrites the index to refresh it.
|
|
921
|
+
const older = new Date(Date.now() - 60_000);
|
|
922
|
+
fs.utimesSync(path.join(repo, 'README.md'), older, older);
|
|
923
|
+
const indexPath = path.join(repo, '.git', 'index');
|
|
924
|
+
const indexBefore = fs.readFileSync(indexPath);
|
|
925
|
+
|
|
926
|
+
await expect(getStatus(repo)).resolves.toMatchObject({ isClean: true });
|
|
927
|
+
|
|
928
|
+
expect(fs.readFileSync(indexPath).equals(indexBefore)).toBe(true);
|
|
929
|
+
});
|
|
930
|
+
|
|
908
931
|
it('names the base an upstream-less branch was counted against, and only then', async () => {
|
|
909
932
|
if (!canRunGit()) return;
|
|
910
933
|
|
|
@@ -1149,6 +1172,48 @@ describe('worktree root resolution', () => {
|
|
|
1149
1172
|
}
|
|
1150
1173
|
});
|
|
1151
1174
|
|
|
1175
|
+
it('creates and removes a managed worktree from a bare repository project directory', async () => {
|
|
1176
|
+
if (!canRunGit()) return;
|
|
1177
|
+
const previousDataHome = process.env.XDG_DATA_HOME;
|
|
1178
|
+
const parent = createTempDir();
|
|
1179
|
+
process.env.XDG_DATA_HOME = path.join(parent, 'data');
|
|
1180
|
+
try {
|
|
1181
|
+
const source = path.join(parent, 'source');
|
|
1182
|
+
fs.mkdirSync(source);
|
|
1183
|
+
runGit(source, ['init', '-b', 'main']);
|
|
1184
|
+
runGit(source, ['config', 'user.email', 'test@example.com']);
|
|
1185
|
+
runGit(source, ['config', 'user.name', 'Test User']);
|
|
1186
|
+
fs.writeFileSync(path.join(source, 'README.md'), 'initial\n');
|
|
1187
|
+
runGit(source, ['add', 'README.md']);
|
|
1188
|
+
runGit(source, ['commit', '-m', 'Initial commit']);
|
|
1189
|
+
|
|
1190
|
+
// The layout this covers: a bare git dir at <project>/.git with linked
|
|
1191
|
+
// worktrees as siblings, the project directory itself being bare.
|
|
1192
|
+
const bareRoot = path.join(parent, 'project');
|
|
1193
|
+
fs.mkdirSync(bareRoot);
|
|
1194
|
+
runGit(source, ['clone', '--bare', source, path.join(bareRoot, '.git')]);
|
|
1195
|
+
|
|
1196
|
+
const created = await createWorktree(bareRoot, {
|
|
1197
|
+
mode: 'new', branchName: 'feature/from-bare', worktreeName: 'from-bare',
|
|
1198
|
+
});
|
|
1199
|
+
await expect.poll(
|
|
1200
|
+
async () => (await getWorktreeBootstrapStatus(created.path)).status,
|
|
1201
|
+
{ timeout: 20_000 },
|
|
1202
|
+
).not.toBe('pending');
|
|
1203
|
+
expect(await getWorktreeBootstrapStatus(created.path)).toMatchObject({ status: 'ready', error: null });
|
|
1204
|
+
expect(fs.readFileSync(path.join(created.path, 'README.md'), 'utf8')).toBe('initial\n');
|
|
1205
|
+
// The bare root lists only real checkouts: the created worktree, not itself.
|
|
1206
|
+
const entries = await getWorktrees(bareRoot);
|
|
1207
|
+
expect(entries.map((entry) => fs.realpathSync(entry.path))).toEqual([fs.realpathSync(created.path)]);
|
|
1208
|
+
await removeWorktree(bareRoot, { directory: created.path });
|
|
1209
|
+
expect(fs.existsSync(created.path)).toBe(false);
|
|
1210
|
+
expect(await getWorktrees(bareRoot)).toHaveLength(0);
|
|
1211
|
+
} finally {
|
|
1212
|
+
if (previousDataHome === undefined) delete process.env.XDG_DATA_HOME;
|
|
1213
|
+
else process.env.XDG_DATA_HOME = previousDataHome;
|
|
1214
|
+
}
|
|
1215
|
+
});
|
|
1216
|
+
|
|
1152
1217
|
it('resolves the git toplevel for a repository subdirectory', async () => {
|
|
1153
1218
|
if (!canRunGit()) return;
|
|
1154
1219
|
|
|
@@ -1178,6 +1243,56 @@ describe('worktree root resolution', () => {
|
|
|
1178
1243
|
});
|
|
1179
1244
|
});
|
|
1180
1245
|
|
|
1246
|
+
describe.runIf(canRunGit())('isPatchEquivalentOfHead', () => {
|
|
1247
|
+
it('finds a rebased commit patch in HEAD history', async () => {
|
|
1248
|
+
const repo = createTempDir();
|
|
1249
|
+
runGit(repo, ['init', '-b', 'main']);
|
|
1250
|
+
runGit(repo, ['config', 'user.email', 'test@example.com']);
|
|
1251
|
+
runGit(repo, ['config', 'user.name', 'Test User']);
|
|
1252
|
+
fs.writeFileSync(path.join(repo, 'file.txt'), 'base\n');
|
|
1253
|
+
runGit(repo, ['add', 'file.txt']);
|
|
1254
|
+
runGit(repo, ['commit', '-m', 'base']);
|
|
1255
|
+
const root = runGit(repo, ['rev-parse', 'HEAD']).trim();
|
|
1256
|
+
runGit(repo, ['checkout', '-b', 'feature']);
|
|
1257
|
+
fs.appendFileSync(path.join(repo, 'file.txt'), 'feature\n');
|
|
1258
|
+
runGit(repo, ['commit', '-am', 'feature change']);
|
|
1259
|
+
const original = runGit(repo, ['rev-parse', 'HEAD']).trim();
|
|
1260
|
+
runGit(repo, ['tag', 'before-rebase', original]);
|
|
1261
|
+
runGit(repo, ['checkout', 'main']);
|
|
1262
|
+
fs.writeFileSync(path.join(repo, 'main.txt'), 'main\n');
|
|
1263
|
+
runGit(repo, ['add', 'main.txt']);
|
|
1264
|
+
runGit(repo, ['commit', '-m', 'main change']);
|
|
1265
|
+
runGit(repo, ['checkout', 'feature']);
|
|
1266
|
+
runGit(repo, ['rebase', 'main']);
|
|
1267
|
+
|
|
1268
|
+
await expect(isPatchEquivalentOfHead(repo, original)).resolves.toBe(true);
|
|
1269
|
+
await expect(isPatchEquivalentOfHead(repo, root)).resolves.toBe(false);
|
|
1270
|
+
});
|
|
1271
|
+
|
|
1272
|
+
it('rejects an unrelated commit with a different patch', async () => {
|
|
1273
|
+
const repo = createTempDir();
|
|
1274
|
+
runGit(repo, ['init', '-b', 'main']);
|
|
1275
|
+
runGit(repo, ['config', 'user.email', 'test@example.com']);
|
|
1276
|
+
runGit(repo, ['config', 'user.name', 'Test User']);
|
|
1277
|
+
runGit(repo, ['commit', '--allow-empty', '-m', 'base']);
|
|
1278
|
+
runGit(repo, ['checkout', '-b', 'old-feature']);
|
|
1279
|
+
fs.writeFileSync(path.join(repo, 'old.txt'), 'old\n');
|
|
1280
|
+
runGit(repo, ['add', 'old.txt']);
|
|
1281
|
+
runGit(repo, ['commit', '-m', 'old change']);
|
|
1282
|
+
const oldCommit = runGit(repo, ['rev-parse', 'HEAD']).trim();
|
|
1283
|
+
runGit(repo, ['commit', '--allow-empty', '-m', 'empty change']);
|
|
1284
|
+
const emptyCommit = runGit(repo, ['rev-parse', 'HEAD']).trim();
|
|
1285
|
+
runGit(repo, ['checkout', 'main']);
|
|
1286
|
+
fs.writeFileSync(path.join(repo, 'new.txt'), 'new\n');
|
|
1287
|
+
runGit(repo, ['add', 'new.txt']);
|
|
1288
|
+
runGit(repo, ['commit', '-m', 'new change']);
|
|
1289
|
+
|
|
1290
|
+
await expect(isPatchEquivalentOfHead(repo, oldCommit)).resolves.toBe(false);
|
|
1291
|
+
await expect(isPatchEquivalentOfHead(repo, emptyCommit)).resolves.toBe(false);
|
|
1292
|
+
await expect(isPatchEquivalentOfHead(repo, 'missing')).resolves.toBe(false);
|
|
1293
|
+
});
|
|
1294
|
+
});
|
|
1295
|
+
|
|
1181
1296
|
// ---------------------------------------------------------------------------
|
|
1182
1297
|
// getWorktrees
|
|
1183
1298
|
// ---------------------------------------------------------------------------
|
|
@@ -1221,6 +1336,36 @@ describe('getWorktrees', () => {
|
|
|
1221
1336
|
expect(Array.isArray(result)).toBe(true);
|
|
1222
1337
|
expect(warnSpy).not.toHaveBeenCalled();
|
|
1223
1338
|
});
|
|
1339
|
+
|
|
1340
|
+
it('lists the linked worktrees of a bare repository used as the project directory', async () => {
|
|
1341
|
+
if (!canRunGit()) return;
|
|
1342
|
+
|
|
1343
|
+
const source = createTempDir();
|
|
1344
|
+
runGit(source, ['init', '-b', 'main']);
|
|
1345
|
+
runGit(source, ['config', 'user.email', 'test@example.com']);
|
|
1346
|
+
runGit(source, ['config', 'user.name', 'Test User']);
|
|
1347
|
+
runGit(source, ['commit', '--allow-empty', '-m', 'init']);
|
|
1348
|
+
|
|
1349
|
+
// Two bare layouts in the wild: a `clone --bare` directory whose git dir
|
|
1350
|
+
// is the directory itself, and one whose git dir is a `.git` child.
|
|
1351
|
+
for (const bareRoot of [path.join(createTempDir(), 'repo.git'), path.join(createTempDir(), 'repo')]) {
|
|
1352
|
+
const gitDir = bareRoot.endsWith('.git') ? bareRoot : path.join(bareRoot, '.git');
|
|
1353
|
+
fs.mkdirSync(path.dirname(gitDir), { recursive: true });
|
|
1354
|
+
runGit(source, ['clone', '--bare', source, gitDir]);
|
|
1355
|
+
const linked = path.join(createTempDir(), 'linked');
|
|
1356
|
+
runGit(gitDir, ['worktree', 'add', linked, 'main']);
|
|
1357
|
+
|
|
1358
|
+
for (const directory of [bareRoot, gitDir, linked]) {
|
|
1359
|
+
expect(await isGitRepository(directory)).toBe(true);
|
|
1360
|
+
const entries = await getWorktrees(directory);
|
|
1361
|
+
// The bare repository lists itself as a worktree; it has no working
|
|
1362
|
+
// tree, so only the linked checkout is a worktree anyone can open.
|
|
1363
|
+
expect(entries.map((entry) => fs.realpathSync(entry.path))).toEqual([fs.realpathSync(linked)]);
|
|
1364
|
+
expect(entries[0].branch).toBe('main');
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
1368
|
+
});
|
|
1224
1369
|
it('notifies subscribers only when another git process changes the worktree set', async () => {
|
|
1225
1370
|
if (!canRunGit()) return;
|
|
1226
1371
|
|
|
@@ -1254,6 +1399,37 @@ describe('getWorktrees', () => {
|
|
|
1254
1399
|
}
|
|
1255
1400
|
});
|
|
1256
1401
|
|
|
1402
|
+
it('renames a worktree branch, tells subscribers, and names the worktree that holds a taken name', async () => {
|
|
1403
|
+
if (!canRunGit()) return;
|
|
1404
|
+
|
|
1405
|
+
const repo = createTempDir();
|
|
1406
|
+
runGit(repo, ['init', '-b', 'main']);
|
|
1407
|
+
runGit(repo, ['config', 'user.email', 'test@example.com']);
|
|
1408
|
+
runGit(repo, ['config', 'user.name', 'Test User']);
|
|
1409
|
+
runGit(repo, ['commit', '--allow-empty', '-m', 'init']);
|
|
1410
|
+
const first = path.join(createTempDir(), 'first');
|
|
1411
|
+
const second = path.join(createTempDir(), 'second');
|
|
1412
|
+
runGit(repo, ['worktree', 'add', first, '-b', 'feature-one']);
|
|
1413
|
+
runGit(repo, ['worktree', 'add', second, '-b', 'feature-two']);
|
|
1414
|
+
runGit(repo, ['branch', 'parked']);
|
|
1415
|
+
|
|
1416
|
+
const events = [];
|
|
1417
|
+
const unsubscribe = subscribeWorktreeTopologyChanges((event) => events.push(event));
|
|
1418
|
+
try {
|
|
1419
|
+
await expect(renameBranch(first, 'feature-one', 'feature-two'))
|
|
1420
|
+
.rejects.toMatchObject({ statusCode: 409, message: expect.stringContaining(second) });
|
|
1421
|
+
await expect(renameBranch(first, 'feature-one', 'parked'))
|
|
1422
|
+
.rejects.toMatchObject({ statusCode: 409, message: 'A branch named parked already exists' });
|
|
1423
|
+
expect(events).toHaveLength(0);
|
|
1424
|
+
|
|
1425
|
+
await renameBranch(first, 'feature-one', 'login-form');
|
|
1426
|
+
expect(runGit(first, ['branch', '--show-current']).trim()).toBe('login-form');
|
|
1427
|
+
expect(events).toHaveLength(1);
|
|
1428
|
+
} finally {
|
|
1429
|
+
unsubscribe();
|
|
1430
|
+
}
|
|
1431
|
+
});
|
|
1432
|
+
|
|
1257
1433
|
it('publishes worktrees this server creates and removes', async () => {
|
|
1258
1434
|
if (!canRunGit()) return;
|
|
1259
1435
|
|
|
@@ -4613,6 +4789,28 @@ describe('parseRemoteListing', () => {
|
|
|
4613
4789
|
{ name: 'origin', fetchUrl: 'git@github.com:owner/repo.git', pushUrl: 'git@github.com:owner/push.git' },
|
|
4614
4790
|
]);
|
|
4615
4791
|
});
|
|
4792
|
+
|
|
4793
|
+
it('ignores decoration after the listing kind, recognized or not (#4479)', () => {
|
|
4794
|
+
// Git only annotates the fetch line in practice; the parser ignores
|
|
4795
|
+
// whatever follows the marker so it does not depend on the annotation's
|
|
4796
|
+
// exact shape.
|
|
4797
|
+
const listing = [
|
|
4798
|
+
'origin\tgit@github.com:owner/repo.git (fetch) [blob:none]',
|
|
4799
|
+
'origin\tgit@github.com:owner/repo.git (push)',
|
|
4800
|
+
'mirror\thttps://example.com/repo.git (fetch) [blob:limit=1m] [tree:1]',
|
|
4801
|
+
'mirror\thttps://example.com/repo.git (push) [blob:none]',
|
|
4802
|
+
'future\thttps://example.com/f.git (fetch) [blob:none] (extra)',
|
|
4803
|
+
'future\thttps://example.com/f.git (push)',
|
|
4804
|
+
'bare\t',
|
|
4805
|
+
'',
|
|
4806
|
+
].join('\n');
|
|
4807
|
+
expect(parseRemoteListing(['bare', 'future', 'mirror', 'origin'], listing)).toEqual([
|
|
4808
|
+
{ name: 'bare', fetchUrl: 'bare', pushUrl: 'bare' },
|
|
4809
|
+
{ name: 'future', fetchUrl: 'https://example.com/f.git', pushUrl: 'https://example.com/f.git' },
|
|
4810
|
+
{ name: 'mirror', fetchUrl: 'https://example.com/repo.git', pushUrl: 'https://example.com/repo.git' },
|
|
4811
|
+
{ name: 'origin', fetchUrl: 'git@github.com:owner/repo.git', pushUrl: 'git@github.com:owner/repo.git' },
|
|
4812
|
+
]);
|
|
4813
|
+
});
|
|
4616
4814
|
});
|
|
4617
4815
|
|
|
4618
4816
|
describe.runIf(canRunGit())('getRepositoryRemoteUrls', () => {
|
|
@@ -4636,6 +4834,22 @@ describe.runIf(canRunGit())('getRepositoryRemoteUrls', () => {
|
|
|
4636
4834
|
expect(remotes.find((remote) => remote.name === 'rewritten')?.fetchUrl).toBe('https://github.com/other/repo.git');
|
|
4637
4835
|
expect(remotes.find((remote) => remote.name === 'origin')?.pushUrl).toBe('git@github.com:owner/push.git');
|
|
4638
4836
|
});
|
|
4837
|
+
|
|
4838
|
+
it('reads a partial-clone remote as `git remote get-url` reports it, without the 2.54 listing annotation (#4479)', async () => {
|
|
4839
|
+
const repository = createTempDir();
|
|
4840
|
+
runGit(repository, ['init', '-b', 'main']);
|
|
4841
|
+
runGit(repository, ['remote', 'add', 'origin', 'git@github.com:owner/repo.git']);
|
|
4842
|
+
// Git 2.54+ annotates the fetch line of `git remote -v` with the filter:
|
|
4843
|
+
// `... (fetch) [blob:none]`. Setting just the config key is enough.
|
|
4844
|
+
runGit(repository, ['config', 'remote.origin.partialclonefilter', 'blob:none']);
|
|
4845
|
+
|
|
4846
|
+
const [origin] = await getRepositoryRemoteUrls(repository);
|
|
4847
|
+
expect(origin).toEqual({
|
|
4848
|
+
name: 'origin',
|
|
4849
|
+
fetchUrl: 'git@github.com:owner/repo.git',
|
|
4850
|
+
pushUrl: 'git@github.com:owner/repo.git',
|
|
4851
|
+
});
|
|
4852
|
+
});
|
|
4639
4853
|
});
|
|
4640
4854
|
|
|
4641
4855
|
describe.runIf(canRunGit())('getUnpushedBranchCounts', () => {
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
+
import { isPlainObject, isString } from '../shared/guards.js';
|
|
1
2
|
import fs from 'node:fs/promises';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { constants as fsConstants } from 'node:fs';
|
|
4
5
|
import { randomUUID } from 'node:crypto';
|
|
5
6
|
import { withSourceControlFileLock } from '../source-control/file-lock.js';
|
|
6
7
|
|
|
7
|
-
const isPlainObject = (value) => value === Object(value)
|
|
8
|
-
&& !Array.isArray(value)
|
|
9
|
-
&& Object.getPrototypeOf(value) === Object.prototype;
|
|
10
|
-
const isString = (value) => Object.prototype.toString.call(value) === '[object String]';
|
|
11
8
|
const hasExactKeys = (value, keys) => {
|
|
12
9
|
const actual = Object.keys(value);
|
|
13
10
|
return actual.length === keys.length && actual.every((key) => keys.includes(key));
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isString } from '../shared/guards.js';
|
|
1
2
|
import { normalizeDiscoveryEndpoint } from './discovery-endpoint.js';
|
|
2
3
|
|
|
3
4
|
export const SUBMODULE_DISCOVERY_LIMITS = Object.freeze({
|
|
@@ -13,7 +14,6 @@ const CONFIG_KEY_PATTERN = /^submodule\.(.+)\.(path|url|update)$/i;
|
|
|
13
14
|
const GITLINK_PATTERN = /^160000 (?:(?:commit )?([0-9a-f]{40}|[0-9a-f]{64})|([0-9a-f]{40}|[0-9a-f]{64}) 0)\t(.+)$/i;
|
|
14
15
|
const CONTROL_PATTERN = /[\0-\x1f\x7f]/;
|
|
15
16
|
const UPDATE_MODES = new Set(['checkout', 'merge', 'rebase', 'none']);
|
|
16
|
-
const isString = (value) => Object.prototype.toString.call(value) === '[object String]';
|
|
17
17
|
|
|
18
18
|
const manifestError = (message, code = 'INVALID_SUBMODULE_MANIFEST') => Object.assign(new Error(message), { code });
|
|
19
19
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isPlainObject } from '../shared/guards.js';
|
|
1
2
|
import crypto, { randomUUID } from 'node:crypto';
|
|
2
3
|
import fs from 'node:fs/promises';
|
|
3
4
|
import path from 'node:path';
|
|
@@ -13,7 +14,7 @@ const ERROR_CODES = new Set([
|
|
|
13
14
|
'INVALID_REQUEST', 'NOT_FOUND', 'STALE_REPOSITORY', 'STALE_BINDING', 'STALE_CONFIG',
|
|
14
15
|
'REMOTE_CHANGED', 'AUTHENTICATION_REQUIRED', 'AUTHENTICATION_FAILED', 'TRANSPORT_FAILED',
|
|
15
16
|
'CONFLICT', 'CANCELLED', 'TIMEOUT', 'OUTCOME_UNKNOWN', 'RUNTIME_UNSUPPORTED', 'UNKNOWN',
|
|
16
|
-
'GIT_LFS_CLIENT_MISSING', 'PATH_LENGTH_LIMIT',
|
|
17
|
+
'GIT_LFS_CLIENT_MISSING', 'PATH_LENGTH_LIMIT', 'CHECKOUT_TOO_LARGE',
|
|
17
18
|
]);
|
|
18
19
|
const HYDRATION_STATUSES = new Set([
|
|
19
20
|
'succeeded', 'authorization-required', 'invalid', 'client-missing', 'failed', 'cancelled', 'not-needed',
|
|
@@ -35,12 +36,10 @@ const SAFE_ERROR_MESSAGES = Object.freeze({
|
|
|
35
36
|
RUNTIME_UNSUPPORTED: 'Worktree checkout hydration is unsupported by this runtime',
|
|
36
37
|
UNKNOWN: 'Worktree bootstrap completion is unknown. Inspect the checkout and repair setup before use.',
|
|
37
38
|
GIT_LFS_CLIENT_MISSING: 'Git LFS is required but unavailable',
|
|
39
|
+
CHECKOUT_TOO_LARGE: 'Worktree checkout has too many files to inspect',
|
|
38
40
|
PATH_LENGTH_LIMIT: 'Git reported "File name too long". The worktree checkout path exceeds this system\'s path-length limit. Enable OS long paths or use a shorter repository path.',
|
|
39
41
|
});
|
|
40
42
|
|
|
41
|
-
const isPlainObject = (value) => value === Object(value)
|
|
42
|
-
&& !Array.isArray(value)
|
|
43
|
-
&& Object.getPrototypeOf(value) === Object.prototype;
|
|
44
43
|
const exactKeys = (value, required, optional = []) => {
|
|
45
44
|
const keys = Object.keys(value);
|
|
46
45
|
return required.every((key) => keys.includes(key))
|
|
@@ -88,6 +88,21 @@ describe('worktree bootstrap storage', () => {
|
|
|
88
88
|
expect(encoded).not.toContain('process output');
|
|
89
89
|
});
|
|
90
90
|
|
|
91
|
+
it('round-trips a checkout with too many files to inspect with its own public message', async () => {
|
|
92
|
+
const dataDirectory = await temporaryDirectory('worktree-bootstrap-too-large-');
|
|
93
|
+
const filePath = path.join(dataDirectory, 'bootstrap.json');
|
|
94
|
+
const failure = { code: 'CHECKOUT_TOO_LARGE', message: 'Checkout has too many files to inspect' };
|
|
95
|
+
await createWorktreeBootstrapStore({ filePath }).write('/worktrees/large', {
|
|
96
|
+
status: 'failed', phase: 'directory-created', error: failure.message, errorCode: failure.code, updatedAt: 12,
|
|
97
|
+
hydration: { status: 'invalid', submodules: [], lfs: [{ path: '.', status: 'invalid', error: failure }] },
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
await expect(createWorktreeBootstrapStore({ filePath }).read('/worktrees/large')).resolves.toMatchObject({
|
|
101
|
+
status: 'failed', errorCode: 'CHECKOUT_TOO_LARGE', error: 'Worktree checkout has too many files to inspect',
|
|
102
|
+
hydration: { lfs: [{ status: 'invalid', error: { code: 'CHECKOUT_TOO_LARGE' } }] },
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
91
106
|
it('turns a restarted pending bootstrap into a git-ready repair blocker without resuming it', async () => {
|
|
92
107
|
if (!gitAvailable()) return;
|
|
93
108
|
const dataDirectory = await temporaryDirectory('worktree-bootstrap-restart-');
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worktree creation with the server's authority checks, shared by
|
|
3
|
+
* `POST /api/git/worktrees` (the New worktree dialog) and the session service
|
|
4
|
+
* (`session.create` with a pull or merge request). One implementation, so a
|
|
5
|
+
* worktree made from a change request is the same kind whichever way it was
|
|
6
|
+
* asked for: the head is resolved and transferred here, never taken from the
|
|
7
|
+
* caller, and a contributor fork gets its named remote, provenance and no
|
|
8
|
+
* upstream.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** A refusal with the HTTP status and body fields the route answers with. */
|
|
12
|
+
export class WorktreeCreationError extends Error {
|
|
13
|
+
constructor(message, status, code, details = {}) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = 'WorktreeCreationError';
|
|
16
|
+
this.status = status;
|
|
17
|
+
this.code = code;
|
|
18
|
+
Object.assign(this, details);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Contributor authority is the server's to derive. A request carrying any of
|
|
24
|
+
* these fields itself is refused before anything else happens.
|
|
25
|
+
*/
|
|
26
|
+
export const hasClientContributorAuthority = (body) => body?.contributorFork !== undefined
|
|
27
|
+
|| body?.changeRequestTransfer !== undefined || body?.contributorTransferComplete !== undefined
|
|
28
|
+
|| (body?.changeRequestSource && body?.ensureRemoteUrl !== undefined);
|
|
29
|
+
|
|
30
|
+
// A change request from this repository itself is fetched onto the primary
|
|
31
|
+
// remote it already has, under that remote's own URL, so the checkout adds no
|
|
32
|
+
// second remote for the same project. Only a fork gets a remote of its own.
|
|
33
|
+
// A fork's remote is named after its owner, which the provider just said; the
|
|
34
|
+
// picker cannot always know it (GitLab lists merge requests without it).
|
|
35
|
+
const forkRemoteName = (owner) => `pr-${String(owner || '').trim().toLowerCase()
|
|
36
|
+
.replace(/[^a-z0-9._-]+/g, '-').replace(/-+/g, '-').replace(/^-+|-+$/g, '') || 'head'}`;
|
|
37
|
+
|
|
38
|
+
const worktreeInputForSource = (input, source) => ({
|
|
39
|
+
...input,
|
|
40
|
+
changeRequestSource: undefined,
|
|
41
|
+
contributorFork: source.classification === 'contributor-fork',
|
|
42
|
+
changeRequestTransfer: true,
|
|
43
|
+
ensureRemoteName: source.requestedRemoteName,
|
|
44
|
+
ensureRemoteUrl: source.remoteUrl ?? source.endpoint,
|
|
45
|
+
expectedRevision: source.headSha,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
export const createWorktreeCreation = ({
|
|
49
|
+
getGitLibraries,
|
|
50
|
+
networkOperations,
|
|
51
|
+
getSourceControlBinding,
|
|
52
|
+
contributorProvenance,
|
|
53
|
+
resolveChangeRequestSource,
|
|
54
|
+
createHttpsCredentialReference,
|
|
55
|
+
resolveSourceControlAccount,
|
|
56
|
+
worktreeBootstrapStore,
|
|
57
|
+
}) => {
|
|
58
|
+
const changeRequestSourceOnRepository = async (directory, source) => {
|
|
59
|
+
if (source.classification !== 'same-repository') {
|
|
60
|
+
return source.sourceProject?.owner
|
|
61
|
+
? Object.freeze({ ...source, requestedRemoteName: forkRemoteName(source.sourceProject.owner) })
|
|
62
|
+
: source;
|
|
63
|
+
}
|
|
64
|
+
const { getRepositoryRemoteUrls } = await getGitLibraries();
|
|
65
|
+
const primary = (await getRepositoryRemoteUrls(directory)).find((remote) => remote.name === source.context.primaryRemote);
|
|
66
|
+
if (!primary?.fetchUrl) return source;
|
|
67
|
+
return Object.freeze({ ...source, requestedRemoteName: primary.name, remoteUrl: primary.fetchUrl });
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/** The input `validateWorktreeCreate` checks: a change request becomes the server-resolved remote and head. */
|
|
71
|
+
const validationInput = async (directory, input) => {
|
|
72
|
+
if (!input.changeRequestSource) return input;
|
|
73
|
+
if (!(resolveChangeRequestSource instanceof Function)) {
|
|
74
|
+
throw new WorktreeCreationError('Contributor worktrees are unavailable', 501, 'RUNTIME_UNSUPPORTED');
|
|
75
|
+
}
|
|
76
|
+
const source = await changeRequestSourceOnRepository(directory, await resolveChangeRequestSource(input.changeRequestSource));
|
|
77
|
+
return worktreeInputForSource(input, source);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Resolves the change request, checks the account that will fetch it, and
|
|
82
|
+
* transfers its head into the repository. Nothing is created on disk before
|
|
83
|
+
* the transfer succeeds.
|
|
84
|
+
*/
|
|
85
|
+
const transferChangeRequestHead = async (directory, input) => {
|
|
86
|
+
const { validateWorktreeCreate } = await getGitLibraries();
|
|
87
|
+
if (!(resolveChangeRequestSource instanceof Function)
|
|
88
|
+
|| !(createHttpsCredentialReference instanceof Function)
|
|
89
|
+
|| !(resolveSourceControlAccount instanceof Function)
|
|
90
|
+
|| !(networkOperations?.transferContributorHead instanceof Function)) {
|
|
91
|
+
throw new WorktreeCreationError('Contributor worktrees are unavailable', 501, 'RUNTIME_UNSUPPORTED');
|
|
92
|
+
}
|
|
93
|
+
const source = await changeRequestSourceOnRepository(directory, await resolveChangeRequestSource(input.changeRequestSource));
|
|
94
|
+
const headBranch = source.headRef.slice('refs/heads/'.length);
|
|
95
|
+
const destinationRef = `refs/remotes/${source.requestedRemoteName}/${headBranch}`;
|
|
96
|
+
const transferInput = {
|
|
97
|
+
...worktreeInputForSource(input, source),
|
|
98
|
+
existingBranch: destinationRef.slice('refs/'.length),
|
|
99
|
+
setUpstream: false,
|
|
100
|
+
};
|
|
101
|
+
const validation = await validateWorktreeCreate(directory, transferInput);
|
|
102
|
+
if (!validation.ok && !validation.errors.every((entry) => entry.code === 'contributor_transfer_unavailable')) {
|
|
103
|
+
const collision = validation.errors.find((entry) => entry.code === 'remote_name_collision');
|
|
104
|
+
if (collision) {
|
|
105
|
+
throw new WorktreeCreationError(collision.message, 409, 'CONTRIBUTOR_REMOTE_COLLISION', { remoteName: transferInput.ensureRemoteName });
|
|
106
|
+
}
|
|
107
|
+
throw new WorktreeCreationError(
|
|
108
|
+
validation.errors.map((entry) => entry.message).filter(Boolean).join('\n') || 'Failed to validate worktree creation',
|
|
109
|
+
409,
|
|
110
|
+
'INVALID_REQUEST',
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
const account = await resolveSourceControlAccount(source.context);
|
|
114
|
+
if (account?.credentialId !== source.context.accountId || account?.status !== 'valid'
|
|
115
|
+
|| !Number.isSafeInteger(account?.credentialRevision) || account.credentialRevision < 1
|
|
116
|
+
|| Object.prototype.toString.call(account?.providerUserId) !== '[object String]' || !account.providerUserId) {
|
|
117
|
+
throw new WorktreeCreationError('Contributor credential is unavailable', 409, 'AUTHENTICATION_REQUIRED');
|
|
118
|
+
}
|
|
119
|
+
const credentialId = createHttpsCredentialReference({
|
|
120
|
+
provider: source.context.provider,
|
|
121
|
+
instance: source.context.instance,
|
|
122
|
+
credentialId: account.credentialId,
|
|
123
|
+
credentialRevision: account.credentialRevision,
|
|
124
|
+
providerUserId: account.providerUserId,
|
|
125
|
+
});
|
|
126
|
+
const transfer = await networkOperations.transferContributorHead({
|
|
127
|
+
directory,
|
|
128
|
+
sourceRequest: input.changeRequestSource,
|
|
129
|
+
source,
|
|
130
|
+
destinationRef,
|
|
131
|
+
credentialId,
|
|
132
|
+
});
|
|
133
|
+
if (transfer.state !== 'succeeded') {
|
|
134
|
+
throw new WorktreeCreationError(
|
|
135
|
+
transfer.error?.message || 'Contributor head transfer failed',
|
|
136
|
+
transfer.error?.code === 'AUTHENTICATION_REQUIRED' ? 401 : 409,
|
|
137
|
+
transfer.error?.code || 'UNKNOWN',
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
return { source, input: { ...transferInput, contributorTransferComplete: true } };
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/** Creates a worktree; with `input.changeRequestSource`, from that change request's head. */
|
|
144
|
+
const create = async (directory, requestInput) => {
|
|
145
|
+
const { createWorktree, validateWorktreeCreate } = await getGitLibraries();
|
|
146
|
+
if (!(createWorktree instanceof Function) || !(validateWorktreeCreate instanceof Function)) {
|
|
147
|
+
throw new WorktreeCreationError('Worktree creation is not available', 501);
|
|
148
|
+
}
|
|
149
|
+
if (hasClientContributorAuthority(requestInput)) {
|
|
150
|
+
throw new WorktreeCreationError('Invalid contributor worktree request', 400, 'INVALID_CONTRIBUTOR_WORKTREE');
|
|
151
|
+
}
|
|
152
|
+
if (!(networkOperations?.hydrateBoundCheckout instanceof Function)
|
|
153
|
+
|| !(worktreeBootstrapStore?.read instanceof Function)
|
|
154
|
+
|| !(worktreeBootstrapStore?.write instanceof Function)) {
|
|
155
|
+
throw new WorktreeCreationError('Worktree checkout bootstrap is not available', 501);
|
|
156
|
+
}
|
|
157
|
+
let input = requestInput || {};
|
|
158
|
+
let source = null;
|
|
159
|
+
if (input.changeRequestSource) {
|
|
160
|
+
({ source, input } = await transferChangeRequestHead(directory, input));
|
|
161
|
+
}
|
|
162
|
+
const bindingRead = getSourceControlBinding instanceof Function
|
|
163
|
+
? await getSourceControlBinding(directory)
|
|
164
|
+
: null;
|
|
165
|
+
// An unbound repository hydrates through the machine's own Git for every
|
|
166
|
+
// remote, the same way it pushes; its authority is the unbound revision.
|
|
167
|
+
const repositoryAuthority = bindingRead ? {
|
|
168
|
+
repositoryId: bindingRead.repository.repositoryId,
|
|
169
|
+
bindingRevision: bindingRead.revision,
|
|
170
|
+
configRevision: bindingRead.repository.configRevision,
|
|
171
|
+
} : null;
|
|
172
|
+
const hydrateCheckout = ({ directory: checkoutDirectory, parentRemoteName }) => networkOperations.hydrateBoundCheckout({
|
|
173
|
+
directory: checkoutDirectory,
|
|
174
|
+
parentRemoteName,
|
|
175
|
+
parentEndpoint: source?.endpoint,
|
|
176
|
+
repositoryAuthority,
|
|
177
|
+
});
|
|
178
|
+
try {
|
|
179
|
+
return await createWorktree(directory, input, {
|
|
180
|
+
contributorProvenance,
|
|
181
|
+
contributorSource: source,
|
|
182
|
+
hydrateCheckout,
|
|
183
|
+
bootstrapStore: worktreeBootstrapStore,
|
|
184
|
+
});
|
|
185
|
+
} catch (error) {
|
|
186
|
+
if (error?.code === 'CONTRIBUTOR_REMOTE_COLLISION') {
|
|
187
|
+
throw new WorktreeCreationError('Contributor remote name is already used by a different endpoint', 409, 'CONTRIBUTOR_REMOTE_COLLISION', { remoteName: error.remoteName });
|
|
188
|
+
}
|
|
189
|
+
throw error;
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
return { validationInput, create };
|
|
194
|
+
};
|