@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,217 @@
1
+ /**
2
+ * Chooses who answers the agent's `browser.*` actions: the in-app browser view
3
+ * (through the broker) or an extension service that provides the browser
4
+ * (`contributes.service.provides: ["browser"]`).
5
+ *
6
+ * The choice is the `browserProvider` setting: `builtin`, or the id of an
7
+ * installed extension. It is read for every action, so a change in Settings
8
+ * applies to the next action without a restart. A selected extension that can
9
+ * no longer serve — paused, removed, approval withdrawn, or a newer version
10
+ * without the role — is not silently kept: the setting goes back to `builtin`,
11
+ * every client is told so it can say so, and the action runs in-app. Doing
12
+ * that at the moment the extension is deactivated (`handleGuestDeactivated`)
13
+ * gives the user the notice right away; doing it again on the next action
14
+ * covers everything else, such as a folder install that went missing.
15
+ *
16
+ * The service is a host-spawned loopback process, so the proxy that panels
17
+ * use serves here too. It starts the process on the first action, which is
18
+ * what lets an agent browse with no panel open, and stops it after
19
+ * `BROWSER_PROVIDER_IDLE_MS` without actions.
20
+ */
21
+
22
+ import {
23
+ BROWSER_PROVIDER_ACTION_TIMEOUT_MS,
24
+ BROWSER_PROVIDER_IDLE_MS,
25
+ BROWSER_PROVIDER_OPEN_TIMEOUT_MS,
26
+ BROWSER_PROVIDER_PATH,
27
+ BROWSER_PROVIDER_RESPONSE_MAX,
28
+ isGuestApproved,
29
+ requestedGuestCapabilities,
30
+ serviceProvides,
31
+ } from '@openchamber/sdk';
32
+ import { browserProviderResultSchema } from '@openchamber/sdk/schemas';
33
+
34
+ import { GuestServiceError, proxyGuestServiceRequest } from '../guests/service.js';
35
+ import { BrowserControlError } from './broker.js';
36
+
37
+ const BUILTIN_BROWSER_PROVIDER = 'builtin';
38
+
39
+ /**
40
+ * Whether this catalog row can answer browser actions right now. Mirrors what
41
+ * the Settings dropdown offers: enabled, fully approved, and declaring the
42
+ * role. The `service` grant is implied by full approval.
43
+ */
44
+ /** The control service builds the caller's scope; a caller without one (tests, CLI) means unknown. */
45
+ const UNKNOWN_CONTEXT = Object.freeze({ directory: null, sessionId: null });
46
+
47
+ export const isBrowserProviderGuest = (guest) => (
48
+ Boolean(guest)
49
+ && guest.enabled !== false
50
+ && serviceProvides(guest.service, 'browser')
51
+ && isGuestApproved({
52
+ requested: requestedGuestCapabilities(guest),
53
+ granted: Array.isArray(guest.capabilityGrants) ? guest.capabilityGrants : [],
54
+ })
55
+ );
56
+
57
+ /**
58
+ * @param {{
59
+ * broker: { request: Function },
60
+ * readSettings: () => Promise<Record<string, unknown> | null>,
61
+ * persistSettings: (changes: Record<string, unknown>) => Promise<unknown>,
62
+ * findGuest: (id: string) => Promise<object | null>,
63
+ * persistPath: string,
64
+ * emitProviderReset: (event: { guestId: string, guestName: string }) => void,
65
+ * createId: () => string,
66
+ * proxyServiceRequest?: typeof proxyGuestServiceRequest,
67
+ * surfaceControl?: { userControls: (guestId: string) => boolean, noteAgentActivity: (guestId: string) => void },
68
+ * }} deps `surfaceControl` is the shared-surface lease: an action is refused
69
+ * while the user is driving the extension's surface, and every action the
70
+ * provider runs counts as agent activity there.
71
+ */
72
+ export const createBrowserControlRouter = ({
73
+ broker,
74
+ readSettings,
75
+ persistSettings,
76
+ findGuest,
77
+ persistPath,
78
+ emitProviderReset,
79
+ createId,
80
+ proxyServiceRequest = proxyGuestServiceRequest,
81
+ surfaceControl = { userControls: () => false, noteAgentActivity: () => undefined },
82
+ }) => {
83
+ // `browserProvider` was sanitized on write (settings-helpers.js keeps a
84
+ // trimmed non-empty string), so the only decision left is builtin or not.
85
+ // A read that fails is not "builtin": the action must not run somewhere
86
+ // the user did not choose.
87
+ const selectedProviderId = async () => {
88
+ let settings;
89
+ try {
90
+ settings = await readSettings();
91
+ } catch {
92
+ throw new BrowserControlError(
93
+ 'OpenChamber could not read which browser answers agent actions, so the action was not run. Try again.',
94
+ 503,
95
+ );
96
+ }
97
+ const value = settings?.browserProvider ?? BUILTIN_BROWSER_PROVIDER;
98
+ return value === BUILTIN_BROWSER_PROVIDER ? null : String(value);
99
+ };
100
+
101
+ const resetToBuiltin = async ({ guestId, guestName }) => {
102
+ await persistSettings({ browserProvider: BUILTIN_BROWSER_PROVIDER });
103
+ emitProviderReset({ guestId, guestName });
104
+ };
105
+
106
+ const requestFromProvider = async (guest, action, parameters, { signal, timeoutMs, context }) => {
107
+ if (surfaceControl.userControls(guest.id)) {
108
+ // Read by the agent: the page is being used by a person right now.
109
+ throw new BrowserControlError(
110
+ 'The user is interacting with this page in the panel right now, so the action was not run. '
111
+ + 'Wait for them to hand control back, or ask them to. Nothing was changed.',
112
+ 409,
113
+ );
114
+ }
115
+ surfaceControl.noteAgentActivity(guest.id);
116
+ const requestId = createId();
117
+ let proxied;
118
+ try {
119
+ proxied = await proxyServiceRequest({
120
+ guestId: guest.id,
121
+ guestName: guest.name,
122
+ packageRoot: guest.packageRoot,
123
+ service: guest.service,
124
+ granted: guest.capabilityGrants,
125
+ persistPath,
126
+ method: 'POST',
127
+ path: BROWSER_PROVIDER_PATH,
128
+ body: JSON.stringify({ requestId, action, parameters, context: context ?? UNKNOWN_CONTEXT }),
129
+ timeoutMs: timeoutMs ?? (action === 'browser.open' ? BROWSER_PROVIDER_OPEN_TIMEOUT_MS : BROWSER_PROVIDER_ACTION_TIMEOUT_MS),
130
+ responseMax: BROWSER_PROVIDER_RESPONSE_MAX,
131
+ idleStopMs: BROWSER_PROVIDER_IDLE_MS,
132
+ signal,
133
+ });
134
+ } catch (error) {
135
+ if (error instanceof GuestServiceError) {
136
+ // Read by the agent. A request that was sent and lost may have been
137
+ // acted on; only a service that never took it leaves the page as it was.
138
+ const outcome = error.code === 'REQUEST_FAILED' || error.code === 'CANCELLED'
139
+ ? 'The action was sent but no answer came back, so it may or may not have run; read the page before repeating it.'
140
+ : 'Nothing was changed.';
141
+ throw new BrowserControlError(
142
+ `The browser provider "${guest.name}" could not complete this action (${error.code}): ${error.message} ${outcome}`,
143
+ error.code === 'REQUEST_FAILED' ? 504 : 503,
144
+ );
145
+ }
146
+ throw error;
147
+ }
148
+ if (proxied.status !== 200) {
149
+ throw new BrowserControlError(
150
+ `The browser provider "${guest.name}" answered HTTP ${proxied.status} instead of a result. Nothing is known about the page.`,
151
+ 502,
152
+ );
153
+ }
154
+ let parsed;
155
+ try {
156
+ parsed = browserProviderResultSchema.safeParse(JSON.parse(proxied.body));
157
+ } catch {
158
+ parsed = { success: false };
159
+ }
160
+ if (!parsed.success) {
161
+ throw new BrowserControlError(
162
+ `The browser provider "${guest.name}" answered something that is not a browser result. Nothing is known about the page.`,
163
+ 502,
164
+ );
165
+ }
166
+ if (!parsed.data.ok) {
167
+ throw new BrowserControlError(parsed.data.error, 400);
168
+ }
169
+ return parsed.data.data;
170
+ };
171
+
172
+ return {
173
+ /** Same signature as the broker; the control service does not know which path ran. */
174
+ async request(action, parameters = {}, options = {}) {
175
+ const providerId = await selectedProviderId();
176
+ if (!providerId) {
177
+ return broker.request(action, parameters, options);
178
+ }
179
+ let guest;
180
+ try {
181
+ guest = await findGuest(providerId);
182
+ } catch {
183
+ // The catalog could not be read; that says nothing about the
184
+ // extension, so neither the choice nor the target browser changes.
185
+ throw new BrowserControlError(
186
+ 'OpenChamber could not read the extension catalog, so the action was not run. Try again.',
187
+ 503,
188
+ );
189
+ }
190
+ if (!isBrowserProviderGuest(guest)) {
191
+ await resetToBuiltin({ guestId: providerId, guestName: guest?.name ?? providerId });
192
+ return broker.request(action, parameters, options);
193
+ }
194
+ return requestFromProvider(guest, action, parameters, options);
195
+ },
196
+
197
+ /**
198
+ * Called when an extension is paused, removed, or loses approval. The
199
+ * catalog may not reflect the change yet, so this does not re-check it:
200
+ * the selection alone decides.
201
+ */
202
+ async handleGuestDeactivated({ guestId, guestName }) {
203
+ // The pause or removal already happened; a failure here must not turn
204
+ // it into an error response. The next action re-checks the catalog
205
+ // and resets the setting then.
206
+ try {
207
+ const providerId = await selectedProviderId();
208
+ if (providerId !== guestId) return false;
209
+ await resetToBuiltin({ guestId, guestName: guestName || guestId });
210
+ return true;
211
+ } catch (error) {
212
+ console.warn('[browser-provider] could not reset the provider after a deactivation:', error instanceof Error ? error.message : error);
213
+ return false;
214
+ }
215
+ },
216
+ };
217
+ };
@@ -0,0 +1,251 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+
3
+ import { BROWSER_PROVIDER_IDLE_MS, BROWSER_PROVIDER_OPEN_TIMEOUT_MS, BROWSER_PROVIDER_PATH } from '@openchamber/sdk';
4
+
5
+ import { BrowserControlError } from './broker.js';
6
+ import { createBrowserControlRouter, isBrowserProviderGuest } from './provider.js';
7
+ import { GuestServiceError } from '../guests/service.js';
8
+
9
+ const providerGuest = (overrides = {}) => ({
10
+ id: 'server-chrome',
11
+ name: 'Server Chrome',
12
+ packageRoot: '/ext/server-chrome',
13
+ enabled: true,
14
+ service: { entry: 'service/main.js', runtime: 'host', provides: ['browser'] },
15
+ capabilityGrants: ['service'],
16
+ panel: { id: 'server-chrome', name: 'Server Chrome', icon: 'window' },
17
+ ...overrides,
18
+ });
19
+
20
+ const createRouter = ({
21
+ settings = { browserProvider: 'server-chrome' },
22
+ guest = providerGuest(),
23
+ serviceAnswer = { status: 200, body: JSON.stringify({ ok: true, data: { url: 'http://localhost:3000/', title: 'App' } }) },
24
+ userControls = false,
25
+ readSettingsError = null,
26
+ findGuestError = null,
27
+ } = {}) => {
28
+ const brokerCalls = [];
29
+ const proxied = [];
30
+ const persisted = [];
31
+ const resets = [];
32
+ const agentActivity = [];
33
+ let current = { ...settings };
34
+ const router = createBrowserControlRouter({
35
+ surfaceControl: {
36
+ userControls: () => userControls,
37
+ noteAgentActivity: (guestId) => agentActivity.push(guestId),
38
+ },
39
+ broker: {
40
+ request: async (action, parameters, options) => {
41
+ brokerCalls.push({ action, parameters, options });
42
+ return { from: 'broker' };
43
+ },
44
+ },
45
+ readSettings: async () => {
46
+ if (readSettingsError) throw readSettingsError;
47
+ return current;
48
+ },
49
+ persistSettings: async (changes) => {
50
+ persisted.push(changes);
51
+ current = { ...current, ...changes };
52
+ },
53
+ findGuest: async (id) => {
54
+ if (findGuestError) throw findGuestError;
55
+ return guest && guest.id === id ? guest : null;
56
+ },
57
+ persistPath: '/data/extensions.json',
58
+ emitProviderReset: (event) => resets.push(event),
59
+ createId: () => 'req-1',
60
+ proxyServiceRequest: async (params) => {
61
+ proxied.push(params);
62
+ if (serviceAnswer instanceof Error) throw serviceAnswer;
63
+ return serviceAnswer;
64
+ },
65
+ });
66
+ return { router, brokerCalls, proxied, persisted, resets, agentActivity };
67
+ };
68
+
69
+ describe('isBrowserProviderGuest', () => {
70
+ test('needs enabled, approved, and the browser role', () => {
71
+ expect(isBrowserProviderGuest(providerGuest())).toBe(true);
72
+ expect(isBrowserProviderGuest(providerGuest({ enabled: false }))).toBe(false);
73
+ expect(isBrowserProviderGuest(providerGuest({ capabilityGrants: [] }))).toBe(false);
74
+ expect(isBrowserProviderGuest(providerGuest({ service: { entry: 'service/main.js', runtime: 'host' } }))).toBe(false);
75
+ expect(isBrowserProviderGuest(null)).toBe(false);
76
+ });
77
+ });
78
+
79
+ describe('browser control router', () => {
80
+ test('sends the action to the in-app broker when no provider is selected', async () => {
81
+ const { router, brokerCalls, proxied } = createRouter({ settings: {} });
82
+ const result = await router.request('browser.snapshot', {}, { timeoutMs: 20_000 });
83
+ expect(result).toEqual({ from: 'broker' });
84
+ expect(brokerCalls).toHaveLength(1);
85
+ expect(proxied).toHaveLength(0);
86
+ });
87
+
88
+ test('"builtin" is the in-app broker as well', async () => {
89
+ const { router, brokerCalls } = createRouter({ settings: { browserProvider: 'builtin' } });
90
+ await router.request('browser.snapshot', {});
91
+ expect(brokerCalls).toHaveLength(1);
92
+ });
93
+
94
+ test('posts the action to the selected provider service and returns its data', async () => {
95
+ const { router, brokerCalls, proxied } = createRouter();
96
+ const result = await router.request('browser.open', { url: 'http://localhost:3000/' }, {
97
+ timeoutMs: 45_000,
98
+ context: { directory: '/repo', sessionId: 'ses_1' },
99
+ });
100
+ expect(result).toEqual({ url: 'http://localhost:3000/', title: 'App' });
101
+ expect(brokerCalls).toHaveLength(0);
102
+ expect(proxied).toHaveLength(1);
103
+ const call = proxied[0];
104
+ expect(call.guestId).toBe('server-chrome');
105
+ expect(call.method).toBe('POST');
106
+ expect(call.path).toBe(BROWSER_PROVIDER_PATH);
107
+ expect(JSON.parse(call.body)).toEqual({
108
+ requestId: 'req-1',
109
+ action: 'browser.open',
110
+ parameters: { url: 'http://localhost:3000/' },
111
+ context: { directory: '/repo', sessionId: 'ses_1' },
112
+ });
113
+ expect(call.idleStopMs).toBe(BROWSER_PROVIDER_IDLE_MS);
114
+ expect(call.granted).toEqual(['service']);
115
+ });
116
+
117
+ test('a slow page gets the open budget when the caller sets none', async () => {
118
+ const { router, proxied } = createRouter();
119
+ await router.request('browser.open', { url: 'http://a/' });
120
+ expect(proxied[0].timeoutMs).toBe(BROWSER_PROVIDER_OPEN_TIMEOUT_MS);
121
+ });
122
+
123
+ test('a provider error becomes the agent-visible error, not a transport failure', async () => {
124
+ const { router } = createRouter({
125
+ serviceAnswer: { status: 200, body: JSON.stringify({ ok: false, error: 'No element matches #save' }) },
126
+ });
127
+ try {
128
+ await router.request('browser.click', { selector: '#save' });
129
+ throw new Error('expected rejection');
130
+ } catch (error) {
131
+ expect(error).toBeInstanceOf(BrowserControlError);
132
+ expect(error.message).toBe('No element matches #save');
133
+ expect(error.status).toBe(400);
134
+ }
135
+ });
136
+
137
+ test('a non-200 or malformed answer is reported as unknown page state', async () => {
138
+ for (const serviceAnswer of [
139
+ { status: 500, body: 'boom' },
140
+ { status: 200, body: 'not json' },
141
+ { status: 200, body: JSON.stringify({ ok: true }) },
142
+ ]) {
143
+ const { router } = createRouter({ serviceAnswer });
144
+ await expect(router.request('browser.snapshot', {})).rejects.toMatchObject({ status: 502 });
145
+ }
146
+ });
147
+
148
+ test('a service that will not start tells the agent nothing changed', async () => {
149
+ const { router } = createRouter({
150
+ serviceAnswer: new GuestServiceError('Guest service failed to become ready.', 'SERVICE_FAILED'),
151
+ });
152
+ try {
153
+ await router.request('browser.snapshot', {});
154
+ throw new Error('expected rejection');
155
+ } catch (error) {
156
+ expect(error).toBeInstanceOf(BrowserControlError);
157
+ expect(error.status).toBe(503);
158
+ expect(error.message).toContain('Server Chrome');
159
+ expect(error.message).toContain('Nothing was changed');
160
+ }
161
+ });
162
+
163
+ test('a selected extension that cannot serve resets the setting, tells clients, and runs in-app', async () => {
164
+ const { router, brokerCalls, persisted, resets } = createRouter({ guest: providerGuest({ enabled: false }) });
165
+ const result = await router.request('browser.snapshot', {});
166
+ expect(result).toEqual({ from: 'broker' });
167
+ expect(brokerCalls).toHaveLength(1);
168
+ expect(persisted).toEqual([{ browserProvider: 'builtin' }]);
169
+ expect(resets).toEqual([{ guestId: 'server-chrome', guestName: 'Server Chrome' }]);
170
+ });
171
+
172
+ test('a removed extension resets by id when no name is known', async () => {
173
+ const { router, resets } = createRouter({ guest: null });
174
+ await router.request('browser.snapshot', {});
175
+ expect(resets).toEqual([{ guestId: 'server-chrome', guestName: 'server-chrome' }]);
176
+ });
177
+
178
+ test('deactivating the selected extension resets right away; another one does not', async () => {
179
+ const { router, persisted, resets } = createRouter();
180
+ expect(await router.handleGuestDeactivated({ guestId: 'other', guestName: 'Other' })).toBe(false);
181
+ expect(persisted).toHaveLength(0);
182
+ expect(await router.handleGuestDeactivated({ guestId: 'server-chrome', guestName: 'Server Chrome' })).toBe(true);
183
+ expect(persisted).toEqual([{ browserProvider: 'builtin' }]);
184
+ expect(resets).toEqual([{ guestId: 'server-chrome', guestName: 'Server Chrome' }]);
185
+ });
186
+
187
+ test('an action is refused while the user drives the shared surface', async () => {
188
+ const { router, proxied, agentActivity } = createRouter({ userControls: true });
189
+ try {
190
+ await router.request('browser.click', { selector: '#save' });
191
+ throw new Error('expected rejection');
192
+ } catch (error) {
193
+ expect(error).toBeInstanceOf(BrowserControlError);
194
+ expect(error.status).toBe(409);
195
+ expect(error.message).toContain('Nothing was changed');
196
+ }
197
+ expect(proxied).toHaveLength(0);
198
+ expect(agentActivity).toHaveLength(0);
199
+ });
200
+
201
+ test('a provider action counts as agent activity on the surface', async () => {
202
+ const { router, agentActivity } = createRouter();
203
+ await router.request('browser.snapshot', {});
204
+ expect(agentActivity).toEqual(['server-chrome']);
205
+ });
206
+
207
+ test('a settings read that fails runs nothing anywhere and changes nothing', async () => {
208
+ const { router, brokerCalls, proxied, persisted, resets } = createRouter({ readSettingsError: new Error('disk') });
209
+ await expect(router.request('browser.click', { selector: '#save' })).rejects.toMatchObject({ status: 503 });
210
+ expect(brokerCalls).toHaveLength(0);
211
+ expect(proxied).toHaveLength(0);
212
+ expect(persisted).toHaveLength(0);
213
+ expect(resets).toHaveLength(0);
214
+ });
215
+
216
+ test('a catalog read that fails runs nothing anywhere and keeps the choice', async () => {
217
+ const { router, brokerCalls, proxied, persisted, resets } = createRouter({ findGuestError: new Error('catalog') });
218
+ await expect(router.request('browser.click', { selector: '#save' })).rejects.toMatchObject({ status: 503 });
219
+ expect(brokerCalls).toHaveLength(0);
220
+ expect(proxied).toHaveLength(0);
221
+ expect(persisted).toHaveLength(0);
222
+ expect(resets).toHaveLength(0);
223
+ });
224
+
225
+ test('a request that was sent and lost is reported as unknown, not as unchanged', async () => {
226
+ const { router } = createRouter({
227
+ serviceAnswer: new GuestServiceError('Guest service request failed.', 'REQUEST_FAILED'),
228
+ });
229
+ try {
230
+ await router.request('browser.click', { selector: '#save' });
231
+ throw new Error('expected rejection');
232
+ } catch (error) {
233
+ expect(error).toBeInstanceOf(BrowserControlError);
234
+ expect(error.status).toBe(504);
235
+ expect(error.message).toContain('may or may not have run');
236
+ expect(error.message).not.toContain('Nothing was changed');
237
+ }
238
+ });
239
+
240
+ test('a settings read that fails during deactivation is logged, not raised', async () => {
241
+ const { router, persisted } = createRouter({ readSettingsError: new Error('disk') });
242
+ const warn = console.warn;
243
+ console.warn = () => undefined;
244
+ try {
245
+ expect(await router.handleGuestDeactivated({ guestId: 'server-chrome', guestName: 'Server Chrome' })).toBe(false);
246
+ } finally {
247
+ console.warn = warn;
248
+ }
249
+ expect(persisted).toHaveLength(0);
250
+ });
251
+ });
@@ -1,3 +1,4 @@
1
+ import { unwrapOpenCodeResponse } from '../opencode/response-envelope.js';
1
2
  const FETCH_TIMEOUT_MS = 15_000;
2
3
  const MESSAGE_FETCH_LIMIT = 20;
3
4
 
@@ -30,10 +31,20 @@ const buildContextPrompt = (entries) => {
30
31
  ].join('\n');
31
32
  };
32
33
 
34
+ /**
35
+ * Re-injects the messages the user pinned as obligatory after a compaction.
36
+ *
37
+ * Both the pinned list and the cursor live in OpenChamber's own session
38
+ * metadata store — v2 accepts session metadata only at create time.
39
+ * `readSessionMetadata` reads it and `persistContextCursor` records how far we
40
+ * got; without both seams the runtime stays inert.
41
+ */
33
42
  export const createContextObligatoryRuntime = ({
34
43
  buildOpenCodeUrl,
35
44
  getOpenCodeAuthHeaders,
36
45
  sessionKnowledgeRuntime = null,
46
+ persistContextCursor = null,
47
+ readSessionMetadata = null,
37
48
  }) => {
38
49
  const inflight = new Set();
39
50
  let stopped = false;
@@ -53,13 +64,15 @@ export const createContextObligatoryRuntime = ({
53
64
  signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
54
65
  });
55
66
  if (!response.ok) throw new Error(`OpenCode ${method} ${fetchPath} failed with ${response.status}`);
56
- return response.json().catch(() => null);
67
+ return unwrapOpenCodeResponse(await response.json().catch(() => null));
57
68
  };
58
69
 
59
70
  const tick = async (sessionId, directory) => {
60
- const session = await openCodeFetch(`/session/${encodeURIComponent(sessionId)}`, { directory });
71
+ const session = await openCodeFetch(`/api/session/${encodeURIComponent(sessionId)}`, { directory });
61
72
  if (session?.parentID) return;
62
- const state = readContextState(session);
73
+ // Pins and the cursor are OpenChamber's, not OpenCode's.
74
+ const stored = { metadata: await readSessionMetadata(sessionId) };
75
+ const state = readContextState(stored);
63
76
 
64
77
  /**
65
78
  * Project knowledge rides along with the pinned messages. Compaction takes
@@ -74,32 +87,40 @@ export const createContextObligatoryRuntime = ({
74
87
  // Compaction removed the previously delivered block, so its stored
75
88
  // signature is no longer evidence that the session still carries it.
76
89
  '',
77
- sessionKnowledgeRuntime.readPins(session),
90
+ sessionKnowledgeRuntime.readPins(stored),
78
91
  )
79
92
  .catch(() => ({ text: '', signature: '' }))
80
93
  : { text: '', signature: '' };
81
94
 
82
95
  if (state.messages.length === 0 && !knowledge.text) return;
83
96
 
84
- const recent = await openCodeFetch(`/session/${encodeURIComponent(sessionId)}/message`, {
97
+ const recent = await openCodeFetch(`/api/session/${encodeURIComponent(sessionId)}/message`, {
85
98
  directory,
86
99
  query: { limit: String(MESSAGE_FETCH_LIMIT) },
87
100
  });
88
- if (!Array.isArray(recent) || recent.length === 0) return;
89
- const summary = recent.toReversed().find((message) =>
90
- message?.info?.role === 'assistant' && message.info.summary === true)?.info;
101
+ // v2 pages messages as `{ data, cursor }`, newest first, and a compaction
102
+ // is its own message role rather than an assistant message flagged
103
+ // `summary`.
104
+ const recentMessages = Array.isArray(recent?.data) ? recent.data : [];
105
+ if (recentMessages.length === 0) return;
106
+ const summary = recentMessages.find((message) => message?.type === 'compaction' && message?.status === 'completed');
91
107
  if (!summary?.id || !summary?.time?.completed) return;
92
108
  if (state.openchamber.context_obligatory_last_compaction_message_id === summary.id) return;
93
109
 
94
110
  const fetched = await Promise.allSettled(state.messages.map(async (pinned) => {
95
111
  const message = await openCodeFetch(
96
- `/session/${encodeURIComponent(sessionId)}/message/${encodeURIComponent(pinned.id)}`,
112
+ `/api/session/${encodeURIComponent(sessionId)}/message/${encodeURIComponent(pinned.id)}`,
97
113
  { directory },
98
114
  );
99
- const text = Array.isArray(message?.parts)
100
- ? message.parts.filter((part) => part?.type === 'text' && typeof part.text === 'string')
101
- .map((part) => part.text.trim()).filter(Boolean).join('\n\n')
102
- : '';
115
+ // A user message carries `text`; an assistant one carries `content[]`.
116
+ const parts = Array.isArray(message?.content)
117
+ ? message.content
118
+ : (typeof message?.text === 'string' ? [{ type: 'text', text: message.text }] : []);
119
+ const text = parts
120
+ .filter((part) => part?.type === 'text' && typeof part.text === 'string')
121
+ .map((part) => part.text.trim())
122
+ .filter(Boolean)
123
+ .join('\n\n');
103
124
  return { pinned, text };
104
125
  }));
105
126
  const entries = fetched
@@ -108,52 +129,42 @@ export const createContextObligatoryRuntime = ({
108
129
  .sort((left, right) => left.pinned.createdAt - right.pinned.createdAt);
109
130
  if (entries.length === 0 && !knowledge.text) return;
110
131
 
111
- const executionInfo = recent.toReversed().find((message) =>
112
- message?.info?.role === 'assistant' && message.info.summary !== true)?.info;
113
- const providerID = typeof executionInfo?.providerID === 'string' ? executionInfo.providerID : '';
114
- const modelID = typeof executionInfo?.modelID === 'string' ? executionInfo.modelID : '';
115
- if (!providerID || !modelID) throw new Error('no pre-compaction assistant provider/model');
116
- const agent = typeof executionInfo.agent === 'string' ? executionInfo.agent : executionInfo.mode;
117
- await openCodeFetch(`/session/${encodeURIComponent(sessionId)}/prompt_async`, {
132
+ // v2 has no inline synthetic parts, and the model/agent selection lives on
133
+ // the session, so the restored context is simply its own synthetic message.
134
+ await openCodeFetch(`/api/session/${encodeURIComponent(sessionId)}/synthetic`, {
118
135
  directory,
119
136
  method: 'POST',
120
137
  body: {
121
- model: { providerID, modelID },
122
- ...(typeof agent === 'string' && agent ? { agent } : {}),
123
- parts: [{
124
- type: 'text',
125
- text: [knowledge.text, entries.length > 0 ? buildContextPrompt(entries) : '']
126
- .filter(Boolean)
127
- .join('\n\n---\n\n'),
128
- synthetic: true,
129
- }],
138
+ text: [knowledge.text, entries.length > 0 ? buildContextPrompt(entries) : '']
139
+ .filter(Boolean)
140
+ .join('\n\n---\n\n'),
141
+ resume: false,
130
142
  },
131
143
  });
132
144
 
133
- const fresh = await openCodeFetch(`/session/${encodeURIComponent(sessionId)}`, { directory });
134
- const freshState = readContextState(fresh);
135
- await openCodeFetch(`/session/${encodeURIComponent(sessionId)}`, {
136
- directory,
137
- method: 'PATCH',
138
- body: {
139
- metadata: {
140
- ...freshState.metadata,
141
- openchamber: {
142
- ...freshState.openchamber,
143
- context_obligatory_last_compaction_message_id: summary.id,
144
- // Recorded together with the cursor: the session now carries this
145
- // knowledge again, so the next send must not repeat it.
146
- ...(knowledge.signature
147
- ? { [sessionKnowledgeRuntime.metadataKey]: knowledge.signature }
148
- : {}),
149
- },
150
- },
151
- },
152
- });
145
+ // A merge patch: the store folds this into whatever else the session's
146
+ // `openchamber` namespace holds, so a concurrent goal or assist write is
147
+ // not clobbered.
148
+ const patch = { context_obligatory_last_compaction_message_id: summary.id };
149
+ if (knowledge.signature) {
150
+ // Recorded together with the cursor: the session now carries this
151
+ // knowledge again, so the next send must not repeat it.
152
+ patch[sessionKnowledgeRuntime.metadataKey] = knowledge.signature;
153
+ }
154
+ await persistContextCursor(sessionId, directory, { openchamber: patch });
153
155
  };
154
156
 
157
+ let parkedNoticeLogged = false;
155
158
  const processPayload = (payload, directoryHint = '') => {
156
- if (stopped || payload?.type !== 'session.compacted') return;
159
+ if (stopped) return;
160
+ if (typeof persistContextCursor !== 'function' || typeof readSessionMetadata !== 'function') {
161
+ if (!parkedNoticeLogged) {
162
+ parkedNoticeLogged = true;
163
+ console.log('[context-obligatory] parked: no session metadata store is wired, so the compaction cursor cannot be saved');
164
+ }
165
+ return;
166
+ }
167
+ if (payload?.type !== 'session.compacted') return;
157
168
  const sessionId = payload?.properties?.sessionID;
158
169
  if (typeof sessionId !== 'string' || inflight.has(sessionId)) return;
159
170
  const directory = payload?.properties?.directory || directoryHint;