@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
@@ -11,6 +11,10 @@ import {
11
11
  extractDirectoryFromRequest,
12
12
  isDirectoryTurnAdmissionPath,
13
13
  } from './instance-recovery-runtime.js';
14
+ import {
15
+ runtimeContractExecutionBlockedBody,
16
+ shouldBlockRuntimeContractExecution,
17
+ } from './runtime-contract.js';
14
18
  import {
15
19
  createReasoningOutboundFilter,
16
20
  createSseBlockSplitter,
@@ -20,6 +24,25 @@ import {
20
24
  readIncludeReasoningQuery,
21
25
  stripIncludeReasoningParam,
22
26
  } from '../event-stream/reasoning-projection.js';
27
+ import {
28
+ isSessionLifecycleEventType,
29
+ projectSessionLifecyclePayload,
30
+ projectSessionWithStoredMap,
31
+ } from '../session-metadata/session-projection.js';
32
+
33
+ /**
34
+ * v2 protocol mounts every JSON/SSE route under `/api`
35
+ * (`@opencode-ai/protocol` groups). Host traffic is already `/api/*`.
36
+ * Express `app.use('/api', …)` strips that prefix from `req.url`; put it back
37
+ * before talking to opencode2. Passing a root `/session` path to v2 returns
38
+ * the bundled Web UI HTML (`text/html`) and the SDK throws UnsupportedContentType.
39
+ */
40
+ export const toUpstreamOpenCodeApiPath = (requestUrl) => {
41
+ const raw = typeof requestUrl === 'string' ? requestUrl : '';
42
+ if (!raw || raw === '/') return '/api';
43
+ if (raw.startsWith('/api/') || raw === '/api' || raw.startsWith('/api?')) return raw;
44
+ return raw.startsWith('/') ? `/api${raw}` : `/api/${raw}`;
45
+ };
23
46
 
24
47
  export const createDirectoryQueryCanonicalizer = ({ realpath, ...cacheOptions } = {}) => {
25
48
  const realpathCache = createRealpathCache({ fallbackOnError: true, realpath, ...cacheOptions });
@@ -183,10 +206,19 @@ const sanitizeSessionListItem = (session) => {
183
206
  };
184
207
 
185
208
  const sanitizeSessionListPayload = (payload) => {
186
- if (!Array.isArray(payload)) {
187
- return payload;
209
+ if (Array.isArray(payload)) {
210
+ return payload.map((session) => sanitizeSessionListItem(session));
211
+ }
212
+ if (payload && typeof payload === 'object' && Array.isArray(payload.data)) {
213
+ return { ...payload, data: payload.data.map((session) => sanitizeSessionListItem(session)) };
188
214
  }
189
- return payload.map((session) => sanitizeSessionListItem(session));
215
+ return payload;
216
+ };
217
+
218
+ const sessionListRecords = (payload) => {
219
+ if (Array.isArray(payload)) return payload;
220
+ if (payload && typeof payload === 'object' && Array.isArray(payload.data)) return payload.data;
221
+ return null;
190
222
  };
191
223
 
192
224
  export const isInteractiveSessionRequest = (method, requestUrl) => {
@@ -221,13 +253,150 @@ export const registerOpenCodeProxy = (app, deps) => {
221
253
  SSE_UPSTREAM_CONNECT_TIMEOUT_MS,
222
254
  SSE_UPSTREAM_STALL_TIMEOUT_MS,
223
255
  getRuntime,
256
+ /** Optional DI: `{ allowMissingContract: true }` for unit factories only. */
257
+ getRuntimeContractGateOptions = null,
224
258
  getOpenCodeAuthHeaders,
225
259
  buildOpenCodeUrl,
226
260
  ensureOpenCodeApiPrefix,
227
261
  onInteractiveSessionRequest,
228
262
  onSessionTurnAdmission,
263
+ // OpenCode 2.x has no session-metadata update route. When the Host store is
264
+ // wired, fold its per-session metadata onto list/detail responses so
265
+ // clients keep reading `session.metadata` where they always did.
266
+ getStoredSessionMetadata = null,
267
+ /** Sync committed snapshot; null = not ready / unknown when store is wired. */
268
+ getStoredSessionMetadataSync = null,
269
+ /** Await Host metadata load before SSE fan-out (resolves ok/false, does not reject). */
270
+ ensureSessionMetadataReady = null,
271
+ /** Best-effort Host metadata cleanup after successful upstream session delete. */
272
+ onSessionDeleted = null,
229
273
  } = deps;
230
274
 
275
+ const storeConfigured = typeof getStoredSessionMetadata === 'function';
276
+
277
+ const waitForHostMetadataReady = async () => {
278
+ if (!storeConfigured) return true;
279
+ if (typeof ensureSessionMetadataReady === 'function') {
280
+ try {
281
+ return await ensureSessionMetadataReady() === true;
282
+ } catch {
283
+ return false;
284
+ }
285
+ }
286
+ // Fallback: one getAll attempt.
287
+ try {
288
+ await getStoredSessionMetadata();
289
+ return true;
290
+ } catch {
291
+ return false;
292
+ }
293
+ };
294
+
295
+ /**
296
+ * When the Host store is wired, read failures are retryable errors — never
297
+ * silent upstream-without-overlay (that would look like authoritative empty
298
+ * Host state). When the store is not wired, leave upstream untouched.
299
+ * @returns {{ status: 'unconfigured' | 'ok' | 'unavailable', stored?: object, error?: string }}
300
+ */
301
+ const readStoredSessionMetadata = async () => {
302
+ if (!storeConfigured) return { status: 'unconfigured' };
303
+ try {
304
+ const stored = await getStoredSessionMetadata();
305
+ if (!stored || typeof stored !== 'object') {
306
+ return { status: 'unavailable', error: 'session metadata is unavailable' };
307
+ }
308
+ return { status: 'ok', stored };
309
+ } catch (error) {
310
+ const message = error instanceof Error ? error.message : 'session metadata is unavailable';
311
+ console.warn('[proxy] session metadata unavailable:', message);
312
+ return { status: 'unavailable', error: message };
313
+ }
314
+ };
315
+
316
+ const readStoredSessionMetadataSync = () => {
317
+ if (typeof getStoredSessionMetadataSync !== 'function') {
318
+ return storeConfigured ? null : undefined;
319
+ }
320
+ try {
321
+ const stored = getStoredSessionMetadataSync();
322
+ if (stored && typeof stored === 'object') return stored;
323
+ return null;
324
+ } catch {
325
+ return null;
326
+ }
327
+ };
328
+
329
+ const metadataUnavailableResponse = (res, error) => {
330
+ res.status(503).json({
331
+ error: error || 'session metadata is unavailable',
332
+ code: 'session_metadata_unavailable',
333
+ retryable: true,
334
+ });
335
+ };
336
+
337
+ /**
338
+ * OpenChamber's stored metadata deep-merges onto upstream create-time
339
+ * metadata, and Host archive authority projects onto `time.archived`.
340
+ */
341
+ const overlaySession = (session, stored) => {
342
+ if (!stored) return session;
343
+ return projectSessionWithStoredMap(session, stored);
344
+ };
345
+
346
+ /**
347
+ * @returns {Promise<{ ok: true, payload: unknown } | { ok: false, error: string }>}
348
+ */
349
+ const overlayOwnedStateOnList = async (payload) => {
350
+ const records = sessionListRecords(payload);
351
+ if (!records) return { ok: true, payload };
352
+ const meta = await readStoredSessionMetadata();
353
+ if (meta.status === 'unconfigured') return { ok: true, payload };
354
+ if (meta.status === 'unavailable') return { ok: false, error: meta.error || 'session metadata is unavailable' };
355
+ const overlaid = records.map((session) => overlaySession(session, meta.stored));
356
+ // Preserve `{ data, cursor }` (and any other list envelope fields).
357
+ return {
358
+ ok: true,
359
+ payload: Array.isArray(payload) ? overlaid : { ...payload, data: overlaid },
360
+ };
361
+ };
362
+
363
+ const createHostSessionSseFilter = () => ({
364
+ projectEvent: (payload) => {
365
+ // Transcript/message hot path stays open. Only session lifecycle frames
366
+ // require Host authority; until the store is ready they are suppressed.
367
+ const stored = readStoredSessionMetadataSync();
368
+ if (storeConfigured && stored == null) {
369
+ const type = payload?.type || payload?.payload?.type;
370
+ if (isSessionLifecycleEventType(type)) return null;
371
+ return payload;
372
+ }
373
+ if (!stored) return payload;
374
+ return projectSessionLifecyclePayload(payload, (sessionId) => {
375
+ const host = stored[sessionId];
376
+ return host && typeof host === 'object' && !Array.isArray(host) ? host : null;
377
+ }, { hostReady: true });
378
+ },
379
+ });
380
+
381
+ const composeSseFilters = (...filters) => {
382
+ const active = filters.filter(Boolean);
383
+ if (active.length === 0) return null;
384
+ if (active.length === 1) return active[0];
385
+ return {
386
+ projectEvent: (payload) => {
387
+ let current = payload;
388
+ for (const filter of active) {
389
+ current = filter.projectEvent(current);
390
+ if (current == null) return null;
391
+ }
392
+ return current;
393
+ },
394
+ dispose: () => {
395
+ for (const filter of active) filter.dispose?.();
396
+ },
397
+ };
398
+ };
399
+
231
400
  if (app.get('opencodeProxyConfigured')) {
232
401
  return;
233
402
  }
@@ -378,6 +547,15 @@ export const registerOpenCodeProxy = (app, deps) => {
378
547
  };
379
548
 
380
549
  const forwardSseRequest = async (req, res) => {
550
+ // Gate session-lifecycle Host authority before attaching upstream SSE so we
551
+ // never drop the only lifecycle frame while metadata is still unknown.
552
+ if (storeConfigured) {
553
+ const ready = await waitForHostMetadataReady();
554
+ if (!ready) {
555
+ return metadataUnavailableResponse(res, 'session metadata is unavailable');
556
+ }
557
+ }
558
+
381
559
  const abortController = new AbortController();
382
560
  const closeUpstream = () => abortController.abort();
383
561
  let upstream = null;
@@ -395,7 +573,13 @@ export const registerOpenCodeProxy = (app, deps) => {
395
573
  const includeReasoning = readIncludeReasoningQuery(req.query)
396
574
  && readIncludeReasoningFromUrl(requestUrl);
397
575
  const reasoningFilter = includeReasoning ? null : createReasoningOutboundFilter();
398
- const sseSplitter = includeReasoning ? null : createSseBlockSplitter();
576
+ const hostSessionFilter = typeof getStoredSessionMetadataSync === 'function'
577
+ ? createHostSessionSseFilter()
578
+ : null;
579
+ const outboundSseFilter = composeSseFilters(reasoningFilter, hostSessionFilter);
580
+ // Parse SSE blocks when any Host projection filter is active (reasoning
581
+ // strip and/or session archive authority). Otherwise byte-identical passthrough.
582
+ const sseSplitter = outboundSseFilter ? createSseBlockSplitter() : null;
399
583
 
400
584
  req.on('close', closeUpstream);
401
585
 
@@ -419,7 +603,7 @@ export const registerOpenCodeProxy = (app, deps) => {
419
603
  try {
420
604
  // Strip includeReasoning before building the upstream OpenCode path.
421
605
  const strippedUrl = stripIncludeReasoningParam(requestUrl);
422
- const upstreamPath = strippedUrl.startsWith('/api') ? strippedUrl.slice(4) || '/' : strippedUrl;
606
+ const upstreamPath = toUpstreamOpenCodeApiPath(strippedUrl);
423
607
  const headers = normalizeForwardedDirectoryHeaders(
424
608
  collectForwardProxyHeaders(req.headers, getOpenCodeAuthHeaders())
425
609
  );
@@ -511,20 +695,20 @@ export const registerOpenCodeProxy = (app, deps) => {
511
695
  }
512
696
  if (value && value.length > 0) {
513
697
  resetStallTimer();
514
- if (includeReasoning) {
515
- // Enabled path: byte-identical passthrough (existing behavior).
698
+ if (!outboundSseFilter || !sseSplitter) {
699
+ // No Host projection: byte-identical passthrough.
516
700
  sseBoundary.observe(value);
517
701
  const canContinue = await enqueueSseWrite(value);
518
702
  if (!canContinue) {
519
703
  break;
520
704
  }
521
705
  } else {
522
- // Disabled path: parse SSE blocks, drop reasoning, re-serialize.
523
- // Preserves id/event lines and GlobalEvent wrap when unchanged drop-only.
706
+ // Parse SSE blocks, apply reasoning strip + Host archive authority.
707
+ // Preserves id/event lines and GlobalEvent wrap when unchanged.
524
708
  const blocks = sseSplitter.push(value);
525
709
  let canContinue = true;
526
710
  for (const block of blocks) {
527
- const filtered = filterSseBlock(block, reasoningFilter);
711
+ const filtered = filterSseBlock(block, outboundSseFilter);
528
712
  if (!filtered) {
529
713
  continue;
530
714
  }
@@ -539,9 +723,9 @@ export const registerOpenCodeProxy = (app, deps) => {
539
723
  }
540
724
  }
541
725
 
542
- if (!includeReasoning && sseSplitter) {
726
+ if (outboundSseFilter && sseSplitter) {
543
727
  for (const block of sseSplitter.finish()) {
544
- const filtered = filterSseBlock(block, reasoningFilter);
728
+ const filtered = filterSseBlock(block, outboundSseFilter);
545
729
  if (filtered) {
546
730
  sseBoundary.observe(filtered);
547
731
  await enqueueSseWrite(filtered);
@@ -570,7 +754,7 @@ export const registerOpenCodeProxy = (app, deps) => {
570
754
  } finally {
571
755
  clearConnectTimer();
572
756
  clearStallTimer();
573
- reasoningFilter?.dispose?.();
757
+ outboundSseFilter?.dispose?.();
574
758
  if (heartbeatTimer) {
575
759
  clearTimeout(heartbeatTimer);
576
760
  heartbeatTimer = null;
@@ -588,7 +772,7 @@ export const registerOpenCodeProxy = (app, deps) => {
588
772
  }
589
773
  };
590
774
 
591
- const fetchSessionListPayload = async (upstreamPath, { req = null, timeoutMs = null } = {}) => {
775
+ const fetchSessionListPayload = async (upstreamPath, { req = null, timeoutMs = null, method = 'GET' } = {}) => {
592
776
  const headers = req
593
777
  ? {
594
778
  ...normalizeForwardedDirectoryHeaders(collectForwardProxyHeaders(req.headers, getOpenCodeAuthHeaders())),
@@ -601,7 +785,7 @@ export const registerOpenCodeProxy = (app, deps) => {
601
785
  'accept-encoding': 'identity',
602
786
  };
603
787
  const upstream = await fetch(buildOpenCodeUrl(upstreamPath, ''), {
604
- method: 'GET',
788
+ method: method || 'GET',
605
789
  headers,
606
790
  ...(typeof timeoutMs === 'number' ? { signal: AbortSignal.timeout(timeoutMs) } : {}),
607
791
  });
@@ -625,8 +809,7 @@ export const registerOpenCodeProxy = (app, deps) => {
625
809
  const requestUrl = typeof req.originalUrl === 'string' && req.originalUrl.length > 0
626
810
  ? req.originalUrl
627
811
  : (typeof req.url === 'string' ? req.url : '');
628
- const upstreamPathRaw = requestUrl.startsWith('/api') ? requestUrl.slice(4) || '/' : requestUrl;
629
- return canonicalizeDirectoryQuery(upstreamPathRaw);
812
+ return canonicalizeDirectoryQuery(toUpstreamOpenCodeApiPath(requestUrl));
630
813
  };
631
814
 
632
815
  const forwardSanitizedSessionListRequest = async (req, res, next, logLabel) => {
@@ -643,14 +826,18 @@ export const registerOpenCodeProxy = (app, deps) => {
643
826
  return;
644
827
  }
645
828
 
646
- if (result.parseError || !Array.isArray(result.payload)) {
829
+ if (result.parseError || !sessionListRecords(result.payload)) {
647
830
  res.setHeader('content-type', result.contentType);
648
831
  res.end(result.bodyText);
649
832
  return;
650
833
  }
651
834
 
652
835
  res.setHeader('content-type', result.contentType);
653
- res.json(sanitizeSessionListPayload(result.payload));
836
+ const overlaid = await overlayOwnedStateOnList(sanitizeSessionListPayload(result.payload));
837
+ if (!overlaid.ok) {
838
+ return metadataUnavailableResponse(res, overlaid.error);
839
+ }
840
+ res.json(overlaid.payload);
654
841
  } catch (error) {
655
842
  if (isAbortError(error)) {
656
843
  return;
@@ -735,7 +922,7 @@ export const registerOpenCodeProxy = (app, deps) => {
735
922
  const READINESS_HOLD_POLL_MS = 75;
736
923
  const READINESS_HOLD_MAX_MS = 6000;
737
924
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
738
- const isStillWaiting = (runtimeState) => {
925
+ const isProcessWarmingUp = (runtimeState) => {
739
926
  const waitElapsed = runtimeState.openCodeNotReadySince === 0 ? 0 : Date.now() - runtimeState.openCodeNotReadySince;
740
927
  return (
741
928
  (!runtimeState.isOpenCodeReady && (runtimeState.openCodeNotReadySince === 0 || waitElapsed < OPEN_CODE_READY_GRACE_MS)) ||
@@ -744,6 +931,18 @@ export const registerOpenCodeProxy = (app, deps) => {
744
931
  );
745
932
  };
746
933
 
934
+ // Process warmup keeps the existing grace window. V1 migration is separate:
935
+ // admitTranscript=false holds with no grace until the gate admits or the
936
+ // client cancels. After grace, a still-running migration must not next()
937
+ // an empty session list through as an authoritative success.
938
+ const isMigrationBlockingTranscript = (runtimeState) => (
939
+ runtimeState?.v1Migration != null && runtimeState.v1Migration.admitTranscript !== true
940
+ );
941
+
942
+ const shouldHoldProxiedRequest = (runtimeState) => (
943
+ isProcessWarmingUp(runtimeState) || isMigrationBlockingTranscript(runtimeState)
944
+ );
945
+
747
946
  app.use('/api', async (req, res, next) => {
748
947
  if (
749
948
  req.path.startsWith('/themes/custom') ||
@@ -753,23 +952,62 @@ export const registerOpenCodeProxy = (app, deps) => {
753
952
  req.path.startsWith('/config/settings') ||
754
953
  req.path.startsWith('/config/skills') ||
755
954
  req.path === '/config/reload' ||
756
- req.path === '/health'
955
+ req.path === '/health' ||
956
+ req.path === '/opencode/health' ||
957
+ req.path === '/opencode/version' ||
958
+ req.path === '/opencode/contract' ||
959
+ req.path === '/opencode/upgrade-status' ||
960
+ req.path === '/opencode/upgrade'
757
961
  ) {
758
962
  return next();
759
963
  }
760
964
 
761
- if (!isStillWaiting(getRuntime())) {
965
+ // Ticket 11: limit execution when the running serve fails contract admission.
966
+ // Diagnostics, reads, and stop-task stay available; health success alone is not full semantics.
967
+ // Require explicit executionAllowed === true for the *current* instance (null/pending blocks).
968
+ // Gate again after readiness hold so initial-unknown → ready-incompatible cannot slip through.
969
+ const pathForGate = req.originalUrl || req.url || req.path;
970
+ const gateOptions = typeof getRuntimeContractGateOptions === 'function'
971
+ ? getRuntimeContractGateOptions()
972
+ : {};
973
+ const blockedByContract = () => {
974
+ const contract = getRuntime()?.runtimeContract;
975
+ if (shouldBlockRuntimeContractExecution(req.method, pathForGate, contract, gateOptions)) {
976
+ return runtimeContractExecutionBlockedBody(contract);
977
+ }
978
+ return null;
979
+ };
980
+
981
+ const earlyBlock = blockedByContract();
982
+ if (earlyBlock) {
983
+ return res.status(409).json(earlyBlock);
984
+ }
985
+
986
+ if (!shouldHoldProxiedRequest(getRuntime())) {
987
+ const lateBlock = blockedByContract();
988
+ if (lateBlock) {
989
+ return res.status(409).json(lateBlock);
990
+ }
762
991
  return next();
763
992
  }
764
993
 
765
- const deadline = Date.now() + Math.min(OPEN_CODE_READY_GRACE_MS, READINESS_HOLD_MAX_MS);
766
- while (Date.now() < deadline) {
994
+ const processDeadline = Date.now() + Math.min(OPEN_CODE_READY_GRACE_MS, READINESS_HOLD_MAX_MS);
995
+ while (true) {
767
996
  // Client gave up (closed/aborted) — stop holding.
768
997
  if (res.writableEnded || req.aborted) return;
769
- await sleep(READINESS_HOLD_POLL_MS);
770
- if (!isStillWaiting(getRuntime())) {
998
+ const runtimeState = getRuntime();
999
+ if (!shouldHoldProxiedRequest(runtimeState)) {
1000
+ // Re-evaluate after wait: startup may have published incompatible/pending.
1001
+ const afterHoldBlock = blockedByContract();
1002
+ if (afterHoldBlock) {
1003
+ return res.status(409).json(afterHoldBlock);
1004
+ }
771
1005
  return next();
772
1006
  }
1007
+ if (!isMigrationBlockingTranscript(runtimeState) && Date.now() >= processDeadline) {
1008
+ break;
1009
+ }
1010
+ await sleep(READINESS_HOLD_POLL_MS);
773
1011
  }
774
1012
 
775
1013
  if (!res.headersSent) {
@@ -852,7 +1090,11 @@ export const registerOpenCodeProxy = (app, deps) => {
852
1090
  return bTime - aTime;
853
1091
  });
854
1092
  console.log(`[SessionMerge] ${globalSessions?.length || 0} global + ${extraSessions.length} extra = ${merged.length} total`);
855
- return res.json(sanitizeSessionListPayload(merged));
1093
+ const overlaid = await overlayOwnedStateOnList(sanitizeSessionListPayload(merged));
1094
+ if (!overlaid.ok) {
1095
+ return metadataUnavailableResponse(res, overlaid.error);
1096
+ }
1097
+ return res.json(overlaid.payload);
856
1098
  } catch (error) {
857
1099
  console.log(`[SessionMerge] Error: ${error.message}`);
858
1100
  return res.status(500).json({ error: error.message || 'Failed to merge Windows sessions' });
@@ -864,6 +1106,102 @@ export const registerOpenCodeProxy = (app, deps) => {
864
1106
  return forwardSanitizedSessionListRequest(req, res, next, 'session.list');
865
1107
  });
866
1108
 
1109
+ // One session: the same metadata overlay as the list, so a detail read agrees
1110
+ // with the list it came from. Everything else about the record is forwarded
1111
+ // untouched. Without a store, fall through to the generic proxy.
1112
+ app.get('/api/session/:sessionID', async (req, res, next) => {
1113
+ if (!storeConfigured) return next();
1114
+ // Nested routes (message, children, …) are registered separately; Express
1115
+ // still matches this pattern for bare session ids only when no further
1116
+ // segment is present — but be defensive if a child path slips through.
1117
+ if (req.path.includes('/message') || String(req.params.sessionID || '').includes('/')) {
1118
+ return next();
1119
+ }
1120
+ try {
1121
+ const upstreamPath = await getRequestUpstreamPath(req);
1122
+ const result = await fetchSessionListPayload(upstreamPath, { req });
1123
+
1124
+ const record = result.isJson && !result.parseError ? result.payload : null;
1125
+ const session = record && typeof record === 'object' && !Array.isArray(record)
1126
+ ? (record.data && typeof record.data === 'object' && !Array.isArray(record.data) ? record.data : record)
1127
+ : null;
1128
+ if (!session || typeof session.id !== 'string') {
1129
+ res.status(result.upstream.status);
1130
+ applyForwardProxyResponseHeaders(result.upstream.headers, res);
1131
+ res.setHeader('content-type', result.contentType);
1132
+ res.end(result.bodyText);
1133
+ return;
1134
+ }
1135
+
1136
+ const meta = await readStoredSessionMetadata();
1137
+ if (meta.status === 'unavailable') {
1138
+ return metadataUnavailableResponse(res, meta.error);
1139
+ }
1140
+
1141
+ res.status(result.upstream.status);
1142
+ applyForwardProxyResponseHeaders(result.upstream.headers, res);
1143
+ res.setHeader('content-type', result.contentType);
1144
+
1145
+ if (meta.status === 'unconfigured') {
1146
+ res.end(result.bodyText);
1147
+ return;
1148
+ }
1149
+
1150
+ const overlaid = overlaySession(session, meta.stored);
1151
+ res.json(record.data && typeof record.data === 'object' && !Array.isArray(record.data)
1152
+ ? { ...record, data: overlaid }
1153
+ : overlaid);
1154
+ } catch (error) {
1155
+ if (isAbortError(error)) return;
1156
+ console.error('[proxy] OpenCode session.get proxy error:', error?.message ?? error);
1157
+ if (!res.headersSent) {
1158
+ res.status(503).json({ error: 'OpenCode service unavailable' });
1159
+ return;
1160
+ }
1161
+ next(error);
1162
+ }
1163
+ });
1164
+
1165
+ // Successful upstream delete → idempotent Host metadata cleanup. Failures are
1166
+ // observable (logged) and compensated by session.deleted event ingest.
1167
+ app.delete('/api/session/:sessionID', async (req, res, next) => {
1168
+ if (typeof onSessionDeleted !== 'function') return next();
1169
+ const sessionID = typeof req.params?.sessionID === 'string' ? req.params.sessionID.trim() : '';
1170
+ if (!sessionID || sessionID.includes('/')) return next();
1171
+ try {
1172
+ const upstreamPath = await getRequestUpstreamPath(req);
1173
+ const result = await fetchSessionListPayload(upstreamPath, {
1174
+ req,
1175
+ method: 'DELETE',
1176
+ });
1177
+ res.status(result.upstream.status);
1178
+ applyForwardProxyResponseHeaders(result.upstream.headers, res);
1179
+ res.setHeader('content-type', result.contentType || 'application/json');
1180
+ if (result.upstream.status >= 200 && result.upstream.status < 300) {
1181
+ try {
1182
+ const cleanup = onSessionDeleted(sessionID);
1183
+ if (cleanup && typeof cleanup.catch === 'function') {
1184
+ cleanup.catch((error) => {
1185
+ console.warn('[proxy] session metadata cleanup after delete failed (retry via event):', error?.message ?? error);
1186
+ });
1187
+ }
1188
+ } catch (error) {
1189
+ console.warn('[proxy] session metadata cleanup after delete failed (retry via event):', error?.message ?? error);
1190
+ }
1191
+ }
1192
+ // Upstream failure keeps Host metadata (do not cleanup).
1193
+ res.end(result.bodyText);
1194
+ } catch (error) {
1195
+ if (isAbortError(error)) return;
1196
+ console.error('[proxy] OpenCode session.delete proxy error:', error?.message ?? error);
1197
+ if (!res.headersSent) {
1198
+ res.status(503).json({ error: 'OpenCode service unavailable' });
1199
+ return;
1200
+ }
1201
+ next(error);
1202
+ }
1203
+ });
1204
+
867
1205
  // Official session.messages list (not exact message — that is owned by
868
1206
  // session-turn-pages and registered before this proxy).
869
1207
  app.get('/api/session/:sessionID/message', (req, res, next) => {
@@ -881,7 +1219,9 @@ export const registerOpenCodeProxy = (app, deps) => {
881
1219
  const apiProxy = createProxyMiddleware({
882
1220
  target: resolveProxyTarget(),
883
1221
  changeOrigin: true,
884
- pathRewrite: { '^/api': '' },
1222
+ // Express already stripped the `/api` mount. Restore it so v2 receives
1223
+ // `/api/agent` rather than `/agent` (which serves the Web UI HTML).
1224
+ pathRewrite: (pathValue) => toUpstreamOpenCodeApiPath(pathValue),
885
1225
  timeout: PROXY_REQUEST_TIMEOUT_MS,
886
1226
  proxyTimeout: PROXY_REQUEST_TIMEOUT_MS,
887
1227
  // Dynamic target — port can change after restart