@vantran-se/openchamber-web 1.24.4 → 2.0.2

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 (367) hide show
  1. package/README.md +2 -8
  2. package/bin/lib/commands-connect-url.js +3 -8
  3. package/bin/lib/commands-tunnel.js +3 -0
  4. package/dist/assets/{CodeMirrorCommentWidgets-B4a0JkUz.js → CodeMirrorCommentWidgets-B82Oye3V.js} +1 -1
  5. package/dist/assets/DiffView-DJLmWn6Z.js +4 -0
  6. package/dist/assets/FilesView-DjksmZRo.js +17 -0
  7. package/dist/assets/{GitView-Byu2DY7s.js → GitView-DiUsUIgr.js} +4 -4
  8. package/dist/assets/{GuestAttachDialog-B808E0-d.js → GuestAttachDialog-CYwWYLr3.js} +1 -1
  9. package/dist/assets/GuestSurfacePane-CmctyRI4.js +1 -0
  10. package/dist/assets/{JsonTreeView-VW_7czo3.js → JsonTreeView-DOHHcbgJ.js} +1 -1
  11. package/dist/assets/LinearIssuesView-DaqnylcV.js +1 -0
  12. package/dist/assets/MarkdownImageGallery-HJNvWt7L.js +1 -0
  13. package/dist/assets/MarkdownRendererImpl-nDZRGg2I.js +12 -0
  14. package/dist/assets/OnboardingScreen-C2B5Bsof.js +1 -0
  15. package/dist/assets/{PatchDiff-DCjGhlYV.js → PatchDiff-C4FLBjpt.js} +1 -1
  16. package/dist/assets/{PierreDiffViewer-CEDZNkU2.js → PierreDiffViewer-C_1S9c9n.js} +1 -1
  17. package/dist/assets/PlanView-_eZvb-G5.js +5 -0
  18. package/dist/assets/PluginPane-sM8fcZIV.js +3 -0
  19. package/dist/assets/SettingsView-BRfr46hb.js +29 -0
  20. package/dist/assets/SettingsWindow-CcUDwQHg.js +1 -0
  21. package/dist/assets/SubmoduleDiffSummary-CrN1ZGwl.js +1 -0
  22. package/dist/assets/ToolOutputDialog-Cij4jI09.js +15 -0
  23. package/dist/assets/ToolPartDiffPreview-BXzwQ-JY.js +8 -0
  24. package/dist/assets/{VirtualizedFileDiff-NU0FHFhT.js → VirtualizedFileDiff-DSWTLJNH.js} +4 -4
  25. package/dist/assets/{WalkthroughView-BZyjhqHv.js → WalkthroughView-CpUUgY8H.js} +2 -2
  26. package/dist/assets/appThemeRegistry-B5tHTkR8.js +1 -0
  27. package/dist/assets/{commitAndPush-BDftrjr6.js → commitAndPush-D7D8qV8p.js} +1 -1
  28. package/dist/assets/{computeVirtualFileMetrics-DlWlEM3x.js → computeVirtualFileMetrics-cq9uQ30i.js} +1 -1
  29. package/dist/assets/de-CUO1-KqF.js +10 -0
  30. package/dist/assets/es-BlbC6sGJ.js +16 -0
  31. package/dist/assets/fr-D2nwe_0W.js +14 -0
  32. package/dist/assets/index-BQOoYIOu.js +1 -0
  33. package/dist/assets/index-CLcM8HKx.css +1 -0
  34. package/dist/assets/index-CvLgnuNI.js +41 -0
  35. package/dist/assets/{isDiffPlainText-CO4e5J_S.js → isDiffPlainText-D0obJ2p8.js} +1 -1
  36. package/dist/assets/{isFilePlainText-ue6kCou7.js → isFilePlainText-DB3_rjyW.js} +1 -1
  37. package/dist/assets/ja-CA7vnDsZ.js +16 -0
  38. package/dist/assets/ko-Chy6a9l-.js +16 -0
  39. package/dist/assets/main-B9JFK3vF.js +896 -0
  40. package/dist/assets/main-Bhiqnrhd.js +2 -0
  41. package/dist/assets/{markdownCore-CK48LG0z.js → markdownCore-tfqOFaBd.js} +4 -4
  42. package/dist/assets/miniChat-B0UOL5zA.js +2 -0
  43. package/dist/assets/mobile-BJppFIEo.js +2 -0
  44. package/dist/assets/{patchFileDiff-D7QVlovD.js → patchFileDiff-Db6KuwCP.js} +3 -3
  45. package/dist/assets/pl-CTR5A_1P.js +16 -0
  46. package/dist/assets/pt-BR-BE1O1KEx.js +16 -0
  47. package/dist/assets/renderElectronMiniChatApp-Yz5sfoFh.js +2 -0
  48. package/dist/assets/renderMobileApp-QQ-yB8aY.js +25 -0
  49. package/dist/assets/runtimeEndpointReset-DKDdzRzm.js +27 -0
  50. package/dist/assets/scheduledTasksApi-C5sCuNqf.js +1 -0
  51. package/dist/assets/{shikiHighlight-BPu3JiQs.js → shikiHighlight-xURoo5t_.js} +1 -1
  52. package/dist/assets/{textMateThemeFromAppTheme-DWqRPo9A.js → textMateThemeFromAppTheme-BZLGuYTU.js} +1 -1
  53. package/dist/assets/tr-Bh5vK6cW.js +7 -0
  54. package/dist/assets/uk-CLDkZFy_.js +16 -0
  55. package/dist/assets/useAppFontEffects-BxrtCnfR.js +7337 -0
  56. package/dist/assets/{useInlineCommentController-CgS0fnw_.js → useInlineCommentController-CeXuR-SR.js} +2 -2
  57. package/dist/assets/useRootScrollLock-DgWxezc1.js +2 -0
  58. package/dist/assets/{vendor-codemirror-language-data-Bx2EHc1O.js → vendor-codemirror-language-data-DOuaQUZ0.js} +2 -2
  59. package/dist/assets/vendor-cron-parser-CjvYVigk.js +2 -0
  60. package/dist/assets/vendor-opencode-client-_BAHQo4R.js +12 -0
  61. package/dist/assets/{vendor-remixicon-react-BDmGmFCA.js → vendor-remixicon-react-D6dZ0UwF.js} +1 -1
  62. package/dist/assets/{vendor-zod-CgAdXWZO.js → vendor-zod-Gea9uBlY.js} +1 -1
  63. package/dist/assets/{web-components-DxX8bZ1o.js → web-components-63H1h21j.js} +2 -2
  64. package/dist/assets/zh-CN-DODgcmYj.js +16 -0
  65. package/dist/assets/zh-TW-ByNfIwLA.js +16 -0
  66. package/dist/index.html +13 -7
  67. package/dist/mini-chat.html +5 -5
  68. package/dist/mobile.html +5 -5
  69. package/package.json +6 -5
  70. package/server/index.d.ts +1 -0
  71. package/server/index.js +246 -91
  72. package/server/lib/agent-tool/DOCUMENTATION.md +32 -15
  73. package/server/lib/agent-tool/runtime.js +135 -54
  74. package/server/lib/agent-tool/runtime.test.js +155 -87
  75. package/server/lib/browser-control/DOCUMENTATION.md +37 -2
  76. package/server/lib/browser-control/provider.js +217 -0
  77. package/server/lib/browser-control/provider.test.js +251 -0
  78. package/server/lib/context-obligatory/runtime.js +61 -50
  79. package/server/lib/context-obligatory/runtime.test.js +62 -162
  80. package/server/lib/event-stream/DOCUMENTATION.md +12 -1
  81. package/server/lib/event-stream/delta-coalescer.js +0 -0
  82. package/server/lib/event-stream/delta-coalescer.test.js +68 -57
  83. package/server/lib/event-stream/directory-ws-bridge.js +1 -1
  84. package/server/lib/event-stream/global-hub.js +10 -1
  85. package/server/lib/event-stream/global-hub.test.js +27 -26
  86. package/server/lib/event-stream/index.js +6 -0
  87. package/server/lib/event-stream/protocol.js +26 -19
  88. package/server/lib/event-stream/protocol.test.js +18 -7
  89. package/server/lib/event-stream/rebind.test.js +5 -5
  90. package/server/lib/event-stream/runtime.test.js +17 -15
  91. package/server/lib/event-stream/translate-v2.js +313 -0
  92. package/server/lib/event-stream/translate-v2.test.js +209 -0
  93. package/server/lib/event-stream/upstream-reader.test.js +16 -16
  94. package/server/lib/fs/DOCUMENTATION.md +4 -2
  95. package/server/lib/fs/byte-range.js +35 -0
  96. package/server/lib/fs/byte-range.test.js +31 -0
  97. package/server/lib/fs/routes.js +47 -42
  98. package/server/lib/fs/routes.test.js +123 -18
  99. package/server/lib/git/service.js +12 -0
  100. package/server/lib/github/DOCUMENTATION.md +1 -1
  101. package/server/lib/github/pr-status.js +22 -2
  102. package/server/lib/github/pr-status.test.js +27 -14
  103. package/server/lib/guests/DOCUMENTATION.md +13 -0
  104. package/server/lib/guests/catalog.js +12 -0
  105. package/server/lib/guests/catalog.test.js +33 -0
  106. package/server/lib/guests/routes.js +10 -1
  107. package/server/lib/guests/service.js +192 -14
  108. package/server/lib/guests/service.test.js +201 -1
  109. package/server/lib/guests/surface.js +630 -0
  110. package/server/lib/guests/surface.test.js +486 -0
  111. package/server/lib/markdown-image-grants/routes.js +18 -5
  112. package/server/lib/markdown-image-grants/routes.test.js +9 -6
  113. package/server/lib/message-queue/DOCUMENTATION.md +33 -22
  114. package/server/lib/message-queue/runtime.js +155 -100
  115. package/server/lib/message-queue/runtime.test.js +168 -82
  116. package/server/lib/notifications/DOCUMENTATION.md +28 -0
  117. package/server/lib/notifications/control-stream.http.test.js +68 -0
  118. package/server/lib/notifications/emitter-runtime.js +15 -0
  119. package/server/lib/notifications/emitter-runtime.test.js +32 -0
  120. package/server/lib/notifications/push-runtime.js +5 -1
  121. package/server/lib/notifications/push-runtime.test.js +2 -2
  122. package/server/lib/notifications/routes.js +1 -1
  123. package/server/lib/notifications/runtime.js +115 -26
  124. package/server/lib/notifications/template-runtime.js +13 -15
  125. package/server/lib/notifications/template-runtime.test.js +16 -9
  126. package/server/lib/notifications/trigger-runtime.test.js +187 -0
  127. package/server/lib/openchamber-control/DOCUMENTATION.md +10 -0
  128. package/server/lib/openchamber-control/actions.js +3 -2
  129. package/server/lib/openchamber-control/error.js +1 -1
  130. package/server/lib/openchamber-control/file-open.js +59 -0
  131. package/server/lib/openchamber-control/file-open.test.js +57 -0
  132. package/server/lib/openchamber-control/routes.test.js +12 -0
  133. package/server/lib/openchamber-control/service.js +102 -49
  134. package/server/lib/openchamber-control/service.test.js +84 -44
  135. package/server/lib/openchamber-sessions/archive-store.js +224 -0
  136. package/server/lib/openchamber-sessions/archive-store.test.js +187 -0
  137. package/server/lib/openchamber-sessions/opencode-client.js +22 -0
  138. package/server/lib/openchamber-sessions/routes.js +398 -328
  139. package/server/lib/openchamber-sessions/routes.test.js +777 -740
  140. package/server/lib/openchamber-sessions/session-metadata-store.js +285 -0
  141. package/server/lib/openchamber-sessions/session-metadata-store.test.js +259 -0
  142. package/server/lib/opencode/DOCUMENTATION.md +449 -29
  143. package/server/lib/opencode/agents.js +233 -327
  144. package/server/lib/opencode/auth.js +41 -44
  145. package/server/lib/opencode/auth.test.js +92 -0
  146. package/server/lib/opencode/cli-upgrade.d.ts +4 -0
  147. package/server/lib/opencode/cli-upgrade.js +18 -0
  148. package/server/lib/opencode/cli-upgrade.test.js +36 -0
  149. package/server/lib/opencode/commands.js +164 -145
  150. package/server/lib/opencode/compatibility.d.ts +16 -0
  151. package/server/lib/opencode/compatibility.js +88 -0
  152. package/server/lib/opencode/compatibility.test.js +62 -0
  153. package/server/lib/opencode/config-entity-routes.js +58 -29
  154. package/server/lib/opencode/config-entity-v2.test.js +290 -0
  155. package/server/lib/opencode/config-mutation-response.js +17 -7
  156. package/server/lib/opencode/config-mutation-response.test.js +4 -7
  157. package/server/lib/opencode/config-paths.test.js +2 -2
  158. package/server/lib/opencode/config-v2.d.ts +183 -0
  159. package/server/lib/opencode/config-v2.js +801 -0
  160. package/server/lib/opencode/config-v2.test.js +466 -0
  161. package/server/lib/opencode/credential-db.js +144 -0
  162. package/server/lib/opencode/credential-db.test.js +104 -0
  163. package/server/lib/opencode/env-runtime.js +48 -9
  164. package/server/lib/opencode/env-runtime.test.js +45 -0
  165. package/server/lib/opencode/feature-routes-runtime.js +15 -5
  166. package/server/lib/opencode/lifecycle-process.test.js +7 -4
  167. package/server/lib/opencode/lifecycle.js +85 -30
  168. package/server/lib/opencode/lifecycle.test.js +80 -8
  169. package/server/lib/opencode/login-shell-env.js +27 -0
  170. package/server/lib/opencode/managed-config-file.js +119 -0
  171. package/server/lib/opencode/managed-config-file.test.js +152 -0
  172. package/server/lib/opencode/managed-plugin-config.js +33 -13
  173. package/server/lib/opencode/mcp.js +43 -149
  174. package/server/lib/opencode/network-runtime.js +6 -7
  175. package/server/lib/opencode/openchamber-routes.test.js +2 -5
  176. package/server/lib/opencode/plugin-routes.js +3 -3
  177. package/server/lib/opencode/plugin-routes.test.js +6 -24
  178. package/server/lib/opencode/plugins.js +91 -40
  179. package/server/lib/opencode/plugins.test.js +28 -15
  180. package/server/lib/opencode/providers.js +63 -83
  181. package/server/lib/opencode/providers.test.js +128 -59
  182. package/server/lib/opencode/proxy-agent-wiring.test.js +4 -2
  183. package/server/lib/opencode/proxy.js +179 -129
  184. package/server/lib/opencode/pwa-manifest-routes.js +4 -2
  185. package/server/lib/opencode/pwa-manifest-routes.test.js +3 -2
  186. package/server/lib/opencode/response-envelope.js +14 -0
  187. package/server/lib/opencode/response-envelope.test.js +23 -0
  188. package/server/lib/opencode/routes-upgrade.test.js +73 -116
  189. package/server/lib/opencode/routes.js +121 -428
  190. package/server/lib/opencode/server-utils-runtime.js +10 -4
  191. package/server/lib/opencode/session-activity.js +78 -0
  192. package/server/lib/opencode/session-runtime.js +21 -10
  193. package/server/lib/opencode/session-runtime.test.js +9 -9
  194. package/server/lib/opencode/settings-helpers.js +9 -9
  195. package/server/lib/opencode/settings-helpers.test.js +7 -12
  196. package/server/lib/opencode/settings-registry.json +6 -8
  197. package/server/lib/opencode/settings-runtime.js +18 -1
  198. package/server/lib/opencode/settings-runtime.test.js +31 -2
  199. package/server/lib/opencode/shared.js +54 -22
  200. package/server/lib/opencode/shared.test.js +8 -4
  201. package/server/lib/opencode/shutdown-runtime.js +54 -7
  202. package/server/lib/opencode/shutdown-runtime.test.js +224 -30
  203. package/server/lib/opencode/skill-routes.js +23 -24
  204. package/server/lib/opencode/skills.js +7 -5
  205. package/server/lib/opencode/tunnel-wiring-runtime.js +2 -1
  206. package/server/lib/opencode/upgrade-capability.test.js +1 -1
  207. package/server/lib/opencode/v1-migration-topup.js +288 -0
  208. package/server/lib/opencode/v1-migration-topup.test.js +266 -0
  209. package/server/lib/opencode/v2-install.d.ts +8 -0
  210. package/server/lib/opencode/v2-install.js +158 -0
  211. package/server/lib/opencode/v2-install.test.js +124 -0
  212. package/server/lib/opencode/watcher.js +10 -3
  213. package/server/lib/opencode/watcher.test.js +17 -21
  214. package/server/lib/opencode/websearch-config.js +66 -0
  215. package/server/lib/package-manager.js +55 -68
  216. package/server/lib/package-manager.test.js +241 -75
  217. package/server/lib/permission-auto-accept/runtime.js +38 -22
  218. package/server/lib/permission-auto-accept/runtime.test.js +39 -17
  219. package/server/lib/quota/DOCUMENTATION.md +4 -0
  220. package/server/lib/quota/providers/xai.js +4 -4
  221. package/server/lib/relay/tunnel-host.js +4 -1
  222. package/server/lib/relay/tunnel-host.test.js +7 -0
  223. package/server/lib/routing/DOCUMENTATION.md +52 -21
  224. package/server/lib/routing/defaults.js +23 -6
  225. package/server/lib/routing/jev.js +17 -7
  226. package/server/lib/routing/routes.http.test.js +68 -33
  227. package/server/lib/routing/routes.js +65 -22
  228. package/server/lib/routing/runtime.js +145 -48
  229. package/server/lib/routing/runtime.test.js +79 -55
  230. package/server/lib/scheduled-tasks/DOCUMENTATION.md +4 -0
  231. package/server/lib/scheduled-tasks/issue-2710-double-execution.test.js +51 -46
  232. package/server/lib/scheduled-tasks/runtime.js +81 -74
  233. package/server/lib/scheduled-tasks/runtime.test.js +53 -1
  234. package/server/lib/session-assist/DOCUMENTATION.md +8 -3
  235. package/server/lib/session-assist/context.js +122 -46
  236. package/server/lib/session-assist/context.test.js +117 -49
  237. package/server/lib/session-assist/runtime.js +52 -22
  238. package/server/lib/session-assist/runtime.test.js +145 -173
  239. package/server/lib/session-goal/DOCUMENTATION.md +23 -15
  240. package/server/lib/session-goal/create.js +9 -12
  241. package/server/lib/session-goal/create.test.js +27 -28
  242. package/server/lib/session-goal/runtime.js +164 -77
  243. package/server/lib/session-goal/runtime.test.js +271 -519
  244. package/server/lib/session-knowledge/DOCUMENTATION.md +5 -0
  245. package/server/lib/session-knowledge/runtime.js +59 -41
  246. package/server/lib/session-knowledge/runtime.test.js +90 -20
  247. package/server/lib/small-model/DOCUMENTATION.md +154 -208
  248. package/server/lib/small-model/client.js +113 -0
  249. package/server/lib/small-model/index.js +296 -158
  250. package/server/lib/small-model/index.test.js +423 -256
  251. package/server/lib/small-model/routes.js +1 -1
  252. package/server/lib/spaces/DOCUMENTATION.md +807 -0
  253. package/server/lib/spaces/code-in-bait.js +185 -0
  254. package/server/lib/spaces/code-in.js +641 -0
  255. package/server/lib/spaces/code-in.test.js +1048 -0
  256. package/server/lib/spaces/errors.js +8 -0
  257. package/server/lib/spaces/exec-http.js +94 -0
  258. package/server/lib/spaces/gatekeeper-channel.js +202 -0
  259. package/server/lib/spaces/gatekeeper-channel.test.js +215 -0
  260. package/server/lib/spaces/gatekeeper-program.cjs +914 -0
  261. package/server/lib/spaces/gatekeeper-program.test.js +1391 -0
  262. package/server/lib/spaces/hardening.js +457 -0
  263. package/server/lib/spaces/hardening.test.js +368 -0
  264. package/server/lib/spaces/host-git.js +127 -0
  265. package/server/lib/spaces/host-git.test.js +137 -0
  266. package/server/lib/spaces/labels.js +234 -0
  267. package/server/lib/spaces/labels.test.js +219 -0
  268. package/server/lib/spaces/layout.js +184 -0
  269. package/server/lib/spaces/layout.test.js +30 -0
  270. package/server/lib/spaces/manager.js +82 -0
  271. package/server/lib/spaces/manager.test.js +114 -0
  272. package/server/lib/spaces/places/code-in.docker.live.test.js +216 -0
  273. package/server/lib/spaces/places/contract-suite.js +156 -0
  274. package/server/lib/spaces/places/contract.docker.live.test.js +7 -0
  275. package/server/lib/spaces/places/contract.test.js +6 -0
  276. package/server/lib/spaces/places/docker-engine.js +126 -0
  277. package/server/lib/spaces/places/docker-live-support.js +202 -0
  278. package/server/lib/spaces/places/docker-tools.js +186 -0
  279. package/server/lib/spaces/places/docker.js +587 -0
  280. package/server/lib/spaces/places/docker.test.js +1610 -0
  281. package/server/lib/spaces/places/escape-suite.js +1018 -0
  282. package/server/lib/spaces/places/escape.docker.live.test.js +7 -0
  283. package/server/lib/spaces/places/fake-docker.js +375 -0
  284. package/server/lib/spaces/places/memory-place.js +103 -0
  285. package/server/lib/spaces/places/packed.docker.live.test.js +77 -0
  286. package/server/lib/spaces/places/registry.js +43 -0
  287. package/server/lib/spaces/places/registry.test.js +50 -0
  288. package/server/lib/spaces/places/server.docker.live.test.js +225 -0
  289. package/server/lib/spaces/run-command.js +261 -0
  290. package/server/lib/spaces/run-command.test.js +396 -0
  291. package/server/lib/spaces/space-server.js +190 -0
  292. package/server/lib/spaces/space-server.test.js +335 -0
  293. package/server/lib/spaces/tools-filler.js +82 -0
  294. package/server/lib/spaces/tools-filler.test.js +148 -0
  295. package/server/lib/spaces/tools-pack.js +49 -0
  296. package/server/lib/spaces/tools-pack.test.js +57 -0
  297. package/server/lib/spaces/tools.js +138 -0
  298. package/server/lib/spaces/tools.test.js +153 -0
  299. package/server/lib/tts/openai-sdk.js +11 -0
  300. package/server/lib/tts/service.js +7 -5
  301. package/server/lib/tts/stt.js +3 -2
  302. package/server/lib/tunnels/DOCUMENTATION.md +2 -0
  303. package/server/lib/tunnels/routes.js +7 -0
  304. package/server/lib/tunnels/routes.test.js +55 -0
  305. package/server/lib/ui-auth/ui-auth.js +38 -2
  306. package/server/lib/ui-auth/ui-auth.test.js +42 -0
  307. package/server/lib/ui-auth/ui-passkeys.js +12 -10
  308. package/server/lib/walkthrough/DOCUMENTATION.md +10 -1
  309. package/server/lib/walkthrough/pull-request.js +61 -8
  310. package/server/lib/walkthrough/pull-request.test.js +54 -1
  311. package/server/lib/walkthrough/routes.js +20 -0
  312. package/server/lib/walkthrough/routes.test.js +19 -0
  313. package/server/opencode-proxy.test.js +190 -166
  314. package/server/opencode-worktree-readiness.test.js +5 -4
  315. package/dist/assets/DiffView-B2CD9wC8.js +0 -4
  316. package/dist/assets/FilesView-C4vMFclc.js +0 -13
  317. package/dist/assets/LinearIssuesView-EzGNCGrd.js +0 -1
  318. package/dist/assets/MarkdownImageGallery-B2YUDFaB.js +0 -1
  319. package/dist/assets/MarkdownRendererImpl-B4ikIzR3.js +0 -12
  320. package/dist/assets/OnboardingScreen-CQHBSTEi.js +0 -1
  321. package/dist/assets/PlanView-D3x-pTkZ.js +0 -5
  322. package/dist/assets/PluginPane-DEcgDQ9h.js +0 -3
  323. package/dist/assets/SettingsView-CSinuH-1.js +0 -29
  324. package/dist/assets/SettingsWindow-B5CHoJFZ.js +0 -1
  325. package/dist/assets/SubmoduleDiffSummary-oxeACCWs.js +0 -1
  326. package/dist/assets/ToolOutputDialog-tLNWmfcW.js +0 -15
  327. package/dist/assets/ToolPartDiffPreview-CflzmK-x.js +0 -8
  328. package/dist/assets/appThemeRegistry-DLicw4dH.js +0 -1
  329. package/dist/assets/de-Bro6_zyl.js +0 -10
  330. package/dist/assets/es-Bb9XQZvd.js +0 -16
  331. package/dist/assets/fr-Dgfzn-IL.js +0 -14
  332. package/dist/assets/index-CtQNmqUL.js +0 -1
  333. package/dist/assets/index-DqaMWNrC.css +0 -1
  334. package/dist/assets/index-Dqc6USl3.js +0 -40
  335. package/dist/assets/ja-BYsakGFh.js +0 -16
  336. package/dist/assets/ko-BSaQz9aN.js +0 -16
  337. package/dist/assets/main-B62_pjL5.js +0 -2
  338. package/dist/assets/main-BEdcOI2P.js +0 -896
  339. package/dist/assets/miniChat-LT8Rqofr.js +0 -2
  340. package/dist/assets/mobile-CqarEynh.js +0 -2
  341. package/dist/assets/pl-OIviPdoj.js +0 -16
  342. package/dist/assets/pt-BR-Cltg-E6s.js +0 -16
  343. package/dist/assets/renderElectronMiniChatApp-COyLTtZx.js +0 -2
  344. package/dist/assets/renderMobileApp-BC1MHg6g.js +0 -25
  345. package/dist/assets/runtimeEndpointReset-DdrazOVH.js +0 -25
  346. package/dist/assets/scheduledTasksApi-B2SWMGEU.js +0 -1
  347. package/dist/assets/tr-CQI-KPre.js +0 -7
  348. package/dist/assets/uk-CqW7R4DA.js +0 -16
  349. package/dist/assets/useAppFontEffects-ne84JBVL.js +0 -7333
  350. package/dist/assets/useRootScrollLock-Cpf1qoGp.js +0 -2
  351. package/dist/assets/vendor-cron-parser-CUcP7OfX.js +0 -2
  352. package/dist/assets/vendor-opencode-sdk-BJ73u-bH.js +0 -7
  353. package/dist/assets/zh-CN-CVZG_o_J.js +0 -16
  354. package/dist/assets/zh-TW-D5ZxzCZp.js +0 -16
  355. package/server/lib/opencode/mcp-oauth-callback.test.js +0 -111
  356. package/server/lib/routing/feature-flag.js +0 -11
  357. package/server/lib/small-model/call.js +0 -851
  358. package/server/lib/small-model/call.test.js +0 -1292
  359. package/server/lib/small-model/catalog.js +0 -13
  360. package/server/lib/small-model/resolve.js +0 -131
  361. package/server/lib/small-model/resolve.test.js +0 -197
  362. package/server/lib/small-model/runtime-providers.js +0 -164
  363. package/server/lib/small-model/runtime-providers.test.js +0 -118
  364. package/server/lib/system-prompt/DOCUMENTATION.md +0 -36
  365. package/server/lib/system-prompt/runtime.js +0 -49
  366. package/server/lib/system-prompt/runtime.test.js +0 -76
  367. package/server/lib/walkthrough/reproduce-2607.test.js +0 -148
@@ -0,0 +1,158 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { createHash } from 'node:crypto';
3
+ import fs from 'node:fs/promises';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { z } from 'zod';
7
+ import { requireOpenCodeV2 } from './compatibility.js';
8
+
9
+ const INSTALL_TIMEOUT_MS = 5 * 60_000;
10
+ const NPM_REGISTRY = 'https://registry.npmjs.org';
11
+
12
+ const releaseSchema = z.object({ version: z.string().regex(/^2\.\d+\.\d+$/) });
13
+ const packageDistSchema = z.object({
14
+ dist: z.object({
15
+ tarball: z.string().url(),
16
+ integrity: z.string().regex(/^sha512-[A-Za-z0-9+/]+={0,2}$/),
17
+ }),
18
+ });
19
+
20
+ // OpenCode's installer is a bash script, so Windows installs the npm platform
21
+ // package that script downloads. x64-baseline serves arm64 too: the native arm64
22
+ // build fails to load (https://github.com/anomalyco/opencode/issues/19130), the
23
+ // same workaround the desktop bundle uses in packages/electron/scripts/prepare-opencode-cli.mjs.
24
+ const WINDOWS_PACKAGE = '@opencode/cli-windows-x64-baseline';
25
+
26
+ export const supportsOpenCodeV2Install = (platform = process.platform) =>
27
+ (platform === 'darwin' || platform === 'linux' || platform === 'win32') && (process.arch === 'x64' || process.arch === 'arm64');
28
+
29
+ const runInstaller = (script, version, env) => new Promise((resolve, reject) => {
30
+ const child = spawn('/bin/bash', [script, '--version', version, '--no-modify-path'], {
31
+ env, cwd: os.tmpdir(), detached: true, stdio: 'ignore',
32
+ });
33
+ const terminate = () => {
34
+ if (!child.pid) return;
35
+ try { process.kill(-child.pid, 'SIGKILL'); } catch { /* Already exited. */ }
36
+ };
37
+ const timer = setTimeout(terminate, INSTALL_TIMEOUT_MS);
38
+ child.once('error', () => {
39
+ clearTimeout(timer);
40
+ reject(new Error('Could not start the OpenCode installer.'));
41
+ });
42
+ child.once('close', (code) => {
43
+ clearTimeout(timer);
44
+ terminate();
45
+ // Installer output may contain environment or registry secrets.
46
+ if (code === 0) resolve();
47
+ else reject(new Error('OpenCode installation failed. Try the official installation guide.'));
48
+ });
49
+ });
50
+
51
+ const extractArchive = (tarCommand, archive, destination) => new Promise((resolve, reject) => {
52
+ const child = spawn(tarCommand, ['-xzf', archive, '-C', destination], {
53
+ stdio: 'ignore', windowsHide: true, timeout: INSTALL_TIMEOUT_MS,
54
+ });
55
+ child.once('error', () => reject(new Error('Could not unpack the OpenCode package.')));
56
+ child.once('close', (code) => {
57
+ if (code === 0) resolve();
58
+ else reject(new Error('Could not unpack the OpenCode package.'));
59
+ });
60
+ });
61
+
62
+ /** Downloads and runs the official installer script. Returns the step that replaces the binary. */
63
+ const prepareInstallerScript = async ({ version, workDirectory, env, fetchImpl }) => {
64
+ const response = await fetchImpl('https://opencode.ai/v2/install', { signal: AbortSignal.timeout(15_000) });
65
+ if (!response.ok) throw new Error('Could not download the OpenCode installer.');
66
+ const script = path.join(workDirectory, 'install.sh');
67
+ await fs.writeFile(script, await response.text(), { mode: 0o600 });
68
+ return () => runInstaller(script, version, env);
69
+ };
70
+
71
+ /**
72
+ * Downloads the Windows platform package, checks it against the integrity npm
73
+ * publishes for it, and unpacks it. Returns the step that replaces the binary.
74
+ */
75
+ const prepareWindowsBinary = async ({ version, directory, workDirectory, fetchImpl, tarCommand }) => {
76
+ const metadataResponse = await fetchImpl(`${NPM_REGISTRY}/${WINDOWS_PACKAGE.replace('/', '%2F')}/${version}`, { signal: AbortSignal.timeout(15_000) });
77
+ if (!metadataResponse.ok) throw new Error('Could not resolve the OpenCode v2 package.');
78
+ const { dist } = packageDistSchema.parse(await metadataResponse.json());
79
+ if (new URL(dist.tarball).origin !== NPM_REGISTRY) throw new Error('The OpenCode v2 package is not served by the npm registry.');
80
+ const archiveResponse = await fetchImpl(dist.tarball, { signal: AbortSignal.timeout(INSTALL_TIMEOUT_MS) });
81
+ if (!archiveResponse.ok) throw new Error('Could not download the OpenCode v2 package.');
82
+ const archive = Buffer.from(await archiveResponse.arrayBuffer());
83
+ if (`sha512-${createHash('sha512').update(archive).digest('base64')}` !== dist.integrity) {
84
+ throw new Error('The downloaded OpenCode v2 package failed its integrity check.');
85
+ }
86
+ const archivePath = path.join(workDirectory, 'opencode.tgz');
87
+ const extractDirectory = path.join(workDirectory, 'extract');
88
+ await fs.writeFile(archivePath, archive);
89
+ await fs.mkdir(extractDirectory);
90
+ await extractArchive(tarCommand, archivePath, extractDirectory);
91
+ const extracted = path.join(extractDirectory, 'package', 'bin', 'opencode.exe');
92
+ await fs.access(extracted).catch(() => { throw new Error('The OpenCode v2 package does not contain opencode.exe.'); });
93
+ return () => fs.copyFile(extracted, path.join(directory, 'opencode.exe'));
94
+ };
95
+
96
+ // Only host-owned callers supply these options. No request body controls a
97
+ // command, URL, version, destination, or environment.
98
+ export const installOpenCodeV2 = async ({
99
+ homeDirectory = os.homedir(),
100
+ env = process.env,
101
+ fetchImpl = fetch,
102
+ platform = process.platform,
103
+ // Windows' own bsdtar. Git Bash's GNU tar on PATH reads `C:\...` as a remote host.
104
+ tarCommand = path.join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'tar.exe'),
105
+ } = {}) => {
106
+ if (!supportsOpenCodeV2Install(platform)) throw new Error('Automatic OpenCode v2 installation is unavailable on this platform.');
107
+ const windows = platform === 'win32';
108
+ const binaryNames = windows ? ['opencode.exe'] : ['opencode', 'opencode2'];
109
+ const directory = path.join(homeDirectory, '.opencode', 'bin');
110
+ await fs.mkdir(directory, { recursive: true });
111
+ const lock = path.join(directory, '.openchamber-install');
112
+ // Also serializes separate OpenChamber/VS Code processes sharing this home.
113
+ await fs.mkdir(lock).catch(() => { throw new Error('Another OpenCode installation is already in progress.'); });
114
+ const snapshots = [];
115
+ let started = false;
116
+ let cleanup = true;
117
+ try {
118
+ const releaseResponse = await fetchImpl(`${NPM_REGISTRY}/@opencode%2Fcli/latest`, { signal: AbortSignal.timeout(15_000) });
119
+ if (!releaseResponse.ok) throw new Error('Could not resolve the OpenCode v2 release.');
120
+ const { version } = releaseSchema.parse(await releaseResponse.json());
121
+ const replaceBinary = windows
122
+ ? await prepareWindowsBinary({ version, directory, workDirectory: lock, fetchImpl, tarCommand })
123
+ : await prepareInstallerScript({ version, workDirectory: lock, env, fetchImpl });
124
+ for (const name of binaryNames) {
125
+ const target = path.join(directory, name);
126
+ const backup = path.join(lock, name);
127
+ const exists = await fs.lstat(target).then(() => true, (error) => {
128
+ if (error.code === 'ENOENT') return false;
129
+ throw error;
130
+ });
131
+ if (exists) await fs.cp(target, backup, { dereference: false, verbatimSymlinks: true });
132
+ snapshots.push({ target, backup, exists });
133
+ }
134
+ started = true;
135
+ await replaceBinary();
136
+ const binary = path.join(directory, binaryNames[0]);
137
+ const installedVersion = await requireOpenCodeV2({ binary, args: [] }, { env });
138
+ if (installedVersion !== version) throw new Error('The installed OpenCode version does not match the requested release.');
139
+ return binary;
140
+ } catch (error) {
141
+ if (started) {
142
+ try {
143
+ for (const { target, backup, exists } of snapshots) {
144
+ await fs.rm(target, { force: true });
145
+ if (exists) await fs.rename(backup, target);
146
+ }
147
+ } catch {
148
+ // Keep any remaining backups for manual recovery; never delete them
149
+ // after a failed rollback or allow another installer to overwrite them.
150
+ cleanup = false;
151
+ throw new Error('Could not restore the previous OpenCode installation. The backup remains in .opencode/bin/.openchamber-install.');
152
+ }
153
+ }
154
+ throw error;
155
+ } finally {
156
+ if (cleanup) await fs.rm(lock, { recursive: true, force: true });
157
+ }
158
+ };
@@ -0,0 +1,124 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
2
+ import { execFileSync } from 'node:child_process';
3
+ import { createHash } from 'node:crypto';
4
+ import fs from 'node:fs/promises';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import { installOpenCodeV2 } from './v2-install.js';
8
+ import { readOpenCodeCliVersion } from './compatibility.js';
9
+
10
+ let homeDirectory;
11
+ let binary;
12
+ const quote = (value) => "'" + value.replaceAll("'", "'\\''") + "'";
13
+ const cli = (version) => '#!/bin/sh\nprintf "%s\\n" "opencode v' + version + '"\n';
14
+ const run = (script, version = '2.0.15') => installOpenCodeV2({
15
+ homeDirectory,
16
+ fetchImpl: async (url) => url.includes('registry.npmjs.org')
17
+ ? Response.json({ version })
18
+ : new Response(script),
19
+ });
20
+
21
+ beforeEach(async () => {
22
+ homeDirectory = await fs.mkdtemp(path.join(os.tmpdir(), 'oc-install-test-'));
23
+ binary = path.join(homeDirectory, '.opencode/bin/opencode');
24
+ await fs.mkdir(path.dirname(binary), { recursive: true });
25
+ await fs.writeFile(binary, cli('1.18.30'), { mode: 0o755 });
26
+ });
27
+ afterEach(async () => { await fs.rm(homeDirectory, { recursive: true, force: true }); });
28
+
29
+ describe('OpenCode v2 installation', () => {
30
+ it('passes the validated release and verifies the actual executable', async () => {
31
+ const script = `#!/bin/bash
32
+ set -eu
33
+ test "$1" = "--version"
34
+ test "$2" = "2.0.15"
35
+ test "$3" = "--no-modify-path"
36
+ printf %s ${quote(cli('2.0.15'))} > ${quote(binary)}
37
+ chmod 755 ${quote(binary)}
38
+ `;
39
+ expect(await run(script)).toBe(binary);
40
+ expect(await readOpenCodeCliVersion({ binary, args: [] })).toBe('2.0.15');
41
+ await expect(fs.stat(path.join(path.dirname(binary), '.openchamber-install'))).rejects.toMatchObject({ code: 'ENOENT' });
42
+ });
43
+
44
+ it('restores the binary and shim when an installer exits zero but leaves v1', async () => {
45
+ const shim = path.join(path.dirname(binary), 'opencode2');
46
+ await fs.symlink('opencode', shim);
47
+ const script = `printf %s broken > ${quote(shim)}\nexit 0\n`;
48
+ await expect(run(script)).rejects.toThrow();
49
+ expect(await readOpenCodeCliVersion({ binary, args: [] })).toBe('1.18.30');
50
+ expect(await fs.readlink(shim)).toBe('opencode');
51
+ });
52
+
53
+ it('restores v1 after a partial installer failure, and permits a retry', async () => {
54
+ const script = `printf %s broken > ${quote(binary)}\nexit 1\n`;
55
+ await expect(run(script)).rejects.toThrow('installation failed');
56
+ expect(await readOpenCodeCliVersion({ binary, args: [] })).toBe('1.18.30');
57
+ await expect(run(script)).rejects.toThrow('installation failed');
58
+ });
59
+
60
+ it('rejects a non-v2 registry release before running the installer', async () => {
61
+ await expect(run(`rm ${quote(binary)}`, '3.0.0')).rejects.toThrow();
62
+ expect(await readOpenCodeCliVersion({ binary, args: [] })).toBe('1.18.30');
63
+ });
64
+
65
+ describe('on Windows', () => {
66
+ const TARBALL = 'https://registry.npmjs.org/@opencode/cli-windows-x64-baseline/-/cli-windows-x64-baseline-2.0.15.tgz';
67
+ let exe;
68
+ const integrityOf = (bytes) => `sha512-${createHash('sha512').update(bytes).digest('base64')}`;
69
+ // The platform package keeps its binary at package/bin/opencode.exe. Here it is a
70
+ // shell script, so the version check runs it on the Linux/macOS test host.
71
+ const pack = async (version) => {
72
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'oc-package-'));
73
+ await fs.mkdir(path.join(root, 'package/bin'), { recursive: true });
74
+ await fs.writeFile(path.join(root, 'package/bin/opencode.exe'), cli(version), { mode: 0o755 });
75
+ execFileSync('tar', ['-czf', path.join(root, 'package.tgz'), '-C', root, 'package']);
76
+ const bytes = await fs.readFile(path.join(root, 'package.tgz'));
77
+ await fs.rm(root, { recursive: true, force: true });
78
+ return bytes;
79
+ };
80
+ const runWindows = (archive, integrity = integrityOf(archive)) => installOpenCodeV2({
81
+ homeDirectory,
82
+ platform: 'win32',
83
+ tarCommand: 'tar',
84
+ fetchImpl: async (url) => {
85
+ if (url.endsWith('/@opencode%2Fcli/latest')) return Response.json({ version: '2.0.15' });
86
+ if (url.endsWith('/@opencode%2Fcli-windows-x64-baseline/2.0.15')) return Response.json({ dist: { tarball: TARBALL, integrity } });
87
+ if (url === TARBALL) return new Response(archive);
88
+ return new Response(null, { status: 404 });
89
+ },
90
+ });
91
+
92
+ beforeEach(async () => {
93
+ exe = path.join(homeDirectory, '.opencode/bin/opencode.exe');
94
+ await fs.writeFile(exe, cli('1.18.30'), { mode: 0o755 });
95
+ });
96
+
97
+ it('installs the verified platform package without the bash installer', async () => {
98
+ expect(await runWindows(await pack('2.0.15'))).toBe(exe);
99
+ expect(await readOpenCodeCliVersion({ binary: exe, args: [] })).toBe('2.0.15');
100
+ expect(await readOpenCodeCliVersion({ binary, args: [] })).toBe('1.18.30');
101
+ await expect(fs.stat(path.join(path.dirname(exe), '.openchamber-install'))).rejects.toMatchObject({ code: 'ENOENT' });
102
+ });
103
+
104
+ it('rejects a package that does not match its published integrity before touching the binary', async () => {
105
+ const archive = await pack('2.0.15');
106
+ await expect(runWindows(archive, integrityOf(Buffer.from('other')))).rejects.toThrow('integrity');
107
+ expect(await readOpenCodeCliVersion({ binary: exe, args: [] })).toBe('1.18.30');
108
+ await expect(fs.stat(path.join(path.dirname(exe), '.openchamber-install'))).rejects.toMatchObject({ code: 'ENOENT' });
109
+ });
110
+
111
+ it('restores the previous binary when the package holds another release', async () => {
112
+ await expect(runWindows(await pack('2.0.14'))).rejects.toThrow();
113
+ expect(await readOpenCodeCliVersion({ binary: exe, args: [] })).toBe('1.18.30');
114
+ });
115
+ });
116
+
117
+ it('does not overwrite an installation owned by another process', async () => {
118
+ const lock = path.join(path.dirname(binary), '.openchamber-install');
119
+ await fs.mkdir(lock);
120
+ await expect(run('exit 0')).rejects.toThrow('already in progress');
121
+ expect((await fs.stat(lock)).isDirectory()).toBe(true);
122
+ expect(await readOpenCodeCliVersion({ binary, args: [] })).toBe('1.18.30');
123
+ });
124
+ });
@@ -1,4 +1,5 @@
1
1
  import { createUpstreamSseReader } from '../event-stream/upstream-reader.js';
2
+ import { translateWireEvent } from '../event-stream/translate-v2.js';
2
3
 
3
4
  export const createOpenCodeWatcherRuntime = (deps) => {
4
5
  const {
@@ -17,6 +18,12 @@ export const createOpenCodeWatcherRuntime = (deps) => {
17
18
  let unsubscribeEvent = null;
18
19
  let unsubscribeStatus = null;
19
20
 
21
+ // `onPayload` consumers speak the server's own event vocabulary, so the v2
22
+ // wire payload is translated here rather than in each consumer.
23
+ const emitTranslated = (payload) => {
24
+ for (const translated of translateWireEvent(payload)) onPayload(translated);
25
+ };
26
+
20
27
  const unwrapGlobalEventPayload = (eventData) => {
21
28
  if (!eventData || typeof eventData !== 'object') {
22
29
  return null;
@@ -45,7 +52,7 @@ export const createOpenCodeWatcherRuntime = (deps) => {
45
52
  if (!payload || typeof payload !== 'object') {
46
53
  return;
47
54
  }
48
- onPayload(payload);
55
+ emitTranslated(payload);
49
56
  });
50
57
  unsubscribeStatus = globalEventHub.subscribeStatus((status) => {
51
58
  if (signal.aborted) {
@@ -65,7 +72,7 @@ export const createOpenCodeWatcherRuntime = (deps) => {
65
72
 
66
73
  reader = createUpstreamSseReader({
67
74
  signal,
68
- buildUrl: () => buildOpenCodeUrl('/global/event', ''),
75
+ buildUrl: () => buildOpenCodeUrl('/api/event', ''),
69
76
  getHeaders: getOpenCodeAuthHeaders,
70
77
  fetchImpl,
71
78
  stallTimeoutMs: upstreamStallTimeoutMs,
@@ -78,7 +85,7 @@ export const createOpenCodeWatcherRuntime = (deps) => {
78
85
  if (!payload || typeof payload !== 'object') {
79
86
  return;
80
87
  }
81
- onPayload(payload);
88
+ emitTranslated(payload);
82
89
  },
83
90
  onError(error) {
84
91
  if (signal.aborted) {
@@ -63,7 +63,7 @@ describe('createOpenCodeWatcherRuntime', () => {
63
63
  return createSseResponse({
64
64
  signal: options.signal,
65
65
  blocks: [
66
- 'id: evt-1\ndata: {"directory":"/tmp/project","payload":{"type":"session.updated","properties":{"sessionID":"ses_1"}}}\n\n',
66
+ 'data: {"directory":"/tmp/project","payload":{"id":"evt-1","type":"session.renamed","location":{"directory":"/tmp/project"},"data":{"sessionID":"ses_1","title":"New"}}}\n\n',
67
67
  ],
68
68
  });
69
69
  },
@@ -74,7 +74,7 @@ describe('createOpenCodeWatcherRuntime', () => {
74
74
 
75
75
  expect(fetchCalls).toEqual([
76
76
  {
77
- url: 'http://127.0.0.1:4096/global/event',
77
+ url: 'http://127.0.0.1:4096/api/event',
78
78
  headers: {
79
79
  Accept: 'text/event-stream',
80
80
  'Cache-Control': 'no-cache',
@@ -83,14 +83,13 @@ describe('createOpenCodeWatcherRuntime', () => {
83
83
  },
84
84
  },
85
85
  ]);
86
- expect(payloads).toEqual([
87
- {
88
- type: 'session.updated',
89
- properties: {
90
- sessionID: 'ses_1',
91
- },
92
- },
93
- ]);
86
+ // The watcher hands its consumers the server's own vocabulary, translated
87
+ // from the v2 wire event.
88
+ expect(payloads).toHaveLength(1);
89
+ expect(payloads[0]).toMatchObject({
90
+ type: 'session.updated',
91
+ properties: { sessionID: 'ses_1', info: { id: 'ses_1', title: 'New' } },
92
+ });
94
93
  });
95
94
 
96
95
  it('resumes watcher reconnects with Last-Event-ID after a stalled upstream stream', async () => {
@@ -118,7 +117,7 @@ describe('createOpenCodeWatcherRuntime', () => {
118
117
  signal: options.signal,
119
118
  holdOpen: true,
120
119
  blocks: [
121
- 'id: evt-1\ndata: {"type":"server.connected","properties":{}}\n\n',
120
+ 'data: {"id":"evt-1","type":"server.connected","data":{}}\n\n',
122
121
  ],
123
122
  });
124
123
  }
@@ -126,7 +125,7 @@ describe('createOpenCodeWatcherRuntime', () => {
126
125
  return createSseResponse({
127
126
  signal: options.signal,
128
127
  blocks: [
129
- 'id: evt-2\ndata: {"type":"session.updated","properties":{}}\n\n',
128
+ 'data: {"id":"evt-2","type":"session.renamed","location":{"directory":"/tmp/project"},"data":{"sessionID":"ses_1","title":"New"}}\n\n',
130
129
  ],
131
130
  });
132
131
  },
@@ -157,7 +156,7 @@ describe('createOpenCodeWatcherRuntime', () => {
157
156
  signal: options.signal,
158
157
  holdOpen: true,
159
158
  blocks: [
160
- 'id: evt-1\ndata: {"payload":{"type":"session.updated","properties":{"sessionID":"ses_1"}}}\n\n',
159
+ 'data: {"payload":{"id":"evt-1","type":"session.renamed","location":{"directory":"/tmp/project"},"data":{"sessionID":"ses_1","title":"New"}}}\n\n',
161
160
  ],
162
161
  });
163
162
  },
@@ -183,14 +182,11 @@ describe('createOpenCodeWatcherRuntime', () => {
183
182
 
184
183
  expect(hubFetchCalls).toBe(1);
185
184
  expect(watcherFetchCalls).toBe(0);
186
- expect(payloads).toEqual([
187
- {
188
- type: 'session.updated',
189
- properties: {
190
- sessionID: 'ses_1',
191
- },
192
- },
193
- ]);
185
+ expect(payloads).toHaveLength(1);
186
+ expect(payloads[0]).toMatchObject({
187
+ type: 'session.updated',
188
+ properties: { sessionID: 'ses_1', info: { id: 'ses_1', title: 'New' } },
189
+ });
194
190
  });
195
191
 
196
192
  it('does not stop a shared global event hub when the watcher stops', async () => {
@@ -0,0 +1,66 @@
1
+ import path from 'path';
2
+ import fs from 'fs';
3
+ import {
4
+ AGENT_SCOPE,
5
+ findWorktreeRoot,
6
+ getAncestors,
7
+ getJsonWriteTarget,
8
+ readConfigFile,
9
+ readConfigLayers,
10
+ writeConfig,
11
+ } from './shared.js';
12
+ import { findWebSearchProjectOverride, writeWebSearchSelection } from './config-v2.js';
13
+
14
+ /**
15
+ * Writes the `websearch` choice into the config OpenCode reads last among the
16
+ * files OpenChamber owns a write to: `OPENCODE_CONFIG` when the user set one,
17
+ * else the user's global `opencode.json`. OpenCode watches both, so the choice
18
+ * applies without a restart. `selection` comes from `parseWebSearchSelection`.
19
+ * The file is left untouched when the choice is already there.
20
+ */
21
+ export function setWebSearchSelection(selection) {
22
+ const layers = readConfigLayers(null);
23
+ const target = getJsonWriteTarget(layers, AGENT_SCOPE.USER);
24
+ const changed = writeWebSearchSelection(target.config, selection);
25
+ if (changed) writeConfig(target.config, target.path);
26
+ return { path: target.path, changed };
27
+ }
28
+
29
+ /**
30
+ * Where the effective `websearch` value for `directory` comes from, as far as
31
+ * a Settings write is concerned: `projectPath` is the project config that
32
+ * overrides whatever Settings writes, or `null` when a Settings write applies.
33
+ */
34
+ export function getWebSearchSource(directory) {
35
+ return { projectPath: findWebSearchProjectOverride(readConfigLayers(directory), readProjectConfigFiles(directory)) };
36
+ }
37
+
38
+ const PROJECT_CONFIG_NAMES = [
39
+ path.join('.opencode', 'opencode.jsonc'),
40
+ path.join('.opencode', 'opencode.json'),
41
+ 'opencode.jsonc',
42
+ 'opencode.json',
43
+ ];
44
+
45
+ /**
46
+ * Every existing project config file OpenCode merges for `directory`, deepest
47
+ * first, from the directory up to its worktree root. An unreadable file is
48
+ * skipped: it can't be told apart from one without the key.
49
+ */
50
+ function readProjectConfigFiles(directory) {
51
+ if (!directory) return [];
52
+ const root = findWorktreeRoot(directory) || path.resolve(directory);
53
+ const files = [];
54
+ for (const base of getAncestors(directory, root)) {
55
+ for (const name of PROJECT_CONFIG_NAMES) {
56
+ const filePath = path.join(base, name);
57
+ if (!fs.existsSync(filePath)) continue;
58
+ try {
59
+ files.push({ path: filePath, config: readConfigFile(filePath) });
60
+ } catch {
61
+ // Skipped; see above.
62
+ }
63
+ }
64
+ }
65
+ return files;
66
+ }