@neuralnomads/codenomad-dev 0.10.3-dev-20260213-ba418a85

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 (462) hide show
  1. package/README.md +126 -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 +128 -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 +437 -0
  10. package/dist/bin.js +24 -0
  11. package/dist/config/binaries.js +148 -0
  12. package/dist/config/location.js +57 -0
  13. package/dist/config/schema.js +70 -0
  14. package/dist/config/store.js +200 -0
  15. package/dist/events/bus.js +41 -0
  16. package/dist/filesystem/__tests__/search-cache.test.js +40 -0
  17. package/dist/filesystem/browser.js +285 -0
  18. package/dist/filesystem/search-cache.js +43 -0
  19. package/dist/filesystem/search.js +135 -0
  20. package/dist/index.js +410 -0
  21. package/dist/integrations/github/bot-signature.js +11 -0
  22. package/dist/integrations/github/git-ops.js +133 -0
  23. package/dist/integrations/github/github-types.js +1 -0
  24. package/dist/integrations/github/job-runner.js +608 -0
  25. package/dist/integrations/github/octokit.js +58 -0
  26. package/dist/integrations/github/sanitize-webhook.js +42 -0
  27. package/dist/integrations/github/webhook-verify.js +21 -0
  28. package/dist/integrations/github/workspace-context.js +10 -0
  29. package/dist/integrations/github/worktree-context.js +15 -0
  30. package/dist/launcher.js +149 -0
  31. package/dist/loader.js +21 -0
  32. package/dist/logger.js +109 -0
  33. package/dist/opencode/request-context.js +39 -0
  34. package/dist/opencode/worktree-directory.js +42 -0
  35. package/dist/opencode-config/README.md +32 -0
  36. package/dist/opencode-config/opencode.jsonc +3 -0
  37. package/dist/opencode-config/package.json +9 -0
  38. package/dist/opencode-config/plugin/codenomad.ts +32 -0
  39. package/dist/opencode-config/plugin/lib/background-process.ts +253 -0
  40. package/dist/opencode-config/plugin/lib/client.ts +133 -0
  41. package/dist/opencode-config/plugin/lib/request.ts +214 -0
  42. package/dist/opencode-config-template/README.md +32 -0
  43. package/dist/opencode-config-template/opencode.jsonc +3 -0
  44. package/dist/opencode-config-template/plugin/codenomad.ts +40 -0
  45. package/dist/opencode-config-template/plugin/lib/background-process.ts +160 -0
  46. package/dist/opencode-config-template/plugin/lib/client.ts +165 -0
  47. package/dist/opencode-config.js +26 -0
  48. package/dist/plugins/channel.js +40 -0
  49. package/dist/plugins/handlers.js +17 -0
  50. package/dist/releases/dev-release-monitor.js +75 -0
  51. package/dist/releases/release-monitor.js +107 -0
  52. package/dist/server/http-server.js +547 -0
  53. package/dist/server/network-addresses.js +72 -0
  54. package/dist/server/routes/auth-pages/login.html +134 -0
  55. package/dist/server/routes/auth-pages/token.html +93 -0
  56. package/dist/server/routes/auth.js +134 -0
  57. package/dist/server/routes/background-processes.js +60 -0
  58. package/dist/server/routes/config.js +59 -0
  59. package/dist/server/routes/events.js +43 -0
  60. package/dist/server/routes/filesystem.js +43 -0
  61. package/dist/server/routes/github-plugin.js +215 -0
  62. package/dist/server/routes/github-webhook.js +32 -0
  63. package/dist/server/routes/meta.js +47 -0
  64. package/dist/server/routes/plugin.js +52 -0
  65. package/dist/server/routes/storage.js +52 -0
  66. package/dist/server/routes/workspaces.js +89 -0
  67. package/dist/server/routes/worktrees.js +156 -0
  68. package/dist/server/tls.js +224 -0
  69. package/dist/storage/instance-store.js +56 -0
  70. package/dist/ui/__tests__/remote-ui.test.js +46 -0
  71. package/dist/ui/remote-ui.js +462 -0
  72. package/dist/workspaces/git-worktrees.js +199 -0
  73. package/dist/workspaces/instance-events.js +180 -0
  74. package/dist/workspaces/manager.js +375 -0
  75. package/dist/workspaces/opencode-auth.js +16 -0
  76. package/dist/workspaces/runtime.js +346 -0
  77. package/dist/workspaces/worktree-map.js +116 -0
  78. package/package.json +49 -0
  79. package/public/apple-touch-icon-180x180.png +0 -0
  80. package/public/assets/CodeNomad-Icon-bmTWNPXy.png +0 -0
  81. package/public/assets/abap-BdImnpbu.js +1 -0
  82. package/public/assets/actionscript-3-CfeIJUat.js +1 -0
  83. package/public/assets/ada-bCR0ucgS.js +1 -0
  84. package/public/assets/andromeeda-C-Jbm3Hp.js +1 -0
  85. package/public/assets/angular-html-CU67Zn6k.js +1 -0
  86. package/public/assets/angular-ts-BwZT4LLn.js +1 -0
  87. package/public/assets/apache-Pmp26Uib.js +1 -0
  88. package/public/assets/apex-DhZLUxFE.js +1 -0
  89. package/public/assets/apl-dKokRX4l.js +1 -0
  90. package/public/assets/applescript-Co6uUVPk.js +1 -0
  91. package/public/assets/ara-BRHolxvo.js +1 -0
  92. package/public/assets/asciidoc-Dv7Oe6Be.js +1 -0
  93. package/public/assets/asm-D_Q5rh1f.js +1 -0
  94. package/public/assets/astro-CbQHKStN.js +1 -0
  95. package/public/assets/aurora-x-D-2ljcwZ.js +1 -0
  96. package/public/assets/awk-DMzUqQB5.js +1 -0
  97. package/public/assets/ayu-dark-Cv9koXgw.js +1 -0
  98. package/public/assets/ballerina-BFfxhgS-.js +1 -0
  99. package/public/assets/bat-BkioyH1T.js +1 -0
  100. package/public/assets/beancount-k_qm7-4y.js +1 -0
  101. package/public/assets/berry-D08WgyRC.js +1 -0
  102. package/public/assets/bibtex-CHM0blh-.js +1 -0
  103. package/public/assets/bicep-Bmn6On1c.js +1 -0
  104. package/public/assets/blade-DVc8C-J4.js +1 -0
  105. package/public/assets/bsl-BO_Y6i37.js +1 -0
  106. package/public/assets/c-BIGW1oBm.js +1 -0
  107. package/public/assets/cadence-Bv_4Rxtq.js +1 -0
  108. package/public/assets/cairo-KRGpt6FW.js +1 -0
  109. package/public/assets/catppuccin-frappe-DFWUc33u.js +1 -0
  110. package/public/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
  111. package/public/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
  112. package/public/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
  113. package/public/assets/clarity-D53aC0YG.js +1 -0
  114. package/public/assets/clojure-P80f7IUj.js +1 -0
  115. package/public/assets/cmake-D1j8_8rp.js +1 -0
  116. package/public/assets/cobol-nwyudZeR.js +1 -0
  117. package/public/assets/codeowners-Bp6g37R7.js +1 -0
  118. package/public/assets/codeql-DsOJ9woJ.js +1 -0
  119. package/public/assets/coffee-Ch7k5sss.js +1 -0
  120. package/public/assets/common-lisp-Cg-RD9OK.js +1 -0
  121. package/public/assets/coq-DkFqJrB1.js +1 -0
  122. package/public/assets/core-BSTVzpXI.js +1 -0
  123. package/public/assets/cpp-CofmeUqb.js +1 -0
  124. package/public/assets/crystal-tKQVLTB8.js +1 -0
  125. package/public/assets/csharp-CX12Zw3r.js +1 -0
  126. package/public/assets/css-DPfMkruS.js +1 -0
  127. package/public/assets/csv-fuZLfV_i.js +1 -0
  128. package/public/assets/cue-D82EKSYY.js +1 -0
  129. package/public/assets/cypher-COkxafJQ.js +1 -0
  130. package/public/assets/d-85-TOEBH.js +1 -0
  131. package/public/assets/dark-plus-eOWES_5F.js +1 -0
  132. package/public/assets/dart-CF10PKvl.js +1 -0
  133. package/public/assets/dax-CEL-wOlO.js +1 -0
  134. package/public/assets/desktop-BmXAJ9_W.js +1 -0
  135. package/public/assets/diff-D97Zzqfu.js +1 -0
  136. package/public/assets/docker-BcOcwvcX.js +1 -0
  137. package/public/assets/dotenv-Da5cRb03.js +1 -0
  138. package/public/assets/dracula-BzJJZx-M.js +1 -0
  139. package/public/assets/dracula-soft-BXkSAIEj.js +1 -0
  140. package/public/assets/dream-maker-BtqSS_iP.js +1 -0
  141. package/public/assets/edge-BkV0erSs.js +1 -0
  142. package/public/assets/elixir-CDX3lj18.js +1 -0
  143. package/public/assets/elm-DbKCFpqz.js +1 -0
  144. package/public/assets/emacs-lisp-C9XAeP06.js +1 -0
  145. package/public/assets/erb-BOJIQeun.js +1 -0
  146. package/public/assets/erlang-DsQrWhSR.js +1 -0
  147. package/public/assets/everforest-dark-BgDCqdQA.js +1 -0
  148. package/public/assets/everforest-light-C8M2exoo.js +1 -0
  149. package/public/assets/fennel-BYunw83y.js +1 -0
  150. package/public/assets/fish-BvzEVeQv.js +1 -0
  151. package/public/assets/fluent-C4IJs8-o.js +1 -0
  152. package/public/assets/fortran-fixed-form-BZjJHVRy.js +1 -0
  153. package/public/assets/fortran-free-form-D22FLkUw.js +1 -0
  154. package/public/assets/fsharp-CXgrBDvD.js +1 -0
  155. package/public/assets/gdresource-B7Tvp0Sc.js +1 -0
  156. package/public/assets/gdscript-DTMYz4Jt.js +1 -0
  157. package/public/assets/gdshader-DkwncUOv.js +1 -0
  158. package/public/assets/genie-D0YGMca9.js +1 -0
  159. package/public/assets/gherkin-DyxjwDmM.js +1 -0
  160. package/public/assets/git-commit-F4YmCXRG.js +1 -0
  161. package/public/assets/git-rebase-r7XF79zn.js +1 -0
  162. package/public/assets/github-dark-DHJKELXO.js +1 -0
  163. package/public/assets/github-dark-default-Cuk6v7N8.js +1 -0
  164. package/public/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
  165. package/public/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
  166. package/public/assets/github-light-DAi9KRSo.js +1 -0
  167. package/public/assets/github-light-default-D7oLnXFd.js +1 -0
  168. package/public/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
  169. package/public/assets/gleam-BspZqrRM.js +1 -0
  170. package/public/assets/glimmer-js-Rg0-pVw9.js +1 -0
  171. package/public/assets/glimmer-ts-U6CK756n.js +1 -0
  172. package/public/assets/glsl-DplSGwfg.js +1 -0
  173. package/public/assets/gnuplot-DdkO51Og.js +1 -0
  174. package/public/assets/go-Dn2_MT6a.js +1 -0
  175. package/public/assets/graphql-ChdNCCLP.js +1 -0
  176. package/public/assets/groovy-gcz8RCvz.js +1 -0
  177. package/public/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
  178. package/public/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
  179. package/public/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
  180. package/public/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
  181. package/public/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
  182. package/public/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
  183. package/public/assets/hack-CaT9iCJl.js +1 -0
  184. package/public/assets/haml-B8DHNrY2.js +1 -0
  185. package/public/assets/handlebars-BL8al0AC.js +1 -0
  186. package/public/assets/haskell-Df6bDoY_.js +1 -0
  187. package/public/assets/haxe-CzTSHFRz.js +1 -0
  188. package/public/assets/hcl-BWvSN4gD.js +1 -0
  189. package/public/assets/hjson-D5-asLiD.js +1 -0
  190. package/public/assets/hlsl-D3lLCCz7.js +1 -0
  191. package/public/assets/houston-DnULxvSX.js +1 -0
  192. package/public/assets/html-GMplVEZG.js +1 -0
  193. package/public/assets/html-derivative-BFtXZ54Q.js +1 -0
  194. package/public/assets/http-jrhK8wxY.js +1 -0
  195. package/public/assets/hurl-irOxFIW8.js +1 -0
  196. package/public/assets/hxml-Bvhsp5Yf.js +1 -0
  197. package/public/assets/hy-DFXneXwc.js +1 -0
  198. package/public/assets/imba-DGztddWO.js +1 -0
  199. package/public/assets/index-D4PT0yE4.js +1 -0
  200. package/public/assets/index-DN20ggb1.js +1 -0
  201. package/public/assets/index-DdQ7zIzB.js +1 -0
  202. package/public/assets/index-Dl-rJJuP.js +1 -0
  203. package/public/assets/index-Dlo2gDiy.css +1 -0
  204. package/public/assets/ini-BEwlwnbL.js +1 -0
  205. package/public/assets/java-CylS5w8V.js +1 -0
  206. package/public/assets/javascript-wDzz0qaB.js +1 -0
  207. package/public/assets/jinja-4LBKfQ-Z.js +1 -0
  208. package/public/assets/jison-wvAkD_A8.js +1 -0
  209. package/public/assets/json-Cp-IABpG.js +1 -0
  210. package/public/assets/json5-C9tS-k6U.js +1 -0
  211. package/public/assets/jsonc-Des-eS-w.js +1 -0
  212. package/public/assets/jsonl-DcaNXYhu.js +1 -0
  213. package/public/assets/jsonnet-DFQXde-d.js +1 -0
  214. package/public/assets/jssm-C2t-YnRu.js +1 -0
  215. package/public/assets/jsx-g9-lgVsj.js +1 -0
  216. package/public/assets/julia-C8NyazO9.js +1 -0
  217. package/public/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
  218. package/public/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
  219. package/public/assets/kanagawa-wave-DWedfzmr.js +1 -0
  220. package/public/assets/kdl-DV7GczEv.js +1 -0
  221. package/public/assets/kotlin-BdnUsdx6.js +1 -0
  222. package/public/assets/kusto-BvAqAH-y.js +1 -0
  223. package/public/assets/laserwave-DUszq2jm.js +1 -0
  224. package/public/assets/latex-BUKiar2Z.js +1 -0
  225. package/public/assets/lean-DP1Csr6i.js +1 -0
  226. package/public/assets/less-B1dDrJ26.js +1 -0
  227. package/public/assets/light-plus-B7mTdjB0.js +1 -0
  228. package/public/assets/liquid-DYVedYrR.js +1 -0
  229. package/public/assets/llvm-BtvRca6l.js +1 -0
  230. package/public/assets/loading-CmEVQgyj.css +1 -0
  231. package/public/assets/loading-DgqIiz-T.js +1 -0
  232. package/public/assets/log-2UxHyX5q.js +1 -0
  233. package/public/assets/logo-BtOb2qkB.js +1 -0
  234. package/public/assets/lua-BbnMAYS6.js +1 -0
  235. package/public/assets/luau-CXu1NL6O.js +1 -0
  236. package/public/assets/main-CSlDZj4f.js +188 -0
  237. package/public/assets/main-HAZkIolJ.css +19 -0
  238. package/public/assets/make-CHLpvVh8.js +1 -0
  239. package/public/assets/markdown-Cvjx9yec.js +1 -0
  240. package/public/assets/marko-CPi9NSCl.js +1 -0
  241. package/public/assets/material-theme-D5KoaKCx.js +1 -0
  242. package/public/assets/material-theme-darker-BfHTSMKl.js +1 -0
  243. package/public/assets/material-theme-lighter-B0m2ddpp.js +1 -0
  244. package/public/assets/material-theme-ocean-CyktbL80.js +1 -0
  245. package/public/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
  246. package/public/assets/matlab-D7o27uSR.js +1 -0
  247. package/public/assets/mdc-DUICxH0z.js +1 -0
  248. package/public/assets/mdx-Cmh6b_Ma.js +1 -0
  249. package/public/assets/mermaid-DKYwYmdq.js +1 -0
  250. package/public/assets/min-dark-CafNBF8u.js +1 -0
  251. package/public/assets/min-light-CTRr51gU.js +1 -0
  252. package/public/assets/mipsasm-CKIfxQSi.js +1 -0
  253. package/public/assets/mojo-1DNp92w6.js +1 -0
  254. package/public/assets/monokai-D4h5O-jR.js +1 -0
  255. package/public/assets/move-Bu9oaDYs.js +1 -0
  256. package/public/assets/narrat-DRg8JJMk.js +1 -0
  257. package/public/assets/nextflow-CUEJCptM.js +1 -0
  258. package/public/assets/nginx-DknmC5AR.js +1 -0
  259. package/public/assets/night-owl-C39BiMTA.js +1 -0
  260. package/public/assets/nim-CVrawwO9.js +1 -0
  261. package/public/assets/nix-BbRYJGeE.js +1 -0
  262. package/public/assets/nord-Ddv68eIx.js +1 -0
  263. package/public/assets/nushell-C-sUppwS.js +1 -0
  264. package/public/assets/objective-c-DXmwc3jG.js +1 -0
  265. package/public/assets/objective-cpp-CLxacb5B.js +1 -0
  266. package/public/assets/ocaml-C0hk2d4L.js +1 -0
  267. package/public/assets/one-dark-pro-DVMEJ2y_.js +1 -0
  268. package/public/assets/one-light-PoHY5YXO.js +1 -0
  269. package/public/assets/pascal-D93ZcfNL.js +1 -0
  270. package/public/assets/perl-C0TMdlhV.js +1 -0
  271. package/public/assets/php-CDn_0X-4.js +1 -0
  272. package/public/assets/pkl-u5AG7uiY.js +1 -0
  273. package/public/assets/plastic-3e1v2bzS.js +1 -0
  274. package/public/assets/plsql-ChMvpjG-.js +1 -0
  275. package/public/assets/po-BTJTHyun.js +1 -0
  276. package/public/assets/poimandres-CS3Unz2-.js +1 -0
  277. package/public/assets/polar-C0HS_06l.js +1 -0
  278. package/public/assets/postcss-CXtECtnM.js +1 -0
  279. package/public/assets/powerquery-CEu0bR-o.js +1 -0
  280. package/public/assets/powershell-Dpen1YoG.js +1 -0
  281. package/public/assets/prisma-Dd19v3D-.js +1 -0
  282. package/public/assets/prolog-CbFg5uaA.js +1 -0
  283. package/public/assets/proto-DyJlTyXw.js +1 -0
  284. package/public/assets/pug-CGlum2m_.js +1 -0
  285. package/public/assets/puppet-BMWR74SV.js +1 -0
  286. package/public/assets/purescript-CklMAg4u.js +1 -0
  287. package/public/assets/python-B6aJPvgy.js +1 -0
  288. package/public/assets/qml-3beO22l8.js +1 -0
  289. package/public/assets/qmldir-C8lEn-DE.js +1 -0
  290. package/public/assets/qss-IeuSbFQv.js +1 -0
  291. package/public/assets/r-DiinP2Uv.js +1 -0
  292. package/public/assets/racket-BqYA7rlc.js +1 -0
  293. package/public/assets/raku-DXvB9xmW.js +1 -0
  294. package/public/assets/razor-WgofotgN.js +1 -0
  295. package/public/assets/red-bN70gL4F.js +1 -0
  296. package/public/assets/reg-C-SQnVFl.js +1 -0
  297. package/public/assets/regexp-CDVJQ6XC.js +1 -0
  298. package/public/assets/rel-C3B-1QV4.js +1 -0
  299. package/public/assets/riscv-BM1_JUlF.js +1 -0
  300. package/public/assets/rose-pine-BHrmToEH.js +1 -0
  301. package/public/assets/rose-pine-dawn-CnK8MTSM.js +1 -0
  302. package/public/assets/rose-pine-moon-NleAzG8P.js +1 -0
  303. package/public/assets/rosmsg-BJDFO7_C.js +1 -0
  304. package/public/assets/rst-B0xPkSld.js +1 -0
  305. package/public/assets/ruby-BvKwtOVI.js +1 -0
  306. package/public/assets/rust-B1yitclQ.js +1 -0
  307. package/public/assets/sas-cz2c8ADy.js +1 -0
  308. package/public/assets/sass-Cj5Yp3dK.js +1 -0
  309. package/public/assets/scala-C151Ov-r.js +1 -0
  310. package/public/assets/scheme-C98Dy4si.js +1 -0
  311. package/public/assets/scss-OYdSNvt2.js +1 -0
  312. package/public/assets/sdbl-DVxCFoDh.js +1 -0
  313. package/public/assets/shaderlab-Dg9Lc6iA.js +1 -0
  314. package/public/assets/shellscript-Yzrsuije.js +1 -0
  315. package/public/assets/shellsession-BADoaaVG.js +1 -0
  316. package/public/assets/slack-dark-BthQWCQV.js +1 -0
  317. package/public/assets/slack-ochin-DqwNpetd.js +1 -0
  318. package/public/assets/smalltalk-BERRCDM3.js +1 -0
  319. package/public/assets/snazzy-light-Bw305WKR.js +1 -0
  320. package/public/assets/solarized-dark-DXbdFlpD.js +1 -0
  321. package/public/assets/solarized-light-L9t79GZl.js +1 -0
  322. package/public/assets/solidity-BbcW6ACK.js +1 -0
  323. package/public/assets/soy-Brmx7dQM.js +1 -0
  324. package/public/assets/sparql-rVzFXLq3.js +1 -0
  325. package/public/assets/splunk-BtCnVYZw.js +1 -0
  326. package/public/assets/sql-BLtJtn59.js +1 -0
  327. package/public/assets/ssh-config-_ykCGR6B.js +1 -0
  328. package/public/assets/stata-BH5u7GGu.js +1 -0
  329. package/public/assets/stylus-BEDo0Tqx.js +1 -0
  330. package/public/assets/svelte-3Dk4HxPD.js +1 -0
  331. package/public/assets/swift-Dg5xB15N.js +1 -0
  332. package/public/assets/synthwave-84-CbfX1IO0.js +1 -0
  333. package/public/assets/system-verilog-CnnmHF94.js +1 -0
  334. package/public/assets/systemd-4A_iFExJ.js +1 -0
  335. package/public/assets/talonscript-CkByrt1z.js +1 -0
  336. package/public/assets/tasl-QIJgUcNo.js +1 -0
  337. package/public/assets/tcl-dwOrl1Do.js +1 -0
  338. package/public/assets/templ-W15q3VgB.js +1 -0
  339. package/public/assets/terraform-BETggiCN.js +1 -0
  340. package/public/assets/tex-Cppo0RY3.js +1 -0
  341. package/public/assets/tokyo-night-hegEt444.js +1 -0
  342. package/public/assets/toml-vGWfd6FD.js +1 -0
  343. package/public/assets/ts-tags-zn1MmPIZ.js +1 -0
  344. package/public/assets/tsv-B_m7g4N7.js +1 -0
  345. package/public/assets/tsx-COt5Ahok.js +1 -0
  346. package/public/assets/turtle-BsS91CYL.js +1 -0
  347. package/public/assets/twig-CO9l9SDP.js +1 -0
  348. package/public/assets/typescript-BPQ3VLAy.js +1 -0
  349. package/public/assets/typespec-Df68jz8_.js +1 -0
  350. package/public/assets/typst-DHCkPAjA.js +1 -0
  351. package/public/assets/v-BcVCzyr7.js +1 -0
  352. package/public/assets/vala-CsfeWuGM.js +1 -0
  353. package/public/assets/vb-D17OF-Vu.js +1 -0
  354. package/public/assets/verilog-BQ8w6xss.js +1 -0
  355. package/public/assets/vesper-DU1UobuO.js +1 -0
  356. package/public/assets/vhdl-CeAyd5Ju.js +1 -0
  357. package/public/assets/viml-CJc9bBzg.js +1 -0
  358. package/public/assets/vitesse-black-Bkuqu6BP.js +1 -0
  359. package/public/assets/vitesse-dark-D0r3Knsf.js +1 -0
  360. package/public/assets/vitesse-light-CVO1_9PV.js +1 -0
  361. package/public/assets/vue-CCoi5OLL.js +1 -0
  362. package/public/assets/vue-html-DAAvJJDi.js +1 -0
  363. package/public/assets/vue-vine-_Ih-lPRR.js +1 -0
  364. package/public/assets/vyper-CDx5xZoG.js +1 -0
  365. package/public/assets/wasm-CG6Dc4jp.js +1 -0
  366. package/public/assets/wasm-MzD3tlZU.js +1 -0
  367. package/public/assets/wenyan-BV7otONQ.js +1 -0
  368. package/public/assets/wgsl-Dx-B1_4e.js +1 -0
  369. package/public/assets/wikitext-BhOHFoWU.js +1 -0
  370. package/public/assets/wit-5i3qLPDT.js +1 -0
  371. package/public/assets/wolfram-lXgVvXCa.js +1 -0
  372. package/public/assets/xml-sdJ4AIDG.js +1 -0
  373. package/public/assets/xsl-CtQFsRM5.js +1 -0
  374. package/public/assets/yaml-Buea-lGh.js +1 -0
  375. package/public/assets/zenscript-DVFEvuxE.js +1 -0
  376. package/public/assets/zig-VOosw3JB.js +1 -0
  377. package/public/favicon.ico +0 -0
  378. package/public/index.html +38 -0
  379. package/public/loading.html +28 -0
  380. package/public/logo.png +0 -0
  381. package/public/manifest.webmanifest +1 -0
  382. package/public/maskable-icon-512x512.png +0 -0
  383. package/public/monaco/vs/base/browser/ui/codicons/codicon/codicon.ttf +0 -0
  384. package/public/monaco/vs/base/worker/workerMain.js +31 -0
  385. package/public/monaco/vs/basic-languages/cpp/cpp.js +10 -0
  386. package/public/monaco/vs/basic-languages/kotlin/kotlin.js +10 -0
  387. package/public/monaco/vs/basic-languages/markdown/markdown.js +10 -0
  388. package/public/monaco/vs/basic-languages/python/python.js +10 -0
  389. package/public/monaco/vs/editor/editor.main.css +8 -0
  390. package/public/monaco/vs/editor/editor.main.js +798 -0
  391. package/public/monaco/vs/language/css/cssMode.js +13 -0
  392. package/public/monaco/vs/language/css/cssWorker.js +77 -0
  393. package/public/monaco/vs/language/html/htmlMode.js +13 -0
  394. package/public/monaco/vs/language/html/htmlWorker.js +454 -0
  395. package/public/monaco/vs/language/json/jsonMode.js +19 -0
  396. package/public/monaco/vs/language/json/jsonWorker.js +42 -0
  397. package/public/monaco/vs/language/typescript/tsMode.js +20 -0
  398. package/public/monaco/vs/language/typescript/tsWorker.js +51328 -0
  399. package/public/monaco/vs/loader.js +11 -0
  400. package/public/monaco.worker.js +7 -0
  401. package/public/pwa-192x192.png +0 -0
  402. package/public/pwa-512x512.png +0 -0
  403. package/public/pwa-64x64.png +0 -0
  404. package/public/registerSW.js +1 -0
  405. package/public/sw.js +1 -0
  406. package/public/ui-version.json +3 -0
  407. package/public/workbox-60d14903.js +1 -0
  408. package/scripts/copy-auth-pages.mjs +22 -0
  409. package/scripts/copy-opencode-config.mjs +61 -0
  410. package/scripts/copy-ui-dist.mjs +21 -0
  411. package/src/api-types.ts +326 -0
  412. package/src/auth/auth-store.ts +175 -0
  413. package/src/auth/http-auth.ts +38 -0
  414. package/src/auth/manager.ts +163 -0
  415. package/src/auth/password-hash.ts +49 -0
  416. package/src/auth/session-manager.ts +23 -0
  417. package/src/auth/token-manager.ts +32 -0
  418. package/src/background-processes/manager.ts +519 -0
  419. package/src/bin.ts +29 -0
  420. package/src/config/binaries.ts +192 -0
  421. package/src/config/location.ts +78 -0
  422. package/src/config/schema.ts +104 -0
  423. package/src/config/store.ts +244 -0
  424. package/src/events/bus.ts +45 -0
  425. package/src/filesystem/__tests__/search-cache.test.ts +61 -0
  426. package/src/filesystem/browser.ts +353 -0
  427. package/src/filesystem/search-cache.ts +66 -0
  428. package/src/filesystem/search.ts +184 -0
  429. package/src/index.ts +540 -0
  430. package/src/launcher.ts +177 -0
  431. package/src/loader.ts +21 -0
  432. package/src/logger.ts +133 -0
  433. package/src/opencode-config.ts +31 -0
  434. package/src/plugins/channel.ts +55 -0
  435. package/src/plugins/handlers.ts +36 -0
  436. package/src/releases/dev-release-monitor.ts +118 -0
  437. package/src/releases/release-monitor.ts +149 -0
  438. package/src/server/http-server.ts +693 -0
  439. package/src/server/network-addresses.ts +75 -0
  440. package/src/server/routes/auth-pages/login.html +134 -0
  441. package/src/server/routes/auth-pages/token.html +93 -0
  442. package/src/server/routes/auth.ts +164 -0
  443. package/src/server/routes/background-processes.ts +85 -0
  444. package/src/server/routes/config.ts +76 -0
  445. package/src/server/routes/events.ts +61 -0
  446. package/src/server/routes/filesystem.ts +54 -0
  447. package/src/server/routes/meta.ts +58 -0
  448. package/src/server/routes/plugin.ts +75 -0
  449. package/src/server/routes/storage.ts +66 -0
  450. package/src/server/routes/workspaces.ts +113 -0
  451. package/src/server/routes/worktrees.ts +195 -0
  452. package/src/server/tls.ts +283 -0
  453. package/src/storage/instance-store.ts +64 -0
  454. package/src/ui/__tests__/remote-ui.test.ts +58 -0
  455. package/src/ui/remote-ui.ts +571 -0
  456. package/src/workspaces/git-worktrees.ts +241 -0
  457. package/src/workspaces/instance-events.ts +226 -0
  458. package/src/workspaces/manager.ts +493 -0
  459. package/src/workspaces/opencode-auth.ts +22 -0
  460. package/src/workspaces/runtime.ts +428 -0
  461. package/src/workspaces/worktree-map.ts +129 -0
  462. package/tsconfig.json +17 -0
@@ -0,0 +1,693 @@
1
+ import Fastify, { type FastifyInstance, type FastifyReply, type FastifyRequest } 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 path from "path"
7
+ import { fetch } from "undici"
8
+ import type { Logger } from "../logger"
9
+ import { WorkspaceManager } from "../workspaces/manager"
10
+ import { isValidWorktreeSlug, listWorktrees, resolveRepoRoot } from "../workspaces/git-worktrees"
11
+
12
+ import { ConfigStore } from "../config/store"
13
+ import { BinaryRegistry } from "../config/binaries"
14
+ import { FileSystemBrowser } from "../filesystem/browser"
15
+ import { EventBus } from "../events/bus"
16
+ import { registerWorkspaceRoutes } from "./routes/workspaces"
17
+ import { registerConfigRoutes } from "./routes/config"
18
+ import { registerFilesystemRoutes } from "./routes/filesystem"
19
+ import { registerMetaRoutes } from "./routes/meta"
20
+ import { registerEventRoutes } from "./routes/events"
21
+ import { registerStorageRoutes } from "./routes/storage"
22
+ import { registerPluginRoutes } from "./routes/plugin"
23
+ import { registerBackgroundProcessRoutes } from "./routes/background-processes"
24
+ import { registerWorktreeRoutes } from "./routes/worktrees"
25
+ import { ServerMeta } from "../api-types"
26
+ import { InstanceStore } from "../storage/instance-store"
27
+ import { BackgroundProcessManager } from "../background-processes/manager"
28
+ import type { AuthManager } from "../auth/manager"
29
+ import { registerAuthRoutes } from "./routes/auth"
30
+ import { sendUnauthorized, wantsHtml } from "../auth/http-auth"
31
+
32
+ interface HttpServerDeps {
33
+ bindHost: string
34
+ bindPort: number
35
+ /** When bindPort is 0, try this first. */
36
+ defaultPort: number
37
+ protocol: "http" | "https"
38
+ httpsOptions?: { key: string | Buffer; cert: string | Buffer; ca?: string | Buffer }
39
+ workspaceManager: WorkspaceManager
40
+ configStore: ConfigStore
41
+ binaryRegistry: BinaryRegistry
42
+ fileSystemBrowser: FileSystemBrowser
43
+ eventBus: EventBus
44
+ serverMeta: ServerMeta
45
+ instanceStore: InstanceStore
46
+ authManager: AuthManager
47
+ uiStaticDir: string
48
+ uiDevServerUrl?: string
49
+ logger: Logger
50
+ }
51
+
52
+ interface HttpServerStartResult {
53
+ port: number
54
+ url: string
55
+ displayHost: string
56
+ }
57
+
58
+ export function createHttpServer(deps: HttpServerDeps) {
59
+ // Fastify's type-level RawServer inference gets noisy when toggling HTTP vs HTTPS.
60
+ // We keep the runtime behavior correct and cast the instance to a generic FastifyInstance.
61
+ const app = Fastify(
62
+ ({
63
+ logger: false,
64
+ ...(deps.protocol === "https" && deps.httpsOptions ? { https: deps.httpsOptions } : {}),
65
+ } as unknown) as any,
66
+ ) as unknown as FastifyInstance
67
+ const proxyLogger = deps.logger.child({ component: "proxy" })
68
+ const apiLogger = deps.logger.child({ component: "http" })
69
+ const sseLogger = deps.logger.child({ component: "sse" })
70
+
71
+ const sseClients = new Set<() => void>()
72
+ const registerSseClient = (cleanup: () => void) => {
73
+ sseClients.add(cleanup)
74
+ return () => sseClients.delete(cleanup)
75
+ }
76
+ const closeSseClients = () => {
77
+ for (const cleanup of Array.from(sseClients)) {
78
+ cleanup()
79
+ }
80
+ sseClients.clear()
81
+ }
82
+
83
+ app.addHook("onRequest", (request, _reply, done) => {
84
+ ;(request as FastifyRequest & { __logMeta?: { start: bigint } }).__logMeta = {
85
+ start: process.hrtime.bigint(),
86
+ }
87
+ done()
88
+ })
89
+
90
+ app.addHook("onResponse", (request, reply, done) => {
91
+ const meta = (request as FastifyRequest & { __logMeta?: { start: bigint } }).__logMeta
92
+ const durationMs = meta ? Number((process.hrtime.bigint() - meta.start) / BigInt(1_000_000)) : undefined
93
+ const base = {
94
+ method: request.method,
95
+ url: request.url,
96
+ status: reply.statusCode,
97
+ durationMs,
98
+ }
99
+ apiLogger.debug(base, "HTTP request completed")
100
+ if (apiLogger.isLevelEnabled("trace")) {
101
+ apiLogger.trace({ ...base, params: request.params, query: request.query, body: request.body }, "HTTP request payload")
102
+ }
103
+ done()
104
+ })
105
+
106
+ const allowedDevOrigins = new Set(["http://localhost:3000", "http://127.0.0.1:3000"])
107
+ const isLoopbackHost = (host: string) => host === "127.0.0.1" || host === "::1" || host.startsWith("127.")
108
+
109
+ const getSelfOrigins = (): Set<string> => {
110
+ const origins = new Set<string>()
111
+ const candidates: Array<string | undefined> = [deps.serverMeta.localUrl, deps.serverMeta.remoteUrl]
112
+ for (const candidate of candidates) {
113
+ if (!candidate) continue
114
+ try {
115
+ origins.add(new URL(candidate).origin)
116
+ } catch {
117
+ // ignore
118
+ }
119
+ }
120
+ for (const addr of deps.serverMeta.addresses ?? []) {
121
+ try {
122
+ origins.add(new URL(addr.remoteUrl).origin)
123
+ } catch {
124
+ // ignore
125
+ }
126
+ }
127
+ return origins
128
+ }
129
+
130
+ app.register(cors, {
131
+ origin: (origin, cb) => {
132
+ if (!origin) {
133
+ cb(null, true)
134
+ return
135
+ }
136
+
137
+ const selfOrigins = getSelfOrigins()
138
+ if (selfOrigins.has(origin)) {
139
+ cb(null, true)
140
+ return
141
+ }
142
+
143
+ if (allowedDevOrigins.has(origin)) {
144
+ cb(null, true)
145
+ return
146
+ }
147
+
148
+ // When we bind to a non-loopback host (e.g., 0.0.0.0 or LAN IP), allow cross-origin UI access.
149
+ if (deps.bindHost === "0.0.0.0" || !isLoopbackHost(deps.bindHost)) {
150
+ cb(null, true)
151
+ return
152
+ }
153
+
154
+
155
+ cb(null, false)
156
+ },
157
+ credentials: true,
158
+ })
159
+
160
+ app.register(replyFrom, {
161
+ contentTypesToEncode: [],
162
+ undici: {
163
+ connections: 16,
164
+ pipelining: 1,
165
+ bodyTimeout: 0,
166
+ headersTimeout: 0,
167
+ },
168
+ })
169
+
170
+ const backgroundProcessManager = new BackgroundProcessManager({
171
+ workspaceManager: deps.workspaceManager,
172
+ eventBus: deps.eventBus,
173
+ logger: deps.logger.child({ component: "background-processes" }),
174
+ })
175
+
176
+ registerAuthRoutes(app, { authManager: deps.authManager })
177
+
178
+ app.addHook("preHandler", (request, reply, done) => {
179
+ const rawUrl = request.raw.url ?? request.url
180
+ const pathname = (rawUrl.split("?")[0] ?? "").trim()
181
+
182
+ const publicApiPaths = new Set(["/api/auth/login", "/api/auth/token", "/api/auth/status", "/api/auth/logout"])
183
+ const publicPagePaths = new Set(["/login"])
184
+ if (deps.authManager.isTokenBootstrapEnabled()) {
185
+ publicPagePaths.add("/auth/token")
186
+ }
187
+
188
+ if (publicApiPaths.has(pathname) || publicPagePaths.has(pathname)) {
189
+ done()
190
+ return
191
+ }
192
+
193
+ const session = deps.authManager.getSessionFromRequest(request)
194
+
195
+ const requiresAuthForApi = pathname.startsWith("/api/") || pathname.startsWith("/workspaces/")
196
+ if (requiresAuthForApi && !session) {
197
+ // Allow OpenCode plugin -> CodeNomad calls with per-instance basic auth.
198
+ const pluginMatch = pathname.match(/^\/workspaces\/([^/]+)\/plugin(?:\/|$)/)
199
+ if (pluginMatch) {
200
+ const workspaceId = pluginMatch[1]
201
+ const expected = deps.workspaceManager.getInstanceAuthorizationHeader(workspaceId)
202
+ const provided = Array.isArray(request.headers.authorization)
203
+ ? request.headers.authorization[0]
204
+ : request.headers.authorization
205
+
206
+ if (expected && provided && provided === expected) {
207
+ done()
208
+ return
209
+ }
210
+ }
211
+
212
+ sendUnauthorized(request, reply)
213
+ return
214
+ }
215
+
216
+ if (!session && wantsHtml(request)) {
217
+ reply.redirect("/login")
218
+ return
219
+ }
220
+
221
+ done()
222
+ })
223
+
224
+ app.get("/", async (request, reply) => {
225
+ const session = deps.authManager.getSessionFromRequest(request)
226
+ if (!session) {
227
+ reply.redirect("/login")
228
+ return
229
+ }
230
+
231
+ if (deps.uiDevServerUrl) {
232
+ await proxyToDevServer(request, reply, deps.uiDevServerUrl)
233
+ return
234
+ }
235
+
236
+ const uiDir = deps.uiStaticDir
237
+ const indexPath = path.join(uiDir, "index.html")
238
+ if (uiDir && fs.existsSync(indexPath)) {
239
+ reply.type("text/html").send(fs.readFileSync(indexPath, "utf-8"))
240
+ return
241
+ }
242
+
243
+ reply.code(404).send({ message: "UI bundle missing" })
244
+ })
245
+
246
+ registerWorkspaceRoutes(app, { workspaceManager: deps.workspaceManager })
247
+ registerConfigRoutes(app, { configStore: deps.configStore, binaryRegistry: deps.binaryRegistry })
248
+ registerFilesystemRoutes(app, { fileSystemBrowser: deps.fileSystemBrowser })
249
+ registerMetaRoutes(app, { serverMeta: deps.serverMeta })
250
+ registerEventRoutes(app, { eventBus: deps.eventBus, registerClient: registerSseClient, logger: sseLogger })
251
+ registerWorktreeRoutes(app, { workspaceManager: deps.workspaceManager })
252
+ registerStorageRoutes(app, {
253
+ instanceStore: deps.instanceStore,
254
+ eventBus: deps.eventBus,
255
+ workspaceManager: deps.workspaceManager,
256
+ })
257
+ registerPluginRoutes(app, { workspaceManager: deps.workspaceManager, eventBus: deps.eventBus, logger: proxyLogger })
258
+ registerBackgroundProcessRoutes(app, { backgroundProcessManager })
259
+ registerInstanceProxyRoutes(app, { workspaceManager: deps.workspaceManager, logger: proxyLogger })
260
+
261
+
262
+ if (deps.uiDevServerUrl) {
263
+ setupDevProxy(app, deps.uiDevServerUrl, deps.authManager)
264
+ } else {
265
+ setupStaticUi(app, deps.uiStaticDir, deps.authManager)
266
+ }
267
+
268
+ return {
269
+ instance: app,
270
+ start: async (): Promise<HttpServerStartResult> => {
271
+ const attemptListen = async (requestedPort: number) => {
272
+ const addressInfo = await app.listen({ port: requestedPort, host: deps.bindHost })
273
+ return { addressInfo, requestedPort }
274
+ }
275
+
276
+ const autoPortRequested = deps.bindPort === 0
277
+ const primaryPort = autoPortRequested ? deps.defaultPort : deps.bindPort
278
+
279
+ const shouldRetryWithEphemeral = (error: unknown) => {
280
+ if (!autoPortRequested) return false
281
+ const err = error as NodeJS.ErrnoException | undefined
282
+ return Boolean(err && err.code === "EADDRINUSE")
283
+ }
284
+
285
+ let listenResult
286
+
287
+ try {
288
+ listenResult = await attemptListen(primaryPort)
289
+ } catch (error) {
290
+ if (!shouldRetryWithEphemeral(error)) {
291
+ throw error
292
+ }
293
+ deps.logger.warn({ err: error, port: primaryPort }, "Preferred port unavailable, retrying on ephemeral port")
294
+ listenResult = await attemptListen(0)
295
+ }
296
+
297
+ let actualPort = listenResult.requestedPort
298
+
299
+ if (typeof listenResult.addressInfo === "string") {
300
+ try {
301
+ const parsed = new URL(listenResult.addressInfo)
302
+ actualPort = Number(parsed.port) || listenResult.requestedPort
303
+ } catch {
304
+ actualPort = listenResult.requestedPort
305
+ }
306
+ } else {
307
+ const address = app.server.address()
308
+ if (typeof address === "object" && address) {
309
+ actualPort = address.port
310
+ }
311
+ }
312
+
313
+ const displayHost = deps.bindHost === "127.0.0.1" ? "localhost" : deps.bindHost
314
+ const serverUrl = `${deps.protocol}://${displayHost}:${actualPort}`
315
+
316
+ deps.logger.info({ port: actualPort, host: deps.bindHost, protocol: deps.protocol }, "HTTP server listening")
317
+
318
+ return { port: actualPort, url: serverUrl, displayHost }
319
+ },
320
+ stop: () => {
321
+ closeSseClients()
322
+ return app.close()
323
+ },
324
+ }
325
+ }
326
+
327
+ interface InstanceProxyDeps {
328
+ workspaceManager: WorkspaceManager
329
+ logger: Logger
330
+ }
331
+
332
+ function registerInstanceProxyRoutes(app: FastifyInstance, deps: InstanceProxyDeps) {
333
+ app.register(async (instance) => {
334
+ instance.removeAllContentTypeParsers()
335
+ instance.addContentTypeParser("*", (req, body, done) => done(null, body))
336
+
337
+ const proxyBaseHandler = async (
338
+ request: FastifyRequest<{ Params: { id: string; slug: string } }>,
339
+ reply: FastifyReply,
340
+ ) => {
341
+ await proxyWorkspaceRequest({
342
+ request,
343
+ reply,
344
+ workspaceManager: deps.workspaceManager,
345
+ worktreeSlug: request.params.slug,
346
+ pathSuffix: "",
347
+ logger: deps.logger,
348
+ })
349
+ }
350
+
351
+ const proxyWildcardHandler = async (
352
+ request: FastifyRequest<{ Params: { id: string; slug: string; "*": string } }>,
353
+ reply: FastifyReply,
354
+ ) => {
355
+ await proxyWorkspaceRequest({
356
+ request,
357
+ reply,
358
+ workspaceManager: deps.workspaceManager,
359
+ worktreeSlug: request.params.slug,
360
+ pathSuffix: request.params["*"] ?? "",
361
+ logger: deps.logger,
362
+ })
363
+ }
364
+
365
+ instance.all("/workspaces/:id/worktrees/:slug/instance", proxyBaseHandler)
366
+ instance.all("/workspaces/:id/worktrees/:slug/instance/*", proxyWildcardHandler)
367
+ })
368
+ }
369
+
370
+ const INSTANCE_PROXY_HOST = "127.0.0.1"
371
+
372
+ async function proxyWorkspaceRequest(args: {
373
+ request: FastifyRequest
374
+ reply: FastifyReply
375
+ workspaceManager: WorkspaceManager
376
+ logger: Logger
377
+ worktreeSlug: string
378
+ pathSuffix?: string
379
+ }) {
380
+ const { request, reply, workspaceManager, logger, worktreeSlug } = args
381
+ const workspaceId = (request.params as { id: string }).id
382
+ const workspace = workspaceManager.get(workspaceId)
383
+
384
+ const bodyToJson = (body: unknown): unknown => {
385
+ if (body == null) return null
386
+
387
+ const anyBody = body as any
388
+ if (anyBody && typeof anyBody.pipe === "function") {
389
+ // Don't consume streams (would break proxying).
390
+ // Best-effort: if the stream already has buffered chunks, parse those.
391
+ try {
392
+ const buffered = anyBody?._readableState?.buffer
393
+ if (Array.isArray(buffered) && buffered.length > 0) {
394
+ const chunks: Buffer[] = []
395
+ for (const entry of buffered) {
396
+ if (!entry) continue
397
+ if (Buffer.isBuffer(entry)) {
398
+ chunks.push(entry)
399
+ continue
400
+ }
401
+ const data = (entry as any).data
402
+ if (Buffer.isBuffer(data)) {
403
+ chunks.push(data)
404
+ }
405
+ }
406
+
407
+ if (chunks.length > 0) {
408
+ const text = Buffer.concat(chunks).toString("utf-8")
409
+ try {
410
+ return JSON.parse(text)
411
+ } catch {
412
+ return { __raw: text }
413
+ }
414
+ }
415
+ }
416
+ } catch {
417
+ // fall through
418
+ }
419
+
420
+ return { __stream: true }
421
+ }
422
+
423
+ const maybeParse = (input: string): unknown => {
424
+ try {
425
+ return JSON.parse(input)
426
+ } catch {
427
+ return { __raw: input }
428
+ }
429
+ }
430
+
431
+ if (Buffer.isBuffer(body)) {
432
+ return maybeParse(body.toString("utf-8"))
433
+ }
434
+
435
+ if (typeof body === "string") {
436
+ return maybeParse(body)
437
+ }
438
+
439
+ if (typeof body === "object") {
440
+ return body
441
+ }
442
+
443
+ return body
444
+ }
445
+
446
+ if (!workspace) {
447
+ reply.code(404).send({ error: "Workspace not found" })
448
+ return
449
+ }
450
+
451
+ const port = workspaceManager.getInstancePort(workspaceId)
452
+ if (!port) {
453
+ reply.code(502).send({ error: "Workspace instance is not ready" })
454
+ return
455
+ }
456
+
457
+ if (!isValidWorktreeSlug(worktreeSlug)) {
458
+ reply.code(400).send({ error: "Invalid worktree slug" })
459
+ return
460
+ }
461
+
462
+ const directory = await resolveWorktreeDirectory({
463
+ workspaceId,
464
+ workspacePath: workspace.path,
465
+ worktreeSlug,
466
+ logger,
467
+ })
468
+
469
+ if (!directory) {
470
+ reply.code(404).send({ error: "Worktree not found" })
471
+ return
472
+ }
473
+
474
+ const normalizedSuffix = normalizeInstanceSuffix(args.pathSuffix)
475
+ const queryIndex = (request.raw.url ?? "").indexOf("?")
476
+ const search = queryIndex >= 0 ? (request.raw.url ?? "").slice(queryIndex) : ""
477
+ const targetUrl = `http://${INSTANCE_PROXY_HOST}:${port}${normalizedSuffix}${search}`
478
+ const instanceAuthHeader = workspaceManager.getInstanceAuthorizationHeader(workspaceId)
479
+
480
+ logger.debug({ workspaceId, method: request.method, targetUrl }, "Proxying request to instance")
481
+ if (logger.isLevelEnabled("trace")) {
482
+ logger.trace({ workspaceId, targetUrl, body: request.body }, "Instance proxy payload")
483
+ }
484
+
485
+ return reply.from(targetUrl, {
486
+ rewriteRequestHeaders: (_originalRequest, headers) => {
487
+ if (instanceAuthHeader) {
488
+ headers.authorization = instanceAuthHeader
489
+ }
490
+
491
+ // OpenCode expects the *full* path; we send it via header to avoid query tampering.
492
+ const isNonASCII = /[^\x00-\x7F]/.test(directory)
493
+ const encodedDirectory = isNonASCII ? encodeURIComponent(directory) : directory
494
+
495
+ // Overwrite any client-provided value (case-insensitive headers are normalized by Node).
496
+ ;(headers as Record<string, unknown>)["x-opencode-directory"] = encodedDirectory
497
+
498
+ if (logger.isLevelEnabled("trace")) {
499
+ const outgoing: Record<string, unknown> = {}
500
+ for (const [key, value] of Object.entries(headers as Record<string, unknown>)) {
501
+ outgoing[key] = value
502
+ }
503
+
504
+ // Redact sensitive headers.
505
+ for (const key of Object.keys(outgoing)) {
506
+ const lower = key.toLowerCase()
507
+ if (lower === "authorization" || lower === "cookie" || lower === "set-cookie") {
508
+ outgoing[key] = "<redacted>"
509
+ }
510
+ }
511
+
512
+ logger.trace(
513
+ {
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
+ },
523
+ "Proxy -> OpenCode request",
524
+ )
525
+ }
526
+
527
+ return headers
528
+ },
529
+ onError: (proxyReply, { error }) => {
530
+ logger.error({ err: error, workspaceId, targetUrl }, "Failed to proxy workspace request")
531
+ if (!proxyReply.sent) {
532
+ proxyReply.code(502).send({ error: "Workspace instance proxy failed" })
533
+ }
534
+ },
535
+ })
536
+ }
537
+
538
+ function normalizeInstanceSuffix(pathSuffix: string | undefined) {
539
+ if (!pathSuffix || pathSuffix === "/") {
540
+ return "/"
541
+ }
542
+ const trimmed = pathSuffix.replace(/^\/+/, "")
543
+ return trimmed.length === 0 ? "/" : `/${trimmed}`
544
+ }
545
+
546
+ type WorktreeCacheEntry = {
547
+ expiresAt: number
548
+ repoRoot: string
549
+ worktrees: Array<{ slug: string; directory: string }>
550
+ }
551
+
552
+ const WORKTREE_CACHE_TTL_MS = 2000
553
+ const worktreeCache = new Map<string, WorktreeCacheEntry>()
554
+
555
+ async function getCachedWorktrees(params: { workspaceId: string; workspacePath: string; logger: Logger }) {
556
+ const cached = worktreeCache.get(params.workspaceId)
557
+ const now = Date.now()
558
+ if (cached && cached.expiresAt > now) {
559
+ return cached
560
+ }
561
+
562
+ const { repoRoot } = await resolveRepoRoot(params.workspacePath, params.logger)
563
+ const worktrees = await listWorktrees({ repoRoot, workspaceFolder: params.workspacePath, logger: params.logger })
564
+ const entry: WorktreeCacheEntry = {
565
+ expiresAt: now + WORKTREE_CACHE_TTL_MS,
566
+ repoRoot,
567
+ worktrees: worktrees.map((wt) => ({ slug: wt.slug, directory: wt.directory })),
568
+ }
569
+ worktreeCache.set(params.workspaceId, entry)
570
+ return entry
571
+ }
572
+
573
+ async function resolveWorktreeDirectory(params: {
574
+ workspaceId: string
575
+ workspacePath: string
576
+ worktreeSlug: string
577
+ logger: Logger
578
+ }): Promise<string | null> {
579
+ const { worktreeSlug } = params
580
+ const cached = await getCachedWorktrees({ workspaceId: params.workspaceId, workspacePath: params.workspacePath, logger: params.logger })
581
+ const match = cached.worktrees.find((wt) => wt.slug === worktreeSlug)
582
+ if (match) {
583
+ return match.directory
584
+ }
585
+
586
+ // If the slug is new (e.g., created moments ago), refresh once.
587
+ worktreeCache.delete(params.workspaceId)
588
+ const refreshed = await getCachedWorktrees({ workspaceId: params.workspaceId, workspacePath: params.workspacePath, logger: params.logger })
589
+ return refreshed.worktrees.find((wt) => wt.slug === worktreeSlug)?.directory ?? null
590
+ }
591
+
592
+ function setupStaticUi(app: FastifyInstance, uiDir: string, authManager: AuthManager) {
593
+ if (!uiDir) {
594
+ app.log.warn("UI static directory not provided; API endpoints only")
595
+ return
596
+ }
597
+
598
+ if (!fs.existsSync(uiDir)) {
599
+ app.log.warn({ uiDir }, "UI static directory missing; API endpoints only")
600
+ return
601
+ }
602
+
603
+ app.register(fastifyStatic, {
604
+ root: uiDir,
605
+ prefix: "/",
606
+ decorateReply: false,
607
+ })
608
+
609
+ const indexPath = path.join(uiDir, "index.html")
610
+
611
+ app.setNotFoundHandler((request: FastifyRequest, reply: FastifyReply) => {
612
+ const url = request.raw.url ?? ""
613
+ if (isApiRequest(url)) {
614
+ reply.code(404).send({ message: "Not Found" })
615
+ return
616
+ }
617
+
618
+ const session = authManager.getSessionFromRequest(request)
619
+ if (!session && wantsHtml(request)) {
620
+ reply.redirect("/login")
621
+ return
622
+ }
623
+
624
+ if (fs.existsSync(indexPath)) {
625
+ reply.type("text/html").send(fs.readFileSync(indexPath, "utf-8"))
626
+ } else {
627
+ reply.code(404).send({ message: "UI bundle missing" })
628
+ }
629
+ })
630
+ }
631
+
632
+ function setupDevProxy(app: FastifyInstance, upstreamBase: string, authManager: AuthManager) {
633
+ app.log.info({ upstreamBase }, "Proxying UI requests to development server")
634
+ app.setNotFoundHandler((request: FastifyRequest, reply: FastifyReply) => {
635
+ const url = request.raw.url ?? ""
636
+ if (isApiRequest(url)) {
637
+ reply.code(404).send({ message: "Not Found" })
638
+ return
639
+ }
640
+
641
+ const session = authManager.getSessionFromRequest(request)
642
+ if (!session && wantsHtml(request)) {
643
+ reply.redirect("/login")
644
+ return
645
+ }
646
+
647
+ void proxyToDevServer(request, reply, upstreamBase)
648
+ })
649
+ }
650
+
651
+ async function proxyToDevServer(request: FastifyRequest, reply: FastifyReply, upstreamBase: string) {
652
+ try {
653
+ const targetUrl = new URL(request.raw.url ?? "/", upstreamBase)
654
+ const response = await fetch(targetUrl, {
655
+ method: request.method,
656
+ headers: buildProxyHeaders(request.headers),
657
+ })
658
+
659
+ response.headers.forEach((value, key) => {
660
+ reply.header(key, value)
661
+ })
662
+
663
+ reply.code(response.status)
664
+
665
+ if (!response.body || request.method === "HEAD") {
666
+ reply.send()
667
+ return
668
+ }
669
+
670
+ const buffer = Buffer.from(await response.arrayBuffer())
671
+ reply.send(buffer)
672
+ } catch (error) {
673
+ request.log.error({ err: error }, "Failed to proxy UI request to dev server")
674
+ if (!reply.sent) {
675
+ reply.code(502).send("UI dev server is unavailable")
676
+ }
677
+ }
678
+ }
679
+
680
+ function isApiRequest(rawUrl: string | null | undefined) {
681
+ if (!rawUrl) return false
682
+ const pathname = rawUrl.split("?")[0] ?? ""
683
+ return pathname === "/api" || pathname.startsWith("/api/")
684
+ }
685
+
686
+ function buildProxyHeaders(headers: FastifyRequest["headers"]): Record<string, string> {
687
+ const result: Record<string, string> = {}
688
+ for (const [key, value] of Object.entries(headers ?? {})) {
689
+ if (!value || key.toLowerCase() === "host") continue
690
+ result[key] = Array.isArray(value) ? value.join(",") : value
691
+ }
692
+ return result
693
+ }