@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
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { buildExternalManualRestartResponse } from './config-mutation-response.js';
|
|
2
|
+
import { ThemeImportStorageError } from './theme-runtime.js';
|
|
3
|
+
import { registerThemeCatalogRoutes } from './theme-catalog.js';
|
|
2
4
|
|
|
3
5
|
const parseLoopbackUrl = (rawUrl) => {
|
|
4
6
|
if (typeof rawUrl !== 'string') {
|
|
@@ -589,7 +591,15 @@ export const registerAuthAndAccessRoutes = (app, dependencies) => {
|
|
|
589
591
|
res.status(statusCode).json({ error: 'Invalid or expired pairing session' });
|
|
590
592
|
};
|
|
591
593
|
|
|
594
|
+
const isGuestOauthCallback = (req) => (
|
|
595
|
+
req.method === 'GET'
|
|
596
|
+
&& /^\/guests\/[a-z][a-z0-9-]*\/oauth\/callback$/.test(req.path || '')
|
|
597
|
+
);
|
|
598
|
+
|
|
592
599
|
const requireApiAuth = async (req, res, next) => {
|
|
600
|
+
if (isGuestOauthCallback(req)) {
|
|
601
|
+
return next();
|
|
602
|
+
}
|
|
593
603
|
const requestScope = tunnelAuthController.classifyRequestScope(req);
|
|
594
604
|
if (requestScope === 'tunnel' || requestScope === 'unknown-public') {
|
|
595
605
|
return tunnelAuthController.requireTunnelSession(req, res, next);
|
|
@@ -995,6 +1005,8 @@ export const registerAuthAndAccessRoutes = (app, dependencies) => {
|
|
|
995
1005
|
export const registerSettingsUtilityRoutes = (app, dependencies) => {
|
|
996
1006
|
const {
|
|
997
1007
|
readCustomThemesFromDisk,
|
|
1008
|
+
saveImportedTheme,
|
|
1009
|
+
deleteImportedTheme,
|
|
998
1010
|
refreshOpenCodeAfterConfigChange,
|
|
999
1011
|
clientReloadDelayMs,
|
|
1000
1012
|
} = dependencies;
|
|
@@ -1009,6 +1021,30 @@ export const registerSettingsUtilityRoutes = (app, dependencies) => {
|
|
|
1009
1021
|
}
|
|
1010
1022
|
});
|
|
1011
1023
|
|
|
1024
|
+
app.post('/api/config/themes', async (req, res) => {
|
|
1025
|
+
try {
|
|
1026
|
+
const theme = await saveImportedTheme(req.body?.theme);
|
|
1027
|
+
res.status(201).json({ theme });
|
|
1028
|
+
} catch (error) {
|
|
1029
|
+
if (error instanceof ThemeImportStorageError) {
|
|
1030
|
+
res.status(error.status).json({ error: error.code });
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
console.error('[themes] Failed to save imported theme');
|
|
1034
|
+
res.status(500).json({ error: 'save' });
|
|
1035
|
+
}
|
|
1036
|
+
});
|
|
1037
|
+
|
|
1038
|
+
registerThemeCatalogRoutes(app);
|
|
1039
|
+
app.delete('/api/config/themes/:id', async (req, res) => {
|
|
1040
|
+
try {
|
|
1041
|
+
await deleteImportedTheme(req.params.id);
|
|
1042
|
+
res.json({ success: true });
|
|
1043
|
+
} catch (error) {
|
|
1044
|
+
res.status(error instanceof ThemeImportStorageError ? error.status : 500).json({ error: 'delete' });
|
|
1045
|
+
}
|
|
1046
|
+
});
|
|
1047
|
+
|
|
1012
1048
|
app.post('/api/config/reload', async (_req, res) => {
|
|
1013
1049
|
try {
|
|
1014
1050
|
console.log('[Server] Manual configuration reload requested');
|
|
@@ -1041,7 +1077,9 @@ export const registerCommonRequestMiddleware = (app, dependencies) => {
|
|
|
1041
1077
|
const { express, verboseRequestLogs = false } = dependencies;
|
|
1042
1078
|
|
|
1043
1079
|
app.use((req, res, next) => {
|
|
1044
|
-
if (req.path.startsWith('/api/
|
|
1080
|
+
if (req.path === '/api/config/themes' || req.path.startsWith('/api/config/themes/')) {
|
|
1081
|
+
express.json({ limit: '1mb' })(req, res, next);
|
|
1082
|
+
} else if (req.path.startsWith('/api/behavior')) {
|
|
1045
1083
|
const contentLength = parseInt(req.headers['content-length'] || '0', 10);
|
|
1046
1084
|
if (contentLength > 1024 * 1024) {
|
|
1047
1085
|
return res.status(413).json({ error: 'Content exceeds maximum size of 1048576 bytes' });
|
|
@@ -1110,7 +1110,11 @@ export const createOpenCodeEnvRuntime = (deps) => {
|
|
|
1110
1110
|
return resolved;
|
|
1111
1111
|
}
|
|
1112
1112
|
|
|
1113
|
-
process.env.
|
|
1113
|
+
// AppImage updates inherit process.env. Keep automatic desktop bundle
|
|
1114
|
+
// selection local so the next app does not treat the old mount as an override.
|
|
1115
|
+
if (process.env.OPENCHAMBER_RUNTIME !== 'desktop' || state.resolvedOpencodeBinarySource !== 'bundled') {
|
|
1116
|
+
process.env.OPENCODE_BINARY = resolved;
|
|
1117
|
+
}
|
|
1114
1118
|
prependToPath(path.dirname(resolved));
|
|
1115
1119
|
ensureOpencodeShimRuntime(resolved);
|
|
1116
1120
|
state.resolvedOpencodeBinary = resolved;
|
|
@@ -14,6 +14,7 @@ const originalResourcesPath = process.resourcesPath;
|
|
|
14
14
|
const originalWslBinary = process.env.WSL_BINARY;
|
|
15
15
|
const originalOpenChamberWslBinary = process.env.OPENCHAMBER_WSL_BINARY;
|
|
16
16
|
const originalPlatform = process.platform;
|
|
17
|
+
const originalRuntime = process.env.OPENCHAMBER_RUNTIME;
|
|
17
18
|
const tempDirs = [];
|
|
18
19
|
const itIf = (condition) => condition ? it : it.skip;
|
|
19
20
|
|
|
@@ -30,6 +31,8 @@ const setPlatform = (platform) => {
|
|
|
30
31
|
};
|
|
31
32
|
|
|
32
33
|
afterEach(() => {
|
|
34
|
+
if (originalRuntime === undefined) delete process.env.OPENCHAMBER_RUNTIME;
|
|
35
|
+
else process.env.OPENCHAMBER_RUNTIME = originalRuntime;
|
|
33
36
|
Object.defineProperty(process, 'platform', {
|
|
34
37
|
value: originalPlatform,
|
|
35
38
|
});
|
|
@@ -117,7 +120,76 @@ const createRuntime = (settings, options = {}) => {
|
|
|
117
120
|
return { runtime, state };
|
|
118
121
|
};
|
|
119
122
|
|
|
123
|
+
const createBundledCli = () => {
|
|
124
|
+
const resourcesPath = createTempDir('openchamber-resources-');
|
|
125
|
+
const directory = path.join(resourcesPath, 'opencode-cli');
|
|
126
|
+
fs.mkdirSync(directory);
|
|
127
|
+
const binary = path.join(directory, process.platform === 'win32' ? 'opencode.exe' : 'opencode');
|
|
128
|
+
fs.writeFileSync(binary, '#!/bin/sh\nexit 0\n', { mode: 0o755 });
|
|
129
|
+
Object.defineProperty(process, 'resourcesPath', { configurable: true, value: resourcesPath });
|
|
130
|
+
return binary;
|
|
131
|
+
};
|
|
132
|
+
|
|
120
133
|
describe('OpenCode env runtime', () => {
|
|
134
|
+
it.each(['linux', 'darwin', 'win32'])('keeps automatic desktop bundled resolution local across relaunch on %s', (platform) => {
|
|
135
|
+
setPlatform(platform);
|
|
136
|
+
process.env.OPENCHAMBER_RUNTIME = 'desktop';
|
|
137
|
+
delete process.env.OPENCODE_BINARY;
|
|
138
|
+
delete process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR;
|
|
139
|
+
|
|
140
|
+
const oldBinary = createBundledCli();
|
|
141
|
+
const oldRuntime = createRuntime({});
|
|
142
|
+
expect(oldRuntime.runtime.ensureOpencodeCliEnv()).toBe(oldBinary);
|
|
143
|
+
expect(oldRuntime.runtime.ensureOpencodeCliEnv()).toBe(oldBinary);
|
|
144
|
+
expect(oldRuntime.state.resolvedOpencodeBinarySource).toBe('bundled');
|
|
145
|
+
expect(process.env.OPENCODE_BINARY).toBeUndefined();
|
|
146
|
+
expect(process.env.PATH.split(path.delimiter)[0]).toBe(path.dirname(oldBinary));
|
|
147
|
+
|
|
148
|
+
// The old binary remains executable and its directory is still in PATH.
|
|
149
|
+
const newBinary = createBundledCli();
|
|
150
|
+
const newRuntime = createRuntime({});
|
|
151
|
+
expect(newRuntime.runtime.ensureOpencodeCliEnv()).toBe(newBinary);
|
|
152
|
+
expect(newRuntime.state.resolvedOpencodeBinarySource).toBe('bundled');
|
|
153
|
+
expect(newRuntime.runtime.isBundledOpenCodeCliPath(newBinary)).toBe(true);
|
|
154
|
+
expect(process.env.OPENCODE_BINARY).toBeUndefined();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it.each(['web', 'ssh-remote'])('preserves automatic binary export outside desktop in %s', (runtimeName) => {
|
|
158
|
+
process.env.OPENCHAMBER_RUNTIME = runtimeName;
|
|
159
|
+
delete process.env.OPENCODE_BINARY;
|
|
160
|
+
delete process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR;
|
|
161
|
+
const binary = createBundledCli();
|
|
162
|
+
const { runtime } = createRuntime({});
|
|
163
|
+
expect(runtime.ensureOpencodeCliEnv()).toBe(binary);
|
|
164
|
+
expect(process.env.OPENCODE_BINARY).toBe(binary);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it.each(['env', 'settings'])('preserves explicit desktop %s selection even when it points at a bundled CLI', async (source) => {
|
|
168
|
+
process.env.OPENCHAMBER_RUNTIME = 'desktop';
|
|
169
|
+
delete process.env.OPENCODE_BINARY;
|
|
170
|
+
delete process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR;
|
|
171
|
+
const binary = createBundledCli();
|
|
172
|
+
if (source === 'env') process.env.OPENCODE_BINARY = binary;
|
|
173
|
+
const { runtime, state } = createRuntime(source === 'settings' ? { opencodeBinary: binary } : {});
|
|
174
|
+
await runtime.applyOpencodeBinaryFromSettings({ strict: true });
|
|
175
|
+
expect(runtime.ensureOpencodeCliEnv()).toBe(binary);
|
|
176
|
+
expect(state.resolvedOpencodeBinarySource).toBe(source);
|
|
177
|
+
expect(process.env.OPENCODE_BINARY).toBe(binary);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('preserves automatic PATH binary export for desktop without a bundled CLI', () => {
|
|
181
|
+
process.env.OPENCHAMBER_RUNTIME = 'desktop';
|
|
182
|
+
delete process.env.OPENCODE_BINARY;
|
|
183
|
+
delete process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR;
|
|
184
|
+
const binary = createBundledCli();
|
|
185
|
+
Object.defineProperty(process, 'resourcesPath', { configurable: true, value: undefined });
|
|
186
|
+
process.env.PATH = path.dirname(binary);
|
|
187
|
+
const { runtime, state } = createRuntime({});
|
|
188
|
+
expect(runtime.ensureOpencodeCliEnv()).toBe(binary);
|
|
189
|
+
expect(state.resolvedOpencodeBinarySource).toBe('path');
|
|
190
|
+
expect(process.env.OPENCODE_BINARY).toBe(binary);
|
|
191
|
+
});
|
|
192
|
+
|
|
121
193
|
it('searches an explicit PATH without mutating the process environment', () => {
|
|
122
194
|
const defaultDir = createTempDir('openchamber-default-path-');
|
|
123
195
|
const explicitDir = createTempDir('openchamber-explicit-path-');
|
|
@@ -5,6 +5,9 @@ import { registerWalkthroughRoutes } from '../walkthrough/routes.js';
|
|
|
5
5
|
import { registerSessionGoalRoutes } from '../session-goal/routes.js';
|
|
6
6
|
import { registerGitHubRoutes } from '../github/routes.js';
|
|
7
7
|
import { registerLinearRoutes } from '../linear/routes.js';
|
|
8
|
+
import { registerGuestRoutes } from '../guests/routes.js';
|
|
9
|
+
import { registerBuiltInGuests } from '../guests/catalog.js';
|
|
10
|
+
import { extensionsPersistPath } from '../guests/persist.js';
|
|
8
11
|
import { registerGitRoutes } from '../git/routes.js';
|
|
9
12
|
import { registerDevServerRoutes } from '../dev-servers/routes.js';
|
|
10
13
|
import { registerMagicPromptRoutes } from '../magic-prompts/routes.js';
|
|
@@ -15,6 +18,7 @@ import { registerAgentMemoryRoutes } from '../agent-memory/routes.js';
|
|
|
15
18
|
import { registerSessionKnowledgeRoutes } from '../session-knowledge/routes.js';
|
|
16
19
|
import { registerPermissionAutoAcceptRoutes } from '../permission-auto-accept/runtime.js';
|
|
17
20
|
import { registerMessageQueueRoutes } from '../message-queue/runtime.js';
|
|
21
|
+
import { registerRoutingPromptRewrite, registerRoutingRoutes } from '../routing/routes.js';
|
|
18
22
|
import { registerConfigEntityRoutes } from './config-entity-routes.js';
|
|
19
23
|
import { registerSettingsUtilityRoutes } from './core-routes.js';
|
|
20
24
|
import { registerProjectIconRoutes } from './project-icon-routes.js';
|
|
@@ -105,6 +109,8 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
|
|
105
109
|
resolveOptionalProjectDirectory,
|
|
106
110
|
validateDirectoryPath,
|
|
107
111
|
readCustomThemesFromDisk,
|
|
112
|
+
saveImportedTheme,
|
|
113
|
+
deleteImportedTheme,
|
|
108
114
|
refreshOpenCodeAfterConfigChange,
|
|
109
115
|
getOpenCodeResolutionSnapshot,
|
|
110
116
|
getOpenCodeUpgradeCapability,
|
|
@@ -136,16 +142,23 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
|
|
136
142
|
emitSessionCreatedEvent,
|
|
137
143
|
permissionAutoAcceptRuntime,
|
|
138
144
|
messageQueueRuntime,
|
|
145
|
+
routingRuntime,
|
|
146
|
+
openchamberVersion,
|
|
139
147
|
} = routeDependencies;
|
|
140
148
|
|
|
141
149
|
registerSettingsUtilityRoutes(app, {
|
|
142
150
|
readCustomThemesFromDisk,
|
|
151
|
+
saveImportedTheme,
|
|
152
|
+
deleteImportedTheme,
|
|
143
153
|
refreshOpenCodeAfterConfigChange,
|
|
144
154
|
clientReloadDelayMs,
|
|
145
155
|
});
|
|
146
156
|
|
|
147
157
|
registerPermissionAutoAcceptRoutes(app, permissionAutoAcceptRuntime);
|
|
148
158
|
registerMessageQueueRoutes(app, messageQueueRuntime);
|
|
159
|
+
registerRoutingRoutes(app, routingRuntime);
|
|
160
|
+
// Before the generic OpenCode proxy: turns `openchamber/auto` into a real model.
|
|
161
|
+
registerRoutingPromptRewrite(app, routingRuntime);
|
|
149
162
|
|
|
150
163
|
registerOpenCodeRoutes(app, {
|
|
151
164
|
crypto,
|
|
@@ -307,7 +320,23 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
|
|
307
320
|
registerSessionGoalRoutes(app);
|
|
308
321
|
registerGitHubRoutes(app);
|
|
309
322
|
registerLinearRoutes(app);
|
|
310
|
-
|
|
323
|
+
await registerBuiltInGuests({ persistPath: extensionsPersistPath(openchamberDataDir), root: routeDependencies.builtInExtensionsDir });
|
|
324
|
+
registerGuestRoutes(app, { openchamberDataDir, openchamberVersion, resolveGitBinaryForSpawn, resolveOptionalProjectDirectory, getSmallModelService });
|
|
325
|
+
registerGitRoutes(app, {
|
|
326
|
+
emitWorktreeChanged: ({ directories, at }) => {
|
|
327
|
+
const clients = getOpenChamberEventClients();
|
|
328
|
+
for (const client of clients) {
|
|
329
|
+
try {
|
|
330
|
+
writeSseEvent(client, {
|
|
331
|
+
type: 'openchamber:worktree-changed',
|
|
332
|
+
properties: { directories, at },
|
|
333
|
+
});
|
|
334
|
+
} catch {
|
|
335
|
+
clients.delete(client);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
});
|
|
311
340
|
registerDevServerRoutes(app, { scanner: devServerScanner, getOwnPorts });
|
|
312
341
|
registerMagicPromptRoutes(app, {
|
|
313
342
|
fsPromises,
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
import { createOpenCodeLifecycleRuntime } from './lifecycle.js';
|
|
6
|
+
|
|
7
|
+
const alive = (pid) => {
|
|
8
|
+
try { process.kill(pid, 0); return true; } catch { return false; }
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const createRuntime = (waitForReady, state) => createOpenCodeLifecycleRuntime({
|
|
12
|
+
state,
|
|
13
|
+
env: { ENV_CONFIGURED_OPENCODE_PORT: 45678, ENV_CONFIGURED_OPENCODE_HOSTNAME: '127.0.0.1' },
|
|
14
|
+
syncToHmrState() {}, syncFromHmrState() {},
|
|
15
|
+
ensureOpencodeCliEnv: () => process.execPath,
|
|
16
|
+
applyOpencodeBinaryFromSettings: async () => {},
|
|
17
|
+
ensureLocalOpenCodeServerPassword: async () => 'fixture-only',
|
|
18
|
+
resolveManagedOpenCodeLaunchSpec: (binary) => ({ binary, args: [] }),
|
|
19
|
+
normalizeApiPrefix: (value) => value,
|
|
20
|
+
setOpenCodePort() {}, setDetectedOpenCodeApiPrefix() {},
|
|
21
|
+
waitForReady,
|
|
22
|
+
managedStartupTimeoutMs: 1500,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('managed process lifecycle with real children', () => {
|
|
26
|
+
for (const failure of ['invalid-readiness', 'health-error', 'startup-timeout', 'shutdown-during-startup', 'none']) {
|
|
27
|
+
it(`reaps the server and its child after ${failure}`, async () => {
|
|
28
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'oc-process-'));
|
|
29
|
+
const previousRegistry = process.env.OPENCHAMBER_MANAGED_PROCESS_REGISTRY;
|
|
30
|
+
process.env.OPENCHAMBER_MANAGED_PROCESS_REGISTRY = path.join(root, 'registry');
|
|
31
|
+
const marker = path.join(root, 'pids');
|
|
32
|
+
const childScript = `process.on('SIGTERM', () => {}); require('node:fs').appendFileSync(${JSON.stringify(marker)}, process.pid + '\\n'); process.stdout.write('ready\\n'); setInterval(() => {}, 1000);`;
|
|
33
|
+
let readinessMessage = 'opencode server listening on http://127.0.0.1:45678\n';
|
|
34
|
+
if (failure === 'invalid-readiness') readinessMessage = 'opencode server listening without a URL\n';
|
|
35
|
+
if (failure === 'startup-timeout' || failure === 'shutdown-during-startup') readinessMessage = '';
|
|
36
|
+
// Node is an isolated stand-in for the native OpenCode binary. Lifecycle
|
|
37
|
+
// still launches its real `serve --hostname ... --port ...` command.
|
|
38
|
+
await fs.writeFile(path.join(root, 'serve'), `
|
|
39
|
+
const fs = require('node:fs');
|
|
40
|
+
fs.appendFileSync(${JSON.stringify(marker)}, process.pid + '\\n');
|
|
41
|
+
const child = require('node:child_process').spawn(process.execPath, ['-e', ${JSON.stringify(childScript)}], { stdio: ['ignore', 'pipe', 'ignore'] });
|
|
42
|
+
child.stdout.once('data', () => {
|
|
43
|
+
process.stdout.write(${JSON.stringify(readinessMessage)});
|
|
44
|
+
});
|
|
45
|
+
setInterval(() => {}, 1000);
|
|
46
|
+
`);
|
|
47
|
+
try {
|
|
48
|
+
const state = { openCodeWorkingDirectory: root, useWslForOpencode: false };
|
|
49
|
+
const runtime = createRuntime(async () => {
|
|
50
|
+
if (failure === 'health-error') throw new Error('fixture health failure');
|
|
51
|
+
return true;
|
|
52
|
+
}, state);
|
|
53
|
+
if (failure === 'none') {
|
|
54
|
+
const server = await runtime.startOpenCode();
|
|
55
|
+
await Promise.all([server.close(), server.close()]);
|
|
56
|
+
} else if (failure === 'shutdown-during-startup') {
|
|
57
|
+
const starting = runtime.startOpenCode();
|
|
58
|
+
const rejected = expect(starting).rejects.toThrow('exited before serving');
|
|
59
|
+
await expect.poll(async () => (await fs.readFile(marker, 'utf8').catch(() => '')).trim().split('\n').filter(Boolean).length).toBe(2);
|
|
60
|
+
state.isShuttingDown = true;
|
|
61
|
+
await state.openCodeProcess.close();
|
|
62
|
+
await rejected;
|
|
63
|
+
} else if (failure === 'startup-timeout') {
|
|
64
|
+
await expect(runtime.startOpenCode()).rejects.toThrow('Timeout waiting for OpenCode');
|
|
65
|
+
} else {
|
|
66
|
+
await expect(runtime.startOpenCode()).rejects.toThrow(failure === 'health-error' ? 'fixture health failure' : 'Failed to parse server url');
|
|
67
|
+
}
|
|
68
|
+
const pids = (await fs.readFile(marker, 'utf8')).trim().split('\n').map(Number);
|
|
69
|
+
expect(pids).toHaveLength(failure === 'none' || failure === 'shutdown-during-startup' ? 2 : 4);
|
|
70
|
+
await expect.poll(() => pids.filter(alive), { timeout: 3000 }).toEqual([]);
|
|
71
|
+
expect(await fs.readdir(path.join(root, 'registry')).catch(() => [])).toEqual([]);
|
|
72
|
+
} finally {
|
|
73
|
+
const pids = (await fs.readFile(marker, 'utf8').catch(() => '')).trim().split('\n').map(Number).filter((pid) => pid > 0);
|
|
74
|
+
for (const pid of pids.reverse()) {
|
|
75
|
+
if (!alive(pid)) continue;
|
|
76
|
+
try { process.kill(pid, 'SIGKILL'); } catch { /* already exited */ }
|
|
77
|
+
}
|
|
78
|
+
if (previousRegistry === undefined) delete process.env.OPENCHAMBER_MANAGED_PROCESS_REGISTRY;
|
|
79
|
+
else process.env.OPENCHAMBER_MANAGED_PROCESS_REGISTRY = previousRegistry;
|
|
80
|
+
await fs.rm(root, { recursive: true, force: true });
|
|
81
|
+
}
|
|
82
|
+
}, 15_000);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
@@ -109,6 +109,7 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
109
109
|
reapManagedOrphanedProcesses = reapOrphanedProcesses,
|
|
110
110
|
getWarmupDirectories = async () => [],
|
|
111
111
|
onOpenCodeRestarted = null,
|
|
112
|
+
managedStartupTimeoutMs = 30_000,
|
|
112
113
|
now = Date.now,
|
|
113
114
|
} = deps;
|
|
114
115
|
|
|
@@ -287,7 +288,7 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
287
288
|
}
|
|
288
289
|
|
|
289
290
|
const pid = child.pid;
|
|
290
|
-
if (!pid || hasChildProcessExited(child)) {
|
|
291
|
+
if (!pid || (process.platform === 'win32' && hasChildProcessExited(child))) {
|
|
291
292
|
await waitForChildProcessClose(child, 250);
|
|
292
293
|
return;
|
|
293
294
|
}
|
|
@@ -301,34 +302,14 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
301
302
|
}
|
|
302
303
|
|
|
303
304
|
try {
|
|
304
|
-
child.kill(signal);
|
|
305
|
+
if (!hasChildProcessExited(child)) child.kill(signal);
|
|
305
306
|
} catch {
|
|
306
307
|
}
|
|
307
308
|
};
|
|
308
309
|
|
|
309
310
|
if (process.platform === 'win32') {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
} catch {
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
if (await waitForChildProcessClose(child, 800)) {
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
try {
|
|
320
|
-
spawnSync('taskkill', ['/pid', String(pid), '/t'], {
|
|
321
|
-
stdio: 'ignore',
|
|
322
|
-
timeout: 3000,
|
|
323
|
-
windowsHide: true,
|
|
324
|
-
});
|
|
325
|
-
} catch {
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
if (await waitForChildProcessClose(child, 1500)) {
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
|
|
311
|
+
// Windows child.kill() terminates only the parent. Kill the owned tree
|
|
312
|
+
// while its parent still exists, otherwise /T cannot find its children.
|
|
332
313
|
try {
|
|
333
314
|
spawnSync('taskkill', ['/pid', String(pid), '/f', '/t'], {
|
|
334
315
|
stdio: 'ignore',
|
|
@@ -343,11 +324,9 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
343
324
|
}
|
|
344
325
|
|
|
345
326
|
signalProcessTree('SIGTERM');
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
}
|
|
350
|
-
|
|
327
|
+
await waitForChildProcessClose(child, 2500);
|
|
328
|
+
// Parent exit does not prove group exit. Tools can ignore SIGTERM and keep
|
|
329
|
+
// running after their server has exited and closed its own stdio.
|
|
351
330
|
signalProcessTree('SIGKILL');
|
|
352
331
|
|
|
353
332
|
await waitForChildProcessClose(child, 1000);
|
|
@@ -377,8 +356,8 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
377
356
|
return parts.length > 0 ? parts.join('\n\n') : 'No stdout/stderr captured';
|
|
378
357
|
};
|
|
379
358
|
|
|
380
|
-
const createManagedOpenCodeServerProcess = async ({ hostname, port, timeout, cwd, env: processEnv, shellEnvKeysCount = 0 }) => {
|
|
381
|
-
let binary = (process.env.OPENCODE_BINARY || 'opencode').trim() || 'opencode';
|
|
359
|
+
const createManagedOpenCodeServerProcess = async ({ resolvedBinary, hostname, port, timeout, cwd, env: processEnv, shellEnvKeysCount = 0 }) => {
|
|
360
|
+
let binary = (resolvedBinary || process.env.OPENCODE_BINARY || 'opencode').trim() || 'opencode';
|
|
382
361
|
const sourceBinary = binary;
|
|
383
362
|
let args = ['serve', '--hostname', hostname, '--port', String(port)];
|
|
384
363
|
let launchWrapperType = null;
|
|
@@ -452,7 +431,29 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
452
431
|
child.on('exit', recordManagedProcessExit);
|
|
453
432
|
child.on('close', recordManagedProcessExit);
|
|
454
433
|
|
|
455
|
-
|
|
434
|
+
// Ownership starts at spawn, including processes that never become ready.
|
|
435
|
+
const registration = registerManagedProcess({
|
|
436
|
+
pid: child.pid,
|
|
437
|
+
ownerPid: process.pid,
|
|
438
|
+
port,
|
|
439
|
+
binary,
|
|
440
|
+
runtime: process.env.OPENCHAMBER_RUNTIME || 'web',
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
let closePromise = null;
|
|
444
|
+
const serverProcess = {
|
|
445
|
+
url: null,
|
|
446
|
+
pid: child.pid || null,
|
|
447
|
+
get exitCode() { return observedExitCode ?? child.exitCode; },
|
|
448
|
+
get signalCode() { return observedSignalCode ?? child.signalCode; },
|
|
449
|
+
get stderrTail() { return getManagedProcessSnapshot().stderrTail; },
|
|
450
|
+
close() {
|
|
451
|
+
if (!closePromise) closePromise = registration.then(() => closeManagedOpenCodeChild(child));
|
|
452
|
+
return closePromise;
|
|
453
|
+
},
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
const readiness = new Promise((resolve, reject) => {
|
|
456
457
|
let stdout = '';
|
|
457
458
|
let stderr = '';
|
|
458
459
|
let done = false;
|
|
@@ -507,37 +508,21 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
507
508
|
child.stderr?.on('data', onStderr);
|
|
508
509
|
child.on('exit', onExit);
|
|
509
510
|
child.on('error', onError);
|
|
511
|
+
}).catch(async (error) => {
|
|
512
|
+
await serverProcess.close();
|
|
513
|
+
if (state.openCodeProcess === serverProcess) {
|
|
514
|
+
state.openCodeProcess = null;
|
|
515
|
+
syncToHmrState();
|
|
516
|
+
}
|
|
517
|
+
throw error;
|
|
510
518
|
});
|
|
511
519
|
|
|
512
|
-
//
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
pid: child.pid,
|
|
519
|
-
ownerPid: process.pid,
|
|
520
|
-
port,
|
|
521
|
-
binary,
|
|
522
|
-
runtime: process.env.OPENCHAMBER_RUNTIME || 'web',
|
|
523
|
-
});
|
|
524
|
-
|
|
525
|
-
return {
|
|
526
|
-
url,
|
|
527
|
-
pid: child.pid || null,
|
|
528
|
-
get exitCode() {
|
|
529
|
-
return observedExitCode ?? child.exitCode;
|
|
530
|
-
},
|
|
531
|
-
get signalCode() {
|
|
532
|
-
return observedSignalCode ?? child.signalCode;
|
|
533
|
-
},
|
|
534
|
-
get stderrTail() {
|
|
535
|
-
return getManagedProcessSnapshot().stderrTail;
|
|
536
|
-
},
|
|
537
|
-
async close() {
|
|
538
|
-
await closeManagedOpenCodeChild(child);
|
|
539
|
-
},
|
|
540
|
-
};
|
|
520
|
+
// Shutdown must be able to close an in-flight startup, not only a ready server.
|
|
521
|
+
state.openCodeProcess = serverProcess;
|
|
522
|
+
syncToHmrState();
|
|
523
|
+
serverProcess.url = await readiness;
|
|
524
|
+
await registration;
|
|
525
|
+
return serverProcess;
|
|
541
526
|
};
|
|
542
527
|
|
|
543
528
|
const resolveManagedOpenCodePort = async (requestedPort, hostname = '127.0.0.1') => {
|
|
@@ -694,7 +679,7 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
694
679
|
);
|
|
695
680
|
|
|
696
681
|
await applyOpencodeBinaryFromSettings({ strict: true });
|
|
697
|
-
ensureOpencodeCliEnv();
|
|
682
|
+
const resolvedBinary = ensureOpencodeCliEnv();
|
|
698
683
|
recordStartupPerformance('opencode.binary.ready', {
|
|
699
684
|
attempt,
|
|
700
685
|
durationMs: performance.now() - phaseStartedAt,
|
|
@@ -719,11 +704,14 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
719
704
|
});
|
|
720
705
|
phaseStartedAt = performance.now();
|
|
721
706
|
|
|
707
|
+
let serverInstance;
|
|
722
708
|
try {
|
|
723
|
-
|
|
709
|
+
if (state.isShuttingDown) throw new Error('OpenCode startup cancelled during shutdown');
|
|
710
|
+
serverInstance = await createManagedOpenCodeServerProcess({
|
|
711
|
+
resolvedBinary,
|
|
724
712
|
hostname: env.ENV_CONFIGURED_OPENCODE_HOSTNAME,
|
|
725
713
|
port: spawnPort,
|
|
726
|
-
timeout:
|
|
714
|
+
timeout: managedStartupTimeoutMs,
|
|
727
715
|
cwd: state.openCodeWorkingDirectory,
|
|
728
716
|
shellEnvKeysCount: Object.keys(shellEnv).length,
|
|
729
717
|
env: stripAppImageArgv0Leak(applyProviderEnvAliases({
|
|
@@ -749,7 +737,9 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
749
737
|
const port = parseInt(url.port, 10);
|
|
750
738
|
const prefix = normalizeApiPrefix(url.pathname);
|
|
751
739
|
|
|
752
|
-
|
|
740
|
+
const ready = await waitForReady(serverInstance.url, 10000);
|
|
741
|
+
if (state.isShuttingDown) throw new Error('OpenCode startup cancelled during shutdown');
|
|
742
|
+
if (ready) {
|
|
753
743
|
setOpenCodePort(port);
|
|
754
744
|
setDetectedOpenCodeApiPrefix(prefix);
|
|
755
745
|
|
|
@@ -767,12 +757,10 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
767
757
|
return serverInstance;
|
|
768
758
|
}
|
|
769
759
|
|
|
770
|
-
try {
|
|
771
|
-
await serverInstance.close();
|
|
772
|
-
} catch {
|
|
773
|
-
}
|
|
774
760
|
throw new Error('Server started but health check failed (timeout)');
|
|
775
761
|
} catch (error) {
|
|
762
|
+
await serverInstance?.close();
|
|
763
|
+
if (serverInstance && state.openCodeProcess === serverInstance) state.openCodeProcess = null;
|
|
776
764
|
const message = error instanceof Error ? error.message : String(error);
|
|
777
765
|
state.lastOpenCodeError = message;
|
|
778
766
|
state.openCodePort = null;
|
|
@@ -794,7 +782,7 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
794
782
|
return await startOpenCodeOnce(attempt);
|
|
795
783
|
} catch (error) {
|
|
796
784
|
lastError = error;
|
|
797
|
-
if (error?.code === 'OPENCODE_BINARY_INVALID') {
|
|
785
|
+
if (state.isShuttingDown || error?.code === 'OPENCODE_BINARY_INVALID') {
|
|
798
786
|
break;
|
|
799
787
|
}
|
|
800
788
|
if (attempt >= START_OPEN_CODE_MAX_ATTEMPTS) {
|
|
@@ -126,6 +126,35 @@ const createRuntime = (overrides = {}, stateOverrides = {}, envOverrides = {}) =
|
|
|
126
126
|
};
|
|
127
127
|
|
|
128
128
|
describe('OpenCode lifecycle', () => {
|
|
129
|
+
it('uses the resolved binary directly on startup and managed restart without an env override', async () => {
|
|
130
|
+
delete process.env.OPENCODE_BINARY;
|
|
131
|
+
const binaries = ['/bundle one/opencode-cli/opencode', '/bundle two/opencode-cli/opencode'];
|
|
132
|
+
const ensureOpencodeCliEnv = vi.fn()
|
|
133
|
+
.mockReturnValueOnce(binaries[0])
|
|
134
|
+
.mockReturnValueOnce(binaries[1]);
|
|
135
|
+
spawnMock.mockImplementation(() => {
|
|
136
|
+
const child = createMockChild();
|
|
137
|
+
queueMicrotask(() => child.stdout.emit('data', 'opencode server listening on http://127.0.0.1:45678\n'));
|
|
138
|
+
return child;
|
|
139
|
+
});
|
|
140
|
+
globalThis.fetch = vi.fn(async () => ({ ok: false }));
|
|
141
|
+
const runtime = createRuntime({ ensureOpencodeCliEnv });
|
|
142
|
+
const firstServer = await runtime.startOpenCode();
|
|
143
|
+
runtime.testState.openCodeProcess = firstServer;
|
|
144
|
+
try {
|
|
145
|
+
await runtime.restartOpenCode();
|
|
146
|
+
expect(firstServer.signalCode).toBe('SIGTERM');
|
|
147
|
+
expect(spawnMock.mock.calls.map(([binary]) => binary)).toEqual(binaries);
|
|
148
|
+
expect(runtime.testState.lastOpenCodeLaunchDiagnostics.sourceBinary).toBe(binaries[1]);
|
|
149
|
+
expect(process.env.OPENCODE_BINARY).toBeUndefined();
|
|
150
|
+
for (const [, , options] of spawnMock.mock.calls) {
|
|
151
|
+
expect(options.env).not.toHaveProperty('OPENCODE_BINARY');
|
|
152
|
+
}
|
|
153
|
+
} finally {
|
|
154
|
+
await runtime.testState.openCodeProcess.close();
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
129
158
|
it('records an authoritative ready terminal event for external startup', async () => {
|
|
130
159
|
globalThis.fetch = vi.fn(async () => ({
|
|
131
160
|
ok: true,
|
|
@@ -8,7 +8,7 @@ const isJsonObject = (value) => value !== null && value !== undefined && Object.
|
|
|
8
8
|
*
|
|
9
9
|
* Existing entries are preserved; an earlier entry for the same URL is dropped
|
|
10
10
|
* so a restart never registers the same plugin twice. Every managed plugin
|
|
11
|
-
* (agent tools, system prompt optimizer
|
|
11
|
+
* (agent tools, system prompt optimizer) goes through this one
|
|
12
12
|
* merge so they compose in any order.
|
|
13
13
|
*
|
|
14
14
|
* @param {string | undefined} rawConfig the current `OPENCODE_CONFIG_CONTENT`, JSONC or unset
|