@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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isRecord } from '../shared/guards.js';
|
|
1
2
|
import fs from 'fs';
|
|
2
3
|
import path from 'path';
|
|
3
4
|
import {
|
|
@@ -57,10 +58,6 @@ function validatePluginSpec(spec) {
|
|
|
57
58
|
return spec.trim();
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
function isRecord(value) {
|
|
61
|
-
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
61
|
function hasOptions(options) {
|
|
65
62
|
return isRecord(options) && Object.keys(options).length > 0;
|
|
66
63
|
}
|
|
@@ -249,7 +246,8 @@ function decodePluginId(id) {
|
|
|
249
246
|
}
|
|
250
247
|
|
|
251
248
|
/** Accept a v1 string/tuple or a v2 `{package, options}` object. */
|
|
252
|
-
|
|
249
|
+
/** @public Exposed to tests that dynamically import the plugins module. */
|
|
250
|
+
export function parsePluginRaw(raw) {
|
|
253
251
|
const entity = toPluginEntity(raw);
|
|
254
252
|
if (!entity) {
|
|
255
253
|
throw codedError('Plugin spec must be a string, [string, object], or {package, options}', 'INVALID_SPEC');
|
|
@@ -260,7 +258,8 @@ function parsePluginRaw(raw) {
|
|
|
260
258
|
}
|
|
261
259
|
|
|
262
260
|
/** Always v2: a bare string, or `{package, options}`. Never a tuple. */
|
|
263
|
-
|
|
261
|
+
/** @public Exposed to tests that dynamically import the plugins module. */
|
|
262
|
+
export function serializePluginEntry(entry) {
|
|
264
263
|
const spec = validatePluginSpec(entry?.spec);
|
|
265
264
|
const serialized = fromPluginEntity({ package: spec, options: hasOptions(entry?.options) ? entry.options : undefined });
|
|
266
265
|
if (serialized === null) {
|
|
@@ -447,7 +446,5 @@ export {
|
|
|
447
446
|
writePluginDirFile,
|
|
448
447
|
deletePluginDirFile,
|
|
449
448
|
encodePluginId,
|
|
450
|
-
decodePluginId
|
|
451
|
-
parsePluginRaw,
|
|
452
|
-
serializePluginEntry,
|
|
449
|
+
decodePluginId
|
|
453
450
|
};
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import path from 'path';
|
|
2
5
|
import express from 'express';
|
|
3
6
|
import request from 'supertest';
|
|
4
7
|
import { registerOpenCodeRoutes } from './routes.js';
|
|
@@ -165,9 +168,10 @@ describe('POST /api/provider/discover-models', () => {
|
|
|
165
168
|
getProviderSources: vi.fn(),
|
|
166
169
|
});
|
|
167
170
|
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
+
// With no credential source configured the route's credential-store read
|
|
172
|
+
// fails and discovery falls back to the key in the form; the stored-config
|
|
173
|
+
// fallback has its own describe below, and env resolution in stored keys
|
|
174
|
+
// is covered in model-discovery.test.js. This keeps the route contract:
|
|
171
175
|
// provider id enters, no credential leaves in the response.
|
|
172
176
|
const response = await request(app)
|
|
173
177
|
.post('/api/provider/discover-models')
|
|
@@ -181,6 +185,100 @@ describe('POST /api/provider/discover-models', () => {
|
|
|
181
185
|
});
|
|
182
186
|
});
|
|
183
187
|
|
|
188
|
+
describe('POST /api/provider/discover-models stored-config fallback', () => {
|
|
189
|
+
// The route reads the stored entry from the merged config layers. Point
|
|
190
|
+
// OPENCODE_CONFIG (resolved on every read) at a temp fixture so the entry
|
|
191
|
+
// is exactly what a provider form save writes: the v2 `providers` shape.
|
|
192
|
+
let root;
|
|
193
|
+
let previousOpenCodeConfig;
|
|
194
|
+
|
|
195
|
+
const serveModels = () => {
|
|
196
|
+
const requests = [];
|
|
197
|
+
globalThis.fetch = vi.fn(async (_url, init) => {
|
|
198
|
+
requests.push(init.headers.Authorization);
|
|
199
|
+
return new Response(JSON.stringify({ data: [{ id: 'model-a' }] }), { status: 200 });
|
|
200
|
+
});
|
|
201
|
+
return requests;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const writeStoredProvider = (providerID, entry) => {
|
|
205
|
+
const configPath = path.join(root, 'opencode.json');
|
|
206
|
+
fs.writeFileSync(configPath, JSON.stringify({ providers: { [providerID]: entry } }), 'utf8');
|
|
207
|
+
process.env.OPENCODE_CONFIG = configPath;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const discover = (body) => {
|
|
211
|
+
const app = express();
|
|
212
|
+
app.use(express.json());
|
|
213
|
+
registerOpenCodeRoutes(app, {
|
|
214
|
+
resolveProjectDirectory: vi.fn(async () => ({ directory: '/projects/app' })),
|
|
215
|
+
getProviderSources: vi.fn(),
|
|
216
|
+
});
|
|
217
|
+
return request(app).post('/api/provider/discover-models').send(body);
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
beforeEach(() => {
|
|
221
|
+
// The credential store is reachable but empty: the fallback must come
|
|
222
|
+
// from the stored config entry.
|
|
223
|
+
configureOpenCodeCredentials({ list: async () => [] });
|
|
224
|
+
root = fs.mkdtempSync(path.join(os.tmpdir(), 'openchamber-discovery-'));
|
|
225
|
+
previousOpenCodeConfig = process.env.OPENCODE_CONFIG;
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
afterEach(() => {
|
|
229
|
+
configureOpenCodeCredentials(null);
|
|
230
|
+
if (previousOpenCodeConfig === undefined) delete process.env.OPENCODE_CONFIG;
|
|
231
|
+
else process.env.OPENCODE_CONFIG = previousOpenCodeConfig;
|
|
232
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('sends the first env variable that is set, not just the first name', async () => {
|
|
236
|
+
const providerID = `env-order-${Date.now()}`;
|
|
237
|
+
process.env.OPENCHAMBER_DISCOVERY_SECOND = 'second-secret';
|
|
238
|
+
delete process.env.OPENCHAMBER_DISCOVERY_FIRST;
|
|
239
|
+
writeStoredProvider(providerID, {
|
|
240
|
+
package: 'aisdk:@ai-sdk/openai-compatible',
|
|
241
|
+
env: ['OPENCHAMBER_DISCOVERY_FIRST', 'OPENCHAMBER_DISCOVERY_SECOND'],
|
|
242
|
+
settings: { baseURL: 'https://stored.test/v1' },
|
|
243
|
+
});
|
|
244
|
+
const requests = serveModels();
|
|
245
|
+
try {
|
|
246
|
+
const response = await discover({ providerID, baseURL: 'https://stored.test/v1', enrich: false }).expect(200);
|
|
247
|
+
expect(requests).toEqual(['Bearer second-secret']);
|
|
248
|
+
expect(JSON.stringify(response.body)).not.toContain('second-secret');
|
|
249
|
+
} finally {
|
|
250
|
+
delete process.env.OPENCHAMBER_DISCOVERY_SECOND;
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('falls back to settings.apiKey when the credential store has none', async () => {
|
|
255
|
+
const providerID = `settings-key-${Date.now()}`;
|
|
256
|
+
writeStoredProvider(providerID, { settings: { baseURL: 'https://stored.test/v1', apiKey: 'config-secret' } });
|
|
257
|
+
const requests = serveModels();
|
|
258
|
+
const response = await discover({ providerID, baseURL: 'https://stored.test/v1', enrich: false }).expect(200);
|
|
259
|
+
expect(requests).toEqual(['Bearer config-secret']);
|
|
260
|
+
expect(JSON.stringify(response.body)).not.toContain('config-secret');
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('prefers the credential store key and keeps the stored base URL guard', async () => {
|
|
264
|
+
const providerID = `store-wins-${Date.now()}`;
|
|
265
|
+
configureOpenCodeCredentials({
|
|
266
|
+
list: async () => [{ integrationID: providerID, active: true, value: { type: 'key', key: 'store-secret' } }],
|
|
267
|
+
});
|
|
268
|
+
writeStoredProvider(providerID, { settings: { baseURL: 'https://stored.test/v1', apiKey: 'config-secret' } });
|
|
269
|
+
const requests = serveModels();
|
|
270
|
+
// Before the v2 config read, storedBaseURL was always undefined and the
|
|
271
|
+
// endpoint guard discarded even the credential-store key.
|
|
272
|
+
await discover({ providerID, baseURL: 'https://stored.test/v1', enrich: false }).expect(200);
|
|
273
|
+
expect(requests).toEqual(['Bearer store-secret']);
|
|
274
|
+
|
|
275
|
+
// A form base URL that moved away from the stored one receives no key.
|
|
276
|
+
requests.length = 0;
|
|
277
|
+
await discover({ providerID, baseURL: 'https://moved.test/v1', enrich: false }).expect(200);
|
|
278
|
+
expect(requests).toEqual([undefined]);
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
|
|
184
282
|
describe('Git initialization through the proxy', () => {
|
|
185
283
|
it('refuses home, a disk root, and a request without a directory', async () => {
|
|
186
284
|
const agent = request(createApp(vi.fn()));
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
|
|
2
|
+
import { spawnSync } from 'child_process';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
|
|
8
|
+
const providersModule = fileURLToPath(new URL('./providers.js', import.meta.url));
|
|
9
|
+
|
|
10
|
+
// The user config path is fixed when shared.js loads, so the calls run in a
|
|
11
|
+
// child process pointed at a temporary OPENCODE_CONFIG_DIR; the real user
|
|
12
|
+
// config is never touched.
|
|
13
|
+
const run = (configDir, body) => {
|
|
14
|
+
const script = `
|
|
15
|
+
import { readDisabledProviders, setProviderDisabled } from ${JSON.stringify(providersModule)};
|
|
16
|
+
${body}
|
|
17
|
+
`;
|
|
18
|
+
const result = spawnSync(process.execPath, ['--input-type=module', '-e', script], {
|
|
19
|
+
env: { ...process.env, OPENCODE_CONFIG_DIR: configDir },
|
|
20
|
+
encoding: 'utf8',
|
|
21
|
+
});
|
|
22
|
+
if (result.status !== 0) throw new Error(result.stderr);
|
|
23
|
+
return JSON.parse(result.stdout.trim().split('\n').pop());
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
describe('disabled providers', () => {
|
|
27
|
+
let configDir;
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
configDir = fs.mkdtempSync(path.join(os.tmpdir(), 'openchamber-disabled-providers-'));
|
|
30
|
+
});
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
fs.rmSync(configDir, { recursive: true, force: true });
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('disables and re-enables a provider in the user config, keeping other keys', () => {
|
|
36
|
+
fs.writeFileSync(path.join(configDir, 'opencode.json'), JSON.stringify({ model: 'a/b', disabled_providers: ['groq'] }));
|
|
37
|
+
|
|
38
|
+
const afterDisable = run(configDir, `
|
|
39
|
+
setProviderDisabled('openrouter', true, null);
|
|
40
|
+
setProviderDisabled('openrouter', true, null);
|
|
41
|
+
console.log(JSON.stringify(readDisabledProviders(null)));
|
|
42
|
+
`);
|
|
43
|
+
expect(afterDisable).toEqual(['groq', 'openrouter']);
|
|
44
|
+
|
|
45
|
+
const afterEnable = run(configDir, `
|
|
46
|
+
setProviderDisabled('groq', false, null);
|
|
47
|
+
setProviderDisabled('openrouter', false, null);
|
|
48
|
+
console.log(JSON.stringify(readDisabledProviders(null)));
|
|
49
|
+
`);
|
|
50
|
+
expect(afterEnable).toEqual([]);
|
|
51
|
+
const written = JSON.parse(fs.readFileSync(path.join(configDir, 'opencode.json'), 'utf8'));
|
|
52
|
+
expect(written.model).toBe('a/b');
|
|
53
|
+
expect(written.disabled_providers).toBeUndefined();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('refuses an invalid provider id', () => {
|
|
57
|
+
expect(() => run(configDir, `setProviderDisabled('Bad Id', true, null); console.log('[]');`)).toThrow('Invalid provider ID');
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -314,8 +314,42 @@ function removeProviderConfig(providerId, workingDirectory, scope = 'user') {
|
|
|
314
314
|
return true;
|
|
315
315
|
}
|
|
316
316
|
|
|
317
|
+
/**
|
|
318
|
+
* Providers turned off in the user's OpenCode config (`disabled_providers`).
|
|
319
|
+
* OpenCode 2 drops them from its provider list, so this list is the only
|
|
320
|
+
* place Settings can still show them to turn them back on.
|
|
321
|
+
*/
|
|
322
|
+
function readDisabledProviders(workingDirectory) {
|
|
323
|
+
const layers = readConfigLayers(workingDirectory);
|
|
324
|
+
const config = getConfigForPath(layers, layers.paths.userPath);
|
|
325
|
+
return Array.isArray(config.disabled_providers)
|
|
326
|
+
? config.disabled_providers.filter((entry) => typeof entry === 'string' && entry.length > 0)
|
|
327
|
+
: [];
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function setProviderDisabled(providerId, disabled, workingDirectory) {
|
|
331
|
+
if (typeof providerId !== 'string' || !PROVIDER_ID_PATTERN.test(providerId)) {
|
|
332
|
+
const error = new Error('Invalid provider ID');
|
|
333
|
+
error.statusCode = 400;
|
|
334
|
+
throw error;
|
|
335
|
+
}
|
|
336
|
+
const layers = readConfigLayers(workingDirectory);
|
|
337
|
+
const targetPath = layers.paths.userPath;
|
|
338
|
+
const config = getConfigForPath(layers, targetPath);
|
|
339
|
+
const current = Array.isArray(config.disabled_providers)
|
|
340
|
+
? config.disabled_providers.filter((entry) => entry !== providerId)
|
|
341
|
+
: [];
|
|
342
|
+
const next = disabled ? [...current, providerId] : current;
|
|
343
|
+
if (next.length > 0) config.disabled_providers = next;
|
|
344
|
+
else delete config.disabled_providers;
|
|
345
|
+
writeConfig(config, targetPath || CONFIG_FILE);
|
|
346
|
+
return next;
|
|
347
|
+
}
|
|
348
|
+
|
|
317
349
|
export {
|
|
318
350
|
getProviderSources,
|
|
351
|
+
readDisabledProviders,
|
|
352
|
+
setProviderDisabled,
|
|
319
353
|
removeProviderConfig,
|
|
320
354
|
upsertProviderConfig,
|
|
321
355
|
validateCustomProviderConfig,
|
|
@@ -459,9 +459,17 @@ export const registerOpenCodeProxy = (app, deps) => {
|
|
|
459
459
|
|
|
460
460
|
const replayParsedBody = (proxyReq, req) => {
|
|
461
461
|
const body = serializeParsedBody(req, proxyReq);
|
|
462
|
-
if (!body)
|
|
463
|
-
|
|
464
|
-
|
|
462
|
+
if (!body) {
|
|
463
|
+
// DO NOT remove transfer-encoding from a stream without content-length:
|
|
464
|
+
// Node then sends tunneled prompt bodies without framing, and OpenCode
|
|
465
|
+
// reads an empty payload (#4576).
|
|
466
|
+
if (proxyReq.getHeader('content-length') !== undefined) {
|
|
467
|
+
proxyReq.removeHeader('transfer-encoding');
|
|
468
|
+
}
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
// DO NOT keep the copied transfer-encoding alongside the replayed body's
|
|
472
|
+
// content-length: OpenCode rejects requests with both headers (#4280).
|
|
465
473
|
proxyReq.removeHeader('transfer-encoding');
|
|
466
474
|
proxyReq.setHeader('content-length', String(body.length));
|
|
467
475
|
proxyReq.write(body);
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
import { getClaudeCliAuthStatus } from './claude-cli-auth.js';
|
|
10
10
|
import { OPENCODE_CONFIG_DIR, readConfigLayers } from './shared.js';
|
|
11
11
|
import { settingsSurfaceOf } from './settings-files.js';
|
|
12
|
-
import { parseWebSearchSelection } from './config-v2.js';
|
|
12
|
+
import { parseWebSearchSelection, readSectionEntry, toProviderEntity } from './config-v2.js';
|
|
13
13
|
import { getWebSearchSource, setWarmingEnabled, setWebSearchSelection } from './websearch-config.js';
|
|
14
14
|
import {
|
|
15
15
|
CREDENTIAL_LIST_ERROR,
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
isProviderConnectRequest,
|
|
20
20
|
} from '../enterprise-mode.js';
|
|
21
21
|
import { discoverProviderModels } from './model-discovery.js';
|
|
22
|
+
import { readDisabledProviders, setProviderDisabled } from './providers.js';
|
|
22
23
|
import { vcsInitRefusal, vcsInitRefusalBody } from '../git/repository-root.js';
|
|
23
24
|
|
|
24
25
|
export const registerOpenCodeRoutes = (app, dependencies) => {
|
|
@@ -268,6 +269,31 @@ export const registerOpenCodeRoutes = (app, dependencies) => {
|
|
|
268
269
|
}
|
|
269
270
|
});
|
|
270
271
|
|
|
272
|
+
// Providers turned off in the user's OpenCode config. OpenCode leaves them
|
|
273
|
+
// out of its provider list, so Settings reads them here to offer them back.
|
|
274
|
+
app.get('/api/provider/disabled', (_req, res) => {
|
|
275
|
+
try {
|
|
276
|
+
return res.json({ providers: readDisabledProviders(null) });
|
|
277
|
+
} catch (error) {
|
|
278
|
+
console.error('Failed to read disabled providers:', error);
|
|
279
|
+
return res.status(500).json({ error: 'Failed to read disabled providers' });
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
app.put('/api/provider/:providerId/disabled', async (req, res) => {
|
|
284
|
+
if (typeof req.body?.disabled !== 'boolean') {
|
|
285
|
+
return res.status(400).json({ error: 'disabled must be true or false' });
|
|
286
|
+
}
|
|
287
|
+
try {
|
|
288
|
+
const providers = setProviderDisabled(req.params.providerId, req.body.disabled, null);
|
|
289
|
+
return res.json({ providers, ...buildAppliedResponse('Provider updated.') });
|
|
290
|
+
} catch (error) {
|
|
291
|
+
if (error?.statusCode === 400) return res.status(400).json({ error: error.message });
|
|
292
|
+
console.error('Failed to update disabled providers:', error);
|
|
293
|
+
return res.status(500).json({ error: 'Failed to update disabled providers' });
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
271
297
|
app.get('/api/provider/:providerId/source', async (req, res) => {
|
|
272
298
|
try {
|
|
273
299
|
const { providerId } = req.params;
|
|
@@ -403,7 +429,19 @@ export const registerOpenCodeRoutes = (app, dependencies) => {
|
|
|
403
429
|
const { getProviderAuth } = await getAuthLibrary();
|
|
404
430
|
const storedAuth = await getProviderAuth(providerID);
|
|
405
431
|
storedApiKey = storedAuth?.type === 'api' && typeof storedAuth.key === 'string' ? storedAuth.key : null;
|
|
406
|
-
|
|
432
|
+
const { value: rawProvider } = readSectionEntry(readConfigLayers().mergedConfig, 'providers', providerID);
|
|
433
|
+
const providerEntity = rawProvider ? toProviderEntity(rawProvider) : null;
|
|
434
|
+
storedBaseURL = providerEntity?.settings?.baseURL;
|
|
435
|
+
if (!storedApiKey) {
|
|
436
|
+
// OpenCode tries each `env` name in order, so pick the first one
|
|
437
|
+
// that is set; when none is, keep the first so discovery can name
|
|
438
|
+
// it in the error instead of failing as an anonymous 401. This
|
|
439
|
+
// reads the server's environment: a variable that exists only in
|
|
440
|
+
// the managed OpenCode launch environment (Settings user env) is
|
|
441
|
+
// not visible here.
|
|
442
|
+
const envName = providerEntity?.env?.find((name) => process.env[name]) ?? providerEntity?.env?.[0];
|
|
443
|
+
storedApiKey = providerEntity?.settings?.apiKey || (envName ? `{env:${envName}}` : null);
|
|
444
|
+
}
|
|
407
445
|
} catch {
|
|
408
446
|
storedApiKey = null;
|
|
409
447
|
}
|
|
@@ -232,7 +232,12 @@ export const createSettingsHelpers = (dependencies) => {
|
|
|
232
232
|
if (typeof candidate.splashFgDark === 'string' && candidate.splashFgDark.trim().length > 0) {
|
|
233
233
|
result.splashFgDark = candidate.splashFgDark.trim();
|
|
234
234
|
}
|
|
235
|
-
|
|
235
|
+
// An empty string forgets the last directory (the last project was
|
|
236
|
+
// removed). Every reader treats it as unset, so nothing is warmed at start
|
|
237
|
+
// or turned back into a project.
|
|
238
|
+
if (candidate.lastDirectory === '') {
|
|
239
|
+
result.lastDirectory = '';
|
|
240
|
+
} else if (typeof candidate.lastDirectory === 'string') {
|
|
236
241
|
const normalized = normalizePathForPersistence(candidate.lastDirectory);
|
|
237
242
|
if (typeof normalized === 'string' && normalized.length > 0) {
|
|
238
243
|
result.lastDirectory = normalized;
|
|
@@ -354,6 +359,9 @@ export const createSettingsHelpers = (dependencies) => {
|
|
|
354
359
|
if (typeof candidate.sidebarShowRecentSection === 'boolean') {
|
|
355
360
|
result.sidebarShowRecentSection = candidate.sidebarShowRecentSection;
|
|
356
361
|
}
|
|
362
|
+
if (typeof candidate.sidebarShowChatsSection === 'boolean') {
|
|
363
|
+
result.sidebarShowChatsSection = candidate.sidebarShowChatsSection;
|
|
364
|
+
}
|
|
357
365
|
|
|
358
366
|
if (Array.isArray(candidate.securityScopedBookmarks)) {
|
|
359
367
|
result.securityScopedBookmarks = normalizeStringArray(candidate.securityScopedBookmarks);
|
|
@@ -389,6 +397,9 @@ export const createSettingsHelpers = (dependencies) => {
|
|
|
389
397
|
if (typeof candidate.draftStartersScheduleTaskAdded === 'boolean') {
|
|
390
398
|
result.draftStartersScheduleTaskAdded = candidate.draftStartersScheduleTaskAdded;
|
|
391
399
|
}
|
|
400
|
+
if (candidate.addProjectDialogDismissed === true || candidate.addProjectDialogDismissed === false) {
|
|
401
|
+
result.addProjectDialogDismissed = candidate.addProjectDialogDismissed;
|
|
402
|
+
}
|
|
392
403
|
|
|
393
404
|
|
|
394
405
|
if (typeof candidate.uiFont === 'string' && candidate.uiFont.length > 0) {
|
|
@@ -446,6 +457,9 @@ export const createSettingsHelpers = (dependencies) => {
|
|
|
446
457
|
if (typeof candidate.diffWrapLines === 'boolean') {
|
|
447
458
|
result.diffWrapLines = candidate.diffWrapLines;
|
|
448
459
|
}
|
|
460
|
+
if (typeof candidate.diffHideWhitespace === 'boolean') {
|
|
461
|
+
result.diffHideWhitespace = candidate.diffHideWhitespace;
|
|
462
|
+
}
|
|
449
463
|
if (typeof candidate.persistChatDraft === 'boolean') {
|
|
450
464
|
result.persistChatDraft = candidate.persistChatDraft;
|
|
451
465
|
}
|
|
@@ -470,6 +484,9 @@ export const createSettingsHelpers = (dependencies) => {
|
|
|
470
484
|
result.fileEditorKeymap = mode;
|
|
471
485
|
}
|
|
472
486
|
}
|
|
487
|
+
if (typeof candidate.fileEditorVimMappings === 'string') {
|
|
488
|
+
result.fileEditorVimMappings = candidate.fileEditorVimMappings.slice(0, 10_000);
|
|
489
|
+
}
|
|
473
490
|
if (Array.isArray(candidate.providerOrder)) {
|
|
474
491
|
result.providerOrder = normalizeStringArray(candidate.providerOrder);
|
|
475
492
|
}
|
|
@@ -485,6 +502,12 @@ export const createSettingsHelpers = (dependencies) => {
|
|
|
485
502
|
if (typeof candidate.sessionWorkAutoOpen === 'boolean') {
|
|
486
503
|
result.sessionWorkAutoOpen = candidate.sessionWorkAutoOpen;
|
|
487
504
|
}
|
|
505
|
+
if (typeof candidate.sessionReviewOfferEnabled === 'boolean') {
|
|
506
|
+
result.sessionReviewOfferEnabled = candidate.sessionReviewOfferEnabled;
|
|
507
|
+
}
|
|
508
|
+
if (typeof candidate.sessionWorkKeepInGroup === 'boolean') {
|
|
509
|
+
result.sessionWorkKeepInGroup = candidate.sessionWorkKeepInGroup;
|
|
510
|
+
}
|
|
488
511
|
if (typeof candidate.sessionGoalEnabled === 'boolean') {
|
|
489
512
|
result.sessionGoalEnabled = candidate.sessionGoalEnabled;
|
|
490
513
|
}
|
|
@@ -847,6 +870,9 @@ export const createSettingsHelpers = (dependencies) => {
|
|
|
847
870
|
if (Array.isArray(candidate.recentAgents)) {
|
|
848
871
|
result.recentAgents = normalizeStringArray(candidate.recentAgents);
|
|
849
872
|
}
|
|
873
|
+
if (Array.isArray(candidate.favoriteAgents)) {
|
|
874
|
+
result.favoriteAgents = normalizeStringArray(candidate.favoriteAgents);
|
|
875
|
+
}
|
|
850
876
|
|
|
851
877
|
const recentEfforts = sanitizeRecentEfforts(candidate.recentEfforts);
|
|
852
878
|
if (recentEfforts) {
|
|
@@ -76,6 +76,15 @@ describe('settings helpers', () => {
|
|
|
76
76
|
expect(helpers.sanitizeSettingsUpdate({ gitlabClientId: 42 })).toEqual({});
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
+
it('forgets the last directory when asked with an empty string', () => {
|
|
80
|
+
// Removing the last project sends this. A kept last directory would be
|
|
81
|
+
// turned back into a project on the next read and warmed at every start.
|
|
82
|
+
const helpers = createTestHelpers();
|
|
83
|
+
expect(helpers.sanitizeSettingsUpdate({ lastDirectory: '' })).toEqual({ lastDirectory: '' });
|
|
84
|
+
expect(helpers.sanitizeSettingsUpdate({ lastDirectory: '/home/testuser/project' })).toEqual({ lastDirectory: '/home/testuser/project' });
|
|
85
|
+
expect(helpers.sanitizeSettingsUpdate({ lastDirectory: 42 })).toEqual({});
|
|
86
|
+
});
|
|
87
|
+
|
|
79
88
|
it('round-trips section order and preserves it across unrelated writes', () => {
|
|
80
89
|
const helpers = createTestHelpers();
|
|
81
90
|
const changes = helpers.sanitizeSettingsUpdate({ workStatusSectionOrder: ['mcp', 'session', 'mcp', null, ''] });
|
|
@@ -101,7 +110,7 @@ describe('settings helpers', () => {
|
|
|
101
110
|
expect(helpers.formatSettingsResponse(next).workStatusHiddenSectionsExplicit).toBe(true);
|
|
102
111
|
expect(helpers.sanitizeSettingsUpdate({ workStatusHiddenSectionsExplicit: 'true' }).workStatusHiddenSectionsExplicit).toBeUndefined();
|
|
103
112
|
});
|
|
104
|
-
it('imports from the packed @openchamber
|
|
113
|
+
it('imports from the packed @vantran-se/openchamber-web tarball without escaping the published package', async () => {
|
|
105
114
|
const tempRoot = mkdtempSync(join(tmpdir(), 'settings-helpers-pack-'));
|
|
106
115
|
const packDir = join(tempRoot, 'pack');
|
|
107
116
|
const extractDir = join(tempRoot, 'extract');
|
|
@@ -658,6 +667,9 @@ describe('settings helpers', () => {
|
|
|
658
667
|
expect(helpers.sanitizeSettingsUpdate({ recentAgents: ['build', 'plan'] })).toEqual({
|
|
659
668
|
recentAgents: ['build', 'plan'],
|
|
660
669
|
});
|
|
670
|
+
expect(helpers.sanitizeSettingsUpdate({ favoriteAgents: ['build', 'incident'] })).toEqual({
|
|
671
|
+
favoriteAgents: ['build', 'incident'],
|
|
672
|
+
});
|
|
661
673
|
});
|
|
662
674
|
|
|
663
675
|
it('round-trips recentEfforts as a Record<string, string[]>', () => {
|
|
@@ -810,7 +822,7 @@ describe('settings registry gate', () => {
|
|
|
810
822
|
lastDirectory: '/home/testuser/project', homeDirectory: '/home/testuser', opencodeBinary: '/usr/local/bin/opencode',
|
|
811
823
|
projects: [{ id: 'p', path: '/home/testuser/project' }], activeProjectId: 'p',
|
|
812
824
|
securityScopedBookmarks: ['bookmark'], pinnedDirectories: ['/home/testuser/project'],
|
|
813
|
-
desktopLanAccessEnabled: true, desktopKeepAwakeEnabled: true, desktopMinimizeToTrayEnabled: true, desktopMacMenuBarEnabled: true, desktopLinuxNativeFrame: true,
|
|
825
|
+
desktopLanAccessEnabled: true, desktopKeepAwakeEnabled: true, desktopMinimizeToTrayEnabled: true, desktopMacMenuBarEnabled: true, desktopLinuxNativeFrame: true, sidebarShowChatsSection: false,
|
|
814
826
|
desktopUiPassword: 'secret', githubClientId: 'client', githubScopes: 'repo', gitlabClientId: 'gitlab-client', skillCatalogs: [{ id: 'c', label: 'C', source: 'https://x' }],
|
|
815
827
|
defaultGitIdentityId: 'global', permissionAutoAccept: { sessions: { s: true }, revision: 1 }, permissionDefaultMode: 'safety', messageSearchEnabled: true, messageSearchReasoningEnabled: true,
|
|
816
828
|
agentControlToolEnabled: true, agentWebToolEnabled: true, browserProvider: 'builtin', agentMemoryToolEnabled: true, agentNotifyToolEnabled: true, agentToolsCodeMode: true, isolatedSpacesEnabled: true, isolatedSpacesIdleStop: { enabled: true, hours: 4 }, openCodeUpdateToastDismissedVersion: '1.0.0',
|
|
@@ -825,19 +837,19 @@ describe('settings registry gate', () => {
|
|
|
825
837
|
chatRenderMode: 'live', activityRenderMode: 'summary', mermaidRenderingMode: 'svg', userMessageRenderingMode: 'markdown', collapsibleUserMessages: true,
|
|
826
838
|
stickyUserHeader: true, promptNavigatorEnabled: true, wideChatLayoutEnabled: true, showSplitAssistantMessageActions: true, showToolFileIcons: true,
|
|
827
839
|
codeBlockLineWrap: true, tableCellWrap: true, copyMessagesAsPlainText: true, showTurnChangedFiles: true, showExpandedBashTools: true, showExpandedEditTools: true, toolJsonViewMode: 'raw',
|
|
828
|
-
timeFormatPreference: '24h', weekStartPreference: 'monday', messageStreamTransport: 'ws', diffLayoutPreference: 'inline', diffWrapLines: true,
|
|
840
|
+
timeFormatPreference: '24h', weekStartPreference: 'monday', messageStreamTransport: 'ws', diffLayoutPreference: 'inline', diffWrapLines: true, diffHideWhitespace: true,
|
|
829
841
|
gitChangesViewMode: 'tree', gitmojiEnabled: true, defaultFileViewerPreview: true, directoryShowHidden: true, filesViewShowGitignored: true,
|
|
830
|
-
fileEditorKeymap: 'vim', autoSaveEnabled: true, autoCreateWorktree: true, sessionTabsEnabled: true,
|
|
842
|
+
fileEditorKeymap: 'vim', fileEditorVimMappings: 'inoremap jk <Esc>', autoSaveEnabled: true, autoCreateWorktree: true, sessionTabsEnabled: true,
|
|
831
843
|
allowPromptingSubagentSessions: true, inputSpellcheckEnabled: true, enterToSend: true, enterToSendConfigured: true, persistChatDraft: true,
|
|
832
844
|
largeTextPasteBehavior: 'attach', followUpBehavior: 'steer', queueModeEnabled: true, inputHistoryScope: 'global', inputHistoryLimit: 40,
|
|
833
|
-
draftStarters: [{ type: 'command', name: 'plan-feature' }], draftStartersVisible: true, draftStartersCraftGoalAdded: true, draftStartersScheduleTaskAdded: true,
|
|
845
|
+
draftStarters: [{ type: 'command', name: 'plan-feature' }], draftStartersVisible: true, draftStartersCraftGoalAdded: true, draftStartersScheduleTaskAdded: true, addProjectDialogDismissed: true,
|
|
834
846
|
fontSize: 100, terminalFontSize: 14, editorFontSize: 14, uiFont: 'inter', monoFont: 'jetbrains-mono', customUiFont: 'Maple Mono NF CN', customMonoFont: 'Maple Mono', padding: 100, cornerRadius: 8,
|
|
835
847
|
shortcutOverrides: { 'chat.send': 'mod+enter' },
|
|
836
848
|
defaultModel: 'anthropic/claude', defaultVariant: 'high', defaultAgent: 'build', smallModelUseDefault: false, smallModelOverride: 'anthropic/haiku',
|
|
837
849
|
walkthroughModelOverride: 'anthropic/claude', zenModel: 'zen/model',
|
|
838
850
|
favoriteModels: [{ providerID: 'anthropic', modelID: 'claude' }], hiddenModels: [{ providerID: 'openai', modelID: 'gpt' }], collapsedModelProviders: ['openai'],
|
|
839
|
-
recentModels: [{ providerID: 'anthropic', modelID: 'claude' }], lastSelectedModel: 'anthropic/claude', recentAgents: ['build'], recentEfforts: { 'anthropic/claude': ['high'] }, providerOrder: ['anthropic'],
|
|
840
|
-
sessionRecapEnabled: true, sessionSuggestionEnabled: true, sessionWorkEnabled: true, sessionWorkAutoOpen: true, sessionGoalEnabled: true, sessionGoalChecker: 'small-model', sessionGoalMaxAutoTurns: 50, sessionGoalDefaultBudgetEnabled: true, sessionGoalDefaultBudget: 5,
|
|
851
|
+
recentModels: [{ providerID: 'anthropic', modelID: 'claude' }], lastSelectedModel: 'anthropic/claude', recentAgents: ['build'], favoriteAgents: ['build'], recentEfforts: { 'anthropic/claude': ['high'] }, providerOrder: ['anthropic'],
|
|
852
|
+
sessionRecapEnabled: true, sessionSuggestionEnabled: true, sessionWorkEnabled: true, sessionWorkAutoOpen: true, sessionReviewOfferEnabled: true, sessionWorkKeepInGroup: true, sessionGoalEnabled: true, sessionGoalChecker: 'small-model', sessionGoalMaxAutoTurns: 50, sessionGoalDefaultBudgetEnabled: true, sessionGoalDefaultBudget: 5,
|
|
841
853
|
summarizeLastMessage: true, summaryThreshold: 100, summaryLength: 50, maxLastMessageLength: 200, showDeletionDialog: true,
|
|
842
854
|
nativeNotificationsEnabled: true, notificationMode: 'always', notifyOnSubtasks: true, notifyOnCompletion: true, notifyOnError: true, notifyOnQuestion: true,
|
|
843
855
|
notificationTemplates: { completion: { title: 't', message: 'm' } }, showOpenCodeUpdateNotifications: true, reportUsage: true,
|
|
@@ -280,6 +280,10 @@
|
|
|
280
280
|
"scope": "profile",
|
|
281
281
|
"perSurface": true
|
|
282
282
|
},
|
|
283
|
+
"sidebarShowChatsSection": {
|
|
284
|
+
"scope": "profile",
|
|
285
|
+
"perSurface": true
|
|
286
|
+
},
|
|
283
287
|
"workStatusSectionOrder": {
|
|
284
288
|
"scope": "profile"
|
|
285
289
|
},
|
|
@@ -377,6 +381,9 @@
|
|
|
377
381
|
"diffWrapLines": {
|
|
378
382
|
"scope": "profile"
|
|
379
383
|
},
|
|
384
|
+
"diffHideWhitespace": {
|
|
385
|
+
"scope": "profile"
|
|
386
|
+
},
|
|
380
387
|
"gitChangesViewMode": {
|
|
381
388
|
"scope": "profile"
|
|
382
389
|
},
|
|
@@ -395,6 +402,9 @@
|
|
|
395
402
|
"fileEditorKeymap": {
|
|
396
403
|
"scope": "profile"
|
|
397
404
|
},
|
|
405
|
+
"fileEditorVimMappings": {
|
|
406
|
+
"scope": "profile"
|
|
407
|
+
},
|
|
398
408
|
"autoSaveEnabled": {
|
|
399
409
|
"scope": "profile"
|
|
400
410
|
},
|
|
@@ -451,6 +461,9 @@
|
|
|
451
461
|
"draftStartersScheduleTaskAdded": {
|
|
452
462
|
"scope": "profile"
|
|
453
463
|
},
|
|
464
|
+
"addProjectDialogDismissed": {
|
|
465
|
+
"scope": "profile"
|
|
466
|
+
},
|
|
454
467
|
"fontSize": {
|
|
455
468
|
"scope": "profile",
|
|
456
469
|
"perSurface": true
|
|
@@ -528,6 +541,9 @@
|
|
|
528
541
|
"recentAgents": {
|
|
529
542
|
"scope": "profile"
|
|
530
543
|
},
|
|
544
|
+
"favoriteAgents": {
|
|
545
|
+
"scope": "profile"
|
|
546
|
+
},
|
|
531
547
|
"recentEfforts": {
|
|
532
548
|
"scope": "profile"
|
|
533
549
|
},
|
|
@@ -551,6 +567,17 @@
|
|
|
551
567
|
"mobile"
|
|
552
568
|
]
|
|
553
569
|
},
|
|
570
|
+
"sessionReviewOfferEnabled": {
|
|
571
|
+
"scope": "profile",
|
|
572
|
+
"surfaces": [
|
|
573
|
+
"web",
|
|
574
|
+
"desktop",
|
|
575
|
+
"mobile"
|
|
576
|
+
]
|
|
577
|
+
},
|
|
578
|
+
"sessionWorkKeepInGroup": {
|
|
579
|
+
"scope": "profile"
|
|
580
|
+
},
|
|
554
581
|
"sessionGoalEnabled": {
|
|
555
582
|
"scope": "profile"
|
|
556
583
|
},
|
|
@@ -785,22 +812,6 @@
|
|
|
785
812
|
"scope": "device",
|
|
786
813
|
"local": true
|
|
787
814
|
},
|
|
788
|
-
"linearIssueListStatus": {
|
|
789
|
-
"scope": "device",
|
|
790
|
-
"local": true
|
|
791
|
-
},
|
|
792
|
-
"linearIssueListAssignee": {
|
|
793
|
-
"scope": "device",
|
|
794
|
-
"local": true
|
|
795
|
-
},
|
|
796
|
-
"linearIssueListTeamIdByRuntime": {
|
|
797
|
-
"scope": "device",
|
|
798
|
-
"local": true
|
|
799
|
-
},
|
|
800
|
-
"linearIssueListPriority": {
|
|
801
|
-
"scope": "device",
|
|
802
|
-
"local": true
|
|
803
|
-
},
|
|
804
815
|
"showTerminalQuickKeysOnDesktop": {
|
|
805
816
|
"scope": "device",
|
|
806
817
|
"local": true
|
|
@@ -82,6 +82,24 @@ describe('settings runtime', () => {
|
|
|
82
82
|
}
|
|
83
83
|
});
|
|
84
84
|
|
|
85
|
+
it('turns a stored last directory into a project only while one is stored', async () => {
|
|
86
|
+
const { runtime, tempRoot, cleanup } = await createRuntime();
|
|
87
|
+
try {
|
|
88
|
+
const directory = path.join(tempRoot, 'removed-project');
|
|
89
|
+
await fsPromises.mkdir(directory);
|
|
90
|
+
// The legacy migration: no projects, a last directory on disk.
|
|
91
|
+
await runtime.writeSettingsToDisk({ projects: [], lastDirectory: directory });
|
|
92
|
+
expect((await runtime.readSettingsFromDiskMigrated()).projects).toEqual([
|
|
93
|
+
expect.objectContaining({ path: directory }),
|
|
94
|
+
]);
|
|
95
|
+
// Removing the last project forgets the last directory with ''.
|
|
96
|
+
await runtime.writeSettingsToDisk({ projects: [], lastDirectory: '' });
|
|
97
|
+
expect((await runtime.readSettingsFromDiskMigrated()).projects).toEqual([]);
|
|
98
|
+
} finally {
|
|
99
|
+
await cleanup();
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
85
103
|
it('uses OpenChamber themes when a new install has no theme preferences', async () => {
|
|
86
104
|
const { runtime, cleanup } = await createRuntime();
|
|
87
105
|
try {
|