@vantran-se/openchamber-web 1.24.4 → 2.0.1

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-ntXIbhV2.js} +1 -1
  5. package/dist/assets/DiffView-CjxUovbM.js +4 -0
  6. package/dist/assets/FilesView-DFWD0RQR.js +17 -0
  7. package/dist/assets/{GitView-Byu2DY7s.js → GitView-BWa7e5vw.js} +4 -4
  8. package/dist/assets/{GuestAttachDialog-B808E0-d.js → GuestAttachDialog-DdOuyZzp.js} +1 -1
  9. package/dist/assets/GuestSurfacePane-DDfzid3U.js +1 -0
  10. package/dist/assets/{JsonTreeView-VW_7czo3.js → JsonTreeView-3sGwqexh.js} +1 -1
  11. package/dist/assets/LinearIssuesView-C6dpIdjw.js +1 -0
  12. package/dist/assets/MarkdownImageGallery-Q7YR_tl4.js +1 -0
  13. package/dist/assets/MarkdownRendererImpl-DxDV56bL.js +12 -0
  14. package/dist/assets/OnboardingScreen-rK4Stg__.js +1 -0
  15. package/dist/assets/{PatchDiff-DCjGhlYV.js → PatchDiff-DS-IMU1j.js} +1 -1
  16. package/dist/assets/{PierreDiffViewer-CEDZNkU2.js → PierreDiffViewer-DW23KWKZ.js} +1 -1
  17. package/dist/assets/PlanView-BCk_VobP.js +5 -0
  18. package/dist/assets/PluginPane-BPP1dbck.js +3 -0
  19. package/dist/assets/SettingsView-kMmrpn43.js +29 -0
  20. package/dist/assets/SettingsWindow-BpvD6IYQ.js +1 -0
  21. package/dist/assets/SubmoduleDiffSummary-BLttSS1X.js +1 -0
  22. package/dist/assets/ToolOutputDialog-CtqDqbyr.js +15 -0
  23. package/dist/assets/ToolPartDiffPreview-5FcKdyRy.js +8 -0
  24. package/dist/assets/{VirtualizedFileDiff-NU0FHFhT.js → VirtualizedFileDiff-C763hNCR.js} +4 -4
  25. package/dist/assets/{WalkthroughView-BZyjhqHv.js → WalkthroughView-lap5btCF.js} +2 -2
  26. package/dist/assets/appThemeRegistry-u4zw1l0B.js +1 -0
  27. package/dist/assets/{commitAndPush-BDftrjr6.js → commitAndPush-CwGJfJG-.js} +1 -1
  28. package/dist/assets/{computeVirtualFileMetrics-DlWlEM3x.js → computeVirtualFileMetrics-DW5jjX4n.js} +1 -1
  29. package/dist/assets/de-CndEg67T.js +10 -0
  30. package/dist/assets/es-DglvPlaC.js +16 -0
  31. package/dist/assets/fr-DejPCpbS.js +14 -0
  32. package/dist/assets/index-C2r64QfD.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-D47a6hMP.js} +1 -1
  36. package/dist/assets/{isFilePlainText-ue6kCou7.js → isFilePlainText-B-mGgeKl.js} +1 -1
  37. package/dist/assets/ja-CVpQawCd.js +16 -0
  38. package/dist/assets/ko-Bw446h9j.js +16 -0
  39. package/dist/assets/main-Bz4WWXbr.js +2 -0
  40. package/dist/assets/main-oreqDsOV.js +896 -0
  41. package/dist/assets/{markdownCore-CK48LG0z.js → markdownCore-D2e5sp9y.js} +4 -4
  42. package/dist/assets/miniChat-D1v2gsBI.js +2 -0
  43. package/dist/assets/mobile-DBj3lCf2.js +2 -0
  44. package/dist/assets/{patchFileDiff-D7QVlovD.js → patchFileDiff-BpeUOV91.js} +3 -3
  45. package/dist/assets/pl-BYNlPdhm.js +16 -0
  46. package/dist/assets/pt-BR-CL-Yfv9H.js +16 -0
  47. package/dist/assets/renderElectronMiniChatApp-DVtfEB2z.js +2 -0
  48. package/dist/assets/renderMobileApp-DFp9-xbG.js +25 -0
  49. package/dist/assets/runtimeEndpointReset-CsIpUep1.js +27 -0
  50. package/dist/assets/scheduledTasksApi-B5OHVpPM.js +1 -0
  51. package/dist/assets/{shikiHighlight-BPu3JiQs.js → shikiHighlight-DNm2Z_Jc.js} +1 -1
  52. package/dist/assets/{textMateThemeFromAppTheme-DWqRPo9A.js → textMateThemeFromAppTheme-DGF2kG7g.js} +1 -1
  53. package/dist/assets/tr-DzmbxLy-.js +7 -0
  54. package/dist/assets/uk-C7vQnwro.js +16 -0
  55. package/dist/assets/useAppFontEffects-Ci3FEaPf.js +7337 -0
  56. package/dist/assets/{useInlineCommentController-CgS0fnw_.js → useInlineCommentController-CuUuakrt.js} +2 -2
  57. package/dist/assets/useRootScrollLock-CQIsyERq.js +2 -0
  58. package/dist/assets/{vendor-codemirror-language-data-Bx2EHc1O.js → vendor-codemirror-language-data-CQa7_1cr.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-B6tKQyJN.js} +2 -2
  64. package/dist/assets/zh-CN-Cl0srS-z.js +16 -0
  65. package/dist/assets/zh-TW-DtII5bDb.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
@@ -90,10 +90,18 @@ export function createPermissionAutoAcceptRuntime({
90
90
 
91
91
  const rememberSession = (info, directoryHint) => {
92
92
  if (!info || typeof info.id !== 'string' || !info.id) return;
93
- sessions.set(info.id, {
94
- parentID: typeof info.parentID === 'string' && info.parentID ? info.parentID : null,
95
- directory: typeof info.directory === 'string' && info.directory ? info.directory : directoryHint,
96
- });
93
+ // v2 session updates are partial (a rename carries only the title), so a
94
+ // field the update does not name keeps what an earlier record said.
95
+ const previous = sessions.get(info.id);
96
+ const parentID = typeof info.parentID === 'string' && info.parentID ? info.parentID : previous?.parentID ?? null;
97
+ // v2 keeps the directory on `location`; translated events already flatten it.
98
+ const directory = typeof info.directory === 'string' && info.directory
99
+ ? info.directory
100
+ : (typeof info.location?.directory === 'string' && info.location.directory
101
+ ? info.location.directory
102
+ : previous?.directory ?? directoryHint);
103
+ if (previous) sessions.delete(info.id);
104
+ sessions.set(info.id, { parentID, directory });
97
105
  if (sessions.size > SESSION_CACHE_LIMIT) {
98
106
  sessions.delete(sessions.keys().next().value);
99
107
  }
@@ -101,12 +109,14 @@ export function createPermissionAutoAcceptRuntime({
101
109
 
102
110
  const request = async (path, { directory, method = 'GET', body } = {}) => {
103
111
  const url = new URL(buildOpenCodeUrl(path, ''));
104
- if (directory) url.searchParams.set('directory', directory);
105
112
  const response = await fetchImpl(url, {
106
113
  method,
107
114
  headers: {
108
115
  Accept: 'application/json',
109
116
  ...(body ? { 'Content-Type': 'application/json' } : {}),
117
+ // OpenCode 2.x scopes a request to a directory through this header;
118
+ // the pending-permission list and services behind it are per location.
119
+ ...(directory ? { 'x-opencode-directory': encodeURIComponent(directory) } : {}),
110
120
  ...getOpenCodeAuthHeaders(),
111
121
  },
112
122
  ...(body ? { body: JSON.stringify(body) } : {}),
@@ -123,7 +133,7 @@ export function createPermissionAutoAcceptRuntime({
123
133
  const getSession = async (sessionId, directory) => {
124
134
  const cached = sessions.get(sessionId);
125
135
  if (cached) return cached;
126
- const info = await request(`/session/${encodeURIComponent(sessionId)}`, { directory });
136
+ const info = await request(`/api/session/${encodeURIComponent(sessionId)}`, { directory });
127
137
  rememberSession(info?.data ?? info, directory);
128
138
  return sessions.get(sessionId) ?? null;
129
139
  };
@@ -156,10 +166,12 @@ export function createPermissionAutoAcceptRuntime({
156
166
  const verdict = await evaluatePermission(permission, directory);
157
167
  if (verdict?.action === 'hold') return true;
158
168
  }
159
- await request(`/permission/${encodeURIComponent(permission.id)}/reply`, {
169
+ // v2 scopes a permission reply under its session.
170
+ await request(`/api/session/${encodeURIComponent(permission.sessionID)}/permission/${encodeURIComponent(permission.id)}/reply`, {
160
171
  directory,
161
172
  method: 'POST',
162
- body: { reply: 'once' },
173
+ // OpenCode 2.0.8 renamed the reply body field `reply` to `decision`.
174
+ body: { decision: 'once' },
163
175
  });
164
176
  return true;
165
177
  };
@@ -198,7 +210,7 @@ export function createPermissionAutoAcceptRuntime({
198
210
  for (const directory of scopes) {
199
211
  let payload;
200
212
  try {
201
- payload = await request('/permission', { directory });
213
+ payload = await request('/api/permission/request', { directory });
202
214
  } catch {
203
215
  continue;
204
216
  }
@@ -217,20 +229,24 @@ export function createPermissionAutoAcceptRuntime({
217
229
  }
218
230
 
219
231
  const processEvent = (event) => {
220
- const raw = event?.payload;
221
- const payload = raw?.payload && typeof raw.payload === 'object' ? raw.payload : raw;
222
232
  const directory = typeof event?.directory === 'string' && event.directory !== 'global' ? event.directory : undefined;
223
- if (payload?.type === 'session.created' || payload?.type === 'session.updated') {
224
- rememberSession(payload.properties?.info, directory);
225
- return;
226
- }
227
- if (payload?.type === 'permission.asked') {
228
- void processPermission(payload.properties, directory);
229
- return;
230
- }
231
- if (payload?.type === 'permission.replied') {
232
- const permissionId = payload.properties?.requestID;
233
- if (typeof permissionId === 'string') onPermissionReplied?.(permissionId);
233
+ for (const payload of event?.translated?.() ?? []) {
234
+ if (payload.type === 'session.created' || payload.type === 'session.updated') {
235
+ rememberSession(payload.properties?.info, directory ?? payload.properties?.directory);
236
+ continue;
237
+ }
238
+ // A v2 permission request is `{ id, sessionID, action, resources, ... }`;
239
+ // only the id and session id are used to reply.
240
+ if (payload.type === 'permission.asked') {
241
+ void processPermission(payload.properties, directory ?? payload.properties?.directory);
242
+ continue;
243
+ }
244
+ // The routing safety net holds a request instead of replying; once the
245
+ // user answers it, stop tracking it.
246
+ if (payload.type === 'permission.replied') {
247
+ const permissionId = payload.properties?.requestID;
248
+ if (typeof permissionId === 'string' && permissionId) onPermissionReplied?.(permissionId);
249
+ }
234
250
  }
235
251
  };
236
252
 
@@ -23,11 +23,17 @@ const createRuntime = ({ stored, fetchImpl, retryDelaysMs = [0], evaluatePermiss
23
23
  return {
24
24
  runtime,
25
25
  getSettings: () => settings,
26
- emit: (payload, directory = '/project') => eventHandler({ payload, directory }),
26
+ // The hub hands server-side subscribers already-translated events.
27
+ emit: (payload, directory = '/project') => eventHandler({ payload, directory, translated: () => [payload] }),
27
28
  connect: () => statusHandler({ type: 'connect' }),
28
29
  };
29
30
  };
30
31
 
32
+ const directoryHeader = (init) => {
33
+ const value = init?.headers?.['x-opencode-directory'];
34
+ return typeof value === 'string' ? decodeURIComponent(value) : null;
35
+ };
36
+
31
37
  const flush = async () => {
32
38
  for (let index = 0; index < 20; index += 1) await Promise.resolve();
33
39
  };
@@ -63,11 +69,24 @@ describe('permission auto-accept runtime', () => {
63
69
  await expect(runtime.isSessionAutoAccepting('grandchild', '/project')).resolves.toBe(true);
64
70
  });
65
71
 
72
+ it('keeps a subagent\'s lineage when a later partial update names only its title', async () => {
73
+ const fetchImpl = vi.fn(async () => Response.json({}));
74
+ const { runtime, emit } = createRuntime({ fetchImpl });
75
+ await runtime.setSessionPolicy('root', true);
76
+ emit({ type: 'session.created', properties: { info: { id: 'child', parentID: 'root', directory: '/project' } } });
77
+ // v2 renames arrive as partial session records without parentID.
78
+ emit({ type: 'session.updated', properties: { info: { id: 'child', title: 'Subagent' } } });
79
+ emit({ type: 'permission.asked', properties: { id: 'p1', sessionID: 'child', permission: 'bash', metadata: {} } });
80
+ await flush();
81
+ const replies = fetchImpl.mock.calls.map(([url]) => new URL(url).pathname).filter((path) => path.endsWith('/reply'));
82
+ expect(replies).toEqual(['/api/session/child/permission/p1/reply']);
83
+ });
84
+
66
85
  it('fetches missing subagent lineage before replying', async () => {
67
86
  const fetchImpl = vi.fn(async (url, init = {}) => {
68
87
  const path = new URL(url).pathname;
69
- if (path === '/permission') return new Response('[]');
70
- if (path === '/session/child') return Response.json({ id: 'child', parentID: 'root', directory: '/project' });
88
+ if (path === '/api/permission/request') return new Response('[]');
89
+ if (path === '/api/session/child') return Response.json({ id: 'child', parentID: 'root', directory: '/project' });
71
90
  if (init.method === 'POST') return Response.json({});
72
91
  return new Response('', { status: 404 });
73
92
  });
@@ -76,15 +95,16 @@ describe('permission auto-accept runtime', () => {
76
95
  fetchImpl,
77
96
  });
78
97
  await expect(runtime.processPermission({ id: 'perm', sessionID: 'child' }, '/project')).resolves.toBe(true);
79
- expect(fetchImpl.mock.calls.some(([url, init]) => new URL(url).pathname === '/permission/perm/reply' && init.method === 'POST')).toBe(true);
98
+ // v2 scopes a permission reply under its session.
99
+ expect(fetchImpl.mock.calls.some(([url, init]) => new URL(url).pathname === '/api/session/child/permission/perm/reply' && init.method === 'POST')).toBe(true);
80
100
  });
81
101
 
82
102
  it('retries a transient reply failure and deduplicates concurrent events', async () => {
83
103
  let replyAttempts = 0;
84
104
  const fetchImpl = vi.fn(async (url, init = {}) => {
85
105
  const path = new URL(url).pathname;
86
- if (path === '/permission') return new Response('[]');
87
- if (path === '/permission/perm/reply' && init.method === 'POST') {
106
+ if (path === '/api/permission/request') return new Response('[]');
107
+ if (path === '/api/session/root/permission/perm/reply' && init.method === 'POST') {
88
108
  replyAttempts += 1;
89
109
  return replyAttempts === 1 ? new Response('', { status: 503 }) : Response.json({});
90
110
  }
@@ -105,8 +125,8 @@ describe('permission auto-accept runtime', () => {
105
125
  it('reconciles pending permissions after reconnect', async () => {
106
126
  const fetchImpl = vi.fn(async (url, init = {}) => {
107
127
  const path = new URL(url).pathname;
108
- if (path === '/permission') return Response.json([{ id: 'pending', sessionID: 'root' }]);
109
- if (path === '/permission/pending/reply' && init.method === 'POST') return Response.json({});
128
+ if (path === '/api/permission/request') return Response.json([{ id: 'pending', sessionID: 'root' }]);
129
+ if (path === '/api/session/root/permission/pending/reply' && init.method === 'POST') return Response.json({});
110
130
  return Response.json({ id: 'root' });
111
131
  });
112
132
  const { connect } = createRuntime({
@@ -115,23 +135,23 @@ describe('permission auto-accept runtime', () => {
115
135
  });
116
136
  connect();
117
137
  await flush();
118
- expect(fetchImpl.mock.calls.some(([url]) => new URL(url).pathname === '/permission/pending/reply')).toBe(true);
138
+ expect(fetchImpl.mock.calls.some(([url]) => new URL(url).pathname === '/api/session/root/permission/pending/reply')).toBe(true);
119
139
  });
120
140
 
121
141
  it('accepts existing pending permissions when a session policy is enabled', async () => {
122
142
  const fetchImpl = vi.fn(async (url, init = {}) => {
123
143
  const parsed = new URL(url);
124
144
  const path = parsed.pathname;
125
- if (path === '/permission') {
126
- return parsed.searchParams.get('directory') === '/project'
145
+ if (path === '/api/permission/request') {
146
+ return directoryHeader(init) === '/project'
127
147
  ? Response.json([
128
148
  { id: 'root-pending', sessionID: 'root' },
129
149
  { id: 'other-pending', sessionID: 'other' },
130
150
  ])
131
151
  : Response.json([]);
132
152
  }
133
- if (path === '/permission/root-pending/reply' && init.method === 'POST') return Response.json({});
134
- if (path === '/session/other') return Response.json({ id: 'other' });
153
+ if (path === '/api/session/root/permission/root-pending/reply' && init.method === 'POST') return Response.json({});
154
+ if (path === '/api/session/other') return Response.json({ id: 'other' });
135
155
  return new Response('', { status: 404 });
136
156
  });
137
157
  const { runtime } = createRuntime({ fetchImpl });
@@ -141,8 +161,9 @@ describe('permission auto-accept runtime', () => {
141
161
  const replyPaths = fetchImpl.mock.calls
142
162
  .filter(([, init]) => init?.method === 'POST')
143
163
  .map(([url]) => new URL(url).pathname);
144
- expect(replyPaths).toEqual(['/permission/root-pending/reply']);
145
- expect(fetchImpl.mock.calls.some(([url]) => new URL(url).searchParams.get('directory') === '/project')).toBe(true);
164
+ expect(replyPaths).toEqual(['/api/session/root/permission/root-pending/reply']);
165
+ // OpenCode 2.x scopes the pending list by header, not by query.
166
+ expect(fetchImpl.mock.calls.some(([, init]) => directoryHeader(init) === '/project')).toBe(true);
146
167
  expect(await runtime.load()).toEqual({ sessions: { root: true }, revision: 1 });
147
168
  });
148
169
 
@@ -163,8 +184,9 @@ describe('permission auto-accept runtime', () => {
163
184
  emit({ type: 'permission.asked', properties: { id: 'safe', sessionID: 'root', permission: 'bash', metadata: {} } });
164
185
  await flush();
165
186
 
166
- const replies = fetchImpl.mock.calls.map(([url]) => String(url)).filter((url) => url.includes('/reply'));
167
- expect(replies).toEqual(['http://opencode.test/permission/safe/reply?directory=%2Fproject']);
187
+ const replies = fetchImpl.mock.calls.filter(([url]) => String(url).includes('/reply'));
188
+ expect(replies.map(([url]) => String(url))).toEqual(['http://opencode.test/api/session/root/permission/safe/reply']);
189
+ expect(directoryHeader(replies[0]?.[1])).toBe('/project');
168
190
  expect(evaluatePermission).toHaveBeenCalledTimes(2);
169
191
 
170
192
  emit({ type: 'permission.replied', properties: { sessionID: 'root', requestID: 'held', reply: 'once' } });
@@ -20,6 +20,10 @@ extension applies the same policy in its own process at activation.
20
20
 
21
21
  These provider IDs are currently dispatchable via `fetchQuotaForProvider(providerId)` in `packages/web/server/lib/quota/providers/index.js`.
22
22
 
23
+ Where this table says "OpenCode `auth.json`", the credential is read through
24
+ `../opencode/auth.js`, which answers from OpenCode 2.x's own credential
25
+ database first and the legacy file second (see the opencode module docs).
26
+
23
27
  | Provider ID | Display name | Module | Auth aliases/keys |
24
28
  | --- | --- | --- | --- |
25
29
  | `claude` | Claude | `providers/claude/` | Claude Code Keychain entry, Claude Code credentials file, OpenCode `auth.json` (`anthropic`, `claude`), `CLAUDE_CODE_OAUTH_TOKEN` |
@@ -1,4 +1,4 @@
1
- import { readAuthFile, writeAuthFile } from '../../opencode/auth.js';
1
+ import { readAuthFile } from '../../opencode/auth.js';
2
2
  import { buildResult, toUsageWindow } from '../utils/index.js';
3
3
 
4
4
  export const providerId = 'xai';
@@ -104,9 +104,9 @@ const refreshXaiOauth = async (entry) => {
104
104
  expires
105
105
  };
106
106
 
107
- const auth = readAuthFile();
108
- auth.xai = refreshed;
109
- writeAuthFile(auth);
107
+ // The refreshed token is kept in memory for this process only: OpenCode
108
+ // 2.x owns `auth.json` (it imports it once and never reads it again), so
109
+ // writing it back would drift from the credential OpenCode actually uses.
110
110
  return refreshed;
111
111
  })().finally(() => {
112
112
  refreshPromise = null;
@@ -34,7 +34,10 @@ const ALLOWED_WS_PATHS = new Set([
34
34
  '/api/dictation/ws',
35
35
  '/api/dev-tunnel',
36
36
  ]);
37
- export const isAllowedRelayWebSocketPath = (pathname) => ALLOWED_WS_PATHS.has(pathname);
37
+ // Extension surfaces carry the extension id in the path, so they are matched
38
+ // by shape rather than listed.
39
+ const GUEST_SURFACE_WS_PATH = /^\/api\/guests\/[a-z][a-z0-9-]*\/surface\/ws$/;
40
+ export const isAllowedRelayWebSocketPath = (pathname) => ALLOWED_WS_PATHS.has(pathname) || GUEST_SURFACE_WS_PATH.test(pathname);
38
41
 
39
42
  // Hop-by-hop headers stripped from tunneled requests; `host` is set by fetch
40
43
  // to the loopback origin. content-length is dropped too because the body is
@@ -152,4 +152,11 @@ describe('relay host WebSocket allowlist', () => {
152
152
  expect(isAllowedRelayWebSocketPath('/api/dev-tunnel/')).toBe(false);
153
153
  expect(isAllowedRelayWebSocketPath('/api/database/ws')).toBe(false);
154
154
  });
155
+
156
+ test('allows an extension surface socket by path shape only', () => {
157
+ expect(isAllowedRelayWebSocketPath('/api/guests/server-chrome/surface/ws')).toBe(true);
158
+ expect(isAllowedRelayWebSocketPath('/api/guests/Server/surface/ws')).toBe(false);
159
+ expect(isAllowedRelayWebSocketPath('/api/guests/server-chrome/surface/ws/x')).toBe(false);
160
+ expect(isAllowedRelayWebSocketPath('/api/guests/server-chrome/service/request')).toBe(false);
161
+ });
155
162
  });
@@ -9,39 +9,53 @@ it with that category's model, thinking variant and agent. With the safety net
9
9
  on, the same call decides whether an auto-accepted permission should stay on
10
10
  screen for the user instead.
11
11
 
12
- Dark by default: `OPENCHAMBER_ROUTING_ENABLE` (`feature-flag.js`, read per call)
13
- gates the routes, the request rewrite, the settings page and the Auto row. VS
14
- Code has no OpenChamber server and never offers Auto.
12
+ Routing needs the OpenChamber server, so it is present wherever that server is
13
+ and absent everywhere else: VS Code talks to OpenCode directly and never offers
14
+ Auto. There is no env gate — the feature shipped dark behind
15
+ `OPENCHAMBER_ROUTING_ENABLE` until Jev stopped needing a key of the user's own
16
+ (see "Where a Jev request goes"), which was the reason to hide it.
15
17
 
16
18
  ## Files
17
19
 
18
- - `feature-flag.js` — the env gate.
19
- - `defaults.js` — the Auto sentinel, Jev endpoint, built-in categories, the
20
- question wording for routing and for the safety net, history excerpt limits.
20
+ - `defaults.js` — the Auto sentinel, both Jev endpoints, built-in categories,
21
+ the question wording for routing and for the safety net, history excerpt
22
+ limits.
21
23
  - `store.js` — `routing.json` (only deviations from the built-ins) and
22
24
  `routing-auth.json` (the Jev key alone, mode 0600) in the OpenChamber data
23
25
  dir. `resolveEffectiveConfig` merges built-ins with stored overrides;
24
26
  `toStoredConfig` is its inverse. A missing file is the defaults, a malformed
25
27
  one throws.
26
- - `jev.js` — request builders, answer parsing, the HTTP call with a timeout.
28
+ - `jev.js` — request builders, answer parsing, the HTTP call with a timeout,
29
+ and `jevEndpoint`, which picks where one request goes.
27
30
  - `history.js` — the last three settled turns through session assist's
28
31
  `loadAssistContext` (text parts only, attached quotes included, no files or
29
32
  tool payloads), each user message cut to its head and each answer to head
30
33
  plus tail. The new request is never cut.
31
- - `runtime.js` — `createRoutingRuntime`: `describe`, `resolvePromptBody`,
34
+ - `runtime.js` — `createRoutingRuntime`: `describe`, `noteModelSelection`,
35
+ `isAutoSession`, `resolveAutoSelection`, `applySessionSelection`, `routeSend`,
32
36
  `evaluatePermission`, config and token writes, event broadcasts.
33
37
  - `routes.js` — `/api/routing` (GET, PUT), `/api/routing/token` (PUT, DELETE)
34
38
  and `registerRoutingPromptRewrite`.
35
39
 
36
40
  ## Invariants
37
41
 
38
- - The sentinel never reaches OpenCode. `resolvePromptBody` rewrites
39
- `body.model` in place for `POST /api/session/:id/{prompt_async,prompt,command}`
40
- ahead of the generic proxy (which replays a parsed body), inside the
41
- message queue's `sendItem`, and in the OpenChamber session service's own
42
- dispatch (`openchamber-sessions/routes.js`), which posts to OpenCode directly
43
- and can pick Auto up from Session Defaults. Without a fallback model it
44
- throws 400 rather than forwarding.
42
+ - The sentinel never reaches OpenCode. OpenCode 2.x holds the model and agent
43
+ on the session and a prompt body carries only the user's text, so Auto is
44
+ per-session state: `POST /api/session` drops the sentinel from the create
45
+ body (the session starts on OpenCode's default; the first send switches it),
46
+ `POST /api/session/:id/model` with the sentinel is
47
+ swallowed and marks the session (`noteModelSelection`), and every
48
+ `POST /api/session/:id/{prompt,command}` in a marked session is routed
49
+ (`routeSend`) and switches the session onto the answer before the send is
50
+ forwarded. Both sit ahead of the generic proxy, which replays a parsed body.
51
+ The message queue calls `resolveAutoSelection` itself and applies the answer
52
+ with the model/agent switches it already makes. Without a fallback model the
53
+ runtime throws 400 rather than forwarding. The OpenChamber session service
54
+ (`openchamber-sessions/routes.js`) talks to OpenCode through the SDK and can
55
+ pick Auto up from Session Defaults, so it calls `resolveAutoSelection` itself
56
+ before switching the session.
57
+ - The mark lives in process memory. A server restart between the model switch
58
+ and the next send drops it (see the TODO in `runtime.js`).
45
59
  - Every failure keeps the user's own behaviour. A Jev error, timeout, unknown
46
60
  category or low confidence routes to the fallback model; the decision carries
47
61
  the reason. A safety-net failure accepts the permission exactly as auto-accept
@@ -49,13 +63,29 @@ Code has no OpenChamber server and never offers Auto.
49
63
  - A category without a model uses the fallback model *and* variant; a variant
50
64
  only travels with the model it was chosen for. A category agent replaces the
51
65
  composer's agent; an empty one keeps it.
52
- - Auto is offered (`autoReady`) only with the flag, `enabled`, a saved key, a
53
- fallback model and at least two enabled categories.
66
+ - Auto is offered (`autoReady`) with `enabled`, a fallback model and at least
67
+ two enabled categories. A key is not among them: see the endpoint rule below.
54
68
  - Held permission decisions are cached for 15 minutes per request id so
55
69
  reconnect reconciliation in `permission-auto-accept` does not re-ask Jev;
56
70
  `permission.replied` forgets them.
57
- - The rewrite parses JSON only while the flag is set, so a build without it
58
- leaves the proxy stream untouched.
71
+ - The send rewrite reads a body only in a session already marked as routed,
72
+ which the URL alone answers; every other send, and anything that is not JSON,
73
+ reaches the proxy as the stream it arrived as.
74
+
75
+ ## Where a Jev request goes
76
+
77
+ A saved TypeSafe key wins — the user pasted it and it carries their own quota —
78
+ and the request goes to `api.typesafe.ai/v1/systemone` as `jev-latest`. Without
79
+ a key the same body goes to `opencode.ai/zen/v1/systemone` as `jev-1.13-free`,
80
+ which OpenCode Zen answers with no credential at all, tagged
81
+ `x-opencode-client: openchamber`. Dax approved this use in Slack on 2026-09-22
82
+ on terms the code and the copy keep together: zen can identify and throttle our
83
+ calls through that header, and Settings → Routing tells the user in plain words
84
+ that the free model is a limited-time OpenCode promotion that will later need a
85
+ key. Zen rejects the `jev-latest` alias, so the versioned free id is sent.
86
+
87
+ Never make the free tier the only path: the zen docs call it "available for a
88
+ limited time", and the key field is what users fall back to when it ends.
59
89
 
60
90
  ## Events
61
91
 
@@ -79,5 +109,6 @@ debounced saves and manages the key.
79
109
  `store.test.js` (defaults, deviation round-trip, deleted built-ins, malformed
80
110
  file, token file mode), `runtime.test.js` (request text, excerpts, decisions,
81
111
  rewrite and fallback paths, safety net hold/skip/off), `routes.http.test.js`
82
- (rewrite ahead of a stand-in proxy, routes, flag off). The queue and
83
- auto-accept tests cover their hooks.
112
+ (sentinel dropped from a create and swallowed on the model switch, routed send
113
+ ahead of a stand-in proxy, the routes). The queue and auto-accept tests cover
114
+ their hooks.
@@ -14,16 +14,33 @@ import { z } from 'zod';
14
14
  const AUTO_PROVIDER_ID = 'openchamber';
15
15
  const AUTO_MODEL_ID = 'auto';
16
16
 
17
- const autoModelSchema = z.object({ providerID: z.literal(AUTO_PROVIDER_ID), modelID: z.literal(AUTO_MODEL_ID) });
18
- const autoModelStringSchema = z.literal(`${AUTO_PROVIDER_ID}/${AUTO_MODEL_ID}`);
17
+ // v2 names a model `{ providerID, id }` (`Model.Ref`); OpenChamber's own stored
18
+ // config and the message queue's send config name it `{ providerID, modelID }`.
19
+ const autoModelSchema = z.object({ providerID: z.literal(AUTO_PROVIDER_ID), id: z.literal(AUTO_MODEL_ID) });
20
+ const autoStoredModelSchema = z.object({ providerID: z.literal(AUTO_PROVIDER_ID), modelID: z.literal(AUTO_MODEL_ID) });
19
21
 
20
- /** The command route carries the model as `provider/model`; the prompt routes as an object. */
21
- export const isAutoModel = (model) => autoModelSchema.safeParse(model).success || autoModelStringSchema.safeParse(model).success;
22
+ export const isAutoModel = (model) => autoModelSchema.safeParse(model).success
23
+ || autoStoredModelSchema.safeParse(model).success;
22
24
 
23
- export const JEV_API_ORIGIN = 'https://api.typesafe.ai';
24
- export const JEV_API_PATH = '/v1/systemone';
25
+ /** The sentinel itself, in the v2 `Model.Ref` shape. */
26
+ export const AUTO_MODEL_REF = { providerID: AUTO_PROVIDER_ID, id: AUTO_MODEL_ID };
27
+
28
+ /** The user's own TypeSafe key: their quota, their account, the `jev-latest` alias. */
29
+ export const JEV_API_URL = 'https://api.typesafe.ai/v1/systemone';
25
30
  export const JEV_MODEL = 'jev-latest';
26
31
 
32
+ /**
33
+ * OpenCode Zen serves the same System One endpoint and answers without any
34
+ * credential while `jev-1.13-free` is free. Dax approved OpenChamber using it
35
+ * (Slack, 2026-09-22) on terms this module keeps: the UI tells the user it is a
36
+ * limited-time free model that will later need a Zen key, and every call names
37
+ * OpenChamber so zen can see or throttle us. Zen rejects the `jev-latest`
38
+ * alias, so the versioned free id is sent instead.
39
+ */
40
+ export const ZEN_JEV_API_URL = 'https://opencode.ai/zen/v1/systemone';
41
+ export const ZEN_JEV_MODEL = 'jev-1.13-free';
42
+ export const ZEN_CLIENT_ID = 'openchamber';
43
+
27
44
  /** Per-attempt timeout; the lab measured 250–700 ms warm, ~1 s on a cold TLS handshake. */
28
45
  export const JEV_TIMEOUT_MS = 4000;
29
46
 
@@ -4,20 +4,30 @@
4
4
  */
5
5
  import { z } from 'zod';
6
6
  import {
7
- JEV_API_ORIGIN,
8
- JEV_API_PATH,
7
+ JEV_API_URL,
9
8
  JEV_MODEL,
10
9
  JEV_TIMEOUT_MS,
11
10
  ROUTING_INSTRUCTIONS,
12
11
  SAFETY_INSTRUCTIONS,
13
12
  SAFETY_KINDS,
13
+ ZEN_CLIENT_ID,
14
+ ZEN_JEV_API_URL,
15
+ ZEN_JEV_MODEL,
14
16
  } from './defaults.js';
15
17
 
18
+ /**
19
+ * Where one request goes. A saved TypeSafe key wins: the user chose it and it
20
+ * carries their own quota. Without one, the same questions go to the free Jev
21
+ * model OpenCode Zen serves without a credential, identified as OpenChamber.
22
+ */
23
+ export const jevEndpoint = (token) => (token
24
+ ? { url: JEV_API_URL, model: JEV_MODEL, headers: { authorization: `Bearer ${token}` }, source: 'typesafe' }
25
+ : { url: ZEN_JEV_API_URL, model: ZEN_JEV_MODEL, headers: { 'x-opencode-client': ZEN_CLIENT_ID }, source: 'zen-free' });
26
+
16
27
  export const buildRoutingRequest = ({ categories, history, request }) => {
17
28
  const criteria = {};
18
29
  for (const category of categories) criteria[category.id] = category.description;
19
30
  return {
20
- model: JEV_MODEL,
21
31
  state: { history, request },
22
32
  questions: { category: { type: 'choice', instructions: ROUTING_INSTRUCTIONS, criteria } },
23
33
  };
@@ -25,7 +35,6 @@ export const buildRoutingRequest = ({ categories, history, request }) => {
25
35
 
26
36
  /** `permission` is what OpenCode reported: the tool kind, its patterns and its metadata. */
27
37
  export const buildPermissionRequest = (permission) => ({
28
- model: JEV_MODEL,
29
38
  state: {
30
39
  permission: {
31
40
  type: permission.permission,
@@ -75,10 +84,11 @@ export const createJevClient = ({ fetchImpl = fetch, timeoutMs = JEV_TIMEOUT_MS
75
84
  const timer = setTimeout(() => abort.abort(), timeoutMs);
76
85
  const started = Date.now();
77
86
  try {
78
- const response = await fetchImpl(JEV_API_ORIGIN + JEV_API_PATH, {
87
+ const endpoint = jevEndpoint(token);
88
+ const response = await fetchImpl(endpoint.url, {
79
89
  method: 'POST',
80
- headers: { authorization: `Bearer ${token}`, 'content-type': 'application/json' },
81
- body: JSON.stringify(request),
90
+ headers: { ...endpoint.headers, 'content-type': 'application/json' },
91
+ body: JSON.stringify({ ...request, model: endpoint.model }),
82
92
  signal: abort.signal,
83
93
  });
84
94
  const text = await response.text();