remote-codex 0.11.1 → 0.11.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/relay-server/dist/index.js +1221 -0
- package/apps/supervisor-api/dist/index.js +2822 -780
- package/apps/supervisor-web/dist/assets/core-DIQen2lE.js +12 -0
- package/apps/supervisor-web/dist/assets/{css-DPfMkruS.js → css-CLj8gQPS.js} +1 -1
- package/apps/supervisor-web/dist/assets/engine-javascript-DBd1bXLz.js +141 -0
- package/apps/supervisor-web/dist/assets/graph-vendor-C5ap-Sga.css +1 -0
- package/apps/supervisor-web/dist/assets/graph-vendor-CGzY-MFv.js +23 -0
- package/apps/supervisor-web/dist/assets/{html-GMplVEZG.js → html-pp8916En.js} +1 -1
- package/apps/supervisor-web/dist/assets/index-CBIze1VS.css +1 -0
- package/apps/supervisor-web/dist/assets/index-YpGAPjED.js +4 -0
- package/apps/supervisor-web/dist/assets/markdown-vendor-hBDTCSB-.js +291 -0
- package/apps/supervisor-web/dist/assets/react-vendor-o1Xrx7m4.js +60 -0
- package/apps/supervisor-web/dist/assets/{shellscript-Yzrsuije.js → shellscript-CEILq0vU.js} +1 -1
- package/apps/supervisor-web/dist/assets/{sql-BLtJtn59.js → sql-CRqJ_cUM.js} +1 -1
- package/apps/supervisor-web/dist/assets/terminal-vendor-Beg8tuEN.css +32 -0
- package/apps/supervisor-web/dist/assets/{xterm-D92BViLH.js → terminal-vendor-CLGgN91S.js} +17 -6
- package/apps/supervisor-web/dist/assets/thread-ui-BEieA99i.css +1 -0
- package/apps/supervisor-web/dist/assets/thread-ui-CF80LEEN.js +3613 -0
- package/apps/supervisor-web/dist/assets/ui-vendor-CW6egZBG.js +430 -0
- package/apps/supervisor-web/dist/index.html +11 -2
- package/apps/supervisor-web/dist/vendor/3Dmol-min.js +2 -0
- package/bin/remote-codex-plugin-mcp.mjs +140 -0
- package/bin/remote-codex.mjs +73 -16
- package/package.json +7 -1
- package/packages/agent-runtime/src/types.ts +1 -0
- package/packages/agent-runtime/src/unavailable-runtime.ts +5 -13
- package/packages/claude/src/runtimeAdapter.ts +7 -5
- package/packages/codex/src/appServerManager.ts +3 -3
- package/packages/codex/src/historyItems.test.ts +2 -4
- package/packages/codex/src/historyItems.ts +13 -18
- package/packages/codex/src/runtime-errors.ts +0 -5
- package/packages/codex/src/runtimeAdapter.ts +3 -0
- package/packages/codex/src/types.ts +1 -0
- package/packages/db/migrations/0018_shell_session_label.sql +1 -0
- package/packages/db/src/repositories.ts +12 -0
- package/packages/db/src/schema.ts +1 -0
- package/packages/opencode/src/historyItems.ts +0 -17
- package/packages/plugin-terminal/package.json +17 -0
- package/packages/plugin-terminal/plugin.json +25 -0
- package/packages/plugin-terminal/src/index.ts +2 -0
- package/packages/plugin-terminal/src/manifest.ts +51 -0
- package/packages/shared/src/index.ts +213 -0
- package/apps/supervisor-api/dist/chunk-6M32PPHZ.js +0 -24507
- package/apps/supervisor-api/dist/chunk-7AA2MFXK.js +0 -24499
- package/apps/supervisor-api/dist/chunk-HKBFCPHH.js +0 -24511
- package/apps/supervisor-api/dist/worker-index.js +0 -33
- package/apps/supervisor-web/dist/assets/abap-BdImnpbu.js +0 -1
- package/apps/supervisor-web/dist/assets/actionscript-3-CoDkCxhg.js +0 -1
- package/apps/supervisor-web/dist/assets/ada-bCR0ucgS.js +0 -1
- package/apps/supervisor-web/dist/assets/addon-fit-YJmn1quW.js +0 -12
- package/apps/supervisor-web/dist/assets/andromeeda-C4gqWexZ.js +0 -1
- package/apps/supervisor-web/dist/assets/angular-html-CU67Zn6k.js +0 -1
- package/apps/supervisor-web/dist/assets/angular-ts-BwZT4LLn.js +0 -1
- package/apps/supervisor-web/dist/assets/apache-Pmp26Uib.js +0 -1
- package/apps/supervisor-web/dist/assets/apex-D8_7TLub.js +0 -1
- package/apps/supervisor-web/dist/assets/apl-dKokRX4l.js +0 -1
- package/apps/supervisor-web/dist/assets/applescript-Co6uUVPk.js +0 -1
- package/apps/supervisor-web/dist/assets/ara-BRHolxvo.js +0 -1
- package/apps/supervisor-web/dist/assets/asciidoc-Ve4PFQV2.js +0 -1
- package/apps/supervisor-web/dist/assets/asm-D_Q5rh1f.js +0 -1
- package/apps/supervisor-web/dist/assets/astro-CbQHKStN.js +0 -1
- package/apps/supervisor-web/dist/assets/aurora-x-D-2ljcwZ.js +0 -1
- package/apps/supervisor-web/dist/assets/awk-DMzUqQB5.js +0 -1
- package/apps/supervisor-web/dist/assets/ayu-mirage-32ctXXKs.js +0 -1
- package/apps/supervisor-web/dist/assets/ballerina-BFfxhgS-.js +0 -1
- package/apps/supervisor-web/dist/assets/bat-BkioyH1T.js +0 -1
- package/apps/supervisor-web/dist/assets/beancount-k_qm7-4y.js +0 -1
- package/apps/supervisor-web/dist/assets/berry-uYugtg8r.js +0 -1
- package/apps/supervisor-web/dist/assets/bibtex-CHM0blh-.js +0 -1
- package/apps/supervisor-web/dist/assets/bicep-Bmn6On1c.js +0 -1
- package/apps/supervisor-web/dist/assets/bird2-DPOp833l.js +0 -1
- package/apps/supervisor-web/dist/assets/blade-D4QpJJKB.js +0 -1
- package/apps/supervisor-web/dist/assets/bsl-BO_Y6i37.js +0 -1
- package/apps/supervisor-web/dist/assets/c-BIGW1oBm.js +0 -1
- package/apps/supervisor-web/dist/assets/c3-eo99z4R2.js +0 -1
- package/apps/supervisor-web/dist/assets/cadence-Bv_4Rxtq.js +0 -1
- package/apps/supervisor-web/dist/assets/cairo-KRGpt6FW.js +0 -1
- package/apps/supervisor-web/dist/assets/catppuccin-frappe-DFWUc33u.js +0 -1
- package/apps/supervisor-web/dist/assets/catppuccin-latte-C9dUb6Cb.js +0 -1
- package/apps/supervisor-web/dist/assets/catppuccin-macchiato-DQyhUUbL.js +0 -1
- package/apps/supervisor-web/dist/assets/catppuccin-mocha-D87Tk5Gz.js +0 -1
- package/apps/supervisor-web/dist/assets/clarity-D53aC0YG.js +0 -1
- package/apps/supervisor-web/dist/assets/clojure-P80f7IUj.js +0 -1
- package/apps/supervisor-web/dist/assets/cmake-D1j8_8rp.js +0 -1
- package/apps/supervisor-web/dist/assets/cobol-nwyudZeR.js +0 -1
- package/apps/supervisor-web/dist/assets/codeowners-Bp6g37R7.js +0 -1
- package/apps/supervisor-web/dist/assets/codeql-DsOJ9woJ.js +0 -1
- package/apps/supervisor-web/dist/assets/coffee-Ch7k5sss.js +0 -1
- package/apps/supervisor-web/dist/assets/common-lisp-Cg-RD9OK.js +0 -1
- package/apps/supervisor-web/dist/assets/coq-DkFqJrB1.js +0 -1
- package/apps/supervisor-web/dist/assets/cpp-CofmeUqb.js +0 -1
- package/apps/supervisor-web/dist/assets/crystal-tKQVLTB8.js +0 -1
- package/apps/supervisor-web/dist/assets/csharp-COcwbKMJ.js +0 -1
- package/apps/supervisor-web/dist/assets/cue-D82EKSYY.js +0 -1
- package/apps/supervisor-web/dist/assets/cypher-COkxafJQ.js +0 -1
- package/apps/supervisor-web/dist/assets/d-85-TOEBH.js +0 -1
- package/apps/supervisor-web/dist/assets/dark-plus-C3mMm8J8.js +0 -1
- package/apps/supervisor-web/dist/assets/dart-CF10PKvl.js +0 -1
- package/apps/supervisor-web/dist/assets/dax-CEL-wOlO.js +0 -1
- package/apps/supervisor-web/dist/assets/desktop-BmXAJ9_W.js +0 -1
- package/apps/supervisor-web/dist/assets/diff-D97Zzqfu.js +0 -1
- package/apps/supervisor-web/dist/assets/docker-BcOcwvcX.js +0 -1
- package/apps/supervisor-web/dist/assets/dotenv-Da5cRb03.js +0 -1
- package/apps/supervisor-web/dist/assets/dracula-BzJJZx-M.js +0 -1
- package/apps/supervisor-web/dist/assets/dracula-soft-BXkSAIEj.js +0 -1
- package/apps/supervisor-web/dist/assets/dream-maker-BtqSS_iP.js +0 -1
- package/apps/supervisor-web/dist/assets/edge-BkV0erSs.js +0 -1
- package/apps/supervisor-web/dist/assets/elixir-CDX3lj18.js +0 -1
- package/apps/supervisor-web/dist/assets/elm-DbKCFpqz.js +0 -1
- package/apps/supervisor-web/dist/assets/emacs-lisp-C9XAeP06.js +0 -1
- package/apps/supervisor-web/dist/assets/erb-B12qg9BL.js +0 -1
- package/apps/supervisor-web/dist/assets/erlang-DsQrWhSR.js +0 -1
- package/apps/supervisor-web/dist/assets/everforest-dark-BgDCqdQA.js +0 -1
- package/apps/supervisor-web/dist/assets/everforest-light-C8M2exoo.js +0 -1
- package/apps/supervisor-web/dist/assets/fennel-BYunw83y.js +0 -1
- package/apps/supervisor-web/dist/assets/fish-BvzEVeQv.js +0 -1
- package/apps/supervisor-web/dist/assets/fluent-C4IJs8-o.js +0 -1
- package/apps/supervisor-web/dist/assets/fortran-fixed-form-CkoXwp7k.js +0 -1
- package/apps/supervisor-web/dist/assets/fortran-free-form-BxgE0vQu.js +0 -1
- package/apps/supervisor-web/dist/assets/fsharp-CXgrBDvD.js +0 -1
- package/apps/supervisor-web/dist/assets/gdresource-BOOCDP_w.js +0 -1
- package/apps/supervisor-web/dist/assets/gdscript-C5YyOfLZ.js +0 -1
- package/apps/supervisor-web/dist/assets/gdshader-DkwncUOv.js +0 -1
- package/apps/supervisor-web/dist/assets/genie-D0YGMca9.js +0 -1
- package/apps/supervisor-web/dist/assets/gherkin-DyxjwDmM.js +0 -1
- package/apps/supervisor-web/dist/assets/git-commit-F4YmCXRG.js +0 -1
- package/apps/supervisor-web/dist/assets/git-rebase-r7XF79zn.js +0 -1
- package/apps/supervisor-web/dist/assets/github-dark-DHJKELXO.js +0 -1
- package/apps/supervisor-web/dist/assets/github-dark-default-Cuk6v7N8.js +0 -1
- package/apps/supervisor-web/dist/assets/github-dark-dimmed-DH5Ifo-i.js +0 -1
- package/apps/supervisor-web/dist/assets/github-dark-high-contrast-E3gJ1_iC.js +0 -1
- package/apps/supervisor-web/dist/assets/github-light-DAi9KRSo.js +0 -1
- package/apps/supervisor-web/dist/assets/github-light-default-D7oLnXFd.js +0 -1
- package/apps/supervisor-web/dist/assets/github-light-high-contrast-BfjtVDDH.js +0 -1
- package/apps/supervisor-web/dist/assets/gleam-BspZqrRM.js +0 -1
- package/apps/supervisor-web/dist/assets/glimmer-js-Rg0-pVw9.js +0 -1
- package/apps/supervisor-web/dist/assets/glimmer-ts-U6CK756n.js +0 -1
- package/apps/supervisor-web/dist/assets/glsl-DplSGwfg.js +0 -1
- package/apps/supervisor-web/dist/assets/gn-n2N0HUVH.js +0 -1
- package/apps/supervisor-web/dist/assets/gnuplot-DdkO51Og.js +0 -1
- package/apps/supervisor-web/dist/assets/go-CxLEBnE3.js +0 -1
- package/apps/supervisor-web/dist/assets/graphql-ChdNCCLP.js +0 -1
- package/apps/supervisor-web/dist/assets/groovy-gcz8RCvz.js +0 -1
- package/apps/supervisor-web/dist/assets/gruvbox-dark-hard-CFHQjOhq.js +0 -1
- package/apps/supervisor-web/dist/assets/gruvbox-dark-medium-GsRaNv29.js +0 -1
- package/apps/supervisor-web/dist/assets/gruvbox-dark-soft-CVdnzihN.js +0 -1
- package/apps/supervisor-web/dist/assets/gruvbox-light-hard-CH1njM8p.js +0 -1
- package/apps/supervisor-web/dist/assets/gruvbox-light-medium-DRw_LuNl.js +0 -1
- package/apps/supervisor-web/dist/assets/gruvbox-light-soft-hJgmCMqR.js +0 -1
- package/apps/supervisor-web/dist/assets/hack-CaT9iCJl.js +0 -1
- package/apps/supervisor-web/dist/assets/haml-B8DHNrY2.js +0 -1
- package/apps/supervisor-web/dist/assets/handlebars-BL8al0AC.js +0 -1
- package/apps/supervisor-web/dist/assets/haskell-Df6bDoY_.js +0 -1
- package/apps/supervisor-web/dist/assets/haxe-CzTSHFRz.js +0 -1
- package/apps/supervisor-web/dist/assets/hcl-BWvSN4gD.js +0 -1
- package/apps/supervisor-web/dist/assets/highlighted-body-OFNGDK62-p31aS0f0.js +0 -1
- package/apps/supervisor-web/dist/assets/hjson-D5-asLiD.js +0 -1
- package/apps/supervisor-web/dist/assets/hlsl-D3lLCCz7.js +0 -1
- package/apps/supervisor-web/dist/assets/horizon-BUw7H-hv.js +0 -1
- package/apps/supervisor-web/dist/assets/horizon-bright-Cn-bp-IR.js +0 -1
- package/apps/supervisor-web/dist/assets/houston-DnULxvSX.js +0 -1
- package/apps/supervisor-web/dist/assets/html-derivative-BFtXZ54Q.js +0 -1
- package/apps/supervisor-web/dist/assets/http-jrhK8wxY.js +0 -1
- package/apps/supervisor-web/dist/assets/hurl-irOxFIW8.js +0 -1
- package/apps/supervisor-web/dist/assets/hxml-Bvhsp5Yf.js +0 -1
- package/apps/supervisor-web/dist/assets/hy-DFXneXwc.js +0 -1
- package/apps/supervisor-web/dist/assets/imba-DGztddWO.js +0 -1
- package/apps/supervisor-web/dist/assets/index-BiuFei_K.css +0 -32
- package/apps/supervisor-web/dist/assets/index-D1R9CUnx.js +0 -2161
- package/apps/supervisor-web/dist/assets/ini-BEwlwnbL.js +0 -1
- package/apps/supervisor-web/dist/assets/java-CylS5w8V.js +0 -1
- package/apps/supervisor-web/dist/assets/jinja-4LBKfQ-Z.js +0 -1
- package/apps/supervisor-web/dist/assets/jison-wvAkD_A8.js +0 -1
- package/apps/supervisor-web/dist/assets/json5-C9tS-k6U.js +0 -1
- package/apps/supervisor-web/dist/assets/jsonc-Des-eS-w.js +0 -1
- package/apps/supervisor-web/dist/assets/jsonl-DcaNXYhu.js +0 -1
- package/apps/supervisor-web/dist/assets/jsonnet-DFQXde-d.js +0 -1
- package/apps/supervisor-web/dist/assets/jssm-C2t-YnRu.js +0 -1
- package/apps/supervisor-web/dist/assets/julia-CxzCAyBv.js +0 -1
- package/apps/supervisor-web/dist/assets/just-Cw27pwNe.js +0 -1
- package/apps/supervisor-web/dist/assets/kanagawa-dragon-CkXjmgJE.js +0 -1
- package/apps/supervisor-web/dist/assets/kanagawa-lotus-CfQXZHmo.js +0 -1
- package/apps/supervisor-web/dist/assets/kanagawa-wave-DWedfzmr.js +0 -1
- package/apps/supervisor-web/dist/assets/kdl-DV7GczEv.js +0 -1
- package/apps/supervisor-web/dist/assets/kotlin-BdnUsdx6.js +0 -1
- package/apps/supervisor-web/dist/assets/kusto-DZf3V79B.js +0 -1
- package/apps/supervisor-web/dist/assets/laserwave-DUszq2jm.js +0 -1
- package/apps/supervisor-web/dist/assets/latex-CWtU0Tv5.js +0 -1
- package/apps/supervisor-web/dist/assets/lean-BZvkOJ9d.js +0 -1
- package/apps/supervisor-web/dist/assets/less-B1dDrJ26.js +0 -1
- package/apps/supervisor-web/dist/assets/light-plus-B7mTdjB0.js +0 -1
- package/apps/supervisor-web/dist/assets/liquid-DYVedYrR.js +0 -1
- package/apps/supervisor-web/dist/assets/llvm-DjAJT7YJ.js +0 -1
- package/apps/supervisor-web/dist/assets/log-2UxHyX5q.js +0 -1
- package/apps/supervisor-web/dist/assets/logo-BtOb2qkB.js +0 -1
- package/apps/supervisor-web/dist/assets/lua-BaeVxFsk.js +0 -1
- package/apps/supervisor-web/dist/assets/luau-C-HG3fhB.js +0 -1
- package/apps/supervisor-web/dist/assets/make-CHLpvVh8.js +0 -1
- package/apps/supervisor-web/dist/assets/marko-CnJfTvn9.js +0 -1
- package/apps/supervisor-web/dist/assets/material-theme-D5KoaKCx.js +0 -1
- package/apps/supervisor-web/dist/assets/material-theme-darker-BfHTSMKl.js +0 -1
- package/apps/supervisor-web/dist/assets/material-theme-lighter-B0m2ddpp.js +0 -1
- package/apps/supervisor-web/dist/assets/material-theme-ocean-CyktbL80.js +0 -1
- package/apps/supervisor-web/dist/assets/material-theme-palenight-Csfq5Kiy.js +0 -1
- package/apps/supervisor-web/dist/assets/matlab-D7o27uSR.js +0 -1
- package/apps/supervisor-web/dist/assets/mdc-BMNejdWA.js +0 -1
- package/apps/supervisor-web/dist/assets/mdx-Cmh6b_Ma.js +0 -1
- package/apps/supervisor-web/dist/assets/mermaid-mWjccvbQ.js +0 -1
- package/apps/supervisor-web/dist/assets/min-dark-CafNBF8u.js +0 -1
- package/apps/supervisor-web/dist/assets/min-light-CTRr51gU.js +0 -1
- package/apps/supervisor-web/dist/assets/mipsasm-CKIfxQSi.js +0 -1
- package/apps/supervisor-web/dist/assets/mojo-rZm6bMo-.js +0 -1
- package/apps/supervisor-web/dist/assets/monokai-D4h5O-jR.js +0 -1
- package/apps/supervisor-web/dist/assets/moonbit-_H4v1dQx.js +0 -1
- package/apps/supervisor-web/dist/assets/move-IF9eRakj.js +0 -1
- package/apps/supervisor-web/dist/assets/narrat-DRg8JJMk.js +0 -1
- package/apps/supervisor-web/dist/assets/nextflow-Zz6hmt5N.js +0 -1
- package/apps/supervisor-web/dist/assets/nextflow-groovy-BeH2EWoN.js +0 -1
- package/apps/supervisor-web/dist/assets/nginx-BpAMiNFr.js +0 -1
- package/apps/supervisor-web/dist/assets/night-owl-C39BiMTA.js +0 -1
- package/apps/supervisor-web/dist/assets/night-owl-light-CMTm3GFP.js +0 -1
- package/apps/supervisor-web/dist/assets/nim-CVrawwO9.js +0 -1
- package/apps/supervisor-web/dist/assets/nix-CwoSXNpI.js +0 -1
- package/apps/supervisor-web/dist/assets/nord-Ddv68eIx.js +0 -1
- package/apps/supervisor-web/dist/assets/nushell-Cz2AlsmD.js +0 -1
- package/apps/supervisor-web/dist/assets/objective-c-DXmwc3jG.js +0 -1
- package/apps/supervisor-web/dist/assets/objective-cpp-CLxacb5B.js +0 -1
- package/apps/supervisor-web/dist/assets/ocaml-C0hk2d4L.js +0 -1
- package/apps/supervisor-web/dist/assets/odin-BBf5iR-q.js +0 -1
- package/apps/supervisor-web/dist/assets/one-dark-pro-DVMEJ2y_.js +0 -1
- package/apps/supervisor-web/dist/assets/one-light-C3Wv6jpd.js +0 -1
- package/apps/supervisor-web/dist/assets/openscad-C4EeE6gA.js +0 -1
- package/apps/supervisor-web/dist/assets/pascal-D93ZcfNL.js +0 -1
- package/apps/supervisor-web/dist/assets/perl-C0TMdlhV.js +0 -1
- package/apps/supervisor-web/dist/assets/php-Dhbhpdrm.js +0 -1
- package/apps/supervisor-web/dist/assets/pkl-u5AG7uiY.js +0 -1
- package/apps/supervisor-web/dist/assets/plastic-3e1v2bzS.js +0 -1
- package/apps/supervisor-web/dist/assets/plsql-ChMvpjG-.js +0 -1
- package/apps/supervisor-web/dist/assets/po-BTJTHyun.js +0 -1
- package/apps/supervisor-web/dist/assets/poimandres-CS3Unz2-.js +0 -1
- package/apps/supervisor-web/dist/assets/polar-C0HS_06l.js +0 -1
- package/apps/supervisor-web/dist/assets/postcss-CXtECtnM.js +0 -1
- package/apps/supervisor-web/dist/assets/powerquery-CEu0bR-o.js +0 -1
- package/apps/supervisor-web/dist/assets/powershell-Dpen1YoG.js +0 -1
- package/apps/supervisor-web/dist/assets/prisma-Dd19v3D-.js +0 -1
- package/apps/supervisor-web/dist/assets/prolog-CbFg5uaA.js +0 -1
- package/apps/supervisor-web/dist/assets/proto-C7zT0LnQ.js +0 -1
- package/apps/supervisor-web/dist/assets/pug-CGlum2m_.js +0 -1
- package/apps/supervisor-web/dist/assets/puppet-BMWR74SV.js +0 -1
- package/apps/supervisor-web/dist/assets/purescript-CklMAg4u.js +0 -1
- package/apps/supervisor-web/dist/assets/qml-3beO22l8.js +0 -1
- package/apps/supervisor-web/dist/assets/qmldir-C8lEn-DE.js +0 -1
- package/apps/supervisor-web/dist/assets/qss-IeuSbFQv.js +0 -1
- package/apps/supervisor-web/dist/assets/r-Dspwwk_N.js +0 -1
- package/apps/supervisor-web/dist/assets/racket-BqYA7rlc.js +0 -1
- package/apps/supervisor-web/dist/assets/raku-DXvB9xmW.js +0 -1
- package/apps/supervisor-web/dist/assets/razor-Uh8Bk_45.js +0 -1
- package/apps/supervisor-web/dist/assets/red-bN70gL4F.js +0 -1
- package/apps/supervisor-web/dist/assets/reg-C-SQnVFl.js +0 -1
- package/apps/supervisor-web/dist/assets/regexp-CDVJQ6XC.js +0 -1
- package/apps/supervisor-web/dist/assets/rel-C3B-1QV4.js +0 -1
- package/apps/supervisor-web/dist/assets/riscv-BM1_JUlF.js +0 -1
- package/apps/supervisor-web/dist/assets/ron-D8l8udqQ.js +0 -1
- package/apps/supervisor-web/dist/assets/rose-pine-dawn-DHQR4-dF.js +0 -1
- package/apps/supervisor-web/dist/assets/rose-pine-moon-D4_iv3hh.js +0 -1
- package/apps/supervisor-web/dist/assets/rose-pine-qdsjHGoJ.js +0 -1
- package/apps/supervisor-web/dist/assets/rosmsg-BJDFO7_C.js +0 -1
- package/apps/supervisor-web/dist/assets/rst-BrH8l1NY.js +0 -1
- package/apps/supervisor-web/dist/assets/ruby-Dw2BHqvy.js +0 -1
- package/apps/supervisor-web/dist/assets/rust-B1yitclQ.js +0 -1
- package/apps/supervisor-web/dist/assets/sas-cz2c8ADy.js +0 -1
- package/apps/supervisor-web/dist/assets/sass-Cj5Yp3dK.js +0 -1
- package/apps/supervisor-web/dist/assets/scala-C151Ov-r.js +0 -1
- package/apps/supervisor-web/dist/assets/scheme-C98Dy4si.js +0 -1
- package/apps/supervisor-web/dist/assets/scss-OYdSNvt2.js +0 -1
- package/apps/supervisor-web/dist/assets/sdbl-DVxCFoDh.js +0 -1
- package/apps/supervisor-web/dist/assets/shaderlab-Dg9Lc6iA.js +0 -1
- package/apps/supervisor-web/dist/assets/shellsession-BADoaaVG.js +0 -1
- package/apps/supervisor-web/dist/assets/slack-dark-BthQWCQV.js +0 -1
- package/apps/supervisor-web/dist/assets/slack-ochin-DqwNpetd.js +0 -1
- package/apps/supervisor-web/dist/assets/smalltalk-BERRCDM3.js +0 -1
- package/apps/supervisor-web/dist/assets/snazzy-light-Bw305WKR.js +0 -1
- package/apps/supervisor-web/dist/assets/solarized-dark-DXbdFlpD.js +0 -1
- package/apps/supervisor-web/dist/assets/solarized-light-L9t79GZl.js +0 -1
- package/apps/supervisor-web/dist/assets/solidity-rGO070M0.js +0 -1
- package/apps/supervisor-web/dist/assets/soy-Brmx7dQM.js +0 -1
- package/apps/supervisor-web/dist/assets/sparql-rVzFXLq3.js +0 -1
- package/apps/supervisor-web/dist/assets/splunk-BtCnVYZw.js +0 -1
- package/apps/supervisor-web/dist/assets/ssh-config-_ykCGR6B.js +0 -1
- package/apps/supervisor-web/dist/assets/stata-BH5u7GGu.js +0 -1
- package/apps/supervisor-web/dist/assets/stylus-BEDo0Tqx.js +0 -1
- package/apps/supervisor-web/dist/assets/surrealql-Bq5Q-fJD.js +0 -1
- package/apps/supervisor-web/dist/assets/svelte-C_ipcX3V.js +0 -1
- package/apps/supervisor-web/dist/assets/swift-D82vCrfD.js +0 -1
- package/apps/supervisor-web/dist/assets/synthwave-84-CbfX1IO0.js +0 -1
- package/apps/supervisor-web/dist/assets/system-verilog-CnnmHF94.js +0 -1
- package/apps/supervisor-web/dist/assets/systemd-4A_iFExJ.js +0 -1
- package/apps/supervisor-web/dist/assets/talonscript-CkByrt1z.js +0 -1
- package/apps/supervisor-web/dist/assets/tasl-QIJgUcNo.js +0 -1
- package/apps/supervisor-web/dist/assets/tcl-dwOrl1Do.js +0 -1
- package/apps/supervisor-web/dist/assets/templ-P3uqSqPl.js +0 -1
- package/apps/supervisor-web/dist/assets/terraform-BETggiCN.js +0 -1
- package/apps/supervisor-web/dist/assets/tex-idrVyKtj.js +0 -1
- package/apps/supervisor-web/dist/assets/tokyo-night-hegEt444.js +0 -1
- package/apps/supervisor-web/dist/assets/ts-tags-zn1MmPIZ.js +0 -1
- package/apps/supervisor-web/dist/assets/tsv-B_m7g4N7.js +0 -1
- package/apps/supervisor-web/dist/assets/turtle-BsS91CYL.js +0 -1
- package/apps/supervisor-web/dist/assets/twig-DNn4PbVi.js +0 -1
- package/apps/supervisor-web/dist/assets/typespec-BGHnOYBU.js +0 -1
- package/apps/supervisor-web/dist/assets/typst-DHCkPAjA.js +0 -1
- package/apps/supervisor-web/dist/assets/v-BcVCzyr7.js +0 -1
- package/apps/supervisor-web/dist/assets/vala-CsfeWuGM.js +0 -1
- package/apps/supervisor-web/dist/assets/vb-D17OF-Vu.js +0 -1
- package/apps/supervisor-web/dist/assets/verilog-BQ8w6xss.js +0 -1
- package/apps/supervisor-web/dist/assets/vesper-DU1UobuO.js +0 -1
- package/apps/supervisor-web/dist/assets/vhdl-CeAyd5Ju.js +0 -1
- package/apps/supervisor-web/dist/assets/viml-CJc9bBzg.js +0 -1
- package/apps/supervisor-web/dist/assets/vitesse-black-Bkuqu6BP.js +0 -1
- package/apps/supervisor-web/dist/assets/vitesse-dark-D0r3Knsf.js +0 -1
- package/apps/supervisor-web/dist/assets/vitesse-light-CVO1_9PV.js +0 -1
- package/apps/supervisor-web/dist/assets/vue-DN_0RTcg.js +0 -1
- package/apps/supervisor-web/dist/assets/vue-html-AaS7Mt5G.js +0 -1
- package/apps/supervisor-web/dist/assets/vue-vine-CQOfvN7w.js +0 -1
- package/apps/supervisor-web/dist/assets/vyper-CDx5xZoG.js +0 -1
- package/apps/supervisor-web/dist/assets/wasm-CG6Dc4jp.js +0 -1
- package/apps/supervisor-web/dist/assets/wasm-MzD3tlZU.js +0 -1
- package/apps/supervisor-web/dist/assets/wenyan-BV7otONQ.js +0 -1
- package/apps/supervisor-web/dist/assets/wgsl-Dx-B1_4e.js +0 -1
- package/apps/supervisor-web/dist/assets/wikitext-BhOHFoWU.js +0 -1
- package/apps/supervisor-web/dist/assets/wit-5i3qLPDT.js +0 -1
- package/apps/supervisor-web/dist/assets/wolfram-lXgVvXCa.js +0 -1
- package/apps/supervisor-web/dist/assets/xml-sdJ4AIDG.js +0 -1
- package/apps/supervisor-web/dist/assets/xsl-CtQFsRM5.js +0 -1
- package/apps/supervisor-web/dist/assets/zenscript-DVFEvuxE.js +0 -1
- package/apps/supervisor-web/dist/assets/zig-VOosw3JB.js +0 -1
- /package/apps/{supervisor-api/dist/worker-index.d.ts → relay-server/dist/index.d.ts} +0 -0
|
@@ -5,15 +5,15 @@ var __export = (target, all) => {
|
|
|
5
5
|
};
|
|
6
6
|
|
|
7
7
|
// src/index.ts
|
|
8
|
-
import
|
|
8
|
+
import fs25 from "fs";
|
|
9
9
|
|
|
10
10
|
// src/app.ts
|
|
11
11
|
import Fastify from "fastify";
|
|
12
12
|
import multipart from "@fastify/multipart";
|
|
13
13
|
import websocket from "@fastify/websocket";
|
|
14
|
-
import { spawn as
|
|
15
|
-
import
|
|
16
|
-
import
|
|
14
|
+
import { spawn as spawn5 } from "child_process";
|
|
15
|
+
import fs24 from "fs";
|
|
16
|
+
import path23 from "path";
|
|
17
17
|
import { ZodError } from "zod";
|
|
18
18
|
|
|
19
19
|
// ../../packages/config/src/index.ts
|
|
@@ -80,6 +80,7 @@ function truncateAutoThreadTitle(value) {
|
|
|
80
80
|
// ../../packages/config/src/index.ts
|
|
81
81
|
var envSchema = z.object({
|
|
82
82
|
NODE_ENV: z.enum(["development", "test", "production"]).optional(),
|
|
83
|
+
REMOTE_CODEX_MODE: z.enum(["local", "server", "relay"]).optional(),
|
|
83
84
|
HOST: z.string().min(1).optional(),
|
|
84
85
|
PORT: z.coerce.number().int().positive().optional(),
|
|
85
86
|
LOG_LEVEL: z.enum(["trace", "debug", "info", "warn", "error", "fatal"]).optional(),
|
|
@@ -88,6 +89,12 @@ var envSchema = z.object({
|
|
|
88
89
|
APP_VERSION: z.string().min(1).optional(),
|
|
89
90
|
WORKSPACE_ROOT: z.string().optional(),
|
|
90
91
|
DATABASE_URL: z.string().optional(),
|
|
92
|
+
REMOTE_CODEX_ADMIN_USERNAME: z.string().min(1).optional(),
|
|
93
|
+
REMOTE_CODEX_ADMIN_PASSWORD: z.string().min(1).optional(),
|
|
94
|
+
REMOTE_CODEX_SESSION_SECRET: z.string().min(16).optional(),
|
|
95
|
+
REMOTE_CODEX_SESSION_TTL_SECONDS: z.coerce.number().int().positive().optional(),
|
|
96
|
+
REMOTE_CODEX_RELAY_SERVER_URL: z.string().url().optional(),
|
|
97
|
+
REMOTE_CODEX_RELAY_AGENT_TOKEN: z.string().min(1).optional(),
|
|
91
98
|
CODEX_HOME: z.string().optional(),
|
|
92
99
|
CODEX_COMMAND: z.string().min(1).optional(),
|
|
93
100
|
CODEX_APP_SERVER_START_TIMEOUT_MS: z.coerce.number().int().positive().optional(),
|
|
@@ -97,6 +104,31 @@ var envSchema = z.object({
|
|
|
97
104
|
OPENCODE_COMMAND: z.string().min(1).optional(),
|
|
98
105
|
REMOTE_CODEX_ENABLED_AGENT_PROVIDERS: z.string().optional()
|
|
99
106
|
});
|
|
107
|
+
function optionalNonEmpty(value) {
|
|
108
|
+
const normalized = value?.trim();
|
|
109
|
+
return normalized ? normalized : void 0;
|
|
110
|
+
}
|
|
111
|
+
function normalizeOptionalEnv(env) {
|
|
112
|
+
return {
|
|
113
|
+
...env,
|
|
114
|
+
WORKSPACE_ROOT: optionalNonEmpty(env.WORKSPACE_ROOT),
|
|
115
|
+
DATABASE_URL: optionalNonEmpty(env.DATABASE_URL),
|
|
116
|
+
REMOTE_CODEX_ADMIN_USERNAME: optionalNonEmpty(env.REMOTE_CODEX_ADMIN_USERNAME),
|
|
117
|
+
REMOTE_CODEX_ADMIN_PASSWORD: optionalNonEmpty(env.REMOTE_CODEX_ADMIN_PASSWORD),
|
|
118
|
+
REMOTE_CODEX_SESSION_SECRET: optionalNonEmpty(env.REMOTE_CODEX_SESSION_SECRET),
|
|
119
|
+
REMOTE_CODEX_RELAY_SERVER_URL: optionalNonEmpty(env.REMOTE_CODEX_RELAY_SERVER_URL),
|
|
120
|
+
REMOTE_CODEX_RELAY_AGENT_TOKEN: optionalNonEmpty(env.REMOTE_CODEX_RELAY_AGENT_TOKEN),
|
|
121
|
+
CODEX_HOME: optionalNonEmpty(env.CODEX_HOME),
|
|
122
|
+
CODEX_COMMAND: optionalNonEmpty(env.CODEX_COMMAND),
|
|
123
|
+
CLAUDE_HOME: optionalNonEmpty(env.CLAUDE_HOME),
|
|
124
|
+
CLAUDE_COMMAND: optionalNonEmpty(env.CLAUDE_COMMAND),
|
|
125
|
+
OPENCODE_HOME: optionalNonEmpty(env.OPENCODE_HOME),
|
|
126
|
+
OPENCODE_COMMAND: optionalNonEmpty(env.OPENCODE_COMMAND),
|
|
127
|
+
REMOTE_CODEX_ENABLED_AGENT_PROVIDERS: optionalNonEmpty(
|
|
128
|
+
env.REMOTE_CODEX_ENABLED_AGENT_PROVIDERS
|
|
129
|
+
)
|
|
130
|
+
};
|
|
131
|
+
}
|
|
100
132
|
function resolveDatabaseUrl(nodeEnv, value) {
|
|
101
133
|
if (value && value.trim()) {
|
|
102
134
|
return path.resolve(value);
|
|
@@ -107,8 +139,9 @@ function resolveDatabaseUrl(nodeEnv, value) {
|
|
|
107
139
|
return path.resolve(".local", "supervisor-dev.sqlite");
|
|
108
140
|
}
|
|
109
141
|
function loadRuntimeConfig(env = process.env) {
|
|
110
|
-
const parsed = envSchema.parse(env);
|
|
142
|
+
const parsed = envSchema.parse(normalizeOptionalEnv(env));
|
|
111
143
|
const nodeEnv = parsed.NODE_ENV ?? "development";
|
|
144
|
+
const mode = parsed.REMOTE_CODEX_MODE ?? "local";
|
|
112
145
|
const workspaceRoot = parsed.WORKSPACE_ROOT?.trim() ? path.resolve(parsed.WORKSPACE_ROOT) : os.homedir();
|
|
113
146
|
const disableRequestLogging = parsed.DISABLE_REQUEST_LOGGING === void 0 ? nodeEnv === "production" : ["1", "true", "yes", "on"].includes(parsed.DISABLE_REQUEST_LOGGING.toLowerCase());
|
|
114
147
|
const enabledProviders = new Set(
|
|
@@ -119,6 +152,7 @@ function loadRuntimeConfig(env = process.env) {
|
|
|
119
152
|
const opencodeHome = parsed.OPENCODE_HOME?.trim() ? path.resolve(parsed.OPENCODE_HOME) : path.join(os.homedir(), agentBackendMetadata.opencode.defaultHomeDir);
|
|
120
153
|
return {
|
|
121
154
|
nodeEnv,
|
|
155
|
+
mode,
|
|
122
156
|
host: parsed.HOST ?? "127.0.0.1",
|
|
123
157
|
port: parsed.PORT ?? 8787,
|
|
124
158
|
logLevel: parsed.LOG_LEVEL ?? (nodeEnv === "production" ? "warn" : "info"),
|
|
@@ -127,6 +161,16 @@ function loadRuntimeConfig(env = process.env) {
|
|
|
127
161
|
appVersion: parsed.APP_VERSION ?? "0.1.0",
|
|
128
162
|
workspaceRoot,
|
|
129
163
|
databaseUrl: resolveDatabaseUrl(nodeEnv, parsed.DATABASE_URL),
|
|
164
|
+
auth: {
|
|
165
|
+
adminUsername: parsed.REMOTE_CODEX_ADMIN_USERNAME ?? null,
|
|
166
|
+
adminPassword: parsed.REMOTE_CODEX_ADMIN_PASSWORD ?? null,
|
|
167
|
+
sessionSecret: parsed.REMOTE_CODEX_SESSION_SECRET ?? null,
|
|
168
|
+
sessionTtlSeconds: parsed.REMOTE_CODEX_SESSION_TTL_SECONDS ?? 60 * 60 * 24 * 7
|
|
169
|
+
},
|
|
170
|
+
relay: {
|
|
171
|
+
serverUrl: parsed.REMOTE_CODEX_RELAY_SERVER_URL ?? null,
|
|
172
|
+
agentToken: parsed.REMOTE_CODEX_RELAY_AGENT_TOKEN ?? null
|
|
173
|
+
},
|
|
130
174
|
agentProviders: {
|
|
131
175
|
codex: {
|
|
132
176
|
provider: "codex",
|
|
@@ -564,6 +608,7 @@ var PluginRegistry = class {
|
|
|
564
608
|
|
|
565
609
|
// ../../packages/plugin-runtime/src/artifacts.ts
|
|
566
610
|
var artifactFenceLanguages = /* @__PURE__ */ new Set(["artifact", "remote-codex-artifact"]);
|
|
611
|
+
var remoteCodexMoleculeMcpToolName = "remote_codex_render_molecule";
|
|
567
612
|
function stableArtifactId(input) {
|
|
568
613
|
return [
|
|
569
614
|
"artifact",
|
|
@@ -634,6 +679,134 @@ function findFencedBlocks(text2, languages) {
|
|
|
634
679
|
}
|
|
635
680
|
return blocks;
|
|
636
681
|
}
|
|
682
|
+
function readBalancedJsonFragment(text2, startIndex) {
|
|
683
|
+
const opener = text2[startIndex];
|
|
684
|
+
const expectedClose = opener === "{" ? "}" : opener === "[" ? "]" : null;
|
|
685
|
+
if (!expectedClose) {
|
|
686
|
+
return null;
|
|
687
|
+
}
|
|
688
|
+
const stack = [expectedClose];
|
|
689
|
+
let inString = false;
|
|
690
|
+
let escaping = false;
|
|
691
|
+
for (let index = startIndex + 1; index < text2.length; index += 1) {
|
|
692
|
+
const char = text2[index];
|
|
693
|
+
if (inString) {
|
|
694
|
+
if (escaping) {
|
|
695
|
+
escaping = false;
|
|
696
|
+
} else if (char === "\\") {
|
|
697
|
+
escaping = true;
|
|
698
|
+
} else if (char === '"') {
|
|
699
|
+
inString = false;
|
|
700
|
+
}
|
|
701
|
+
continue;
|
|
702
|
+
}
|
|
703
|
+
if (char === '"') {
|
|
704
|
+
inString = true;
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
if (char === "{") {
|
|
708
|
+
stack.push("}");
|
|
709
|
+
continue;
|
|
710
|
+
}
|
|
711
|
+
if (char === "[") {
|
|
712
|
+
stack.push("]");
|
|
713
|
+
continue;
|
|
714
|
+
}
|
|
715
|
+
if (char === stack.at(-1)) {
|
|
716
|
+
stack.pop();
|
|
717
|
+
if (stack.length === 0) {
|
|
718
|
+
return text2.slice(startIndex, index + 1);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
return null;
|
|
723
|
+
}
|
|
724
|
+
function containsArtifactFence(text2) {
|
|
725
|
+
return text2.includes("```artifact") || text2.includes("```remote-codex-artifact") || text2.includes("~~~artifact") || text2.includes("~~~remote-codex-artifact");
|
|
726
|
+
}
|
|
727
|
+
function collectArtifactCandidateStrings(value, output, budget, depth = 0) {
|
|
728
|
+
if (output.length >= 20 || depth > 12 || budget.nodes <= 0) {
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
budget.nodes -= 1;
|
|
732
|
+
if (typeof value === "string") {
|
|
733
|
+
if (containsArtifactFence(value)) {
|
|
734
|
+
output.push(value);
|
|
735
|
+
}
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
if (Array.isArray(value)) {
|
|
739
|
+
for (const entry of value) {
|
|
740
|
+
collectArtifactCandidateStrings(entry, output, budget, depth + 1);
|
|
741
|
+
if (output.length >= 20 || budget.nodes <= 0) {
|
|
742
|
+
break;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
if (value && typeof value === "object") {
|
|
748
|
+
for (const entry of Object.values(value)) {
|
|
749
|
+
collectArtifactCandidateStrings(entry, output, budget, depth + 1);
|
|
750
|
+
if (output.length >= 20 || budget.nodes <= 0) {
|
|
751
|
+
break;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
function parseJsonArtifactCandidateStrings(fragment, output) {
|
|
757
|
+
try {
|
|
758
|
+
collectArtifactCandidateStrings(JSON.parse(fragment), output, { nodes: 2e3 });
|
|
759
|
+
} catch {
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
function findJsonFragmentAt(text2, index) {
|
|
763
|
+
let startIndex = index;
|
|
764
|
+
while (startIndex < text2.length && /\s/.test(text2[startIndex] ?? "")) {
|
|
765
|
+
startIndex += 1;
|
|
766
|
+
}
|
|
767
|
+
const char = text2[startIndex];
|
|
768
|
+
if (char !== "{" && char !== "[") {
|
|
769
|
+
return null;
|
|
770
|
+
}
|
|
771
|
+
return readBalancedJsonFragment(text2, startIndex);
|
|
772
|
+
}
|
|
773
|
+
function extractToolJsonArtifactCandidateStrings(text2) {
|
|
774
|
+
const values = [];
|
|
775
|
+
const seenFragments = /* @__PURE__ */ new Set();
|
|
776
|
+
const addFragment = (fragment) => {
|
|
777
|
+
if (!fragment || seenFragments.has(fragment)) {
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
seenFragments.add(fragment);
|
|
781
|
+
parseJsonArtifactCandidateStrings(fragment, values);
|
|
782
|
+
};
|
|
783
|
+
addFragment(findJsonFragmentAt(text2, 0));
|
|
784
|
+
const labelPattern = /(?:^|\n)(?:Arguments|Result)\n/g;
|
|
785
|
+
for (const match of text2.matchAll(labelPattern)) {
|
|
786
|
+
addFragment(findJsonFragmentAt(text2, match.index + match[0].length));
|
|
787
|
+
if (seenFragments.size >= 4 || values.length >= 20) {
|
|
788
|
+
break;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
return values;
|
|
792
|
+
}
|
|
793
|
+
function extractArtifactCandidateTexts(item, text2) {
|
|
794
|
+
const values = [text2];
|
|
795
|
+
if (item.kind !== "toolCall" || ![item.text, item.previewText, text2].some(
|
|
796
|
+
(value) => typeof value === "string" && value.includes(remoteCodexMoleculeMcpToolName)
|
|
797
|
+
)) {
|
|
798
|
+
return values;
|
|
799
|
+
}
|
|
800
|
+
const seen = new Set(values);
|
|
801
|
+
for (const value of extractToolJsonArtifactCandidateStrings(text2)) {
|
|
802
|
+
if (seen.has(value)) {
|
|
803
|
+
continue;
|
|
804
|
+
}
|
|
805
|
+
seen.add(value);
|
|
806
|
+
values.push(value);
|
|
807
|
+
}
|
|
808
|
+
return values;
|
|
809
|
+
}
|
|
637
810
|
var ManifestArtifactExtractor = class {
|
|
638
811
|
constructor(manifests) {
|
|
639
812
|
this.manifests = manifests;
|
|
@@ -650,46 +823,57 @@ var ManifestArtifactExtractor = class {
|
|
|
650
823
|
return results;
|
|
651
824
|
}
|
|
652
825
|
extractFromItem(turn, item, context) {
|
|
653
|
-
|
|
826
|
+
const extractableText = [item.text, item.detailText ?? ""].map((entry) => entry.trim()).filter(Boolean).join("\n\n");
|
|
827
|
+
if (item.kind === "artifact" || !extractableText) {
|
|
654
828
|
return [];
|
|
655
829
|
}
|
|
656
830
|
const artifacts = [];
|
|
657
|
-
artifacts.push(...this.extractJsonArtifacts(turn, item, context));
|
|
831
|
+
artifacts.push(...this.extractJsonArtifacts(turn, item, context, extractableText));
|
|
658
832
|
return artifacts;
|
|
659
833
|
}
|
|
660
|
-
extractJsonArtifacts(turn, item, context) {
|
|
834
|
+
extractJsonArtifacts(turn, item, context, text2) {
|
|
661
835
|
const artifacts = [];
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
836
|
+
const extractableTexts = extractArtifactCandidateTexts(item, text2);
|
|
837
|
+
const seenBlocks = /* @__PURE__ */ new Set();
|
|
838
|
+
for (const extractableText of extractableTexts) {
|
|
839
|
+
for (const block of findFencedBlocks(extractableText, artifactFenceLanguages)) {
|
|
840
|
+
const blockKey = `${block.language}
|
|
841
|
+
${block.content}`;
|
|
842
|
+
if (seenBlocks.has(blockKey)) {
|
|
843
|
+
continue;
|
|
844
|
+
}
|
|
845
|
+
seenBlocks.add(blockKey);
|
|
846
|
+
if (!block.content) {
|
|
847
|
+
continue;
|
|
848
|
+
}
|
|
849
|
+
let parsed;
|
|
850
|
+
try {
|
|
851
|
+
parsed = JSON.parse(block.content);
|
|
852
|
+
} catch {
|
|
853
|
+
continue;
|
|
854
|
+
}
|
|
855
|
+
const payload = maybeParseArtifactPayload(parsed);
|
|
856
|
+
if (!payload || !this.hasArtifactType(payload.artifactType)) {
|
|
857
|
+
continue;
|
|
858
|
+
}
|
|
859
|
+
artifacts.push({
|
|
860
|
+
id: stableArtifactId({
|
|
861
|
+
turnId: turn.id,
|
|
862
|
+
itemId: item.id,
|
|
863
|
+
pluginId: this.pluginIdForArtifactType(payload.artifactType) ?? "unknown",
|
|
864
|
+
artifactType: payload.artifactType,
|
|
865
|
+
index: artifacts.length
|
|
866
|
+
}),
|
|
680
867
|
pluginId: this.pluginIdForArtifactType(payload.artifactType) ?? "unknown",
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
sourceItemId: item.id,
|
|
691
|
-
createdAt: context.now
|
|
692
|
-
});
|
|
868
|
+
type: payload.artifactType,
|
|
869
|
+
title: payload.title ?? "Plugin artifact",
|
|
870
|
+
summaryText: payload.summaryText ?? null,
|
|
871
|
+
payload: payload.payload,
|
|
872
|
+
sourceTurnId: turn.id,
|
|
873
|
+
sourceItemId: item.id,
|
|
874
|
+
createdAt: context.now
|
|
875
|
+
});
|
|
876
|
+
}
|
|
693
877
|
}
|
|
694
878
|
return artifacts;
|
|
695
879
|
}
|
|
@@ -760,6 +944,19 @@ function optionalStringArray(value, field) {
|
|
|
760
944
|
}
|
|
761
945
|
return value;
|
|
762
946
|
}
|
|
947
|
+
function optionalStringRecord(value, field) {
|
|
948
|
+
if (value === void 0) {
|
|
949
|
+
return void 0;
|
|
950
|
+
}
|
|
951
|
+
if (!isRecord2(value)) {
|
|
952
|
+
throw new Error(`Plugin manifest field "${field}" must be an object.`);
|
|
953
|
+
}
|
|
954
|
+
const entries = Object.entries(value);
|
|
955
|
+
if (entries.some(([, entry]) => typeof entry !== "string")) {
|
|
956
|
+
throw new Error(`Plugin manifest field "${field}" must contain string values.`);
|
|
957
|
+
}
|
|
958
|
+
return Object.fromEntries(entries);
|
|
959
|
+
}
|
|
763
960
|
function parsePluginManifest(value) {
|
|
764
961
|
if (!isRecord2(value)) {
|
|
765
962
|
throw new Error("Plugin manifest must be an object.");
|
|
@@ -788,6 +985,14 @@ function parsePluginManifest(value) {
|
|
|
788
985
|
if (backend !== void 0 && !isRecord2(backend)) {
|
|
789
986
|
throw new Error('Plugin manifest field "capabilities.backend" must be an object.');
|
|
790
987
|
}
|
|
988
|
+
const modelHints = capabilities.modelHints;
|
|
989
|
+
if (modelHints !== void 0 && !Array.isArray(modelHints)) {
|
|
990
|
+
throw new Error('Plugin manifest field "capabilities.modelHints" must be an array.');
|
|
991
|
+
}
|
|
992
|
+
const mcpServers = capabilities.mcpServers;
|
|
993
|
+
if (mcpServers !== void 0 && !Array.isArray(mcpServers)) {
|
|
994
|
+
throw new Error('Plugin manifest field "capabilities.mcpServers" must be an array.');
|
|
995
|
+
}
|
|
791
996
|
return {
|
|
792
997
|
id: assertString(value.id, "id"),
|
|
793
998
|
name: assertString(value.name, "name"),
|
|
@@ -824,12 +1029,46 @@ function parsePluginManifest(value) {
|
|
|
824
1029
|
return {
|
|
825
1030
|
id: assertString(entry.id, `capabilities.threadPanels[${index}].id`),
|
|
826
1031
|
label: assertString(entry.label, `capabilities.threadPanels[${index}].label`),
|
|
1032
|
+
...typeof entry.kind === "string" ? { kind: entry.kind } : {},
|
|
827
1033
|
artifactTypes: optionalStringArray(
|
|
828
1034
|
entry.artifactTypes,
|
|
829
1035
|
`capabilities.threadPanels[${index}].artifactTypes`
|
|
830
1036
|
) ?? []
|
|
831
1037
|
};
|
|
832
1038
|
}),
|
|
1039
|
+
modelHints: (modelHints ?? []).map((entry, index) => {
|
|
1040
|
+
if (!isRecord2(entry)) {
|
|
1041
|
+
throw new Error(
|
|
1042
|
+
`Plugin manifest field "capabilities.modelHints[${index}]" must be an object.`
|
|
1043
|
+
);
|
|
1044
|
+
}
|
|
1045
|
+
return {
|
|
1046
|
+
id: assertString(entry.id, `capabilities.modelHints[${index}].id`),
|
|
1047
|
+
text: assertString(entry.text, `capabilities.modelHints[${index}].text`)
|
|
1048
|
+
};
|
|
1049
|
+
}),
|
|
1050
|
+
mcpServers: (mcpServers ?? []).map((entry, index) => {
|
|
1051
|
+
if (!isRecord2(entry)) {
|
|
1052
|
+
throw new Error(
|
|
1053
|
+
`Plugin manifest field "capabilities.mcpServers[${index}]" must be an object.`
|
|
1054
|
+
);
|
|
1055
|
+
}
|
|
1056
|
+
const args = optionalStringArray(
|
|
1057
|
+
entry.args,
|
|
1058
|
+
`capabilities.mcpServers[${index}].args`
|
|
1059
|
+
);
|
|
1060
|
+
const env = optionalStringRecord(
|
|
1061
|
+
entry.env,
|
|
1062
|
+
`capabilities.mcpServers[${index}].env`
|
|
1063
|
+
);
|
|
1064
|
+
return {
|
|
1065
|
+
id: assertString(entry.id, `capabilities.mcpServers[${index}].id`),
|
|
1066
|
+
name: assertString(entry.name, `capabilities.mcpServers[${index}].name`),
|
|
1067
|
+
command: assertString(entry.command, `capabilities.mcpServers[${index}].command`),
|
|
1068
|
+
...args ? { args } : {},
|
|
1069
|
+
...env ? { env } : {}
|
|
1070
|
+
};
|
|
1071
|
+
}),
|
|
833
1072
|
...frontend ? {
|
|
834
1073
|
frontend: {
|
|
835
1074
|
...typeof frontend.entry === "string" ? { entry: frontend.entry } : {},
|
|
@@ -2011,7 +2250,7 @@ Subquery.prototype.getSQL = function() {
|
|
|
2011
2250
|
function mapResultRow(columns, row, joinsNotNullableMap) {
|
|
2012
2251
|
const nullifyMap = {};
|
|
2013
2252
|
const result = columns.reduce(
|
|
2014
|
-
(result2, { path:
|
|
2253
|
+
(result2, { path: path24, field }, columnIndex) => {
|
|
2015
2254
|
let decoder;
|
|
2016
2255
|
if (is(field, Column)) {
|
|
2017
2256
|
decoder = field;
|
|
@@ -2021,8 +2260,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
|
|
|
2021
2260
|
decoder = field.sql.decoder;
|
|
2022
2261
|
}
|
|
2023
2262
|
let node = result2;
|
|
2024
|
-
for (const [pathChunkIndex, pathChunk] of
|
|
2025
|
-
if (pathChunkIndex <
|
|
2263
|
+
for (const [pathChunkIndex, pathChunk] of path24.entries()) {
|
|
2264
|
+
if (pathChunkIndex < path24.length - 1) {
|
|
2026
2265
|
if (!(pathChunk in node)) {
|
|
2027
2266
|
node[pathChunk] = {};
|
|
2028
2267
|
}
|
|
@@ -2030,8 +2269,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
|
|
|
2030
2269
|
} else {
|
|
2031
2270
|
const rawValue = row[columnIndex];
|
|
2032
2271
|
const value = node[pathChunk] = rawValue === null ? null : decoder.mapFromDriverValue(rawValue);
|
|
2033
|
-
if (joinsNotNullableMap && is(field, Column) &&
|
|
2034
|
-
const objectName =
|
|
2272
|
+
if (joinsNotNullableMap && is(field, Column) && path24.length === 2) {
|
|
2273
|
+
const objectName = path24[0];
|
|
2035
2274
|
if (!(objectName in nullifyMap)) {
|
|
2036
2275
|
nullifyMap[objectName] = value === null ? getTableName(field.table) : false;
|
|
2037
2276
|
} else if (typeof nullifyMap[objectName] === "string" && nullifyMap[objectName] !== getTableName(field.table)) {
|
|
@@ -6132,6 +6371,7 @@ var shellSessions = sqliteTable("shell_sessions", {
|
|
|
6132
6371
|
id: text("id").primaryKey(),
|
|
6133
6372
|
workspaceId: text("workspace_id").notNull(),
|
|
6134
6373
|
threadId: text("thread_id"),
|
|
6374
|
+
label: text("label"),
|
|
6135
6375
|
tmuxSessionName: text("tmux_session_name"),
|
|
6136
6376
|
cwd: text("cwd").notNull(),
|
|
6137
6377
|
status: text("status"),
|
|
@@ -6722,12 +6962,16 @@ function getShellSessionRecordById(db, id) {
|
|
|
6722
6962
|
function getShellSessionRecordByThreadId(db, threadId) {
|
|
6723
6963
|
return db.select().from(shellSessions).where(eq(shellSessions.threadId, threadId)).get();
|
|
6724
6964
|
}
|
|
6965
|
+
function listShellSessionRecordsByThreadId(db, threadId) {
|
|
6966
|
+
return db.select().from(shellSessions).where(eq(shellSessions.threadId, threadId)).orderBy(desc(shellSessions.lastActivityAt), desc(shellSessions.createdAt)).all();
|
|
6967
|
+
}
|
|
6725
6968
|
function createShellSessionRecord(db, input) {
|
|
6726
6969
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6727
6970
|
const record = {
|
|
6728
6971
|
id: randomUUID(),
|
|
6729
6972
|
workspaceId: input.workspaceId,
|
|
6730
6973
|
threadId: input.threadId,
|
|
6974
|
+
label: input.label ?? null,
|
|
6731
6975
|
tmuxSessionName: input.tmuxSessionName,
|
|
6732
6976
|
cwd: input.cwd,
|
|
6733
6977
|
status: input.status,
|
|
@@ -7509,12 +7753,12 @@ var CodexAppServerManager = class extends EventEmitter3 {
|
|
|
7509
7753
|
serviceTier: input.serviceTier === void 0 ? void 0 : input.serviceTier,
|
|
7510
7754
|
effort: input.effort ?? null,
|
|
7511
7755
|
sandboxPolicy: input.sandboxPolicy ?? null,
|
|
7512
|
-
collaborationMode: input.collaborationMode ? {
|
|
7513
|
-
mode: input.collaborationMode,
|
|
7756
|
+
collaborationMode: input.collaborationMode || input.developerInstructions ? {
|
|
7757
|
+
mode: input.collaborationMode ?? "default",
|
|
7514
7758
|
settings: {
|
|
7515
7759
|
model: input.model ?? "",
|
|
7516
7760
|
reasoning_effort: input.effort ?? null,
|
|
7517
|
-
developer_instructions: null
|
|
7761
|
+
developer_instructions: input.developerInstructions ?? null
|
|
7518
7762
|
}
|
|
7519
7763
|
} : null
|
|
7520
7764
|
});
|
|
@@ -8311,7 +8555,7 @@ function extractFileChangeEntries(item) {
|
|
|
8311
8555
|
isRecord4(entry.summary) ? entry.summary : null,
|
|
8312
8556
|
isRecord4(entry.diff) ? entry.diff : null
|
|
8313
8557
|
].filter((candidate) => Boolean(candidate));
|
|
8314
|
-
const
|
|
8558
|
+
const path24 = uniqueStrings([
|
|
8315
8559
|
stringOrNull(valueFromRecords(nestedRecords, ["path", "filePath", "targetPath"])),
|
|
8316
8560
|
stringOrNull(
|
|
8317
8561
|
valueFromRecords(nestedRecords, [
|
|
@@ -8358,7 +8602,7 @@ function extractFileChangeEntries(item) {
|
|
|
8358
8602
|
const diffStats = explicitAdditions === 0 && explicitDeletions === 0 && diffText ? countUnifiedDiffStats(diffText) : null;
|
|
8359
8603
|
const additions = explicitAdditions || diffStats?.additions || 0;
|
|
8360
8604
|
const deletions = explicitDeletions || diffStats?.deletions || 0;
|
|
8361
|
-
const normalizedPath =
|
|
8605
|
+
const normalizedPath = path24 ?? (diffText ? projectRelativePathLabel(extractPathFromDiffText(diffText)) : null);
|
|
8362
8606
|
if (!normalizedPath && additions === 0 && deletions === 0) {
|
|
8363
8607
|
return null;
|
|
8364
8608
|
}
|
|
@@ -10167,6 +10411,9 @@ var CodexRuntimeAdapter = class extends EventEmitter4 {
|
|
|
10167
10411
|
if (input.collaborationMode !== void 0) {
|
|
10168
10412
|
turnInput.collaborationMode = input.collaborationMode;
|
|
10169
10413
|
}
|
|
10414
|
+
if (input.developerInstructions !== void 0) {
|
|
10415
|
+
turnInput.developerInstructions = input.developerInstructions;
|
|
10416
|
+
}
|
|
10170
10417
|
const sandboxPolicy = buildSandboxPolicy(input.sandboxMode, input.workspacePath);
|
|
10171
10418
|
if (sandboxPolicy !== void 0) {
|
|
10172
10419
|
turnInput.sandboxPolicy = sandboxPolicy;
|
|
@@ -11899,13 +12146,16 @@ var ClaudeRuntimeAdapter = class extends EventEmitter5 {
|
|
|
11899
12146
|
const servers = await active.query.mcpServerStatus();
|
|
11900
12147
|
return servers.map((server) => this.mapMcpServer(server));
|
|
11901
12148
|
}
|
|
11902
|
-
mapProviderRequest(request,
|
|
12149
|
+
mapProviderRequest(request, options) {
|
|
12150
|
+
void options;
|
|
11903
12151
|
return mapClaudeAskUserQuestionRequest(request);
|
|
11904
12152
|
}
|
|
11905
12153
|
buildProviderRequestResponse(pending, input) {
|
|
11906
12154
|
return buildClaudeProviderRequestResponse(pending, input);
|
|
11907
12155
|
}
|
|
11908
|
-
respondToProviderRequest(
|
|
12156
|
+
respondToProviderRequest(id, result) {
|
|
12157
|
+
void id;
|
|
12158
|
+
void result;
|
|
11909
12159
|
}
|
|
11910
12160
|
async consumeQuery(state) {
|
|
11911
12161
|
const rawMessages = [];
|
|
@@ -12593,14 +12843,14 @@ function displayPath(pathValue, options) {
|
|
|
12593
12843
|
}
|
|
12594
12844
|
return relativePath;
|
|
12595
12845
|
}
|
|
12596
|
-
function toolIsLowInformationPatch(normalized, state, input, patchText,
|
|
12846
|
+
function toolIsLowInformationPatch(normalized, state, input, patchText, path24, metadataStats) {
|
|
12597
12847
|
if (normalized !== "applypatch" && normalized !== "patch") {
|
|
12598
12848
|
return false;
|
|
12599
12849
|
}
|
|
12600
12850
|
if (toolStateStatus(state) !== "running") {
|
|
12601
12851
|
return false;
|
|
12602
12852
|
}
|
|
12603
|
-
if (
|
|
12853
|
+
if (path24 || patchText || metadataStats || stringValue2(state.output)) {
|
|
12604
12854
|
return false;
|
|
12605
12855
|
}
|
|
12606
12856
|
return !isRecord9(input) || Object.keys(input).length === 0;
|
|
@@ -12643,7 +12893,7 @@ function fileChangeStatsFromMetadata(metadata) {
|
|
|
12643
12893
|
if (files.length === 0) {
|
|
12644
12894
|
return null;
|
|
12645
12895
|
}
|
|
12646
|
-
const paths = files.map((file) => stringValue2(file.filePath) ?? stringValue2(file.path) ?? stringValue2(file.relativePath)).filter((
|
|
12896
|
+
const paths = files.map((file) => stringValue2(file.filePath) ?? stringValue2(file.path) ?? stringValue2(file.relativePath)).filter((path24) => Boolean(path24));
|
|
12647
12897
|
const addedLines = files.reduce((total, file) => total + (numberValue(file.additions) ?? numberValue(file.addedLines) ?? numberValue(file.added) ?? 0), 0);
|
|
12648
12898
|
const removedLines = files.reduce((total, file) => total + (numberValue(file.deletions) ?? numberValue(file.removedLines) ?? numberValue(file.removed) ?? 0), 0);
|
|
12649
12899
|
return {
|
|
@@ -12791,28 +13041,28 @@ function mapAssistantTool(messageId2, tool, options) {
|
|
|
12791
13041
|
].includes(normalized)) {
|
|
12792
13042
|
const metadataStats = fileChangeStatsFromMetadata(state.metadata);
|
|
12793
13043
|
const patchText = isRecord9(input) ? stringValue2(input.patchText) ?? stringValue2(input.patch) ?? stringValue2(input.diff) : null;
|
|
12794
|
-
const
|
|
12795
|
-
if (toolIsLowInformationPatch(normalized, state, input, patchText,
|
|
13044
|
+
const path24 = metadataStats?.path ?? filePathFromInput(input) ?? extractPathFromPatchText(patchText);
|
|
13045
|
+
if (toolIsLowInformationPatch(normalized, state, input, patchText, path24, metadataStats)) {
|
|
12796
13046
|
return null;
|
|
12797
13047
|
}
|
|
12798
13048
|
const output = stringValue2(state.output);
|
|
12799
13049
|
const diffStats = countUnifiedDiffStats2(patchText);
|
|
12800
|
-
const displayFilePath = displayPath(
|
|
13050
|
+
const displayFilePath = displayPath(path24, options);
|
|
12801
13051
|
return {
|
|
12802
13052
|
id,
|
|
12803
13053
|
kind: "fileChange",
|
|
12804
13054
|
text: metadataStats ? metadataStats.changedFiles > 1 ? `${metadataStats.changedFiles} changed files` : displayFilePath ?? metadataStats.previewText : displayFilePath ?? output ?? summary ?? name,
|
|
12805
13055
|
previewText: metadataStats ? metadataStats.changedFiles > 1 ? `${metadataStats.changedFiles} changed files` : displayFilePath ?? metadataStats.previewText : displayFilePath ? `${name}: ${displayFilePath}` : output ?? summary ?? name,
|
|
12806
13056
|
detailText,
|
|
12807
|
-
changedFiles: metadataStats?.changedFiles ?? (
|
|
13057
|
+
changedFiles: metadataStats?.changedFiles ?? (path24 ? 1 : null),
|
|
12808
13058
|
addedLines: metadataStats?.addedLines ?? diffStats?.addedLines ?? null,
|
|
12809
13059
|
removedLines: metadataStats?.removedLines ?? diffStats?.removedLines ?? null,
|
|
12810
13060
|
status: toolStateStatus(state)
|
|
12811
13061
|
};
|
|
12812
13062
|
}
|
|
12813
13063
|
if (["read", "grep", "glob", "list", "ls", "bashoutput"].includes(normalized)) {
|
|
12814
|
-
const
|
|
12815
|
-
const text2 = displayPath(
|
|
13064
|
+
const path24 = filePathFromInput(input);
|
|
13065
|
+
const text2 = displayPath(path24, options) ?? summary ?? name;
|
|
12816
13066
|
return {
|
|
12817
13067
|
id,
|
|
12818
13068
|
kind: "fileRead",
|
|
@@ -14843,7 +15093,7 @@ function shouldPersistLiveHistoryItem(item) {
|
|
|
14843
15093
|
function shouldPersistFinalHistoryItem(item) {
|
|
14844
15094
|
return item.kind === "agentMessage" || shouldPersistLiveHistoryItem(item);
|
|
14845
15095
|
}
|
|
14846
|
-
function shouldPersistRuntimeFinalHistoryItem(item
|
|
15096
|
+
function shouldPersistRuntimeFinalHistoryItem(item) {
|
|
14847
15097
|
if (item.kind === "agentMessage" && isTransientAgentHistoryItem(item)) {
|
|
14848
15098
|
return false;
|
|
14849
15099
|
}
|
|
@@ -14895,13 +15145,7 @@ function copyPersistedOrderingHints(item, persistedItem) {
|
|
|
14895
15145
|
return nextItem;
|
|
14896
15146
|
}
|
|
14897
15147
|
function sortHistoryItemsBySequence(items) {
|
|
14898
|
-
|
|
14899
|
-
return items;
|
|
14900
|
-
}
|
|
14901
|
-
return items.map((item, index) => ({ item, index })).sort((left, right) => {
|
|
14902
|
-
const sequenceDelta = historyItemSequence(left.item) - historyItemSequence(right.item);
|
|
14903
|
-
return sequenceDelta === 0 ? left.index - right.index : sequenceDelta;
|
|
14904
|
-
}).map((entry) => entry.item);
|
|
15148
|
+
return sortTurnItemsByRecordedSequence(items);
|
|
14905
15149
|
}
|
|
14906
15150
|
function sortTurnItemsByRecordedSequence(items) {
|
|
14907
15151
|
const leadingItems = [];
|
|
@@ -14914,9 +15158,6 @@ function sortTurnItemsByRecordedSequence(items) {
|
|
|
14914
15158
|
if (!trailingItems.some(hasHistoryItemSequence)) {
|
|
14915
15159
|
return items;
|
|
14916
15160
|
}
|
|
14917
|
-
if (trailingItems.some((item) => historyItemTranscriptOrder(item) !== null)) {
|
|
14918
|
-
return items;
|
|
14919
|
-
}
|
|
14920
15161
|
const sequenceValues = trailingItems.map((item) => historyItemSequence(item)).filter(Number.isFinite);
|
|
14921
15162
|
const maxSequence = sequenceValues.length > 0 ? Math.max(...sequenceValues) : 0;
|
|
14922
15163
|
const orderedItems2 = [];
|
|
@@ -15074,7 +15315,9 @@ function agentTurnToThreadTurnDto(turn, deferredDetails) {
|
|
|
15074
15315
|
startedAt: turn.startedAt ?? parseUuidV7Timestamp(turn.providerTurnId),
|
|
15075
15316
|
status: turn.status,
|
|
15076
15317
|
error: turn.error?.message ?? null,
|
|
15077
|
-
items: visibleRuntimeTurnItems(turn.items)
|
|
15318
|
+
items: visibleRuntimeTurnItems(turn.items).map(
|
|
15319
|
+
(item, transcriptIndex) => item.transcriptOrder === transcriptIndex ? item : { ...item, transcriptOrder: transcriptIndex }
|
|
15320
|
+
)
|
|
15078
15321
|
};
|
|
15079
15322
|
return deferredDetails ? deferLargeHistoryItemDetails(baseTurn, deferredDetails) : baseTurn;
|
|
15080
15323
|
}
|
|
@@ -15113,6 +15356,7 @@ var ThreadLiveStateStore = class {
|
|
|
15113
15356
|
threadTurnItemOrder = /* @__PURE__ */ new Map();
|
|
15114
15357
|
threadNextTurnItemSequence = /* @__PURE__ */ new Map();
|
|
15115
15358
|
threadMaterializedAgentMessageCounts = /* @__PURE__ */ new Map();
|
|
15359
|
+
threadAgentMessageOrderingHints = /* @__PURE__ */ new Map();
|
|
15116
15360
|
displayTurnIdForRuntimeTurn(localThreadId, runtimeTurnId) {
|
|
15117
15361
|
if (!runtimeTurnId) {
|
|
15118
15362
|
return null;
|
|
@@ -15155,6 +15399,7 @@ var ThreadLiveStateStore = class {
|
|
|
15155
15399
|
this.threadLivePlans.delete(localThreadId);
|
|
15156
15400
|
this.threadLiveItems.delete(localThreadId);
|
|
15157
15401
|
this.threadMaterializedAgentMessageCounts.delete(localThreadId);
|
|
15402
|
+
this.threadAgentMessageOrderingHints.delete(localThreadId);
|
|
15158
15403
|
this.clearRecordedTurnItemOrders(localThreadId);
|
|
15159
15404
|
this.runtimeDisplayTurnIds.delete(localThreadId);
|
|
15160
15405
|
this.hiddenRuntimeTurnIds.delete(localThreadId);
|
|
@@ -15179,10 +15424,12 @@ var ThreadLiveStateStore = class {
|
|
|
15179
15424
|
resetRecordedTurnItemOrder(localThreadId, turnId) {
|
|
15180
15425
|
this.threadTurnItemOrder.get(localThreadId)?.delete(turnId);
|
|
15181
15426
|
this.threadNextTurnItemSequence.get(localThreadId)?.delete(turnId);
|
|
15427
|
+
this.threadAgentMessageOrderingHints.get(localThreadId)?.delete(turnId);
|
|
15182
15428
|
}
|
|
15183
15429
|
clearRecordedTurnItemOrders(localThreadId) {
|
|
15184
15430
|
this.threadTurnItemOrder.delete(localThreadId);
|
|
15185
15431
|
this.threadNextTurnItemSequence.delete(localThreadId);
|
|
15432
|
+
this.threadAgentMessageOrderingHints.delete(localThreadId);
|
|
15186
15433
|
}
|
|
15187
15434
|
recordTurnItemOrder(localThreadId, turnId, itemId) {
|
|
15188
15435
|
let threadOrders = this.threadTurnItemOrder.get(localThreadId);
|
|
@@ -15212,6 +15459,71 @@ var ThreadLiveStateStore = class {
|
|
|
15212
15459
|
turnItemOrderSnapshot(localThreadId) {
|
|
15213
15460
|
return this.threadTurnItemOrder.get(localThreadId) ?? /* @__PURE__ */ new Map();
|
|
15214
15461
|
}
|
|
15462
|
+
finalTurnAgentMessageOrderingHints(localThreadId, turnId, items, options = {}) {
|
|
15463
|
+
const hints = /* @__PURE__ */ new Map();
|
|
15464
|
+
const turnOrder = this.threadTurnItemOrder.get(localThreadId)?.get(turnId);
|
|
15465
|
+
const liveAgentMessages = [
|
|
15466
|
+
...this.threadAgentMessageOrderingHints.get(localThreadId)?.get(turnId)?.values() ?? []
|
|
15467
|
+
].map((item) => ({
|
|
15468
|
+
id: item.id,
|
|
15469
|
+
text: normalizeAgentMessageForMatching(item.text),
|
|
15470
|
+
sequence: item.sequence
|
|
15471
|
+
}));
|
|
15472
|
+
const usedLiveAgentIds = /* @__PURE__ */ new Set();
|
|
15473
|
+
const finalAgentItems = items.filter((item) => item.kind === "agentMessage");
|
|
15474
|
+
for (const item of finalAgentItems) {
|
|
15475
|
+
const existingSequence = turnOrder?.get(item.id);
|
|
15476
|
+
if (existingSequence !== void 0) {
|
|
15477
|
+
hints.set(item.id, existingSequence);
|
|
15478
|
+
const matchingLiveAgent = liveAgentMessages.find(
|
|
15479
|
+
(liveAgent) => liveAgent.id === item.id || liveAgent.sequence === existingSequence
|
|
15480
|
+
);
|
|
15481
|
+
if (matchingLiveAgent) {
|
|
15482
|
+
usedLiveAgentIds.add(matchingLiveAgent.id);
|
|
15483
|
+
}
|
|
15484
|
+
continue;
|
|
15485
|
+
}
|
|
15486
|
+
const text2 = normalizeAgentMessageForMatching(item.text);
|
|
15487
|
+
if (!text2) {
|
|
15488
|
+
continue;
|
|
15489
|
+
}
|
|
15490
|
+
let bestMatch = null;
|
|
15491
|
+
for (const liveAgent of liveAgentMessages) {
|
|
15492
|
+
if (usedLiveAgentIds.has(liveAgent.id) || !liveAgent.text) {
|
|
15493
|
+
continue;
|
|
15494
|
+
}
|
|
15495
|
+
const score = agentMessageMatchScore(text2, liveAgent.text);
|
|
15496
|
+
if (score === 0 || bestMatch && bestMatch.score >= score) {
|
|
15497
|
+
continue;
|
|
15498
|
+
}
|
|
15499
|
+
bestMatch = {
|
|
15500
|
+
id: liveAgent.id,
|
|
15501
|
+
sequence: liveAgent.sequence,
|
|
15502
|
+
score
|
|
15503
|
+
};
|
|
15504
|
+
}
|
|
15505
|
+
if (bestMatch) {
|
|
15506
|
+
usedLiveAgentIds.add(bestMatch.id);
|
|
15507
|
+
hints.set(item.id, bestMatch.sequence);
|
|
15508
|
+
}
|
|
15509
|
+
}
|
|
15510
|
+
if (options.allowUnmatchedFallback ?? true) {
|
|
15511
|
+
const remainingLiveAgents = liveAgentMessages.filter((liveAgent) => !usedLiveAgentIds.has(liveAgent.id)).sort((left, right) => left.sequence - right.sequence);
|
|
15512
|
+
let remainingLiveAgentIndex = 0;
|
|
15513
|
+
for (const item of finalAgentItems) {
|
|
15514
|
+
if (hints.has(item.id) || !normalizeAgentMessageForMatching(item.text)) {
|
|
15515
|
+
continue;
|
|
15516
|
+
}
|
|
15517
|
+
const liveAgent = remainingLiveAgents[remainingLiveAgentIndex];
|
|
15518
|
+
if (!liveAgent) {
|
|
15519
|
+
break;
|
|
15520
|
+
}
|
|
15521
|
+
hints.set(item.id, liveAgent.sequence);
|
|
15522
|
+
remainingLiveAgentIndex += 1;
|
|
15523
|
+
}
|
|
15524
|
+
}
|
|
15525
|
+
return hints;
|
|
15526
|
+
}
|
|
15215
15527
|
getLiveItems(localThreadId, allTurns, visibleTurns = allTurns) {
|
|
15216
15528
|
const current = this.threadLiveItems.get(localThreadId);
|
|
15217
15529
|
if (!current) {
|
|
@@ -15227,10 +15539,8 @@ var ThreadLiveStateStore = class {
|
|
|
15227
15539
|
upsertLiveItem(localThreadId, turnId, item) {
|
|
15228
15540
|
const current = this.threadLiveItems.get(localThreadId);
|
|
15229
15541
|
const currentItems = current?.turnId === turnId ? current.items : [];
|
|
15230
|
-
const
|
|
15231
|
-
|
|
15232
|
-
item
|
|
15233
|
-
];
|
|
15542
|
+
const existingIndex = currentItems.findIndex((entry) => entry.id === item.id);
|
|
15543
|
+
const nextItems = existingIndex >= 0 ? currentItems.map((entry, index) => index === existingIndex ? item : entry) : [...currentItems, item];
|
|
15234
15544
|
this.setLiveItems(localThreadId, {
|
|
15235
15545
|
turnId,
|
|
15236
15546
|
items: sortHistoryItemsBySequence(nextItems),
|
|
@@ -15251,16 +15561,43 @@ var ThreadLiveStateStore = class {
|
|
|
15251
15561
|
text: input.delta,
|
|
15252
15562
|
sequence: input.sequence
|
|
15253
15563
|
};
|
|
15564
|
+
this.recordAgentMessageOrderingHint(
|
|
15565
|
+
input.localThreadId,
|
|
15566
|
+
input.turnId,
|
|
15567
|
+
nextItem,
|
|
15568
|
+
input.sequence
|
|
15569
|
+
);
|
|
15254
15570
|
this.setLiveItems(input.localThreadId, {
|
|
15255
15571
|
turnId: input.turnId,
|
|
15256
|
-
items: sortHistoryItemsBySequence(
|
|
15257
|
-
|
|
15258
|
-
|
|
15259
|
-
|
|
15572
|
+
items: sortHistoryItemsBySequence(
|
|
15573
|
+
existing ? currentItems.map(
|
|
15574
|
+
(entry) => entry.id === input.itemId ? nextItem : entry
|
|
15575
|
+
) : [...currentItems, nextItem]
|
|
15576
|
+
),
|
|
15260
15577
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
15261
15578
|
});
|
|
15262
15579
|
return nextItem;
|
|
15263
15580
|
}
|
|
15581
|
+
recordAgentMessageOrderingHint(localThreadId, turnId, item, sequence) {
|
|
15582
|
+
if (item.kind !== "agentMessage" || !Number.isFinite(sequence)) {
|
|
15583
|
+
return;
|
|
15584
|
+
}
|
|
15585
|
+
let threadHints = this.threadAgentMessageOrderingHints.get(localThreadId);
|
|
15586
|
+
if (!threadHints) {
|
|
15587
|
+
threadHints = /* @__PURE__ */ new Map();
|
|
15588
|
+
this.threadAgentMessageOrderingHints.set(localThreadId, threadHints);
|
|
15589
|
+
}
|
|
15590
|
+
let turnHints = threadHints.get(turnId);
|
|
15591
|
+
if (!turnHints) {
|
|
15592
|
+
turnHints = /* @__PURE__ */ new Map();
|
|
15593
|
+
threadHints.set(turnId, turnHints);
|
|
15594
|
+
}
|
|
15595
|
+
turnHints.set(item.id, {
|
|
15596
|
+
id: item.id,
|
|
15597
|
+
text: item.text,
|
|
15598
|
+
sequence
|
|
15599
|
+
});
|
|
15600
|
+
}
|
|
15264
15601
|
reconcileLiveItems(localThreadId, turns) {
|
|
15265
15602
|
const current = this.threadLiveItems.get(localThreadId);
|
|
15266
15603
|
if (!current) {
|
|
@@ -15312,6 +15649,21 @@ var ThreadLiveStateStore = class {
|
|
|
15312
15649
|
return nextLiveItems;
|
|
15313
15650
|
}
|
|
15314
15651
|
};
|
|
15652
|
+
function normalizeAgentMessageForMatching(text2) {
|
|
15653
|
+
return text2.replace(/\s+/g, " ").trim();
|
|
15654
|
+
}
|
|
15655
|
+
function agentMessageMatchScore(finalText, liveText) {
|
|
15656
|
+
if (finalText === liveText) {
|
|
15657
|
+
return 3;
|
|
15658
|
+
}
|
|
15659
|
+
if (liveText.length >= 8 && finalText.includes(liveText)) {
|
|
15660
|
+
return 2;
|
|
15661
|
+
}
|
|
15662
|
+
if (finalText.length >= 8 && liveText.includes(finalText)) {
|
|
15663
|
+
return 1;
|
|
15664
|
+
}
|
|
15665
|
+
return 0;
|
|
15666
|
+
}
|
|
15315
15667
|
|
|
15316
15668
|
// src/thread-usage-accounting.ts
|
|
15317
15669
|
var CONTEXT_BASELINE_TOKENS = 12e3;
|
|
@@ -15899,9 +16251,10 @@ var ThreadRuntimeEventProjector = class {
|
|
|
15899
16251
|
lastError: event.turn.error?.message ?? null,
|
|
15900
16252
|
lastTurnCompletedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
15901
16253
|
});
|
|
16254
|
+
callbacks.persistFinalTurnOrderingHints(record.id, turnId, turnItems);
|
|
16255
|
+
callbacks.persistRuntimeTurnItemsAsDisplayTurn(record.id, rawTurnId, turnId, turnItems);
|
|
15902
16256
|
liveState.setLivePlan(record.id, null);
|
|
15903
16257
|
liveState.setLiveItems(record.id, null);
|
|
15904
|
-
callbacks.persistRuntimeTurnItemsAsDisplayTurn(record.id, rawTurnId, turnId, turnItems);
|
|
15905
16258
|
if (rawTurnId !== turnId) {
|
|
15906
16259
|
callbacks.deletePersistedHistoryItemsForTurn(record.id, rawTurnId);
|
|
15907
16260
|
liveState.resetRecordedTurnItemOrder(record.id, rawTurnId);
|
|
@@ -16310,34 +16663,82 @@ var ThreadDetailAssembler = class {
|
|
|
16310
16663
|
}
|
|
16311
16664
|
input;
|
|
16312
16665
|
threadDetailCache = /* @__PURE__ */ new Map();
|
|
16313
|
-
getCache(localThreadId) {
|
|
16314
|
-
const
|
|
16315
|
-
if (!
|
|
16666
|
+
getCache(localThreadId, options = {}) {
|
|
16667
|
+
const bucket = this.threadDetailCache.get(localThreadId);
|
|
16668
|
+
if (!bucket) {
|
|
16316
16669
|
return null;
|
|
16317
16670
|
}
|
|
16318
|
-
|
|
16319
|
-
|
|
16671
|
+
const key = cacheKeyForDetailOptions(options);
|
|
16672
|
+
if (key === "uncached") {
|
|
16320
16673
|
return null;
|
|
16321
16674
|
}
|
|
16322
|
-
|
|
16675
|
+
const cached = key === "full" ? bucket.full : bucket.latestPages.get(key) ?? null;
|
|
16676
|
+
if (cached && !isExpiredThreadDetailCacheEntry(cached)) {
|
|
16677
|
+
return cached;
|
|
16678
|
+
}
|
|
16679
|
+
if (key === "full") {
|
|
16680
|
+
bucket.full = null;
|
|
16681
|
+
} else {
|
|
16682
|
+
bucket.latestPages.delete(key);
|
|
16683
|
+
}
|
|
16684
|
+
if (!bucket.full && bucket.latestPages.size === 0) {
|
|
16685
|
+
this.threadDetailCache.delete(localThreadId);
|
|
16686
|
+
}
|
|
16687
|
+
return null;
|
|
16323
16688
|
}
|
|
16324
|
-
setCache(localThreadId, entry) {
|
|
16325
|
-
this.threadDetailCache.
|
|
16689
|
+
setCache(localThreadId, options = {}, entry) {
|
|
16690
|
+
const bucket = this.threadDetailCache.get(localThreadId) ?? {
|
|
16691
|
+
full: null,
|
|
16692
|
+
latestPages: /* @__PURE__ */ new Map()
|
|
16693
|
+
};
|
|
16694
|
+
const nextEntry = {
|
|
16326
16695
|
...entry,
|
|
16327
16696
|
cachedAt: Date.now()
|
|
16328
|
-
}
|
|
16697
|
+
};
|
|
16698
|
+
const key = cacheKeyForDetailOptions(options);
|
|
16699
|
+
if (key === "uncached") {
|
|
16700
|
+
return;
|
|
16701
|
+
}
|
|
16702
|
+
if (key === "full") {
|
|
16703
|
+
bucket.full = nextEntry;
|
|
16704
|
+
} else {
|
|
16705
|
+
bucket.latestPages.set(key, nextEntry);
|
|
16706
|
+
}
|
|
16707
|
+
this.threadDetailCache.set(localThreadId, bucket);
|
|
16329
16708
|
}
|
|
16330
16709
|
invalidate(localThreadId) {
|
|
16331
16710
|
this.threadDetailCache.delete(localThreadId);
|
|
16332
16711
|
}
|
|
16333
16712
|
cachedTurns(localThreadId) {
|
|
16334
|
-
|
|
16713
|
+
const bucket = this.threadDetailCache.get(localThreadId);
|
|
16714
|
+
if (!bucket) {
|
|
16715
|
+
return [];
|
|
16716
|
+
}
|
|
16717
|
+
if (bucket.full && !isExpiredThreadDetailCacheEntry(bucket.full)) {
|
|
16718
|
+
return bucket.full.turns;
|
|
16719
|
+
}
|
|
16720
|
+
let newest = null;
|
|
16721
|
+
for (const [key, entry] of bucket.latestPages.entries()) {
|
|
16722
|
+
if (isExpiredThreadDetailCacheEntry(entry)) {
|
|
16723
|
+
bucket.latestPages.delete(key);
|
|
16724
|
+
continue;
|
|
16725
|
+
}
|
|
16726
|
+
if (!newest || entry.cachedAt > newest.cachedAt) {
|
|
16727
|
+
newest = entry;
|
|
16728
|
+
}
|
|
16729
|
+
}
|
|
16730
|
+
if (!bucket.full && bucket.latestPages.size === 0) {
|
|
16731
|
+
this.threadDetailCache.delete(localThreadId);
|
|
16732
|
+
}
|
|
16733
|
+
return newest?.turns ?? [];
|
|
16335
16734
|
}
|
|
16336
16735
|
async buildCacheEntry(input) {
|
|
16337
16736
|
const options = input.options ?? {};
|
|
16338
16737
|
const shouldCacheFullDetail = options.limit === void 0 && options.beforeTurnId === void 0;
|
|
16339
|
-
const
|
|
16340
|
-
|
|
16738
|
+
const cacheKey = cacheKeyForDetailOptions(options);
|
|
16739
|
+
const isPaged = !shouldCacheFullDetail;
|
|
16740
|
+
const cached = this.getCache(input.localThreadId, options);
|
|
16741
|
+
if (cached) {
|
|
16341
16742
|
return cached;
|
|
16342
16743
|
}
|
|
16343
16744
|
let remoteSession = await this.input.callbacks.readRemoteSession(
|
|
@@ -16347,6 +16748,7 @@ var ThreadDetailAssembler = class {
|
|
|
16347
16748
|
if (!remoteSession) {
|
|
16348
16749
|
return this.buildLocalFallbackEntry({
|
|
16349
16750
|
...input,
|
|
16751
|
+
options,
|
|
16350
16752
|
shouldCacheFullDetail
|
|
16351
16753
|
});
|
|
16352
16754
|
}
|
|
@@ -16376,13 +16778,18 @@ var ThreadDetailAssembler = class {
|
|
|
16376
16778
|
remoteSession.turns
|
|
16377
16779
|
);
|
|
16378
16780
|
const visibleTurns = this.input.liveState.visibleRemoteTurns(input.localThreadId, remoteSession.turns).map((turn) => agentTurnToThreadTurnDto(turn, deferredDetails));
|
|
16379
|
-
const
|
|
16781
|
+
const orderedVisibleTurns = applyLiveAgentMessageOrderingHints(
|
|
16380
16782
|
visibleTurns,
|
|
16783
|
+
input.localThreadId,
|
|
16784
|
+
this.input.liveState
|
|
16785
|
+
);
|
|
16786
|
+
const resolvedTurnMetadataById = resolveTurnMetadataByVisibleTurnId(
|
|
16787
|
+
orderedVisibleTurns,
|
|
16381
16788
|
input.turnMetadataById
|
|
16382
16789
|
);
|
|
16383
16790
|
const turns = mergePersistedHistoryItemsIntoTurns(
|
|
16384
16791
|
applyRecordedTurnItemOrders(
|
|
16385
|
-
|
|
16792
|
+
orderedVisibleTurns,
|
|
16386
16793
|
this.input.liveState.turnItemOrderSnapshot(input.localThreadId)
|
|
16387
16794
|
),
|
|
16388
16795
|
persistedItemsByTurnId,
|
|
@@ -16393,12 +16800,13 @@ var ThreadDetailAssembler = class {
|
|
|
16393
16800
|
const entry = {
|
|
16394
16801
|
cachedAt: Date.now(),
|
|
16395
16802
|
turns,
|
|
16396
|
-
totalTurnCount: shouldCacheFullDetail ? turns.length : remoteSession.totalTurnCount ??
|
|
16397
|
-
deferredDetails
|
|
16803
|
+
totalTurnCount: shouldCacheFullDetail ? turns.length : remoteSession.totalTurnCount ?? turns.length,
|
|
16804
|
+
deferredDetails,
|
|
16805
|
+
isPaged
|
|
16398
16806
|
};
|
|
16399
|
-
if (
|
|
16400
|
-
this.setCache(input.localThreadId, entry);
|
|
16401
|
-
return this.
|
|
16807
|
+
if (cacheKey !== "uncached") {
|
|
16808
|
+
this.setCache(input.localThreadId, options, entry);
|
|
16809
|
+
return this.getCache(input.localThreadId, options);
|
|
16402
16810
|
}
|
|
16403
16811
|
return entry;
|
|
16404
16812
|
}
|
|
@@ -16417,7 +16825,7 @@ var ThreadDetailAssembler = class {
|
|
|
16417
16825
|
input.record,
|
|
16418
16826
|
latestThreadTurnMetadata(input.turnMetadataById)
|
|
16419
16827
|
);
|
|
16420
|
-
const localTurns = localSession?.turns ?? [...persistedItemsByTurnId.
|
|
16828
|
+
const localTurns = localSession?.turns ?? [...persistedItemsByTurnId.keys()].map((turnId) => ({
|
|
16421
16829
|
id: turnId,
|
|
16422
16830
|
startedAt: null,
|
|
16423
16831
|
status: "completed",
|
|
@@ -16441,15 +16849,61 @@ var ThreadDetailAssembler = class {
|
|
|
16441
16849
|
cachedAt: Date.now(),
|
|
16442
16850
|
turns,
|
|
16443
16851
|
totalTurnCount: turns.length,
|
|
16444
|
-
deferredDetails
|
|
16852
|
+
deferredDetails,
|
|
16853
|
+
isPaged: !input.shouldCacheFullDetail
|
|
16445
16854
|
};
|
|
16446
|
-
|
|
16447
|
-
|
|
16448
|
-
|
|
16855
|
+
const cacheOptions = input.shouldCacheFullDetail ? {} : input.options;
|
|
16856
|
+
if (cacheKeyForDetailOptions(cacheOptions) !== "uncached") {
|
|
16857
|
+
this.setCache(input.localThreadId, cacheOptions, entry);
|
|
16858
|
+
return this.getCache(input.localThreadId, cacheOptions);
|
|
16449
16859
|
}
|
|
16450
16860
|
return entry;
|
|
16451
16861
|
}
|
|
16452
16862
|
};
|
|
16863
|
+
function isExpiredThreadDetailCacheEntry(entry) {
|
|
16864
|
+
return Date.now() - entry.cachedAt > THREAD_DETAIL_CACHE_TTL_MS;
|
|
16865
|
+
}
|
|
16866
|
+
function cacheKeyForDetailOptions(options) {
|
|
16867
|
+
if (options.beforeTurnId !== void 0) {
|
|
16868
|
+
return "uncached";
|
|
16869
|
+
}
|
|
16870
|
+
if (options.limit === void 0) {
|
|
16871
|
+
return "full";
|
|
16872
|
+
}
|
|
16873
|
+
return `latest:${options.limit}`;
|
|
16874
|
+
}
|
|
16875
|
+
function applyLiveAgentMessageOrderingHints(turns, localThreadId, liveState) {
|
|
16876
|
+
return turns.map((turn) => {
|
|
16877
|
+
const orderingHints = liveState.finalTurnAgentMessageOrderingHints(
|
|
16878
|
+
localThreadId,
|
|
16879
|
+
turn.id,
|
|
16880
|
+
turn.items,
|
|
16881
|
+
{ allowUnmatchedFallback: false }
|
|
16882
|
+
);
|
|
16883
|
+
if (orderingHints.size === 0) {
|
|
16884
|
+
return turn;
|
|
16885
|
+
}
|
|
16886
|
+
let changed = false;
|
|
16887
|
+
const items = turn.items.map((item) => {
|
|
16888
|
+
if (item.kind !== "agentMessage") {
|
|
16889
|
+
return item;
|
|
16890
|
+
}
|
|
16891
|
+
const sequence = orderingHints.get(item.id);
|
|
16892
|
+
if (sequence === void 0 || item.sequence === sequence) {
|
|
16893
|
+
return item;
|
|
16894
|
+
}
|
|
16895
|
+
changed = true;
|
|
16896
|
+
return {
|
|
16897
|
+
...item,
|
|
16898
|
+
sequence
|
|
16899
|
+
};
|
|
16900
|
+
});
|
|
16901
|
+
return changed ? {
|
|
16902
|
+
...turn,
|
|
16903
|
+
items: sortHistoryItemsBySequence(items)
|
|
16904
|
+
} : turn;
|
|
16905
|
+
});
|
|
16906
|
+
}
|
|
16453
16907
|
function buildTurnDto(turn, metadata) {
|
|
16454
16908
|
const tokenUsage = parseThreadTurnTokenUsageJson(metadata?.tokenUsageJson);
|
|
16455
16909
|
return {
|
|
@@ -16932,7 +17386,8 @@ var ThreadPromptTurnCoordinator = class {
|
|
|
16932
17386
|
reasoningEffort: input.normalizedReasoning,
|
|
16933
17387
|
collaborationMode: input.collaborationMode,
|
|
16934
17388
|
sandboxMode: input.sandboxMode,
|
|
16935
|
-
workspacePath: input.workspacePath
|
|
17389
|
+
workspacePath: input.workspacePath,
|
|
17390
|
+
developerInstructions: input.developerInstructions ?? null
|
|
16936
17391
|
};
|
|
16937
17392
|
if (input.hidden !== void 0) {
|
|
16938
17393
|
startTurnInput.hidden = input.hidden;
|
|
@@ -17049,7 +17504,8 @@ var ThreadPromptTurnCoordinator = class {
|
|
|
17049
17504
|
collaborationMode: input.collaborationMode,
|
|
17050
17505
|
sandboxMode: input.sandboxMode,
|
|
17051
17506
|
performanceMode: input.performanceMode,
|
|
17052
|
-
workspacePath: input.workspacePath
|
|
17507
|
+
workspacePath: input.workspacePath,
|
|
17508
|
+
developerInstructions: input.developerInstructions ?? null
|
|
17053
17509
|
});
|
|
17054
17510
|
}
|
|
17055
17511
|
};
|
|
@@ -17480,12 +17936,38 @@ var ThreadHistoryPersistenceCoordinator = class {
|
|
|
17480
17936
|
deletePersistedHistoryItemsForTurn(localThreadId, turnId) {
|
|
17481
17937
|
deleteThreadHistoryItemRecordsByThreadAndTurnId(this.db, localThreadId, turnId);
|
|
17482
17938
|
}
|
|
17939
|
+
persistFinalTurnOrderingHints(localThreadId, turnId, items) {
|
|
17940
|
+
const orderingHints = this.liveState.finalTurnAgentMessageOrderingHints(
|
|
17941
|
+
localThreadId,
|
|
17942
|
+
turnId,
|
|
17943
|
+
items
|
|
17944
|
+
);
|
|
17945
|
+
for (const item of items) {
|
|
17946
|
+
if (item.kind !== "agentMessage" || !shouldPersistRuntimeFinalHistoryItem(item)) {
|
|
17947
|
+
continue;
|
|
17948
|
+
}
|
|
17949
|
+
const sequence = orderingHints.get(item.id);
|
|
17950
|
+
if (sequence === void 0) {
|
|
17951
|
+
continue;
|
|
17952
|
+
}
|
|
17953
|
+
upsertThreadHistoryItemRecord(this.db, {
|
|
17954
|
+
threadId: localThreadId,
|
|
17955
|
+
turnId,
|
|
17956
|
+
itemId: item.id,
|
|
17957
|
+
itemJson: JSON.stringify({
|
|
17958
|
+
...item,
|
|
17959
|
+
sequence,
|
|
17960
|
+
sourceTurnId: turnId
|
|
17961
|
+
})
|
|
17962
|
+
});
|
|
17963
|
+
}
|
|
17964
|
+
}
|
|
17483
17965
|
persistRuntimeTurnItemsAsDisplayTurn(localThreadId, runtimeTurnId, displayTurnId, items) {
|
|
17484
17966
|
if (runtimeTurnId === displayTurnId) {
|
|
17485
17967
|
return;
|
|
17486
17968
|
}
|
|
17487
17969
|
for (const item of items) {
|
|
17488
|
-
if (!shouldPersistRuntimeFinalHistoryItem(item
|
|
17970
|
+
if (!shouldPersistRuntimeFinalHistoryItem(item)) {
|
|
17489
17971
|
continue;
|
|
17490
17972
|
}
|
|
17491
17973
|
const sequence = this.liveState.recordTurnItemOrder(localThreadId, displayTurnId, item.id);
|
|
@@ -19168,6 +19650,16 @@ async function pathExists3(absPath) {
|
|
|
19168
19650
|
return false;
|
|
19169
19651
|
}
|
|
19170
19652
|
}
|
|
19653
|
+
function canUseRuntimePagedTurns(cachedDetail, enrichedTurns, options) {
|
|
19654
|
+
const requestedLimit = options.limit ?? 10;
|
|
19655
|
+
if (enrichedTurns.length > requestedLimit) {
|
|
19656
|
+
return false;
|
|
19657
|
+
}
|
|
19658
|
+
return cachedDetail.totalTurnCount > enrichedTurns.length;
|
|
19659
|
+
}
|
|
19660
|
+
function pluginDeveloperInstructions(pluginService) {
|
|
19661
|
+
return pluginService?.modelContextPrompt() ?? null;
|
|
19662
|
+
}
|
|
19171
19663
|
var ThreadService = class {
|
|
19172
19664
|
constructor(db, agentRuntimes, eventBus, localSessionStore, workspaceRoot, providerManagement, pluginService) {
|
|
19173
19665
|
this.db = db;
|
|
@@ -19343,6 +19835,7 @@ var ThreadService = class {
|
|
|
19343
19835
|
normalizeReasoningEffort: normalizeReasoningEffort2,
|
|
19344
19836
|
normalizeThreadGoalStatusForThread: (goal, record) => this.goalCoordinator.normalizeThreadGoalStatusForThread(goal, record),
|
|
19345
19837
|
persistLiveHistoryItem: (localThreadId, turnId, item) => this.historyPersistence.persistLiveHistoryItem(localThreadId, turnId, item),
|
|
19838
|
+
persistFinalTurnOrderingHints: (localThreadId, turnId, items) => this.historyPersistence.persistFinalTurnOrderingHints(localThreadId, turnId, items),
|
|
19346
19839
|
persistRuntimeTurnItemsAsDisplayTurn: (localThreadId, runtimeTurnId, displayTurnId, items) => this.historyPersistence.persistRuntimeTurnItemsAsDisplayTurn(
|
|
19347
19840
|
localThreadId,
|
|
19348
19841
|
runtimeTurnId,
|
|
@@ -19583,9 +20076,13 @@ var ThreadService = class {
|
|
|
19583
20076
|
const enrichedTurns = this.pluginService?.enrichTurnsWithArtifacts({
|
|
19584
20077
|
threadId: updated.id,
|
|
19585
20078
|
workspacePath: workspace.absPath,
|
|
19586
|
-
turns: cachedDetail.turns
|
|
20079
|
+
turns: cachedDetail.turns,
|
|
20080
|
+
deferredDetails: cachedDetail.deferredDetails
|
|
19587
20081
|
}) ?? cachedDetail.turns;
|
|
19588
|
-
const pagedTurns =
|
|
20082
|
+
const pagedTurns = cachedDetail.isPaged && canUseRuntimePagedTurns(cachedDetail, enrichedTurns, options) ? {
|
|
20083
|
+
turns: enrichedTurns,
|
|
20084
|
+
totalTurnCount: cachedDetail.totalTurnCount
|
|
20085
|
+
} : this.detailAssembler.sliceTurns(enrichedTurns, options);
|
|
19589
20086
|
this.syncPendingPlanDecisionRequestFromTurns(
|
|
19590
20087
|
updated.id,
|
|
19591
20088
|
updated.collaborationMode,
|
|
@@ -19776,6 +20273,7 @@ var ThreadService = class {
|
|
|
19776
20273
|
...record,
|
|
19777
20274
|
providerSessionId
|
|
19778
20275
|
};
|
|
20276
|
+
const developerInstructions = pluginDeveloperInstructions(this.pluginService);
|
|
19779
20277
|
if (record.providerTurnId && record.status === "running") {
|
|
19780
20278
|
if (!turnConfig.supportsRunningTurnInput) {
|
|
19781
20279
|
throw new HttpError(409, {
|
|
@@ -19795,7 +20293,8 @@ var ThreadService = class {
|
|
|
19795
20293
|
collaborationMode: turnConfig.collaborationMode,
|
|
19796
20294
|
sandboxMode: turnConfig.sandboxMode,
|
|
19797
20295
|
performanceMode: turnConfig.performanceMode,
|
|
19798
|
-
workspacePath: workspace.absPath
|
|
20296
|
+
workspacePath: workspace.absPath,
|
|
20297
|
+
developerInstructions
|
|
19799
20298
|
});
|
|
19800
20299
|
}
|
|
19801
20300
|
return this.promptTurnCoordinator.startPromptTurn(localThreadId, connectedRecord, {
|
|
@@ -19806,7 +20305,8 @@ var ThreadService = class {
|
|
|
19806
20305
|
collaborationMode: turnConfig.collaborationMode,
|
|
19807
20306
|
sandboxMode: turnConfig.sandboxMode,
|
|
19808
20307
|
performanceMode: turnConfig.performanceMode,
|
|
19809
|
-
workspacePath: workspace.absPath
|
|
20308
|
+
workspacePath: workspace.absPath,
|
|
20309
|
+
developerInstructions
|
|
19810
20310
|
});
|
|
19811
20311
|
}
|
|
19812
20312
|
async updateThreadSettings(localThreadId, input) {
|
|
@@ -20638,36 +21138,8 @@ function runShellCommand(command, timeoutMs = 0) {
|
|
|
20638
21138
|
});
|
|
20639
21139
|
}
|
|
20640
21140
|
|
|
20641
|
-
// src/routes/
|
|
21141
|
+
// src/routes/system.ts
|
|
20642
21142
|
import { z as z4 } from "zod";
|
|
20643
|
-
async function registerShellRoutes(app2) {
|
|
20644
|
-
const threadIdParams = z4.object({ id: z4.string().uuid() });
|
|
20645
|
-
const shellIdParams = z4.object({ id: z4.string().uuid() });
|
|
20646
|
-
const createShellSchema = z4.object({
|
|
20647
|
-
cols: z4.number().int().positive().optional(),
|
|
20648
|
-
rows: z4.number().int().positive().optional()
|
|
20649
|
-
});
|
|
20650
|
-
app2.get("/api/threads/:id/shell", async (request) => {
|
|
20651
|
-
const params = threadIdParams.parse(request.params);
|
|
20652
|
-
return app2.services.shellService.getThreadShellState(params.id);
|
|
20653
|
-
});
|
|
20654
|
-
app2.post("/api/threads/:id/shell", async (request) => {
|
|
20655
|
-
const params = threadIdParams.parse(request.params);
|
|
20656
|
-
const body = createShellSchema.parse(request.body ?? {});
|
|
20657
|
-
const input = {
|
|
20658
|
-
...body.cols !== void 0 ? { cols: body.cols } : {},
|
|
20659
|
-
...body.rows !== void 0 ? { rows: body.rows } : {}
|
|
20660
|
-
};
|
|
20661
|
-
return app2.services.shellService.createShellForThread(params.id, input);
|
|
20662
|
-
});
|
|
20663
|
-
app2.post("/api/shells/:id/terminate", async (request) => {
|
|
20664
|
-
const params = shellIdParams.parse(request.params);
|
|
20665
|
-
return app2.services.shellService.terminateShell(params.id);
|
|
20666
|
-
});
|
|
20667
|
-
}
|
|
20668
|
-
|
|
20669
|
-
// src/routes/system.ts
|
|
20670
|
-
import { z as z5 } from "zod";
|
|
20671
21143
|
|
|
20672
21144
|
// src/workspace-settings.ts
|
|
20673
21145
|
var DEV_HOME_POLICY_KEY = "dev_home";
|
|
@@ -20731,27 +21203,27 @@ async function saveWorkspaceSettings(db, workspaceRoot, input) {
|
|
|
20731
21203
|
}
|
|
20732
21204
|
|
|
20733
21205
|
// src/routes/system.ts
|
|
20734
|
-
var updateProviderHostFileSchema =
|
|
20735
|
-
content:
|
|
21206
|
+
var updateProviderHostFileSchema = z4.object({
|
|
21207
|
+
content: z4.string()
|
|
20736
21208
|
});
|
|
20737
|
-
var archiveIdSchema =
|
|
20738
|
-
var createProviderHostConfigArchiveSchema =
|
|
20739
|
-
label:
|
|
21209
|
+
var archiveIdSchema = z4.string().regex(/^[a-zA-Z0-9_-]+$/);
|
|
21210
|
+
var createProviderHostConfigArchiveSchema = z4.object({
|
|
21211
|
+
label: z4.string().trim().min(1).max(120).optional()
|
|
20740
21212
|
});
|
|
20741
|
-
var renameProviderHostConfigArchiveSchema =
|
|
20742
|
-
label:
|
|
21213
|
+
var renameProviderHostConfigArchiveSchema = z4.object({
|
|
21214
|
+
label: z4.string().trim().min(1).max(120)
|
|
20743
21215
|
});
|
|
20744
|
-
var updateWorkspaceSettingsSchema =
|
|
20745
|
-
devHome:
|
|
21216
|
+
var updateWorkspaceSettingsSchema = z4.object({
|
|
21217
|
+
devHome: z4.string().trim().min(1),
|
|
20746
21218
|
defaultBackend: agentBackendIdSchema.optional()
|
|
20747
21219
|
});
|
|
20748
|
-
var providerParamSchema2 =
|
|
21220
|
+
var providerParamSchema2 = z4.object({
|
|
20749
21221
|
provider: agentBackendIdSchema
|
|
20750
21222
|
});
|
|
20751
21223
|
function parseProviderHostFileParams(params) {
|
|
20752
|
-
return
|
|
21224
|
+
return z4.object({
|
|
20753
21225
|
...providerParamSchema2.shape,
|
|
20754
|
-
name:
|
|
21226
|
+
name: z4.string()
|
|
20755
21227
|
}).parse(params);
|
|
20756
21228
|
}
|
|
20757
21229
|
async function registerSystemRoutes(app2) {
|
|
@@ -20779,6 +21251,7 @@ async function registerSystemRoutes(app2) {
|
|
|
20779
21251
|
return {
|
|
20780
21252
|
appName: app2.services.config.appName,
|
|
20781
21253
|
appVersion: app2.services.config.appVersion,
|
|
21254
|
+
mode: app2.services.config.mode,
|
|
20782
21255
|
host: app2.services.config.host,
|
|
20783
21256
|
port: app2.services.config.port,
|
|
20784
21257
|
workspaceRoot: app2.services.config.workspaceRoot,
|
|
@@ -20831,7 +21304,7 @@ async function registerSystemRoutes(app2) {
|
|
|
20831
21304
|
return app2.services.providerHostConfigService.createArchive(provider, body);
|
|
20832
21305
|
});
|
|
20833
21306
|
app2.patch("/api/config/providers/:provider/archives/:id", async (request) => {
|
|
20834
|
-
const params =
|
|
21307
|
+
const params = z4.object({
|
|
20835
21308
|
...providerParamSchema2.shape,
|
|
20836
21309
|
id: archiveIdSchema
|
|
20837
21310
|
}).parse(request.params);
|
|
@@ -20845,7 +21318,7 @@ async function registerSystemRoutes(app2) {
|
|
|
20845
21318
|
);
|
|
20846
21319
|
});
|
|
20847
21320
|
app2.post("/api/config/providers/:provider/archives/:id/apply", async (request) => {
|
|
20848
|
-
const params =
|
|
21321
|
+
const params = z4.object({
|
|
20849
21322
|
...providerParamSchema2.shape,
|
|
20850
21323
|
id: archiveIdSchema
|
|
20851
21324
|
}).parse(request.params);
|
|
@@ -20856,67 +21329,67 @@ async function registerSystemRoutes(app2) {
|
|
|
20856
21329
|
// src/routes/threads.ts
|
|
20857
21330
|
import fs16 from "fs/promises";
|
|
20858
21331
|
import path16 from "path";
|
|
20859
|
-
import { z as
|
|
20860
|
-
var createThreadSchema =
|
|
20861
|
-
workspaceId:
|
|
20862
|
-
title:
|
|
21332
|
+
import { z as z5 } from "zod";
|
|
21333
|
+
var createThreadSchema = z5.object({
|
|
21334
|
+
workspaceId: z5.string().uuid(),
|
|
21335
|
+
title: z5.string().optional(),
|
|
20863
21336
|
provider: agentBackendIdSchema.optional(),
|
|
20864
|
-
model:
|
|
20865
|
-
approvalMode:
|
|
21337
|
+
model: z5.string().min(1),
|
|
21338
|
+
approvalMode: z5.enum(["yolo", "guarded"]).default("yolo")
|
|
20866
21339
|
});
|
|
20867
|
-
var promptSchema =
|
|
20868
|
-
prompt:
|
|
20869
|
-
clientRequestId:
|
|
20870
|
-
model:
|
|
20871
|
-
reasoningEffort:
|
|
20872
|
-
collaborationMode:
|
|
20873
|
-
sandboxMode:
|
|
21340
|
+
var promptSchema = z5.object({
|
|
21341
|
+
prompt: z5.string().min(1),
|
|
21342
|
+
clientRequestId: z5.string().min(1).optional(),
|
|
21343
|
+
model: z5.string().min(1).optional(),
|
|
21344
|
+
reasoningEffort: z5.enum(["none", "minimal", "low", "medium", "high", "xhigh"]).nullable().optional(),
|
|
21345
|
+
collaborationMode: z5.enum(["default", "plan"]).optional(),
|
|
21346
|
+
sandboxMode: z5.enum(["read-only", "workspace-write", "danger-full-access"]).nullable().optional()
|
|
20874
21347
|
});
|
|
20875
|
-
var promptAttachmentManifestEntrySchema =
|
|
20876
|
-
clientId:
|
|
20877
|
-
kind:
|
|
20878
|
-
originalName:
|
|
20879
|
-
placeholder:
|
|
21348
|
+
var promptAttachmentManifestEntrySchema = z5.object({
|
|
21349
|
+
clientId: z5.string().min(1),
|
|
21350
|
+
kind: z5.enum(["photo", "file"]),
|
|
21351
|
+
originalName: z5.string().optional(),
|
|
21352
|
+
placeholder: z5.string().min(1)
|
|
20880
21353
|
});
|
|
20881
|
-
var updateThreadSchema =
|
|
20882
|
-
title:
|
|
21354
|
+
var updateThreadSchema = z5.object({
|
|
21355
|
+
title: z5.string().min(1)
|
|
20883
21356
|
});
|
|
20884
|
-
var updateThreadSettingsSchema =
|
|
20885
|
-
model:
|
|
20886
|
-
reasoningEffort:
|
|
20887
|
-
fastMode:
|
|
20888
|
-
collaborationMode:
|
|
20889
|
-
sandboxMode:
|
|
21357
|
+
var updateThreadSettingsSchema = z5.object({
|
|
21358
|
+
model: z5.string().min(1).optional(),
|
|
21359
|
+
reasoningEffort: z5.enum(["none", "minimal", "low", "medium", "high", "xhigh"]).nullable().optional(),
|
|
21360
|
+
fastMode: z5.boolean().optional(),
|
|
21361
|
+
collaborationMode: z5.enum(["default", "plan"]).optional(),
|
|
21362
|
+
sandboxMode: z5.enum(["read-only", "workspace-write", "danger-full-access"]).nullable().optional()
|
|
20890
21363
|
}).refine((body) => Object.keys(body).length > 0, {
|
|
20891
21364
|
message: "At least one thread setting must be provided."
|
|
20892
21365
|
});
|
|
20893
|
-
var updateThreadGoalSchema =
|
|
20894
|
-
objective:
|
|
20895
|
-
status:
|
|
20896
|
-
tokenBudget:
|
|
21366
|
+
var updateThreadGoalSchema = z5.object({
|
|
21367
|
+
objective: z5.string().min(1).nullable().optional(),
|
|
21368
|
+
status: z5.enum(["active", "paused", "budgetLimited", "complete", "terminated"]).nullable().optional(),
|
|
21369
|
+
tokenBudget: z5.number().int().positive().nullable().optional()
|
|
20897
21370
|
}).refine((body) => Object.keys(body).length > 0, {
|
|
20898
21371
|
message: "At least one goal field must be provided."
|
|
20899
21372
|
});
|
|
20900
|
-
var interruptSchema =
|
|
20901
|
-
turnId:
|
|
21373
|
+
var interruptSchema = z5.object({
|
|
21374
|
+
turnId: z5.string().optional()
|
|
20902
21375
|
});
|
|
20903
|
-
var importThreadSchema =
|
|
20904
|
-
sessionId:
|
|
21376
|
+
var importThreadSchema = z5.object({
|
|
21377
|
+
sessionId: z5.string().min(1)
|
|
20905
21378
|
});
|
|
20906
|
-
var resumeThreadSchema =
|
|
20907
|
-
model:
|
|
20908
|
-
sandboxMode:
|
|
21379
|
+
var resumeThreadSchema = z5.object({
|
|
21380
|
+
model: z5.string().min(1).optional(),
|
|
21381
|
+
sandboxMode: z5.enum(["read-only", "workspace-write", "danger-full-access"]).nullable().optional()
|
|
20909
21382
|
});
|
|
20910
|
-
var forkThreadSchema =
|
|
20911
|
-
|
|
20912
|
-
mode:
|
|
21383
|
+
var forkThreadSchema = z5.discriminatedUnion("mode", [
|
|
21384
|
+
z5.object({
|
|
21385
|
+
mode: z5.literal("latest")
|
|
20913
21386
|
}),
|
|
20914
|
-
|
|
20915
|
-
mode:
|
|
20916
|
-
turnId:
|
|
21387
|
+
z5.object({
|
|
21388
|
+
mode: z5.literal("turn"),
|
|
21389
|
+
turnId: z5.string().min(1)
|
|
20917
21390
|
})
|
|
20918
21391
|
]);
|
|
20919
|
-
var hookEventNameSchema =
|
|
21392
|
+
var hookEventNameSchema = z5.enum([
|
|
20920
21393
|
"preToolUse",
|
|
20921
21394
|
"permissionRequest",
|
|
20922
21395
|
"postToolUse",
|
|
@@ -20926,48 +21399,48 @@ var hookEventNameSchema = z6.enum([
|
|
|
20926
21399
|
"userPromptSubmit",
|
|
20927
21400
|
"stop"
|
|
20928
21401
|
]);
|
|
20929
|
-
var createThreadHookSchema =
|
|
20930
|
-
scope:
|
|
21402
|
+
var createThreadHookSchema = z5.object({
|
|
21403
|
+
scope: z5.enum(["global", "project"]),
|
|
20931
21404
|
eventName: hookEventNameSchema,
|
|
20932
|
-
matcher:
|
|
20933
|
-
command:
|
|
20934
|
-
timeoutSec:
|
|
20935
|
-
statusMessage:
|
|
21405
|
+
matcher: z5.string().nullable().optional(),
|
|
21406
|
+
command: z5.string().trim().min(1),
|
|
21407
|
+
timeoutSec: z5.number().int().positive().max(86400).nullable().optional(),
|
|
21408
|
+
statusMessage: z5.string().nullable().optional()
|
|
20936
21409
|
});
|
|
20937
21410
|
var updateThreadHookSchema = createThreadHookSchema.extend({
|
|
20938
21411
|
target: createThreadHookSchema
|
|
20939
21412
|
});
|
|
20940
|
-
var trustThreadHookSchema =
|
|
20941
|
-
key:
|
|
20942
|
-
currentHash:
|
|
21413
|
+
var trustThreadHookSchema = z5.object({
|
|
21414
|
+
key: z5.string().min(1),
|
|
21415
|
+
currentHash: z5.string().min(1)
|
|
20943
21416
|
});
|
|
20944
|
-
var untrustThreadHookSchema =
|
|
20945
|
-
key:
|
|
21417
|
+
var untrustThreadHookSchema = z5.object({
|
|
21418
|
+
key: z5.string().min(1)
|
|
20946
21419
|
});
|
|
20947
|
-
var respondThreadRequestSchema =
|
|
20948
|
-
answers:
|
|
20949
|
-
answers:
|
|
21420
|
+
var respondThreadRequestSchema = z5.object({
|
|
21421
|
+
answers: z5.record(z5.string(), z5.object({
|
|
21422
|
+
answers: z5.array(z5.string())
|
|
20950
21423
|
}))
|
|
20951
21424
|
});
|
|
20952
|
-
var threadDetailQuerySchema =
|
|
20953
|
-
limit:
|
|
20954
|
-
beforeTurnId:
|
|
21425
|
+
var threadDetailQuerySchema = z5.object({
|
|
21426
|
+
limit: z5.coerce.number().int().positive().max(100).optional(),
|
|
21427
|
+
beforeTurnId: z5.string().min(1).optional()
|
|
20955
21428
|
});
|
|
20956
|
-
var exportThreadPdfSchema =
|
|
20957
|
-
format:
|
|
20958
|
-
mode:
|
|
20959
|
-
limit:
|
|
20960
|
-
turnIds:
|
|
20961
|
-
profile:
|
|
20962
|
-
options:
|
|
20963
|
-
includeTokenAndPrice:
|
|
20964
|
-
includeCommandOutput:
|
|
20965
|
-
includeAbsolutePaths:
|
|
21429
|
+
var exportThreadPdfSchema = z5.object({
|
|
21430
|
+
format: z5.enum(["pdf", "html"]).optional(),
|
|
21431
|
+
mode: z5.enum(["latest", "selected"]),
|
|
21432
|
+
limit: z5.number().int().positive().max(100).optional(),
|
|
21433
|
+
turnIds: z5.array(z5.string().min(1)).max(100).optional(),
|
|
21434
|
+
profile: z5.enum(["review", "technical"]).optional(),
|
|
21435
|
+
options: z5.object({
|
|
21436
|
+
includeTokenAndPrice: z5.boolean().optional(),
|
|
21437
|
+
includeCommandOutput: z5.boolean().optional(),
|
|
21438
|
+
includeAbsolutePaths: z5.boolean().optional()
|
|
20966
21439
|
}).optional()
|
|
20967
21440
|
}).refine((body) => body.mode !== "selected" || (body.turnIds?.length ?? 0) > 0, {
|
|
20968
21441
|
message: "turnIds are required for selected exports."
|
|
20969
21442
|
});
|
|
20970
|
-
var queryBooleanSchema =
|
|
21443
|
+
var queryBooleanSchema = z5.preprocess((value) => {
|
|
20971
21444
|
if (value === "true" || value === "1") {
|
|
20972
21445
|
return true;
|
|
20973
21446
|
}
|
|
@@ -20975,21 +21448,21 @@ var queryBooleanSchema = z6.preprocess((value) => {
|
|
|
20975
21448
|
return false;
|
|
20976
21449
|
}
|
|
20977
21450
|
return value;
|
|
20978
|
-
},
|
|
20979
|
-
var exportThreadPdfQuerySchema =
|
|
20980
|
-
format:
|
|
20981
|
-
mode:
|
|
20982
|
-
limit:
|
|
20983
|
-
turnIds:
|
|
20984
|
-
profile:
|
|
21451
|
+
}, z5.boolean());
|
|
21452
|
+
var exportThreadPdfQuerySchema = z5.object({
|
|
21453
|
+
format: z5.enum(["pdf", "html"]).optional(),
|
|
21454
|
+
mode: z5.enum(["latest", "selected"]),
|
|
21455
|
+
limit: z5.coerce.number().int().positive().max(100).optional(),
|
|
21456
|
+
turnIds: z5.string().optional(),
|
|
21457
|
+
profile: z5.enum(["review", "technical"]).optional(),
|
|
20985
21458
|
includeTokenAndPrice: queryBooleanSchema.optional(),
|
|
20986
21459
|
includeCommandOutput: queryBooleanSchema.optional(),
|
|
20987
21460
|
includeAbsolutePaths: queryBooleanSchema.optional()
|
|
20988
21461
|
}).refine((query) => query.mode !== "selected" || Boolean(query.turnIds?.trim()), {
|
|
20989
21462
|
message: "turnIds are required for selected exports."
|
|
20990
21463
|
});
|
|
20991
|
-
var threadImageQuerySchema =
|
|
20992
|
-
path:
|
|
21464
|
+
var threadImageQuerySchema = z5.object({
|
|
21465
|
+
path: z5.string().min(1)
|
|
20993
21466
|
});
|
|
20994
21467
|
async function sendThreadExport(app2, reply, threadId, input) {
|
|
20995
21468
|
const result = await app2.services.threadService.exportThreadTranscript(threadId, input);
|
|
@@ -21089,7 +21562,7 @@ async function parseMultipartPromptRequest(request) {
|
|
|
21089
21562
|
message: "attachmentManifest must be valid JSON."
|
|
21090
21563
|
});
|
|
21091
21564
|
}
|
|
21092
|
-
const manifest =
|
|
21565
|
+
const manifest = z5.array(promptAttachmentManifestEntrySchema).max(MAX_PROMPT_ATTACHMENTS).parse(manifestParsed);
|
|
21093
21566
|
if (manifest.length !== uploadedFiles.length) {
|
|
21094
21567
|
throw new HttpError(400, {
|
|
21095
21568
|
code: "bad_request",
|
|
@@ -21133,7 +21606,7 @@ async function registerThreadRoutes(app2) {
|
|
|
21133
21606
|
return app2.services.threadService.importThread(body.sessionId);
|
|
21134
21607
|
});
|
|
21135
21608
|
app2.get("/api/threads/:id", async (request) => {
|
|
21136
|
-
const params =
|
|
21609
|
+
const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
|
|
21137
21610
|
const query = threadDetailQuerySchema.parse(request.query);
|
|
21138
21611
|
return app2.services.threadService.getThreadDetail(params.id, {
|
|
21139
21612
|
...query.limit !== void 0 ? { limit: query.limit } : {},
|
|
@@ -21141,11 +21614,11 @@ async function registerThreadRoutes(app2) {
|
|
|
21141
21614
|
});
|
|
21142
21615
|
});
|
|
21143
21616
|
app2.get("/api/threads/:id/export-turns", async (request) => {
|
|
21144
|
-
const params =
|
|
21617
|
+
const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
|
|
21145
21618
|
return app2.services.threadService.listThreadExportTurns(params.id);
|
|
21146
21619
|
});
|
|
21147
21620
|
app2.get("/api/threads/:id/exports/pdf", async (request, reply) => {
|
|
21148
|
-
const params =
|
|
21621
|
+
const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
|
|
21149
21622
|
const query = exportThreadPdfQuerySchema.parse(request.query);
|
|
21150
21623
|
const turnIds = query.turnIds?.split(",").map((turnId) => turnId.trim()).filter(Boolean);
|
|
21151
21624
|
const options = {};
|
|
@@ -21169,7 +21642,7 @@ async function registerThreadRoutes(app2) {
|
|
|
21169
21642
|
return sendThreadExport(app2, reply, params.id, input);
|
|
21170
21643
|
});
|
|
21171
21644
|
app2.post("/api/threads/:id/exports/pdf", async (request, reply) => {
|
|
21172
|
-
const params =
|
|
21645
|
+
const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
|
|
21173
21646
|
const parsed = exportThreadPdfSchema.parse(request.body);
|
|
21174
21647
|
const input = {
|
|
21175
21648
|
...parsed.format !== void 0 ? { format: parsed.format } : {},
|
|
@@ -21188,9 +21661,9 @@ async function registerThreadRoutes(app2) {
|
|
|
21188
21661
|
return sendThreadExport(app2, reply, params.id, input);
|
|
21189
21662
|
});
|
|
21190
21663
|
app2.get("/api/threads/:id/items/:itemId/detail", async (request) => {
|
|
21191
|
-
const params =
|
|
21192
|
-
id:
|
|
21193
|
-
itemId:
|
|
21664
|
+
const params = z5.object({
|
|
21665
|
+
id: z5.string().uuid(),
|
|
21666
|
+
itemId: z5.string().min(1)
|
|
21194
21667
|
}).parse(request.params);
|
|
21195
21668
|
return app2.services.threadService.getThreadHistoryItemDetail(
|
|
21196
21669
|
params.id,
|
|
@@ -21198,7 +21671,7 @@ async function registerThreadRoutes(app2) {
|
|
|
21198
21671
|
);
|
|
21199
21672
|
});
|
|
21200
21673
|
app2.get("/api/threads/:id/assets/image", async (request, reply) => {
|
|
21201
|
-
const params =
|
|
21674
|
+
const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
|
|
21202
21675
|
const query = threadImageQuerySchema.parse(request.query);
|
|
21203
21676
|
const record = getThreadRecordById(app2.services.database.db, params.id);
|
|
21204
21677
|
if (!record) {
|
|
@@ -21244,12 +21717,12 @@ async function registerThreadRoutes(app2) {
|
|
|
21244
21717
|
return reply.send(await fs16.readFile(requestedPath));
|
|
21245
21718
|
});
|
|
21246
21719
|
app2.patch("/api/threads/:id", async (request) => {
|
|
21247
|
-
const params =
|
|
21720
|
+
const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
|
|
21248
21721
|
const body = updateThreadSchema.parse(request.body);
|
|
21249
21722
|
return app2.services.threadService.updateThreadTitle(params.id, body.title);
|
|
21250
21723
|
});
|
|
21251
21724
|
app2.delete("/api/threads/:id", async (request) => {
|
|
21252
|
-
const params =
|
|
21725
|
+
const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
|
|
21253
21726
|
const shell = getShellSessionRecordByThreadId(app2.services.database.db, params.id);
|
|
21254
21727
|
if (shell) {
|
|
21255
21728
|
if (shell.status !== "exited" && shell.status !== "not_found") {
|
|
@@ -21261,7 +21734,7 @@ async function registerThreadRoutes(app2) {
|
|
|
21261
21734
|
return app2.services.threadService.deleteThread(params.id);
|
|
21262
21735
|
});
|
|
21263
21736
|
app2.patch("/api/threads/:id/settings", async (request) => {
|
|
21264
|
-
const params =
|
|
21737
|
+
const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
|
|
21265
21738
|
const body = updateThreadSettingsSchema.parse(request.body);
|
|
21266
21739
|
const input = {
|
|
21267
21740
|
...body.model !== void 0 ? { model: body.model } : {},
|
|
@@ -21273,15 +21746,15 @@ async function registerThreadRoutes(app2) {
|
|
|
21273
21746
|
return app2.services.threadService.updateThreadSettings(params.id, input);
|
|
21274
21747
|
});
|
|
21275
21748
|
app2.post("/api/threads/:id/compact", async (request) => {
|
|
21276
|
-
const params =
|
|
21749
|
+
const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
|
|
21277
21750
|
return app2.services.threadService.compactThread(params.id);
|
|
21278
21751
|
});
|
|
21279
21752
|
app2.get("/api/threads/:id/goal", async (request) => {
|
|
21280
|
-
const params =
|
|
21753
|
+
const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
|
|
21281
21754
|
return { goal: await app2.services.threadService.getThreadGoal(params.id) };
|
|
21282
21755
|
});
|
|
21283
21756
|
app2.patch("/api/threads/:id/goal", async (request) => {
|
|
21284
|
-
const params =
|
|
21757
|
+
const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
|
|
21285
21758
|
const parsedBody = updateThreadGoalSchema.parse(request.body);
|
|
21286
21759
|
const body = {
|
|
21287
21760
|
...parsedBody.objective !== void 0 ? { objective: parsedBody.objective } : {},
|
|
@@ -21291,32 +21764,32 @@ async function registerThreadRoutes(app2) {
|
|
|
21291
21764
|
return { goal: await app2.services.threadService.updateThreadGoal(params.id, body) };
|
|
21292
21765
|
});
|
|
21293
21766
|
app2.delete("/api/threads/:id/goal", async (request) => {
|
|
21294
|
-
const params =
|
|
21767
|
+
const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
|
|
21295
21768
|
return app2.services.threadService.clearThreadGoal(params.id);
|
|
21296
21769
|
});
|
|
21297
21770
|
app2.get("/api/threads/:id/fork-turns", async (request) => {
|
|
21298
|
-
const params =
|
|
21771
|
+
const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
|
|
21299
21772
|
return app2.services.threadService.listForkTurnOptions(params.id);
|
|
21300
21773
|
});
|
|
21301
21774
|
app2.post("/api/threads/:id/fork", async (request) => {
|
|
21302
|
-
const params =
|
|
21775
|
+
const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
|
|
21303
21776
|
const body = forkThreadSchema.parse(request.body);
|
|
21304
21777
|
return app2.services.threadService.forkThread(params.id, body);
|
|
21305
21778
|
});
|
|
21306
21779
|
app2.get("/api/threads/:id/skills", async (request) => {
|
|
21307
|
-
const params =
|
|
21780
|
+
const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
|
|
21308
21781
|
return app2.services.threadService.listThreadSkills(params.id);
|
|
21309
21782
|
});
|
|
21310
21783
|
app2.get("/api/threads/:id/mcp-servers", async (request) => {
|
|
21311
|
-
const params =
|
|
21784
|
+
const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
|
|
21312
21785
|
return app2.services.threadService.listThreadMcpServers(params.id);
|
|
21313
21786
|
});
|
|
21314
21787
|
app2.get("/api/threads/:id/hooks", async (request) => {
|
|
21315
|
-
const params =
|
|
21788
|
+
const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
|
|
21316
21789
|
return app2.services.threadService.listThreadHooks(params.id);
|
|
21317
21790
|
});
|
|
21318
21791
|
app2.post("/api/threads/:id/hooks", async (request) => {
|
|
21319
|
-
const params =
|
|
21792
|
+
const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
|
|
21320
21793
|
const parsedBody = createThreadHookSchema.parse(request.body);
|
|
21321
21794
|
const body = {
|
|
21322
21795
|
scope: parsedBody.scope,
|
|
@@ -21329,7 +21802,7 @@ async function registerThreadRoutes(app2) {
|
|
|
21329
21802
|
return app2.services.threadService.createThreadHook(params.id, body);
|
|
21330
21803
|
});
|
|
21331
21804
|
app2.put("/api/threads/:id/hooks", async (request) => {
|
|
21332
|
-
const params =
|
|
21805
|
+
const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
|
|
21333
21806
|
const parsedBody = updateThreadHookSchema.parse(request.body);
|
|
21334
21807
|
const body = {
|
|
21335
21808
|
scope: parsedBody.scope,
|
|
@@ -21350,17 +21823,17 @@ async function registerThreadRoutes(app2) {
|
|
|
21350
21823
|
return app2.services.threadService.updateThreadHook(params.id, body);
|
|
21351
21824
|
});
|
|
21352
21825
|
app2.post("/api/threads/:id/hooks/trust", async (request) => {
|
|
21353
|
-
const params =
|
|
21826
|
+
const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
|
|
21354
21827
|
const body = trustThreadHookSchema.parse(request.body);
|
|
21355
21828
|
return app2.services.threadService.trustThreadHook(params.id, body);
|
|
21356
21829
|
});
|
|
21357
21830
|
app2.post("/api/threads/:id/hooks/untrust", async (request) => {
|
|
21358
|
-
const params =
|
|
21831
|
+
const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
|
|
21359
21832
|
const body = untrustThreadHookSchema.parse(request.body);
|
|
21360
21833
|
return app2.services.threadService.untrustThreadHook(params.id, body);
|
|
21361
21834
|
});
|
|
21362
21835
|
app2.post("/api/threads/:id/resume", async (request) => {
|
|
21363
|
-
const params =
|
|
21836
|
+
const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
|
|
21364
21837
|
const body = resumeThreadSchema.parse(request.body ?? {});
|
|
21365
21838
|
const input = {
|
|
21366
21839
|
...body.model !== void 0 ? { model: body.model } : {},
|
|
@@ -21369,13 +21842,13 @@ async function registerThreadRoutes(app2) {
|
|
|
21369
21842
|
return app2.services.threadService.resumeThread(params.id, input);
|
|
21370
21843
|
});
|
|
21371
21844
|
app2.post("/api/threads/:id/disconnect", async (request) => {
|
|
21372
|
-
const params =
|
|
21845
|
+
const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
|
|
21373
21846
|
const detail = await app2.services.threadService.disconnectThread(params.id);
|
|
21374
21847
|
await app2.services.shellService.detachThreadViewers(params.id);
|
|
21375
21848
|
return detail;
|
|
21376
21849
|
});
|
|
21377
21850
|
app2.post("/api/threads/:id/prompt", async (request) => {
|
|
21378
|
-
const params =
|
|
21851
|
+
const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
|
|
21379
21852
|
const parsed = request.isMultipart() ? await parseMultipartPromptRequest(request) : {
|
|
21380
21853
|
input: (() => {
|
|
21381
21854
|
const parsedBody = promptSchema.parse(request.body);
|
|
@@ -21400,15 +21873,15 @@ async function registerThreadRoutes(app2) {
|
|
|
21400
21873
|
});
|
|
21401
21874
|
});
|
|
21402
21875
|
app2.post("/api/threads/:id/requests/:requestId/respond", async (request) => {
|
|
21403
|
-
const params =
|
|
21404
|
-
id:
|
|
21405
|
-
requestId:
|
|
21876
|
+
const params = z5.object({
|
|
21877
|
+
id: z5.string().uuid(),
|
|
21878
|
+
requestId: z5.string().min(1)
|
|
21406
21879
|
}).parse(request.params);
|
|
21407
21880
|
const body = respondThreadRequestSchema.parse(request.body);
|
|
21408
21881
|
return app2.services.threadService.respondToRequest(params.id, params.requestId, body);
|
|
21409
21882
|
});
|
|
21410
21883
|
app2.post("/api/threads/:id/interrupt", async (request) => {
|
|
21411
|
-
const params =
|
|
21884
|
+
const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
|
|
21412
21885
|
const body = interruptSchema.parse(request.body ?? {});
|
|
21413
21886
|
return app2.services.threadService.interruptThread(params.id, body.turnId);
|
|
21414
21887
|
});
|
|
@@ -21418,27 +21891,46 @@ async function registerThreadRoutes(app2) {
|
|
|
21418
21891
|
import fs17 from "fs/promises";
|
|
21419
21892
|
import path17 from "path";
|
|
21420
21893
|
import { spawn as spawn3 } from "child_process";
|
|
21421
|
-
import {
|
|
21422
|
-
|
|
21423
|
-
|
|
21424
|
-
|
|
21425
|
-
|
|
21894
|
+
import { Readable } from "stream";
|
|
21895
|
+
import { z as z6 } from "zod";
|
|
21896
|
+
var createWorkspaceSchema = z6.union([
|
|
21897
|
+
z6.object({
|
|
21898
|
+
absPath: z6.string().min(1),
|
|
21899
|
+
label: z6.string().min(1).optional()
|
|
21426
21900
|
}),
|
|
21427
|
-
|
|
21428
|
-
gitUrl:
|
|
21429
|
-
label:
|
|
21901
|
+
z6.object({
|
|
21902
|
+
gitUrl: z6.string().min(1),
|
|
21903
|
+
label: z6.string().min(1).optional()
|
|
21430
21904
|
})
|
|
21431
21905
|
]);
|
|
21432
|
-
var updateFavoriteSchema =
|
|
21433
|
-
isFavorite:
|
|
21906
|
+
var updateFavoriteSchema = z6.object({
|
|
21907
|
+
isFavorite: z6.boolean()
|
|
21908
|
+
});
|
|
21909
|
+
var updateWorkspaceSchema = z6.object({
|
|
21910
|
+
label: z6.string().min(1)
|
|
21434
21911
|
});
|
|
21435
|
-
var
|
|
21436
|
-
|
|
21912
|
+
var treeQuerySchema = z6.object({
|
|
21913
|
+
path: z6.string().optional(),
|
|
21914
|
+
showHidden: z6.coerce.boolean().optional()
|
|
21437
21915
|
});
|
|
21438
|
-
var
|
|
21439
|
-
path:
|
|
21440
|
-
showHidden: z7.coerce.boolean().optional()
|
|
21916
|
+
var workspaceFileQuerySchema = z6.object({
|
|
21917
|
+
path: z6.string().optional().default("")
|
|
21441
21918
|
});
|
|
21919
|
+
var workspacePreviewQuerySchema = z6.object({
|
|
21920
|
+
path: z6.string().min(1),
|
|
21921
|
+
offset: z6.coerce.number().int().min(0).optional(),
|
|
21922
|
+
limit: z6.coerce.number().int().positive().max(25e4).optional()
|
|
21923
|
+
});
|
|
21924
|
+
var PREVIEW_DEFAULT_LIMIT_BYTES = 5e4;
|
|
21925
|
+
var WORKSPACE_UPLOAD_MAX_BYTES = 50 * 1024 * 1024;
|
|
21926
|
+
var WORKSPACE_TREE_IGNORED_NAMES = /* @__PURE__ */ new Set([
|
|
21927
|
+
".git",
|
|
21928
|
+
"node_modules",
|
|
21929
|
+
".next",
|
|
21930
|
+
".turbo",
|
|
21931
|
+
"dist",
|
|
21932
|
+
"build"
|
|
21933
|
+
]);
|
|
21442
21934
|
function toWorkspaceDto2(record) {
|
|
21443
21935
|
return {
|
|
21444
21936
|
id: record.id,
|
|
@@ -21450,6 +21942,159 @@ function toWorkspaceDto2(record) {
|
|
|
21450
21942
|
lastOpenedAt: record.lastOpenedAt
|
|
21451
21943
|
};
|
|
21452
21944
|
}
|
|
21945
|
+
function languageForPath(filePath) {
|
|
21946
|
+
const extension = path17.extname(filePath).slice(1).toLowerCase();
|
|
21947
|
+
switch (extension) {
|
|
21948
|
+
case "js":
|
|
21949
|
+
case "jsx":
|
|
21950
|
+
return "javascript";
|
|
21951
|
+
case "ts":
|
|
21952
|
+
case "tsx":
|
|
21953
|
+
return extension;
|
|
21954
|
+
case "md":
|
|
21955
|
+
case "markdown":
|
|
21956
|
+
return "markdown";
|
|
21957
|
+
case "yml":
|
|
21958
|
+
return "yaml";
|
|
21959
|
+
case "sh":
|
|
21960
|
+
case "bash":
|
|
21961
|
+
return "bash";
|
|
21962
|
+
case "py":
|
|
21963
|
+
return "python";
|
|
21964
|
+
case "rb":
|
|
21965
|
+
return "ruby";
|
|
21966
|
+
case "rs":
|
|
21967
|
+
return "rust";
|
|
21968
|
+
case "go":
|
|
21969
|
+
return "go";
|
|
21970
|
+
case "c":
|
|
21971
|
+
case "h":
|
|
21972
|
+
return "c";
|
|
21973
|
+
case "cc":
|
|
21974
|
+
case "cpp":
|
|
21975
|
+
case "cxx":
|
|
21976
|
+
case "hpp":
|
|
21977
|
+
return "cpp";
|
|
21978
|
+
case "html":
|
|
21979
|
+
case "css":
|
|
21980
|
+
case "json":
|
|
21981
|
+
case "jsonl":
|
|
21982
|
+
case "toml":
|
|
21983
|
+
case "xml":
|
|
21984
|
+
case "sql":
|
|
21985
|
+
case "txt":
|
|
21986
|
+
return extension;
|
|
21987
|
+
default:
|
|
21988
|
+
return extension || "text";
|
|
21989
|
+
}
|
|
21990
|
+
}
|
|
21991
|
+
function relativeWorkspacePath(rootPath, absPath) {
|
|
21992
|
+
const relative = path17.relative(rootPath, absPath);
|
|
21993
|
+
return relative === "" ? "" : relative.split(path17.sep).join("/");
|
|
21994
|
+
}
|
|
21995
|
+
async function resolveWorkspaceItemPath(rootPath, relativePath = "") {
|
|
21996
|
+
const candidate = path17.resolve(rootPath, relativePath || ".");
|
|
21997
|
+
const comparable = await assertPathWithinRoot(rootPath, candidate);
|
|
21998
|
+
return comparable;
|
|
21999
|
+
}
|
|
22000
|
+
async function buildWorkspaceTreeNode(rootPath, absPath, depth = 0) {
|
|
22001
|
+
const stats = await fs17.stat(absPath);
|
|
22002
|
+
const relativePath = relativeWorkspacePath(rootPath, absPath);
|
|
22003
|
+
const name = relativePath ? path17.basename(absPath) : path17.basename(rootPath);
|
|
22004
|
+
if (!stats.isDirectory()) {
|
|
22005
|
+
return {
|
|
22006
|
+
name,
|
|
22007
|
+
path: relativePath,
|
|
22008
|
+
kind: "file",
|
|
22009
|
+
size: stats.size
|
|
22010
|
+
};
|
|
22011
|
+
}
|
|
22012
|
+
const node = {
|
|
22013
|
+
name,
|
|
22014
|
+
path: relativePath,
|
|
22015
|
+
kind: "directory",
|
|
22016
|
+
children: []
|
|
22017
|
+
};
|
|
22018
|
+
if (depth >= 6) {
|
|
22019
|
+
return node;
|
|
22020
|
+
}
|
|
22021
|
+
let entries;
|
|
22022
|
+
try {
|
|
22023
|
+
entries = await fs17.readdir(absPath, { withFileTypes: true });
|
|
22024
|
+
} catch {
|
|
22025
|
+
return node;
|
|
22026
|
+
}
|
|
22027
|
+
const visible = entries.filter((entry) => !entry.name.startsWith(".")).filter((entry) => !WORKSPACE_TREE_IGNORED_NAMES.has(entry.name)).sort((left, right) => {
|
|
22028
|
+
if (left.isDirectory() && !right.isDirectory()) {
|
|
22029
|
+
return -1;
|
|
22030
|
+
}
|
|
22031
|
+
if (!left.isDirectory() && right.isDirectory()) {
|
|
22032
|
+
return 1;
|
|
22033
|
+
}
|
|
22034
|
+
return left.name.localeCompare(right.name);
|
|
22035
|
+
}).slice(0, 400);
|
|
22036
|
+
node.children = (await Promise.all(
|
|
22037
|
+
visible.map(async (entry) => {
|
|
22038
|
+
const childPath = path17.join(absPath, entry.name);
|
|
22039
|
+
try {
|
|
22040
|
+
if (!entry.isDirectory() && !entry.isFile()) {
|
|
22041
|
+
return null;
|
|
22042
|
+
}
|
|
22043
|
+
return await buildWorkspaceTreeNode(rootPath, childPath, depth + 1);
|
|
22044
|
+
} catch {
|
|
22045
|
+
return null;
|
|
22046
|
+
}
|
|
22047
|
+
})
|
|
22048
|
+
)).filter((child) => child !== null);
|
|
22049
|
+
return node;
|
|
22050
|
+
}
|
|
22051
|
+
function requireWorkspaceRecord(app2, workspaceId) {
|
|
22052
|
+
const record = getWorkspaceRecordById(app2.services.database.db, workspaceId);
|
|
22053
|
+
if (!record) {
|
|
22054
|
+
throw new HttpError(404, {
|
|
22055
|
+
code: "not_found",
|
|
22056
|
+
message: "Workspace was not found."
|
|
22057
|
+
});
|
|
22058
|
+
}
|
|
22059
|
+
return record;
|
|
22060
|
+
}
|
|
22061
|
+
function contentTypeForPath(filePath) {
|
|
22062
|
+
switch (path17.extname(filePath).slice(1).toLowerCase()) {
|
|
22063
|
+
case "png":
|
|
22064
|
+
return "image/png";
|
|
22065
|
+
case "jpg":
|
|
22066
|
+
case "jpeg":
|
|
22067
|
+
return "image/jpeg";
|
|
22068
|
+
case "gif":
|
|
22069
|
+
return "image/gif";
|
|
22070
|
+
case "webp":
|
|
22071
|
+
return "image/webp";
|
|
22072
|
+
case "svg":
|
|
22073
|
+
return "image/svg+xml";
|
|
22074
|
+
case "pdf":
|
|
22075
|
+
return "application/pdf";
|
|
22076
|
+
case "json":
|
|
22077
|
+
return "application/json; charset=utf-8";
|
|
22078
|
+
case "html":
|
|
22079
|
+
return "text/html; charset=utf-8";
|
|
22080
|
+
case "css":
|
|
22081
|
+
return "text/css; charset=utf-8";
|
|
22082
|
+
case "js":
|
|
22083
|
+
case "mjs":
|
|
22084
|
+
case "ts":
|
|
22085
|
+
case "tsx":
|
|
22086
|
+
return "text/plain; charset=utf-8";
|
|
22087
|
+
default:
|
|
22088
|
+
return "application/octet-stream";
|
|
22089
|
+
}
|
|
22090
|
+
}
|
|
22091
|
+
function sanitizeUploadFilename(filename) {
|
|
22092
|
+
const baseName = path17.basename(filename?.trim() || "upload");
|
|
22093
|
+
if (!baseName || baseName === "." || baseName === "..") {
|
|
22094
|
+
return "upload";
|
|
22095
|
+
}
|
|
22096
|
+
return baseName;
|
|
22097
|
+
}
|
|
21453
22098
|
function inferGitRepoName(gitUrl) {
|
|
21454
22099
|
const trimmed = gitUrl.trim();
|
|
21455
22100
|
const withoutQuery = trimmed.split(/[?#]/)[0] ?? trimmed;
|
|
@@ -21534,7 +22179,7 @@ async function registerWorkspaceRoutes(app2) {
|
|
|
21534
22179
|
};
|
|
21535
22180
|
});
|
|
21536
22181
|
app2.get("/api/workspaces/:id", async (request) => {
|
|
21537
|
-
const params =
|
|
22182
|
+
const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
|
|
21538
22183
|
const record = getWorkspaceRecordById(app2.services.database.db, params.id);
|
|
21539
22184
|
if (!record) {
|
|
21540
22185
|
throw new HttpError(404, {
|
|
@@ -21544,6 +22189,114 @@ async function registerWorkspaceRoutes(app2) {
|
|
|
21544
22189
|
}
|
|
21545
22190
|
return toWorkspaceDto2(record);
|
|
21546
22191
|
});
|
|
22192
|
+
app2.get("/api/workspaces/:id/files/tree", async (request) => {
|
|
22193
|
+
const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
|
|
22194
|
+
const query = workspaceFileQuerySchema.parse(request.query);
|
|
22195
|
+
const record = requireWorkspaceRecord(app2, params.id);
|
|
22196
|
+
const rootPath = await fs17.realpath(record.absPath);
|
|
22197
|
+
const targetPath = await resolveWorkspaceItemPath(rootPath, query.path);
|
|
22198
|
+
return buildWorkspaceTreeNode(rootPath, targetPath);
|
|
22199
|
+
});
|
|
22200
|
+
app2.get("/api/workspaces/:id/files/preview", async (request) => {
|
|
22201
|
+
const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
|
|
22202
|
+
const query = workspacePreviewQuerySchema.parse(request.query);
|
|
22203
|
+
const record = requireWorkspaceRecord(app2, params.id);
|
|
22204
|
+
const rootPath = await fs17.realpath(record.absPath);
|
|
22205
|
+
const filePath = await resolveWorkspaceItemPath(rootPath, query.path);
|
|
22206
|
+
const stats = await fs17.stat(filePath);
|
|
22207
|
+
if (!stats.isFile()) {
|
|
22208
|
+
throw new HttpError(400, {
|
|
22209
|
+
code: "bad_request",
|
|
22210
|
+
message: "Workspace preview path must point to a file."
|
|
22211
|
+
});
|
|
22212
|
+
}
|
|
22213
|
+
const offset = query.offset ?? 0;
|
|
22214
|
+
const limit = query.limit ?? PREVIEW_DEFAULT_LIMIT_BYTES;
|
|
22215
|
+
const handle = await fs17.open(filePath, "r");
|
|
22216
|
+
try {
|
|
22217
|
+
const length = Math.min(limit, Math.max(0, stats.size - offset));
|
|
22218
|
+
const buffer = Buffer.alloc(length);
|
|
22219
|
+
const read = await handle.read(buffer, 0, length, offset);
|
|
22220
|
+
const nextOffset = offset + read.bytesRead;
|
|
22221
|
+
return {
|
|
22222
|
+
path: relativeWorkspacePath(rootPath, filePath),
|
|
22223
|
+
name: path17.basename(filePath),
|
|
22224
|
+
content: buffer.subarray(0, read.bytesRead).toString("utf8"),
|
|
22225
|
+
language: languageForPath(filePath),
|
|
22226
|
+
size: stats.size,
|
|
22227
|
+
truncated: nextOffset < stats.size,
|
|
22228
|
+
nextOffset
|
|
22229
|
+
};
|
|
22230
|
+
} finally {
|
|
22231
|
+
await handle.close();
|
|
22232
|
+
}
|
|
22233
|
+
});
|
|
22234
|
+
app2.get("/api/workspaces/:id/files/raw", async (request, reply) => {
|
|
22235
|
+
const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
|
|
22236
|
+
const query = workspacePreviewQuerySchema.pick({ path: true }).parse(request.query);
|
|
22237
|
+
const record = requireWorkspaceRecord(app2, params.id);
|
|
22238
|
+
const rootPath = await fs17.realpath(record.absPath);
|
|
22239
|
+
const filePath = await resolveWorkspaceItemPath(rootPath, query.path);
|
|
22240
|
+
const stats = await fs17.stat(filePath);
|
|
22241
|
+
if (!stats.isFile()) {
|
|
22242
|
+
throw new HttpError(400, {
|
|
22243
|
+
code: "bad_request",
|
|
22244
|
+
message: "Raw workspace path must point to a file."
|
|
22245
|
+
});
|
|
22246
|
+
}
|
|
22247
|
+
reply.header("content-type", contentTypeForPath(filePath));
|
|
22248
|
+
return reply.send(Readable.from(await fs17.readFile(filePath)));
|
|
22249
|
+
});
|
|
22250
|
+
app2.get("/api/workspaces/:id/files/download", async (request, reply) => {
|
|
22251
|
+
const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
|
|
22252
|
+
const query = workspaceFileQuerySchema.parse(request.query);
|
|
22253
|
+
const record = requireWorkspaceRecord(app2, params.id);
|
|
22254
|
+
const rootPath = await fs17.realpath(record.absPath);
|
|
22255
|
+
const itemPath = await resolveWorkspaceItemPath(rootPath, query.path);
|
|
22256
|
+
const stats = await fs17.stat(itemPath);
|
|
22257
|
+
if (!stats.isFile()) {
|
|
22258
|
+
throw new HttpError(400, {
|
|
22259
|
+
code: "bad_request",
|
|
22260
|
+
message: "Only file downloads are supported from this endpoint."
|
|
22261
|
+
});
|
|
22262
|
+
}
|
|
22263
|
+
const filename = path17.basename(itemPath);
|
|
22264
|
+
reply.header("content-type", contentTypeForPath(itemPath)).header(
|
|
22265
|
+
"content-disposition",
|
|
22266
|
+
`attachment; filename="${filename}"; filename*=UTF-8''${encodeURIComponent(filename)}`
|
|
22267
|
+
);
|
|
22268
|
+
return reply.send(Readable.from(await fs17.readFile(itemPath)));
|
|
22269
|
+
});
|
|
22270
|
+
app2.post("/api/workspaces/:id/files/upload", async (request) => {
|
|
22271
|
+
const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
|
|
22272
|
+
const record = requireWorkspaceRecord(app2, params.id);
|
|
22273
|
+
const rootPath = await fs17.realpath(record.absPath);
|
|
22274
|
+
const part = await request.file();
|
|
22275
|
+
if (!part) {
|
|
22276
|
+
throw new HttpError(400, {
|
|
22277
|
+
code: "bad_request",
|
|
22278
|
+
message: "A file field is required."
|
|
22279
|
+
});
|
|
22280
|
+
}
|
|
22281
|
+
const buffer = await part.toBuffer();
|
|
22282
|
+
if (buffer.byteLength > WORKSPACE_UPLOAD_MAX_BYTES) {
|
|
22283
|
+
throw new HttpError(400, {
|
|
22284
|
+
code: "bad_request",
|
|
22285
|
+
message: "Workspace uploads must be 50 MB or smaller."
|
|
22286
|
+
});
|
|
22287
|
+
}
|
|
22288
|
+
const filename = sanitizeUploadFilename(part.filename);
|
|
22289
|
+
const destination = await resolveWorkspaceItemPath(rootPath, filename);
|
|
22290
|
+
await fs17.writeFile(destination, buffer);
|
|
22291
|
+
return {
|
|
22292
|
+
kind: "file",
|
|
22293
|
+
file: {
|
|
22294
|
+
path: relativeWorkspacePath(rootPath, destination),
|
|
22295
|
+
name: filename,
|
|
22296
|
+
size: buffer.byteLength
|
|
22297
|
+
}
|
|
22298
|
+
};
|
|
22299
|
+
});
|
|
21547
22300
|
app2.post("/api/workspaces", async (request) => {
|
|
21548
22301
|
const body = createWorkspaceSchema.parse(request.body);
|
|
21549
22302
|
const settings = await getWorkspaceSettings(
|
|
@@ -21588,7 +22341,7 @@ async function registerWorkspaceRoutes(app2) {
|
|
|
21588
22341
|
return toWorkspaceDto2(created);
|
|
21589
22342
|
});
|
|
21590
22343
|
app2.patch("/api/workspaces/:id", async (request) => {
|
|
21591
|
-
const params =
|
|
22344
|
+
const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
|
|
21592
22345
|
const body = updateWorkspaceSchema.parse(request.body);
|
|
21593
22346
|
const record = getWorkspaceRecordById(app2.services.database.db, params.id);
|
|
21594
22347
|
if (!record) {
|
|
@@ -21609,7 +22362,7 @@ async function registerWorkspaceRoutes(app2) {
|
|
|
21609
22362
|
return toWorkspaceDto2(updated);
|
|
21610
22363
|
});
|
|
21611
22364
|
app2.delete("/api/workspaces/:id", async (request) => {
|
|
21612
|
-
const params =
|
|
22365
|
+
const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
|
|
21613
22366
|
const record = getWorkspaceRecordById(app2.services.database.db, params.id);
|
|
21614
22367
|
if (!record) {
|
|
21615
22368
|
throw new HttpError(404, {
|
|
@@ -21633,7 +22386,7 @@ async function registerWorkspaceRoutes(app2) {
|
|
|
21633
22386
|
return { id: params.id };
|
|
21634
22387
|
});
|
|
21635
22388
|
app2.post("/api/workspaces/:id/favorite", async (request) => {
|
|
21636
|
-
const params =
|
|
22389
|
+
const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
|
|
21637
22390
|
const body = updateFavoriteSchema.parse(request.body);
|
|
21638
22391
|
const record = getWorkspaceRecordById(app2.services.database.db, params.id);
|
|
21639
22392
|
if (!record) {
|
|
@@ -21647,7 +22400,7 @@ async function registerWorkspaceRoutes(app2) {
|
|
|
21647
22400
|
return toWorkspaceDto2(updated);
|
|
21648
22401
|
});
|
|
21649
22402
|
app2.post("/api/workspaces/:id/open", async (request) => {
|
|
21650
|
-
const params =
|
|
22403
|
+
const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
|
|
21651
22404
|
const record = getWorkspaceRecordById(app2.services.database.db, params.id);
|
|
21652
22405
|
if (!record) {
|
|
21653
22406
|
throw new HttpError(404, {
|
|
@@ -21662,21 +22415,32 @@ async function registerWorkspaceRoutes(app2) {
|
|
|
21662
22415
|
}
|
|
21663
22416
|
|
|
21664
22417
|
// src/routes/plugins.ts
|
|
21665
|
-
import { z as
|
|
21666
|
-
var pluginParamsSchema =
|
|
21667
|
-
pluginId:
|
|
22418
|
+
import { z as z7 } from "zod";
|
|
22419
|
+
var pluginParamsSchema = z7.object({
|
|
22420
|
+
pluginId: z7.string().min(1)
|
|
21668
22421
|
});
|
|
21669
|
-
var updatePluginSchema =
|
|
21670
|
-
enabled:
|
|
22422
|
+
var updatePluginSchema = z7.object({
|
|
22423
|
+
enabled: z7.boolean()
|
|
21671
22424
|
});
|
|
21672
|
-
var importPluginSchema =
|
|
21673
|
-
enabled:
|
|
21674
|
-
manifestJson:
|
|
21675
|
-
manifest:
|
|
22425
|
+
var importPluginSchema = z7.object({
|
|
22426
|
+
enabled: z7.boolean().optional(),
|
|
22427
|
+
manifestJson: z7.string().optional(),
|
|
22428
|
+
manifest: z7.unknown().optional()
|
|
21676
22429
|
}).refine((value) => value.manifest !== void 0 || value.manifestJson !== void 0, {
|
|
21677
22430
|
message: "Plugin import requires manifest or manifestJson."
|
|
21678
22431
|
});
|
|
21679
22432
|
async function registerPluginRoutes(app2) {
|
|
22433
|
+
async function syncManagedPluginMcpConfig() {
|
|
22434
|
+
await app2.services.pluginService.syncManagedCodexMcpConfig({
|
|
22435
|
+
codexHome: app2.services.config.agentProviders.codex.home ?? null,
|
|
22436
|
+
repoRoot: app2.services.repoRoot
|
|
22437
|
+
});
|
|
22438
|
+
const codexRuntime = app2.services.agentRuntimes.getOptional("codex");
|
|
22439
|
+
if (codexRuntime) {
|
|
22440
|
+
await codexRuntime.stop();
|
|
22441
|
+
await codexRuntime.start();
|
|
22442
|
+
}
|
|
22443
|
+
}
|
|
21680
22444
|
app2.get("/api/plugins", async () => {
|
|
21681
22445
|
return app2.services.pluginService.listPlugins();
|
|
21682
22446
|
});
|
|
@@ -21688,7 +22452,9 @@ async function registerPluginRoutes(app2) {
|
|
|
21688
22452
|
...parsed.manifestJson === void 0 ? {} : { manifestJson: parsed.manifestJson }
|
|
21689
22453
|
};
|
|
21690
22454
|
try {
|
|
21691
|
-
|
|
22455
|
+
const plugin = app2.services.pluginService.importPlugin(body);
|
|
22456
|
+
await syncManagedPluginMcpConfig();
|
|
22457
|
+
return plugin;
|
|
21692
22458
|
} catch (error) {
|
|
21693
22459
|
if (error instanceof SyntaxError) {
|
|
21694
22460
|
throw new HttpError(400, {
|
|
@@ -21717,7 +22483,9 @@ async function registerPluginRoutes(app2) {
|
|
|
21717
22483
|
const { pluginId } = pluginParamsSchema.parse(request.params);
|
|
21718
22484
|
const body = updatePluginSchema.parse(request.body);
|
|
21719
22485
|
try {
|
|
21720
|
-
|
|
22486
|
+
const plugin = app2.services.pluginService.setPluginEnabled(pluginId, body.enabled);
|
|
22487
|
+
await syncManagedPluginMcpConfig();
|
|
22488
|
+
return plugin;
|
|
21721
22489
|
} catch {
|
|
21722
22490
|
throw new HttpError(404, {
|
|
21723
22491
|
code: "not_found",
|
|
@@ -21727,6 +22495,46 @@ async function registerPluginRoutes(app2) {
|
|
|
21727
22495
|
});
|
|
21728
22496
|
}
|
|
21729
22497
|
|
|
22498
|
+
// src/routes/auth.ts
|
|
22499
|
+
import { z as z8 } from "zod";
|
|
22500
|
+
var loginSchema = z8.object({
|
|
22501
|
+
username: z8.string().min(1),
|
|
22502
|
+
password: z8.string().min(1)
|
|
22503
|
+
});
|
|
22504
|
+
async function registerAuthRoutes(app2) {
|
|
22505
|
+
app2.get("/api/auth/session", async (request) => {
|
|
22506
|
+
return app2.services.authService.verifyRequest(request);
|
|
22507
|
+
});
|
|
22508
|
+
app2.post("/api/auth/login", async (request, reply) => {
|
|
22509
|
+
const body = loginSchema.parse(request.body ?? {});
|
|
22510
|
+
const login = app2.services.authService.login(body);
|
|
22511
|
+
if (!login) {
|
|
22512
|
+
reply.status(401).send({
|
|
22513
|
+
code: "unauthorized",
|
|
22514
|
+
message: "Invalid username or password."
|
|
22515
|
+
});
|
|
22516
|
+
return;
|
|
22517
|
+
}
|
|
22518
|
+
if (login.token) {
|
|
22519
|
+
app2.services.authService.attachSessionCookie(reply, login.token);
|
|
22520
|
+
}
|
|
22521
|
+
return {
|
|
22522
|
+
token: login.token,
|
|
22523
|
+
session: login.session
|
|
22524
|
+
};
|
|
22525
|
+
});
|
|
22526
|
+
app2.post("/api/auth/logout", async (_request, reply) => {
|
|
22527
|
+
app2.services.authService.clearSessionCookie(reply);
|
|
22528
|
+
return {
|
|
22529
|
+
authenticated: false,
|
|
22530
|
+
username: null,
|
|
22531
|
+
expiresAt: null,
|
|
22532
|
+
mode: app2.services.authService.mode,
|
|
22533
|
+
authRequired: app2.services.authService.required
|
|
22534
|
+
};
|
|
22535
|
+
});
|
|
22536
|
+
}
|
|
22537
|
+
|
|
21730
22538
|
// src/provider-host-config-service.ts
|
|
21731
22539
|
import fs18 from "fs/promises";
|
|
21732
22540
|
import path18 from "path";
|
|
@@ -21953,28 +22761,12 @@ var ProviderHostConfigService = class {
|
|
|
21953
22761
|
};
|
|
21954
22762
|
|
|
21955
22763
|
// src/shell/shell-session-service.ts
|
|
22764
|
+
import fs20 from "fs/promises";
|
|
22765
|
+
|
|
22766
|
+
// src/shell/shell-prompt.ts
|
|
21956
22767
|
import fs19 from "fs/promises";
|
|
21957
22768
|
import os3 from "os";
|
|
21958
22769
|
import path19 from "path";
|
|
21959
|
-
async function pathExists5(filePath) {
|
|
21960
|
-
try {
|
|
21961
|
-
await fs19.access(filePath);
|
|
21962
|
-
return true;
|
|
21963
|
-
} catch {
|
|
21964
|
-
return false;
|
|
21965
|
-
}
|
|
21966
|
-
}
|
|
21967
|
-
function nowIso() {
|
|
21968
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
21969
|
-
}
|
|
21970
|
-
function waitForShellTick(milliseconds) {
|
|
21971
|
-
if (process.env.VITEST) {
|
|
21972
|
-
return Promise.resolve();
|
|
21973
|
-
}
|
|
21974
|
-
return new Promise((resolve) => {
|
|
21975
|
-
setTimeout(resolve, milliseconds);
|
|
21976
|
-
});
|
|
21977
|
-
}
|
|
21978
22770
|
function basenameFromPath2(filePath) {
|
|
21979
22771
|
if (!filePath) {
|
|
21980
22772
|
return "";
|
|
@@ -22043,9 +22835,6 @@ function resolveEnvironmentPrefix(environmentText) {
|
|
|
22043
22835
|
}
|
|
22044
22836
|
return null;
|
|
22045
22837
|
}
|
|
22046
|
-
function shellSingleQuote(value) {
|
|
22047
|
-
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
22048
|
-
}
|
|
22049
22838
|
async function resolvePaneEnvironmentPrefix(tmuxManager, sessionName, panePid) {
|
|
22050
22839
|
const sessionPrefix = await tmuxManager.getSessionEnvironmentVariable(
|
|
22051
22840
|
sessionName,
|
|
@@ -22061,6 +22850,9 @@ async function resolvePaneEnvironmentPrefix(tmuxManager, sessionName, panePid) {
|
|
|
22061
22850
|
return null;
|
|
22062
22851
|
}
|
|
22063
22852
|
}
|
|
22853
|
+
function shellSingleQuote(value) {
|
|
22854
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
22855
|
+
}
|
|
22064
22856
|
function buildShellPromptInitScriptContents(command) {
|
|
22065
22857
|
const normalized = command.trim().toLowerCase();
|
|
22066
22858
|
if (normalized === "zsh") {
|
|
@@ -22160,6 +22952,26 @@ clear
|
|
|
22160
22952
|
` : `${sourceCommand}
|
|
22161
22953
|
`;
|
|
22162
22954
|
}
|
|
22955
|
+
|
|
22956
|
+
// src/shell/shell-session-service.ts
|
|
22957
|
+
async function pathExists5(filePath) {
|
|
22958
|
+
try {
|
|
22959
|
+
await fs20.access(filePath);
|
|
22960
|
+
return true;
|
|
22961
|
+
} catch {
|
|
22962
|
+
return false;
|
|
22963
|
+
}
|
|
22964
|
+
}
|
|
22965
|
+
function nowIso() {
|
|
22966
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
22967
|
+
}
|
|
22968
|
+
function uniqueShellSessionName(baseName, index) {
|
|
22969
|
+
if (index <= 1) {
|
|
22970
|
+
return baseName;
|
|
22971
|
+
}
|
|
22972
|
+
const suffix = `-${index}`;
|
|
22973
|
+
return `${baseName.slice(0, Math.max(1, 64 - suffix.length))}${suffix}`;
|
|
22974
|
+
}
|
|
22163
22975
|
function shellThreadId(shell) {
|
|
22164
22976
|
if (!shell.threadId) {
|
|
22165
22977
|
throw new ShellServiceError(
|
|
@@ -22220,25 +23032,25 @@ var ShellServiceError = class extends Error {
|
|
|
22220
23032
|
code;
|
|
22221
23033
|
};
|
|
22222
23034
|
var ShellSessionService = class {
|
|
22223
|
-
constructor(db, eventBus,
|
|
23035
|
+
constructor(db, eventBus, shellBackend) {
|
|
22224
23036
|
this.db = db;
|
|
22225
23037
|
this.eventBus = eventBus;
|
|
22226
|
-
this.
|
|
23038
|
+
this.shellBackend = shellBackend;
|
|
22227
23039
|
}
|
|
22228
23040
|
db;
|
|
22229
23041
|
eventBus;
|
|
22230
|
-
|
|
23042
|
+
shellBackend;
|
|
22231
23043
|
attachments = /* @__PURE__ */ new Map();
|
|
22232
23044
|
async stop() {
|
|
22233
23045
|
for (const [shellId, attachment] of this.attachments) {
|
|
22234
|
-
|
|
23046
|
+
attachment.backendAttachment.dispose();
|
|
22235
23047
|
deleteViewerSessionRecord(this.db, attachment.viewerId);
|
|
22236
23048
|
this.attachments.delete(shellId);
|
|
22237
23049
|
}
|
|
22238
23050
|
}
|
|
22239
23051
|
async syncShellStateOnStartup() {
|
|
22240
23052
|
const records = listShellSessionRecords(this.db);
|
|
22241
|
-
const sessionNames = new Set(await this.
|
|
23053
|
+
const sessionNames = new Set(await this.shellBackend.listSessionNames());
|
|
22242
23054
|
for (const record of records) {
|
|
22243
23055
|
const sessionName = record.tmuxSessionName ?? "";
|
|
22244
23056
|
const nextStatus = sessionNames.has(sessionName) ? "running" : record.status === "exited" ? "exited" : "not_found";
|
|
@@ -22261,23 +23073,33 @@ var ShellSessionService = class {
|
|
|
22261
23073
|
if (!workspace) {
|
|
22262
23074
|
throw new ShellServiceError("thread_not_found", "Workspace not found.");
|
|
22263
23075
|
}
|
|
22264
|
-
const
|
|
23076
|
+
const shells = listShellSessionRecordsByThreadId(this.db, threadId);
|
|
22265
23077
|
const workspacePathStatus = await pathExists5(workspace.absPath) ? "present" : "missing";
|
|
22266
|
-
if (
|
|
23078
|
+
if (shells.length === 0) {
|
|
22267
23079
|
return {
|
|
22268
23080
|
threadId: thread.id,
|
|
22269
23081
|
workspaceId: workspace.id,
|
|
22270
23082
|
workspacePathStatus,
|
|
22271
23083
|
state: workspacePathStatus === "missing" ? "workspace_missing" : "not_created",
|
|
22272
|
-
shell: null
|
|
23084
|
+
shell: null,
|
|
23085
|
+
shells: [],
|
|
23086
|
+
activeShellId: null
|
|
22273
23087
|
};
|
|
22274
23088
|
}
|
|
23089
|
+
const shellDtos = await Promise.all(
|
|
23090
|
+
shells.map((shell) => this.toShellSessionDto(shell.id))
|
|
23091
|
+
);
|
|
23092
|
+
const activeShell = shellDtos.find((shell) => shell.status === "attached") ?? shellDtos.find(
|
|
23093
|
+
(shell) => shell.status !== "exited" && shell.status !== "not_found"
|
|
23094
|
+
) ?? shellDtos[0] ?? null;
|
|
22275
23095
|
return {
|
|
22276
23096
|
threadId: thread.id,
|
|
22277
23097
|
workspaceId: workspace.id,
|
|
22278
23098
|
workspacePathStatus,
|
|
22279
|
-
state:
|
|
22280
|
-
shell:
|
|
23099
|
+
state: activeShell ? activeShell.status : "not_created",
|
|
23100
|
+
shell: activeShell,
|
|
23101
|
+
shells: shellDtos,
|
|
23102
|
+
activeShellId: activeShell?.id ?? null
|
|
22281
23103
|
};
|
|
22282
23104
|
}
|
|
22283
23105
|
async createShellForThread(threadId, options = {}) {
|
|
@@ -22301,26 +23123,21 @@ var ShellSessionService = class {
|
|
|
22301
23123
|
"Workspace path is missing on this machine."
|
|
22302
23124
|
);
|
|
22303
23125
|
}
|
|
22304
|
-
const
|
|
22305
|
-
const
|
|
22306
|
-
|
|
22307
|
-
|
|
22308
|
-
|
|
22309
|
-
|
|
22310
|
-
|
|
22311
|
-
|
|
22312
|
-
|
|
22313
|
-
|
|
22314
|
-
updateShellSessionRecord(this.db, existing.id, {
|
|
22315
|
-
tmuxSessionName,
|
|
22316
|
-
cwd: workspace.absPath,
|
|
22317
|
-
status: "creating",
|
|
22318
|
-
lastActivityAt: nowIso()
|
|
22319
|
-
});
|
|
23126
|
+
const baseSessionName = this.shellBackend.sessionNameForThread(thread.id);
|
|
23127
|
+
const existingShells = listShellSessionRecordsByThreadId(this.db, threadId);
|
|
23128
|
+
const existingSessionNames = new Set(
|
|
23129
|
+
existingShells.map((shell) => shell.tmuxSessionName).filter((name) => Boolean(name))
|
|
23130
|
+
);
|
|
23131
|
+
let sessionIndex = existingShells.length + 1;
|
|
23132
|
+
let tmuxSessionName = uniqueShellSessionName(baseSessionName, sessionIndex);
|
|
23133
|
+
while (existingSessionNames.has(tmuxSessionName) || await this.shellBackend.hasSession(tmuxSessionName)) {
|
|
23134
|
+
sessionIndex += 1;
|
|
23135
|
+
tmuxSessionName = uniqueShellSessionName(baseSessionName, sessionIndex);
|
|
22320
23136
|
}
|
|
22321
|
-
const record =
|
|
23137
|
+
const record = createShellSessionRecord(this.db, {
|
|
22322
23138
|
workspaceId: workspace.id,
|
|
22323
23139
|
threadId: thread.id,
|
|
23140
|
+
label: options.label ?? null,
|
|
22324
23141
|
tmuxSessionName,
|
|
22325
23142
|
cwd: workspace.absPath,
|
|
22326
23143
|
status: "creating"
|
|
@@ -22330,27 +23147,15 @@ var ShellSessionService = class {
|
|
|
22330
23147
|
state: "creating"
|
|
22331
23148
|
});
|
|
22332
23149
|
try {
|
|
22333
|
-
const existingSession = await this.
|
|
23150
|
+
const existingSession = await this.shellBackend.hasSession(tmuxSessionName);
|
|
22334
23151
|
if (!existingSession) {
|
|
22335
|
-
await this.
|
|
22336
|
-
|
|
23152
|
+
await this.shellBackend.createSession({
|
|
23153
|
+
sessionId: tmuxSessionName,
|
|
23154
|
+
threadId: thread.id,
|
|
22337
23155
|
cwd: workspace.absPath,
|
|
22338
23156
|
...options.cols !== void 0 ? { cols: options.cols } : {},
|
|
22339
23157
|
...options.rows !== void 0 ? { rows: options.rows } : {}
|
|
22340
23158
|
});
|
|
22341
|
-
try {
|
|
22342
|
-
const runtime = await this.tmuxManager.getPaneRuntimeInfo(tmuxSessionName);
|
|
22343
|
-
if (isInteractiveShellCommand(runtime.currentCommand)) {
|
|
22344
|
-
await this.tmuxManager.sendInput(
|
|
22345
|
-
tmuxSessionName,
|
|
22346
|
-
await buildShellPromptInitCommand(runtime.currentCommand, {
|
|
22347
|
-
clearScreen: true
|
|
22348
|
-
})
|
|
22349
|
-
);
|
|
22350
|
-
await waitForShellTick(120);
|
|
22351
|
-
}
|
|
22352
|
-
} catch {
|
|
22353
|
-
}
|
|
22354
23159
|
}
|
|
22355
23160
|
updateShellSessionRecord(this.db, record.id, {
|
|
22356
23161
|
status: "running",
|
|
@@ -22361,7 +23166,7 @@ var ShellSessionService = class {
|
|
|
22361
23166
|
status: "not_found"
|
|
22362
23167
|
});
|
|
22363
23168
|
throw new ShellServiceError(
|
|
22364
|
-
"
|
|
23169
|
+
"shell_backend_error",
|
|
22365
23170
|
error instanceof Error ? error.message : "Unable to start shell."
|
|
22366
23171
|
);
|
|
22367
23172
|
}
|
|
@@ -22369,7 +23174,32 @@ var ShellSessionService = class {
|
|
|
22369
23174
|
threadId: thread.id,
|
|
22370
23175
|
state: "detached"
|
|
22371
23176
|
});
|
|
22372
|
-
|
|
23177
|
+
const state = await this.getThreadShellState(threadId);
|
|
23178
|
+
const createdShell = await this.toShellSessionDto(record.id);
|
|
23179
|
+
return {
|
|
23180
|
+
...state,
|
|
23181
|
+
state: createdShell.status,
|
|
23182
|
+
shell: createdShell,
|
|
23183
|
+
activeShellId: createdShell.id
|
|
23184
|
+
};
|
|
23185
|
+
}
|
|
23186
|
+
async updateShell(shellId, input) {
|
|
23187
|
+
const shell = getShellSessionRecordById(this.db, shellId);
|
|
23188
|
+
if (!shell) {
|
|
23189
|
+
throw new ShellServiceError("shell_not_found", "Shell not found.");
|
|
23190
|
+
}
|
|
23191
|
+
const updates = {};
|
|
23192
|
+
if ("label" in input) {
|
|
23193
|
+
const label = input.label?.trim() ?? "";
|
|
23194
|
+
updates.label = label.length > 0 ? label : null;
|
|
23195
|
+
}
|
|
23196
|
+
updateShellSessionRecord(this.db, shell.id, updates);
|
|
23197
|
+
const shellDto = await this.toShellSessionDto(shell.id);
|
|
23198
|
+
this.emitShellEvent(shell.id, "shell.status", {
|
|
23199
|
+
threadId: shellThreadId(shell),
|
|
23200
|
+
state: shellDto.status
|
|
23201
|
+
});
|
|
23202
|
+
return shellDto;
|
|
22373
23203
|
}
|
|
22374
23204
|
async detachThreadViewers(threadId) {
|
|
22375
23205
|
const shell = getShellSessionRecordByThreadId(this.db, threadId);
|
|
@@ -22381,7 +23211,7 @@ var ShellSessionService = class {
|
|
|
22381
23211
|
deleteViewerSessionsByThreadId(this.db, threadId);
|
|
22382
23212
|
return;
|
|
22383
23213
|
}
|
|
22384
|
-
|
|
23214
|
+
attachment.backendAttachment.dispose();
|
|
22385
23215
|
deleteViewerSessionRecord(this.db, attachment.viewerId);
|
|
22386
23216
|
deleteViewerSessionsByThreadId(this.db, threadId);
|
|
22387
23217
|
this.attachments.delete(shell.id);
|
|
@@ -22404,7 +23234,7 @@ var ShellSessionService = class {
|
|
|
22404
23234
|
const existingViewer = getViewerSessionRecordByShellId(this.db, shell.id);
|
|
22405
23235
|
const existingAttachment = this.attachments.get(shell.id);
|
|
22406
23236
|
if (existingAttachment) {
|
|
22407
|
-
|
|
23237
|
+
existingAttachment.backendAttachment.dispose();
|
|
22408
23238
|
deleteViewerSessionRecord(this.db, existingAttachment.viewerId);
|
|
22409
23239
|
this.attachments.delete(shell.id);
|
|
22410
23240
|
this.emitShellEvent(shell.id, "shell.detached", {
|
|
@@ -22416,7 +23246,7 @@ var ShellSessionService = class {
|
|
|
22416
23246
|
} else if (existingViewer) {
|
|
22417
23247
|
deleteViewerSessionRecord(this.db, existingViewer.id);
|
|
22418
23248
|
}
|
|
22419
|
-
const hasSession = await this.
|
|
23249
|
+
const hasSession = await this.shellBackend.hasSession(shellSessionName(shell));
|
|
22420
23250
|
if (!hasSession) {
|
|
22421
23251
|
updateShellSessionRecord(this.db, shell.id, {
|
|
22422
23252
|
status: "not_found"
|
|
@@ -22427,7 +23257,7 @@ var ShellSessionService = class {
|
|
|
22427
23257
|
});
|
|
22428
23258
|
throw new ShellServiceError(
|
|
22429
23259
|
"shell_not_running",
|
|
22430
|
-
"The
|
|
23260
|
+
"The terminal is no longer available."
|
|
22431
23261
|
);
|
|
22432
23262
|
}
|
|
22433
23263
|
const viewer = createViewerSessionRecord(this.db, {
|
|
@@ -22435,31 +23265,52 @@ var ShellSessionService = class {
|
|
|
22435
23265
|
shellId: shell.id,
|
|
22436
23266
|
activeTab: "shell"
|
|
22437
23267
|
});
|
|
22438
|
-
await this.
|
|
22439
|
-
|
|
22440
|
-
options.
|
|
22441
|
-
|
|
22442
|
-
|
|
22443
|
-
|
|
22444
|
-
|
|
22445
|
-
|
|
22446
|
-
|
|
23268
|
+
const attached = await this.shellBackend.attach(shellSessionName(shell), {
|
|
23269
|
+
cols: options.cols,
|
|
23270
|
+
rows: options.rows,
|
|
23271
|
+
onData: (data, session, backendOptions) => {
|
|
23272
|
+
updateShellSessionRecord(this.db, shell.id, {
|
|
23273
|
+
lastActivityAt: nowIso()
|
|
23274
|
+
});
|
|
23275
|
+
updateViewerSessionRecord(this.db, viewer.id, {
|
|
23276
|
+
lastHeartbeatAt: nowIso(),
|
|
23277
|
+
activeTab: "shell"
|
|
23278
|
+
});
|
|
23279
|
+
options.onData(
|
|
23280
|
+
data,
|
|
23281
|
+
shellOutputOptions({
|
|
23282
|
+
replace: backendOptions?.replace === true,
|
|
23283
|
+
cursorX: session.runtime.cursorX,
|
|
23284
|
+
cursorY: session.runtime.cursorY,
|
|
23285
|
+
paneHeight: session.runtime.paneHeight,
|
|
23286
|
+
cwdBaseName: basenameFromPath2(session.runtime.currentPath || shell.cwd),
|
|
23287
|
+
envPrefix: session.runtime.envPrefix ?? void 0,
|
|
23288
|
+
isCommandRunning: session.runtime.isCommandRunning
|
|
23289
|
+
})
|
|
23290
|
+
);
|
|
23291
|
+
},
|
|
23292
|
+
onExit: () => {
|
|
23293
|
+
void this.handleMissingShell(shell, viewer.id);
|
|
23294
|
+
}
|
|
23295
|
+
});
|
|
23296
|
+
const initialSnapshot = attached.session.snapshot;
|
|
23297
|
+
const initialRuntime = attached.session.runtime;
|
|
22447
23298
|
const initialCwdBaseName = basenameFromPath2(initialRuntime.currentPath || shell.cwd);
|
|
22448
|
-
const initialEnvPrefix = await resolvePaneEnvironmentPrefix(
|
|
22449
|
-
this.tmuxManager,
|
|
22450
|
-
shellSessionName(shell),
|
|
22451
|
-
initialRuntime.panePid
|
|
22452
|
-
);
|
|
22453
23299
|
const attachment = {
|
|
22454
23300
|
viewerId: viewer.id,
|
|
22455
23301
|
onData: options.onData,
|
|
22456
|
-
|
|
22457
|
-
void this.pollAttachment(shell.id);
|
|
22458
|
-
}, 250),
|
|
22459
|
-
lastSnapshot: initialSnapshot,
|
|
22460
|
-
polling: false
|
|
23302
|
+
backendAttachment: attached.attachment
|
|
22461
23303
|
};
|
|
22462
23304
|
this.attachments.set(shell.id, attachment);
|
|
23305
|
+
updateShellSessionRecord(this.db, shell.id, {
|
|
23306
|
+
status: "running",
|
|
23307
|
+
lastActivityAt: nowIso()
|
|
23308
|
+
});
|
|
23309
|
+
const shellDto = await this.toShellSessionDto(shell.id);
|
|
23310
|
+
options.onConnected?.({
|
|
23311
|
+
viewerId: viewer.id,
|
|
23312
|
+
shell: shellDto
|
|
23313
|
+
});
|
|
22463
23314
|
if (initialSnapshot) {
|
|
22464
23315
|
options.onData(
|
|
22465
23316
|
initialSnapshot,
|
|
@@ -22469,17 +23320,11 @@ var ShellSessionService = class {
|
|
|
22469
23320
|
cursorY: initialRuntime.cursorY,
|
|
22470
23321
|
paneHeight: initialRuntime.paneHeight,
|
|
22471
23322
|
cwdBaseName: initialCwdBaseName,
|
|
22472
|
-
envPrefix:
|
|
22473
|
-
isCommandRunning:
|
|
22474
|
-
initialRuntime.currentCommand
|
|
22475
|
-
)
|
|
23323
|
+
envPrefix: initialRuntime.envPrefix ?? void 0,
|
|
23324
|
+
isCommandRunning: initialRuntime.isCommandRunning
|
|
22476
23325
|
})
|
|
22477
23326
|
);
|
|
22478
23327
|
}
|
|
22479
|
-
updateShellSessionRecord(this.db, shell.id, {
|
|
22480
|
-
status: "running",
|
|
22481
|
-
lastActivityAt: nowIso()
|
|
22482
|
-
});
|
|
22483
23328
|
this.emitShellEvent(shell.id, "shell.status", {
|
|
22484
23329
|
threadId,
|
|
22485
23330
|
state: "attached",
|
|
@@ -22487,7 +23332,7 @@ var ShellSessionService = class {
|
|
|
22487
23332
|
});
|
|
22488
23333
|
return {
|
|
22489
23334
|
viewerId: viewer.id,
|
|
22490
|
-
shell:
|
|
23335
|
+
shell: shellDto
|
|
22491
23336
|
};
|
|
22492
23337
|
}
|
|
22493
23338
|
async detachShell(shellId, viewerId) {
|
|
@@ -22508,7 +23353,7 @@ var ShellSessionService = class {
|
|
|
22508
23353
|
"This browser session does not own the shell attachment."
|
|
22509
23354
|
);
|
|
22510
23355
|
}
|
|
22511
|
-
|
|
23356
|
+
attachment.backendAttachment.dispose();
|
|
22512
23357
|
deleteViewerSessionRecord(this.db, viewerId);
|
|
22513
23358
|
this.attachments.delete(shell.id);
|
|
22514
23359
|
updateShellSessionRecord(this.db, shell.id, {
|
|
@@ -22523,7 +23368,7 @@ var ShellSessionService = class {
|
|
|
22523
23368
|
}
|
|
22524
23369
|
async sendInput(shellId, viewerId, data) {
|
|
22525
23370
|
const { shell } = this.requireOwnedAttachment(shellId, viewerId);
|
|
22526
|
-
await this.
|
|
23371
|
+
await this.shellBackend.sendInput(shellSessionName(shell), data);
|
|
22527
23372
|
updateShellSessionRecord(this.db, shellId, {
|
|
22528
23373
|
lastActivityAt: nowIso()
|
|
22529
23374
|
});
|
|
@@ -22535,10 +23380,7 @@ var ShellSessionService = class {
|
|
|
22535
23380
|
async clearShell(shellId, viewerId) {
|
|
22536
23381
|
const { shell, attachment } = this.requireOwnedAttachment(shellId, viewerId);
|
|
22537
23382
|
const sessionName = shellSessionName(shell);
|
|
22538
|
-
await this.
|
|
22539
|
-
await waitForShellTick(60);
|
|
22540
|
-
await this.tmuxManager.clearHistory(sessionName);
|
|
22541
|
-
await waitForShellTick(60);
|
|
23383
|
+
const session = await this.shellBackend.clear(sessionName);
|
|
22542
23384
|
updateShellSessionRecord(this.db, shellId, {
|
|
22543
23385
|
lastActivityAt: nowIso()
|
|
22544
23386
|
});
|
|
@@ -22546,7 +23388,18 @@ var ShellSessionService = class {
|
|
|
22546
23388
|
lastHeartbeatAt: nowIso(),
|
|
22547
23389
|
activeTab: "shell"
|
|
22548
23390
|
});
|
|
22549
|
-
|
|
23391
|
+
attachment.onData(
|
|
23392
|
+
session.snapshot,
|
|
23393
|
+
shellOutputOptions({
|
|
23394
|
+
replace: true,
|
|
23395
|
+
cursorX: session.runtime.cursorX,
|
|
23396
|
+
cursorY: session.runtime.cursorY,
|
|
23397
|
+
paneHeight: session.runtime.paneHeight,
|
|
23398
|
+
cwdBaseName: basenameFromPath2(session.runtime.currentPath || shell.cwd),
|
|
23399
|
+
envPrefix: session.runtime.envPrefix ?? void 0,
|
|
23400
|
+
isCommandRunning: session.runtime.isCommandRunning
|
|
23401
|
+
})
|
|
23402
|
+
);
|
|
22550
23403
|
}
|
|
22551
23404
|
async resizeShell(shellId, viewerId, cols, rows) {
|
|
22552
23405
|
const shell = getShellSessionRecordById(this.db, shellId);
|
|
@@ -22566,7 +23419,7 @@ var ShellSessionService = class {
|
|
|
22566
23419
|
"This browser session does not own the shell attachment."
|
|
22567
23420
|
);
|
|
22568
23421
|
}
|
|
22569
|
-
await this.
|
|
23422
|
+
await this.shellBackend.resize(shellSessionName(shell), cols, rows);
|
|
22570
23423
|
updateViewerSessionRecord(this.db, viewerId, {
|
|
22571
23424
|
lastHeartbeatAt: nowIso(),
|
|
22572
23425
|
activeTab: "shell"
|
|
@@ -22579,11 +23432,11 @@ var ShellSessionService = class {
|
|
|
22579
23432
|
}
|
|
22580
23433
|
const attachment = this.attachments.get(shell.id);
|
|
22581
23434
|
if (attachment) {
|
|
22582
|
-
|
|
23435
|
+
attachment.backendAttachment.dispose();
|
|
22583
23436
|
deleteViewerSessionRecord(this.db, attachment.viewerId);
|
|
22584
23437
|
this.attachments.delete(shell.id);
|
|
22585
23438
|
}
|
|
22586
|
-
await this.
|
|
23439
|
+
await this.shellBackend.killSession(shellSessionName(shell));
|
|
22587
23440
|
updateShellSessionRecord(this.db, shell.id, {
|
|
22588
23441
|
status: "exited",
|
|
22589
23442
|
lastActivityAt: nowIso()
|
|
@@ -22605,7 +23458,9 @@ var ShellSessionService = class {
|
|
|
22605
23458
|
id: shell.id,
|
|
22606
23459
|
threadId: shellThreadId(shell),
|
|
22607
23460
|
workspaceId: shell.workspaceId,
|
|
23461
|
+
label: shell.label ?? null,
|
|
22608
23462
|
tmuxSessionName: shellSessionName(shell),
|
|
23463
|
+
backend: this.shellBackend.kind,
|
|
22609
23464
|
cwd: shell.cwd,
|
|
22610
23465
|
status: shellDtoStatus(shell.status, status),
|
|
22611
23466
|
attachedViewerId: this.attachments.get(shell.id)?.viewerId ?? null,
|
|
@@ -22631,31 +23486,7 @@ var ShellSessionService = class {
|
|
|
22631
23486
|
const attachment = this.attachments.get(shell.id);
|
|
22632
23487
|
return attachment ? "attached" : "detached";
|
|
22633
23488
|
}
|
|
22634
|
-
|
|
22635
|
-
const attachment = this.attachments.get(shellId);
|
|
22636
|
-
if (!attachment || attachment.polling) {
|
|
22637
|
-
return;
|
|
22638
|
-
}
|
|
22639
|
-
attachment.polling = true;
|
|
22640
|
-
const shell = getShellSessionRecordById(this.db, shellId);
|
|
22641
|
-
if (!shell) {
|
|
22642
|
-
clearInterval(attachment.pollHandle);
|
|
22643
|
-
this.attachments.delete(shellId);
|
|
22644
|
-
deleteViewerSessionRecord(this.db, attachment.viewerId);
|
|
22645
|
-
return;
|
|
22646
|
-
}
|
|
22647
|
-
try {
|
|
22648
|
-
const hasSession = await this.tmuxManager.hasSession(shellSessionName(shell));
|
|
22649
|
-
if (!hasSession) {
|
|
22650
|
-
await this.handleMissingShell(shell, attachment.viewerId);
|
|
22651
|
-
return;
|
|
22652
|
-
}
|
|
22653
|
-
await this.pushSnapshot(shell, attachment);
|
|
22654
|
-
} finally {
|
|
22655
|
-
attachment.polling = false;
|
|
22656
|
-
}
|
|
22657
|
-
}
|
|
22658
|
-
requireOwnedAttachment(shellId, viewerId) {
|
|
23489
|
+
requireOwnedAttachment(shellId, viewerId) {
|
|
22659
23490
|
const shell = getShellSessionRecordById(this.db, shellId);
|
|
22660
23491
|
if (!shell) {
|
|
22661
23492
|
throw new ShellServiceError("shell_not_found", "Shell not found.");
|
|
@@ -22675,42 +23506,10 @@ var ShellSessionService = class {
|
|
|
22675
23506
|
}
|
|
22676
23507
|
return { shell, attachment };
|
|
22677
23508
|
}
|
|
22678
|
-
async pushSnapshot(shell, attachment) {
|
|
22679
|
-
const sessionName = shellSessionName(shell);
|
|
22680
|
-
const snapshot = await this.tmuxManager.capturePane(sessionName);
|
|
22681
|
-
const runtime = await this.tmuxManager.getPaneRuntimeInfo(sessionName);
|
|
22682
|
-
if (snapshot === attachment.lastSnapshot) {
|
|
22683
|
-
return;
|
|
22684
|
-
}
|
|
22685
|
-
attachment.lastSnapshot = snapshot;
|
|
22686
|
-
updateShellSessionRecord(this.db, shell.id, {
|
|
22687
|
-
lastActivityAt: nowIso()
|
|
22688
|
-
});
|
|
22689
|
-
updateViewerSessionRecord(this.db, attachment.viewerId, {
|
|
22690
|
-
lastHeartbeatAt: nowIso(),
|
|
22691
|
-
activeTab: "shell"
|
|
22692
|
-
});
|
|
22693
|
-
attachment.onData(
|
|
22694
|
-
snapshot,
|
|
22695
|
-
shellOutputOptions({
|
|
22696
|
-
replace: true,
|
|
22697
|
-
cursorX: runtime.cursorX,
|
|
22698
|
-
cursorY: runtime.cursorY,
|
|
22699
|
-
paneHeight: runtime.paneHeight,
|
|
22700
|
-
cwdBaseName: basenameFromPath2(runtime.currentPath || shell.cwd),
|
|
22701
|
-
envPrefix: await resolvePaneEnvironmentPrefix(
|
|
22702
|
-
this.tmuxManager,
|
|
22703
|
-
sessionName,
|
|
22704
|
-
runtime.panePid
|
|
22705
|
-
) ?? void 0,
|
|
22706
|
-
isCommandRunning: !isInteractiveShellCommand(runtime.currentCommand)
|
|
22707
|
-
})
|
|
22708
|
-
);
|
|
22709
|
-
}
|
|
22710
23509
|
async handleMissingShell(shell, viewerId) {
|
|
22711
23510
|
const attachment = this.attachments.get(shell.id);
|
|
22712
23511
|
if (attachment) {
|
|
22713
|
-
|
|
23512
|
+
attachment.backendAttachment.dispose();
|
|
22714
23513
|
this.attachments.delete(shell.id);
|
|
22715
23514
|
}
|
|
22716
23515
|
deleteViewerSessionRecord(this.db, viewerId);
|
|
@@ -22733,9 +23532,695 @@ var ShellSessionService = class {
|
|
|
22733
23532
|
}
|
|
22734
23533
|
};
|
|
22735
23534
|
|
|
23535
|
+
// ../../packages/plugin-terminal/src/manifest.ts
|
|
23536
|
+
var TERMINAL_PLUGIN_ID = "remote-codex.terminal";
|
|
23537
|
+
var terminalPluginManifest = {
|
|
23538
|
+
id: TERMINAL_PLUGIN_ID,
|
|
23539
|
+
name: "Terminal",
|
|
23540
|
+
version: "0.1.0",
|
|
23541
|
+
description: "Built-in durable terminal panel backed by the supervisor PTY host.",
|
|
23542
|
+
remoteCodex: "^0.11.0",
|
|
23543
|
+
capabilities: {
|
|
23544
|
+
artifactTypes: [],
|
|
23545
|
+
timelineRenderers: [],
|
|
23546
|
+
threadPanels: [
|
|
23547
|
+
{
|
|
23548
|
+
id: "terminal",
|
|
23549
|
+
label: "Terminal",
|
|
23550
|
+
kind: "terminal",
|
|
23551
|
+
artifactTypes: []
|
|
23552
|
+
}
|
|
23553
|
+
],
|
|
23554
|
+
frontend: {
|
|
23555
|
+
entry: "./dist/index.js"
|
|
23556
|
+
},
|
|
23557
|
+
backend: {
|
|
23558
|
+
entry: "./dist/backend.js"
|
|
23559
|
+
}
|
|
23560
|
+
}
|
|
23561
|
+
};
|
|
23562
|
+
|
|
23563
|
+
// ../../packages/plugin-xyz-viewer/src/manifest.ts
|
|
23564
|
+
var XYZ_MOLECULE_ARTIFACT_TYPE = "chemistry.molecule3d";
|
|
23565
|
+
var xyzViewerPluginManifest = {
|
|
23566
|
+
id: "remote-codex.xyz-viewer",
|
|
23567
|
+
name: "XYZ Molecule Viewer",
|
|
23568
|
+
version: "0.1.0",
|
|
23569
|
+
description: "A draft built-in plugin for previewing xyz, extxyz, cif, and pdb molecular structures with 3Dmol.js.",
|
|
23570
|
+
remoteCodex: "^0.11.0",
|
|
23571
|
+
capabilities: {
|
|
23572
|
+
artifactTypes: [
|
|
23573
|
+
{
|
|
23574
|
+
type: XYZ_MOLECULE_ARTIFACT_TYPE,
|
|
23575
|
+
title: "3D Molecule",
|
|
23576
|
+
fileExtensions: ["xyz", "extxyz", "cif", "pdb"]
|
|
23577
|
+
}
|
|
23578
|
+
],
|
|
23579
|
+
timelineRenderers: [XYZ_MOLECULE_ARTIFACT_TYPE],
|
|
23580
|
+
threadPanels: [
|
|
23581
|
+
{
|
|
23582
|
+
id: "xyz-viewer",
|
|
23583
|
+
label: "Molecules",
|
|
23584
|
+
artifactTypes: [XYZ_MOLECULE_ARTIFACT_TYPE]
|
|
23585
|
+
}
|
|
23586
|
+
],
|
|
23587
|
+
modelHints: [
|
|
23588
|
+
{
|
|
23589
|
+
id: "render-molecule",
|
|
23590
|
+
text: "XYZ Molecule Viewer is enabled. When outputting a molecular structure, you must call remote_codex_render_molecule; do not output plain xyz, pdb, cif, or extxyz text. Do not invent coordinates unless asked for an example."
|
|
23591
|
+
}
|
|
23592
|
+
],
|
|
23593
|
+
mcpServers: [
|
|
23594
|
+
{
|
|
23595
|
+
id: "remote-codex-plugin-mcp",
|
|
23596
|
+
name: "remote_codex_plugins",
|
|
23597
|
+
command: "node",
|
|
23598
|
+
args: ["bin/remote-codex-plugin-mcp.mjs"]
|
|
23599
|
+
}
|
|
23600
|
+
],
|
|
23601
|
+
frontend: {
|
|
23602
|
+
entry: "./dist/index.js",
|
|
23603
|
+
style: "./src/styles.css"
|
|
23604
|
+
}
|
|
23605
|
+
}
|
|
23606
|
+
};
|
|
23607
|
+
|
|
23608
|
+
// src/plugins/builtin-plugins.ts
|
|
23609
|
+
var builtinPlugins = [
|
|
23610
|
+
{
|
|
23611
|
+
manifest: terminalPluginManifest,
|
|
23612
|
+
enabledByDefault: true
|
|
23613
|
+
},
|
|
23614
|
+
{
|
|
23615
|
+
manifest: xyzViewerPluginManifest,
|
|
23616
|
+
enabledByDefault: true
|
|
23617
|
+
}
|
|
23618
|
+
];
|
|
23619
|
+
|
|
23620
|
+
// src/plugins/plugin-service.ts
|
|
23621
|
+
import fs21 from "fs/promises";
|
|
23622
|
+
import path20 from "path";
|
|
23623
|
+
var MANAGED_CODEX_MCP_BEGIN = "# BEGIN remote-codex managed plugin MCP servers";
|
|
23624
|
+
var MANAGED_CODEX_MCP_END = "# END remote-codex managed plugin MCP servers";
|
|
23625
|
+
var REMOTE_CODEX_MOLECULE_MCP_TOOL_NAME = "remote_codex_render_molecule";
|
|
23626
|
+
function jsonString(value) {
|
|
23627
|
+
return JSON.stringify(value);
|
|
23628
|
+
}
|
|
23629
|
+
function normalizeManagedCommand(server, repoRoot) {
|
|
23630
|
+
if (server.name === "remote_codex_plugins") {
|
|
23631
|
+
return {
|
|
23632
|
+
command: process.execPath,
|
|
23633
|
+
args: [path20.join(repoRoot, "bin", "remote-codex-plugin-mcp.mjs")]
|
|
23634
|
+
};
|
|
23635
|
+
}
|
|
23636
|
+
return {
|
|
23637
|
+
command: server.command,
|
|
23638
|
+
args: server.args ?? []
|
|
23639
|
+
};
|
|
23640
|
+
}
|
|
23641
|
+
function stripManagedCodexMcpBlock(content) {
|
|
23642
|
+
const pattern = new RegExp(
|
|
23643
|
+
`\\n?${MANAGED_CODEX_MCP_BEGIN.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${MANAGED_CODEX_MCP_END.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`,
|
|
23644
|
+
"g"
|
|
23645
|
+
);
|
|
23646
|
+
return content.replace(pattern, "\n").replace(/\n{3,}/g, "\n\n").trimEnd();
|
|
23647
|
+
}
|
|
23648
|
+
function stripCodexMcpServerTables(content, serverNames) {
|
|
23649
|
+
const names = new Set(serverNames);
|
|
23650
|
+
if (names.size === 0) {
|
|
23651
|
+
return content.trimEnd();
|
|
23652
|
+
}
|
|
23653
|
+
const output = [];
|
|
23654
|
+
let current = [];
|
|
23655
|
+
let shouldDropCurrentTable = false;
|
|
23656
|
+
function flushCurrent() {
|
|
23657
|
+
if (!shouldDropCurrentTable) {
|
|
23658
|
+
output.push(...current);
|
|
23659
|
+
}
|
|
23660
|
+
current = [];
|
|
23661
|
+
shouldDropCurrentTable = false;
|
|
23662
|
+
}
|
|
23663
|
+
for (const line of content.split("\n")) {
|
|
23664
|
+
const tableMatch = line.match(/^\s*\[([^\]]+)\]\s*$/);
|
|
23665
|
+
if (tableMatch) {
|
|
23666
|
+
flushCurrent();
|
|
23667
|
+
const tablePath = tableMatch[1] ?? "";
|
|
23668
|
+
shouldDropCurrentTable = [...names].some(
|
|
23669
|
+
(name) => tablePath === `mcp_servers.${name}` || tablePath.startsWith(`mcp_servers.${name}.`)
|
|
23670
|
+
);
|
|
23671
|
+
}
|
|
23672
|
+
current.push(line);
|
|
23673
|
+
}
|
|
23674
|
+
flushCurrent();
|
|
23675
|
+
return output.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd();
|
|
23676
|
+
}
|
|
23677
|
+
function buildManagedCodexMcpBlock(servers, repoRoot) {
|
|
23678
|
+
if (servers.length === 0) {
|
|
23679
|
+
return "";
|
|
23680
|
+
}
|
|
23681
|
+
const lines = [
|
|
23682
|
+
MANAGED_CODEX_MCP_BEGIN,
|
|
23683
|
+
"# This block is generated from enabled Remote Codex plugins."
|
|
23684
|
+
];
|
|
23685
|
+
for (const server of servers) {
|
|
23686
|
+
const normalized = normalizeManagedCommand(server, repoRoot);
|
|
23687
|
+
lines.push(
|
|
23688
|
+
"",
|
|
23689
|
+
`[mcp_servers.${server.name}]`,
|
|
23690
|
+
`command = ${jsonString(normalized.command)}`,
|
|
23691
|
+
`args = ${JSON.stringify(normalized.args)}`
|
|
23692
|
+
);
|
|
23693
|
+
const envEntries = Object.entries(server.env ?? {});
|
|
23694
|
+
if (envEntries.length > 0) {
|
|
23695
|
+
lines.push(`[mcp_servers.${server.name}.env]`);
|
|
23696
|
+
for (const [key, value] of envEntries) {
|
|
23697
|
+
lines.push(`${key} = ${jsonString(value)}`);
|
|
23698
|
+
}
|
|
23699
|
+
}
|
|
23700
|
+
}
|
|
23701
|
+
lines.push(MANAGED_CODEX_MCP_END);
|
|
23702
|
+
return lines.join("\n");
|
|
23703
|
+
}
|
|
23704
|
+
function upsertManagedCodexMcpBlock(content, servers, repoRoot, managedServerNames = servers.map((server) => server.name)) {
|
|
23705
|
+
const stripped = stripCodexMcpServerTables(
|
|
23706
|
+
stripManagedCodexMcpBlock(content),
|
|
23707
|
+
managedServerNames
|
|
23708
|
+
);
|
|
23709
|
+
const managedBlock = buildManagedCodexMcpBlock(servers, repoRoot);
|
|
23710
|
+
if (!managedBlock) {
|
|
23711
|
+
return stripped ? `${stripped}
|
|
23712
|
+
` : "";
|
|
23713
|
+
}
|
|
23714
|
+
return `${stripped ? `${stripped}
|
|
23715
|
+
|
|
23716
|
+
` : ""}${managedBlock}
|
|
23717
|
+
`;
|
|
23718
|
+
}
|
|
23719
|
+
var PluginService = class {
|
|
23720
|
+
constructor(registry, settingsStore) {
|
|
23721
|
+
this.registry = registry;
|
|
23722
|
+
this.settingsStore = settingsStore;
|
|
23723
|
+
this.loadPersistedSettings();
|
|
23724
|
+
}
|
|
23725
|
+
registry;
|
|
23726
|
+
settingsStore;
|
|
23727
|
+
settings = {
|
|
23728
|
+
enabled: {},
|
|
23729
|
+
imported: []
|
|
23730
|
+
};
|
|
23731
|
+
listPlugins() {
|
|
23732
|
+
return this.registry.list();
|
|
23733
|
+
}
|
|
23734
|
+
getPlugin(pluginId) {
|
|
23735
|
+
return this.registry.get(pluginId);
|
|
23736
|
+
}
|
|
23737
|
+
setPluginEnabled(pluginId, enabled) {
|
|
23738
|
+
const plugin = this.registry.setEnabled(pluginId, enabled);
|
|
23739
|
+
this.settings.enabled[pluginId] = enabled;
|
|
23740
|
+
this.persistSettings();
|
|
23741
|
+
return plugin;
|
|
23742
|
+
}
|
|
23743
|
+
modelContextPrompt() {
|
|
23744
|
+
const hints = this.registry.enabledManifests().flatMap(
|
|
23745
|
+
(manifest) => manifest.capabilities.modelHints ?? []
|
|
23746
|
+
);
|
|
23747
|
+
const text2 = hints.map((hint) => hint.text.trim()).filter(Boolean).join("\n");
|
|
23748
|
+
return text2 || null;
|
|
23749
|
+
}
|
|
23750
|
+
enabledMcpServers() {
|
|
23751
|
+
const byName = /* @__PURE__ */ new Map();
|
|
23752
|
+
for (const manifest of this.registry.enabledManifests()) {
|
|
23753
|
+
for (const server of manifest.capabilities.mcpServers ?? []) {
|
|
23754
|
+
const existing = byName.get(server.name);
|
|
23755
|
+
if (existing) {
|
|
23756
|
+
byName.set(server.name, {
|
|
23757
|
+
...existing,
|
|
23758
|
+
env: {
|
|
23759
|
+
...existing.env ?? {},
|
|
23760
|
+
...server.env ?? {}
|
|
23761
|
+
},
|
|
23762
|
+
pluginIds: [...existing.pluginIds, manifest.id]
|
|
23763
|
+
});
|
|
23764
|
+
} else {
|
|
23765
|
+
byName.set(server.name, {
|
|
23766
|
+
...server,
|
|
23767
|
+
pluginIds: [manifest.id]
|
|
23768
|
+
});
|
|
23769
|
+
}
|
|
23770
|
+
}
|
|
23771
|
+
}
|
|
23772
|
+
return [...byName.values()].map(({ pluginIds, ...server }) => ({
|
|
23773
|
+
...server,
|
|
23774
|
+
env: {
|
|
23775
|
+
...server.env ?? {},
|
|
23776
|
+
REMOTE_CODEX_ENABLED_PLUGIN_IDS: [...new Set(pluginIds)].sort().join(",")
|
|
23777
|
+
}
|
|
23778
|
+
}));
|
|
23779
|
+
}
|
|
23780
|
+
managedMcpServerNames() {
|
|
23781
|
+
return [
|
|
23782
|
+
...new Set(
|
|
23783
|
+
this.registry.list().flatMap(
|
|
23784
|
+
(plugin) => (plugin.capabilities.mcpServers ?? []).map((server) => server.name)
|
|
23785
|
+
)
|
|
23786
|
+
)
|
|
23787
|
+
].sort();
|
|
23788
|
+
}
|
|
23789
|
+
async syncManagedCodexMcpConfig(input) {
|
|
23790
|
+
if (!input.codexHome) {
|
|
23791
|
+
return;
|
|
23792
|
+
}
|
|
23793
|
+
const configPath = path20.join(input.codexHome, "config.toml");
|
|
23794
|
+
let current = "";
|
|
23795
|
+
try {
|
|
23796
|
+
current = await fs21.readFile(configPath, "utf8");
|
|
23797
|
+
} catch (error) {
|
|
23798
|
+
if (error.code !== "ENOENT") {
|
|
23799
|
+
throw error;
|
|
23800
|
+
}
|
|
23801
|
+
}
|
|
23802
|
+
const next = upsertManagedCodexMcpBlock(
|
|
23803
|
+
current,
|
|
23804
|
+
this.enabledMcpServers(),
|
|
23805
|
+
input.repoRoot,
|
|
23806
|
+
this.managedMcpServerNames()
|
|
23807
|
+
);
|
|
23808
|
+
if (next === current) {
|
|
23809
|
+
return;
|
|
23810
|
+
}
|
|
23811
|
+
await fs21.mkdir(path20.dirname(configPath), { recursive: true });
|
|
23812
|
+
await fs21.writeFile(configPath, next, "utf8");
|
|
23813
|
+
}
|
|
23814
|
+
importPlugin(input) {
|
|
23815
|
+
const manifestInput = input.manifest ?? this.parseManifestJson(input.manifestJson);
|
|
23816
|
+
const manifest = parsePluginManifest(manifestInput);
|
|
23817
|
+
const enabled = input.enabled ?? true;
|
|
23818
|
+
const existing = this.registry.getRegistered(manifest.id);
|
|
23819
|
+
if (existing && existing.source !== "imported") {
|
|
23820
|
+
throw new Error(`Built-in plugin cannot be replaced: ${manifest.id}`);
|
|
23821
|
+
}
|
|
23822
|
+
this.registerImportedManifest(manifest, enabled);
|
|
23823
|
+
const existingIndex = this.settings.imported.findIndex(
|
|
23824
|
+
(entry) => entry.id === manifest.id
|
|
23825
|
+
);
|
|
23826
|
+
if (existingIndex >= 0) {
|
|
23827
|
+
this.settings.imported[existingIndex] = manifest;
|
|
23828
|
+
} else {
|
|
23829
|
+
this.settings.imported.push(manifest);
|
|
23830
|
+
}
|
|
23831
|
+
this.settings.enabled[manifest.id] = enabled;
|
|
23832
|
+
this.persistSettings();
|
|
23833
|
+
const plugin = this.registry.get(manifest.id);
|
|
23834
|
+
if (!plugin) {
|
|
23835
|
+
throw new Error(`Plugin import failed: ${manifest.id}`);
|
|
23836
|
+
}
|
|
23837
|
+
return plugin;
|
|
23838
|
+
}
|
|
23839
|
+
enrichTurnsWithArtifacts(input) {
|
|
23840
|
+
const manifests = this.registry.enabledManifests();
|
|
23841
|
+
if (manifests.length === 0) {
|
|
23842
|
+
return input.turns;
|
|
23843
|
+
}
|
|
23844
|
+
const turnsForExtraction = input.deferredDetails ? materializeDeferredDetailsForArtifactExtraction(
|
|
23845
|
+
input.turns,
|
|
23846
|
+
input.deferredDetails
|
|
23847
|
+
) : input.turns;
|
|
23848
|
+
const enrichedTurns = appendArtifactItemsToTurns(
|
|
23849
|
+
turnsForExtraction,
|
|
23850
|
+
new ManifestArtifactExtractor(manifests),
|
|
23851
|
+
{
|
|
23852
|
+
threadId: input.threadId,
|
|
23853
|
+
workspacePath: input.workspacePath,
|
|
23854
|
+
now: (/* @__PURE__ */ new Date()).toISOString()
|
|
23855
|
+
}
|
|
23856
|
+
);
|
|
23857
|
+
return turnsForExtraction === input.turns ? enrichedTurns : restoreOriginalNonArtifactItems(enrichedTurns, input.turns);
|
|
23858
|
+
}
|
|
23859
|
+
loadPersistedSettings() {
|
|
23860
|
+
if (!this.settingsStore) {
|
|
23861
|
+
return;
|
|
23862
|
+
}
|
|
23863
|
+
this.settings = this.settingsStore.load();
|
|
23864
|
+
for (const manifest of this.settings.imported) {
|
|
23865
|
+
this.registerImportedManifest(
|
|
23866
|
+
manifest,
|
|
23867
|
+
this.settings.enabled[manifest.id] ?? true
|
|
23868
|
+
);
|
|
23869
|
+
}
|
|
23870
|
+
for (const [pluginId, enabled] of Object.entries(this.settings.enabled)) {
|
|
23871
|
+
if (this.registry.get(pluginId)) {
|
|
23872
|
+
this.registry.setEnabled(pluginId, enabled);
|
|
23873
|
+
}
|
|
23874
|
+
}
|
|
23875
|
+
}
|
|
23876
|
+
registerImportedManifest(manifest, enabled) {
|
|
23877
|
+
if (this.registry.get(manifest.id)) {
|
|
23878
|
+
const existing = this.registry.getRegistered(manifest.id);
|
|
23879
|
+
if (existing?.source === "imported") {
|
|
23880
|
+
this.registry.updateImported({
|
|
23881
|
+
manifest,
|
|
23882
|
+
enabledByDefault: enabled,
|
|
23883
|
+
source: "imported"
|
|
23884
|
+
});
|
|
23885
|
+
} else {
|
|
23886
|
+
this.registry.setEnabled(manifest.id, enabled);
|
|
23887
|
+
}
|
|
23888
|
+
return;
|
|
23889
|
+
}
|
|
23890
|
+
this.registry.register({
|
|
23891
|
+
manifest,
|
|
23892
|
+
enabledByDefault: enabled,
|
|
23893
|
+
source: "imported"
|
|
23894
|
+
});
|
|
23895
|
+
}
|
|
23896
|
+
persistSettings() {
|
|
23897
|
+
this.settingsStore?.save(this.settings);
|
|
23898
|
+
}
|
|
23899
|
+
parseManifestJson(manifestJson) {
|
|
23900
|
+
if (!manifestJson?.trim()) {
|
|
23901
|
+
throw new Error("Plugin import requires a manifest object or manifestJson string.");
|
|
23902
|
+
}
|
|
23903
|
+
return JSON.parse(manifestJson);
|
|
23904
|
+
}
|
|
23905
|
+
};
|
|
23906
|
+
function restoreOriginalNonArtifactItems(enrichedTurns, originalTurns) {
|
|
23907
|
+
const originalItemsByTurnId = new Map(
|
|
23908
|
+
originalTurns.map((turn) => [
|
|
23909
|
+
turn.id,
|
|
23910
|
+
new Map(turn.items.map((item) => [item.id, item]))
|
|
23911
|
+
])
|
|
23912
|
+
);
|
|
23913
|
+
return enrichedTurns.map((turn) => {
|
|
23914
|
+
const originalItems = originalItemsByTurnId.get(turn.id);
|
|
23915
|
+
if (!originalItems) {
|
|
23916
|
+
return turn;
|
|
23917
|
+
}
|
|
23918
|
+
return {
|
|
23919
|
+
...turn,
|
|
23920
|
+
items: turn.items.map(
|
|
23921
|
+
(item) => item.kind === "artifact" ? item : originalItems.get(item.id) ?? item
|
|
23922
|
+
)
|
|
23923
|
+
};
|
|
23924
|
+
});
|
|
23925
|
+
}
|
|
23926
|
+
function materializeDeferredDetailsForArtifactExtraction(turns, deferredDetails) {
|
|
23927
|
+
if (deferredDetails.size === 0) {
|
|
23928
|
+
return turns;
|
|
23929
|
+
}
|
|
23930
|
+
return turns.map((turn) => {
|
|
23931
|
+
let changed = false;
|
|
23932
|
+
const items = turn.items.map((item) => {
|
|
23933
|
+
if (!item.hasDeferredDetail || item.detailText || item.kind !== "toolCall" || ![item.text, item.previewText].some(
|
|
23934
|
+
(value) => typeof value === "string" && value.includes(REMOTE_CODEX_MOLECULE_MCP_TOOL_NAME)
|
|
23935
|
+
)) {
|
|
23936
|
+
return item;
|
|
23937
|
+
}
|
|
23938
|
+
const detail = deferredDetails.get(item.id);
|
|
23939
|
+
if (!detail?.text) {
|
|
23940
|
+
return item;
|
|
23941
|
+
}
|
|
23942
|
+
changed = true;
|
|
23943
|
+
return {
|
|
23944
|
+
...item,
|
|
23945
|
+
detailText: detail.text
|
|
23946
|
+
};
|
|
23947
|
+
});
|
|
23948
|
+
return changed ? {
|
|
23949
|
+
...turn,
|
|
23950
|
+
items
|
|
23951
|
+
} : turn;
|
|
23952
|
+
});
|
|
23953
|
+
}
|
|
23954
|
+
|
|
23955
|
+
// src/plugins/plugin-settings-store.ts
|
|
23956
|
+
var PLUGIN_SETTINGS_POLICY_KEY = "plugins";
|
|
23957
|
+
function emptySettings() {
|
|
23958
|
+
return {
|
|
23959
|
+
enabled: {},
|
|
23960
|
+
imported: []
|
|
23961
|
+
};
|
|
23962
|
+
}
|
|
23963
|
+
function parseEnabled(value) {
|
|
23964
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
23965
|
+
return {};
|
|
23966
|
+
}
|
|
23967
|
+
const output = {};
|
|
23968
|
+
for (const [key, enabled] of Object.entries(value)) {
|
|
23969
|
+
if (typeof enabled === "boolean") {
|
|
23970
|
+
output[key] = enabled;
|
|
23971
|
+
} else if (enabled && typeof enabled === "object" && !Array.isArray(enabled) && typeof enabled.enabled === "boolean") {
|
|
23972
|
+
output[key] = enabled.enabled;
|
|
23973
|
+
}
|
|
23974
|
+
}
|
|
23975
|
+
return output;
|
|
23976
|
+
}
|
|
23977
|
+
var PluginSettingsStore = class {
|
|
23978
|
+
constructor(db) {
|
|
23979
|
+
this.db = db;
|
|
23980
|
+
}
|
|
23981
|
+
db;
|
|
23982
|
+
load() {
|
|
23983
|
+
const record = getPolicyRecordByKey(this.db, PLUGIN_SETTINGS_POLICY_KEY);
|
|
23984
|
+
if (!record?.valueJson) {
|
|
23985
|
+
return emptySettings();
|
|
23986
|
+
}
|
|
23987
|
+
try {
|
|
23988
|
+
const parsed = JSON.parse(record.valueJson);
|
|
23989
|
+
return {
|
|
23990
|
+
enabled: parseEnabled(parsed.enabled),
|
|
23991
|
+
imported: Array.isArray(parsed.imported) ? parsed.imported.map((entry) => parsePluginManifest(entry)) : []
|
|
23992
|
+
};
|
|
23993
|
+
} catch {
|
|
23994
|
+
return emptySettings();
|
|
23995
|
+
}
|
|
23996
|
+
}
|
|
23997
|
+
save(settings) {
|
|
23998
|
+
upsertPolicyRecord(
|
|
23999
|
+
this.db,
|
|
24000
|
+
PLUGIN_SETTINGS_POLICY_KEY,
|
|
24001
|
+
JSON.stringify({
|
|
24002
|
+
enabled: settings.enabled,
|
|
24003
|
+
imported: settings.imported
|
|
24004
|
+
})
|
|
24005
|
+
);
|
|
24006
|
+
}
|
|
24007
|
+
};
|
|
24008
|
+
|
|
24009
|
+
// src/plugins/backend-plugin-host.ts
|
|
24010
|
+
var BackendPluginHost = class {
|
|
24011
|
+
constructor(app2) {
|
|
24012
|
+
this.app = app2;
|
|
24013
|
+
}
|
|
24014
|
+
app;
|
|
24015
|
+
socketHandlers = [];
|
|
24016
|
+
registerSocketHandler(handler) {
|
|
24017
|
+
this.socketHandlers.push(handler);
|
|
24018
|
+
}
|
|
24019
|
+
register(contribution) {
|
|
24020
|
+
contribution.registerHttp?.(this.app);
|
|
24021
|
+
contribution.registerSocket?.(this);
|
|
24022
|
+
}
|
|
24023
|
+
async handleSocketMessage(context) {
|
|
24024
|
+
for (const handler of this.socketHandlers) {
|
|
24025
|
+
if (await handler(context)) {
|
|
24026
|
+
return true;
|
|
24027
|
+
}
|
|
24028
|
+
}
|
|
24029
|
+
return false;
|
|
24030
|
+
}
|
|
24031
|
+
};
|
|
24032
|
+
|
|
24033
|
+
// src/shell/pty-shell-backend.ts
|
|
24034
|
+
import path21 from "path";
|
|
24035
|
+
import { spawn as spawn4 } from "@homebridge/node-pty-prebuilt-multiarch";
|
|
24036
|
+
|
|
24037
|
+
// src/shell/default-shell.ts
|
|
24038
|
+
import fs22 from "fs";
|
|
24039
|
+
var POSIX_SHELL_CANDIDATES = ["/bin/bash", "/usr/bin/bash", "/bin/sh"];
|
|
24040
|
+
function resolveDefaultShell(env = process.env) {
|
|
24041
|
+
if (process.platform === "win32") {
|
|
24042
|
+
return env.COMSPEC ?? "cmd.exe";
|
|
24043
|
+
}
|
|
24044
|
+
if (env.SHELL && fs22.existsSync(env.SHELL)) {
|
|
24045
|
+
return env.SHELL;
|
|
24046
|
+
}
|
|
24047
|
+
return POSIX_SHELL_CANDIDATES.find((candidate) => fs22.existsSync(candidate)) ?? "/bin/sh";
|
|
24048
|
+
}
|
|
24049
|
+
|
|
24050
|
+
// src/shell/pty-shell-backend.ts
|
|
24051
|
+
var MAX_SCROLLBACK_BYTES = 512 * 1024;
|
|
24052
|
+
var ANSI_ESCAPE_PATTERN = new RegExp(String.raw`\u001B\[[0-?]*[ -/]*[@-~]`, "g");
|
|
24053
|
+
function shellArgs(shell) {
|
|
24054
|
+
const shellName = path21.basename(shell).toLowerCase();
|
|
24055
|
+
if (process.platform === "win32") {
|
|
24056
|
+
return [];
|
|
24057
|
+
}
|
|
24058
|
+
if (shellName === "bash" || shellName === "zsh" || shellName === "sh") {
|
|
24059
|
+
return ["-l"];
|
|
24060
|
+
}
|
|
24061
|
+
return [];
|
|
24062
|
+
}
|
|
24063
|
+
function trimScrollback(value) {
|
|
24064
|
+
if (value.length <= MAX_SCROLLBACK_BYTES) {
|
|
24065
|
+
return value;
|
|
24066
|
+
}
|
|
24067
|
+
return value.slice(value.length - MAX_SCROLLBACK_BYTES);
|
|
24068
|
+
}
|
|
24069
|
+
function lastVisibleLine(snapshot) {
|
|
24070
|
+
const normalized = snapshot.replace(ANSI_ESCAPE_PATTERN, "");
|
|
24071
|
+
const lines = normalized.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
|
24072
|
+
return lines.findLast((line) => line.trim().length > 0) ?? "";
|
|
24073
|
+
}
|
|
24074
|
+
function inferRuntime(session) {
|
|
24075
|
+
const promptLine = lastVisibleLine(session.scrollback);
|
|
24076
|
+
const shell = path21.basename(session.shell);
|
|
24077
|
+
const isCommandRunning = session.exitCode !== null ? false : !/[$#>]\s*$/.test(promptLine.trimEnd());
|
|
24078
|
+
return {
|
|
24079
|
+
panePid: session.pty.pid,
|
|
24080
|
+
paneWidth: session.pty.cols,
|
|
24081
|
+
paneHeight: session.pty.rows,
|
|
24082
|
+
currentCommand: isCommandRunning ? session.pty.process : shell,
|
|
24083
|
+
currentPath: session.cwd,
|
|
24084
|
+
isCommandRunning
|
|
24085
|
+
};
|
|
24086
|
+
}
|
|
24087
|
+
var PtyShellBackend = class {
|
|
24088
|
+
kind = "pty";
|
|
24089
|
+
sessions = /* @__PURE__ */ new Map();
|
|
24090
|
+
shell = resolveDefaultShell();
|
|
24091
|
+
sessionNameForThread(threadId) {
|
|
24092
|
+
return `rcx-${threadId.replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 28)}`;
|
|
24093
|
+
}
|
|
24094
|
+
async listSessionNames() {
|
|
24095
|
+
return [...this.sessions.keys()];
|
|
24096
|
+
}
|
|
24097
|
+
async hasSession(sessionId) {
|
|
24098
|
+
return this.sessions.has(sessionId);
|
|
24099
|
+
}
|
|
24100
|
+
async createSession(input) {
|
|
24101
|
+
if (this.sessions.has(input.sessionId)) {
|
|
24102
|
+
return;
|
|
24103
|
+
}
|
|
24104
|
+
const pty = spawn4(this.shell, shellArgs(this.shell), {
|
|
24105
|
+
name: "xterm-256color",
|
|
24106
|
+
cwd: input.cwd,
|
|
24107
|
+
cols: input.cols ?? 120,
|
|
24108
|
+
rows: input.rows ?? 36,
|
|
24109
|
+
env: {
|
|
24110
|
+
...process.env,
|
|
24111
|
+
TERM: "xterm-256color",
|
|
24112
|
+
COLORTERM: process.env.COLORTERM ?? "truecolor"
|
|
24113
|
+
},
|
|
24114
|
+
handleFlowControl: true
|
|
24115
|
+
});
|
|
24116
|
+
const session = {
|
|
24117
|
+
id: input.sessionId,
|
|
24118
|
+
cwd: input.cwd,
|
|
24119
|
+
shell: this.shell,
|
|
24120
|
+
pty,
|
|
24121
|
+
scrollback: "",
|
|
24122
|
+
exitCode: null,
|
|
24123
|
+
listeners: /* @__PURE__ */ new Set(),
|
|
24124
|
+
exitListeners: /* @__PURE__ */ new Set(),
|
|
24125
|
+
dataSubscription: { dispose() {
|
|
24126
|
+
} },
|
|
24127
|
+
exitSubscription: { dispose() {
|
|
24128
|
+
} }
|
|
24129
|
+
};
|
|
24130
|
+
session.dataSubscription = pty.onData((data) => {
|
|
24131
|
+
session.scrollback = trimScrollback(session.scrollback + data);
|
|
24132
|
+
for (const listener of session.listeners) {
|
|
24133
|
+
listener(data);
|
|
24134
|
+
}
|
|
24135
|
+
});
|
|
24136
|
+
session.exitSubscription = pty.onExit(() => {
|
|
24137
|
+
session.exitCode = 0;
|
|
24138
|
+
this.sessions.delete(session.id);
|
|
24139
|
+
for (const listener of session.exitListeners) {
|
|
24140
|
+
listener();
|
|
24141
|
+
}
|
|
24142
|
+
session.dataSubscription.dispose();
|
|
24143
|
+
session.exitSubscription.dispose();
|
|
24144
|
+
});
|
|
24145
|
+
this.sessions.set(input.sessionId, session);
|
|
24146
|
+
}
|
|
24147
|
+
async attach(sessionId, options) {
|
|
24148
|
+
const session = this.requireSession(sessionId);
|
|
24149
|
+
this.resizeIfChanged(session, options.cols, options.rows);
|
|
24150
|
+
const onData = (data) => options.onData(data, this.toBackendSession(session));
|
|
24151
|
+
const onExit = () => options.onExit();
|
|
24152
|
+
session.listeners.add(onData);
|
|
24153
|
+
session.exitListeners.add(onExit);
|
|
24154
|
+
return {
|
|
24155
|
+
session: this.toBackendSession(session),
|
|
24156
|
+
attachment: {
|
|
24157
|
+
dispose: () => {
|
|
24158
|
+
session.listeners.delete(onData);
|
|
24159
|
+
session.exitListeners.delete(onExit);
|
|
24160
|
+
}
|
|
24161
|
+
}
|
|
24162
|
+
};
|
|
24163
|
+
}
|
|
24164
|
+
async sendInput(sessionId, data) {
|
|
24165
|
+
this.requireSession(sessionId).pty.write(data);
|
|
24166
|
+
}
|
|
24167
|
+
async clear(sessionId) {
|
|
24168
|
+
const session = this.requireSession(sessionId);
|
|
24169
|
+
session.scrollback = "";
|
|
24170
|
+
session.pty.clear();
|
|
24171
|
+
session.pty.write("\f");
|
|
24172
|
+
return this.toBackendSession(session);
|
|
24173
|
+
}
|
|
24174
|
+
async resize(sessionId, cols, rows) {
|
|
24175
|
+
this.resizeIfChanged(this.requireSession(sessionId), cols, rows);
|
|
24176
|
+
}
|
|
24177
|
+
async snapshot(sessionId) {
|
|
24178
|
+
return this.toBackendSession(this.requireSession(sessionId));
|
|
24179
|
+
}
|
|
24180
|
+
async killSession(sessionId) {
|
|
24181
|
+
const session = this.sessions.get(sessionId);
|
|
24182
|
+
if (!session) {
|
|
24183
|
+
return;
|
|
24184
|
+
}
|
|
24185
|
+
session.dataSubscription.dispose();
|
|
24186
|
+
session.exitSubscription.dispose();
|
|
24187
|
+
this.sessions.delete(sessionId);
|
|
24188
|
+
try {
|
|
24189
|
+
session.pty.kill();
|
|
24190
|
+
} catch {
|
|
24191
|
+
}
|
|
24192
|
+
}
|
|
24193
|
+
requireSession(sessionId) {
|
|
24194
|
+
const session = this.sessions.get(sessionId);
|
|
24195
|
+
if (!session) {
|
|
24196
|
+
throw new Error("Shell session is no longer available.");
|
|
24197
|
+
}
|
|
24198
|
+
return session;
|
|
24199
|
+
}
|
|
24200
|
+
resizeIfChanged(session, cols, rows) {
|
|
24201
|
+
if (cols <= 0 || rows <= 0) {
|
|
24202
|
+
return;
|
|
24203
|
+
}
|
|
24204
|
+
if (session.pty.cols === cols && session.pty.rows === rows) {
|
|
24205
|
+
return;
|
|
24206
|
+
}
|
|
24207
|
+
session.pty.resize(cols, rows);
|
|
24208
|
+
}
|
|
24209
|
+
toBackendSession(session) {
|
|
24210
|
+
return {
|
|
24211
|
+
id: session.id,
|
|
24212
|
+
cwd: session.cwd,
|
|
24213
|
+
cols: session.pty.cols,
|
|
24214
|
+
rows: session.pty.rows,
|
|
24215
|
+
snapshot: session.scrollback,
|
|
24216
|
+
runtime: inferRuntime(session)
|
|
24217
|
+
};
|
|
24218
|
+
}
|
|
24219
|
+
};
|
|
24220
|
+
|
|
22736
24221
|
// src/shell/tmux-manager.ts
|
|
22737
|
-
import
|
|
22738
|
-
import
|
|
24222
|
+
import fs23 from "fs";
|
|
24223
|
+
import path22 from "path";
|
|
22739
24224
|
import { spawn as spawnChild } from "child_process";
|
|
22740
24225
|
async function defaultExecCommand(command, args) {
|
|
22741
24226
|
return await new Promise((resolve, reject) => {
|
|
@@ -22762,17 +24247,17 @@ async function defaultExecCommand(command, args) {
|
|
|
22762
24247
|
});
|
|
22763
24248
|
}
|
|
22764
24249
|
function resolveExecutablePath(command) {
|
|
22765
|
-
if (command.includes(
|
|
24250
|
+
if (command.includes(path22.sep)) {
|
|
22766
24251
|
return command;
|
|
22767
24252
|
}
|
|
22768
24253
|
const searchPath = process.env.PATH ?? "";
|
|
22769
|
-
for (const entry of searchPath.split(
|
|
24254
|
+
for (const entry of searchPath.split(path22.delimiter)) {
|
|
22770
24255
|
const trimmed = entry.trim();
|
|
22771
24256
|
if (!trimmed) {
|
|
22772
24257
|
continue;
|
|
22773
24258
|
}
|
|
22774
|
-
const candidate =
|
|
22775
|
-
if (
|
|
24259
|
+
const candidate = path22.join(trimmed, command);
|
|
24260
|
+
if (fs23.existsSync(candidate)) {
|
|
22776
24261
|
return candidate;
|
|
22777
24262
|
}
|
|
22778
24263
|
}
|
|
@@ -22784,7 +24269,7 @@ var TmuxManager = class {
|
|
|
22784
24269
|
execCommand;
|
|
22785
24270
|
constructor(options = {}) {
|
|
22786
24271
|
this.command = resolveExecutablePath(options.command ?? "tmux");
|
|
22787
|
-
this.defaultShell = options.defaultShell ??
|
|
24272
|
+
this.defaultShell = options.defaultShell ?? resolveDefaultShell();
|
|
22788
24273
|
this.execCommand = options.execCommand ?? defaultExecCommand;
|
|
22789
24274
|
}
|
|
22790
24275
|
sessionNameForThread(threadId) {
|
|
@@ -23087,215 +24572,740 @@ function tokenizeTmuxInput(data) {
|
|
|
23087
24572
|
return tokens;
|
|
23088
24573
|
}
|
|
23089
24574
|
|
|
23090
|
-
//
|
|
23091
|
-
var
|
|
23092
|
-
|
|
23093
|
-
|
|
23094
|
-
|
|
23095
|
-
|
|
23096
|
-
|
|
23097
|
-
|
|
23098
|
-
|
|
23099
|
-
|
|
23100
|
-
|
|
23101
|
-
|
|
23102
|
-
|
|
23103
|
-
|
|
23104
|
-
|
|
23105
|
-
|
|
23106
|
-
|
|
23107
|
-
|
|
23108
|
-
|
|
23109
|
-
|
|
23110
|
-
|
|
23111
|
-
|
|
24575
|
+
// src/shell/tmux-shell-backend.ts
|
|
24576
|
+
var TmuxShellBackend = class {
|
|
24577
|
+
constructor(tmuxManager = new TmuxManager()) {
|
|
24578
|
+
this.tmuxManager = tmuxManager;
|
|
24579
|
+
}
|
|
24580
|
+
tmuxManager;
|
|
24581
|
+
kind = "tmux";
|
|
24582
|
+
attachments = /* @__PURE__ */ new Map();
|
|
24583
|
+
sessionNameForThread(threadId) {
|
|
24584
|
+
return this.tmuxManager.sessionNameForThread(threadId);
|
|
24585
|
+
}
|
|
24586
|
+
listSessionNames() {
|
|
24587
|
+
return this.tmuxManager.listSessionNames();
|
|
24588
|
+
}
|
|
24589
|
+
hasSession(sessionId) {
|
|
24590
|
+
return this.tmuxManager.hasSession(sessionId);
|
|
24591
|
+
}
|
|
24592
|
+
async createSession(input) {
|
|
24593
|
+
await this.tmuxManager.createSession({
|
|
24594
|
+
sessionName: input.sessionId,
|
|
24595
|
+
cwd: input.cwd,
|
|
24596
|
+
...input.cols !== void 0 ? { cols: input.cols } : {},
|
|
24597
|
+
...input.rows !== void 0 ? { rows: input.rows } : {}
|
|
24598
|
+
});
|
|
24599
|
+
try {
|
|
24600
|
+
const runtime = await this.tmuxManager.getPaneRuntimeInfo(input.sessionId);
|
|
24601
|
+
if (isInteractiveShellCommand(runtime.currentCommand)) {
|
|
24602
|
+
await this.tmuxManager.sendInput(
|
|
24603
|
+
input.sessionId,
|
|
24604
|
+
await buildShellPromptInitCommand(runtime.currentCommand, {
|
|
24605
|
+
clearScreen: true
|
|
24606
|
+
})
|
|
24607
|
+
);
|
|
23112
24608
|
}
|
|
23113
|
-
|
|
23114
|
-
frontend: {
|
|
23115
|
-
entry: "./dist/index.js",
|
|
23116
|
-
style: "./src/styles.css"
|
|
24609
|
+
} catch {
|
|
23117
24610
|
}
|
|
23118
24611
|
}
|
|
23119
|
-
|
|
23120
|
-
|
|
23121
|
-
|
|
23122
|
-
|
|
23123
|
-
|
|
23124
|
-
|
|
23125
|
-
|
|
24612
|
+
async attach(sessionId, options) {
|
|
24613
|
+
const previous = this.attachments.get(sessionId);
|
|
24614
|
+
if (previous) {
|
|
24615
|
+
previous.disposed = true;
|
|
24616
|
+
clearInterval(previous.pollHandle);
|
|
24617
|
+
this.attachments.delete(sessionId);
|
|
24618
|
+
}
|
|
24619
|
+
await this.tmuxManager.resizeWindow(sessionId, options.cols, options.rows);
|
|
24620
|
+
const session = await this.snapshot(sessionId);
|
|
24621
|
+
const attachment = {
|
|
24622
|
+
disposed: false,
|
|
24623
|
+
lastSnapshot: session.snapshot,
|
|
24624
|
+
polling: false,
|
|
24625
|
+
pollHandle: setInterval(() => {
|
|
24626
|
+
void this.poll(sessionId, attachment, options);
|
|
24627
|
+
}, 250)
|
|
24628
|
+
};
|
|
24629
|
+
this.attachments.set(sessionId, attachment);
|
|
24630
|
+
return {
|
|
24631
|
+
session,
|
|
24632
|
+
attachment: {
|
|
24633
|
+
dispose: () => {
|
|
24634
|
+
attachment.disposed = true;
|
|
24635
|
+
clearInterval(attachment.pollHandle);
|
|
24636
|
+
if (this.attachments.get(sessionId) === attachment) {
|
|
24637
|
+
this.attachments.delete(sessionId);
|
|
24638
|
+
}
|
|
24639
|
+
}
|
|
24640
|
+
}
|
|
24641
|
+
};
|
|
23126
24642
|
}
|
|
23127
|
-
|
|
23128
|
-
|
|
23129
|
-
// src/plugins/plugin-service.ts
|
|
23130
|
-
var PluginService = class {
|
|
23131
|
-
constructor(registry, settingsStore) {
|
|
23132
|
-
this.registry = registry;
|
|
23133
|
-
this.settingsStore = settingsStore;
|
|
23134
|
-
this.loadPersistedSettings();
|
|
24643
|
+
sendInput(sessionId, data) {
|
|
24644
|
+
return this.tmuxManager.sendInput(sessionId, data);
|
|
23135
24645
|
}
|
|
23136
|
-
|
|
23137
|
-
|
|
23138
|
-
|
|
23139
|
-
|
|
23140
|
-
|
|
23141
|
-
|
|
23142
|
-
|
|
23143
|
-
|
|
24646
|
+
async clear(sessionId) {
|
|
24647
|
+
await this.tmuxManager.sendInput(sessionId, "\f");
|
|
24648
|
+
await this.tmuxManager.clearHistory(sessionId);
|
|
24649
|
+
const session = await this.snapshot(sessionId);
|
|
24650
|
+
const attachment = this.attachments.get(sessionId);
|
|
24651
|
+
if (attachment) {
|
|
24652
|
+
attachment.lastSnapshot = session.snapshot;
|
|
24653
|
+
}
|
|
24654
|
+
return session;
|
|
23144
24655
|
}
|
|
23145
|
-
|
|
23146
|
-
return this.
|
|
24656
|
+
resize(sessionId, cols, rows) {
|
|
24657
|
+
return this.tmuxManager.resizeWindow(sessionId, cols, rows);
|
|
23147
24658
|
}
|
|
23148
|
-
|
|
23149
|
-
const
|
|
23150
|
-
this.
|
|
23151
|
-
|
|
23152
|
-
|
|
24659
|
+
async snapshot(sessionId) {
|
|
24660
|
+
const snapshot = await this.tmuxManager.capturePane(sessionId);
|
|
24661
|
+
const runtime = await this.tmuxManager.getPaneRuntimeInfo(sessionId);
|
|
24662
|
+
const envPrefix = await resolvePaneEnvironmentPrefix(
|
|
24663
|
+
this.tmuxManager,
|
|
24664
|
+
sessionId,
|
|
24665
|
+
runtime.panePid
|
|
24666
|
+
);
|
|
24667
|
+
return {
|
|
24668
|
+
id: sessionId,
|
|
24669
|
+
cwd: runtime.currentPath,
|
|
24670
|
+
cols: runtime.paneWidth,
|
|
24671
|
+
rows: runtime.paneHeight,
|
|
24672
|
+
snapshot,
|
|
24673
|
+
runtime: {
|
|
24674
|
+
cursorX: runtime.cursorX,
|
|
24675
|
+
cursorY: runtime.cursorY,
|
|
24676
|
+
paneWidth: runtime.paneWidth,
|
|
24677
|
+
paneHeight: runtime.paneHeight,
|
|
24678
|
+
panePid: runtime.panePid,
|
|
24679
|
+
currentCommand: runtime.currentCommand,
|
|
24680
|
+
currentPath: runtime.currentPath,
|
|
24681
|
+
envPrefix,
|
|
24682
|
+
isCommandRunning: !isInteractiveShellCommand(runtime.currentCommand)
|
|
24683
|
+
}
|
|
24684
|
+
};
|
|
23153
24685
|
}
|
|
23154
|
-
|
|
23155
|
-
const
|
|
23156
|
-
|
|
23157
|
-
|
|
23158
|
-
|
|
23159
|
-
|
|
23160
|
-
throw new Error(`Built-in plugin cannot be replaced: ${manifest.id}`);
|
|
24686
|
+
killSession(sessionId) {
|
|
24687
|
+
const attachment = this.attachments.get(sessionId);
|
|
24688
|
+
if (attachment) {
|
|
24689
|
+
attachment.disposed = true;
|
|
24690
|
+
clearInterval(attachment.pollHandle);
|
|
24691
|
+
this.attachments.delete(sessionId);
|
|
23161
24692
|
}
|
|
23162
|
-
this.
|
|
23163
|
-
|
|
23164
|
-
|
|
24693
|
+
return this.tmuxManager.killSession(sessionId);
|
|
24694
|
+
}
|
|
24695
|
+
async poll(sessionId, attachment, options) {
|
|
24696
|
+
if (attachment.disposed || attachment.polling) {
|
|
24697
|
+
return;
|
|
24698
|
+
}
|
|
24699
|
+
attachment.polling = true;
|
|
24700
|
+
try {
|
|
24701
|
+
const exists2 = await this.hasSession(sessionId);
|
|
24702
|
+
if (!exists2) {
|
|
24703
|
+
attachment.disposed = true;
|
|
24704
|
+
clearInterval(attachment.pollHandle);
|
|
24705
|
+
this.attachments.delete(sessionId);
|
|
24706
|
+
options.onExit();
|
|
24707
|
+
return;
|
|
24708
|
+
}
|
|
24709
|
+
const session = await this.snapshot(sessionId);
|
|
24710
|
+
if (session.snapshot !== attachment.lastSnapshot) {
|
|
24711
|
+
attachment.lastSnapshot = session.snapshot;
|
|
24712
|
+
options.onData(session.snapshot, session, { replace: true });
|
|
24713
|
+
}
|
|
24714
|
+
} catch {
|
|
24715
|
+
attachment.disposed = true;
|
|
24716
|
+
clearInterval(attachment.pollHandle);
|
|
24717
|
+
this.attachments.delete(sessionId);
|
|
24718
|
+
options.onExit();
|
|
24719
|
+
} finally {
|
|
24720
|
+
attachment.polling = false;
|
|
24721
|
+
}
|
|
24722
|
+
}
|
|
24723
|
+
};
|
|
24724
|
+
|
|
24725
|
+
// src/routes/shells.ts
|
|
24726
|
+
import { z as z9 } from "zod";
|
|
24727
|
+
async function registerShellRoutes(app2, options = {}) {
|
|
24728
|
+
const threadIdParams = z9.object({ id: z9.string().uuid() });
|
|
24729
|
+
const shellIdParams = z9.object({ id: z9.string().uuid() });
|
|
24730
|
+
const createShellSchema = z9.object({
|
|
24731
|
+
cols: z9.number().int().positive().optional(),
|
|
24732
|
+
rows: z9.number().int().positive().optional(),
|
|
24733
|
+
label: z9.string().trim().min(1).max(80).optional()
|
|
24734
|
+
});
|
|
24735
|
+
const updateShellSchema = z9.object({
|
|
24736
|
+
label: z9.string().trim().min(1).max(80).nullable().optional()
|
|
24737
|
+
});
|
|
24738
|
+
const routeOptions = options.preHandler ? { preHandler: options.preHandler } : {};
|
|
24739
|
+
app2.get("/api/threads/:id/shell", routeOptions, async (request) => {
|
|
24740
|
+
const params = threadIdParams.parse(request.params);
|
|
24741
|
+
return app2.services.shellService.getThreadShellState(params.id);
|
|
24742
|
+
});
|
|
24743
|
+
app2.post("/api/threads/:id/shell", routeOptions, async (request) => {
|
|
24744
|
+
const params = threadIdParams.parse(request.params);
|
|
24745
|
+
const body = createShellSchema.parse(request.body ?? {});
|
|
24746
|
+
const input = {
|
|
24747
|
+
...body.cols !== void 0 ? { cols: body.cols } : {},
|
|
24748
|
+
...body.rows !== void 0 ? { rows: body.rows } : {},
|
|
24749
|
+
...body.label !== void 0 ? { label: body.label } : {}
|
|
24750
|
+
};
|
|
24751
|
+
return app2.services.shellService.createShellForThread(params.id, input);
|
|
24752
|
+
});
|
|
24753
|
+
app2.post("/api/shells/:id/terminate", routeOptions, async (request) => {
|
|
24754
|
+
const params = shellIdParams.parse(request.params);
|
|
24755
|
+
return app2.services.shellService.terminateShell(params.id);
|
|
24756
|
+
});
|
|
24757
|
+
app2.patch("/api/shells/:id", routeOptions, async (request) => {
|
|
24758
|
+
const params = shellIdParams.parse(request.params);
|
|
24759
|
+
const body = updateShellSchema.parse(request.body ?? {});
|
|
24760
|
+
const input = {
|
|
24761
|
+
..."label" in body ? { label: body.label ?? null } : {}
|
|
24762
|
+
};
|
|
24763
|
+
return app2.services.shellService.updateShell(params.id, input);
|
|
24764
|
+
});
|
|
24765
|
+
}
|
|
24766
|
+
|
|
24767
|
+
// src/plugins/terminal-plugin-backend.ts
|
|
24768
|
+
function createTerminalShellBackend(env = process.env) {
|
|
24769
|
+
return env.REMOTE_CODEX_SHELL_BACKEND === "tmux" ? new TmuxShellBackend() : new PtyShellBackend();
|
|
24770
|
+
}
|
|
24771
|
+
function isTerminalPluginEnabled(app2) {
|
|
24772
|
+
return app2.services.pluginService.getPlugin(TERMINAL_PLUGIN_ID)?.enabled === true;
|
|
24773
|
+
}
|
|
24774
|
+
function requireTerminalPluginEnabled(app2) {
|
|
24775
|
+
if (!isTerminalPluginEnabled(app2)) {
|
|
24776
|
+
throw new ShellServiceError(
|
|
24777
|
+
"plugin_disabled",
|
|
24778
|
+
"The Terminal plugin is disabled."
|
|
23165
24779
|
);
|
|
23166
|
-
|
|
23167
|
-
|
|
23168
|
-
|
|
23169
|
-
|
|
24780
|
+
}
|
|
24781
|
+
}
|
|
24782
|
+
function registerTerminalPluginBackend(app2) {
|
|
24783
|
+
app2.register(registerShellRoutes, {
|
|
24784
|
+
preHandler: async () => {
|
|
24785
|
+
requireTerminalPluginEnabled(app2);
|
|
23170
24786
|
}
|
|
23171
|
-
|
|
23172
|
-
|
|
23173
|
-
|
|
23174
|
-
|
|
23175
|
-
|
|
24787
|
+
});
|
|
24788
|
+
}
|
|
24789
|
+
function createTerminalPluginBackendContribution() {
|
|
24790
|
+
return {
|
|
24791
|
+
pluginId: TERMINAL_PLUGIN_ID,
|
|
24792
|
+
registerHttp: registerTerminalPluginBackend,
|
|
24793
|
+
registerSocket(host2) {
|
|
24794
|
+
host2.registerSocketHandler(createTerminalSocketHandler());
|
|
23176
24795
|
}
|
|
23177
|
-
|
|
24796
|
+
};
|
|
24797
|
+
}
|
|
24798
|
+
function createTerminalSocketHandler() {
|
|
24799
|
+
return async (context) => {
|
|
24800
|
+
const { app: app2, message, send } = context;
|
|
24801
|
+
const shellService = app2.services.shellService;
|
|
24802
|
+
const stateKey = `${TERMINAL_PLUGIN_ID}:attached-shell`;
|
|
24803
|
+
const cleanupRegisteredKey = `${TERMINAL_PLUGIN_ID}:cleanup-registered`;
|
|
24804
|
+
const getAttachedShell = () => context.state.get(stateKey);
|
|
24805
|
+
const setAttachedShell = (value) => {
|
|
24806
|
+
if (value) {
|
|
24807
|
+
context.state.set(stateKey, value);
|
|
24808
|
+
} else {
|
|
24809
|
+
context.state.delete(stateKey);
|
|
24810
|
+
}
|
|
24811
|
+
};
|
|
24812
|
+
try {
|
|
24813
|
+
if (message.type === "shell.attach") {
|
|
24814
|
+
requireTerminalPluginEnabled(app2);
|
|
24815
|
+
const attachedShell = getAttachedShell();
|
|
24816
|
+
if (attachedShell && attachedShell.shellId !== message.shellId) {
|
|
24817
|
+
await shellService.detachShell(
|
|
24818
|
+
attachedShell.shellId,
|
|
24819
|
+
attachedShell.viewerId
|
|
24820
|
+
);
|
|
24821
|
+
setAttachedShell(null);
|
|
24822
|
+
}
|
|
24823
|
+
const attachment = await shellService.attachShell(message.shellId, {
|
|
24824
|
+
cols: message.cols,
|
|
24825
|
+
rows: message.rows,
|
|
24826
|
+
onConnected: (connected) => {
|
|
24827
|
+
setAttachedShell({
|
|
24828
|
+
shellId: message.shellId,
|
|
24829
|
+
viewerId: connected.viewerId
|
|
24830
|
+
});
|
|
24831
|
+
send({
|
|
24832
|
+
type: "shell.connected",
|
|
24833
|
+
shellId: message.shellId,
|
|
24834
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
24835
|
+
payload: {
|
|
24836
|
+
viewerId: connected.viewerId
|
|
24837
|
+
}
|
|
24838
|
+
});
|
|
24839
|
+
send({
|
|
24840
|
+
type: "shell.status",
|
|
24841
|
+
shellId: message.shellId,
|
|
24842
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
24843
|
+
payload: {
|
|
24844
|
+
threadId: connected.shell.threadId,
|
|
24845
|
+
state: "attached",
|
|
24846
|
+
viewerId: connected.viewerId
|
|
24847
|
+
}
|
|
24848
|
+
});
|
|
24849
|
+
},
|
|
24850
|
+
onData: (data, options) => {
|
|
24851
|
+
send({
|
|
24852
|
+
type: "shell.output",
|
|
24853
|
+
shellId: message.shellId,
|
|
24854
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
24855
|
+
payload: {
|
|
24856
|
+
data,
|
|
24857
|
+
...options?.replace ? { replace: true } : {},
|
|
24858
|
+
...options?.cursorX !== void 0 ? { cursorX: options.cursorX } : {},
|
|
24859
|
+
...options?.cursorY !== void 0 ? { cursorY: options.cursorY } : {},
|
|
24860
|
+
...options?.paneHeight !== void 0 ? { paneHeight: options.paneHeight } : {},
|
|
24861
|
+
...options?.cwdBaseName !== void 0 ? { cwdBaseName: options.cwdBaseName } : {},
|
|
24862
|
+
...options?.envPrefix !== void 0 ? { envPrefix: options.envPrefix } : {},
|
|
24863
|
+
...options?.isCommandRunning !== void 0 ? { isCommandRunning: options.isCommandRunning } : {}
|
|
24864
|
+
}
|
|
24865
|
+
});
|
|
24866
|
+
}
|
|
24867
|
+
});
|
|
24868
|
+
if (!getAttachedShell()) {
|
|
24869
|
+
setAttachedShell({
|
|
24870
|
+
shellId: message.shellId,
|
|
24871
|
+
viewerId: attachment.viewerId
|
|
24872
|
+
});
|
|
24873
|
+
}
|
|
24874
|
+
if (context.state.get(cleanupRegisteredKey) !== true) {
|
|
24875
|
+
context.state.set(cleanupRegisteredKey, true);
|
|
24876
|
+
context.onClose(() => {
|
|
24877
|
+
const attached = getAttachedShell();
|
|
24878
|
+
if (attached) {
|
|
24879
|
+
void shellService.detachShell(attached.shellId, attached.viewerId).catch(() => {
|
|
24880
|
+
});
|
|
24881
|
+
setAttachedShell(null);
|
|
24882
|
+
}
|
|
24883
|
+
});
|
|
24884
|
+
}
|
|
24885
|
+
return true;
|
|
24886
|
+
}
|
|
24887
|
+
if (message.type === "shell.detach") {
|
|
24888
|
+
requireTerminalPluginEnabled(app2);
|
|
24889
|
+
await shellService.detachShell(message.shellId, message.viewerId);
|
|
24890
|
+
const attachedShell = getAttachedShell();
|
|
24891
|
+
if (attachedShell?.shellId === message.shellId && attachedShell.viewerId === message.viewerId) {
|
|
24892
|
+
setAttachedShell(null);
|
|
24893
|
+
}
|
|
24894
|
+
return true;
|
|
24895
|
+
}
|
|
24896
|
+
if (message.type === "shell.input") {
|
|
24897
|
+
requireTerminalPluginEnabled(app2);
|
|
24898
|
+
await shellService.sendInput(
|
|
24899
|
+
message.shellId,
|
|
24900
|
+
message.viewerId,
|
|
24901
|
+
message.data
|
|
24902
|
+
);
|
|
24903
|
+
return true;
|
|
24904
|
+
}
|
|
24905
|
+
if (message.type === "shell.resize") {
|
|
24906
|
+
requireTerminalPluginEnabled(app2);
|
|
24907
|
+
await shellService.resizeShell(
|
|
24908
|
+
message.shellId,
|
|
24909
|
+
message.viewerId,
|
|
24910
|
+
message.cols,
|
|
24911
|
+
message.rows
|
|
24912
|
+
);
|
|
24913
|
+
return true;
|
|
24914
|
+
}
|
|
24915
|
+
if (message.type === "shell.clear") {
|
|
24916
|
+
requireTerminalPluginEnabled(app2);
|
|
24917
|
+
await shellService.clearShell(message.shellId, message.viewerId);
|
|
24918
|
+
return true;
|
|
24919
|
+
}
|
|
24920
|
+
return false;
|
|
24921
|
+
} catch (error) {
|
|
24922
|
+
if ("shellId" in message) {
|
|
24923
|
+
send(makeShellErrorEnvelope(message.shellId, error));
|
|
24924
|
+
return true;
|
|
24925
|
+
}
|
|
24926
|
+
throw error;
|
|
24927
|
+
}
|
|
24928
|
+
};
|
|
24929
|
+
}
|
|
24930
|
+
function makeShellErrorEnvelope(shellId, error) {
|
|
24931
|
+
if (error instanceof ShellServiceError) {
|
|
24932
|
+
return {
|
|
24933
|
+
type: "shell.error",
|
|
24934
|
+
shellId,
|
|
24935
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
24936
|
+
payload: {
|
|
24937
|
+
code: error.code,
|
|
24938
|
+
message: error.message
|
|
24939
|
+
}
|
|
24940
|
+
};
|
|
23178
24941
|
}
|
|
23179
|
-
|
|
23180
|
-
|
|
23181
|
-
|
|
23182
|
-
|
|
24942
|
+
return {
|
|
24943
|
+
type: "shell.error",
|
|
24944
|
+
shellId,
|
|
24945
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
24946
|
+
payload: {
|
|
24947
|
+
code: "unknown",
|
|
24948
|
+
message: error instanceof Error ? error.message : "Unexpected shell error."
|
|
23183
24949
|
}
|
|
23184
|
-
|
|
23185
|
-
|
|
23186
|
-
|
|
23187
|
-
|
|
23188
|
-
|
|
23189
|
-
|
|
23190
|
-
|
|
24950
|
+
};
|
|
24951
|
+
}
|
|
24952
|
+
|
|
24953
|
+
// src/auth.ts
|
|
24954
|
+
import crypto2 from "crypto";
|
|
24955
|
+
var AUTH_COOKIE_NAME = "remote_codex_session";
|
|
24956
|
+
var AuthService = class {
|
|
24957
|
+
required;
|
|
24958
|
+
mode;
|
|
24959
|
+
username;
|
|
24960
|
+
password;
|
|
24961
|
+
secret;
|
|
24962
|
+
sessionTtlSeconds;
|
|
24963
|
+
constructor(config) {
|
|
24964
|
+
this.mode = config.mode;
|
|
24965
|
+
this.required = config.mode === "server" || config.mode === "relay";
|
|
24966
|
+
this.username = config.auth.adminUsername;
|
|
24967
|
+
this.password = config.auth.adminPassword;
|
|
24968
|
+
this.secret = config.auth.sessionSecret;
|
|
24969
|
+
this.sessionTtlSeconds = config.auth.sessionTtlSeconds;
|
|
24970
|
+
if (this.required) {
|
|
24971
|
+
const missing = [
|
|
24972
|
+
this.username ? null : "REMOTE_CODEX_ADMIN_USERNAME",
|
|
24973
|
+
this.password ? null : "REMOTE_CODEX_ADMIN_PASSWORD",
|
|
24974
|
+
this.secret ? null : "REMOTE_CODEX_SESSION_SECRET"
|
|
24975
|
+
].filter(Boolean);
|
|
24976
|
+
if (missing.length > 0) {
|
|
24977
|
+
throw new Error(
|
|
24978
|
+
`${config.mode} mode requires auth configuration: ${missing.join(", ")}.`
|
|
24979
|
+
);
|
|
23191
24980
|
}
|
|
23192
|
-
|
|
24981
|
+
}
|
|
23193
24982
|
}
|
|
23194
|
-
|
|
23195
|
-
if (!this.
|
|
23196
|
-
return
|
|
24983
|
+
login(input) {
|
|
24984
|
+
if (!this.required) {
|
|
24985
|
+
return {
|
|
24986
|
+
token: null,
|
|
24987
|
+
session: {
|
|
24988
|
+
authenticated: true,
|
|
24989
|
+
username: null,
|
|
24990
|
+
expiresAt: null,
|
|
24991
|
+
mode: this.mode,
|
|
24992
|
+
authRequired: false
|
|
24993
|
+
}
|
|
24994
|
+
};
|
|
23197
24995
|
}
|
|
23198
|
-
this.
|
|
23199
|
-
|
|
23200
|
-
this.registerImportedManifest(
|
|
23201
|
-
manifest,
|
|
23202
|
-
this.settings.enabled[manifest.id] ?? true
|
|
23203
|
-
);
|
|
24996
|
+
if (!this.username || !this.password || !this.secret) {
|
|
24997
|
+
return null;
|
|
23204
24998
|
}
|
|
23205
|
-
|
|
23206
|
-
|
|
23207
|
-
this.registry.setEnabled(pluginId, enabled);
|
|
23208
|
-
}
|
|
24999
|
+
if (!constantTimeEqual(input.username, this.username) || !constantTimeEqual(input.password, this.password)) {
|
|
25000
|
+
return null;
|
|
23209
25001
|
}
|
|
25002
|
+
return this.createSession(this.username);
|
|
23210
25003
|
}
|
|
23211
|
-
|
|
23212
|
-
if (this.
|
|
23213
|
-
|
|
23214
|
-
|
|
23215
|
-
|
|
23216
|
-
|
|
23217
|
-
|
|
23218
|
-
|
|
23219
|
-
|
|
23220
|
-
} else {
|
|
23221
|
-
this.registry.setEnabled(manifest.id, enabled);
|
|
23222
|
-
}
|
|
23223
|
-
return;
|
|
25004
|
+
verifyRequest(request) {
|
|
25005
|
+
if (!this.required) {
|
|
25006
|
+
return {
|
|
25007
|
+
authenticated: true,
|
|
25008
|
+
username: null,
|
|
25009
|
+
expiresAt: null,
|
|
25010
|
+
mode: this.mode,
|
|
25011
|
+
authRequired: false
|
|
25012
|
+
};
|
|
23224
25013
|
}
|
|
23225
|
-
|
|
23226
|
-
|
|
23227
|
-
|
|
23228
|
-
|
|
23229
|
-
|
|
25014
|
+
const token = readBearerToken(request) ?? readQueryToken(request) ?? readCookieToken(request);
|
|
25015
|
+
if (!token || !this.secret) {
|
|
25016
|
+
return unauthenticatedSession(this.mode);
|
|
25017
|
+
}
|
|
25018
|
+
return this.verifyToken(token);
|
|
23230
25019
|
}
|
|
23231
|
-
|
|
23232
|
-
|
|
25020
|
+
attachSessionCookie(reply, token) {
|
|
25021
|
+
reply.header(
|
|
25022
|
+
"set-cookie",
|
|
25023
|
+
`${AUTH_COOKIE_NAME}=${encodeURIComponent(token)}; HttpOnly; SameSite=Lax; Path=/; Max-Age=${this.sessionTtlSeconds}`
|
|
25024
|
+
);
|
|
23233
25025
|
}
|
|
23234
|
-
|
|
23235
|
-
|
|
23236
|
-
|
|
25026
|
+
clearSessionCookie(reply) {
|
|
25027
|
+
reply.header(
|
|
25028
|
+
"set-cookie",
|
|
25029
|
+
`${AUTH_COOKIE_NAME}=; HttpOnly; SameSite=Lax; Path=/; Max-Age=0`
|
|
25030
|
+
);
|
|
25031
|
+
}
|
|
25032
|
+
createSession(username) {
|
|
25033
|
+
const expiresAtMs = Date.now() + this.sessionTtlSeconds * 1e3;
|
|
25034
|
+
const payload = {
|
|
25035
|
+
username,
|
|
25036
|
+
expiresAt: expiresAtMs,
|
|
25037
|
+
nonce: crypto2.randomBytes(16).toString("base64url")
|
|
25038
|
+
};
|
|
25039
|
+
const payloadText = Buffer.from(JSON.stringify(payload), "utf8").toString(
|
|
25040
|
+
"base64url"
|
|
25041
|
+
);
|
|
25042
|
+
const signature = this.sign(payloadText);
|
|
25043
|
+
return {
|
|
25044
|
+
token: `${payloadText}.${signature}`,
|
|
25045
|
+
session: {
|
|
25046
|
+
authenticated: true,
|
|
25047
|
+
username,
|
|
25048
|
+
expiresAt: new Date(expiresAtMs).toISOString(),
|
|
25049
|
+
mode: this.mode,
|
|
25050
|
+
authRequired: this.required
|
|
25051
|
+
}
|
|
25052
|
+
};
|
|
25053
|
+
}
|
|
25054
|
+
verifyToken(token) {
|
|
25055
|
+
const [payloadText, signature, extra] = token.split(".");
|
|
25056
|
+
if (!payloadText || !signature || extra !== void 0) {
|
|
25057
|
+
return unauthenticatedSession(this.mode);
|
|
23237
25058
|
}
|
|
23238
|
-
|
|
25059
|
+
if (!constantTimeEqual(signature, this.sign(payloadText))) {
|
|
25060
|
+
return unauthenticatedSession(this.mode);
|
|
25061
|
+
}
|
|
25062
|
+
let payload;
|
|
25063
|
+
try {
|
|
25064
|
+
payload = JSON.parse(
|
|
25065
|
+
Buffer.from(payloadText, "base64url").toString("utf8")
|
|
25066
|
+
);
|
|
25067
|
+
} catch {
|
|
25068
|
+
return unauthenticatedSession(this.mode);
|
|
25069
|
+
}
|
|
25070
|
+
if (!isSessionPayload(payload)) {
|
|
25071
|
+
return unauthenticatedSession(this.mode);
|
|
25072
|
+
}
|
|
25073
|
+
if (payload.expiresAt <= Date.now()) {
|
|
25074
|
+
return unauthenticatedSession(this.mode);
|
|
25075
|
+
}
|
|
25076
|
+
return {
|
|
25077
|
+
authenticated: true,
|
|
25078
|
+
username: payload.username,
|
|
25079
|
+
expiresAt: new Date(payload.expiresAt).toISOString(),
|
|
25080
|
+
mode: this.mode,
|
|
25081
|
+
authRequired: this.required
|
|
25082
|
+
};
|
|
25083
|
+
}
|
|
25084
|
+
sign(payloadText) {
|
|
25085
|
+
return crypto2.createHmac("sha256", this.secret ?? "").update(payloadText).digest("base64url");
|
|
23239
25086
|
}
|
|
23240
25087
|
};
|
|
23241
|
-
|
|
23242
|
-
// src/plugins/plugin-settings-store.ts
|
|
23243
|
-
var PLUGIN_SETTINGS_POLICY_KEY = "plugins";
|
|
23244
|
-
function emptySettings() {
|
|
25088
|
+
function unauthorizedPayload() {
|
|
23245
25089
|
return {
|
|
23246
|
-
|
|
23247
|
-
|
|
25090
|
+
code: "unauthorized",
|
|
25091
|
+
message: "Authentication is required."
|
|
23248
25092
|
};
|
|
23249
25093
|
}
|
|
23250
|
-
function
|
|
23251
|
-
|
|
23252
|
-
|
|
25094
|
+
function readBearerToken(request) {
|
|
25095
|
+
const authorization = request.headers.authorization;
|
|
25096
|
+
if (!authorization) {
|
|
25097
|
+
return null;
|
|
23253
25098
|
}
|
|
23254
|
-
const
|
|
23255
|
-
|
|
23256
|
-
|
|
23257
|
-
|
|
23258
|
-
|
|
23259
|
-
|
|
25099
|
+
const match = /^Bearer\s+(.+)$/i.exec(authorization);
|
|
25100
|
+
return match ? match[1].trim() : null;
|
|
25101
|
+
}
|
|
25102
|
+
function readCookieToken(request) {
|
|
25103
|
+
const cookie = request.headers.cookie;
|
|
25104
|
+
if (!cookie) {
|
|
25105
|
+
return null;
|
|
25106
|
+
}
|
|
25107
|
+
const entries = cookie.split(";");
|
|
25108
|
+
for (const entry of entries) {
|
|
25109
|
+
const [name, ...valueParts] = entry.trim().split("=");
|
|
25110
|
+
if (name === AUTH_COOKIE_NAME) {
|
|
25111
|
+
return decodeURIComponent(valueParts.join("="));
|
|
23260
25112
|
}
|
|
23261
25113
|
}
|
|
23262
|
-
return
|
|
25114
|
+
return null;
|
|
23263
25115
|
}
|
|
23264
|
-
|
|
23265
|
-
|
|
23266
|
-
|
|
25116
|
+
function readQueryToken(request) {
|
|
25117
|
+
const query = request.query;
|
|
25118
|
+
if (!query || typeof query !== "object" || !("token" in query)) {
|
|
25119
|
+
return null;
|
|
23267
25120
|
}
|
|
23268
|
-
|
|
23269
|
-
|
|
23270
|
-
|
|
23271
|
-
|
|
23272
|
-
|
|
25121
|
+
const token = query.token;
|
|
25122
|
+
return typeof token === "string" && token.trim() ? token.trim() : null;
|
|
25123
|
+
}
|
|
25124
|
+
function unauthenticatedSession(mode) {
|
|
25125
|
+
return {
|
|
25126
|
+
authenticated: false,
|
|
25127
|
+
username: null,
|
|
25128
|
+
expiresAt: null,
|
|
25129
|
+
mode,
|
|
25130
|
+
authRequired: mode === "server" || mode === "relay"
|
|
25131
|
+
};
|
|
25132
|
+
}
|
|
25133
|
+
function isSessionPayload(value) {
|
|
25134
|
+
return typeof value === "object" && value !== null && "username" in value && typeof value.username === "string" && "expiresAt" in value && typeof value.expiresAt === "number" && "nonce" in value && typeof value.nonce === "string";
|
|
25135
|
+
}
|
|
25136
|
+
function constantTimeEqual(left, right) {
|
|
25137
|
+
const leftBuffer = Buffer.from(left);
|
|
25138
|
+
const rightBuffer = Buffer.from(right);
|
|
25139
|
+
if (leftBuffer.length !== rightBuffer.length) {
|
|
25140
|
+
return false;
|
|
25141
|
+
}
|
|
25142
|
+
return crypto2.timingSafeEqual(leftBuffer, rightBuffer);
|
|
25143
|
+
}
|
|
25144
|
+
|
|
25145
|
+
// src/relay-tunnel-client.ts
|
|
25146
|
+
var RELAY_HEARTBEAT_INTERVAL_MS = 3e4;
|
|
25147
|
+
var RELAY_RECONNECT_INITIAL_DELAY_MS = 1e3;
|
|
25148
|
+
var RELAY_RECONNECT_MAX_DELAY_MS = 3e4;
|
|
25149
|
+
var RelayTunnelClient = class {
|
|
25150
|
+
constructor(config, handleRequest, handleClientConnected, handleClientMessage) {
|
|
25151
|
+
this.config = config;
|
|
25152
|
+
this.handleRequest = handleRequest;
|
|
25153
|
+
this.handleClientConnected = handleClientConnected;
|
|
25154
|
+
this.handleClientMessage = handleClientMessage;
|
|
25155
|
+
}
|
|
25156
|
+
config;
|
|
25157
|
+
handleRequest;
|
|
25158
|
+
handleClientConnected;
|
|
25159
|
+
handleClientMessage;
|
|
25160
|
+
socket = null;
|
|
25161
|
+
heartbeatHandle = null;
|
|
25162
|
+
reconnectHandle = null;
|
|
25163
|
+
reconnectDelayMs = RELAY_RECONNECT_INITIAL_DELAY_MS;
|
|
25164
|
+
stopped = false;
|
|
25165
|
+
relayClientCleanup = /* @__PURE__ */ new Map();
|
|
25166
|
+
validateConfig() {
|
|
25167
|
+
if (!this.config.serverUrl || !this.config.agentToken) {
|
|
25168
|
+
throw new Error(
|
|
25169
|
+
"Relay mode requires REMOTE_CODEX_RELAY_SERVER_URL and REMOTE_CODEX_RELAY_AGENT_TOKEN."
|
|
25170
|
+
);
|
|
25171
|
+
}
|
|
25172
|
+
}
|
|
25173
|
+
start() {
|
|
25174
|
+
this.validateConfig();
|
|
25175
|
+
this.stopped = false;
|
|
25176
|
+
this.clearReconnect();
|
|
25177
|
+
if (this.socket) {
|
|
25178
|
+
return;
|
|
25179
|
+
}
|
|
25180
|
+
const url = new URL("/supervisor/tunnel", this.config.serverUrl ?? void 0);
|
|
25181
|
+
url.searchParams.set("token", this.config.agentToken ?? "");
|
|
25182
|
+
url.searchParams.set("deviceToken", this.config.agentToken ?? "");
|
|
25183
|
+
this.socket = new WebSocket(url);
|
|
25184
|
+
this.socket.addEventListener("open", () => {
|
|
25185
|
+
this.reconnectDelayMs = RELAY_RECONNECT_INITIAL_DELAY_MS;
|
|
25186
|
+
this.sendHeartbeat();
|
|
25187
|
+
this.heartbeatHandle = setInterval(() => {
|
|
25188
|
+
this.sendHeartbeat();
|
|
25189
|
+
}, RELAY_HEARTBEAT_INTERVAL_MS);
|
|
25190
|
+
});
|
|
25191
|
+
this.socket.addEventListener("close", () => {
|
|
25192
|
+
this.clearHeartbeat();
|
|
25193
|
+
this.cleanupRelayClients();
|
|
25194
|
+
this.socket = null;
|
|
25195
|
+
this.scheduleReconnect();
|
|
25196
|
+
});
|
|
25197
|
+
this.socket.addEventListener("message", (event) => {
|
|
25198
|
+
void this.handleMessage(String(event.data));
|
|
25199
|
+
});
|
|
25200
|
+
}
|
|
25201
|
+
stop() {
|
|
25202
|
+
this.stopped = true;
|
|
25203
|
+
this.clearHeartbeat();
|
|
25204
|
+
this.clearReconnect();
|
|
25205
|
+
this.cleanupRelayClients();
|
|
25206
|
+
this.socket?.close();
|
|
25207
|
+
this.socket = null;
|
|
25208
|
+
}
|
|
25209
|
+
sendHeartbeat() {
|
|
25210
|
+
if (this.socket?.readyState !== WebSocket.OPEN) {
|
|
25211
|
+
return;
|
|
23273
25212
|
}
|
|
25213
|
+
this.socket.send(
|
|
25214
|
+
JSON.stringify({
|
|
25215
|
+
type: "relay.heartbeat",
|
|
25216
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
25217
|
+
})
|
|
25218
|
+
);
|
|
25219
|
+
}
|
|
25220
|
+
async handleMessage(rawMessage) {
|
|
25221
|
+
let parsed;
|
|
23274
25222
|
try {
|
|
23275
|
-
|
|
23276
|
-
return {
|
|
23277
|
-
enabled: parseEnabled(parsed.enabled),
|
|
23278
|
-
imported: Array.isArray(parsed.imported) ? parsed.imported.map((entry) => parsePluginManifest(entry)) : []
|
|
23279
|
-
};
|
|
25223
|
+
parsed = JSON.parse(rawMessage);
|
|
23280
25224
|
} catch {
|
|
23281
|
-
return
|
|
25225
|
+
return;
|
|
25226
|
+
}
|
|
25227
|
+
if (parsed.type !== "relay.request") {
|
|
25228
|
+
if (parsed.type === "relay.client.connected") {
|
|
25229
|
+
const cleanup = this.handleClientConnected(parsed.clientId, (message) => {
|
|
25230
|
+
this.sendClientMessage(parsed.clientId, message);
|
|
25231
|
+
});
|
|
25232
|
+
this.relayClientCleanup.set(parsed.clientId, cleanup);
|
|
25233
|
+
return;
|
|
25234
|
+
}
|
|
25235
|
+
if (parsed.type === "relay.client.disconnected") {
|
|
25236
|
+
this.relayClientCleanup.get(parsed.clientId)?.();
|
|
25237
|
+
this.relayClientCleanup.delete(parsed.clientId);
|
|
25238
|
+
return;
|
|
25239
|
+
}
|
|
25240
|
+
if (parsed.type === "relay.client.message") {
|
|
25241
|
+
await this.handleClientMessage(parsed.clientId, parsed.payload, (message) => {
|
|
25242
|
+
this.sendClientMessage(parsed.clientId, message);
|
|
25243
|
+
});
|
|
25244
|
+
return;
|
|
25245
|
+
}
|
|
25246
|
+
return;
|
|
23282
25247
|
}
|
|
25248
|
+
const response = await this.handleRequest(parsed.payload);
|
|
25249
|
+
this.socket?.send(
|
|
25250
|
+
JSON.stringify({
|
|
25251
|
+
type: "relay.response",
|
|
25252
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25253
|
+
requestId: parsed.requestId,
|
|
25254
|
+
payload: response
|
|
25255
|
+
})
|
|
25256
|
+
);
|
|
23283
25257
|
}
|
|
23284
|
-
|
|
23285
|
-
|
|
23286
|
-
|
|
23287
|
-
|
|
25258
|
+
sendClientMessage(clientId, message) {
|
|
25259
|
+
if (this.socket?.readyState !== WebSocket.OPEN) {
|
|
25260
|
+
return;
|
|
25261
|
+
}
|
|
25262
|
+
this.socket.send(
|
|
23288
25263
|
JSON.stringify({
|
|
23289
|
-
|
|
23290
|
-
|
|
25264
|
+
type: "relay.server.message",
|
|
25265
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25266
|
+
clientId,
|
|
25267
|
+
payload: message
|
|
23291
25268
|
})
|
|
23292
25269
|
);
|
|
23293
25270
|
}
|
|
25271
|
+
clearHeartbeat() {
|
|
25272
|
+
if (this.heartbeatHandle) {
|
|
25273
|
+
clearInterval(this.heartbeatHandle);
|
|
25274
|
+
this.heartbeatHandle = null;
|
|
25275
|
+
}
|
|
25276
|
+
}
|
|
25277
|
+
scheduleReconnect() {
|
|
25278
|
+
if (this.stopped || this.reconnectHandle) {
|
|
25279
|
+
return;
|
|
25280
|
+
}
|
|
25281
|
+
const delayMs = this.reconnectDelayMs;
|
|
25282
|
+
this.reconnectDelayMs = Math.min(
|
|
25283
|
+
this.reconnectDelayMs * 2,
|
|
25284
|
+
RELAY_RECONNECT_MAX_DELAY_MS
|
|
25285
|
+
);
|
|
25286
|
+
this.reconnectHandle = setTimeout(() => {
|
|
25287
|
+
this.reconnectHandle = null;
|
|
25288
|
+
this.start();
|
|
25289
|
+
}, delayMs);
|
|
25290
|
+
}
|
|
25291
|
+
clearReconnect() {
|
|
25292
|
+
if (this.reconnectHandle) {
|
|
25293
|
+
clearTimeout(this.reconnectHandle);
|
|
25294
|
+
this.reconnectHandle = null;
|
|
25295
|
+
}
|
|
25296
|
+
}
|
|
25297
|
+
cleanupRelayClients() {
|
|
25298
|
+
for (const [clientId, cleanup] of this.relayClientCleanup) {
|
|
25299
|
+
cleanup();
|
|
25300
|
+
this.relayClientCleanup.delete(clientId);
|
|
25301
|
+
}
|
|
25302
|
+
}
|
|
23294
25303
|
};
|
|
23295
25304
|
|
|
23296
25305
|
// src/app.ts
|
|
23297
25306
|
var MAX_PROMPT_ATTACHMENTS2 = 10;
|
|
23298
25307
|
var MAX_PROMPT_ATTACHMENT_BYTES2 = 25 * 1024 * 1024;
|
|
25308
|
+
var RELAY_FORWARD_HEADER = "x-remote-codex-relay-forwarded";
|
|
23299
25309
|
var HttpError = class extends Error {
|
|
23300
25310
|
constructor(statusCode, payload) {
|
|
23301
25311
|
super(payload.message);
|
|
@@ -23307,16 +25317,16 @@ var HttpError = class extends Error {
|
|
|
23307
25317
|
};
|
|
23308
25318
|
function findRepoRoot(start = process.cwd()) {
|
|
23309
25319
|
if (process.env.REMOTE_CODEX_REPO_ROOT) {
|
|
23310
|
-
return
|
|
25320
|
+
return path23.resolve(process.env.REMOTE_CODEX_REPO_ROOT);
|
|
23311
25321
|
}
|
|
23312
|
-
let current =
|
|
23313
|
-
while (current !==
|
|
23314
|
-
if (
|
|
25322
|
+
let current = path23.resolve(start);
|
|
25323
|
+
while (current !== path23.dirname(current)) {
|
|
25324
|
+
if (fs24.existsSync(path23.join(current, "pnpm-workspace.yaml")) && fs24.existsSync(path23.join(current, "scripts", "service-restart.mjs"))) {
|
|
23315
25325
|
return current;
|
|
23316
25326
|
}
|
|
23317
|
-
current =
|
|
25327
|
+
current = path23.dirname(current);
|
|
23318
25328
|
}
|
|
23319
|
-
return
|
|
25329
|
+
return path23.resolve(process.cwd());
|
|
23320
25330
|
}
|
|
23321
25331
|
function createServiceLifecycle() {
|
|
23322
25332
|
return {
|
|
@@ -23328,14 +25338,14 @@ function createServiceLifecycle() {
|
|
|
23328
25338
|
});
|
|
23329
25339
|
}
|
|
23330
25340
|
const repoRoot = findRepoRoot();
|
|
23331
|
-
const restartScript =
|
|
23332
|
-
if (!
|
|
25341
|
+
const restartScript = path23.join(repoRoot, "scripts", "service-restart.mjs");
|
|
25342
|
+
if (!fs24.existsSync(restartScript) || !fs24.existsSync(path23.join(repoRoot, "pnpm-workspace.yaml"))) {
|
|
23333
25343
|
throw new HttpError(503, {
|
|
23334
25344
|
code: "service_unavailable",
|
|
23335
25345
|
message: "Build and restart requires a Remote Codex source checkout. Set REMOTE_CODEX_REPO_ROOT to the checkout path, or update the npm package with npm install -g remote-codex@latest."
|
|
23336
25346
|
});
|
|
23337
25347
|
}
|
|
23338
|
-
const child =
|
|
25348
|
+
const child = spawn5(process.execPath, [restartScript, "launch"], {
|
|
23339
25349
|
cwd: repoRoot,
|
|
23340
25350
|
detached: true,
|
|
23341
25351
|
env: process.env,
|
|
@@ -23355,7 +25365,9 @@ function buildApp(options = {}) {
|
|
|
23355
25365
|
const pluginRegistry = new PluginRegistry(builtinPlugins);
|
|
23356
25366
|
const pluginSettingsStore = new PluginSettingsStore(database.db);
|
|
23357
25367
|
const pluginService = new PluginService(pluginRegistry, pluginSettingsStore);
|
|
25368
|
+
const authService = new AuthService(config);
|
|
23358
25369
|
const runtimeBootstrap = options.runtimeBootstrap ?? createAgentRuntimeBootstrap(config);
|
|
25370
|
+
const repoRoot = findRepoRoot();
|
|
23359
25371
|
const agentRuntimes = options.agentRuntimes ?? runtimeBootstrap.agentRuntimes;
|
|
23360
25372
|
const threadService = new ThreadService(
|
|
23361
25373
|
database.db,
|
|
@@ -23366,7 +25378,11 @@ function buildApp(options = {}) {
|
|
|
23366
25378
|
runtimeBootstrap.codexManagement,
|
|
23367
25379
|
pluginService
|
|
23368
25380
|
);
|
|
23369
|
-
const shellService = options.shellService ?? new ShellSessionService(
|
|
25381
|
+
const shellService = options.shellService ?? new ShellSessionService(
|
|
25382
|
+
database.db,
|
|
25383
|
+
eventBus,
|
|
25384
|
+
createTerminalShellBackend(options.env)
|
|
25385
|
+
);
|
|
23370
25386
|
const providerHostConfigService = new ProviderHostConfigService(
|
|
23371
25387
|
agentRuntimes,
|
|
23372
25388
|
runtimeBootstrap.providerHostHomes
|
|
@@ -23383,6 +25399,16 @@ function buildApp(options = {}) {
|
|
|
23383
25399
|
fileSize: MAX_PROMPT_ATTACHMENT_BYTES2
|
|
23384
25400
|
}
|
|
23385
25401
|
});
|
|
25402
|
+
const backendPluginHost = new BackendPluginHost(app2);
|
|
25403
|
+
backendPluginHost.register(createTerminalPluginBackendContribution());
|
|
25404
|
+
const relaySocketBridge = createRelaySocketBridge(app2, eventBus, backendPluginHost);
|
|
25405
|
+
const relayTunnelClient = config.mode === "relay" ? options.relayTunnelClient ?? new RelayTunnelClient(
|
|
25406
|
+
config.relay,
|
|
25407
|
+
createRelayRequestHandler(app2),
|
|
25408
|
+
relaySocketBridge.handleConnected,
|
|
25409
|
+
relaySocketBridge.handleMessage
|
|
25410
|
+
) : null;
|
|
25411
|
+
relayTunnelClient?.validateConfig();
|
|
23386
25412
|
app2.decorate("services", {
|
|
23387
25413
|
config,
|
|
23388
25414
|
database,
|
|
@@ -23393,7 +25419,29 @@ function buildApp(options = {}) {
|
|
|
23393
25419
|
shellService,
|
|
23394
25420
|
providerHostConfigService,
|
|
23395
25421
|
pluginRegistry,
|
|
23396
|
-
pluginService
|
|
25422
|
+
pluginService,
|
|
25423
|
+
authService,
|
|
25424
|
+
relayTunnelClient,
|
|
25425
|
+
repoRoot
|
|
25426
|
+
});
|
|
25427
|
+
app2.addHook("onRequest", async (request, reply) => {
|
|
25428
|
+
if (!authService.required) {
|
|
25429
|
+
return;
|
|
25430
|
+
}
|
|
25431
|
+
const requestPath = new URL(request.url, "http://localhost").pathname;
|
|
25432
|
+
if (!requestPath.startsWith("/api/")) {
|
|
25433
|
+
return;
|
|
25434
|
+
}
|
|
25435
|
+
if (requestPath === "/api/auth/login" || requestPath === "/api/auth/logout" || requestPath === "/api/auth/session") {
|
|
25436
|
+
return;
|
|
25437
|
+
}
|
|
25438
|
+
if (config.mode === "relay" && request.headers[RELAY_FORWARD_HEADER] === "1") {
|
|
25439
|
+
return;
|
|
25440
|
+
}
|
|
25441
|
+
const session = authService.verifyRequest(request);
|
|
25442
|
+
if (!session.authenticated) {
|
|
25443
|
+
return reply.status(401).send(unauthorizedPayload());
|
|
25444
|
+
}
|
|
23397
25445
|
});
|
|
23398
25446
|
app2.register(async (realtimeApp) => {
|
|
23399
25447
|
await realtimeApp.register(websocket);
|
|
@@ -23406,135 +25454,34 @@ function buildApp(options = {}) {
|
|
|
23406
25454
|
message: "Upgrade to websocket is required."
|
|
23407
25455
|
});
|
|
23408
25456
|
},
|
|
23409
|
-
wsHandler: (socket) => {
|
|
23410
|
-
|
|
23411
|
-
|
|
23412
|
-
|
|
23413
|
-
|
|
23414
|
-
}
|
|
25457
|
+
wsHandler: (socket, request) => {
|
|
25458
|
+
const session = authService.verifyRequest(request);
|
|
25459
|
+
if (!session.authenticated) {
|
|
25460
|
+
socket.close(1008, "Authentication is required.");
|
|
25461
|
+
return;
|
|
23415
25462
|
}
|
|
23416
|
-
|
|
23417
|
-
|
|
23418
|
-
|
|
23419
|
-
|
|
23420
|
-
|
|
23421
|
-
|
|
23422
|
-
|
|
23423
|
-
const unsubscribeShell = eventBus.onShellEvent((event) => {
|
|
23424
|
-
send(event);
|
|
23425
|
-
});
|
|
23426
|
-
socket.on("message", async (rawMessage) => {
|
|
23427
|
-
let parsed;
|
|
23428
|
-
try {
|
|
23429
|
-
parsed = JSON.parse(rawMessage.toString());
|
|
23430
|
-
} catch {
|
|
23431
|
-
return;
|
|
23432
|
-
}
|
|
23433
|
-
try {
|
|
23434
|
-
if (parsed.type === "shell.attach") {
|
|
23435
|
-
if (attachedShell && attachedShell.shellId !== parsed.shellId) {
|
|
23436
|
-
await shellService.detachShell(
|
|
23437
|
-
attachedShell.shellId,
|
|
23438
|
-
attachedShell.viewerId
|
|
23439
|
-
);
|
|
23440
|
-
attachedShell = null;
|
|
23441
|
-
}
|
|
23442
|
-
const attachment = await shellService.attachShell(parsed.shellId, {
|
|
23443
|
-
cols: parsed.cols,
|
|
23444
|
-
rows: parsed.rows,
|
|
23445
|
-
onData: (data, options2) => {
|
|
23446
|
-
send({
|
|
23447
|
-
type: "shell.output",
|
|
23448
|
-
shellId: parsed.shellId,
|
|
23449
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
23450
|
-
payload: {
|
|
23451
|
-
data,
|
|
23452
|
-
...options2?.replace ? { replace: true } : {},
|
|
23453
|
-
...options2?.cursorX !== void 0 ? { cursorX: options2.cursorX } : {},
|
|
23454
|
-
...options2?.cursorY !== void 0 ? { cursorY: options2.cursorY } : {},
|
|
23455
|
-
...options2?.paneHeight !== void 0 ? { paneHeight: options2.paneHeight } : {},
|
|
23456
|
-
...options2?.cwdBaseName !== void 0 ? { cwdBaseName: options2.cwdBaseName } : {},
|
|
23457
|
-
...options2?.envPrefix !== void 0 ? { envPrefix: options2.envPrefix } : {},
|
|
23458
|
-
...options2?.isCommandRunning !== void 0 ? { isCommandRunning: options2.isCommandRunning } : {}
|
|
23459
|
-
}
|
|
23460
|
-
});
|
|
23461
|
-
}
|
|
23462
|
-
});
|
|
23463
|
-
attachedShell = {
|
|
23464
|
-
shellId: parsed.shellId,
|
|
23465
|
-
viewerId: attachment.viewerId
|
|
23466
|
-
};
|
|
23467
|
-
send({
|
|
23468
|
-
type: "shell.connected",
|
|
23469
|
-
shellId: parsed.shellId,
|
|
23470
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
23471
|
-
payload: {
|
|
23472
|
-
viewerId: attachment.viewerId
|
|
23473
|
-
}
|
|
23474
|
-
});
|
|
23475
|
-
return;
|
|
23476
|
-
}
|
|
23477
|
-
if (parsed.type === "supervisor.ping") {
|
|
23478
|
-
send({
|
|
23479
|
-
type: "supervisor.pong",
|
|
23480
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
23481
|
-
payload: {
|
|
23482
|
-
requestTimestamp: typeof parsed.timestamp === "string" ? parsed.timestamp : null
|
|
23483
|
-
}
|
|
23484
|
-
});
|
|
23485
|
-
return;
|
|
23486
|
-
}
|
|
23487
|
-
if (parsed.type === "shell.detach") {
|
|
23488
|
-
await shellService.detachShell(parsed.shellId, parsed.viewerId);
|
|
23489
|
-
if (attachedShell?.shellId === parsed.shellId && attachedShell.viewerId === parsed.viewerId) {
|
|
23490
|
-
attachedShell = null;
|
|
23491
|
-
}
|
|
23492
|
-
return;
|
|
23493
|
-
}
|
|
23494
|
-
if (parsed.type === "shell.input") {
|
|
23495
|
-
await shellService.sendInput(
|
|
23496
|
-
parsed.shellId,
|
|
23497
|
-
parsed.viewerId,
|
|
23498
|
-
parsed.data
|
|
23499
|
-
);
|
|
23500
|
-
return;
|
|
23501
|
-
}
|
|
23502
|
-
if (parsed.type === "shell.resize") {
|
|
23503
|
-
await shellService.resizeShell(
|
|
23504
|
-
parsed.shellId,
|
|
23505
|
-
parsed.viewerId,
|
|
23506
|
-
parsed.cols,
|
|
23507
|
-
parsed.rows
|
|
23508
|
-
);
|
|
23509
|
-
return;
|
|
23510
|
-
}
|
|
23511
|
-
if (parsed.type === "shell.clear") {
|
|
23512
|
-
await shellService.clearShell(parsed.shellId, parsed.viewerId);
|
|
23513
|
-
}
|
|
23514
|
-
} catch (error) {
|
|
23515
|
-
if ("shellId" in parsed) {
|
|
23516
|
-
send(makeShellErrorEnvelope(parsed.shellId, error));
|
|
25463
|
+
const supervisorSession = createSupervisorSocketSession({
|
|
25464
|
+
app: app2,
|
|
25465
|
+
eventBus,
|
|
25466
|
+
backendPluginHost,
|
|
25467
|
+
send(message) {
|
|
25468
|
+
if (socket.readyState === 1) {
|
|
25469
|
+
socket.send(JSON.stringify(message));
|
|
23517
25470
|
}
|
|
23518
25471
|
}
|
|
23519
25472
|
});
|
|
25473
|
+
socket.on("message", async (rawMessage) => {
|
|
25474
|
+
await supervisorSession.handleMessage(rawMessage.toString());
|
|
25475
|
+
});
|
|
23520
25476
|
socket.on("close", () => {
|
|
23521
|
-
|
|
23522
|
-
void shellService.detachShell(
|
|
23523
|
-
attachedShell.shellId,
|
|
23524
|
-
attachedShell.viewerId
|
|
23525
|
-
).catch(() => {
|
|
23526
|
-
});
|
|
23527
|
-
attachedShell = null;
|
|
23528
|
-
}
|
|
23529
|
-
unsubscribe();
|
|
23530
|
-
unsubscribeShell();
|
|
25477
|
+
supervisorSession.close();
|
|
23531
25478
|
});
|
|
23532
25479
|
}
|
|
23533
25480
|
});
|
|
23534
25481
|
});
|
|
25482
|
+
app2.register(registerAuthRoutes);
|
|
23535
25483
|
app2.register(registerSystemRoutes);
|
|
23536
25484
|
app2.register(registerAgentRuntimeRoutes);
|
|
23537
|
-
app2.register(registerShellRoutes);
|
|
23538
25485
|
app2.register(registerPluginRoutes);
|
|
23539
25486
|
app2.register(registerThreadRoutes);
|
|
23540
25487
|
app2.register(registerWorkspaceRoutes);
|
|
@@ -23576,7 +25523,7 @@ function buildApp(options = {}) {
|
|
|
23576
25523
|
return;
|
|
23577
25524
|
}
|
|
23578
25525
|
if (error instanceof ShellServiceError) {
|
|
23579
|
-
const statusCode = error.code === "thread_not_found" || error.code === "shell_not_found" ? 404 : error.code === "viewer_conflict" || error.code === "thread_not_connected" || error.code === "shell_exists" || error.code === "workspace_missing" || error.code === "shell_not_running" || error.code === "viewer_not_attached" || error.code === "invalid_viewer" ? 409 : 503;
|
|
25526
|
+
const statusCode = error.code === "thread_not_found" || error.code === "shell_not_found" ? 404 : error.code === "viewer_conflict" || error.code === "thread_not_connected" || error.code === "shell_exists" || error.code === "workspace_missing" || error.code === "shell_not_running" || error.code === "viewer_not_attached" || error.code === "invalid_viewer" || error.code === "plugin_disabled" ? 409 : 503;
|
|
23580
25527
|
reply.status(statusCode).send({
|
|
23581
25528
|
code: statusCode === 404 ? "not_found" : statusCode === 409 ? "conflict" : "service_unavailable",
|
|
23582
25529
|
message: error.message,
|
|
@@ -23641,11 +25588,17 @@ function buildApp(options = {}) {
|
|
|
23641
25588
|
});
|
|
23642
25589
|
app2.addHook("onClose", async () => {
|
|
23643
25590
|
await shellService.stop();
|
|
25591
|
+
relayTunnelClient?.stop();
|
|
23644
25592
|
await Promise.all(agentRuntimes.all().map((runtime) => runtime.stop()));
|
|
23645
25593
|
database.sqlite.close();
|
|
23646
25594
|
});
|
|
23647
25595
|
app2.addHook("onReady", async () => {
|
|
23648
25596
|
try {
|
|
25597
|
+
await pluginService.syncManagedCodexMcpConfig({
|
|
25598
|
+
codexHome: runtimeBootstrap.providerHostHomes.codex ?? null,
|
|
25599
|
+
repoRoot
|
|
25600
|
+
});
|
|
25601
|
+
relayTunnelClient?.start();
|
|
23649
25602
|
await Promise.all(agentRuntimes.all().map((runtime) => runtime.start()));
|
|
23650
25603
|
await shellService.syncShellStateOnStartup();
|
|
23651
25604
|
} catch (error) {
|
|
@@ -23661,31 +25614,120 @@ function requestLog(app2, error) {
|
|
|
23661
25614
|
}
|
|
23662
25615
|
app2.log.error({ error }, "Non-error value reached Fastify error handler.");
|
|
23663
25616
|
}
|
|
23664
|
-
function
|
|
23665
|
-
|
|
25617
|
+
function createRelayRequestHandler(app2) {
|
|
25618
|
+
return async function handleRelayRequest(request) {
|
|
25619
|
+
const response = await app2.inject({
|
|
25620
|
+
method: request.method,
|
|
25621
|
+
url: request.path,
|
|
25622
|
+
headers: {
|
|
25623
|
+
...request.headers,
|
|
25624
|
+
[RELAY_FORWARD_HEADER]: "1"
|
|
25625
|
+
},
|
|
25626
|
+
...request.body !== null ? { payload: request.body } : {}
|
|
25627
|
+
});
|
|
23666
25628
|
return {
|
|
23667
|
-
|
|
23668
|
-
|
|
23669
|
-
|
|
23670
|
-
payload: {
|
|
23671
|
-
code: error.code,
|
|
23672
|
-
message: error.message
|
|
23673
|
-
}
|
|
25629
|
+
statusCode: response.statusCode,
|
|
25630
|
+
headers: relayResponseHeaders(response.headers),
|
|
25631
|
+
body: response.body
|
|
23674
25632
|
};
|
|
23675
|
-
}
|
|
25633
|
+
};
|
|
25634
|
+
}
|
|
25635
|
+
function createSupervisorSocketSession(input) {
|
|
25636
|
+
const closeHandlers = [];
|
|
25637
|
+
const socketState = /* @__PURE__ */ new Map();
|
|
25638
|
+
const onClose = (handler) => {
|
|
25639
|
+
closeHandlers.push(handler);
|
|
25640
|
+
};
|
|
25641
|
+
input.send({
|
|
25642
|
+
type: "supervisor.connected",
|
|
25643
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
25644
|
+
});
|
|
25645
|
+
const unsubscribeThread = input.eventBus.onThreadEvent((event) => {
|
|
25646
|
+
input.send(event);
|
|
25647
|
+
});
|
|
25648
|
+
const unsubscribeShell = input.eventBus.onShellEvent((event) => {
|
|
25649
|
+
input.send(event);
|
|
25650
|
+
});
|
|
23676
25651
|
return {
|
|
23677
|
-
|
|
23678
|
-
|
|
23679
|
-
|
|
23680
|
-
|
|
23681
|
-
|
|
23682
|
-
|
|
25652
|
+
async handleMessage(rawMessage) {
|
|
25653
|
+
let parsed;
|
|
25654
|
+
try {
|
|
25655
|
+
parsed = JSON.parse(rawMessage);
|
|
25656
|
+
} catch {
|
|
25657
|
+
return;
|
|
25658
|
+
}
|
|
25659
|
+
try {
|
|
25660
|
+
if (parsed.type === "supervisor.ping") {
|
|
25661
|
+
input.send({
|
|
25662
|
+
type: "supervisor.pong",
|
|
25663
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25664
|
+
payload: {
|
|
25665
|
+
requestTimestamp: typeof parsed.timestamp === "string" ? parsed.timestamp : null
|
|
25666
|
+
}
|
|
25667
|
+
});
|
|
25668
|
+
return;
|
|
25669
|
+
}
|
|
25670
|
+
await input.backendPluginHost.handleSocketMessage({
|
|
25671
|
+
app: input.app,
|
|
25672
|
+
send: input.send,
|
|
25673
|
+
onClose,
|
|
25674
|
+
state: socketState,
|
|
25675
|
+
message: parsed
|
|
25676
|
+
});
|
|
25677
|
+
} catch {
|
|
25678
|
+
return;
|
|
25679
|
+
}
|
|
25680
|
+
},
|
|
25681
|
+
close() {
|
|
25682
|
+
for (const handler of closeHandlers.splice(0)) {
|
|
25683
|
+
handler();
|
|
25684
|
+
}
|
|
25685
|
+
unsubscribeThread();
|
|
25686
|
+
unsubscribeShell();
|
|
25687
|
+
}
|
|
25688
|
+
};
|
|
25689
|
+
}
|
|
25690
|
+
function createRelaySocketBridge(app2, eventBus, backendPluginHost) {
|
|
25691
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
25692
|
+
return {
|
|
25693
|
+
handleConnected(clientId, send) {
|
|
25694
|
+
const existing = sessions.get(clientId);
|
|
25695
|
+
existing?.close();
|
|
25696
|
+
const session = createSupervisorSocketSession({
|
|
25697
|
+
app: app2,
|
|
25698
|
+
eventBus,
|
|
25699
|
+
backendPluginHost,
|
|
25700
|
+
send
|
|
25701
|
+
});
|
|
25702
|
+
sessions.set(clientId, session);
|
|
25703
|
+
return () => {
|
|
25704
|
+
session.close();
|
|
25705
|
+
sessions.delete(clientId);
|
|
25706
|
+
};
|
|
25707
|
+
},
|
|
25708
|
+
async handleMessage(clientId, message) {
|
|
25709
|
+
const session = sessions.get(clientId);
|
|
25710
|
+
if (!session) {
|
|
25711
|
+
return;
|
|
25712
|
+
}
|
|
25713
|
+
await session.handleMessage(JSON.stringify(message));
|
|
23683
25714
|
}
|
|
23684
25715
|
};
|
|
23685
25716
|
}
|
|
25717
|
+
function relayResponseHeaders(headers) {
|
|
25718
|
+
const output = {};
|
|
25719
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
25720
|
+
if (Array.isArray(value)) {
|
|
25721
|
+
output[name] = value.join(", ");
|
|
25722
|
+
} else if (value !== void 0) {
|
|
25723
|
+
output[name] = String(value);
|
|
25724
|
+
}
|
|
25725
|
+
}
|
|
25726
|
+
return output;
|
|
25727
|
+
}
|
|
23686
25728
|
|
|
23687
25729
|
// src/index.ts
|
|
23688
|
-
if (
|
|
25730
|
+
if (fs25.existsSync(".env")) {
|
|
23689
25731
|
process.loadEnvFile?.(".env");
|
|
23690
25732
|
}
|
|
23691
25733
|
var app = buildApp();
|