@thevinci/web 1.0.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 (337) hide show
  1. package/README.md +197 -0
  2. package/bin/cli-entry.js +55 -0
  3. package/bin/cli-output.js +145 -0
  4. package/bin/cli.js +4887 -0
  5. package/bin/cli.test.js +64 -0
  6. package/dist/apple-touch-icon-120x120.png +0 -0
  7. package/dist/apple-touch-icon-152x152.png +0 -0
  8. package/dist/apple-touch-icon-167x167.png +0 -0
  9. package/dist/apple-touch-icon-180x180.png +0 -0
  10. package/dist/apple-touch-icon.png +0 -0
  11. package/dist/apple-touch-icon.svg +528 -0
  12. package/dist/assets/JsonTreeView-CSm9OzXG.js +1 -0
  13. package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  14. package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  15. package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  16. package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  17. package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  18. package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  19. package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  20. package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  21. package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  22. package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  23. package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  24. package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  25. package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  26. package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  27. package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  28. package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  29. package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  30. package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  31. package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  32. package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  33. package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  34. package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  35. package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  36. package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  37. package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  38. package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  39. package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  40. package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  41. package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  42. package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  43. package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  44. package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  45. package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  46. package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  47. package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  48. package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  49. package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  50. package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  51. package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  52. package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  53. package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  54. package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  55. package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  56. package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  57. package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  58. package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  59. package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  60. package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  61. package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  62. package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  63. package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  64. package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  65. package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  66. package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  67. package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  68. package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  69. package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  70. package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  71. package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  72. package/dist/assets/MarkdownRendererImpl-DensKOLc.js +6 -0
  73. package/dist/assets/MultiRunWindow-Bo7THayo.js +1 -0
  74. package/dist/assets/OnboardingScreen-BDqmzTVR.js +2 -0
  75. package/dist/assets/SettingsWindow-coz__Ykw.js +1 -0
  76. package/dist/assets/TerminalView-DrZ-i3Dr.js +1 -0
  77. package/dist/assets/ToolOutputDialog-Eglzslt3.js +16 -0
  78. package/dist/assets/es-4o9ciP61.js +15 -0
  79. package/dist/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  80. package/dist/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  81. package/dist/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  82. package/dist/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  83. package/dist/assets/ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2 +0 -0
  84. package/dist/assets/ibm-plex-mono-latin-600-normal-DWFSQ4vo.woff +0 -0
  85. package/dist/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
  86. package/dist/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
  87. package/dist/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
  88. package/dist/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
  89. package/dist/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
  90. package/dist/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
  91. package/dist/assets/index-DLTDToSP.css +1 -0
  92. package/dist/assets/index-DgiFEKGN.js +1 -0
  93. package/dist/assets/ko-B20imCHE.js +15 -0
  94. package/dist/assets/main-BV3KOtdA.css +1 -0
  95. package/dist/assets/main-CDKJj0sH.js +226 -0
  96. package/dist/assets/main-LC-PSNVM.js +2 -0
  97. package/dist/assets/miniChat-CQUiG_cr.js +2 -0
  98. package/dist/assets/modelPrefsAutoSave-Dm799vzR.js +6986 -0
  99. package/dist/assets/pl-DQJ7LSzj.js +15 -0
  100. package/dist/assets/pt-BR-OmjHUz9y.js +15 -0
  101. package/dist/assets/renderElectronMiniChatApp-CARbeW0G.js +2 -0
  102. package/dist/assets/uk-BNFxOlO4.js +15 -0
  103. package/dist/assets/vendor--DBfsbEis.css +1 -0
  104. package/dist/assets/vendor-.bun-B9l0ZNi2.js +4094 -0
  105. package/dist/assets/wasm-CG6Dc4jp.js +1 -0
  106. package/dist/assets/wasmSttWorker-Dtlxac_K.js +1 -0
  107. package/dist/assets/wasmSttWorker-oo7Dm_jy.js +1806 -0
  108. package/dist/assets/worker-CbT6TVo7.js +155 -0
  109. package/dist/assets/zh-CN-C6T-Ac7F.js +15 -0
  110. package/dist/favicon-16.png +0 -0
  111. package/dist/favicon-32.png +0 -0
  112. package/dist/favicon.png +0 -0
  113. package/dist/favicon.svg +528 -0
  114. package/dist/index.html +607 -0
  115. package/dist/logo-dark-192x192.png +0 -0
  116. package/dist/logo-dark-512x512.png +0 -0
  117. package/dist/logo-dark-512x512.svg +528 -0
  118. package/dist/logo-light-192x192.png +0 -0
  119. package/dist/logo-light-512x512.png +0 -0
  120. package/dist/logo-light-512x512.svg +528 -0
  121. package/dist/mini-chat.html +16 -0
  122. package/dist/pwa-192.png +0 -0
  123. package/dist/pwa-512.png +0 -0
  124. package/dist/pwa-maskable-192.png +0 -0
  125. package/dist/pwa-maskable-512.png +0 -0
  126. package/dist/site.webmanifest +21 -0
  127. package/dist/sw.js +1 -0
  128. package/package.json +118 -0
  129. package/public/apple-touch-icon-120x120.png +0 -0
  130. package/public/apple-touch-icon-152x152.png +0 -0
  131. package/public/apple-touch-icon-167x167.png +0 -0
  132. package/public/apple-touch-icon-180x180.png +0 -0
  133. package/public/apple-touch-icon.png +0 -0
  134. package/public/apple-touch-icon.svg +528 -0
  135. package/public/favicon-16.png +0 -0
  136. package/public/favicon-32.png +0 -0
  137. package/public/favicon.png +0 -0
  138. package/public/favicon.svg +528 -0
  139. package/public/logo-dark-192x192.png +0 -0
  140. package/public/logo-dark-512x512.png +0 -0
  141. package/public/logo-dark-512x512.svg +528 -0
  142. package/public/logo-light-192x192.png +0 -0
  143. package/public/logo-light-512x512.png +0 -0
  144. package/public/logo-light-512x512.svg +528 -0
  145. package/public/pwa-192.png +0 -0
  146. package/public/pwa-512.png +0 -0
  147. package/public/pwa-maskable-192.png +0 -0
  148. package/public/pwa-maskable-512.png +0 -0
  149. package/public/site.webmanifest +21 -0
  150. package/server/TERMINAL_WS_PROTOCOL.md +48 -0
  151. package/server/index.d.ts +39 -0
  152. package/server/index.js +1311 -0
  153. package/server/lib/cloudflare-tunnel.js +650 -0
  154. package/server/lib/event-stream/DOCUMENTATION.md +61 -0
  155. package/server/lib/event-stream/directory-ws-bridge.js +185 -0
  156. package/server/lib/event-stream/global-hub.js +158 -0
  157. package/server/lib/event-stream/global-hub.test.js +140 -0
  158. package/server/lib/event-stream/global-ws-bridge.js +206 -0
  159. package/server/lib/event-stream/index.js +25 -0
  160. package/server/lib/event-stream/protocol.js +131 -0
  161. package/server/lib/event-stream/protocol.test.js +182 -0
  162. package/server/lib/event-stream/runtime.js +180 -0
  163. package/server/lib/event-stream/runtime.test.js +512 -0
  164. package/server/lib/event-stream/upstream-reader.js +226 -0
  165. package/server/lib/event-stream/upstream-reader.test.js +276 -0
  166. package/server/lib/fs/DOCUMENTATION.md +36 -0
  167. package/server/lib/fs/routes.js +1040 -0
  168. package/server/lib/fs/search.js +238 -0
  169. package/server/lib/git/DOCUMENTATION.md +152 -0
  170. package/server/lib/git/credentials.js +74 -0
  171. package/server/lib/git/identity-storage.js +112 -0
  172. package/server/lib/git/index.js +6 -0
  173. package/server/lib/git/routes.js +972 -0
  174. package/server/lib/git/service.js +3432 -0
  175. package/server/lib/git/service.test.js +39 -0
  176. package/server/lib/github/DOCUMENTATION.md +171 -0
  177. package/server/lib/github/auth.js +307 -0
  178. package/server/lib/github/device-flow.js +50 -0
  179. package/server/lib/github/index.js +24 -0
  180. package/server/lib/github/octokit.js +10 -0
  181. package/server/lib/github/pr-status.js +519 -0
  182. package/server/lib/github/repo/fork-detection.js +102 -0
  183. package/server/lib/github/repo/index.js +55 -0
  184. package/server/lib/github/routes.js +1560 -0
  185. package/server/lib/magic-prompts/routes.js +63 -0
  186. package/server/lib/magic-prompts/runtime.js +119 -0
  187. package/server/lib/notifications/DOCUMENTATION.md +122 -0
  188. package/server/lib/notifications/emitter-runtime.js +102 -0
  189. package/server/lib/notifications/index.js +4 -0
  190. package/server/lib/notifications/message.js +52 -0
  191. package/server/lib/notifications/message.test.js +34 -0
  192. package/server/lib/notifications/push-runtime.js +304 -0
  193. package/server/lib/notifications/routes.js +315 -0
  194. package/server/lib/notifications/runtime.js +566 -0
  195. package/server/lib/notifications/template-runtime.js +349 -0
  196. package/server/lib/notifications/template-runtime.test.js +26 -0
  197. package/server/lib/opencode/DOCUMENTATION.md +362 -0
  198. package/server/lib/opencode/agents.js +634 -0
  199. package/server/lib/opencode/auth-state-runtime.js +88 -0
  200. package/server/lib/opencode/auth.js +83 -0
  201. package/server/lib/opencode/bootstrap-runtime.js +131 -0
  202. package/server/lib/opencode/cli-entry-runtime.js +43 -0
  203. package/server/lib/opencode/cli-options.js +128 -0
  204. package/server/lib/opencode/commands.js +339 -0
  205. package/server/lib/opencode/config-entity-routes.js +370 -0
  206. package/server/lib/opencode/core-routes.js +500 -0
  207. package/server/lib/opencode/core-routes.test.js +26 -0
  208. package/server/lib/opencode/env-config.js +74 -0
  209. package/server/lib/opencode/env-keys.js +68 -0
  210. package/server/lib/opencode/env-runtime.js +1162 -0
  211. package/server/lib/opencode/env-runtime.test.js +116 -0
  212. package/server/lib/opencode/feature-routes-runtime.js +244 -0
  213. package/server/lib/opencode/hmr-state-runtime.js +85 -0
  214. package/server/lib/opencode/index.js +66 -0
  215. package/server/lib/opencode/lifecycle.js +1019 -0
  216. package/server/lib/opencode/lifecycle.test.js +240 -0
  217. package/server/lib/opencode/mcp.js +278 -0
  218. package/server/lib/opencode/network-runtime.js +104 -0
  219. package/server/lib/opencode/network-runtime.test.js +37 -0
  220. package/server/lib/opencode/opencode-resolution-runtime.js +71 -0
  221. package/server/lib/opencode/path-utils.js +100 -0
  222. package/server/lib/opencode/path-utils.test.js +71 -0
  223. package/server/lib/opencode/project-directory-runtime.js +124 -0
  224. package/server/lib/opencode/project-icon-routes.js +399 -0
  225. package/server/lib/opencode/project-icon-routes.test.js +107 -0
  226. package/server/lib/opencode/providers.js +96 -0
  227. package/server/lib/opencode/proxy.js +445 -0
  228. package/server/lib/opencode/pwa-manifest-routes.js +257 -0
  229. package/server/lib/opencode/pwa-manifest-routes.test.js +133 -0
  230. package/server/lib/opencode/routes.js +541 -0
  231. package/server/lib/opencode/server-startup-runtime.js +156 -0
  232. package/server/lib/opencode/server-utils-runtime.js +168 -0
  233. package/server/lib/opencode/server-utils-runtime.test.js +135 -0
  234. package/server/lib/opencode/session-runtime.js +356 -0
  235. package/server/lib/opencode/session-runtime.test.js +151 -0
  236. package/server/lib/opencode/settings-helpers.js +770 -0
  237. package/server/lib/opencode/settings-helpers.test.js +109 -0
  238. package/server/lib/opencode/settings-normalization-runtime.js +428 -0
  239. package/server/lib/opencode/settings-runtime.js +826 -0
  240. package/server/lib/opencode/settings-runtime.test.js +85 -0
  241. package/server/lib/opencode/shared.js +615 -0
  242. package/server/lib/opencode/shutdown-runtime.js +139 -0
  243. package/server/lib/opencode/shutdown-runtime.test.js +58 -0
  244. package/server/lib/opencode/skill-routes.js +701 -0
  245. package/server/lib/opencode/skills.js +548 -0
  246. package/server/lib/opencode/startup-pipeline-runtime.js +130 -0
  247. package/server/lib/opencode/static-routes-runtime.js +65 -0
  248. package/server/lib/opencode/theme-runtime.js +167 -0
  249. package/server/lib/opencode/tunnel-auth.js +591 -0
  250. package/server/lib/opencode/tunnel-wiring-runtime.js +94 -0
  251. package/server/lib/opencode/vinci-routes.js +76 -0
  252. package/server/lib/opencode/watcher.js +115 -0
  253. package/server/lib/opencode/watcher.test.js +239 -0
  254. package/server/lib/preview/proxy-runtime.js +1333 -0
  255. package/server/lib/preview/proxy-runtime.test.js +144 -0
  256. package/server/lib/projects/project-config.js +567 -0
  257. package/server/lib/projects/project-config.test.js +175 -0
  258. package/server/lib/projects/project-id.js +13 -0
  259. package/server/lib/quota/DOCUMENTATION.md +58 -0
  260. package/server/lib/quota/index.js +25 -0
  261. package/server/lib/quota/providers/claude.js +107 -0
  262. package/server/lib/quota/providers/codex.js +113 -0
  263. package/server/lib/quota/providers/copilot.js +165 -0
  264. package/server/lib/quota/providers/google/api.js +92 -0
  265. package/server/lib/quota/providers/google/auth.js +108 -0
  266. package/server/lib/quota/providers/google/index.js +124 -0
  267. package/server/lib/quota/providers/google/transforms.js +109 -0
  268. package/server/lib/quota/providers/index.js +168 -0
  269. package/server/lib/quota/providers/interface.js +55 -0
  270. package/server/lib/quota/providers/kimi.js +108 -0
  271. package/server/lib/quota/providers/minimax-cn-coding-plan.js +140 -0
  272. package/server/lib/quota/providers/minimax-coding-plan.js +139 -0
  273. package/server/lib/quota/providers/nanogpt.js +124 -0
  274. package/server/lib/quota/providers/ollama-cloud.js +112 -0
  275. package/server/lib/quota/providers/openai.js +91 -0
  276. package/server/lib/quota/providers/openrouter.js +92 -0
  277. package/server/lib/quota/providers/zai.js +91 -0
  278. package/server/lib/quota/providers/zhipuai-coding-plan.js +133 -0
  279. package/server/lib/quota/providers/zhipuai.js +114 -0
  280. package/server/lib/quota/routes.js +27 -0
  281. package/server/lib/quota/utils/auth.js +50 -0
  282. package/server/lib/quota/utils/formatters.js +85 -0
  283. package/server/lib/quota/utils/formatters.test.js +54 -0
  284. package/server/lib/quota/utils/index.js +10 -0
  285. package/server/lib/quota/utils/transformers.js +55 -0
  286. package/server/lib/scheduled-tasks/DOCUMENTATION.md +44 -0
  287. package/server/lib/scheduled-tasks/routes.js +235 -0
  288. package/server/lib/scheduled-tasks/runtime.js +773 -0
  289. package/server/lib/scheduled-tasks/runtime.test.js +100 -0
  290. package/server/lib/security/request-security.js +115 -0
  291. package/server/lib/session-folders/routes.js +63 -0
  292. package/server/lib/session-folders/routes.test.js +102 -0
  293. package/server/lib/skills-catalog/DOCUMENTATION.md +178 -0
  294. package/server/lib/skills-catalog/cache.js +29 -0
  295. package/server/lib/skills-catalog/clawdhub/api.js +158 -0
  296. package/server/lib/skills-catalog/clawdhub/index.js +30 -0
  297. package/server/lib/skills-catalog/clawdhub/install.js +238 -0
  298. package/server/lib/skills-catalog/clawdhub/scan.js +113 -0
  299. package/server/lib/skills-catalog/curated-sources.js +21 -0
  300. package/server/lib/skills-catalog/git.js +77 -0
  301. package/server/lib/skills-catalog/index.js +42 -0
  302. package/server/lib/skills-catalog/install.js +294 -0
  303. package/server/lib/skills-catalog/scan.js +221 -0
  304. package/server/lib/skills-catalog/source.js +87 -0
  305. package/server/lib/terminal/DOCUMENTATION.md +76 -0
  306. package/server/lib/terminal/index.js +31 -0
  307. package/server/lib/terminal/output-replay-buffer.js +78 -0
  308. package/server/lib/terminal/output-replay-buffer.test.js +75 -0
  309. package/server/lib/terminal/runtime.js +850 -0
  310. package/server/lib/terminal/runtime.test.js +96 -0
  311. package/server/lib/terminal/terminal-ws-protocol.js +68 -0
  312. package/server/lib/terminal/terminal-ws-protocol.test.js +145 -0
  313. package/server/lib/text/DOCUMENTATION.md +35 -0
  314. package/server/lib/text/summarization.js +138 -0
  315. package/server/lib/text/summarization.test.js +34 -0
  316. package/server/lib/tts/DOCUMENTATION.md +146 -0
  317. package/server/lib/tts/base-url.js +62 -0
  318. package/server/lib/tts/capability-runtime.js +31 -0
  319. package/server/lib/tts/index.js +19 -0
  320. package/server/lib/tts/routes.js +261 -0
  321. package/server/lib/tts/routes.test.js +53 -0
  322. package/server/lib/tts/service.js +178 -0
  323. package/server/lib/tts/stt.js +75 -0
  324. package/server/lib/tunnels/DOCUMENTATION.md +18 -0
  325. package/server/lib/tunnels/index.js +166 -0
  326. package/server/lib/tunnels/managed-config.js +201 -0
  327. package/server/lib/tunnels/providers/cloudflare.js +260 -0
  328. package/server/lib/tunnels/registry.js +51 -0
  329. package/server/lib/tunnels/routes.js +605 -0
  330. package/server/lib/tunnels/types.js +219 -0
  331. package/server/lib/ui-auth/DOCUMENTATION.md +38 -0
  332. package/server/lib/ui-auth/ui-auth.js +673 -0
  333. package/server/lib/ui-auth/ui-passkeys.js +545 -0
  334. package/server/opencode-proxy.test.js +151 -0
  335. package/server/proxy-headers.js +61 -0
  336. package/server/proxy-headers.test.js +58 -0
  337. package/server/sse-routes.test.js +152 -0
@@ -0,0 +1,131 @@
1
+ export const MESSAGE_STREAM_GLOBAL_WS_PATH = '/api/global/event/ws';
2
+ export const MESSAGE_STREAM_DIRECTORY_WS_PATH = '/api/event/ws';
3
+ export const MESSAGE_STREAM_WS_HEARTBEAT_INTERVAL_MS = 15 * 1000;
4
+ // Per-client pending outbound WS buffer, not a payload or stream-size limit.
5
+ // Healthy clients stay near 0; this only trips when a client is far behind.
6
+ // Raised from 4 MB → 16 MB to tolerate bursts during long agent sessions
7
+ // (e.g. ultrawork / multi-tool loops) where the browser briefly falls behind.
8
+ export const MESSAGE_STREAM_WS_MAX_BUFFERED_BYTES = 16 * 1024 * 1024;
9
+
10
+ // Threshold at which we emit a backpressure warning frame so the client can
11
+ // proactively start shedding low-priority updates before the hard disconnect.
12
+ export const MESSAGE_STREAM_WS_BACKPRESSURE_WARN_BYTES = 12 * 1024 * 1024;
13
+
14
+ export function parseSseEventEnvelope(block) {
15
+ if (!block || typeof block !== 'string') {
16
+ return null;
17
+ }
18
+
19
+ const eventId = block
20
+ .split('\n')
21
+ .find((line) => line.startsWith('id:'))
22
+ ?.slice(3)
23
+ .trim() || null;
24
+
25
+ const dataLines = block
26
+ .split('\n')
27
+ .filter((line) => line.startsWith('data:'))
28
+ .map((line) => line.slice(5).replace(/^\s/, ''));
29
+
30
+ if (dataLines.length === 0) {
31
+ return null;
32
+ }
33
+
34
+ const payloadText = dataLines.join('\n').trim();
35
+ if (!payloadText) {
36
+ return null;
37
+ }
38
+
39
+ try {
40
+ const parsed = JSON.parse(payloadText);
41
+ if (
42
+ parsed &&
43
+ typeof parsed === 'object' &&
44
+ typeof parsed.payload === 'object' &&
45
+ parsed.payload !== null
46
+ ) {
47
+ return {
48
+ eventId,
49
+ directory: typeof parsed.directory === 'string' && parsed.directory.length > 0 ? parsed.directory : null,
50
+ payload: parsed.payload,
51
+ };
52
+ }
53
+
54
+ const directory =
55
+ typeof parsed?.directory === 'string' && parsed.directory.length > 0
56
+ ? parsed.directory
57
+ : typeof parsed?.properties?.directory === 'string' && parsed.properties.directory.length > 0
58
+ ? parsed.properties.directory
59
+ : null;
60
+
61
+ return {
62
+ eventId,
63
+ directory,
64
+ payload: parsed,
65
+ };
66
+ } catch {
67
+ return null;
68
+ }
69
+ }
70
+
71
+ export function sendMessageStreamWsFrame(socket, payload) {
72
+ if (!socket || socket.readyState !== 1) {
73
+ return false;
74
+ }
75
+
76
+ const buffered = typeof socket.bufferedAmount === 'number' ? socket.bufferedAmount : 0;
77
+
78
+ if (buffered > MESSAGE_STREAM_WS_MAX_BUFFERED_BYTES) {
79
+ try {
80
+ socket.close(1013, 'Message stream client is too slow');
81
+ } catch {
82
+ }
83
+ return false;
84
+ }
85
+
86
+ try {
87
+ socket.send(JSON.stringify(payload));
88
+ const bufferedAfter = typeof socket.bufferedAmount === 'number' ? socket.bufferedAmount : 0;
89
+ if (bufferedAfter > MESSAGE_STREAM_WS_MAX_BUFFERED_BYTES) {
90
+ try {
91
+ socket.close(1013, 'Message stream client is too slow');
92
+ } catch {
93
+ }
94
+ return false;
95
+ }
96
+
97
+ // Emit a one-shot backpressure warning when the buffer is building up.
98
+ // The flag prevents sending repeated warnings that would themselves
99
+ // increase the buffer. It resets once the buffer drains below the
100
+ // threshold.
101
+ if (bufferedAfter > MESSAGE_STREAM_WS_BACKPRESSURE_WARN_BYTES) {
102
+ if (!socket._ocBackpressureWarned) {
103
+ socket._ocBackpressureWarned = true;
104
+ try {
105
+ socket.send(JSON.stringify({
106
+ type: 'backpressure',
107
+ bufferedBytes: bufferedAfter,
108
+ maxBytes: MESSAGE_STREAM_WS_MAX_BUFFERED_BYTES,
109
+ }));
110
+ } catch {
111
+ // Best-effort warning — ignore send failures.
112
+ }
113
+ }
114
+ } else if (socket._ocBackpressureWarned) {
115
+ socket._ocBackpressureWarned = false;
116
+ }
117
+
118
+ return true;
119
+ } catch {
120
+ return false;
121
+ }
122
+ }
123
+
124
+ export function sendMessageStreamWsEvent(socket, payload, options = {}) {
125
+ return sendMessageStreamWsFrame(socket, {
126
+ type: 'event',
127
+ payload,
128
+ ...(typeof options.eventId === 'string' && options.eventId.length > 0 ? { eventId: options.eventId } : {}),
129
+ ...(typeof options.directory === 'string' && options.directory.length > 0 ? { directory: options.directory } : {}),
130
+ });
131
+ }
@@ -0,0 +1,182 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import {
4
+ MESSAGE_STREAM_DIRECTORY_WS_PATH,
5
+ MESSAGE_STREAM_GLOBAL_WS_PATH,
6
+ MESSAGE_STREAM_WS_MAX_BUFFERED_BYTES,
7
+ MESSAGE_STREAM_WS_BACKPRESSURE_WARN_BYTES,
8
+ parseSseEventEnvelope,
9
+ sendMessageStreamWsEvent,
10
+ sendMessageStreamWsFrame,
11
+ } from './protocol.js';
12
+
13
+ describe('event stream protocol helpers', () => {
14
+ it('exports stable websocket paths', () => {
15
+ expect(MESSAGE_STREAM_GLOBAL_WS_PATH).toBe('/api/global/event/ws');
16
+ expect(MESSAGE_STREAM_DIRECTORY_WS_PATH).toBe('/api/event/ws');
17
+ });
18
+
19
+ it('parses wrapped SSE payloads with event id and directory', () => {
20
+ const envelope = parseSseEventEnvelope(
21
+ 'id: evt-1\n' +
22
+ 'event: message\n' +
23
+ 'data: {"directory":"/tmp/project","payload":{"type":"session.updated"}}\n'
24
+ );
25
+
26
+ expect(envelope).toEqual({
27
+ eventId: 'evt-1',
28
+ directory: '/tmp/project',
29
+ payload: { type: 'session.updated' },
30
+ });
31
+ });
32
+
33
+ it('derives directory from payload properties when not wrapped', () => {
34
+ const envelope = parseSseEventEnvelope(
35
+ 'data: {"type":"vinci:notification","properties":{"directory":"/tmp/project"}}\n'
36
+ );
37
+
38
+ expect(envelope).toEqual({
39
+ eventId: null,
40
+ directory: '/tmp/project',
41
+ payload: {
42
+ type: 'vinci:notification',
43
+ properties: { directory: '/tmp/project' },
44
+ },
45
+ });
46
+ });
47
+
48
+ it('returns null for malformed SSE blocks', () => {
49
+ expect(parseSseEventEnvelope('event: message\n')).toBeNull();
50
+ expect(parseSseEventEnvelope('data: {oops}\n')).toBeNull();
51
+ });
52
+
53
+ it('serializes generic websocket frames', () => {
54
+ let rawPayload = null;
55
+ const socket = {
56
+ readyState: 1,
57
+ send(payload) {
58
+ rawPayload = payload;
59
+ },
60
+ };
61
+
62
+ const sent = sendMessageStreamWsFrame(socket, { type: 'ready' });
63
+
64
+ expect(sent).toBe(true);
65
+ expect(rawPayload).toBe('{"type":"ready"}');
66
+ });
67
+
68
+ it('closes slow websocket clients instead of adding more buffered data', () => {
69
+ let closeCall = null;
70
+ let sendCalls = 0;
71
+ const socket = {
72
+ readyState: 1,
73
+ bufferedAmount: MESSAGE_STREAM_WS_MAX_BUFFERED_BYTES + 1,
74
+ send() {
75
+ sendCalls += 1;
76
+ },
77
+ close(code, reason) {
78
+ closeCall = { code, reason };
79
+ },
80
+ };
81
+
82
+ const sent = sendMessageStreamWsFrame(socket, { type: 'ready' });
83
+
84
+ expect(sent).toBe(false);
85
+ expect(sendCalls).toBe(0);
86
+ expect(closeCall).toEqual({
87
+ code: 1013,
88
+ reason: 'Message stream client is too slow',
89
+ });
90
+ });
91
+
92
+ it('emits a backpressure warning when buffer exceeds the warn threshold', () => {
93
+ const sentPayloads = [];
94
+ const socket = {
95
+ readyState: 1,
96
+ bufferedAmount: MESSAGE_STREAM_WS_BACKPRESSURE_WARN_BYTES + 1,
97
+ send(payload) {
98
+ sentPayloads.push(payload);
99
+ },
100
+ };
101
+
102
+ const sent = sendMessageStreamWsFrame(socket, { type: 'test' });
103
+
104
+ expect(sent).toBe(true);
105
+ expect(sentPayloads).toHaveLength(2);
106
+
107
+ const warning = JSON.parse(sentPayloads[1]);
108
+ expect(warning.type).toBe('backpressure');
109
+ expect(warning.bufferedBytes).toBeGreaterThan(0);
110
+ expect(warning.maxBytes).toBe(MESSAGE_STREAM_WS_MAX_BUFFERED_BYTES);
111
+ });
112
+
113
+ it('does not repeat backpressure warnings while still above threshold', () => {
114
+ const sentPayloads = [];
115
+ const socket = {
116
+ readyState: 1,
117
+ bufferedAmount: MESSAGE_STREAM_WS_BACKPRESSURE_WARN_BYTES + 1,
118
+ send(payload) {
119
+ sentPayloads.push(payload);
120
+ },
121
+ };
122
+
123
+ sendMessageStreamWsFrame(socket, { type: 'test1' });
124
+ sendMessageStreamWsFrame(socket, { type: 'test2' });
125
+
126
+ // First call: data + warning = 2 sends. Second call: data only = 1 send.
127
+ expect(sentPayloads).toHaveLength(3);
128
+ expect(JSON.parse(sentPayloads[1]).type).toBe('backpressure');
129
+ expect(JSON.parse(sentPayloads[2])).toEqual({ type: 'test2' });
130
+ });
131
+
132
+ it('resets backpressure warning flag when buffer drains', () => {
133
+ const sentPayloads = [];
134
+ const socket = {
135
+ readyState: 1,
136
+ bufferedAmount: MESSAGE_STREAM_WS_BACKPRESSURE_WARN_BYTES + 1,
137
+ send(payload) {
138
+ sentPayloads.push(payload);
139
+ },
140
+ };
141
+
142
+ sendMessageStreamWsFrame(socket, { type: 'first' });
143
+ expect(socket._ocBackpressureWarned).toBe(true);
144
+
145
+ // Buffer drains
146
+ socket.bufferedAmount = 100;
147
+ sendMessageStreamWsFrame(socket, { type: 'recovered' });
148
+ expect(socket._ocBackpressureWarned).toBe(false);
149
+
150
+ // Buffer spikes again — warning should fire again
151
+ socket.bufferedAmount = MESSAGE_STREAM_WS_BACKPRESSURE_WARN_BYTES + 1;
152
+ sendMessageStreamWsFrame(socket, { type: 'again' });
153
+ const backpressureFrames = sentPayloads
154
+ .map((p) => JSON.parse(p))
155
+ .filter((p) => p.type === 'backpressure');
156
+ expect(backpressureFrames).toHaveLength(2);
157
+ });
158
+
159
+ it('serializes event frames with routing metadata', () => {
160
+ let rawPayload = null;
161
+ const socket = {
162
+ readyState: 1,
163
+ send(payload) {
164
+ rawPayload = payload;
165
+ },
166
+ };
167
+
168
+ const sent = sendMessageStreamWsEvent(
169
+ socket,
170
+ { type: 'vinci:heartbeat', timestamp: 1 },
171
+ { eventId: 'evt-2', directory: '/tmp/project' }
172
+ );
173
+
174
+ expect(sent).toBe(true);
175
+ expect(JSON.parse(rawPayload)).toEqual({
176
+ type: 'event',
177
+ payload: { type: 'vinci:heartbeat', timestamp: 1 },
178
+ eventId: 'evt-2',
179
+ directory: '/tmp/project',
180
+ });
181
+ });
182
+ });
@@ -0,0 +1,180 @@
1
+ import { WebSocketServer } from 'ws';
2
+
3
+ import { parseRequestPathname } from '../terminal/index.js';
4
+ import {
5
+ MESSAGE_STREAM_DIRECTORY_WS_PATH,
6
+ MESSAGE_STREAM_GLOBAL_WS_PATH,
7
+ MESSAGE_STREAM_WS_HEARTBEAT_INTERVAL_MS,
8
+ sendMessageStreamWsEvent,
9
+ } from './protocol.js';
10
+ import { createGlobalMessageStreamHub } from './global-hub.js';
11
+ import { createGlobalMessageStreamWsBridge } from './global-ws-bridge.js';
12
+ import { acceptDirectoryMessageStreamWsConnection } from './directory-ws-bridge.js';
13
+ import {
14
+ DEFAULT_UPSTREAM_RECONNECT_DELAY_MS,
15
+ DEFAULT_UPSTREAM_STALL_TIMEOUT_MS,
16
+ } from './upstream-reader.js';
17
+
18
+ export function createGlobalUiEventBroadcaster({
19
+ sseClients,
20
+ wsClients,
21
+ writeSseEvent,
22
+ }) {
23
+ return (payload, options = {}) => {
24
+ const hasSseClients = sseClients.size > 0;
25
+ const hasWsClients = wsClients.size > 0;
26
+ if (!hasSseClients && !hasWsClients) {
27
+ return;
28
+ }
29
+
30
+ if (hasSseClients) {
31
+ for (const res of sseClients) {
32
+ try {
33
+ writeSseEvent(res, payload);
34
+ } catch {
35
+ }
36
+ }
37
+ }
38
+
39
+ if (hasWsClients) {
40
+ for (const socket of Array.from(wsClients)) {
41
+ const sent = sendMessageStreamWsEvent(socket, payload, {
42
+ directory: typeof options.directory === 'string' && options.directory.length > 0 ? options.directory : 'global',
43
+ eventId: typeof options.eventId === 'string' && options.eventId.length > 0 ? options.eventId : undefined,
44
+ });
45
+ if (!sent) {
46
+ wsClients.delete(socket);
47
+ }
48
+ }
49
+ }
50
+ };
51
+ }
52
+
53
+ export function createMessageStreamWsRuntime({
54
+ server,
55
+ uiAuthController,
56
+ isRequestOriginAllowed,
57
+ rejectWebSocketUpgrade,
58
+ buildOpenCodeUrl,
59
+ getOpenCodeAuthHeaders,
60
+ processForwardedEventPayload,
61
+ wsClients,
62
+ triggerHealthCheck,
63
+ heartbeatIntervalMs = MESSAGE_STREAM_WS_HEARTBEAT_INTERVAL_MS,
64
+ upstreamStallTimeoutMs = DEFAULT_UPSTREAM_STALL_TIMEOUT_MS,
65
+ upstreamReconnectDelayMs = DEFAULT_UPSTREAM_RECONNECT_DELAY_MS,
66
+ fetchImpl = fetch,
67
+ globalEventHub = null,
68
+ }) {
69
+ const wsServer = new WebSocketServer({
70
+ noServer: true,
71
+ });
72
+
73
+ const ownsGlobalHub = !globalEventHub;
74
+ const globalHub = globalEventHub ?? createGlobalMessageStreamHub({
75
+ buildOpenCodeUrl,
76
+ getOpenCodeAuthHeaders,
77
+ fetchImpl,
78
+ upstreamStallTimeoutMs,
79
+ upstreamReconnectDelayMs,
80
+ });
81
+
82
+ const globalBridge = createGlobalMessageStreamWsBridge({
83
+ globalHub,
84
+ ownsGlobalHub,
85
+ wsClients,
86
+ processForwardedEventPayload,
87
+ triggerHealthCheck,
88
+ heartbeatIntervalMs,
89
+ });
90
+
91
+ wsServer.on('connection', (socket, req) => {
92
+ const rawUrl = typeof req?.url === 'string' ? req.url : MESSAGE_STREAM_GLOBAL_WS_PATH;
93
+ const pathname = parseRequestPathname(rawUrl);
94
+ const requestUrl = new URL(rawUrl, 'http://127.0.0.1');
95
+ const isGlobalStream = pathname === MESSAGE_STREAM_GLOBAL_WS_PATH;
96
+ const requestedLastEventId = requestUrl.searchParams.get('lastEventId')?.trim() || '';
97
+ const requestedDirectory = requestUrl.searchParams.get('directory')?.trim() || '';
98
+
99
+ if (isGlobalStream) {
100
+ globalBridge.accept(socket, {
101
+ requestedLastEventId,
102
+ });
103
+ return;
104
+ }
105
+
106
+ acceptDirectoryMessageStreamWsConnection({
107
+ socket,
108
+ requestedLastEventId,
109
+ requestedDirectory,
110
+ buildOpenCodeUrl,
111
+ getOpenCodeAuthHeaders,
112
+ processForwardedEventPayload,
113
+ wsClients,
114
+ triggerHealthCheck,
115
+ heartbeatIntervalMs,
116
+ upstreamStallTimeoutMs,
117
+ upstreamReconnectDelayMs,
118
+ fetchImpl,
119
+ });
120
+ });
121
+
122
+ const upgradeHandler = (req, socket, head) => {
123
+ const pathname = parseRequestPathname(req.url);
124
+ if (pathname !== MESSAGE_STREAM_GLOBAL_WS_PATH && pathname !== MESSAGE_STREAM_DIRECTORY_WS_PATH) {
125
+ return;
126
+ }
127
+
128
+ const handleUpgrade = async () => {
129
+ try {
130
+ if (uiAuthController?.enabled) {
131
+ const sessionToken = await uiAuthController?.ensureSessionToken?.(req, null);
132
+ if (!sessionToken) {
133
+ rejectWebSocketUpgrade(socket, 401, 'UI authentication required');
134
+ return;
135
+ }
136
+
137
+ const originAllowed = await isRequestOriginAllowed(req);
138
+ if (!originAllowed) {
139
+ rejectWebSocketUpgrade(socket, 403, 'Invalid origin');
140
+ return;
141
+ }
142
+ }
143
+
144
+ wsServer.handleUpgrade(req, socket, head, (ws) => {
145
+ wsServer.emit('connection', ws, req);
146
+ });
147
+ } catch {
148
+ rejectWebSocketUpgrade(socket, 500, 'Upgrade failed');
149
+ }
150
+ };
151
+
152
+ void handleUpgrade();
153
+ };
154
+
155
+ server.on('upgrade', upgradeHandler);
156
+
157
+ return {
158
+ wsServer,
159
+ async close() {
160
+ server.off('upgrade', upgradeHandler);
161
+ globalBridge.close();
162
+
163
+ try {
164
+ for (const client of wsServer.clients) {
165
+ try {
166
+ client.terminate();
167
+ } catch {
168
+ }
169
+ }
170
+
171
+ await new Promise((resolve) => {
172
+ wsServer.close(() => resolve());
173
+ });
174
+ } catch {
175
+ } finally {
176
+ wsClients.clear();
177
+ }
178
+ },
179
+ };
180
+ }