@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
@@ -18,6 +18,10 @@ import {
18
18
  projectSlimParts,
19
19
  SLIM_PARTS_PROJECTION,
20
20
  } from './service.js';
21
+ import {
22
+ projectSessionMessage,
23
+ projectSessionMessageRecords as projectRecords,
24
+ } from './session-message-projection.js';
21
25
 
22
26
  const TURNS_MIN = 1;
23
27
  const TURNS_MAX = 10;
@@ -210,103 +214,6 @@ const requestSignal = (req, res) => {
210
214
  return controller.signal;
211
215
  };
212
216
 
213
- /**
214
- * Native SessionMessageInfo carrier fields that become `parts`.
215
- * Must not remain on `info` after normalize — otherwise
216
- * includeReasoning=false can strip parts while leaking content/text.
217
- */
218
- const MESSAGE_PART_CARRIER_KEYS = new Set(['content', 'text', 'files', 'parts', 'agents', 'skills']);
219
-
220
- const stripMessagePartCarriers = (value) => {
221
- if (!value || typeof value !== 'object' || Array.isArray(value)) return value;
222
- let changed = false;
223
- const next = {};
224
- for (const [key, field] of Object.entries(value)) {
225
- if (MESSAGE_PART_CARRIER_KEYS.has(key)) {
226
- changed = true;
227
- continue;
228
- }
229
- next[key] = field;
230
- }
231
- return changed ? next : value;
232
- };
233
-
234
- /**
235
- * Project SessionMessageInfo (or an already-projected {info, parts} row)
236
- * into the host HTTP DTO. External route contract stays {info, parts}.
237
- */
238
- const projectV2Parts = (entry) => {
239
- if (Array.isArray(entry?.parts)) return entry.parts;
240
- const messageID = typeof entry?.id === 'string' ? entry.id : '';
241
- const sessionID = typeof entry?.sessionID === 'string' ? entry.sessionID : '';
242
- const parts = [];
243
- if (typeof entry?.text === 'string' && entry.text) {
244
- parts.push({ id: `${messageID}:text`, sessionID, messageID, type: 'text', text: entry.text });
245
- }
246
- if (Array.isArray(entry?.files)) {
247
- entry.files.forEach((file, index) => {
248
- parts.push({
249
- id: `${messageID}:file:${index}`,
250
- sessionID,
251
- messageID,
252
- type: 'file',
253
- mime: file?.mime || 'application/octet-stream',
254
- url: file?.uri ?? file?.url,
255
- ...(file?.name ? { filename: file.name } : {}),
256
- });
257
- });
258
- }
259
- if (Array.isArray(entry?.content)) {
260
- entry.content.forEach((item, index) => {
261
- const partID = item?.id || `${messageID}:content:${index}`;
262
- if (item?.type === 'text') {
263
- parts.push({ id: partID, sessionID, messageID, type: 'text', text: item.text ?? '' });
264
- } else if (item?.type === 'reasoning') {
265
- parts.push({ id: partID, sessionID, messageID, type: 'reasoning', text: item.text ?? '' });
266
- } else if (item?.type === 'tool') {
267
- parts.push({
268
- id: partID,
269
- sessionID,
270
- messageID,
271
- type: 'tool',
272
- tool: item.name,
273
- callID: item.id,
274
- state: item.state ?? {},
275
- });
276
- }
277
- });
278
- }
279
- return parts;
280
- };
281
-
282
- const projectSessionMessage = (entry) => {
283
- if (entry && typeof entry === 'object' && entry.info && typeof entry.info === 'object') {
284
- const info = stripMessagePartCarriers(entry.info);
285
- return {
286
- info: info === entry.info ? entry.info : info,
287
- parts: Array.isArray(entry.parts) ? entry.parts : [],
288
- };
289
- }
290
- if (!entry || typeof entry !== 'object' || typeof entry.id !== 'string' || entry.id.length === 0) {
291
- return entry;
292
- }
293
- const role = entry.type === 'user' || entry.type === 'assistant' ? entry.type : (entry.role ?? entry.type);
294
- const info = {
295
- ...stripMessagePartCarriers(entry),
296
- id: entry.id,
297
- sessionID: entry.sessionID,
298
- role,
299
- time: entry.time ?? { created: 0 },
300
- };
301
- return {
302
- info,
303
- parts: projectV2Parts(entry),
304
- };
305
- };
306
-
307
- const projectRecords = (records) =>
308
- (Array.isArray(records) ? records : []).map(projectSessionMessage);
309
-
310
217
  /**
311
218
  * Host outbound order: normalize → slim/L1 bounds → reasoning strip.
312
219
  * Slim/reasoning must run on `{info, parts}` so native `content` cannot
@@ -327,11 +234,12 @@ const createV2FetchPage = ({ buildOpenCodeUrl, getOpenCodeAuthHeaders, logger })
327
234
  const headers = typeof getOpenCodeAuthHeaders === 'function' ? getOpenCodeAuthHeaders() : {};
328
235
  const client = makeOpenCodeV2Client({ baseUrl, authHeaders: headers });
329
236
 
237
+ // Upstream rejects `order` together with `cursor` (400); the cursor
238
+ // already encodes the desc direction of the first page.
330
239
  const result = await client.message.list({
331
240
  sessionID,
332
241
  ...(Number.isFinite(limit) ? { limit } : {}),
333
- order: 'desc',
334
- ...(typeof before === 'string' && before.length > 0 ? { cursor: before } : {}),
242
+ ...(typeof before === 'string' && before.length > 0 ? { cursor: before } : { order: 'desc' }),
335
243
  }, { signal });
336
244
 
337
245
  const items = Array.isArray(result?.data) ? result.data : null;
@@ -342,8 +250,8 @@ const createV2FetchPage = ({ buildOpenCodeUrl, getOpenCodeAuthHeaders, logger })
342
250
  throw error;
343
251
  }
344
252
 
345
- const previous = result?.cursor?.previous;
346
- const nextCursor = typeof previous === 'string' && previous.length > 0 ? previous : null;
253
+ const next = result?.cursor?.next;
254
+ const nextCursor = typeof next === 'string' && next.length > 0 ? next : null;
347
255
 
348
256
  return {
349
257
  records: items.slice().reverse(),
@@ -418,7 +326,14 @@ const createSdkFetchMessage = ({ buildOpenCodeUrl, getOpenCodeAuthHeaders, logge
418
326
  if (!data || typeof data !== 'object' || Array.isArray(data)) {
419
327
  throwUpstream(logger, '[session-turn-pages] session.message malformed payload');
420
328
  }
421
- return projectSessionMessage(data);
329
+ const projected = projectSessionMessage(data);
330
+ // Control rows (idle / model-switched) are not chat messages.
331
+ if (!projected || !projected.info) {
332
+ const notFound = new Error('not_found');
333
+ notFound.code = 'not_found';
334
+ throw notFound;
335
+ }
336
+ return projected;
422
337
  };
423
338
  };
424
339
 
@@ -2,6 +2,10 @@ import { describe, expect, it, vi } from 'vitest';
2
2
 
3
3
  import { makeOpenCodeV2Client } from '../opencode/v2-client.js';
4
4
  import { registerSessionTurnPageRoutes } from './routes.js';
5
+ import {
6
+ projectSessionMessage as projectSessionMessageRecord,
7
+ projectSessionMessageRecords,
8
+ } from './session-message-projection.js';
5
9
 
6
10
  vi.mock('../opencode/v2-client.js', () => ({ makeOpenCodeV2Client: vi.fn() }));
7
11
 
@@ -33,6 +37,85 @@ const response = () => ({
33
37
 
34
38
  const ROUTE = '/api/openchamber/sessions/:sessionID/messages';
35
39
 
40
+ describe('projectSessionMessageRecord (host SessionMessage → {info, parts})', () => {
41
+ it('drops idle control and projects two-turn wire without duplicate text', () => {
42
+ const records = projectSessionMessageRecords([
43
+ {
44
+ id: 'msg_u1',
45
+ type: 'user',
46
+ time: { created: 1 },
47
+ text: 'UI-E2E-FINAL-BUILD',
48
+ sessionID: 'ses_1',
49
+ },
50
+ {
51
+ id: 'msg_a1',
52
+ type: 'assistant',
53
+ time: { created: 2, completed: 3 },
54
+ sessionID: 'ses_1',
55
+ agent: 'build',
56
+ model: { id: 'glm-5.3-flash', providerID: 'zai-coding-plan', variant: 'default' },
57
+ content: [
58
+ { type: 'reasoning', text: 'think' },
59
+ { type: 'text', text: 'FINAL_BUILD_OK' },
60
+ ],
61
+ finish: 'stop',
62
+ rawFinish: 'stop',
63
+ },
64
+ {
65
+ id: 'msg_u2',
66
+ type: 'user',
67
+ time: { created: 4 },
68
+ text: 'UI-E2E-FINAL-FOLLOWUP',
69
+ sessionID: 'ses_1',
70
+ },
71
+ {
72
+ id: 'msg_a2',
73
+ type: 'assistant',
74
+ time: { created: 5, completed: 6 },
75
+ sessionID: 'ses_1',
76
+ agent: 'build',
77
+ model: { id: 'glm-5.3-flash', providerID: 'zai-coding-plan', variant: 'default' },
78
+ text: 'FINAL_FOLLOWUP_OK',
79
+ content: [
80
+ { type: 'reasoning', text: 'think2' },
81
+ { type: 'text', text: 'FINAL_FOLLOWUP_OK' },
82
+ ],
83
+ finish: 'stop',
84
+ },
85
+ {
86
+ id: 'msg_idle',
87
+ type: 'idle',
88
+ outcome: 'succeeded',
89
+ time: { created: 7 },
90
+ },
91
+ ]);
92
+
93
+ expect(records.map((row) => [row.info.role, row.info.id])).toEqual([
94
+ ['user', 'msg_u1'],
95
+ ['assistant', 'msg_a1'],
96
+ ['user', 'msg_u2'],
97
+ ['assistant', 'msg_a2'],
98
+ ]);
99
+ expect(projectSessionMessageRecord({
100
+ id: 'msg_idle2',
101
+ type: 'idle',
102
+ outcome: 'failed',
103
+ time: { created: 1 },
104
+ })).toBeNull();
105
+
106
+ const follow = records[3];
107
+ expect(follow.parts.filter((part) => part.type === 'text')).toHaveLength(1);
108
+ expect(follow.parts.find((part) => part.type === 'text').text).toBe('FINAL_FOLLOWUP_OK');
109
+ expect(follow.info.model).toEqual({
110
+ providerID: 'zai-coding-plan',
111
+ modelID: 'glm-5.3-flash',
112
+ variant: 'default',
113
+ });
114
+ expect(follow.info.type).toBeUndefined();
115
+ expect(follow.info.rawFinish).toBeUndefined();
116
+ });
117
+ });
118
+
36
119
  describe('registerSessionTurnPageRoutes', () => {
37
120
  it('registers GET /api/openchamber/sessions/:sessionID/messages', () => {
38
121
  const { app, route } = registry();
@@ -737,6 +820,204 @@ describe('registerSessionTurnPageRoutes', () => {
737
820
  ]);
738
821
  });
739
822
 
823
+ it('default fetch continues desc pages with cursor.next only, never order + cursor', async () => {
824
+ const list = vi.fn()
825
+ .mockResolvedValueOnce({
826
+ data: [{ id: 'msg_a2', type: 'assistant', time: { created: 4, completed: 5 }, content: [{ type: 'text', text: 'later' }] }],
827
+ cursor: { previous: 'raw_prev', next: 'raw_next' },
828
+ })
829
+ .mockResolvedValueOnce({
830
+ data: [
831
+ { id: 'msg_a1', type: 'assistant', time: { created: 2, completed: 3 }, content: [{ type: 'text', text: 'ok' }] },
832
+ { id: 'msg_u1', type: 'user', time: { created: 1 }, text: 'hi' },
833
+ ],
834
+ cursor: { previous: 'raw_prev_2', next: null },
835
+ });
836
+ makeOpenCodeV2Client.mockReturnValue({
837
+ message: { list },
838
+ session: { messages: undefined, status: undefined, abort: undefined },
839
+ });
840
+ const { app, route } = registry();
841
+ registerSessionTurnPageRoutes(app, {
842
+ buildOpenCodeUrl: () => 'http://open.code/',
843
+ getOpenCodeAuthHeaders: () => ({}),
844
+ });
845
+ const res = response();
846
+ await route('GET', ROUTE)({
847
+ params: { sessionID: 'ses_1' },
848
+ query: { turns: '1' },
849
+ headers: {},
850
+ }, res);
851
+
852
+ expect(list).toHaveBeenCalledTimes(2);
853
+ expect(list.mock.calls[0][0]).toEqual({ sessionID: 'ses_1', limit: 100, order: 'desc' });
854
+ expect(list.mock.calls[1][0]).toEqual({ sessionID: 'ses_1', limit: 100, cursor: 'raw_next' });
855
+ expect(res.statusCode).toBe(200);
856
+ expect(res.body.complete).toBe(true);
857
+ expect(res.body.records.map((record) => record.info.id)).toEqual(['msg_u1', 'msg_a1', 'msg_a2']);
858
+ });
859
+
860
+ it('drops v2 idle/control rows and does not double-expand text+content (FINAL-GATE cold reload)', async () => {
861
+ // Real wire shape from isolated backend hard-reload sessions:
862
+ // user + assistant(content) + idle outcome=succeeded, and assistants that
863
+ // occasionally carry both top-level text and content[].text.
864
+ const list = vi.fn(async () => ({
865
+ data: [
866
+ {
867
+ id: 'msg_idle_ok',
868
+ time: { created: 4 },
869
+ type: 'idle',
870
+ outcome: 'succeeded',
871
+ },
872
+ {
873
+ id: 'msg_a_follow',
874
+ time: { created: 3, completed: 4 },
875
+ type: 'assistant',
876
+ agent: 'build',
877
+ model: { id: 'glm-5.3-flash', providerID: 'zai-coding-plan', variant: 'default' },
878
+ // Both carriers present — must project exactly one text part.
879
+ text: 'FINAL_FOLLOWUP_OK',
880
+ content: [
881
+ { type: 'reasoning', text: 'think' },
882
+ { type: 'text', text: 'FINAL_FOLLOWUP_OK' },
883
+ ],
884
+ finish: 'stop',
885
+ rawFinish: 'stop',
886
+ cost: 0,
887
+ tokens: { input: 1, output: 1, reasoning: 1, cache: { read: 0, write: 0 } },
888
+ },
889
+ {
890
+ id: 'msg_u_follow',
891
+ time: { created: 2 },
892
+ type: 'user',
893
+ text: 'UI-E2E-FINAL-FOLLOWUP',
894
+ },
895
+ {
896
+ id: 'msg_a1',
897
+ time: { created: 1, completed: 2 },
898
+ type: 'assistant',
899
+ agent: 'build',
900
+ model: { id: 'glm-5.3-flash', providerID: 'zai-coding-plan', variant: 'default' },
901
+ content: [
902
+ { type: 'reasoning', text: 'first think' },
903
+ { type: 'text', text: 'FINAL_BUILD_OK' },
904
+ ],
905
+ finish: 'stop',
906
+ },
907
+ {
908
+ id: 'msg_u1',
909
+ time: { created: 0 },
910
+ type: 'user',
911
+ text: 'UI-E2E-FINAL-BUILD',
912
+ },
913
+ ],
914
+ cursor: { previous: null, next: null },
915
+ }));
916
+ makeOpenCodeV2Client.mockReturnValue({
917
+ message: { list },
918
+ session: { messages: undefined, status: undefined, abort: undefined },
919
+ });
920
+ const { app, route } = registry();
921
+ registerSessionTurnPageRoutes(app, {
922
+ buildOpenCodeUrl: () => 'http://open.code/',
923
+ getOpenCodeAuthHeaders: () => ({}),
924
+ });
925
+ const res = response();
926
+ await route('GET', ROUTE)({
927
+ params: { sessionID: 'ses_final_build' },
928
+ query: { turns: '2' },
929
+ headers: {},
930
+ }, res);
931
+
932
+ expect(res.statusCode).toBe(200);
933
+ expect(res.body.records.map((row) => row.info.role)).toEqual([
934
+ 'user',
935
+ 'assistant',
936
+ 'user',
937
+ 'assistant',
938
+ ]);
939
+ expect(res.body.records.map((row) => row.info.id)).toEqual([
940
+ 'msg_u1',
941
+ 'msg_a1',
942
+ 'msg_u_follow',
943
+ 'msg_a_follow',
944
+ ]);
945
+ // No idle / control leakage into ChatMessage props.
946
+ expect(res.body.records.some((row) => row.info.role === 'idle' || row.info.type === 'idle')).toBe(false);
947
+ expect(JSON.stringify(res.body)).not.toContain('"type":"idle"');
948
+
949
+ const firstAssistant = res.body.records[1];
950
+ expect(firstAssistant.info.agent).toBe('build');
951
+ expect(firstAssistant.info.modelID).toBe('glm-5.3-flash');
952
+ expect(firstAssistant.info.model).toEqual({
953
+ providerID: 'zai-coding-plan',
954
+ modelID: 'glm-5.3-flash',
955
+ variant: 'default',
956
+ });
957
+ expect(firstAssistant.info.type).toBeUndefined();
958
+ expect(firstAssistant.info.rawFinish).toBeUndefined();
959
+ const firstTexts = firstAssistant.parts.filter((part) => part.type === 'text');
960
+ expect(firstTexts).toHaveLength(1);
961
+ expect(firstTexts[0].text).toBe('FINAL_BUILD_OK');
962
+
963
+ const followAssistant = res.body.records[3];
964
+ const followTexts = followAssistant.parts.filter((part) => part.type === 'text');
965
+ expect(followTexts).toHaveLength(1);
966
+ expect(followTexts[0].text).toBe('FINAL_FOLLOWUP_OK');
967
+ expect(followAssistant.parts.filter((part) => part.type === 'reasoning')).toHaveLength(1);
968
+ });
969
+
970
+ it('keeps provider.auth 401 assistant error and drops idle failed control', async () => {
971
+ const list = vi.fn(async () => ({
972
+ data: [
973
+ { id: 'msg_idle_fail', time: { created: 3 }, type: 'idle', outcome: 'failed' },
974
+ {
975
+ id: 'msg_a_401',
976
+ time: { created: 2, completed: 3 },
977
+ type: 'assistant',
978
+ agent: 'build',
979
+ model: { id: 'deepseek-flash', providerID: 'deepseek', variant: 'default' },
980
+ content: null,
981
+ finish: 'error',
982
+ error: {
983
+ type: 'provider.auth',
984
+ message: 'Authentication Fails, Your api key: **** is invalid',
985
+ status: 401,
986
+ },
987
+ },
988
+ { id: 'msg_u_401', time: { created: 1 }, type: 'user', text: 'UI-E2E-FINAL-DS' },
989
+ ],
990
+ cursor: { previous: null, next: null },
991
+ }));
992
+ makeOpenCodeV2Client.mockReturnValue({
993
+ message: { list },
994
+ session: { messages: undefined, status: undefined, abort: undefined },
995
+ });
996
+ const { app, route } = registry();
997
+ registerSessionTurnPageRoutes(app, {
998
+ buildOpenCodeUrl: () => 'http://open.code/',
999
+ getOpenCodeAuthHeaders: () => ({}),
1000
+ });
1001
+ const res = response();
1002
+ await route('GET', ROUTE)({
1003
+ params: { sessionID: 'ses_ds' },
1004
+ query: { turns: '1' },
1005
+ headers: {},
1006
+ }, res);
1007
+
1008
+ expect(res.statusCode).toBe(200);
1009
+ expect(res.body.records).toHaveLength(2);
1010
+ expect(res.body.records.map((row) => row.info.role)).toEqual(['user', 'assistant']);
1011
+ const assistant = res.body.records[1];
1012
+ expect(assistant.info.finish).toBe('error');
1013
+ expect(assistant.info.error).toEqual({
1014
+ type: 'provider.auth',
1015
+ message: 'Authentication Fails, Your api key: **** is invalid',
1016
+ status: 401,
1017
+ });
1018
+ expect(assistant.parts).toEqual([]);
1019
+ });
1020
+
740
1021
  it('default fetch throws on message.list failure and maps to upstream', async () => {
741
1022
  makeOpenCodeV2Client.mockReturnValue({
742
1023
  message: { list: vi.fn(async () => { throw Object.assign(new Error('upstream'), { status: 500 }); }) },
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Type surface for session-message-projection.js (consumed by the VS Code
3
+ * turn-page bridge). Runtime implementation stays in the .js module.
4
+ */
5
+
6
+ export type ProjectedSessionMessage = {
7
+ info: { id: string; role: string; [key: string]: unknown };
8
+ parts: unknown[];
9
+ };
10
+
11
+ export function projectSessionMessage(entry: unknown): ProjectedSessionMessage | null;
12
+
13
+ export function projectSessionMessageRecords(records: unknown): ProjectedSessionMessage[];