remote-codex 0.11.1 → 0.11.3

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