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

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 (287) hide show
  1. package/README.md +15 -11
  2. package/bin/cli-opencode.test.js +4 -4
  3. package/bin/cli.js +1 -1
  4. package/bin/lib/cli-startup.js +1 -1
  5. package/bin/lib/cli-startup.test.js +18 -4
  6. package/dist/assets/{AgentAvatar-CLWWNu_Q.js → AgentAvatar-Q1SmFRx2.js} +1 -1
  7. package/dist/assets/AssistantView-CwXkiCAd.js +3 -0
  8. package/dist/assets/{DiagramView-xuSJTnv7.js → DiagramView-BW92schO.js} +1 -1
  9. package/dist/assets/{FadeInOnReveal-Ov0D96d0.js → FadeInOnReveal-BZmwl-zE.js} +1 -1
  10. package/dist/assets/{Icon-DD-BuMfZ.js → Icon-AQPvxMiU.js} +1 -1
  11. package/dist/assets/{MarkdownRenderer-bc_p1H7l.js → MarkdownRenderer-Bv05iDZZ.js} +2 -2
  12. package/dist/assets/{MarkdownRendererImpl-SKi4oG0c.js → MarkdownRendererImpl-C81xqYib.js} +3 -3
  13. package/dist/assets/MarkstreamRendererImpl-CXUCQuKJ.js +3 -0
  14. package/dist/assets/{MobileDetailNavigation-Bnx59U_J.js → MobileDetailNavigation-BMgIu7zc.js} +1 -1
  15. package/dist/assets/{MobileShareBridge-BQlP4J-F.js → MobileShareBridge-BOXGNHmC.js} +2 -2
  16. package/dist/assets/MobileSurface-xuc_z0_G.js +2 -0
  17. package/dist/assets/{MultiRunWindow-ChZGob0v.js → MultiRunWindow-B5At2Z95.js} +1 -1
  18. package/dist/assets/{OnboardingScreen-DtQJxZG-.js → OnboardingScreen-bsPLgz4r.js} +1 -1
  19. package/dist/assets/OverlayScrollbar-4wp4migh.js +2 -0
  20. package/dist/assets/SettingsView-CJ6tcH5_.js +25 -0
  21. package/dist/assets/{SettingsWindow-DfnsFa8y.js → SettingsWindow-CMtX2bMl.js} +1 -1
  22. package/dist/assets/{TerminalView-Dh9MWCpS.js → TerminalView-Dvr-ysTS.js} +1 -1
  23. package/dist/assets/{ToolOutputDialog-BejmjrRk.js → ToolOutputDialog-uH1ZZDSS.js} +2 -2
  24. package/dist/assets/appThemeRegistry-DK7q2juD.js +1 -0
  25. package/dist/assets/{assistant-attachment-cache-C0aRnFkj.js → assistant-attachment-cache-DphJleHr.js} +1 -1
  26. package/dist/assets/{button-D-rATN7w.js → button-CcNJUxUd.js} +1 -1
  27. package/dist/assets/collapsible-ZmZ6rLWs.js +3 -0
  28. package/dist/assets/context-menu-B2LXJgXL.js +1 -0
  29. package/dist/assets/{desktopHosts-B1ixEylr.js → desktopHosts-C6HrP0jq.js} +1 -1
  30. package/dist/assets/{desktopRelayRestore-4JmFUw8r.js → desktopRelayRestore-DdVBp45U.js} +1 -1
  31. package/dist/assets/{diagram-DQf7Y_uR.js → diagram-CxTs9hCQ.js} +1 -1
  32. package/dist/assets/{dialog-bfbvR4VH.js → dialog-KMVL9QKF.js} +1 -1
  33. package/dist/assets/es-CN3E_zXj.js +15 -0
  34. package/dist/assets/{fileMentionTouchSelection-BvrNCroG.js → fileMentionTouchSelection-7RIwlpkB.js} +1 -1
  35. package/dist/assets/{fileQueries-DQc8EJUf.js → fileQueries-CjdBRy8y.js} +1 -1
  36. package/dist/assets/fr-9zZvQZD2.js +13 -0
  37. package/dist/assets/gitApi-yuWg9BN4.js +10 -0
  38. package/dist/assets/{imageSource-BlAJJUEI.js → imageSource-9vQBq3zp.js} +1 -1
  39. package/dist/assets/{input-8r6F_Fza.js → input-CrIvk3HZ.js} +1 -1
  40. package/dist/assets/insertionBoundaries-BiUCSkx-.js +1 -0
  41. package/dist/assets/ja-Dnjk_82N.js +15 -0
  42. package/dist/assets/ko-BxCEE99C.js +15 -0
  43. package/dist/assets/{main-CCE_4y5F.js → main-C9mIBo77.js} +2 -2
  44. package/dist/assets/main-DiL6w6KF.js +169 -0
  45. package/dist/assets/markdown-worker-bS5OgGO3.js +2 -0
  46. package/dist/assets/{markdownHeightCache-1Qo6y6IJ.js → markdownHeightCache-BmXVY_gc.js} +1 -1
  47. package/dist/assets/{markdownTheme-DOfGg-A7.js → markdownTheme-CKac3wKC.js} +1 -1
  48. package/dist/assets/mcpQueries-C7jknHV8.js +1 -0
  49. package/dist/assets/miniChat-CwtUaElU.js +2 -0
  50. package/dist/assets/mobile-DB-6--ws.js +2 -0
  51. package/dist/assets/multirun-TAsxFI8e.js +4 -0
  52. package/dist/assets/{persistence-Ce3AnpEr.js → persistence-56kq8Z4V.js} +2 -2
  53. package/dist/assets/pl-CNNsWnPM.js +15 -0
  54. package/dist/assets/platform-C13fMVEs.js +6 -0
  55. package/dist/assets/pt-BR-BZB2DJMp.js +15 -0
  56. package/dist/assets/{relay-image-stream-DM7atqJs.js → relay-image-stream-DrdVj99c.js} +1 -1
  57. package/dist/assets/renderElectronMiniChatApp-CGj0MNRU.js +2 -0
  58. package/dist/assets/renderMobileApp-CGMMqbsO.js +28 -0
  59. package/dist/assets/runtime-fetch-Fs9ZV2V8.js +1 -0
  60. package/dist/assets/{runtimeConfig-BMvqa5we.js → runtimeConfig-BEANkvOd.js} +1 -1
  61. package/dist/assets/runtimeEndpointReset-A_ZBUJFS.js +147 -0
  62. package/dist/assets/sessionChangeSummary-qOLrP9fw.js +2 -0
  63. package/dist/assets/{sessionLookup-gh-hVRIc.js → sessionLookup-34XFmP9C.js} +1 -1
  64. package/dist/assets/{sortable-tabs-strip-CudoSgV4.js → sortable-tabs-strip-DyEV0Heg.js} +1 -1
  65. package/dist/assets/src-ubCWXZhR.css +2 -0
  66. package/dist/assets/store-DLCiZaA6.js +15 -0
  67. package/dist/assets/{terminalApi-DiF6JmBP.js → terminalApi-CXaBEoa_.js} +1 -1
  68. package/dist/assets/textarea-BBOlRI9O.js +1 -0
  69. package/dist/assets/{tooltip-CQvpK-UV.js → tooltip-CPI4fr1a.js} +1 -1
  70. package/dist/assets/uk-rwQrxeYM.js +15 -0
  71. package/dist/assets/{url-_z7evzdK.js → url-DRDQXjyX.js} +1 -1
  72. package/dist/assets/{useAppFontEffects-Cpmpo7Wb.js → useAppFontEffects-CiDe42I5.js} +57 -43
  73. package/dist/assets/{useAssistantUIStore-CMutVh9B.js → useAssistantUIStore-asNG6K7n.js} +1 -1
  74. package/dist/assets/useDesktopWindowControlsLayout-BlizAlOt.js +1 -0
  75. package/dist/assets/useEffectiveDirectory-C_CouL5v.js +1 -0
  76. package/dist/assets/{useFontPreferences-DcbXQOkc.js → useFontPreferences-B0oUZAiB.js} +1 -1
  77. package/dist/assets/useMobileNavigationStore-DfPClaW-.js +414 -0
  78. package/dist/assets/{useRuntimeAPIs-DSLXJybW.js → useRuntimeAPIs-DwLtO0c_.js} +1 -1
  79. package/dist/assets/useSessionAutoCleanup-DtrfbxAp.js +2 -0
  80. package/dist/assets/{useThemeSystem-DMzWVJaW.js → useThemeSystem-BlNUWH47.js} +1 -1
  81. package/dist/assets/useUIStore-CQYKJnrg.js +1 -0
  82. package/dist/assets/{useWorkerHighlightedLines-Bqn4AiBL.js → useWorkerHighlightedLines-CPNYz6Wf.js} +2 -2
  83. package/dist/assets/{vendor-base-ui-CnHfUmDN.js → vendor-base-ui-CDjyofeD.js} +1 -1
  84. package/dist/assets/{vendor-cmdk-CeAGfrLo.js → vendor-cmdk-BFH0UpXX.js} +1 -1
  85. package/dist/assets/{vendor-dnd-kit-accessibility-BY5LhhUB.js → vendor-dnd-kit-accessibility-BrR3AKvM.js} +1 -1
  86. package/dist/assets/{vendor-dnd-kit-core-CvWxUPdb.js → vendor-dnd-kit-core-CNfdvk6H.js} +1 -1
  87. package/dist/assets/{vendor-dnd-kit-sortable-hHdP9lLb.js → vendor-dnd-kit-sortable-DQUaTeGj.js} +1 -1
  88. package/dist/assets/{vendor-framer-motion-DFy_BSVd.js → vendor-framer-motion-4dogsUk3.js} +1 -1
  89. package/dist/assets/{vendor-legendapp-list-WE32lwNk.js → vendor-legendapp-list-BU7MdPZR.js} +1 -1
  90. package/dist/assets/{vendor-markstream-react-DX55XLGO.js → vendor-markstream-react-qHC2FykU.js} +3 -3
  91. package/dist/assets/{vendor-pierre-diffs-DX7XTPG3.js → vendor-pierre-diffs-CUfqC003.js} +1 -1
  92. package/dist/assets/{vendor-react-DLPElpoQ.js → vendor-react-C94A0AHl.js} +1 -1
  93. package/dist/assets/{vendor-react-drawio-ECtEwvbc.js → vendor-react-drawio-FVlWqKVE.js} +1 -1
  94. package/dist/assets/{vendor-reactuses-core-BnW6GxJt.js → vendor-reactuses-core-C2A3EjUi.js} +1 -1
  95. package/dist/assets/{vendor-sonner-DVzioF3d.js → vendor-sonner-DNqHmUQs.js} +1 -1
  96. package/dist/assets/{vendor-stream-diffs-DWy3xSmp.js → vendor-stream-diffs-CwbKsmlv.js} +2 -2
  97. package/dist/assets/{vendor-tanstack-react-query-D7pys_c9.js → vendor-tanstack-react-query-D0iFK57C.js} +1 -1
  98. package/dist/assets/{vendor-tanstack-react-virtual-tlDgH6PG.js → vendor-tanstack-react-virtual-B2kV6cnT.js} +1 -1
  99. package/dist/assets/{vendor-zustand-B4dlGEM2.js → vendor-zustand-BqLKf6ZY.js} +1 -1
  100. package/dist/assets/zh-CN-BvmBDnkD.js +15 -0
  101. package/dist/assets/zh-TW-9wcHTX0y.js +15 -0
  102. package/dist/index.html +13 -13
  103. package/dist/mini-chat.html +13 -13
  104. package/dist/mobile.html +13 -13
  105. package/package.json +1 -1
  106. package/server/index.js +327 -62
  107. package/server/lib/assistants/DOCUMENTATION.md +14 -9
  108. package/server/lib/assistants/contracts.js +11 -1
  109. package/server/lib/assistants/projection-history.test.js +265 -18
  110. package/server/lib/assistants/routes.js +6 -0
  111. package/server/lib/assistants/service.js +417 -231
  112. package/server/lib/assistants/service.test.js +392 -148
  113. package/server/lib/conversations/DOCUMENTATION.md +1 -1
  114. package/server/lib/conversations/service.js +1 -0
  115. package/server/lib/conversations/service.test.js +16 -0
  116. package/server/lib/conversations/validation.js +11 -1
  117. package/server/lib/event-stream/DOCUMENTATION.md +3 -1
  118. package/server/lib/event-stream/directory-ws-bridge.js +13 -2
  119. package/server/lib/event-stream/global-hub.js +49 -4
  120. package/server/lib/event-stream/reasoning-projection.js +16 -1
  121. package/server/lib/event-stream/reasoning-projection.test.js +15 -0
  122. package/server/lib/event-stream/runtime.js +57 -20
  123. package/server/lib/event-stream/runtime.test.js +112 -0
  124. package/server/lib/llm/DOCUMENTATION.md +33 -9
  125. package/server/lib/llm/catalog.js +1 -1
  126. package/server/lib/llm/completions.js +5 -1
  127. package/server/lib/llm/completions.test.js +3 -3
  128. package/server/lib/llm/generate.js +237 -145
  129. package/server/lib/llm/generate.test.js +201 -46
  130. package/server/lib/llm/routes.js +2 -0
  131. package/server/lib/message-queue/runtime.js +15 -2
  132. package/server/lib/notifications/runtime.js +23 -5
  133. package/server/lib/notifications/runtime.test.js +11 -0
  134. package/server/lib/opencode/DOCUMENTATION.md +85 -17
  135. package/server/lib/opencode/agent-document.d.ts +28 -0
  136. package/server/lib/opencode/agent-document.js +280 -0
  137. package/server/lib/opencode/agent-document.test.js +137 -0
  138. package/server/lib/opencode/agents.js +96 -0
  139. package/server/lib/opencode/auth-state-runtime.js +5 -0
  140. package/server/lib/opencode/bootstrap-runtime.js +2 -0
  141. package/server/lib/opencode/config-entity-routes.js +42 -99
  142. package/server/lib/opencode/config-entity-routes.test.js +107 -0
  143. package/server/lib/opencode/config-hot-reload.integration.test.js +105 -0
  144. package/server/lib/opencode/ensure-cli.js +1 -2
  145. package/server/lib/opencode/env-runtime.js +40 -26
  146. package/server/lib/opencode/env-runtime.test.js +10 -17
  147. package/server/lib/opencode/feature-routes-runtime.js +43 -4
  148. package/server/lib/opencode/feature-routes-runtime.test.js +18 -0
  149. package/server/lib/opencode/http-server-connections.js +98 -0
  150. package/server/lib/opencode/http-server-connections.test.js +321 -0
  151. package/server/lib/opencode/lifecycle.js +349 -39
  152. package/server/lib/opencode/lifecycle.test.js +322 -0
  153. package/server/lib/opencode/managed-capabilities-runtime.js +15 -2
  154. package/server/lib/opencode/managed-capabilities-runtime.test.js +1 -0
  155. package/server/lib/opencode/openchamber-routes.js +7 -1
  156. package/server/lib/opencode/opencode2-pin.js +4 -4
  157. package/server/lib/opencode/opencode2-pin.test.js +1 -0
  158. package/server/lib/opencode/opencode2-sidecar.e2e.test.js +127 -4
  159. package/server/lib/opencode/owned-runtime-upgrade.js +431 -0
  160. package/server/lib/opencode/owned-runtime-upgrade.test.js +160 -0
  161. package/server/lib/opencode/plugin-routes.js +3 -18
  162. package/server/lib/opencode/plugin-routes.test.js +9 -9
  163. package/server/lib/opencode/provider-catalog.js +58 -12
  164. package/server/lib/opencode/provider-catalog.test.js +48 -0
  165. package/server/lib/opencode/proxy.js +252 -46
  166. package/server/lib/opencode/proxy.test.js +235 -2
  167. package/server/lib/opencode/routes.js +296 -62
  168. package/server/lib/opencode/routes.test.js +278 -12
  169. package/server/lib/opencode/runtime-contract.js +401 -0
  170. package/server/lib/opencode/runtime-contract.test.js +214 -0
  171. package/server/lib/opencode/server-opencode-fetch.js +242 -0
  172. package/server/lib/opencode/server-opencode-fetch.test.js +130 -0
  173. package/server/lib/opencode/server-utils-runtime.js +33 -0
  174. package/server/lib/opencode/shared-service.integration.test.js +66 -0
  175. package/server/lib/opencode/shared-service.js +147 -0
  176. package/server/lib/opencode/shared-service.test.js +93 -0
  177. package/server/lib/opencode/shared.js +3 -3
  178. package/server/lib/opencode/shutdown-runtime.js +15 -6
  179. package/server/lib/opencode/shutdown-runtime.test.js +49 -1
  180. package/server/lib/opencode/skill-routes.js +20 -33
  181. package/server/lib/opencode/skill-routes.test.js +35 -2
  182. package/server/lib/opencode/skills.test.js +15 -0
  183. package/server/lib/opencode/startup-pipeline-runtime.js +4 -0
  184. package/server/lib/opencode/static-routes-runtime.js +3 -1
  185. package/server/lib/opencode/static-routes-runtime.test.js +33 -0
  186. package/server/lib/opencode/v2-client.js +14 -3
  187. package/server/lib/opencode/v2-plugin-host-shim/index.mjs +155 -0
  188. package/server/lib/opencode/v2-plugin-host-shim/package.json +4 -0
  189. package/server/lib/permission-auto-accept/DOCUMENTATION.md +5 -5
  190. package/server/lib/permission-auto-accept/runtime.js +14 -7
  191. package/server/lib/permission-auto-accept/runtime.test.js +27 -12
  192. package/server/lib/relay/reasoning-projection.e2e.test.ts +22 -7
  193. package/server/lib/scheduled-tasks/DOCUMENTATION.md +8 -0
  194. package/server/lib/scheduled-tasks/runtime.js +52 -4
  195. package/server/lib/scheduled-tasks/runtime.test.js +80 -1
  196. package/server/lib/session-goal/DOCUMENTATION.md +113 -13
  197. package/server/lib/session-goal/routes.js +9 -0
  198. package/server/lib/session-goal/runtime.js +1579 -117
  199. package/server/lib/session-goal/runtime.test.js +1453 -84
  200. package/server/lib/session-index/DOCUMENTATION.md +18 -15
  201. package/server/lib/session-index/event-ingest.js +89 -16
  202. package/server/lib/session-index/event-ingest.test.js +73 -0
  203. package/server/lib/session-index/host-authority-snapshot.js +92 -0
  204. package/server/lib/session-index/routes.host-archive.test.js +201 -0
  205. package/server/lib/session-index/routes.js +63 -10
  206. package/server/lib/session-index/routes.test.js +8 -8
  207. package/server/lib/session-index/service.js +7 -1
  208. package/server/lib/session-index/sync-runtime.js +161 -34
  209. package/server/lib/session-index/sync-runtime.test.js +155 -1
  210. package/server/lib/session-metadata/DOCUMENTATION.md +61 -16
  211. package/server/lib/session-metadata/routes.js +108 -9
  212. package/server/lib/session-metadata/routes.test.js +47 -2
  213. package/server/lib/session-metadata/session-archive.d.ts +73 -0
  214. package/server/lib/session-metadata/session-archive.js +654 -0
  215. package/server/lib/session-metadata/session-archive.test.js +359 -0
  216. package/server/lib/session-metadata/session-metadata-store.d.ts +36 -0
  217. package/server/lib/session-metadata/session-metadata-store.js +212 -72
  218. package/server/lib/session-metadata/session-metadata-store.test.js +205 -10
  219. package/server/lib/session-metadata/session-projection.d.ts +41 -0
  220. package/server/lib/session-metadata/session-projection.js +255 -0
  221. package/server/lib/session-metadata/session-projection.test.js +64 -0
  222. package/server/lib/session-metadata/system-session.js +55 -0
  223. package/server/lib/session-metadata/system-session.test.js +55 -0
  224. package/server/lib/session-title/DOCUMENTATION.md +40 -7
  225. package/server/lib/session-title/native-runtime.test.js +167 -0
  226. package/server/lib/session-title/runtime.js +67 -42
  227. package/server/lib/session-title/session-access.js +46 -0
  228. package/server/lib/session-title/session-access.test.js +91 -0
  229. package/server/lib/session-turn-pages/DOCUMENTATION.md +9 -8
  230. package/server/lib/session-turn-pages/routes.js +17 -102
  231. package/server/lib/session-turn-pages/routes.test.js +281 -0
  232. package/server/lib/session-turn-pages/session-message-projection.d.ts +13 -0
  233. package/server/lib/session-turn-pages/session-message-projection.js +281 -0
  234. package/server/lib/small-model/DOCUMENTATION.md +101 -147
  235. package/server/lib/small-model/catalog.js +31 -51
  236. package/server/lib/small-model/catalog.test.js +65 -10
  237. package/server/lib/small-model/custom-api.js +68 -0
  238. package/server/lib/small-model/custom-api.test.js +64 -0
  239. package/server/lib/small-model/index.js +124 -240
  240. package/server/lib/small-model/resolve.js +35 -120
  241. package/server/lib/small-model/resolve.test.js +20 -110
  242. package/server/lib/small-model/routes.js +1 -3
  243. package/server/lib/small-model/summary-settings.test.js +157 -253
  244. package/server/opencode-proxy.test.js +2 -0
  245. package/dist/assets/AssistantView-BTCkm8yk.js +0 -3
  246. package/dist/assets/MarkstreamRendererImpl-CnkodGJW.js +0 -3
  247. package/dist/assets/MobileSurface-BXLeiZ4d.js +0 -2
  248. package/dist/assets/OverlayScrollbar-pMwxaCca.js +0 -2
  249. package/dist/assets/SettingsView-B5I0KDTZ.js +0 -25
  250. package/dist/assets/appThemeRegistry-COGfxTFV.js +0 -1
  251. package/dist/assets/collapsible-BXRd5_Hr.js +0 -1
  252. package/dist/assets/context-menu-jGtH6fR9.js +0 -1
  253. package/dist/assets/es-c52LTHHe.js +0 -15
  254. package/dist/assets/fr-PisFbUE8.js +0 -13
  255. package/dist/assets/gitApi-ButwzQ0N.js +0 -9
  256. package/dist/assets/insertionBoundaries-DGs7S5zv.js +0 -1
  257. package/dist/assets/ja-C8Ikun3Z.js +0 -15
  258. package/dist/assets/ko-ClFys5o2.js +0 -15
  259. package/dist/assets/main-Dn9rpsgl.js +0 -169
  260. package/dist/assets/markdown-worker-CptoICmq.js +0 -2
  261. package/dist/assets/miniChat-BpjxVqgg.js +0 -2
  262. package/dist/assets/mobile-nMJCaNq1.js +0 -2
  263. package/dist/assets/multirun-BvSG94mW.js +0 -4
  264. package/dist/assets/pl-CnaKWN5F.js +0 -15
  265. package/dist/assets/platform-CWTIp02q.js +0 -4
  266. package/dist/assets/projectMeta-DOe49dM_.js +0 -3
  267. package/dist/assets/pt-BR-s8OpfRI6.js +0 -15
  268. package/dist/assets/renderElectronMiniChatApp-R6dKh79f.js +0 -2
  269. package/dist/assets/renderMobileApp-y-S8y8v_.js +0 -28
  270. package/dist/assets/runtime-fetch-zIalUnbb.js +0 -1
  271. package/dist/assets/runtimeEndpointReset-B6BxhvR0.js +0 -147
  272. package/dist/assets/sessionChangeSummary-VVBlEXuh.js +0 -2
  273. package/dist/assets/src-BzX5UI-c.css +0 -2
  274. package/dist/assets/store-DJczmczv.js +0 -15
  275. package/dist/assets/textarea-3XR8sUfy.js +0 -1
  276. package/dist/assets/uk-CaX0iqkB.js +0 -15
  277. package/dist/assets/useDesktopWindowControlsLayout-B1GX59-L.js +0 -1
  278. package/dist/assets/useEffectiveDirectory-CN6JoCFw.js +0 -1
  279. package/dist/assets/useMobileNavigationStore-BVjFtXSy.js +0 -414
  280. package/dist/assets/useSessionAutoCleanup-BLjymaFt.js +0 -2
  281. package/dist/assets/useUIStore-Bxv3f23A.js +0 -1
  282. package/dist/assets/zh-CN-20MYEjha.js +0 -15
  283. package/dist/assets/zh-TW-DiN04qTw.js +0 -15
  284. package/server/lib/small-model/call.js +0 -427
  285. package/server/lib/small-model/call.test.js +0 -382
  286. package/server/lib/small-model/opencode-session.js +0 -183
  287. package/server/lib/small-model/opencode-session.test.js +0 -147
@@ -7,50 +7,51 @@ const tempRoot = await fsPromises.mkdtemp(path.join(os.tmpdir(), 'oc-summary-set
7
7
  const originalDataDir = process.env.OPENCHAMBER_DATA_DIR;
8
8
  process.env.OPENCHAMBER_DATA_DIR = tempRoot;
9
9
 
10
- vi.mock('../opencode/auth.js', () => ({
11
- readAuthFile: vi.fn(() => ({})),
12
- }));
13
-
14
10
  vi.mock('../opencode/shared.js', () => ({
15
11
  readConfigLayers: vi.fn(() => ({ mergedConfig: {} })),
16
12
  }));
17
13
 
18
- vi.mock('./call.js', () => ({
19
- callSmallModel: vi.fn(async () => 'Generated summary'),
20
- }));
21
-
22
- vi.mock('./opencode-session.js', () => ({
23
- generateViaOpenCodeSession: vi.fn(async () => 'Session path summary'),
24
- stop: vi.fn(async () => {}),
14
+ vi.mock('./custom-api.js', async (importOriginal) => ({
15
+ ...(await importOriginal()),
16
+ generateCustomSummaryText: vi.fn(async () => 'Generated summary'),
25
17
  }));
26
18
 
27
19
  const { createSmallModelService } = await import('./index.js');
28
- const { callSmallModel } = await import('./call.js');
29
- const { generateViaOpenCodeSession } = await import('./opencode-session.js');
30
- const { readAuthFile } = await import('../opencode/auth.js');
31
-
32
- const createService = (catalog = {}, connected = null) => createSmallModelService({
33
- buildOpenCodeUrl: () => 'http://127.0.0.1:4096/',
34
- getOpenCodeAuthHeaders: () => ({}),
35
- getModelCatalog: async () => catalog,
36
- getConnectedCatalog: async () => {
37
- if (connected === null) throw new Error('connected catalog unavailable');
38
- return { connected, providers: [], models: [] };
39
- },
20
+ const { generateCustomSummaryText } = await import('./custom-api.js');
21
+
22
+ const writeSettings = (settings) => fsPromises.writeFile(
23
+ path.join(tempRoot, 'settings.json'),
24
+ JSON.stringify(settings),
25
+ 'utf8',
26
+ );
27
+
28
+ const completionOf = (text) => ({
29
+ completion: { choices: [{ message: { role: 'assistant', content: text } }] },
40
30
  });
41
31
 
32
+ const createService = ({ catalog = {}, defaultModel = null } = {}) => {
33
+ const createChatCompletion = vi.fn(async () => completionOf('Gateway summary'));
34
+ const modelDefault = vi.fn(async () => ({ data: defaultModel }));
35
+ const service = createSmallModelService({
36
+ buildOpenCodeUrl: () => 'http://127.0.0.1:4096/',
37
+ getOpenCodeAuthHeaders: () => ({}),
38
+ getModelCatalog: async () => catalog,
39
+ openCodeClientFactory: () => ({ model: { default: modelDefault } }),
40
+ createChatCompletion,
41
+ });
42
+ return { ...service, createChatCompletion, modelDefault };
43
+ };
44
+
42
45
  describe('summary AI settings', () => {
43
46
  beforeEach(async () => {
44
- vi.mocked(callSmallModel).mockClear();
45
- vi.mocked(generateViaOpenCodeSession).mockClear();
46
- vi.mocked(readAuthFile).mockReturnValue({});
47
- await fsPromises.writeFile(path.join(tempRoot, 'settings.json'), JSON.stringify({
47
+ vi.mocked(generateCustomSummaryText).mockClear();
48
+ await writeSettings({
48
49
  summaryModelMode: 'custom',
49
50
  summaryCustomBaseURL: 'https://summary.example.test/v1',
50
51
  summaryModelID: 'summary-model',
51
52
  summaryCustomAPIToken: 'summary-token',
52
53
  summaryCommitPrompt: 'Return commit JSON.',
53
- }), 'utf8');
54
+ });
54
55
  });
55
56
 
56
57
  afterAll(async () => {
@@ -63,7 +64,7 @@ describe('summary AI settings', () => {
63
64
  });
64
65
 
65
66
  it('uses a persisted custom API and prompt for commit summaries', async () => {
66
- const { generateSmallModelText } = createService();
67
+ const { generateSmallModelText, createChatCompletion } = createService();
67
68
  const result = await generateSmallModelText({
68
69
  purpose: 'commit',
69
70
  prompt: 'Diff content',
@@ -71,16 +72,15 @@ describe('summary AI settings', () => {
71
72
  maxOutputTokens: 64,
72
73
  });
73
74
 
74
- expect(callSmallModel).toHaveBeenCalledWith(expect.objectContaining({
75
- providerID: 'custom',
75
+ expect(createChatCompletion).not.toHaveBeenCalled();
76
+ expect(generateCustomSummaryText).toHaveBeenCalledWith({
77
+ baseURL: 'https://summary.example.test/v1',
78
+ apiToken: 'summary-token',
76
79
  modelID: 'summary-model',
80
+ prompt: 'Diff content',
77
81
  system: 'Return commit JSON.',
78
- custom: {
79
- baseURL: 'https://summary.example.test/v1',
80
- apiToken: 'summary-token',
81
- modelID: 'summary-model',
82
- },
83
- }));
82
+ maxOutputTokens: 64,
83
+ });
84
84
  expect(result).toEqual({
85
85
  text: 'Generated summary',
86
86
  providerID: 'custom',
@@ -90,251 +90,105 @@ describe('summary AI settings', () => {
90
90
  expect(JSON.stringify(result)).not.toContain('summary-token');
91
91
  });
92
92
 
93
- it('uses the displayed default Summary AI model across session providers', async () => {
94
- vi.mocked(readAuthFile).mockReturnValue({
95
- openai: {
96
- type: 'oauth',
97
- access: 'openai-access-token',
98
- },
93
+ it('routes a saved provider model through the Assistant LLM gateway', async () => {
94
+ await writeSettings({
95
+ summaryModelMode: 'provider',
96
+ summaryProviderID: 'openai',
97
+ summaryModelID: 'gpt-5.4-mini',
98
+ summaryCommitPrompt: 'Return commit JSON.',
99
99
  });
100
- await fsPromises.writeFile(path.join(tempRoot, 'settings.json'), JSON.stringify({}), 'utf8');
101
100
 
102
- const { generateSmallModelText } = createService();
101
+ const { generateSmallModelText, createChatCompletion, modelDefault } = createService();
103
102
  const result = await generateSmallModelText({
104
- purpose: 'session-title',
105
- prompt: 'Conversation content',
106
- preferredProviderID: 'anthropic',
107
- preferredModelID: 'claude-sonnet-4-5',
108
- restrictToPreferredProvider: true,
103
+ purpose: 'commit',
104
+ prompt: 'Diff content',
105
+ system: 'Fallback system prompt',
109
106
  });
110
107
 
111
- expect(callSmallModel).toHaveBeenCalledWith(expect.objectContaining({
112
- providerID: 'openai',
113
- modelID: 'gpt-5.4-mini',
108
+ expect(modelDefault).not.toHaveBeenCalled();
109
+ expect(generateCustomSummaryText).not.toHaveBeenCalled();
110
+ expect(createChatCompletion).toHaveBeenCalledWith(expect.objectContaining({
111
+ body: {
112
+ providerID: 'openai',
113
+ modelID: 'gpt-5.4-mini',
114
+ messages: [
115
+ { role: 'system', content: 'Return commit JSON.' },
116
+ { role: 'user', content: 'Diff content' },
117
+ ],
118
+ },
114
119
  }));
115
120
  expect(result).toEqual({
116
- text: 'Generated summary',
121
+ text: 'Gateway summary',
117
122
  providerID: 'openai',
118
123
  modelID: 'gpt-5.4-mini',
119
- source: 'summary-default',
120
- });
121
- });
122
-
123
- it('excludes dedicated providers with unsupported auth types from callable lists', async () => {
124
- vi.mocked(readAuthFile).mockReturnValue({
125
- openai: { type: 'wellknown', token: 'openai-wellknown-token' },
126
- anthropic: { type: 'oauth', access: 'anthropic-access', refresh: 'anthropic-refresh', expires: 0 },
127
- google: { type: 'oauth', access: 'google-access', refresh: 'google-refresh', expires: 0 },
128
- mistral: { type: 'api', key: 'mistral-key' },
129
- });
130
-
131
- const catalog = {
132
- mistral: {
133
- id: 'mistral',
134
- name: 'Mistral',
135
- models: {
136
- 'mistral-small': {
137
- id: 'mistral-small',
138
- api: { url: 'https://api.mistral.ai/v1' },
139
- },
140
- },
141
- },
142
- anthropic: {
143
- id: 'anthropic',
144
- name: 'Anthropic',
145
- models: {
146
- 'claude-haiku-4-5': { id: 'claude-haiku-4-5', family: 'claude-haiku' },
147
- },
148
- },
149
- };
150
-
151
- const { listCallableProviders, listCallableModels } = createService(catalog);
152
- const providers = await listCallableProviders();
153
- const models = await listCallableModels();
154
-
155
- expect(providers).toEqual(['mistral']);
156
- expect(models).toEqual({ mistral: ['mistral-small'] });
157
- expect(providers).not.toContain('openai');
158
- expect(providers).not.toContain('anthropic');
159
- expect(providers).not.toContain('google');
160
- });
161
-
162
- it('lists OpenAI api/oauth, Anthropic/Google api keys, and Copilot aliases as callable', async () => {
163
- vi.mocked(readAuthFile).mockReturnValue({
164
- openai: { type: 'api', key: 'sk-openai' },
165
- anthropic: { type: 'api', key: 'sk-anthropic' },
166
- google: { type: 'api', key: 'google-key' },
167
- copilot: { type: 'oauth', access: 'copilot-token', refresh: 'copilot-token', expires: 0 },
168
- });
169
-
170
- const catalog = {
171
- openai: {
172
- id: 'openai',
173
- name: 'OpenAI',
174
- models: { 'gpt-4.1-nano': { id: 'gpt-4.1-nano', family: 'gpt-nano' } },
175
- },
176
- anthropic: {
177
- id: 'anthropic',
178
- name: 'Anthropic',
179
- models: { 'claude-haiku-4-5': { id: 'claude-haiku-4-5', family: 'claude-haiku' } },
180
- },
181
- google: {
182
- id: 'google',
183
- name: 'Google',
184
- models: { 'gemini-2.5-flash': { id: 'gemini-2.5-flash', family: 'gemini-flash' } },
185
- },
186
- };
187
-
188
- const { listCallableProviders, listCallableModels } = createService(catalog);
189
- const providers = await listCallableProviders();
190
- const models = await listCallableModels();
191
-
192
- expect(providers.sort()).toEqual(['anthropic', 'github-copilot', 'google', 'openai']);
193
- expect(models.openai).toEqual(['gpt-4.1-nano']);
194
- expect(models.anthropic).toEqual(['claude-haiku-4-5']);
195
- expect(models.google).toEqual(['gemini-2.5-flash']);
196
- expect(models['github-copilot']).toEqual(['gpt-5.4-nano']);
197
- });
198
-
199
- it('keeps OpenAI oauth callable with the codex small model only', async () => {
200
- vi.mocked(readAuthFile).mockReturnValue({
201
- openai: { type: 'oauth', access: 'openai-access', refresh: 'openai-refresh', expires: 0 },
202
- });
203
-
204
- const { listCallableProviders, listCallableModels } = createService({});
205
- expect(await listCallableProviders()).toEqual(['openai']);
206
- expect(await listCallableModels()).toEqual({ openai: ['gpt-5.4-mini'] });
207
- });
208
-
209
- it('lists logged-in providers with catalog models even without api.url', async () => {
210
- vi.mocked(readAuthFile).mockReturnValue({
211
- mistral: { type: 'api', key: 'mistral-key' },
212
- deepseek: { type: 'api', key: 'deepseek-key' },
213
- emptyplug: { type: 'api', key: 'empty-key' },
214
- });
215
-
216
- const catalog = {
217
- mistral: {
218
- id: 'mistral',
219
- name: 'Mistral',
220
- models: {
221
- 'mistral-small': { id: 'mistral-small' },
222
- },
223
- },
224
- deepseek: {
225
- id: 'deepseek',
226
- name: 'DeepSeek',
227
- models: {
228
- 'deepseek-chat': {
229
- id: 'deepseek-chat',
230
- api: { url: 'https://api.deepseek.com/v1' },
231
- },
232
- },
233
- },
234
- emptyplug: {
235
- id: 'emptyplug',
236
- name: 'Empty',
237
- models: {},
238
- },
239
- };
240
-
241
- const { listCallableProviders, listCallableModels } = createService(catalog);
242
- expect((await listCallableProviders()).sort()).toEqual(['deepseek', 'mistral']);
243
- expect(await listCallableModels()).toEqual({
244
- mistral: ['mistral-small'],
245
- deepseek: ['deepseek-chat'],
124
+ source: 'summary-provider',
246
125
  });
247
126
  });
248
127
 
249
- it('routes non-dedicated providers through the OpenCode session path', async () => {
250
- vi.mocked(readAuthFile).mockReturnValue({
251
- codebuddy: { type: 'api', key: 'codebuddy-key' },
128
+ it('follows the OpenCode default model when no summary model is saved', async () => {
129
+ await writeSettings({});
130
+ const { generateSmallModelText, createChatCompletion } = createService({
131
+ defaultModel: { id: 'claude-sonnet-4-5', modelID: 'internal-pack', providerID: 'anthropic' },
252
132
  });
253
- await fsPromises.writeFile(path.join(tempRoot, 'settings.json'), JSON.stringify({
254
- summaryModelMode: 'provider',
255
- summaryProviderID: 'codebuddy',
256
- summaryModelID: 'codebuddy-flash',
257
- }), 'utf8');
258
-
259
- const catalog = {
260
- codebuddy: {
261
- id: 'codebuddy',
262
- name: 'CodeBuddy',
263
- models: {
264
- 'codebuddy-flash': { id: 'codebuddy-flash' },
265
- },
266
- },
267
- };
268
133
 
269
- const { generateSmallModelText } = createService(catalog);
270
134
  const result = await generateSmallModelText({
271
- purpose: 'commit',
272
- prompt: 'Diff content',
273
- system: 'Fallback system prompt',
135
+ purpose: 'session-title',
136
+ prompt: 'Conversation content',
137
+ system: 'Title system prompt',
138
+ preferredProviderID: 'openai',
139
+ restrictToPreferredProvider: true,
274
140
  });
275
141
 
276
- expect(callSmallModel).not.toHaveBeenCalled();
277
- expect(generateViaOpenCodeSession).toHaveBeenCalledWith(expect.objectContaining({
278
- providerID: 'codebuddy',
279
- modelID: 'codebuddy-flash',
280
- purpose: 'commit',
142
+ expect(createChatCompletion).toHaveBeenCalledWith(expect.objectContaining({
143
+ body: expect.objectContaining({ providerID: 'anthropic', modelID: 'claude-sonnet-4-5' }),
281
144
  }));
282
- expect(result).toEqual({
283
- text: 'Session path summary',
284
- providerID: 'codebuddy',
285
- modelID: 'codebuddy-flash',
286
- source: 'summary-provider',
145
+ expect(result).toMatchObject({
146
+ providerID: 'anthropic',
147
+ modelID: 'claude-sonnet-4-5',
148
+ source: 'summary-default',
287
149
  });
288
150
  });
289
151
 
290
- it('lists connected plugin providers that have no auth.json entry', async () => {
291
- vi.mocked(readAuthFile).mockReturnValue({});
292
- const catalog = {
293
- codebuddy: {
294
- id: 'codebuddy',
295
- name: 'CodeBuddy',
296
- models: { 'codebuddy-flash': { id: 'codebuddy-flash' } },
297
- },
298
- };
299
-
300
- const { listCallableProviders, listCallableModels } = createService(catalog, ['codebuddy']);
301
- expect(await listCallableProviders()).toEqual(['codebuddy']);
302
- expect(await listCallableModels()).toEqual({ codebuddy: ['codebuddy-flash'] });
303
- });
152
+ it('fails explicitly when OpenCode has no default model', async () => {
153
+ await writeSettings({ summaryModelMode: 'provider' });
154
+ const { generateSmallModelText, createChatCompletion } = createService();
304
155
 
305
- it('does not list auth.json plugin providers that are not connected', async () => {
306
- vi.mocked(readAuthFile).mockReturnValue({
307
- mistral: { type: 'api', key: 'mistral-key' },
156
+ await expect(generateSmallModelText({
157
+ purpose: 'commit',
158
+ prompt: 'Diff content',
159
+ })).rejects.toMatchObject({
160
+ message: 'No OpenCode default model is configured',
161
+ statusCode: 404,
308
162
  });
309
- const catalog = {
310
- mistral: {
311
- id: 'mistral',
312
- name: 'Mistral',
313
- models: { 'mistral-small': { id: 'mistral-small' } },
314
- },
315
- };
316
-
317
- const { listCallableProviders, listCallableModels } = createService(catalog, []);
318
- expect(await listCallableProviders()).toEqual([]);
319
- expect(await listCallableModels()).toEqual({});
163
+ expect(createChatCompletion).not.toHaveBeenCalled();
320
164
  });
321
165
 
322
- it('keeps dedicated providers callable from auth.json even when they are not connected', async () => {
323
- vi.mocked(readAuthFile).mockReturnValue({
324
- openai: { type: 'oauth', access: 'openai-access', refresh: 'openai-refresh', expires: 0 },
166
+ it('surfaces gateway failures instead of falling back to another model', async () => {
167
+ await writeSettings({
168
+ summaryModelMode: 'provider',
169
+ summaryProviderID: 'openai',
170
+ summaryModelID: 'missing-model',
325
171
  });
172
+ const { generateSmallModelText, createChatCompletion } = createService();
173
+ createChatCompletion.mockRejectedValueOnce(Object.assign(
174
+ new Error('No connected OpenCode provider for openai/missing-model'),
175
+ { statusCode: 400, code: 'no_provider' },
176
+ ));
326
177
 
327
- const { listCallableProviders, listCallableModels } = createService({}, []);
328
- expect(await listCallableProviders()).toEqual(['openai']);
329
- expect(await listCallableModels()).toEqual({ openai: ['gpt-5.4-mini'] });
178
+ await expect(generateSmallModelText({
179
+ purpose: 'commit',
180
+ prompt: 'Diff content',
181
+ })).rejects.toMatchObject({ statusCode: 400, code: 'no_provider' });
182
+ expect(createChatCompletion).toHaveBeenCalledTimes(1);
183
+ expect(generateCustomSummaryText).not.toHaveBeenCalled();
330
184
  });
331
185
 
332
186
  it('requires a custom base URL, model ID, and token before enabling custom mode', async () => {
333
- await fsPromises.writeFile(path.join(tempRoot, 'settings.json'), JSON.stringify({
187
+ await writeSettings({
334
188
  summaryModelMode: 'custom',
335
189
  summaryCustomBaseURL: 'https://summary.example.test/v1',
336
190
  summaryModelID: 'summary-model',
337
- }), 'utf8');
191
+ });
338
192
 
339
193
  const { generateSmallModelText } = createService();
340
194
  await expect(generateSmallModelText({
@@ -347,13 +201,13 @@ describe('summary AI settings', () => {
347
201
  });
348
202
 
349
203
  it('prefers summaryCustomModelID over the shared summaryModelID for custom calls', async () => {
350
- await fsPromises.writeFile(path.join(tempRoot, 'settings.json'), JSON.stringify({
204
+ await writeSettings({
351
205
  summaryModelMode: 'custom',
352
206
  summaryCustomBaseURL: 'https://summary.example.test/v1',
353
207
  summaryModelID: 'provider-model',
354
208
  summaryCustomModelID: 'custom-model',
355
209
  summaryCustomAPIToken: 'summary-token',
356
- }), 'utf8');
210
+ });
357
211
 
358
212
  const { generateSmallModelText } = createService();
359
213
  await generateSmallModelText({
@@ -361,9 +215,59 @@ describe('summary AI settings', () => {
361
215
  prompt: 'Diff content',
362
216
  });
363
217
 
364
- expect(callSmallModel).toHaveBeenCalledWith(expect.objectContaining({
218
+ expect(generateCustomSummaryText).toHaveBeenCalledWith(expect.objectContaining({
365
219
  modelID: 'custom-model',
366
- custom: expect.objectContaining({ modelID: 'custom-model' }),
367
220
  }));
368
221
  });
222
+
223
+ it('sends explicit non-summary models through the same gateway', async () => {
224
+ await writeSettings({ summaryModelMode: 'provider', summaryProviderID: 'openai', summaryModelID: 'gpt-5.4' });
225
+
226
+ const { generateSmallModelText, createChatCompletion } = createService({
227
+ catalog: { codebuddy: { id: 'codebuddy', name: 'CodeBuddy', models: { 'codebuddy-flash': { id: 'codebuddy-flash' } } } },
228
+ });
229
+ const result = await generateSmallModelText({
230
+ purpose: 'goal',
231
+ prompt: 'Goal content',
232
+ model: 'codebuddy/codebuddy-flash',
233
+ });
234
+
235
+ expect(createChatCompletion).toHaveBeenCalledWith(expect.objectContaining({
236
+ body: expect.objectContaining({ providerID: 'codebuddy', modelID: 'codebuddy-flash' }),
237
+ }));
238
+ expect(result).toMatchObject({ providerID: 'codebuddy', modelID: 'codebuddy-flash', source: 'request' });
239
+ });
240
+
241
+ it('resolves non-summary purposes from the connected catalog on the session provider', async () => {
242
+ await writeSettings({});
243
+ const { generateSmallModelText, createChatCompletion } = createService({
244
+ catalog: {
245
+ openai: { id: 'openai', models: { 'gpt-5.5': { id: 'gpt-5.5' } } },
246
+ google: { id: 'google', models: { 'gemini-2.5-flash': { id: 'gemini-2.5-flash', family: 'gemini-flash' } } },
247
+ },
248
+ });
249
+ const result = await generateSmallModelText({
250
+ purpose: 'goal',
251
+ prompt: 'Goal content',
252
+ preferredProviderID: 'openai',
253
+ preferredModelID: 'gpt-5.5',
254
+ restrictToPreferredProvider: true,
255
+ });
256
+
257
+ expect(createChatCompletion).toHaveBeenCalledWith(expect.objectContaining({
258
+ body: expect.objectContaining({ providerID: 'openai', modelID: 'gpt-5.5' }),
259
+ }));
260
+ expect(result).toMatchObject({ source: 'session-model' });
261
+ });
262
+
263
+ it('fails explicitly when no connected provider has a model', async () => {
264
+ await writeSettings({});
265
+ const { generateSmallModelText, createChatCompletion } = createService({ catalog: {} });
266
+
267
+ await expect(generateSmallModelText({
268
+ purpose: 'goal',
269
+ prompt: 'Goal content',
270
+ })).rejects.toMatchObject({ statusCode: 404 });
271
+ expect(createChatCompletion).not.toHaveBeenCalled();
272
+ });
369
273
  });
@@ -379,6 +379,7 @@ describe('OpenCode proxy SSE forwarding', () => {
379
379
  isOpenCodeReady: true,
380
380
  openCodeNotReadySince: 0,
381
381
  isRestartingOpenCode: false,
382
+ runtimeContract: { executionAllowed: true },
382
383
  }),
383
384
  getOpenCodeAuthHeaders: () => ({ Authorization: 'Bearer replay-token' }),
384
385
  buildOpenCodeUrl: (requestPath) => `${externalBaseUrl}${requestPath}`,
@@ -618,6 +619,7 @@ describe('OpenCode proxy SSE forwarding', () => {
618
619
  isOpenCodeReady: true,
619
620
  openCodeNotReadySince: 0,
620
621
  isRestartingOpenCode: false,
622
+ runtimeContract: { executionAllowed: true },
621
623
  }),
622
624
  getOpenCodeAuthHeaders: () => ({}),
623
625
  buildOpenCodeUrl: (requestPath) => `${externalBaseUrl}${requestPath}`,