remote-codex 0.11.1 → 0.11.2

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.
Files changed (334) hide show
  1. package/apps/supervisor-api/dist/index.js +2271 -773
  2. package/apps/supervisor-web/dist/assets/core-DIQen2lE.js +12 -0
  3. package/apps/supervisor-web/dist/assets/{css-DPfMkruS.js → css-CLj8gQPS.js} +1 -1
  4. package/apps/supervisor-web/dist/assets/engine-javascript-DBd1bXLz.js +141 -0
  5. package/apps/supervisor-web/dist/assets/graph-vendor-C5ap-Sga.css +1 -0
  6. package/apps/supervisor-web/dist/assets/graph-vendor-CGzY-MFv.js +23 -0
  7. package/apps/supervisor-web/dist/assets/{html-GMplVEZG.js → html-pp8916En.js} +1 -1
  8. package/apps/supervisor-web/dist/assets/index-CbDzXN9T.css +1 -0
  9. package/apps/supervisor-web/dist/assets/index-DQpHiQXN.js +4 -0
  10. package/apps/supervisor-web/dist/assets/markdown-vendor-hBDTCSB-.js +291 -0
  11. package/apps/supervisor-web/dist/assets/react-vendor-o1Xrx7m4.js +60 -0
  12. package/apps/supervisor-web/dist/assets/{shellscript-Yzrsuije.js → shellscript-CEILq0vU.js} +1 -1
  13. package/apps/supervisor-web/dist/assets/{sql-BLtJtn59.js → sql-CRqJ_cUM.js} +1 -1
  14. package/apps/supervisor-web/dist/assets/terminal-vendor-Beg8tuEN.css +32 -0
  15. package/apps/supervisor-web/dist/assets/{xterm-D92BViLH.js → terminal-vendor-CLGgN91S.js} +17 -6
  16. package/apps/supervisor-web/dist/assets/thread-ui-BEieA99i.css +1 -0
  17. package/apps/supervisor-web/dist/assets/thread-ui-CDk3ExRH.js +3516 -0
  18. package/apps/supervisor-web/dist/assets/ui-vendor-CgOZX1B8.js +425 -0
  19. package/apps/supervisor-web/dist/index.html +11 -2
  20. package/apps/supervisor-web/dist/vendor/3Dmol-min.js +2 -0
  21. package/bin/remote-codex-plugin-mcp.mjs +140 -0
  22. package/package.json +6 -1
  23. package/packages/agent-runtime/src/types.ts +1 -0
  24. package/packages/agent-runtime/src/unavailable-runtime.ts +5 -13
  25. package/packages/claude/src/runtimeAdapter.ts +7 -5
  26. package/packages/codex/src/appServerManager.ts +3 -3
  27. package/packages/codex/src/historyItems.test.ts +2 -4
  28. package/packages/codex/src/historyItems.ts +13 -18
  29. package/packages/codex/src/runtime-errors.ts +0 -5
  30. package/packages/codex/src/runtimeAdapter.ts +3 -0
  31. package/packages/codex/src/types.ts +1 -0
  32. package/packages/db/migrations/0018_shell_session_label.sql +1 -0
  33. package/packages/db/src/repositories.ts +12 -0
  34. package/packages/db/src/schema.ts +1 -0
  35. package/packages/opencode/src/historyItems.ts +0 -17
  36. package/packages/plugin-terminal/package.json +17 -0
  37. package/packages/plugin-terminal/plugin.json +25 -0
  38. package/packages/plugin-terminal/src/index.ts +2 -0
  39. package/packages/plugin-terminal/src/manifest.ts +51 -0
  40. package/packages/shared/src/index.ts +59 -0
  41. package/apps/supervisor-api/dist/chunk-6M32PPHZ.js +0 -24507
  42. package/apps/supervisor-api/dist/chunk-7AA2MFXK.js +0 -24499
  43. package/apps/supervisor-api/dist/chunk-HKBFCPHH.js +0 -24511
  44. package/apps/supervisor-api/dist/worker-index.d.ts +0 -2
  45. package/apps/supervisor-api/dist/worker-index.js +0 -33
  46. package/apps/supervisor-web/dist/assets/abap-BdImnpbu.js +0 -1
  47. package/apps/supervisor-web/dist/assets/actionscript-3-CoDkCxhg.js +0 -1
  48. package/apps/supervisor-web/dist/assets/ada-bCR0ucgS.js +0 -1
  49. package/apps/supervisor-web/dist/assets/addon-fit-YJmn1quW.js +0 -12
  50. package/apps/supervisor-web/dist/assets/andromeeda-C4gqWexZ.js +0 -1
  51. package/apps/supervisor-web/dist/assets/angular-html-CU67Zn6k.js +0 -1
  52. package/apps/supervisor-web/dist/assets/angular-ts-BwZT4LLn.js +0 -1
  53. package/apps/supervisor-web/dist/assets/apache-Pmp26Uib.js +0 -1
  54. package/apps/supervisor-web/dist/assets/apex-D8_7TLub.js +0 -1
  55. package/apps/supervisor-web/dist/assets/apl-dKokRX4l.js +0 -1
  56. package/apps/supervisor-web/dist/assets/applescript-Co6uUVPk.js +0 -1
  57. package/apps/supervisor-web/dist/assets/ara-BRHolxvo.js +0 -1
  58. package/apps/supervisor-web/dist/assets/asciidoc-Ve4PFQV2.js +0 -1
  59. package/apps/supervisor-web/dist/assets/asm-D_Q5rh1f.js +0 -1
  60. package/apps/supervisor-web/dist/assets/astro-CbQHKStN.js +0 -1
  61. package/apps/supervisor-web/dist/assets/aurora-x-D-2ljcwZ.js +0 -1
  62. package/apps/supervisor-web/dist/assets/awk-DMzUqQB5.js +0 -1
  63. package/apps/supervisor-web/dist/assets/ayu-mirage-32ctXXKs.js +0 -1
  64. package/apps/supervisor-web/dist/assets/ballerina-BFfxhgS-.js +0 -1
  65. package/apps/supervisor-web/dist/assets/bat-BkioyH1T.js +0 -1
  66. package/apps/supervisor-web/dist/assets/beancount-k_qm7-4y.js +0 -1
  67. package/apps/supervisor-web/dist/assets/berry-uYugtg8r.js +0 -1
  68. package/apps/supervisor-web/dist/assets/bibtex-CHM0blh-.js +0 -1
  69. package/apps/supervisor-web/dist/assets/bicep-Bmn6On1c.js +0 -1
  70. package/apps/supervisor-web/dist/assets/bird2-DPOp833l.js +0 -1
  71. package/apps/supervisor-web/dist/assets/blade-D4QpJJKB.js +0 -1
  72. package/apps/supervisor-web/dist/assets/bsl-BO_Y6i37.js +0 -1
  73. package/apps/supervisor-web/dist/assets/c-BIGW1oBm.js +0 -1
  74. package/apps/supervisor-web/dist/assets/c3-eo99z4R2.js +0 -1
  75. package/apps/supervisor-web/dist/assets/cadence-Bv_4Rxtq.js +0 -1
  76. package/apps/supervisor-web/dist/assets/cairo-KRGpt6FW.js +0 -1
  77. package/apps/supervisor-web/dist/assets/catppuccin-frappe-DFWUc33u.js +0 -1
  78. package/apps/supervisor-web/dist/assets/catppuccin-latte-C9dUb6Cb.js +0 -1
  79. package/apps/supervisor-web/dist/assets/catppuccin-macchiato-DQyhUUbL.js +0 -1
  80. package/apps/supervisor-web/dist/assets/catppuccin-mocha-D87Tk5Gz.js +0 -1
  81. package/apps/supervisor-web/dist/assets/clarity-D53aC0YG.js +0 -1
  82. package/apps/supervisor-web/dist/assets/clojure-P80f7IUj.js +0 -1
  83. package/apps/supervisor-web/dist/assets/cmake-D1j8_8rp.js +0 -1
  84. package/apps/supervisor-web/dist/assets/cobol-nwyudZeR.js +0 -1
  85. package/apps/supervisor-web/dist/assets/codeowners-Bp6g37R7.js +0 -1
  86. package/apps/supervisor-web/dist/assets/codeql-DsOJ9woJ.js +0 -1
  87. package/apps/supervisor-web/dist/assets/coffee-Ch7k5sss.js +0 -1
  88. package/apps/supervisor-web/dist/assets/common-lisp-Cg-RD9OK.js +0 -1
  89. package/apps/supervisor-web/dist/assets/coq-DkFqJrB1.js +0 -1
  90. package/apps/supervisor-web/dist/assets/cpp-CofmeUqb.js +0 -1
  91. package/apps/supervisor-web/dist/assets/crystal-tKQVLTB8.js +0 -1
  92. package/apps/supervisor-web/dist/assets/csharp-COcwbKMJ.js +0 -1
  93. package/apps/supervisor-web/dist/assets/cue-D82EKSYY.js +0 -1
  94. package/apps/supervisor-web/dist/assets/cypher-COkxafJQ.js +0 -1
  95. package/apps/supervisor-web/dist/assets/d-85-TOEBH.js +0 -1
  96. package/apps/supervisor-web/dist/assets/dark-plus-C3mMm8J8.js +0 -1
  97. package/apps/supervisor-web/dist/assets/dart-CF10PKvl.js +0 -1
  98. package/apps/supervisor-web/dist/assets/dax-CEL-wOlO.js +0 -1
  99. package/apps/supervisor-web/dist/assets/desktop-BmXAJ9_W.js +0 -1
  100. package/apps/supervisor-web/dist/assets/diff-D97Zzqfu.js +0 -1
  101. package/apps/supervisor-web/dist/assets/docker-BcOcwvcX.js +0 -1
  102. package/apps/supervisor-web/dist/assets/dotenv-Da5cRb03.js +0 -1
  103. package/apps/supervisor-web/dist/assets/dracula-BzJJZx-M.js +0 -1
  104. package/apps/supervisor-web/dist/assets/dracula-soft-BXkSAIEj.js +0 -1
  105. package/apps/supervisor-web/dist/assets/dream-maker-BtqSS_iP.js +0 -1
  106. package/apps/supervisor-web/dist/assets/edge-BkV0erSs.js +0 -1
  107. package/apps/supervisor-web/dist/assets/elixir-CDX3lj18.js +0 -1
  108. package/apps/supervisor-web/dist/assets/elm-DbKCFpqz.js +0 -1
  109. package/apps/supervisor-web/dist/assets/emacs-lisp-C9XAeP06.js +0 -1
  110. package/apps/supervisor-web/dist/assets/erb-B12qg9BL.js +0 -1
  111. package/apps/supervisor-web/dist/assets/erlang-DsQrWhSR.js +0 -1
  112. package/apps/supervisor-web/dist/assets/everforest-dark-BgDCqdQA.js +0 -1
  113. package/apps/supervisor-web/dist/assets/everforest-light-C8M2exoo.js +0 -1
  114. package/apps/supervisor-web/dist/assets/fennel-BYunw83y.js +0 -1
  115. package/apps/supervisor-web/dist/assets/fish-BvzEVeQv.js +0 -1
  116. package/apps/supervisor-web/dist/assets/fluent-C4IJs8-o.js +0 -1
  117. package/apps/supervisor-web/dist/assets/fortran-fixed-form-CkoXwp7k.js +0 -1
  118. package/apps/supervisor-web/dist/assets/fortran-free-form-BxgE0vQu.js +0 -1
  119. package/apps/supervisor-web/dist/assets/fsharp-CXgrBDvD.js +0 -1
  120. package/apps/supervisor-web/dist/assets/gdresource-BOOCDP_w.js +0 -1
  121. package/apps/supervisor-web/dist/assets/gdscript-C5YyOfLZ.js +0 -1
  122. package/apps/supervisor-web/dist/assets/gdshader-DkwncUOv.js +0 -1
  123. package/apps/supervisor-web/dist/assets/genie-D0YGMca9.js +0 -1
  124. package/apps/supervisor-web/dist/assets/gherkin-DyxjwDmM.js +0 -1
  125. package/apps/supervisor-web/dist/assets/git-commit-F4YmCXRG.js +0 -1
  126. package/apps/supervisor-web/dist/assets/git-rebase-r7XF79zn.js +0 -1
  127. package/apps/supervisor-web/dist/assets/github-dark-DHJKELXO.js +0 -1
  128. package/apps/supervisor-web/dist/assets/github-dark-default-Cuk6v7N8.js +0 -1
  129. package/apps/supervisor-web/dist/assets/github-dark-dimmed-DH5Ifo-i.js +0 -1
  130. package/apps/supervisor-web/dist/assets/github-dark-high-contrast-E3gJ1_iC.js +0 -1
  131. package/apps/supervisor-web/dist/assets/github-light-DAi9KRSo.js +0 -1
  132. package/apps/supervisor-web/dist/assets/github-light-default-D7oLnXFd.js +0 -1
  133. package/apps/supervisor-web/dist/assets/github-light-high-contrast-BfjtVDDH.js +0 -1
  134. package/apps/supervisor-web/dist/assets/gleam-BspZqrRM.js +0 -1
  135. package/apps/supervisor-web/dist/assets/glimmer-js-Rg0-pVw9.js +0 -1
  136. package/apps/supervisor-web/dist/assets/glimmer-ts-U6CK756n.js +0 -1
  137. package/apps/supervisor-web/dist/assets/glsl-DplSGwfg.js +0 -1
  138. package/apps/supervisor-web/dist/assets/gn-n2N0HUVH.js +0 -1
  139. package/apps/supervisor-web/dist/assets/gnuplot-DdkO51Og.js +0 -1
  140. package/apps/supervisor-web/dist/assets/go-CxLEBnE3.js +0 -1
  141. package/apps/supervisor-web/dist/assets/graphql-ChdNCCLP.js +0 -1
  142. package/apps/supervisor-web/dist/assets/groovy-gcz8RCvz.js +0 -1
  143. package/apps/supervisor-web/dist/assets/gruvbox-dark-hard-CFHQjOhq.js +0 -1
  144. package/apps/supervisor-web/dist/assets/gruvbox-dark-medium-GsRaNv29.js +0 -1
  145. package/apps/supervisor-web/dist/assets/gruvbox-dark-soft-CVdnzihN.js +0 -1
  146. package/apps/supervisor-web/dist/assets/gruvbox-light-hard-CH1njM8p.js +0 -1
  147. package/apps/supervisor-web/dist/assets/gruvbox-light-medium-DRw_LuNl.js +0 -1
  148. package/apps/supervisor-web/dist/assets/gruvbox-light-soft-hJgmCMqR.js +0 -1
  149. package/apps/supervisor-web/dist/assets/hack-CaT9iCJl.js +0 -1
  150. package/apps/supervisor-web/dist/assets/haml-B8DHNrY2.js +0 -1
  151. package/apps/supervisor-web/dist/assets/handlebars-BL8al0AC.js +0 -1
  152. package/apps/supervisor-web/dist/assets/haskell-Df6bDoY_.js +0 -1
  153. package/apps/supervisor-web/dist/assets/haxe-CzTSHFRz.js +0 -1
  154. package/apps/supervisor-web/dist/assets/hcl-BWvSN4gD.js +0 -1
  155. package/apps/supervisor-web/dist/assets/highlighted-body-OFNGDK62-p31aS0f0.js +0 -1
  156. package/apps/supervisor-web/dist/assets/hjson-D5-asLiD.js +0 -1
  157. package/apps/supervisor-web/dist/assets/hlsl-D3lLCCz7.js +0 -1
  158. package/apps/supervisor-web/dist/assets/horizon-BUw7H-hv.js +0 -1
  159. package/apps/supervisor-web/dist/assets/horizon-bright-Cn-bp-IR.js +0 -1
  160. package/apps/supervisor-web/dist/assets/houston-DnULxvSX.js +0 -1
  161. package/apps/supervisor-web/dist/assets/html-derivative-BFtXZ54Q.js +0 -1
  162. package/apps/supervisor-web/dist/assets/http-jrhK8wxY.js +0 -1
  163. package/apps/supervisor-web/dist/assets/hurl-irOxFIW8.js +0 -1
  164. package/apps/supervisor-web/dist/assets/hxml-Bvhsp5Yf.js +0 -1
  165. package/apps/supervisor-web/dist/assets/hy-DFXneXwc.js +0 -1
  166. package/apps/supervisor-web/dist/assets/imba-DGztddWO.js +0 -1
  167. package/apps/supervisor-web/dist/assets/index-BiuFei_K.css +0 -32
  168. package/apps/supervisor-web/dist/assets/index-D1R9CUnx.js +0 -2161
  169. package/apps/supervisor-web/dist/assets/ini-BEwlwnbL.js +0 -1
  170. package/apps/supervisor-web/dist/assets/java-CylS5w8V.js +0 -1
  171. package/apps/supervisor-web/dist/assets/jinja-4LBKfQ-Z.js +0 -1
  172. package/apps/supervisor-web/dist/assets/jison-wvAkD_A8.js +0 -1
  173. package/apps/supervisor-web/dist/assets/json5-C9tS-k6U.js +0 -1
  174. package/apps/supervisor-web/dist/assets/jsonc-Des-eS-w.js +0 -1
  175. package/apps/supervisor-web/dist/assets/jsonl-DcaNXYhu.js +0 -1
  176. package/apps/supervisor-web/dist/assets/jsonnet-DFQXde-d.js +0 -1
  177. package/apps/supervisor-web/dist/assets/jssm-C2t-YnRu.js +0 -1
  178. package/apps/supervisor-web/dist/assets/julia-CxzCAyBv.js +0 -1
  179. package/apps/supervisor-web/dist/assets/just-Cw27pwNe.js +0 -1
  180. package/apps/supervisor-web/dist/assets/kanagawa-dragon-CkXjmgJE.js +0 -1
  181. package/apps/supervisor-web/dist/assets/kanagawa-lotus-CfQXZHmo.js +0 -1
  182. package/apps/supervisor-web/dist/assets/kanagawa-wave-DWedfzmr.js +0 -1
  183. package/apps/supervisor-web/dist/assets/kdl-DV7GczEv.js +0 -1
  184. package/apps/supervisor-web/dist/assets/kotlin-BdnUsdx6.js +0 -1
  185. package/apps/supervisor-web/dist/assets/kusto-DZf3V79B.js +0 -1
  186. package/apps/supervisor-web/dist/assets/laserwave-DUszq2jm.js +0 -1
  187. package/apps/supervisor-web/dist/assets/latex-CWtU0Tv5.js +0 -1
  188. package/apps/supervisor-web/dist/assets/lean-BZvkOJ9d.js +0 -1
  189. package/apps/supervisor-web/dist/assets/less-B1dDrJ26.js +0 -1
  190. package/apps/supervisor-web/dist/assets/light-plus-B7mTdjB0.js +0 -1
  191. package/apps/supervisor-web/dist/assets/liquid-DYVedYrR.js +0 -1
  192. package/apps/supervisor-web/dist/assets/llvm-DjAJT7YJ.js +0 -1
  193. package/apps/supervisor-web/dist/assets/log-2UxHyX5q.js +0 -1
  194. package/apps/supervisor-web/dist/assets/logo-BtOb2qkB.js +0 -1
  195. package/apps/supervisor-web/dist/assets/lua-BaeVxFsk.js +0 -1
  196. package/apps/supervisor-web/dist/assets/luau-C-HG3fhB.js +0 -1
  197. package/apps/supervisor-web/dist/assets/make-CHLpvVh8.js +0 -1
  198. package/apps/supervisor-web/dist/assets/marko-CnJfTvn9.js +0 -1
  199. package/apps/supervisor-web/dist/assets/material-theme-D5KoaKCx.js +0 -1
  200. package/apps/supervisor-web/dist/assets/material-theme-darker-BfHTSMKl.js +0 -1
  201. package/apps/supervisor-web/dist/assets/material-theme-lighter-B0m2ddpp.js +0 -1
  202. package/apps/supervisor-web/dist/assets/material-theme-ocean-CyktbL80.js +0 -1
  203. package/apps/supervisor-web/dist/assets/material-theme-palenight-Csfq5Kiy.js +0 -1
  204. package/apps/supervisor-web/dist/assets/matlab-D7o27uSR.js +0 -1
  205. package/apps/supervisor-web/dist/assets/mdc-BMNejdWA.js +0 -1
  206. package/apps/supervisor-web/dist/assets/mdx-Cmh6b_Ma.js +0 -1
  207. package/apps/supervisor-web/dist/assets/mermaid-mWjccvbQ.js +0 -1
  208. package/apps/supervisor-web/dist/assets/min-dark-CafNBF8u.js +0 -1
  209. package/apps/supervisor-web/dist/assets/min-light-CTRr51gU.js +0 -1
  210. package/apps/supervisor-web/dist/assets/mipsasm-CKIfxQSi.js +0 -1
  211. package/apps/supervisor-web/dist/assets/mojo-rZm6bMo-.js +0 -1
  212. package/apps/supervisor-web/dist/assets/monokai-D4h5O-jR.js +0 -1
  213. package/apps/supervisor-web/dist/assets/moonbit-_H4v1dQx.js +0 -1
  214. package/apps/supervisor-web/dist/assets/move-IF9eRakj.js +0 -1
  215. package/apps/supervisor-web/dist/assets/narrat-DRg8JJMk.js +0 -1
  216. package/apps/supervisor-web/dist/assets/nextflow-Zz6hmt5N.js +0 -1
  217. package/apps/supervisor-web/dist/assets/nextflow-groovy-BeH2EWoN.js +0 -1
  218. package/apps/supervisor-web/dist/assets/nginx-BpAMiNFr.js +0 -1
  219. package/apps/supervisor-web/dist/assets/night-owl-C39BiMTA.js +0 -1
  220. package/apps/supervisor-web/dist/assets/night-owl-light-CMTm3GFP.js +0 -1
  221. package/apps/supervisor-web/dist/assets/nim-CVrawwO9.js +0 -1
  222. package/apps/supervisor-web/dist/assets/nix-CwoSXNpI.js +0 -1
  223. package/apps/supervisor-web/dist/assets/nord-Ddv68eIx.js +0 -1
  224. package/apps/supervisor-web/dist/assets/nushell-Cz2AlsmD.js +0 -1
  225. package/apps/supervisor-web/dist/assets/objective-c-DXmwc3jG.js +0 -1
  226. package/apps/supervisor-web/dist/assets/objective-cpp-CLxacb5B.js +0 -1
  227. package/apps/supervisor-web/dist/assets/ocaml-C0hk2d4L.js +0 -1
  228. package/apps/supervisor-web/dist/assets/odin-BBf5iR-q.js +0 -1
  229. package/apps/supervisor-web/dist/assets/one-dark-pro-DVMEJ2y_.js +0 -1
  230. package/apps/supervisor-web/dist/assets/one-light-C3Wv6jpd.js +0 -1
  231. package/apps/supervisor-web/dist/assets/openscad-C4EeE6gA.js +0 -1
  232. package/apps/supervisor-web/dist/assets/pascal-D93ZcfNL.js +0 -1
  233. package/apps/supervisor-web/dist/assets/perl-C0TMdlhV.js +0 -1
  234. package/apps/supervisor-web/dist/assets/php-Dhbhpdrm.js +0 -1
  235. package/apps/supervisor-web/dist/assets/pkl-u5AG7uiY.js +0 -1
  236. package/apps/supervisor-web/dist/assets/plastic-3e1v2bzS.js +0 -1
  237. package/apps/supervisor-web/dist/assets/plsql-ChMvpjG-.js +0 -1
  238. package/apps/supervisor-web/dist/assets/po-BTJTHyun.js +0 -1
  239. package/apps/supervisor-web/dist/assets/poimandres-CS3Unz2-.js +0 -1
  240. package/apps/supervisor-web/dist/assets/polar-C0HS_06l.js +0 -1
  241. package/apps/supervisor-web/dist/assets/postcss-CXtECtnM.js +0 -1
  242. package/apps/supervisor-web/dist/assets/powerquery-CEu0bR-o.js +0 -1
  243. package/apps/supervisor-web/dist/assets/powershell-Dpen1YoG.js +0 -1
  244. package/apps/supervisor-web/dist/assets/prisma-Dd19v3D-.js +0 -1
  245. package/apps/supervisor-web/dist/assets/prolog-CbFg5uaA.js +0 -1
  246. package/apps/supervisor-web/dist/assets/proto-C7zT0LnQ.js +0 -1
  247. package/apps/supervisor-web/dist/assets/pug-CGlum2m_.js +0 -1
  248. package/apps/supervisor-web/dist/assets/puppet-BMWR74SV.js +0 -1
  249. package/apps/supervisor-web/dist/assets/purescript-CklMAg4u.js +0 -1
  250. package/apps/supervisor-web/dist/assets/qml-3beO22l8.js +0 -1
  251. package/apps/supervisor-web/dist/assets/qmldir-C8lEn-DE.js +0 -1
  252. package/apps/supervisor-web/dist/assets/qss-IeuSbFQv.js +0 -1
  253. package/apps/supervisor-web/dist/assets/r-Dspwwk_N.js +0 -1
  254. package/apps/supervisor-web/dist/assets/racket-BqYA7rlc.js +0 -1
  255. package/apps/supervisor-web/dist/assets/raku-DXvB9xmW.js +0 -1
  256. package/apps/supervisor-web/dist/assets/razor-Uh8Bk_45.js +0 -1
  257. package/apps/supervisor-web/dist/assets/red-bN70gL4F.js +0 -1
  258. package/apps/supervisor-web/dist/assets/reg-C-SQnVFl.js +0 -1
  259. package/apps/supervisor-web/dist/assets/regexp-CDVJQ6XC.js +0 -1
  260. package/apps/supervisor-web/dist/assets/rel-C3B-1QV4.js +0 -1
  261. package/apps/supervisor-web/dist/assets/riscv-BM1_JUlF.js +0 -1
  262. package/apps/supervisor-web/dist/assets/ron-D8l8udqQ.js +0 -1
  263. package/apps/supervisor-web/dist/assets/rose-pine-dawn-DHQR4-dF.js +0 -1
  264. package/apps/supervisor-web/dist/assets/rose-pine-moon-D4_iv3hh.js +0 -1
  265. package/apps/supervisor-web/dist/assets/rose-pine-qdsjHGoJ.js +0 -1
  266. package/apps/supervisor-web/dist/assets/rosmsg-BJDFO7_C.js +0 -1
  267. package/apps/supervisor-web/dist/assets/rst-BrH8l1NY.js +0 -1
  268. package/apps/supervisor-web/dist/assets/ruby-Dw2BHqvy.js +0 -1
  269. package/apps/supervisor-web/dist/assets/rust-B1yitclQ.js +0 -1
  270. package/apps/supervisor-web/dist/assets/sas-cz2c8ADy.js +0 -1
  271. package/apps/supervisor-web/dist/assets/sass-Cj5Yp3dK.js +0 -1
  272. package/apps/supervisor-web/dist/assets/scala-C151Ov-r.js +0 -1
  273. package/apps/supervisor-web/dist/assets/scheme-C98Dy4si.js +0 -1
  274. package/apps/supervisor-web/dist/assets/scss-OYdSNvt2.js +0 -1
  275. package/apps/supervisor-web/dist/assets/sdbl-DVxCFoDh.js +0 -1
  276. package/apps/supervisor-web/dist/assets/shaderlab-Dg9Lc6iA.js +0 -1
  277. package/apps/supervisor-web/dist/assets/shellsession-BADoaaVG.js +0 -1
  278. package/apps/supervisor-web/dist/assets/slack-dark-BthQWCQV.js +0 -1
  279. package/apps/supervisor-web/dist/assets/slack-ochin-DqwNpetd.js +0 -1
  280. package/apps/supervisor-web/dist/assets/smalltalk-BERRCDM3.js +0 -1
  281. package/apps/supervisor-web/dist/assets/snazzy-light-Bw305WKR.js +0 -1
  282. package/apps/supervisor-web/dist/assets/solarized-dark-DXbdFlpD.js +0 -1
  283. package/apps/supervisor-web/dist/assets/solarized-light-L9t79GZl.js +0 -1
  284. package/apps/supervisor-web/dist/assets/solidity-rGO070M0.js +0 -1
  285. package/apps/supervisor-web/dist/assets/soy-Brmx7dQM.js +0 -1
  286. package/apps/supervisor-web/dist/assets/sparql-rVzFXLq3.js +0 -1
  287. package/apps/supervisor-web/dist/assets/splunk-BtCnVYZw.js +0 -1
  288. package/apps/supervisor-web/dist/assets/ssh-config-_ykCGR6B.js +0 -1
  289. package/apps/supervisor-web/dist/assets/stata-BH5u7GGu.js +0 -1
  290. package/apps/supervisor-web/dist/assets/stylus-BEDo0Tqx.js +0 -1
  291. package/apps/supervisor-web/dist/assets/surrealql-Bq5Q-fJD.js +0 -1
  292. package/apps/supervisor-web/dist/assets/svelte-C_ipcX3V.js +0 -1
  293. package/apps/supervisor-web/dist/assets/swift-D82vCrfD.js +0 -1
  294. package/apps/supervisor-web/dist/assets/synthwave-84-CbfX1IO0.js +0 -1
  295. package/apps/supervisor-web/dist/assets/system-verilog-CnnmHF94.js +0 -1
  296. package/apps/supervisor-web/dist/assets/systemd-4A_iFExJ.js +0 -1
  297. package/apps/supervisor-web/dist/assets/talonscript-CkByrt1z.js +0 -1
  298. package/apps/supervisor-web/dist/assets/tasl-QIJgUcNo.js +0 -1
  299. package/apps/supervisor-web/dist/assets/tcl-dwOrl1Do.js +0 -1
  300. package/apps/supervisor-web/dist/assets/templ-P3uqSqPl.js +0 -1
  301. package/apps/supervisor-web/dist/assets/terraform-BETggiCN.js +0 -1
  302. package/apps/supervisor-web/dist/assets/tex-idrVyKtj.js +0 -1
  303. package/apps/supervisor-web/dist/assets/tokyo-night-hegEt444.js +0 -1
  304. package/apps/supervisor-web/dist/assets/ts-tags-zn1MmPIZ.js +0 -1
  305. package/apps/supervisor-web/dist/assets/tsv-B_m7g4N7.js +0 -1
  306. package/apps/supervisor-web/dist/assets/turtle-BsS91CYL.js +0 -1
  307. package/apps/supervisor-web/dist/assets/twig-DNn4PbVi.js +0 -1
  308. package/apps/supervisor-web/dist/assets/typespec-BGHnOYBU.js +0 -1
  309. package/apps/supervisor-web/dist/assets/typst-DHCkPAjA.js +0 -1
  310. package/apps/supervisor-web/dist/assets/v-BcVCzyr7.js +0 -1
  311. package/apps/supervisor-web/dist/assets/vala-CsfeWuGM.js +0 -1
  312. package/apps/supervisor-web/dist/assets/vb-D17OF-Vu.js +0 -1
  313. package/apps/supervisor-web/dist/assets/verilog-BQ8w6xss.js +0 -1
  314. package/apps/supervisor-web/dist/assets/vesper-DU1UobuO.js +0 -1
  315. package/apps/supervisor-web/dist/assets/vhdl-CeAyd5Ju.js +0 -1
  316. package/apps/supervisor-web/dist/assets/viml-CJc9bBzg.js +0 -1
  317. package/apps/supervisor-web/dist/assets/vitesse-black-Bkuqu6BP.js +0 -1
  318. package/apps/supervisor-web/dist/assets/vitesse-dark-D0r3Knsf.js +0 -1
  319. package/apps/supervisor-web/dist/assets/vitesse-light-CVO1_9PV.js +0 -1
  320. package/apps/supervisor-web/dist/assets/vue-DN_0RTcg.js +0 -1
  321. package/apps/supervisor-web/dist/assets/vue-html-AaS7Mt5G.js +0 -1
  322. package/apps/supervisor-web/dist/assets/vue-vine-CQOfvN7w.js +0 -1
  323. package/apps/supervisor-web/dist/assets/vyper-CDx5xZoG.js +0 -1
  324. package/apps/supervisor-web/dist/assets/wasm-CG6Dc4jp.js +0 -1
  325. package/apps/supervisor-web/dist/assets/wasm-MzD3tlZU.js +0 -1
  326. package/apps/supervisor-web/dist/assets/wenyan-BV7otONQ.js +0 -1
  327. package/apps/supervisor-web/dist/assets/wgsl-Dx-B1_4e.js +0 -1
  328. package/apps/supervisor-web/dist/assets/wikitext-BhOHFoWU.js +0 -1
  329. package/apps/supervisor-web/dist/assets/wit-5i3qLPDT.js +0 -1
  330. package/apps/supervisor-web/dist/assets/wolfram-lXgVvXCa.js +0 -1
  331. package/apps/supervisor-web/dist/assets/xml-sdJ4AIDG.js +0 -1
  332. package/apps/supervisor-web/dist/assets/xsl-CtQFsRM5.js +0 -1
  333. package/apps/supervisor-web/dist/assets/zenscript-DVFEvuxE.js +0 -1
  334. package/apps/supervisor-web/dist/assets/zig-VOosw3JB.js +0 -1
@@ -5,15 +5,15 @@ var __export = (target, all) => {
5
5
  };
6
6
 
7
7
  // src/index.ts
8
- import fs22 from "fs";
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 spawn4 } from "child_process";
15
- import fs21 from "fs";
16
- import path21 from "path";
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
@@ -564,6 +564,7 @@ var PluginRegistry = class {
564
564
 
565
565
  // ../../packages/plugin-runtime/src/artifacts.ts
566
566
  var artifactFenceLanguages = /* @__PURE__ */ new Set(["artifact", "remote-codex-artifact"]);
567
+ var remoteCodexMoleculeMcpToolName = "remote_codex_render_molecule";
567
568
  function stableArtifactId(input) {
568
569
  return [
569
570
  "artifact",
@@ -634,6 +635,134 @@ function findFencedBlocks(text2, languages) {
634
635
  }
635
636
  return blocks;
636
637
  }
638
+ function readBalancedJsonFragment(text2, startIndex) {
639
+ const opener = text2[startIndex];
640
+ const expectedClose = opener === "{" ? "}" : opener === "[" ? "]" : null;
641
+ if (!expectedClose) {
642
+ return null;
643
+ }
644
+ const stack = [expectedClose];
645
+ let inString = false;
646
+ let escaping = false;
647
+ for (let index = startIndex + 1; index < text2.length; index += 1) {
648
+ const char = text2[index];
649
+ if (inString) {
650
+ if (escaping) {
651
+ escaping = false;
652
+ } else if (char === "\\") {
653
+ escaping = true;
654
+ } else if (char === '"') {
655
+ inString = false;
656
+ }
657
+ continue;
658
+ }
659
+ if (char === '"') {
660
+ inString = true;
661
+ continue;
662
+ }
663
+ if (char === "{") {
664
+ stack.push("}");
665
+ continue;
666
+ }
667
+ if (char === "[") {
668
+ stack.push("]");
669
+ continue;
670
+ }
671
+ if (char === stack.at(-1)) {
672
+ stack.pop();
673
+ if (stack.length === 0) {
674
+ return text2.slice(startIndex, index + 1);
675
+ }
676
+ }
677
+ }
678
+ return null;
679
+ }
680
+ function containsArtifactFence(text2) {
681
+ return text2.includes("```artifact") || text2.includes("```remote-codex-artifact") || text2.includes("~~~artifact") || text2.includes("~~~remote-codex-artifact");
682
+ }
683
+ function collectArtifactCandidateStrings(value, output, budget, depth = 0) {
684
+ if (output.length >= 20 || depth > 12 || budget.nodes <= 0) {
685
+ return;
686
+ }
687
+ budget.nodes -= 1;
688
+ if (typeof value === "string") {
689
+ if (containsArtifactFence(value)) {
690
+ output.push(value);
691
+ }
692
+ return;
693
+ }
694
+ if (Array.isArray(value)) {
695
+ for (const entry of value) {
696
+ collectArtifactCandidateStrings(entry, output, budget, depth + 1);
697
+ if (output.length >= 20 || budget.nodes <= 0) {
698
+ break;
699
+ }
700
+ }
701
+ return;
702
+ }
703
+ if (value && typeof value === "object") {
704
+ for (const entry of Object.values(value)) {
705
+ collectArtifactCandidateStrings(entry, output, budget, depth + 1);
706
+ if (output.length >= 20 || budget.nodes <= 0) {
707
+ break;
708
+ }
709
+ }
710
+ }
711
+ }
712
+ function parseJsonArtifactCandidateStrings(fragment, output) {
713
+ try {
714
+ collectArtifactCandidateStrings(JSON.parse(fragment), output, { nodes: 2e3 });
715
+ } catch {
716
+ }
717
+ }
718
+ function findJsonFragmentAt(text2, index) {
719
+ let startIndex = index;
720
+ while (startIndex < text2.length && /\s/.test(text2[startIndex] ?? "")) {
721
+ startIndex += 1;
722
+ }
723
+ const char = text2[startIndex];
724
+ if (char !== "{" && char !== "[") {
725
+ return null;
726
+ }
727
+ return readBalancedJsonFragment(text2, startIndex);
728
+ }
729
+ function extractToolJsonArtifactCandidateStrings(text2) {
730
+ const values = [];
731
+ const seenFragments = /* @__PURE__ */ new Set();
732
+ const addFragment = (fragment) => {
733
+ if (!fragment || seenFragments.has(fragment)) {
734
+ return;
735
+ }
736
+ seenFragments.add(fragment);
737
+ parseJsonArtifactCandidateStrings(fragment, values);
738
+ };
739
+ addFragment(findJsonFragmentAt(text2, 0));
740
+ const labelPattern = /(?:^|\n)(?:Arguments|Result)\n/g;
741
+ for (const match of text2.matchAll(labelPattern)) {
742
+ addFragment(findJsonFragmentAt(text2, match.index + match[0].length));
743
+ if (seenFragments.size >= 4 || values.length >= 20) {
744
+ break;
745
+ }
746
+ }
747
+ return values;
748
+ }
749
+ function extractArtifactCandidateTexts(item, text2) {
750
+ const values = [text2];
751
+ if (item.kind !== "toolCall" || ![item.text, item.previewText, text2].some(
752
+ (value) => typeof value === "string" && value.includes(remoteCodexMoleculeMcpToolName)
753
+ )) {
754
+ return values;
755
+ }
756
+ const seen = new Set(values);
757
+ for (const value of extractToolJsonArtifactCandidateStrings(text2)) {
758
+ if (seen.has(value)) {
759
+ continue;
760
+ }
761
+ seen.add(value);
762
+ values.push(value);
763
+ }
764
+ return values;
765
+ }
637
766
  var ManifestArtifactExtractor = class {
638
767
  constructor(manifests) {
639
768
  this.manifests = manifests;
@@ -650,46 +779,57 @@ var ManifestArtifactExtractor = class {
650
779
  return results;
651
780
  }
652
781
  extractFromItem(turn, item, context) {
653
- if (item.kind === "artifact" || !item.text.trim()) {
782
+ const extractableText = [item.text, item.detailText ?? ""].map((entry) => entry.trim()).filter(Boolean).join("\n\n");
783
+ if (item.kind === "artifact" || !extractableText) {
654
784
  return [];
655
785
  }
656
786
  const artifacts = [];
657
- artifacts.push(...this.extractJsonArtifacts(turn, item, context));
787
+ artifacts.push(...this.extractJsonArtifacts(turn, item, context, extractableText));
658
788
  return artifacts;
659
789
  }
660
- extractJsonArtifacts(turn, item, context) {
790
+ extractJsonArtifacts(turn, item, context, text2) {
661
791
  const artifacts = [];
662
- for (const block of findFencedBlocks(item.text, artifactFenceLanguages)) {
663
- if (!block.content) {
664
- continue;
665
- }
666
- let parsed;
667
- try {
668
- parsed = JSON.parse(block.content);
669
- } catch {
670
- continue;
671
- }
672
- const payload = maybeParseArtifactPayload(parsed);
673
- if (!payload || !this.hasArtifactType(payload.artifactType)) {
674
- continue;
675
- }
676
- artifacts.push({
677
- id: stableArtifactId({
678
- turnId: turn.id,
679
- itemId: item.id,
792
+ const extractableTexts = extractArtifactCandidateTexts(item, text2);
793
+ const seenBlocks = /* @__PURE__ */ new Set();
794
+ for (const extractableText of extractableTexts) {
795
+ for (const block of findFencedBlocks(extractableText, artifactFenceLanguages)) {
796
+ const blockKey = `${block.language}
797
+ ${block.content}`;
798
+ if (seenBlocks.has(blockKey)) {
799
+ continue;
800
+ }
801
+ seenBlocks.add(blockKey);
802
+ if (!block.content) {
803
+ continue;
804
+ }
805
+ let parsed;
806
+ try {
807
+ parsed = JSON.parse(block.content);
808
+ } catch {
809
+ continue;
810
+ }
811
+ const payload = maybeParseArtifactPayload(parsed);
812
+ if (!payload || !this.hasArtifactType(payload.artifactType)) {
813
+ continue;
814
+ }
815
+ artifacts.push({
816
+ id: stableArtifactId({
817
+ turnId: turn.id,
818
+ itemId: item.id,
819
+ pluginId: this.pluginIdForArtifactType(payload.artifactType) ?? "unknown",
820
+ artifactType: payload.artifactType,
821
+ index: artifacts.length
822
+ }),
680
823
  pluginId: this.pluginIdForArtifactType(payload.artifactType) ?? "unknown",
681
- artifactType: payload.artifactType,
682
- index: artifacts.length
683
- }),
684
- pluginId: this.pluginIdForArtifactType(payload.artifactType) ?? "unknown",
685
- type: payload.artifactType,
686
- title: payload.title ?? "Plugin artifact",
687
- summaryText: payload.summaryText ?? null,
688
- payload: payload.payload,
689
- sourceTurnId: turn.id,
690
- sourceItemId: item.id,
691
- createdAt: context.now
692
- });
824
+ type: payload.artifactType,
825
+ title: payload.title ?? "Plugin artifact",
826
+ summaryText: payload.summaryText ?? null,
827
+ payload: payload.payload,
828
+ sourceTurnId: turn.id,
829
+ sourceItemId: item.id,
830
+ createdAt: context.now
831
+ });
832
+ }
693
833
  }
694
834
  return artifacts;
695
835
  }
@@ -760,6 +900,19 @@ function optionalStringArray(value, field) {
760
900
  }
761
901
  return value;
762
902
  }
903
+ function optionalStringRecord(value, field) {
904
+ if (value === void 0) {
905
+ return void 0;
906
+ }
907
+ if (!isRecord2(value)) {
908
+ throw new Error(`Plugin manifest field "${field}" must be an object.`);
909
+ }
910
+ const entries = Object.entries(value);
911
+ if (entries.some(([, entry]) => typeof entry !== "string")) {
912
+ throw new Error(`Plugin manifest field "${field}" must contain string values.`);
913
+ }
914
+ return Object.fromEntries(entries);
915
+ }
763
916
  function parsePluginManifest(value) {
764
917
  if (!isRecord2(value)) {
765
918
  throw new Error("Plugin manifest must be an object.");
@@ -788,6 +941,14 @@ function parsePluginManifest(value) {
788
941
  if (backend !== void 0 && !isRecord2(backend)) {
789
942
  throw new Error('Plugin manifest field "capabilities.backend" must be an object.');
790
943
  }
944
+ const modelHints = capabilities.modelHints;
945
+ if (modelHints !== void 0 && !Array.isArray(modelHints)) {
946
+ throw new Error('Plugin manifest field "capabilities.modelHints" must be an array.');
947
+ }
948
+ const mcpServers = capabilities.mcpServers;
949
+ if (mcpServers !== void 0 && !Array.isArray(mcpServers)) {
950
+ throw new Error('Plugin manifest field "capabilities.mcpServers" must be an array.');
951
+ }
791
952
  return {
792
953
  id: assertString(value.id, "id"),
793
954
  name: assertString(value.name, "name"),
@@ -824,12 +985,46 @@ function parsePluginManifest(value) {
824
985
  return {
825
986
  id: assertString(entry.id, `capabilities.threadPanels[${index}].id`),
826
987
  label: assertString(entry.label, `capabilities.threadPanels[${index}].label`),
988
+ ...typeof entry.kind === "string" ? { kind: entry.kind } : {},
827
989
  artifactTypes: optionalStringArray(
828
990
  entry.artifactTypes,
829
991
  `capabilities.threadPanels[${index}].artifactTypes`
830
992
  ) ?? []
831
993
  };
832
994
  }),
995
+ modelHints: (modelHints ?? []).map((entry, index) => {
996
+ if (!isRecord2(entry)) {
997
+ throw new Error(
998
+ `Plugin manifest field "capabilities.modelHints[${index}]" must be an object.`
999
+ );
1000
+ }
1001
+ return {
1002
+ id: assertString(entry.id, `capabilities.modelHints[${index}].id`),
1003
+ text: assertString(entry.text, `capabilities.modelHints[${index}].text`)
1004
+ };
1005
+ }),
1006
+ mcpServers: (mcpServers ?? []).map((entry, index) => {
1007
+ if (!isRecord2(entry)) {
1008
+ throw new Error(
1009
+ `Plugin manifest field "capabilities.mcpServers[${index}]" must be an object.`
1010
+ );
1011
+ }
1012
+ const args = optionalStringArray(
1013
+ entry.args,
1014
+ `capabilities.mcpServers[${index}].args`
1015
+ );
1016
+ const env = optionalStringRecord(
1017
+ entry.env,
1018
+ `capabilities.mcpServers[${index}].env`
1019
+ );
1020
+ return {
1021
+ id: assertString(entry.id, `capabilities.mcpServers[${index}].id`),
1022
+ name: assertString(entry.name, `capabilities.mcpServers[${index}].name`),
1023
+ command: assertString(entry.command, `capabilities.mcpServers[${index}].command`),
1024
+ ...args ? { args } : {},
1025
+ ...env ? { env } : {}
1026
+ };
1027
+ }),
833
1028
  ...frontend ? {
834
1029
  frontend: {
835
1030
  ...typeof frontend.entry === "string" ? { entry: frontend.entry } : {},
@@ -2011,7 +2206,7 @@ Subquery.prototype.getSQL = function() {
2011
2206
  function mapResultRow(columns, row, joinsNotNullableMap) {
2012
2207
  const nullifyMap = {};
2013
2208
  const result = columns.reduce(
2014
- (result2, { path: path22, field }, columnIndex) => {
2209
+ (result2, { path: path24, field }, columnIndex) => {
2015
2210
  let decoder;
2016
2211
  if (is(field, Column)) {
2017
2212
  decoder = field;
@@ -2021,8 +2216,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
2021
2216
  decoder = field.sql.decoder;
2022
2217
  }
2023
2218
  let node = result2;
2024
- for (const [pathChunkIndex, pathChunk] of path22.entries()) {
2025
- if (pathChunkIndex < path22.length - 1) {
2219
+ for (const [pathChunkIndex, pathChunk] of path24.entries()) {
2220
+ if (pathChunkIndex < path24.length - 1) {
2026
2221
  if (!(pathChunk in node)) {
2027
2222
  node[pathChunk] = {};
2028
2223
  }
@@ -2030,8 +2225,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
2030
2225
  } else {
2031
2226
  const rawValue = row[columnIndex];
2032
2227
  const value = node[pathChunk] = rawValue === null ? null : decoder.mapFromDriverValue(rawValue);
2033
- if (joinsNotNullableMap && is(field, Column) && path22.length === 2) {
2034
- const objectName = path22[0];
2228
+ if (joinsNotNullableMap && is(field, Column) && path24.length === 2) {
2229
+ const objectName = path24[0];
2035
2230
  if (!(objectName in nullifyMap)) {
2036
2231
  nullifyMap[objectName] = value === null ? getTableName(field.table) : false;
2037
2232
  } else if (typeof nullifyMap[objectName] === "string" && nullifyMap[objectName] !== getTableName(field.table)) {
@@ -6132,6 +6327,7 @@ var shellSessions = sqliteTable("shell_sessions", {
6132
6327
  id: text("id").primaryKey(),
6133
6328
  workspaceId: text("workspace_id").notNull(),
6134
6329
  threadId: text("thread_id"),
6330
+ label: text("label"),
6135
6331
  tmuxSessionName: text("tmux_session_name"),
6136
6332
  cwd: text("cwd").notNull(),
6137
6333
  status: text("status"),
@@ -6722,12 +6918,16 @@ function getShellSessionRecordById(db, id) {
6722
6918
  function getShellSessionRecordByThreadId(db, threadId) {
6723
6919
  return db.select().from(shellSessions).where(eq(shellSessions.threadId, threadId)).get();
6724
6920
  }
6921
+ function listShellSessionRecordsByThreadId(db, threadId) {
6922
+ return db.select().from(shellSessions).where(eq(shellSessions.threadId, threadId)).orderBy(desc(shellSessions.lastActivityAt), desc(shellSessions.createdAt)).all();
6923
+ }
6725
6924
  function createShellSessionRecord(db, input) {
6726
6925
  const now = (/* @__PURE__ */ new Date()).toISOString();
6727
6926
  const record = {
6728
6927
  id: randomUUID(),
6729
6928
  workspaceId: input.workspaceId,
6730
6929
  threadId: input.threadId,
6930
+ label: input.label ?? null,
6731
6931
  tmuxSessionName: input.tmuxSessionName,
6732
6932
  cwd: input.cwd,
6733
6933
  status: input.status,
@@ -7509,12 +7709,12 @@ var CodexAppServerManager = class extends EventEmitter3 {
7509
7709
  serviceTier: input.serviceTier === void 0 ? void 0 : input.serviceTier,
7510
7710
  effort: input.effort ?? null,
7511
7711
  sandboxPolicy: input.sandboxPolicy ?? null,
7512
- collaborationMode: input.collaborationMode ? {
7513
- mode: input.collaborationMode,
7712
+ collaborationMode: input.collaborationMode || input.developerInstructions ? {
7713
+ mode: input.collaborationMode ?? "default",
7514
7714
  settings: {
7515
7715
  model: input.model ?? "",
7516
7716
  reasoning_effort: input.effort ?? null,
7517
- developer_instructions: null
7717
+ developer_instructions: input.developerInstructions ?? null
7518
7718
  }
7519
7719
  } : null
7520
7720
  });
@@ -8311,7 +8511,7 @@ function extractFileChangeEntries(item) {
8311
8511
  isRecord4(entry.summary) ? entry.summary : null,
8312
8512
  isRecord4(entry.diff) ? entry.diff : null
8313
8513
  ].filter((candidate) => Boolean(candidate));
8314
- const path22 = uniqueStrings([
8514
+ const path24 = uniqueStrings([
8315
8515
  stringOrNull(valueFromRecords(nestedRecords, ["path", "filePath", "targetPath"])),
8316
8516
  stringOrNull(
8317
8517
  valueFromRecords(nestedRecords, [
@@ -8358,7 +8558,7 @@ function extractFileChangeEntries(item) {
8358
8558
  const diffStats = explicitAdditions === 0 && explicitDeletions === 0 && diffText ? countUnifiedDiffStats(diffText) : null;
8359
8559
  const additions = explicitAdditions || diffStats?.additions || 0;
8360
8560
  const deletions = explicitDeletions || diffStats?.deletions || 0;
8361
- const normalizedPath = path22 ?? (diffText ? projectRelativePathLabel(extractPathFromDiffText(diffText)) : null);
8561
+ const normalizedPath = path24 ?? (diffText ? projectRelativePathLabel(extractPathFromDiffText(diffText)) : null);
8362
8562
  if (!normalizedPath && additions === 0 && deletions === 0) {
8363
8563
  return null;
8364
8564
  }
@@ -10167,6 +10367,9 @@ var CodexRuntimeAdapter = class extends EventEmitter4 {
10167
10367
  if (input.collaborationMode !== void 0) {
10168
10368
  turnInput.collaborationMode = input.collaborationMode;
10169
10369
  }
10370
+ if (input.developerInstructions !== void 0) {
10371
+ turnInput.developerInstructions = input.developerInstructions;
10372
+ }
10170
10373
  const sandboxPolicy = buildSandboxPolicy(input.sandboxMode, input.workspacePath);
10171
10374
  if (sandboxPolicy !== void 0) {
10172
10375
  turnInput.sandboxPolicy = sandboxPolicy;
@@ -11899,13 +12102,16 @@ var ClaudeRuntimeAdapter = class extends EventEmitter5 {
11899
12102
  const servers = await active.query.mcpServerStatus();
11900
12103
  return servers.map((server) => this.mapMcpServer(server));
11901
12104
  }
11902
- mapProviderRequest(request, _options) {
12105
+ mapProviderRequest(request, options) {
12106
+ void options;
11903
12107
  return mapClaudeAskUserQuestionRequest(request);
11904
12108
  }
11905
12109
  buildProviderRequestResponse(pending, input) {
11906
12110
  return buildClaudeProviderRequestResponse(pending, input);
11907
12111
  }
11908
- respondToProviderRequest(_id, _result) {
12112
+ respondToProviderRequest(id, result) {
12113
+ void id;
12114
+ void result;
11909
12115
  }
11910
12116
  async consumeQuery(state) {
11911
12117
  const rawMessages = [];
@@ -12593,14 +12799,14 @@ function displayPath(pathValue, options) {
12593
12799
  }
12594
12800
  return relativePath;
12595
12801
  }
12596
- function toolIsLowInformationPatch(normalized, state, input, patchText, path22, metadataStats) {
12802
+ function toolIsLowInformationPatch(normalized, state, input, patchText, path24, metadataStats) {
12597
12803
  if (normalized !== "applypatch" && normalized !== "patch") {
12598
12804
  return false;
12599
12805
  }
12600
12806
  if (toolStateStatus(state) !== "running") {
12601
12807
  return false;
12602
12808
  }
12603
- if (path22 || patchText || metadataStats || stringValue2(state.output)) {
12809
+ if (path24 || patchText || metadataStats || stringValue2(state.output)) {
12604
12810
  return false;
12605
12811
  }
12606
12812
  return !isRecord9(input) || Object.keys(input).length === 0;
@@ -12643,7 +12849,7 @@ function fileChangeStatsFromMetadata(metadata) {
12643
12849
  if (files.length === 0) {
12644
12850
  return null;
12645
12851
  }
12646
- const paths = files.map((file) => stringValue2(file.filePath) ?? stringValue2(file.path) ?? stringValue2(file.relativePath)).filter((path22) => Boolean(path22));
12852
+ const paths = files.map((file) => stringValue2(file.filePath) ?? stringValue2(file.path) ?? stringValue2(file.relativePath)).filter((path24) => Boolean(path24));
12647
12853
  const addedLines = files.reduce((total, file) => total + (numberValue(file.additions) ?? numberValue(file.addedLines) ?? numberValue(file.added) ?? 0), 0);
12648
12854
  const removedLines = files.reduce((total, file) => total + (numberValue(file.deletions) ?? numberValue(file.removedLines) ?? numberValue(file.removed) ?? 0), 0);
12649
12855
  return {
@@ -12791,28 +12997,28 @@ function mapAssistantTool(messageId2, tool, options) {
12791
12997
  ].includes(normalized)) {
12792
12998
  const metadataStats = fileChangeStatsFromMetadata(state.metadata);
12793
12999
  const patchText = isRecord9(input) ? stringValue2(input.patchText) ?? stringValue2(input.patch) ?? stringValue2(input.diff) : null;
12794
- const path22 = metadataStats?.path ?? filePathFromInput(input) ?? extractPathFromPatchText(patchText);
12795
- if (toolIsLowInformationPatch(normalized, state, input, patchText, path22, metadataStats)) {
13000
+ const path24 = metadataStats?.path ?? filePathFromInput(input) ?? extractPathFromPatchText(patchText);
13001
+ if (toolIsLowInformationPatch(normalized, state, input, patchText, path24, metadataStats)) {
12796
13002
  return null;
12797
13003
  }
12798
13004
  const output = stringValue2(state.output);
12799
13005
  const diffStats = countUnifiedDiffStats2(patchText);
12800
- const displayFilePath = displayPath(path22, options);
13006
+ const displayFilePath = displayPath(path24, options);
12801
13007
  return {
12802
13008
  id,
12803
13009
  kind: "fileChange",
12804
13010
  text: metadataStats ? metadataStats.changedFiles > 1 ? `${metadataStats.changedFiles} changed files` : displayFilePath ?? metadataStats.previewText : displayFilePath ?? output ?? summary ?? name,
12805
13011
  previewText: metadataStats ? metadataStats.changedFiles > 1 ? `${metadataStats.changedFiles} changed files` : displayFilePath ?? metadataStats.previewText : displayFilePath ? `${name}: ${displayFilePath}` : output ?? summary ?? name,
12806
13012
  detailText,
12807
- changedFiles: metadataStats?.changedFiles ?? (path22 ? 1 : null),
13013
+ changedFiles: metadataStats?.changedFiles ?? (path24 ? 1 : null),
12808
13014
  addedLines: metadataStats?.addedLines ?? diffStats?.addedLines ?? null,
12809
13015
  removedLines: metadataStats?.removedLines ?? diffStats?.removedLines ?? null,
12810
13016
  status: toolStateStatus(state)
12811
13017
  };
12812
13018
  }
12813
13019
  if (["read", "grep", "glob", "list", "ls", "bashoutput"].includes(normalized)) {
12814
- const path22 = filePathFromInput(input);
12815
- const text2 = displayPath(path22, options) ?? summary ?? name;
13020
+ const path24 = filePathFromInput(input);
13021
+ const text2 = displayPath(path24, options) ?? summary ?? name;
12816
13022
  return {
12817
13023
  id,
12818
13024
  kind: "fileRead",
@@ -14843,7 +15049,7 @@ function shouldPersistLiveHistoryItem(item) {
14843
15049
  function shouldPersistFinalHistoryItem(item) {
14844
15050
  return item.kind === "agentMessage" || shouldPersistLiveHistoryItem(item);
14845
15051
  }
14846
- function shouldPersistRuntimeFinalHistoryItem(item, allItems) {
15052
+ function shouldPersistRuntimeFinalHistoryItem(item) {
14847
15053
  if (item.kind === "agentMessage" && isTransientAgentHistoryItem(item)) {
14848
15054
  return false;
14849
15055
  }
@@ -14895,13 +15101,7 @@ function copyPersistedOrderingHints(item, persistedItem) {
14895
15101
  return nextItem;
14896
15102
  }
14897
15103
  function sortHistoryItemsBySequence(items) {
14898
- if (!items.some(hasHistoryItemSequence)) {
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);
15104
+ return sortTurnItemsByRecordedSequence(items);
14905
15105
  }
14906
15106
  function sortTurnItemsByRecordedSequence(items) {
14907
15107
  const leadingItems = [];
@@ -14914,9 +15114,6 @@ function sortTurnItemsByRecordedSequence(items) {
14914
15114
  if (!trailingItems.some(hasHistoryItemSequence)) {
14915
15115
  return items;
14916
15116
  }
14917
- if (trailingItems.some((item) => historyItemTranscriptOrder(item) !== null)) {
14918
- return items;
14919
- }
14920
15117
  const sequenceValues = trailingItems.map((item) => historyItemSequence(item)).filter(Number.isFinite);
14921
15118
  const maxSequence = sequenceValues.length > 0 ? Math.max(...sequenceValues) : 0;
14922
15119
  const orderedItems2 = [];
@@ -15074,7 +15271,9 @@ function agentTurnToThreadTurnDto(turn, deferredDetails) {
15074
15271
  startedAt: turn.startedAt ?? parseUuidV7Timestamp(turn.providerTurnId),
15075
15272
  status: turn.status,
15076
15273
  error: turn.error?.message ?? null,
15077
- items: visibleRuntimeTurnItems(turn.items)
15274
+ items: visibleRuntimeTurnItems(turn.items).map(
15275
+ (item, transcriptIndex) => item.transcriptOrder === transcriptIndex ? item : { ...item, transcriptOrder: transcriptIndex }
15276
+ )
15078
15277
  };
15079
15278
  return deferredDetails ? deferLargeHistoryItemDetails(baseTurn, deferredDetails) : baseTurn;
15080
15279
  }
@@ -15113,6 +15312,7 @@ var ThreadLiveStateStore = class {
15113
15312
  threadTurnItemOrder = /* @__PURE__ */ new Map();
15114
15313
  threadNextTurnItemSequence = /* @__PURE__ */ new Map();
15115
15314
  threadMaterializedAgentMessageCounts = /* @__PURE__ */ new Map();
15315
+ threadAgentMessageOrderingHints = /* @__PURE__ */ new Map();
15116
15316
  displayTurnIdForRuntimeTurn(localThreadId, runtimeTurnId) {
15117
15317
  if (!runtimeTurnId) {
15118
15318
  return null;
@@ -15155,6 +15355,7 @@ var ThreadLiveStateStore = class {
15155
15355
  this.threadLivePlans.delete(localThreadId);
15156
15356
  this.threadLiveItems.delete(localThreadId);
15157
15357
  this.threadMaterializedAgentMessageCounts.delete(localThreadId);
15358
+ this.threadAgentMessageOrderingHints.delete(localThreadId);
15158
15359
  this.clearRecordedTurnItemOrders(localThreadId);
15159
15360
  this.runtimeDisplayTurnIds.delete(localThreadId);
15160
15361
  this.hiddenRuntimeTurnIds.delete(localThreadId);
@@ -15179,10 +15380,12 @@ var ThreadLiveStateStore = class {
15179
15380
  resetRecordedTurnItemOrder(localThreadId, turnId) {
15180
15381
  this.threadTurnItemOrder.get(localThreadId)?.delete(turnId);
15181
15382
  this.threadNextTurnItemSequence.get(localThreadId)?.delete(turnId);
15383
+ this.threadAgentMessageOrderingHints.get(localThreadId)?.delete(turnId);
15182
15384
  }
15183
15385
  clearRecordedTurnItemOrders(localThreadId) {
15184
15386
  this.threadTurnItemOrder.delete(localThreadId);
15185
15387
  this.threadNextTurnItemSequence.delete(localThreadId);
15388
+ this.threadAgentMessageOrderingHints.delete(localThreadId);
15186
15389
  }
15187
15390
  recordTurnItemOrder(localThreadId, turnId, itemId) {
15188
15391
  let threadOrders = this.threadTurnItemOrder.get(localThreadId);
@@ -15212,6 +15415,71 @@ var ThreadLiveStateStore = class {
15212
15415
  turnItemOrderSnapshot(localThreadId) {
15213
15416
  return this.threadTurnItemOrder.get(localThreadId) ?? /* @__PURE__ */ new Map();
15214
15417
  }
15418
+ finalTurnAgentMessageOrderingHints(localThreadId, turnId, items, options = {}) {
15419
+ const hints = /* @__PURE__ */ new Map();
15420
+ const turnOrder = this.threadTurnItemOrder.get(localThreadId)?.get(turnId);
15421
+ const liveAgentMessages = [
15422
+ ...this.threadAgentMessageOrderingHints.get(localThreadId)?.get(turnId)?.values() ?? []
15423
+ ].map((item) => ({
15424
+ id: item.id,
15425
+ text: normalizeAgentMessageForMatching(item.text),
15426
+ sequence: item.sequence
15427
+ }));
15428
+ const usedLiveAgentIds = /* @__PURE__ */ new Set();
15429
+ const finalAgentItems = items.filter((item) => item.kind === "agentMessage");
15430
+ for (const item of finalAgentItems) {
15431
+ const existingSequence = turnOrder?.get(item.id);
15432
+ if (existingSequence !== void 0) {
15433
+ hints.set(item.id, existingSequence);
15434
+ const matchingLiveAgent = liveAgentMessages.find(
15435
+ (liveAgent) => liveAgent.id === item.id || liveAgent.sequence === existingSequence
15436
+ );
15437
+ if (matchingLiveAgent) {
15438
+ usedLiveAgentIds.add(matchingLiveAgent.id);
15439
+ }
15440
+ continue;
15441
+ }
15442
+ const text2 = normalizeAgentMessageForMatching(item.text);
15443
+ if (!text2) {
15444
+ continue;
15445
+ }
15446
+ let bestMatch = null;
15447
+ for (const liveAgent of liveAgentMessages) {
15448
+ if (usedLiveAgentIds.has(liveAgent.id) || !liveAgent.text) {
15449
+ continue;
15450
+ }
15451
+ const score = agentMessageMatchScore(text2, liveAgent.text);
15452
+ if (score === 0 || bestMatch && bestMatch.score >= score) {
15453
+ continue;
15454
+ }
15455
+ bestMatch = {
15456
+ id: liveAgent.id,
15457
+ sequence: liveAgent.sequence,
15458
+ score
15459
+ };
15460
+ }
15461
+ if (bestMatch) {
15462
+ usedLiveAgentIds.add(bestMatch.id);
15463
+ hints.set(item.id, bestMatch.sequence);
15464
+ }
15465
+ }
15466
+ if (options.allowUnmatchedFallback ?? true) {
15467
+ const remainingLiveAgents = liveAgentMessages.filter((liveAgent) => !usedLiveAgentIds.has(liveAgent.id)).sort((left, right) => left.sequence - right.sequence);
15468
+ let remainingLiveAgentIndex = 0;
15469
+ for (const item of finalAgentItems) {
15470
+ if (hints.has(item.id) || !normalizeAgentMessageForMatching(item.text)) {
15471
+ continue;
15472
+ }
15473
+ const liveAgent = remainingLiveAgents[remainingLiveAgentIndex];
15474
+ if (!liveAgent) {
15475
+ break;
15476
+ }
15477
+ hints.set(item.id, liveAgent.sequence);
15478
+ remainingLiveAgentIndex += 1;
15479
+ }
15480
+ }
15481
+ return hints;
15482
+ }
15215
15483
  getLiveItems(localThreadId, allTurns, visibleTurns = allTurns) {
15216
15484
  const current = this.threadLiveItems.get(localThreadId);
15217
15485
  if (!current) {
@@ -15227,10 +15495,8 @@ var ThreadLiveStateStore = class {
15227
15495
  upsertLiveItem(localThreadId, turnId, item) {
15228
15496
  const current = this.threadLiveItems.get(localThreadId);
15229
15497
  const currentItems = current?.turnId === turnId ? current.items : [];
15230
- const nextItems = [
15231
- ...currentItems.filter((entry) => entry.id !== item.id),
15232
- item
15233
- ];
15498
+ const existingIndex = currentItems.findIndex((entry) => entry.id === item.id);
15499
+ const nextItems = existingIndex >= 0 ? currentItems.map((entry, index) => index === existingIndex ? item : entry) : [...currentItems, item];
15234
15500
  this.setLiveItems(localThreadId, {
15235
15501
  turnId,
15236
15502
  items: sortHistoryItemsBySequence(nextItems),
@@ -15251,16 +15517,43 @@ var ThreadLiveStateStore = class {
15251
15517
  text: input.delta,
15252
15518
  sequence: input.sequence
15253
15519
  };
15520
+ this.recordAgentMessageOrderingHint(
15521
+ input.localThreadId,
15522
+ input.turnId,
15523
+ nextItem,
15524
+ input.sequence
15525
+ );
15254
15526
  this.setLiveItems(input.localThreadId, {
15255
15527
  turnId: input.turnId,
15256
- items: sortHistoryItemsBySequence([
15257
- ...currentItems.filter((entry) => entry.id !== input.itemId),
15258
- nextItem
15259
- ]),
15528
+ items: sortHistoryItemsBySequence(
15529
+ existing ? currentItems.map(
15530
+ (entry) => entry.id === input.itemId ? nextItem : entry
15531
+ ) : [...currentItems, nextItem]
15532
+ ),
15260
15533
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
15261
15534
  });
15262
15535
  return nextItem;
15263
15536
  }
15537
+ recordAgentMessageOrderingHint(localThreadId, turnId, item, sequence) {
15538
+ if (item.kind !== "agentMessage" || !Number.isFinite(sequence)) {
15539
+ return;
15540
+ }
15541
+ let threadHints = this.threadAgentMessageOrderingHints.get(localThreadId);
15542
+ if (!threadHints) {
15543
+ threadHints = /* @__PURE__ */ new Map();
15544
+ this.threadAgentMessageOrderingHints.set(localThreadId, threadHints);
15545
+ }
15546
+ let turnHints = threadHints.get(turnId);
15547
+ if (!turnHints) {
15548
+ turnHints = /* @__PURE__ */ new Map();
15549
+ threadHints.set(turnId, turnHints);
15550
+ }
15551
+ turnHints.set(item.id, {
15552
+ id: item.id,
15553
+ text: item.text,
15554
+ sequence
15555
+ });
15556
+ }
15264
15557
  reconcileLiveItems(localThreadId, turns) {
15265
15558
  const current = this.threadLiveItems.get(localThreadId);
15266
15559
  if (!current) {
@@ -15312,6 +15605,21 @@ var ThreadLiveStateStore = class {
15312
15605
  return nextLiveItems;
15313
15606
  }
15314
15607
  };
15608
+ function normalizeAgentMessageForMatching(text2) {
15609
+ return text2.replace(/\s+/g, " ").trim();
15610
+ }
15611
+ function agentMessageMatchScore(finalText, liveText) {
15612
+ if (finalText === liveText) {
15613
+ return 3;
15614
+ }
15615
+ if (liveText.length >= 8 && finalText.includes(liveText)) {
15616
+ return 2;
15617
+ }
15618
+ if (finalText.length >= 8 && liveText.includes(finalText)) {
15619
+ return 1;
15620
+ }
15621
+ return 0;
15622
+ }
15315
15623
 
15316
15624
  // src/thread-usage-accounting.ts
15317
15625
  var CONTEXT_BASELINE_TOKENS = 12e3;
@@ -15899,9 +16207,10 @@ var ThreadRuntimeEventProjector = class {
15899
16207
  lastError: event.turn.error?.message ?? null,
15900
16208
  lastTurnCompletedAt: (/* @__PURE__ */ new Date()).toISOString()
15901
16209
  });
16210
+ callbacks.persistFinalTurnOrderingHints(record.id, turnId, turnItems);
16211
+ callbacks.persistRuntimeTurnItemsAsDisplayTurn(record.id, rawTurnId, turnId, turnItems);
15902
16212
  liveState.setLivePlan(record.id, null);
15903
16213
  liveState.setLiveItems(record.id, null);
15904
- callbacks.persistRuntimeTurnItemsAsDisplayTurn(record.id, rawTurnId, turnId, turnItems);
15905
16214
  if (rawTurnId !== turnId) {
15906
16215
  callbacks.deletePersistedHistoryItemsForTurn(record.id, rawTurnId);
15907
16216
  liveState.resetRecordedTurnItemOrder(record.id, rawTurnId);
@@ -16310,34 +16619,82 @@ var ThreadDetailAssembler = class {
16310
16619
  }
16311
16620
  input;
16312
16621
  threadDetailCache = /* @__PURE__ */ new Map();
16313
- getCache(localThreadId) {
16314
- const cached = this.threadDetailCache.get(localThreadId);
16315
- if (!cached) {
16622
+ getCache(localThreadId, options = {}) {
16623
+ const bucket = this.threadDetailCache.get(localThreadId);
16624
+ if (!bucket) {
16316
16625
  return null;
16317
16626
  }
16318
- if (Date.now() - cached.cachedAt > THREAD_DETAIL_CACHE_TTL_MS) {
16319
- this.threadDetailCache.delete(localThreadId);
16627
+ const key = cacheKeyForDetailOptions(options);
16628
+ if (key === "uncached") {
16320
16629
  return null;
16321
16630
  }
16322
- return cached;
16631
+ const cached = key === "full" ? bucket.full : bucket.latestPages.get(key) ?? null;
16632
+ if (cached && !isExpiredThreadDetailCacheEntry(cached)) {
16633
+ return cached;
16634
+ }
16635
+ if (key === "full") {
16636
+ bucket.full = null;
16637
+ } else {
16638
+ bucket.latestPages.delete(key);
16639
+ }
16640
+ if (!bucket.full && bucket.latestPages.size === 0) {
16641
+ this.threadDetailCache.delete(localThreadId);
16642
+ }
16643
+ return null;
16323
16644
  }
16324
- setCache(localThreadId, entry) {
16325
- this.threadDetailCache.set(localThreadId, {
16645
+ setCache(localThreadId, options = {}, entry) {
16646
+ const bucket = this.threadDetailCache.get(localThreadId) ?? {
16647
+ full: null,
16648
+ latestPages: /* @__PURE__ */ new Map()
16649
+ };
16650
+ const nextEntry = {
16326
16651
  ...entry,
16327
16652
  cachedAt: Date.now()
16328
- });
16653
+ };
16654
+ const key = cacheKeyForDetailOptions(options);
16655
+ if (key === "uncached") {
16656
+ return;
16657
+ }
16658
+ if (key === "full") {
16659
+ bucket.full = nextEntry;
16660
+ } else {
16661
+ bucket.latestPages.set(key, nextEntry);
16662
+ }
16663
+ this.threadDetailCache.set(localThreadId, bucket);
16329
16664
  }
16330
16665
  invalidate(localThreadId) {
16331
16666
  this.threadDetailCache.delete(localThreadId);
16332
16667
  }
16333
16668
  cachedTurns(localThreadId) {
16334
- return this.threadDetailCache.get(localThreadId)?.turns ?? [];
16669
+ const bucket = this.threadDetailCache.get(localThreadId);
16670
+ if (!bucket) {
16671
+ return [];
16672
+ }
16673
+ if (bucket.full && !isExpiredThreadDetailCacheEntry(bucket.full)) {
16674
+ return bucket.full.turns;
16675
+ }
16676
+ let newest = null;
16677
+ for (const [key, entry] of bucket.latestPages.entries()) {
16678
+ if (isExpiredThreadDetailCacheEntry(entry)) {
16679
+ bucket.latestPages.delete(key);
16680
+ continue;
16681
+ }
16682
+ if (!newest || entry.cachedAt > newest.cachedAt) {
16683
+ newest = entry;
16684
+ }
16685
+ }
16686
+ if (!bucket.full && bucket.latestPages.size === 0) {
16687
+ this.threadDetailCache.delete(localThreadId);
16688
+ }
16689
+ return newest?.turns ?? [];
16335
16690
  }
16336
16691
  async buildCacheEntry(input) {
16337
16692
  const options = input.options ?? {};
16338
16693
  const shouldCacheFullDetail = options.limit === void 0 && options.beforeTurnId === void 0;
16339
- const cached = this.getCache(input.localThreadId);
16340
- if (cached && shouldCacheFullDetail) {
16694
+ const cacheKey = cacheKeyForDetailOptions(options);
16695
+ const isPaged = !shouldCacheFullDetail;
16696
+ const cached = this.getCache(input.localThreadId, options);
16697
+ if (cached) {
16341
16698
  return cached;
16342
16699
  }
16343
16700
  let remoteSession = await this.input.callbacks.readRemoteSession(
@@ -16347,6 +16704,7 @@ var ThreadDetailAssembler = class {
16347
16704
  if (!remoteSession) {
16348
16705
  return this.buildLocalFallbackEntry({
16349
16706
  ...input,
16707
+ options,
16350
16708
  shouldCacheFullDetail
16351
16709
  });
16352
16710
  }
@@ -16376,13 +16734,18 @@ var ThreadDetailAssembler = class {
16376
16734
  remoteSession.turns
16377
16735
  );
16378
16736
  const visibleTurns = this.input.liveState.visibleRemoteTurns(input.localThreadId, remoteSession.turns).map((turn) => agentTurnToThreadTurnDto(turn, deferredDetails));
16379
- const resolvedTurnMetadataById = resolveTurnMetadataByVisibleTurnId(
16737
+ const orderedVisibleTurns = applyLiveAgentMessageOrderingHints(
16380
16738
  visibleTurns,
16739
+ input.localThreadId,
16740
+ this.input.liveState
16741
+ );
16742
+ const resolvedTurnMetadataById = resolveTurnMetadataByVisibleTurnId(
16743
+ orderedVisibleTurns,
16381
16744
  input.turnMetadataById
16382
16745
  );
16383
16746
  const turns = mergePersistedHistoryItemsIntoTurns(
16384
16747
  applyRecordedTurnItemOrders(
16385
- visibleTurns,
16748
+ orderedVisibleTurns,
16386
16749
  this.input.liveState.turnItemOrderSnapshot(input.localThreadId)
16387
16750
  ),
16388
16751
  persistedItemsByTurnId,
@@ -16393,12 +16756,13 @@ var ThreadDetailAssembler = class {
16393
16756
  const entry = {
16394
16757
  cachedAt: Date.now(),
16395
16758
  turns,
16396
- totalTurnCount: shouldCacheFullDetail ? turns.length : remoteSession.totalTurnCount ?? Math.max(cached?.totalTurnCount ?? 0, turns.length),
16397
- deferredDetails
16759
+ totalTurnCount: shouldCacheFullDetail ? turns.length : remoteSession.totalTurnCount ?? turns.length,
16760
+ deferredDetails,
16761
+ isPaged
16398
16762
  };
16399
- if (shouldCacheFullDetail) {
16400
- this.setCache(input.localThreadId, entry);
16401
- return this.threadDetailCache.get(input.localThreadId);
16763
+ if (cacheKey !== "uncached") {
16764
+ this.setCache(input.localThreadId, options, entry);
16765
+ return this.getCache(input.localThreadId, options);
16402
16766
  }
16403
16767
  return entry;
16404
16768
  }
@@ -16417,7 +16781,7 @@ var ThreadDetailAssembler = class {
16417
16781
  input.record,
16418
16782
  latestThreadTurnMetadata(input.turnMetadataById)
16419
16783
  );
16420
- const localTurns = localSession?.turns ?? [...persistedItemsByTurnId.entries()].map(([turnId, items]) => ({
16784
+ const localTurns = localSession?.turns ?? [...persistedItemsByTurnId.keys()].map((turnId) => ({
16421
16785
  id: turnId,
16422
16786
  startedAt: null,
16423
16787
  status: "completed",
@@ -16441,15 +16805,61 @@ var ThreadDetailAssembler = class {
16441
16805
  cachedAt: Date.now(),
16442
16806
  turns,
16443
16807
  totalTurnCount: turns.length,
16444
- deferredDetails
16808
+ deferredDetails,
16809
+ isPaged: !input.shouldCacheFullDetail
16445
16810
  };
16446
- if (input.shouldCacheFullDetail) {
16447
- this.setCache(input.localThreadId, entry);
16448
- return this.threadDetailCache.get(input.localThreadId);
16811
+ const cacheOptions = input.shouldCacheFullDetail ? {} : input.options;
16812
+ if (cacheKeyForDetailOptions(cacheOptions) !== "uncached") {
16813
+ this.setCache(input.localThreadId, cacheOptions, entry);
16814
+ return this.getCache(input.localThreadId, cacheOptions);
16449
16815
  }
16450
16816
  return entry;
16451
16817
  }
16452
16818
  };
16819
+ function isExpiredThreadDetailCacheEntry(entry) {
16820
+ return Date.now() - entry.cachedAt > THREAD_DETAIL_CACHE_TTL_MS;
16821
+ }
16822
+ function cacheKeyForDetailOptions(options) {
16823
+ if (options.beforeTurnId !== void 0) {
16824
+ return "uncached";
16825
+ }
16826
+ if (options.limit === void 0) {
16827
+ return "full";
16828
+ }
16829
+ return `latest:${options.limit}`;
16830
+ }
16831
+ function applyLiveAgentMessageOrderingHints(turns, localThreadId, liveState) {
16832
+ return turns.map((turn) => {
16833
+ const orderingHints = liveState.finalTurnAgentMessageOrderingHints(
16834
+ localThreadId,
16835
+ turn.id,
16836
+ turn.items,
16837
+ { allowUnmatchedFallback: false }
16838
+ );
16839
+ if (orderingHints.size === 0) {
16840
+ return turn;
16841
+ }
16842
+ let changed = false;
16843
+ const items = turn.items.map((item) => {
16844
+ if (item.kind !== "agentMessage") {
16845
+ return item;
16846
+ }
16847
+ const sequence = orderingHints.get(item.id);
16848
+ if (sequence === void 0 || item.sequence === sequence) {
16849
+ return item;
16850
+ }
16851
+ changed = true;
16852
+ return {
16853
+ ...item,
16854
+ sequence
16855
+ };
16856
+ });
16857
+ return changed ? {
16858
+ ...turn,
16859
+ items: sortHistoryItemsBySequence(items)
16860
+ } : turn;
16861
+ });
16862
+ }
16453
16863
  function buildTurnDto(turn, metadata) {
16454
16864
  const tokenUsage = parseThreadTurnTokenUsageJson(metadata?.tokenUsageJson);
16455
16865
  return {
@@ -16932,7 +17342,8 @@ var ThreadPromptTurnCoordinator = class {
16932
17342
  reasoningEffort: input.normalizedReasoning,
16933
17343
  collaborationMode: input.collaborationMode,
16934
17344
  sandboxMode: input.sandboxMode,
16935
- workspacePath: input.workspacePath
17345
+ workspacePath: input.workspacePath,
17346
+ developerInstructions: input.developerInstructions ?? null
16936
17347
  };
16937
17348
  if (input.hidden !== void 0) {
16938
17349
  startTurnInput.hidden = input.hidden;
@@ -17049,7 +17460,8 @@ var ThreadPromptTurnCoordinator = class {
17049
17460
  collaborationMode: input.collaborationMode,
17050
17461
  sandboxMode: input.sandboxMode,
17051
17462
  performanceMode: input.performanceMode,
17052
- workspacePath: input.workspacePath
17463
+ workspacePath: input.workspacePath,
17464
+ developerInstructions: input.developerInstructions ?? null
17053
17465
  });
17054
17466
  }
17055
17467
  };
@@ -17480,12 +17892,38 @@ var ThreadHistoryPersistenceCoordinator = class {
17480
17892
  deletePersistedHistoryItemsForTurn(localThreadId, turnId) {
17481
17893
  deleteThreadHistoryItemRecordsByThreadAndTurnId(this.db, localThreadId, turnId);
17482
17894
  }
17895
+ persistFinalTurnOrderingHints(localThreadId, turnId, items) {
17896
+ const orderingHints = this.liveState.finalTurnAgentMessageOrderingHints(
17897
+ localThreadId,
17898
+ turnId,
17899
+ items
17900
+ );
17901
+ for (const item of items) {
17902
+ if (item.kind !== "agentMessage" || !shouldPersistRuntimeFinalHistoryItem(item)) {
17903
+ continue;
17904
+ }
17905
+ const sequence = orderingHints.get(item.id);
17906
+ if (sequence === void 0) {
17907
+ continue;
17908
+ }
17909
+ upsertThreadHistoryItemRecord(this.db, {
17910
+ threadId: localThreadId,
17911
+ turnId,
17912
+ itemId: item.id,
17913
+ itemJson: JSON.stringify({
17914
+ ...item,
17915
+ sequence,
17916
+ sourceTurnId: turnId
17917
+ })
17918
+ });
17919
+ }
17920
+ }
17483
17921
  persistRuntimeTurnItemsAsDisplayTurn(localThreadId, runtimeTurnId, displayTurnId, items) {
17484
17922
  if (runtimeTurnId === displayTurnId) {
17485
17923
  return;
17486
17924
  }
17487
17925
  for (const item of items) {
17488
- if (!shouldPersistRuntimeFinalHistoryItem(item, items)) {
17926
+ if (!shouldPersistRuntimeFinalHistoryItem(item)) {
17489
17927
  continue;
17490
17928
  }
17491
17929
  const sequence = this.liveState.recordTurnItemOrder(localThreadId, displayTurnId, item.id);
@@ -19168,6 +19606,16 @@ async function pathExists3(absPath) {
19168
19606
  return false;
19169
19607
  }
19170
19608
  }
19609
+ function canUseRuntimePagedTurns(cachedDetail, enrichedTurns, options) {
19610
+ const requestedLimit = options.limit ?? 10;
19611
+ if (enrichedTurns.length > requestedLimit) {
19612
+ return false;
19613
+ }
19614
+ return cachedDetail.totalTurnCount > enrichedTurns.length;
19615
+ }
19616
+ function pluginDeveloperInstructions(pluginService) {
19617
+ return pluginService?.modelContextPrompt() ?? null;
19618
+ }
19171
19619
  var ThreadService = class {
19172
19620
  constructor(db, agentRuntimes, eventBus, localSessionStore, workspaceRoot, providerManagement, pluginService) {
19173
19621
  this.db = db;
@@ -19343,6 +19791,7 @@ var ThreadService = class {
19343
19791
  normalizeReasoningEffort: normalizeReasoningEffort2,
19344
19792
  normalizeThreadGoalStatusForThread: (goal, record) => this.goalCoordinator.normalizeThreadGoalStatusForThread(goal, record),
19345
19793
  persistLiveHistoryItem: (localThreadId, turnId, item) => this.historyPersistence.persistLiveHistoryItem(localThreadId, turnId, item),
19794
+ persistFinalTurnOrderingHints: (localThreadId, turnId, items) => this.historyPersistence.persistFinalTurnOrderingHints(localThreadId, turnId, items),
19346
19795
  persistRuntimeTurnItemsAsDisplayTurn: (localThreadId, runtimeTurnId, displayTurnId, items) => this.historyPersistence.persistRuntimeTurnItemsAsDisplayTurn(
19347
19796
  localThreadId,
19348
19797
  runtimeTurnId,
@@ -19583,9 +20032,13 @@ var ThreadService = class {
19583
20032
  const enrichedTurns = this.pluginService?.enrichTurnsWithArtifacts({
19584
20033
  threadId: updated.id,
19585
20034
  workspacePath: workspace.absPath,
19586
- turns: cachedDetail.turns
20035
+ turns: cachedDetail.turns,
20036
+ deferredDetails: cachedDetail.deferredDetails
19587
20037
  }) ?? cachedDetail.turns;
19588
- const pagedTurns = this.detailAssembler.sliceTurns(enrichedTurns, options);
20038
+ const pagedTurns = cachedDetail.isPaged && canUseRuntimePagedTurns(cachedDetail, enrichedTurns, options) ? {
20039
+ turns: enrichedTurns,
20040
+ totalTurnCount: cachedDetail.totalTurnCount
20041
+ } : this.detailAssembler.sliceTurns(enrichedTurns, options);
19589
20042
  this.syncPendingPlanDecisionRequestFromTurns(
19590
20043
  updated.id,
19591
20044
  updated.collaborationMode,
@@ -19776,6 +20229,7 @@ var ThreadService = class {
19776
20229
  ...record,
19777
20230
  providerSessionId
19778
20231
  };
20232
+ const developerInstructions = pluginDeveloperInstructions(this.pluginService);
19779
20233
  if (record.providerTurnId && record.status === "running") {
19780
20234
  if (!turnConfig.supportsRunningTurnInput) {
19781
20235
  throw new HttpError(409, {
@@ -19795,7 +20249,8 @@ var ThreadService = class {
19795
20249
  collaborationMode: turnConfig.collaborationMode,
19796
20250
  sandboxMode: turnConfig.sandboxMode,
19797
20251
  performanceMode: turnConfig.performanceMode,
19798
- workspacePath: workspace.absPath
20252
+ workspacePath: workspace.absPath,
20253
+ developerInstructions
19799
20254
  });
19800
20255
  }
19801
20256
  return this.promptTurnCoordinator.startPromptTurn(localThreadId, connectedRecord, {
@@ -19806,7 +20261,8 @@ var ThreadService = class {
19806
20261
  collaborationMode: turnConfig.collaborationMode,
19807
20262
  sandboxMode: turnConfig.sandboxMode,
19808
20263
  performanceMode: turnConfig.performanceMode,
19809
- workspacePath: workspace.absPath
20264
+ workspacePath: workspace.absPath,
20265
+ developerInstructions
19810
20266
  });
19811
20267
  }
19812
20268
  async updateThreadSettings(localThreadId, input) {
@@ -20638,36 +21094,8 @@ function runShellCommand(command, timeoutMs = 0) {
20638
21094
  });
20639
21095
  }
20640
21096
 
20641
- // src/routes/shells.ts
20642
- 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
21097
  // src/routes/system.ts
20670
- import { z as z5 } from "zod";
21098
+ import { z as z4 } from "zod";
20671
21099
 
20672
21100
  // src/workspace-settings.ts
20673
21101
  var DEV_HOME_POLICY_KEY = "dev_home";
@@ -20731,27 +21159,27 @@ async function saveWorkspaceSettings(db, workspaceRoot, input) {
20731
21159
  }
20732
21160
 
20733
21161
  // src/routes/system.ts
20734
- var updateProviderHostFileSchema = z5.object({
20735
- content: z5.string()
21162
+ var updateProviderHostFileSchema = z4.object({
21163
+ content: z4.string()
20736
21164
  });
20737
- var archiveIdSchema = z5.string().regex(/^[a-zA-Z0-9_-]+$/);
20738
- var createProviderHostConfigArchiveSchema = z5.object({
20739
- label: z5.string().trim().min(1).max(120).optional()
21165
+ var archiveIdSchema = z4.string().regex(/^[a-zA-Z0-9_-]+$/);
21166
+ var createProviderHostConfigArchiveSchema = z4.object({
21167
+ label: z4.string().trim().min(1).max(120).optional()
20740
21168
  });
20741
- var renameProviderHostConfigArchiveSchema = z5.object({
20742
- label: z5.string().trim().min(1).max(120)
21169
+ var renameProviderHostConfigArchiveSchema = z4.object({
21170
+ label: z4.string().trim().min(1).max(120)
20743
21171
  });
20744
- var updateWorkspaceSettingsSchema = z5.object({
20745
- devHome: z5.string().trim().min(1),
21172
+ var updateWorkspaceSettingsSchema = z4.object({
21173
+ devHome: z4.string().trim().min(1),
20746
21174
  defaultBackend: agentBackendIdSchema.optional()
20747
21175
  });
20748
- var providerParamSchema2 = z5.object({
21176
+ var providerParamSchema2 = z4.object({
20749
21177
  provider: agentBackendIdSchema
20750
21178
  });
20751
21179
  function parseProviderHostFileParams(params) {
20752
- return z5.object({
21180
+ return z4.object({
20753
21181
  ...providerParamSchema2.shape,
20754
- name: z5.string()
21182
+ name: z4.string()
20755
21183
  }).parse(params);
20756
21184
  }
20757
21185
  async function registerSystemRoutes(app2) {
@@ -20831,7 +21259,7 @@ async function registerSystemRoutes(app2) {
20831
21259
  return app2.services.providerHostConfigService.createArchive(provider, body);
20832
21260
  });
20833
21261
  app2.patch("/api/config/providers/:provider/archives/:id", async (request) => {
20834
- const params = z5.object({
21262
+ const params = z4.object({
20835
21263
  ...providerParamSchema2.shape,
20836
21264
  id: archiveIdSchema
20837
21265
  }).parse(request.params);
@@ -20845,7 +21273,7 @@ async function registerSystemRoutes(app2) {
20845
21273
  );
20846
21274
  });
20847
21275
  app2.post("/api/config/providers/:provider/archives/:id/apply", async (request) => {
20848
- const params = z5.object({
21276
+ const params = z4.object({
20849
21277
  ...providerParamSchema2.shape,
20850
21278
  id: archiveIdSchema
20851
21279
  }).parse(request.params);
@@ -20856,67 +21284,67 @@ async function registerSystemRoutes(app2) {
20856
21284
  // src/routes/threads.ts
20857
21285
  import fs16 from "fs/promises";
20858
21286
  import path16 from "path";
20859
- import { z as z6 } from "zod";
20860
- var createThreadSchema = z6.object({
20861
- workspaceId: z6.string().uuid(),
20862
- title: z6.string().optional(),
21287
+ import { z as z5 } from "zod";
21288
+ var createThreadSchema = z5.object({
21289
+ workspaceId: z5.string().uuid(),
21290
+ title: z5.string().optional(),
20863
21291
  provider: agentBackendIdSchema.optional(),
20864
- model: z6.string().min(1),
20865
- approvalMode: z6.enum(["yolo", "guarded"]).default("yolo")
21292
+ model: z5.string().min(1),
21293
+ approvalMode: z5.enum(["yolo", "guarded"]).default("yolo")
20866
21294
  });
20867
- var promptSchema = z6.object({
20868
- prompt: z6.string().min(1),
20869
- clientRequestId: z6.string().min(1).optional(),
20870
- model: z6.string().min(1).optional(),
20871
- reasoningEffort: z6.enum(["none", "minimal", "low", "medium", "high", "xhigh"]).nullable().optional(),
20872
- collaborationMode: z6.enum(["default", "plan"]).optional(),
20873
- sandboxMode: z6.enum(["read-only", "workspace-write", "danger-full-access"]).nullable().optional()
21295
+ var promptSchema = z5.object({
21296
+ prompt: z5.string().min(1),
21297
+ clientRequestId: z5.string().min(1).optional(),
21298
+ model: z5.string().min(1).optional(),
21299
+ reasoningEffort: z5.enum(["none", "minimal", "low", "medium", "high", "xhigh"]).nullable().optional(),
21300
+ collaborationMode: z5.enum(["default", "plan"]).optional(),
21301
+ sandboxMode: z5.enum(["read-only", "workspace-write", "danger-full-access"]).nullable().optional()
20874
21302
  });
20875
- var promptAttachmentManifestEntrySchema = z6.object({
20876
- clientId: z6.string().min(1),
20877
- kind: z6.enum(["photo", "file"]),
20878
- originalName: z6.string().optional(),
20879
- placeholder: z6.string().min(1)
21303
+ var promptAttachmentManifestEntrySchema = z5.object({
21304
+ clientId: z5.string().min(1),
21305
+ kind: z5.enum(["photo", "file"]),
21306
+ originalName: z5.string().optional(),
21307
+ placeholder: z5.string().min(1)
20880
21308
  });
20881
- var updateThreadSchema = z6.object({
20882
- title: z6.string().min(1)
21309
+ var updateThreadSchema = z5.object({
21310
+ title: z5.string().min(1)
20883
21311
  });
20884
- var updateThreadSettingsSchema = z6.object({
20885
- model: z6.string().min(1).optional(),
20886
- reasoningEffort: z6.enum(["none", "minimal", "low", "medium", "high", "xhigh"]).nullable().optional(),
20887
- fastMode: z6.boolean().optional(),
20888
- collaborationMode: z6.enum(["default", "plan"]).optional(),
20889
- sandboxMode: z6.enum(["read-only", "workspace-write", "danger-full-access"]).nullable().optional()
21312
+ var updateThreadSettingsSchema = z5.object({
21313
+ model: z5.string().min(1).optional(),
21314
+ reasoningEffort: z5.enum(["none", "minimal", "low", "medium", "high", "xhigh"]).nullable().optional(),
21315
+ fastMode: z5.boolean().optional(),
21316
+ collaborationMode: z5.enum(["default", "plan"]).optional(),
21317
+ sandboxMode: z5.enum(["read-only", "workspace-write", "danger-full-access"]).nullable().optional()
20890
21318
  }).refine((body) => Object.keys(body).length > 0, {
20891
21319
  message: "At least one thread setting must be provided."
20892
21320
  });
20893
- var updateThreadGoalSchema = z6.object({
20894
- objective: z6.string().min(1).nullable().optional(),
20895
- status: z6.enum(["active", "paused", "budgetLimited", "complete", "terminated"]).nullable().optional(),
20896
- tokenBudget: z6.number().int().positive().nullable().optional()
21321
+ var updateThreadGoalSchema = z5.object({
21322
+ objective: z5.string().min(1).nullable().optional(),
21323
+ status: z5.enum(["active", "paused", "budgetLimited", "complete", "terminated"]).nullable().optional(),
21324
+ tokenBudget: z5.number().int().positive().nullable().optional()
20897
21325
  }).refine((body) => Object.keys(body).length > 0, {
20898
21326
  message: "At least one goal field must be provided."
20899
21327
  });
20900
- var interruptSchema = z6.object({
20901
- turnId: z6.string().optional()
21328
+ var interruptSchema = z5.object({
21329
+ turnId: z5.string().optional()
20902
21330
  });
20903
- var importThreadSchema = z6.object({
20904
- sessionId: z6.string().min(1)
21331
+ var importThreadSchema = z5.object({
21332
+ sessionId: z5.string().min(1)
20905
21333
  });
20906
- var resumeThreadSchema = z6.object({
20907
- model: z6.string().min(1).optional(),
20908
- sandboxMode: z6.enum(["read-only", "workspace-write", "danger-full-access"]).nullable().optional()
21334
+ var resumeThreadSchema = z5.object({
21335
+ model: z5.string().min(1).optional(),
21336
+ sandboxMode: z5.enum(["read-only", "workspace-write", "danger-full-access"]).nullable().optional()
20909
21337
  });
20910
- var forkThreadSchema = z6.discriminatedUnion("mode", [
20911
- z6.object({
20912
- mode: z6.literal("latest")
21338
+ var forkThreadSchema = z5.discriminatedUnion("mode", [
21339
+ z5.object({
21340
+ mode: z5.literal("latest")
20913
21341
  }),
20914
- z6.object({
20915
- mode: z6.literal("turn"),
20916
- turnId: z6.string().min(1)
21342
+ z5.object({
21343
+ mode: z5.literal("turn"),
21344
+ turnId: z5.string().min(1)
20917
21345
  })
20918
21346
  ]);
20919
- var hookEventNameSchema = z6.enum([
21347
+ var hookEventNameSchema = z5.enum([
20920
21348
  "preToolUse",
20921
21349
  "permissionRequest",
20922
21350
  "postToolUse",
@@ -20926,48 +21354,48 @@ var hookEventNameSchema = z6.enum([
20926
21354
  "userPromptSubmit",
20927
21355
  "stop"
20928
21356
  ]);
20929
- var createThreadHookSchema = z6.object({
20930
- scope: z6.enum(["global", "project"]),
21357
+ var createThreadHookSchema = z5.object({
21358
+ scope: z5.enum(["global", "project"]),
20931
21359
  eventName: hookEventNameSchema,
20932
- matcher: z6.string().nullable().optional(),
20933
- command: z6.string().trim().min(1),
20934
- timeoutSec: z6.number().int().positive().max(86400).nullable().optional(),
20935
- statusMessage: z6.string().nullable().optional()
21360
+ matcher: z5.string().nullable().optional(),
21361
+ command: z5.string().trim().min(1),
21362
+ timeoutSec: z5.number().int().positive().max(86400).nullable().optional(),
21363
+ statusMessage: z5.string().nullable().optional()
20936
21364
  });
20937
21365
  var updateThreadHookSchema = createThreadHookSchema.extend({
20938
21366
  target: createThreadHookSchema
20939
21367
  });
20940
- var trustThreadHookSchema = z6.object({
20941
- key: z6.string().min(1),
20942
- currentHash: z6.string().min(1)
21368
+ var trustThreadHookSchema = z5.object({
21369
+ key: z5.string().min(1),
21370
+ currentHash: z5.string().min(1)
20943
21371
  });
20944
- var untrustThreadHookSchema = z6.object({
20945
- key: z6.string().min(1)
21372
+ var untrustThreadHookSchema = z5.object({
21373
+ key: z5.string().min(1)
20946
21374
  });
20947
- var respondThreadRequestSchema = z6.object({
20948
- answers: z6.record(z6.string(), z6.object({
20949
- answers: z6.array(z6.string())
21375
+ var respondThreadRequestSchema = z5.object({
21376
+ answers: z5.record(z5.string(), z5.object({
21377
+ answers: z5.array(z5.string())
20950
21378
  }))
20951
21379
  });
20952
- var threadDetailQuerySchema = z6.object({
20953
- limit: z6.coerce.number().int().positive().max(100).optional(),
20954
- beforeTurnId: z6.string().min(1).optional()
21380
+ var threadDetailQuerySchema = z5.object({
21381
+ limit: z5.coerce.number().int().positive().max(100).optional(),
21382
+ beforeTurnId: z5.string().min(1).optional()
20955
21383
  });
20956
- var exportThreadPdfSchema = z6.object({
20957
- format: z6.enum(["pdf", "html"]).optional(),
20958
- mode: z6.enum(["latest", "selected"]),
20959
- limit: z6.number().int().positive().max(100).optional(),
20960
- turnIds: z6.array(z6.string().min(1)).max(100).optional(),
20961
- profile: z6.enum(["review", "technical"]).optional(),
20962
- options: z6.object({
20963
- includeTokenAndPrice: z6.boolean().optional(),
20964
- includeCommandOutput: z6.boolean().optional(),
20965
- includeAbsolutePaths: z6.boolean().optional()
21384
+ var exportThreadPdfSchema = z5.object({
21385
+ format: z5.enum(["pdf", "html"]).optional(),
21386
+ mode: z5.enum(["latest", "selected"]),
21387
+ limit: z5.number().int().positive().max(100).optional(),
21388
+ turnIds: z5.array(z5.string().min(1)).max(100).optional(),
21389
+ profile: z5.enum(["review", "technical"]).optional(),
21390
+ options: z5.object({
21391
+ includeTokenAndPrice: z5.boolean().optional(),
21392
+ includeCommandOutput: z5.boolean().optional(),
21393
+ includeAbsolutePaths: z5.boolean().optional()
20966
21394
  }).optional()
20967
21395
  }).refine((body) => body.mode !== "selected" || (body.turnIds?.length ?? 0) > 0, {
20968
21396
  message: "turnIds are required for selected exports."
20969
21397
  });
20970
- var queryBooleanSchema = z6.preprocess((value) => {
21398
+ var queryBooleanSchema = z5.preprocess((value) => {
20971
21399
  if (value === "true" || value === "1") {
20972
21400
  return true;
20973
21401
  }
@@ -20975,21 +21403,21 @@ var queryBooleanSchema = z6.preprocess((value) => {
20975
21403
  return false;
20976
21404
  }
20977
21405
  return value;
20978
- }, z6.boolean());
20979
- var exportThreadPdfQuerySchema = z6.object({
20980
- format: z6.enum(["pdf", "html"]).optional(),
20981
- mode: z6.enum(["latest", "selected"]),
20982
- limit: z6.coerce.number().int().positive().max(100).optional(),
20983
- turnIds: z6.string().optional(),
20984
- profile: z6.enum(["review", "technical"]).optional(),
21406
+ }, z5.boolean());
21407
+ var exportThreadPdfQuerySchema = z5.object({
21408
+ format: z5.enum(["pdf", "html"]).optional(),
21409
+ mode: z5.enum(["latest", "selected"]),
21410
+ limit: z5.coerce.number().int().positive().max(100).optional(),
21411
+ turnIds: z5.string().optional(),
21412
+ profile: z5.enum(["review", "technical"]).optional(),
20985
21413
  includeTokenAndPrice: queryBooleanSchema.optional(),
20986
21414
  includeCommandOutput: queryBooleanSchema.optional(),
20987
21415
  includeAbsolutePaths: queryBooleanSchema.optional()
20988
21416
  }).refine((query) => query.mode !== "selected" || Boolean(query.turnIds?.trim()), {
20989
21417
  message: "turnIds are required for selected exports."
20990
21418
  });
20991
- var threadImageQuerySchema = z6.object({
20992
- path: z6.string().min(1)
21419
+ var threadImageQuerySchema = z5.object({
21420
+ path: z5.string().min(1)
20993
21421
  });
20994
21422
  async function sendThreadExport(app2, reply, threadId, input) {
20995
21423
  const result = await app2.services.threadService.exportThreadTranscript(threadId, input);
@@ -21089,7 +21517,7 @@ async function parseMultipartPromptRequest(request) {
21089
21517
  message: "attachmentManifest must be valid JSON."
21090
21518
  });
21091
21519
  }
21092
- const manifest = z6.array(promptAttachmentManifestEntrySchema).max(MAX_PROMPT_ATTACHMENTS).parse(manifestParsed);
21520
+ const manifest = z5.array(promptAttachmentManifestEntrySchema).max(MAX_PROMPT_ATTACHMENTS).parse(manifestParsed);
21093
21521
  if (manifest.length !== uploadedFiles.length) {
21094
21522
  throw new HttpError(400, {
21095
21523
  code: "bad_request",
@@ -21133,7 +21561,7 @@ async function registerThreadRoutes(app2) {
21133
21561
  return app2.services.threadService.importThread(body.sessionId);
21134
21562
  });
21135
21563
  app2.get("/api/threads/:id", async (request) => {
21136
- const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
21564
+ const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
21137
21565
  const query = threadDetailQuerySchema.parse(request.query);
21138
21566
  return app2.services.threadService.getThreadDetail(params.id, {
21139
21567
  ...query.limit !== void 0 ? { limit: query.limit } : {},
@@ -21141,11 +21569,11 @@ async function registerThreadRoutes(app2) {
21141
21569
  });
21142
21570
  });
21143
21571
  app2.get("/api/threads/:id/export-turns", async (request) => {
21144
- const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
21572
+ const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
21145
21573
  return app2.services.threadService.listThreadExportTurns(params.id);
21146
21574
  });
21147
21575
  app2.get("/api/threads/:id/exports/pdf", async (request, reply) => {
21148
- const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
21576
+ const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
21149
21577
  const query = exportThreadPdfQuerySchema.parse(request.query);
21150
21578
  const turnIds = query.turnIds?.split(",").map((turnId) => turnId.trim()).filter(Boolean);
21151
21579
  const options = {};
@@ -21169,7 +21597,7 @@ async function registerThreadRoutes(app2) {
21169
21597
  return sendThreadExport(app2, reply, params.id, input);
21170
21598
  });
21171
21599
  app2.post("/api/threads/:id/exports/pdf", async (request, reply) => {
21172
- const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
21600
+ const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
21173
21601
  const parsed = exportThreadPdfSchema.parse(request.body);
21174
21602
  const input = {
21175
21603
  ...parsed.format !== void 0 ? { format: parsed.format } : {},
@@ -21188,9 +21616,9 @@ async function registerThreadRoutes(app2) {
21188
21616
  return sendThreadExport(app2, reply, params.id, input);
21189
21617
  });
21190
21618
  app2.get("/api/threads/:id/items/:itemId/detail", async (request) => {
21191
- const params = z6.object({
21192
- id: z6.string().uuid(),
21193
- itemId: z6.string().min(1)
21619
+ const params = z5.object({
21620
+ id: z5.string().uuid(),
21621
+ itemId: z5.string().min(1)
21194
21622
  }).parse(request.params);
21195
21623
  return app2.services.threadService.getThreadHistoryItemDetail(
21196
21624
  params.id,
@@ -21198,7 +21626,7 @@ async function registerThreadRoutes(app2) {
21198
21626
  );
21199
21627
  });
21200
21628
  app2.get("/api/threads/:id/assets/image", async (request, reply) => {
21201
- const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
21629
+ const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
21202
21630
  const query = threadImageQuerySchema.parse(request.query);
21203
21631
  const record = getThreadRecordById(app2.services.database.db, params.id);
21204
21632
  if (!record) {
@@ -21244,12 +21672,12 @@ async function registerThreadRoutes(app2) {
21244
21672
  return reply.send(await fs16.readFile(requestedPath));
21245
21673
  });
21246
21674
  app2.patch("/api/threads/:id", async (request) => {
21247
- const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
21675
+ const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
21248
21676
  const body = updateThreadSchema.parse(request.body);
21249
21677
  return app2.services.threadService.updateThreadTitle(params.id, body.title);
21250
21678
  });
21251
21679
  app2.delete("/api/threads/:id", async (request) => {
21252
- const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
21680
+ const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
21253
21681
  const shell = getShellSessionRecordByThreadId(app2.services.database.db, params.id);
21254
21682
  if (shell) {
21255
21683
  if (shell.status !== "exited" && shell.status !== "not_found") {
@@ -21261,7 +21689,7 @@ async function registerThreadRoutes(app2) {
21261
21689
  return app2.services.threadService.deleteThread(params.id);
21262
21690
  });
21263
21691
  app2.patch("/api/threads/:id/settings", async (request) => {
21264
- const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
21692
+ const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
21265
21693
  const body = updateThreadSettingsSchema.parse(request.body);
21266
21694
  const input = {
21267
21695
  ...body.model !== void 0 ? { model: body.model } : {},
@@ -21273,15 +21701,15 @@ async function registerThreadRoutes(app2) {
21273
21701
  return app2.services.threadService.updateThreadSettings(params.id, input);
21274
21702
  });
21275
21703
  app2.post("/api/threads/:id/compact", async (request) => {
21276
- const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
21704
+ const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
21277
21705
  return app2.services.threadService.compactThread(params.id);
21278
21706
  });
21279
21707
  app2.get("/api/threads/:id/goal", async (request) => {
21280
- const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
21708
+ const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
21281
21709
  return { goal: await app2.services.threadService.getThreadGoal(params.id) };
21282
21710
  });
21283
21711
  app2.patch("/api/threads/:id/goal", async (request) => {
21284
- const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
21712
+ const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
21285
21713
  const parsedBody = updateThreadGoalSchema.parse(request.body);
21286
21714
  const body = {
21287
21715
  ...parsedBody.objective !== void 0 ? { objective: parsedBody.objective } : {},
@@ -21291,32 +21719,32 @@ async function registerThreadRoutes(app2) {
21291
21719
  return { goal: await app2.services.threadService.updateThreadGoal(params.id, body) };
21292
21720
  });
21293
21721
  app2.delete("/api/threads/:id/goal", async (request) => {
21294
- const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
21722
+ const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
21295
21723
  return app2.services.threadService.clearThreadGoal(params.id);
21296
21724
  });
21297
21725
  app2.get("/api/threads/:id/fork-turns", async (request) => {
21298
- const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
21726
+ const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
21299
21727
  return app2.services.threadService.listForkTurnOptions(params.id);
21300
21728
  });
21301
21729
  app2.post("/api/threads/:id/fork", async (request) => {
21302
- const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
21730
+ const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
21303
21731
  const body = forkThreadSchema.parse(request.body);
21304
21732
  return app2.services.threadService.forkThread(params.id, body);
21305
21733
  });
21306
21734
  app2.get("/api/threads/:id/skills", async (request) => {
21307
- const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
21735
+ const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
21308
21736
  return app2.services.threadService.listThreadSkills(params.id);
21309
21737
  });
21310
21738
  app2.get("/api/threads/:id/mcp-servers", async (request) => {
21311
- const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
21739
+ const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
21312
21740
  return app2.services.threadService.listThreadMcpServers(params.id);
21313
21741
  });
21314
21742
  app2.get("/api/threads/:id/hooks", async (request) => {
21315
- const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
21743
+ const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
21316
21744
  return app2.services.threadService.listThreadHooks(params.id);
21317
21745
  });
21318
21746
  app2.post("/api/threads/:id/hooks", async (request) => {
21319
- const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
21747
+ const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
21320
21748
  const parsedBody = createThreadHookSchema.parse(request.body);
21321
21749
  const body = {
21322
21750
  scope: parsedBody.scope,
@@ -21329,7 +21757,7 @@ async function registerThreadRoutes(app2) {
21329
21757
  return app2.services.threadService.createThreadHook(params.id, body);
21330
21758
  });
21331
21759
  app2.put("/api/threads/:id/hooks", async (request) => {
21332
- const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
21760
+ const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
21333
21761
  const parsedBody = updateThreadHookSchema.parse(request.body);
21334
21762
  const body = {
21335
21763
  scope: parsedBody.scope,
@@ -21350,17 +21778,17 @@ async function registerThreadRoutes(app2) {
21350
21778
  return app2.services.threadService.updateThreadHook(params.id, body);
21351
21779
  });
21352
21780
  app2.post("/api/threads/:id/hooks/trust", async (request) => {
21353
- const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
21781
+ const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
21354
21782
  const body = trustThreadHookSchema.parse(request.body);
21355
21783
  return app2.services.threadService.trustThreadHook(params.id, body);
21356
21784
  });
21357
21785
  app2.post("/api/threads/:id/hooks/untrust", async (request) => {
21358
- const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
21786
+ const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
21359
21787
  const body = untrustThreadHookSchema.parse(request.body);
21360
21788
  return app2.services.threadService.untrustThreadHook(params.id, body);
21361
21789
  });
21362
21790
  app2.post("/api/threads/:id/resume", async (request) => {
21363
- const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
21791
+ const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
21364
21792
  const body = resumeThreadSchema.parse(request.body ?? {});
21365
21793
  const input = {
21366
21794
  ...body.model !== void 0 ? { model: body.model } : {},
@@ -21369,13 +21797,13 @@ async function registerThreadRoutes(app2) {
21369
21797
  return app2.services.threadService.resumeThread(params.id, input);
21370
21798
  });
21371
21799
  app2.post("/api/threads/:id/disconnect", async (request) => {
21372
- const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
21800
+ const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
21373
21801
  const detail = await app2.services.threadService.disconnectThread(params.id);
21374
21802
  await app2.services.shellService.detachThreadViewers(params.id);
21375
21803
  return detail;
21376
21804
  });
21377
21805
  app2.post("/api/threads/:id/prompt", async (request) => {
21378
- const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
21806
+ const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
21379
21807
  const parsed = request.isMultipart() ? await parseMultipartPromptRequest(request) : {
21380
21808
  input: (() => {
21381
21809
  const parsedBody = promptSchema.parse(request.body);
@@ -21400,15 +21828,15 @@ async function registerThreadRoutes(app2) {
21400
21828
  });
21401
21829
  });
21402
21830
  app2.post("/api/threads/:id/requests/:requestId/respond", async (request) => {
21403
- const params = z6.object({
21404
- id: z6.string().uuid(),
21405
- requestId: z6.string().min(1)
21831
+ const params = z5.object({
21832
+ id: z5.string().uuid(),
21833
+ requestId: z5.string().min(1)
21406
21834
  }).parse(request.params);
21407
21835
  const body = respondThreadRequestSchema.parse(request.body);
21408
21836
  return app2.services.threadService.respondToRequest(params.id, params.requestId, body);
21409
21837
  });
21410
21838
  app2.post("/api/threads/:id/interrupt", async (request) => {
21411
- const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
21839
+ const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
21412
21840
  const body = interruptSchema.parse(request.body ?? {});
21413
21841
  return app2.services.threadService.interruptThread(params.id, body.turnId);
21414
21842
  });
@@ -21418,27 +21846,46 @@ async function registerThreadRoutes(app2) {
21418
21846
  import fs17 from "fs/promises";
21419
21847
  import path17 from "path";
21420
21848
  import { spawn as spawn3 } from "child_process";
21421
- import { z as z7 } from "zod";
21422
- var createWorkspaceSchema = z7.union([
21423
- z7.object({
21424
- absPath: z7.string().min(1),
21425
- label: z7.string().min(1).optional()
21849
+ import { Readable } from "stream";
21850
+ import { z as z6 } from "zod";
21851
+ var createWorkspaceSchema = z6.union([
21852
+ z6.object({
21853
+ absPath: z6.string().min(1),
21854
+ label: z6.string().min(1).optional()
21426
21855
  }),
21427
- z7.object({
21428
- gitUrl: z7.string().min(1),
21429
- label: z7.string().min(1).optional()
21856
+ z6.object({
21857
+ gitUrl: z6.string().min(1),
21858
+ label: z6.string().min(1).optional()
21430
21859
  })
21431
21860
  ]);
21432
- var updateFavoriteSchema = z7.object({
21433
- isFavorite: z7.boolean()
21861
+ var updateFavoriteSchema = z6.object({
21862
+ isFavorite: z6.boolean()
21863
+ });
21864
+ var updateWorkspaceSchema = z6.object({
21865
+ label: z6.string().min(1)
21866
+ });
21867
+ var treeQuerySchema = z6.object({
21868
+ path: z6.string().optional(),
21869
+ showHidden: z6.coerce.boolean().optional()
21434
21870
  });
21435
- var updateWorkspaceSchema = z7.object({
21436
- label: z7.string().min(1)
21871
+ var workspaceFileQuerySchema = z6.object({
21872
+ path: z6.string().optional().default("")
21437
21873
  });
21438
- var treeQuerySchema = z7.object({
21439
- path: z7.string().optional(),
21440
- showHidden: z7.coerce.boolean().optional()
21874
+ var workspacePreviewQuerySchema = z6.object({
21875
+ path: z6.string().min(1),
21876
+ offset: z6.coerce.number().int().min(0).optional(),
21877
+ limit: z6.coerce.number().int().positive().max(25e4).optional()
21441
21878
  });
21879
+ var PREVIEW_DEFAULT_LIMIT_BYTES = 5e4;
21880
+ var WORKSPACE_UPLOAD_MAX_BYTES = 50 * 1024 * 1024;
21881
+ var WORKSPACE_TREE_IGNORED_NAMES = /* @__PURE__ */ new Set([
21882
+ ".git",
21883
+ "node_modules",
21884
+ ".next",
21885
+ ".turbo",
21886
+ "dist",
21887
+ "build"
21888
+ ]);
21442
21889
  function toWorkspaceDto2(record) {
21443
21890
  return {
21444
21891
  id: record.id,
@@ -21450,6 +21897,159 @@ function toWorkspaceDto2(record) {
21450
21897
  lastOpenedAt: record.lastOpenedAt
21451
21898
  };
21452
21899
  }
21900
+ function languageForPath(filePath) {
21901
+ const extension = path17.extname(filePath).slice(1).toLowerCase();
21902
+ switch (extension) {
21903
+ case "js":
21904
+ case "jsx":
21905
+ return "javascript";
21906
+ case "ts":
21907
+ case "tsx":
21908
+ return extension;
21909
+ case "md":
21910
+ case "markdown":
21911
+ return "markdown";
21912
+ case "yml":
21913
+ return "yaml";
21914
+ case "sh":
21915
+ case "bash":
21916
+ return "bash";
21917
+ case "py":
21918
+ return "python";
21919
+ case "rb":
21920
+ return "ruby";
21921
+ case "rs":
21922
+ return "rust";
21923
+ case "go":
21924
+ return "go";
21925
+ case "c":
21926
+ case "h":
21927
+ return "c";
21928
+ case "cc":
21929
+ case "cpp":
21930
+ case "cxx":
21931
+ case "hpp":
21932
+ return "cpp";
21933
+ case "html":
21934
+ case "css":
21935
+ case "json":
21936
+ case "jsonl":
21937
+ case "toml":
21938
+ case "xml":
21939
+ case "sql":
21940
+ case "txt":
21941
+ return extension;
21942
+ default:
21943
+ return extension || "text";
21944
+ }
21945
+ }
21946
+ function relativeWorkspacePath(rootPath, absPath) {
21947
+ const relative = path17.relative(rootPath, absPath);
21948
+ return relative === "" ? "" : relative.split(path17.sep).join("/");
21949
+ }
21950
+ async function resolveWorkspaceItemPath(rootPath, relativePath = "") {
21951
+ const candidate = path17.resolve(rootPath, relativePath || ".");
21952
+ const comparable = await assertPathWithinRoot(rootPath, candidate);
21953
+ return comparable;
21954
+ }
21955
+ async function buildWorkspaceTreeNode(rootPath, absPath, depth = 0) {
21956
+ const stats = await fs17.stat(absPath);
21957
+ const relativePath = relativeWorkspacePath(rootPath, absPath);
21958
+ const name = relativePath ? path17.basename(absPath) : path17.basename(rootPath);
21959
+ if (!stats.isDirectory()) {
21960
+ return {
21961
+ name,
21962
+ path: relativePath,
21963
+ kind: "file",
21964
+ size: stats.size
21965
+ };
21966
+ }
21967
+ const node = {
21968
+ name,
21969
+ path: relativePath,
21970
+ kind: "directory",
21971
+ children: []
21972
+ };
21973
+ if (depth >= 6) {
21974
+ return node;
21975
+ }
21976
+ let entries;
21977
+ try {
21978
+ entries = await fs17.readdir(absPath, { withFileTypes: true });
21979
+ } catch {
21980
+ return node;
21981
+ }
21982
+ const visible = entries.filter((entry) => !entry.name.startsWith(".")).filter((entry) => !WORKSPACE_TREE_IGNORED_NAMES.has(entry.name)).sort((left, right) => {
21983
+ if (left.isDirectory() && !right.isDirectory()) {
21984
+ return -1;
21985
+ }
21986
+ if (!left.isDirectory() && right.isDirectory()) {
21987
+ return 1;
21988
+ }
21989
+ return left.name.localeCompare(right.name);
21990
+ }).slice(0, 400);
21991
+ node.children = (await Promise.all(
21992
+ visible.map(async (entry) => {
21993
+ const childPath = path17.join(absPath, entry.name);
21994
+ try {
21995
+ if (!entry.isDirectory() && !entry.isFile()) {
21996
+ return null;
21997
+ }
21998
+ return await buildWorkspaceTreeNode(rootPath, childPath, depth + 1);
21999
+ } catch {
22000
+ return null;
22001
+ }
22002
+ })
22003
+ )).filter((child) => child !== null);
22004
+ return node;
22005
+ }
22006
+ function requireWorkspaceRecord(app2, workspaceId) {
22007
+ const record = getWorkspaceRecordById(app2.services.database.db, workspaceId);
22008
+ if (!record) {
22009
+ throw new HttpError(404, {
22010
+ code: "not_found",
22011
+ message: "Workspace was not found."
22012
+ });
22013
+ }
22014
+ return record;
22015
+ }
22016
+ function contentTypeForPath(filePath) {
22017
+ switch (path17.extname(filePath).slice(1).toLowerCase()) {
22018
+ case "png":
22019
+ return "image/png";
22020
+ case "jpg":
22021
+ case "jpeg":
22022
+ return "image/jpeg";
22023
+ case "gif":
22024
+ return "image/gif";
22025
+ case "webp":
22026
+ return "image/webp";
22027
+ case "svg":
22028
+ return "image/svg+xml";
22029
+ case "pdf":
22030
+ return "application/pdf";
22031
+ case "json":
22032
+ return "application/json; charset=utf-8";
22033
+ case "html":
22034
+ return "text/html; charset=utf-8";
22035
+ case "css":
22036
+ return "text/css; charset=utf-8";
22037
+ case "js":
22038
+ case "mjs":
22039
+ case "ts":
22040
+ case "tsx":
22041
+ return "text/plain; charset=utf-8";
22042
+ default:
22043
+ return "application/octet-stream";
22044
+ }
22045
+ }
22046
+ function sanitizeUploadFilename(filename) {
22047
+ const baseName = path17.basename(filename?.trim() || "upload");
22048
+ if (!baseName || baseName === "." || baseName === "..") {
22049
+ return "upload";
22050
+ }
22051
+ return baseName;
22052
+ }
21453
22053
  function inferGitRepoName(gitUrl) {
21454
22054
  const trimmed = gitUrl.trim();
21455
22055
  const withoutQuery = trimmed.split(/[?#]/)[0] ?? trimmed;
@@ -21534,7 +22134,7 @@ async function registerWorkspaceRoutes(app2) {
21534
22134
  };
21535
22135
  });
21536
22136
  app2.get("/api/workspaces/:id", async (request) => {
21537
- const params = z7.object({ id: z7.string().uuid() }).parse(request.params);
22137
+ const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
21538
22138
  const record = getWorkspaceRecordById(app2.services.database.db, params.id);
21539
22139
  if (!record) {
21540
22140
  throw new HttpError(404, {
@@ -21544,6 +22144,114 @@ async function registerWorkspaceRoutes(app2) {
21544
22144
  }
21545
22145
  return toWorkspaceDto2(record);
21546
22146
  });
22147
+ app2.get("/api/workspaces/:id/files/tree", async (request) => {
22148
+ const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
22149
+ const query = workspaceFileQuerySchema.parse(request.query);
22150
+ const record = requireWorkspaceRecord(app2, params.id);
22151
+ const rootPath = await fs17.realpath(record.absPath);
22152
+ const targetPath = await resolveWorkspaceItemPath(rootPath, query.path);
22153
+ return buildWorkspaceTreeNode(rootPath, targetPath);
22154
+ });
22155
+ app2.get("/api/workspaces/:id/files/preview", async (request) => {
22156
+ const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
22157
+ const query = workspacePreviewQuerySchema.parse(request.query);
22158
+ const record = requireWorkspaceRecord(app2, params.id);
22159
+ const rootPath = await fs17.realpath(record.absPath);
22160
+ const filePath = await resolveWorkspaceItemPath(rootPath, query.path);
22161
+ const stats = await fs17.stat(filePath);
22162
+ if (!stats.isFile()) {
22163
+ throw new HttpError(400, {
22164
+ code: "bad_request",
22165
+ message: "Workspace preview path must point to a file."
22166
+ });
22167
+ }
22168
+ const offset = query.offset ?? 0;
22169
+ const limit = query.limit ?? PREVIEW_DEFAULT_LIMIT_BYTES;
22170
+ const handle = await fs17.open(filePath, "r");
22171
+ try {
22172
+ const length = Math.min(limit, Math.max(0, stats.size - offset));
22173
+ const buffer = Buffer.alloc(length);
22174
+ const read = await handle.read(buffer, 0, length, offset);
22175
+ const nextOffset = offset + read.bytesRead;
22176
+ return {
22177
+ path: relativeWorkspacePath(rootPath, filePath),
22178
+ name: path17.basename(filePath),
22179
+ content: buffer.subarray(0, read.bytesRead).toString("utf8"),
22180
+ language: languageForPath(filePath),
22181
+ size: stats.size,
22182
+ truncated: nextOffset < stats.size,
22183
+ nextOffset
22184
+ };
22185
+ } finally {
22186
+ await handle.close();
22187
+ }
22188
+ });
22189
+ app2.get("/api/workspaces/:id/files/raw", async (request, reply) => {
22190
+ const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
22191
+ const query = workspacePreviewQuerySchema.pick({ path: true }).parse(request.query);
22192
+ const record = requireWorkspaceRecord(app2, params.id);
22193
+ const rootPath = await fs17.realpath(record.absPath);
22194
+ const filePath = await resolveWorkspaceItemPath(rootPath, query.path);
22195
+ const stats = await fs17.stat(filePath);
22196
+ if (!stats.isFile()) {
22197
+ throw new HttpError(400, {
22198
+ code: "bad_request",
22199
+ message: "Raw workspace path must point to a file."
22200
+ });
22201
+ }
22202
+ reply.header("content-type", contentTypeForPath(filePath));
22203
+ return reply.send(Readable.from(await fs17.readFile(filePath)));
22204
+ });
22205
+ app2.get("/api/workspaces/:id/files/download", async (request, reply) => {
22206
+ const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
22207
+ const query = workspaceFileQuerySchema.parse(request.query);
22208
+ const record = requireWorkspaceRecord(app2, params.id);
22209
+ const rootPath = await fs17.realpath(record.absPath);
22210
+ const itemPath = await resolveWorkspaceItemPath(rootPath, query.path);
22211
+ const stats = await fs17.stat(itemPath);
22212
+ if (!stats.isFile()) {
22213
+ throw new HttpError(400, {
22214
+ code: "bad_request",
22215
+ message: "Only file downloads are supported from this endpoint."
22216
+ });
22217
+ }
22218
+ const filename = path17.basename(itemPath);
22219
+ reply.header("content-type", contentTypeForPath(itemPath)).header(
22220
+ "content-disposition",
22221
+ `attachment; filename="${filename}"; filename*=UTF-8''${encodeURIComponent(filename)}`
22222
+ );
22223
+ return reply.send(Readable.from(await fs17.readFile(itemPath)));
22224
+ });
22225
+ app2.post("/api/workspaces/:id/files/upload", async (request) => {
22226
+ const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
22227
+ const record = requireWorkspaceRecord(app2, params.id);
22228
+ const rootPath = await fs17.realpath(record.absPath);
22229
+ const part = await request.file();
22230
+ if (!part) {
22231
+ throw new HttpError(400, {
22232
+ code: "bad_request",
22233
+ message: "A file field is required."
22234
+ });
22235
+ }
22236
+ const buffer = await part.toBuffer();
22237
+ if (buffer.byteLength > WORKSPACE_UPLOAD_MAX_BYTES) {
22238
+ throw new HttpError(400, {
22239
+ code: "bad_request",
22240
+ message: "Workspace uploads must be 50 MB or smaller."
22241
+ });
22242
+ }
22243
+ const filename = sanitizeUploadFilename(part.filename);
22244
+ const destination = await resolveWorkspaceItemPath(rootPath, filename);
22245
+ await fs17.writeFile(destination, buffer);
22246
+ return {
22247
+ kind: "file",
22248
+ file: {
22249
+ path: relativeWorkspacePath(rootPath, destination),
22250
+ name: filename,
22251
+ size: buffer.byteLength
22252
+ }
22253
+ };
22254
+ });
21547
22255
  app2.post("/api/workspaces", async (request) => {
21548
22256
  const body = createWorkspaceSchema.parse(request.body);
21549
22257
  const settings = await getWorkspaceSettings(
@@ -21588,7 +22296,7 @@ async function registerWorkspaceRoutes(app2) {
21588
22296
  return toWorkspaceDto2(created);
21589
22297
  });
21590
22298
  app2.patch("/api/workspaces/:id", async (request) => {
21591
- const params = z7.object({ id: z7.string().uuid() }).parse(request.params);
22299
+ const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
21592
22300
  const body = updateWorkspaceSchema.parse(request.body);
21593
22301
  const record = getWorkspaceRecordById(app2.services.database.db, params.id);
21594
22302
  if (!record) {
@@ -21609,7 +22317,7 @@ async function registerWorkspaceRoutes(app2) {
21609
22317
  return toWorkspaceDto2(updated);
21610
22318
  });
21611
22319
  app2.delete("/api/workspaces/:id", async (request) => {
21612
- const params = z7.object({ id: z7.string().uuid() }).parse(request.params);
22320
+ const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
21613
22321
  const record = getWorkspaceRecordById(app2.services.database.db, params.id);
21614
22322
  if (!record) {
21615
22323
  throw new HttpError(404, {
@@ -21633,7 +22341,7 @@ async function registerWorkspaceRoutes(app2) {
21633
22341
  return { id: params.id };
21634
22342
  });
21635
22343
  app2.post("/api/workspaces/:id/favorite", async (request) => {
21636
- const params = z7.object({ id: z7.string().uuid() }).parse(request.params);
22344
+ const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
21637
22345
  const body = updateFavoriteSchema.parse(request.body);
21638
22346
  const record = getWorkspaceRecordById(app2.services.database.db, params.id);
21639
22347
  if (!record) {
@@ -21647,7 +22355,7 @@ async function registerWorkspaceRoutes(app2) {
21647
22355
  return toWorkspaceDto2(updated);
21648
22356
  });
21649
22357
  app2.post("/api/workspaces/:id/open", async (request) => {
21650
- const params = z7.object({ id: z7.string().uuid() }).parse(request.params);
22358
+ const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
21651
22359
  const record = getWorkspaceRecordById(app2.services.database.db, params.id);
21652
22360
  if (!record) {
21653
22361
  throw new HttpError(404, {
@@ -21662,21 +22370,32 @@ async function registerWorkspaceRoutes(app2) {
21662
22370
  }
21663
22371
 
21664
22372
  // src/routes/plugins.ts
21665
- import { z as z8 } from "zod";
21666
- var pluginParamsSchema = z8.object({
21667
- pluginId: z8.string().min(1)
22373
+ import { z as z7 } from "zod";
22374
+ var pluginParamsSchema = z7.object({
22375
+ pluginId: z7.string().min(1)
21668
22376
  });
21669
- var updatePluginSchema = z8.object({
21670
- enabled: z8.boolean()
22377
+ var updatePluginSchema = z7.object({
22378
+ enabled: z7.boolean()
21671
22379
  });
21672
- var importPluginSchema = z8.object({
21673
- enabled: z8.boolean().optional(),
21674
- manifestJson: z8.string().optional(),
21675
- manifest: z8.unknown().optional()
22380
+ var importPluginSchema = z7.object({
22381
+ enabled: z7.boolean().optional(),
22382
+ manifestJson: z7.string().optional(),
22383
+ manifest: z7.unknown().optional()
21676
22384
  }).refine((value) => value.manifest !== void 0 || value.manifestJson !== void 0, {
21677
22385
  message: "Plugin import requires manifest or manifestJson."
21678
22386
  });
21679
22387
  async function registerPluginRoutes(app2) {
22388
+ async function syncManagedPluginMcpConfig() {
22389
+ await app2.services.pluginService.syncManagedCodexMcpConfig({
22390
+ codexHome: app2.services.config.agentProviders.codex.home ?? null,
22391
+ repoRoot: app2.services.repoRoot
22392
+ });
22393
+ const codexRuntime = app2.services.agentRuntimes.getOptional("codex");
22394
+ if (codexRuntime) {
22395
+ await codexRuntime.stop();
22396
+ await codexRuntime.start();
22397
+ }
22398
+ }
21680
22399
  app2.get("/api/plugins", async () => {
21681
22400
  return app2.services.pluginService.listPlugins();
21682
22401
  });
@@ -21688,7 +22407,9 @@ async function registerPluginRoutes(app2) {
21688
22407
  ...parsed.manifestJson === void 0 ? {} : { manifestJson: parsed.manifestJson }
21689
22408
  };
21690
22409
  try {
21691
- return app2.services.pluginService.importPlugin(body);
22410
+ const plugin = app2.services.pluginService.importPlugin(body);
22411
+ await syncManagedPluginMcpConfig();
22412
+ return plugin;
21692
22413
  } catch (error) {
21693
22414
  if (error instanceof SyntaxError) {
21694
22415
  throw new HttpError(400, {
@@ -21717,7 +22438,9 @@ async function registerPluginRoutes(app2) {
21717
22438
  const { pluginId } = pluginParamsSchema.parse(request.params);
21718
22439
  const body = updatePluginSchema.parse(request.body);
21719
22440
  try {
21720
- return app2.services.pluginService.setPluginEnabled(pluginId, body.enabled);
22441
+ const plugin = app2.services.pluginService.setPluginEnabled(pluginId, body.enabled);
22442
+ await syncManagedPluginMcpConfig();
22443
+ return plugin;
21721
22444
  } catch {
21722
22445
  throw new HttpError(404, {
21723
22446
  code: "not_found",
@@ -21953,28 +22676,12 @@ var ProviderHostConfigService = class {
21953
22676
  };
21954
22677
 
21955
22678
  // src/shell/shell-session-service.ts
22679
+ import fs20 from "fs/promises";
22680
+
22681
+ // src/shell/shell-prompt.ts
21956
22682
  import fs19 from "fs/promises";
21957
22683
  import os3 from "os";
21958
22684
  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
22685
  function basenameFromPath2(filePath) {
21979
22686
  if (!filePath) {
21980
22687
  return "";
@@ -22043,9 +22750,6 @@ function resolveEnvironmentPrefix(environmentText) {
22043
22750
  }
22044
22751
  return null;
22045
22752
  }
22046
- function shellSingleQuote(value) {
22047
- return `'${value.replace(/'/g, `'\\''`)}'`;
22048
- }
22049
22753
  async function resolvePaneEnvironmentPrefix(tmuxManager, sessionName, panePid) {
22050
22754
  const sessionPrefix = await tmuxManager.getSessionEnvironmentVariable(
22051
22755
  sessionName,
@@ -22061,6 +22765,9 @@ async function resolvePaneEnvironmentPrefix(tmuxManager, sessionName, panePid) {
22061
22765
  return null;
22062
22766
  }
22063
22767
  }
22768
+ function shellSingleQuote(value) {
22769
+ return `'${value.replace(/'/g, `'\\''`)}'`;
22770
+ }
22064
22771
  function buildShellPromptInitScriptContents(command) {
22065
22772
  const normalized = command.trim().toLowerCase();
22066
22773
  if (normalized === "zsh") {
@@ -22160,6 +22867,26 @@ clear
22160
22867
  ` : `${sourceCommand}
22161
22868
  `;
22162
22869
  }
22870
+
22871
+ // src/shell/shell-session-service.ts
22872
+ async function pathExists5(filePath) {
22873
+ try {
22874
+ await fs20.access(filePath);
22875
+ return true;
22876
+ } catch {
22877
+ return false;
22878
+ }
22879
+ }
22880
+ function nowIso() {
22881
+ return (/* @__PURE__ */ new Date()).toISOString();
22882
+ }
22883
+ function uniqueShellSessionName(baseName, index) {
22884
+ if (index <= 1) {
22885
+ return baseName;
22886
+ }
22887
+ const suffix = `-${index}`;
22888
+ return `${baseName.slice(0, Math.max(1, 64 - suffix.length))}${suffix}`;
22889
+ }
22163
22890
  function shellThreadId(shell) {
22164
22891
  if (!shell.threadId) {
22165
22892
  throw new ShellServiceError(
@@ -22220,25 +22947,25 @@ var ShellServiceError = class extends Error {
22220
22947
  code;
22221
22948
  };
22222
22949
  var ShellSessionService = class {
22223
- constructor(db, eventBus, tmuxManager) {
22950
+ constructor(db, eventBus, shellBackend) {
22224
22951
  this.db = db;
22225
22952
  this.eventBus = eventBus;
22226
- this.tmuxManager = tmuxManager;
22953
+ this.shellBackend = shellBackend;
22227
22954
  }
22228
22955
  db;
22229
22956
  eventBus;
22230
- tmuxManager;
22957
+ shellBackend;
22231
22958
  attachments = /* @__PURE__ */ new Map();
22232
22959
  async stop() {
22233
22960
  for (const [shellId, attachment] of this.attachments) {
22234
- clearInterval(attachment.pollHandle);
22961
+ attachment.backendAttachment.dispose();
22235
22962
  deleteViewerSessionRecord(this.db, attachment.viewerId);
22236
22963
  this.attachments.delete(shellId);
22237
22964
  }
22238
22965
  }
22239
22966
  async syncShellStateOnStartup() {
22240
22967
  const records = listShellSessionRecords(this.db);
22241
- const sessionNames = new Set(await this.tmuxManager.listSessionNames());
22968
+ const sessionNames = new Set(await this.shellBackend.listSessionNames());
22242
22969
  for (const record of records) {
22243
22970
  const sessionName = record.tmuxSessionName ?? "";
22244
22971
  const nextStatus = sessionNames.has(sessionName) ? "running" : record.status === "exited" ? "exited" : "not_found";
@@ -22261,23 +22988,33 @@ var ShellSessionService = class {
22261
22988
  if (!workspace) {
22262
22989
  throw new ShellServiceError("thread_not_found", "Workspace not found.");
22263
22990
  }
22264
- const shell = getShellSessionRecordByThreadId(this.db, threadId);
22991
+ const shells = listShellSessionRecordsByThreadId(this.db, threadId);
22265
22992
  const workspacePathStatus = await pathExists5(workspace.absPath) ? "present" : "missing";
22266
- if (!shell) {
22993
+ if (shells.length === 0) {
22267
22994
  return {
22268
22995
  threadId: thread.id,
22269
22996
  workspaceId: workspace.id,
22270
22997
  workspacePathStatus,
22271
22998
  state: workspacePathStatus === "missing" ? "workspace_missing" : "not_created",
22272
- shell: null
22999
+ shell: null,
23000
+ shells: [],
23001
+ activeShellId: null
22273
23002
  };
22274
23003
  }
23004
+ const shellDtos = await Promise.all(
23005
+ shells.map((shell) => this.toShellSessionDto(shell.id))
23006
+ );
23007
+ const activeShell = shellDtos.find((shell) => shell.status === "attached") ?? shellDtos.find(
23008
+ (shell) => shell.status !== "exited" && shell.status !== "not_found"
23009
+ ) ?? shellDtos[0] ?? null;
22275
23010
  return {
22276
23011
  threadId: thread.id,
22277
23012
  workspaceId: workspace.id,
22278
23013
  workspacePathStatus,
22279
- state: await this.resolveShellState(shell.id),
22280
- shell: await this.toShellSessionDto(shell.id)
23014
+ state: activeShell ? activeShell.status : "not_created",
23015
+ shell: activeShell,
23016
+ shells: shellDtos,
23017
+ activeShellId: activeShell?.id ?? null
22281
23018
  };
22282
23019
  }
22283
23020
  async createShellForThread(threadId, options = {}) {
@@ -22301,26 +23038,21 @@ var ShellSessionService = class {
22301
23038
  "Workspace path is missing on this machine."
22302
23039
  );
22303
23040
  }
22304
- const tmuxSessionName = this.tmuxManager.sessionNameForThread(thread.id);
22305
- const existing = getShellSessionRecordByThreadId(this.db, threadId);
22306
- if (existing) {
22307
- const canRevive = existing.status === "exited" || existing.status === "not_found";
22308
- if (!canRevive) {
22309
- throw new ShellServiceError(
22310
- "shell_exists",
22311
- "A durable shell already exists for this thread."
22312
- );
22313
- }
22314
- updateShellSessionRecord(this.db, existing.id, {
22315
- tmuxSessionName,
22316
- cwd: workspace.absPath,
22317
- status: "creating",
22318
- lastActivityAt: nowIso()
22319
- });
23041
+ const baseSessionName = this.shellBackend.sessionNameForThread(thread.id);
23042
+ const existingShells = listShellSessionRecordsByThreadId(this.db, threadId);
23043
+ const existingSessionNames = new Set(
23044
+ existingShells.map((shell) => shell.tmuxSessionName).filter((name) => Boolean(name))
23045
+ );
23046
+ let sessionIndex = existingShells.length + 1;
23047
+ let tmuxSessionName = uniqueShellSessionName(baseSessionName, sessionIndex);
23048
+ while (existingSessionNames.has(tmuxSessionName) || await this.shellBackend.hasSession(tmuxSessionName)) {
23049
+ sessionIndex += 1;
23050
+ tmuxSessionName = uniqueShellSessionName(baseSessionName, sessionIndex);
22320
23051
  }
22321
- const record = existing ?? createShellSessionRecord(this.db, {
23052
+ const record = createShellSessionRecord(this.db, {
22322
23053
  workspaceId: workspace.id,
22323
23054
  threadId: thread.id,
23055
+ label: options.label ?? null,
22324
23056
  tmuxSessionName,
22325
23057
  cwd: workspace.absPath,
22326
23058
  status: "creating"
@@ -22330,27 +23062,15 @@ var ShellSessionService = class {
22330
23062
  state: "creating"
22331
23063
  });
22332
23064
  try {
22333
- const existingSession = await this.tmuxManager.hasSession(tmuxSessionName);
23065
+ const existingSession = await this.shellBackend.hasSession(tmuxSessionName);
22334
23066
  if (!existingSession) {
22335
- await this.tmuxManager.createSession({
22336
- sessionName: tmuxSessionName,
23067
+ await this.shellBackend.createSession({
23068
+ sessionId: tmuxSessionName,
23069
+ threadId: thread.id,
22337
23070
  cwd: workspace.absPath,
22338
23071
  ...options.cols !== void 0 ? { cols: options.cols } : {},
22339
23072
  ...options.rows !== void 0 ? { rows: options.rows } : {}
22340
23073
  });
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
23074
  }
22355
23075
  updateShellSessionRecord(this.db, record.id, {
22356
23076
  status: "running",
@@ -22361,7 +23081,7 @@ var ShellSessionService = class {
22361
23081
  status: "not_found"
22362
23082
  });
22363
23083
  throw new ShellServiceError(
22364
- "tmux_error",
23084
+ "shell_backend_error",
22365
23085
  error instanceof Error ? error.message : "Unable to start shell."
22366
23086
  );
22367
23087
  }
@@ -22369,7 +23089,32 @@ var ShellSessionService = class {
22369
23089
  threadId: thread.id,
22370
23090
  state: "detached"
22371
23091
  });
22372
- return this.getThreadShellState(threadId);
23092
+ const state = await this.getThreadShellState(threadId);
23093
+ const createdShell = await this.toShellSessionDto(record.id);
23094
+ return {
23095
+ ...state,
23096
+ state: createdShell.status,
23097
+ shell: createdShell,
23098
+ activeShellId: createdShell.id
23099
+ };
23100
+ }
23101
+ async updateShell(shellId, input) {
23102
+ const shell = getShellSessionRecordById(this.db, shellId);
23103
+ if (!shell) {
23104
+ throw new ShellServiceError("shell_not_found", "Shell not found.");
23105
+ }
23106
+ const updates = {};
23107
+ if ("label" in input) {
23108
+ const label = input.label?.trim() ?? "";
23109
+ updates.label = label.length > 0 ? label : null;
23110
+ }
23111
+ updateShellSessionRecord(this.db, shell.id, updates);
23112
+ const shellDto = await this.toShellSessionDto(shell.id);
23113
+ this.emitShellEvent(shell.id, "shell.status", {
23114
+ threadId: shellThreadId(shell),
23115
+ state: shellDto.status
23116
+ });
23117
+ return shellDto;
22373
23118
  }
22374
23119
  async detachThreadViewers(threadId) {
22375
23120
  const shell = getShellSessionRecordByThreadId(this.db, threadId);
@@ -22381,7 +23126,7 @@ var ShellSessionService = class {
22381
23126
  deleteViewerSessionsByThreadId(this.db, threadId);
22382
23127
  return;
22383
23128
  }
22384
- clearInterval(attachment.pollHandle);
23129
+ attachment.backendAttachment.dispose();
22385
23130
  deleteViewerSessionRecord(this.db, attachment.viewerId);
22386
23131
  deleteViewerSessionsByThreadId(this.db, threadId);
22387
23132
  this.attachments.delete(shell.id);
@@ -22404,7 +23149,7 @@ var ShellSessionService = class {
22404
23149
  const existingViewer = getViewerSessionRecordByShellId(this.db, shell.id);
22405
23150
  const existingAttachment = this.attachments.get(shell.id);
22406
23151
  if (existingAttachment) {
22407
- clearInterval(existingAttachment.pollHandle);
23152
+ existingAttachment.backendAttachment.dispose();
22408
23153
  deleteViewerSessionRecord(this.db, existingAttachment.viewerId);
22409
23154
  this.attachments.delete(shell.id);
22410
23155
  this.emitShellEvent(shell.id, "shell.detached", {
@@ -22416,7 +23161,7 @@ var ShellSessionService = class {
22416
23161
  } else if (existingViewer) {
22417
23162
  deleteViewerSessionRecord(this.db, existingViewer.id);
22418
23163
  }
22419
- const hasSession = await this.tmuxManager.hasSession(shellSessionName(shell));
23164
+ const hasSession = await this.shellBackend.hasSession(shellSessionName(shell));
22420
23165
  if (!hasSession) {
22421
23166
  updateShellSessionRecord(this.db, shell.id, {
22422
23167
  status: "not_found"
@@ -22427,7 +23172,7 @@ var ShellSessionService = class {
22427
23172
  });
22428
23173
  throw new ShellServiceError(
22429
23174
  "shell_not_running",
22430
- "The durable shell is no longer available."
23175
+ "The terminal is no longer available."
22431
23176
  );
22432
23177
  }
22433
23178
  const viewer = createViewerSessionRecord(this.db, {
@@ -22435,31 +23180,52 @@ var ShellSessionService = class {
22435
23180
  shellId: shell.id,
22436
23181
  activeTab: "shell"
22437
23182
  });
22438
- await this.tmuxManager.resizeWindow(
22439
- shellSessionName(shell),
22440
- options.cols,
22441
- options.rows
22442
- );
22443
- const initialSnapshot = await this.tmuxManager.capturePane(shellSessionName(shell));
22444
- const initialRuntime = await this.tmuxManager.getPaneRuntimeInfo(
22445
- shellSessionName(shell)
22446
- );
23183
+ const attached = await this.shellBackend.attach(shellSessionName(shell), {
23184
+ cols: options.cols,
23185
+ rows: options.rows,
23186
+ onData: (data, session, backendOptions) => {
23187
+ updateShellSessionRecord(this.db, shell.id, {
23188
+ lastActivityAt: nowIso()
23189
+ });
23190
+ updateViewerSessionRecord(this.db, viewer.id, {
23191
+ lastHeartbeatAt: nowIso(),
23192
+ activeTab: "shell"
23193
+ });
23194
+ options.onData(
23195
+ data,
23196
+ shellOutputOptions({
23197
+ replace: backendOptions?.replace === true,
23198
+ cursorX: session.runtime.cursorX,
23199
+ cursorY: session.runtime.cursorY,
23200
+ paneHeight: session.runtime.paneHeight,
23201
+ cwdBaseName: basenameFromPath2(session.runtime.currentPath || shell.cwd),
23202
+ envPrefix: session.runtime.envPrefix ?? void 0,
23203
+ isCommandRunning: session.runtime.isCommandRunning
23204
+ })
23205
+ );
23206
+ },
23207
+ onExit: () => {
23208
+ void this.handleMissingShell(shell, viewer.id);
23209
+ }
23210
+ });
23211
+ const initialSnapshot = attached.session.snapshot;
23212
+ const initialRuntime = attached.session.runtime;
22447
23213
  const initialCwdBaseName = basenameFromPath2(initialRuntime.currentPath || shell.cwd);
22448
- const initialEnvPrefix = await resolvePaneEnvironmentPrefix(
22449
- this.tmuxManager,
22450
- shellSessionName(shell),
22451
- initialRuntime.panePid
22452
- );
22453
23214
  const attachment = {
22454
23215
  viewerId: viewer.id,
22455
23216
  onData: options.onData,
22456
- pollHandle: setInterval(() => {
22457
- void this.pollAttachment(shell.id);
22458
- }, 250),
22459
- lastSnapshot: initialSnapshot,
22460
- polling: false
23217
+ backendAttachment: attached.attachment
22461
23218
  };
22462
23219
  this.attachments.set(shell.id, attachment);
23220
+ updateShellSessionRecord(this.db, shell.id, {
23221
+ status: "running",
23222
+ lastActivityAt: nowIso()
23223
+ });
23224
+ const shellDto = await this.toShellSessionDto(shell.id);
23225
+ options.onConnected?.({
23226
+ viewerId: viewer.id,
23227
+ shell: shellDto
23228
+ });
22463
23229
  if (initialSnapshot) {
22464
23230
  options.onData(
22465
23231
  initialSnapshot,
@@ -22469,17 +23235,11 @@ var ShellSessionService = class {
22469
23235
  cursorY: initialRuntime.cursorY,
22470
23236
  paneHeight: initialRuntime.paneHeight,
22471
23237
  cwdBaseName: initialCwdBaseName,
22472
- envPrefix: initialEnvPrefix ?? void 0,
22473
- isCommandRunning: !isInteractiveShellCommand(
22474
- initialRuntime.currentCommand
22475
- )
23238
+ envPrefix: initialRuntime.envPrefix ?? void 0,
23239
+ isCommandRunning: initialRuntime.isCommandRunning
22476
23240
  })
22477
23241
  );
22478
23242
  }
22479
- updateShellSessionRecord(this.db, shell.id, {
22480
- status: "running",
22481
- lastActivityAt: nowIso()
22482
- });
22483
23243
  this.emitShellEvent(shell.id, "shell.status", {
22484
23244
  threadId,
22485
23245
  state: "attached",
@@ -22487,7 +23247,7 @@ var ShellSessionService = class {
22487
23247
  });
22488
23248
  return {
22489
23249
  viewerId: viewer.id,
22490
- shell: await this.toShellSessionDto(shell.id)
23250
+ shell: shellDto
22491
23251
  };
22492
23252
  }
22493
23253
  async detachShell(shellId, viewerId) {
@@ -22508,7 +23268,7 @@ var ShellSessionService = class {
22508
23268
  "This browser session does not own the shell attachment."
22509
23269
  );
22510
23270
  }
22511
- clearInterval(attachment.pollHandle);
23271
+ attachment.backendAttachment.dispose();
22512
23272
  deleteViewerSessionRecord(this.db, viewerId);
22513
23273
  this.attachments.delete(shell.id);
22514
23274
  updateShellSessionRecord(this.db, shell.id, {
@@ -22523,7 +23283,7 @@ var ShellSessionService = class {
22523
23283
  }
22524
23284
  async sendInput(shellId, viewerId, data) {
22525
23285
  const { shell } = this.requireOwnedAttachment(shellId, viewerId);
22526
- await this.tmuxManager.sendInput(shellSessionName(shell), data);
23286
+ await this.shellBackend.sendInput(shellSessionName(shell), data);
22527
23287
  updateShellSessionRecord(this.db, shellId, {
22528
23288
  lastActivityAt: nowIso()
22529
23289
  });
@@ -22535,10 +23295,7 @@ var ShellSessionService = class {
22535
23295
  async clearShell(shellId, viewerId) {
22536
23296
  const { shell, attachment } = this.requireOwnedAttachment(shellId, viewerId);
22537
23297
  const sessionName = shellSessionName(shell);
22538
- await this.tmuxManager.sendInput(sessionName, "\f");
22539
- await waitForShellTick(60);
22540
- await this.tmuxManager.clearHistory(sessionName);
22541
- await waitForShellTick(60);
23298
+ const session = await this.shellBackend.clear(sessionName);
22542
23299
  updateShellSessionRecord(this.db, shellId, {
22543
23300
  lastActivityAt: nowIso()
22544
23301
  });
@@ -22546,7 +23303,18 @@ var ShellSessionService = class {
22546
23303
  lastHeartbeatAt: nowIso(),
22547
23304
  activeTab: "shell"
22548
23305
  });
22549
- await this.pushSnapshot(shell, attachment);
23306
+ attachment.onData(
23307
+ session.snapshot,
23308
+ shellOutputOptions({
23309
+ replace: true,
23310
+ cursorX: session.runtime.cursorX,
23311
+ cursorY: session.runtime.cursorY,
23312
+ paneHeight: session.runtime.paneHeight,
23313
+ cwdBaseName: basenameFromPath2(session.runtime.currentPath || shell.cwd),
23314
+ envPrefix: session.runtime.envPrefix ?? void 0,
23315
+ isCommandRunning: session.runtime.isCommandRunning
23316
+ })
23317
+ );
22550
23318
  }
22551
23319
  async resizeShell(shellId, viewerId, cols, rows) {
22552
23320
  const shell = getShellSessionRecordById(this.db, shellId);
@@ -22566,7 +23334,7 @@ var ShellSessionService = class {
22566
23334
  "This browser session does not own the shell attachment."
22567
23335
  );
22568
23336
  }
22569
- await this.tmuxManager.resizeWindow(shellSessionName(shell), cols, rows);
23337
+ await this.shellBackend.resize(shellSessionName(shell), cols, rows);
22570
23338
  updateViewerSessionRecord(this.db, viewerId, {
22571
23339
  lastHeartbeatAt: nowIso(),
22572
23340
  activeTab: "shell"
@@ -22579,11 +23347,11 @@ var ShellSessionService = class {
22579
23347
  }
22580
23348
  const attachment = this.attachments.get(shell.id);
22581
23349
  if (attachment) {
22582
- clearInterval(attachment.pollHandle);
23350
+ attachment.backendAttachment.dispose();
22583
23351
  deleteViewerSessionRecord(this.db, attachment.viewerId);
22584
23352
  this.attachments.delete(shell.id);
22585
23353
  }
22586
- await this.tmuxManager.killSession(shellSessionName(shell));
23354
+ await this.shellBackend.killSession(shellSessionName(shell));
22587
23355
  updateShellSessionRecord(this.db, shell.id, {
22588
23356
  status: "exited",
22589
23357
  lastActivityAt: nowIso()
@@ -22605,7 +23373,9 @@ var ShellSessionService = class {
22605
23373
  id: shell.id,
22606
23374
  threadId: shellThreadId(shell),
22607
23375
  workspaceId: shell.workspaceId,
23376
+ label: shell.label ?? null,
22608
23377
  tmuxSessionName: shellSessionName(shell),
23378
+ backend: this.shellBackend.kind,
22609
23379
  cwd: shell.cwd,
22610
23380
  status: shellDtoStatus(shell.status, status),
22611
23381
  attachedViewerId: this.attachments.get(shell.id)?.viewerId ?? null,
@@ -22631,30 +23401,6 @@ var ShellSessionService = class {
22631
23401
  const attachment = this.attachments.get(shell.id);
22632
23402
  return attachment ? "attached" : "detached";
22633
23403
  }
22634
- async pollAttachment(shellId) {
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
23404
  requireOwnedAttachment(shellId, viewerId) {
22659
23405
  const shell = getShellSessionRecordById(this.db, shellId);
22660
23406
  if (!shell) {
@@ -22675,52 +23421,20 @@ var ShellSessionService = class {
22675
23421
  }
22676
23422
  return { shell, attachment };
22677
23423
  }
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;
23424
+ async handleMissingShell(shell, viewerId) {
23425
+ const attachment = this.attachments.get(shell.id);
23426
+ if (attachment) {
23427
+ attachment.backendAttachment.dispose();
23428
+ this.attachments.delete(shell.id);
22684
23429
  }
22685
- attachment.lastSnapshot = snapshot;
23430
+ deleteViewerSessionRecord(this.db, viewerId);
22686
23431
  updateShellSessionRecord(this.db, shell.id, {
23432
+ status: "not_found",
22687
23433
  lastActivityAt: nowIso()
22688
23434
  });
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
- async handleMissingShell(shell, viewerId) {
22711
- const attachment = this.attachments.get(shell.id);
22712
- if (attachment) {
22713
- clearInterval(attachment.pollHandle);
22714
- this.attachments.delete(shell.id);
22715
- }
22716
- deleteViewerSessionRecord(this.db, viewerId);
22717
- updateShellSessionRecord(this.db, shell.id, {
22718
- status: "not_found",
22719
- lastActivityAt: nowIso()
22720
- });
22721
- this.emitShellEvent(shell.id, "shell.exited", {
22722
- threadId: shellThreadId(shell),
22723
- state: "not_found"
23435
+ this.emitShellEvent(shell.id, "shell.exited", {
23436
+ threadId: shellThreadId(shell),
23437
+ state: "not_found"
22724
23438
  });
22725
23439
  }
22726
23440
  emitShellEvent(shellId, type, payload) {
@@ -22733,9 +23447,695 @@ var ShellSessionService = class {
22733
23447
  }
22734
23448
  };
22735
23449
 
22736
- // src/shell/tmux-manager.ts
22737
- import fs20 from "fs";
23450
+ // ../../packages/plugin-terminal/src/manifest.ts
23451
+ var TERMINAL_PLUGIN_ID = "remote-codex.terminal";
23452
+ var terminalPluginManifest = {
23453
+ id: TERMINAL_PLUGIN_ID,
23454
+ name: "Terminal",
23455
+ version: "0.1.0",
23456
+ description: "Built-in durable terminal panel backed by the supervisor PTY host.",
23457
+ remoteCodex: "^0.11.0",
23458
+ capabilities: {
23459
+ artifactTypes: [],
23460
+ timelineRenderers: [],
23461
+ threadPanels: [
23462
+ {
23463
+ id: "terminal",
23464
+ label: "Terminal",
23465
+ kind: "terminal",
23466
+ artifactTypes: []
23467
+ }
23468
+ ],
23469
+ frontend: {
23470
+ entry: "./dist/index.js"
23471
+ },
23472
+ backend: {
23473
+ entry: "./dist/backend.js"
23474
+ }
23475
+ }
23476
+ };
23477
+
23478
+ // ../../packages/plugin-xyz-viewer/src/manifest.ts
23479
+ var XYZ_MOLECULE_ARTIFACT_TYPE = "chemistry.molecule3d";
23480
+ var xyzViewerPluginManifest = {
23481
+ id: "remote-codex.xyz-viewer",
23482
+ name: "XYZ Molecule Viewer",
23483
+ version: "0.1.0",
23484
+ description: "A draft built-in plugin for previewing xyz, extxyz, cif, and pdb molecular structures with 3Dmol.js.",
23485
+ remoteCodex: "^0.11.0",
23486
+ capabilities: {
23487
+ artifactTypes: [
23488
+ {
23489
+ type: XYZ_MOLECULE_ARTIFACT_TYPE,
23490
+ title: "3D Molecule",
23491
+ fileExtensions: ["xyz", "extxyz", "cif", "pdb"]
23492
+ }
23493
+ ],
23494
+ timelineRenderers: [XYZ_MOLECULE_ARTIFACT_TYPE],
23495
+ threadPanels: [
23496
+ {
23497
+ id: "xyz-viewer",
23498
+ label: "Molecules",
23499
+ artifactTypes: [XYZ_MOLECULE_ARTIFACT_TYPE]
23500
+ }
23501
+ ],
23502
+ modelHints: [
23503
+ {
23504
+ id: "render-molecule",
23505
+ 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."
23506
+ }
23507
+ ],
23508
+ mcpServers: [
23509
+ {
23510
+ id: "remote-codex-plugin-mcp",
23511
+ name: "remote_codex_plugins",
23512
+ command: "node",
23513
+ args: ["bin/remote-codex-plugin-mcp.mjs"]
23514
+ }
23515
+ ],
23516
+ frontend: {
23517
+ entry: "./dist/index.js",
23518
+ style: "./src/styles.css"
23519
+ }
23520
+ }
23521
+ };
23522
+
23523
+ // src/plugins/builtin-plugins.ts
23524
+ var builtinPlugins = [
23525
+ {
23526
+ manifest: terminalPluginManifest,
23527
+ enabledByDefault: true
23528
+ },
23529
+ {
23530
+ manifest: xyzViewerPluginManifest,
23531
+ enabledByDefault: true
23532
+ }
23533
+ ];
23534
+
23535
+ // src/plugins/plugin-service.ts
23536
+ import fs21 from "fs/promises";
22738
23537
  import path20 from "path";
23538
+ var MANAGED_CODEX_MCP_BEGIN = "# BEGIN remote-codex managed plugin MCP servers";
23539
+ var MANAGED_CODEX_MCP_END = "# END remote-codex managed plugin MCP servers";
23540
+ var REMOTE_CODEX_MOLECULE_MCP_TOOL_NAME = "remote_codex_render_molecule";
23541
+ function jsonString(value) {
23542
+ return JSON.stringify(value);
23543
+ }
23544
+ function normalizeManagedCommand(server, repoRoot) {
23545
+ if (server.name === "remote_codex_plugins") {
23546
+ return {
23547
+ command: process.execPath,
23548
+ args: [path20.join(repoRoot, "bin", "remote-codex-plugin-mcp.mjs")]
23549
+ };
23550
+ }
23551
+ return {
23552
+ command: server.command,
23553
+ args: server.args ?? []
23554
+ };
23555
+ }
23556
+ function stripManagedCodexMcpBlock(content) {
23557
+ const pattern = new RegExp(
23558
+ `\\n?${MANAGED_CODEX_MCP_BEGIN.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${MANAGED_CODEX_MCP_END.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`,
23559
+ "g"
23560
+ );
23561
+ return content.replace(pattern, "\n").replace(/\n{3,}/g, "\n\n").trimEnd();
23562
+ }
23563
+ function stripCodexMcpServerTables(content, serverNames) {
23564
+ const names = new Set(serverNames);
23565
+ if (names.size === 0) {
23566
+ return content.trimEnd();
23567
+ }
23568
+ const output = [];
23569
+ let current = [];
23570
+ let shouldDropCurrentTable = false;
23571
+ function flushCurrent() {
23572
+ if (!shouldDropCurrentTable) {
23573
+ output.push(...current);
23574
+ }
23575
+ current = [];
23576
+ shouldDropCurrentTable = false;
23577
+ }
23578
+ for (const line of content.split("\n")) {
23579
+ const tableMatch = line.match(/^\s*\[([^\]]+)\]\s*$/);
23580
+ if (tableMatch) {
23581
+ flushCurrent();
23582
+ const tablePath = tableMatch[1] ?? "";
23583
+ shouldDropCurrentTable = [...names].some(
23584
+ (name) => tablePath === `mcp_servers.${name}` || tablePath.startsWith(`mcp_servers.${name}.`)
23585
+ );
23586
+ }
23587
+ current.push(line);
23588
+ }
23589
+ flushCurrent();
23590
+ return output.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd();
23591
+ }
23592
+ function buildManagedCodexMcpBlock(servers, repoRoot) {
23593
+ if (servers.length === 0) {
23594
+ return "";
23595
+ }
23596
+ const lines = [
23597
+ MANAGED_CODEX_MCP_BEGIN,
23598
+ "# This block is generated from enabled Remote Codex plugins."
23599
+ ];
23600
+ for (const server of servers) {
23601
+ const normalized = normalizeManagedCommand(server, repoRoot);
23602
+ lines.push(
23603
+ "",
23604
+ `[mcp_servers.${server.name}]`,
23605
+ `command = ${jsonString(normalized.command)}`,
23606
+ `args = ${JSON.stringify(normalized.args)}`
23607
+ );
23608
+ const envEntries = Object.entries(server.env ?? {});
23609
+ if (envEntries.length > 0) {
23610
+ lines.push(`[mcp_servers.${server.name}.env]`);
23611
+ for (const [key, value] of envEntries) {
23612
+ lines.push(`${key} = ${jsonString(value)}`);
23613
+ }
23614
+ }
23615
+ }
23616
+ lines.push(MANAGED_CODEX_MCP_END);
23617
+ return lines.join("\n");
23618
+ }
23619
+ function upsertManagedCodexMcpBlock(content, servers, repoRoot, managedServerNames = servers.map((server) => server.name)) {
23620
+ const stripped = stripCodexMcpServerTables(
23621
+ stripManagedCodexMcpBlock(content),
23622
+ managedServerNames
23623
+ );
23624
+ const managedBlock = buildManagedCodexMcpBlock(servers, repoRoot);
23625
+ if (!managedBlock) {
23626
+ return stripped ? `${stripped}
23627
+ ` : "";
23628
+ }
23629
+ return `${stripped ? `${stripped}
23630
+
23631
+ ` : ""}${managedBlock}
23632
+ `;
23633
+ }
23634
+ var PluginService = class {
23635
+ constructor(registry, settingsStore) {
23636
+ this.registry = registry;
23637
+ this.settingsStore = settingsStore;
23638
+ this.loadPersistedSettings();
23639
+ }
23640
+ registry;
23641
+ settingsStore;
23642
+ settings = {
23643
+ enabled: {},
23644
+ imported: []
23645
+ };
23646
+ listPlugins() {
23647
+ return this.registry.list();
23648
+ }
23649
+ getPlugin(pluginId) {
23650
+ return this.registry.get(pluginId);
23651
+ }
23652
+ setPluginEnabled(pluginId, enabled) {
23653
+ const plugin = this.registry.setEnabled(pluginId, enabled);
23654
+ this.settings.enabled[pluginId] = enabled;
23655
+ this.persistSettings();
23656
+ return plugin;
23657
+ }
23658
+ modelContextPrompt() {
23659
+ const hints = this.registry.enabledManifests().flatMap(
23660
+ (manifest) => manifest.capabilities.modelHints ?? []
23661
+ );
23662
+ const text2 = hints.map((hint) => hint.text.trim()).filter(Boolean).join("\n");
23663
+ return text2 || null;
23664
+ }
23665
+ enabledMcpServers() {
23666
+ const byName = /* @__PURE__ */ new Map();
23667
+ for (const manifest of this.registry.enabledManifests()) {
23668
+ for (const server of manifest.capabilities.mcpServers ?? []) {
23669
+ const existing = byName.get(server.name);
23670
+ if (existing) {
23671
+ byName.set(server.name, {
23672
+ ...existing,
23673
+ env: {
23674
+ ...existing.env ?? {},
23675
+ ...server.env ?? {}
23676
+ },
23677
+ pluginIds: [...existing.pluginIds, manifest.id]
23678
+ });
23679
+ } else {
23680
+ byName.set(server.name, {
23681
+ ...server,
23682
+ pluginIds: [manifest.id]
23683
+ });
23684
+ }
23685
+ }
23686
+ }
23687
+ return [...byName.values()].map(({ pluginIds, ...server }) => ({
23688
+ ...server,
23689
+ env: {
23690
+ ...server.env ?? {},
23691
+ REMOTE_CODEX_ENABLED_PLUGIN_IDS: [...new Set(pluginIds)].sort().join(",")
23692
+ }
23693
+ }));
23694
+ }
23695
+ managedMcpServerNames() {
23696
+ return [
23697
+ ...new Set(
23698
+ this.registry.list().flatMap(
23699
+ (plugin) => (plugin.capabilities.mcpServers ?? []).map((server) => server.name)
23700
+ )
23701
+ )
23702
+ ].sort();
23703
+ }
23704
+ async syncManagedCodexMcpConfig(input) {
23705
+ if (!input.codexHome) {
23706
+ return;
23707
+ }
23708
+ const configPath = path20.join(input.codexHome, "config.toml");
23709
+ let current = "";
23710
+ try {
23711
+ current = await fs21.readFile(configPath, "utf8");
23712
+ } catch (error) {
23713
+ if (error.code !== "ENOENT") {
23714
+ throw error;
23715
+ }
23716
+ }
23717
+ const next = upsertManagedCodexMcpBlock(
23718
+ current,
23719
+ this.enabledMcpServers(),
23720
+ input.repoRoot,
23721
+ this.managedMcpServerNames()
23722
+ );
23723
+ if (next === current) {
23724
+ return;
23725
+ }
23726
+ await fs21.mkdir(path20.dirname(configPath), { recursive: true });
23727
+ await fs21.writeFile(configPath, next, "utf8");
23728
+ }
23729
+ importPlugin(input) {
23730
+ const manifestInput = input.manifest ?? this.parseManifestJson(input.manifestJson);
23731
+ const manifest = parsePluginManifest(manifestInput);
23732
+ const enabled = input.enabled ?? true;
23733
+ const existing = this.registry.getRegistered(manifest.id);
23734
+ if (existing && existing.source !== "imported") {
23735
+ throw new Error(`Built-in plugin cannot be replaced: ${manifest.id}`);
23736
+ }
23737
+ this.registerImportedManifest(manifest, enabled);
23738
+ const existingIndex = this.settings.imported.findIndex(
23739
+ (entry) => entry.id === manifest.id
23740
+ );
23741
+ if (existingIndex >= 0) {
23742
+ this.settings.imported[existingIndex] = manifest;
23743
+ } else {
23744
+ this.settings.imported.push(manifest);
23745
+ }
23746
+ this.settings.enabled[manifest.id] = enabled;
23747
+ this.persistSettings();
23748
+ const plugin = this.registry.get(manifest.id);
23749
+ if (!plugin) {
23750
+ throw new Error(`Plugin import failed: ${manifest.id}`);
23751
+ }
23752
+ return plugin;
23753
+ }
23754
+ enrichTurnsWithArtifacts(input) {
23755
+ const manifests = this.registry.enabledManifests();
23756
+ if (manifests.length === 0) {
23757
+ return input.turns;
23758
+ }
23759
+ const turnsForExtraction = input.deferredDetails ? materializeDeferredDetailsForArtifactExtraction(
23760
+ input.turns,
23761
+ input.deferredDetails
23762
+ ) : input.turns;
23763
+ const enrichedTurns = appendArtifactItemsToTurns(
23764
+ turnsForExtraction,
23765
+ new ManifestArtifactExtractor(manifests),
23766
+ {
23767
+ threadId: input.threadId,
23768
+ workspacePath: input.workspacePath,
23769
+ now: (/* @__PURE__ */ new Date()).toISOString()
23770
+ }
23771
+ );
23772
+ return turnsForExtraction === input.turns ? enrichedTurns : restoreOriginalNonArtifactItems(enrichedTurns, input.turns);
23773
+ }
23774
+ loadPersistedSettings() {
23775
+ if (!this.settingsStore) {
23776
+ return;
23777
+ }
23778
+ this.settings = this.settingsStore.load();
23779
+ for (const manifest of this.settings.imported) {
23780
+ this.registerImportedManifest(
23781
+ manifest,
23782
+ this.settings.enabled[manifest.id] ?? true
23783
+ );
23784
+ }
23785
+ for (const [pluginId, enabled] of Object.entries(this.settings.enabled)) {
23786
+ if (this.registry.get(pluginId)) {
23787
+ this.registry.setEnabled(pluginId, enabled);
23788
+ }
23789
+ }
23790
+ }
23791
+ registerImportedManifest(manifest, enabled) {
23792
+ if (this.registry.get(manifest.id)) {
23793
+ const existing = this.registry.getRegistered(manifest.id);
23794
+ if (existing?.source === "imported") {
23795
+ this.registry.updateImported({
23796
+ manifest,
23797
+ enabledByDefault: enabled,
23798
+ source: "imported"
23799
+ });
23800
+ } else {
23801
+ this.registry.setEnabled(manifest.id, enabled);
23802
+ }
23803
+ return;
23804
+ }
23805
+ this.registry.register({
23806
+ manifest,
23807
+ enabledByDefault: enabled,
23808
+ source: "imported"
23809
+ });
23810
+ }
23811
+ persistSettings() {
23812
+ this.settingsStore?.save(this.settings);
23813
+ }
23814
+ parseManifestJson(manifestJson) {
23815
+ if (!manifestJson?.trim()) {
23816
+ throw new Error("Plugin import requires a manifest object or manifestJson string.");
23817
+ }
23818
+ return JSON.parse(manifestJson);
23819
+ }
23820
+ };
23821
+ function restoreOriginalNonArtifactItems(enrichedTurns, originalTurns) {
23822
+ const originalItemsByTurnId = new Map(
23823
+ originalTurns.map((turn) => [
23824
+ turn.id,
23825
+ new Map(turn.items.map((item) => [item.id, item]))
23826
+ ])
23827
+ );
23828
+ return enrichedTurns.map((turn) => {
23829
+ const originalItems = originalItemsByTurnId.get(turn.id);
23830
+ if (!originalItems) {
23831
+ return turn;
23832
+ }
23833
+ return {
23834
+ ...turn,
23835
+ items: turn.items.map(
23836
+ (item) => item.kind === "artifact" ? item : originalItems.get(item.id) ?? item
23837
+ )
23838
+ };
23839
+ });
23840
+ }
23841
+ function materializeDeferredDetailsForArtifactExtraction(turns, deferredDetails) {
23842
+ if (deferredDetails.size === 0) {
23843
+ return turns;
23844
+ }
23845
+ return turns.map((turn) => {
23846
+ let changed = false;
23847
+ const items = turn.items.map((item) => {
23848
+ if (!item.hasDeferredDetail || item.detailText || item.kind !== "toolCall" || ![item.text, item.previewText].some(
23849
+ (value) => typeof value === "string" && value.includes(REMOTE_CODEX_MOLECULE_MCP_TOOL_NAME)
23850
+ )) {
23851
+ return item;
23852
+ }
23853
+ const detail = deferredDetails.get(item.id);
23854
+ if (!detail?.text) {
23855
+ return item;
23856
+ }
23857
+ changed = true;
23858
+ return {
23859
+ ...item,
23860
+ detailText: detail.text
23861
+ };
23862
+ });
23863
+ return changed ? {
23864
+ ...turn,
23865
+ items
23866
+ } : turn;
23867
+ });
23868
+ }
23869
+
23870
+ // src/plugins/plugin-settings-store.ts
23871
+ var PLUGIN_SETTINGS_POLICY_KEY = "plugins";
23872
+ function emptySettings() {
23873
+ return {
23874
+ enabled: {},
23875
+ imported: []
23876
+ };
23877
+ }
23878
+ function parseEnabled(value) {
23879
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
23880
+ return {};
23881
+ }
23882
+ const output = {};
23883
+ for (const [key, enabled] of Object.entries(value)) {
23884
+ if (typeof enabled === "boolean") {
23885
+ output[key] = enabled;
23886
+ } else if (enabled && typeof enabled === "object" && !Array.isArray(enabled) && typeof enabled.enabled === "boolean") {
23887
+ output[key] = enabled.enabled;
23888
+ }
23889
+ }
23890
+ return output;
23891
+ }
23892
+ var PluginSettingsStore = class {
23893
+ constructor(db) {
23894
+ this.db = db;
23895
+ }
23896
+ db;
23897
+ load() {
23898
+ const record = getPolicyRecordByKey(this.db, PLUGIN_SETTINGS_POLICY_KEY);
23899
+ if (!record?.valueJson) {
23900
+ return emptySettings();
23901
+ }
23902
+ try {
23903
+ const parsed = JSON.parse(record.valueJson);
23904
+ return {
23905
+ enabled: parseEnabled(parsed.enabled),
23906
+ imported: Array.isArray(parsed.imported) ? parsed.imported.map((entry) => parsePluginManifest(entry)) : []
23907
+ };
23908
+ } catch {
23909
+ return emptySettings();
23910
+ }
23911
+ }
23912
+ save(settings) {
23913
+ upsertPolicyRecord(
23914
+ this.db,
23915
+ PLUGIN_SETTINGS_POLICY_KEY,
23916
+ JSON.stringify({
23917
+ enabled: settings.enabled,
23918
+ imported: settings.imported
23919
+ })
23920
+ );
23921
+ }
23922
+ };
23923
+
23924
+ // src/plugins/backend-plugin-host.ts
23925
+ var BackendPluginHost = class {
23926
+ constructor(app2) {
23927
+ this.app = app2;
23928
+ }
23929
+ app;
23930
+ socketHandlers = [];
23931
+ registerSocketHandler(handler) {
23932
+ this.socketHandlers.push(handler);
23933
+ }
23934
+ register(contribution) {
23935
+ contribution.registerHttp?.(this.app);
23936
+ contribution.registerSocket?.(this);
23937
+ }
23938
+ async handleSocketMessage(context) {
23939
+ for (const handler of this.socketHandlers) {
23940
+ if (await handler(context)) {
23941
+ return true;
23942
+ }
23943
+ }
23944
+ return false;
23945
+ }
23946
+ };
23947
+
23948
+ // src/shell/pty-shell-backend.ts
23949
+ import path21 from "path";
23950
+ import { spawn as spawn4 } from "@homebridge/node-pty-prebuilt-multiarch";
23951
+
23952
+ // src/shell/default-shell.ts
23953
+ import fs22 from "fs";
23954
+ var POSIX_SHELL_CANDIDATES = ["/bin/bash", "/usr/bin/bash", "/bin/sh"];
23955
+ function resolveDefaultShell(env = process.env) {
23956
+ if (process.platform === "win32") {
23957
+ return env.COMSPEC ?? "cmd.exe";
23958
+ }
23959
+ if (env.SHELL && fs22.existsSync(env.SHELL)) {
23960
+ return env.SHELL;
23961
+ }
23962
+ return POSIX_SHELL_CANDIDATES.find((candidate) => fs22.existsSync(candidate)) ?? "/bin/sh";
23963
+ }
23964
+
23965
+ // src/shell/pty-shell-backend.ts
23966
+ var MAX_SCROLLBACK_BYTES = 512 * 1024;
23967
+ var ANSI_ESCAPE_PATTERN = new RegExp(String.raw`\u001B\[[0-?]*[ -/]*[@-~]`, "g");
23968
+ function shellArgs(shell) {
23969
+ const shellName = path21.basename(shell).toLowerCase();
23970
+ if (process.platform === "win32") {
23971
+ return [];
23972
+ }
23973
+ if (shellName === "bash" || shellName === "zsh" || shellName === "sh") {
23974
+ return ["-l"];
23975
+ }
23976
+ return [];
23977
+ }
23978
+ function trimScrollback(value) {
23979
+ if (value.length <= MAX_SCROLLBACK_BYTES) {
23980
+ return value;
23981
+ }
23982
+ return value.slice(value.length - MAX_SCROLLBACK_BYTES);
23983
+ }
23984
+ function lastVisibleLine(snapshot) {
23985
+ const normalized = snapshot.replace(ANSI_ESCAPE_PATTERN, "");
23986
+ const lines = normalized.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
23987
+ return lines.findLast((line) => line.trim().length > 0) ?? "";
23988
+ }
23989
+ function inferRuntime(session) {
23990
+ const promptLine = lastVisibleLine(session.scrollback);
23991
+ const shell = path21.basename(session.shell);
23992
+ const isCommandRunning = session.exitCode !== null ? false : !/[$#>]\s*$/.test(promptLine.trimEnd());
23993
+ return {
23994
+ panePid: session.pty.pid,
23995
+ paneWidth: session.pty.cols,
23996
+ paneHeight: session.pty.rows,
23997
+ currentCommand: isCommandRunning ? session.pty.process : shell,
23998
+ currentPath: session.cwd,
23999
+ isCommandRunning
24000
+ };
24001
+ }
24002
+ var PtyShellBackend = class {
24003
+ kind = "pty";
24004
+ sessions = /* @__PURE__ */ new Map();
24005
+ shell = resolveDefaultShell();
24006
+ sessionNameForThread(threadId) {
24007
+ return `rcx-${threadId.replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 28)}`;
24008
+ }
24009
+ async listSessionNames() {
24010
+ return [...this.sessions.keys()];
24011
+ }
24012
+ async hasSession(sessionId) {
24013
+ return this.sessions.has(sessionId);
24014
+ }
24015
+ async createSession(input) {
24016
+ if (this.sessions.has(input.sessionId)) {
24017
+ return;
24018
+ }
24019
+ const pty = spawn4(this.shell, shellArgs(this.shell), {
24020
+ name: "xterm-256color",
24021
+ cwd: input.cwd,
24022
+ cols: input.cols ?? 120,
24023
+ rows: input.rows ?? 36,
24024
+ env: {
24025
+ ...process.env,
24026
+ TERM: "xterm-256color",
24027
+ COLORTERM: process.env.COLORTERM ?? "truecolor"
24028
+ },
24029
+ handleFlowControl: true
24030
+ });
24031
+ const session = {
24032
+ id: input.sessionId,
24033
+ cwd: input.cwd,
24034
+ shell: this.shell,
24035
+ pty,
24036
+ scrollback: "",
24037
+ exitCode: null,
24038
+ listeners: /* @__PURE__ */ new Set(),
24039
+ exitListeners: /* @__PURE__ */ new Set(),
24040
+ dataSubscription: { dispose() {
24041
+ } },
24042
+ exitSubscription: { dispose() {
24043
+ } }
24044
+ };
24045
+ session.dataSubscription = pty.onData((data) => {
24046
+ session.scrollback = trimScrollback(session.scrollback + data);
24047
+ for (const listener of session.listeners) {
24048
+ listener(data);
24049
+ }
24050
+ });
24051
+ session.exitSubscription = pty.onExit(() => {
24052
+ session.exitCode = 0;
24053
+ this.sessions.delete(session.id);
24054
+ for (const listener of session.exitListeners) {
24055
+ listener();
24056
+ }
24057
+ session.dataSubscription.dispose();
24058
+ session.exitSubscription.dispose();
24059
+ });
24060
+ this.sessions.set(input.sessionId, session);
24061
+ }
24062
+ async attach(sessionId, options) {
24063
+ const session = this.requireSession(sessionId);
24064
+ this.resizeIfChanged(session, options.cols, options.rows);
24065
+ const onData = (data) => options.onData(data, this.toBackendSession(session));
24066
+ const onExit = () => options.onExit();
24067
+ session.listeners.add(onData);
24068
+ session.exitListeners.add(onExit);
24069
+ return {
24070
+ session: this.toBackendSession(session),
24071
+ attachment: {
24072
+ dispose: () => {
24073
+ session.listeners.delete(onData);
24074
+ session.exitListeners.delete(onExit);
24075
+ }
24076
+ }
24077
+ };
24078
+ }
24079
+ async sendInput(sessionId, data) {
24080
+ this.requireSession(sessionId).pty.write(data);
24081
+ }
24082
+ async clear(sessionId) {
24083
+ const session = this.requireSession(sessionId);
24084
+ session.scrollback = "";
24085
+ session.pty.clear();
24086
+ session.pty.write("\f");
24087
+ return this.toBackendSession(session);
24088
+ }
24089
+ async resize(sessionId, cols, rows) {
24090
+ this.resizeIfChanged(this.requireSession(sessionId), cols, rows);
24091
+ }
24092
+ async snapshot(sessionId) {
24093
+ return this.toBackendSession(this.requireSession(sessionId));
24094
+ }
24095
+ async killSession(sessionId) {
24096
+ const session = this.sessions.get(sessionId);
24097
+ if (!session) {
24098
+ return;
24099
+ }
24100
+ session.dataSubscription.dispose();
24101
+ session.exitSubscription.dispose();
24102
+ this.sessions.delete(sessionId);
24103
+ try {
24104
+ session.pty.kill();
24105
+ } catch {
24106
+ }
24107
+ }
24108
+ requireSession(sessionId) {
24109
+ const session = this.sessions.get(sessionId);
24110
+ if (!session) {
24111
+ throw new Error("Shell session is no longer available.");
24112
+ }
24113
+ return session;
24114
+ }
24115
+ resizeIfChanged(session, cols, rows) {
24116
+ if (cols <= 0 || rows <= 0) {
24117
+ return;
24118
+ }
24119
+ if (session.pty.cols === cols && session.pty.rows === rows) {
24120
+ return;
24121
+ }
24122
+ session.pty.resize(cols, rows);
24123
+ }
24124
+ toBackendSession(session) {
24125
+ return {
24126
+ id: session.id,
24127
+ cwd: session.cwd,
24128
+ cols: session.pty.cols,
24129
+ rows: session.pty.rows,
24130
+ snapshot: session.scrollback,
24131
+ runtime: inferRuntime(session)
24132
+ };
24133
+ }
24134
+ };
24135
+
24136
+ // src/shell/tmux-manager.ts
24137
+ import fs23 from "fs";
24138
+ import path22 from "path";
22739
24139
  import { spawn as spawnChild } from "child_process";
22740
24140
  async function defaultExecCommand(command, args) {
22741
24141
  return await new Promise((resolve, reject) => {
@@ -22762,17 +24162,17 @@ async function defaultExecCommand(command, args) {
22762
24162
  });
22763
24163
  }
22764
24164
  function resolveExecutablePath(command) {
22765
- if (command.includes(path20.sep)) {
24165
+ if (command.includes(path22.sep)) {
22766
24166
  return command;
22767
24167
  }
22768
24168
  const searchPath = process.env.PATH ?? "";
22769
- for (const entry of searchPath.split(path20.delimiter)) {
24169
+ for (const entry of searchPath.split(path22.delimiter)) {
22770
24170
  const trimmed = entry.trim();
22771
24171
  if (!trimmed) {
22772
24172
  continue;
22773
24173
  }
22774
- const candidate = path20.join(trimmed, command);
22775
- if (fs20.existsSync(candidate)) {
24174
+ const candidate = path22.join(trimmed, command);
24175
+ if (fs23.existsSync(candidate)) {
22776
24176
  return candidate;
22777
24177
  }
22778
24178
  }
@@ -22784,7 +24184,7 @@ var TmuxManager = class {
22784
24184
  execCommand;
22785
24185
  constructor(options = {}) {
22786
24186
  this.command = resolveExecutablePath(options.command ?? "tmux");
22787
- this.defaultShell = options.defaultShell ?? process.env.SHELL ?? "/bin/zsh";
24187
+ this.defaultShell = options.defaultShell ?? resolveDefaultShell();
22788
24188
  this.execCommand = options.execCommand ?? defaultExecCommand;
22789
24189
  }
22790
24190
  sessionNameForThread(threadId) {
@@ -23087,211 +24487,383 @@ function tokenizeTmuxInput(data) {
23087
24487
  return tokens;
23088
24488
  }
23089
24489
 
23090
- // ../../packages/plugin-xyz-viewer/src/manifest.ts
23091
- var XYZ_MOLECULE_ARTIFACT_TYPE = "chemistry.molecule3d";
23092
- var xyzViewerPluginManifest = {
23093
- id: "remote-codex.xyz-viewer",
23094
- name: "XYZ Molecule Viewer",
23095
- version: "0.1.0",
23096
- description: "A draft built-in plugin for previewing xyz, extxyz, cif, and pdb molecular structures with 3Dmol.js.",
23097
- remoteCodex: "^0.11.0",
23098
- capabilities: {
23099
- artifactTypes: [
23100
- {
23101
- type: XYZ_MOLECULE_ARTIFACT_TYPE,
23102
- title: "3D Molecule",
23103
- fileExtensions: ["xyz", "extxyz", "cif", "pdb"]
23104
- }
23105
- ],
23106
- timelineRenderers: [XYZ_MOLECULE_ARTIFACT_TYPE],
23107
- threadPanels: [
23108
- {
23109
- id: "xyz-viewer",
23110
- label: "Molecules",
23111
- artifactTypes: [XYZ_MOLECULE_ARTIFACT_TYPE]
23112
- }
23113
- ],
23114
- frontend: {
23115
- entry: "./dist/index.js",
23116
- style: "./src/styles.css"
23117
- }
23118
- }
23119
- };
23120
-
23121
- // src/plugins/builtin-plugins.ts
23122
- var builtinPlugins = [
23123
- {
23124
- manifest: xyzViewerPluginManifest,
23125
- enabledByDefault: true
23126
- }
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();
24490
+ // src/shell/tmux-shell-backend.ts
24491
+ var TmuxShellBackend = class {
24492
+ constructor(tmuxManager = new TmuxManager()) {
24493
+ this.tmuxManager = tmuxManager;
23135
24494
  }
23136
- registry;
23137
- settingsStore;
23138
- settings = {
23139
- enabled: {},
23140
- imported: []
23141
- };
23142
- listPlugins() {
23143
- return this.registry.list();
24495
+ tmuxManager;
24496
+ kind = "tmux";
24497
+ attachments = /* @__PURE__ */ new Map();
24498
+ sessionNameForThread(threadId) {
24499
+ return this.tmuxManager.sessionNameForThread(threadId);
23144
24500
  }
23145
- getPlugin(pluginId) {
23146
- return this.registry.get(pluginId);
24501
+ listSessionNames() {
24502
+ return this.tmuxManager.listSessionNames();
23147
24503
  }
23148
- setPluginEnabled(pluginId, enabled) {
23149
- const plugin = this.registry.setEnabled(pluginId, enabled);
23150
- this.settings.enabled[pluginId] = enabled;
23151
- this.persistSettings();
23152
- return plugin;
24504
+ hasSession(sessionId) {
24505
+ return this.tmuxManager.hasSession(sessionId);
23153
24506
  }
23154
- importPlugin(input) {
23155
- const manifestInput = input.manifest ?? this.parseManifestJson(input.manifestJson);
23156
- const manifest = parsePluginManifest(manifestInput);
23157
- const enabled = input.enabled ?? true;
23158
- const existing = this.registry.getRegistered(manifest.id);
23159
- if (existing && existing.source !== "imported") {
23160
- throw new Error(`Built-in plugin cannot be replaced: ${manifest.id}`);
23161
- }
23162
- this.registerImportedManifest(manifest, enabled);
23163
- const existingIndex = this.settings.imported.findIndex(
23164
- (entry) => entry.id === manifest.id
23165
- );
23166
- if (existingIndex >= 0) {
23167
- this.settings.imported[existingIndex] = manifest;
23168
- } else {
23169
- this.settings.imported.push(manifest);
23170
- }
23171
- this.settings.enabled[manifest.id] = enabled;
23172
- this.persistSettings();
23173
- const plugin = this.registry.get(manifest.id);
23174
- if (!plugin) {
23175
- throw new Error(`Plugin import failed: ${manifest.id}`);
24507
+ async createSession(input) {
24508
+ await this.tmuxManager.createSession({
24509
+ sessionName: input.sessionId,
24510
+ cwd: input.cwd,
24511
+ ...input.cols !== void 0 ? { cols: input.cols } : {},
24512
+ ...input.rows !== void 0 ? { rows: input.rows } : {}
24513
+ });
24514
+ try {
24515
+ const runtime = await this.tmuxManager.getPaneRuntimeInfo(input.sessionId);
24516
+ if (isInteractiveShellCommand(runtime.currentCommand)) {
24517
+ await this.tmuxManager.sendInput(
24518
+ input.sessionId,
24519
+ await buildShellPromptInitCommand(runtime.currentCommand, {
24520
+ clearScreen: true
24521
+ })
24522
+ );
24523
+ }
24524
+ } catch {
23176
24525
  }
23177
- return plugin;
23178
24526
  }
23179
- enrichTurnsWithArtifacts(input) {
23180
- const manifests = this.registry.enabledManifests();
23181
- if (manifests.length === 0) {
23182
- return input.turns;
24527
+ async attach(sessionId, options) {
24528
+ const previous = this.attachments.get(sessionId);
24529
+ if (previous) {
24530
+ previous.disposed = true;
24531
+ clearInterval(previous.pollHandle);
24532
+ this.attachments.delete(sessionId);
23183
24533
  }
23184
- return appendArtifactItemsToTurns(
23185
- input.turns,
23186
- new ManifestArtifactExtractor(manifests),
23187
- {
23188
- threadId: input.threadId,
23189
- workspacePath: input.workspacePath,
23190
- now: (/* @__PURE__ */ new Date()).toISOString()
24534
+ await this.tmuxManager.resizeWindow(sessionId, options.cols, options.rows);
24535
+ const session = await this.snapshot(sessionId);
24536
+ const attachment = {
24537
+ disposed: false,
24538
+ lastSnapshot: session.snapshot,
24539
+ polling: false,
24540
+ pollHandle: setInterval(() => {
24541
+ void this.poll(sessionId, attachment, options);
24542
+ }, 250)
24543
+ };
24544
+ this.attachments.set(sessionId, attachment);
24545
+ return {
24546
+ session,
24547
+ attachment: {
24548
+ dispose: () => {
24549
+ attachment.disposed = true;
24550
+ clearInterval(attachment.pollHandle);
24551
+ if (this.attachments.get(sessionId) === attachment) {
24552
+ this.attachments.delete(sessionId);
24553
+ }
24554
+ }
23191
24555
  }
23192
- );
24556
+ };
23193
24557
  }
23194
- loadPersistedSettings() {
23195
- if (!this.settingsStore) {
23196
- return;
23197
- }
23198
- this.settings = this.settingsStore.load();
23199
- for (const manifest of this.settings.imported) {
23200
- this.registerImportedManifest(
23201
- manifest,
23202
- this.settings.enabled[manifest.id] ?? true
23203
- );
24558
+ sendInput(sessionId, data) {
24559
+ return this.tmuxManager.sendInput(sessionId, data);
24560
+ }
24561
+ async clear(sessionId) {
24562
+ await this.tmuxManager.sendInput(sessionId, "\f");
24563
+ await this.tmuxManager.clearHistory(sessionId);
24564
+ const session = await this.snapshot(sessionId);
24565
+ const attachment = this.attachments.get(sessionId);
24566
+ if (attachment) {
24567
+ attachment.lastSnapshot = session.snapshot;
23204
24568
  }
23205
- for (const [pluginId, enabled] of Object.entries(this.settings.enabled)) {
23206
- if (this.registry.get(pluginId)) {
23207
- this.registry.setEnabled(pluginId, enabled);
24569
+ return session;
24570
+ }
24571
+ resize(sessionId, cols, rows) {
24572
+ return this.tmuxManager.resizeWindow(sessionId, cols, rows);
24573
+ }
24574
+ async snapshot(sessionId) {
24575
+ const snapshot = await this.tmuxManager.capturePane(sessionId);
24576
+ const runtime = await this.tmuxManager.getPaneRuntimeInfo(sessionId);
24577
+ const envPrefix = await resolvePaneEnvironmentPrefix(
24578
+ this.tmuxManager,
24579
+ sessionId,
24580
+ runtime.panePid
24581
+ );
24582
+ return {
24583
+ id: sessionId,
24584
+ cwd: runtime.currentPath,
24585
+ cols: runtime.paneWidth,
24586
+ rows: runtime.paneHeight,
24587
+ snapshot,
24588
+ runtime: {
24589
+ cursorX: runtime.cursorX,
24590
+ cursorY: runtime.cursorY,
24591
+ paneWidth: runtime.paneWidth,
24592
+ paneHeight: runtime.paneHeight,
24593
+ panePid: runtime.panePid,
24594
+ currentCommand: runtime.currentCommand,
24595
+ currentPath: runtime.currentPath,
24596
+ envPrefix,
24597
+ isCommandRunning: !isInteractiveShellCommand(runtime.currentCommand)
23208
24598
  }
24599
+ };
24600
+ }
24601
+ killSession(sessionId) {
24602
+ const attachment = this.attachments.get(sessionId);
24603
+ if (attachment) {
24604
+ attachment.disposed = true;
24605
+ clearInterval(attachment.pollHandle);
24606
+ this.attachments.delete(sessionId);
23209
24607
  }
24608
+ return this.tmuxManager.killSession(sessionId);
23210
24609
  }
23211
- registerImportedManifest(manifest, enabled) {
23212
- if (this.registry.get(manifest.id)) {
23213
- const existing = this.registry.getRegistered(manifest.id);
23214
- if (existing?.source === "imported") {
23215
- this.registry.updateImported({
23216
- manifest,
23217
- enabledByDefault: enabled,
23218
- source: "imported"
23219
- });
23220
- } else {
23221
- this.registry.setEnabled(manifest.id, enabled);
23222
- }
24610
+ async poll(sessionId, attachment, options) {
24611
+ if (attachment.disposed || attachment.polling) {
23223
24612
  return;
23224
24613
  }
23225
- this.registry.register({
23226
- manifest,
23227
- enabledByDefault: enabled,
23228
- source: "imported"
23229
- });
23230
- }
23231
- persistSettings() {
23232
- this.settingsStore?.save(this.settings);
23233
- }
23234
- parseManifestJson(manifestJson) {
23235
- if (!manifestJson?.trim()) {
23236
- throw new Error("Plugin import requires a manifest object or manifestJson string.");
24614
+ attachment.polling = true;
24615
+ try {
24616
+ const exists2 = await this.hasSession(sessionId);
24617
+ if (!exists2) {
24618
+ attachment.disposed = true;
24619
+ clearInterval(attachment.pollHandle);
24620
+ this.attachments.delete(sessionId);
24621
+ options.onExit();
24622
+ return;
24623
+ }
24624
+ const session = await this.snapshot(sessionId);
24625
+ if (session.snapshot !== attachment.lastSnapshot) {
24626
+ attachment.lastSnapshot = session.snapshot;
24627
+ options.onData(session.snapshot, session, { replace: true });
24628
+ }
24629
+ } catch {
24630
+ attachment.disposed = true;
24631
+ clearInterval(attachment.pollHandle);
24632
+ this.attachments.delete(sessionId);
24633
+ options.onExit();
24634
+ } finally {
24635
+ attachment.polling = false;
23237
24636
  }
23238
- return JSON.parse(manifestJson);
23239
24637
  }
23240
24638
  };
23241
24639
 
23242
- // src/plugins/plugin-settings-store.ts
23243
- var PLUGIN_SETTINGS_POLICY_KEY = "plugins";
23244
- function emptySettings() {
23245
- return {
23246
- enabled: {},
23247
- imported: []
23248
- };
24640
+ // src/routes/shells.ts
24641
+ import { z as z8 } from "zod";
24642
+ async function registerShellRoutes(app2, options = {}) {
24643
+ const threadIdParams = z8.object({ id: z8.string().uuid() });
24644
+ const shellIdParams = z8.object({ id: z8.string().uuid() });
24645
+ const createShellSchema = z8.object({
24646
+ cols: z8.number().int().positive().optional(),
24647
+ rows: z8.number().int().positive().optional(),
24648
+ label: z8.string().trim().min(1).max(80).optional()
24649
+ });
24650
+ const updateShellSchema = z8.object({
24651
+ label: z8.string().trim().min(1).max(80).nullable().optional()
24652
+ });
24653
+ const routeOptions = options.preHandler ? { preHandler: options.preHandler } : {};
24654
+ app2.get("/api/threads/:id/shell", routeOptions, async (request) => {
24655
+ const params = threadIdParams.parse(request.params);
24656
+ return app2.services.shellService.getThreadShellState(params.id);
24657
+ });
24658
+ app2.post("/api/threads/:id/shell", routeOptions, async (request) => {
24659
+ const params = threadIdParams.parse(request.params);
24660
+ const body = createShellSchema.parse(request.body ?? {});
24661
+ const input = {
24662
+ ...body.cols !== void 0 ? { cols: body.cols } : {},
24663
+ ...body.rows !== void 0 ? { rows: body.rows } : {},
24664
+ ...body.label !== void 0 ? { label: body.label } : {}
24665
+ };
24666
+ return app2.services.shellService.createShellForThread(params.id, input);
24667
+ });
24668
+ app2.post("/api/shells/:id/terminate", routeOptions, async (request) => {
24669
+ const params = shellIdParams.parse(request.params);
24670
+ return app2.services.shellService.terminateShell(params.id);
24671
+ });
24672
+ app2.patch("/api/shells/:id", routeOptions, async (request) => {
24673
+ const params = shellIdParams.parse(request.params);
24674
+ const body = updateShellSchema.parse(request.body ?? {});
24675
+ const input = {
24676
+ ..."label" in body ? { label: body.label ?? null } : {}
24677
+ };
24678
+ return app2.services.shellService.updateShell(params.id, input);
24679
+ });
23249
24680
  }
23250
- function parseEnabled(value) {
23251
- if (!value || typeof value !== "object" || Array.isArray(value)) {
23252
- return {};
24681
+
24682
+ // src/plugins/terminal-plugin-backend.ts
24683
+ function createTerminalShellBackend(env = process.env) {
24684
+ return env.REMOTE_CODEX_SHELL_BACKEND === "tmux" ? new TmuxShellBackend() : new PtyShellBackend();
24685
+ }
24686
+ function isTerminalPluginEnabled(app2) {
24687
+ return app2.services.pluginService.getPlugin(TERMINAL_PLUGIN_ID)?.enabled === true;
24688
+ }
24689
+ function requireTerminalPluginEnabled(app2) {
24690
+ if (!isTerminalPluginEnabled(app2)) {
24691
+ throw new ShellServiceError(
24692
+ "plugin_disabled",
24693
+ "The Terminal plugin is disabled."
24694
+ );
23253
24695
  }
23254
- const output = {};
23255
- for (const [key, enabled] of Object.entries(value)) {
23256
- if (typeof enabled === "boolean") {
23257
- output[key] = enabled;
23258
- } else if (enabled && typeof enabled === "object" && !Array.isArray(enabled) && typeof enabled.enabled === "boolean") {
23259
- output[key] = enabled.enabled;
24696
+ }
24697
+ function registerTerminalPluginBackend(app2) {
24698
+ app2.register(registerShellRoutes, {
24699
+ preHandler: async () => {
24700
+ requireTerminalPluginEnabled(app2);
23260
24701
  }
23261
- }
23262
- return output;
24702
+ });
23263
24703
  }
23264
- var PluginSettingsStore = class {
23265
- constructor(db) {
23266
- this.db = db;
23267
- }
23268
- db;
23269
- load() {
23270
- const record = getPolicyRecordByKey(this.db, PLUGIN_SETTINGS_POLICY_KEY);
23271
- if (!record?.valueJson) {
23272
- return emptySettings();
24704
+ function createTerminalPluginBackendContribution() {
24705
+ return {
24706
+ pluginId: TERMINAL_PLUGIN_ID,
24707
+ registerHttp: registerTerminalPluginBackend,
24708
+ registerSocket(host2) {
24709
+ host2.registerSocketHandler(createTerminalSocketHandler());
23273
24710
  }
24711
+ };
24712
+ }
24713
+ function createTerminalSocketHandler() {
24714
+ return async (context) => {
24715
+ const { app: app2, message, send } = context;
24716
+ const shellService = app2.services.shellService;
24717
+ const stateKey = `${TERMINAL_PLUGIN_ID}:attached-shell`;
24718
+ const cleanupRegisteredKey = `${TERMINAL_PLUGIN_ID}:cleanup-registered`;
24719
+ const getAttachedShell = () => context.state.get(stateKey);
24720
+ const setAttachedShell = (value) => {
24721
+ if (value) {
24722
+ context.state.set(stateKey, value);
24723
+ } else {
24724
+ context.state.delete(stateKey);
24725
+ }
24726
+ };
23274
24727
  try {
23275
- const parsed = JSON.parse(record.valueJson);
23276
- return {
23277
- enabled: parseEnabled(parsed.enabled),
23278
- imported: Array.isArray(parsed.imported) ? parsed.imported.map((entry) => parsePluginManifest(entry)) : []
23279
- };
23280
- } catch {
23281
- return emptySettings();
24728
+ if (message.type === "shell.attach") {
24729
+ requireTerminalPluginEnabled(app2);
24730
+ const attachedShell = getAttachedShell();
24731
+ if (attachedShell && attachedShell.shellId !== message.shellId) {
24732
+ await shellService.detachShell(
24733
+ attachedShell.shellId,
24734
+ attachedShell.viewerId
24735
+ );
24736
+ setAttachedShell(null);
24737
+ }
24738
+ const attachment = await shellService.attachShell(message.shellId, {
24739
+ cols: message.cols,
24740
+ rows: message.rows,
24741
+ onConnected: (connected) => {
24742
+ setAttachedShell({
24743
+ shellId: message.shellId,
24744
+ viewerId: connected.viewerId
24745
+ });
24746
+ send({
24747
+ type: "shell.connected",
24748
+ shellId: message.shellId,
24749
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
24750
+ payload: {
24751
+ viewerId: connected.viewerId
24752
+ }
24753
+ });
24754
+ send({
24755
+ type: "shell.status",
24756
+ shellId: message.shellId,
24757
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
24758
+ payload: {
24759
+ threadId: connected.shell.threadId,
24760
+ state: "attached",
24761
+ viewerId: connected.viewerId
24762
+ }
24763
+ });
24764
+ },
24765
+ onData: (data, options) => {
24766
+ send({
24767
+ type: "shell.output",
24768
+ shellId: message.shellId,
24769
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
24770
+ payload: {
24771
+ data,
24772
+ ...options?.replace ? { replace: true } : {},
24773
+ ...options?.cursorX !== void 0 ? { cursorX: options.cursorX } : {},
24774
+ ...options?.cursorY !== void 0 ? { cursorY: options.cursorY } : {},
24775
+ ...options?.paneHeight !== void 0 ? { paneHeight: options.paneHeight } : {},
24776
+ ...options?.cwdBaseName !== void 0 ? { cwdBaseName: options.cwdBaseName } : {},
24777
+ ...options?.envPrefix !== void 0 ? { envPrefix: options.envPrefix } : {},
24778
+ ...options?.isCommandRunning !== void 0 ? { isCommandRunning: options.isCommandRunning } : {}
24779
+ }
24780
+ });
24781
+ }
24782
+ });
24783
+ if (!getAttachedShell()) {
24784
+ setAttachedShell({
24785
+ shellId: message.shellId,
24786
+ viewerId: attachment.viewerId
24787
+ });
24788
+ }
24789
+ if (context.state.get(cleanupRegisteredKey) !== true) {
24790
+ context.state.set(cleanupRegisteredKey, true);
24791
+ context.onClose(() => {
24792
+ const attached = getAttachedShell();
24793
+ if (attached) {
24794
+ void shellService.detachShell(attached.shellId, attached.viewerId).catch(() => {
24795
+ });
24796
+ setAttachedShell(null);
24797
+ }
24798
+ });
24799
+ }
24800
+ return true;
24801
+ }
24802
+ if (message.type === "shell.detach") {
24803
+ requireTerminalPluginEnabled(app2);
24804
+ await shellService.detachShell(message.shellId, message.viewerId);
24805
+ const attachedShell = getAttachedShell();
24806
+ if (attachedShell?.shellId === message.shellId && attachedShell.viewerId === message.viewerId) {
24807
+ setAttachedShell(null);
24808
+ }
24809
+ return true;
24810
+ }
24811
+ if (message.type === "shell.input") {
24812
+ requireTerminalPluginEnabled(app2);
24813
+ await shellService.sendInput(
24814
+ message.shellId,
24815
+ message.viewerId,
24816
+ message.data
24817
+ );
24818
+ return true;
24819
+ }
24820
+ if (message.type === "shell.resize") {
24821
+ requireTerminalPluginEnabled(app2);
24822
+ await shellService.resizeShell(
24823
+ message.shellId,
24824
+ message.viewerId,
24825
+ message.cols,
24826
+ message.rows
24827
+ );
24828
+ return true;
24829
+ }
24830
+ if (message.type === "shell.clear") {
24831
+ requireTerminalPluginEnabled(app2);
24832
+ await shellService.clearShell(message.shellId, message.viewerId);
24833
+ return true;
24834
+ }
24835
+ return false;
24836
+ } catch (error) {
24837
+ if ("shellId" in message) {
24838
+ send(makeShellErrorEnvelope(message.shellId, error));
24839
+ return true;
24840
+ }
24841
+ throw error;
23282
24842
  }
24843
+ };
24844
+ }
24845
+ function makeShellErrorEnvelope(shellId, error) {
24846
+ if (error instanceof ShellServiceError) {
24847
+ return {
24848
+ type: "shell.error",
24849
+ shellId,
24850
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
24851
+ payload: {
24852
+ code: error.code,
24853
+ message: error.message
24854
+ }
24855
+ };
23283
24856
  }
23284
- save(settings) {
23285
- upsertPolicyRecord(
23286
- this.db,
23287
- PLUGIN_SETTINGS_POLICY_KEY,
23288
- JSON.stringify({
23289
- enabled: settings.enabled,
23290
- imported: settings.imported
23291
- })
23292
- );
23293
- }
23294
- };
24857
+ return {
24858
+ type: "shell.error",
24859
+ shellId,
24860
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
24861
+ payload: {
24862
+ code: "unknown",
24863
+ message: error instanceof Error ? error.message : "Unexpected shell error."
24864
+ }
24865
+ };
24866
+ }
23295
24867
 
23296
24868
  // src/app.ts
23297
24869
  var MAX_PROMPT_ATTACHMENTS2 = 10;
@@ -23307,16 +24879,16 @@ var HttpError = class extends Error {
23307
24879
  };
23308
24880
  function findRepoRoot(start = process.cwd()) {
23309
24881
  if (process.env.REMOTE_CODEX_REPO_ROOT) {
23310
- return path21.resolve(process.env.REMOTE_CODEX_REPO_ROOT);
24882
+ return path23.resolve(process.env.REMOTE_CODEX_REPO_ROOT);
23311
24883
  }
23312
- let current = path21.resolve(start);
23313
- while (current !== path21.dirname(current)) {
23314
- if (fs21.existsSync(path21.join(current, "pnpm-workspace.yaml")) && fs21.existsSync(path21.join(current, "scripts", "service-restart.mjs"))) {
24884
+ let current = path23.resolve(start);
24885
+ while (current !== path23.dirname(current)) {
24886
+ if (fs24.existsSync(path23.join(current, "pnpm-workspace.yaml")) && fs24.existsSync(path23.join(current, "scripts", "service-restart.mjs"))) {
23315
24887
  return current;
23316
24888
  }
23317
- current = path21.dirname(current);
24889
+ current = path23.dirname(current);
23318
24890
  }
23319
- return path21.resolve(process.cwd());
24891
+ return path23.resolve(process.cwd());
23320
24892
  }
23321
24893
  function createServiceLifecycle() {
23322
24894
  return {
@@ -23328,14 +24900,14 @@ function createServiceLifecycle() {
23328
24900
  });
23329
24901
  }
23330
24902
  const repoRoot = findRepoRoot();
23331
- const restartScript = path21.join(repoRoot, "scripts", "service-restart.mjs");
23332
- if (!fs21.existsSync(restartScript) || !fs21.existsSync(path21.join(repoRoot, "pnpm-workspace.yaml"))) {
24903
+ const restartScript = path23.join(repoRoot, "scripts", "service-restart.mjs");
24904
+ if (!fs24.existsSync(restartScript) || !fs24.existsSync(path23.join(repoRoot, "pnpm-workspace.yaml"))) {
23333
24905
  throw new HttpError(503, {
23334
24906
  code: "service_unavailable",
23335
24907
  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
24908
  });
23337
24909
  }
23338
- const child = spawn4(process.execPath, [restartScript, "launch"], {
24910
+ const child = spawn5(process.execPath, [restartScript, "launch"], {
23339
24911
  cwd: repoRoot,
23340
24912
  detached: true,
23341
24913
  env: process.env,
@@ -23356,6 +24928,7 @@ function buildApp(options = {}) {
23356
24928
  const pluginSettingsStore = new PluginSettingsStore(database.db);
23357
24929
  const pluginService = new PluginService(pluginRegistry, pluginSettingsStore);
23358
24930
  const runtimeBootstrap = options.runtimeBootstrap ?? createAgentRuntimeBootstrap(config);
24931
+ const repoRoot = findRepoRoot();
23359
24932
  const agentRuntimes = options.agentRuntimes ?? runtimeBootstrap.agentRuntimes;
23360
24933
  const threadService = new ThreadService(
23361
24934
  database.db,
@@ -23366,7 +24939,11 @@ function buildApp(options = {}) {
23366
24939
  runtimeBootstrap.codexManagement,
23367
24940
  pluginService
23368
24941
  );
23369
- const shellService = options.shellService ?? new ShellSessionService(database.db, eventBus, new TmuxManager());
24942
+ const shellService = options.shellService ?? new ShellSessionService(
24943
+ database.db,
24944
+ eventBus,
24945
+ createTerminalShellBackend(options.env)
24946
+ );
23370
24947
  const providerHostConfigService = new ProviderHostConfigService(
23371
24948
  agentRuntimes,
23372
24949
  runtimeBootstrap.providerHostHomes
@@ -23393,8 +24970,11 @@ function buildApp(options = {}) {
23393
24970
  shellService,
23394
24971
  providerHostConfigService,
23395
24972
  pluginRegistry,
23396
- pluginService
24973
+ pluginService,
24974
+ repoRoot
23397
24975
  });
24976
+ const backendPluginHost = new BackendPluginHost(app2);
24977
+ backendPluginHost.register(createTerminalPluginBackendContribution());
23398
24978
  app2.register(async (realtimeApp) => {
23399
24979
  await realtimeApp.register(websocket);
23400
24980
  realtimeApp.route({
@@ -23407,7 +24987,11 @@ function buildApp(options = {}) {
23407
24987
  });
23408
24988
  },
23409
24989
  wsHandler: (socket) => {
23410
- let attachedShell = null;
24990
+ const closeHandlers = [];
24991
+ const socketState = /* @__PURE__ */ new Map();
24992
+ const onClose = (handler) => {
24993
+ closeHandlers.push(handler);
24994
+ };
23411
24995
  function send(message) {
23412
24996
  if (socket.readyState === 1) {
23413
24997
  socket.send(JSON.stringify(message));
@@ -23431,49 +25015,6 @@ function buildApp(options = {}) {
23431
25015
  return;
23432
25016
  }
23433
25017
  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
25018
  if (parsed.type === "supervisor.ping") {
23478
25019
  send({
23479
25020
  type: "supervisor.pong",
@@ -23484,47 +25025,23 @@ function buildApp(options = {}) {
23484
25025
  });
23485
25026
  return;
23486
25027
  }
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
- );
25028
+ const handled = await backendPluginHost.handleSocketMessage({
25029
+ app: app2,
25030
+ send,
25031
+ onClose,
25032
+ state: socketState,
25033
+ message: parsed
25034
+ });
25035
+ if (!handled) {
23509
25036
  return;
23510
25037
  }
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));
23517
- }
25038
+ } catch {
25039
+ return;
23518
25040
  }
23519
25041
  });
23520
25042
  socket.on("close", () => {
23521
- if (attachedShell) {
23522
- void shellService.detachShell(
23523
- attachedShell.shellId,
23524
- attachedShell.viewerId
23525
- ).catch(() => {
23526
- });
23527
- attachedShell = null;
25043
+ for (const handler of closeHandlers.splice(0)) {
25044
+ handler();
23528
25045
  }
23529
25046
  unsubscribe();
23530
25047
  unsubscribeShell();
@@ -23534,7 +25051,6 @@ function buildApp(options = {}) {
23534
25051
  });
23535
25052
  app2.register(registerSystemRoutes);
23536
25053
  app2.register(registerAgentRuntimeRoutes);
23537
- app2.register(registerShellRoutes);
23538
25054
  app2.register(registerPluginRoutes);
23539
25055
  app2.register(registerThreadRoutes);
23540
25056
  app2.register(registerWorkspaceRoutes);
@@ -23576,7 +25092,7 @@ function buildApp(options = {}) {
23576
25092
  return;
23577
25093
  }
23578
25094
  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;
25095
+ 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
25096
  reply.status(statusCode).send({
23581
25097
  code: statusCode === 404 ? "not_found" : statusCode === 409 ? "conflict" : "service_unavailable",
23582
25098
  message: error.message,
@@ -23646,6 +25162,10 @@ function buildApp(options = {}) {
23646
25162
  });
23647
25163
  app2.addHook("onReady", async () => {
23648
25164
  try {
25165
+ await pluginService.syncManagedCodexMcpConfig({
25166
+ codexHome: runtimeBootstrap.providerHostHomes.codex ?? null,
25167
+ repoRoot
25168
+ });
23649
25169
  await Promise.all(agentRuntimes.all().map((runtime) => runtime.start()));
23650
25170
  await shellService.syncShellStateOnStartup();
23651
25171
  } catch (error) {
@@ -23661,31 +25181,9 @@ function requestLog(app2, error) {
23661
25181
  }
23662
25182
  app2.log.error({ error }, "Non-error value reached Fastify error handler.");
23663
25183
  }
23664
- function makeShellErrorEnvelope(shellId, error) {
23665
- if (error instanceof ShellServiceError) {
23666
- return {
23667
- type: "shell.error",
23668
- shellId,
23669
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
23670
- payload: {
23671
- code: error.code,
23672
- message: error.message
23673
- }
23674
- };
23675
- }
23676
- return {
23677
- type: "shell.error",
23678
- shellId,
23679
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
23680
- payload: {
23681
- code: "unknown",
23682
- message: error instanceof Error ? error.message : "Unexpected shell error."
23683
- }
23684
- };
23685
- }
23686
25184
 
23687
25185
  // src/index.ts
23688
- if (fs22.existsSync(".env")) {
25186
+ if (fs25.existsSync(".env")) {
23689
25187
  process.loadEnvFile?.(".env");
23690
25188
  }
23691
25189
  var app = buildApp();