@vividcodeai/embeddedcowork 0.0.22 → 0.0.24
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/releases/rollback.js +54 -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-Uu8GvqIU.js +2 -0
- package/public/assets/DiffToolbar-BgdJy7ap.js +1 -0
- package/public/assets/EmbeddedCowork-Icon-DSw5nKk7.png +0 -0
- package/public/assets/FilesTab-BlKXggYW.js +2 -0
- package/public/assets/GitChangesTab-DRAyGtTx.js +2 -0
- package/public/assets/SplitFilePanel-DR88B4Vy.js +1 -0
- package/public/assets/StatusTab-BS7K80gj.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-BusrfhOv.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-BDYT5S-Q.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/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-DaSE45SW.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-BKMyzTSR.js +1 -0
- package/public/assets/index-BWhMWtCP.js +2 -0
- package/public/assets/index-Bsb7z85O.js +1 -0
- package/public/assets/index-By8QJmOa.js +1 -0
- package/public/assets/index-CLSJ4cO9.js +1 -0
- package/public/assets/index-DBPzV8JQ.js +1 -0
- package/public/assets/index-DhOdialG.js +1 -0
- package/public/assets/index-DlobuxMv.js +1 -0
- package/public/assets/index-QdhU2jY3.css +1 -0
- package/public/assets/index-WYKVZxn4.js +1 -0
- package/public/assets/index-gBWCTkMr.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-C4yc63Qi.js +2 -0
- package/public/assets/loading-CugGjKDZ.css +1 -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-y4WbiJBN.js +53 -0
- package/public/assets/make-CHLpvVh8.js +1 -0
- package/public/assets/markdown-Cvjx9yec.js +1 -0
- package/public/assets/markdown-DmuP22dE.js +58 -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-OXO07Mos.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-_6sSKM36.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-ChcZ5m6V.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-BkweRxZy.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-DDrpiK4b.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,180 @@
|
|
|
1
|
+
import { fetch } from "undici";
|
|
2
|
+
import { Agent as UndiciAgent } from "undici";
|
|
3
|
+
const INSTANCE_HOST = "127.0.0.1";
|
|
4
|
+
const STREAM_AGENT = new UndiciAgent({ bodyTimeout: 0, headersTimeout: 0 });
|
|
5
|
+
const RECONNECT_DELAY_MS = 1000;
|
|
6
|
+
export class InstanceEventBridge {
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.options = options;
|
|
9
|
+
this.streams = new Map();
|
|
10
|
+
const bus = this.options.eventBus;
|
|
11
|
+
bus.on("workspace.started", (event) => this.startStream(event.workspace.id));
|
|
12
|
+
bus.on("workspace.stopped", (event) => this.stopStream(event.workspaceId, "workspace stopped"));
|
|
13
|
+
bus.on("workspace.error", (event) => this.stopStream(event.workspace.id, "workspace error"));
|
|
14
|
+
}
|
|
15
|
+
shutdown() {
|
|
16
|
+
for (const [id, active] of this.streams) {
|
|
17
|
+
active.controller.abort();
|
|
18
|
+
this.publishStatus(id, "disconnected");
|
|
19
|
+
}
|
|
20
|
+
this.streams.clear();
|
|
21
|
+
}
|
|
22
|
+
startStream(workspaceId) {
|
|
23
|
+
if (this.streams.has(workspaceId)) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const controller = new AbortController();
|
|
27
|
+
const task = this.runStream(workspaceId, controller.signal)
|
|
28
|
+
.catch((error) => {
|
|
29
|
+
if (!controller.signal.aborted) {
|
|
30
|
+
this.options.logger.warn({ workspaceId, err: error }, "Instance event stream failed");
|
|
31
|
+
this.publishStatus(workspaceId, "error", error instanceof Error ? error.message : String(error));
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
.finally(() => {
|
|
35
|
+
const active = this.streams.get(workspaceId);
|
|
36
|
+
if (active?.controller === controller) {
|
|
37
|
+
this.streams.delete(workspaceId);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
this.streams.set(workspaceId, { controller, task });
|
|
41
|
+
}
|
|
42
|
+
stopStream(workspaceId, reason) {
|
|
43
|
+
const active = this.streams.get(workspaceId);
|
|
44
|
+
if (!active) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
active.controller.abort();
|
|
48
|
+
this.streams.delete(workspaceId);
|
|
49
|
+
this.publishStatus(workspaceId, "disconnected", reason);
|
|
50
|
+
}
|
|
51
|
+
async runStream(workspaceId, signal) {
|
|
52
|
+
while (!signal.aborted) {
|
|
53
|
+
const port = this.options.workspaceManager.getInstancePort(workspaceId);
|
|
54
|
+
if (!port) {
|
|
55
|
+
await this.delay(RECONNECT_DELAY_MS, signal);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
this.publishStatus(workspaceId, "connecting");
|
|
59
|
+
try {
|
|
60
|
+
await this.consumeStream(workspaceId, port, signal);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
if (signal.aborted) {
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
this.options.logger.warn({ workspaceId, err: error }, "Instance event stream disconnected");
|
|
67
|
+
this.publishStatus(workspaceId, "error", error instanceof Error ? error.message : String(error));
|
|
68
|
+
await this.delay(RECONNECT_DELAY_MS, signal);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async consumeStream(workspaceId, port, signal) {
|
|
73
|
+
const url = `http://${INSTANCE_HOST}:${port}/global/event`;
|
|
74
|
+
const headers = { Accept: "text/event-stream" };
|
|
75
|
+
const authHeader = this.options.workspaceManager.getInstanceAuthorizationHeader(workspaceId);
|
|
76
|
+
if (authHeader) {
|
|
77
|
+
headers["Authorization"] = authHeader;
|
|
78
|
+
}
|
|
79
|
+
const response = await fetch(url, {
|
|
80
|
+
headers,
|
|
81
|
+
signal,
|
|
82
|
+
dispatcher: STREAM_AGENT,
|
|
83
|
+
});
|
|
84
|
+
if (!response.ok || !response.body) {
|
|
85
|
+
throw new Error(`Instance event stream unavailable (${response.status})`);
|
|
86
|
+
}
|
|
87
|
+
this.publishStatus(workspaceId, "connected");
|
|
88
|
+
const reader = response.body.getReader();
|
|
89
|
+
const decoder = new TextDecoder();
|
|
90
|
+
let buffer = "";
|
|
91
|
+
while (!signal.aborted) {
|
|
92
|
+
const { done, value } = await reader.read();
|
|
93
|
+
if (done || !value) {
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
buffer += decoder.decode(value, { stream: true });
|
|
97
|
+
buffer = this.flushEvents(buffer, workspaceId);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
flushEvents(buffer, workspaceId) {
|
|
101
|
+
let separatorIndex = buffer.indexOf("\n\n");
|
|
102
|
+
while (separatorIndex >= 0) {
|
|
103
|
+
const chunk = buffer.slice(0, separatorIndex);
|
|
104
|
+
buffer = buffer.slice(separatorIndex + 2);
|
|
105
|
+
this.processChunk(chunk, workspaceId);
|
|
106
|
+
separatorIndex = buffer.indexOf("\n\n");
|
|
107
|
+
}
|
|
108
|
+
return buffer;
|
|
109
|
+
}
|
|
110
|
+
processChunk(chunk, workspaceId) {
|
|
111
|
+
const lines = chunk.split(/\r?\n/);
|
|
112
|
+
const dataLines = [];
|
|
113
|
+
for (const line of lines) {
|
|
114
|
+
if (line.startsWith(":")) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (line.startsWith("data:")) {
|
|
118
|
+
dataLines.push(line.slice(5).trimStart());
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (dataLines.length === 0) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const payload = dataLines.join("\n").trim();
|
|
125
|
+
if (!payload) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
const parsed = JSON.parse(payload);
|
|
130
|
+
if (!parsed || typeof parsed !== "object") {
|
|
131
|
+
this.options.logger.warn({ workspaceId, chunk: payload }, "Dropped malformed instance event");
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
// OpenCode SSE payload shapes vary across versions.
|
|
135
|
+
// Common variants:
|
|
136
|
+
// - { type, properties, ... }
|
|
137
|
+
// - { payload: { type, properties, ... }, directory: "/abs/path" }
|
|
138
|
+
// - { payload: { type, properties, ... } }
|
|
139
|
+
const base = parsed.payload && typeof parsed.payload === "object" ? parsed.payload : parsed;
|
|
140
|
+
const event = base && typeof base === "object" ? { ...base } : null;
|
|
141
|
+
// Attach directory when available (don't overwrite if already present).
|
|
142
|
+
if (event && !event.directory && typeof parsed.directory === "string") {
|
|
143
|
+
;
|
|
144
|
+
event.directory = parsed.directory;
|
|
145
|
+
}
|
|
146
|
+
if (!event || typeof event.type !== "string") {
|
|
147
|
+
this.options.logger.warn({ workspaceId, chunk: payload }, "Dropped malformed instance event");
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
this.options.logger.debug({ workspaceId, eventType: event.type }, "Instance SSE event received");
|
|
151
|
+
if (this.options.logger.isLevelEnabled("trace")) {
|
|
152
|
+
this.options.logger.trace({ workspaceId, event }, "Instance SSE event payload");
|
|
153
|
+
}
|
|
154
|
+
this.options.eventBus.publish({ type: "instance.event", instanceId: workspaceId, event });
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
this.options.logger.warn({ workspaceId, chunk: payload, err: error }, "Failed to parse instance SSE payload");
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
publishStatus(instanceId, status, reason) {
|
|
161
|
+
this.options.logger.debug({ instanceId, status, reason }, "Instance SSE status updated");
|
|
162
|
+
this.options.eventBus.publish({ type: "instance.eventStatus", instanceId, status, reason });
|
|
163
|
+
}
|
|
164
|
+
delay(duration, signal) {
|
|
165
|
+
if (duration <= 0) {
|
|
166
|
+
return Promise.resolve();
|
|
167
|
+
}
|
|
168
|
+
return new Promise((resolve) => {
|
|
169
|
+
const timeout = setTimeout(() => {
|
|
170
|
+
signal.removeEventListener("abort", onAbort);
|
|
171
|
+
resolve();
|
|
172
|
+
}, duration);
|
|
173
|
+
const onAbort = () => {
|
|
174
|
+
clearTimeout(timeout);
|
|
175
|
+
resolve();
|
|
176
|
+
};
|
|
177
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import os from "os";
|
|
3
|
+
import { createHash } from "crypto";
|
|
4
|
+
import { existsSync, mkdirSync } from "fs";
|
|
5
|
+
import { execSync, spawnSync } from "child_process";
|
|
6
|
+
import { connect } from "net";
|
|
7
|
+
import { FileSystemBrowser } from "../filesystem/browser";
|
|
8
|
+
import { searchWorkspaceFiles } from "../filesystem/search";
|
|
9
|
+
import { clearWorkspaceSearchCache } from "../filesystem/search-cache";
|
|
10
|
+
import { WorkspaceRuntime } from "./runtime";
|
|
11
|
+
import { getOpencodeConfigDir } from "../opencode-config.js";
|
|
12
|
+
import { BIN_DIR, BINARY_NAME, triggerBinaryDownload, resolveBinaryPathFromUserShell } from "../opencode-paths";
|
|
13
|
+
import { buildOpencodeBasicAuthHeader, DEFAULT_OPENCODE_USERNAME, generateOpencodeServerPassword, OPENCODE_SERVER_PASSWORD_ENV, OPENCODE_SERVER_USERNAME_ENV, } from "./opencode-auth";
|
|
14
|
+
const STARTUP_STABILITY_DELAY_MS = 1500;
|
|
15
|
+
export class WorkspaceManager {
|
|
16
|
+
constructor(options) {
|
|
17
|
+
this.options = options;
|
|
18
|
+
this.workspaces = new Map();
|
|
19
|
+
this.opencodeAuth = new Map();
|
|
20
|
+
this.runtime = new WorkspaceRuntime(this.options.eventBus, this.options.logger);
|
|
21
|
+
this.opencodeConfigDir = getOpencodeConfigDir();
|
|
22
|
+
}
|
|
23
|
+
list() {
|
|
24
|
+
return Array.from(this.workspaces.values());
|
|
25
|
+
}
|
|
26
|
+
get(id) {
|
|
27
|
+
return this.workspaces.get(id);
|
|
28
|
+
}
|
|
29
|
+
getInstancePort(id) {
|
|
30
|
+
return this.workspaces.get(id)?.port;
|
|
31
|
+
}
|
|
32
|
+
getInstanceAuthorizationHeader(id) {
|
|
33
|
+
return this.opencodeAuth.get(id)?.authorization;
|
|
34
|
+
}
|
|
35
|
+
listFiles(workspaceId, relativePath = ".") {
|
|
36
|
+
const workspace = this.requireWorkspace(workspaceId);
|
|
37
|
+
const browser = new FileSystemBrowser({ rootDir: workspace.path });
|
|
38
|
+
return browser.list(relativePath);
|
|
39
|
+
}
|
|
40
|
+
searchFiles(workspaceId, query, options) {
|
|
41
|
+
const workspace = this.requireWorkspace(workspaceId);
|
|
42
|
+
return searchWorkspaceFiles(workspace.path, query, options);
|
|
43
|
+
}
|
|
44
|
+
readFile(workspaceId, relativePath) {
|
|
45
|
+
const workspace = this.requireWorkspace(workspaceId);
|
|
46
|
+
const browser = new FileSystemBrowser({ rootDir: workspace.path });
|
|
47
|
+
const contents = browser.readFile(relativePath);
|
|
48
|
+
return {
|
|
49
|
+
workspaceId,
|
|
50
|
+
relativePath,
|
|
51
|
+
contents,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
writeFile(workspaceId, relativePath, contents) {
|
|
55
|
+
const workspace = this.requireWorkspace(workspaceId);
|
|
56
|
+
const browser = new FileSystemBrowser({ rootDir: workspace.path });
|
|
57
|
+
browser.writeFile(relativePath, contents);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Compute a deterministic workspace ID based on the folder's content identity.
|
|
61
|
+
*
|
|
62
|
+
* - If the folder is inside a git repository: ID is derived from the git root
|
|
63
|
+
* commit hash + the folder's relative path from git root. This ensures the
|
|
64
|
+
* ID stays the same when the folder is moved/renamed within the repo.
|
|
65
|
+
* - Otherwise: fall back to hashing the absolute path.
|
|
66
|
+
*/
|
|
67
|
+
computeWorkspaceId(folder) {
|
|
68
|
+
const absPath = path.resolve(folder);
|
|
69
|
+
// Attempt git-based identity
|
|
70
|
+
try {
|
|
71
|
+
const topLevel = execSync("git rev-parse --show-toplevel", { cwd: absPath, encoding: "utf8", timeout: 5000 }).trim();
|
|
72
|
+
const rootHash = execSync("git rev-list --max-parents=0 HEAD", { cwd: absPath, encoding: "utf8", timeout: 5000 })
|
|
73
|
+
.trim()
|
|
74
|
+
.split("\n")
|
|
75
|
+
.filter(Boolean)
|
|
76
|
+
.sort()[0];
|
|
77
|
+
if (rootHash) {
|
|
78
|
+
const relPath = path.relative(topLevel, absPath) || "/";
|
|
79
|
+
return createHash("sha256").update(`${rootHash}:${relPath}`).digest("hex").slice(0, 12);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Not a git repo or git unavailable — fall through
|
|
84
|
+
}
|
|
85
|
+
// Fallback: use absolute path hash
|
|
86
|
+
return createHash("sha256").update(absPath).digest("hex").slice(0, 12);
|
|
87
|
+
}
|
|
88
|
+
async create(folder, name) {
|
|
89
|
+
const workspacePath = path.isAbsolute(folder) ? folder : path.resolve(this.options.rootDir, folder);
|
|
90
|
+
const id = this.computeWorkspaceId(workspacePath);
|
|
91
|
+
const binary = this.options.binaryResolver.resolveDefault();
|
|
92
|
+
let resolvedBinaryPath = this.resolveBinaryPath(binary.path);
|
|
93
|
+
clearWorkspaceSearchCache(workspacePath);
|
|
94
|
+
this.options.logger.info({ workspaceId: id, folder: workspacePath, binary: resolvedBinaryPath }, "Creating workspace");
|
|
95
|
+
const proxyPath = `/workspaces/${id}/worktrees/root/instance`;
|
|
96
|
+
const descriptor = {
|
|
97
|
+
id,
|
|
98
|
+
path: workspacePath,
|
|
99
|
+
name,
|
|
100
|
+
status: "starting",
|
|
101
|
+
proxyPath,
|
|
102
|
+
binaryId: resolvedBinaryPath,
|
|
103
|
+
binaryLabel: binary.label,
|
|
104
|
+
binaryVersion: binary.version,
|
|
105
|
+
createdAt: new Date().toISOString(),
|
|
106
|
+
updatedAt: new Date().toISOString(),
|
|
107
|
+
};
|
|
108
|
+
this.workspaces.set(id, descriptor);
|
|
109
|
+
this.options.eventBus.publish({ type: "workspace.created", workspace: descriptor });
|
|
110
|
+
if (!existsSync(resolvedBinaryPath)) {
|
|
111
|
+
this.options.logger.info({ workspaceId: id, binary: resolvedBinaryPath }, "Binary not found, waiting for download");
|
|
112
|
+
await triggerBinaryDownload(this.options.logger);
|
|
113
|
+
resolvedBinaryPath = this.resolveBinaryPath(binary.path);
|
|
114
|
+
if (!existsSync(resolvedBinaryPath)) {
|
|
115
|
+
throw new Error(`OpenCode binary still not found after auto-download: ${resolvedBinaryPath}`);
|
|
116
|
+
}
|
|
117
|
+
descriptor.binaryId = resolvedBinaryPath;
|
|
118
|
+
descriptor.binaryLabel = "auto-downloaded";
|
|
119
|
+
this.options.logger.info({ workspaceId: id, path: resolvedBinaryPath }, "Binary ready after auto-download");
|
|
120
|
+
}
|
|
121
|
+
const serverConfig = this.options.settings.getOwner("config", "server");
|
|
122
|
+
const envVars = serverConfig?.environmentVariables;
|
|
123
|
+
const userEnvironment = envVars && typeof envVars === "object" && !Array.isArray(envVars) ? envVars : {};
|
|
124
|
+
const opencodeUsername = DEFAULT_OPENCODE_USERNAME;
|
|
125
|
+
const opencodePassword = generateOpencodeServerPassword();
|
|
126
|
+
const authorization = buildOpencodeBasicAuthHeader({ username: opencodeUsername, password: opencodePassword });
|
|
127
|
+
if (!authorization) {
|
|
128
|
+
throw new Error("Failed to build OpenCode auth header");
|
|
129
|
+
}
|
|
130
|
+
this.opencodeAuth.set(id, { username: opencodeUsername, password: opencodePassword, authorization });
|
|
131
|
+
// Determine session database path based on user preference.
|
|
132
|
+
// - "project": place DB inside the workspace folder so it travels with the project
|
|
133
|
+
// - "global": place DB in a centralized directory keyed by workspaceId
|
|
134
|
+
const sessionStorageMode = serverConfig?.sessionStorageMode ?? "project";
|
|
135
|
+
let dbPath;
|
|
136
|
+
if (sessionStorageMode === "global") {
|
|
137
|
+
dbPath = path.join(os.homedir(), ".embeddedcowork", "session-data", `${id}.db`);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
dbPath = path.join(workspacePath, ".embeddedcowork", "session", "data.db");
|
|
141
|
+
}
|
|
142
|
+
// Ensure parent directory exists before opencode starts; Database(path, { create: true })
|
|
143
|
+
// only creates the file, not intermediate directories.
|
|
144
|
+
mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
145
|
+
const environment = {
|
|
146
|
+
...userEnvironment,
|
|
147
|
+
OPENCODE_CONFIG_DIR: this.opencodeConfigDir,
|
|
148
|
+
OPENCODE_DB: dbPath,
|
|
149
|
+
EMBEDDEDCOWORK_INSTANCE_ID: id,
|
|
150
|
+
EMBEDDEDCOWORK_BASE_URL: this.options.getServerBaseUrl(),
|
|
151
|
+
...(this.options.nodeExtraCaCertsPath ? { NODE_EXTRA_CA_CERTS: this.options.nodeExtraCaCertsPath } : {}),
|
|
152
|
+
[OPENCODE_SERVER_USERNAME_ENV]: opencodeUsername,
|
|
153
|
+
[OPENCODE_SERVER_PASSWORD_ENV]: opencodePassword,
|
|
154
|
+
};
|
|
155
|
+
const logLevel = serverConfig?.logLevel;
|
|
156
|
+
try {
|
|
157
|
+
const { pid, port, exitPromise, getLastOutput } = await this.runtime.launch({
|
|
158
|
+
workspaceId: id,
|
|
159
|
+
folder: workspacePath,
|
|
160
|
+
binaryPath: resolvedBinaryPath,
|
|
161
|
+
environment,
|
|
162
|
+
logLevel,
|
|
163
|
+
onExit: (info) => this.handleProcessExit(info.workspaceId, info),
|
|
164
|
+
});
|
|
165
|
+
const runtimeVersion = await this.waitForWorkspaceReadiness({ workspaceId: id, port, exitPromise, getLastOutput });
|
|
166
|
+
if (runtimeVersion) {
|
|
167
|
+
descriptor.binaryVersion = runtimeVersion;
|
|
168
|
+
}
|
|
169
|
+
descriptor.pid = pid;
|
|
170
|
+
descriptor.port = port;
|
|
171
|
+
descriptor.status = "ready";
|
|
172
|
+
descriptor.updatedAt = new Date().toISOString();
|
|
173
|
+
this.options.eventBus.publish({ type: "workspace.started", workspace: descriptor });
|
|
174
|
+
this.options.logger.info({ workspaceId: id, port }, "Workspace ready");
|
|
175
|
+
return descriptor;
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
descriptor.status = "error";
|
|
179
|
+
descriptor.error = error instanceof Error ? error.message : String(error);
|
|
180
|
+
descriptor.updatedAt = new Date().toISOString();
|
|
181
|
+
this.options.eventBus.publish({ type: "workspace.error", workspace: descriptor });
|
|
182
|
+
this.options.logger.error({ workspaceId: id, err: error }, "Workspace failed to start");
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
async delete(id) {
|
|
187
|
+
const workspace = this.workspaces.get(id);
|
|
188
|
+
if (!workspace)
|
|
189
|
+
return undefined;
|
|
190
|
+
this.options.logger.info({ workspaceId: id }, "Stopping workspace");
|
|
191
|
+
const wasRunning = Boolean(workspace.pid);
|
|
192
|
+
if (wasRunning) {
|
|
193
|
+
await this.runtime.stop(id).catch((error) => {
|
|
194
|
+
this.options.logger.warn({ workspaceId: id, err: error }, "Failed to stop workspace process cleanly");
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
this.workspaces.delete(id);
|
|
198
|
+
this.opencodeAuth.delete(id);
|
|
199
|
+
clearWorkspaceSearchCache(workspace.path);
|
|
200
|
+
if (!wasRunning) {
|
|
201
|
+
this.options.eventBus.publish({ type: "workspace.stopped", workspaceId: id });
|
|
202
|
+
}
|
|
203
|
+
return workspace;
|
|
204
|
+
}
|
|
205
|
+
async shutdown() {
|
|
206
|
+
this.options.logger.info("Shutting down all workspaces");
|
|
207
|
+
const stopTasks = [];
|
|
208
|
+
for (const [id, workspace] of this.workspaces) {
|
|
209
|
+
if (!workspace.pid) {
|
|
210
|
+
this.options.logger.debug({ workspaceId: id }, "Workspace already stopped");
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
this.options.logger.info({ workspaceId: id }, "Stopping workspace during shutdown");
|
|
214
|
+
stopTasks.push(this.runtime.stop(id).catch((error) => {
|
|
215
|
+
this.options.logger.error({ workspaceId: id, err: error }, "Failed to stop workspace during shutdown");
|
|
216
|
+
}));
|
|
217
|
+
}
|
|
218
|
+
if (stopTasks.length > 0) {
|
|
219
|
+
await Promise.allSettled(stopTasks);
|
|
220
|
+
}
|
|
221
|
+
this.workspaces.clear();
|
|
222
|
+
this.opencodeAuth.clear();
|
|
223
|
+
this.options.logger.info("All workspaces cleared");
|
|
224
|
+
}
|
|
225
|
+
requireWorkspace(id) {
|
|
226
|
+
const workspace = this.workspaces.get(id);
|
|
227
|
+
if (!workspace) {
|
|
228
|
+
throw new Error("Workspace not found");
|
|
229
|
+
}
|
|
230
|
+
return workspace;
|
|
231
|
+
}
|
|
232
|
+
resolveBinaryPath(identifier) {
|
|
233
|
+
if (!identifier) {
|
|
234
|
+
return identifier;
|
|
235
|
+
}
|
|
236
|
+
const looksLikePath = identifier.includes("/") || identifier.includes("\\") || identifier.startsWith(".");
|
|
237
|
+
if (path.isAbsolute(identifier) || looksLikePath) {
|
|
238
|
+
return identifier;
|
|
239
|
+
}
|
|
240
|
+
const locator = process.platform === "win32" ? "where" : "which";
|
|
241
|
+
try {
|
|
242
|
+
const result = spawnSync(locator, [identifier], { encoding: "utf8" });
|
|
243
|
+
if (result.status === 0 && result.stdout) {
|
|
244
|
+
const candidates = result.stdout
|
|
245
|
+
.split(/\r?\n/)
|
|
246
|
+
.map((line) => line.trim())
|
|
247
|
+
.filter((line) => line.length > 0)
|
|
248
|
+
.filter((line) => !/^INFO:/i.test(line));
|
|
249
|
+
if (candidates.length > 0) {
|
|
250
|
+
const resolved = this.pickBinaryCandidate(candidates);
|
|
251
|
+
this.options.logger.debug({ identifier, resolved, candidates }, "Resolved binary path from system PATH");
|
|
252
|
+
return resolved;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
else if (result.error) {
|
|
256
|
+
this.options.logger.warn({ identifier, err: result.error }, "Failed to resolve binary path via locator command");
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
this.options.logger.warn({ identifier, err: error }, "Failed to resolve binary path from system PATH");
|
|
261
|
+
}
|
|
262
|
+
const shellResolved = resolveBinaryPathFromUserShell(identifier);
|
|
263
|
+
if (shellResolved) {
|
|
264
|
+
this.options.logger.debug({ identifier, resolved: shellResolved }, "Resolved binary path from user shell");
|
|
265
|
+
return shellResolved;
|
|
266
|
+
}
|
|
267
|
+
const installedPath = path.join(BIN_DIR, BINARY_NAME);
|
|
268
|
+
if (existsSync(installedPath)) {
|
|
269
|
+
this.options.logger.debug({ identifier, resolved: installedPath }, "Resolved binary path from installed directory");
|
|
270
|
+
return installedPath;
|
|
271
|
+
}
|
|
272
|
+
return identifier;
|
|
273
|
+
}
|
|
274
|
+
pickBinaryCandidate(candidates) {
|
|
275
|
+
if (process.platform !== "win32") {
|
|
276
|
+
return candidates[0] ?? "";
|
|
277
|
+
}
|
|
278
|
+
const extensionPreference = [".exe", ".cmd", ".bat", ".ps1"];
|
|
279
|
+
for (const ext of extensionPreference) {
|
|
280
|
+
const match = candidates.find((candidate) => candidate.toLowerCase().endsWith(ext));
|
|
281
|
+
if (match) {
|
|
282
|
+
return match;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return candidates[0] ?? "";
|
|
286
|
+
}
|
|
287
|
+
async waitForWorkspaceReadiness(params) {
|
|
288
|
+
await Promise.race([
|
|
289
|
+
this.waitForPortAvailability(params.port),
|
|
290
|
+
params.exitPromise.then((info) => {
|
|
291
|
+
throw this.buildStartupError(params.workspaceId, "exited before becoming ready", info, params.getLastOutput());
|
|
292
|
+
}),
|
|
293
|
+
]);
|
|
294
|
+
const version = await this.waitForInstanceHealth(params);
|
|
295
|
+
await Promise.race([
|
|
296
|
+
this.delay(STARTUP_STABILITY_DELAY_MS),
|
|
297
|
+
params.exitPromise.then((info) => {
|
|
298
|
+
throw this.buildStartupError(params.workspaceId, "exited shortly after start", info, params.getLastOutput());
|
|
299
|
+
}),
|
|
300
|
+
]);
|
|
301
|
+
return version;
|
|
302
|
+
}
|
|
303
|
+
async waitForInstanceHealth(params) {
|
|
304
|
+
const probeResult = await Promise.race([
|
|
305
|
+
this.probeInstance(params.workspaceId, params.port),
|
|
306
|
+
params.exitPromise.then((info) => {
|
|
307
|
+
throw this.buildStartupError(params.workspaceId, "exited during health checks", info, params.getLastOutput());
|
|
308
|
+
}),
|
|
309
|
+
]);
|
|
310
|
+
if (probeResult.ok) {
|
|
311
|
+
return probeResult.version;
|
|
312
|
+
}
|
|
313
|
+
const latestOutput = params.getLastOutput().trim();
|
|
314
|
+
if (latestOutput) {
|
|
315
|
+
throw new Error(latestOutput);
|
|
316
|
+
}
|
|
317
|
+
const reason = probeResult.reason ?? "Health check failed";
|
|
318
|
+
throw new Error(`Workspace ${params.workspaceId} failed health check: ${reason}.`);
|
|
319
|
+
}
|
|
320
|
+
async probeInstance(workspaceId, port) {
|
|
321
|
+
const url = `http://127.0.0.1:${port}/global/health`;
|
|
322
|
+
try {
|
|
323
|
+
const headers = {};
|
|
324
|
+
const authHeader = this.opencodeAuth.get(workspaceId)?.authorization;
|
|
325
|
+
if (authHeader) {
|
|
326
|
+
headers["Authorization"] = authHeader;
|
|
327
|
+
}
|
|
328
|
+
const response = await fetch(url, { headers });
|
|
329
|
+
if (!response.ok) {
|
|
330
|
+
const reason = `/global/health returned HTTP ${response.status}`;
|
|
331
|
+
this.options.logger.debug({ workspaceId, status: response.status }, "Health probe returned server error");
|
|
332
|
+
return { ok: false, reason };
|
|
333
|
+
}
|
|
334
|
+
const payload = (await response.json().catch(() => null));
|
|
335
|
+
const healthy = payload?.healthy === true;
|
|
336
|
+
const version = typeof payload?.version === "string" ? payload.version.trim() : undefined;
|
|
337
|
+
if (!healthy) {
|
|
338
|
+
const reason = "Instance reported unhealthy";
|
|
339
|
+
this.options.logger.debug({ workspaceId, payload }, "Health probe returned unhealthy response");
|
|
340
|
+
return { ok: false, reason };
|
|
341
|
+
}
|
|
342
|
+
return { ok: true, version: version || undefined };
|
|
343
|
+
}
|
|
344
|
+
catch (error) {
|
|
345
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
346
|
+
this.options.logger.debug({ workspaceId, err: error }, "Health probe failed");
|
|
347
|
+
return { ok: false, reason };
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
buildStartupError(workspaceId, phase, exitInfo, lastOutput) {
|
|
351
|
+
const exitDetails = this.describeExit(exitInfo);
|
|
352
|
+
const trimmedOutput = lastOutput.trim();
|
|
353
|
+
const outputDetails = trimmedOutput ? ` Last output: ${trimmedOutput}` : "";
|
|
354
|
+
return new Error(`Workspace ${workspaceId} ${phase} (${exitDetails}).${outputDetails}`);
|
|
355
|
+
}
|
|
356
|
+
waitForPortAvailability(port, timeoutMs = 5000) {
|
|
357
|
+
return new Promise((resolve, reject) => {
|
|
358
|
+
const deadline = Date.now() + timeoutMs;
|
|
359
|
+
let settled = false;
|
|
360
|
+
let retryTimer = null;
|
|
361
|
+
const cleanup = () => {
|
|
362
|
+
settled = true;
|
|
363
|
+
if (retryTimer) {
|
|
364
|
+
clearTimeout(retryTimer);
|
|
365
|
+
retryTimer = null;
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
const tryConnect = () => {
|
|
369
|
+
if (settled) {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
const socket = connect({ port, host: "127.0.0.1" }, () => {
|
|
373
|
+
cleanup();
|
|
374
|
+
socket.end();
|
|
375
|
+
resolve();
|
|
376
|
+
});
|
|
377
|
+
socket.once("error", () => {
|
|
378
|
+
socket.destroy();
|
|
379
|
+
if (settled) {
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
if (Date.now() >= deadline) {
|
|
383
|
+
cleanup();
|
|
384
|
+
reject(new Error(`Workspace port ${port} did not become ready within ${timeoutMs}ms`));
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
retryTimer = setTimeout(() => {
|
|
388
|
+
retryTimer = null;
|
|
389
|
+
tryConnect();
|
|
390
|
+
}, 100);
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
};
|
|
394
|
+
tryConnect();
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
delay(durationMs) {
|
|
398
|
+
if (durationMs <= 0) {
|
|
399
|
+
return Promise.resolve();
|
|
400
|
+
}
|
|
401
|
+
return new Promise((resolve) => setTimeout(resolve, durationMs));
|
|
402
|
+
}
|
|
403
|
+
describeExit(info) {
|
|
404
|
+
if (info.signal) {
|
|
405
|
+
return `signal ${info.signal}`;
|
|
406
|
+
}
|
|
407
|
+
if (info.code !== null) {
|
|
408
|
+
return `code ${info.code}`;
|
|
409
|
+
}
|
|
410
|
+
return "unknown reason";
|
|
411
|
+
}
|
|
412
|
+
handleProcessExit(workspaceId, info) {
|
|
413
|
+
const workspace = this.workspaces.get(workspaceId);
|
|
414
|
+
if (!workspace)
|
|
415
|
+
return;
|
|
416
|
+
this.opencodeAuth.delete(workspaceId);
|
|
417
|
+
this.options.logger.info({ workspaceId, ...info }, "Workspace process exited");
|
|
418
|
+
workspace.pid = undefined;
|
|
419
|
+
workspace.port = undefined;
|
|
420
|
+
workspace.updatedAt = new Date().toISOString();
|
|
421
|
+
if (info.requested || info.code === 0) {
|
|
422
|
+
workspace.status = "stopped";
|
|
423
|
+
workspace.error = undefined;
|
|
424
|
+
this.options.eventBus.publish({ type: "workspace.stopped", workspaceId });
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
427
|
+
workspace.status = "error";
|
|
428
|
+
workspace.error = `Process exited with code ${info.code}`;
|
|
429
|
+
this.options.eventBus.publish({ type: "workspace.error", workspace });
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
export const OPENCODE_SERVER_USERNAME_ENV = "OPENCODE_SERVER_USERNAME";
|
|
3
|
+
export const OPENCODE_SERVER_PASSWORD_ENV = "OPENCODE_SERVER_PASSWORD";
|
|
4
|
+
export const DEFAULT_OPENCODE_USERNAME = "embeddedcowork";
|
|
5
|
+
export function generateOpencodeServerPassword() {
|
|
6
|
+
return crypto.randomBytes(32).toString("base64url");
|
|
7
|
+
}
|
|
8
|
+
export function buildOpencodeBasicAuthHeader(params) {
|
|
9
|
+
const username = params.username;
|
|
10
|
+
const password = params.password;
|
|
11
|
+
if (!username || !password) {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
const token = Buffer.from(`${username}:${password}`, "utf8").toString("base64");
|
|
15
|
+
return `Basic ${token}`;
|
|
16
|
+
}
|