@dyyz1993/codenomad 0.15.0

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 (496) hide show
  1. package/README.md +173 -0
  2. package/dist/api-types.js +1 -0
  3. package/dist/auth/auth-store.js +134 -0
  4. package/dist/auth/http-auth.js +37 -0
  5. package/dist/auth/manager.js +140 -0
  6. package/dist/auth/password-hash.js +32 -0
  7. package/dist/auth/session-manager.js +17 -0
  8. package/dist/auth/token-manager.js +27 -0
  9. package/dist/background-processes/manager.js +576 -0
  10. package/dist/bin.js +24 -0
  11. package/dist/cli-upgrade.js +53 -0
  12. package/dist/cli-upgrade.test.js +31 -0
  13. package/dist/clients/connection-manager.js +93 -0
  14. package/dist/config/location.js +57 -0
  15. package/dist/config/schema.js +71 -0
  16. package/dist/events/bus.js +45 -0
  17. package/dist/filesystem/__tests__/browser.test.js +65 -0
  18. package/dist/filesystem/__tests__/search-cache.test.js +40 -0
  19. package/dist/filesystem/browser.js +306 -0
  20. package/dist/filesystem/search-cache.js +43 -0
  21. package/dist/filesystem/search.js +135 -0
  22. package/dist/index.js +477 -0
  23. package/dist/launcher.js +149 -0
  24. package/dist/loader.js +21 -0
  25. package/dist/logger.js +109 -0
  26. package/dist/opencode-config/README.md +32 -0
  27. package/dist/opencode-config/opencode.jsonc +3 -0
  28. package/dist/opencode-config/package-lock.json +380 -0
  29. package/dist/opencode-config/package.json +9 -0
  30. package/dist/opencode-config/plugin/codenomad.ts +62 -0
  31. package/dist/opencode-config/plugin/lib/background-process.ts +265 -0
  32. package/dist/opencode-config/plugin/lib/client.ts +133 -0
  33. package/dist/opencode-config/plugin/lib/request.ts +214 -0
  34. package/dist/opencode-config.js +26 -0
  35. package/dist/plugins/channel.js +40 -0
  36. package/dist/plugins/handlers.js +17 -0
  37. package/dist/plugins/voice-mode.js +78 -0
  38. package/dist/releases/dev-release-monitor.js +75 -0
  39. package/dist/releases/release-monitor.js +107 -0
  40. package/dist/server/__tests__/network-addresses.test.js +68 -0
  41. package/dist/server/__tests__/remote-proxy.test.js +204 -0
  42. package/dist/server/http-server.js +926 -0
  43. package/dist/server/network-addresses.js +114 -0
  44. package/dist/server/remote-proxy.js +466 -0
  45. package/dist/server/routes/auth-pages/login.html +135 -0
  46. package/dist/server/routes/auth-pages/token.html +93 -0
  47. package/dist/server/routes/auth.js +148 -0
  48. package/dist/server/routes/background-processes.js +78 -0
  49. package/dist/server/routes/events.js +66 -0
  50. package/dist/server/routes/filesystem.js +43 -0
  51. package/dist/server/routes/meta.js +44 -0
  52. package/dist/server/routes/plugin.js +70 -0
  53. package/dist/server/routes/remote-proxy.js +42 -0
  54. package/dist/server/routes/remote-servers.js +142 -0
  55. package/dist/server/routes/settings.js +69 -0
  56. package/dist/server/routes/sidecars.js +46 -0
  57. package/dist/server/routes/speech.js +63 -0
  58. package/dist/server/routes/storage.js +52 -0
  59. package/dist/server/routes/workspaces.js +221 -0
  60. package/dist/server/routes/worktrees.js +156 -0
  61. package/dist/server/tls.js +224 -0
  62. package/dist/settings/binaries.js +37 -0
  63. package/dist/settings/merge-patch.js +33 -0
  64. package/dist/settings/migrate.js +238 -0
  65. package/dist/settings/public-config.js +33 -0
  66. package/dist/settings/service.js +101 -0
  67. package/dist/settings/yaml-doc-store.js +96 -0
  68. package/dist/sidecars/manager.js +193 -0
  69. package/dist/speech/providers/openai-compatible.js +189 -0
  70. package/dist/speech/service.js +58 -0
  71. package/dist/storage/instance-store.js +56 -0
  72. package/dist/ui/__tests__/remote-ui.test.js +67 -0
  73. package/dist/ui/remote-ui.js +462 -0
  74. package/dist/workspaces/__tests__/git-worktrees.test.js +43 -0
  75. package/dist/workspaces/__tests__/spawn.test.js +140 -0
  76. package/dist/workspaces/git-mutations.js +98 -0
  77. package/dist/workspaces/git-status.js +323 -0
  78. package/dist/workspaces/git-worktrees.js +216 -0
  79. package/dist/workspaces/instance-events.js +180 -0
  80. package/dist/workspaces/manager.js +368 -0
  81. package/dist/workspaces/opencode-auth.js +34 -0
  82. package/dist/workspaces/opencode-auth.test.js +34 -0
  83. package/dist/workspaces/runtime.js +366 -0
  84. package/dist/workspaces/spawn.js +219 -0
  85. package/dist/workspaces/worktree-directory.js +74 -0
  86. package/dist/workspaces/worktree-map.js +116 -0
  87. package/package.json +50 -0
  88. package/public/apple-touch-icon-180x180.png +0 -0
  89. package/public/assets/ChangesTab-BRNmSjeC.js +2 -0
  90. package/public/assets/CodeNomad-Icon-bmTWNPXy.png +0 -0
  91. package/public/assets/DiffToolbar-DPPgfx42.js +1 -0
  92. package/public/assets/FilesTab-sEeGPOoz.js +2 -0
  93. package/public/assets/GitChangesTab-tJ-TCq0c.js +2 -0
  94. package/public/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  95. package/public/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  96. package/public/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  97. package/public/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  98. package/public/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  99. package/public/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  100. package/public/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  101. package/public/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  102. package/public/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  103. package/public/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  104. package/public/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  105. package/public/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  106. package/public/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  107. package/public/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  108. package/public/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  109. package/public/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  110. package/public/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  111. package/public/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  112. package/public/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  113. package/public/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  114. package/public/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  115. package/public/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  116. package/public/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  117. package/public/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  118. package/public/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  119. package/public/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  120. package/public/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  121. package/public/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  122. package/public/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  123. package/public/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  124. package/public/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  125. package/public/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  126. package/public/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  127. package/public/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  128. package/public/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  129. package/public/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  130. package/public/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  131. package/public/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  132. package/public/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  133. package/public/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  134. package/public/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  135. package/public/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  136. package/public/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  137. package/public/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  138. package/public/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  139. package/public/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  140. package/public/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  141. package/public/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  142. package/public/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  143. package/public/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  144. package/public/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  145. package/public/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  146. package/public/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  147. package/public/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  148. package/public/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  149. package/public/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  150. package/public/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  151. package/public/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  152. package/public/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  153. package/public/assets/SplitFilePanel-CHqNLZvU.js +1 -0
  154. package/public/assets/StatusTab-DMd0ByCA.js +1 -0
  155. package/public/assets/abap-BdImnpbu.js +1 -0
  156. package/public/assets/actionscript-3-CfeIJUat.js +1 -0
  157. package/public/assets/ada-bCR0ucgS.js +1 -0
  158. package/public/assets/align-justify-BWapkGTe.js +1 -0
  159. package/public/assets/andromeeda-C-Jbm3Hp.js +1 -0
  160. package/public/assets/angular-html-CU67Zn6k.js +1 -0
  161. package/public/assets/angular-ts-BwZT4LLn.js +1 -0
  162. package/public/assets/apache-Pmp26Uib.js +1 -0
  163. package/public/assets/apex-DhZLUxFE.js +1 -0
  164. package/public/assets/apl-dKokRX4l.js +1 -0
  165. package/public/assets/applescript-Co6uUVPk.js +1 -0
  166. package/public/assets/ara-BRHolxvo.js +1 -0
  167. package/public/assets/asciidoc-Dv7Oe6Be.js +1 -0
  168. package/public/assets/asm-D_Q5rh1f.js +1 -0
  169. package/public/assets/astro-CbQHKStN.js +1 -0
  170. package/public/assets/aurora-x-D-2ljcwZ.js +1 -0
  171. package/public/assets/awk-DMzUqQB5.js +1 -0
  172. package/public/assets/ayu-dark-Cv9koXgw.js +1 -0
  173. package/public/assets/ballerina-BFfxhgS-.js +1 -0
  174. package/public/assets/bat-BkioyH1T.js +1 -0
  175. package/public/assets/beancount-k_qm7-4y.js +1 -0
  176. package/public/assets/berry-D08WgyRC.js +1 -0
  177. package/public/assets/bibtex-CHM0blh-.js +1 -0
  178. package/public/assets/bicep-Bmn6On1c.js +1 -0
  179. package/public/assets/blade-DVc8C-J4.js +1 -0
  180. package/public/assets/bsl-BO_Y6i37.js +1 -0
  181. package/public/assets/bundle-full-CXlKQh5B.js +13 -0
  182. package/public/assets/c-BIGW1oBm.js +1 -0
  183. package/public/assets/cadence-Bv_4Rxtq.js +1 -0
  184. package/public/assets/cairo-KRGpt6FW.js +1 -0
  185. package/public/assets/catppuccin-frappe-DFWUc33u.js +1 -0
  186. package/public/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
  187. package/public/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
  188. package/public/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
  189. package/public/assets/clarity-D53aC0YG.js +1 -0
  190. package/public/assets/clojure-P80f7IUj.js +1 -0
  191. package/public/assets/cmake-D1j8_8rp.js +1 -0
  192. package/public/assets/cobol-nwyudZeR.js +1 -0
  193. package/public/assets/codeowners-Bp6g37R7.js +1 -0
  194. package/public/assets/codeql-DsOJ9woJ.js +1 -0
  195. package/public/assets/coffee-Ch7k5sss.js +1 -0
  196. package/public/assets/common-lisp-Cg-RD9OK.js +1 -0
  197. package/public/assets/coq-DkFqJrB1.js +1 -0
  198. package/public/assets/cpp-CofmeUqb.js +1 -0
  199. package/public/assets/crystal-tKQVLTB8.js +1 -0
  200. package/public/assets/csharp-CX12Zw3r.js +1 -0
  201. package/public/assets/css-DPfMkruS.js +1 -0
  202. package/public/assets/csv-fuZLfV_i.js +1 -0
  203. package/public/assets/cue-D82EKSYY.js +1 -0
  204. package/public/assets/cypher-COkxafJQ.js +1 -0
  205. package/public/assets/d-85-TOEBH.js +1 -0
  206. package/public/assets/dark-plus-eOWES_5F.js +1 -0
  207. package/public/assets/dart-CF10PKvl.js +1 -0
  208. package/public/assets/dax-CEL-wOlO.js +1 -0
  209. package/public/assets/desktop-BmXAJ9_W.js +1 -0
  210. package/public/assets/diff-D97Zzqfu.js +1 -0
  211. package/public/assets/diff-viewer-BFtW5J8P.js +1 -0
  212. package/public/assets/docker-BcOcwvcX.js +1 -0
  213. package/public/assets/dotenv-Da5cRb03.js +1 -0
  214. package/public/assets/dracula-BzJJZx-M.js +1 -0
  215. package/public/assets/dracula-soft-BXkSAIEj.js +1 -0
  216. package/public/assets/dream-maker-BtqSS_iP.js +1 -0
  217. package/public/assets/edge-BkV0erSs.js +1 -0
  218. package/public/assets/elixir-CDX3lj18.js +1 -0
  219. package/public/assets/elm-DbKCFpqz.js +1 -0
  220. package/public/assets/emacs-lisp-C9XAeP06.js +1 -0
  221. package/public/assets/erb-BOJIQeun.js +1 -0
  222. package/public/assets/erlang-DsQrWhSR.js +1 -0
  223. package/public/assets/everforest-dark-BgDCqdQA.js +1 -0
  224. package/public/assets/everforest-light-C8M2exoo.js +1 -0
  225. package/public/assets/fast-diff-vendor-DgdwVvTQ.js +1 -0
  226. package/public/assets/fennel-BYunw83y.js +1 -0
  227. package/public/assets/fish-BvzEVeQv.js +1 -0
  228. package/public/assets/fluent-C4IJs8-o.js +1 -0
  229. package/public/assets/fortran-fixed-form-BZjJHVRy.js +1 -0
  230. package/public/assets/fortran-free-form-D22FLkUw.js +1 -0
  231. package/public/assets/fsharp-CXgrBDvD.js +1 -0
  232. package/public/assets/gdresource-B7Tvp0Sc.js +1 -0
  233. package/public/assets/gdscript-DTMYz4Jt.js +1 -0
  234. package/public/assets/gdshader-DkwncUOv.js +1 -0
  235. package/public/assets/genie-D0YGMca9.js +1 -0
  236. package/public/assets/gherkin-DyxjwDmM.js +1 -0
  237. package/public/assets/git-commit-F4YmCXRG.js +1 -0
  238. package/public/assets/git-diff-vendor-CSgooKT_.js +52 -0
  239. package/public/assets/git-diff-vendor-HAZkIolJ.css +19 -0
  240. package/public/assets/git-rebase-r7XF79zn.js +1 -0
  241. package/public/assets/github-dark-DHJKELXO.js +1 -0
  242. package/public/assets/github-dark-default-Cuk6v7N8.js +1 -0
  243. package/public/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
  244. package/public/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
  245. package/public/assets/github-light-DAi9KRSo.js +1 -0
  246. package/public/assets/github-light-default-D7oLnXFd.js +1 -0
  247. package/public/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
  248. package/public/assets/gleam-BspZqrRM.js +1 -0
  249. package/public/assets/glimmer-js-Rg0-pVw9.js +1 -0
  250. package/public/assets/glimmer-ts-U6CK756n.js +1 -0
  251. package/public/assets/glsl-DplSGwfg.js +1 -0
  252. package/public/assets/gnuplot-DdkO51Og.js +1 -0
  253. package/public/assets/go-Dn2_MT6a.js +1 -0
  254. package/public/assets/graphql-ChdNCCLP.js +1 -0
  255. package/public/assets/groovy-gcz8RCvz.js +1 -0
  256. package/public/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
  257. package/public/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
  258. package/public/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
  259. package/public/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
  260. package/public/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
  261. package/public/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
  262. package/public/assets/hack-CaT9iCJl.js +1 -0
  263. package/public/assets/haml-B8DHNrY2.js +1 -0
  264. package/public/assets/handlebars-BL8al0AC.js +1 -0
  265. package/public/assets/haskell-Df6bDoY_.js +1 -0
  266. package/public/assets/haxe-CzTSHFRz.js +1 -0
  267. package/public/assets/hcl-BWvSN4gD.js +1 -0
  268. package/public/assets/highlight-vendor-8FKMu9os.js +10 -0
  269. package/public/assets/hjson-D5-asLiD.js +1 -0
  270. package/public/assets/hlsl-D3lLCCz7.js +1 -0
  271. package/public/assets/houston-DnULxvSX.js +1 -0
  272. package/public/assets/html-GMplVEZG.js +1 -0
  273. package/public/assets/html-derivative-BFtXZ54Q.js +1 -0
  274. package/public/assets/http-jrhK8wxY.js +1 -0
  275. package/public/assets/hurl-irOxFIW8.js +1 -0
  276. package/public/assets/hxml-Bvhsp5Yf.js +1 -0
  277. package/public/assets/hy-DFXneXwc.js +1 -0
  278. package/public/assets/imba-DGztddWO.js +1 -0
  279. package/public/assets/index-Bdy3MTIn.js +1 -0
  280. package/public/assets/index-BvRe9GiS.js +1 -0
  281. package/public/assets/index-Ctq8RqRf.js +1 -0
  282. package/public/assets/index-D-NvysdX.js +1 -0
  283. package/public/assets/index-D6RMBXUk.js +2 -0
  284. package/public/assets/index-DXI9DoVB.css +1 -0
  285. package/public/assets/index-DYWkPhKo.js +1 -0
  286. package/public/assets/index-D_FDiI6a.js +1 -0
  287. package/public/assets/index-Dty5bSHf.js +1 -0
  288. package/public/assets/index-H16e4Rqc.js +1 -0
  289. package/public/assets/ini-BEwlwnbL.js +1 -0
  290. package/public/assets/java-CylS5w8V.js +1 -0
  291. package/public/assets/javascript-wDzz0qaB.js +1 -0
  292. package/public/assets/jinja-4LBKfQ-Z.js +1 -0
  293. package/public/assets/jison-wvAkD_A8.js +1 -0
  294. package/public/assets/json-Cp-IABpG.js +1 -0
  295. package/public/assets/json5-C9tS-k6U.js +1 -0
  296. package/public/assets/jsonc-Des-eS-w.js +1 -0
  297. package/public/assets/jsonl-DcaNXYhu.js +1 -0
  298. package/public/assets/jsonnet-DFQXde-d.js +1 -0
  299. package/public/assets/jssm-C2t-YnRu.js +1 -0
  300. package/public/assets/jsx-g9-lgVsj.js +1 -0
  301. package/public/assets/julia-C8NyazO9.js +1 -0
  302. package/public/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
  303. package/public/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
  304. package/public/assets/kanagawa-wave-DWedfzmr.js +1 -0
  305. package/public/assets/kdl-DV7GczEv.js +1 -0
  306. package/public/assets/kotlin-BdnUsdx6.js +1 -0
  307. package/public/assets/kusto-BvAqAH-y.js +1 -0
  308. package/public/assets/laserwave-DUszq2jm.js +1 -0
  309. package/public/assets/latex-BUKiar2Z.js +1 -0
  310. package/public/assets/lean-DP1Csr6i.js +1 -0
  311. package/public/assets/less-B1dDrJ26.js +1 -0
  312. package/public/assets/light-plus-B7mTdjB0.js +1 -0
  313. package/public/assets/liquid-DYVedYrR.js +1 -0
  314. package/public/assets/llvm-BtvRca6l.js +1 -0
  315. package/public/assets/loading-CmEVQgyj.css +1 -0
  316. package/public/assets/loading-W2Y_wR0P.js +1 -0
  317. package/public/assets/log-2UxHyX5q.js +1 -0
  318. package/public/assets/logo-BtOb2qkB.js +1 -0
  319. package/public/assets/lua-BbnMAYS6.js +1 -0
  320. package/public/assets/luau-CXu1NL6O.js +1 -0
  321. package/public/assets/main-Bala0YJ4.js +57 -0
  322. package/public/assets/make-CHLpvVh8.js +1 -0
  323. package/public/assets/markdown-Cvjx9yec.js +1 -0
  324. package/public/assets/markdown-Db8cvZ4Z.js +315 -0
  325. package/public/assets/marko-CPi9NSCl.js +1 -0
  326. package/public/assets/material-theme-D5KoaKCx.js +1 -0
  327. package/public/assets/material-theme-darker-BfHTSMKl.js +1 -0
  328. package/public/assets/material-theme-lighter-B0m2ddpp.js +1 -0
  329. package/public/assets/material-theme-ocean-CyktbL80.js +1 -0
  330. package/public/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
  331. package/public/assets/matlab-D7o27uSR.js +1 -0
  332. package/public/assets/mdc-DUICxH0z.js +1 -0
  333. package/public/assets/mdx-Cmh6b_Ma.js +1 -0
  334. package/public/assets/mermaid-DKYwYmdq.js +1 -0
  335. package/public/assets/min-dark-CafNBF8u.js +1 -0
  336. package/public/assets/min-light-CTRr51gU.js +1 -0
  337. package/public/assets/mipsasm-CKIfxQSi.js +1 -0
  338. package/public/assets/mojo-1DNp92w6.js +1 -0
  339. package/public/assets/monaco-viewer-DWQqXKm3.js +26 -0
  340. package/public/assets/monokai-D4h5O-jR.js +1 -0
  341. package/public/assets/move-Bu9oaDYs.js +1 -0
  342. package/public/assets/narrat-DRg8JJMk.js +1 -0
  343. package/public/assets/nextflow-CUEJCptM.js +1 -0
  344. package/public/assets/nginx-DknmC5AR.js +1 -0
  345. package/public/assets/night-owl-C39BiMTA.js +1 -0
  346. package/public/assets/nim-CVrawwO9.js +1 -0
  347. package/public/assets/nix-BbRYJGeE.js +1 -0
  348. package/public/assets/nord-Ddv68eIx.js +1 -0
  349. package/public/assets/nushell-C-sUppwS.js +1 -0
  350. package/public/assets/objective-c-DXmwc3jG.js +1 -0
  351. package/public/assets/objective-cpp-CLxacb5B.js +1 -0
  352. package/public/assets/ocaml-C0hk2d4L.js +1 -0
  353. package/public/assets/one-dark-pro-DVMEJ2y_.js +1 -0
  354. package/public/assets/one-light-PoHY5YXO.js +1 -0
  355. package/public/assets/pascal-D93ZcfNL.js +1 -0
  356. package/public/assets/perl-C0TMdlhV.js +1 -0
  357. package/public/assets/php-CDn_0X-4.js +1 -0
  358. package/public/assets/pkl-u5AG7uiY.js +1 -0
  359. package/public/assets/plastic-3e1v2bzS.js +1 -0
  360. package/public/assets/plsql-ChMvpjG-.js +1 -0
  361. package/public/assets/po-BTJTHyun.js +1 -0
  362. package/public/assets/poimandres-CS3Unz2-.js +1 -0
  363. package/public/assets/polar-C0HS_06l.js +1 -0
  364. package/public/assets/postcss-CXtECtnM.js +1 -0
  365. package/public/assets/powerquery-CEu0bR-o.js +1 -0
  366. package/public/assets/powershell-Dpen1YoG.js +1 -0
  367. package/public/assets/prisma-Dd19v3D-.js +1 -0
  368. package/public/assets/prolog-CbFg5uaA.js +1 -0
  369. package/public/assets/proto-DyJlTyXw.js +1 -0
  370. package/public/assets/pug-CGlum2m_.js +1 -0
  371. package/public/assets/puppet-BMWR74SV.js +1 -0
  372. package/public/assets/purescript-CklMAg4u.js +1 -0
  373. package/public/assets/python-B6aJPvgy.js +1 -0
  374. package/public/assets/qml-3beO22l8.js +1 -0
  375. package/public/assets/qmldir-C8lEn-DE.js +1 -0
  376. package/public/assets/qss-IeuSbFQv.js +1 -0
  377. package/public/assets/r-DiinP2Uv.js +1 -0
  378. package/public/assets/racket-BqYA7rlc.js +1 -0
  379. package/public/assets/raku-DXvB9xmW.js +1 -0
  380. package/public/assets/razor-WgofotgN.js +1 -0
  381. package/public/assets/red-bN70gL4F.js +1 -0
  382. package/public/assets/reg-C-SQnVFl.js +1 -0
  383. package/public/assets/regexp-CDVJQ6XC.js +1 -0
  384. package/public/assets/rel-C3B-1QV4.js +1 -0
  385. package/public/assets/riscv-BM1_JUlF.js +1 -0
  386. package/public/assets/rose-pine-BHrmToEH.js +1 -0
  387. package/public/assets/rose-pine-dawn-CnK8MTSM.js +1 -0
  388. package/public/assets/rose-pine-moon-NleAzG8P.js +1 -0
  389. package/public/assets/rosmsg-BJDFO7_C.js +1 -0
  390. package/public/assets/rst-B0xPkSld.js +1 -0
  391. package/public/assets/ruby-BvKwtOVI.js +1 -0
  392. package/public/assets/rust-B1yitclQ.js +1 -0
  393. package/public/assets/sas-cz2c8ADy.js +1 -0
  394. package/public/assets/sass-Cj5Yp3dK.js +1 -0
  395. package/public/assets/scala-C151Ov-r.js +1 -0
  396. package/public/assets/scheme-C98Dy4si.js +1 -0
  397. package/public/assets/scss-OYdSNvt2.js +1 -0
  398. package/public/assets/sdbl-DVxCFoDh.js +1 -0
  399. package/public/assets/shaderlab-Dg9Lc6iA.js +1 -0
  400. package/public/assets/shellscript-Yzrsuije.js +1 -0
  401. package/public/assets/shellsession-BADoaaVG.js +1 -0
  402. package/public/assets/slack-dark-BthQWCQV.js +1 -0
  403. package/public/assets/slack-ochin-DqwNpetd.js +1 -0
  404. package/public/assets/smalltalk-BERRCDM3.js +1 -0
  405. package/public/assets/snazzy-light-Bw305WKR.js +1 -0
  406. package/public/assets/solarized-dark-DXbdFlpD.js +1 -0
  407. package/public/assets/solarized-light-L9t79GZl.js +1 -0
  408. package/public/assets/solidity-BbcW6ACK.js +1 -0
  409. package/public/assets/soy-Brmx7dQM.js +1 -0
  410. package/public/assets/sparql-rVzFXLq3.js +1 -0
  411. package/public/assets/splunk-BtCnVYZw.js +1 -0
  412. package/public/assets/sql-BLtJtn59.js +1 -0
  413. package/public/assets/ssh-config-_ykCGR6B.js +1 -0
  414. package/public/assets/stata-BH5u7GGu.js +1 -0
  415. package/public/assets/stylus-BEDo0Tqx.js +1 -0
  416. package/public/assets/svelte-3Dk4HxPD.js +1 -0
  417. package/public/assets/swift-Dg5xB15N.js +1 -0
  418. package/public/assets/synthwave-84-CbfX1IO0.js +1 -0
  419. package/public/assets/system-verilog-CnnmHF94.js +1 -0
  420. package/public/assets/systemd-4A_iFExJ.js +1 -0
  421. package/public/assets/talonscript-CkByrt1z.js +1 -0
  422. package/public/assets/tasl-QIJgUcNo.js +1 -0
  423. package/public/assets/tcl-dwOrl1Do.js +1 -0
  424. package/public/assets/templ-W15q3VgB.js +1 -0
  425. package/public/assets/terraform-BETggiCN.js +1 -0
  426. package/public/assets/tex-Cppo0RY3.js +1 -0
  427. package/public/assets/tokyo-night-hegEt444.js +1 -0
  428. package/public/assets/toml-vGWfd6FD.js +1 -0
  429. package/public/assets/tool-call-D0BYXlFe.js +59 -0
  430. package/public/assets/ts-tags-zn1MmPIZ.js +1 -0
  431. package/public/assets/tsv-B_m7g4N7.js +1 -0
  432. package/public/assets/tsx-COt5Ahok.js +1 -0
  433. package/public/assets/turtle-BsS91CYL.js +1 -0
  434. package/public/assets/twig-CO9l9SDP.js +1 -0
  435. package/public/assets/typescript-BPQ3VLAy.js +1 -0
  436. package/public/assets/typespec-Df68jz8_.js +1 -0
  437. package/public/assets/typst-DHCkPAjA.js +1 -0
  438. package/public/assets/unified-picker-QgONWj_1.js +1 -0
  439. package/public/assets/v-BcVCzyr7.js +1 -0
  440. package/public/assets/vala-CsfeWuGM.js +1 -0
  441. package/public/assets/vb-D17OF-Vu.js +1 -0
  442. package/public/assets/verilog-BQ8w6xss.js +1 -0
  443. package/public/assets/vesper-DU1UobuO.js +1 -0
  444. package/public/assets/vhdl-CeAyd5Ju.js +1 -0
  445. package/public/assets/viml-CJc9bBzg.js +1 -0
  446. package/public/assets/vitesse-black-Bkuqu6BP.js +1 -0
  447. package/public/assets/vitesse-dark-D0r3Knsf.js +1 -0
  448. package/public/assets/vitesse-light-CVO1_9PV.js +1 -0
  449. package/public/assets/vue-CCoi5OLL.js +1 -0
  450. package/public/assets/vue-html-DAAvJJDi.js +1 -0
  451. package/public/assets/vue-vine-_Ih-lPRR.js +1 -0
  452. package/public/assets/vyper-CDx5xZoG.js +1 -0
  453. package/public/assets/wasm-CG6Dc4jp.js +1 -0
  454. package/public/assets/wasm-MzD3tlZU.js +1 -0
  455. package/public/assets/wenyan-BV7otONQ.js +1 -0
  456. package/public/assets/wgsl-Dx-B1_4e.js +1 -0
  457. package/public/assets/wikitext-BhOHFoWU.js +1 -0
  458. package/public/assets/wit-5i3qLPDT.js +1 -0
  459. package/public/assets/wolfram-lXgVvXCa.js +1 -0
  460. package/public/assets/wrap-text-CEH9wOr_.js +1 -0
  461. package/public/assets/xml-sdJ4AIDG.js +1 -0
  462. package/public/assets/xsl-CtQFsRM5.js +1 -0
  463. package/public/assets/yaml-Buea-lGh.js +1 -0
  464. package/public/assets/zenscript-DVFEvuxE.js +1 -0
  465. package/public/assets/zig-VOosw3JB.js +1 -0
  466. package/public/favicon.ico +0 -0
  467. package/public/index.html +42 -0
  468. package/public/loading.html +33 -0
  469. package/public/logo.png +0 -0
  470. package/public/manifest.webmanifest +1 -0
  471. package/public/maskable-icon-512x512.png +0 -0
  472. package/public/monaco/vs/base/browser/ui/codicons/codicon/codicon.ttf +0 -0
  473. package/public/monaco/vs/base/worker/workerMain.js +31 -0
  474. package/public/monaco/vs/basic-languages/cpp/cpp.js +10 -0
  475. package/public/monaco/vs/basic-languages/kotlin/kotlin.js +10 -0
  476. package/public/monaco/vs/basic-languages/markdown/markdown.js +10 -0
  477. package/public/monaco/vs/basic-languages/python/python.js +10 -0
  478. package/public/monaco/vs/editor/editor.main.css +8 -0
  479. package/public/monaco/vs/editor/editor.main.js +798 -0
  480. package/public/monaco/vs/language/css/cssMode.js +13 -0
  481. package/public/monaco/vs/language/css/cssWorker.js +77 -0
  482. package/public/monaco/vs/language/html/htmlMode.js +13 -0
  483. package/public/monaco/vs/language/html/htmlWorker.js +454 -0
  484. package/public/monaco/vs/language/json/jsonMode.js +19 -0
  485. package/public/monaco/vs/language/json/jsonWorker.js +42 -0
  486. package/public/monaco/vs/language/typescript/tsMode.js +20 -0
  487. package/public/monaco/vs/language/typescript/tsWorker.js +51328 -0
  488. package/public/monaco/vs/loader.js +11 -0
  489. package/public/monaco.worker.js +7 -0
  490. package/public/pwa-192x192.png +0 -0
  491. package/public/pwa-512x512.png +0 -0
  492. package/public/pwa-64x64.png +0 -0
  493. package/public/registerSW.js +1 -0
  494. package/public/sw.js +1 -0
  495. package/public/ui-version.json +3 -0
  496. package/public/workbox-60d14903.js +1 -0
@@ -0,0 +1,926 @@
1
+ import Fastify from "fastify";
2
+ import cors from "@fastify/cors";
3
+ import fastifyStatic from "@fastify/static";
4
+ import replyFrom from "@fastify/reply-from";
5
+ import fs from "fs";
6
+ import { connect as connectTcp } from "net";
7
+ import path from "path";
8
+ import { connect as connectTls } from "tls";
9
+ import { fetch } from "undici";
10
+ import { isValidWorktreeSlug } from "../workspaces/git-worktrees";
11
+ import { resolveWorktreeDirectory } from "../workspaces/worktree-directory";
12
+ import { registerWorkspaceRoutes } from "./routes/workspaces";
13
+ import { registerSettingsRoutes } from "./routes/settings";
14
+ import { registerFilesystemRoutes } from "./routes/filesystem";
15
+ import { registerMetaRoutes } from "./routes/meta";
16
+ import { registerEventRoutes } from "./routes/events";
17
+ import { registerStorageRoutes } from "./routes/storage";
18
+ import { registerPluginRoutes } from "./routes/plugin";
19
+ import { registerBackgroundProcessRoutes } from "./routes/background-processes";
20
+ import { registerWorktreeRoutes } from "./routes/worktrees";
21
+ import { registerSpeechRoutes } from "./routes/speech";
22
+ import { registerRemoteServerRoutes } from "./routes/remote-servers";
23
+ import { registerRemoteProxyRoutes } from "./routes/remote-proxy";
24
+ import { registerSideCarRoutes } from "./routes/sidecars";
25
+ import { BackgroundProcessManager } from "../background-processes/manager";
26
+ import { registerAuthRoutes } from "./routes/auth";
27
+ import { sendUnauthorized, wantsHtml } from "../auth/http-auth";
28
+ export function createHttpServer(deps) {
29
+ // Fastify's type-level RawServer inference gets noisy when toggling HTTP vs HTTPS.
30
+ // We keep the runtime behavior correct and cast the instance to a generic FastifyInstance.
31
+ const app = Fastify({
32
+ logger: false,
33
+ ...(deps.protocol === "https" && deps.httpsOptions ? { https: deps.httpsOptions } : {}),
34
+ });
35
+ const proxyLogger = deps.logger.child({ component: "proxy" });
36
+ const apiLogger = deps.logger.child({ component: "http" });
37
+ const sseLogger = deps.logger.child({ component: "sse" });
38
+ const sseClients = new Set();
39
+ const registerSseClient = (cleanup) => {
40
+ sseClients.add(cleanup);
41
+ return () => sseClients.delete(cleanup);
42
+ };
43
+ const closeSseClients = () => {
44
+ for (const cleanup of Array.from(sseClients)) {
45
+ cleanup();
46
+ }
47
+ sseClients.clear();
48
+ };
49
+ app.addHook("onRequest", (request, _reply, done) => {
50
+ ;
51
+ request.__logMeta = {
52
+ start: process.hrtime.bigint(),
53
+ };
54
+ done();
55
+ });
56
+ app.addHook("onResponse", (request, reply, done) => {
57
+ const meta = request.__logMeta;
58
+ const durationMs = meta ? Number((process.hrtime.bigint() - meta.start) / BigInt(1000000)) : undefined;
59
+ const base = {
60
+ method: request.method,
61
+ url: request.url,
62
+ status: reply.statusCode,
63
+ durationMs,
64
+ };
65
+ apiLogger.debug(base, "HTTP request completed");
66
+ if (apiLogger.isLevelEnabled("trace")) {
67
+ apiLogger.trace({ ...base, params: request.params, query: request.query, body: request.body }, "HTTP request payload");
68
+ }
69
+ done();
70
+ });
71
+ const allowedDevOrigins = new Set(["http://localhost:3000", "http://127.0.0.1:3000"]);
72
+ const isLoopbackHost = (host) => host === "127.0.0.1" || host === "::1" || host.startsWith("127.");
73
+ const getSelfOrigins = () => {
74
+ const origins = new Set();
75
+ const candidates = [deps.serverMeta.localUrl, deps.serverMeta.remoteUrl];
76
+ for (const candidate of candidates) {
77
+ if (!candidate)
78
+ continue;
79
+ try {
80
+ origins.add(new URL(candidate).origin);
81
+ }
82
+ catch {
83
+ // ignore
84
+ }
85
+ }
86
+ for (const addr of deps.serverMeta.addresses ?? []) {
87
+ try {
88
+ origins.add(new URL(addr.remoteUrl).origin);
89
+ }
90
+ catch {
91
+ // ignore
92
+ }
93
+ }
94
+ return origins;
95
+ };
96
+ app.register(cors, {
97
+ origin: (origin, cb) => {
98
+ if (!origin) {
99
+ cb(null, true);
100
+ return;
101
+ }
102
+ const selfOrigins = getSelfOrigins();
103
+ if (selfOrigins.has(origin)) {
104
+ cb(null, true);
105
+ return;
106
+ }
107
+ if (allowedDevOrigins.has(origin)) {
108
+ cb(null, true);
109
+ return;
110
+ }
111
+ // When we bind to a non-loopback host (e.g., 0.0.0.0 or LAN IP), allow cross-origin UI access.
112
+ if (deps.bindHost === "0.0.0.0" || !isLoopbackHost(deps.bindHost)) {
113
+ cb(null, true);
114
+ return;
115
+ }
116
+ cb(null, false);
117
+ },
118
+ credentials: true,
119
+ });
120
+ app.register(replyFrom, {
121
+ contentTypesToEncode: [],
122
+ undici: {
123
+ connections: 16,
124
+ pipelining: 1,
125
+ bodyTimeout: 0,
126
+ headersTimeout: 0,
127
+ },
128
+ });
129
+ const backgroundProcessManager = new BackgroundProcessManager({
130
+ workspaceManager: deps.workspaceManager,
131
+ eventBus: deps.eventBus,
132
+ logger: deps.logger.child({ component: "background-processes" }),
133
+ });
134
+ registerAuthRoutes(app, { authManager: deps.authManager });
135
+ app.addHook("preHandler", (request, reply, done) => {
136
+ const rawUrl = request.raw.url ?? request.url;
137
+ const pathname = (rawUrl.split("?")[0] ?? "").trim();
138
+ const publicApiPaths = new Set(["/api/auth/login", "/api/auth/token", "/api/auth/status", "/api/auth/logout"]);
139
+ const publicPagePaths = new Set(["/login"]);
140
+ if (deps.authManager.isTokenBootstrapEnabled()) {
141
+ publicPagePaths.add("/auth/token");
142
+ }
143
+ const isLoopbackRemoteProxyDelete = request.method === "DELETE" &&
144
+ pathname.startsWith("/api/remote-proxy/sessions/") &&
145
+ deps.authManager.isLoopbackRequest(request);
146
+ if (publicApiPaths.has(pathname) || publicPagePaths.has(pathname) || isLoopbackRemoteProxyDelete) {
147
+ done();
148
+ return;
149
+ }
150
+ const session = deps.authManager.getSessionFromRequest(request);
151
+ const requiresAuthForApi = pathname.startsWith("/api/") || pathname.startsWith("/workspaces/") || pathname.startsWith("/sidecars/");
152
+ if (requiresAuthForApi && !session) {
153
+ // Allow OpenCode plugin -> CodeNomad calls with per-instance basic auth.
154
+ const pluginMatch = pathname.match(/^\/workspaces\/([^/]+)\/plugin(?:\/|$)/);
155
+ if (pluginMatch) {
156
+ const workspaceId = pluginMatch[1];
157
+ const expected = deps.workspaceManager.getInstanceAuthorizationHeader(workspaceId);
158
+ const provided = Array.isArray(request.headers.authorization)
159
+ ? request.headers.authorization[0]
160
+ : request.headers.authorization;
161
+ if (expected && provided && provided === expected) {
162
+ done();
163
+ return;
164
+ }
165
+ }
166
+ sendUnauthorized(request, reply);
167
+ return;
168
+ }
169
+ if (!session && wantsHtml(request)) {
170
+ reply.redirect("/login");
171
+ return;
172
+ }
173
+ done();
174
+ });
175
+ app.get("/", async (request, reply) => {
176
+ const session = deps.authManager.getSessionFromRequest(request);
177
+ if (!session) {
178
+ reply.redirect("/login");
179
+ return;
180
+ }
181
+ if (deps.uiDevServerUrl) {
182
+ await proxyToDevServer(request, reply, deps.uiDevServerUrl);
183
+ return;
184
+ }
185
+ const uiDir = deps.uiStaticDir;
186
+ const indexPath = path.join(uiDir, "index.html");
187
+ if (uiDir && fs.existsSync(indexPath)) {
188
+ reply.type("text/html").send(fs.readFileSync(indexPath, "utf-8"));
189
+ return;
190
+ }
191
+ reply.code(404).send({ message: "UI bundle missing" });
192
+ });
193
+ registerWorkspaceRoutes(app, { workspaceManager: deps.workspaceManager });
194
+ registerSettingsRoutes(app, { settings: deps.settings, logger: apiLogger });
195
+ registerFilesystemRoutes(app, { fileSystemBrowser: deps.fileSystemBrowser });
196
+ registerMetaRoutes(app, { serverMeta: deps.serverMeta });
197
+ registerEventRoutes(app, {
198
+ eventBus: deps.eventBus,
199
+ registerClient: registerSseClient,
200
+ logger: sseLogger,
201
+ connectionManager: deps.clientConnectionManager,
202
+ });
203
+ registerWorktreeRoutes(app, { workspaceManager: deps.workspaceManager });
204
+ registerStorageRoutes(app, {
205
+ instanceStore: deps.instanceStore,
206
+ eventBus: deps.eventBus,
207
+ workspaceManager: deps.workspaceManager,
208
+ });
209
+ registerRemoteServerRoutes(app, { logger: apiLogger });
210
+ registerRemoteProxyRoutes(app, { logger: proxyLogger, sessionManager: deps.remoteProxySessionManager });
211
+ registerSpeechRoutes(app, { speechService: deps.speechService });
212
+ registerSideCarRoutes(app, { sidecarManager: deps.sidecarManager });
213
+ registerSideCarProxyRoutes(app, { sidecarManager: deps.sidecarManager, logger: proxyLogger });
214
+ setupSideCarWebSocketProxy(app, {
215
+ sidecarManager: deps.sidecarManager,
216
+ authManager: deps.authManager,
217
+ logger: proxyLogger,
218
+ });
219
+ registerPluginRoutes(app, {
220
+ workspaceManager: deps.workspaceManager,
221
+ eventBus: deps.eventBus,
222
+ logger: proxyLogger,
223
+ channel: deps.pluginChannel,
224
+ voiceModeManager: deps.voiceModeManager,
225
+ });
226
+ registerBackgroundProcessRoutes(app, { backgroundProcessManager });
227
+ registerInstanceProxyRoutes(app, { workspaceManager: deps.workspaceManager, logger: proxyLogger });
228
+ if (deps.uiDevServerUrl) {
229
+ setupDevProxy(app, deps.uiDevServerUrl, deps.authManager);
230
+ }
231
+ else {
232
+ setupStaticUi(app, deps.uiStaticDir, deps.authManager);
233
+ }
234
+ return {
235
+ instance: app,
236
+ start: async () => {
237
+ const attemptListen = async (requestedPort) => {
238
+ const addressInfo = await app.listen({ port: requestedPort, host: deps.bindHost });
239
+ return { addressInfo, requestedPort };
240
+ };
241
+ const autoPortRequested = deps.bindPort === 0;
242
+ const primaryPort = autoPortRequested ? deps.defaultPort : deps.bindPort;
243
+ const shouldRetryWithEphemeral = (error) => {
244
+ if (!autoPortRequested)
245
+ return false;
246
+ const err = error;
247
+ return Boolean(err && err.code === "EADDRINUSE");
248
+ };
249
+ let listenResult;
250
+ try {
251
+ listenResult = await attemptListen(primaryPort);
252
+ }
253
+ catch (error) {
254
+ if (!shouldRetryWithEphemeral(error)) {
255
+ throw error;
256
+ }
257
+ deps.logger.warn({ err: error, port: primaryPort }, "Preferred port unavailable, retrying on ephemeral port");
258
+ listenResult = await attemptListen(0);
259
+ }
260
+ let actualPort = listenResult.requestedPort;
261
+ if (typeof listenResult.addressInfo === "string") {
262
+ try {
263
+ const parsed = new URL(listenResult.addressInfo);
264
+ actualPort = Number(parsed.port) || listenResult.requestedPort;
265
+ }
266
+ catch {
267
+ actualPort = listenResult.requestedPort;
268
+ }
269
+ }
270
+ else {
271
+ const address = app.server.address();
272
+ if (typeof address === "object" && address) {
273
+ actualPort = address.port;
274
+ }
275
+ }
276
+ const displayHost = deps.bindHost === "127.0.0.1" ? "localhost" : deps.bindHost;
277
+ const serverUrl = `${deps.protocol}://${displayHost}:${actualPort}`;
278
+ deps.logger.info({ port: actualPort, host: deps.bindHost, protocol: deps.protocol }, "HTTP server listening");
279
+ return { port: actualPort, url: serverUrl, displayHost };
280
+ },
281
+ stop: () => {
282
+ closeSseClients();
283
+ return app.close();
284
+ },
285
+ };
286
+ }
287
+ function registerSideCarProxyRoutes(app, deps) {
288
+ const proxyBaseHandler = async (request, reply) => {
289
+ await proxySideCarRequest({
290
+ request,
291
+ reply,
292
+ sidecarManager: deps.sidecarManager,
293
+ logger: deps.logger,
294
+ pathSuffix: "",
295
+ });
296
+ };
297
+ const proxyWildcardHandler = async (request, reply) => {
298
+ await proxySideCarRequest({
299
+ request,
300
+ reply,
301
+ sidecarManager: deps.sidecarManager,
302
+ logger: deps.logger,
303
+ pathSuffix: request.params["*"] ?? "",
304
+ });
305
+ };
306
+ app.all("/sidecars/:id", proxyBaseHandler);
307
+ app.all("/sidecars/:id/*", proxyWildcardHandler);
308
+ }
309
+ function setupSideCarWebSocketProxy(app, deps) {
310
+ app.server.on("upgrade", (request, socket, head) => {
311
+ const rawUrl = request.url ?? "/";
312
+ const parsed = parseSideCarUpgradePath(rawUrl);
313
+ if (!parsed) {
314
+ return;
315
+ }
316
+ void proxySideCarWebSocketUpgrade({
317
+ request,
318
+ socket: socket,
319
+ head,
320
+ sidecarId: parsed.sidecarId,
321
+ incomingPath: parsed.pathname,
322
+ search: parsed.search,
323
+ sidecarManager: deps.sidecarManager,
324
+ authManager: deps.authManager,
325
+ logger: deps.logger,
326
+ });
327
+ });
328
+ }
329
+ function registerInstanceProxyRoutes(app, deps) {
330
+ app.register(async (instance) => {
331
+ instance.removeAllContentTypeParsers();
332
+ instance.addContentTypeParser("*", (req, body, done) => done(null, body));
333
+ const proxyBaseHandler = async (request, reply) => {
334
+ await proxyWorkspaceRequest({
335
+ request,
336
+ reply,
337
+ workspaceManager: deps.workspaceManager,
338
+ worktreeSlug: request.params.slug,
339
+ pathSuffix: "",
340
+ logger: deps.logger,
341
+ });
342
+ };
343
+ const proxyWildcardHandler = async (request, reply) => {
344
+ await proxyWorkspaceRequest({
345
+ request,
346
+ reply,
347
+ workspaceManager: deps.workspaceManager,
348
+ worktreeSlug: request.params.slug,
349
+ pathSuffix: request.params["*"] ?? "",
350
+ logger: deps.logger,
351
+ });
352
+ };
353
+ instance.all("/workspaces/:id/worktrees/:slug/instance", proxyBaseHandler);
354
+ instance.all("/workspaces/:id/worktrees/:slug/instance/*", proxyWildcardHandler);
355
+ });
356
+ }
357
+ const INSTANCE_PROXY_HOST = "127.0.0.1";
358
+ // Special-case OpenCode directory override.
359
+ //
360
+ // UI clients may need to scope certain requests to an arbitrary directory that is not
361
+ // part of the Git worktree list. Since the OpenCode SDK does not reliably support
362
+ // injecting per-request headers, we encode an override into the *path* and strip it
363
+ // before proxying to the instance.
364
+ //
365
+ // Example proxied request path:
366
+ // /workspaces/:id/worktrees/:slug/instance/__dir/<base64url>/session/create
367
+ //
368
+ // The server will decode <base64url> -> absolute directory, validate it, then set
369
+ // x-opencode-directory accordingly and forward the request to /session/create.
370
+ const OPENCODE_DIR_OVERRIDE_PREFIX = "__dir/";
371
+ const OPENCODE_DIR_OVERRIDE_MAX_LEN = 4096;
372
+ async function proxyWorkspaceRequest(args) {
373
+ const { request, reply, workspaceManager, logger, worktreeSlug } = args;
374
+ const workspaceId = request.params.id;
375
+ const workspace = workspaceManager.get(workspaceId);
376
+ const bodyToJson = (body) => {
377
+ if (body == null)
378
+ return null;
379
+ const anyBody = body;
380
+ if (anyBody && typeof anyBody.pipe === "function") {
381
+ // Don't consume streams (would break proxying).
382
+ // Best-effort: if the stream already has buffered chunks, parse those.
383
+ try {
384
+ const buffered = anyBody?._readableState?.buffer;
385
+ if (Array.isArray(buffered) && buffered.length > 0) {
386
+ const chunks = [];
387
+ for (const entry of buffered) {
388
+ if (!entry)
389
+ continue;
390
+ if (Buffer.isBuffer(entry)) {
391
+ chunks.push(entry);
392
+ continue;
393
+ }
394
+ const data = entry.data;
395
+ if (Buffer.isBuffer(data)) {
396
+ chunks.push(data);
397
+ }
398
+ }
399
+ if (chunks.length > 0) {
400
+ const text = Buffer.concat(chunks).toString("utf-8");
401
+ try {
402
+ return JSON.parse(text);
403
+ }
404
+ catch {
405
+ return { __raw: text };
406
+ }
407
+ }
408
+ }
409
+ }
410
+ catch {
411
+ // fall through
412
+ }
413
+ return { __stream: true };
414
+ }
415
+ const maybeParse = (input) => {
416
+ try {
417
+ return JSON.parse(input);
418
+ }
419
+ catch {
420
+ return { __raw: input };
421
+ }
422
+ };
423
+ if (Buffer.isBuffer(body)) {
424
+ return maybeParse(body.toString("utf-8"));
425
+ }
426
+ if (typeof body === "string") {
427
+ return maybeParse(body);
428
+ }
429
+ if (typeof body === "object") {
430
+ return body;
431
+ }
432
+ return body;
433
+ };
434
+ if (!workspace) {
435
+ reply.code(404).send({ error: "Workspace not found" });
436
+ return;
437
+ }
438
+ const port = workspaceManager.getInstancePort(workspaceId);
439
+ if (!port) {
440
+ reply.code(502).send({ error: "Workspace instance is not ready" });
441
+ return;
442
+ }
443
+ if (!isValidWorktreeSlug(worktreeSlug)) {
444
+ reply.code(400).send({ error: "Invalid worktree slug" });
445
+ return;
446
+ }
447
+ let extracted;
448
+ try {
449
+ extracted = extractOpencodeDirectoryOverride(args.pathSuffix);
450
+ }
451
+ catch (error) {
452
+ const message = error instanceof Error ? error.message : "Invalid directory override";
453
+ reply.code(400).send({ error: message });
454
+ return;
455
+ }
456
+ let directory = null;
457
+ let forwardedSuffix = extracted.forwardedSuffix;
458
+ if (extracted.overrideDirectory) {
459
+ try {
460
+ directory = validateAndNormalizeOverrideDirectory({
461
+ overrideDirectory: extracted.overrideDirectory,
462
+ workspaceRoot: workspace.path,
463
+ });
464
+ }
465
+ catch (error) {
466
+ const message = error instanceof Error ? error.message : "Invalid directory override";
467
+ reply.code(400).send({ error: message });
468
+ return;
469
+ }
470
+ }
471
+ else {
472
+ directory = await resolveWorktreeDirectory({
473
+ workspaceId,
474
+ workspacePath: workspace.path,
475
+ worktreeSlug,
476
+ logger,
477
+ });
478
+ if (!directory) {
479
+ reply.code(404).send({ error: "Worktree not found" });
480
+ return;
481
+ }
482
+ }
483
+ const normalizedSuffix = normalizeInstanceSuffix(forwardedSuffix);
484
+ const queryIndex = (request.raw.url ?? "").indexOf("?");
485
+ const search = queryIndex >= 0 ? (request.raw.url ?? "").slice(queryIndex) : "";
486
+ const targetUrl = `http://${INSTANCE_PROXY_HOST}:${port}${normalizedSuffix}${search}`;
487
+ const instanceAuthHeader = workspaceManager.getInstanceAuthorizationHeader(workspaceId);
488
+ logger.debug({ workspaceId, method: request.method, targetUrl }, "Proxying request to instance");
489
+ if (logger.isLevelEnabled("trace")) {
490
+ logger.trace({ workspaceId, targetUrl, body: request.body }, "Instance proxy payload");
491
+ }
492
+ return reply.from(targetUrl, {
493
+ rewriteRequestHeaders: (_originalRequest, headers) => {
494
+ if (instanceAuthHeader) {
495
+ headers.authorization = instanceAuthHeader;
496
+ }
497
+ // OpenCode expects the *full* path; we send it via header to avoid query tampering.
498
+ const isNonASCII = /[^\x00-\x7F]/.test(directory);
499
+ const encodedDirectory = isNonASCII ? encodeURIComponent(directory) : directory;
500
+ headers["x-opencode-directory"] = encodedDirectory;
501
+ if (logger.isLevelEnabled("trace")) {
502
+ const outgoing = {};
503
+ for (const [key, value] of Object.entries(headers)) {
504
+ outgoing[key] = value;
505
+ }
506
+ // Redact sensitive headers.
507
+ for (const key of Object.keys(outgoing)) {
508
+ const lower = key.toLowerCase();
509
+ if (lower === "authorization" || lower === "cookie" || lower === "set-cookie") {
510
+ outgoing[key] = "<redacted>";
511
+ }
512
+ }
513
+ logger.trace({
514
+ workspaceId,
515
+ method: request.method,
516
+ targetUrl,
517
+ worktreeSlug,
518
+ directory,
519
+ contentType: request.headers["content-type"],
520
+ body: bodyToJson(request.body),
521
+ headers: outgoing,
522
+ }, "Proxy -> OpenCode request");
523
+ }
524
+ return headers;
525
+ },
526
+ onError: (proxyReply, { error }) => {
527
+ logger.error({ err: error, workspaceId, targetUrl }, "Failed to proxy workspace request");
528
+ if (!proxyReply.sent) {
529
+ proxyReply.code(502).send({ error: "Workspace instance proxy failed" });
530
+ }
531
+ },
532
+ });
533
+ }
534
+ function extractOpencodeDirectoryOverride(pathSuffix) {
535
+ if (!pathSuffix) {
536
+ return { overrideDirectory: null, forwardedSuffix: pathSuffix };
537
+ }
538
+ // Fastify wildcard param does not include a leading slash.
539
+ const trimmed = pathSuffix.replace(/^\/+/, "");
540
+ if (!trimmed.startsWith(OPENCODE_DIR_OVERRIDE_PREFIX)) {
541
+ return { overrideDirectory: null, forwardedSuffix: pathSuffix };
542
+ }
543
+ const rest = trimmed.slice(OPENCODE_DIR_OVERRIDE_PREFIX.length);
544
+ const slashIndex = rest.indexOf("/");
545
+ const encoded = (slashIndex >= 0 ? rest.slice(0, slashIndex) : rest).trim();
546
+ const remaining = slashIndex >= 0 ? rest.slice(slashIndex + 1) : "";
547
+ if (!encoded) {
548
+ throw new Error("Missing directory override");
549
+ }
550
+ if (encoded.length > OPENCODE_DIR_OVERRIDE_MAX_LEN) {
551
+ throw new Error("Directory override too large");
552
+ }
553
+ let overrideDirectory = "";
554
+ try {
555
+ overrideDirectory = decodeBase64Url(encoded);
556
+ }
557
+ catch {
558
+ throw new Error("Invalid directory override");
559
+ }
560
+ const forwardedSuffix = remaining;
561
+ return { overrideDirectory, forwardedSuffix };
562
+ }
563
+ function decodeBase64Url(input) {
564
+ // base64url -> base64
565
+ const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
566
+ const padding = normalized.length % 4 === 0 ? "" : "=".repeat(4 - (normalized.length % 4));
567
+ const base64 = `${normalized}${padding}`;
568
+ return Buffer.from(base64, "base64").toString("utf-8");
569
+ }
570
+ function validateAndNormalizeOverrideDirectory(params) {
571
+ const raw = params.overrideDirectory.trim();
572
+ if (!raw) {
573
+ throw new Error("Override directory is empty");
574
+ }
575
+ if (!path.isAbsolute(raw)) {
576
+ throw new Error("Override directory must be an absolute path");
577
+ }
578
+ if (!fs.existsSync(raw)) {
579
+ throw new Error(`Override directory does not exist: ${raw}`);
580
+ }
581
+ const stats = fs.statSync(raw);
582
+ if (!stats.isDirectory()) {
583
+ throw new Error(`Override path is not a directory: ${raw}`);
584
+ }
585
+ const normalizedOverride = fs.realpathSync(raw);
586
+ const normalizedRoot = fs.realpathSync(params.workspaceRoot);
587
+ if (!isSubpath(normalizedOverride, normalizedRoot)) {
588
+ throw new Error("Override directory must be within the workspace root");
589
+ }
590
+ return normalizedOverride;
591
+ }
592
+ function isSubpath(candidate, root) {
593
+ const rel = path.relative(root, candidate);
594
+ if (rel === "")
595
+ return true;
596
+ if (rel === "..")
597
+ return false;
598
+ if (rel.startsWith(`..${path.sep}`))
599
+ return false;
600
+ if (path.isAbsolute(rel))
601
+ return false;
602
+ return true;
603
+ }
604
+ function normalizeInstanceSuffix(pathSuffix) {
605
+ if (!pathSuffix || pathSuffix === "/") {
606
+ return "/";
607
+ }
608
+ const trimmed = pathSuffix.replace(/^\/+/, "");
609
+ return trimmed.length === 0 ? "/" : `/${trimmed}`;
610
+ }
611
+ function setupStaticUi(app, uiDir, authManager) {
612
+ if (!uiDir) {
613
+ app.log.warn("UI static directory not provided; API endpoints only");
614
+ return;
615
+ }
616
+ if (!fs.existsSync(uiDir)) {
617
+ app.log.warn({ uiDir }, "UI static directory missing; API endpoints only");
618
+ return;
619
+ }
620
+ app.register(fastifyStatic, {
621
+ root: uiDir,
622
+ prefix: "/",
623
+ decorateReply: false,
624
+ });
625
+ const indexPath = path.join(uiDir, "index.html");
626
+ app.setNotFoundHandler((request, reply) => {
627
+ const url = request.raw.url ?? "";
628
+ if (isApiRequest(url)) {
629
+ reply.code(404).send({ message: "Not Found" });
630
+ return;
631
+ }
632
+ const session = authManager.getSessionFromRequest(request);
633
+ if (!session && wantsHtml(request)) {
634
+ reply.redirect("/login");
635
+ return;
636
+ }
637
+ if (fs.existsSync(indexPath)) {
638
+ reply.type("text/html").send(fs.readFileSync(indexPath, "utf-8"));
639
+ }
640
+ else {
641
+ reply.code(404).send({ message: "UI bundle missing" });
642
+ }
643
+ });
644
+ }
645
+ function setupDevProxy(app, upstreamBase, authManager) {
646
+ app.log.info({ upstreamBase }, "Proxying UI requests to development server");
647
+ app.setNotFoundHandler((request, reply) => {
648
+ const url = request.raw.url ?? "";
649
+ if (isApiRequest(url)) {
650
+ reply.code(404).send({ message: "Not Found" });
651
+ return;
652
+ }
653
+ const session = authManager.getSessionFromRequest(request);
654
+ if (!session && wantsHtml(request)) {
655
+ reply.redirect("/login");
656
+ return;
657
+ }
658
+ void proxyToDevServer(request, reply, upstreamBase);
659
+ });
660
+ }
661
+ async function proxyToDevServer(request, reply, upstreamBase) {
662
+ try {
663
+ const targetUrl = new URL(request.raw.url ?? "/", upstreamBase);
664
+ const response = await fetch(targetUrl, {
665
+ method: request.method,
666
+ headers: buildProxyHeaders(request.headers),
667
+ });
668
+ response.headers.forEach((value, key) => {
669
+ reply.header(key, value);
670
+ });
671
+ reply.code(response.status);
672
+ if (!response.body || request.method === "HEAD") {
673
+ reply.send();
674
+ return;
675
+ }
676
+ const buffer = Buffer.from(await response.arrayBuffer());
677
+ reply.send(buffer);
678
+ }
679
+ catch (error) {
680
+ request.log.error({ err: error }, "Failed to proxy UI request to dev server");
681
+ if (!reply.sent) {
682
+ reply.code(502).send("UI dev server is unavailable");
683
+ }
684
+ }
685
+ }
686
+ function isApiRequest(rawUrl) {
687
+ if (!rawUrl)
688
+ return false;
689
+ const pathname = rawUrl.split("?")[0] ?? "";
690
+ return pathname === "/api" || pathname.startsWith("/api/");
691
+ }
692
+ function buildProxyHeaders(headers) {
693
+ const result = {};
694
+ for (const [key, value] of Object.entries(headers ?? {})) {
695
+ if (!value || key.toLowerCase() === "host")
696
+ continue;
697
+ result[key] = Array.isArray(value) ? value.join(",") : value;
698
+ }
699
+ return result;
700
+ }
701
+ async function proxySideCarRequest(args) {
702
+ const sidecarId = args.request.params.id ?? "";
703
+ const sidecar = await args.sidecarManager.get(sidecarId);
704
+ if (!sidecar) {
705
+ args.reply.code(404).send({ error: "SideCar not found" });
706
+ return;
707
+ }
708
+ const pathname = (args.request.raw.url ?? args.request.url ?? "").split("?")[0] ?? "";
709
+ const queryIndex = (args.request.raw.url ?? args.request.url ?? "").indexOf("?");
710
+ const search = queryIndex >= 0 ? (args.request.raw.url ?? args.request.url ?? "").slice(queryIndex) : "";
711
+ const pathSuffix = args.pathSuffix ?? "";
712
+ const requestPath = pathSuffix ? `${args.sidecarManager.buildProxyBasePath(sidecarId)}/${pathSuffix.replace(/^\/+/, "")}` : args.sidecarManager.buildProxyBasePath(sidecarId);
713
+ const targetPath = args.sidecarManager.buildTargetPath(sidecarId, requestPath, search);
714
+ const targetOrigin = args.sidecarManager.buildTargetOrigin(sidecar);
715
+ const targetUrl = `${targetOrigin}${targetPath}`;
716
+ args.logger.debug({ sidecarId: sidecar.id, targetUrl, pathname, prefixMode: sidecar.prefixMode }, "Proxying request to SideCar");
717
+ await args.reply.from(targetUrl, {
718
+ rewriteRequestHeaders: (_originalRequest, headers) => sanitizeSideCarProxyRequestHeaders(headers, targetOrigin),
719
+ rewriteHeaders: (headers) => rewriteSideCarResponseHeaders(headers, sidecarId, targetOrigin, sidecar.prefixMode),
720
+ onError: (reply, { error }) => {
721
+ args.logger.error({ sidecarId: sidecar.id, err: error, targetUrl }, "Failed to proxy SideCar request");
722
+ if (!reply.sent) {
723
+ reply.code(502).send({ error: "SideCar proxy failed" });
724
+ }
725
+ },
726
+ });
727
+ }
728
+ function parseSideCarUpgradePath(rawUrl) {
729
+ let parsed;
730
+ try {
731
+ parsed = new URL(rawUrl, "http://localhost");
732
+ }
733
+ catch {
734
+ return null;
735
+ }
736
+ const match = parsed.pathname.match(/^\/sidecars\/([^/]+)(?:\/.*)?$/);
737
+ if (!match) {
738
+ return null;
739
+ }
740
+ try {
741
+ return {
742
+ sidecarId: decodeURIComponent(match[1] ?? ""),
743
+ pathname: parsed.pathname,
744
+ search: parsed.search,
745
+ };
746
+ }
747
+ catch {
748
+ return null;
749
+ }
750
+ }
751
+ async function proxySideCarWebSocketUpgrade(args) {
752
+ const { request, socket, head, sidecarId, incomingPath, search, sidecarManager, authManager, logger } = args;
753
+ if (!isWebSocketUpgradeRequest(request)) {
754
+ rejectUpgrade(socket, 400, "Bad Request");
755
+ return;
756
+ }
757
+ const session = authManager.getSessionFromHeaders(request.headers);
758
+ if (!session) {
759
+ rejectUpgrade(socket, 401, "Unauthorized");
760
+ return;
761
+ }
762
+ const sidecar = await sidecarManager.get(sidecarId);
763
+ if (!sidecar) {
764
+ rejectUpgrade(socket, 404, "Not Found");
765
+ return;
766
+ }
767
+ const targetOrigin = sidecarManager.buildTargetOrigin(sidecar);
768
+ const targetPath = sidecarManager.buildTargetPath(sidecarId, incomingPath, search);
769
+ const targetUrl = new URL(`${targetOrigin}${targetPath}`);
770
+ logger.debug({ sidecarId, targetUrl: targetUrl.toString(), prefixMode: sidecar.prefixMode }, "Proxying websocket to SideCar");
771
+ const { socket: upstream, readyEvent } = createSideCarUpstreamSocket(targetUrl);
772
+ const closeBoth = () => {
773
+ if (!socket.destroyed) {
774
+ socket.destroy();
775
+ }
776
+ if (!upstream.destroyed) {
777
+ upstream.destroy();
778
+ }
779
+ };
780
+ upstream.once("error", (error) => {
781
+ logger.error({ sidecarId, err: error, targetUrl: targetUrl.toString() }, "Failed to proxy SideCar websocket");
782
+ rejectUpgrade(socket, 502, "Bad Gateway");
783
+ if (!upstream.destroyed) {
784
+ upstream.destroy();
785
+ }
786
+ });
787
+ socket.once("error", (error) => {
788
+ logger.debug({ sidecarId, err: error }, "SideCar websocket client socket errored");
789
+ if (!upstream.destroyed) {
790
+ upstream.destroy();
791
+ }
792
+ });
793
+ upstream.once(readyEvent, () => {
794
+ try {
795
+ upstream.write(buildSideCarWebSocketRequest(request, targetUrl));
796
+ if (head.length > 0) {
797
+ upstream.write(head);
798
+ }
799
+ upstream.pipe(socket);
800
+ socket.pipe(upstream);
801
+ }
802
+ catch (error) {
803
+ logger.error({ sidecarId, err: error, targetUrl: targetUrl.toString() }, "Failed to forward SideCar websocket upgrade");
804
+ closeBoth();
805
+ }
806
+ });
807
+ upstream.once("close", () => {
808
+ if (!socket.destroyed) {
809
+ socket.end();
810
+ }
811
+ });
812
+ socket.once("close", () => {
813
+ if (!upstream.destroyed) {
814
+ upstream.end();
815
+ }
816
+ });
817
+ }
818
+ function createSideCarUpstreamSocket(targetUrl) {
819
+ const port = Number(targetUrl.port || (targetUrl.protocol === "https:" ? 443 : 80));
820
+ if (targetUrl.protocol === "https:") {
821
+ return {
822
+ socket: connectTls({
823
+ host: targetUrl.hostname,
824
+ port,
825
+ servername: targetUrl.hostname,
826
+ }),
827
+ readyEvent: "secureConnect",
828
+ };
829
+ }
830
+ return {
831
+ socket: connectTcp(port, targetUrl.hostname),
832
+ readyEvent: "connect",
833
+ };
834
+ }
835
+ function buildSideCarWebSocketRequest(request, targetUrl) {
836
+ const pathWithQuery = `${targetUrl.pathname}${targetUrl.search}`;
837
+ const requestLine = `${request.method ?? "GET"} ${pathWithQuery} HTTP/${request.httpVersion}\r\n`;
838
+ const headerLines = [];
839
+ const rawHeaders = request.rawHeaders ?? [];
840
+ const blockedHeaders = getBlockedSideCarRequestHeaders();
841
+ for (let index = 0; index < rawHeaders.length; index += 2) {
842
+ const key = rawHeaders[index];
843
+ const value = rawHeaders[index + 1];
844
+ if (!key || value === undefined)
845
+ continue;
846
+ const lower = key.toLowerCase();
847
+ if (blockedHeaders.has(lower))
848
+ continue;
849
+ if (lower === "origin") {
850
+ headerLines.push(`Origin: ${targetUrl.origin}\r\n`);
851
+ continue;
852
+ }
853
+ headerLines.push(`${key}: ${value}\r\n`);
854
+ }
855
+ const hostValue = targetUrl.port ? `${targetUrl.hostname}:${targetUrl.port}` : targetUrl.hostname;
856
+ headerLines.push(`Host: ${hostValue}\r\n`);
857
+ headerLines.push("\r\n");
858
+ return requestLine + headerLines.join("");
859
+ }
860
+ function isWebSocketUpgradeRequest(request) {
861
+ const upgrade = request.headers.upgrade;
862
+ if (typeof upgrade !== "string" || upgrade.toLowerCase() !== "websocket") {
863
+ return false;
864
+ }
865
+ const connection = request.headers.connection;
866
+ const connectionValue = Array.isArray(connection) ? connection.join(",") : connection ?? "";
867
+ return connectionValue.toLowerCase().split(",").map((part) => part.trim()).includes("upgrade");
868
+ }
869
+ function rejectUpgrade(socket, statusCode, statusText) {
870
+ if (socket.destroyed) {
871
+ return;
872
+ }
873
+ socket.write(`HTTP/1.1 ${statusCode} ${statusText}\r\nConnection: close\r\nContent-Length: 0\r\n\r\n`);
874
+ socket.destroy();
875
+ }
876
+ function rewriteSideCarResponseHeaders(headers, sidecarId, targetOrigin, prefixMode) {
877
+ if (prefixMode === "preserve") {
878
+ return headers;
879
+ }
880
+ const next = { ...headers };
881
+ const locationHeader = next.location;
882
+ const location = Array.isArray(locationHeader) ? locationHeader[0] : locationHeader;
883
+ if (!location) {
884
+ return next;
885
+ }
886
+ const publicBase = `/sidecars/${encodeURIComponent(sidecarId)}`;
887
+ if (location.startsWith("/")) {
888
+ next.location = `${publicBase}${location}`;
889
+ return next;
890
+ }
891
+ try {
892
+ const parsed = new URL(location);
893
+ if (parsed.origin === targetOrigin) {
894
+ next.location = `${publicBase}${parsed.pathname}${parsed.search}${parsed.hash}`;
895
+ }
896
+ }
897
+ catch {
898
+ // Relative redirects should continue to resolve against the public sidecar path.
899
+ }
900
+ return next;
901
+ }
902
+ function sanitizeSideCarProxyRequestHeaders(headers, targetOrigin) {
903
+ const blockedHeaders = getBlockedSideCarRequestHeaders();
904
+ const next = {};
905
+ for (const [key, value] of Object.entries(headers)) {
906
+ if (!value)
907
+ continue;
908
+ if (blockedHeaders.has(key.toLowerCase()))
909
+ continue;
910
+ next[key] = value;
911
+ }
912
+ next.origin = targetOrigin;
913
+ return next;
914
+ }
915
+ function getBlockedSideCarRequestHeaders() {
916
+ return new Set([
917
+ "host",
918
+ "authorization",
919
+ "proxy-authorization",
920
+ "forwarded",
921
+ "x-forwarded-for",
922
+ "x-forwarded-host",
923
+ "x-forwarded-port",
924
+ "x-forwarded-proto",
925
+ ]);
926
+ }