@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
|
@@ -25,8 +25,17 @@ const MAX_PENDING_BYTES = 256 * 1024;
|
|
|
25
25
|
/** A handshake that has not completed by now is not going to. */
|
|
26
26
|
const HANDSHAKE_TIMEOUT_MS = 15_000;
|
|
27
27
|
|
|
28
|
-
const
|
|
29
|
-
|
|
28
|
+
const SPACE_ID = /^[0-9a-f]{12}$/;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The host's own tunnel, or the one of an isolated space on that host: the
|
|
32
|
+
* host forwards `/api/spaces/<id>/dev-tunnel` to the server inside the space,
|
|
33
|
+
* which dials the port on its own loopback, the space's.
|
|
34
|
+
*/
|
|
35
|
+
const tunnelPath = (spaceId) => (spaceId ? `/api/spaces/${spaceId}/dev-tunnel` : '/api/dev-tunnel');
|
|
36
|
+
|
|
37
|
+
const toWebSocketUrl = (baseUrl, port, spaceId) => {
|
|
38
|
+
const parsed = new URL(tunnelPath(spaceId), baseUrl);
|
|
30
39
|
// WHATWG URL silently ignores a protocol assignment that crosses from a
|
|
31
40
|
// non-special scheme (custom app protocols, relay-virtual URLs) to `ws:`.
|
|
32
41
|
// Without this check the stale scheme survives into `new WebSocket(...)`,
|
|
@@ -45,8 +54,9 @@ export const createDevTunnelClient = ({
|
|
|
45
54
|
handshakeTimeoutMs = HANDSHAKE_TIMEOUT_MS,
|
|
46
55
|
maxPendingBytes = MAX_PENDING_BYTES,
|
|
47
56
|
} = {}) => {
|
|
48
|
-
/** Keyed by
|
|
57
|
+
/** Keyed by base URL, space and remote port, so repeat opens reuse one listener. */
|
|
49
58
|
const tunnels = new Map();
|
|
59
|
+
const tunnelKey = (baseUrl, remotePort, spaceId) => `${baseUrl}|${spaceId ?? ''}|${remotePort}`;
|
|
50
60
|
|
|
51
61
|
const closeTunnel = (key) => {
|
|
52
62
|
const tunnel = tunnels.get(key);
|
|
@@ -65,19 +75,20 @@ export const createDevTunnelClient = ({
|
|
|
65
75
|
* Rejects if the listener cannot bind; per-connection failures close only
|
|
66
76
|
* that connection, so one failed request cannot take the tunnel down.
|
|
67
77
|
*/
|
|
68
|
-
async open({ baseUrl, port, headers = {} }) {
|
|
78
|
+
async open({ baseUrl, port, headers = {}, spaceId = null }) {
|
|
69
79
|
const remotePort = Number.parseInt(String(port), 10);
|
|
70
80
|
if (!Number.isInteger(remotePort) || remotePort <= 0 || remotePort > 65535) {
|
|
71
81
|
throw new Error('A valid remote port is required');
|
|
72
82
|
}
|
|
73
83
|
const base = String(baseUrl || '').trim();
|
|
74
84
|
if (!base) throw new Error('A remote base URL is required');
|
|
85
|
+
if (spaceId !== null && !SPACE_ID.test(String(spaceId))) throw new Error('A valid space id is required');
|
|
75
86
|
|
|
76
|
-
const key =
|
|
87
|
+
const key = tunnelKey(base, remotePort, spaceId);
|
|
77
88
|
const existing = tunnels.get(key);
|
|
78
89
|
if (existing) return { localPort: existing.localPort, reused: true };
|
|
79
90
|
|
|
80
|
-
const target = toWebSocketUrl(base, remotePort);
|
|
91
|
+
const target = toWebSocketUrl(base, remotePort, spaceId);
|
|
81
92
|
const sockets = new Set();
|
|
82
93
|
|
|
83
94
|
const server = net.createServer((socket) => {
|
|
@@ -173,12 +184,12 @@ export const createDevTunnelClient = ({
|
|
|
173
184
|
logger.warn?.(`[dev-tunnel] listener error for port ${remotePort}: ${error?.message || error}`);
|
|
174
185
|
});
|
|
175
186
|
|
|
176
|
-
tunnels.set(key, { server, sockets, localPort, remotePort, baseUrl: base });
|
|
187
|
+
tunnels.set(key, { server, sockets, localPort, remotePort, baseUrl: base, spaceId });
|
|
177
188
|
return { localPort, reused: false };
|
|
178
189
|
},
|
|
179
190
|
|
|
180
|
-
close({ baseUrl, port }) {
|
|
181
|
-
return closeTunnel(
|
|
191
|
+
close({ baseUrl, port, spaceId = null }) {
|
|
192
|
+
return closeTunnel(tunnelKey(String(baseUrl || '').trim(), Number.parseInt(String(port), 10), spaceId));
|
|
182
193
|
},
|
|
183
194
|
|
|
184
195
|
/** Closes every tunnel; used when the desktop switches runtime or quits. */
|
|
@@ -187,7 +198,7 @@ export const createDevTunnelClient = ({
|
|
|
187
198
|
},
|
|
188
199
|
|
|
189
200
|
list() {
|
|
190
|
-
return [...tunnels.values()].map(({ localPort, remotePort, baseUrl }) => ({ localPort, remotePort, baseUrl }));
|
|
201
|
+
return [...tunnels.values()].map(({ localPort, remotePort, baseUrl, spaceId }) => ({ localPort, remotePort, baseUrl, spaceId }));
|
|
191
202
|
},
|
|
192
203
|
};
|
|
193
204
|
};
|
|
@@ -175,6 +175,40 @@ describe('dev tunnel end to end', () => {
|
|
|
175
175
|
expect(second.reused).toBe(true);
|
|
176
176
|
});
|
|
177
177
|
|
|
178
|
+
test('a space tunnel dials the space prefix on the host, and is a tunnel of its own', async () => {
|
|
179
|
+
// The host forwards `/api/spaces/<id>/dev-tunnel` into the space; what matters
|
|
180
|
+
// here is that the client asks for exactly that path, under its own key.
|
|
181
|
+
const paths = [];
|
|
182
|
+
const server = http.createServer((_req, res) => res.end('host'));
|
|
183
|
+
const sockets = trackSockets(server);
|
|
184
|
+
server.on('upgrade', (req, socket) => {
|
|
185
|
+
paths.push(req.url);
|
|
186
|
+
socket.write('HTTP/1.1 404 Not Found\r\n\r\n');
|
|
187
|
+
socket.destroy();
|
|
188
|
+
});
|
|
189
|
+
const hostPort = await listen(server);
|
|
190
|
+
started.push(stopServer(server, sockets));
|
|
191
|
+
const baseUrl = `http://127.0.0.1:${hostPort}`;
|
|
192
|
+
|
|
193
|
+
const client = createDevTunnelClient({ logger: { warn: () => {} } });
|
|
194
|
+
started.push(() => client.closeAll());
|
|
195
|
+
const space = await client.open({ baseUrl, port: 4321, spaceId: '84369ed6edda' });
|
|
196
|
+
const host = await client.open({ baseUrl, port: 4321 });
|
|
197
|
+
expect(space.localPort).not.toBe(host.localPort);
|
|
198
|
+
expect(client.list()).toEqual([
|
|
199
|
+
{ localPort: space.localPort, remotePort: 4321, baseUrl, spaceId: '84369ed6edda' },
|
|
200
|
+
{ localPort: host.localPort, remotePort: 4321, baseUrl, spaceId: null },
|
|
201
|
+
]);
|
|
202
|
+
|
|
203
|
+
await expect(httpGet(space.localPort, '/')).rejects.toThrow();
|
|
204
|
+
await expect(httpGet(host.localPort, '/')).rejects.toThrow();
|
|
205
|
+
expect(paths).toEqual(['/api/spaces/84369ed6edda/dev-tunnel?port=4321', '/api/dev-tunnel?port=4321']);
|
|
206
|
+
|
|
207
|
+
expect(client.close({ baseUrl, port: 4321, spaceId: '84369ed6edda' })).toBe(true);
|
|
208
|
+
expect(client.list()).toEqual([{ localPort: host.localPort, remotePort: 4321, baseUrl, spaceId: null }]);
|
|
209
|
+
await expect(client.open({ baseUrl, port: 4321, spaceId: '../etc' })).rejects.toThrow(/space id/);
|
|
210
|
+
});
|
|
211
|
+
|
|
178
212
|
test('refuses a port discovery does not report, so it is not a loopback proxy', async () => {
|
|
179
213
|
const secret = await startDevServer((_req, res) => res.end('secret service'));
|
|
180
214
|
const host = await startHost({ allowedPorts: [] });
|
|
@@ -39,7 +39,7 @@ const MESSAGE_PAGE_LIMIT = 50;
|
|
|
39
39
|
// is cut, and the agent can read the rest with session.messages.
|
|
40
40
|
const ANSWER_CHAR_LIMIT = 50_000;
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
const DISPATCH_RESULT_SOURCE = 'openchamber-session';
|
|
43
43
|
|
|
44
44
|
const ID_CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
|
45
45
|
|
|
@@ -18,6 +18,7 @@ export interface EnterprisePolicy {
|
|
|
18
18
|
allowLocalExtensions: boolean;
|
|
19
19
|
/** OpenCode CLI path pinned by the policy file; null when nothing is pinned. */
|
|
20
20
|
opencodeBinary: string | null;
|
|
21
|
+
hideBuiltinSkillCatalogs: boolean;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
export type PublicEnterprisePolicy = Pick<EnterprisePolicy, 'enterpriseMode' | 'source' | 'organization' | 'policyError' | 'opencodeBinary'> & { networkAccessBlocked: boolean };
|
|
@@ -69,6 +69,11 @@ import { z } from 'zod';
|
|
|
69
69
|
* and OpenChamber neither installs nor upgrades it (`opencode/env-runtime.js`,
|
|
70
70
|
* `packages/vscode/src/opencode.ts`).
|
|
71
71
|
*
|
|
72
|
+
* The file can also hide the skills catalogs OpenChamber ships with
|
|
73
|
+
* (`hideBuiltinSkillCatalogs`), with or without enterprise mode, so the only
|
|
74
|
+
* catalogs on offer are the ones the organization added
|
|
75
|
+
* (`skills-catalog/curated-sources.js`, `packages/vscode/src/skillsCatalog.ts`).
|
|
76
|
+
*
|
|
72
77
|
* The VS Code extension host, which runs no OpenChamber server, reads the
|
|
73
78
|
* same policy through this module for the parts it has (provider connection,
|
|
74
79
|
* update checks).
|
|
@@ -108,6 +113,7 @@ const policyFileSchema = z.object({
|
|
|
108
113
|
allowLocalExtensions: z.boolean().optional(),
|
|
109
114
|
jev: z.object({ url: optionalText, model: optionalText, apiKey: optionalText }).optional(),
|
|
110
115
|
opencodeBinary: optionalText,
|
|
116
|
+
hideBuiltinSkillCatalogs: z.boolean().optional(),
|
|
111
117
|
}).refine((policy) => !policy.jev || policy.jev.url || (!policy.jev.model && !policy.jev.apiKey), {
|
|
112
118
|
message: '"jev" needs a "url"',
|
|
113
119
|
path: ['jev'],
|
|
@@ -128,7 +134,7 @@ const parsePolicyFile = (text) => {
|
|
|
128
134
|
const field = issue.path.length > 0 ? `"${issue.path.join('.')}": ` : '';
|
|
129
135
|
throw new Error(`${field}${issue.message}`);
|
|
130
136
|
}
|
|
131
|
-
const { enterpriseMode, organization, relayUrl, allowNetworkAccess, allowedExtensions, allowLocalExtensions, jev, opencodeBinary } = parsed.data;
|
|
137
|
+
const { enterpriseMode, organization, relayUrl, allowNetworkAccess, allowedExtensions, allowLocalExtensions, jev, opencodeBinary, hideBuiltinSkillCatalogs } = parsed.data;
|
|
132
138
|
return {
|
|
133
139
|
enterpriseMode: enterpriseMode === true,
|
|
134
140
|
organization: organization ?? null,
|
|
@@ -138,6 +144,7 @@ const parsePolicyFile = (text) => {
|
|
|
138
144
|
allowLocalExtensions,
|
|
139
145
|
jev: jev?.url ? { url: jev.url, model: jev.model ?? null, apiKey: jev.apiKey ?? null } : undefined,
|
|
140
146
|
opencodeBinary: opencodeBinary ?? null,
|
|
147
|
+
hideBuiltinSkillCatalogs: hideBuiltinSkillCatalogs === true,
|
|
141
148
|
};
|
|
142
149
|
};
|
|
143
150
|
|
|
@@ -208,6 +215,8 @@ export const readEnterprisePolicy = (options = {}) => {
|
|
|
208
215
|
allowedExtensions: [],
|
|
209
216
|
allowLocalExtensions: false,
|
|
210
217
|
opencodeBinary: null,
|
|
218
|
+
// Fails closed like the rest: a broken policy offers no catalog it might have hidden.
|
|
219
|
+
hideBuiltinSkillCatalogs: true,
|
|
211
220
|
};
|
|
212
221
|
}
|
|
213
222
|
|
|
@@ -242,6 +251,8 @@ export const readEnterprisePolicy = (options = {}) => {
|
|
|
242
251
|
// File only, in or out of enterprise mode: OPENCODE_BINARY already lets a
|
|
243
252
|
// user pick a binary, and a pin they could override would not be one.
|
|
244
253
|
opencodeBinary: fromFile?.opencodeBinary ?? null,
|
|
254
|
+
// File only, in or out of enterprise mode, for the same reason.
|
|
255
|
+
hideBuiltinSkillCatalogs: fromFile?.hideBuiltinSkillCatalogs === true,
|
|
245
256
|
};
|
|
246
257
|
};
|
|
247
258
|
|
|
@@ -306,7 +317,7 @@ const isMcpSignIn = (integrationId, rest) => (
|
|
|
306
317
|
/**
|
|
307
318
|
* Whether a request to OpenCode would add a way to reach a model provider:
|
|
308
319
|
* every POST under `/api/integration/:id/connect` (key, oauth start and
|
|
309
|
-
* complete, command), storing a key with `POST /api/credential`, and adding a
|
|
320
|
+
* complete, command, external since 2.0.25), storing a key with `POST /api/credential`, and adding a
|
|
310
321
|
* well-known integration. Signing in to a
|
|
311
322
|
* remote MCP server goes through the same routes and stays allowed. Reads,
|
|
312
323
|
* cancelling an attempt and removing or switching an existing account stay
|
|
@@ -41,6 +41,7 @@ describe('enterprise policy', () => {
|
|
|
41
41
|
allowedExtensions: [],
|
|
42
42
|
allowLocalExtensions: false,
|
|
43
43
|
opencodeBinary: null,
|
|
44
|
+
hideBuiltinSkillCatalogs: false,
|
|
44
45
|
});
|
|
45
46
|
});
|
|
46
47
|
|
|
@@ -70,6 +71,25 @@ describe('enterprise policy', () => {
|
|
|
70
71
|
allowedExtensions: [],
|
|
71
72
|
allowLocalExtensions: false,
|
|
72
73
|
opencodeBinary: null,
|
|
74
|
+
hideBuiltinSkillCatalogs: false,
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe('hidden built-in skill catalogs', () => {
|
|
79
|
+
it('comes from the file with or without enterprise mode', () => {
|
|
80
|
+
expect(readEnterprisePolicy(machine({ [LINUX_POLICY]: '{"hideBuiltinSkillCatalogs": true}' })))
|
|
81
|
+
.toMatchObject({ enterpriseMode: false, hideBuiltinSkillCatalogs: true });
|
|
82
|
+
expect(readEnterprisePolicy(machine({ [LINUX_POLICY]: '{"enterpriseMode": true, "hideBuiltinSkillCatalogs": true}' })).hideBuiltinSkillCatalogs)
|
|
83
|
+
.toBe(true);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('is never taken from the environment', () => {
|
|
87
|
+
expect(readEnterprisePolicy(machine({}, { env: { OPENCHAMBER_ENTERPRISE_MODE: '1', OPENCHAMBER_HIDE_BUILTIN_SKILL_CATALOGS: '1' } })).hideBuiltinSkillCatalogs)
|
|
88
|
+
.toBe(false);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('hides them when the file is broken', () => {
|
|
92
|
+
expect(readEnterprisePolicy(machine({ [LINUX_POLICY]: '{"hideBuiltinSkillCatalogs": "yes"}' })).hideBuiltinSkillCatalogs).toBe(true);
|
|
73
93
|
});
|
|
74
94
|
});
|
|
75
95
|
|
|
@@ -257,6 +277,7 @@ describe('enterprise policy', () => {
|
|
|
257
277
|
'/api/integration/anthropic/connect/oauth',
|
|
258
278
|
'/api/integration/anthropic/connect/oauth/att_1/complete',
|
|
259
279
|
'/api/integration/github-copilot/connect/command',
|
|
280
|
+
'/api/integration/amazon-bedrock/connect/external',
|
|
260
281
|
'/api/experimental/integration/wellknown',
|
|
261
282
|
'/api/integration/openai/connect',
|
|
262
283
|
'/api/integration/openai/connect/key/',
|
|
@@ -29,7 +29,7 @@ This module contains the OpenChamber message-stream WebSocket protocol and runti
|
|
|
29
29
|
- `serializeMessageStreamWsEvent(payload, options)` and `sendSerializedMessageStreamWsFrame(socket, frame)` separate encoding from per-socket delivery. Delivery retains ready-state and backpressure checks.
|
|
30
30
|
|
|
31
31
|
### Runtime helpers
|
|
32
|
-
- `createGlobalMessageStreamHub(...)`: creates a shared `/global/event` upstream SSE hub with event/status subscribers and bounded event-id replay.
|
|
32
|
+
- `createGlobalMessageStreamHub(...)`: creates a shared `/global/event` upstream SSE hub with event/status subscribers and bounded event-id replay.
|
|
33
33
|
- `createGlobalUiEventBroadcaster({ sseClients, wsClients, writeSseEvent })`: returns a broadcaster that fans out the same synthetic UI event to SSE and WS clients.
|
|
34
34
|
- `createMessageStreamWsRuntime(...)`: mounts the message-stream WS server, upgrade handler, and SSE-to-WS bridge onto the web HTTP server.
|
|
35
35
|
|
|
@@ -59,7 +59,7 @@ This module contains the OpenChamber message-stream WebSocket protocol and runti
|
|
|
59
59
|
- The hub numbers every event that arrives without an SSE id (`oc-<process>-<sequence>`). OpenCode 1.18 sends no ids at all, and an event without an id used to skip the replay buffer, so reconnecting clients had no cursor and lost whatever fell into the gap. The process prefix makes a cursor from before a server restart miss, which reports `replayReset`, instead of matching an unrelated sequence number.
|
|
60
60
|
- Directory WS clients still attach one upstream `/event?directory=...` SSE reader per connection because directory streams are scoped.
|
|
61
61
|
- If an upstream SSE stream stalls after the browser WS is already ready, the reader aborts that upstream fetch and reconnects upstream with `Last-Event-ID`, keeping the browser WS alive when recovery is fast.
|
|
62
|
-
- When the shared global upstream reconnects after it was previously ready, the global WS bridge sends a fresh `ready` frame to already-ready browser clients. The browser treats this as a reconnect edge and
|
|
62
|
+
- When the shared global upstream reconnects after it was previously ready, the global WS bridge sends a fresh `ready` frame to already-ready browser clients. The browser treats this as a reconnect edge and can run scoped state repair without requiring the browser WS to close.
|
|
63
63
|
- Health checks are reserved for initial upstream connect failures and explicit upstream-unavailable responses, not for ordinary stall recovery on an already-established stream.
|
|
64
64
|
- Global synthetic events such as `openchamber:session-status`, `openchamber:session-activity`, `openchamber:notification`, and `openchamber:heartbeat` are preserved on the WS path, but heartbeat frames are emitted only while an upstream SSE stream is actively attached.
|
|
65
65
|
- Global UI broadcasts are fan-out capable across both SSE and WS clients.
|
|
@@ -21,8 +21,7 @@
|
|
|
21
21
|
* assistant `message.updated`; only the last one carries `finish: "stop"`,
|
|
22
22
|
* which is the same signal v1 gave once per turn.
|
|
23
23
|
*/
|
|
24
|
-
|
|
25
|
-
const isRecord = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
24
|
+
import { isRecord } from '../shared/guards.js';
|
|
26
25
|
|
|
27
26
|
const trimmed = (value) => (typeof value === 'string' ? value.trim() : '');
|
|
28
27
|
|
|
@@ -131,13 +130,6 @@ export function translateWireEvent(payload) {
|
|
|
131
130
|
info: compact({ id: sessionID, directory: directory || undefined, cost: data.cost, tokens: data.tokens, time: { updated: created } }),
|
|
132
131
|
})];
|
|
133
132
|
|
|
134
|
-
case 'session.metadata.updated':
|
|
135
|
-
if (!sessionID) return [];
|
|
136
|
-
return [event('session.updated', {
|
|
137
|
-
sessionID,
|
|
138
|
-
info: compact({ id: sessionID, directory: directory || undefined, metadata: data.metadata, time: { updated: created } }),
|
|
139
|
-
})];
|
|
140
|
-
|
|
141
133
|
case 'session.deleted':
|
|
142
134
|
if (!sessionID) return [];
|
|
143
135
|
return [event('session.deleted', { sessionID, info: { id: sessionID } })];
|
|
@@ -50,17 +50,6 @@ describe('translateWireEvent', () => {
|
|
|
50
50
|
});
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
-
test('metadata updates carry the adoption marker as session.updated', () => {
|
|
54
|
-
const [event] = translateWireEvent(wire('session.metadata.updated', {
|
|
55
|
-
sessionID: 's1',
|
|
56
|
-
metadata: { openchamber: { adopted: true } },
|
|
57
|
-
}));
|
|
58
|
-
expect(event).toMatchObject({
|
|
59
|
-
type: 'session.updated',
|
|
60
|
-
properties: { info: { id: 's1', metadata: { openchamber: { adopted: true } } } },
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
|
|
64
53
|
test('a finished compaction reads as session.compacted', () => {
|
|
65
54
|
expect(translateWireEvent(wire('session.compaction.ended', { sessionID: 's1', reason: 'auto', text: 'summary', recent: '' })))
|
|
66
55
|
.toEqual([expect.objectContaining({ type: 'session.compacted', properties: { sessionID: 's1', directory: '/repo' } })]);
|
|
@@ -21,7 +21,10 @@ Own filesystem API behavior for the web server runtime, including workspace-boun
|
|
|
21
21
|
- `POST /api/fs/write`
|
|
22
22
|
- `POST /api/fs/upload`
|
|
23
23
|
- `POST /api/fs/delete`
|
|
24
|
-
- `POST /api/fs/rename`
|
|
24
|
+
- `POST /api/fs/rename` — never replaces an existing destination: it
|
|
25
|
+
returns `409` with `reason: 'already-exists'`, except for a case-only
|
|
26
|
+
rename where the destination is the source itself. The check runs before
|
|
27
|
+
the rename, so a file created in between can still be replaced.
|
|
25
28
|
- `POST /api/fs/reveal`
|
|
26
29
|
- `POST /api/fs/clone`
|
|
27
30
|
- `POST /api/fs/exec`
|
|
@@ -71,7 +74,7 @@ The Git service validates a selected commit identity before planning or spawning
|
|
|
71
74
|
- Filesystem `EPERM`/`EACCES` failures use the stable `reason: "os-permission"` response marker. Workspace-boundary policy denials must not use that marker because a native folder picker cannot remediate them.
|
|
72
75
|
- `GET /api/fs/directory-stat?path=...` uses one `stat` without listing contents or resolving project topology. It follows the same authenticated directory-discovery path policy as `/api/fs/list`, including targets outside the active workspace. A directory returns `{ isDirectory: true }`; `ENOENT` returns `not-found`, and a file or `ENOTDIR` returns `not-directory`. Permission and other failures remain distinct from a missing path. VS Code explicitly returns 501, so the shared client treats its probe as unknown.
|
|
73
76
|
- HTML previews are untrusted content. `POST /api/fs/preview` (normal API auth, body `{ path }`, workspace resolved like any read) mints a grant `{ grant, expiresAt }` for one page. `GET /api/fs/preview/<grant>/<absolute path>` serves the page and its files: the grant sits in the path, so the page's relative URLs carry it and no session credential appears in a URL the page can read. `core-routes.js` lets these GETs past API auth; the route itself rejects an unknown or expired grant with 403. A grant fixes the workspace base at mint time (the page's project, or the managed root it lives in) and lapses after ten idle minutes; every served file extends it. Every answer carries `Content-Security-Policy: sandbox allow-scripts allow-forms allow-popups allow-modals allow-downloads`, so the page is an opaque-origin document even when opened in its own tab, and the Files view frame has no `allow-same-origin`. Embedding (images, stylesheets, classic scripts) works anywhere the grant's base allows. Reading bytes from script (`fetch`, fonts, module scripts) is a CORS request from origin `null`, answered with `Access-Control-Allow-Origin: null` only inside the grant's read root: the page's project, or for a page inside a managed root, the page's own folder. Other files under `~/.config/openchamber` stay unreadable to the page.
|
|
74
|
-
- `GET /api/fs/raw` answers one `Range: bytes=…` span with `206`, `Content-Range` and a stream from disk (`byte-range.js` reduces the header to a span). Media elements send a span on every seek, and Chromium and WebKit refuse to seek without a `206`, so the viewer's audio and video players depend on this
|
|
77
|
+
- `GET /api/fs/raw` answers one `Range: bytes=…` span with `206`, `Content-Range` and a stream from disk (`byte-range.js` reduces the header to a span). Media elements send a span on every seek, and Chromium and WebKit refuse to seek without a `206`, so the viewer's audio and video players depend on this. A whole-file request streams from disk the same way, so a large PDF or recording never sits in server memory. The MIME table (`FILE_MIME_MAP`) is shared with `/api/fs/preview` and covers images, PDF, audio, video, fonts, CSV/TSV and Mermaid.
|
|
75
78
|
- File read, raw and stat routes accept outside paths with `allowOutsideWorkspace=true` under the normal server authentication and OS permissions. They do not require a separate file grant. Legacy grant parameters are ignored. Workspace-scoped reads resolve symlinks after checking the requested path. Write routes keep canonical-target boundary checks.
|
|
76
79
|
- If adding new `/api/fs/*` endpoints, add them in `routes.js` and extend this document.
|
|
77
80
|
- `GET /api/fs/list` may resolve symlinks with `realpath` to read directory contents, but the response `path` and each entry `path` must stay in the caller's requested path space (`path.join(requestedPath, name)`). Returning real paths breaks file-tree expansion for directories reached through workspace symlinks.
|
package/server/lib/fs/routes.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createRealpathCache } from '../path-realpath-cache.js';
|
|
2
2
|
import { redactGitText } from '../git/redaction.js';
|
|
3
3
|
import { resolveByteRange } from './byte-range.js';
|
|
4
|
+
import { createWorkspaceFileNameIndex } from './workspace-file-names.js';
|
|
4
5
|
import nodeFsPromises from 'node:fs/promises';
|
|
5
6
|
import nodePath from 'node:path';
|
|
6
7
|
|
|
@@ -972,6 +973,22 @@ export const registerFsRoutes = (app, dependencies) => {
|
|
|
972
973
|
}
|
|
973
974
|
});
|
|
974
975
|
|
|
976
|
+
// A bare file name an agent wrote without its folder (`Foo.tsx:12`): the
|
|
977
|
+
// workspace files with that name. One cached git listing per workspace
|
|
978
|
+
// answers every name (see workspace-file-names.js).
|
|
979
|
+
const workspaceFileNames = createWorkspaceFileNameIndex({ spawn, resolveGitBinary: resolveGitBinaryForSpawn });
|
|
980
|
+
app.get('/api/fs/find-by-name', async (req, res) => {
|
|
981
|
+
const name = typeof req.query.name === 'string' ? req.query.name.trim() : '';
|
|
982
|
+
if (!name || name.length > 255 || name === '.' || name === '..' || /[/\\*?[\]:]/.test(name)) {
|
|
983
|
+
return res.status(400).json({ error: 'A plain file name is required' });
|
|
984
|
+
}
|
|
985
|
+
const project = await resolveProjectDirectory(req);
|
|
986
|
+
if (!project.directory) {
|
|
987
|
+
return res.status(400).json({ error: project.error || 'Active workspace is required' });
|
|
988
|
+
}
|
|
989
|
+
return res.json({ paths: await workspaceFileNames.find(project.directory, name) });
|
|
990
|
+
});
|
|
991
|
+
|
|
975
992
|
app.get('/api/fs/stat', async (req, res) => {
|
|
976
993
|
const filePath = typeof req.query.path === 'string' ? req.query.path.trim() : '';
|
|
977
994
|
const optional = req.query.optional === 'true';
|
|
@@ -1138,32 +1155,49 @@ export const registerFsRoutes = (app, dependencies) => {
|
|
|
1138
1155
|
res.setHeader('Referrer-Policy', 'no-referrer');
|
|
1139
1156
|
res.setHeader('Accept-Ranges', 'bytes');
|
|
1140
1157
|
|
|
1141
|
-
//
|
|
1142
|
-
//
|
|
1143
|
-
// hundreds of megabytes.
|
|
1158
|
+
// The file is streamed from disk, never read whole: the audio and video
|
|
1159
|
+
// players ask for a span on every seek, and a recording or a PDF can be
|
|
1160
|
+
// hundreds of megabytes the server would otherwise hold in memory.
|
|
1144
1161
|
const range = resolveByteRange(req.headers?.range, stats.size);
|
|
1145
1162
|
if (range.kind === 'unsatisfiable') {
|
|
1146
1163
|
res.setHeader('Content-Range', `bytes */${stats.size}`);
|
|
1147
1164
|
return res.status(416).end();
|
|
1148
1165
|
}
|
|
1166
|
+
res.type(mimeType);
|
|
1167
|
+
// An empty file has no byte to read, and a read stream over `0..-1` would fail.
|
|
1168
|
+
if (stats.size === 0) {
|
|
1169
|
+
res.setHeader('Content-Length', '0');
|
|
1170
|
+
return res.end();
|
|
1171
|
+
}
|
|
1172
|
+
// Opened before any span header is set, so a failed open answers a clean error.
|
|
1173
|
+
const handle = await fsPromises.open(canonicalPath, 'r');
|
|
1174
|
+
const span = range.kind === 'range' ? range : { start: 0, end: stats.size - 1 };
|
|
1149
1175
|
if (range.kind === 'range') {
|
|
1150
|
-
const handle = await fsPromises.open(canonicalPath, 'r');
|
|
1151
1176
|
res.status(206);
|
|
1152
|
-
res.setHeader('Content-Range', `bytes ${
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1177
|
+
res.setHeader('Content-Range', `bytes ${span.start}-${span.end}/${stats.size}`);
|
|
1178
|
+
}
|
|
1179
|
+
res.setHeader('Content-Length', String(span.end - span.start + 1));
|
|
1180
|
+
// pipe() leaves the source paused when a media player disconnects.
|
|
1181
|
+
// Destroy it too so autoClose releases the FileHandle before GC.
|
|
1182
|
+
try {
|
|
1183
|
+
const stream = handle.createReadStream({ start: span.start, end: span.end });
|
|
1184
|
+
const onResponseClose = () => stream.destroy();
|
|
1185
|
+
res.once('close', onResponseClose);
|
|
1186
|
+
stream.once('close', () => res.removeListener('close', onResponseClose));
|
|
1157
1187
|
stream.on('error', (error) => {
|
|
1158
|
-
console.error('Failed to stream raw file
|
|
1188
|
+
console.error('Failed to stream raw file:', error);
|
|
1159
1189
|
res.destroy(error);
|
|
1160
1190
|
});
|
|
1161
|
-
|
|
1162
|
-
|
|
1191
|
+
if (res.destroyed) {
|
|
1192
|
+
stream.destroy();
|
|
1193
|
+
} else {
|
|
1194
|
+
stream.pipe(res);
|
|
1195
|
+
}
|
|
1196
|
+
} catch (error) {
|
|
1197
|
+
await handle.close();
|
|
1198
|
+
throw error;
|
|
1163
1199
|
}
|
|
1164
|
-
|
|
1165
|
-
const content = await fsPromises.readFile(canonicalPath);
|
|
1166
|
-
return res.type(mimeType).send(content);
|
|
1200
|
+
return undefined;
|
|
1167
1201
|
} catch (error) {
|
|
1168
1202
|
const err = error;
|
|
1169
1203
|
if (err && typeof err === 'object' && err.code === 'ENOENT') {
|
|
@@ -1534,6 +1568,19 @@ export const registerFsRoutes = (app, dependencies) => {
|
|
|
1534
1568
|
return res.status(400).json({ error: 'Source and destination must share the same workspace root' });
|
|
1535
1569
|
}
|
|
1536
1570
|
|
|
1571
|
+
// fs.rename silently replaces an existing file. A destination that is the
|
|
1572
|
+
// source itself is a case-only rename on a case-insensitive filesystem.
|
|
1573
|
+
const [sourceStats, destinationStats] = await Promise.all([
|
|
1574
|
+
fsPromises.lstat(resolvedOld.resolved),
|
|
1575
|
+
fsPromises.lstat(resolvedNew.resolved).catch((error) => {
|
|
1576
|
+
if (error?.code === 'ENOENT') return null;
|
|
1577
|
+
throw error;
|
|
1578
|
+
}),
|
|
1579
|
+
]);
|
|
1580
|
+
if (destinationStats && (destinationStats.dev !== sourceStats.dev || destinationStats.ino !== sourceStats.ino)) {
|
|
1581
|
+
return res.status(409).json({ error: 'Destination path already exists', reason: 'already-exists' });
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1537
1584
|
await fsPromises.rename(resolvedOld.resolved, resolvedNew.resolved);
|
|
1538
1585
|
return res.json({ success: true, path: resolvedNew.resolved });
|
|
1539
1586
|
} catch (error) {
|