@vantran-se/openchamber-web 1.23.0 → 1.24.3
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/bin/cli.test.js +29 -6
- package/bin/lib/cli-http.js +5 -1
- package/bin/lib/commands-status.js +5 -0
- package/dist/assets/{CodeMirrorCommentWidgets-DFg2wnpo.js → CodeMirrorCommentWidgets-BjJHrzmU.js} +1 -1
- package/dist/assets/DiffView-COVQhWGd.js +4 -0
- package/dist/assets/FilesView-B1V5JxFX.js +13 -0
- package/dist/assets/GitView-CDOJy5Ay.js +6 -0
- package/dist/assets/GuestAttachDialog-D3S0Rjic.js +1 -0
- package/dist/assets/{JsonTreeView-BKdsjg5B.js → JsonTreeView-D0C9nU7K.js} +1 -1
- package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/assets/LinearIssuesView-D7FKg1XJ.js +1 -0
- package/dist/assets/{MarkdownImageGallery-_5gEVr0X.js → MarkdownImageGallery-BrFjAVEf.js} +1 -1
- package/dist/assets/MarkdownRendererImpl-DWepTafh.js +12 -0
- package/dist/assets/{OnboardingScreen-DdP4_nuc.js → OnboardingScreen-Dyxxiqdt.js} +1 -1
- package/dist/assets/{PatchDiff-am3SAss2.js → PatchDiff-DnEhjXcC.js} +1 -1
- package/dist/assets/PierreDiffViewer-DyxZVVVR.js +210 -0
- package/dist/assets/{PlanView-BoJxdRZe.js → PlanView-LW2z_EAP.js} +4 -4
- package/dist/assets/PluginPane-B4tKno2h.js +3 -0
- package/dist/assets/SettingsView-DEaMulN9.js +29 -0
- package/dist/assets/{SettingsWindow-DVTdDSol.js → SettingsWindow-FLDJXOgQ.js} +1 -1
- package/dist/assets/SubmoduleDiffSummary-CvVwD-Vi.js +1 -0
- package/dist/assets/ToolOutputDialog-BiP0x2Rw.js +15 -0
- package/dist/assets/{ToolPartDiffPreview-C7WLk5i9.js → ToolPartDiffPreview-CkR4msKP.js} +2 -2
- package/dist/assets/{VirtualizedFileDiff-DcdgkH-x.js → VirtualizedFileDiff-0pyr_mHY.js} +4 -4
- package/dist/assets/WalkthroughView-yNj4nOP5.js +4 -0
- package/dist/assets/appThemeRegistry-BuUKhbYQ.js +1 -0
- package/dist/assets/commitAndPush-DvJKa3Ks.js +1 -0
- package/dist/assets/{computeVirtualFileMetrics-C8byMnRh.js → computeVirtualFileMetrics-DxTBMuu-.js} +2 -2
- package/dist/assets/de-DgxgFyGG.js +10 -0
- package/dist/assets/es-BFsZj5F_.js +16 -0
- package/dist/assets/fr-jdZ5KKQi.js +14 -0
- package/dist/assets/{index-BB7ryz8y.js → index-CKHjPKbq.js} +1 -1
- package/dist/assets/index-Cbci4i1W.js +40 -0
- package/dist/assets/index-DqaMWNrC.css +1 -0
- package/dist/assets/{isDiffPlainText-45ujMPGb.js → isDiffPlainText-D7FkBMP6.js} +1 -1
- package/dist/assets/{isFilePlainText-Cc4KFSYi.js → isFilePlainText-CL2vsrIZ.js} +1 -1
- package/dist/assets/ja-C5kKrhO4.js +16 -0
- package/dist/assets/ko-DXDTT_0O.js +16 -0
- package/dist/assets/main-CZALtVek.js +896 -0
- package/dist/assets/main-CgXMBNvG.js +2 -0
- package/dist/assets/{markdown-shiki.worker-D5fBU-lw.js → markdown-shiki.worker-CahLvHIq.js} +5 -5
- package/dist/assets/markdownCore-Dh7WEsdM.js +5 -0
- package/dist/assets/miniChat-CwTmDixi.js +2 -0
- package/dist/assets/mobile-CLf2vCtO.js +2 -0
- package/dist/assets/patchFileDiff-Bsba8KFp.js +13 -0
- package/dist/assets/pl-D-7x4Y8Q.js +16 -0
- package/dist/assets/pt-BR-CSBZrHa8.js +16 -0
- package/dist/assets/renderElectronMiniChatApp-C5gAFBk0.js +2 -0
- package/dist/assets/renderMobileApp-V4hYd32H.js +25 -0
- package/dist/assets/runtimeEndpointReset-DhlHCn-2.js +25 -0
- package/dist/assets/scheduledTasksApi-DzjbLd8P.js +1 -0
- package/dist/assets/shikiHighlight-8_3csmr6.js +1 -0
- package/dist/assets/surface-B6n7wJtt.js +1 -0
- package/dist/assets/textMateThemeFromAppTheme-CHo2fCgW.js +1 -0
- package/dist/assets/tr-Dr-oJ22L.js +7 -0
- package/dist/assets/uk-9Nx83UcI.js +16 -0
- package/dist/assets/useAppFontEffects-khZXpFL_.js +7333 -0
- package/dist/assets/useInlineCommentController-DkQ_gqpn.js +2 -0
- package/dist/assets/useRootScrollLock-DdvHf9Pt.js +2 -0
- package/dist/assets/{vendor-base-ui-DdV8vhHn.js → vendor-base-ui-CsXYnR5c.js} +1 -1
- package/dist/assets/{vendor-codemirror-language-data-DzlSIu_W.js → vendor-codemirror-language-data-CdzUmUyZ.js} +2 -2
- package/dist/assets/vendor-framer-motion-Bq3iTZ6r.js +1 -0
- package/dist/assets/vendor-katex-Cp579UoJ.css +1 -0
- package/dist/assets/{vendor-motion-dom-D_o3BTI3.js → vendor-motion-dom-DcCgeG69.js} +1 -1
- package/dist/assets/{vendor-remixicon-react-Dil0zKKT.js → vendor-remixicon-react-BDmGmFCA.js} +1 -1
- package/dist/assets/vendor-strip-json-comments-5dJlOkP1.js +4 -0
- package/dist/assets/{vendor-tanstack-react-virtual-g05Cx3z9.js → vendor-tanstack-react-virtual-ClYN8CzB.js} +1 -1
- package/dist/assets/{vendor-tanstack-virtual-core-Dubg10g5.js → vendor-tanstack-virtual-core-BewVQryB.js} +1 -1
- package/dist/assets/vendor-zod-CgAdXWZO.js +39 -0
- package/dist/assets/{web-components-CdGfR2XJ.js → web-components-B8IcVQAA.js} +2 -2
- package/dist/assets/zh-CN-Bxy_Hj3r.js +16 -0
- package/dist/assets/zh-TW-0BB4gpHM.js +16 -0
- package/dist/index.html +6 -4
- package/dist/mini-chat.html +6 -4
- package/dist/mobile.html +6 -4
- package/package.json +12 -9
- package/server/built-in-extensions/registry.json +4 -0
- package/server/index.d.ts +2 -0
- package/server/index.js +47 -23
- package/server/lib/agent-memory/runtime.js +7 -2
- package/server/lib/agent-memory/runtime.test.js +22 -0
- package/server/lib/agent-tool/DOCUMENTATION.md +17 -4
- package/server/lib/agent-tool/runtime.js +62 -15
- package/server/lib/agent-tool/runtime.test.js +88 -2
- package/server/lib/changelog/update-notes.js +2 -1
- package/server/lib/changelog/update-notes.test.js +19 -0
- package/server/lib/event-stream/DOCUMENTATION.md +26 -0
- package/server/lib/event-stream/delta-coalescer.js +0 -0
- package/server/lib/event-stream/delta-coalescer.test.js +292 -0
- package/server/lib/event-stream/global-hub.js +68 -13
- package/server/lib/event-stream/global-hub.test.js +182 -0
- package/server/lib/event-stream/global-ws-bridge.js +16 -18
- package/server/lib/event-stream/index.js +4 -0
- package/server/lib/event-stream/protocol.js +21 -7
- package/server/lib/event-stream/runtime.js +9 -6
- package/server/lib/event-stream/runtime.test.js +60 -0
- package/server/lib/fs/DOCUMENTATION.md +10 -1
- package/server/lib/fs/git-process.test.js +77 -0
- package/server/lib/fs/routes.js +58 -5
- package/server/lib/fs/routes.test.js +116 -0
- package/server/lib/fs/search.js +2 -1
- package/server/lib/git/DOCUMENTATION.md +30 -6
- package/server/lib/git/routes.js +37 -11
- package/server/lib/git/routes.test.js +78 -0
- package/server/lib/git/serial-refresh.js +93 -0
- package/server/lib/git/serial-refresh.test.js +112 -0
- package/server/lib/git/service.js +588 -74
- package/server/lib/git/service.test.js +492 -0
- package/server/lib/github/DOCUMENTATION.md +5 -0
- package/server/lib/github/pr-status.js +5 -5
- package/server/lib/github/pr-status.test.js +1 -1
- package/server/lib/github/routes.js +6 -6
- package/server/lib/guests/DOCUMENTATION.md +68 -0
- package/server/lib/guests/auth-store.js +184 -0
- package/server/lib/guests/auth-store.test.js +112 -0
- package/server/lib/guests/background.test.js +83 -0
- package/server/lib/guests/builtins.js +34 -0
- package/server/lib/guests/builtins.test.js +174 -0
- package/server/lib/guests/catalog.js +477 -0
- package/server/lib/guests/catalog.test.js +390 -0
- package/server/lib/guests/clone.js +297 -0
- package/server/lib/guests/extract-zip.js +136 -0
- package/server/lib/guests/extract-zip.test.js +109 -0
- package/server/lib/guests/files.js +308 -0
- package/server/lib/guests/files.test.js +262 -0
- package/server/lib/guests/grant-scope.js +81 -0
- package/server/lib/guests/grant-scope.test.js +75 -0
- package/server/lib/guests/host-session.js +73 -0
- package/server/lib/guests/host-session.test.js +30 -0
- package/server/lib/guests/html-styles.js +6 -0
- package/server/lib/guests/html-styles.test.js +45 -0
- package/server/lib/guests/html-tokens.js +73 -0
- package/server/lib/guests/html-tokens.test.js +53 -0
- package/server/lib/guests/install.js +406 -0
- package/server/lib/guests/install.test.js +338 -0
- package/server/lib/guests/oauth.js +501 -0
- package/server/lib/guests/oauth.test.js +481 -0
- package/server/lib/guests/persist.js +336 -0
- package/server/lib/guests/persist.test.js +288 -0
- package/server/lib/guests/request.js +112 -0
- package/server/lib/guests/request.test.js +206 -0
- package/server/lib/guests/routes.js +838 -0
- package/server/lib/guests/service.js +538 -0
- package/server/lib/guests/service.test.js +277 -0
- package/server/lib/guests/sockets.js +129 -0
- package/server/lib/guests/sockets.test.js +110 -0
- package/server/lib/guests/ssh-install.test.js +188 -0
- package/server/lib/guests/storage.js +68 -0
- package/server/lib/guests/storage.test.js +59 -0
- package/server/lib/guests/updates.js +294 -0
- package/server/lib/guests/updates.test.js +334 -0
- package/server/lib/guests/upload.js +50 -0
- package/server/lib/guests/upload.test.js +131 -0
- package/server/lib/linear/DOCUMENTATION.md +1 -1
- package/server/lib/message-queue/DOCUMENTATION.md +17 -1
- package/server/lib/message-queue/runtime.js +34 -1
- package/server/lib/message-queue/runtime.test.js +83 -6
- package/server/lib/notifications/DOCUMENTATION.md +1 -1
- package/server/lib/notifications/emitter-runtime.js +4 -3
- package/server/lib/notifications/routes.js +5 -0
- package/server/lib/openchamber-control/DOCUMENTATION.md +6 -0
- package/server/lib/openchamber-sessions/routes.js +62 -29
- package/server/lib/openchamber-sessions/routes.test.js +96 -0
- package/server/lib/opencode/DOCUMENTATION.md +73 -5
- package/server/lib/opencode/bootstrap-runtime.js +1 -0
- package/server/lib/opencode/core-routes.js +39 -1
- package/server/lib/opencode/env-runtime.js +5 -1
- package/server/lib/opencode/env-runtime.test.js +72 -0
- package/server/lib/opencode/feature-routes-runtime.js +30 -1
- package/server/lib/opencode/lifecycle-process.test.js +84 -0
- package/server/lib/opencode/lifecycle.js +58 -70
- package/server/lib/opencode/lifecycle.test.js +29 -0
- package/server/lib/opencode/managed-plugin-config.js +1 -1
- package/server/lib/opencode/openchamber-routes.js +72 -14
- package/server/lib/opencode/openchamber-routes.test.js +71 -3
- package/server/lib/opencode/proxy.js +33 -0
- package/server/lib/opencode/session-runtime.js +68 -0
- package/server/lib/opencode/session-runtime.test.js +43 -0
- package/server/lib/opencode/settings-helpers.js +11 -0
- package/server/lib/opencode/settings-helpers.test.js +24 -2
- package/server/lib/opencode/settings-normalization-runtime.js +2 -0
- package/server/lib/opencode/settings-normalization-runtime.test.js +13 -0
- package/server/lib/opencode/settings-registry.json +18 -0
- package/server/lib/opencode/settings-runtime.js +36 -9
- package/server/lib/opencode/settings-runtime.test.js +158 -2
- package/server/lib/opencode/shutdown-runtime.js +3 -0
- package/server/lib/opencode/shutdown-runtime.test.js +24 -0
- package/server/lib/opencode/theme-archive.js +53 -0
- package/server/lib/opencode/theme-catalog.js +171 -0
- package/server/lib/opencode/theme-catalog.test.js +125 -0
- package/server/lib/opencode/theme-import.test.js +144 -0
- package/server/lib/opencode/theme-runtime.js +100 -28
- package/server/lib/opencode/theme-runtime.test.js +15 -0
- package/server/lib/permission-auto-accept/DOCUMENTATION.md +8 -0
- package/server/lib/permission-auto-accept/runtime.js +13 -0
- package/server/lib/permission-auto-accept/runtime.test.js +37 -1
- package/server/lib/project-context/DOCUMENTATION.md +13 -3
- package/server/lib/project-context/runtime.js +8 -2
- package/server/lib/project-context/runtime.test.js +54 -0
- package/server/lib/projects/DOCUMENTATION.md +2 -1
- package/server/lib/projects/project-config.js +40 -11
- package/server/lib/projects/project-config.test.js +97 -0
- package/server/lib/projects/project-id.js +26 -0
- package/server/lib/quota/DOCUMENTATION.md +0 -1
- package/server/lib/quota/providers/index.js +0 -7
- package/server/lib/realtime-proxy-terminal.test.js +150 -0
- package/server/lib/realtime-proxy.js +2 -0
- package/server/lib/relay/DOCUMENTATION.md +20 -0
- package/server/lib/relay/host-client.js +7 -0
- package/server/lib/relay/host-client.test.js +14 -0
- package/server/lib/routing/DOCUMENTATION.md +83 -0
- package/server/lib/routing/defaults.js +98 -0
- package/server/lib/routing/feature-flag.js +11 -0
- package/server/lib/routing/history.js +34 -0
- package/server/lib/routing/jev.js +98 -0
- package/server/lib/routing/routes.http.test.js +100 -0
- package/server/lib/routing/routes.js +81 -0
- package/server/lib/routing/runtime.js +217 -0
- package/server/lib/routing/runtime.test.js +188 -0
- package/server/lib/routing/store.js +249 -0
- package/server/lib/routing/store.test.js +96 -0
- package/server/lib/scheduled-tasks/DOCUMENTATION.md +9 -1
- package/server/lib/scheduled-tasks/issue-2710-double-execution.test.js +28 -0
- package/server/lib/scheduled-tasks/runtime.js +14 -4
- package/server/lib/scheduled-tasks/runtime.test.js +67 -0
- package/server/lib/security/request-security.js +9 -5
- package/server/lib/security/request-security.test.js +37 -0
- package/server/lib/session-assist/DOCUMENTATION.md +131 -66
- package/server/lib/session-assist/context.js +127 -0
- package/server/lib/session-assist/context.test.js +132 -0
- package/server/lib/session-assist/prompt.js +54 -0
- package/server/lib/session-assist/runtime.js +111 -257
- package/server/lib/session-assist/runtime.test.js +203 -0
- package/server/lib/small-model/DOCUMENTATION.md +14 -2
- package/server/lib/small-model/call.js +37 -6
- package/server/lib/small-model/call.test.js +182 -0
- package/server/lib/terminal/DOCUMENTATION.md +23 -2
- package/server/lib/terminal/runtime.js +27 -5
- package/server/lib/terminal/runtime.test.js +67 -0
- package/server/lib/terminal/shutdown.js +106 -0
- package/server/lib/terminal/shutdown.test.js +84 -0
- package/server/lib/tts/service.test.js +78 -0
- package/server/lib/ui-auth/DOCUMENTATION.md +8 -0
- package/server/lib/ui-auth/session-cookie.js +51 -0
- package/server/lib/ui-auth/session-cookie.test.js +46 -0
- package/server/lib/ui-auth/ui-auth.js +56 -17
- package/server/lib/ui-auth/ui-auth.test.js +96 -0
- package/server/lib/walkthrough/DOCUMENTATION.md +15 -5
- package/server/lib/walkthrough/pr-source.test.js +21 -0
- package/server/lib/walkthrough/pull-request.js +7 -4
- package/server/lib/walkthrough/pull-request.test.js +16 -0
- package/server/lib/walkthrough/routes.js +19 -0
- package/server/lib/walkthrough/routes.test.js +26 -0
- package/server/lib/walkthrough/sources.js +13 -2
- package/server/opencode-worktree-readiness.test.js +130 -0
- package/dist/assets/DiffView-3Dn4QHbu.js +0 -4
- package/dist/assets/DirtyBranchSwitchDialog-CwOerqVi.js +0 -1
- package/dist/assets/FilesView-BdTOpew9.js +0 -17
- package/dist/assets/GitView-Dqf9vHJh.js +0 -6
- package/dist/assets/LinearIssuesView-CmbeL7I6.js +0 -1
- package/dist/assets/MarkdownRendererImpl-wVZlx4dd.js +0 -12
- package/dist/assets/PierreDiffViewer-DCsydlKs.js +0 -161
- package/dist/assets/SettingsView-CcM8pF5O.js +0 -28
- package/dist/assets/ToolOutputDialog-BgfTbDZ4.js +0 -15
- package/dist/assets/WalkthroughView-BEVUDxuw.js +0 -4
- package/dist/assets/appThemeRegistry-BK0dHKa6.js +0 -1
- package/dist/assets/de-C8jV7J9y.js +0 -10
- package/dist/assets/es-BI6J1M5R.js +0 -16
- package/dist/assets/fr-D6oDFt7E.js +0 -14
- package/dist/assets/index-BqhiCmYS.js +0 -39
- package/dist/assets/index-FqSOBz0J.css +0 -1
- package/dist/assets/ja-C_4i5EQu.js +0 -16
- package/dist/assets/ko-BFbOTpbq.js +0 -16
- package/dist/assets/main-C9ETLKFc.js +0 -2
- package/dist/assets/main-CJmb2dF8.js +0 -930
- package/dist/assets/markdownCore-DVuZpnli.js +0 -5
- package/dist/assets/miniChat-B4Ytoq2n.js +0 -2
- package/dist/assets/mobile-BUT6-lo1.js +0 -2
- package/dist/assets/patchFileDiff-8j1apICU.js +0 -13
- package/dist/assets/pl-COFvT56l.js +0 -16
- package/dist/assets/pt-BR-DtVOO2Lj.js +0 -16
- package/dist/assets/registerCustomTheme-BBXUCRnG.js +0 -1
- package/dist/assets/renderElectronMiniChatApp-B6vUG-vF.js +0 -2
- package/dist/assets/renderMobileApp-BNgHraq6.js +0 -25
- package/dist/assets/runtimeEndpointReset-B8LYpWvr.js +0 -17
- package/dist/assets/scheduledTasksApi-GC4epIYA.js +0 -1
- package/dist/assets/shikiHighlight-BJISeZsQ.js +0 -1
- package/dist/assets/surface-BvRzS58U.js +0 -1
- package/dist/assets/tr-B8Q0Go2l.js +0 -7
- package/dist/assets/uk-Dk4a-Fn-.js +0 -16
- package/dist/assets/useAppFontEffects-DpFNefUA.js +0 -7284
- package/dist/assets/useGitComparison-Dfxaqd0s.js +0 -1
- package/dist/assets/useInlineCommentController-CscsE4t-.js +0 -2
- package/dist/assets/useRootScrollLock-BaCvuRlT.js +0 -1
- package/dist/assets/vendor-framer-motion-livSKyyV.js +0 -1
- package/dist/assets/vendor-zod-BPAANqvH.js +0 -39
- package/dist/assets/zh-CN-B_BXxG1Q.js +0 -16
- package/dist/assets/zh-TW-DLrl1SPZ.js +0 -16
- package/server/lib/mcp-reconnect/DOCUMENTATION.md +0 -55
- package/server/lib/mcp-reconnect/runtime.js +0 -122
- package/server/lib/mcp-reconnect/runtime.test.js +0 -140
- package/server/lib/quota/providers/crof.js +0 -96
- package/server/lib/quota/providers/crof.test.js +0 -98
- /package/dist/assets/{vendor-katex-k3Ppq1Ha.js → vendor-katex-CHD9BdDO.js} +0 -0
|
@@ -16,6 +16,43 @@ function quotePosixShell(value) {
|
|
|
16
16
|
return `'${String(value).replace(/'/g, "'\\''")}'`;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* `echo` in batch treats `& | < > ( ) ^` as syntax and `%var%` as expansion,
|
|
21
|
+
* so a line of the log preamble is escaped before it becomes an `echo`.
|
|
22
|
+
*/
|
|
23
|
+
const escapeBatchEchoLine = (line) => {
|
|
24
|
+
if (!line) return 'echo.';
|
|
25
|
+
return `echo ${line.replace(/%/g, '%%').replace(/[&|<>()^]/g, '^$&')}`;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The Windows update runs as a batch file, one command per line, so the
|
|
30
|
+
* `if ... else` block and the multi-line log preamble keep their structure.
|
|
31
|
+
*/
|
|
32
|
+
const buildWindowsUpdateScript = ({ logPreamble, updateCmd, restartCmd }) => [
|
|
33
|
+
'@echo off',
|
|
34
|
+
...logPreamble.split('\n').map(escapeBatchEchoLine),
|
|
35
|
+
// `timeout` refuses redirected stdin, which is what a detached child has;
|
|
36
|
+
// a ping to loopback waits about two seconds without a console.
|
|
37
|
+
'ping -n 3 127.0.0.1 >nul',
|
|
38
|
+
// npm, pnpm and yarn are .cmd shims on Windows. Without `call`, a batch
|
|
39
|
+
// file hands control to them for good and the restart below never runs.
|
|
40
|
+
// Read from a file, cmd expands `%x%` and drops a lone `%`, so a `%` in a
|
|
41
|
+
// host or UI password would change the restart command. Doubling keeps it.
|
|
42
|
+
`call ${updateCmd.replace(/%/g, '%%')}`,
|
|
43
|
+
'if %ERRORLEVEL% EQU 0 (',
|
|
44
|
+
' echo Update successful, restarting OpenChamber...',
|
|
45
|
+
` ${restartCmd ? restartCmd.replace(/%/g, '%%') : 'echo Service manager will restart OpenChamber.'}`,
|
|
46
|
+
') else (',
|
|
47
|
+
' echo Update failed',
|
|
48
|
+
')',
|
|
49
|
+
// The restart command carries the server's own flags, `--ui-password`
|
|
50
|
+
// included, so the file does not outlive the run. Deleting the running
|
|
51
|
+
// batch file on its last line is safe: cmd has already read it.
|
|
52
|
+
'del "%~f0"',
|
|
53
|
+
'',
|
|
54
|
+
].join('\r\n');
|
|
55
|
+
|
|
19
56
|
export const registerOpenChamberRoutes = (app, dependencies) => {
|
|
20
57
|
const {
|
|
21
58
|
fs,
|
|
@@ -300,6 +337,26 @@ export const registerOpenChamberRoutes = (app, dependencies) => {
|
|
|
300
337
|
`logPath=${updateLogPath}`,
|
|
301
338
|
].join('\n');
|
|
302
339
|
|
|
340
|
+
const shell = isWindows ? (process.env.ComSpec || 'cmd.exe') : 'sh';
|
|
341
|
+
const shellFlag = isWindows ? '/c' : '-c';
|
|
342
|
+
// cmd.exe /c takes one command line: a newline inside it ends the
|
|
343
|
+
// command, so a multi-line script passed as the argument ran nothing
|
|
344
|
+
// and exited 0, and the server shut itself down believing the update
|
|
345
|
+
// was under way (#3084). Batch runs from a file instead, written before
|
|
346
|
+
// the client is told to expect a restart.
|
|
347
|
+
const windowsScriptPath = path.join(openchamberDataDir, 'update-install.cmd');
|
|
348
|
+
if (isWindows) {
|
|
349
|
+
try {
|
|
350
|
+
fs.mkdirSync(path.dirname(windowsScriptPath), { recursive: true });
|
|
351
|
+
fs.writeFileSync(windowsScriptPath, buildWindowsUpdateScript({ logPreamble, updateCmd, restartCmd }), 'utf8');
|
|
352
|
+
} catch (scriptError) {
|
|
353
|
+
console.error('Failed to write the update script, update not started:', scriptError);
|
|
354
|
+
return res.status(500).json({
|
|
355
|
+
error: `Could not write the update script at ${windowsScriptPath}: ${scriptError instanceof Error ? scriptError.message : String(scriptError)}`,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
303
360
|
res.json({
|
|
304
361
|
success: true,
|
|
305
362
|
message: 'Update starting, server will restart shortly',
|
|
@@ -314,21 +371,8 @@ export const registerOpenChamberRoutes = (app, dependencies) => {
|
|
|
314
371
|
console.log(`Running: ${updateCmd}`);
|
|
315
372
|
console.log(logPreamble);
|
|
316
373
|
|
|
317
|
-
const shell = isWindows ? (process.env.ComSpec || 'cmd.exe') : 'sh';
|
|
318
|
-
const shellFlag = isWindows ? '/c' : '-c';
|
|
319
374
|
const script = isWindows
|
|
320
|
-
?
|
|
321
|
-
echo ${quoteCmd(logPreamble)}
|
|
322
|
-
timeout /t 2 /nobreak >nul
|
|
323
|
-
${updateCmd}
|
|
324
|
-
if %ERRORLEVEL% EQU 0 (
|
|
325
|
-
echo Update successful, restarting OpenChamber...
|
|
326
|
-
${restartCmd || 'echo Service manager will restart OpenChamber.'}
|
|
327
|
-
) else (
|
|
328
|
-
echo Update failed
|
|
329
|
-
exit /b 1
|
|
330
|
-
)
|
|
331
|
-
`
|
|
375
|
+
? windowsScriptPath
|
|
332
376
|
: `
|
|
333
377
|
printf '%s\n' ${quotePosix(logPreamble)}
|
|
334
378
|
sleep 2
|
|
@@ -350,10 +394,24 @@ export const registerOpenChamberRoutes = (app, dependencies) => {
|
|
|
350
394
|
console.warn('Failed to open update log file, continuing without log capture:', logError);
|
|
351
395
|
}
|
|
352
396
|
|
|
397
|
+
if (isWindows) {
|
|
398
|
+
// On Windows the detached child inherits this process's listening
|
|
399
|
+
// socket, and keeps the port for as long as the batch runs. The
|
|
400
|
+
// restart inside that batch then fails with "port already in use",
|
|
401
|
+
// and the update ends with no server. Closing the listener first
|
|
402
|
+
// leaves nothing to inherit; the process exits right after anyway.
|
|
403
|
+
try {
|
|
404
|
+
server.close();
|
|
405
|
+
} catch (closeError) {
|
|
406
|
+
console.warn('Failed to close the listener before the update script:', closeError);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
353
410
|
const child = spawnChild(shell, [shellFlag, script], {
|
|
354
411
|
detached: true,
|
|
355
412
|
stdio: logFd !== null ? ['ignore', logFd, logFd] : 'ignore',
|
|
356
413
|
env: process.env,
|
|
414
|
+
windowsHide: true,
|
|
357
415
|
});
|
|
358
416
|
child.unref();
|
|
359
417
|
|
|
@@ -18,11 +18,15 @@ const childProcess = await import('child_process');
|
|
|
18
18
|
const packageManager = await import('../package-manager.js');
|
|
19
19
|
const { registerOpenChamberRoutes } = await import('./openchamber-routes.js');
|
|
20
20
|
|
|
21
|
-
const createApp = ({ environment = {}, storedOptions = {}, desktopUpdater } = {}) => {
|
|
21
|
+
const createApp = ({ environment = {}, storedOptions = {}, desktopUpdater, platform = 'linux', execPath = '/usr/bin/node' } = {}) => {
|
|
22
22
|
const app = express();
|
|
23
23
|
const dependencies = {
|
|
24
24
|
fs: {
|
|
25
25
|
existsSync: vi.fn(() => false),
|
|
26
|
+
mkdirSync: vi.fn(),
|
|
27
|
+
writeFileSync: vi.fn(),
|
|
28
|
+
openSync: vi.fn(() => 7),
|
|
29
|
+
closeSync: vi.fn(),
|
|
26
30
|
promises: {
|
|
27
31
|
readFile: vi.fn(async () => JSON.stringify({
|
|
28
32
|
launchMode: 'foreground',
|
|
@@ -34,11 +38,13 @@ const createApp = ({ environment = {}, storedOptions = {}, desktopUpdater } = {}
|
|
|
34
38
|
path,
|
|
35
39
|
process: {
|
|
36
40
|
env: environment,
|
|
37
|
-
platform
|
|
38
|
-
execPath
|
|
41
|
+
platform,
|
|
42
|
+
execPath,
|
|
43
|
+
exit: vi.fn(),
|
|
39
44
|
},
|
|
40
45
|
server: {
|
|
41
46
|
address: () => ({ port: 7897 }),
|
|
47
|
+
close: vi.fn(),
|
|
42
48
|
},
|
|
43
49
|
__dirname: '/opt/openchamber/server',
|
|
44
50
|
openchamberDataDir: '/tmp/openchamber',
|
|
@@ -268,3 +274,65 @@ describe('OpenChamber foreground update route', () => {
|
|
|
268
274
|
});
|
|
269
275
|
});
|
|
270
276
|
});
|
|
277
|
+
|
|
278
|
+
describe('OpenChamber web update route on Windows', () => {
|
|
279
|
+
it('runs the install-and-restart script from a batch file instead of a cmd.exe /c argument', async () => {
|
|
280
|
+
const { app, dependencies } = createApp({
|
|
281
|
+
platform: 'win32',
|
|
282
|
+
execPath: 'C:\\Program Files\\nodejs\\node.exe',
|
|
283
|
+
environment: { ComSpec: 'C:\\Windows\\system32\\cmd.exe' },
|
|
284
|
+
storedOptions: { launchMode: 'daemon', port: 7897, uiPassword: 'pa%ss' },
|
|
285
|
+
});
|
|
286
|
+
childProcess.spawn.mockReturnValue({ unref: vi.fn() });
|
|
287
|
+
vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
288
|
+
|
|
289
|
+
await request(app).post('/api/openchamber/update-install').expect(200);
|
|
290
|
+
await new Promise((resolve) => setTimeout(resolve, 1300));
|
|
291
|
+
|
|
292
|
+
const scriptPath = path.join('/tmp/openchamber', 'update-install.cmd');
|
|
293
|
+
expect(dependencies.fs.writeFileSync).toHaveBeenCalledWith(scriptPath, expect.any(String), 'utf8');
|
|
294
|
+
const script = dependencies.fs.writeFileSync.mock.calls[0][1];
|
|
295
|
+
const lines = script.split('\r\n');
|
|
296
|
+
expect(lines[0]).toBe('@echo off');
|
|
297
|
+
// Every preamble line is an echo; none is left to run as a command.
|
|
298
|
+
expect(lines.filter((line) => line.startsWith('currentVersion=') || line.startsWith('restartCommand='))).toEqual([]);
|
|
299
|
+
expect(lines).toContain('echo packageManager=npm');
|
|
300
|
+
expect(lines).toContain('echo restartCommand=^("C:\\Program Files\\nodejs\\node.exe" "/opt/openchamber/bin/cli.js" serve --port 7897 --ui-password "pa%%ss"^) ^|^| ^(openchamber serve --port 7897 --ui-password "pa%%ss"^)');
|
|
301
|
+
// A .cmd shim (npm, pnpm, yarn) must be `call`ed or the script ends there.
|
|
302
|
+
expect(lines).toContain('call npm install -g @openchamber/web@latest');
|
|
303
|
+
expect(lines).toContain('ping -n 3 127.0.0.1 >nul');
|
|
304
|
+
expect(lines.some((line) => line.startsWith('timeout '))).toBe(false);
|
|
305
|
+
expect(lines).toContain('if %ERRORLEVEL% EQU 0 (');
|
|
306
|
+
expect(lines.at(-2)).toBe('del "%~f0"');
|
|
307
|
+
// A `%` in the password survives batch expansion only when doubled.
|
|
308
|
+
expect(lines).toContain(' ("C:\\Program Files\\nodejs\\node.exe" "/opt/openchamber/bin/cli.js" serve --port 7897 --ui-password "pa%%ss") || (openchamber serve --port 7897 --ui-password "pa%%ss")');
|
|
309
|
+
|
|
310
|
+
expect(childProcess.spawn).toHaveBeenCalledWith(
|
|
311
|
+
'C:\\Windows\\system32\\cmd.exe',
|
|
312
|
+
['/c', scriptPath],
|
|
313
|
+
expect.objectContaining({ detached: true, windowsHide: true }),
|
|
314
|
+
);
|
|
315
|
+
// The listener is closed before the batch is spawned, so the detached
|
|
316
|
+
// child cannot inherit the socket and hold the port against the restart.
|
|
317
|
+
expect(dependencies.server.close).toHaveBeenCalledOnce();
|
|
318
|
+
expect(dependencies.server.close.mock.invocationCallOrder[0]).toBeLessThan(childProcess.spawn.mock.invocationCallOrder[0]);
|
|
319
|
+
expect(dependencies.process.exit).toHaveBeenCalledWith(0);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it('answers 500 and keeps the server up when the batch file cannot be written', async () => {
|
|
323
|
+
const { app, dependencies } = createApp({ platform: 'win32', storedOptions: { launchMode: 'daemon', port: 7897 } });
|
|
324
|
+
dependencies.fs.writeFileSync.mockImplementation(() => { throw new Error('EACCES'); });
|
|
325
|
+
vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
326
|
+
const logError = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
327
|
+
|
|
328
|
+
const response = await request(app).post('/api/openchamber/update-install').expect(500);
|
|
329
|
+
await new Promise((resolve) => setTimeout(resolve, 1300));
|
|
330
|
+
|
|
331
|
+
expect(response.body.error).toContain('update-install.cmd');
|
|
332
|
+
expect(response.body.error).toContain('EACCES');
|
|
333
|
+
expect(childProcess.spawn).not.toHaveBeenCalled();
|
|
334
|
+
expect(dependencies.process.exit).not.toHaveBeenCalled();
|
|
335
|
+
expect(logError).toHaveBeenCalledOnce();
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { createRealpathCache } from '../path-realpath-cache.js';
|
|
12
12
|
import { DEFAULT_UPSTREAM_STALL_TIMEOUT_MS } from '../event-stream/upstream-reader.js';
|
|
13
13
|
import { recordStartupPerformance } from './startup-performance.js';
|
|
14
|
+
import { getWorktreeBootstrapStatus } from '../git/service.js';
|
|
14
15
|
|
|
15
16
|
const DEFAULT_SSE_HEARTBEAT_INTERVAL_MS = 20_000;
|
|
16
17
|
|
|
@@ -286,6 +287,8 @@ export const registerOpenCodeProxy = (app, deps) => {
|
|
|
286
287
|
SSE_HEARTBEAT_INTERVAL_MS = DEFAULT_SSE_HEARTBEAT_INTERVAL_MS,
|
|
287
288
|
SSE_UPSTREAM_STALL_TIMEOUT_MS = DEFAULT_UPSTREAM_STALL_TIMEOUT_MS,
|
|
288
289
|
getSseUpstreamStallTimeoutMs = () => SSE_UPSTREAM_STALL_TIMEOUT_MS,
|
|
290
|
+
readWorktreeBootstrapStatus = getWorktreeBootstrapStatus,
|
|
291
|
+
WORKTREE_READY_TIMEOUT_MS = 5 * 60 * 1000,
|
|
289
292
|
} = deps;
|
|
290
293
|
|
|
291
294
|
if (app.get('opencodeProxyConfigured')) {
|
|
@@ -775,6 +778,36 @@ export const registerOpenCodeProxy = (app, deps) => {
|
|
|
775
778
|
}
|
|
776
779
|
});
|
|
777
780
|
|
|
781
|
+
// Any directory-scoped read can initialize OpenCode's cached project/config,
|
|
782
|
+
// before session.create runs. Hold all upstream requests until Git population
|
|
783
|
+
// finishes, independently of the user's optional setup-script wait.
|
|
784
|
+
app.use('/api', async (req, res, next) => {
|
|
785
|
+
normalizeForwardedDirectoryHeaders(req.headers);
|
|
786
|
+
const url = new URL(req.url, 'http://localhost');
|
|
787
|
+
const directory = url.searchParams.get('directory') || req.get('x-opencode-directory');
|
|
788
|
+
if (!directory) return next();
|
|
789
|
+
|
|
790
|
+
const deadline = Date.now() + WORKTREE_READY_TIMEOUT_MS;
|
|
791
|
+
try {
|
|
792
|
+
while (!res.destroyed && !res.writableEnded && !req.aborted) {
|
|
793
|
+
const status = await readWorktreeBootstrapStatus(directory);
|
|
794
|
+
if (res.destroyed || res.writableEnded || req.aborted) return;
|
|
795
|
+
if (status.status === 'failed') {
|
|
796
|
+
return res.status(503).json({ error: status.error || 'Worktree bootstrap failed' });
|
|
797
|
+
}
|
|
798
|
+
if (status.status === 'ready' || status.phase === 'git-ready' || status.phase === 'setup-ready') {
|
|
799
|
+
return next();
|
|
800
|
+
}
|
|
801
|
+
if (Date.now() >= deadline) {
|
|
802
|
+
return res.status(503).json({ error: 'Timed out waiting for worktree checkout' });
|
|
803
|
+
}
|
|
804
|
+
await sleep(75);
|
|
805
|
+
}
|
|
806
|
+
} catch (error) {
|
|
807
|
+
next(error);
|
|
808
|
+
}
|
|
809
|
+
});
|
|
810
|
+
|
|
778
811
|
// Windows: session merge for cross-directory session listing
|
|
779
812
|
if (process.platform === 'win32') {
|
|
780
813
|
app.get('/api/session', async (req, res, next) => {
|
|
@@ -38,13 +38,76 @@ const extractSessionStatusUpdate = (payload) => {
|
|
|
38
38
|
};
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
+
const readRequestId = (value) => (typeof value === 'string' && value.trim() ? value.trim() : '');
|
|
42
|
+
|
|
41
43
|
export const createSessionRuntime = ({ writeSseEvent, getNotificationClients, broadcastEvent }) => {
|
|
42
44
|
const sessionActivityPhases = new Map();
|
|
43
45
|
const sessionActivityCooldowns = new Map();
|
|
44
46
|
const sessionStates = new Map();
|
|
45
47
|
const sessionAttentionStates = new Map();
|
|
48
|
+
// Pending permission and question requests per session, kept from the same
|
|
49
|
+
// upstream stream. Clients that do not initialize a directory cannot read
|
|
50
|
+
// its pending list from OpenCode (that read creates an instance), so this
|
|
51
|
+
// map is their seed. Entries live until the matching reply, the session's
|
|
52
|
+
// deletion, or an OpenCode restart, which drops every pending request.
|
|
53
|
+
const pendingRequestsBySession = new Map();
|
|
46
54
|
let activeSessionCount = 0;
|
|
47
55
|
|
|
56
|
+
const getOrCreatePendingRequests = (sessionId) => {
|
|
57
|
+
let entry = pendingRequestsBySession.get(sessionId);
|
|
58
|
+
if (!entry) {
|
|
59
|
+
entry = { permissions: new Map(), questions: new Map() };
|
|
60
|
+
pendingRequestsBySession.set(sessionId, entry);
|
|
61
|
+
}
|
|
62
|
+
return entry;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const settlePendingRequest = (kind, sessionId, requestId) => {
|
|
66
|
+
const entry = pendingRequestsBySession.get(sessionId);
|
|
67
|
+
if (!entry) return;
|
|
68
|
+
// OpenCode may omit the request ID on a reply; the session then has no
|
|
69
|
+
// pending request of that kind we can still vouch for.
|
|
70
|
+
if (requestId) entry[kind].delete(requestId);
|
|
71
|
+
else entry[kind].clear();
|
|
72
|
+
if (entry.permissions.size === 0 && entry.questions.size === 0) pendingRequestsBySession.delete(sessionId);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const processBlockingRequestPayload = (payload) => {
|
|
76
|
+
if (!payload || typeof payload.type !== 'string') return;
|
|
77
|
+
const properties = payload.properties && typeof payload.properties === 'object' ? payload.properties : {};
|
|
78
|
+
if (payload.type === 'permission.asked' || payload.type === 'question.asked') {
|
|
79
|
+
const sessionId = readRequestId(properties.sessionID);
|
|
80
|
+
const requestId = readRequestId(properties.id);
|
|
81
|
+
if (!sessionId || !requestId) return;
|
|
82
|
+
getOrCreatePendingRequests(sessionId)[payload.type === 'permission.asked' ? 'permissions' : 'questions'].set(requestId, properties);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (payload.type === 'permission.replied') {
|
|
86
|
+
settlePendingRequest('permissions', readRequestId(properties.sessionID), readRequestId(properties.requestID));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (payload.type === 'question.replied' || payload.type === 'question.rejected') {
|
|
90
|
+
settlePendingRequest('questions', readRequestId(properties.sessionID), readRequestId(properties.requestID));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (payload.type === 'session.deleted') {
|
|
94
|
+
const info = properties.info && typeof properties.info === 'object' ? properties.info : {};
|
|
95
|
+
const sessionId = readRequestId(properties.sessionID) || readRequestId(info.id);
|
|
96
|
+
if (sessionId) pendingRequestsBySession.delete(sessionId);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const getPendingBlockingRequestsSnapshot = () => {
|
|
101
|
+
const result = {};
|
|
102
|
+
for (const [sessionId, entry] of pendingRequestsBySession) {
|
|
103
|
+
result[sessionId] = {
|
|
104
|
+
permissions: [...entry.permissions.values()],
|
|
105
|
+
questions: [...entry.questions.values()],
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return result;
|
|
109
|
+
};
|
|
110
|
+
|
|
48
111
|
const getOrCreateAttentionState = (sessionId) => {
|
|
49
112
|
if (!sessionId || typeof sessionId !== 'string') return null;
|
|
50
113
|
|
|
@@ -307,6 +370,8 @@ export const createSessionRuntime = ({ writeSseEvent, getNotificationClients, br
|
|
|
307
370
|
}
|
|
308
371
|
}
|
|
309
372
|
|
|
373
|
+
// A restarted OpenCode forgot every pending request with the turns.
|
|
374
|
+
pendingRequestsBySession.clear();
|
|
310
375
|
const eventId = `opencode-restart-${Date.now()}`;
|
|
311
376
|
for (const sessionId of interruptedSessionIds) {
|
|
312
377
|
updateSessionState(sessionId, 'idle', eventId, {
|
|
@@ -354,6 +419,7 @@ export const createSessionRuntime = ({ writeSseEvent, getNotificationClients, br
|
|
|
354
419
|
const cleanupInterval = setInterval(cleanupOldSessionStates, SESSION_STATE_CLEANUP_INTERVAL_MS);
|
|
355
420
|
|
|
356
421
|
const processOpenCodeSsePayload = (payload) => {
|
|
422
|
+
processBlockingRequestPayload(payload);
|
|
357
423
|
const update = extractSessionStatusUpdate(payload);
|
|
358
424
|
if (!update) return;
|
|
359
425
|
|
|
@@ -379,6 +445,7 @@ export const createSessionRuntime = ({ writeSseEvent, getNotificationClients, br
|
|
|
379
445
|
sessionActivityPhases.clear();
|
|
380
446
|
sessionStates.clear();
|
|
381
447
|
sessionAttentionStates.clear();
|
|
448
|
+
pendingRequestsBySession.clear();
|
|
382
449
|
activeSessionCount = 0;
|
|
383
450
|
};
|
|
384
451
|
|
|
@@ -387,6 +454,7 @@ export const createSessionRuntime = ({ writeSseEvent, getNotificationClients, br
|
|
|
387
454
|
getSessionActivitySnapshot,
|
|
388
455
|
getActiveSessionCount,
|
|
389
456
|
getSessionStateSnapshot,
|
|
457
|
+
getPendingBlockingRequestsSnapshot,
|
|
390
458
|
getSessionAttentionSnapshot,
|
|
391
459
|
getSessionState,
|
|
392
460
|
getSessionAttentionState,
|
|
@@ -12,6 +12,49 @@ describe('session runtime', () => {
|
|
|
12
12
|
runtimes.length = 0;
|
|
13
13
|
});
|
|
14
14
|
|
|
15
|
+
it('keeps pending permission and question requests until they are answered', () => {
|
|
16
|
+
const runtime = createSessionRuntime({
|
|
17
|
+
writeSseEvent() {},
|
|
18
|
+
getNotificationClients: () => new Set(),
|
|
19
|
+
broadcastEvent: () => {},
|
|
20
|
+
});
|
|
21
|
+
runtimes.push(runtime);
|
|
22
|
+
const permission = { id: 'perm-1', sessionID: 'session-1', permission: 'bash', patterns: ['rm *'], metadata: {}, always: [] };
|
|
23
|
+
const question = { id: 'q-1', sessionID: 'session-2', questions: [{ header: 'Pick', question: 'Which?', options: [] }] };
|
|
24
|
+
|
|
25
|
+
runtime.processOpenCodeSsePayload({ type: 'permission.asked', properties: permission });
|
|
26
|
+
runtime.processOpenCodeSsePayload({ type: 'permission.asked', properties: permission });
|
|
27
|
+
runtime.processOpenCodeSsePayload({ type: 'question.asked', properties: question });
|
|
28
|
+
expect(runtime.getPendingBlockingRequestsSnapshot()).toEqual({
|
|
29
|
+
'session-1': { permissions: [permission], questions: [] },
|
|
30
|
+
'session-2': { permissions: [], questions: [question] },
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
runtime.processOpenCodeSsePayload({ type: 'permission.replied', properties: { sessionID: 'session-1', requestID: 'perm-1' } });
|
|
34
|
+
runtime.processOpenCodeSsePayload({ type: 'question.rejected', properties: { sessionID: 'session-2' } });
|
|
35
|
+
expect(runtime.getPendingBlockingRequestsSnapshot()).toEqual({});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('drops pending requests when the session is deleted or OpenCode restarts', () => {
|
|
39
|
+
const runtime = createSessionRuntime({
|
|
40
|
+
writeSseEvent() {},
|
|
41
|
+
getNotificationClients: () => new Set(),
|
|
42
|
+
broadcastEvent: () => {},
|
|
43
|
+
});
|
|
44
|
+
runtimes.push(runtime);
|
|
45
|
+
const ask = (sessionID, id) => runtime.processOpenCodeSsePayload({
|
|
46
|
+
type: 'permission.asked', properties: { id, sessionID, permission: 'edit', patterns: [], metadata: {}, always: [] },
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
ask('session-1', 'perm-1');
|
|
50
|
+
ask('session-2', 'perm-2');
|
|
51
|
+
runtime.processOpenCodeSsePayload({ type: 'session.deleted', properties: { info: { id: 'session-1' } } });
|
|
52
|
+
expect(Object.keys(runtime.getPendingBlockingRequestsSnapshot())).toEqual(['session-2']);
|
|
53
|
+
|
|
54
|
+
runtime.interruptBusySessionsAfterRestart();
|
|
55
|
+
expect(runtime.getPendingBlockingRequestsSnapshot()).toEqual({});
|
|
56
|
+
});
|
|
57
|
+
|
|
15
58
|
it('broadcasts attention clears through the shared broadcaster', () => {
|
|
16
59
|
const events = [];
|
|
17
60
|
const runtime = createSessionRuntime({
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
2
|
|
|
3
3
|
import { isAgentMemoryFeatureAvailable } from '../agent-memory/feature-flag.js';
|
|
4
|
+
import { isRoutingFeatureAvailable } from '../routing/feature-flag.js';
|
|
4
5
|
|
|
5
6
|
// Generated from packages/ui/src/lib/settings/registry.ts by
|
|
6
7
|
// `bun run settings-registry:generate`; `registry.test.ts` fails when stale.
|
|
@@ -233,6 +234,11 @@ export const createSettingsHelpers = (dependencies) => {
|
|
|
233
234
|
if (typeof candidate.workStatusHiddenSectionsExplicit === 'boolean') {
|
|
234
235
|
result.workStatusHiddenSectionsExplicit = candidate.workStatusHiddenSectionsExplicit;
|
|
235
236
|
}
|
|
237
|
+
if (Array.isArray(candidate.workStatusSectionOrder)) {
|
|
238
|
+
result.workStatusSectionOrder = [
|
|
239
|
+
...new Set(candidate.workStatusSectionOrder.filter((entry) => typeof entry === 'string' && entry.length > 0)),
|
|
240
|
+
];
|
|
241
|
+
}
|
|
236
242
|
if (typeof candidate.desktopLanAccessEnabled === 'boolean') {
|
|
237
243
|
result.desktopLanAccessEnabled = candidate.desktopLanAccessEnabled;
|
|
238
244
|
}
|
|
@@ -473,6 +479,9 @@ export const createSettingsHelpers = (dependencies) => {
|
|
|
473
479
|
if (candidate.sessionRetentionAction === 'archive' || candidate.sessionRetentionAction === 'delete') {
|
|
474
480
|
result.sessionRetentionAction = candidate.sessionRetentionAction;
|
|
475
481
|
}
|
|
482
|
+
if (typeof candidate.sessionRetentionOnlyArchived === 'boolean') {
|
|
483
|
+
result.sessionRetentionOnlyArchived = candidate.sessionRetentionOnlyArchived;
|
|
484
|
+
}
|
|
476
485
|
if (candidate.tunnelBootstrapTtlMs === null) {
|
|
477
486
|
result.tunnelBootstrapTtlMs = null;
|
|
478
487
|
} else if (typeof candidate.tunnelBootstrapTtlMs === 'number' && Number.isFinite(candidate.tunnelBootstrapTtlMs)) {
|
|
@@ -1005,6 +1014,8 @@ export const createSettingsHelpers = (dependencies) => {
|
|
|
1005
1014
|
// Tells the client whether agent memory exists in this build at all, so
|
|
1006
1015
|
// its settings row and panel tab can be absent rather than merely off.
|
|
1007
1016
|
agentMemoryFeatureAvailable: isAgentMemoryFeatureAvailable(),
|
|
1017
|
+
// Same idea for Jev routing: absent from the picker and Settings unless the build has it.
|
|
1018
|
+
routingFeatureAvailable: isRoutingFeatureAvailable(),
|
|
1008
1019
|
...(pwaAppName ? { pwaAppName } : {}),
|
|
1009
1020
|
pwaOrientation,
|
|
1010
1021
|
mobileKeyboardMode,
|
|
@@ -69,6 +69,18 @@ const createTestHelpersWithRealSanitizers = () => {
|
|
|
69
69
|
};
|
|
70
70
|
|
|
71
71
|
describe('settings helpers', () => {
|
|
72
|
+
it('round-trips section order and preserves it across unrelated writes', () => {
|
|
73
|
+
const helpers = createTestHelpers();
|
|
74
|
+
const changes = helpers.sanitizeSettingsUpdate({ workStatusSectionOrder: ['mcp', 'session', 'mcp', null, ''] });
|
|
75
|
+
expect(changes.workStatusSectionOrder).toEqual(['mcp', 'session']);
|
|
76
|
+
const saved = helpers.mergePersistedSettings({}, changes);
|
|
77
|
+
const reloaded = helpers.formatSettingsResponse(JSON.parse(JSON.stringify(saved)));
|
|
78
|
+
expect(reloaded.workStatusSectionOrder).toEqual(['mcp', 'session']);
|
|
79
|
+
const next = helpers.mergePersistedSettings(reloaded, helpers.sanitizeSettingsUpdate({ workStatusPanelEnabled: false }));
|
|
80
|
+
expect(helpers.formatSettingsResponse(next).workStatusSectionOrder).toEqual(['mcp', 'session']);
|
|
81
|
+
expect(helpers.sanitizeSettingsUpdate({ workStatusSectionOrder: 'bad' }).workStatusSectionOrder).toBeUndefined();
|
|
82
|
+
expect(helpers.sanitizeSettingsUpdate({ workStatusSectionOrder: [] }).workStatusSectionOrder).toEqual([]);
|
|
83
|
+
});
|
|
72
84
|
it('round-trips telemetry opt-in with the hidden list and preserves it across unrelated writes', () => {
|
|
73
85
|
const helpers = createTestHelpers();
|
|
74
86
|
const legacy = helpers.sanitizeSettingsUpdate({ workStatusHiddenSections: [] });
|
|
@@ -690,6 +702,14 @@ describe('settings helpers', () => {
|
|
|
690
702
|
});
|
|
691
703
|
|
|
692
704
|
describe('session retention settings persistence', () => {
|
|
705
|
+
it('round-trips archived-only retention and rejects non-boolean values', () => {
|
|
706
|
+
const helpers = createTestHelpersWithRealSanitizers();
|
|
707
|
+
for (const sessionRetentionOnlyArchived of [true, false]) {
|
|
708
|
+
expect(helpers.sanitizeSettingsUpdate({ sessionRetentionOnlyArchived })).toEqual({ sessionRetentionOnlyArchived });
|
|
709
|
+
}
|
|
710
|
+
expect(helpers.sanitizeSettingsUpdate({ sessionRetentionOnlyArchived: 'true' })).toEqual({});
|
|
711
|
+
expect(helpers.sanitizeSettingsUpdate({ sessionRetentionOnlyArchived: null })).toEqual({});
|
|
712
|
+
});
|
|
693
713
|
it('round-trips sessionRetentionAction archive and delete through the sanitizer', () => {
|
|
694
714
|
const helpers = createTestHelpersWithRealSanitizers();
|
|
695
715
|
|
|
@@ -714,6 +734,7 @@ describe('settings helpers', () => {
|
|
|
714
734
|
autoDeleteEnabled: true,
|
|
715
735
|
autoDeleteAfterDays: 60,
|
|
716
736
|
sessionRetentionAction: 'delete',
|
|
737
|
+
sessionRetentionOnlyArchived: true,
|
|
717
738
|
};
|
|
718
739
|
|
|
719
740
|
const sanitized = helpers.sanitizeSettingsUpdate(payload);
|
|
@@ -721,6 +742,7 @@ describe('settings helpers', () => {
|
|
|
721
742
|
expect(sanitized.autoDeleteEnabled).toBe(true);
|
|
722
743
|
expect(sanitized.autoDeleteAfterDays).toBe(60);
|
|
723
744
|
expect(sanitized.sessionRetentionAction).toBe('delete');
|
|
745
|
+
expect(sanitized.sessionRetentionOnlyArchived).toBe(true);
|
|
724
746
|
});
|
|
725
747
|
});
|
|
726
748
|
});
|
|
@@ -746,13 +768,13 @@ describe('settings registry gate', () => {
|
|
|
746
768
|
desktopUiPassword: 'secret', githubClientId: 'client', githubScopes: 'repo', skillCatalogs: [{ id: 'c', label: 'C', source: 'https://x' }],
|
|
747
769
|
defaultGitIdentityId: 'global', permissionAutoAccept: { sessions: { s: true }, revision: 1 },
|
|
748
770
|
agentControlToolEnabled: true, agentWebToolEnabled: true, agentMemoryToolEnabled: true, openCodeUpdateToastDismissedVersion: '1.0.0',
|
|
749
|
-
autoDeleteEnabled: true, autoDeleteAfterDays: 30, sessionRetentionAction: 'archive', terminalShell: 'zsh', terminalLoginShells: ['zsh'],
|
|
771
|
+
autoDeleteEnabled: true, autoDeleteAfterDays: 30, sessionRetentionOnlyArchived: false, sessionRetentionAction: 'archive', terminalShell: 'zsh', terminalLoginShells: ['zsh'],
|
|
750
772
|
openInAppId: 'vscode', dictationEnabled: true, sttProvider: 'local', sttServerUrl: 'http://localhost:8001/v1', sttModel: 'm', sttLocalModel: 'm', sttLanguage: 'en',
|
|
751
773
|
tunnelProvider: 'cloudflare', tunnelMode: 'quick', tunnelBootstrapTtlMs: 600000, tunnelSessionTtlMs: 86400000, managedLocalTunnelConfigPath: '/tmp/x',
|
|
752
774
|
managedRemoteTunnelHostname: 'x.example', managedRemoteTunnelToken: 'token', managedRemoteTunnelPresets: [{ id: 'a', name: 'A', hostname: 'a.example' }],
|
|
753
775
|
managedRemoteTunnelSelectedPresetId: 'a', managedRemoteTunnelPresetTokens: { a: 'token' },
|
|
754
776
|
sidebarProjectDisplayMode: 'all', sidebarSessionGroupingMode: 'flat', sidebarProjectSortOrder: 'manual', sidebarShowRecentSection: true,
|
|
755
|
-
workStatusPanelEnabled: true, workStatusHiddenSections: ['mcp'], workStatusHiddenSectionsExplicit: true,
|
|
777
|
+
workStatusPanelEnabled: true, workStatusHiddenSections: ['mcp'], workStatusHiddenSectionsExplicit: true, workStatusSectionOrder: ['mcp', 'session'],
|
|
756
778
|
showReasoningTraces: true, streamingAutoFollowEnabled: true, collapsibleThinkingBlocks: true, showTextJustificationActivity: true,
|
|
757
779
|
chatRenderMode: 'live', activityRenderMode: 'summary', mermaidRenderingMode: 'svg', userMessageRenderingMode: 'markdown', collapsibleUserMessages: true,
|
|
758
780
|
stickyUserHeader: true, promptNavigatorEnabled: true, wideChatLayoutEnabled: true, showSplitAssistantMessageActions: true, showToolFileIcons: true,
|
|
@@ -154,6 +154,7 @@ export const createSettingsNormalizationRuntime = (dependencies) => {
|
|
|
154
154
|
: null;
|
|
155
155
|
const iconBackground = normalizeIconBackground(candidate.iconBackground);
|
|
156
156
|
const color = typeof candidate.color === 'string' ? candidate.color.trim() : '';
|
|
157
|
+
const defaultAgent = typeof candidate.defaultAgent === 'string' ? candidate.defaultAgent.trim() : '';
|
|
157
158
|
const defaultModel = typeof candidate.defaultModel === 'string' ? candidate.defaultModel.trim() : '';
|
|
158
159
|
const defaultVariant = typeof candidate.defaultVariant === 'string' ? candidate.defaultVariant.trim() : '';
|
|
159
160
|
const addedAt = Number.isFinite(candidate.addedAt) ? Number(candidate.addedAt) : null;
|
|
@@ -175,6 +176,7 @@ export const createSettingsNormalizationRuntime = (dependencies) => {
|
|
|
175
176
|
...(icon ? { icon } : {}),
|
|
176
177
|
...(iconBackground ? { iconBackground } : {}),
|
|
177
178
|
...(color ? { color } : {}),
|
|
179
|
+
...(defaultAgent ? { defaultAgent } : {}),
|
|
178
180
|
...(defaultModel && defaultModel.includes('/') ? { defaultModel } : {}),
|
|
179
181
|
// A variant is meaningless without the model it belongs to.
|
|
180
182
|
...(defaultModel && defaultModel.includes('/') && defaultVariant ? { defaultVariant } : {}),
|
|
@@ -122,6 +122,19 @@ describe('settings normalization runtime - symlink resolution', () => {
|
|
|
122
122
|
expect(result[1].defaultVariant).toBe(undefined);
|
|
123
123
|
});
|
|
124
124
|
|
|
125
|
+
it('keeps a project default agent', () => {
|
|
126
|
+
const runtime = createTestRuntime({
|
|
127
|
+
realpathSync: (p) => p,
|
|
128
|
+
path: { resolve: (p) => p, sep: '/', dirname: (p) => p.split('/').slice(0, -1).join('/') || '/' },
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const result = runtime.sanitizeProjects([
|
|
132
|
+
{ id: 'proj1', path: '/a', defaultAgent: 'plan' },
|
|
133
|
+
]);
|
|
134
|
+
|
|
135
|
+
expect(result[0].defaultAgent).toBe('plan');
|
|
136
|
+
});
|
|
137
|
+
|
|
125
138
|
it('deduplicates projects that resolve to the same realpath', () => {
|
|
126
139
|
const runtime = createTestRuntime({
|
|
127
140
|
realpathSync: (p) => p.startsWith('/symlink') ? '/real/project' : p,
|
|
@@ -127,6 +127,10 @@
|
|
|
127
127
|
"scope": "instance",
|
|
128
128
|
"computed": true
|
|
129
129
|
},
|
|
130
|
+
"routingFeatureAvailable": {
|
|
131
|
+
"scope": "instance",
|
|
132
|
+
"computed": true
|
|
133
|
+
},
|
|
130
134
|
"openCodeUpdateToastDismissedVersion": {
|
|
131
135
|
"scope": "instance"
|
|
132
136
|
},
|
|
@@ -136,6 +140,9 @@
|
|
|
136
140
|
"autoDeleteAfterDays": {
|
|
137
141
|
"scope": "instance"
|
|
138
142
|
},
|
|
143
|
+
"sessionRetentionOnlyArchived": {
|
|
144
|
+
"scope": "instance"
|
|
145
|
+
},
|
|
139
146
|
"sessionRetentionAction": {
|
|
140
147
|
"scope": "instance"
|
|
141
148
|
},
|
|
@@ -214,6 +221,9 @@
|
|
|
214
221
|
"sidebarShowRecentSection": {
|
|
215
222
|
"scope": "profile"
|
|
216
223
|
},
|
|
224
|
+
"workStatusSectionOrder": {
|
|
225
|
+
"scope": "profile"
|
|
226
|
+
},
|
|
217
227
|
"workStatusPanelEnabled": {
|
|
218
228
|
"scope": "profile"
|
|
219
229
|
},
|
|
@@ -602,6 +612,10 @@
|
|
|
602
612
|
"scope": "device",
|
|
603
613
|
"local": true
|
|
604
614
|
},
|
|
615
|
+
"contextEditorVisible": {
|
|
616
|
+
"scope": "device",
|
|
617
|
+
"local": true
|
|
618
|
+
},
|
|
605
619
|
"contextEditorTreeWidth": {
|
|
606
620
|
"scope": "device",
|
|
607
621
|
"local": true
|
|
@@ -614,6 +628,10 @@
|
|
|
614
628
|
"scope": "device",
|
|
615
629
|
"local": true
|
|
616
630
|
},
|
|
631
|
+
"messageQueueExpanded": {
|
|
632
|
+
"scope": "device",
|
|
633
|
+
"local": true
|
|
634
|
+
},
|
|
617
635
|
"workStatusScrollTop": {
|
|
618
636
|
"scope": "device",
|
|
619
637
|
"local": true
|