@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
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
injectGuestAssetTokens,
|
|
5
|
+
listRelativeGuestScriptHrefs,
|
|
6
|
+
parseGuestUrlToken,
|
|
7
|
+
resolveGuestHtmlRelativePath,
|
|
8
|
+
} from './html-tokens.js';
|
|
9
|
+
|
|
10
|
+
describe('parseGuestUrlToken', () => {
|
|
11
|
+
test('reads a string or the first array entry', () => {
|
|
12
|
+
expect(parseGuestUrlToken('abc')).toBe('abc');
|
|
13
|
+
expect(parseGuestUrlToken(['abc', 'def'])).toBe('abc');
|
|
14
|
+
expect(parseGuestUrlToken(undefined)).toBe('');
|
|
15
|
+
expect(parseGuestUrlToken('')).toBe('');
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('listRelativeGuestScriptHrefs', () => {
|
|
20
|
+
test('keeps relative scripts and drops remote ones', () => {
|
|
21
|
+
expect(listRelativeGuestScriptHrefs(
|
|
22
|
+
'<script src="./main.js"></script><script src="https://cdn.example/a.js"></script>',
|
|
23
|
+
)).toEqual(['./main.js']);
|
|
24
|
+
expect(listRelativeGuestScriptHrefs('')).toEqual([]);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('resolveGuestHtmlRelativePath', () => {
|
|
29
|
+
test('resolves against the entry file and blocks escape', () => {
|
|
30
|
+
expect(resolveGuestHtmlRelativePath('panel/index.html', './main.js')).toBe('panel/main.js');
|
|
31
|
+
expect(resolveGuestHtmlRelativePath('panel/index.html', 'main.js?oc_url_token=x')).toBe('panel/main.js');
|
|
32
|
+
expect(resolveGuestHtmlRelativePath('panel/index.html', '../secret.js')).toBe('secret.js');
|
|
33
|
+
expect(resolveGuestHtmlRelativePath('panel/index.html', '../../outside.js')).toBeNull();
|
|
34
|
+
expect(resolveGuestHtmlRelativePath('index.html', './panel/main.js')).toBe('panel/main.js');
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('injectGuestAssetTokens', () => {
|
|
39
|
+
test('adds the token to a relative script src', () => {
|
|
40
|
+
expect(injectGuestAssetTokens('<script src="./main.js"></script>', 'tok/en'))
|
|
41
|
+
.toBe('<script src="./main.js?oc_url_token=tok%2Fen"></script>');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('leaves absolute and data urls alone', () => {
|
|
45
|
+
const html = '<script src="https://cdn.example/a.js"></script><img src="data:image/svg+xml,x">';
|
|
46
|
+
expect(injectGuestAssetTokens(html, 'tok')).toBe(html);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('does not duplicate an existing token', () => {
|
|
50
|
+
expect(injectGuestAssetTokens('<script src="./main.js?oc_url_token=old"></script>', 'new'))
|
|
51
|
+
.toBe('<script src="./main.js?oc_url_token=old"></script>');
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import { removeGuestStorage } from './storage.js';
|
|
3
|
+
import https from 'node:https';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
inspectGuestPackage,
|
|
9
|
+
listInstalledGuests,
|
|
10
|
+
resolveGuestPackageRoot,
|
|
11
|
+
toPublicGuest,
|
|
12
|
+
} from './catalog.js';
|
|
13
|
+
import { stopGuestService } from './service.js';
|
|
14
|
+
import { cloneGitRepository, isHttpsZipUrl, isPublicHostname, parseGitInstallUrl, publicAddressesOf } from './clone.js';
|
|
15
|
+
import { isReservedBuiltInId } from './builtins.js';
|
|
16
|
+
import { extractZipBuffer, unwrapGuestRoot } from './extract-zip.js';
|
|
17
|
+
import {
|
|
18
|
+
guestCopiesDir,
|
|
19
|
+
isCopiedGuestRoot,
|
|
20
|
+
readExtensionStore,
|
|
21
|
+
updateExtensionStore,
|
|
22
|
+
} from './persist.js';
|
|
23
|
+
|
|
24
|
+
const MAX_ZIP_BYTES = 20 * 1024 * 1024;
|
|
25
|
+
const DOWNLOAD_TIMEOUT_MS = 60_000;
|
|
26
|
+
|
|
27
|
+
const installBodySchema = z.object({
|
|
28
|
+
path: z.string().trim().min(1).optional(),
|
|
29
|
+
url: z.string().trim().min(1).optional(),
|
|
30
|
+
gitIdentityId: z.string().trim().min(1).max(128).optional(),
|
|
31
|
+
replace: z.boolean().optional(),
|
|
32
|
+
}).refine((value) => Boolean(value.path) !== Boolean(value.url));
|
|
33
|
+
|
|
34
|
+
export const parseInstallRequest = (body) => {
|
|
35
|
+
const parsed = installBodySchema.safeParse(body);
|
|
36
|
+
return parsed.success ? parsed.data : null;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const persistGuest = async (guest, root, source, persistPath, { replace = false, origin = null } = {}) => {
|
|
40
|
+
if (isReservedBuiltInId(guest.id)) return { ok: false, code: 'reserved-id' };
|
|
41
|
+
const stored = await readExtensionStore(persistPath);
|
|
42
|
+
const storedRoots = await Promise.all(stored.paths.map((entry) => resolveGuestPackageRoot(entry)));
|
|
43
|
+
if (storedRoots.some((entry) => entry === root)) {
|
|
44
|
+
if (!replace) {
|
|
45
|
+
return { ok: false, code: 'already-installed', id: guest.id };
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
ok: true,
|
|
49
|
+
replaced: true,
|
|
50
|
+
guest: toPublicGuest({
|
|
51
|
+
...guest,
|
|
52
|
+
source,
|
|
53
|
+
path: root,
|
|
54
|
+
capabilityGrants: stored.capabilityGrants?.[guest.id] ?? [],
|
|
55
|
+
enabled: !stored.disabledGuests?.[guest.id],
|
|
56
|
+
}),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const existing = await listInstalledGuests({ persistPath });
|
|
60
|
+
const clash = existing.find((entry) => entry.id === guest.id);
|
|
61
|
+
if (clash) {
|
|
62
|
+
if (!replace) {
|
|
63
|
+
return { ok: false, code: 'id-taken', id: guest.id };
|
|
64
|
+
}
|
|
65
|
+
const removed = await uninstallGuest(guest.id, persistPath);
|
|
66
|
+
if (!removed.ok) {
|
|
67
|
+
return removed;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
await updateExtensionStore(persistPath, (after) => ({
|
|
71
|
+
...after,
|
|
72
|
+
paths: after.paths.includes(root) ? after.paths : [...after.paths, root],
|
|
73
|
+
sources: { ...after.sources, [root]: source },
|
|
74
|
+
gitOrigins: origin ? { ...after.gitOrigins, [root]: origin } : after.gitOrigins,
|
|
75
|
+
}));
|
|
76
|
+
return {
|
|
77
|
+
ok: true,
|
|
78
|
+
replaced: Boolean(clash),
|
|
79
|
+
guest: toPublicGuest({ ...guest, source, path: root, capabilityGrants: [], enabled: true }),
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const removeDir = async (dir) => {
|
|
84
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const installCopiedGuest = async ({ source, prepare, persistPath, openchamberVersion, replace = false, origin = null }) => {
|
|
88
|
+
const copies = guestCopiesDir(persistPath);
|
|
89
|
+
await fs.mkdir(copies, { recursive: true });
|
|
90
|
+
const staging = path.join(copies, `.tmp-${process.pid}-${Date.now()}`);
|
|
91
|
+
try {
|
|
92
|
+
const prepared = await prepare(staging);
|
|
93
|
+
if (!prepared.ok) {
|
|
94
|
+
await removeDir(staging);
|
|
95
|
+
return prepared;
|
|
96
|
+
}
|
|
97
|
+
const packageRoot = await unwrapGuestRoot(prepared.root ?? staging);
|
|
98
|
+
const inspected = await inspectGuestPackage(packageRoot, { openchamberVersion });
|
|
99
|
+
if (!inspected.ok) {
|
|
100
|
+
await removeDir(staging);
|
|
101
|
+
return inspected;
|
|
102
|
+
}
|
|
103
|
+
if (isReservedBuiltInId(inspected.guest.id)) {
|
|
104
|
+
await removeDir(staging);
|
|
105
|
+
return { ok: false, code: 'reserved-id' };
|
|
106
|
+
}
|
|
107
|
+
const dest = path.join(copies, inspected.guest.id);
|
|
108
|
+
const store = await readExtensionStore(persistPath);
|
|
109
|
+
// The store holds the copy's realpath (on macOS /tmp is a symlink, so
|
|
110
|
+
// that differs from `dest` as spelled); either spelling is the same
|
|
111
|
+
// package already installed, not another folder claiming the id.
|
|
112
|
+
const destReal = await fs.realpath(dest).catch(() => dest);
|
|
113
|
+
const registered = store.paths.some((entry) => path.resolve(entry) === dest || path.resolve(entry) === destReal);
|
|
114
|
+
if (registered) {
|
|
115
|
+
if (!replace) {
|
|
116
|
+
await removeDir(staging);
|
|
117
|
+
return { ok: false, code: 'already-installed', id: inspected.guest.id };
|
|
118
|
+
}
|
|
119
|
+
const removed = await uninstallGuest(inspected.guest.id, persistPath);
|
|
120
|
+
if (!removed.ok) {
|
|
121
|
+
await removeDir(staging);
|
|
122
|
+
return removed;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// A copy on disk that the store does not know about is a leftover from an
|
|
126
|
+
// install that died between the move and the persist. It would otherwise
|
|
127
|
+
// block this id forever, so it is replaced rather than reported.
|
|
128
|
+
await removeDir(dest);
|
|
129
|
+
await fs.rename(packageRoot, dest);
|
|
130
|
+
if (packageRoot !== staging) {
|
|
131
|
+
await removeDir(staging);
|
|
132
|
+
}
|
|
133
|
+
const root = await fs.realpath(dest);
|
|
134
|
+
const persisted = await persistGuest(inspected.guest, root, source, persistPath, { replace, origin });
|
|
135
|
+
if (!persisted.ok) {
|
|
136
|
+
await removeDir(dest);
|
|
137
|
+
return persisted;
|
|
138
|
+
}
|
|
139
|
+
// A registered copy was uninstalled above before this one took its place.
|
|
140
|
+
return registered ? { ...persisted, replaced: true } : persisted;
|
|
141
|
+
} catch {
|
|
142
|
+
await removeDir(staging);
|
|
143
|
+
return { ok: false, code: source === 'git' ? 'clone-failed' : 'extract-failed' };
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// A redirect hop may land on a signed download URL without a .zip suffix; it
|
|
148
|
+
// still has to be https on a public host.
|
|
149
|
+
const isHttpsRedirectUrl = (value) => {
|
|
150
|
+
try {
|
|
151
|
+
const parsed = new URL(value);
|
|
152
|
+
return parsed.protocol === 'https:' && parsed.username === '' && parsed.password === '' && isPublicHostname(parsed.hostname);
|
|
153
|
+
} catch {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const readLocalZip = async (filePath) => {
|
|
159
|
+
try {
|
|
160
|
+
const stat = await fs.stat(filePath);
|
|
161
|
+
if (!stat.isFile() || stat.size > MAX_ZIP_BYTES) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
return await fs.readFile(filePath);
|
|
165
|
+
} catch {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const MAX_ZIP_REDIRECTS = 5;
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* One https request pinned to an address the caller already checked. The
|
|
174
|
+
* socket goes to `address`; TLS still verifies the certificate against the
|
|
175
|
+
* URL's hostname. Redirects are not followed: the status and `location` come
|
|
176
|
+
* back for the caller to check. The body is capped while it streams.
|
|
177
|
+
* @param {string} url
|
|
178
|
+
* @param {{ address: string, family: 4 | 6 }} target
|
|
179
|
+
* @param {number} maxBytes
|
|
180
|
+
* @returns {Promise<{ status: number, location: string | null, body: Buffer | null }>}
|
|
181
|
+
*/
|
|
182
|
+
const requestHttpsPinned = (url, target, maxBytes) => new Promise((resolve, reject) => {
|
|
183
|
+
const parsed = new URL(url);
|
|
184
|
+
const request = https.request(parsed, {
|
|
185
|
+
method: 'GET',
|
|
186
|
+
// Node's connector asks for `all` addresses when it tries families in
|
|
187
|
+
// parallel; either way it only ever gets the one that was checked.
|
|
188
|
+
lookup: (_hostname, options, callback) => (
|
|
189
|
+
options?.all
|
|
190
|
+
? callback(null, [{ address: target.address, family: target.family }])
|
|
191
|
+
: callback(null, target.address, target.family)
|
|
192
|
+
),
|
|
193
|
+
autoSelectFamily: false,
|
|
194
|
+
headers: { 'User-Agent': 'openchamber' },
|
|
195
|
+
timeout: DOWNLOAD_TIMEOUT_MS,
|
|
196
|
+
}, (response) => {
|
|
197
|
+
const status = response.statusCode ?? 0;
|
|
198
|
+
const location = typeof response.headers.location === 'string' ? response.headers.location : null;
|
|
199
|
+
if (status >= 300 && status < 400) {
|
|
200
|
+
response.resume();
|
|
201
|
+
resolve({ status, location, body: null });
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const declared = Number(response.headers['content-length']);
|
|
205
|
+
if (Number.isFinite(declared) && declared > maxBytes) {
|
|
206
|
+
response.destroy();
|
|
207
|
+
resolve({ status, location, body: null });
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
/** @type {Buffer[]} */
|
|
211
|
+
const chunks = [];
|
|
212
|
+
let received = 0;
|
|
213
|
+
response.on('data', (chunk) => {
|
|
214
|
+
received += chunk.length;
|
|
215
|
+
if (received > maxBytes) {
|
|
216
|
+
response.destroy();
|
|
217
|
+
resolve({ status, location, body: null });
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
chunks.push(chunk);
|
|
221
|
+
});
|
|
222
|
+
response.on('end', () => resolve({ status, location, body: Buffer.concat(chunks) }));
|
|
223
|
+
response.on('error', reject);
|
|
224
|
+
});
|
|
225
|
+
request.on('timeout', () => request.destroy(new Error('download timed out')));
|
|
226
|
+
request.on('error', reject);
|
|
227
|
+
request.end();
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Fetch the archive by hand, one hop at a time. A public URL can redirect to
|
|
232
|
+
* a private one, so every hop is checked (https, public host, public DNS
|
|
233
|
+
* answer) before the request is made, and the request goes to the address
|
|
234
|
+
* that was checked, not to whatever a second lookup would say.
|
|
235
|
+
* @param {string} url
|
|
236
|
+
* @param {{ request?: typeof requestHttpsPinned, lookup?: Parameters<typeof publicAddressesOf>[1] }} [options]
|
|
237
|
+
*/
|
|
238
|
+
export const downloadZip = async (url, { request = requestHttpsPinned, lookup } = {}) => {
|
|
239
|
+
let current = url;
|
|
240
|
+
for (let hop = 0; hop <= MAX_ZIP_REDIRECTS; hop += 1) {
|
|
241
|
+
if (!isHttpsZipUrl(current) && !isHttpsRedirectUrl(current)) {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
const addresses = await publicAddressesOf(new URL(current).hostname, lookup);
|
|
245
|
+
if (!addresses) {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
const response = await request(current, addresses[0], MAX_ZIP_BYTES);
|
|
249
|
+
if (response.status >= 300 && response.status < 400) {
|
|
250
|
+
if (!response.location) {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
current = new URL(response.location, current).href;
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (response.status < 200 || response.status >= 300 || !response.body) {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
return response.body;
|
|
260
|
+
}
|
|
261
|
+
return null;
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Install from zip bytes already in memory. Local `.zip` paths, https zip
|
|
266
|
+
* URLs, and browser uploads (`POST /api/guests/upload`) all land here, so the
|
|
267
|
+
* archive limits, unwrap, inspection, and store write are one path. The
|
|
268
|
+
* archive size cap is the caller's job: path and URL installs stop at
|
|
269
|
+
* `MAX_ZIP_BYTES`, the upload route at its own configured limit.
|
|
270
|
+
*/
|
|
271
|
+
export const installGuestFromZipBuffer = async (buffer, persistPath, { openchamberVersion, replace = false } = {}) => (
|
|
272
|
+
installCopiedGuest({
|
|
273
|
+
source: 'zip',
|
|
274
|
+
persistPath,
|
|
275
|
+
openchamberVersion,
|
|
276
|
+
replace,
|
|
277
|
+
prepare: async (staging) => {
|
|
278
|
+
const extracted = await extractZipBuffer(buffer, staging);
|
|
279
|
+
return extracted.ok ? { ok: true, root: staging } : extracted;
|
|
280
|
+
},
|
|
281
|
+
})
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
export const installGuestFromPath = async (rawPath, persistPath, { openchamberVersion, replace = false } = {}) => {
|
|
285
|
+
if (!path.isAbsolute(rawPath)) {
|
|
286
|
+
return { ok: false, code: 'invalid-path' };
|
|
287
|
+
}
|
|
288
|
+
try {
|
|
289
|
+
const stat = await fs.stat(rawPath);
|
|
290
|
+
if (stat.isFile() && rawPath.toLowerCase().endsWith('.zip')) {
|
|
291
|
+
const buffer = await readLocalZip(rawPath);
|
|
292
|
+
if (!buffer) {
|
|
293
|
+
return { ok: false, code: 'not-found' };
|
|
294
|
+
}
|
|
295
|
+
return installGuestFromZipBuffer(buffer, persistPath, { openchamberVersion, replace });
|
|
296
|
+
}
|
|
297
|
+
} catch {
|
|
298
|
+
return { ok: false, code: 'not-found' };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const root = await resolveGuestPackageRoot(rawPath);
|
|
302
|
+
if (!root) {
|
|
303
|
+
return { ok: false, code: 'not-found' };
|
|
304
|
+
}
|
|
305
|
+
const inspected = await inspectGuestPackage(root, { openchamberVersion });
|
|
306
|
+
if (!inspected.ok) {
|
|
307
|
+
return inspected;
|
|
308
|
+
}
|
|
309
|
+
return persistGuest(inspected.guest, root, 'path', persistPath, { replace });
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
export const installGuestFromUrl = async (rawUrl, persistPath, { openchamberVersion, replace = false, gitBinary, gitIdentityId } = {}) => {
|
|
313
|
+
if (isHttpsZipUrl(rawUrl)) {
|
|
314
|
+
try {
|
|
315
|
+
const buffer = await downloadZip(rawUrl);
|
|
316
|
+
if (!buffer) {
|
|
317
|
+
return { ok: false, code: 'extract-failed' };
|
|
318
|
+
}
|
|
319
|
+
return installGuestFromZipBuffer(buffer, persistPath, { openchamberVersion, replace });
|
|
320
|
+
} catch {
|
|
321
|
+
return { ok: false, code: 'extract-failed' };
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
const gitSource = parseGitInstallUrl(rawUrl);
|
|
325
|
+
if (!gitSource) {
|
|
326
|
+
return { ok: false, code: 'invalid-url' };
|
|
327
|
+
}
|
|
328
|
+
return installGuestFromGitSource(gitSource.url, persistPath, { openchamberVersion, replace, gitBinary, ref: gitSource.ref, gitIdentityId });
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* `source` is the clone URL without its `#ref` fragment; `ref` is the branch
|
|
333
|
+
* or tag to pin (omitted means the remote default branch). Both are stored
|
|
334
|
+
* as the guest's origin so Settings → Extensions can check for updates later.
|
|
335
|
+
*/
|
|
336
|
+
export const installGuestFromGitSource = async (source, persistPath, { openchamberVersion, replace = false, gitBinary, ref, gitIdentityId, lookup } = {}) => {
|
|
337
|
+
const origin = { url: source };
|
|
338
|
+
if (ref) origin.ref = ref;
|
|
339
|
+
if (gitIdentityId) origin.gitIdentityId = gitIdentityId;
|
|
340
|
+
return installCopiedGuest({
|
|
341
|
+
source: 'git',
|
|
342
|
+
persistPath,
|
|
343
|
+
openchamberVersion,
|
|
344
|
+
replace,
|
|
345
|
+
origin,
|
|
346
|
+
prepare: async (staging) => {
|
|
347
|
+
const cloned = await cloneGitRepository(source, staging, { gitBinary, ref, gitIdentityId, lookup });
|
|
348
|
+
return cloned.ok ? { ok: true, root: staging } : cloned;
|
|
349
|
+
},
|
|
350
|
+
});
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
export const installGuest = async (request, persistPath, { openchamberVersion, gitBinary } = {}) => {
|
|
354
|
+
const replace = Boolean(request.replace);
|
|
355
|
+
if (request.url) {
|
|
356
|
+
return installGuestFromUrl(request.url, persistPath, { openchamberVersion, replace, gitBinary, gitIdentityId: request.gitIdentityId });
|
|
357
|
+
}
|
|
358
|
+
return installGuestFromPath(request.path, persistPath, { openchamberVersion, replace });
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
export const uninstallGuest = async (id, persistPath) => {
|
|
362
|
+
const existing = await listInstalledGuests({ persistPath });
|
|
363
|
+
const guest = existing.find((entry) => entry.id === id);
|
|
364
|
+
if (!guest) {
|
|
365
|
+
return { ok: false, code: 'not-found' };
|
|
366
|
+
}
|
|
367
|
+
if (guest.source === 'bundled') {
|
|
368
|
+
return { ok: false, code: 'bundled' };
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
let removedRoot = null;
|
|
372
|
+
await updateExtensionStore(persistPath, async (stored) => {
|
|
373
|
+
const kept = [];
|
|
374
|
+
const sources = {};
|
|
375
|
+
const gitOrigins = {};
|
|
376
|
+
for (const entry of stored.paths) {
|
|
377
|
+
const root = await resolveGuestPackageRoot(entry);
|
|
378
|
+
if (root === guest.packageRoot) {
|
|
379
|
+
removedRoot = root;
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
kept.push(entry);
|
|
383
|
+
if (stored.sources[entry]) {
|
|
384
|
+
sources[entry] = stored.sources[entry];
|
|
385
|
+
}
|
|
386
|
+
if (stored.gitOrigins[entry]) {
|
|
387
|
+
gitOrigins[entry] = stored.gitOrigins[entry];
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
const capabilityGrants = { ...(stored.capabilityGrants ?? {}) };
|
|
391
|
+
delete capabilityGrants[id];
|
|
392
|
+
const capabilityScopes = { ...(stored.capabilityScopes ?? {}) };
|
|
393
|
+
delete capabilityScopes[id];
|
|
394
|
+
const disabledGuests = { ...(stored.disabledGuests ?? {}) };
|
|
395
|
+
delete disabledGuests[id];
|
|
396
|
+
const serviceSocketOverrides = { ...(stored.serviceSocketOverrides ?? {}) };
|
|
397
|
+
delete serviceSocketOverrides[id];
|
|
398
|
+
return { paths: kept, sources, gitOrigins, capabilityGrants, capabilityScopes, disabledGuests, serviceSocketOverrides };
|
|
399
|
+
});
|
|
400
|
+
await stopGuestService(id);
|
|
401
|
+
await removeGuestStorage(persistPath, id);
|
|
402
|
+
if (removedRoot && isCopiedGuestRoot(removedRoot, persistPath)) {
|
|
403
|
+
await fs.rm(removedRoot, { recursive: true, force: true });
|
|
404
|
+
}
|
|
405
|
+
return { ok: true };
|
|
406
|
+
};
|