@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
|
@@ -3,21 +3,6 @@ import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
|
3
3
|
import { createGlobalMessageStreamHub } from '../event-stream/global-hub.js';
|
|
4
4
|
import { createOpenCodeWatcherRuntime } from './watcher.js';
|
|
5
5
|
|
|
6
|
-
async function waitForAssertion(assertion) {
|
|
7
|
-
const deadline = Date.now() + 1000;
|
|
8
|
-
let lastError;
|
|
9
|
-
while (Date.now() < deadline) {
|
|
10
|
-
try {
|
|
11
|
-
assertion();
|
|
12
|
-
return;
|
|
13
|
-
} catch (error) {
|
|
14
|
-
lastError = error;
|
|
15
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
throw lastError;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
6
|
function createSseResponse({ blocks = [], signal, holdOpen = false }) {
|
|
22
7
|
const encoder = new TextEncoder();
|
|
23
8
|
let index = 0;
|
|
@@ -204,45 +189,6 @@ describe('createOpenCodeWatcherRuntime', () => {
|
|
|
204
189
|
});
|
|
205
190
|
});
|
|
206
191
|
|
|
207
|
-
it('keeps its shared subscription across a hub rebind', async () => {
|
|
208
|
-
vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
209
|
-
let generation = 0;
|
|
210
|
-
const payloads = [];
|
|
211
|
-
const globalEventHub = createGlobalMessageStreamHub({
|
|
212
|
-
buildOpenCodeUrl: () => `http://127.0.0.1:${generation === 0 ? 4096 : 5096}/api/event`,
|
|
213
|
-
getOpenCodeAuthHeaders: () => ({ Authorization: generation === 0 ? 'Basic first' : 'Basic second' }),
|
|
214
|
-
upstreamReconnectDelayMs: 60_000,
|
|
215
|
-
fetchImpl: async (_url, options) => createSseResponse({
|
|
216
|
-
signal: options.signal,
|
|
217
|
-
holdOpen: true,
|
|
218
|
-
blocks: [generation === 0
|
|
219
|
-
? 'data: {"id":"evt-1","type":"server.connected","data":{}}\n\n'
|
|
220
|
-
: 'data: {"id":"evt-2","type":"session.renamed","location":{"directory":"/tmp/project"},"data":{"sessionID":"ses_1","title":"Recovered"}}\n\n'],
|
|
221
|
-
}),
|
|
222
|
-
});
|
|
223
|
-
const watcher = createOpenCodeWatcherRuntime({
|
|
224
|
-
waitForOpenCodePort: async () => {},
|
|
225
|
-
buildOpenCodeUrl: () => '',
|
|
226
|
-
getOpenCodeAuthHeaders: () => ({}),
|
|
227
|
-
globalEventHub,
|
|
228
|
-
onPayload: (payload) => payloads.push(payload),
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
try {
|
|
232
|
-
await watcher.start();
|
|
233
|
-
await waitForAssertion(() => expect(payloads.map((payload) => payload.type)).toContain('server.connected'));
|
|
234
|
-
generation = 1;
|
|
235
|
-
globalEventHub.rebind();
|
|
236
|
-
await waitForAssertion(() => expect(payloads).toContainEqual(expect.objectContaining({
|
|
237
|
-
type: 'session.updated',
|
|
238
|
-
properties: expect.objectContaining({ sessionID: 'ses_1' }),
|
|
239
|
-
})));
|
|
240
|
-
} finally {
|
|
241
|
-
watcher.stop();
|
|
242
|
-
globalEventHub.stop();
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
|
|
246
192
|
it('does not stop a shared global event hub when the watcher stops', async () => {
|
|
247
193
|
const events = new Set();
|
|
248
194
|
const statuses = new Set();
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
|
|
3
|
+
// Outbound requests from the server follow the standard proxy variables
|
|
4
|
+
// (HTTP_PROXY, HTTPS_PROXY, NO_PROXY, either case), so usage quotas, catalogs
|
|
5
|
+
// and other upstream calls work behind a corporate or local proxy without
|
|
6
|
+
// NODE_USE_ENV_PROXY. Loopback always stays direct: OAuth callbacks, the
|
|
7
|
+
// managed OpenCode server and SSH forwards live there, and a proxy that
|
|
8
|
+
// cannot reach this machine's 127.0.0.1 would break them.
|
|
9
|
+
|
|
10
|
+
const LOOPBACK_HOSTS = ['localhost', '127.0.0.1', '::1'];
|
|
11
|
+
|
|
12
|
+
const readVariable = (env, name) => String(env[name] ?? env[name.toLowerCase()] ?? '').trim();
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The proxy settings the environment asks for, with loopback added to the
|
|
16
|
+
* bypass list, or null when no proxy is configured.
|
|
17
|
+
*/
|
|
18
|
+
export const resolveOutboundProxyEnv = (env) => {
|
|
19
|
+
const httpProxy = readVariable(env, 'HTTP_PROXY');
|
|
20
|
+
const httpsProxy = readVariable(env, 'HTTPS_PROXY');
|
|
21
|
+
if (!httpProxy && !httpsProxy) return null;
|
|
22
|
+
const bypass = readVariable(env, 'NO_PROXY').split(',').map((entry) => entry.trim()).filter(Boolean);
|
|
23
|
+
for (const host of LOOPBACK_HOSTS) {
|
|
24
|
+
if (!bypass.includes(host)) bypass.push(host);
|
|
25
|
+
}
|
|
26
|
+
const proxyEnv = { NO_PROXY: bypass.join(',') };
|
|
27
|
+
if (httpProxy) proxyEnv.HTTP_PROXY = httpProxy;
|
|
28
|
+
if (httpsProxy) proxyEnv.HTTPS_PROXY = httpsProxy;
|
|
29
|
+
return proxyEnv;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Route this process's fetch and http(s) requests through the configured
|
|
34
|
+
* proxy. NO_PROXY is written back to the environment so clients that read it
|
|
35
|
+
* themselves (Bun's fetch, child processes) keep loopback direct too.
|
|
36
|
+
* Returns whether a proxy was configured.
|
|
37
|
+
*/
|
|
38
|
+
export const applyOutboundProxyFromEnv = ({ env = process.env, httpModule = http } = {}) => {
|
|
39
|
+
const proxyEnv = resolveOutboundProxyEnv(env);
|
|
40
|
+
if (!proxyEnv) return false;
|
|
41
|
+
env.NO_PROXY = proxyEnv.NO_PROXY;
|
|
42
|
+
env.no_proxy = proxyEnv.NO_PROXY;
|
|
43
|
+
// Node 24 and Electron's Node apply this to fetch and http(s); Bun's fetch
|
|
44
|
+
// reads the variables directly.
|
|
45
|
+
try {
|
|
46
|
+
httpModule.setGlobalProxyFromEnv?.(proxyEnv);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.warn('Could not apply the proxy from HTTP_PROXY/HTTPS_PROXY:', error?.message || error);
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
return true;
|
|
52
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
|
|
4
|
+
import { applyOutboundProxyFromEnv, resolveOutboundProxyEnv } from './outbound-proxy.js';
|
|
5
|
+
|
|
6
|
+
describe('resolveOutboundProxyEnv', () => {
|
|
7
|
+
it('is null without a proxy', () => {
|
|
8
|
+
expect(resolveOutboundProxyEnv({ NO_PROXY: 'internal.example' })).toBeNull();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('reads either case and always bypasses loopback', () => {
|
|
12
|
+
expect(resolveOutboundProxyEnv({ https_proxy: 'http://proxy:7897', no_proxy: 'corp.example, 127.0.0.1' })).toEqual({
|
|
13
|
+
HTTPS_PROXY: 'http://proxy:7897',
|
|
14
|
+
NO_PROXY: 'corp.example,127.0.0.1,localhost,::1',
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('applyOutboundProxyFromEnv', () => {
|
|
20
|
+
it('leaves the process alone without a proxy', () => {
|
|
21
|
+
const setGlobalProxyFromEnv = vi.fn();
|
|
22
|
+
expect(applyOutboundProxyFromEnv({ env: {}, httpModule: { setGlobalProxyFromEnv } })).toBe(false);
|
|
23
|
+
expect(setGlobalProxyFromEnv).not.toHaveBeenCalled();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('writes the loopback bypass back for clients that read the environment', () => {
|
|
27
|
+
const env = { HTTP_PROXY: 'http://proxy:3128' };
|
|
28
|
+
const setGlobalProxyFromEnv = vi.fn();
|
|
29
|
+
expect(applyOutboundProxyFromEnv({ env, httpModule: { setGlobalProxyFromEnv } })).toBe(true);
|
|
30
|
+
expect(env.NO_PROXY).toBe('localhost,127.0.0.1,::1');
|
|
31
|
+
expect(env.no_proxy).toBe('localhost,127.0.0.1,::1');
|
|
32
|
+
expect(setGlobalProxyFromEnv).toHaveBeenCalledWith({ HTTP_PROXY: 'http://proxy:3128', NO_PROXY: 'localhost,127.0.0.1,::1' });
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('sends fetch through the proxy and keeps loopback direct in a real Node process', () => {
|
|
36
|
+
const moduleUrl = new URL('./outbound-proxy.js', import.meta.url).href;
|
|
37
|
+
const result = spawnSync('node', ['--input-type=module', '--eval', `
|
|
38
|
+
import http from 'node:http';
|
|
39
|
+
import assert from 'node:assert/strict';
|
|
40
|
+
const seen = [];
|
|
41
|
+
const proxy = http.createServer((req, res) => res.end());
|
|
42
|
+
proxy.on('connect', (req, socket) => { seen.push('proxy ' + req.url); socket.end('HTTP/1.1 502 Bad Gateway\\r\\n\\r\\n'); });
|
|
43
|
+
const local = http.createServer((req, res) => { seen.push('direct'); res.end('ok'); });
|
|
44
|
+
await new Promise((resolve) => proxy.listen(0, '127.0.0.1', resolve));
|
|
45
|
+
await new Promise((resolve) => local.listen(0, '127.0.0.1', resolve));
|
|
46
|
+
const { applyOutboundProxyFromEnv } = await import(${JSON.stringify(moduleUrl)});
|
|
47
|
+
assert.equal(applyOutboundProxyFromEnv({ env: { HTTPS_PROXY: 'http://127.0.0.1:' + proxy.address().port, HTTP_PROXY: 'http://127.0.0.1:' + proxy.address().port } }), true);
|
|
48
|
+
await fetch('http://upstream.invalid/usage').catch(() => {});
|
|
49
|
+
assert.equal(await (await fetch('http://127.0.0.1:' + local.address().port + '/')).text(), 'ok');
|
|
50
|
+
assert.deepEqual(seen, ['proxy upstream.invalid:80', 'direct']);
|
|
51
|
+
process.exit(0);
|
|
52
|
+
`], { encoding: 'utf8', timeout: 15_000, windowsHide: true });
|
|
53
|
+
expect(result.status, result.stderr).toBe(0);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -16,6 +16,7 @@ const PACKAGE_NAME = '@vantran-se/openchamber-web';
|
|
|
16
16
|
const PACKAGE_PATH_SEGMENTS = PACKAGE_NAME.split('/');
|
|
17
17
|
const GITHUB_RELEASES_URL = 'https://github.com/vantran-se/openchamber/releases';
|
|
18
18
|
const GITHUB_RELEASES_API_URL = 'https://api.github.com/repos/vantran-se/openchamber/releases';
|
|
19
|
+
const UPSTREAM_RELEASES_API_URL = 'https://api.github.com/repos/openchamber/openchamber/releases';
|
|
19
20
|
let cachedDetectedPm = null;
|
|
20
21
|
|
|
21
22
|
function getSpawnSyncBaseOptions() {
|
|
@@ -98,7 +99,7 @@ async function resolveAndroidApkUrl(version, candidateUrl) {
|
|
|
98
99
|
}
|
|
99
100
|
|
|
100
101
|
try {
|
|
101
|
-
const response = await fetch(`${
|
|
102
|
+
const response = await fetch(`${UPSTREAM_RELEASES_API_URL}/tags/v${version}`, {
|
|
102
103
|
headers: {
|
|
103
104
|
Accept: 'application/vnd.github+json',
|
|
104
105
|
'User-Agent': 'openchamber-update-check',
|
|
@@ -600,6 +601,15 @@ function resolvePackageManagerCommand(pm) {
|
|
|
600
601
|
return pm;
|
|
601
602
|
}
|
|
602
603
|
|
|
604
|
+
function quoteCommand(command) {
|
|
605
|
+
if (!command) return command;
|
|
606
|
+
if (!/\s/.test(command)) return command;
|
|
607
|
+
if (process.platform === 'win32') {
|
|
608
|
+
return `"${command.replace(/"/g, '""')}"`;
|
|
609
|
+
}
|
|
610
|
+
return `'${command.replace(/'/g, "'\\''")}'`;
|
|
611
|
+
}
|
|
612
|
+
|
|
603
613
|
function isCommandAvailable(command) {
|
|
604
614
|
try {
|
|
605
615
|
const result = spawnSync(command, ['--version'], {
|
|
@@ -648,7 +658,7 @@ function isPackageInstalledWith(pm) {
|
|
|
648
658
|
|
|
649
659
|
// npm, bun and yarn print `name@version`; pnpm separates the name and the
|
|
650
660
|
// version with whitespace. An optional `v` covers yarn listing formats.
|
|
651
|
-
const GLOBAL_VERSION_PATTERN =
|
|
661
|
+
const GLOBAL_VERSION_PATTERN = /(?:@vantran-se\/openchamber-web|@openchamber\/web)[@\s]+v?(\d[\w.+-]*)/;
|
|
652
662
|
|
|
653
663
|
/**
|
|
654
664
|
* Read the globally installed version of the package as reported by the
|
|
@@ -687,30 +697,25 @@ function normalizeTargetVersion(value) {
|
|
|
687
697
|
* package manager see different metadata, and dist-tag resolution can lag the
|
|
688
698
|
* check behind a fresh release (stale packument cache, pnpm minimumReleaseAge).
|
|
689
699
|
*/
|
|
690
|
-
function
|
|
700
|
+
export function getUpdateCommand(pm = detectPackageManager(), options = {}) {
|
|
691
701
|
const targetVersion = normalizeTargetVersion(options.targetVersion);
|
|
692
702
|
if (options.targetVersion != null && !targetVersion) {
|
|
693
703
|
throw new Error(`Invalid target version for update: ${String(options.targetVersion)}`);
|
|
694
704
|
}
|
|
695
|
-
const
|
|
696
|
-
const
|
|
705
|
+
const versionSpec = targetVersion ? `@${targetVersion}` : '@latest';
|
|
706
|
+
const pmCommand = quoteCommand(resolvePackageManagerCommand(pm));
|
|
697
707
|
switch (pm) {
|
|
698
708
|
case 'pnpm':
|
|
699
|
-
return {
|
|
709
|
+
return `${pmCommand} add -g ${PACKAGE_NAME}${versionSpec}`;
|
|
700
710
|
case 'yarn':
|
|
701
|
-
return {
|
|
711
|
+
return `${pmCommand} global add ${PACKAGE_NAME}${versionSpec}`;
|
|
702
712
|
case 'bun':
|
|
703
|
-
return {
|
|
713
|
+
return `${pmCommand} add -g ${PACKAGE_NAME}${versionSpec}`;
|
|
704
714
|
default:
|
|
705
|
-
return {
|
|
715
|
+
return `${pmCommand} install -g ${PACKAGE_NAME}${versionSpec}`;
|
|
706
716
|
}
|
|
707
717
|
}
|
|
708
718
|
|
|
709
|
-
export function getUpdateCommand(pm = detectPackageManager(), options = {}) {
|
|
710
|
-
const invocation = getUpdateInvocation(pm, options);
|
|
711
|
-
return [invocation.command, ...invocation.args].join(' ');
|
|
712
|
-
}
|
|
713
|
-
|
|
714
719
|
/**
|
|
715
720
|
* Get current installed version from package.json
|
|
716
721
|
*/
|
|
@@ -850,14 +855,15 @@ export async function checkForUpdates(options = {}) {
|
|
|
850
855
|
* and the caller must not report success without the version matching.
|
|
851
856
|
*/
|
|
852
857
|
export function executeUpdate(pm = detectPackageManager(), options = {}) {
|
|
853
|
-
const
|
|
858
|
+
const command = getUpdateCommand(pm, { targetVersion: options.targetVersion });
|
|
854
859
|
if (!options?.silent) {
|
|
855
860
|
console.log(`Updating ${PACKAGE_NAME} using ${pm}...`);
|
|
856
|
-
console.log(`Running: ${
|
|
861
|
+
console.log(`Running: ${command}`);
|
|
857
862
|
}
|
|
858
863
|
|
|
859
|
-
const result = spawnSync(
|
|
864
|
+
const result = spawnSync(command, {
|
|
860
865
|
stdio: 'inherit',
|
|
866
|
+
shell: true,
|
|
861
867
|
...getSpawnSyncBaseOptions(),
|
|
862
868
|
});
|
|
863
869
|
|
|
@@ -39,22 +39,6 @@ function createFetchMock() {
|
|
|
39
39
|
return mock;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
describe('fork package update command', () => {
|
|
43
|
-
it('targets the published fork without a runtime environment override', () => {
|
|
44
|
-
expect(getUpdateCommand('npm')).toBe('npm install -g @vantran-se/openchamber-web@latest');
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('executes the package manager without a shell', () => {
|
|
48
|
-
spawnSync.mockClear();
|
|
49
|
-
executeUpdate('npm', { silent: true });
|
|
50
|
-
expect(spawnSync).toHaveBeenCalledWith(
|
|
51
|
-
'npm',
|
|
52
|
-
['install', '-g', '@vantran-se/openchamber-web@latest'],
|
|
53
|
-
expect.not.objectContaining({ shell: true }),
|
|
54
|
-
);
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
|
|
58
42
|
describe('checkForUpdates', () => {
|
|
59
43
|
let fetchMock;
|
|
60
44
|
let originalFetch;
|
|
@@ -277,7 +261,7 @@ describe('checkForUpdates', () => {
|
|
|
277
261
|
downloadUrl: 'https://github.com/openchamber/openchamber/releases/download/v1.10.0/OpenChamber-1.10.0-42-android.aab',
|
|
278
262
|
}),
|
|
279
263
|
})
|
|
280
|
-
.when('api.github.com/repos/
|
|
264
|
+
.when('api.github.com/repos/openchamber/openchamber/releases/tags/v1.10.0', {
|
|
281
265
|
ok: true,
|
|
282
266
|
json: async () => ({
|
|
283
267
|
assets: [
|
|
@@ -463,13 +447,16 @@ describe('getUpdateCommand', () => {
|
|
|
463
447
|
describe('executeUpdate', () => {
|
|
464
448
|
function stubSpawnSync({ installStatus = 0, listingStdout = '', listingStatus = 0 } = {}) {
|
|
465
449
|
spawnSync.mockImplementation((command, args) => {
|
|
450
|
+
if (!Array.isArray(args)) {
|
|
451
|
+
return { status: installStatus, stdout: '', stderr: '' };
|
|
452
|
+
}
|
|
466
453
|
if (args.includes('--version')) {
|
|
467
454
|
return { status: 0, stdout: '10.0.0', stderr: '' };
|
|
468
455
|
}
|
|
469
456
|
if (args[0] === 'list' || args[0] === 'pm' || args[0] === 'global') {
|
|
470
457
|
return { status: listingStatus, stdout: listingStdout, stderr: '' };
|
|
471
458
|
}
|
|
472
|
-
return { status:
|
|
459
|
+
return { status: 0, stdout: '', stderr: '' };
|
|
473
460
|
});
|
|
474
461
|
}
|
|
475
462
|
|
|
@@ -18,7 +18,7 @@ Policies written before the modes stored booleans. The first read converts them
|
|
|
18
18
|
|
|
19
19
|
## Default mode
|
|
20
20
|
|
|
21
|
-
`permissionDefaultMode` (Settings → Sessions) is written onto each top-level session when
|
|
21
|
+
`permissionDefaultMode` (Settings → Sessions) is written onto each new top-level session when `session.created` arrives, and only when no policy exists for it yet: a mode the creating flow already set wins, and changing the default never reaches back into older sessions. Subagents inherit instead. `ask` writes nothing. Mode resolution waits for pending writes, so a session's first permission request sees its default.
|
|
22
22
|
|
|
23
23
|
## Runtime
|
|
24
24
|
|
|
@@ -18,13 +18,7 @@ const storedPolicySchema = z.object({
|
|
|
18
18
|
|
|
19
19
|
const readStoredPolicy = (value) => storedPolicySchema.parse(value ?? {});
|
|
20
20
|
|
|
21
|
-
const createdSessionSchema = z.object({
|
|
22
|
-
id: z.string().min(1),
|
|
23
|
-
parentID: z.string().nullish(),
|
|
24
|
-
metadata: z.object({
|
|
25
|
-
openchamber: z.object({ adopted: z.literal(true).optional() }).optional(),
|
|
26
|
-
}).optional(),
|
|
27
|
-
});
|
|
21
|
+
const createdSessionSchema = z.object({ id: z.string().min(1), parentID: z.string().nullish() });
|
|
28
22
|
|
|
29
23
|
const hasLegacyEntries = (stored) => Object.values(stored.sessions).some((entry) => entry === true || entry === false);
|
|
30
24
|
|
|
@@ -148,29 +142,17 @@ export function createPermissionAutoAcceptRuntime({
|
|
|
148
142
|
* sessions; a policy the creating flow already set wins. Subagents inherit
|
|
149
143
|
* from their parent instead.
|
|
150
144
|
*/
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const mode = isPermissionMode(configuredMode) ? configuredMode : 'ask';
|
|
163
|
-
await persistUpdate((current) => (Object.hasOwn(current.sessions, sessionId) ? current : {
|
|
164
|
-
...current,
|
|
165
|
-
sessions: { ...current.sessions, [sessionId]: mode },
|
|
166
|
-
revision: current.revision + 1,
|
|
167
|
-
}));
|
|
168
|
-
})();
|
|
169
|
-
defaultModePromises.set(sessionId, pending);
|
|
170
|
-
void pending.finally(() => {
|
|
171
|
-
if (defaultModePromises.get(sessionId) === pending) defaultModePromises.delete(sessionId);
|
|
172
|
-
});
|
|
173
|
-
return pending;
|
|
145
|
+
const applyDefaultMode = async (sessionId) => {
|
|
146
|
+
await load();
|
|
147
|
+
if (Object.hasOwn(policy.sessions, sessionId)) return;
|
|
148
|
+
const settings = await readSettingsFromDiskMigrated();
|
|
149
|
+
const mode = settings?.[DEFAULT_MODE_SETTINGS_KEY];
|
|
150
|
+
if (!isPermissionMode(mode) || mode === 'ask') return;
|
|
151
|
+
await persistUpdate((current) => (Object.hasOwn(current.sessions, sessionId) ? current : {
|
|
152
|
+
...current,
|
|
153
|
+
sessions: { ...current.sessions, [sessionId]: mode },
|
|
154
|
+
revision: current.revision + 1,
|
|
155
|
+
}));
|
|
174
156
|
};
|
|
175
157
|
|
|
176
158
|
const rememberSession = (info, directoryHint) => {
|
|
@@ -178,9 +160,7 @@ export function createPermissionAutoAcceptRuntime({
|
|
|
178
160
|
// v2 session updates are partial (a rename carries only the title), so a
|
|
179
161
|
// field the update does not name keeps what an earlier record said.
|
|
180
162
|
const previous = sessions.get(info.id);
|
|
181
|
-
const parentID = typeof info.parentID === 'string' && info.parentID
|
|
182
|
-
? info.parentID
|
|
183
|
-
: (previous ? previous.parentID : undefined);
|
|
163
|
+
const parentID = typeof info.parentID === 'string' && info.parentID ? info.parentID : previous?.parentID ?? null;
|
|
184
164
|
// v2 keeps the directory on `location`; translated events already flatten it.
|
|
185
165
|
const directory = typeof info.directory === 'string' && info.directory
|
|
186
166
|
? info.directory
|
|
@@ -219,7 +199,7 @@ export function createPermissionAutoAcceptRuntime({
|
|
|
219
199
|
|
|
220
200
|
const getSession = async (sessionId, directory) => {
|
|
221
201
|
const cached = sessions.get(sessionId);
|
|
222
|
-
if (cached
|
|
202
|
+
if (cached) return cached;
|
|
223
203
|
const info = await request(`/api/session/${encodeURIComponent(sessionId)}`, { directory });
|
|
224
204
|
rememberSession(info?.data ?? info, directory);
|
|
225
205
|
return sessions.get(sessionId) ?? null;
|
|
@@ -228,16 +208,13 @@ export function createPermissionAutoAcceptRuntime({
|
|
|
228
208
|
/** The nearest explicit mode up the session's lineage; unknown lineage fails closed to `ask`. */
|
|
229
209
|
const resolveSessionMode = async (sessionId, directory) => {
|
|
230
210
|
await load();
|
|
231
|
-
// A default being
|
|
232
|
-
//
|
|
233
|
-
await defaultModePromises.get(sessionId)?.catch(() => undefined);
|
|
211
|
+
// A default being written for a just-created session must be visible to
|
|
212
|
+
// its first permission request.
|
|
234
213
|
await writePromise.catch(() => undefined);
|
|
235
214
|
const seen = new Set();
|
|
236
215
|
let current = sessionId;
|
|
237
216
|
let currentDirectory = directory;
|
|
238
217
|
while (current && !seen.has(current)) {
|
|
239
|
-
await defaultModePromises.get(current)?.catch(() => undefined);
|
|
240
|
-
await writePromise.catch(() => undefined);
|
|
241
218
|
if (Object.hasOwn(policy.sessions, current)) return policy.sessions[current];
|
|
242
219
|
seen.add(current);
|
|
243
220
|
let info;
|
|
@@ -375,14 +352,9 @@ export function createPermissionAutoAcceptRuntime({
|
|
|
375
352
|
if (payload.type === 'session.created' || payload.type === 'session.updated') {
|
|
376
353
|
const info = payload.properties?.info;
|
|
377
354
|
rememberSession(info, directory ?? payload.properties?.directory);
|
|
378
|
-
const
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
void applyDefaultMode(adopted.data.id, async () => {
|
|
382
|
-
const parentID = remembered?.parentID;
|
|
383
|
-
if (parentID !== undefined) return parentID;
|
|
384
|
-
return (await getSession(adopted.data.id, remembered?.directory))?.parentID ?? null;
|
|
385
|
-
}).catch((error) => {
|
|
355
|
+
const created = payload.type === 'session.created' ? createdSessionSchema.safeParse(info) : null;
|
|
356
|
+
if (created?.success && !created.data.parentID) {
|
|
357
|
+
void applyDefaultMode(created.data.id).catch((error) => {
|
|
386
358
|
console.warn('[permission-auto-accept] failed to apply the default mode:', error?.message ?? error);
|
|
387
359
|
});
|
|
388
360
|
}
|
|
@@ -77,11 +77,11 @@ describe('permission auto-accept runtime', () => {
|
|
|
77
77
|
expect(resolveLegacyEnabledMode).toHaveBeenCalledTimes(1);
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
-
it('writes the default mode onto a new
|
|
80
|
+
it('writes the default mode onto a new top-level session only', async () => {
|
|
81
81
|
const { runtime, emit, getSettings } = createRuntime();
|
|
82
82
|
await runtime.load();
|
|
83
83
|
getSettings().permissionDefaultMode = 'safety';
|
|
84
|
-
emit({ type: 'session.created', properties: { info: { id: 'root'
|
|
84
|
+
emit({ type: 'session.created', properties: { info: { id: 'root' } } });
|
|
85
85
|
emit({ type: 'session.created', properties: { info: { id: 'child', parentID: 'root' } } });
|
|
86
86
|
await flush();
|
|
87
87
|
await expect(runtime.resolveSessionMode('root', '/project')).resolves.toBe('safety');
|
|
@@ -100,7 +100,7 @@ collision gets a numeric suffix. Sharing is refused only when the checkout canno
|
|
|
100
100
|
| PATCH | `/api/project-context/:projectId/notes/:noteId` | patches `body`; legacy `pinned` input is ignored by session knowledge; `404` when unknown |
|
|
101
101
|
| DELETE | `/api/project-context/:projectId/notes/:noteId` | `404` when unknown |
|
|
102
102
|
| PATCH | `/api/project-context/:projectId/plans/:planId` | legacy project pin state only; session attachment uses session knowledge; `404` when unknown |
|
|
103
|
-
| GET | `/api/project-context/:projectId/plans/:planId` | `404` when the link or its markdown is gone |
|
|
103
|
+
| GET | `/api/project-context/:projectId/plans/:planId` | `404` when the link or its markdown is gone; the answer carries `path`, the markdown's absolute path, so comments on the plan name the file to the agent (reads and writes stay id-addressed) |
|
|
104
104
|
| POST | `/api/project-context/:projectId/plans` | `201`; takes `{title, body}`, never a path |
|
|
105
105
|
| PUT | `/api/project-context/:projectId/plans/:planId` | takes the whole `{raw}` document; `404` when the link or its markdown is gone |
|
|
106
106
|
| DELETE | `/api/project-context/:projectId/plans/:planId` | `404` when unknown |
|
|
@@ -119,6 +119,12 @@ on a bare express app so that failure mode fails the suite instead of the user.
|
|
|
119
119
|
separators and traversal. Validation failures are `400`; malformed stored data
|
|
120
120
|
and I/O failures are `500`.
|
|
121
121
|
|
|
122
|
+
## Change notifications
|
|
123
|
+
|
|
124
|
+
After `writeContext` atomically commits `context.json`, the runtime calls its optional `onChanged(projectId)` dependency. The server broadcasts `openchamber:project-context-changed` with `{ projectId }` on the existing OpenChamber control stream. It carries the path-derived owner id, not the bounded storage stem or any note content. Failed writes and missing-entry no-ops announce nothing. Updating or deleting a shared plan through the API announces the same way after the file write, since shared plans have no manifest entry.
|
|
125
|
+
|
|
126
|
+
Clients re-read authoritative context for the visible owner. This is a commit notification, not a filesystem watcher; direct changes to shared plan files outside the API retain their existing read-on-open behavior.
|
|
127
|
+
|
|
122
128
|
## Invariants
|
|
123
129
|
|
|
124
130
|
- **Missing is not malformed.** A missing `context.json` is authoritative empty
|
|
@@ -13,12 +13,11 @@
|
|
|
13
13
|
* undefined and rejects every write as a malformed body.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
import { isRecord as isObjectRecord } from '../shared/guards.js';
|
|
16
17
|
import express from 'express';
|
|
17
18
|
|
|
18
19
|
const parseJsonBody = express.json({ limit: '1mb' });
|
|
19
20
|
|
|
20
|
-
const isObjectRecord = (value) => Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
21
|
-
|
|
22
21
|
const isValidationError = (error) => {
|
|
23
22
|
const message = error instanceof Error ? error.message : '';
|
|
24
23
|
return message.includes('is required') || message.includes('unsupported characters');
|
|
@@ -12,12 +12,13 @@
|
|
|
12
12
|
* directory never invalidates a reference.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
import { asNonEmptyString, isRecord as isObjectRecord } from '../shared/guards.js';
|
|
15
16
|
import { projectConfigFileStemOf } from '../projects/project-id.js';
|
|
16
17
|
|
|
17
18
|
const PROJECT_CONTEXT_VERSION = 2;
|
|
18
19
|
const PROJECT_NOTE_BODY_MAX_LENGTH = 3000;
|
|
19
20
|
const PROJECT_NOTE_MAX_ITEMS = 200;
|
|
20
|
-
const PROJECT_TODO_TEXT_MAX_LENGTH =
|
|
21
|
+
const PROJECT_TODO_TEXT_MAX_LENGTH = 1000;
|
|
21
22
|
const PROJECT_PLAN_TITLE_MAX_LENGTH = 160;
|
|
22
23
|
const PROJECT_PLAN_BODY_MAX_LENGTH = 200_000;
|
|
23
24
|
const PROJECT_TODO_MAX_ITEMS = 500;
|
|
@@ -26,19 +27,11 @@ const PROJECT_PLAN_MAX_ITEMS = 500;
|
|
|
26
27
|
const PROJECT_ID_PATTERN = /^[a-zA-Z0-9._:-]+$/;
|
|
27
28
|
const PLAN_FILE_PATTERN = /^[a-zA-Z0-9._-]+\.md$/;
|
|
28
29
|
|
|
29
|
-
const asNonEmptyString = (value) => {
|
|
30
|
-
if (typeof value !== 'string') return null;
|
|
31
|
-
const trimmed = value.trim();
|
|
32
|
-
return trimmed.length > 0 ? trimmed : null;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
30
|
const clampLength = (value, maxLength) => {
|
|
36
31
|
if (typeof value !== 'string') return '';
|
|
37
32
|
return value.length > maxLength ? value.slice(0, maxLength) : value;
|
|
38
33
|
};
|
|
39
34
|
|
|
40
|
-
const isObjectRecord = (value) => Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
41
|
-
|
|
42
35
|
const NOTE_SOURCES = new Set(['manual', 'selection', 'agent']);
|
|
43
36
|
|
|
44
37
|
const sanitizeNoteOrigin = (value) => {
|
|
@@ -197,7 +190,7 @@ const createEmptyContext = () => ({
|
|
|
197
190
|
const SHARED_PLAN_ID_PREFIX = 'shared:';
|
|
198
191
|
|
|
199
192
|
export const createProjectContextRuntime = (deps) => {
|
|
200
|
-
const { fsPromises, path, projectsDirPath, createId, resolveSharedPlansDir } = deps;
|
|
193
|
+
const { fsPromises, path, projectsDirPath, createId, resolveSharedPlansDir, onChanged } = deps;
|
|
201
194
|
// The team's shared plans folder for a project (absolute path) or null; the
|
|
202
195
|
// project config runtime owns that answer (`plansDir` in the shared file).
|
|
203
196
|
const sharedPlansDirFor = typeof resolveSharedPlansDir === 'function'
|
|
@@ -473,6 +466,7 @@ export const createProjectContextRuntime = (deps) => {
|
|
|
473
466
|
todos: context.todos,
|
|
474
467
|
plans: context.plans,
|
|
475
468
|
});
|
|
469
|
+
onChanged?.(sanitizeProjectId(projectId));
|
|
476
470
|
};
|
|
477
471
|
|
|
478
472
|
const saveTodos = async (projectId, todos) => {
|
|
@@ -586,15 +580,16 @@ export const createProjectContextRuntime = (deps) => {
|
|
|
586
580
|
if (sharedFile) {
|
|
587
581
|
const dir = await sharedPlansDirFor(projectId);
|
|
588
582
|
if (!dir) return null;
|
|
583
|
+
const planPath = path.join(dir, sharedFile);
|
|
589
584
|
let raw;
|
|
590
585
|
try {
|
|
591
|
-
raw = await fsPromises.readFile(
|
|
586
|
+
raw = await fsPromises.readFile(planPath, 'utf8');
|
|
592
587
|
} catch (error) {
|
|
593
588
|
if (error && error.code === 'ENOENT') return null;
|
|
594
589
|
throw error;
|
|
595
590
|
}
|
|
596
591
|
const parsed = parsePlanMarkdown(raw);
|
|
597
|
-
return { id, file: sharedFile, createdAt: 0, title: parsed.title, body: parsed.body, raw, source: 'shared' };
|
|
592
|
+
return { id, file: sharedFile, path: planPath, createdAt: 0, title: parsed.title, body: parsed.body, raw, source: 'shared' };
|
|
598
593
|
}
|
|
599
594
|
const context = await readStoredContext(projectId);
|
|
600
595
|
const link = context.plans.find((entry) => entry.id === id);
|
|
@@ -604,16 +599,17 @@ export const createProjectContextRuntime = (deps) => {
|
|
|
604
599
|
|
|
605
600
|
const folder = await folderOfLink(projectId, link);
|
|
606
601
|
if (!folder) return null;
|
|
602
|
+
const planPath = path.join(folder, link.file);
|
|
607
603
|
let raw;
|
|
608
604
|
try {
|
|
609
|
-
raw = await fsPromises.readFile(
|
|
605
|
+
raw = await fsPromises.readFile(planPath, 'utf8');
|
|
610
606
|
} catch (error) {
|
|
611
607
|
if (error && error.code === 'ENOENT') return null;
|
|
612
608
|
throw error;
|
|
613
609
|
}
|
|
614
610
|
|
|
615
611
|
const parsed = parsePlanMarkdown(raw);
|
|
616
|
-
return { id: link.id, file: link.file, createdAt: link.createdAt, title: parsed.title, body: parsed.body, raw, source: link.shared ? 'shared' : 'personal' };
|
|
612
|
+
return { id: link.id, file: link.file, path: planPath, createdAt: link.createdAt, title: parsed.title, body: parsed.body, raw, source: link.shared ? 'shared' : 'personal' };
|
|
617
613
|
};
|
|
618
614
|
|
|
619
615
|
/**
|
|
@@ -652,6 +648,7 @@ export const createProjectContextRuntime = (deps) => {
|
|
|
652
648
|
throw error;
|
|
653
649
|
}
|
|
654
650
|
await fsPromises.writeFile(filePath, raw, 'utf8');
|
|
651
|
+
onChanged?.(sanitizeProjectId(projectId));
|
|
655
652
|
const parsed = parsePlanMarkdown(raw);
|
|
656
653
|
const context = await readContext(projectId);
|
|
657
654
|
const plan = context.plans.find((entry) => entry.id === id) ?? { id, file: sharedFile, title: parsed.title, createdAt: Date.now(), pinned: false, source: 'shared' };
|
|
@@ -771,6 +768,7 @@ export const createProjectContextRuntime = (deps) => {
|
|
|
771
768
|
const exists = filePath ? await fsPromises.access(filePath).then(() => true, () => false) : false;
|
|
772
769
|
if (!exists) return { deleted: false, context: await readContext(projectId) };
|
|
773
770
|
await fsPromises.rm(filePath, { force: true });
|
|
771
|
+
onChanged?.(sanitizeProjectId(projectId));
|
|
774
772
|
return { deleted: true, context: await readContext(projectId) };
|
|
775
773
|
});
|
|
776
774
|
}
|