@vividcodeai/embeddedcowork 0.0.21 → 0.0.23
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/dist/api-types.js +1 -0
- package/dist/auth/auth-store.js +134 -0
- package/dist/auth/http-auth.js +37 -0
- package/dist/auth/manager.js +140 -0
- package/dist/auth/password-hash.js +32 -0
- package/dist/auth/session-manager.js +17 -0
- package/dist/auth/token-manager.js +27 -0
- package/dist/background-processes/manager.js +576 -0
- package/dist/bin.js +24 -0
- package/dist/clients/connection-manager.js +93 -0
- package/dist/config/location.js +57 -0
- package/dist/config/schema.js +72 -0
- package/dist/events/bus.js +47 -0
- package/dist/filesystem/__tests__/search-cache.test.js +40 -0
- package/dist/filesystem/browser.js +292 -0
- package/dist/filesystem/search-cache.js +43 -0
- package/dist/filesystem/search.js +135 -0
- package/dist/index.js +496 -0
- package/dist/launcher.js +149 -0
- package/dist/loader.js +21 -0
- package/dist/logger.js +109 -0
- package/dist/opencode-config/README.md +32 -0
- package/dist/opencode-config/opencode.jsonc +35 -0
- package/dist/opencode-config/package.json +9 -0
- package/dist/opencode-config/plugin/embeddedcowork.ts +62 -0
- package/dist/opencode-config/plugin/lib/background-process.ts +265 -0
- package/dist/opencode-config/plugin/lib/client.ts +133 -0
- package/dist/opencode-config/plugin/lib/request.ts +214 -0
- package/dist/opencode-config.js +15 -0
- package/dist/opencode-downloader.js +295 -0
- package/dist/opencode-paths.js +167 -0
- package/dist/plugins/channel.js +40 -0
- package/dist/plugins/handlers.js +17 -0
- package/dist/plugins/voice-mode.js +78 -0
- package/dist/releases/dev-release-monitor.js +75 -0
- package/dist/releases/release-monitor.js +107 -0
- package/dist/runtime-paths.js +67 -0
- package/dist/server/__tests__/network-addresses.test.js +68 -0
- package/dist/server/__tests__/remote-proxy.test.js +204 -0
- package/dist/server/http-server.js +998 -0
- package/dist/server/network-addresses.js +114 -0
- package/dist/server/remote-proxy.js +466 -0
- package/dist/server/routes/auth-pages/login.html +135 -0
- package/dist/server/routes/auth-pages/token.html +93 -0
- package/dist/server/routes/auth.js +149 -0
- package/dist/server/routes/background-processes.js +78 -0
- package/dist/server/routes/events.js +66 -0
- package/dist/server/routes/filesystem.js +43 -0
- package/dist/server/routes/meta.js +44 -0
- package/dist/server/routes/opencode-status.js +10 -0
- package/dist/server/routes/plugin.js +70 -0
- package/dist/server/routes/remote-proxy.js +42 -0
- package/dist/server/routes/remote-servers.js +142 -0
- package/dist/server/routes/settings.js +69 -0
- package/dist/server/routes/sidecars.js +46 -0
- package/dist/server/routes/speech.js +63 -0
- package/dist/server/routes/storage.js +52 -0
- package/dist/server/routes/workspaces.js +221 -0
- package/dist/server/routes/worktrees.js +156 -0
- package/dist/server/tls.js +224 -0
- package/dist/settings/binaries.js +37 -0
- package/dist/settings/merge-patch.js +33 -0
- package/dist/settings/migrate.js +238 -0
- package/dist/settings/public-config.js +33 -0
- package/dist/settings/service.js +101 -0
- package/dist/settings/yaml-doc-store.js +96 -0
- package/dist/sidecars/manager.js +193 -0
- package/dist/speech/providers/openai-compatible.js +189 -0
- package/dist/speech/service.js +58 -0
- package/dist/storage/instance-store.js +56 -0
- package/dist/ui/__tests__/remote-ui.test.js +67 -0
- package/dist/ui/remote-ui.js +462 -0
- package/dist/workspaces/__tests__/spawn.test.js +139 -0
- package/dist/workspaces/git-mutations.js +98 -0
- package/dist/workspaces/git-status.js +323 -0
- package/dist/workspaces/git-worktrees.js +216 -0
- package/dist/workspaces/instance-events.js +180 -0
- package/dist/workspaces/manager.js +432 -0
- package/dist/workspaces/opencode-auth.js +16 -0
- package/dist/workspaces/runtime.js +366 -0
- package/dist/workspaces/spawn.js +219 -0
- package/dist/workspaces/worktree-directory.js +74 -0
- package/dist/workspaces/worktree-map.js +116 -0
- package/package.json +3 -12
- package/public/apple-touch-icon-180x180.png +0 -0
- package/public/assets/ChangesTab-C4_zdV74.js +2 -0
- package/public/assets/DiffToolbar-BsUPigM5.js +1 -0
- package/public/assets/EmbeddedCowork-Icon-DSw5nKk7.png +0 -0
- package/public/assets/FilesTab-Drvo30nM.js +2 -0
- package/public/assets/GitChangesTab-BhV6qTfP.js +2 -0
- package/public/assets/SplitFilePanel-BBrs329I.js +1 -0
- package/public/assets/StatusTab-DfYWlTSX.js +1 -0
- package/public/assets/abap-BdImnpbu.js +1 -0
- package/public/assets/actionscript-3-CfeIJUat.js +1 -0
- package/public/assets/ada-bCR0ucgS.js +1 -0
- package/public/assets/andromeeda-C-Jbm3Hp.js +1 -0
- package/public/assets/angular-html-CU67Zn6k.js +1 -0
- package/public/assets/angular-ts-BwZT4LLn.js +1 -0
- package/public/assets/apache-Pmp26Uib.js +1 -0
- package/public/assets/apex-DhZLUxFE.js +1 -0
- package/public/assets/apl-dKokRX4l.js +1 -0
- package/public/assets/applescript-Co6uUVPk.js +1 -0
- package/public/assets/ara-BRHolxvo.js +1 -0
- package/public/assets/asciidoc-Dv7Oe6Be.js +1 -0
- package/public/assets/asm-D_Q5rh1f.js +1 -0
- package/public/assets/astro-CbQHKStN.js +1 -0
- package/public/assets/aurora-x-D-2ljcwZ.js +1 -0
- package/public/assets/awk-DMzUqQB5.js +1 -0
- package/public/assets/ayu-dark-Cv9koXgw.js +1 -0
- package/public/assets/ballerina-BFfxhgS-.js +1 -0
- package/public/assets/bat-BkioyH1T.js +1 -0
- package/public/assets/beancount-k_qm7-4y.js +1 -0
- package/public/assets/berry-D08WgyRC.js +1 -0
- package/public/assets/bibtex-CHM0blh-.js +1 -0
- package/public/assets/bicep-Bmn6On1c.js +1 -0
- package/public/assets/blade-DVc8C-J4.js +1 -0
- package/public/assets/bsl-BO_Y6i37.js +1 -0
- package/public/assets/bundle-full-CAZqmV2E.js +13 -0
- package/public/assets/c-BIGW1oBm.js +1 -0
- package/public/assets/cadence-Bv_4Rxtq.js +1 -0
- package/public/assets/cairo-KRGpt6FW.js +1 -0
- package/public/assets/catppuccin-frappe-DFWUc33u.js +1 -0
- package/public/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
- package/public/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
- package/public/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
- package/public/assets/clarity-D53aC0YG.js +1 -0
- package/public/assets/clojure-P80f7IUj.js +1 -0
- package/public/assets/cmake-D1j8_8rp.js +1 -0
- package/public/assets/cobol-nwyudZeR.js +1 -0
- package/public/assets/codeowners-Bp6g37R7.js +1 -0
- package/public/assets/codeql-DsOJ9woJ.js +1 -0
- package/public/assets/coffee-Ch7k5sss.js +1 -0
- package/public/assets/common-lisp-Cg-RD9OK.js +1 -0
- package/public/assets/coq-DkFqJrB1.js +1 -0
- package/public/assets/core-DhEqZVGG.js +1 -0
- package/public/assets/cpp-CofmeUqb.js +1 -0
- package/public/assets/crystal-tKQVLTB8.js +1 -0
- package/public/assets/csharp-CX12Zw3r.js +1 -0
- package/public/assets/css-DPfMkruS.js +1 -0
- package/public/assets/csv-fuZLfV_i.js +1 -0
- package/public/assets/cue-D82EKSYY.js +1 -0
- package/public/assets/cypher-COkxafJQ.js +1 -0
- package/public/assets/d-85-TOEBH.js +1 -0
- package/public/assets/dark-plus-eOWES_5F.js +1 -0
- package/public/assets/dart-CF10PKvl.js +1 -0
- package/public/assets/dax-CEL-wOlO.js +1 -0
- package/public/assets/desktop-BmXAJ9_W.js +1 -0
- package/public/assets/diff-D97Zzqfu.js +1 -0
- package/public/assets/diff-viewer--UIZ5GEn.js +1 -0
- package/public/assets/docker-BcOcwvcX.js +1 -0
- package/public/assets/dotenv-Da5cRb03.js +1 -0
- package/public/assets/dracula-BzJJZx-M.js +1 -0
- package/public/assets/dracula-soft-BXkSAIEj.js +1 -0
- package/public/assets/dream-maker-BtqSS_iP.js +1 -0
- package/public/assets/edge-BkV0erSs.js +1 -0
- package/public/assets/elixir-CDX3lj18.js +1 -0
- package/public/assets/elm-DbKCFpqz.js +1 -0
- package/public/assets/emacs-lisp-C9XAeP06.js +1 -0
- package/public/assets/erb-BOJIQeun.js +1 -0
- package/public/assets/erlang-DsQrWhSR.js +1 -0
- package/public/assets/event-DjZVAIBO.js +1 -0
- package/public/assets/everforest-dark-BgDCqdQA.js +1 -0
- package/public/assets/everforest-light-C8M2exoo.js +1 -0
- package/public/assets/fast-diff-vendor-DgdwVvTQ.js +1 -0
- package/public/assets/fennel-BYunw83y.js +1 -0
- package/public/assets/fish-BvzEVeQv.js +1 -0
- package/public/assets/fluent-C4IJs8-o.js +1 -0
- package/public/assets/fortran-fixed-form-BZjJHVRy.js +1 -0
- package/public/assets/fortran-free-form-D22FLkUw.js +1 -0
- package/public/assets/fsharp-CXgrBDvD.js +1 -0
- package/public/assets/gdresource-B7Tvp0Sc.js +1 -0
- package/public/assets/gdscript-DTMYz4Jt.js +1 -0
- package/public/assets/gdshader-DkwncUOv.js +1 -0
- package/public/assets/genie-D0YGMca9.js +1 -0
- package/public/assets/gherkin-DyxjwDmM.js +1 -0
- package/public/assets/git-commit-F4YmCXRG.js +1 -0
- package/public/assets/git-diff-vendor-CSgooKT_.js +52 -0
- package/public/assets/git-diff-vendor-HAZkIolJ.css +19 -0
- package/public/assets/git-rebase-r7XF79zn.js +1 -0
- package/public/assets/github-dark-DHJKELXO.js +1 -0
- package/public/assets/github-dark-default-Cuk6v7N8.js +1 -0
- package/public/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
- package/public/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
- package/public/assets/github-light-DAi9KRSo.js +1 -0
- package/public/assets/github-light-default-D7oLnXFd.js +1 -0
- package/public/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
- package/public/assets/gleam-BspZqrRM.js +1 -0
- package/public/assets/glimmer-js-Rg0-pVw9.js +1 -0
- package/public/assets/glimmer-ts-U6CK756n.js +1 -0
- package/public/assets/glsl-DplSGwfg.js +1 -0
- package/public/assets/gnuplot-DdkO51Og.js +1 -0
- package/public/assets/go-Dn2_MT6a.js +1 -0
- package/public/assets/graphql-ChdNCCLP.js +1 -0
- package/public/assets/groovy-gcz8RCvz.js +1 -0
- package/public/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
- package/public/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
- package/public/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
- package/public/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
- package/public/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
- package/public/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
- package/public/assets/hack-CaT9iCJl.js +1 -0
- package/public/assets/haml-B8DHNrY2.js +1 -0
- package/public/assets/handlebars-BL8al0AC.js +1 -0
- package/public/assets/haskell-Df6bDoY_.js +1 -0
- package/public/assets/haxe-CzTSHFRz.js +1 -0
- package/public/assets/hcl-BWvSN4gD.js +1 -0
- package/public/assets/highlight-vendor-8FKMu9os.js +10 -0
- package/public/assets/hjson-D5-asLiD.js +1 -0
- package/public/assets/hlsl-D3lLCCz7.js +1 -0
- package/public/assets/houston-DnULxvSX.js +1 -0
- package/public/assets/html-GMplVEZG.js +1 -0
- package/public/assets/html-derivative-BFtXZ54Q.js +1 -0
- package/public/assets/http-jrhK8wxY.js +1 -0
- package/public/assets/hurl-irOxFIW8.js +1 -0
- package/public/assets/hxml-Bvhsp5Yf.js +1 -0
- package/public/assets/hy-DFXneXwc.js +1 -0
- package/public/assets/imba-DGztddWO.js +1 -0
- package/public/assets/index--0EL0_Tu.js +2 -0
- package/public/assets/index-BKMyzTSR.js +1 -0
- package/public/assets/index-BLouhjer.js +1 -0
- package/public/assets/index-CLSJ4cO9.js +1 -0
- package/public/assets/index-CgjnEdBS.js +1 -0
- package/public/assets/index-ChCPoe9m.js +1 -0
- package/public/assets/index-Co-dJ-Xs.js +1 -0
- package/public/assets/index-Ctcq8Rhl.css +1 -0
- package/public/assets/index-Da0V-sLI.js +1 -0
- package/public/assets/index-hOT6sqTO.js +1 -0
- package/public/assets/ini-BEwlwnbL.js +1 -0
- package/public/assets/java-CylS5w8V.js +1 -0
- package/public/assets/javascript-wDzz0qaB.js +1 -0
- package/public/assets/jinja-4LBKfQ-Z.js +1 -0
- package/public/assets/jison-wvAkD_A8.js +1 -0
- package/public/assets/json-Cp-IABpG.js +1 -0
- package/public/assets/json5-C9tS-k6U.js +1 -0
- package/public/assets/jsonc-Des-eS-w.js +1 -0
- package/public/assets/jsonl-DcaNXYhu.js +1 -0
- package/public/assets/jsonnet-DFQXde-d.js +1 -0
- package/public/assets/jssm-C2t-YnRu.js +1 -0
- package/public/assets/jsx-g9-lgVsj.js +1 -0
- package/public/assets/julia-C8NyazO9.js +1 -0
- package/public/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
- package/public/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
- package/public/assets/kanagawa-wave-DWedfzmr.js +1 -0
- package/public/assets/kdl-DV7GczEv.js +1 -0
- package/public/assets/kotlin-BdnUsdx6.js +1 -0
- package/public/assets/kusto-BvAqAH-y.js +1 -0
- package/public/assets/laserwave-DUszq2jm.js +1 -0
- package/public/assets/latex-BUKiar2Z.js +1 -0
- package/public/assets/lean-DP1Csr6i.js +1 -0
- package/public/assets/less-B1dDrJ26.js +1 -0
- package/public/assets/light-plus-B7mTdjB0.js +1 -0
- package/public/assets/liquid-DYVedYrR.js +1 -0
- package/public/assets/llvm-BtvRca6l.js +1 -0
- package/public/assets/loading-CugGjKDZ.css +1 -0
- package/public/assets/loading-CvW03p4Z.js +2 -0
- package/public/assets/log-2UxHyX5q.js +1 -0
- package/public/assets/logo-BtOb2qkB.js +1 -0
- package/public/assets/lua-BbnMAYS6.js +1 -0
- package/public/assets/luau-CXu1NL6O.js +1 -0
- package/public/assets/main-ByuKWHRz.js +53 -0
- package/public/assets/make-CHLpvVh8.js +1 -0
- package/public/assets/markdown-BBFC6uy4.js +58 -0
- package/public/assets/markdown-Cvjx9yec.js +1 -0
- package/public/assets/marko-CPi9NSCl.js +1 -0
- package/public/assets/material-theme-D5KoaKCx.js +1 -0
- package/public/assets/material-theme-darker-BfHTSMKl.js +1 -0
- package/public/assets/material-theme-lighter-B0m2ddpp.js +1 -0
- package/public/assets/material-theme-ocean-CyktbL80.js +1 -0
- package/public/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
- package/public/assets/matlab-D7o27uSR.js +1 -0
- package/public/assets/mdc-DUICxH0z.js +1 -0
- package/public/assets/mdx-Cmh6b_Ma.js +1 -0
- package/public/assets/mermaid-DKYwYmdq.js +1 -0
- package/public/assets/min-dark-CafNBF8u.js +1 -0
- package/public/assets/min-light-CTRr51gU.js +1 -0
- package/public/assets/mipsasm-CKIfxQSi.js +1 -0
- package/public/assets/mojo-1DNp92w6.js +1 -0
- package/public/assets/monaco-viewer-DnczYBfh.js +26 -0
- package/public/assets/monokai-D4h5O-jR.js +1 -0
- package/public/assets/move-Bu9oaDYs.js +1 -0
- package/public/assets/narrat-DRg8JJMk.js +1 -0
- package/public/assets/nextflow-CUEJCptM.js +1 -0
- package/public/assets/nginx-DknmC5AR.js +1 -0
- package/public/assets/night-owl-C39BiMTA.js +1 -0
- package/public/assets/nim-CVrawwO9.js +1 -0
- package/public/assets/nix-BbRYJGeE.js +1 -0
- package/public/assets/nord-Ddv68eIx.js +1 -0
- package/public/assets/nushell-C-sUppwS.js +1 -0
- package/public/assets/objective-c-DXmwc3jG.js +1 -0
- package/public/assets/objective-cpp-CLxacb5B.js +1 -0
- package/public/assets/ocaml-C0hk2d4L.js +1 -0
- package/public/assets/one-dark-pro-DVMEJ2y_.js +1 -0
- package/public/assets/one-light-PoHY5YXO.js +1 -0
- package/public/assets/pascal-D93ZcfNL.js +1 -0
- package/public/assets/perl-C0TMdlhV.js +1 -0
- package/public/assets/php-CDn_0X-4.js +1 -0
- package/public/assets/pkl-u5AG7uiY.js +1 -0
- package/public/assets/plastic-3e1v2bzS.js +1 -0
- package/public/assets/plsql-ChMvpjG-.js +1 -0
- package/public/assets/po-BTJTHyun.js +1 -0
- package/public/assets/poimandres-CS3Unz2-.js +1 -0
- package/public/assets/polar-C0HS_06l.js +1 -0
- package/public/assets/postcss-CXtECtnM.js +1 -0
- package/public/assets/powerquery-CEu0bR-o.js +1 -0
- package/public/assets/powershell-Dpen1YoG.js +1 -0
- package/public/assets/prisma-Dd19v3D-.js +1 -0
- package/public/assets/prolog-CbFg5uaA.js +1 -0
- package/public/assets/proto-DyJlTyXw.js +1 -0
- package/public/assets/pug-CGlum2m_.js +1 -0
- package/public/assets/puppet-BMWR74SV.js +1 -0
- package/public/assets/purescript-CklMAg4u.js +1 -0
- package/public/assets/python-B6aJPvgy.js +1 -0
- package/public/assets/qml-3beO22l8.js +1 -0
- package/public/assets/qmldir-C8lEn-DE.js +1 -0
- package/public/assets/qss-IeuSbFQv.js +1 -0
- package/public/assets/r-DiinP2Uv.js +1 -0
- package/public/assets/racket-BqYA7rlc.js +1 -0
- package/public/assets/raku-DXvB9xmW.js +1 -0
- package/public/assets/razor-WgofotgN.js +1 -0
- package/public/assets/red-bN70gL4F.js +1 -0
- package/public/assets/reg-C-SQnVFl.js +1 -0
- package/public/assets/regexp-CDVJQ6XC.js +1 -0
- package/public/assets/rel-C3B-1QV4.js +1 -0
- package/public/assets/riscv-BM1_JUlF.js +1 -0
- package/public/assets/rose-pine-BHrmToEH.js +1 -0
- package/public/assets/rose-pine-dawn-CnK8MTSM.js +1 -0
- package/public/assets/rose-pine-moon-NleAzG8P.js +1 -0
- package/public/assets/rosmsg-BJDFO7_C.js +1 -0
- package/public/assets/rst-B0xPkSld.js +1 -0
- package/public/assets/ruby-BvKwtOVI.js +1 -0
- package/public/assets/rust-B1yitclQ.js +1 -0
- package/public/assets/sas-cz2c8ADy.js +1 -0
- package/public/assets/sass-Cj5Yp3dK.js +1 -0
- package/public/assets/scala-C151Ov-r.js +1 -0
- package/public/assets/scheme-C98Dy4si.js +1 -0
- package/public/assets/scss-OYdSNvt2.js +1 -0
- package/public/assets/sdbl-DVxCFoDh.js +1 -0
- package/public/assets/shaderlab-Dg9Lc6iA.js +1 -0
- package/public/assets/shellscript-Yzrsuije.js +1 -0
- package/public/assets/shellsession-BADoaaVG.js +1 -0
- package/public/assets/slack-dark-BthQWCQV.js +1 -0
- package/public/assets/slack-ochin-DqwNpetd.js +1 -0
- package/public/assets/smalltalk-BERRCDM3.js +1 -0
- package/public/assets/snazzy-light-Bw305WKR.js +1 -0
- package/public/assets/solarized-dark-DXbdFlpD.js +1 -0
- package/public/assets/solarized-light-L9t79GZl.js +1 -0
- package/public/assets/solidity-BbcW6ACK.js +1 -0
- package/public/assets/soy-Brmx7dQM.js +1 -0
- package/public/assets/sparql-rVzFXLq3.js +1 -0
- package/public/assets/splunk-BtCnVYZw.js +1 -0
- package/public/assets/sql-BLtJtn59.js +1 -0
- package/public/assets/ssh-config-_ykCGR6B.js +1 -0
- package/public/assets/stata-BH5u7GGu.js +1 -0
- package/public/assets/stylus-BEDo0Tqx.js +1 -0
- package/public/assets/svelte-3Dk4HxPD.js +1 -0
- package/public/assets/swift-Dg5xB15N.js +1 -0
- package/public/assets/synthwave-84-CbfX1IO0.js +1 -0
- package/public/assets/system-verilog-CnnmHF94.js +1 -0
- package/public/assets/systemd-4A_iFExJ.js +1 -0
- package/public/assets/talonscript-CkByrt1z.js +1 -0
- package/public/assets/tasl-QIJgUcNo.js +1 -0
- package/public/assets/tcl-dwOrl1Do.js +1 -0
- package/public/assets/templ-W15q3VgB.js +1 -0
- package/public/assets/terraform-BETggiCN.js +1 -0
- package/public/assets/tex-Cppo0RY3.js +1 -0
- package/public/assets/todo-BLpUqy61.js +1 -0
- package/public/assets/tokyo-night-hegEt444.js +1 -0
- package/public/assets/toml-vGWfd6FD.js +1 -0
- package/public/assets/tool-call-B4Xz6FbC.js +60 -0
- package/public/assets/ts-tags-zn1MmPIZ.js +1 -0
- package/public/assets/tsv-B_m7g4N7.js +1 -0
- package/public/assets/tsx-COt5Ahok.js +1 -0
- package/public/assets/turtle-BsS91CYL.js +1 -0
- package/public/assets/twig-CO9l9SDP.js +1 -0
- package/public/assets/typescript-BPQ3VLAy.js +1 -0
- package/public/assets/typespec-Df68jz8_.js +1 -0
- package/public/assets/typst-DHCkPAjA.js +1 -0
- package/public/assets/unified-picker-BrzM_sH6.js +1 -0
- package/public/assets/v-BcVCzyr7.js +1 -0
- package/public/assets/vala-CsfeWuGM.js +1 -0
- package/public/assets/vb-D17OF-Vu.js +1 -0
- package/public/assets/verilog-BQ8w6xss.js +1 -0
- package/public/assets/vesper-DU1UobuO.js +1 -0
- package/public/assets/vhdl-CeAyd5Ju.js +1 -0
- package/public/assets/viml-CJc9bBzg.js +1 -0
- package/public/assets/vitesse-black-Bkuqu6BP.js +1 -0
- package/public/assets/vitesse-dark-D0r3Knsf.js +1 -0
- package/public/assets/vitesse-light-CVO1_9PV.js +1 -0
- package/public/assets/vue-CCoi5OLL.js +1 -0
- package/public/assets/vue-html-DAAvJJDi.js +1 -0
- package/public/assets/vue-vine-_Ih-lPRR.js +1 -0
- package/public/assets/vyper-CDx5xZoG.js +1 -0
- package/public/assets/wasm-CG6Dc4jp.js +1 -0
- package/public/assets/wasm-MzD3tlZU.js +1 -0
- package/public/assets/wenyan-BV7otONQ.js +1 -0
- package/public/assets/wgsl-Dx-B1_4e.js +1 -0
- package/public/assets/wikitext-BhOHFoWU.js +1 -0
- package/public/assets/wit-5i3qLPDT.js +1 -0
- package/public/assets/wolfram-lXgVvXCa.js +1 -0
- package/public/assets/wrap-text-B1i7zgLk.js +1 -0
- package/public/assets/xml-sdJ4AIDG.js +1 -0
- package/public/assets/xsl-CtQFsRM5.js +1 -0
- package/public/assets/yaml-Buea-lGh.js +1 -0
- package/public/assets/zenscript-DVFEvuxE.js +1 -0
- package/public/assets/zig-VOosw3JB.js +1 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +44 -0
- package/public/loading.html +33 -0
- package/public/logo.png +0 -0
- package/public/manifest.webmanifest +1 -0
- package/public/maskable-icon-512x512.png +0 -0
- package/public/monaco/vs/base/browser/ui/codicons/codicon/codicon.ttf +0 -0
- package/public/monaco/vs/base/worker/workerMain.js +31 -0
- package/public/monaco/vs/basic-languages/cpp/cpp.js +10 -0
- package/public/monaco/vs/basic-languages/kotlin/kotlin.js +10 -0
- package/public/monaco/vs/basic-languages/markdown/markdown.js +10 -0
- package/public/monaco/vs/basic-languages/python/python.js +10 -0
- package/public/monaco/vs/editor/editor.main.css +8 -0
- package/public/monaco/vs/editor/editor.main.js +798 -0
- package/public/monaco/vs/language/css/cssMode.js +13 -0
- package/public/monaco/vs/language/css/cssWorker.js +77 -0
- package/public/monaco/vs/language/html/htmlMode.js +13 -0
- package/public/monaco/vs/language/html/htmlWorker.js +454 -0
- package/public/monaco/vs/language/json/jsonMode.js +19 -0
- package/public/monaco/vs/language/json/jsonWorker.js +42 -0
- package/public/monaco/vs/language/typescript/tsMode.js +20 -0
- package/public/monaco/vs/language/typescript/tsWorker.js +51328 -0
- package/public/monaco/vs/loader.js +11 -0
- package/public/monaco.worker.js +7 -0
- package/public/pwa-192x192.png +0 -0
- package/public/pwa-512x512.png +0 -0
- package/public/pwa-64x64.png +0 -0
- package/public/registerSW.js +1 -0
- package/public/sw.js +1 -0
- package/public/ui-version.json +3 -0
- package/public/workbox-60d14903.js +1 -0
- package/bin/cli.js +0 -56
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export class PluginChannelManager {
|
|
2
|
+
constructor(logger) {
|
|
3
|
+
this.logger = logger;
|
|
4
|
+
this.clients = new Set();
|
|
5
|
+
}
|
|
6
|
+
register(workspaceId, reply) {
|
|
7
|
+
const connection = { workspaceId, reply };
|
|
8
|
+
this.clients.add(connection);
|
|
9
|
+
this.logger.debug({ workspaceId }, "Plugin SSE client connected");
|
|
10
|
+
let closed = false;
|
|
11
|
+
const close = () => {
|
|
12
|
+
if (closed)
|
|
13
|
+
return;
|
|
14
|
+
closed = true;
|
|
15
|
+
this.clients.delete(connection);
|
|
16
|
+
this.logger.debug({ workspaceId }, "Plugin SSE client disconnected");
|
|
17
|
+
};
|
|
18
|
+
return { close };
|
|
19
|
+
}
|
|
20
|
+
send(workspaceId, event) {
|
|
21
|
+
for (const client of this.clients) {
|
|
22
|
+
if (client.workspaceId !== workspaceId)
|
|
23
|
+
continue;
|
|
24
|
+
this.write(client.reply, event);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
broadcast(event) {
|
|
28
|
+
for (const client of this.clients) {
|
|
29
|
+
this.write(client.reply, event);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
write(reply, event) {
|
|
33
|
+
try {
|
|
34
|
+
reply.raw.write(`data: ${JSON.stringify(event)}\n\n`);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
this.logger.warn({ err: error }, "Failed to write plugin SSE event");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function handlePluginEvent(workspaceId, event, deps) {
|
|
2
|
+
switch (event.type) {
|
|
3
|
+
case "embeddedcowork.pong":
|
|
4
|
+
deps.logger.debug({ workspaceId, properties: event.properties }, "Plugin pong received");
|
|
5
|
+
return;
|
|
6
|
+
default:
|
|
7
|
+
deps.logger.debug({ workspaceId, eventType: event.type }, "Unhandled plugin event");
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export function buildPingEvent() {
|
|
11
|
+
return {
|
|
12
|
+
type: "embeddedcowork.ping",
|
|
13
|
+
properties: {
|
|
14
|
+
ts: Date.now(),
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export class VoiceModeManager {
|
|
2
|
+
constructor(options) {
|
|
3
|
+
this.options = options;
|
|
4
|
+
this.enabledConnectionsByInstance = new Map();
|
|
5
|
+
this.aggregateByInstance = new Map();
|
|
6
|
+
this.options.connections.subscribe((event) => {
|
|
7
|
+
if (event.type !== "disconnected")
|
|
8
|
+
return;
|
|
9
|
+
this.clearConnection(event.connection);
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
setEnabled(instanceId, connection, enabled) {
|
|
13
|
+
if (enabled && !this.options.connections.isConnected(connection)) {
|
|
14
|
+
this.options.logger.debug({ instanceId, clientId: connection.clientId, connectionId: connection.connectionId }, "Ignoring voice mode enable for disconnected client connection");
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
const key = getConnectionKey(connection);
|
|
18
|
+
const current = this.enabledConnectionsByInstance.get(instanceId) ?? new Set();
|
|
19
|
+
if (enabled) {
|
|
20
|
+
current.add(key);
|
|
21
|
+
this.enabledConnectionsByInstance.set(instanceId, current);
|
|
22
|
+
}
|
|
23
|
+
else if (current.delete(key)) {
|
|
24
|
+
if (current.size === 0) {
|
|
25
|
+
this.enabledConnectionsByInstance.delete(instanceId);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
this.enabledConnectionsByInstance.set(instanceId, current);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
this.options.logger.debug({ instanceId, clientId: connection.clientId, connectionId: connection.connectionId, enabled }, "Voice mode updated for client connection");
|
|
32
|
+
this.publishIfChanged(instanceId);
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
syncInstance(instanceId) {
|
|
36
|
+
this.options.channel.send(instanceId, buildVoiceModeEvent(this.isEnabled(instanceId)));
|
|
37
|
+
}
|
|
38
|
+
isEnabled(instanceId) {
|
|
39
|
+
return this.aggregateByInstance.get(instanceId) === true;
|
|
40
|
+
}
|
|
41
|
+
clearConnection(connection) {
|
|
42
|
+
const key = getConnectionKey(connection);
|
|
43
|
+
for (const [instanceId, enabledConnections] of Array.from(this.enabledConnectionsByInstance.entries())) {
|
|
44
|
+
if (!enabledConnections.delete(key))
|
|
45
|
+
continue;
|
|
46
|
+
if (enabledConnections.size === 0) {
|
|
47
|
+
this.enabledConnectionsByInstance.delete(instanceId);
|
|
48
|
+
}
|
|
49
|
+
this.publishIfChanged(instanceId);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
publishIfChanged(instanceId) {
|
|
53
|
+
const enabled = (this.enabledConnectionsByInstance.get(instanceId)?.size ?? 0) > 0;
|
|
54
|
+
const previous = this.aggregateByInstance.get(instanceId) === true;
|
|
55
|
+
if (enabled === previous)
|
|
56
|
+
return;
|
|
57
|
+
if (enabled) {
|
|
58
|
+
this.aggregateByInstance.set(instanceId, true);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
this.aggregateByInstance.delete(instanceId);
|
|
62
|
+
}
|
|
63
|
+
this.options.logger.debug({ instanceId, enabled }, "Broadcasting aggregate voice mode");
|
|
64
|
+
this.options.channel.send(instanceId, buildVoiceModeEvent(enabled));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function buildVoiceModeEvent(enabled) {
|
|
68
|
+
return {
|
|
69
|
+
type: "embeddedcowork.voiceMode",
|
|
70
|
+
properties: {
|
|
71
|
+
enabled,
|
|
72
|
+
formatVersion: "v1",
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function getConnectionKey(connection) {
|
|
77
|
+
return `${connection.clientId}:${connection.connectionId}`;
|
|
78
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { fetch } from "undici";
|
|
2
|
+
import { compareVersionStrings, stripTagPrefix } from "./release-monitor";
|
|
3
|
+
const DEFAULT_POLL_INTERVAL_MS = 15 * 60 * 1000;
|
|
4
|
+
export function startDevReleaseMonitor(options) {
|
|
5
|
+
let stopped = false;
|
|
6
|
+
let timer = null;
|
|
7
|
+
const pollIntervalMs = Number.isFinite(options.pollIntervalMs) && (options.pollIntervalMs ?? 0) > 0
|
|
8
|
+
? options.pollIntervalMs
|
|
9
|
+
: DEFAULT_POLL_INTERVAL_MS;
|
|
10
|
+
const refresh = async () => {
|
|
11
|
+
if (stopped)
|
|
12
|
+
return;
|
|
13
|
+
try {
|
|
14
|
+
const release = await fetchLatestPrerelease({
|
|
15
|
+
repo: options.repo,
|
|
16
|
+
currentVersion: options.currentVersion,
|
|
17
|
+
});
|
|
18
|
+
options.onUpdate(release);
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
options.logger.debug({ err: error }, "Failed to refresh dev prerelease information");
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
void refresh();
|
|
25
|
+
timer = setInterval(() => void refresh(), pollIntervalMs);
|
|
26
|
+
return {
|
|
27
|
+
stop() {
|
|
28
|
+
stopped = true;
|
|
29
|
+
if (timer) {
|
|
30
|
+
clearInterval(timer);
|
|
31
|
+
timer = null;
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
async function fetchLatestPrerelease(args) {
|
|
37
|
+
const normalizedRepo = args.repo.trim();
|
|
38
|
+
if (!/^[^/\s]+\/[^/\s]+$/.test(normalizedRepo)) {
|
|
39
|
+
throw new Error(`Invalid GitHub repo: ${args.repo}`);
|
|
40
|
+
}
|
|
41
|
+
const apiUrl = `https://api.github.com/repos/${normalizedRepo}/releases?per_page=20`;
|
|
42
|
+
const response = await fetch(apiUrl, {
|
|
43
|
+
headers: {
|
|
44
|
+
Accept: "application/vnd.github+json",
|
|
45
|
+
"User-Agent": "EmbeddedCowork-CLI",
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
throw new Error(`GitHub releases API responded with ${response.status}`);
|
|
50
|
+
}
|
|
51
|
+
const list = (await response.json());
|
|
52
|
+
const latest = list.find((r) => r && r.prerelease === true && r.draft !== true);
|
|
53
|
+
if (!latest) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
const tag = latest.tag_name || latest.name;
|
|
57
|
+
if (!tag) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const normalizedVersion = stripTagPrefix(tag);
|
|
61
|
+
if (!normalizedVersion) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
if (compareVersionStrings(normalizedVersion, args.currentVersion) <= 0) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
version: normalizedVersion,
|
|
69
|
+
tag,
|
|
70
|
+
url: latest.html_url ?? `https://github.com/${normalizedRepo}/releases/tag/${encodeURIComponent(tag)}`,
|
|
71
|
+
channel: "dev",
|
|
72
|
+
publishedAt: latest.published_at ?? latest.created_at,
|
|
73
|
+
notes: latest.body,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { fetch } from "undici";
|
|
2
|
+
const RELEASES_API_URL = "https://api.github.com/repos/vividcode-ai/EmbeddedCowork/releases/latest";
|
|
3
|
+
export function startReleaseMonitor(options) {
|
|
4
|
+
let stopped = false;
|
|
5
|
+
const refreshRelease = async () => {
|
|
6
|
+
if (stopped)
|
|
7
|
+
return;
|
|
8
|
+
try {
|
|
9
|
+
const release = await fetchLatestRelease(options);
|
|
10
|
+
options.onUpdate(release);
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
options.logger.warn({ err: error }, "Failed to refresh release information");
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
void refreshRelease();
|
|
17
|
+
return {
|
|
18
|
+
stop() {
|
|
19
|
+
stopped = true;
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export function compareVersionStrings(a, b) {
|
|
24
|
+
const left = parseVersion(a);
|
|
25
|
+
const right = parseVersion(b);
|
|
26
|
+
return compareVersions(left, right);
|
|
27
|
+
}
|
|
28
|
+
async function fetchLatestRelease(options) {
|
|
29
|
+
const response = await fetch(RELEASES_API_URL, {
|
|
30
|
+
headers: {
|
|
31
|
+
Accept: "application/vnd.github+json",
|
|
32
|
+
"User-Agent": "EmbeddedCowork-CLI",
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
throw new Error(`Release API responded with ${response.status}`);
|
|
37
|
+
}
|
|
38
|
+
const json = (await response.json());
|
|
39
|
+
const tagFromServer = json.tag_name || json.name;
|
|
40
|
+
if (!tagFromServer) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const normalizedVersion = stripTagPrefix(tagFromServer);
|
|
44
|
+
if (!normalizedVersion) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const current = parseVersion(options.currentVersion);
|
|
48
|
+
const remote = parseVersion(normalizedVersion);
|
|
49
|
+
if (compareVersions(remote, current) <= 0) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
version: normalizedVersion,
|
|
54
|
+
tag: tagFromServer,
|
|
55
|
+
url: json.html_url ?? `https://github.com/vividcode-ai/EmbeddedCowork/releases/tag/${encodeURIComponent(tagFromServer)}`,
|
|
56
|
+
channel: json.prerelease || normalizedVersion.includes("-") ? "dev" : "stable",
|
|
57
|
+
publishedAt: json.published_at ?? json.created_at,
|
|
58
|
+
notes: json.body,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export function stripTagPrefix(tag) {
|
|
62
|
+
if (!tag)
|
|
63
|
+
return null;
|
|
64
|
+
const trimmed = tag.trim();
|
|
65
|
+
if (!trimmed)
|
|
66
|
+
return null;
|
|
67
|
+
return trimmed.replace(/^v/i, "");
|
|
68
|
+
}
|
|
69
|
+
function parseVersion(value) {
|
|
70
|
+
const normalized = stripTagPrefix(value) ?? "0.0.0";
|
|
71
|
+
const dashIndex = normalized.indexOf("-");
|
|
72
|
+
const core = dashIndex >= 0 ? normalized.slice(0, dashIndex) : normalized;
|
|
73
|
+
const prerelease = dashIndex >= 0 ? normalized.slice(dashIndex + 1) : null;
|
|
74
|
+
const [major = 0, minor = 0, patch = 0] = core.split(".").map((segment) => {
|
|
75
|
+
const parsed = Number.parseInt(segment, 10);
|
|
76
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
77
|
+
});
|
|
78
|
+
return {
|
|
79
|
+
major,
|
|
80
|
+
minor,
|
|
81
|
+
patch,
|
|
82
|
+
prerelease,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function compareVersions(a, b) {
|
|
86
|
+
if (a.major !== b.major) {
|
|
87
|
+
return a.major > b.major ? 1 : -1;
|
|
88
|
+
}
|
|
89
|
+
if (a.minor !== b.minor) {
|
|
90
|
+
return a.minor > b.minor ? 1 : -1;
|
|
91
|
+
}
|
|
92
|
+
if (a.patch !== b.patch) {
|
|
93
|
+
return a.patch > b.patch ? 1 : -1;
|
|
94
|
+
}
|
|
95
|
+
const aPre = a.prerelease && a.prerelease.length > 0 ? a.prerelease : null;
|
|
96
|
+
const bPre = b.prerelease && b.prerelease.length > 0 ? b.prerelease : null;
|
|
97
|
+
if (aPre === bPre) {
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
100
|
+
if (!aPre) {
|
|
101
|
+
return 1;
|
|
102
|
+
}
|
|
103
|
+
if (!bPre) {
|
|
104
|
+
return -1;
|
|
105
|
+
}
|
|
106
|
+
return aPre.localeCompare(bPre);
|
|
107
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
function safeModuleDir(importMetaUrl) {
|
|
5
|
+
try {
|
|
6
|
+
return path.dirname(fileURLToPath(importMetaUrl));
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function firstExistingPath(candidates, predicate) {
|
|
13
|
+
for (const candidate of candidates) {
|
|
14
|
+
if (!candidate)
|
|
15
|
+
continue;
|
|
16
|
+
if (predicate(candidate)) {
|
|
17
|
+
return candidate;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
export function getPackagedDistDir() {
|
|
23
|
+
return path.dirname(process.execPath);
|
|
24
|
+
}
|
|
25
|
+
export function resolveServerPackageRoot(importMetaUrl) {
|
|
26
|
+
const moduleDir = safeModuleDir(importMetaUrl);
|
|
27
|
+
const configuredRoot = process.env.EMBEDDEDCOWORK_SERVER_ROOT?.trim();
|
|
28
|
+
const candidates = [
|
|
29
|
+
configuredRoot ? path.resolve(configuredRoot) : null,
|
|
30
|
+
moduleDir ? path.resolve(moduleDir, "..") : null,
|
|
31
|
+
path.resolve(getPackagedDistDir(), ".."),
|
|
32
|
+
];
|
|
33
|
+
return (firstExistingPath(candidates, (value) => fs.existsSync(path.join(value, "package.json"))) ??
|
|
34
|
+
candidates.find((value) => Boolean(value)) ??
|
|
35
|
+
process.cwd());
|
|
36
|
+
}
|
|
37
|
+
export function resolveServerPublicDir(importMetaUrl) {
|
|
38
|
+
const moduleDir = safeModuleDir(importMetaUrl);
|
|
39
|
+
const candidates = [moduleDir ? path.resolve(moduleDir, "../public") : null, path.join(resolveServerPackageRoot(importMetaUrl), "public")];
|
|
40
|
+
return firstExistingPath(candidates, (value) => fs.existsSync(value)) ?? candidates[candidates.length - 1];
|
|
41
|
+
}
|
|
42
|
+
export function resolveAuthTemplatePath(importMetaUrl, fileName) {
|
|
43
|
+
const moduleDir = safeModuleDir(importMetaUrl);
|
|
44
|
+
const distDir = getPackagedDistDir();
|
|
45
|
+
const candidates = [
|
|
46
|
+
moduleDir ? path.join(moduleDir, "auth-pages", fileName) : null,
|
|
47
|
+
path.join(distDir, "auth-pages", fileName),
|
|
48
|
+
path.join(distDir, "server", "routes", "auth-pages", fileName),
|
|
49
|
+
];
|
|
50
|
+
return firstExistingPath(candidates, (value) => fs.existsSync(value)) ?? candidates[0];
|
|
51
|
+
}
|
|
52
|
+
export function resolveOpencodeTemplateDir(importMetaUrl) {
|
|
53
|
+
const moduleDir = safeModuleDir(importMetaUrl);
|
|
54
|
+
const resourcesPath = process.resourcesPath;
|
|
55
|
+
const candidates = [
|
|
56
|
+
moduleDir ? path.resolve(moduleDir, "../../opencode-config") : null,
|
|
57
|
+
resourcesPath ? path.resolve(resourcesPath, "opencode-config") : null,
|
|
58
|
+
moduleDir ? path.resolve(moduleDir, "opencode-config") : null,
|
|
59
|
+
path.join(getPackagedDistDir(), "opencode-config"),
|
|
60
|
+
];
|
|
61
|
+
return firstExistingPath(candidates, (value) => fs.existsSync(value)) ?? candidates[candidates.length - 1];
|
|
62
|
+
}
|
|
63
|
+
export function readServerPackageVersion(importMetaUrl) {
|
|
64
|
+
const packageJsonPath = path.join(resolveServerPackageRoot(importMetaUrl), "package.json");
|
|
65
|
+
const parsed = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
66
|
+
return typeof parsed.version === "string" && parsed.version.trim().length > 0 ? parsed.version : "0.0.0";
|
|
67
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import { describe, it } from "node:test";
|
|
4
|
+
import { resolveNetworkAddresses, resolveRemoteAddresses } from "../network-addresses";
|
|
5
|
+
describe("resolveNetworkAddresses", () => {
|
|
6
|
+
it("preserves interface order among external addresses", () => {
|
|
7
|
+
const addresses = [
|
|
8
|
+
{ address: "172.24.0.1", family: "IPv4", internal: false },
|
|
9
|
+
{ address: "192.168.1.128", family: "IPv4", internal: false },
|
|
10
|
+
{ address: "10.0.0.8", family: 4, internal: false },
|
|
11
|
+
{ address: "127.0.0.1", family: "IPv4", internal: true },
|
|
12
|
+
{ address: "169.254.10.20", family: "IPv4", internal: false },
|
|
13
|
+
];
|
|
14
|
+
usingMockedNetworkInterfaces(addresses, () => {
|
|
15
|
+
const result = resolveNetworkAddresses({ host: "0.0.0.0", protocol: "https", port: 9898 });
|
|
16
|
+
assert.deepEqual(result.map((entry) => entry.ip), ["172.24.0.1", "192.168.1.128", "10.0.0.8", "169.254.10.20", "127.0.0.1"]);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
describe("resolveRemoteAddresses", () => {
|
|
21
|
+
it("keeps all external addresses user-visible while preferring non-link-local addresses for the primary URL", () => {
|
|
22
|
+
const addresses = [
|
|
23
|
+
{ address: "169.254.10.20", family: "IPv4", internal: false },
|
|
24
|
+
{ address: "192.168.1.128", family: "IPv4", internal: false },
|
|
25
|
+
{ address: "172.24.0.1", family: "IPv4", internal: false },
|
|
26
|
+
];
|
|
27
|
+
usingMockedNetworkInterfaces(addresses, () => {
|
|
28
|
+
const result = resolveRemoteAddresses({ host: "0.0.0.0", protocol: "https", port: 9898 });
|
|
29
|
+
assert.deepEqual(result.userVisible.map((entry) => entry.ip), ["192.168.1.128", "172.24.0.1", "169.254.10.20"]);
|
|
30
|
+
assert.equal(result.primaryRemoteUrl, "https://192.168.1.128:9898");
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
it("prefers private LAN addresses over public addresses", () => {
|
|
34
|
+
const addresses = [
|
|
35
|
+
{ address: "203.0.113.40", family: "IPv4", internal: false },
|
|
36
|
+
{ address: "192.168.1.128", family: "IPv4", internal: false },
|
|
37
|
+
{ address: "8.8.8.8", family: "IPv4", internal: false },
|
|
38
|
+
];
|
|
39
|
+
usingMockedNetworkInterfaces(addresses, () => {
|
|
40
|
+
const result = resolveRemoteAddresses({ host: "0.0.0.0", protocol: "https", port: 9898 });
|
|
41
|
+
assert.deepEqual(result.userVisible.map((entry) => entry.ip), ["192.168.1.128", "203.0.113.40", "8.8.8.8"]);
|
|
42
|
+
assert.equal(result.primaryRemoteUrl, "https://192.168.1.128:9898");
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
it("uses a public address when no private LAN address is available", () => {
|
|
46
|
+
const addresses = [
|
|
47
|
+
{ address: "169.254.10.20", family: "IPv4", internal: false },
|
|
48
|
+
{ address: "203.0.113.40", family: "IPv4", internal: false },
|
|
49
|
+
];
|
|
50
|
+
usingMockedNetworkInterfaces(addresses, () => {
|
|
51
|
+
const result = resolveRemoteAddresses({ host: "0.0.0.0", protocol: "https", port: 9898 });
|
|
52
|
+
assert.deepEqual(result.userVisible.map((entry) => entry.ip), ["203.0.113.40", "169.254.10.20"]);
|
|
53
|
+
assert.equal(result.primaryRemoteUrl, "https://203.0.113.40:9898");
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
function usingMockedNetworkInterfaces(addresses, callback) {
|
|
58
|
+
const original = os.networkInterfaces;
|
|
59
|
+
os.networkInterfaces = (() => ({
|
|
60
|
+
ethernet0: addresses,
|
|
61
|
+
}));
|
|
62
|
+
try {
|
|
63
|
+
callback();
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
os.networkInterfaces = original;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { after, afterEach, describe, it } from "node:test";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import http from "node:http";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { Agent, fetch } from "undici";
|
|
8
|
+
import { RemoteProxySessionManager } from "../remote-proxy";
|
|
9
|
+
import { resolveHttpsOptions } from "../tls";
|
|
10
|
+
const sharedTempDir = fs.mkdtempSync(path.join(os.tmpdir(), "embeddedcowork-remote-proxy-test-"));
|
|
11
|
+
const sharedTls = resolveHttpsOptions({
|
|
12
|
+
enabled: true,
|
|
13
|
+
configDir: sharedTempDir,
|
|
14
|
+
host: "127.0.0.1",
|
|
15
|
+
logger: createStubLogger(),
|
|
16
|
+
});
|
|
17
|
+
if (!sharedTls) {
|
|
18
|
+
throw new Error("Failed to generate HTTPS options for remote proxy tests");
|
|
19
|
+
}
|
|
20
|
+
const sharedHttpsOptions = sharedTls.httpsOptions;
|
|
21
|
+
const httpsDispatcher = new Agent({ connect: { rejectUnauthorized: false } });
|
|
22
|
+
const managers = new Set();
|
|
23
|
+
afterEach(async () => {
|
|
24
|
+
for (const manager of managers) {
|
|
25
|
+
await disposeManager(manager);
|
|
26
|
+
}
|
|
27
|
+
managers.clear();
|
|
28
|
+
});
|
|
29
|
+
after(() => {
|
|
30
|
+
fs.rmSync(sharedTempDir, { recursive: true, force: true });
|
|
31
|
+
httpsDispatcher.close().catch(() => { });
|
|
32
|
+
});
|
|
33
|
+
describe("RemoteProxySessionManager", () => {
|
|
34
|
+
it("blocks proxying before activation and keeps bootstrap tokens scoped per session", async () => {
|
|
35
|
+
await withUpstreamServer(async (upstreamBaseUrl) => {
|
|
36
|
+
const manager = createSessionManager();
|
|
37
|
+
const session1 = await createSession(manager, `${upstreamBaseUrl}/base`);
|
|
38
|
+
const session2 = await createSession(manager, `${upstreamBaseUrl}/base`);
|
|
39
|
+
const blocked = await proxyFetch(`${session1.proxyOrigin}/status`);
|
|
40
|
+
assert.equal(blocked.status, 403);
|
|
41
|
+
const wrongTokenResponse = await proxyFetch(`${session1.proxyOrigin}/__embeddedcowork/api/auth/token`, {
|
|
42
|
+
method: "POST",
|
|
43
|
+
headers: { "content-type": "application/json" },
|
|
44
|
+
body: JSON.stringify({ token: session2.token }),
|
|
45
|
+
});
|
|
46
|
+
assert.equal(wrongTokenResponse.status, 401);
|
|
47
|
+
assert.equal(await activateSession(session1), true);
|
|
48
|
+
assert.equal(await activateSession(session2), true);
|
|
49
|
+
}, (req, res) => {
|
|
50
|
+
res.writeHead(200, { "content-type": "text/plain" });
|
|
51
|
+
res.end(req.url ?? "");
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
it("preserves remote base paths and rewrites same-origin redirects to the local proxy origin", async () => {
|
|
55
|
+
await withUpstreamServer(async (upstreamBaseUrl) => {
|
|
56
|
+
const manager = createSessionManager();
|
|
57
|
+
const session = await createSession(manager, `${upstreamBaseUrl}/base`);
|
|
58
|
+
await activateSession(session);
|
|
59
|
+
const apiResponse = await proxyFetch(`${session.proxyOrigin}/api/auth/status?foo=bar`);
|
|
60
|
+
assert.equal(apiResponse.status, 200);
|
|
61
|
+
assert.equal(await apiResponse.text(), "/base/api/auth/status?foo=bar");
|
|
62
|
+
const redirectResponse = await proxyFetch(`${session.proxyOrigin}/redirect`, { redirect: "manual" });
|
|
63
|
+
assert.equal(redirectResponse.status, 302);
|
|
64
|
+
assert.equal(redirectResponse.headers.get("location"), `${session.proxyOrigin}/base/after?ok=1`);
|
|
65
|
+
}, (req, res) => {
|
|
66
|
+
const requestUrl = req.url ?? "";
|
|
67
|
+
if (requestUrl === "/base/redirect") {
|
|
68
|
+
res.writeHead(302, { location: "/base/after?ok=1" });
|
|
69
|
+
res.end();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
res.writeHead(200, { "content-type": "text/plain" });
|
|
73
|
+
res.end(requestUrl);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
it("rewrites set-cookie names for the proxy and restores cookie names on proxied requests", async () => {
|
|
77
|
+
await withUpstreamServer(async (upstreamBaseUrl) => {
|
|
78
|
+
const manager = createSessionManager();
|
|
79
|
+
const session = await createSession(manager, `${upstreamBaseUrl}/base`);
|
|
80
|
+
await activateSession(session);
|
|
81
|
+
const loginResponse = await proxyFetch(`${session.proxyOrigin}/login`);
|
|
82
|
+
assert.equal(loginResponse.status, 200);
|
|
83
|
+
const setCookie = getSetCookie(loginResponse)[0];
|
|
84
|
+
assert.match(setCookie, /^cnrp_[0-9a-f]+_session=abc123/i);
|
|
85
|
+
assert.doesNotMatch(setCookie, /domain=/i);
|
|
86
|
+
const cookieHeader = setCookie.split(";", 1)[0];
|
|
87
|
+
const whoamiResponse = await proxyFetch(`${session.proxyOrigin}/whoami`, {
|
|
88
|
+
headers: { cookie: cookieHeader },
|
|
89
|
+
});
|
|
90
|
+
assert.equal(await whoamiResponse.text(), "session=abc123");
|
|
91
|
+
}, (req, res) => {
|
|
92
|
+
const requestUrl = req.url ?? "";
|
|
93
|
+
if (requestUrl === "/base/login") {
|
|
94
|
+
res.writeHead(200, {
|
|
95
|
+
"content-type": "text/plain",
|
|
96
|
+
"set-cookie": "session=abc123; Path=/; Secure; HttpOnly; Domain=127.0.0.1",
|
|
97
|
+
});
|
|
98
|
+
res.end("ok");
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (requestUrl === "/base/whoami") {
|
|
102
|
+
res.writeHead(200, { "content-type": "text/plain" });
|
|
103
|
+
res.end(req.headers.cookie ?? "");
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
res.writeHead(404, { "content-type": "text/plain" });
|
|
107
|
+
res.end(requestUrl);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
it("supports explicit deletion and idle cleanup of sessions", async () => {
|
|
111
|
+
await withUpstreamServer(async (upstreamBaseUrl) => {
|
|
112
|
+
const manager = createSessionManager();
|
|
113
|
+
const session = await createSession(manager, `${upstreamBaseUrl}/base`);
|
|
114
|
+
assert.equal(await manager.deleteSession(session.sessionId), true);
|
|
115
|
+
assert.equal(await manager.deleteSession(session.sessionId), false);
|
|
116
|
+
const session3 = await createSession(manager, `${upstreamBaseUrl}/base`);
|
|
117
|
+
const internalSessions = manager.sessions;
|
|
118
|
+
const internalCleanup = manager.cleanupExpiredSessions;
|
|
119
|
+
internalSessions.get(session3.sessionId).lastAccessAt = Date.now() - 31 * 60000;
|
|
120
|
+
await internalCleanup.call(manager);
|
|
121
|
+
assert.equal(internalSessions.has(session3.sessionId), false);
|
|
122
|
+
assert.equal(await manager.deleteSession(session3.sessionId), false);
|
|
123
|
+
}, (_req, res) => {
|
|
124
|
+
res.writeHead(200, { "content-type": "text/plain" });
|
|
125
|
+
res.end("ok");
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
function createSessionManager() {
|
|
130
|
+
const manager = new RemoteProxySessionManager({
|
|
131
|
+
authManager: {
|
|
132
|
+
isLoopbackRequest: () => true,
|
|
133
|
+
},
|
|
134
|
+
logger: createStubLogger(),
|
|
135
|
+
httpsOptions: sharedHttpsOptions,
|
|
136
|
+
});
|
|
137
|
+
managers.add(manager);
|
|
138
|
+
return manager;
|
|
139
|
+
}
|
|
140
|
+
async function createSession(manager, baseUrl) {
|
|
141
|
+
const created = await manager.createSession(baseUrl, false);
|
|
142
|
+
const windowUrl = new URL(created.windowUrl);
|
|
143
|
+
return {
|
|
144
|
+
sessionId: created.sessionId,
|
|
145
|
+
windowUrl,
|
|
146
|
+
proxyOrigin: windowUrl.origin,
|
|
147
|
+
token: decodeURIComponent(windowUrl.hash.replace(/^#/, "")),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
async function activateSession(session) {
|
|
151
|
+
const response = await proxyFetch(`${session.proxyOrigin}/__embeddedcowork/api/auth/token`, {
|
|
152
|
+
method: "POST",
|
|
153
|
+
headers: { "content-type": "application/json" },
|
|
154
|
+
body: JSON.stringify({ token: session.token }),
|
|
155
|
+
});
|
|
156
|
+
if (!response.ok) {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
const body = (await response.json());
|
|
160
|
+
return body.ok === true;
|
|
161
|
+
}
|
|
162
|
+
function getSetCookie(response) {
|
|
163
|
+
const values = response.headers.getSetCookie?.();
|
|
164
|
+
if (Array.isArray(values) && values.length > 0) {
|
|
165
|
+
return values;
|
|
166
|
+
}
|
|
167
|
+
const fallback = response.headers.get("set-cookie");
|
|
168
|
+
return fallback ? [fallback] : [];
|
|
169
|
+
}
|
|
170
|
+
async function proxyFetch(url, init) {
|
|
171
|
+
return fetch(url, { dispatcher: httpsDispatcher, ...init });
|
|
172
|
+
}
|
|
173
|
+
async function disposeManager(manager) {
|
|
174
|
+
const sessions = Array.from(manager.sessions.keys());
|
|
175
|
+
for (const sessionId of sessions) {
|
|
176
|
+
await manager.deleteSession(sessionId);
|
|
177
|
+
}
|
|
178
|
+
clearInterval(manager.cleanupTimer);
|
|
179
|
+
}
|
|
180
|
+
async function withUpstreamServer(callback, handler) {
|
|
181
|
+
const server = http.createServer(handler);
|
|
182
|
+
await new Promise((resolve) => server.listen(0, "127.0.0.1", () => resolve()));
|
|
183
|
+
try {
|
|
184
|
+
const address = server.address();
|
|
185
|
+
if (!address || typeof address === "string") {
|
|
186
|
+
throw new Error("Failed to resolve upstream server address");
|
|
187
|
+
}
|
|
188
|
+
await callback(`http://127.0.0.1:${address.port}`);
|
|
189
|
+
}
|
|
190
|
+
finally {
|
|
191
|
+
await new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function createStubLogger() {
|
|
195
|
+
const logger = {
|
|
196
|
+
info() { },
|
|
197
|
+
warn() { },
|
|
198
|
+
error() { },
|
|
199
|
+
child() {
|
|
200
|
+
return logger;
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
return logger;
|
|
204
|
+
}
|