@openchambery/web 2.0.0-beta.3 → 2.0.0-beta.30

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 (292) hide show
  1. package/README.md +14 -0
  2. package/dist/assets/{AgentAvatar-B_JQoreg.js → AgentAvatar-O2x_1cFU.js} +1 -1
  3. package/dist/assets/AssistantView-pBywAsqy.js +3 -0
  4. package/dist/assets/{DiagramView-BNnda8qO.js → DiagramView-B984aE-R.js} +1 -1
  5. package/dist/assets/{FadeInOnReveal-VYQTSAkr.js → FadeInOnReveal-CMBd5BTd.js} +1 -1
  6. package/dist/assets/Icon-BN87CyS4.js +1 -0
  7. package/dist/assets/MarkdownRenderer-DHiNU8n3.js +2 -0
  8. package/dist/assets/{MarkdownRendererImpl-DwrHFGUp.js → MarkdownRendererImpl-xJHMGHju.js} +3 -3
  9. package/dist/assets/MarkstreamRendererImpl-BZflns6x.js +3 -0
  10. package/dist/assets/{MobileDetailNavigation-BXr6AGLT.js → MobileDetailNavigation-DUvxsvrB.js} +1 -1
  11. package/dist/assets/{MobileShareBridge-D7OE9OP_.js → MobileShareBridge-DWo-J3Vc.js} +3 -3
  12. package/dist/assets/MobileSurface-B-Y6U5xm.js +2 -0
  13. package/dist/assets/{MultiRunWindow-DF3iIFCd.js → MultiRunWindow-rYiVSNKK.js} +1 -1
  14. package/dist/assets/{OnboardingScreen-BjHmlwlw.js → OnboardingScreen-BLjZIBOs.js} +1 -1
  15. package/dist/assets/{OverlayScrollbar-DLtWO_Gp.js → OverlayScrollbar-CoZ4wtau.js} +1 -1
  16. package/dist/assets/SettingsView-pjD0LrHE.js +25 -0
  17. package/dist/assets/{SettingsWindow-BI5swspb.js → SettingsWindow-CFOQRwjA.js} +1 -1
  18. package/dist/assets/TerminalView-Dw5jaYkc.js +8 -0
  19. package/dist/assets/{ToolOutputDialog-BEWhBcRd.js → ToolOutputDialog-B4v-ona2.js} +1 -1
  20. package/dist/assets/appThemeRegistry-BqDCGocy.js +1 -0
  21. package/dist/assets/{assistant-attachment-cache-C0aRnFkj.js → assistant-attachment-cache-sG8YQsVs.js} +1 -1
  22. package/dist/assets/{button-DKuz3L8l.js → button-Ck8ipzBm.js} +1 -1
  23. package/dist/assets/collapsible-nU99prkD.js +1 -0
  24. package/dist/assets/context-menu-DMzZN1oi.js +1 -0
  25. package/dist/assets/{desktopHosts-CoDkOnyS.js → desktopHosts-DuftEj8B.js} +1 -1
  26. package/dist/assets/desktopRelayRestore-g378BdZf.js +1 -0
  27. package/dist/assets/{diagram-D7T8HQgj.js → diagram-CvO4P8ds.js} +1 -1
  28. package/dist/assets/dialog-ByGUkS3c.js +1 -0
  29. package/dist/assets/dropdown-menu.styles-ayLWk2gf.js +1 -0
  30. package/dist/assets/es-DCjl24dV.js +15 -0
  31. package/dist/assets/fileMentionTouchSelection-Cgkxwzqb.js +2 -0
  32. package/dist/assets/{fileQueries-JrpcsOCs.js → fileQueries-DuF89nFO.js} +1 -1
  33. package/dist/assets/fr-DfJ5j0t5.js +13 -0
  34. package/dist/assets/{gitApi-CM42qUed.js → gitApi-Dwqkc2nZ.js} +3 -3
  35. package/dist/assets/{imageSource-CIl0FL10.js → imageSource-BpUsiwFd.js} +1 -1
  36. package/dist/assets/{input-CzQy8UhV.js → input-CXhFmHcq.js} +1 -1
  37. package/dist/assets/insertionBoundaries-BhWfAOPY.js +1 -0
  38. package/dist/assets/ja-B_9_w2Xf.js +15 -0
  39. package/dist/assets/ko-DlGbo3kV.js +15 -0
  40. package/dist/assets/main-CGMinhgQ.js +169 -0
  41. package/dist/assets/main-pMpcLJha.js +2 -0
  42. package/dist/assets/{markdownHeightCache-Cj1I5NDw.js → markdownHeightCache-g_UsWzbA.js} +1 -1
  43. package/dist/assets/markdownTheme-DM5h1i18.js +1 -0
  44. package/dist/assets/{mcpQueries-D5mQymgM.js → mcpQueries-DfrW3VHF.js} +1 -1
  45. package/dist/assets/miniChat-YOt30ECc.js +2 -0
  46. package/dist/assets/mobile-BK1bBCYD.js +2 -0
  47. package/dist/assets/multirun-Dua24A69.js +3 -0
  48. package/dist/assets/persistence-Dnj1QOdX.js +3 -0
  49. package/dist/assets/pl-DjUsS8Ei.js +15 -0
  50. package/dist/assets/platform-X3Y3GyM5.js +6 -0
  51. package/dist/assets/projectMeta-DXdoPmbz.js +1 -0
  52. package/dist/assets/pt-BR-B8sV7qo1.js +15 -0
  53. package/dist/assets/{relay-image-stream-DM7atqJs.js → relay-image-stream-D2XooXKZ.js} +1 -1
  54. package/dist/assets/renderElectronMiniChatApp-BOuEz_oT.js +2 -0
  55. package/dist/assets/renderMobileApp-C8Alpv-B.js +28 -0
  56. package/dist/assets/runtime-fetch-CiqQd__Y.js +1 -0
  57. package/dist/assets/runtime-switch-BJM2k_uH.js +1 -0
  58. package/dist/assets/runtimeConfig-DX_NP2Zm.js +2 -0
  59. package/dist/assets/runtimeEndpointReset-BSQU3e5U.js +148 -0
  60. package/dist/assets/runtimeEndpointReset-DsuP5FPT.css +1 -0
  61. package/dist/assets/sessionChangeSummary-8AIBkExf.js +2 -0
  62. package/dist/assets/{sessionLookup-myHe5ltu.js → sessionLookup-Bdwd1Fit.js} +1 -1
  63. package/dist/assets/{sortable-tabs-strip-BQtmHx9r.js → sortable-tabs-strip-T-dk_L69.js} +1 -1
  64. package/dist/assets/src-OzbnJsph.css +2 -0
  65. package/dist/assets/store-Cuktref-.js +15 -0
  66. package/dist/assets/{terminalApi-BxcdiduX.js → terminalApi-C_4joKUG.js} +1 -1
  67. package/dist/assets/textarea-biAqiqnJ.js +1 -0
  68. package/dist/assets/{tooltip-Cx5gTbdY.js → tooltip-BKL7J5hx.js} +1 -1
  69. package/dist/assets/uk-Bh7HXyLp.js +15 -0
  70. package/dist/assets/{url-CDHnjlsm.js → url-DNRMq_0R.js} +1 -1
  71. package/dist/assets/{useAppFontEffects-CNeo0aZs.js → useAppFontEffects-bafOEpgK.js} +57 -52
  72. package/dist/assets/{useAssistantUIStore-asNG6K7n.js → useAssistantUIStore-DBwfg9nX.js} +1 -1
  73. package/dist/assets/useDesktopWindowControlsLayout-CkCVOkxa.js +1 -0
  74. package/dist/assets/{useEffectiveDirectory-B4LvR8Gt.js → useEffectiveDirectory-B7htpJfU.js} +1 -1
  75. package/dist/assets/{useFontPreferences-CUnQ4TVm.js → useFontPreferences-CMrbTA0r.js} +1 -1
  76. package/dist/assets/useMobileNavigationStore-D2tnhKYV.js +415 -0
  77. package/dist/assets/{useRuntimeAPIs-DwLtO0c_.js → useRuntimeAPIs-DSLXJybW.js} +1 -1
  78. package/dist/assets/useSessionAutoCleanup-BC5OOH3M.js +2 -0
  79. package/dist/assets/{useThemeSystem-BlNUWH47.js → useThemeSystem-DMzWVJaW.js} +1 -1
  80. package/dist/assets/useUIStore-Dfx6TKvi.js +1 -0
  81. package/dist/assets/{useWorkerHighlightedLines-CJZbJDW3.js → useWorkerHighlightedLines-BNQUAMEB.js} +1 -1
  82. package/dist/assets/{vendor-base-ui-CDjyofeD.js → vendor-base-ui-CnHfUmDN.js} +1 -1
  83. package/dist/assets/{vendor-cmdk-BFH0UpXX.js → vendor-cmdk-CeAGfrLo.js} +1 -1
  84. package/dist/assets/{vendor-dnd-kit-accessibility-BrR3AKvM.js → vendor-dnd-kit-accessibility-BY5LhhUB.js} +1 -1
  85. package/dist/assets/{vendor-dnd-kit-core-CNfdvk6H.js → vendor-dnd-kit-core-CvWxUPdb.js} +1 -1
  86. package/dist/assets/{vendor-dnd-kit-sortable-DQUaTeGj.js → vendor-dnd-kit-sortable-hHdP9lLb.js} +1 -1
  87. package/dist/assets/{vendor-framer-motion-4dogsUk3.js → vendor-framer-motion-DFy_BSVd.js} +1 -1
  88. package/dist/assets/{vendor-legendapp-list-BU7MdPZR.js → vendor-legendapp-list-WE32lwNk.js} +1 -1
  89. package/dist/assets/{vendor-markstream-react-qHC2FykU.js → vendor-markstream-react-DX55XLGO.js} +3 -3
  90. package/dist/assets/vendor-opencode-client-MF2GBrFx.js +12 -0
  91. package/dist/assets/{vendor-pierre-diffs-CUfqC003.js → vendor-pierre-diffs-DX7XTPG3.js} +1 -1
  92. package/dist/assets/{vendor-react-C94A0AHl.js → vendor-react-DLPElpoQ.js} +1 -1
  93. package/dist/assets/{vendor-react-drawio-FVlWqKVE.js → vendor-react-drawio-ECtEwvbc.js} +1 -1
  94. package/dist/assets/{vendor-reactuses-core-C2A3EjUi.js → vendor-reactuses-core-Dq5ocTF8.js} +1 -1
  95. package/dist/assets/{vendor-sonner-DNqHmUQs.js → vendor-sonner-DVzioF3d.js} +1 -1
  96. package/dist/assets/{vendor-stream-diffs-CwbKsmlv.js → vendor-stream-diffs-DWy3xSmp.js} +2 -2
  97. package/dist/assets/{vendor-tanstack-react-query-D0iFK57C.js → vendor-tanstack-react-query-D7pys_c9.js} +1 -1
  98. package/dist/assets/{vendor-tanstack-react-virtual-B2kV6cnT.js → vendor-tanstack-react-virtual-tlDgH6PG.js} +1 -1
  99. package/dist/assets/{vendor-zustand-BqLKf6ZY.js → vendor-zustand-DzRqcymP.js} +2 -2
  100. package/dist/assets/zh-CN-Uzia0gk4.js +15 -0
  101. package/dist/assets/zh-TW-CiI7DLdB.js +15 -0
  102. package/dist/index.html +15 -15
  103. package/dist/mini-chat.html +15 -15
  104. package/dist/mobile.html +15 -15
  105. package/package.json +3 -2
  106. package/server/index.js +46 -27
  107. package/server/lib/browser-provider/DOCUMENTATION.md +141 -0
  108. package/server/lib/browser-provider/contract.js +312 -0
  109. package/server/lib/browser-provider/host.js +421 -0
  110. package/server/lib/browser-provider/host.test.js +304 -0
  111. package/server/lib/browser-provider/routes.js +122 -0
  112. package/server/lib/browser-provider/routes.test.js +120 -0
  113. package/server/lib/browser-provider/selection-store.js +43 -0
  114. package/server/lib/browser-provider/selection-store.test.js +35 -0
  115. package/server/lib/event-stream/DOCUMENTATION.md +3 -1
  116. package/server/lib/event-stream/reasoning-projection.js +16 -1
  117. package/server/lib/event-stream/reasoning-projection.test.js +15 -0
  118. package/server/lib/git/DOCUMENTATION.md +1 -0
  119. package/server/lib/git/routes.js +7 -4
  120. package/server/lib/git/routes.test.js +34 -0
  121. package/server/lib/git/service.js +3 -1
  122. package/server/lib/llm/DOCUMENTATION.md +6 -4
  123. package/server/lib/llm/generate.js +8 -9
  124. package/server/lib/llm/generate.test.js +33 -6
  125. package/server/lib/message-queue/service.js +1 -1
  126. package/server/lib/notifications/DOCUMENTATION.md +3 -1
  127. package/server/lib/notifications/runtime.js +128 -31
  128. package/server/lib/notifications/runtime.test.js +103 -0
  129. package/server/lib/notifications/template-runtime.js +130 -38
  130. package/server/lib/notifications/template-runtime.test.js +96 -0
  131. package/server/lib/opencode/DOCUMENTATION.md +36 -19
  132. package/server/lib/opencode/agents.js +13 -22
  133. package/server/lib/opencode/auth-state-runtime.js +3 -3
  134. package/server/lib/opencode/auth-state-runtime.test.js +59 -0
  135. package/server/lib/opencode/auth.d.ts +13 -0
  136. package/server/lib/opencode/auth.js +74 -64
  137. package/server/lib/opencode/auth.test.js +57 -0
  138. package/server/lib/opencode/commands.js +1 -1
  139. package/server/lib/opencode/config-entities-layering.test.js +72 -0
  140. package/server/lib/opencode/config-entity-routes.js +16 -124
  141. package/server/lib/opencode/config-entity-routes.test.js +48 -11
  142. package/server/lib/opencode/config-files.d.ts +17 -0
  143. package/server/lib/opencode/config-files.js +187 -0
  144. package/server/lib/opencode/config-files.test.js +201 -0
  145. package/server/lib/opencode/core-routes.js +0 -1
  146. package/server/lib/opencode/core-routes.test.js +0 -15
  147. package/server/lib/opencode/credential-boundary.d.ts +1 -0
  148. package/server/lib/opencode/credential-boundary.js +11 -0
  149. package/server/lib/opencode/ensure-cli.d.ts +8 -0
  150. package/server/lib/opencode/ensure-cli.js +4 -1
  151. package/server/lib/opencode/env-config.js +4 -1
  152. package/server/lib/opencode/env-config.test.js +28 -0
  153. package/server/lib/opencode/env-runtime.test.js +18 -1
  154. package/server/lib/opencode/feature-routes-runtime.js +19 -7
  155. package/server/lib/opencode/feature-routes-runtime.test.js +7 -0
  156. package/server/lib/opencode/hmr-state-runtime.js +4 -4
  157. package/server/lib/opencode/lifecycle.js +1 -49
  158. package/server/lib/opencode/lifecycle.test.js +30 -4
  159. package/server/lib/opencode/location-release.d.ts +8 -0
  160. package/server/lib/opencode/location-release.js +32 -0
  161. package/server/lib/opencode/location-release.test.js +43 -0
  162. package/server/lib/opencode/mcp.js +1 -1
  163. package/server/lib/opencode/opencode2-pin.js +2 -1
  164. package/server/lib/opencode/owned-runtime-upgrade.test.js +5 -5
  165. package/server/lib/opencode/plugin-routes.test.js +4 -2
  166. package/server/lib/opencode/plugins.js +12 -60
  167. package/server/lib/opencode/plugins.test.js +5 -3
  168. package/server/lib/opencode/providers.js +6 -3
  169. package/server/lib/opencode/proxy.js +13 -1
  170. package/server/lib/opencode/proxy.test.js +31 -3
  171. package/server/lib/opencode/remote-upgrade.integration.test.js +66 -0
  172. package/server/lib/opencode/routes.js +40 -8
  173. package/server/lib/opencode/routes.test.js +51 -10
  174. package/server/lib/opencode/runtime-contract.js +3 -2
  175. package/server/lib/opencode/runtime-contract.test.js +12 -12
  176. package/server/lib/opencode/shared.js +6 -175
  177. package/server/lib/opencode/update-discovery.js +39 -0
  178. package/server/lib/opencode/update-discovery.test.js +39 -0
  179. package/server/lib/opencode/upgrade-screen.d.ts +30 -0
  180. package/server/lib/opencode/upgrade-screen.js +292 -0
  181. package/server/lib/opencode/upgrade-screen.test.js +196 -0
  182. package/server/lib/opencode/version-alignment.test.js +40 -0
  183. package/server/lib/question-auto-delegate/DOCUMENTATION.md +21 -6
  184. package/server/lib/question-auto-delegate/core.d.ts +5 -1
  185. package/server/lib/question-auto-delegate/core.js +21 -13
  186. package/server/lib/question-auto-delegate/form-io.d.ts +6 -0
  187. package/server/lib/question-auto-delegate/form-io.js +39 -0
  188. package/server/lib/question-auto-delegate/runtime.js +4 -24
  189. package/server/lib/question-auto-delegate/runtime.test.js +138 -0
  190. package/server/lib/quota/DOCUMENTATION.md +9 -0
  191. package/server/lib/quota/provider-credentials.test.js +29 -0
  192. package/server/lib/quota/providers/claude.js +4 -4
  193. package/server/lib/quota/providers/codex.js +4 -4
  194. package/server/lib/quota/providers/codex.test.js +1 -1
  195. package/server/lib/quota/providers/copilot.js +5 -5
  196. package/server/lib/quota/providers/google/auth.js +3 -3
  197. package/server/lib/quota/providers/google/index.js +2 -2
  198. package/server/lib/quota/providers/index.js +3 -7
  199. package/server/lib/quota/providers/index.test.js +2 -2
  200. package/server/lib/quota/providers/kimi.js +4 -4
  201. package/server/lib/quota/providers/minimax-shared.js +4 -4
  202. package/server/lib/quota/providers/nanogpt.js +4 -4
  203. package/server/lib/quota/providers/openai.js +4 -4
  204. package/server/lib/quota/providers/openrouter.js +4 -4
  205. package/server/lib/quota/providers/wafer.js +4 -4
  206. package/server/lib/quota/providers/zai.js +4 -4
  207. package/server/lib/quota/providers/zhipuai-coding-plan.js +6 -20
  208. package/server/lib/quota/routes.js +1 -1
  209. package/server/lib/relay/DOCUMENTATION.md +3 -1
  210. package/server/lib/relay/service.js +1 -0
  211. package/server/lib/relay/service.test.js +13 -0
  212. package/server/lib/scheduled-tasks/DOCUMENTATION.md +8 -1
  213. package/server/lib/scheduled-tasks/runtime.js +12 -4
  214. package/server/lib/scheduled-tasks/runtime.test.js +81 -6
  215. package/server/lib/session-goal/DOCUMENTATION.md +11 -0
  216. package/server/lib/session-goal/runtime.js +4 -4
  217. package/server/lib/session-goal/runtime.test.js +32 -3
  218. package/server/lib/session-index/DOCUMENTATION.md +7 -0
  219. package/server/lib/session-index/event-ingest.js +13 -0
  220. package/server/lib/session-index/event-ingest.test.js +7 -0
  221. package/server/lib/session-index/service.js +45 -0
  222. package/server/lib/session-index/service.test.js +47 -0
  223. package/server/lib/session-metadata/DOCUMENTATION.md +33 -17
  224. package/server/lib/session-metadata/opencode-session-record.d.ts +57 -0
  225. package/server/lib/session-metadata/opencode-session-record.js +316 -0
  226. package/server/lib/session-metadata/opencode-session-record.test.js +242 -0
  227. package/server/lib/session-metadata/routes.js +5 -1
  228. package/server/lib/session-metadata/session-archive.js +1 -1
  229. package/server/lib/session-metadata/session-metadata-store.d.ts +2 -0
  230. package/server/lib/session-metadata/session-metadata-store.js +63 -16
  231. package/server/lib/session-metadata/session-metadata-store.test.js +23 -0
  232. package/server/lib/session-metadata/session-projection.d.ts +1 -0
  233. package/server/lib/session-metadata/session-projection.js +49 -2
  234. package/server/lib/session-metadata/session-projection.test.js +35 -0
  235. package/server/lib/session-title/DOCUMENTATION.md +52 -14
  236. package/server/lib/session-title/native-runtime.test.js +59 -5
  237. package/server/lib/session-title/runtime.js +65 -19
  238. package/server/lib/session-title/session-access.js +108 -3
  239. package/server/lib/session-title/session-access.test.js +125 -7
  240. package/server/lib/tts/DOCUMENTATION.md +7 -5
  241. package/server/lib/tts/credential-source.test.js +23 -0
  242. package/server/lib/tts/routes.js +2 -2
  243. package/server/lib/tts/service.js +16 -37
  244. package/dist/assets/AssistantView-ClQAfGOT.js +0 -3
  245. package/dist/assets/Icon-BiEQEcih.js +0 -1
  246. package/dist/assets/MarkdownRenderer-CJHSsWin.js +0 -2
  247. package/dist/assets/MarkstreamRendererImpl-CSnbQMu3.js +0 -3
  248. package/dist/assets/MobileSurface-D2jUkX9l.js +0 -2
  249. package/dist/assets/SettingsView-BRxELjow.js +0 -25
  250. package/dist/assets/TerminalView-BU3x8-fr.js +0 -8
  251. package/dist/assets/appThemeRegistry-4MR0F7Jz.js +0 -1
  252. package/dist/assets/collapsible-DXbDynN4.js +0 -3
  253. package/dist/assets/context-menu-CqolsLCm.js +0 -1
  254. package/dist/assets/desktopRelayRestore-DDlVKyOE.js +0 -1
  255. package/dist/assets/dialog-DQYl3W_o.js +0 -1
  256. package/dist/assets/dropdown-menu.styles-Bzo6L3FH.js +0 -1
  257. package/dist/assets/es-CN3E_zXj.js +0 -15
  258. package/dist/assets/fileMentionTouchSelection-BZ6ovBYH.js +0 -4
  259. package/dist/assets/fr-9zZvQZD2.js +0 -13
  260. package/dist/assets/insertionBoundaries-CtkZ9720.js +0 -1
  261. package/dist/assets/ja-Dnjk_82N.js +0 -15
  262. package/dist/assets/ko-BxCEE99C.js +0 -15
  263. package/dist/assets/main-BILgBI3B.js +0 -169
  264. package/dist/assets/main-BeveiW6F.js +0 -2
  265. package/dist/assets/markdownTheme-CKac3wKC.js +0 -1
  266. package/dist/assets/miniChat-BgrzM3u7.js +0 -2
  267. package/dist/assets/mobile-DtYoQE8C.js +0 -2
  268. package/dist/assets/multirun-jy8dEI96.js +0 -4
  269. package/dist/assets/persistence-C9LQpiOS.js +0 -3
  270. package/dist/assets/pl-CNNsWnPM.js +0 -15
  271. package/dist/assets/platform-BQghHRfp.js +0 -6
  272. package/dist/assets/pt-BR-BZB2DJMp.js +0 -15
  273. package/dist/assets/renderElectronMiniChatApp-BGrrmeME.js +0 -2
  274. package/dist/assets/renderMobileApp-u3c4GtFC.js +0 -28
  275. package/dist/assets/runtime-fetch-zIalUnbb.js +0 -1
  276. package/dist/assets/runtime-switch-LvWDU-SL.js +0 -1
  277. package/dist/assets/runtimeConfig-BIGBRGQI.js +0 -2
  278. package/dist/assets/runtimeEndpointReset-D2savkuw.js +0 -147
  279. package/dist/assets/sessionChangeSummary-eIbpSfrO.js +0 -2
  280. package/dist/assets/src-CLN7Z-zx.css +0 -2
  281. package/dist/assets/store-DLCiZaA6.js +0 -15
  282. package/dist/assets/textarea-CakKvqFw.js +0 -1
  283. package/dist/assets/uk-rwQrxeYM.js +0 -15
  284. package/dist/assets/useDesktopWindowControlsLayout-DBJ56qIo.js +0 -1
  285. package/dist/assets/useMobileNavigationStore-ZHJPxlHt.js +0 -414
  286. package/dist/assets/useSessionAutoCleanup-BIVmrnYU.js +0 -2
  287. package/dist/assets/useUIStore-CQYKJnrg.js +0 -1
  288. package/dist/assets/vendor-opencode-client-BOe2qpQb.js +0 -12
  289. package/dist/assets/zh-CN-BvmBDnkD.js +0 -15
  290. package/dist/assets/zh-TW-9wcHTX0y.js +0 -15
  291. package/server/lib/opencode/snippets.js +0 -242
  292. package/server/lib/opencode/snippets.test.js +0 -97
@@ -457,6 +457,17 @@ describe('contact turn notifications', () => {
457
457
  });
458
458
 
459
459
  describe('notification trigger live activity end', () => {
460
+ it('treats session.execution.succeeded as completion, including a v2 data envelope', async () => {
461
+ vi.stubGlobal('fetch', vi.fn(async () => rootSessionResponse('ses_done')));
462
+ const { runtime, sendLiveActivityEnd, emitDesktopNotification } = createRuntime();
463
+ await runtime.maybeSendPushForTrigger({
464
+ type: 'session.execution.succeeded',
465
+ data: { sessionID: 'ses_done' },
466
+ });
467
+ expect(sendLiveActivityEnd).toHaveBeenCalledWith({ sessionId: 'ses_done', status: 'complete' });
468
+ expect(emitDesktopNotification).toHaveBeenCalled();
469
+ });
470
+
460
471
  it('ends the live activity on top-level completion and error', async () => {
461
472
  vi.stubGlobal('fetch', vi.fn(async () => rootSessionResponse()));
462
473
  const { runtime, sendLiveActivityEnd } = createRuntime();
@@ -615,6 +626,97 @@ describe('notification event toggles', () => {
615
626
  expect(sendPushToAllUiSessions).not.toHaveBeenCalled();
616
627
  });
617
628
 
629
+ it('notifies on an OpenCode 2 question form and a permission request', async () => {
630
+ vi.useFakeTimers();
631
+ vi.stubGlobal('fetch', vi.fn(async () => jsonResponse({
632
+ data: { id: 'ses_ask', parentID: null, title: '发布说明', metadata: {} },
633
+ })));
634
+ const resolveNotificationTemplate = vi.fn((template, variables) => (
635
+ typeof template === 'string'
636
+ ? template.replace(/\{(\w+)\}/g, (_match, key) => variables?.[key] ?? '')
637
+ : template
638
+ ));
639
+ const { runtime, emitDesktopNotification, sendApnsToAllUiSessions } = createRuntime({
640
+ buildTemplateVariables: vi.fn(async () => ({ session_name: '' })),
641
+ resolveNotificationTemplate,
642
+ readSettingsFromDisk: vi.fn(async () => ({
643
+ ...defaultSettings,
644
+ notificationTemplates: {
645
+ question: { title: 'Needs your answer', message: '{last_message}' },
646
+ },
647
+ })),
648
+ });
649
+
650
+ await runtime.maybeSendPushForTrigger({
651
+ type: 'form.created',
652
+ data: {
653
+ form: {
654
+ id: 'frm_1',
655
+ sessionID: 'ses_ask',
656
+ title: 'Questions',
657
+ metadata: { kind: 'question' },
658
+ fields: [{ key: 'q0', title: '确认入口', description: '从哪个入口触发?' }],
659
+ },
660
+ },
661
+ });
662
+ await vi.runAllTimersAsync();
663
+
664
+ expect(emitDesktopNotification).toHaveBeenCalledWith(expect.objectContaining({
665
+ kind: 'question',
666
+ body: '从哪个入口触发?',
667
+ }));
668
+ expect(sendApnsToAllUiSessions).toHaveBeenCalledWith(
669
+ expect.objectContaining({ type: 'question', sessionName: '发布说明' }),
670
+ { requireNoSse: true },
671
+ );
672
+
673
+ emitDesktopNotification.mockClear();
674
+ sendApnsToAllUiSessions.mockClear();
675
+ await runtime.maybeSendPushForTrigger({
676
+ type: 'permission.asked',
677
+ data: {
678
+ id: 'per_1',
679
+ sessionID: 'ses_ask',
680
+ action: 'edit',
681
+ message: '修改 README',
682
+ resources: ['README.md'],
683
+ },
684
+ });
685
+ await vi.runAllTimersAsync();
686
+
687
+ expect(emitDesktopNotification).toHaveBeenCalledWith(expect.objectContaining({
688
+ kind: 'permission',
689
+ body: '修改 README',
690
+ }));
691
+ expect(sendApnsToAllUiSessions).toHaveBeenCalledWith(
692
+ expect.objectContaining({ type: 'permission', sessionName: '发布说明' }),
693
+ { requireNoSse: true },
694
+ );
695
+ });
696
+
697
+ it('uses the wrapped OpenCode 2 session title when the template has none', async () => {
698
+ vi.stubGlobal('fetch', vi.fn(async () => jsonResponse({
699
+ data: {
700
+ id: 'ses_done',
701
+ parentID: null,
702
+ title: '发布说明',
703
+ metadata: {},
704
+ },
705
+ })));
706
+ const { runtime, sendApnsToAllUiSessions } = createRuntime({
707
+ buildTemplateVariables: vi.fn(async () => ({ session_name: '' })),
708
+ });
709
+ await runtime.maybeSendPushForTrigger({
710
+ type: 'session.execution.succeeded',
711
+ location: { directory: '/repo' },
712
+ data: { sessionID: 'ses_done' },
713
+ });
714
+ expect(sendApnsToAllUiSessions).toHaveBeenCalledWith(
715
+ expect.objectContaining({ type: 'ready', sessionName: '发布说明' }),
716
+ { requireNoSse: true },
717
+ );
718
+ });
719
+
618
720
  it('still sends goal settle push by default', async () => {
619
721
  vi.stubGlobal('fetch', vi.fn(async () => jsonResponse({ id: 'ses_goal', title: 'Goal run' })));
620
722
  const { runtime, sendPushToAllUiSessions } = createRuntime();
@@ -626,6 +728,7 @@ describe('notification event toggles', () => {
626
728
  });
627
729
  expect(sendPushToAllUiSessions).toHaveBeenCalledTimes(1);
628
730
  expect(sendPushToAllUiSessions.mock.calls[0][0]).toMatchObject({ tag: 'goal-ses_goal' });
731
+ expect(sendPushToAllUiSessions.mock.calls[0][0].data).toMatchObject({ sessionName: 'Goal run' });
629
732
  });
630
733
  });
631
734
 
@@ -1,5 +1,75 @@
1
+ import { projectSessionMessageRecords } from '../session-turn-pages/session-message-projection.js';
1
2
  import { summarizeText as summarizeSharedText } from '../text/summarization.js';
2
3
 
4
+ const trimmedText = (value) => (typeof value === 'string' && value.trim() ? value.trim() : '');
5
+
6
+ const isSessionRecord = (value) => {
7
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
8
+ return typeof value.id === 'string'
9
+ || typeof value.sessionID === 'string'
10
+ || typeof value.title === 'string'
11
+ || typeof value.parentID === 'string'
12
+ || (value.metadata && typeof value.metadata === 'object');
13
+ };
14
+
15
+ /**
16
+ * OpenCode 2 `GET /api/session/:id` returns `{ data: Session }`.
17
+ * Legacy responses and tests still return the session object itself.
18
+ * @param {unknown} payload
19
+ * @returns {object | null}
20
+ */
21
+ export const readNotificationSessionRecord = (payload) => {
22
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return null;
23
+ if (isSessionRecord(payload.data)) return payload.data;
24
+ return isSessionRecord(payload) ? payload : null;
25
+ };
26
+
27
+ const TITLE_EVENT_TYPES = new Set(['session.created', 'session.updated', 'session.renamed']);
28
+
29
+ /**
30
+ * Title carried by a session lifecycle event.
31
+ * v2 puts it on `data.title` (`session.created` / `session.renamed`).
32
+ * Legacy events put it on `properties.info.title`.
33
+ * @param {object | null | undefined} payload
34
+ * @returns {{ sessionId: string, title: string } | null}
35
+ */
36
+ export const readSessionTitleFromEvent = (payload) => {
37
+ if (!payload || typeof payload !== 'object' || !TITLE_EVENT_TYPES.has(payload.type)) return null;
38
+ const bodies = [payload.properties, payload.data].filter((body) => body && typeof body === 'object' && !Array.isArray(body));
39
+ for (const body of bodies) {
40
+ const info = body.info && typeof body.info === 'object' ? body.info : null;
41
+ const title = trimmedText(info?.title) || trimmedText(body.title);
42
+ const sessionId = trimmedText(info?.id)
43
+ || trimmedText(info?.sessionID)
44
+ || trimmedText(body.sessionID)
45
+ || trimmedText(body.id);
46
+ if (title && sessionId) return { sessionId, title };
47
+ }
48
+ return null;
49
+ };
50
+
51
+ const readNotificationDirectory = (payload) => {
52
+ const properties = payload?.properties && typeof payload.properties === 'object' ? payload.properties : {};
53
+ const data = payload?.data && typeof payload.data === 'object' && !Array.isArray(payload.data) ? payload.data : {};
54
+ const info = properties.info && typeof properties.info === 'object' ? properties.info : {};
55
+ const dataInfo = data.info && typeof data.info === 'object' ? data.info : {};
56
+ const candidates = [
57
+ properties.directory,
58
+ info.directory,
59
+ info.location?.directory,
60
+ payload?.location?.directory,
61
+ data.directory,
62
+ data.location?.directory,
63
+ dataInfo.directory,
64
+ dataInfo.location?.directory,
65
+ ];
66
+ for (const value of candidates) {
67
+ const directory = trimmedText(value);
68
+ if (directory) return directory;
69
+ }
70
+ return '';
71
+ };
72
+
3
73
  export const createNotificationTemplateRuntime = (deps) => {
4
74
  const {
5
75
  readSettingsFromDisk,
@@ -133,37 +203,45 @@ export const createNotificationTemplateRuntime = (deps) => {
133
203
  return '';
134
204
  };
135
205
 
136
- const fetchLastAssistantMessageText = async (sessionId, messageId, maxLength = NOTIFICATION_BODY_MAX_CHARS) => {
206
+ const fetchLastAssistantMessageText = async (sessionId, messageId, maxLength = NOTIFICATION_BODY_MAX_CHARS, directory) => {
137
207
  if (!sessionId) return '';
138
208
 
139
209
  try {
140
- const url = buildOpenCodeUrl(`/session/${encodeURIComponent(sessionId)}/message`, '');
141
- const response = await fetch(`${url}?limit=5`, {
210
+ const base = buildOpenCodeUrl(`/session/${encodeURIComponent(sessionId)}/message`, '');
211
+ const params = new URLSearchParams({ limit: '5', order: 'desc' });
212
+ const scopedDirectory = trimmedText(directory);
213
+ if (scopedDirectory) params.set('directory', scopedDirectory);
214
+ const response = await fetch(`${base}?${params.toString()}`, {
142
215
  method: 'GET',
143
216
  headers: {
144
217
  Accept: 'application/json',
145
- ...getOpenCodeAuthHeaders(),
218
+ ...(typeof getOpenCodeAuthHeaders === 'function' ? getOpenCodeAuthHeaders() : {}),
146
219
  },
147
220
  signal: AbortSignal.timeout(3000),
148
221
  });
149
222
 
150
223
  if (!response.ok) return '';
151
224
 
152
- const messages = await response.json().catch(() => null);
153
- if (!Array.isArray(messages)) return '';
225
+ const payload = await response.json().catch(() => null);
226
+ // Legacy lists are a chronological array. OpenCode 2 returns `{ data }`
227
+ // and `order=desc` puts the newest turn first.
228
+ const newestFirst = !Array.isArray(payload);
229
+ const records = Array.isArray(payload)
230
+ ? payload
231
+ : (Array.isArray(payload?.data) ? payload.data : null);
232
+ if (!records) return '';
233
+ const projected = projectSessionMessageRecords(records);
234
+ const messages = newestFirst ? projected : [...projected].reverse();
154
235
 
155
236
  let target = null;
156
237
  if (messageId) {
157
238
  target = messages.find((message) => message?.info?.id === messageId && message?.info?.role === 'assistant');
158
239
  }
159
240
  if (!target) {
160
- for (let i = messages.length - 1; i >= 0; i -= 1) {
161
- const message = messages[i];
162
- if (message?.info?.role === 'assistant' && message?.info?.finish === 'stop') {
163
- target = message;
164
- break;
165
- }
166
- }
241
+ target = messages.find((message) => (
242
+ message?.info?.role === 'assistant'
243
+ && extractTextFromParts(message.parts, maxLength).length > 0
244
+ ));
167
245
  }
168
246
 
169
247
  if (!target || !Array.isArray(target.parts)) return '';
@@ -175,9 +253,9 @@ export const createNotificationTemplateRuntime = (deps) => {
175
253
  };
176
254
 
177
255
  const cacheSessionTitle = (sessionId, title) => {
178
- if (typeof sessionId === 'string' && sessionId.length > 0 && typeof title === 'string' && title.length > 0) {
179
- sessionTitleCache.set(sessionId, title);
180
- }
256
+ const id = trimmedText(sessionId);
257
+ const name = trimmedText(title);
258
+ if (id && name) sessionTitleCache.set(id, name);
181
259
  };
182
260
 
183
261
  const getCachedSessionTitle = (sessionId) => {
@@ -185,15 +263,12 @@ export const createNotificationTemplateRuntime = (deps) => {
185
263
  };
186
264
 
187
265
  const maybeCacheSessionInfoFromEvent = (payload) => {
188
- if (!payload || typeof payload !== 'object') return;
189
- const type = payload.type;
190
- if (type !== 'session.updated' && type !== 'session.created') return;
191
- const info = payload.properties?.info;
192
- if (!info || typeof info !== 'object') return;
193
- cacheSessionTitle(info.id, info.title);
266
+ const titled = readSessionTitleFromEvent(payload);
267
+ if (!titled) return;
268
+ cacheSessionTitle(titled.sessionId, titled.title);
194
269
  };
195
270
 
196
- const fetchSessionInfo = async (sessionId) => {
271
+ const fetchSessionInfo = async (sessionId, directory) => {
197
272
  if (!sessionId) return null;
198
273
 
199
274
  const cached = sessionInfoCache.get(sessionId);
@@ -202,20 +277,24 @@ export const createNotificationTemplateRuntime = (deps) => {
202
277
  }
203
278
 
204
279
  try {
205
- const url = buildOpenCodeUrl(`/session/${encodeURIComponent(sessionId)}`, '');
280
+ const base = buildOpenCodeUrl(`/session/${encodeURIComponent(sessionId)}`, '');
281
+ const url = directory ? `${base}?directory=${encodeURIComponent(directory)}` : base;
206
282
  const response = await fetch(url, {
207
283
  method: 'GET',
208
- headers: { Accept: 'application/json' },
284
+ headers: {
285
+ Accept: 'application/json',
286
+ ...(typeof getOpenCodeAuthHeaders === 'function' ? getOpenCodeAuthHeaders() : {}),
287
+ },
209
288
  signal: AbortSignal.timeout(2000),
210
289
  });
211
290
  if (!response.ok) {
212
291
  console.warn(`[Notification] fetchSessionInfo: ${response.status} for session ${sessionId}`);
213
292
  return null;
214
293
  }
215
- const data = await response.json().catch(() => null);
216
- if (data && typeof data === 'object') {
217
- sessionInfoCache.set(sessionId, { data, at: Date.now() });
218
- return data;
294
+ const session = readNotificationSessionRecord(await response.json().catch(() => null));
295
+ if (session) {
296
+ sessionInfoCache.set(sessionId, { data: session, at: Date.now() });
297
+ return session;
219
298
  }
220
299
  return null;
221
300
  } catch (error) {
@@ -227,7 +306,11 @@ export const createNotificationTemplateRuntime = (deps) => {
227
306
  const buildTemplateVariables = async (payload, sessionId) => {
228
307
  const info = payload?.properties?.info || {};
229
308
 
230
- let sessionTitle = payload?.properties?.sessionTitle || payload?.properties?.session?.title || (typeof info.sessionTitle === 'string' ? info.sessionTitle : '') || '';
309
+ const fromEvent = readSessionTitleFromEvent(payload);
310
+ let sessionTitle = trimmedText(payload?.properties?.sessionTitle)
311
+ || trimmedText(payload?.properties?.session?.title)
312
+ || trimmedText(info.sessionTitle)
313
+ || (fromEvent && (!sessionId || fromEvent.sessionId === sessionId) ? fromEvent.title : '');
231
314
 
232
315
  if (!sessionTitle && sessionId) {
233
316
  const cached = getCachedSessionTitle(sessionId);
@@ -238,25 +321,34 @@ export const createNotificationTemplateRuntime = (deps) => {
238
321
 
239
322
  let sessionInfo = null;
240
323
  if (!sessionTitle && sessionId) {
241
- sessionInfo = await fetchSessionInfo(sessionId);
242
- if (sessionInfo && typeof sessionInfo.title === 'string') {
243
- sessionTitle = sessionInfo.title;
324
+ sessionInfo = await fetchSessionInfo(sessionId, readNotificationDirectory(payload));
325
+ const fetchedTitle = trimmedText(sessionInfo?.title);
326
+ if (fetchedTitle) {
327
+ sessionTitle = fetchedTitle;
244
328
  cacheSessionTitle(sessionId, sessionTitle);
245
329
  }
246
330
  }
247
331
 
332
+ const eventAgent = trimmedText(info.agent) || trimmedText(info.mode);
333
+ const eventModel = trimmedText(info.modelID)
334
+ || trimmedText(info.model?.modelID)
335
+ || trimmedText(info.model?.id);
336
+ if ((!eventAgent || !eventModel) && sessionId && !sessionInfo) {
337
+ sessionInfo = await fetchSessionInfo(sessionId, readNotificationDirectory(payload));
338
+ }
339
+
248
340
  const agentName = (() => {
249
- const mode = typeof info.agent === 'string' && info.agent.trim().length > 0
250
- ? info.agent.trim()
251
- : (typeof info.mode === 'string' ? info.mode.trim() : '');
341
+ const mode = eventAgent || trimmedText(sessionInfo?.agent) || trimmedText(sessionInfo?.mode);
252
342
  if (!mode) return 'Agent';
253
343
  return mode.split(/[-_\s]+/).filter(Boolean)
254
344
  .map((token) => token.charAt(0).toUpperCase() + token.slice(1)).join(' ');
255
345
  })();
256
346
 
257
347
  const modelName = (() => {
258
- const raw = typeof info.modelID === 'string' ? info.modelID.trim()
259
- : (typeof info.model?.modelID === 'string' ? info.model.modelID.trim() : '');
348
+ const raw = eventModel
349
+ || trimmedText(sessionInfo?.model?.modelID)
350
+ || trimmedText(sessionInfo?.model?.id)
351
+ || trimmedText(sessionInfo?.modelID);
260
352
  if (!raw) return 'Assistant';
261
353
  return raw.split(/[-_]+/).filter(Boolean)
262
354
  .map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(' ');
@@ -82,3 +82,99 @@ describe('notification template message extraction', () => {
82
82
  await expect(runtime.fetchLastAssistantMessageText('session-1', 'msg-1')).resolves.toBe('final answer');
83
83
  });
84
84
  });
85
+
86
+ describe('notification session titles', () => {
87
+ afterEach(() => {
88
+ globalThis.fetch = originalFetch;
89
+ });
90
+
91
+ it('reads the OpenCode 2 session title from the data envelope', async () => {
92
+ const runtime = createNotificationTemplateRuntime({
93
+ readSettingsFromDisk: async () => ({}),
94
+ buildOpenCodeUrl: (path) => `http://opencode.test${path}`,
95
+ getOpenCodeAuthHeaders: () => ({ authorization: 'Basic test' }),
96
+ resolveGitBinaryForSpawn: () => 'git',
97
+ });
98
+ globalThis.fetch = vi.fn(async () => new Response(JSON.stringify({
99
+ data: { id: 'ses_1', title: '发布说明' },
100
+ })));
101
+
102
+ const variables = await runtime.buildTemplateVariables({
103
+ type: 'message.updated',
104
+ location: { directory: '/repo' },
105
+ properties: { info: { sessionID: 'ses_1', role: 'assistant', finish: 'stop' } },
106
+ }, 'ses_1');
107
+
108
+ expect(variables.session_name).toBe('发布说明');
109
+ expect(globalThis.fetch).toHaveBeenCalledWith(
110
+ 'http://opencode.test/session/ses_1?directory=%2Frepo',
111
+ expect.objectContaining({
112
+ headers: expect.objectContaining({ authorization: 'Basic test' }),
113
+ }),
114
+ );
115
+ });
116
+
117
+ it('keeps a title learned from session.renamed when the session fetch fails', async () => {
118
+ const runtime = createRuntime();
119
+ globalThis.fetch = vi.fn(async () => new Response(null, { status: 401 }));
120
+ runtime.maybeCacheSessionInfoFromEvent({
121
+ type: 'session.renamed',
122
+ data: { sessionID: 'ses_1', title: '修通知标题' },
123
+ });
124
+
125
+ const variables = await runtime.buildTemplateVariables({
126
+ type: 'session.execution.succeeded',
127
+ data: { sessionID: 'ses_1' },
128
+ }, 'ses_1');
129
+
130
+ expect(variables.session_name).toBe('修通知标题');
131
+ });
132
+
133
+ it('reads the latest assistant text from an OpenCode 2 message page', async () => {
134
+ const runtime = createRuntime();
135
+ globalThis.fetch = vi.fn(async () => new Response(JSON.stringify({
136
+ data: [
137
+ {
138
+ id: 'msg_new',
139
+ sessionID: 'ses_1',
140
+ type: 'assistant',
141
+ agent: 'build',
142
+ model: { id: 'gpt-5', providerID: 'openai' },
143
+ content: [
144
+ { type: 'reasoning', text: 'private' },
145
+ { type: 'text', text: '发布说明已写好' },
146
+ ],
147
+ time: { created: 2 },
148
+ },
149
+ {
150
+ id: 'msg_old',
151
+ sessionID: 'ses_1',
152
+ type: 'assistant',
153
+ content: [{ type: 'text', text: 'older' }],
154
+ finish: 'stop',
155
+ time: { created: 1 },
156
+ },
157
+ ],
158
+ })));
159
+
160
+ await expect(runtime.fetchLastAssistantMessageText('ses_1')).resolves.toBe('发布说明已写好');
161
+ });
162
+
163
+ it('fills agent and model names from the OpenCode 2 session record', async () => {
164
+ const runtime = createRuntime();
165
+ globalThis.fetch = vi.fn(async () => new Response(JSON.stringify({
166
+ data: { id: 'ses_1', title: '发布说明', agent: 'build', model: { id: 'gpt-5', providerID: 'openai' } },
167
+ })));
168
+
169
+ const variables = await runtime.buildTemplateVariables({
170
+ type: 'session.execution.succeeded',
171
+ data: { sessionID: 'ses_1' },
172
+ }, 'ses_1');
173
+
174
+ expect(variables).toMatchObject({
175
+ session_name: '发布说明',
176
+ agent_name: 'Build',
177
+ model_name: 'Gpt 5',
178
+ });
179
+ });
180
+ });