@openchambery/web 1.19.22-beta.1 → 2.0.0-beta.3

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 (311) hide show
  1. package/README.md +13 -7
  2. package/bin/cli-opencode.test.js +208 -0
  3. package/bin/cli.js +62 -9
  4. package/bin/lib/cli-startup.js +105 -2
  5. package/bin/lib/cli-startup.test.js +179 -0
  6. package/dist/assets/{AgentAvatar-CRaBJXKs.js → AgentAvatar-B_JQoreg.js} +1 -1
  7. package/dist/assets/AssistantView-ClQAfGOT.js +3 -0
  8. package/dist/assets/{DiagramView-D1LGK_s2.js → DiagramView-BNnda8qO.js} +1 -1
  9. package/dist/assets/{FadeInOnReveal-B0kJo9Ii.js → FadeInOnReveal-VYQTSAkr.js} +1 -1
  10. package/dist/assets/{Icon-BgdfPhNs.js → Icon-BiEQEcih.js} +1 -1
  11. package/dist/assets/MarkdownRenderer-CJHSsWin.js +2 -0
  12. package/dist/assets/{MarkdownRendererImpl-DfoCJ1Mq.js → MarkdownRendererImpl-DwrHFGUp.js} +3 -3
  13. package/dist/assets/{MarkstreamRendererImpl-D2dsW5Lg.js → MarkstreamRendererImpl-CSnbQMu3.js} +1 -1
  14. package/dist/assets/MobileDetailNavigation-BXr6AGLT.js +1 -0
  15. package/dist/assets/{MobileShareBridge-2Yjn6wXg.js → MobileShareBridge-D7OE9OP_.js} +2 -2
  16. package/dist/assets/MobileSurface-D2jUkX9l.js +2 -0
  17. package/dist/assets/{MultiRunWindow-tTbMFowS.js → MultiRunWindow-DF3iIFCd.js} +1 -1
  18. package/dist/assets/{OnboardingScreen-CD2KtlF3.js → OnboardingScreen-BjHmlwlw.js} +1 -1
  19. package/dist/assets/OverlayScrollbar-DLtWO_Gp.js +2 -0
  20. package/dist/assets/SettingsView-BRxELjow.js +25 -0
  21. package/dist/assets/{SettingsWindow-zIa76s4t.js → SettingsWindow-BI5swspb.js} +1 -1
  22. package/dist/assets/{TerminalView-CDdzF4yi.js → TerminalView-BU3x8-fr.js} +1 -1
  23. package/dist/assets/{ToolOutputDialog-DNqA4uBl.js → ToolOutputDialog-BEWhBcRd.js} +2 -2
  24. package/dist/assets/appThemeRegistry-4MR0F7Jz.js +1 -0
  25. package/dist/assets/{button-CI7Q-Zw_.js → button-DKuz3L8l.js} +1 -1
  26. package/dist/assets/collapsible-DXbDynN4.js +3 -0
  27. package/dist/assets/context-menu-CqolsLCm.js +1 -0
  28. package/dist/assets/{desktopHosts-B1ixEylr.js → desktopHosts-CoDkOnyS.js} +1 -1
  29. package/dist/assets/{desktopRelayRestore-4JmFUw8r.js → desktopRelayRestore-DDlVKyOE.js} +1 -1
  30. package/dist/assets/{diagram-DdLO8HZ9.js → diagram-D7T8HQgj.js} +1 -1
  31. package/dist/assets/{dialog-B6pnIYmp.js → dialog-DQYl3W_o.js} +1 -1
  32. package/dist/assets/es-CN3E_zXj.js +15 -0
  33. package/dist/assets/{fileMentionTouchSelection-DNeiqns-.js → fileMentionTouchSelection-BZ6ovBYH.js} +1 -1
  34. package/dist/assets/{fileQueries-DQc8EJUf.js → fileQueries-JrpcsOCs.js} +1 -1
  35. package/dist/assets/fr-9zZvQZD2.js +13 -0
  36. package/dist/assets/gitApi-CM42qUed.js +10 -0
  37. package/dist/assets/{imageSource-BlAJJUEI.js → imageSource-CIl0FL10.js} +1 -1
  38. package/dist/assets/{input-D-__fwRi.js → input-CzQy8UhV.js} +1 -1
  39. package/dist/assets/insertionBoundaries-CtkZ9720.js +1 -0
  40. package/dist/assets/ja-Dnjk_82N.js +15 -0
  41. package/dist/assets/ko-BxCEE99C.js +15 -0
  42. package/dist/assets/main-BILgBI3B.js +169 -0
  43. package/dist/assets/main-BeveiW6F.js +2 -0
  44. package/dist/assets/markdown-worker-bS5OgGO3.js +2 -0
  45. package/dist/assets/{markdownHeightCache-BhxqJOvf.js → markdownHeightCache-Cj1I5NDw.js} +1 -1
  46. package/dist/assets/{markdownTheme-DOfGg-A7.js → markdownTheme-CKac3wKC.js} +1 -1
  47. package/dist/assets/mcpQueries-D5mQymgM.js +1 -0
  48. package/dist/assets/miniChat-BgrzM3u7.js +2 -0
  49. package/dist/assets/mobile-DtYoQE8C.js +2 -0
  50. package/dist/assets/multirun-jy8dEI96.js +4 -0
  51. package/dist/assets/{persistence-Ce3AnpEr.js → persistence-C9LQpiOS.js} +2 -2
  52. package/dist/assets/pl-CNNsWnPM.js +15 -0
  53. package/dist/assets/platform-BQghHRfp.js +6 -0
  54. package/dist/assets/pt-BR-BZB2DJMp.js +15 -0
  55. package/dist/assets/renderElectronMiniChatApp-BGrrmeME.js +2 -0
  56. package/dist/assets/renderMobileApp-u3c4GtFC.js +28 -0
  57. package/dist/assets/{runtimeConfig-CMkwoQ_X.js → runtimeConfig-BIGBRGQI.js} +1 -1
  58. package/dist/assets/{runtimeEndpointReset-ChOxt-J7.js → runtimeEndpointReset-D2savkuw.js} +12 -12
  59. package/dist/assets/{sessionChangeSummary-NnxcqCTI.js → sessionChangeSummary-eIbpSfrO.js} +1 -1
  60. package/dist/assets/{sessionLookup-BrU3rFHJ.js → sessionLookup-myHe5ltu.js} +1 -1
  61. package/dist/assets/{sortable-tabs-strip-Fnfhf3K9.js → sortable-tabs-strip-BQtmHx9r.js} +1 -1
  62. package/dist/assets/src-CLN7Z-zx.css +2 -0
  63. package/dist/assets/store-DLCiZaA6.js +15 -0
  64. package/dist/assets/{terminalApi-xjqssKke.js → terminalApi-BxcdiduX.js} +1 -1
  65. package/dist/assets/{textarea-bHYxZtfH.js → textarea-CakKvqFw.js} +1 -1
  66. package/dist/assets/{tooltip-0T5jv0cu.js → tooltip-Cx5gTbdY.js} +1 -1
  67. package/dist/assets/uk-rwQrxeYM.js +15 -0
  68. package/dist/assets/{url-_z7evzdK.js → url-CDHnjlsm.js} +1 -1
  69. package/dist/assets/{useAppFontEffects-C02YFyAv.js → useAppFontEffects-CNeo0aZs.js} +59 -45
  70. package/dist/assets/{useAssistantUIStore-CMutVh9B.js → useAssistantUIStore-asNG6K7n.js} +1 -1
  71. package/dist/assets/useDesktopWindowControlsLayout-DBJ56qIo.js +1 -0
  72. package/dist/assets/useEffectiveDirectory-B4LvR8Gt.js +1 -0
  73. package/dist/assets/{useFontPreferences-B1JYyzpK.js → useFontPreferences-CUnQ4TVm.js} +1 -1
  74. package/dist/assets/useMobileNavigationStore-ZHJPxlHt.js +414 -0
  75. package/dist/assets/{useRuntimeAPIs-DSLXJybW.js → useRuntimeAPIs-DwLtO0c_.js} +1 -1
  76. package/dist/assets/useSessionAutoCleanup-BIVmrnYU.js +2 -0
  77. package/dist/assets/{useThemeSystem-DMzWVJaW.js → useThemeSystem-BlNUWH47.js} +1 -1
  78. package/dist/assets/useUIStore-CQYKJnrg.js +1 -0
  79. package/dist/assets/{useWorkerHighlightedLines-C9z0ML1z.js → useWorkerHighlightedLines-CJZbJDW3.js} +2 -2
  80. package/dist/assets/{vendor-base-ui-CnHfUmDN.js → vendor-base-ui-CDjyofeD.js} +1 -1
  81. package/dist/assets/{vendor-cmdk-CeAGfrLo.js → vendor-cmdk-BFH0UpXX.js} +1 -1
  82. package/dist/assets/{vendor-dnd-kit-accessibility-BY5LhhUB.js → vendor-dnd-kit-accessibility-BrR3AKvM.js} +1 -1
  83. package/dist/assets/{vendor-dnd-kit-core-CvWxUPdb.js → vendor-dnd-kit-core-CNfdvk6H.js} +1 -1
  84. package/dist/assets/{vendor-dnd-kit-sortable-hHdP9lLb.js → vendor-dnd-kit-sortable-DQUaTeGj.js} +1 -1
  85. package/dist/assets/{vendor-framer-motion-DFy_BSVd.js → vendor-framer-motion-4dogsUk3.js} +1 -1
  86. package/dist/assets/{vendor-legendapp-list-WE32lwNk.js → vendor-legendapp-list-BU7MdPZR.js} +1 -1
  87. package/dist/assets/{vendor-markstream-react-DX55XLGO.js → vendor-markstream-react-qHC2FykU.js} +3 -3
  88. package/dist/assets/vendor-opencode-client-BOe2qpQb.js +12 -0
  89. package/dist/assets/{vendor-pierre-diffs-DX7XTPG3.js → vendor-pierre-diffs-CUfqC003.js} +1 -1
  90. package/dist/assets/{vendor-react-DLPElpoQ.js → vendor-react-C94A0AHl.js} +1 -1
  91. package/dist/assets/{vendor-react-drawio-ECtEwvbc.js → vendor-react-drawio-FVlWqKVE.js} +1 -1
  92. package/dist/assets/{vendor-reactuses-core-BnW6GxJt.js → vendor-reactuses-core-C2A3EjUi.js} +1 -1
  93. package/dist/assets/{vendor-sonner-DVzioF3d.js → vendor-sonner-DNqHmUQs.js} +1 -1
  94. package/dist/assets/{vendor-stream-diffs-DWy3xSmp.js → vendor-stream-diffs-CwbKsmlv.js} +2 -2
  95. package/dist/assets/{vendor-tanstack-react-query-D7pys_c9.js → vendor-tanstack-react-query-D0iFK57C.js} +1 -1
  96. package/dist/assets/{vendor-tanstack-react-virtual-tlDgH6PG.js → vendor-tanstack-react-virtual-B2kV6cnT.js} +1 -1
  97. package/dist/assets/{vendor-yaml-DIcAK3EC.js → vendor-yaml-C9KlJAlj.js} +37 -37
  98. package/dist/assets/{vendor-zustand-B4dlGEM2.js → vendor-zustand-BqLKf6ZY.js} +1 -1
  99. package/dist/assets/zh-CN-BvmBDnkD.js +15 -0
  100. package/dist/assets/zh-TW-9wcHTX0y.js +15 -0
  101. package/dist/index.html +13 -13
  102. package/dist/mini-chat.html +13 -13
  103. package/dist/mobile.html +13 -13
  104. package/package.json +2 -2
  105. package/server/index.js +392 -20
  106. package/server/lib/assistants/DOCUMENTATION.md +26 -21
  107. package/server/lib/assistants/assign.js +2 -1
  108. package/server/lib/assistants/contact-store.js +37 -19
  109. package/server/lib/assistants/contracts.js +11 -1
  110. package/server/lib/assistants/projection-history.test.js +454 -0
  111. package/server/lib/assistants/routes.js +11 -5
  112. package/server/lib/assistants/service.js +850 -294
  113. package/server/lib/assistants/service.test.js +588 -274
  114. package/server/lib/conversations/DOCUMENTATION.md +5 -5
  115. package/server/lib/conversations/service.js +87 -57
  116. package/server/lib/conversations/service.test.js +132 -100
  117. package/server/lib/conversations/validation.js +11 -1
  118. package/server/lib/event-stream/directory-ws-bridge.js +13 -2
  119. package/server/lib/event-stream/global-hub.js +51 -5
  120. package/server/lib/event-stream/runtime.js +57 -20
  121. package/server/lib/event-stream/runtime.test.js +112 -0
  122. package/server/lib/llm/DOCUMENTATION.md +95 -84
  123. package/server/lib/llm/catalog.js +111 -45
  124. package/server/lib/llm/catalog.test.js +121 -59
  125. package/server/lib/llm/completions.js +41 -12
  126. package/server/lib/llm/completions.test.js +45 -4
  127. package/server/lib/llm/generate.js +516 -453
  128. package/server/lib/llm/generate.test.js +705 -484
  129. package/server/lib/llm/routes.js +8 -4
  130. package/server/lib/message-queue/DOCUMENTATION.md +5 -5
  131. package/server/lib/message-queue/opencode-adapter.js +191 -104
  132. package/server/lib/message-queue/opencode-adapter.test.js +328 -58
  133. package/server/lib/message-queue/runtime.js +15 -2
  134. package/server/lib/message-queue/runtime.test.js +6 -6
  135. package/server/lib/message-queue/worker.js +7 -1
  136. package/server/lib/notifications/runtime.js +5 -0
  137. package/server/lib/opencode/DOCUMENTATION.md +103 -16
  138. package/server/lib/opencode/agent-document.d.ts +28 -0
  139. package/server/lib/opencode/agent-document.js +280 -0
  140. package/server/lib/opencode/agent-document.test.js +137 -0
  141. package/server/lib/opencode/agents.js +96 -0
  142. package/server/lib/opencode/auth-state-runtime.js +5 -0
  143. package/server/lib/opencode/bootstrap-runtime.js +2 -0
  144. package/server/lib/opencode/config-entity-routes.js +111 -102
  145. package/server/lib/opencode/config-entity-routes.test.js +123 -10
  146. package/server/lib/opencode/config-hot-reload.integration.test.js +105 -0
  147. package/server/lib/opencode/ensure-cli.js +238 -0
  148. package/server/lib/opencode/ensure-cli.test.js +119 -0
  149. package/server/lib/opencode/env-runtime.js +208 -111
  150. package/server/lib/opencode/env-runtime.test.js +211 -14
  151. package/server/lib/opencode/feature-routes-runtime.js +80 -5
  152. package/server/lib/opencode/feature-routes-runtime.test.js +18 -0
  153. package/server/lib/opencode/http-server-connections.js +98 -0
  154. package/server/lib/opencode/http-server-connections.test.js +321 -0
  155. package/server/lib/opencode/lifecycle.js +539 -95
  156. package/server/lib/opencode/lifecycle.test.js +698 -6
  157. package/server/lib/opencode/managed-capabilities-runtime.js +15 -2
  158. package/server/lib/opencode/managed-capabilities-runtime.test.js +1 -0
  159. package/server/lib/opencode/network-runtime.js +28 -16
  160. package/server/lib/opencode/network-runtime.test.js +68 -4
  161. package/server/lib/opencode/openchamber-routes.js +7 -1
  162. package/server/lib/opencode/opencode2-pin.js +139 -0
  163. package/server/lib/opencode/opencode2-pin.test.js +88 -0
  164. package/server/lib/opencode/opencode2-sidecar.e2e.test.js +326 -0
  165. package/server/lib/opencode/owned-runtime-upgrade.js +431 -0
  166. package/server/lib/opencode/owned-runtime-upgrade.test.js +160 -0
  167. package/server/lib/opencode/plugin-compatibility.js +15 -0
  168. package/server/lib/opencode/plugin-compatibility.test.js +21 -0
  169. package/server/lib/opencode/plugin-routes.js +3 -18
  170. package/server/lib/opencode/plugin-routes.test.js +9 -9
  171. package/server/lib/opencode/plugins.js +4 -3
  172. package/server/lib/opencode/plugins.test.js +12 -0
  173. package/server/lib/opencode/provider-catalog.js +58 -12
  174. package/server/lib/opencode/provider-catalog.test.js +48 -0
  175. package/server/lib/opencode/proxy.js +368 -28
  176. package/server/lib/opencode/proxy.test.js +338 -0
  177. package/server/lib/opencode/routes.js +303 -76
  178. package/server/lib/opencode/routes.test.js +315 -50
  179. package/server/lib/opencode/runtime-contract.js +401 -0
  180. package/server/lib/opencode/runtime-contract.test.js +214 -0
  181. package/server/lib/opencode/server-opencode-fetch.js +242 -0
  182. package/server/lib/opencode/server-opencode-fetch.test.js +130 -0
  183. package/server/lib/opencode/server-utils-runtime.js +35 -0
  184. package/server/lib/opencode/shared-service.integration.test.js +66 -0
  185. package/server/lib/opencode/shared-service.js +147 -0
  186. package/server/lib/opencode/shared-service.test.js +93 -0
  187. package/server/lib/opencode/shared.js +3 -3
  188. package/server/lib/opencode/shutdown-runtime.js +15 -6
  189. package/server/lib/opencode/shutdown-runtime.test.js +49 -1
  190. package/server/lib/opencode/skill-routes.js +26 -40
  191. package/server/lib/opencode/skill-routes.test.js +71 -7
  192. package/server/lib/opencode/skills.test.js +15 -0
  193. package/server/lib/opencode/startup-pipeline-runtime.js +4 -0
  194. package/server/lib/opencode/static-routes-runtime.js +3 -1
  195. package/server/lib/opencode/static-routes-runtime.test.js +33 -0
  196. package/server/lib/opencode/v1-migration-gate.js +157 -0
  197. package/server/lib/opencode/v1-migration-gate.test.js +111 -0
  198. package/server/lib/opencode/v2-client.js +60 -0
  199. package/server/lib/opencode/v2-plugin-host-shim/index.mjs +155 -0
  200. package/server/lib/opencode/v2-plugin-host-shim/package.json +4 -0
  201. package/server/lib/opencode/watcher.js +2 -1
  202. package/server/lib/opencode/watcher.test.js +1 -1
  203. package/server/lib/permission-auto-accept/DOCUMENTATION.md +5 -5
  204. package/server/lib/permission-auto-accept/runtime.js +14 -7
  205. package/server/lib/permission-auto-accept/runtime.test.js +27 -12
  206. package/server/lib/relay/reasoning-projection.e2e.test.ts +22 -7
  207. package/server/lib/scheduled-tasks/DOCUMENTATION.md +79 -39
  208. package/server/lib/scheduled-tasks/managed-tool-route.js +31 -1
  209. package/server/lib/scheduled-tasks/managed-tool-route.test.js +23 -0
  210. package/server/lib/scheduled-tasks/routes.js +27 -0
  211. package/server/lib/scheduled-tasks/routes.test.js +94 -0
  212. package/server/lib/scheduled-tasks/runtime.js +381 -310
  213. package/server/lib/scheduled-tasks/runtime.test.js +600 -309
  214. package/server/lib/session-goal/DOCUMENTATION.md +173 -22
  215. package/server/lib/session-goal/capability.js +57 -0
  216. package/server/lib/session-goal/capability.test.js +33 -0
  217. package/server/lib/session-goal/routes.js +240 -2
  218. package/server/lib/session-goal/routes.test.js +211 -0
  219. package/server/lib/session-goal/runtime.js +1617 -113
  220. package/server/lib/session-goal/runtime.test.js +1500 -70
  221. package/server/lib/session-index/DOCUMENTATION.md +18 -15
  222. package/server/lib/session-index/event-ingest.js +89 -16
  223. package/server/lib/session-index/event-ingest.test.js +73 -0
  224. package/server/lib/session-index/host-authority-snapshot.js +92 -0
  225. package/server/lib/session-index/routes.host-archive.test.js +201 -0
  226. package/server/lib/session-index/routes.js +63 -10
  227. package/server/lib/session-index/routes.test.js +8 -8
  228. package/server/lib/session-index/service.js +7 -1
  229. package/server/lib/session-index/sync-runtime.js +171 -41
  230. package/server/lib/session-index/sync-runtime.test.js +164 -9
  231. package/server/lib/session-metadata/DOCUMENTATION.md +86 -0
  232. package/server/lib/session-metadata/routes.js +193 -0
  233. package/server/lib/session-metadata/routes.test.js +156 -0
  234. package/server/lib/session-metadata/session-archive.d.ts +73 -0
  235. package/server/lib/session-metadata/session-archive.js +654 -0
  236. package/server/lib/session-metadata/session-archive.test.js +359 -0
  237. package/server/lib/session-metadata/session-metadata-store.d.ts +36 -0
  238. package/server/lib/session-metadata/session-metadata-store.js +375 -0
  239. package/server/lib/session-metadata/session-metadata-store.test.js +379 -0
  240. package/server/lib/session-metadata/session-projection.d.ts +41 -0
  241. package/server/lib/session-metadata/session-projection.js +255 -0
  242. package/server/lib/session-metadata/session-projection.test.js +64 -0
  243. package/server/lib/session-metadata/system-session.js +55 -0
  244. package/server/lib/session-metadata/system-session.test.js +55 -0
  245. package/server/lib/session-title/DOCUMENTATION.md +40 -7
  246. package/server/lib/session-title/native-runtime.test.js +167 -0
  247. package/server/lib/session-title/runtime.js +67 -42
  248. package/server/lib/session-title/session-access.js +46 -0
  249. package/server/lib/session-title/session-access.test.js +91 -0
  250. package/server/lib/session-turn-pages/DOCUMENTATION.md +12 -11
  251. package/server/lib/session-turn-pages/reconcile.service.js +1 -1
  252. package/server/lib/session-turn-pages/reconcile.service.test.js +16 -0
  253. package/server/lib/session-turn-pages/routes.js +123 -75
  254. package/server/lib/session-turn-pages/routes.test.js +566 -0
  255. package/server/lib/session-turn-pages/service.js +6 -2
  256. package/server/lib/session-turn-pages/service.test.js +34 -0
  257. package/server/lib/session-turn-pages/session-message-projection.d.ts +13 -0
  258. package/server/lib/session-turn-pages/session-message-projection.js +281 -0
  259. package/server/lib/small-model/DOCUMENTATION.md +101 -146
  260. package/server/lib/small-model/catalog.js +84 -54
  261. package/server/lib/small-model/catalog.test.js +111 -9
  262. package/server/lib/small-model/custom-api.js +68 -0
  263. package/server/lib/small-model/custom-api.test.js +64 -0
  264. package/server/lib/small-model/index.js +125 -241
  265. package/server/lib/small-model/resolve.js +35 -120
  266. package/server/lib/small-model/resolve.test.js +20 -110
  267. package/server/lib/small-model/routes.js +1 -3
  268. package/server/lib/small-model/summary-settings.test.js +157 -253
  269. package/server/opencode-proxy.test.js +63 -12
  270. package/dist/assets/AssistantView-BUveAgc4.js +0 -3
  271. package/dist/assets/MarkdownRenderer-BbrbDH6-.js +0 -2
  272. package/dist/assets/MobileDetailNavigation-B0aqKXd7.js +0 -1
  273. package/dist/assets/MobileSurface-DW7WfPsy.js +0 -2
  274. package/dist/assets/OverlayScrollbar-DrA0Vsg5.js +0 -2
  275. package/dist/assets/SettingsView-PkXIavr7.js +0 -25
  276. package/dist/assets/appThemeRegistry-D5Yxg4YF.js +0 -1
  277. package/dist/assets/collapsible-D5Phaykh.js +0 -1
  278. package/dist/assets/context-menu-CubSDW2g.js +0 -1
  279. package/dist/assets/es-DA3MqOo0.js +0 -15
  280. package/dist/assets/fr-Dbz0CdIA.js +0 -13
  281. package/dist/assets/gitApi-DXyC4MiV.js +0 -7
  282. package/dist/assets/insertionBoundaries-KlOVrHHt.js +0 -1
  283. package/dist/assets/ja-DumanVQA.js +0 -15
  284. package/dist/assets/ko-C0IkiUjQ.js +0 -15
  285. package/dist/assets/main-BPS5ylib.js +0 -2
  286. package/dist/assets/main-Cpd9VWYs.js +0 -169
  287. package/dist/assets/markdown-worker-CptoICmq.js +0 -2
  288. package/dist/assets/miniChat-B8_KLWiY.js +0 -2
  289. package/dist/assets/mobile-CtktKrXn.js +0 -2
  290. package/dist/assets/multirun-CYfP9Cfm.js +0 -4
  291. package/dist/assets/pl-B9No6MQp.js +0 -15
  292. package/dist/assets/platform-0w_Q9_ji.js +0 -2
  293. package/dist/assets/projectMeta-S1hJ7oBp.js +0 -3
  294. package/dist/assets/pt-BR-DyQ0WTDh.js +0 -15
  295. package/dist/assets/renderElectronMiniChatApp-iK14LWm8.js +0 -2
  296. package/dist/assets/renderMobileApp-DORLPWnb.js +0 -28
  297. package/dist/assets/src-rS3zCcsC.css +0 -2
  298. package/dist/assets/store-5B_LDnLM.js +0 -15
  299. package/dist/assets/uk-C_1KkqyP.js +0 -15
  300. package/dist/assets/useDesktopWindowControlsLayout-Dnr-jX0C.js +0 -1
  301. package/dist/assets/useEffectiveDirectory-CZLLyarc.js +0 -1
  302. package/dist/assets/useMobileNavigationStore-CoCiqNgm.js +0 -413
  303. package/dist/assets/useSessionAutoCleanup-xQyETQkG.js +0 -2
  304. package/dist/assets/useUIStore-Bxv3f23A.js +0 -1
  305. package/dist/assets/vendor-opencode-sdk-C07HQypa.js +0 -7
  306. package/dist/assets/zh-CN-uWILgBdt.js +0 -15
  307. package/dist/assets/zh-TW-MYfdvhbD.js +0 -15
  308. package/server/lib/small-model/call.js +0 -427
  309. package/server/lib/small-model/call.test.js +0 -382
  310. package/server/lib/small-model/opencode-session.js +0 -339
  311. package/server/lib/small-model/opencode-session.test.js +0 -177
@@ -1,15 +1,29 @@
1
1
  import { describe, expect, it, vi, beforeEach } from 'vitest';
2
2
  import { validateConversationInput } from './validation.js';
3
3
 
4
- // Mock the SDK module before any imports
5
- vi.mock('@opencode-ai/sdk/v2', () => ({
6
- createOpencodeClient: vi.fn(),
4
+ // Mock the official v2 client before any imports
5
+ vi.mock('@opencode/client', () => ({
6
+ OpenCode: { make: vi.fn() },
7
7
  }));
8
8
 
9
9
  // Lazy import to allow vi.mock to take effect
10
10
  const { createConversationsService } = await import('./service.js');
11
11
 
12
- const mockCreateOpencodeClient = (await import('@opencode-ai/sdk/v2')).createOpencodeClient;
12
+ const mockMake = (await import('@opencode/client')).OpenCode.make;
13
+
14
+ const clientError = (reason, cause) => {
15
+ const error = new Error(reason);
16
+ error.name = 'ClientError';
17
+ error.reason = reason;
18
+ if (cause !== undefined) error.cause = cause;
19
+ return error;
20
+ };
21
+
22
+ const httpError = (status, message) => {
23
+ const error = new Error(message);
24
+ error.status = status;
25
+ return error;
26
+ };
13
27
 
14
28
  describe('conversations validation', () => {
15
29
  it('rejects non-object body', () => {
@@ -270,7 +284,7 @@ describe('conversations service', () => {
270
284
  mockBuildUrl.mockReturnValue('http://127.0.0.1:4096/');
271
285
  mockGetHeaders.mockReturnValue({ Authorization: 'Basic test' });
272
286
  mockWaitReady.mockResolvedValue(undefined);
273
- mockCreateOpencodeClient.mockReset();
287
+ mockMake.mockReset();
274
288
  });
275
289
 
276
290
  const baseInput = (overrides = {}) => ({
@@ -295,9 +309,9 @@ describe('conversations service', () => {
295
309
  // --- create failures ---
296
310
 
297
311
  it('returns create failure with safe error when SDK returns error', async () => {
298
- mockCreateOpencodeClient.mockReturnValue({
312
+ mockMake.mockReturnValue({
299
313
  session: {
300
- create: async () => ({ data: null, error: new Error('internal: port 4096 unreachable'), response: { status: 500 } }),
314
+ create: async () => { throw clientError('UnexpectedStatus', { status: 500 }); },
301
315
  },
302
316
  });
303
317
  const service = createService();
@@ -311,7 +325,7 @@ describe('conversations service', () => {
311
325
  });
312
326
 
313
327
  it('returns create failure on transport error during create', async () => {
314
- mockCreateOpencodeClient.mockReturnValue({
328
+ mockMake.mockReturnValue({
315
329
  session: {
316
330
  create: async () => { throw new Error('Connection refused'); },
317
331
  },
@@ -325,15 +339,19 @@ describe('conversations service', () => {
325
339
 
326
340
  // --- create: no agent/model ---
327
341
 
328
- it('does NOT pass agent/model/variant to session.create', async () => {
329
- mockCreateOpencodeClient.mockReturnValue({
342
+ it('passes agent/model/variant to session.create', async () => {
343
+ mockMake.mockReturnValue({
330
344
  session: {
331
345
  create: async (params) => {
332
- expect(params.agent).toBeUndefined();
333
- expect(params.model).toBeUndefined();
334
- return { data: { id: 'ses_ok' }, error: null, response: { status: 200 } };
346
+ expect(params.agent).toBe('builder');
347
+ expect(params.model).toEqual({
348
+ id: 'gpt-4o',
349
+ providerID: 'openai',
350
+ variant: 'v1',
351
+ });
352
+ return { id: 'ses_ok' };
335
353
  },
336
- promptAsync: async () => ({ data: undefined, error: null, response: { status: 204 } }),
354
+ prompt: async () => undefined,
337
355
  },
338
356
  });
339
357
  const service = createService();
@@ -343,42 +361,71 @@ describe('conversations service', () => {
343
361
  expect(result.ok).toBe(true);
344
362
  });
345
363
 
346
- // --- promptAsync usage ---
364
+ // --- prompt usage ---
347
365
 
348
- it('uses promptAsync (not prompt) and does not pass delivery', async () => {
349
- let promptAsyncCalled = false;
350
- mockCreateOpencodeClient.mockReturnValue({
366
+ it('uses prompt with delivery=steer and does not cache message body', async () => {
367
+ let promptCalled = false;
368
+ mockMake.mockReturnValue({
351
369
  session: {
352
- create: async () => ({ data: { id: 'ses_pa' }, error: null, response: { status: 200 } }),
353
- promptAsync: async (params) => {
354
- promptAsyncCalled = true;
355
- expect(params.delivery).toBeUndefined();
356
- return { data: undefined, error: null, response: { status: 204 } };
370
+ create: async () => ({ id: 'ses_pa' }),
371
+ prompt: async (params) => {
372
+ promptCalled = true;
373
+ expect(params.delivery).toBe('steer');
374
+ return {
375
+ id: 'msg_test',
376
+ sessionID: 'ses_pa',
377
+ type: 'user',
378
+ delivery: 'steer',
379
+ payload: { text: 'hello' },
380
+ timeCreated: 1,
381
+ };
357
382
  },
358
383
  },
359
384
  });
360
385
  const service = createService();
361
386
  const result = await service.createAndPrompt({ sanitizedInput: baseInput() });
362
- expect(promptAsyncCalled).toBe(true);
387
+ expect(promptCalled).toBe(true);
363
388
  expect(result.ok).toBe(true);
389
+ expect(result.inbox).toEqual(expect.objectContaining({ id: 'msg_test', delivery: 'steer' }));
390
+ expect(result.parts).toBeUndefined();
391
+ expect(result.text).toBeUndefined();
364
392
  });
365
393
 
366
394
  // --- success ---
367
395
 
368
- it('returns success when create + promptAsync succeed', async () => {
396
+ it('validates and forwards skill ids with the first prompt', async () => {
397
+ const validated = validateConversationInput({
398
+ input: { type: 'prompt' },
399
+ ...baseInput({ parts: [{ type: 'text', text: '[skill:release] prepare notes' }], skills: [{ id: 'release', text: 'untrusted body' }] }),
400
+ });
401
+ expect(validated.valid).toBe(true);
402
+ expect(validated.sanitized.skills).toEqual([{ id: 'release' }]);
403
+ const prompt = vi.fn(async () => undefined);
404
+ mockMake.mockReturnValue({ session: { create: async () => ({ id: 'ses_skill' }), prompt } });
405
+ const result = await createService().createAndPrompt({ sanitizedInput: validated.sanitized });
406
+ expect(result.ok).toBe(true);
407
+ expect(prompt.mock.calls[0][0]).toMatchObject({
408
+ text: '[skill:release] prepare notes', skills: [{ id: 'release', name: 'release' }], delivery: 'steer',
409
+ });
410
+ });
411
+
412
+ it('returns success when create + prompt succeed', async () => {
369
413
  const sessionData = { id: 'ses_created', title: 'Test' };
370
- mockCreateOpencodeClient.mockReturnValue({
414
+ mockMake.mockReturnValue({
371
415
  session: {
372
416
  create: async (params) => {
373
- expect(params.directory).toBe('/test');
374
- return { data: sessionData, error: null, response: { status: 200 } };
417
+ expect(params.location).toEqual({ directory: '/test' });
418
+ expect(params.model).toEqual({ id: 'gpt-4o', providerID: 'openai' });
419
+ return sessionData;
375
420
  },
376
- promptAsync: async (params) => {
421
+ prompt: async (params) => {
377
422
  expect(params.sessionID).toBe('ses_created');
378
- expect(params.messageID).toBe('msg_test');
379
- expect(params.model).toEqual({ providerID: 'openai', modelID: 'gpt-4o' });
380
- expect(params.parts).toEqual([{ type: 'text', text: 'hello' }]);
381
- return { data: undefined, error: null, response: { status: 204 } };
423
+ expect(params.id).toBe('msg_test');
424
+ expect(params.text).toBe('hello');
425
+ expect(params.delivery).toBe('steer');
426
+ expect(params.model).toBeUndefined();
427
+ expect(params.parts).toBeUndefined();
428
+ return undefined;
382
429
  },
383
430
  },
384
431
  });
@@ -390,14 +437,19 @@ describe('conversations service', () => {
390
437
  expect(mockMarkSent).toHaveBeenCalledWith('ses_created');
391
438
  });
392
439
 
393
- it('passes agent, variant to promptAsync', async () => {
394
- mockCreateOpencodeClient.mockReturnValue({
440
+ it('passes agent, variant to session.create and not prompt', async () => {
441
+ mockMake.mockReturnValue({
395
442
  session: {
396
- create: async () => ({ data: { id: 'ses_avd' }, error: null, response: { status: 200 } }),
397
- promptAsync: async (params) => {
443
+ create: async (params) => {
398
444
  expect(params.agent).toBe('builder');
399
- expect(params.variant).toBe('fast');
400
- return { data: undefined, error: null, response: { status: 204 } };
445
+ expect(params.model).toEqual({ id: 'gpt-4o', providerID: 'openai', variant: 'fast' });
446
+ return { id: 'ses_avd' };
447
+ },
448
+ prompt: async (params) => {
449
+ expect(params.agent).toBeUndefined();
450
+ expect(params.variant).toBeUndefined();
451
+ expect(params.delivery).toBe('steer');
452
+ return undefined;
401
453
  },
402
454
  },
403
455
  });
@@ -412,14 +464,10 @@ describe('conversations service', () => {
412
464
 
413
465
  it('returns prompt failure with ambiguous=false for 400 SDK error, safe error, no mark', async () => {
414
466
  const sessionData = { id: 'ses_400' };
415
- mockCreateOpencodeClient.mockReturnValue({
467
+ mockMake.mockReturnValue({
416
468
  session: {
417
- create: async () => ({ data: sessionData, error: null, response: { status: 200 } }),
418
- promptAsync: async () => ({
419
- data: null,
420
- error: new Error('path /foo/bar does not exist'),
421
- response: { status: 400 },
422
- }),
469
+ create: async () => sessionData,
470
+ prompt: async () => { throw httpError(400, 'path /foo/bar does not exist'); },
423
471
  },
424
472
  });
425
473
  const service = createService();
@@ -433,14 +481,10 @@ describe('conversations service', () => {
433
481
  });
434
482
 
435
483
  it('returns prompt failure with ambiguous=false for 404, no mark', async () => {
436
- mockCreateOpencodeClient.mockReturnValue({
484
+ mockMake.mockReturnValue({
437
485
  session: {
438
- create: async () => ({ data: { id: 'ses_404' }, error: null, response: { status: 200 } }),
439
- promptAsync: async () => ({
440
- data: null,
441
- error: new Error('Session not found'),
442
- response: { status: 404 },
443
- }),
486
+ create: async () => ({ id: 'ses_404' }),
487
+ prompt: async () => { throw httpError(404, 'Session not found'); },
444
488
  },
445
489
  });
446
490
  const service = createService();
@@ -456,14 +500,10 @@ describe('conversations service', () => {
456
500
 
457
501
  it('returns ambiguous=true for 503, safe error, marks activity', async () => {
458
502
  const sessionData = { id: 'ses_503' };
459
- mockCreateOpencodeClient.mockReturnValue({
503
+ mockMake.mockReturnValue({
460
504
  session: {
461
- create: async () => ({ data: sessionData, error: null, response: { status: 200 } }),
462
- promptAsync: async () => ({
463
- data: null,
464
- error: new Error('Service Unavailable'),
465
- response: { status: 503 },
466
- }),
505
+ create: async () => sessionData,
506
+ prompt: async () => { throw clientError('UnexpectedStatus', { status: 503 }); },
467
507
  },
468
508
  });
469
509
  const service = createService();
@@ -477,10 +517,10 @@ describe('conversations service', () => {
477
517
  });
478
518
 
479
519
  it('returns ambiguous=true for transport throw, marks activity', async () => {
480
- mockCreateOpencodeClient.mockReturnValue({
520
+ mockMake.mockReturnValue({
481
521
  session: {
482
- create: async () => ({ data: { id: 'ses_transport' }, error: null, response: { status: 200 } }),
483
- promptAsync: async () => { throw new TypeError('fetch failed'); },
522
+ create: async () => ({ id: 'ses_transport' }),
523
+ prompt: async () => { throw new TypeError('fetch failed'); },
484
524
  },
485
525
  });
486
526
  const service = createService();
@@ -493,14 +533,10 @@ describe('conversations service', () => {
493
533
  });
494
534
 
495
535
  it('returns ambiguous=true for 408, marks activity', async () => {
496
- mockCreateOpencodeClient.mockReturnValue({
536
+ mockMake.mockReturnValue({
497
537
  session: {
498
- create: async () => ({ data: { id: 'ses_408' }, error: null, response: { status: 200 } }),
499
- promptAsync: async () => ({
500
- data: null,
501
- error: new Error('Request Timeout'),
502
- response: { status: 408 },
503
- }),
538
+ create: async () => ({ id: 'ses_408' }),
539
+ prompt: async () => { throw clientError('UnexpectedStatus', { status: 408 }); },
504
540
  },
505
541
  });
506
542
  const service = createService();
@@ -511,14 +547,10 @@ describe('conversations service', () => {
511
547
  });
512
548
 
513
549
  it('returns ambiguous=true for 429, marks activity', async () => {
514
- mockCreateOpencodeClient.mockReturnValue({
550
+ mockMake.mockReturnValue({
515
551
  session: {
516
- create: async () => ({ data: { id: 'ses_429' }, error: null, response: { status: 200 } }),
517
- promptAsync: async () => ({
518
- data: null,
519
- error: new Error('Too Many Requests'),
520
- response: { status: 429 },
521
- }),
552
+ create: async () => ({ id: 'ses_429' }),
553
+ prompt: async () => { throw clientError('UnexpectedStatus', { status: 429 }); },
522
554
  },
523
555
  });
524
556
  const service = createService();
@@ -529,10 +561,10 @@ describe('conversations service', () => {
529
561
  });
530
562
 
531
563
  it('does NOT mark on non-transport, non-ambiguous throw', async () => {
532
- mockCreateOpencodeClient.mockReturnValue({
564
+ mockMake.mockReturnValue({
533
565
  session: {
534
- create: async () => ({ data: { id: 'ses_perm' }, error: null, response: { status: 200 } }),
535
- promptAsync: async () => { throw new Error('Some permanent failure'); },
566
+ create: async () => ({ id: 'ses_perm' }),
567
+ prompt: async () => { throw new Error('Some permanent failure'); },
536
568
  },
537
569
  });
538
570
  const service = createService();
@@ -552,10 +584,10 @@ describe('conversations service', () => {
552
584
  markUserMessageSent: throwingMark,
553
585
  waitForOpenCodeReady: mockWaitReady,
554
586
  });
555
- mockCreateOpencodeClient.mockReturnValue({
587
+ mockMake.mockReturnValue({
556
588
  session: {
557
- create: async () => ({ data: { id: 'ses_safe' }, error: null, response: { status: 200 } }),
558
- promptAsync: async () => ({ data: undefined, error: null, response: { status: 204 } }),
589
+ create: async () => ({ id: 'ses_safe' }),
590
+ prompt: async () => undefined,
559
591
  },
560
592
  });
561
593
  const result = await svc.createAndPrompt({ sanitizedInput: baseInput() });
@@ -568,13 +600,13 @@ describe('conversations service', () => {
568
600
  it('passes directory/headers through buildOpenCodeUrl and getOpenCodeAuthHeaders', async () => {
569
601
  mockBuildUrl.mockReturnValue('http://custom:9999/');
570
602
  mockGetHeaders.mockReturnValue({ Authorization: 'Bearer secret123' });
571
- mockCreateOpencodeClient.mockImplementation((config) => {
603
+ mockMake.mockImplementation((config) => {
572
604
  expect(config.baseUrl).toBe('http://custom:9999');
573
605
  expect(config.headers).toEqual({ Authorization: 'Bearer secret123' });
574
606
  return {
575
607
  session: {
576
- create: async () => ({ data: { id: 'ses_url' }, error: null, response: { status: 200 } }),
577
- promptAsync: async () => ({ data: undefined, error: null, response: { status: 204 } }),
608
+ create: async () => ({ id: 'ses_url' }),
609
+ prompt: async () => undefined,
578
610
  },
579
611
  };
580
612
  });
@@ -585,10 +617,10 @@ describe('conversations service', () => {
585
617
 
586
618
  it('awaits waitForOpenCodeReady before calling SDK', async () => {
587
619
  let createCalled = false;
588
- mockCreateOpencodeClient.mockReturnValue({
620
+ mockMake.mockReturnValue({
589
621
  session: {
590
- create: async () => { createCalled = true; return { data: { id: 'ses_w' }, error: null, response: { status: 200 } }; },
591
- promptAsync: async () => ({ data: undefined, error: null, response: { status: 204 } }),
622
+ create: async () => { createCalled = true; return { id: 'ses_w' }; },
623
+ prompt: async () => undefined,
592
624
  },
593
625
  });
594
626
  const service = createService();
@@ -600,16 +632,16 @@ describe('conversations service', () => {
600
632
  // --- internal timeouts ---
601
633
 
602
634
  it('uses internal timeout signal for session.create', async () => {
603
- mockCreateOpencodeClient.mockReturnValue({
635
+ mockMake.mockReturnValue({
604
636
  session: {
605
637
  create: async (_params, opts) => {
606
638
  expect(opts.signal).toBeDefined();
607
639
  expect(opts.signal.aborted).toBe(false);
608
- return { data: { id: 'ses_to' }, error: null, response: { status: 200 } };
640
+ return { id: 'ses_to' };
609
641
  },
610
- promptAsync: async (_params, opts) => {
642
+ prompt: async (_params, opts) => {
611
643
  expect(opts.signal).toBeDefined();
612
- return { data: undefined, error: null, response: { status: 204 } };
644
+ return undefined;
613
645
  },
614
646
  },
615
647
  });
@@ -619,7 +651,7 @@ describe('conversations service', () => {
619
651
  });
620
652
 
621
653
  it('maps AbortError from create to create failure', async () => {
622
- mockCreateOpencodeClient.mockReturnValue({
654
+ mockMake.mockReturnValue({
623
655
  session: {
624
656
  create: async () => { throw new DOMException('Aborted', 'AbortError'); },
625
657
  },
@@ -631,11 +663,11 @@ describe('conversations service', () => {
631
663
  expect(result.error).toBe('Failed to create session');
632
664
  });
633
665
 
634
- it('maps AbortError from promptAsync to ambiguous=true', async () => {
635
- mockCreateOpencodeClient.mockReturnValue({
666
+ it('maps AbortError from prompt to ambiguous=true', async () => {
667
+ mockMake.mockReturnValue({
636
668
  session: {
637
- create: async () => ({ data: { id: 'ses_pto' }, error: null, response: { status: 200 } }),
638
- promptAsync: async () => { throw new DOMException('Aborted', 'AbortError'); },
669
+ create: async () => ({ id: 'ses_pto' }),
670
+ prompt: async () => { throw new DOMException('Aborted', 'AbortError'); },
639
671
  },
640
672
  });
641
673
  const service = createService();
@@ -649,9 +681,9 @@ describe('conversations service', () => {
649
681
  // --- create failure no session ID ---
650
682
 
651
683
  it('returns create failure when no session ID in response', async () => {
652
- mockCreateOpencodeClient.mockReturnValue({
684
+ mockMake.mockReturnValue({
653
685
  session: {
654
- create: async () => ({ data: null, error: null, response: { status: 200 } }),
686
+ create: async () => null,
655
687
  },
656
688
  });
657
689
  const service = createService();
@@ -1,6 +1,6 @@
1
1
  const ALLOWED_TOP_LEVEL_KEYS = new Set([
2
2
  'input', 'directory', 'messageID', 'model', 'parts',
3
- 'title', 'parentID', 'agent', 'variant', 'metadata',
3
+ 'title', 'parentID', 'agent', 'variant', 'metadata', 'skills',
4
4
  ]);
5
5
  const ALLOWED_PART_TYPES = new Set(['text', 'file', 'agent']);
6
6
 
@@ -139,6 +139,15 @@ export const validateConversationInput = (body) => {
139
139
  }
140
140
 
141
141
  // Optional fields
142
+ let skills;
143
+ if (body.skills !== undefined) {
144
+ if (!Array.isArray(body.skills) || body.skills.some((skill) =>
145
+ !skill || typeof skill.id !== 'string' || !skill.id.trim())) {
146
+ errors.push('skills must be an array of non-empty skill ids');
147
+ } else {
148
+ skills = body.skills.map((skill) => ({ id: skill.id }));
149
+ }
150
+ }
142
151
  const title = asOptionalNonEmptyString(body.title);
143
152
  if (title === null) errors.push('title must be a non-empty string if provided');
144
153
 
@@ -168,6 +177,7 @@ export const validateConversationInput = (body) => {
168
177
  modelID: body.model.modelID.trim(),
169
178
  },
170
179
  parts: sanitizedParts,
180
+ ...(skills?.length ? { skills } : {}),
171
181
  title,
172
182
  parentID,
173
183
  agent,
@@ -21,6 +21,8 @@ export function acceptDirectoryMessageStreamWsConnection({
21
21
  buildOpenCodeUrl,
22
22
  getOpenCodeAuthHeaders,
23
23
  processForwardedEventPayload,
24
+ /** Optional Host archive/metadata projection for session lifecycle events. */
25
+ projectOutboundSessionPayload = null,
24
26
  wsClients,
25
27
  triggerHealthCheck,
26
28
  heartbeatIntervalMs,
@@ -78,14 +80,23 @@ export function acceptDirectoryMessageStreamWsConnection({
78
80
  const run = async () => {
79
81
  const forwardEvent = ({ envelope, payload }) => {
80
82
  const directory = requestedDirectory || envelope?.directory || 'global';
83
+ let outbound = payload;
84
+ if (typeof projectOutboundSessionPayload === 'function') {
85
+ try {
86
+ const projected = projectOutboundSessionPayload(payload);
87
+ if (projected != null) outbound = projected;
88
+ } catch (error) {
89
+ console.warn('[event-stream] directory session projection failed:', error?.message ?? error);
90
+ }
91
+ }
81
92
 
82
- sendMessageStreamWsEvent(socket, payload, {
93
+ sendMessageStreamWsEvent(socket, outbound, {
83
94
  directory,
84
95
  eventId: typeof envelope?.eventId === 'string' && envelope.eventId.length > 0 ? envelope.eventId : undefined,
85
96
  reasoningFilter,
86
97
  });
87
98
 
88
- processForwardedEventPayload(payload, (syntheticPayload) => {
99
+ processForwardedEventPayload(outbound, (syntheticPayload) => {
89
100
  sendMessageStreamWsEvent(socket, syntheticPayload, {
90
101
  directory: 'global',
91
102
  reasoningFilter,
@@ -64,6 +64,13 @@ export function createGlobalMessageStreamHub({
64
64
  upstreamReconnectDelayMs,
65
65
  replayLimit = MESSAGE_STREAM_GLOBAL_REPLAY_LIMIT,
66
66
  getRuntimeIdentity = null,
67
+ /**
68
+ * Optional Host authority projection for session.created / session.updated
69
+ * (and GlobalEvent wrap). Applied before fan-out and replay so browser and
70
+ * server subscribers share the same archive view. Must be sync and cheap.
71
+ * @type {((payload: unknown) => unknown) | null}
72
+ */
73
+ projectOutboundSessionPayload = null,
67
74
  }) {
68
75
  const eventSubscribers = new Set();
69
76
  const statusSubscribers = new Set();
@@ -97,12 +104,19 @@ export function createGlobalMessageStreamHub({
97
104
  }
98
105
  };
99
106
 
107
+ let sessionPayloadProjector = typeof projectOutboundSessionPayload === 'function'
108
+ ? projectOutboundSessionPayload
109
+ : null;
110
+
111
+ /**
112
+ * Diff-summarize only. Host archive projection is applied at fan-out and
113
+ * replayAfter time against the *current* committed store — never baked into
114
+ * the replay buffer (stale archive stamps must not stick after Host writes).
115
+ */
100
116
  const normalizeEvent = ({ envelope, payload, connectionContext }) => {
101
117
  const directory =
102
118
  typeof envelope?.directory === 'string' && envelope.directory.length > 0 ? envelope.directory : 'global';
103
119
  const eventId = typeof envelope?.eventId === 'string' && envelope.eventId.length > 0 ? envelope.eventId : undefined;
104
- // Summarize FileDiff bodies before fan-out and replay buffering so reconnect
105
- // replay cannot re-amplify full patch frames into >64KB WS payloads.
106
120
  const outboundPayload = summarizeOutboundEventPayload(payload);
107
121
  return {
108
122
  envelope,
@@ -113,6 +127,19 @@ export function createGlobalMessageStreamHub({
113
127
  };
114
128
  };
115
129
 
130
+ const projectForOutbound = (payload) => {
131
+ if (typeof sessionPayloadProjector !== 'function') return payload;
132
+ try {
133
+ const projected = sessionPayloadProjector(payload);
134
+ // null = Host not ready; suppress this lifecycle frame.
135
+ if (projected == null) return null;
136
+ return projected;
137
+ } catch (error) {
138
+ console.warn('[event-stream] session payload projection failed:', error?.message ?? error);
139
+ return payload;
140
+ }
141
+ };
142
+
116
143
  const start = () => {
117
144
  if (reader) {
118
145
  return;
@@ -127,7 +154,8 @@ export function createGlobalMessageStreamHub({
127
154
  buildUrl: () => {
128
155
  buildUrlFailed = false;
129
156
  try {
130
- return new URL(buildOpenCodeUrl('/global/event', ''));
157
+ // v2 serves the event stream at `/api/event`; `/api/global/event` is gone.
158
+ return new URL(buildOpenCodeUrl('/event', ''));
131
159
  } catch {
132
160
  buildUrlFailed = true;
133
161
  throw new Error('OpenCode service unavailable');
@@ -160,6 +188,7 @@ export function createGlobalMessageStreamHub({
160
188
  ...event,
161
189
  connectionContext: event?.connectionContext ?? activeConnectionRuntimeIdentity,
162
190
  });
191
+ // Buffer the unprojected payload so reconnect replay uses current Host authority.
163
192
  if (normalized.eventId) {
164
193
  replay.push(normalized);
165
194
  if (replay.length > replayLimit) {
@@ -167,8 +196,14 @@ export function createGlobalMessageStreamHub({
167
196
  }
168
197
  }
169
198
 
199
+ const projectedPayload = projectForOutbound(normalized.payload);
200
+ if (projectedPayload == null) return;
201
+ const outbound = projectedPayload === normalized.payload
202
+ ? normalized
203
+ : { ...normalized, payload: projectedPayload };
204
+
170
205
  for (const subscriber of Array.from(eventSubscribers)) {
171
- notifySubscriber('event', subscriber, normalized);
206
+ notifySubscriber('event', subscriber, outbound);
172
207
  }
173
208
  },
174
209
  onError(error) {
@@ -226,10 +261,21 @@ export function createGlobalMessageStreamHub({
226
261
  }
227
262
 
228
263
  const index = replay.findIndex((entry) => entry.eventId === eventId);
229
- return index === -1 ? [] : replay.slice(index + 1);
264
+ if (index === -1) return [];
265
+ // Re-project with current committed Host authority at replay time.
266
+ const out = [];
267
+ for (const entry of replay.slice(index + 1)) {
268
+ const projectedPayload = projectForOutbound(entry.payload);
269
+ if (projectedPayload == null) continue;
270
+ out.push(projectedPayload === entry.payload ? entry : { ...entry, payload: projectedPayload });
271
+ }
272
+ return out;
230
273
  },
231
274
  setRuntimeIdentityProvider(provider) {
232
275
  runtimeIdentityProvider = typeof provider === 'function' ? provider : null;
233
276
  },
277
+ setSessionPayloadProjector(projector) {
278
+ sessionPayloadProjector = typeof projector === 'function' ? projector : null;
279
+ },
234
280
  };
235
281
  }