@openchambery/web 2.0.0-beta.1 → 2.0.0-beta.11

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 (296) hide show
  1. package/README.md +15 -11
  2. package/bin/cli-opencode.test.js +4 -4
  3. package/bin/cli.js +1 -1
  4. package/bin/lib/cli-startup.js +1 -1
  5. package/bin/lib/cli-startup.test.js +18 -4
  6. package/dist/assets/{AgentAvatar-CLWWNu_Q.js → AgentAvatar-pLaqWWmB.js} +1 -1
  7. package/dist/assets/AssistantView-C4GkABnI.js +3 -0
  8. package/dist/assets/{DiagramView-xuSJTnv7.js → DiagramView-ByYLIfWo.js} +1 -1
  9. package/dist/assets/{FadeInOnReveal-Ov0D96d0.js → FadeInOnReveal-MuJAzJq_.js} +1 -1
  10. package/dist/assets/Icon-Dz2kO9RN.js +1 -0
  11. package/dist/assets/MarkdownRenderer-CPU6kSHd.js +2 -0
  12. package/dist/assets/{MarkdownRendererImpl-SKi4oG0c.js → MarkdownRendererImpl-2hw5cdqQ.js} +3 -3
  13. package/dist/assets/MarkstreamRendererImpl-DXZQkLTa.js +3 -0
  14. package/dist/assets/{MobileDetailNavigation-Bnx59U_J.js → MobileDetailNavigation-C9BzieQY.js} +1 -1
  15. package/dist/assets/{MobileShareBridge-BQlP4J-F.js → MobileShareBridge-2_iOfzqO.js} +2 -2
  16. package/dist/assets/MobileSurface-DNVA7K_1.js +2 -0
  17. package/dist/assets/{MultiRunWindow-ChZGob0v.js → MultiRunWindow-QA6pMrJ9.js} +1 -1
  18. package/dist/assets/{OnboardingScreen-DtQJxZG-.js → OnboardingScreen-XWkr7MjT.js} +1 -1
  19. package/dist/assets/OverlayScrollbar-DCWyGseO.js +2 -0
  20. package/dist/assets/SettingsView-dWuu0VIb.js +25 -0
  21. package/dist/assets/{SettingsWindow-DfnsFa8y.js → SettingsWindow-CetgiJMX.js} +1 -1
  22. package/dist/assets/TerminalView-BuXuo7gm.js +8 -0
  23. package/dist/assets/{ToolOutputDialog-BejmjrRk.js → ToolOutputDialog-DNSSOaCt.js} +2 -2
  24. package/dist/assets/appThemeRegistry-Cb7qFBsc.js +1 -0
  25. package/dist/assets/{assistant-attachment-cache-C0aRnFkj.js → assistant-attachment-cache-DphJleHr.js} +1 -1
  26. package/dist/assets/{button-D-rATN7w.js → button-Doq9FI1I.js} +1 -1
  27. package/dist/assets/{collapsible-BXRd5_Hr.js → collapsible-CYUeu3_N.js} +1 -1
  28. package/dist/assets/{context-menu-jGtH6fR9.js → context-menu-C8kOMVtC.js} +1 -1
  29. package/dist/assets/{desktopHosts-B1ixEylr.js → desktopHosts-DbxnIQdS.js} +1 -1
  30. package/dist/assets/{desktopRelayRestore-4JmFUw8r.js → desktopRelayRestore-dvxF4V2p.js} +1 -1
  31. package/dist/assets/{diagram-DQf7Y_uR.js → diagram-B1p9Yn1h.js} +1 -1
  32. package/dist/assets/{dialog-bfbvR4VH.js → dialog-D08-Lg-e.js} +1 -1
  33. package/dist/assets/es-Dsmc0A_3.js +15 -0
  34. package/dist/assets/fileMentionTouchSelection-BCAKb-NS.js +3 -0
  35. package/dist/assets/{fileQueries-DQc8EJUf.js → fileQueries-X773xxvX.js} +1 -1
  36. package/dist/assets/fr-C_ZlZDI2.js +13 -0
  37. package/dist/assets/gitApi-3VBj3fbR.js +10 -0
  38. package/dist/assets/{imageSource-BlAJJUEI.js → imageSource-BMztRu0c.js} +1 -1
  39. package/dist/assets/{input-8r6F_Fza.js → input-DZZuQ1IL.js} +1 -1
  40. package/dist/assets/insertionBoundaries-BMuCcvPy.js +1 -0
  41. package/dist/assets/ja-BqqTaWTi.js +15 -0
  42. package/dist/assets/ko-DP2XPM37.js +15 -0
  43. package/dist/assets/main-COEILjRF.js +169 -0
  44. package/dist/assets/main-D2BzpnUt.css +1 -0
  45. package/dist/assets/{main-CCE_4y5F.js → main-DsUewWPt.js} +2 -2
  46. package/dist/assets/markdown-worker-bS5OgGO3.js +2 -0
  47. package/dist/assets/{markdownHeightCache-1Qo6y6IJ.js → markdownHeightCache-DBgXGze8.js} +1 -1
  48. package/dist/assets/markdownTheme-DM5h1i18.js +1 -0
  49. package/dist/assets/mcpQueries-B05QR67V.js +1 -0
  50. package/dist/assets/miniChat-D7IPMm-k.js +2 -0
  51. package/dist/assets/mobile-BnCoDxnv.js +2 -0
  52. package/dist/assets/multirun-BDmFjX9N.js +4 -0
  53. package/dist/assets/persistence-DznVIKwz.js +3 -0
  54. package/dist/assets/pl-BCf5uLlK.js +15 -0
  55. package/dist/assets/platform-DSTtx-DI.js +6 -0
  56. package/dist/assets/projectMeta-C5gcAd5f.js +3 -0
  57. package/dist/assets/pt-BR-W09Q2pvn.js +15 -0
  58. package/dist/assets/{relay-image-stream-DM7atqJs.js → relay-image-stream-DrdVj99c.js} +1 -1
  59. package/dist/assets/renderElectronMiniChatApp-DsAaWCss.js +2 -0
  60. package/dist/assets/renderMobileApp-DOaoohlp.js +28 -0
  61. package/dist/assets/runtime-fetch-Fs9ZV2V8.js +1 -0
  62. package/dist/assets/{runtimeConfig-BMvqa5we.js → runtimeConfig-CzPK1uMj.js} +1 -1
  63. package/dist/assets/runtimeEndpointReset-DKjVJCdt.js +149 -0
  64. package/dist/assets/sessionChangeSummary-CktSWasa.js +2 -0
  65. package/dist/assets/{sessionLookup-gh-hVRIc.js → sessionLookup-CDA1aAcI.js} +1 -1
  66. package/dist/assets/{sortable-tabs-strip-CudoSgV4.js → sortable-tabs-strip-Q3bb6MVj.js} +1 -1
  67. package/dist/assets/src-lS1keDVV.css +2 -0
  68. package/dist/assets/store-jaP79mqI.js +15 -0
  69. package/dist/assets/{terminalApi-DiF6JmBP.js → terminalApi-Da_DO_Uh.js} +1 -1
  70. package/dist/assets/textarea--CYehp07.js +1 -0
  71. package/dist/assets/{tooltip-CQvpK-UV.js → tooltip-CGLzBwEK.js} +1 -1
  72. package/dist/assets/uk-BgFzz8m2.js +15 -0
  73. package/dist/assets/{url-_z7evzdK.js → url-Byz3kd29.js} +1 -1
  74. package/dist/assets/{useAppFontEffects-Cpmpo7Wb.js → useAppFontEffects-2zzPMQF2.js} +64 -45
  75. package/dist/assets/{useAssistantUIStore-CMutVh9B.js → useAssistantUIStore-Bz4tRflF.js} +1 -1
  76. package/dist/assets/useDesktopWindowControlsLayout-B-2YooUd.js +1 -0
  77. package/dist/assets/useEffectiveDirectory-K3U4eL5F.js +1 -0
  78. package/dist/assets/{useFontPreferences-DcbXQOkc.js → useFontPreferences-D9OIZs1p.js} +1 -1
  79. package/dist/assets/useMobileNavigationStore-B0zSdCwP.js +414 -0
  80. package/dist/assets/useSessionAutoCleanup-BCXHAcbX.js +2 -0
  81. package/dist/assets/useUIStore-syVmbuDA.js +1 -0
  82. package/dist/assets/{useWorkerHighlightedLines-Bqn4AiBL.js → useWorkerHighlightedLines-CsoMQNik.js} +2 -2
  83. package/dist/assets/vendor-opencode-client-C12gfjag.js +12 -0
  84. package/dist/assets/{vendor-reactuses-core-BnW6GxJt.js → vendor-reactuses-core-Dq5ocTF8.js} +1 -1
  85. package/dist/assets/zh-CN-BhqSLgUY.js +15 -0
  86. package/dist/assets/zh-TW-BsaBgY-p.js +15 -0
  87. package/dist/index.html +12 -12
  88. package/dist/mini-chat.html +12 -12
  89. package/dist/mobile.html +12 -12
  90. package/package.json +3 -2
  91. package/server/index.js +359 -63
  92. package/server/lib/assistants/DOCUMENTATION.md +14 -9
  93. package/server/lib/assistants/contracts.js +11 -1
  94. package/server/lib/assistants/projection-history.test.js +265 -18
  95. package/server/lib/assistants/routes.js +6 -0
  96. package/server/lib/assistants/service.js +417 -231
  97. package/server/lib/assistants/service.test.js +392 -148
  98. package/server/lib/browser-provider/DOCUMENTATION.md +141 -0
  99. package/server/lib/browser-provider/contract.js +312 -0
  100. package/server/lib/browser-provider/host.js +421 -0
  101. package/server/lib/browser-provider/host.test.js +304 -0
  102. package/server/lib/browser-provider/routes.js +122 -0
  103. package/server/lib/browser-provider/routes.test.js +120 -0
  104. package/server/lib/browser-provider/selection-store.js +43 -0
  105. package/server/lib/browser-provider/selection-store.test.js +35 -0
  106. package/server/lib/conversations/DOCUMENTATION.md +1 -1
  107. package/server/lib/conversations/service.js +1 -0
  108. package/server/lib/conversations/service.test.js +16 -0
  109. package/server/lib/conversations/validation.js +11 -1
  110. package/server/lib/event-stream/DOCUMENTATION.md +3 -1
  111. package/server/lib/event-stream/directory-ws-bridge.js +13 -2
  112. package/server/lib/event-stream/global-hub.js +49 -4
  113. package/server/lib/event-stream/reasoning-projection.js +16 -1
  114. package/server/lib/event-stream/reasoning-projection.test.js +15 -0
  115. package/server/lib/event-stream/runtime.js +57 -20
  116. package/server/lib/event-stream/runtime.test.js +112 -0
  117. package/server/lib/llm/DOCUMENTATION.md +33 -9
  118. package/server/lib/llm/catalog.js +1 -1
  119. package/server/lib/llm/completions.js +5 -1
  120. package/server/lib/llm/completions.test.js +3 -3
  121. package/server/lib/llm/generate.js +237 -145
  122. package/server/lib/llm/generate.test.js +201 -46
  123. package/server/lib/llm/routes.js +2 -0
  124. package/server/lib/message-queue/runtime.js +15 -2
  125. package/server/lib/notifications/DOCUMENTATION.md +3 -1
  126. package/server/lib/notifications/runtime.js +133 -31
  127. package/server/lib/notifications/runtime.test.js +103 -0
  128. package/server/lib/notifications/template-runtime.js +130 -38
  129. package/server/lib/notifications/template-runtime.test.js +96 -0
  130. package/server/lib/opencode/DOCUMENTATION.md +89 -17
  131. package/server/lib/opencode/agent-document.d.ts +28 -0
  132. package/server/lib/opencode/agent-document.js +280 -0
  133. package/server/lib/opencode/agent-document.test.js +137 -0
  134. package/server/lib/opencode/agents.js +96 -0
  135. package/server/lib/opencode/auth-state-runtime.js +5 -0
  136. package/server/lib/opencode/bootstrap-runtime.js +2 -0
  137. package/server/lib/opencode/config-entity-routes.js +42 -99
  138. package/server/lib/opencode/config-entity-routes.test.js +107 -0
  139. package/server/lib/opencode/config-hot-reload.integration.test.js +105 -0
  140. package/server/lib/opencode/ensure-cli.d.ts +8 -0
  141. package/server/lib/opencode/ensure-cli.js +1 -2
  142. package/server/lib/opencode/env-runtime.js +40 -26
  143. package/server/lib/opencode/env-runtime.test.js +10 -17
  144. package/server/lib/opencode/feature-routes-runtime.js +53 -4
  145. package/server/lib/opencode/feature-routes-runtime.test.js +25 -0
  146. package/server/lib/opencode/http-server-connections.js +98 -0
  147. package/server/lib/opencode/http-server-connections.test.js +321 -0
  148. package/server/lib/opencode/lifecycle.js +349 -39
  149. package/server/lib/opencode/lifecycle.test.js +322 -0
  150. package/server/lib/opencode/managed-capabilities-runtime.js +15 -2
  151. package/server/lib/opencode/managed-capabilities-runtime.test.js +1 -0
  152. package/server/lib/opencode/openchamber-routes.js +7 -1
  153. package/server/lib/opencode/opencode2-pin.js +4 -4
  154. package/server/lib/opencode/opencode2-pin.test.js +1 -0
  155. package/server/lib/opencode/opencode2-sidecar.e2e.test.js +127 -4
  156. package/server/lib/opencode/owned-runtime-upgrade.js +431 -0
  157. package/server/lib/opencode/owned-runtime-upgrade.test.js +160 -0
  158. package/server/lib/opencode/plugin-routes.js +3 -18
  159. package/server/lib/opencode/plugin-routes.test.js +9 -9
  160. package/server/lib/opencode/provider-catalog.js +58 -12
  161. package/server/lib/opencode/provider-catalog.test.js +48 -0
  162. package/server/lib/opencode/proxy.js +254 -46
  163. package/server/lib/opencode/proxy.test.js +238 -5
  164. package/server/lib/opencode/routes.js +326 -62
  165. package/server/lib/opencode/routes.test.js +278 -12
  166. package/server/lib/opencode/runtime-contract.js +401 -0
  167. package/server/lib/opencode/runtime-contract.test.js +214 -0
  168. package/server/lib/opencode/server-opencode-fetch.js +242 -0
  169. package/server/lib/opencode/server-opencode-fetch.test.js +130 -0
  170. package/server/lib/opencode/server-utils-runtime.js +33 -0
  171. package/server/lib/opencode/shared-service.integration.test.js +66 -0
  172. package/server/lib/opencode/shared-service.js +147 -0
  173. package/server/lib/opencode/shared-service.test.js +93 -0
  174. package/server/lib/opencode/shared.js +3 -3
  175. package/server/lib/opencode/shutdown-runtime.js +15 -6
  176. package/server/lib/opencode/shutdown-runtime.test.js +49 -1
  177. package/server/lib/opencode/skill-routes.js +20 -33
  178. package/server/lib/opencode/skill-routes.test.js +35 -2
  179. package/server/lib/opencode/skills.test.js +15 -0
  180. package/server/lib/opencode/startup-pipeline-runtime.js +4 -0
  181. package/server/lib/opencode/static-routes-runtime.js +3 -1
  182. package/server/lib/opencode/static-routes-runtime.test.js +33 -0
  183. package/server/lib/opencode/upgrade-screen.d.ts +30 -0
  184. package/server/lib/opencode/upgrade-screen.js +299 -0
  185. package/server/lib/opencode/upgrade-screen.test.js +196 -0
  186. package/server/lib/opencode/v2-client.js +14 -3
  187. package/server/lib/opencode/v2-plugin-host-shim/index.mjs +155 -0
  188. package/server/lib/opencode/v2-plugin-host-shim/package.json +4 -0
  189. package/server/lib/permission-auto-accept/DOCUMENTATION.md +5 -5
  190. package/server/lib/permission-auto-accept/runtime.js +14 -7
  191. package/server/lib/permission-auto-accept/runtime.test.js +27 -12
  192. package/server/lib/relay/reasoning-projection.e2e.test.ts +22 -7
  193. package/server/lib/scheduled-tasks/DOCUMENTATION.md +8 -0
  194. package/server/lib/scheduled-tasks/runtime.js +52 -4
  195. package/server/lib/scheduled-tasks/runtime.test.js +80 -1
  196. package/server/lib/session-goal/DOCUMENTATION.md +113 -13
  197. package/server/lib/session-goal/routes.js +9 -0
  198. package/server/lib/session-goal/runtime.js +1579 -117
  199. package/server/lib/session-goal/runtime.test.js +1453 -84
  200. package/server/lib/session-index/DOCUMENTATION.md +18 -15
  201. package/server/lib/session-index/event-ingest.js +89 -16
  202. package/server/lib/session-index/event-ingest.test.js +73 -0
  203. package/server/lib/session-index/host-authority-snapshot.js +92 -0
  204. package/server/lib/session-index/routes.host-archive.test.js +201 -0
  205. package/server/lib/session-index/routes.js +63 -10
  206. package/server/lib/session-index/routes.test.js +8 -8
  207. package/server/lib/session-index/service.js +7 -1
  208. package/server/lib/session-index/sync-runtime.js +161 -34
  209. package/server/lib/session-index/sync-runtime.test.js +155 -1
  210. package/server/lib/session-metadata/DOCUMENTATION.md +73 -18
  211. package/server/lib/session-metadata/opencode-session-record.d.ts +57 -0
  212. package/server/lib/session-metadata/opencode-session-record.js +316 -0
  213. package/server/lib/session-metadata/opencode-session-record.test.js +242 -0
  214. package/server/lib/session-metadata/routes.js +112 -9
  215. package/server/lib/session-metadata/routes.test.js +47 -2
  216. package/server/lib/session-metadata/session-archive.d.ts +73 -0
  217. package/server/lib/session-metadata/session-archive.js +654 -0
  218. package/server/lib/session-metadata/session-archive.test.js +359 -0
  219. package/server/lib/session-metadata/session-metadata-store.d.ts +38 -0
  220. package/server/lib/session-metadata/session-metadata-store.js +270 -83
  221. package/server/lib/session-metadata/session-metadata-store.test.js +205 -10
  222. package/server/lib/session-metadata/session-projection.d.ts +41 -0
  223. package/server/lib/session-metadata/session-projection.js +279 -0
  224. package/server/lib/session-metadata/session-projection.test.js +64 -0
  225. package/server/lib/session-metadata/system-session.js +55 -0
  226. package/server/lib/session-metadata/system-session.test.js +55 -0
  227. package/server/lib/session-title/DOCUMENTATION.md +40 -7
  228. package/server/lib/session-title/native-runtime.test.js +167 -0
  229. package/server/lib/session-title/runtime.js +69 -44
  230. package/server/lib/session-title/session-access.js +46 -0
  231. package/server/lib/session-title/session-access.test.js +91 -0
  232. package/server/lib/session-turn-pages/DOCUMENTATION.md +9 -8
  233. package/server/lib/session-turn-pages/routes.js +17 -102
  234. package/server/lib/session-turn-pages/routes.test.js +281 -0
  235. package/server/lib/session-turn-pages/session-message-projection.d.ts +13 -0
  236. package/server/lib/session-turn-pages/session-message-projection.js +281 -0
  237. package/server/lib/small-model/DOCUMENTATION.md +101 -147
  238. package/server/lib/small-model/catalog.js +31 -51
  239. package/server/lib/small-model/catalog.test.js +65 -10
  240. package/server/lib/small-model/custom-api.js +68 -0
  241. package/server/lib/small-model/custom-api.test.js +64 -0
  242. package/server/lib/small-model/index.js +124 -240
  243. package/server/lib/small-model/resolve.js +35 -120
  244. package/server/lib/small-model/resolve.test.js +20 -110
  245. package/server/lib/small-model/routes.js +1 -3
  246. package/server/lib/small-model/summary-settings.test.js +157 -253
  247. package/server/opencode-proxy.test.js +2 -0
  248. package/dist/assets/AssistantView-BTCkm8yk.js +0 -3
  249. package/dist/assets/Icon-DD-BuMfZ.js +0 -1
  250. package/dist/assets/MarkdownRenderer-bc_p1H7l.js +0 -2
  251. package/dist/assets/MarkstreamRendererImpl-CnkodGJW.js +0 -3
  252. package/dist/assets/MobileSurface-BXLeiZ4d.js +0 -2
  253. package/dist/assets/OverlayScrollbar-pMwxaCca.js +0 -2
  254. package/dist/assets/SettingsView-B5I0KDTZ.js +0 -25
  255. package/dist/assets/TerminalView-Dh9MWCpS.js +0 -8
  256. package/dist/assets/appThemeRegistry-COGfxTFV.js +0 -1
  257. package/dist/assets/es-c52LTHHe.js +0 -15
  258. package/dist/assets/fileMentionTouchSelection-BvrNCroG.js +0 -4
  259. package/dist/assets/fr-PisFbUE8.js +0 -13
  260. package/dist/assets/gitApi-ButwzQ0N.js +0 -9
  261. package/dist/assets/insertionBoundaries-DGs7S5zv.js +0 -1
  262. package/dist/assets/ja-C8Ikun3Z.js +0 -15
  263. package/dist/assets/ko-ClFys5o2.js +0 -15
  264. package/dist/assets/main-Dn9rpsgl.js +0 -169
  265. package/dist/assets/main-qSeeJRW1.css +0 -1
  266. package/dist/assets/markdown-worker-CptoICmq.js +0 -2
  267. package/dist/assets/markdownTheme-DOfGg-A7.js +0 -1
  268. package/dist/assets/miniChat-BpjxVqgg.js +0 -2
  269. package/dist/assets/mobile-nMJCaNq1.js +0 -2
  270. package/dist/assets/multirun-BvSG94mW.js +0 -4
  271. package/dist/assets/persistence-Ce3AnpEr.js +0 -3
  272. package/dist/assets/pl-CnaKWN5F.js +0 -15
  273. package/dist/assets/platform-CWTIp02q.js +0 -4
  274. package/dist/assets/projectMeta-DOe49dM_.js +0 -3
  275. package/dist/assets/pt-BR-s8OpfRI6.js +0 -15
  276. package/dist/assets/renderElectronMiniChatApp-R6dKh79f.js +0 -2
  277. package/dist/assets/renderMobileApp-y-S8y8v_.js +0 -28
  278. package/dist/assets/runtime-fetch-zIalUnbb.js +0 -1
  279. package/dist/assets/runtimeEndpointReset-B6BxhvR0.js +0 -147
  280. package/dist/assets/sessionChangeSummary-VVBlEXuh.js +0 -2
  281. package/dist/assets/src-BzX5UI-c.css +0 -2
  282. package/dist/assets/store-DJczmczv.js +0 -15
  283. package/dist/assets/textarea-3XR8sUfy.js +0 -1
  284. package/dist/assets/uk-CaX0iqkB.js +0 -15
  285. package/dist/assets/useDesktopWindowControlsLayout-B1GX59-L.js +0 -1
  286. package/dist/assets/useEffectiveDirectory-CN6JoCFw.js +0 -1
  287. package/dist/assets/useMobileNavigationStore-BVjFtXSy.js +0 -414
  288. package/dist/assets/useSessionAutoCleanup-BLjymaFt.js +0 -2
  289. package/dist/assets/useUIStore-Bxv3f23A.js +0 -1
  290. package/dist/assets/vendor-opencode-client-BOe2qpQb.js +0 -12
  291. package/dist/assets/zh-CN-20MYEjha.js +0 -15
  292. package/dist/assets/zh-TW-DiN04qTw.js +0 -15
  293. package/server/lib/small-model/call.js +0 -427
  294. package/server/lib/small-model/call.test.js +0 -382
  295. package/server/lib/small-model/opencode-session.js +0 -183
  296. package/server/lib/small-model/opencode-session.test.js +0 -147
@@ -0,0 +1,141 @@
1
+ # Browser Provider Host
2
+
3
+ ## Purpose
4
+
5
+ An installed extension can declare that it provides `browser`. Agent `browser.*`
6
+ actions then go to that extension, and the host adopts the extension's result.
7
+ This fork has no `packages/sdk` and no guest process platform, so this module is
8
+ the minimal host: recognize the declaration, list reachable providers, send one
9
+ action to the selected provider, and refuse when none is selected.
10
+
11
+ It does not render a settings dropdown, a live rail, or takeover / handoff.
12
+ Those belong to a later surface that calls this contract.
13
+
14
+ ## What this fork does not do
15
+
16
+ - It does not spawn extension processes, approve capabilities, or proxy a guest panel.
17
+ - It does not drive the in-app browser pane. No provider means the action is
18
+ refused. The pane's existing behavior is unchanged because this module never
19
+ calls it.
20
+ - It does not persist the installed-extension catalog. Registration is
21
+ in-process. The selected id is persisted; the extension must be registered
22
+ again after a restart before that id is listed or used.
23
+ - It does not write `browser.capture` images to disk. The extension's `data` is
24
+ returned as-is.
25
+
26
+ ## Declaration
27
+
28
+ An extension declares the role with the first array among:
29
+
30
+ 1. `service.provides`
31
+ 2. `provides`
32
+ 3. `contributes.service.provides`
33
+
34
+ `"browser"` in that array is the declaration. A service array that does not
35
+ include `browser` is not a browser provider, even if another field says it is.
36
+ The host recognizes that declaration with `extensionDeclaresBrowser`.
37
+
38
+ Registration is in-process, not an open HTTP install route (an endpoint field
39
+ would otherwise be a client-controlled fetch):
40
+
41
+ ```js
42
+ getBrowserProviderHost().install({
43
+ id: 'server-chrome',
44
+ name: 'Server Chrome',
45
+ service: { provides: ['browser'] },
46
+ answer: async (request) => ({ ok: true, data: { url, title } }),
47
+ // or endpoint: 'http://127.0.0.1:9' → POST /browser-control
48
+ });
49
+ ```
50
+
51
+ `answer` wins when both are set. `endpoint` must be loopback `http:` or
52
+ `https:` with no userinfo. A bare origin is posted to `/browser-control`.
53
+ A disabled extension, a missing id, or a declaration with no way to answer is
54
+ rejected and not listed.
55
+
56
+ ## Agent action
57
+
58
+ `host.dispatchAgentBrowserAction({ action, parameters, context, providerId, signal })`
59
+ is the agent entry. `providerId` overrides the stored selection for that call
60
+ only. Otherwise the stored selection is used. The model does not supply
61
+ `context`; callers pass `{ directory, sessionId }`, each `null` when unknown.
62
+
63
+ The extension receives:
64
+
65
+ ```json
66
+ {
67
+ "requestId": "…",
68
+ "action": "browser.click",
69
+ "parameters": { "selector": "#save" },
70
+ "context": { "directory": "/repo", "sessionId": "ses_1" }
71
+ }
72
+ ```
73
+
74
+ It answers `{ "ok": true, "data": { } }` or `{ "ok": false, "error": "…" }`.
75
+ `data` is what the agent gets. `ok: false` is the agent-visible error, not an
76
+ empty success. A missing, non-JSON, or non-200 answer is unknown page state.
77
+ A call that was sent and then lost may have run; a call that never left the
78
+ host did not.
79
+
80
+ Actions: `browser.open`, `browser.snapshot`, `browser.click`, `browser.type`,
81
+ `browser.scroll`, `browser.back`, `browser.forward`, `browser.inspect`,
82
+ `browser.capture`, `browser.resize`. Parameters are validated before send.
83
+ `browser.open` waits 45s; other actions wait 20s.
84
+
85
+ ## HTTP contract
86
+
87
+ Registered on the web server before the generic OpenCode proxy. The global
88
+ `/api` auth gate already covers these routes; this module does not add a second
89
+ one. Web, Electron
90
+ (in-process server), hosted mobile, and Capacitor mobile use these routes.
91
+ VS Code returns **501** `{ ok: false, code: "UNSUPPORTED", error }` for the
92
+ same paths and does not proxy them. That is not an empty catalog.
93
+
94
+ Settings and the rail call the first two routes. Provider ids match
95
+ `^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$`. `surface` is always `false` here: this
96
+ host does not push a live picture, and reporting `true` would open a socket
97
+ that does not exist.
98
+
99
+ | Call | Success | Refusal |
100
+ |---|---|---|
101
+ | `GET /api/browser-providers` | `200 { providers: [{ id, name, surface: false }], selectedId }` | `503 { error, code: "CATALOG_UNAVAILABLE" }` — never `providers: []` |
102
+ | `PUT /api/browser-providers/selection` body `{ id }` | `200 { selectedId }` naming that same id | `404 PROVIDER_NOT_FOUND`, `400 INVALID_SELECTION`, or `503 SELECTION_UNAVAILABLE`; selection unchanged |
103
+ | `POST /api/browser-providers/actions` body `{ action, parameters?, context?, providerId? }` | `200 { ok: true, providerId, data }` | `ok: false` plus `code` — never `{ ok: true, data: {} }` |
104
+
105
+ `selectedId` is `null` unless it names a provider in `providers`. A saved id
106
+ for an extension that is not installed right now is kept on disk but not
107
+ reported as connected. The selection file is
108
+ `browser-provider-selection.json` under the OpenChamber data dir and stores
109
+ only `{ selectedId }`. A missing file is no selection. A read or write failure
110
+ is not an empty catalog and does not change the previous choice.
111
+
112
+ No endpoint, handler, or credential is returned.
113
+
114
+ Action codes:
115
+
116
+ | code | status | meaning |
117
+ |---|---|---|
118
+ | `NO_PROVIDER` | 409 | nothing selected; nothing ran |
119
+ | `PROVIDER_NOT_FOUND` | 404 | id is not a reachable provider; nothing ran |
120
+ | `CATALOG_UNAVAILABLE` | 503 | catalog read failed; nothing ran |
121
+ | `INVALID_ACTION` | 400 | action or parameters rejected; nothing sent |
122
+ | `PROVIDER_REJECTED` | 400 | extension `ok: false`; `error` is the extension text |
123
+ | `UNKNOWN_RESULT` | 502 | answer was not a browser result; page state unknown |
124
+ | `REQUEST_FAILED` | 504 | sent, no usable answer; may have run |
125
+ | `PROVIDER_UNAVAILABLE` | 503 | never sent |
126
+ | `CANCELLED` | 499 | cancelled before send |
127
+ | `UNSUPPORTED` | 501 | VS Code only |
128
+
129
+ An empty `providers` array with status 200 means the catalog was read and no
130
+ reachable browser provider is installed. That is not a connected browser.
131
+
132
+ ## Invariants
133
+
134
+ - No selected provider does not fall through to the in-app pane and does not
135
+ return success.
136
+ - A catalog read failure is not "no providers" and does not run the action.
137
+ - The extension is re-read from the catalog on every action. A stale selection
138
+ that is no longer listed fails; it is not silently retargeted.
139
+ - Choosing an unknown id does not clear a previous valid selection.
140
+ - Failure after the request was handed to the extension is not described as
141
+ "nothing changed".
@@ -0,0 +1,312 @@
1
+ /**
2
+ * Narrow contract for an installed extension that answers agent browser actions.
3
+ *
4
+ * This fork has no guest SDK and no `service.provides` platform. A declaration
5
+ * is recognized when `service.provides`, top-level `provides`, or
6
+ * `contributes.service.provides` includes `"browser"`. The first of those that
7
+ * is an array wins, so a service contribution is not overridden by a looser
8
+ * field. The host posts the same envelope upstream posts to `POST /browser-control`.
9
+ */
10
+
11
+ const BROWSER_PROVIDER_ROLE = 'browser';
12
+
13
+ /** Path on an extension loopback the host posts every action to. */
14
+ export const BROWSER_PROVIDER_PATH = '/browser-control';
15
+
16
+ export const BROWSER_PROVIDERS_ROUTE = '/api/browser-providers';
17
+ export const BROWSER_PROVIDER_SELECTION_ROUTE = '/api/browser-providers/selection';
18
+ export const BROWSER_PROVIDER_ACTIONS_ROUTE = '/api/browser-providers/actions';
19
+
20
+ /** Same id rule the settings catalog parser accepts. */
21
+ const PROVIDER_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
22
+ const PROVIDER_NAME_MAX = 200;
23
+
24
+ const BROWSER_CONTROL_ACTIONS = [
25
+ 'browser.open',
26
+ 'browser.snapshot',
27
+ 'browser.click',
28
+ 'browser.type',
29
+ 'browser.scroll',
30
+ 'browser.back',
31
+ 'browser.forward',
32
+ 'browser.inspect',
33
+ 'browser.capture',
34
+ 'browser.resize',
35
+ ];
36
+
37
+ const BROWSER_VIEWPORT_MODES = ['mobile', 'tablet', 'desktop', 'fill'];
38
+ const BROWSER_SCROLL_DIRECTIONS = ['up', 'down', 'top', 'bottom'];
39
+
40
+ export const BROWSER_PROVIDER_OPEN_TIMEOUT_MS = 45_000;
41
+ export const BROWSER_PROVIDER_ACTION_TIMEOUT_MS = 20_000;
42
+ /** A screenshot is the largest answer the host will accept. */
43
+ export const BROWSER_PROVIDER_RESPONSE_MAX = 12_000_000;
44
+
45
+ const CONTROL_ACTIONS = new Set(BROWSER_CONTROL_ACTIONS);
46
+
47
+ export class BrowserProviderError extends Error {
48
+ /**
49
+ * @param {string} message
50
+ * @param {{ code: string, status: number }} details
51
+ */
52
+ constructor(message, { code, status }) {
53
+ super(message);
54
+ this.name = 'BrowserProviderError';
55
+ this.code = code;
56
+ this.status = status;
57
+ }
58
+ }
59
+
60
+ const isBrowserControlAction = (value) => (
61
+ typeof value === 'string' && CONTROL_ACTIONS.has(value)
62
+ );
63
+
64
+ /**
65
+ * Roles an extension claims. `null` means the object does not declare a
66
+ * provides list at all — that is not the same as declaring an empty one.
67
+ *
68
+ * @param {unknown} declaration
69
+ * @returns {unknown[] | null}
70
+ */
71
+ const readDeclaredProvides = (declaration) => {
72
+ if (!declaration || typeof declaration !== 'object') return null;
73
+ const record = /** @type {{ service?: { provides?: unknown }, provides?: unknown, contributes?: { service?: { provides?: unknown } } }} */ (declaration);
74
+ if (Array.isArray(record.service?.provides)) return record.service.provides;
75
+ if (Array.isArray(record.provides)) return record.provides;
76
+ if (Array.isArray(record.contributes?.service?.provides)) return record.contributes.service.provides;
77
+ return null;
78
+ };
79
+
80
+ /** Whether this declaration says the extension provides the browser role. */
81
+ export const extensionDeclaresBrowser = (declaration) => {
82
+ const provides = readDeclaredProvides(declaration);
83
+ return Boolean(provides?.some((role) => role === BROWSER_PROVIDER_ROLE));
84
+ };
85
+
86
+ /**
87
+ * @param {string} endpoint
88
+ * @returns {string | null} Absolute loopback URL, or null when it must not be called.
89
+ */
90
+ export const resolveBrowserProviderEndpoint = (endpoint) => {
91
+ if (typeof endpoint !== 'string' || endpoint.length === 0) return null;
92
+ let parsed;
93
+ try {
94
+ parsed = new URL(endpoint);
95
+ } catch {
96
+ return null;
97
+ }
98
+ if (parsed.username || parsed.password) return null;
99
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') return null;
100
+ const host = parsed.hostname.replace(/^\[|\]$/g, '');
101
+ if (host !== '127.0.0.1' && host !== 'localhost' && host !== '::1') return null;
102
+ if (parsed.pathname === '/' || parsed.pathname === '') {
103
+ parsed.pathname = BROWSER_PROVIDER_PATH;
104
+ }
105
+ return parsed.toString();
106
+ };
107
+
108
+ export const readBrowserProviderId = (value) => (
109
+ typeof value === 'string' && PROVIDER_ID_PATTERN.test(value) ? value : null
110
+ );
111
+
112
+ const readProviderName = (value, id) => {
113
+ if (typeof value !== 'string') return id;
114
+ const trimmed = value.trim();
115
+ if (!trimmed || trimmed.length > PROVIDER_NAME_MAX) return null;
116
+ return trimmed;
117
+ };
118
+
119
+ /**
120
+ * Public row for settings and the rail. Transport details stay on the host.
121
+ * A declaration the host cannot reach is not listed: offering it would look
122
+ * like a connected browser that cannot answer.
123
+ *
124
+ * @param {unknown} record
125
+ * @returns {{ id: string, name: string, surface: false } | null}
126
+ */
127
+ export const toPublicBrowserProvider = (record) => {
128
+ if (!record || typeof record !== 'object') return null;
129
+ const row = /** @type {{ id?: unknown, name?: unknown, enabled?: unknown, answer?: unknown, endpoint?: unknown }} */ (record);
130
+ if (row.enabled === false) return null;
131
+ if (!extensionDeclaresBrowser(record)) return null;
132
+ const id = readBrowserProviderId(row.id);
133
+ if (!id) return null;
134
+ const name = readProviderName(row.name, id);
135
+ if (!name) return null;
136
+ const endpoint = typeof row.endpoint === 'string' ? resolveBrowserProviderEndpoint(row.endpoint) : null;
137
+ if (typeof row.answer !== 'function' && !endpoint) return null;
138
+ // This host does not push frames. A true surface would make the rail open a
139
+ // picture that does not exist.
140
+ return { id, name, surface: false };
141
+ };
142
+
143
+ /**
144
+ * @param {unknown} value
145
+ * @returns {{ directory: string | null, sessionId: string | null }}
146
+ */
147
+ export const readBrowserProviderContext = (value) => {
148
+ const record = value && typeof value === 'object'
149
+ ? /** @type {{ directory?: unknown, sessionId?: unknown }} */ (value)
150
+ : {};
151
+ const directory = record.directory;
152
+ const sessionId = record.sessionId;
153
+ return {
154
+ directory: typeof directory === 'string' && directory.length > 0 ? directory : null,
155
+ sessionId: typeof sessionId === 'string' && sessionId.length > 0 ? sessionId : null,
156
+ };
157
+ };
158
+
159
+ /**
160
+ * Extension-side read of the body the host posts. `null` is not a browser action.
161
+ *
162
+ * @param {string} body
163
+ */
164
+ export const readBrowserProviderRequest = (body) => {
165
+ let wire;
166
+ try {
167
+ wire = JSON.parse(body);
168
+ } catch {
169
+ return null;
170
+ }
171
+ if (!wire || typeof wire !== 'object' || Array.isArray(wire)) return null;
172
+ const { requestId, action, parameters, context } = wire;
173
+ if (typeof requestId !== 'string' || requestId.length === 0) return null;
174
+ if (!isBrowserControlAction(action)) return null;
175
+ if (!parameters || typeof parameters !== 'object' || Array.isArray(parameters)) return null;
176
+ return {
177
+ requestId,
178
+ action,
179
+ parameters,
180
+ context: readBrowserProviderContext(context),
181
+ };
182
+ };
183
+
184
+ /**
185
+ * Host-side read of an extension answer. `null` is not a browser result.
186
+ *
187
+ * @param {unknown} value
188
+ * @returns {{ ok: true, data: Record<string, unknown> } | { ok: false, error: string } | null}
189
+ */
190
+ export const parseBrowserProviderResult = (value) => {
191
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
192
+ const row = /** @type {{ ok?: unknown, data?: unknown, error?: unknown }} */ (value);
193
+ if (row.ok === true) {
194
+ if (!row.data || typeof row.data !== 'object' || Array.isArray(row.data)) return null;
195
+ return { ok: true, data: /** @type {Record<string, unknown>} */ (row.data) };
196
+ }
197
+ if (row.ok === false) {
198
+ if (typeof row.error !== 'string' || row.error.length === 0 || row.error.length > 2_000) return null;
199
+ return { ok: false, error: row.error };
200
+ }
201
+ return null;
202
+ };
203
+
204
+ const invalidAction = (message) => new BrowserProviderError(message, { code: 'INVALID_ACTION', status: 400 });
205
+
206
+ const readOptionalString = (value) => (typeof value === 'string' && value.trim().length > 0 ? value.trim() : null);
207
+
208
+ const readViewport = (value, required) => {
209
+ const viewport = readOptionalString(value);
210
+ if (!viewport) {
211
+ if (required) throw invalidAction('viewport is required for browser.resize. Nothing was sent.');
212
+ return undefined;
213
+ }
214
+ if (!BROWSER_VIEWPORT_MODES.includes(viewport)) {
215
+ throw invalidAction('viewport must be mobile, tablet, desktop, or fill. Nothing was sent.');
216
+ }
217
+ return viewport;
218
+ };
219
+
220
+ /**
221
+ * Validates an agent action before it is sent. The object returned is the
222
+ * only parameters the extension sees.
223
+ *
224
+ * @param {unknown} action
225
+ * @param {unknown} parameters
226
+ * @returns {{ action: string, parameters: Record<string, unknown> }}
227
+ */
228
+ export const normalizeBrowserAction = (action, parameters) => {
229
+ if (!isBrowserControlAction(action)) {
230
+ throw invalidAction('Unknown browser action. Nothing was sent.');
231
+ }
232
+ const input = parameters == null ? {} : parameters;
233
+ if (!input || typeof input !== 'object' || Array.isArray(input)) {
234
+ throw invalidAction('parameters must be an object. Nothing was sent.');
235
+ }
236
+ const source = /** @type {Record<string, unknown>} */ (input);
237
+ /** @type {Record<string, unknown>} */
238
+ const next = {};
239
+
240
+ if (action === 'browser.resize') {
241
+ next.viewport = readViewport(source.viewport, true);
242
+ }
243
+
244
+ if (action === 'browser.capture') {
245
+ const label = readOptionalString(source.label);
246
+ if (label) next.label = label;
247
+ }
248
+
249
+ if (action === 'browser.open') {
250
+ const viewport = readViewport(source.viewport, false);
251
+ if (viewport) next.viewport = viewport;
252
+ const url = readOptionalString(source.url);
253
+ if (!url) throw invalidAction('url is required for browser.open. Nothing was sent.');
254
+ let parsed;
255
+ try {
256
+ parsed = new URL(url);
257
+ } catch {
258
+ throw invalidAction('url must be an absolute http(s) URL. Nothing was sent.');
259
+ }
260
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
261
+ throw invalidAction('url must use http or https. Nothing was sent.');
262
+ }
263
+ next.url = parsed.toString();
264
+ }
265
+
266
+ if (action === 'browser.click') {
267
+ const selector = readOptionalString(source.selector);
268
+ const text = readOptionalString(source.text);
269
+ if (!selector && !text) {
270
+ throw invalidAction('browser.click requires selector or text. Nothing was sent.');
271
+ }
272
+ if (selector) next.selector = selector;
273
+ if (text) next.text = text;
274
+ }
275
+
276
+ if (action === 'browser.snapshot') {
277
+ const selector = readOptionalString(source.selector);
278
+ if (selector) next.selector = selector;
279
+ }
280
+
281
+ if (action === 'browser.inspect') {
282
+ const selector = readOptionalString(source.selector);
283
+ if (!selector) throw invalidAction('selector is required for browser.inspect. Nothing was sent.');
284
+ next.selector = selector;
285
+ }
286
+
287
+ if (action === 'browser.type') {
288
+ const selector = readOptionalString(source.selector);
289
+ if (!selector) throw invalidAction('selector is required for browser.type. Nothing was sent.');
290
+ if (typeof source.value !== 'string') {
291
+ throw invalidAction('value is required for browser.type. Nothing was sent.');
292
+ }
293
+ next.selector = selector;
294
+ next.value = source.value;
295
+ next.submit = source.submit === true;
296
+ }
297
+
298
+ if (action === 'browser.scroll') {
299
+ const selector = readOptionalString(source.selector);
300
+ const direction = readOptionalString(source.direction);
301
+ if (!selector && !direction) {
302
+ throw invalidAction('browser.scroll requires direction or selector. Nothing was sent.');
303
+ }
304
+ if (direction && !BROWSER_SCROLL_DIRECTIONS.includes(direction)) {
305
+ throw invalidAction('direction must be up, down, top, or bottom. Nothing was sent.');
306
+ }
307
+ if (selector) next.selector = selector;
308
+ if (direction) next.direction = direction;
309
+ }
310
+
311
+ return { action, parameters: next };
312
+ };