@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
|
@@ -67,7 +67,7 @@ describe.skipIf(!PACKED_ENABLED)('development build inside a space: docker (live
|
|
|
67
67
|
const installed = lock.packages['node_modules/@openchamber/sdk'];
|
|
68
68
|
expect(installed.version).toBe(JSON.parse(sdk).version);
|
|
69
69
|
expect(installed.resolved).toMatch(/^file:.*openchamber-sdk\.tgz$/);
|
|
70
|
-
expect(lock.packages['node_modules/@openchamber
|
|
70
|
+
expect(lock.packages['node_modules/@vantran-se/openchamber-web'].resolved).toMatch(/^file:.*openchamber-web\.tgz$/);
|
|
71
71
|
});
|
|
72
72
|
|
|
73
73
|
it('leaves the tarballs out of the volume', async () => {
|
|
@@ -43,6 +43,7 @@ const STATUS_BY_CODE = new Map([
|
|
|
43
43
|
['space_preparing', 409],
|
|
44
44
|
['space_creation_failed', 409],
|
|
45
45
|
['space_busy', 409],
|
|
46
|
+
['image_pulling', 409],
|
|
46
47
|
['isolated_spaces_off', 404],
|
|
47
48
|
['space_not_running', 409],
|
|
48
49
|
['branch_exists', 409],
|
|
@@ -205,6 +206,10 @@ export function registerSpaceRoutes(app, { getJourney, getPlaces = () => [], rea
|
|
|
205
206
|
app.post(`${SPACES_ROUTE}/places/:placeId/clean-up`, withJourney(async (journey, req, res) => {
|
|
206
207
|
res.json(await journey.cleanUpDisk(String(req.params.placeId ?? '')));
|
|
207
208
|
}));
|
|
209
|
+
// Downloads the image ahead of the first space; answers the disk at once, the download goes on.
|
|
210
|
+
app.post(`${SPACES_ROUTE}/places/:placeId/image`, withJourney(async (journey, req, res) => {
|
|
211
|
+
res.status(202).json(await journey.pullImage(String(req.params.placeId ?? '')));
|
|
212
|
+
}));
|
|
208
213
|
|
|
209
214
|
app.get(SPACES_ROUTE, withJourney(async (journey, _req, res) => {
|
|
210
215
|
res.json({ spaces: await journey.listSpaces({ access: true }) });
|
|
@@ -68,6 +68,7 @@ const journeyOf = (overrides = {}) => {
|
|
|
68
68
|
readSetup: record('readSetup', { setup: null, output: null }),
|
|
69
69
|
readDisk: record('readDisk', { imageBytes: null, toolsBytes: 0, spacesBytes: 0, freeBytes: 0, freesImage: false }),
|
|
70
70
|
cleanUpDisk: record('cleanUpDisk', { freedBytes: 0, kept: [], disk: null }),
|
|
71
|
+
pullImage: record('pullImage', { imageBytes: null, imagePulling: true, imageFailure: null, toolsBytes: 0, spacesBytes: 0, freeBytes: 0, freesImage: false }),
|
|
71
72
|
...overrides,
|
|
72
73
|
};
|
|
73
74
|
};
|
|
@@ -75,7 +76,7 @@ const journeyOf = (overrides = {}) => {
|
|
|
75
76
|
describe('space routes', () => {
|
|
76
77
|
it('answers every journey route with 404 and isolated_spaces_off while the feature is off, and the switch still works', async () => {
|
|
77
78
|
const { call, calls } = await serve({ journey: null, switchState: { enabled: false } });
|
|
78
|
-
for (const [method, path] of [['GET', ''], ['POST', ''], ['GET', '/places'], ['POST', `/${ID}/start`], ['POST', `/${ID}/stop`], ['POST', `/${ID}/restart`], ['POST', `/${ID}/restart-opencode`], ['POST', `/${ID}/grants`], ['DELETE', `/${ID}`], ['GET', `/${ID}/journal`], ['GET', `/${ID}/apply`], ['POST', `/${ID}/apply`], ['GET', '/idle-stop'], ['PUT', '/idle-stop'], ['POST', `/${ID}/setup`], ['GET', `/${ID}/setup`], ['GET', '/places/docker/disk'], ['POST', '/places/docker/clean-up']]) {
|
|
79
|
+
for (const [method, path] of [['GET', ''], ['POST', ''], ['GET', '/places'], ['POST', `/${ID}/start`], ['POST', `/${ID}/stop`], ['POST', `/${ID}/restart`], ['POST', `/${ID}/restart-opencode`], ['POST', `/${ID}/grants`], ['DELETE', `/${ID}`], ['GET', `/${ID}/journal`], ['GET', `/${ID}/apply`], ['POST', `/${ID}/apply`], ['GET', '/idle-stop'], ['PUT', '/idle-stop'], ['POST', `/${ID}/setup`], ['GET', `/${ID}/setup`], ['GET', '/places/docker/disk'], ['POST', '/places/docker/clean-up'], ['POST', '/places/docker/image']]) {
|
|
79
80
|
expect(await call(method, `${SPACES_ROUTE}${path}`, method === 'GET' || method === 'DELETE' ? undefined : {}), `${method} ${path}`).toEqual({ status: 404, body: { code: 'isolated_spaces_off', message: 'Isolated spaces are turned off.', details: null } });
|
|
80
81
|
}
|
|
81
82
|
expect(await call('GET', `${SPACES_ROUTE}/switch`)).toEqual({ status: 200, body: { enabled: false, spaces: [] } });
|
|
@@ -107,6 +108,7 @@ describe('space routes', () => {
|
|
|
107
108
|
expect(await call('GET', SPACES_ROUTE)).toEqual({ status: 200, body: { spaces: [{ id: ID, name: 'One', state: 'running' }] } });
|
|
108
109
|
expect(await call('GET', `${SPACES_ROUTE}/places/docker/disk`)).toEqual({ status: 200, body: { imageBytes: null, toolsBytes: 0, spacesBytes: 0, freeBytes: 0, freesImage: false } });
|
|
109
110
|
expect(await call('POST', `${SPACES_ROUTE}/places/docker/clean-up`)).toEqual({ status: 200, body: { freedBytes: 0, kept: [], disk: null } });
|
|
111
|
+
expect(await call('POST', `${SPACES_ROUTE}/places/docker/image`)).toEqual({ status: 202, body: { imageBytes: null, imagePulling: true, imageFailure: null, toolsBytes: 0, spacesBytes: 0, freeBytes: 0, freesImage: false } });
|
|
110
112
|
const request = { projectDirectory: '/home/me/project', name: 'One', start: 'clean', network: { mode: 'open' } };
|
|
111
113
|
expect(await call('POST', SPACES_ROUTE, request)).toEqual({ status: 202, body: { id: ID, state: 'preparing' } });
|
|
112
114
|
expect(await call('POST', `${SPACES_ROUTE}/${ID}/start`)).toEqual({ status: 200, body: { id: ID, state: 'running' } });
|
|
@@ -122,7 +124,7 @@ describe('space routes', () => {
|
|
|
122
124
|
expect(await call('GET', `${SPACES_ROUTE}/${ID}/setup`)).toEqual({ status: 200, body: { setup: null, output: null } });
|
|
123
125
|
expect(await call('DELETE', `${SPACES_ROUTE}/${ID}`)).toEqual({ status: 200, body: { id: ID, removed: true } });
|
|
124
126
|
expect(journey.calls).toEqual([
|
|
125
|
-
['listSpaces', { access: true }], ['readDisk', 'docker'], ['cleanUpDisk', 'docker'], ['createSpace', request], ['startSpace', ID], ['stopSpace', ID], ['restartSpace', ID], ['restartOpenCode', ID], ['grantAccess', ID, { kind: 'domain', upstream: 'https://registry.example.com/' }], ['openDomain', ID, { domain: 'registry.npmjs.org' }], ['readJournal', ID], ['previewApply', ID], ['applySpace', ID, { as: 'branch', branch: 'b' }], ['runSetup', ID, { commands: ['npm ci'] }], ['readSetup', ID], ['removeSpace', ID, { allowUnsaved: false }],
|
|
127
|
+
['listSpaces', { access: true }], ['readDisk', 'docker'], ['cleanUpDisk', 'docker'], ['pullImage', 'docker'], ['createSpace', request], ['startSpace', ID], ['stopSpace', ID], ['restartSpace', ID], ['restartOpenCode', ID], ['grantAccess', ID, { kind: 'domain', upstream: 'https://registry.example.com/' }], ['openDomain', ID, { domain: 'registry.npmjs.org' }], ['readJournal', ID], ['previewApply', ID], ['applySpace', ID, { as: 'branch', branch: 'b' }], ['runSetup', ID, { commands: ['npm ci'] }], ['readSetup', ID], ['removeSpace', ID, { allowUnsaved: false }],
|
|
126
128
|
]);
|
|
127
129
|
});
|
|
128
130
|
|
|
@@ -475,12 +475,19 @@ class CommandStream extends Duplex {
|
|
|
475
475
|
callback(error);
|
|
476
476
|
}
|
|
477
477
|
|
|
478
|
-
/**
|
|
478
|
+
/**
|
|
479
|
+
* Emits `timeout` after `milliseconds` without a byte in either direction. Zero cancels it, and
|
|
480
|
+
* takes the callback off again, as `net.Socket` does: the http client sets a timeout with its
|
|
481
|
+
* listener on every request a pooled stream carries, and cancels it with the same listener.
|
|
482
|
+
*/
|
|
479
483
|
setTimeout(milliseconds, callback) {
|
|
480
484
|
if (this.#timer) clearTimeout(this.#timer);
|
|
481
485
|
this.#timer = null;
|
|
482
486
|
this.timeout = milliseconds;
|
|
483
|
-
if (callback)
|
|
487
|
+
if (callback) {
|
|
488
|
+
if (milliseconds > 0) this.once('timeout', callback);
|
|
489
|
+
else this.removeListener('timeout', callback);
|
|
490
|
+
}
|
|
484
491
|
if (milliseconds > 0) {
|
|
485
492
|
this.#timer = setTimeout(() => { this.#timer = null; this.emit('timeout'); }, milliseconds);
|
|
486
493
|
this.#timer.unref?.();
|
|
@@ -15,7 +15,6 @@ import { FILLER_PROGRAM } from './tools-filler.js';
|
|
|
15
15
|
// Where the filler puts the tarballs of a packed source. It is a tmpfs, so they vanish with the filler.
|
|
16
16
|
export const TOOLS_STAGING_PATH = '/tmp/openchamber-fill';
|
|
17
17
|
|
|
18
|
-
const WEB_PACKAGE_NAME = '@vantran-se/openchamber-web';
|
|
19
18
|
const WEB_TARBALL_NAME = 'openchamber-web.tgz';
|
|
20
19
|
const SDK_TARBALL_NAME = 'openchamber-sdk.tgz';
|
|
21
20
|
|
|
@@ -57,7 +56,7 @@ const requireRevision = (value) => {
|
|
|
57
56
|
const openCodePackages = (version) => ({ '@opencode/cli': version, '@opencode/plugin': version });
|
|
58
57
|
|
|
59
58
|
/**
|
|
60
|
-
* The versions a released host installs: its own web
|
|
59
|
+
* The versions a released host installs: its own `@vantran-se/openchamber-web`, and OpenCode at the
|
|
61
60
|
* version of the `@opencode/client` this server was built against.
|
|
62
61
|
*/
|
|
63
62
|
export function readHostToolVersions(packageJsonUrl = new URL('../../../package.json', import.meta.url)) {
|
|
@@ -68,14 +67,14 @@ export function readHostToolVersions(packageJsonUrl = new URL('../../../package.
|
|
|
68
67
|
throw new SpaceError('tools_versions_unreadable', `Could not read the package.json of the OpenChamber server: ${error.message}`);
|
|
69
68
|
}
|
|
70
69
|
return {
|
|
71
|
-
webVersion: requireVersion(manifest.version,
|
|
70
|
+
webVersion: requireVersion(manifest.version, '@vantran-se/openchamber-web'),
|
|
72
71
|
openCodeVersion: requireVersion(manifest.dependencies?.['@opencode/client'], 'OpenCode'),
|
|
73
72
|
};
|
|
74
73
|
}
|
|
75
74
|
|
|
76
75
|
export function createRegistryToolsSource({ webVersion, openCodeVersion, revision = '' }) {
|
|
77
76
|
const packages = {
|
|
78
|
-
|
|
77
|
+
'@vantran-se/openchamber-web': requireVersion(webVersion, '@vantran-se/openchamber-web'),
|
|
79
78
|
...openCodePackages(requireVersion(openCodeVersion, 'OpenCode')),
|
|
80
79
|
};
|
|
81
80
|
return finishSource({
|
|
@@ -111,7 +110,7 @@ export function createPackedToolsSource({ webTarballPath, sdkTarballPath, openCo
|
|
|
111
110
|
canonical: { kind: 'packed', tarballs: files.map((file) => ({ name: file.name, sha256: sha256(file.bytes) })), packages, revision: requireRevision(revision) },
|
|
112
111
|
packageJson: packageJsonText({
|
|
113
112
|
dependencies: {
|
|
114
|
-
'@openchamber
|
|
113
|
+
'@vantran-se/openchamber-web': `file:${TOOLS_STAGING_PATH}/${WEB_TARBALL_NAME}`,
|
|
115
114
|
'@openchamber/sdk': `file:${TOOLS_STAGING_PATH}/${SDK_TARBALL_NAME}`,
|
|
116
115
|
...packages,
|
|
117
116
|
},
|
|
@@ -44,7 +44,7 @@ describe('readHostToolVersions', () => {
|
|
|
44
44
|
});
|
|
45
45
|
|
|
46
46
|
describe('createRegistryToolsSource', () => {
|
|
47
|
-
it('asks for the
|
|
47
|
+
it('asks for exact versions, with the plugin at the OpenCode version', () => {
|
|
48
48
|
const source = createRegistryToolsSource(VERSIONS);
|
|
49
49
|
|
|
50
50
|
expect(source.description).toBe('web 1.24.2, opencode 1.18.31');
|
|
@@ -79,7 +79,7 @@ describe('createPackedToolsSource', () => {
|
|
|
79
79
|
name: 'openchamber-space-tools',
|
|
80
80
|
private: true,
|
|
81
81
|
dependencies: {
|
|
82
|
-
'@openchamber
|
|
82
|
+
'@vantran-se/openchamber-web': 'file:/tmp/openchamber-fill/openchamber-web.tgz',
|
|
83
83
|
'@openchamber/sdk': 'file:/tmp/openchamber-fill/openchamber-sdk.tgz',
|
|
84
84
|
'@opencode/cli': '1.18.31',
|
|
85
85
|
'@opencode/plugin': '1.18.31',
|
|
@@ -95,6 +95,7 @@ export function createTerminalRuntime({
|
|
|
95
95
|
isRequestOriginAllowed, rejectWebSocketUpgrade, TERMINAL_INPUT_WS_HEARTBEAT_INTERVAL_MS,
|
|
96
96
|
loadPtyProvider, terminalTerminationGraceMs = TERMINATION_GRACE_MS, shutdownProcesses = shutdownTerminalProcesses,
|
|
97
97
|
environmentRuntime = null,
|
|
98
|
+
logger = console,
|
|
98
99
|
}) {
|
|
99
100
|
const sessions = new Map();
|
|
100
101
|
const pendingSessionCreates = new Map();
|
|
@@ -407,11 +408,22 @@ export function createTerminalRuntime({
|
|
|
407
408
|
|
|
408
409
|
const upgradeHandler = (req, socket, head) => {
|
|
409
410
|
if (parseRequestPathname(req.url) !== TERMINAL_WS_PATH) return;
|
|
411
|
+
const logUpgradeFailure = (error) => {
|
|
412
|
+
logger.warn?.(`[terminal] websocket upgrade failed: ${error?.message || error}`);
|
|
413
|
+
};
|
|
410
414
|
const accept = () => {
|
|
411
415
|
if (!wsServer) { rejectWebSocketUpgrade(socket, 500, 'Terminal WebSocket unavailable'); return; }
|
|
412
416
|
try {
|
|
413
417
|
wsServer.handleUpgrade(req, socket, head, (ws) => wsServer.emit('connection', ws, req));
|
|
414
|
-
} catch
|
|
418
|
+
} catch (error) {
|
|
419
|
+
// handleUpgrade only throws once it has taken the socket: either the
|
|
420
|
+
// handshake already wrote its 101 (a second upgrade of the same socket,
|
|
421
|
+
// or a throw from the connection callback), so the connection is no
|
|
422
|
+
// longer HTTP. Writing a status line here would append an HTTP response
|
|
423
|
+
// to a switched-protocol socket; drop it and log why instead.
|
|
424
|
+
logUpgradeFailure(error);
|
|
425
|
+
socket.destroy();
|
|
426
|
+
}
|
|
415
427
|
};
|
|
416
428
|
const checkOrigin = () => {
|
|
417
429
|
try {
|
|
@@ -424,15 +436,15 @@ export function createTerminalRuntime({
|
|
|
424
436
|
void result.then((allowed) => {
|
|
425
437
|
if (allowed) accept();
|
|
426
438
|
else rejectWebSocketUpgrade(socket, 403, 'Invalid origin');
|
|
427
|
-
}).catch(() => rejectWebSocketUpgrade(socket, 500, 'Upgrade failed'));
|
|
428
|
-
} catch { rejectWebSocketUpgrade(socket, 500, 'Upgrade failed'); }
|
|
439
|
+
}).catch((error) => { logUpgradeFailure(error); rejectWebSocketUpgrade(socket, 500, 'Upgrade failed'); });
|
|
440
|
+
} catch (error) { logUpgradeFailure(error); rejectWebSocketUpgrade(socket, 500, 'Upgrade failed'); }
|
|
429
441
|
};
|
|
430
442
|
if (isOpaqueOriginRequest(req)) { rejectWebSocketUpgrade(socket, 403, 'Invalid origin'); return; }
|
|
431
443
|
if (!uiAuthController?.enabled) {
|
|
432
444
|
void isPasswordlessSocketOriginAllowed(req, isRequestOriginAllowed).then((allowed) => {
|
|
433
445
|
if (allowed) accept();
|
|
434
446
|
else rejectWebSocketUpgrade(socket, 403, 'Invalid origin');
|
|
435
|
-
}).catch(() => rejectWebSocketUpgrade(socket, 500, 'Upgrade failed'));
|
|
447
|
+
}).catch((error) => { logUpgradeFailure(error); rejectWebSocketUpgrade(socket, 500, 'Upgrade failed'); });
|
|
436
448
|
return;
|
|
437
449
|
}
|
|
438
450
|
try {
|
|
@@ -445,8 +457,8 @@ export function createTerminalRuntime({
|
|
|
445
457
|
void result.then((sessionToken) => {
|
|
446
458
|
if (sessionToken) checkOrigin();
|
|
447
459
|
else rejectWebSocketUpgrade(socket, 401, 'UI authentication required');
|
|
448
|
-
}).catch(() => rejectWebSocketUpgrade(socket, 500, 'Upgrade failed'));
|
|
449
|
-
} catch { rejectWebSocketUpgrade(socket, 500, 'Upgrade failed'); }
|
|
460
|
+
}).catch((error) => { logUpgradeFailure(error); rejectWebSocketUpgrade(socket, 500, 'Upgrade failed'); });
|
|
461
|
+
} catch (error) { logUpgradeFailure(error); rejectWebSocketUpgrade(socket, 500, 'Upgrade failed'); }
|
|
450
462
|
};
|
|
451
463
|
server.on('upgrade', upgradeHandler);
|
|
452
464
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
2
|
import http from 'node:http';
|
|
3
|
+
import net from 'node:net';
|
|
4
|
+
import crypto from 'node:crypto';
|
|
3
5
|
import fs from 'node:fs';
|
|
4
6
|
import path from 'node:path';
|
|
5
7
|
import { describe, expect, it, vi } from 'vitest';
|
|
@@ -8,6 +10,24 @@ import { WebSocket } from 'ws';
|
|
|
8
10
|
import { createTerminalRuntime } from './runtime.js';
|
|
9
11
|
import { createTerminalWsControlFrame, readTerminalWsControlFrame } from './terminal-ws-protocol.js';
|
|
10
12
|
|
|
13
|
+
// Drives the raw upgrade handler by hand. A `ws` client hides the HTTP response
|
|
14
|
+
// once the 101 lands, and a real client cannot be made to upgrade one socket
|
|
15
|
+
// twice, so the test emits the `upgrade` event directly on a real socket.
|
|
16
|
+
function manualUpgradeRequest(port) {
|
|
17
|
+
const key = crypto.randomBytes(16).toString('base64');
|
|
18
|
+
return {
|
|
19
|
+
url: '/api/terminal/ws',
|
|
20
|
+
method: 'GET',
|
|
21
|
+
headers: {
|
|
22
|
+
host: `127.0.0.1:${port}`,
|
|
23
|
+
upgrade: 'websocket',
|
|
24
|
+
connection: 'Upgrade',
|
|
25
|
+
'sec-websocket-key': key,
|
|
26
|
+
'sec-websocket-version': '13',
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
11
31
|
function createResponse() {
|
|
12
32
|
return {
|
|
13
33
|
statusCode: 200,
|
|
@@ -764,6 +784,52 @@ describe('terminal runtime', () => {
|
|
|
764
784
|
}
|
|
765
785
|
});
|
|
766
786
|
|
|
787
|
+
it('does not write an HTTP rejection onto a socket that already completed its upgrade', async () => {
|
|
788
|
+
const server = http.createServer();
|
|
789
|
+
const refused = [];
|
|
790
|
+
const logged = [];
|
|
791
|
+
const runtime = createRuntime(server, {
|
|
792
|
+
logger: { warn: (...args) => logged.push(args.join(' ')) },
|
|
793
|
+
rejectWebSocketUpgrade(socket, status, reason) {
|
|
794
|
+
refused.push({ status, reason });
|
|
795
|
+
if (!socket || socket.destroyed) return;
|
|
796
|
+
// Mirrors the real helper: it writes an HTTP status line, which is only
|
|
797
|
+
// valid before the handshake switches protocols.
|
|
798
|
+
try { socket.write(`HTTP/1.1 ${status} ${reason}\r\nConnection: close\r\n\r\n`); } catch { /* closed */ }
|
|
799
|
+
try { socket.destroy(); } catch { /* closed */ }
|
|
800
|
+
},
|
|
801
|
+
});
|
|
802
|
+
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
|
|
803
|
+
const port = server.address().port;
|
|
804
|
+
const socket = net.connect(port, '127.0.0.1');
|
|
805
|
+
await new Promise((resolve, reject) => {
|
|
806
|
+
socket.once('connect', resolve);
|
|
807
|
+
socket.once('error', reject);
|
|
808
|
+
});
|
|
809
|
+
let bytes = '';
|
|
810
|
+
socket.on('data', (chunk) => { bytes += chunk.toString('latin1'); });
|
|
811
|
+
try {
|
|
812
|
+
// First upgrade completes and writes the 101. The second one hits ws's
|
|
813
|
+
// "called more than once" throw, which used to land in the bare catch and
|
|
814
|
+
// append a 500 status line to a connection that is no longer HTTP.
|
|
815
|
+
const req = manualUpgradeRequest(port);
|
|
816
|
+
server.emit('upgrade', req, socket, Buffer.alloc(0));
|
|
817
|
+
server.emit('upgrade', req, socket, Buffer.alloc(0));
|
|
818
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
819
|
+
|
|
820
|
+
// The socket was destroyed by the failure path, and no HTTP rejection was
|
|
821
|
+
// written for a connection that had already switched protocols.
|
|
822
|
+
expect(refused).toEqual([]);
|
|
823
|
+
expect(bytes).not.toContain('500');
|
|
824
|
+
// The upgrade error was not swallowed: it reached the logger.
|
|
825
|
+
expect(logged.some((line) => /terminal/i.test(line) && /upgrade/i.test(line))).toBe(true);
|
|
826
|
+
} finally {
|
|
827
|
+
socket.destroy();
|
|
828
|
+
await runtime.shutdown();
|
|
829
|
+
await new Promise((resolve) => server.close(resolve));
|
|
830
|
+
}
|
|
831
|
+
});
|
|
832
|
+
|
|
767
833
|
it('runs snapshot-first attach, scoped I/O, replay, reconnect, and close over a real websocket', async () => {
|
|
768
834
|
const app = createHttpTestApp();
|
|
769
835
|
const server = http.createServer(app);
|
package/server/lib/tts/index.js
CHANGED
|
@@ -4,16 +4,19 @@
|
|
|
4
4
|
* Public export surface for the Text-to-Speech domain module.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
/** @public */
|
|
7
8
|
export {
|
|
8
9
|
ttsService,
|
|
9
10
|
TTSService,
|
|
10
11
|
TTS_VOICES,
|
|
11
12
|
} from './service.js';
|
|
12
13
|
|
|
14
|
+
/** @public */
|
|
13
15
|
export {
|
|
14
16
|
summarizeText,
|
|
15
17
|
sanitizeForTTS,
|
|
16
18
|
sanitizeForNote,
|
|
17
19
|
} from '../text/summarization.js';
|
|
18
20
|
|
|
21
|
+
/** @public */
|
|
19
22
|
export { transcribeAudio } from './stt.js';
|
|
@@ -10,6 +10,7 @@ import { loadOpenAI } from './openai-sdk.js';
|
|
|
10
10
|
import { normalizeCustomOpenAIBaseURL } from './base-url.js';
|
|
11
11
|
|
|
12
12
|
// Voice options from OpenAI
|
|
13
|
+
/** @public */
|
|
13
14
|
export const TTS_VOICES = [
|
|
14
15
|
'alloy', 'ash', 'ballad', 'coral', 'echo', 'fable',
|
|
15
16
|
'nova', 'onyx', 'sage', 'shimmer', 'verse', 'marin', 'cedar'
|
|
@@ -47,7 +48,8 @@ async function getOpenAIApiKey() {
|
|
|
47
48
|
return null;
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
/** @public */
|
|
52
|
+
export class TTSService {
|
|
51
53
|
constructor() {
|
|
52
54
|
this._client = null;
|
|
53
55
|
this._lastApiKey = null;
|
|
@@ -182,5 +184,5 @@ class TTSService {
|
|
|
182
184
|
}
|
|
183
185
|
|
|
184
186
|
// Export singleton instance
|
|
187
|
+
/** @public */
|
|
185
188
|
export const ttsService = new TTSService();
|
|
186
|
-
export { TTSService };
|
|
@@ -95,7 +95,9 @@ PR mode uses the same searchable, paginated selector as Changes. Its list loads
|
|
|
95
95
|
only while PR mode is visible. Selection ownership and handoff rules are in
|
|
96
96
|
`packages/ui/src/stores/DOCUMENTATION.md`.
|
|
97
97
|
|
|
98
|
-
PR sources may include `sourceRepo: { owner, repo }`.
|
|
98
|
+
PR sources may include `sourceRepo: { owner, repo }`. `owner` is a single
|
|
99
|
+
segment on GitHub and a namespace path on GitLab (`group/subgroup`), matching
|
|
100
|
+
what each provider's project mapper produces. It qualifies the cache/job
|
|
99
101
|
key as `pr:<owner>/<repo>:<number>`, so equal numbers in different repositories
|
|
100
102
|
stay apart. Number-only sources keep `pr:<number>` and read the repository behind
|
|
101
103
|
the bound primary remote. A named repository is honoured only inside the bound
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isString } from '../shared/guards.js';
|
|
1
2
|
import { getRepositoryRoot } from '../git/service.js';
|
|
2
3
|
import { describeSmallModel, generateSmallModelText } from '../small-model/index.js';
|
|
3
4
|
import { buildDigest } from './digest.js';
|
|
@@ -101,7 +102,6 @@ const jobs = new Map();
|
|
|
101
102
|
const schemaRefusedBy = new Set();
|
|
102
103
|
|
|
103
104
|
const modelKey = (model) => `${model.providerID}/${model.modelID}`;
|
|
104
|
-
const isString = (value) => Object.prototype.toString.call(value) === '[object String]';
|
|
105
105
|
const addReadContext = (result, readContext) => {
|
|
106
106
|
if (readContext) result.readContext = readContext;
|
|
107
107
|
return result;
|
|
@@ -166,6 +166,7 @@ export function isGenerating(repoRoot, sourceKeyValue, readContext) {
|
|
|
166
166
|
* Resolve the pair the job registry is keyed by, for callers that need to look
|
|
167
167
|
* a job up without doing any diff work.
|
|
168
168
|
*/
|
|
169
|
+
/** @public */
|
|
169
170
|
export async function getRepositoryRootFor(directory, rawSource, rawReadContext) {
|
|
170
171
|
const source = parseSource(rawSource);
|
|
171
172
|
const readContext = readContextForSource(source, rawReadContext);
|
|
@@ -642,5 +643,3 @@ async function runGeneration({ directory, source, repoRoot, key, force, explicit
|
|
|
642
643
|
...resolveAgainstCurrent(walkthrough, hunkIndex),
|
|
643
644
|
}, readContext);
|
|
644
645
|
}
|
|
645
|
-
|
|
646
|
-
export { WalkthroughSourceError };
|
|
@@ -25,3 +25,25 @@ describe('repository-qualified PR sources', () => {
|
|
|
25
25
|
expect(received).toEqual(['/repo', 42, readContext, { sourceRepo: source.sourceRepo }]);
|
|
26
26
|
});
|
|
27
27
|
});
|
|
28
|
+
|
|
29
|
+
describe('GitLab namespace owners', () => {
|
|
30
|
+
it('accepts a subgroup namespace owner so MR walkthrough selection works below the root group', () => {
|
|
31
|
+
const source = parseSource({ kind: 'pr', number: 12, sourceRepo: { owner: 'group/subgroup', repo: 'repo' } });
|
|
32
|
+
expect(source.sourceRepo).toEqual({ owner: 'group/subgroup', repo: 'repo' });
|
|
33
|
+
expect(sourceKey(source)).toBe('pr:group/subgroup/repo:12');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('accepts deeper nesting and dot/underscore segments as GitLab namespaces spell them', () => {
|
|
37
|
+
for (const owner of ['group/sub/sub', 'group.name/sub_group']) {
|
|
38
|
+
const source = parseSource({ kind: 'pr', number: 3, sourceRepo: { owner, repo: 'repo' } });
|
|
39
|
+
expect(source.sourceRepo.owner).toBe(owner);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('keeps rejecting values that are not owner paths', () => {
|
|
44
|
+
for (const owner of ['group/../repo', '/group/repo', 'group/repo/', 'group//repo', '.', 'group/./repo']) {
|
|
45
|
+
expect(() => parseSource({ kind: 'pr', number: 3, sourceRepo: { owner, repo: 'repo' } }))
|
|
46
|
+
.toThrow('pr sources require a valid repository');
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -49,7 +49,12 @@ export function parseSource(raw) {
|
|
|
49
49
|
if (raw.sourceRepo !== undefined) {
|
|
50
50
|
const { owner, repo } = raw.sourceRepo ?? {};
|
|
51
51
|
try {
|
|
52
|
-
|
|
52
|
+
// Owners are a single segment on GitHub and a namespace path on GitLab
|
|
53
|
+
// (subgroups included). Each segment allows the same characters as a
|
|
54
|
+
// repo name with dots kept internal, so "." and ".." can never be a
|
|
55
|
+
// segment. Downstream the owner is only compared against the bound
|
|
56
|
+
// repository's network, never turned into a filesystem path.
|
|
57
|
+
assert.match(owner, /^[a-zA-Z0-9_-]+(?:\.[a-zA-Z0-9_-]+)*(?:\/[a-zA-Z0-9_-]+(?:\.[a-zA-Z0-9_-]+)*)*$/);
|
|
53
58
|
assert.match(repo, /^[a-zA-Z0-9_.-]+$/);
|
|
54
59
|
} catch {
|
|
55
60
|
throw new WalkthroughSourceError('pr sources require a valid repository');
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import { EventEmitter } from 'node:events';
|
|
3
|
+
import net from 'node:net';
|
|
3
4
|
import express from 'express';
|
|
4
5
|
import path from 'path';
|
|
5
6
|
|
|
@@ -26,6 +27,51 @@ const closeServer = (server) => new Promise((resolve, reject) => {
|
|
|
26
27
|
});
|
|
27
28
|
});
|
|
28
29
|
|
|
30
|
+
const sendChunkedPost = (port, route, body, contentType) => new Promise((resolve, reject) => {
|
|
31
|
+
const socket = net.connect(port, '127.0.0.1');
|
|
32
|
+
const chunks = [];
|
|
33
|
+
socket.setTimeout(5000, () => socket.destroy(new Error('Timed out waiting for proxy response')));
|
|
34
|
+
socket.on('connect', () => {
|
|
35
|
+
const payload = Buffer.from(body);
|
|
36
|
+
socket.write([
|
|
37
|
+
`POST ${route} HTTP/1.1`,
|
|
38
|
+
`Host: 127.0.0.1:${port}`,
|
|
39
|
+
'Connection: close',
|
|
40
|
+
'Transfer-Encoding: chunked',
|
|
41
|
+
...(contentType ? [`Content-Type: ${contentType}`] : []),
|
|
42
|
+
'',
|
|
43
|
+
payload.length > 0 ? `${payload.length.toString(16)}\r\n${body}\r\n0\r\n\r\n` : '0\r\n\r\n',
|
|
44
|
+
].join('\r\n'));
|
|
45
|
+
});
|
|
46
|
+
socket.on('data', (chunk) => chunks.push(chunk));
|
|
47
|
+
socket.on('error', reject);
|
|
48
|
+
socket.on('end', () => {
|
|
49
|
+
const response = Buffer.concat(chunks).toString('utf8');
|
|
50
|
+
const headerEnd = response.indexOf('\r\n\r\n');
|
|
51
|
+
if (headerEnd === -1) {
|
|
52
|
+
reject(new Error('Proxy sent no response headers'));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
let body = response.slice(headerEnd + 4);
|
|
56
|
+
if (/\r\ntransfer-encoding: chunked\r\n/i.test(response.slice(0, headerEnd) + '\r\n')) {
|
|
57
|
+
const decoded = [];
|
|
58
|
+
let offset = 0;
|
|
59
|
+
while (offset < body.length) {
|
|
60
|
+
const lineEnd = body.indexOf('\r\n', offset);
|
|
61
|
+
const size = Number.parseInt(body.slice(offset, lineEnd), 16);
|
|
62
|
+
if (size === 0) break;
|
|
63
|
+
decoded.push(body.slice(lineEnd + 2, lineEnd + 2 + size));
|
|
64
|
+
offset = lineEnd + 2 + size + 2;
|
|
65
|
+
}
|
|
66
|
+
body = decoded.join('');
|
|
67
|
+
}
|
|
68
|
+
resolve({
|
|
69
|
+
status: Number(response.slice(0, response.indexOf('\r\n')).split(' ')[1]),
|
|
70
|
+
body,
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
29
75
|
describe('OpenCode proxy SSE forwarding', () => {
|
|
30
76
|
let upstreamServer;
|
|
31
77
|
let replacementUpstreamServer;
|
|
@@ -509,6 +555,46 @@ describe('OpenCode proxy SSE forwarding', () => {
|
|
|
509
555
|
expect(Number(data.contentLength)).toBeGreaterThan(0);
|
|
510
556
|
});
|
|
511
557
|
|
|
558
|
+
it('forwards an unparsed chunked prompt body to OpenCode', async () => {
|
|
559
|
+
const upstream = express();
|
|
560
|
+
upstream.post('/api/session/abc/prompt', express.json(), (req, res) => {
|
|
561
|
+
res.json({
|
|
562
|
+
body: req.body,
|
|
563
|
+
transferEncoding: req.headers['transfer-encoding'] ?? null,
|
|
564
|
+
contentLength: req.headers['content-length'] ?? null,
|
|
565
|
+
});
|
|
566
|
+
});
|
|
567
|
+
upstreamServer = await listen(upstream);
|
|
568
|
+
const upstreamPort = upstreamServer.address().port;
|
|
569
|
+
|
|
570
|
+
const app = express();
|
|
571
|
+
app.use('/api', express.urlencoded({ extended: true }));
|
|
572
|
+
registerOpenCodeProxy(app, {
|
|
573
|
+
fs: {},
|
|
574
|
+
OPEN_CODE_READY_GRACE_MS: 0,
|
|
575
|
+
getRuntime: () => ({
|
|
576
|
+
openCodePort: upstreamPort,
|
|
577
|
+
isOpenCodeReady: true,
|
|
578
|
+
openCodeNotReadySince: 0,
|
|
579
|
+
isRestartingOpenCode: false,
|
|
580
|
+
}),
|
|
581
|
+
getOpenCodeAuthHeaders: () => ({}),
|
|
582
|
+
buildOpenCodeUrl: (requestPath) => `http://127.0.0.1:${upstreamPort}${requestPath}`,
|
|
583
|
+
ensureOpenCodeApiPrefix: () => {},
|
|
584
|
+
});
|
|
585
|
+
proxyServer = await listen(app);
|
|
586
|
+
const proxyPort = proxyServer.address().port;
|
|
587
|
+
|
|
588
|
+
const payload = { parts: [{ type: 'text', text: 'hello' }] };
|
|
589
|
+
const response = await sendChunkedPost(proxyPort, '/api/session/abc/prompt', JSON.stringify(payload), 'application/json');
|
|
590
|
+
|
|
591
|
+
expect(response.status).toBe(200);
|
|
592
|
+
const data = JSON.parse(response.body);
|
|
593
|
+
expect(data.body).toEqual(payload);
|
|
594
|
+
expect(data.transferEncoding).toBe('chunked');
|
|
595
|
+
expect(data.contentLength).toBeNull();
|
|
596
|
+
});
|
|
597
|
+
|
|
512
598
|
it('replays parsed chunked JSON bodies with a single framing header', async () => {
|
|
513
599
|
const upstream = express();
|
|
514
600
|
upstream.post('/api/session/abc/model', express.json(), (req, res) => {
|
|
@@ -560,6 +646,47 @@ describe('OpenCode proxy SSE forwarding', () => {
|
|
|
560
646
|
expect(Number(data.contentLength)).toBe(JSON.stringify(payload).length);
|
|
561
647
|
});
|
|
562
648
|
|
|
649
|
+
it('forwards an empty chunked request with one framing header', async () => {
|
|
650
|
+
const upstream = express();
|
|
651
|
+
upstream.post('/api/session/abc/interrupt', (req, res) => {
|
|
652
|
+
res.json({
|
|
653
|
+
transferEncoding: req.headers['transfer-encoding'] ?? null,
|
|
654
|
+
contentLength: req.headers['content-length'] ?? null,
|
|
655
|
+
});
|
|
656
|
+
});
|
|
657
|
+
upstreamServer = await listen(upstream);
|
|
658
|
+
const upstreamPort = upstreamServer.address().port;
|
|
659
|
+
const externalBaseUrl = `http://127.0.0.1:${upstreamPort}`;
|
|
660
|
+
|
|
661
|
+
const app = express();
|
|
662
|
+
registerOpenCodeProxy(app, {
|
|
663
|
+
fs: {},
|
|
664
|
+
os: {},
|
|
665
|
+
path,
|
|
666
|
+
OPEN_CODE_READY_GRACE_MS: 0,
|
|
667
|
+
getRuntime: () => ({
|
|
668
|
+
openCodePort: upstreamPort,
|
|
669
|
+
openCodeBaseUrl: externalBaseUrl,
|
|
670
|
+
isOpenCodeReady: true,
|
|
671
|
+
openCodeNotReadySince: 0,
|
|
672
|
+
isRestartingOpenCode: false,
|
|
673
|
+
}),
|
|
674
|
+
getOpenCodeAuthHeaders: () => ({}),
|
|
675
|
+
buildOpenCodeUrl: (requestPath) => `${externalBaseUrl}${requestPath}`,
|
|
676
|
+
ensureOpenCodeApiPrefix: () => {},
|
|
677
|
+
});
|
|
678
|
+
proxyServer = await listen(app);
|
|
679
|
+
const proxyPort = proxyServer.address().port;
|
|
680
|
+
|
|
681
|
+
// A tunnel sends the terminal chunk with no content-length or content-type.
|
|
682
|
+
const response = await sendChunkedPost(proxyPort, '/api/session/abc/interrupt', '');
|
|
683
|
+
|
|
684
|
+
expect(response.status).toBe(200);
|
|
685
|
+
const data = JSON.parse(response.body);
|
|
686
|
+
const framings = [data.transferEncoding !== null, data.contentLength !== null].filter(Boolean);
|
|
687
|
+
expect(framings).toHaveLength(1);
|
|
688
|
+
});
|
|
689
|
+
|
|
563
690
|
it.each([
|
|
564
691
|
['win32', ''],
|
|
565
692
|
['win32', '&directory=%2Flink%2Frepo'],
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { startupCommand } from './commands-startup.js';
|
|
4
|
-
|
|
5
|
-
const quiet = { quiet: true };
|
|
6
|
-
|
|
7
|
-
describe('startup command service controls', () => {
|
|
8
|
-
for (const action of ['start', 'stop', 'restart']) {
|
|
9
|
-
it(`${action}s the installed startup service without changing installation`, async () => {
|
|
10
|
-
const controlStartupService = vi.fn(() => ({ supported: true, enabled: true, active: action !== 'stop', platform: 'linux' }));
|
|
11
|
-
await startupCommand(quiet, action, { controlStartupService });
|
|
12
|
-
expect(controlStartupService).toHaveBeenCalledWith(action);
|
|
13
|
-
});
|
|
14
|
-
}
|
|
15
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{j as a}from"./vendor-react-BUCujlKS.js";import{I as l,a as I}from"./useInlineCommentController-CrjSGCpi.js";function E(g){const{drafts:x,editingDraftId:d,commentText:o,onTextChange:r,selection:n,isDragging:u,fileLabel:s,newWidgetId:f,mapDraftToRange:p,onSave:m,onCancel:c,onEdit:C,onDelete:h}=g,t=[];for(const e of x){const i=p(e);if(e.id===d){t.push({afterLine:i.end,id:`edit-${e.id}`,content:a.jsx(l,{initialText:o,onTextChange:r,fileLabel:s,lineRange:i,isEditing:!0,onSave:m,onCancel:c},`edit-${e.id}`)});continue}t.push({afterLine:i.end,id:`card-${e.id}`,content:a.jsx(I,{draft:e,onEdit:()=>C(e),onDelete:()=>h(e)},`card-${e.id}`)})}if(n&&!d&&!u){const e={...n,start:Math.min(n.start,n.end),end:Math.max(n.start,n.end)};t.push({afterLine:e.end,id:f,content:a.jsx(l,{initialText:o,onTextChange:r,fileLabel:s,lineRange:e,isEditing:!1,onSave:m,onCancel:c},f)})}return t}export{E as b};
|