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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (311) hide show
  1. package/README.md +13 -7
  2. package/bin/cli-opencode.test.js +208 -0
  3. package/bin/cli.js +62 -9
  4. package/bin/lib/cli-startup.js +105 -2
  5. package/bin/lib/cli-startup.test.js +179 -0
  6. package/dist/assets/{AgentAvatar-CRaBJXKs.js → AgentAvatar-B_JQoreg.js} +1 -1
  7. package/dist/assets/AssistantView-ClQAfGOT.js +3 -0
  8. package/dist/assets/{DiagramView-D1LGK_s2.js → DiagramView-BNnda8qO.js} +1 -1
  9. package/dist/assets/{FadeInOnReveal-B0kJo9Ii.js → FadeInOnReveal-VYQTSAkr.js} +1 -1
  10. package/dist/assets/{Icon-BgdfPhNs.js → Icon-BiEQEcih.js} +1 -1
  11. package/dist/assets/MarkdownRenderer-CJHSsWin.js +2 -0
  12. package/dist/assets/{MarkdownRendererImpl-DfoCJ1Mq.js → MarkdownRendererImpl-DwrHFGUp.js} +3 -3
  13. package/dist/assets/{MarkstreamRendererImpl-D2dsW5Lg.js → MarkstreamRendererImpl-CSnbQMu3.js} +1 -1
  14. package/dist/assets/MobileDetailNavigation-BXr6AGLT.js +1 -0
  15. package/dist/assets/{MobileShareBridge-2Yjn6wXg.js → MobileShareBridge-D7OE9OP_.js} +2 -2
  16. package/dist/assets/MobileSurface-D2jUkX9l.js +2 -0
  17. package/dist/assets/{MultiRunWindow-tTbMFowS.js → MultiRunWindow-DF3iIFCd.js} +1 -1
  18. package/dist/assets/{OnboardingScreen-CD2KtlF3.js → OnboardingScreen-BjHmlwlw.js} +1 -1
  19. package/dist/assets/OverlayScrollbar-DLtWO_Gp.js +2 -0
  20. package/dist/assets/SettingsView-BRxELjow.js +25 -0
  21. package/dist/assets/{SettingsWindow-zIa76s4t.js → SettingsWindow-BI5swspb.js} +1 -1
  22. package/dist/assets/{TerminalView-CDdzF4yi.js → TerminalView-BU3x8-fr.js} +1 -1
  23. package/dist/assets/{ToolOutputDialog-DNqA4uBl.js → ToolOutputDialog-BEWhBcRd.js} +2 -2
  24. package/dist/assets/appThemeRegistry-4MR0F7Jz.js +1 -0
  25. package/dist/assets/{button-CI7Q-Zw_.js → button-DKuz3L8l.js} +1 -1
  26. package/dist/assets/collapsible-DXbDynN4.js +3 -0
  27. package/dist/assets/context-menu-CqolsLCm.js +1 -0
  28. package/dist/assets/{desktopHosts-B1ixEylr.js → desktopHosts-CoDkOnyS.js} +1 -1
  29. package/dist/assets/{desktopRelayRestore-4JmFUw8r.js → desktopRelayRestore-DDlVKyOE.js} +1 -1
  30. package/dist/assets/{diagram-DdLO8HZ9.js → diagram-D7T8HQgj.js} +1 -1
  31. package/dist/assets/{dialog-B6pnIYmp.js → dialog-DQYl3W_o.js} +1 -1
  32. package/dist/assets/es-CN3E_zXj.js +15 -0
  33. package/dist/assets/{fileMentionTouchSelection-DNeiqns-.js → fileMentionTouchSelection-BZ6ovBYH.js} +1 -1
  34. package/dist/assets/{fileQueries-DQc8EJUf.js → fileQueries-JrpcsOCs.js} +1 -1
  35. package/dist/assets/fr-9zZvQZD2.js +13 -0
  36. package/dist/assets/gitApi-CM42qUed.js +10 -0
  37. package/dist/assets/{imageSource-BlAJJUEI.js → imageSource-CIl0FL10.js} +1 -1
  38. package/dist/assets/{input-D-__fwRi.js → input-CzQy8UhV.js} +1 -1
  39. package/dist/assets/insertionBoundaries-CtkZ9720.js +1 -0
  40. package/dist/assets/ja-Dnjk_82N.js +15 -0
  41. package/dist/assets/ko-BxCEE99C.js +15 -0
  42. package/dist/assets/main-BILgBI3B.js +169 -0
  43. package/dist/assets/main-BeveiW6F.js +2 -0
  44. package/dist/assets/markdown-worker-bS5OgGO3.js +2 -0
  45. package/dist/assets/{markdownHeightCache-BhxqJOvf.js → markdownHeightCache-Cj1I5NDw.js} +1 -1
  46. package/dist/assets/{markdownTheme-DOfGg-A7.js → markdownTheme-CKac3wKC.js} +1 -1
  47. package/dist/assets/mcpQueries-D5mQymgM.js +1 -0
  48. package/dist/assets/miniChat-BgrzM3u7.js +2 -0
  49. package/dist/assets/mobile-DtYoQE8C.js +2 -0
  50. package/dist/assets/multirun-jy8dEI96.js +4 -0
  51. package/dist/assets/{persistence-Ce3AnpEr.js → persistence-C9LQpiOS.js} +2 -2
  52. package/dist/assets/pl-CNNsWnPM.js +15 -0
  53. package/dist/assets/platform-BQghHRfp.js +6 -0
  54. package/dist/assets/pt-BR-BZB2DJMp.js +15 -0
  55. package/dist/assets/renderElectronMiniChatApp-BGrrmeME.js +2 -0
  56. package/dist/assets/renderMobileApp-u3c4GtFC.js +28 -0
  57. package/dist/assets/{runtimeConfig-CMkwoQ_X.js → runtimeConfig-BIGBRGQI.js} +1 -1
  58. package/dist/assets/{runtimeEndpointReset-ChOxt-J7.js → runtimeEndpointReset-D2savkuw.js} +12 -12
  59. package/dist/assets/{sessionChangeSummary-NnxcqCTI.js → sessionChangeSummary-eIbpSfrO.js} +1 -1
  60. package/dist/assets/{sessionLookup-BrU3rFHJ.js → sessionLookup-myHe5ltu.js} +1 -1
  61. package/dist/assets/{sortable-tabs-strip-Fnfhf3K9.js → sortable-tabs-strip-BQtmHx9r.js} +1 -1
  62. package/dist/assets/src-CLN7Z-zx.css +2 -0
  63. package/dist/assets/store-DLCiZaA6.js +15 -0
  64. package/dist/assets/{terminalApi-xjqssKke.js → terminalApi-BxcdiduX.js} +1 -1
  65. package/dist/assets/{textarea-bHYxZtfH.js → textarea-CakKvqFw.js} +1 -1
  66. package/dist/assets/{tooltip-0T5jv0cu.js → tooltip-Cx5gTbdY.js} +1 -1
  67. package/dist/assets/uk-rwQrxeYM.js +15 -0
  68. package/dist/assets/{url-_z7evzdK.js → url-CDHnjlsm.js} +1 -1
  69. package/dist/assets/{useAppFontEffects-C02YFyAv.js → useAppFontEffects-CNeo0aZs.js} +59 -45
  70. package/dist/assets/{useAssistantUIStore-CMutVh9B.js → useAssistantUIStore-asNG6K7n.js} +1 -1
  71. package/dist/assets/useDesktopWindowControlsLayout-DBJ56qIo.js +1 -0
  72. package/dist/assets/useEffectiveDirectory-B4LvR8Gt.js +1 -0
  73. package/dist/assets/{useFontPreferences-B1JYyzpK.js → useFontPreferences-CUnQ4TVm.js} +1 -1
  74. package/dist/assets/useMobileNavigationStore-ZHJPxlHt.js +414 -0
  75. package/dist/assets/{useRuntimeAPIs-DSLXJybW.js → useRuntimeAPIs-DwLtO0c_.js} +1 -1
  76. package/dist/assets/useSessionAutoCleanup-BIVmrnYU.js +2 -0
  77. package/dist/assets/{useThemeSystem-DMzWVJaW.js → useThemeSystem-BlNUWH47.js} +1 -1
  78. package/dist/assets/useUIStore-CQYKJnrg.js +1 -0
  79. package/dist/assets/{useWorkerHighlightedLines-C9z0ML1z.js → useWorkerHighlightedLines-CJZbJDW3.js} +2 -2
  80. package/dist/assets/{vendor-base-ui-CnHfUmDN.js → vendor-base-ui-CDjyofeD.js} +1 -1
  81. package/dist/assets/{vendor-cmdk-CeAGfrLo.js → vendor-cmdk-BFH0UpXX.js} +1 -1
  82. package/dist/assets/{vendor-dnd-kit-accessibility-BY5LhhUB.js → vendor-dnd-kit-accessibility-BrR3AKvM.js} +1 -1
  83. package/dist/assets/{vendor-dnd-kit-core-CvWxUPdb.js → vendor-dnd-kit-core-CNfdvk6H.js} +1 -1
  84. package/dist/assets/{vendor-dnd-kit-sortable-hHdP9lLb.js → vendor-dnd-kit-sortable-DQUaTeGj.js} +1 -1
  85. package/dist/assets/{vendor-framer-motion-DFy_BSVd.js → vendor-framer-motion-4dogsUk3.js} +1 -1
  86. package/dist/assets/{vendor-legendapp-list-WE32lwNk.js → vendor-legendapp-list-BU7MdPZR.js} +1 -1
  87. package/dist/assets/{vendor-markstream-react-DX55XLGO.js → vendor-markstream-react-qHC2FykU.js} +3 -3
  88. package/dist/assets/vendor-opencode-client-BOe2qpQb.js +12 -0
  89. package/dist/assets/{vendor-pierre-diffs-DX7XTPG3.js → vendor-pierre-diffs-CUfqC003.js} +1 -1
  90. package/dist/assets/{vendor-react-DLPElpoQ.js → vendor-react-C94A0AHl.js} +1 -1
  91. package/dist/assets/{vendor-react-drawio-ECtEwvbc.js → vendor-react-drawio-FVlWqKVE.js} +1 -1
  92. package/dist/assets/{vendor-reactuses-core-BnW6GxJt.js → vendor-reactuses-core-C2A3EjUi.js} +1 -1
  93. package/dist/assets/{vendor-sonner-DVzioF3d.js → vendor-sonner-DNqHmUQs.js} +1 -1
  94. package/dist/assets/{vendor-stream-diffs-DWy3xSmp.js → vendor-stream-diffs-CwbKsmlv.js} +2 -2
  95. package/dist/assets/{vendor-tanstack-react-query-D7pys_c9.js → vendor-tanstack-react-query-D0iFK57C.js} +1 -1
  96. package/dist/assets/{vendor-tanstack-react-virtual-tlDgH6PG.js → vendor-tanstack-react-virtual-B2kV6cnT.js} +1 -1
  97. package/dist/assets/{vendor-yaml-DIcAK3EC.js → vendor-yaml-C9KlJAlj.js} +37 -37
  98. package/dist/assets/{vendor-zustand-B4dlGEM2.js → vendor-zustand-BqLKf6ZY.js} +1 -1
  99. package/dist/assets/zh-CN-BvmBDnkD.js +15 -0
  100. package/dist/assets/zh-TW-9wcHTX0y.js +15 -0
  101. package/dist/index.html +13 -13
  102. package/dist/mini-chat.html +13 -13
  103. package/dist/mobile.html +13 -13
  104. package/package.json +2 -2
  105. package/server/index.js +392 -20
  106. package/server/lib/assistants/DOCUMENTATION.md +26 -21
  107. package/server/lib/assistants/assign.js +2 -1
  108. package/server/lib/assistants/contact-store.js +37 -19
  109. package/server/lib/assistants/contracts.js +11 -1
  110. package/server/lib/assistants/projection-history.test.js +454 -0
  111. package/server/lib/assistants/routes.js +11 -5
  112. package/server/lib/assistants/service.js +850 -294
  113. package/server/lib/assistants/service.test.js +588 -274
  114. package/server/lib/conversations/DOCUMENTATION.md +5 -5
  115. package/server/lib/conversations/service.js +87 -57
  116. package/server/lib/conversations/service.test.js +132 -100
  117. package/server/lib/conversations/validation.js +11 -1
  118. package/server/lib/event-stream/directory-ws-bridge.js +13 -2
  119. package/server/lib/event-stream/global-hub.js +51 -5
  120. package/server/lib/event-stream/runtime.js +57 -20
  121. package/server/lib/event-stream/runtime.test.js +112 -0
  122. package/server/lib/llm/DOCUMENTATION.md +95 -84
  123. package/server/lib/llm/catalog.js +111 -45
  124. package/server/lib/llm/catalog.test.js +121 -59
  125. package/server/lib/llm/completions.js +41 -12
  126. package/server/lib/llm/completions.test.js +45 -4
  127. package/server/lib/llm/generate.js +516 -453
  128. package/server/lib/llm/generate.test.js +705 -484
  129. package/server/lib/llm/routes.js +8 -4
  130. package/server/lib/message-queue/DOCUMENTATION.md +5 -5
  131. package/server/lib/message-queue/opencode-adapter.js +191 -104
  132. package/server/lib/message-queue/opencode-adapter.test.js +328 -58
  133. package/server/lib/message-queue/runtime.js +15 -2
  134. package/server/lib/message-queue/runtime.test.js +6 -6
  135. package/server/lib/message-queue/worker.js +7 -1
  136. package/server/lib/notifications/runtime.js +5 -0
  137. package/server/lib/opencode/DOCUMENTATION.md +103 -16
  138. package/server/lib/opencode/agent-document.d.ts +28 -0
  139. package/server/lib/opencode/agent-document.js +280 -0
  140. package/server/lib/opencode/agent-document.test.js +137 -0
  141. package/server/lib/opencode/agents.js +96 -0
  142. package/server/lib/opencode/auth-state-runtime.js +5 -0
  143. package/server/lib/opencode/bootstrap-runtime.js +2 -0
  144. package/server/lib/opencode/config-entity-routes.js +111 -102
  145. package/server/lib/opencode/config-entity-routes.test.js +123 -10
  146. package/server/lib/opencode/config-hot-reload.integration.test.js +105 -0
  147. package/server/lib/opencode/ensure-cli.js +238 -0
  148. package/server/lib/opencode/ensure-cli.test.js +119 -0
  149. package/server/lib/opencode/env-runtime.js +208 -111
  150. package/server/lib/opencode/env-runtime.test.js +211 -14
  151. package/server/lib/opencode/feature-routes-runtime.js +80 -5
  152. package/server/lib/opencode/feature-routes-runtime.test.js +18 -0
  153. package/server/lib/opencode/http-server-connections.js +98 -0
  154. package/server/lib/opencode/http-server-connections.test.js +321 -0
  155. package/server/lib/opencode/lifecycle.js +539 -95
  156. package/server/lib/opencode/lifecycle.test.js +698 -6
  157. package/server/lib/opencode/managed-capabilities-runtime.js +15 -2
  158. package/server/lib/opencode/managed-capabilities-runtime.test.js +1 -0
  159. package/server/lib/opencode/network-runtime.js +28 -16
  160. package/server/lib/opencode/network-runtime.test.js +68 -4
  161. package/server/lib/opencode/openchamber-routes.js +7 -1
  162. package/server/lib/opencode/opencode2-pin.js +139 -0
  163. package/server/lib/opencode/opencode2-pin.test.js +88 -0
  164. package/server/lib/opencode/opencode2-sidecar.e2e.test.js +326 -0
  165. package/server/lib/opencode/owned-runtime-upgrade.js +431 -0
  166. package/server/lib/opencode/owned-runtime-upgrade.test.js +160 -0
  167. package/server/lib/opencode/plugin-compatibility.js +15 -0
  168. package/server/lib/opencode/plugin-compatibility.test.js +21 -0
  169. package/server/lib/opencode/plugin-routes.js +3 -18
  170. package/server/lib/opencode/plugin-routes.test.js +9 -9
  171. package/server/lib/opencode/plugins.js +4 -3
  172. package/server/lib/opencode/plugins.test.js +12 -0
  173. package/server/lib/opencode/provider-catalog.js +58 -12
  174. package/server/lib/opencode/provider-catalog.test.js +48 -0
  175. package/server/lib/opencode/proxy.js +368 -28
  176. package/server/lib/opencode/proxy.test.js +338 -0
  177. package/server/lib/opencode/routes.js +303 -76
  178. package/server/lib/opencode/routes.test.js +315 -50
  179. package/server/lib/opencode/runtime-contract.js +401 -0
  180. package/server/lib/opencode/runtime-contract.test.js +214 -0
  181. package/server/lib/opencode/server-opencode-fetch.js +242 -0
  182. package/server/lib/opencode/server-opencode-fetch.test.js +130 -0
  183. package/server/lib/opencode/server-utils-runtime.js +35 -0
  184. package/server/lib/opencode/shared-service.integration.test.js +66 -0
  185. package/server/lib/opencode/shared-service.js +147 -0
  186. package/server/lib/opencode/shared-service.test.js +93 -0
  187. package/server/lib/opencode/shared.js +3 -3
  188. package/server/lib/opencode/shutdown-runtime.js +15 -6
  189. package/server/lib/opencode/shutdown-runtime.test.js +49 -1
  190. package/server/lib/opencode/skill-routes.js +26 -40
  191. package/server/lib/opencode/skill-routes.test.js +71 -7
  192. package/server/lib/opencode/skills.test.js +15 -0
  193. package/server/lib/opencode/startup-pipeline-runtime.js +4 -0
  194. package/server/lib/opencode/static-routes-runtime.js +3 -1
  195. package/server/lib/opencode/static-routes-runtime.test.js +33 -0
  196. package/server/lib/opencode/v1-migration-gate.js +157 -0
  197. package/server/lib/opencode/v1-migration-gate.test.js +111 -0
  198. package/server/lib/opencode/v2-client.js +60 -0
  199. package/server/lib/opencode/v2-plugin-host-shim/index.mjs +155 -0
  200. package/server/lib/opencode/v2-plugin-host-shim/package.json +4 -0
  201. package/server/lib/opencode/watcher.js +2 -1
  202. package/server/lib/opencode/watcher.test.js +1 -1
  203. package/server/lib/permission-auto-accept/DOCUMENTATION.md +5 -5
  204. package/server/lib/permission-auto-accept/runtime.js +14 -7
  205. package/server/lib/permission-auto-accept/runtime.test.js +27 -12
  206. package/server/lib/relay/reasoning-projection.e2e.test.ts +22 -7
  207. package/server/lib/scheduled-tasks/DOCUMENTATION.md +79 -39
  208. package/server/lib/scheduled-tasks/managed-tool-route.js +31 -1
  209. package/server/lib/scheduled-tasks/managed-tool-route.test.js +23 -0
  210. package/server/lib/scheduled-tasks/routes.js +27 -0
  211. package/server/lib/scheduled-tasks/routes.test.js +94 -0
  212. package/server/lib/scheduled-tasks/runtime.js +381 -310
  213. package/server/lib/scheduled-tasks/runtime.test.js +600 -309
  214. package/server/lib/session-goal/DOCUMENTATION.md +173 -22
  215. package/server/lib/session-goal/capability.js +57 -0
  216. package/server/lib/session-goal/capability.test.js +33 -0
  217. package/server/lib/session-goal/routes.js +240 -2
  218. package/server/lib/session-goal/routes.test.js +211 -0
  219. package/server/lib/session-goal/runtime.js +1617 -113
  220. package/server/lib/session-goal/runtime.test.js +1500 -70
  221. package/server/lib/session-index/DOCUMENTATION.md +18 -15
  222. package/server/lib/session-index/event-ingest.js +89 -16
  223. package/server/lib/session-index/event-ingest.test.js +73 -0
  224. package/server/lib/session-index/host-authority-snapshot.js +92 -0
  225. package/server/lib/session-index/routes.host-archive.test.js +201 -0
  226. package/server/lib/session-index/routes.js +63 -10
  227. package/server/lib/session-index/routes.test.js +8 -8
  228. package/server/lib/session-index/service.js +7 -1
  229. package/server/lib/session-index/sync-runtime.js +171 -41
  230. package/server/lib/session-index/sync-runtime.test.js +164 -9
  231. package/server/lib/session-metadata/DOCUMENTATION.md +86 -0
  232. package/server/lib/session-metadata/routes.js +193 -0
  233. package/server/lib/session-metadata/routes.test.js +156 -0
  234. package/server/lib/session-metadata/session-archive.d.ts +73 -0
  235. package/server/lib/session-metadata/session-archive.js +654 -0
  236. package/server/lib/session-metadata/session-archive.test.js +359 -0
  237. package/server/lib/session-metadata/session-metadata-store.d.ts +36 -0
  238. package/server/lib/session-metadata/session-metadata-store.js +375 -0
  239. package/server/lib/session-metadata/session-metadata-store.test.js +379 -0
  240. package/server/lib/session-metadata/session-projection.d.ts +41 -0
  241. package/server/lib/session-metadata/session-projection.js +255 -0
  242. package/server/lib/session-metadata/session-projection.test.js +64 -0
  243. package/server/lib/session-metadata/system-session.js +55 -0
  244. package/server/lib/session-metadata/system-session.test.js +55 -0
  245. package/server/lib/session-title/DOCUMENTATION.md +40 -7
  246. package/server/lib/session-title/native-runtime.test.js +167 -0
  247. package/server/lib/session-title/runtime.js +67 -42
  248. package/server/lib/session-title/session-access.js +46 -0
  249. package/server/lib/session-title/session-access.test.js +91 -0
  250. package/server/lib/session-turn-pages/DOCUMENTATION.md +12 -11
  251. package/server/lib/session-turn-pages/reconcile.service.js +1 -1
  252. package/server/lib/session-turn-pages/reconcile.service.test.js +16 -0
  253. package/server/lib/session-turn-pages/routes.js +123 -75
  254. package/server/lib/session-turn-pages/routes.test.js +566 -0
  255. package/server/lib/session-turn-pages/service.js +6 -2
  256. package/server/lib/session-turn-pages/service.test.js +34 -0
  257. package/server/lib/session-turn-pages/session-message-projection.d.ts +13 -0
  258. package/server/lib/session-turn-pages/session-message-projection.js +281 -0
  259. package/server/lib/small-model/DOCUMENTATION.md +101 -146
  260. package/server/lib/small-model/catalog.js +84 -54
  261. package/server/lib/small-model/catalog.test.js +111 -9
  262. package/server/lib/small-model/custom-api.js +68 -0
  263. package/server/lib/small-model/custom-api.test.js +64 -0
  264. package/server/lib/small-model/index.js +125 -241
  265. package/server/lib/small-model/resolve.js +35 -120
  266. package/server/lib/small-model/resolve.test.js +20 -110
  267. package/server/lib/small-model/routes.js +1 -3
  268. package/server/lib/small-model/summary-settings.test.js +157 -253
  269. package/server/opencode-proxy.test.js +63 -12
  270. package/dist/assets/AssistantView-BUveAgc4.js +0 -3
  271. package/dist/assets/MarkdownRenderer-BbrbDH6-.js +0 -2
  272. package/dist/assets/MobileDetailNavigation-B0aqKXd7.js +0 -1
  273. package/dist/assets/MobileSurface-DW7WfPsy.js +0 -2
  274. package/dist/assets/OverlayScrollbar-DrA0Vsg5.js +0 -2
  275. package/dist/assets/SettingsView-PkXIavr7.js +0 -25
  276. package/dist/assets/appThemeRegistry-D5Yxg4YF.js +0 -1
  277. package/dist/assets/collapsible-D5Phaykh.js +0 -1
  278. package/dist/assets/context-menu-CubSDW2g.js +0 -1
  279. package/dist/assets/es-DA3MqOo0.js +0 -15
  280. package/dist/assets/fr-Dbz0CdIA.js +0 -13
  281. package/dist/assets/gitApi-DXyC4MiV.js +0 -7
  282. package/dist/assets/insertionBoundaries-KlOVrHHt.js +0 -1
  283. package/dist/assets/ja-DumanVQA.js +0 -15
  284. package/dist/assets/ko-C0IkiUjQ.js +0 -15
  285. package/dist/assets/main-BPS5ylib.js +0 -2
  286. package/dist/assets/main-Cpd9VWYs.js +0 -169
  287. package/dist/assets/markdown-worker-CptoICmq.js +0 -2
  288. package/dist/assets/miniChat-B8_KLWiY.js +0 -2
  289. package/dist/assets/mobile-CtktKrXn.js +0 -2
  290. package/dist/assets/multirun-CYfP9Cfm.js +0 -4
  291. package/dist/assets/pl-B9No6MQp.js +0 -15
  292. package/dist/assets/platform-0w_Q9_ji.js +0 -2
  293. package/dist/assets/projectMeta-S1hJ7oBp.js +0 -3
  294. package/dist/assets/pt-BR-DyQ0WTDh.js +0 -15
  295. package/dist/assets/renderElectronMiniChatApp-iK14LWm8.js +0 -2
  296. package/dist/assets/renderMobileApp-DORLPWnb.js +0 -28
  297. package/dist/assets/src-rS3zCcsC.css +0 -2
  298. package/dist/assets/store-5B_LDnLM.js +0 -15
  299. package/dist/assets/uk-C_1KkqyP.js +0 -15
  300. package/dist/assets/useDesktopWindowControlsLayout-Dnr-jX0C.js +0 -1
  301. package/dist/assets/useEffectiveDirectory-CZLLyarc.js +0 -1
  302. package/dist/assets/useMobileNavigationStore-CoCiqNgm.js +0 -413
  303. package/dist/assets/useSessionAutoCleanup-xQyETQkG.js +0 -2
  304. package/dist/assets/useUIStore-Bxv3f23A.js +0 -1
  305. package/dist/assets/vendor-opencode-sdk-C07HQypa.js +0 -7
  306. package/dist/assets/zh-CN-uWILgBdt.js +0 -15
  307. package/dist/assets/zh-TW-MYfdvhbD.js +0 -15
  308. package/server/lib/small-model/call.js +0 -427
  309. package/server/lib/small-model/call.test.js +0 -382
  310. package/server/lib/small-model/opencode-session.js +0 -339
  311. package/server/lib/small-model/opencode-session.test.js +0 -177
@@ -1,6 +1,6 @@
1
1
  import { createChatCompletion, LlmError } from './completions.js';
2
2
  import { loadConnectedCatalog } from './catalog.js';
3
- import { createOpencodeClient } from '@opencode-ai/sdk/v2';
3
+ import { OpenCode } from '@opencode/client';
4
4
  import { ensureLlmTempDirectory } from './temp-directory.js';
5
5
 
6
6
  const fail = (res, error) => {
@@ -11,14 +11,16 @@ const fail = (res, error) => {
11
11
  ? 400
12
12
  : code === 'validation_error'
13
13
  ? 400
14
- : code === 'upstream_error'
14
+ : code === 'llm_attachment_generation_unavailable'
15
15
  ? 502
16
- : 500;
16
+ : code === 'upstream_error'
17
+ ? 502
18
+ : 500;
17
19
  res.status(status).json({ ok: false, error: code, message: error?.message || code });
18
20
  };
19
21
 
20
22
  export const registerLlmRoutes = (app, dependencies) => {
21
- const client = () => createOpencodeClient({
23
+ const client = () => OpenCode.make({
22
24
  baseUrl: dependencies.buildOpenCodeUrl('/', '').replace(/\/$/, ''),
23
25
  headers: dependencies.getOpenCodeAuthHeaders(),
24
26
  });
@@ -38,6 +40,8 @@ export const registerLlmRoutes = (app, dependencies) => {
38
40
  getOpenCodeAuthHeaders: dependencies.getOpenCodeAuthHeaders,
39
41
  clientFactory: client,
40
42
  ensureTempDirectory: ensureLlmTempDirectory,
43
+ persistSessionMetadata: dependencies.persistSessionMetadata,
44
+ onSystemSessionPersisted: dependencies.onSystemSessionPersisted,
41
45
  }))
42
46
  .then(({ completion }) => {
43
47
  res.status(200).json(completion);
@@ -44,12 +44,12 @@ Claim selection and update fence a scope with every `sending` row and every vali
44
44
 
45
45
  The worker starts paused. Start, timer, and wake calls share one launched run flight; their public wake promise settles with status, while rejected flights emit a worker diagnostic and later wakes may retry. Stop aborts controllers and waits for every active task to settle before the runtime closes SQLite. Each run captures one runtime key and adapter runtime snapshot, **starts dispatch probe tasks first** (up to concurrency), then claims and awaits due reconciliation work, and finally joins active probes. That ordering keeps a hung `findMessage` (bounded by the 10s reconcile timeout) from delaying a waiting manual intent's POST while same-scope `sending` and same-row leases still prevent a second concurrent POST; automatic candidates remain gated on `available` + `idle` + `settled`. It reserves a queued candidate with an expiring probe token, and promotes that token only after the bounded eligibility probe permits dispatch. Stateless Assistant items deliberately bypass OpenCode eligibility and automatic admission tokens because their synthetic scope is not an execution Session; same-Assistant scope claiming still serializes them, while different Assistant scopes use normal worker concurrency. A successful stateless Assistant `promptAsync` admission first persists its user message in Assistant SQLite and advances the Assistant binding revision, then completes and deletes the queue row. The resulting Assistant revision tip refreshes clients that were still displaying the previous disposable binding. An ambiguous stateless delivery becomes a client-mutable `failed` row instead of an indefinitely active reconciliation row, and terminal rows do not block later dispatchable rows in their scope. Upgrade recovery also settles legacy stateless reconciliation rows without querying their disposable Session: null-error rows represent the old accepted path and complete, while rows carrying a transport error become failed. Busy, unsettled, timed-out, and unavailable candidates remain queued without row-version, scope-revision, or revision-tip churn. Every lease mutation carries `{ queueItemID, leaseToken, fenceGeneration, runtimeKey }`. `claimNext` returns `{ item, leaseToken, leaseExpiresAt, fenceGeneration }`; the item includes scope ID, directory, and session ID. The service receives the durable queue runtime identity, while the adapter receives an independently injected upstream OpenCode runtime snapshot.
46
46
 
47
- OpenCode adapter (SDK 1.18.x) hardening:
47
+ OpenCode adapter (`@opencode/client` / v2) hardening:
48
48
 
49
- - `safeStatus` reads `result.response.status`, `result.error.status`, then `result.status`.
50
- - `send` treats only explicit 2xx (including empty 200/202/204) with no `error` as `ok`. `undefined`/malformed results are never success.
51
- - Definitive 4xx (except 408/429) is `failed`; 408/429/5xx and transport throws are `ambiguous`.
52
- - `findMessage` prefers `client.v2.session.message` when present; 404 means `found: false`. On 404/405/501 or unsupported surfaces it falls back to legacy `client.session.message` (if the SDK exposes it), then the existing bounded `session.messages` scan—without re-POSTing.
49
+ - `send` builds the official SessionPrompt body only (`id` / `text` / `files` / `delivery`). Captured per-item `sendConfig` (`providerID`/`modelID`/`agent`/`variant`) is **not** stuffed into the prompt body — real prompt rejects that shape.
50
+ - At the session serial submit boundary, `send` applies config first: `POST .../agent` then `POST .../model` (model uses `{ id, providerID, variant? }`), then a single `POST .../prompt`. Switch failure or session busy returns **without** posting prompt (no wrong-model send / no double admission). Busy → `{ kind: 'retry', code: 'session_busy' }` (worker schedules retry; not marked accepted). Definitive switch 4xx → `{ kind: 'failed', code: 'config_switch_failed' }`.
51
+ - `send` treats only explicit 2xx (including empty 200/202/204) as `ok`. `undefined`/malformed results are never success. Prompt 409 → retry/`session_busy`; definitive other 4xx (except 408/429) is `failed`; 408/429/5xx and transport throws are `ambiguous`.
52
+ - `findMessage` uses inbox then `session.message` projection. Real client not-found is `_tag: MessageNotFoundError` / `SessionNotFoundError` (not only HTTP 404 status) → `found: false`. Projection is `await`ed so rejections stay inside the adapter catch (`unavailable`) instead of escaping. Non-404 failures stay `unavailable` without re-POSTing.
53
53
 
54
54
  ## Confirmation and reconciliation
55
55
 
@@ -1,24 +1,50 @@
1
- import { createOpencodeClient } from '@opencode-ai/sdk/v2';
1
+ import { makeOpenCodeV2Client } from '../opencode/v2-client.js';
2
2
  import { createAscendingMessageID } from './message-id.js';
3
3
  import { createSessionTurnGate } from './session-turn-gate.js';
4
4
 
5
5
  // SDK 1.18 result shapes vary: success may be 2xx with empty body (200/202/204),
6
6
  // failures may place status on response, error, or the top-level result.
7
- const safeStatus = (result) => {
8
- for (const candidate of [result?.response?.status, result?.error?.status, result?.status]) {
7
+ const isSuccessStatus = (status) => Number.isInteger(status) && status >= 200 && status < 300;
8
+ const runtimeToken = (config, generation) => JSON.stringify([generation ?? null, config?.apiBaseUrl ?? config?.baseUrl ?? null]);
9
+ const messageIdentity = (message) => message?.info?.id ?? message?.id;
10
+ const inboxItemIdentity = (item) => item?.id ?? item?.info?.id ?? messageIdentity(item);
11
+ const httpStatus = (error) => {
12
+ for (const candidate of [error?.cause?.status, error?.status, error?.response?.status, error?.statusCode]) {
9
13
  if (Number.isInteger(candidate)) return candidate;
10
14
  }
11
15
  return undefined;
12
16
  };
13
- const isSuccessStatus = (status) => Number.isInteger(status) && status >= 200 && status < 300;
14
- const runtimeToken = (config, generation) => JSON.stringify([generation ?? null, config?.apiBaseUrl ?? config?.baseUrl ?? null]);
15
- const messageIdentity = (message) => message?.info?.id ?? message?.id;
16
- const isNotFoundOrUnsupported = (status) => status === 404 || status === 405 || status === 501;
17
- const isUnsupportedLookupError = (error) => {
18
- if (!error) return false;
19
- if (error instanceof TypeError) return true;
20
- const message = typeof error?.message === 'string' ? error.message : '';
21
- return /is not a function|not a function|undefined is not|Cannot read properties of undefined/i.test(message);
17
+ /**
18
+ * Real `@opencode/client` throws declared JSON with `_tag` (no status).
19
+ * MessageNotFoundError / SessionNotFoundError must map to found:false, not unavailable.
20
+ */
21
+ const isNotFoundError = (error) => {
22
+ if (!error || typeof error !== 'object') return false;
23
+ const tag = error._tag ?? error.name;
24
+ if (tag === 'MessageNotFoundError' || tag === 'SessionNotFoundError') return true;
25
+ if (httpStatus(error) === 404) return true;
26
+ const code = error.code ?? error.type ?? error.error?.code ?? error.error?.type ?? error.error?._tag;
27
+ return code === 'not_found'
28
+ || code === 'NotFound'
29
+ || code === 'MessageNotFoundError'
30
+ || code === 'SessionNotFoundError';
31
+ };
32
+ const isSessionBusyError = (error) => {
33
+ if (!error || typeof error !== 'object') return false;
34
+ if ((error._tag ?? error.name) === 'SessionBusyError') return true;
35
+ const code = error.code ?? error.type ?? error.error?._tag;
36
+ return code === 'session_busy' || code === 'SessionBusyError';
37
+ };
38
+ const isAbortError = (error) => (
39
+ error?.name === 'AbortError'
40
+ || error?.code === 'aborted'
41
+ || error?.cause?.name === 'AbortError'
42
+ );
43
+ const messageType = (message) => {
44
+ const info = message?.info ?? message;
45
+ if (info?.type === 'assistant' || info?.type === 'user') return info.type;
46
+ if (info?.role === 'assistant' || info?.role === 'user') return info.role;
47
+ return message ? 'unknown' : null;
22
48
  };
23
49
 
24
50
  export const createOpenCodeMessageQueueAdapter = ({
@@ -35,7 +61,7 @@ export const createOpenCodeMessageQueueAdapter = ({
35
61
  } = {}) => {
36
62
  const captureRuntime = () => { const config = getRuntimeConfig(); const generation = getRuntimeGeneration(); return { config: { ...config, apiBaseUrl: config?.apiBaseUrl ?? config?.baseUrl ?? buildOpenCodeUrl('/', ''), authHeaders: { ...getOpenCodeAuthHeaders() } }, generation, token: runtimeToken(config, generation) }; };
37
63
  const isCurrent = (runtime) => !runtime || runtime.token === runtimeToken(getRuntimeConfig(), getRuntimeGeneration());
38
- const client = (runtime) => createOpencodeClient({ baseUrl: (runtime?.config?.apiBaseUrl ?? buildOpenCodeUrl('/', '')).replace(/\/$/, ''), headers: runtime?.config?.authHeaders ?? getOpenCodeAuthHeaders() });
64
+ const client = (runtime) => makeOpenCodeV2Client({ baseUrl: (runtime?.config?.apiBaseUrl ?? buildOpenCodeUrl('/', '')).replace(/\/$/, ''), authHeaders: runtime?.config?.authHeaders ?? getOpenCodeAuthHeaders() });
39
65
  const turnKey = (scope, runtime) => JSON.stringify([runtime?.token ?? runtimeToken(getRuntimeConfig(), getRuntimeGeneration()), scope.directory, scope.sessionID]);
40
66
  const checkEligibility = async (scope, runtime, { signal } = {}) => {
41
67
  const key = turnKey(scope, runtime);
@@ -44,24 +70,25 @@ export const createOpenCodeMessageQueueAdapter = ({
44
70
  return { available: false, idle: false, settled: false };
45
71
  };
46
72
  try {
47
- const api = client(runtime); const status = getSessionEligibility ? await getSessionEligibility(scope, { signal }) : await api.session.status({ directory: scope.directory }, { signal });
48
- const messages = getLatestMessageID ? null : await api.session.messages({ sessionID: scope.sessionID, directory: scope.directory }, { signal });
73
+ const api = client(runtime);
74
+ const status = getSessionEligibility ? await getSessionEligibility(scope, { signal }) : await api.session.active({ signal });
75
+ const listed = getLatestMessageID ? null : await api.message.list({ sessionID: scope.sessionID, limit: 1, order: 'desc' }, { signal });
76
+ const messages = getLatestMessageID ? null : listed?.data;
49
77
  const injectedStatus = getSessionEligibility && status && typeof status === 'object' && typeof status.idle === 'boolean' && typeof status.settled === 'boolean';
50
- const apiStatus = !getSessionEligibility && status?.data && typeof status.data === 'object' && !Array.isArray(status.data);
51
- if (status?.error || messages?.error || (!injectedStatus && !apiStatus) || (!getSessionEligibility && !Array.isArray(messages?.data))) return unavailable();
52
- const latestMessageID = getLatestMessageID ? await getLatestMessageID(scope, { signal }) : (messages?.data ?? []).at(-1)?.info?.id ?? (messages?.data ?? []).at(-1)?.id;
78
+ const activeMap = !getSessionEligibility && status && typeof status === 'object' && !Array.isArray(status);
79
+ if (!injectedStatus && !activeMap) return unavailable();
80
+ if (!getLatestMessageID && !Array.isArray(messages)) return unavailable();
81
+ const latest = Array.isArray(messages) ? messages[0] : null;
82
+ const latestMessageID = getLatestMessageID ? await getLatestMessageID(scope, { signal }) : latest?.id ?? latest?.info?.id;
53
83
  if (latestMessageID !== undefined && latestMessageID !== null && typeof latestMessageID !== 'string') return unavailable();
54
- const last = (messages?.data ?? []).at(-1); const lastInfo = last?.info ?? last;
55
- const statusMap = status?.data;
56
- const statusValue = statusMap && typeof statusMap === 'object' && !Array.isArray(statusMap) ? statusMap[scope.sessionID] : statusMap;
57
- const missingSessionStatus = statusMap && typeof statusMap === 'object' && !Array.isArray(statusMap) && !Object.hasOwn(statusMap, scope.sessionID);
58
- const idle = getSessionEligibility ? status.idle : missingSessionStatus || statusValue?.type === 'idle' || statusValue?.status === 'idle' || status?.idle === true;
84
+ const lastInfo = latest?.info ?? latest;
85
+ const idle = getSessionEligibility ? status.idle : !Object.hasOwn(status, scope.sessionID);
59
86
  if (getSessionEligibility) return { available: true, idle, settled: status?.settled === true, latestMessageID };
60
87
  const settlement = turnGate.evaluate(key, {
61
88
  available: true,
62
89
  idle,
63
90
  tailID: typeof lastInfo?.id === 'string' ? lastInfo.id : null,
64
- tailRole: lastInfo?.role === 'assistant' || lastInfo?.role === 'user' ? lastInfo.role : last ? 'unknown' : null,
91
+ tailRole: messageType(latest),
65
92
  tailCompleted: Boolean(lastInfo?.time?.completed),
66
93
  });
67
94
  return { available: true, idle, settled: settlement.ready, latestMessageID, settlementReason: settlement.reason, ...(settlement.nextCheckAt === undefined ? {} : { nextCheckAt: settlement.nextCheckAt }) };
@@ -84,95 +111,156 @@ export const createOpenCodeMessageQueueAdapter = ({
84
111
  return { type: 'file', mime: part.mime, url: file.url };
85
112
  }));
86
113
  };
114
+ const openCodeUrl = (pathname, directory, runtime) => {
115
+ const base = (runtime?.config?.apiBaseUrl ?? buildOpenCodeUrl('/', '')).replace(/\/$/, '');
116
+ const url = new URL(`${base}${pathname.startsWith('/') ? pathname : `/${pathname}`}`);
117
+ if (directory) url.searchParams.set('directory', directory);
118
+ return url;
119
+ };
120
+ const authHeaders = (runtime) => runtime?.config?.authHeaders ?? getOpenCodeAuthHeaders();
121
+ /**
122
+ * Official v2 SessionPrompt body: id/text/files/delivery only.
123
+ * model/agent/variant are NOT accepted on prompt — applied via
124
+ * session.switchAgent / session.switchModel at the serial submit boundary.
125
+ */
126
+ const promptBodyFromContext = (context) => {
127
+ const parts = Array.isArray(context.parts) ? context.parts : [];
128
+ const text = typeof context.content === 'string' && context.content
129
+ ? context.content
130
+ : parts.filter((part) => part?.type === 'text' && typeof part.text === 'string').map((part) => part.text).join('\n');
131
+ const files = parts
132
+ .filter((part) => part?.type === 'file' && typeof part.url === 'string')
133
+ .map((part) => ({
134
+ uri: part.url,
135
+ ...(part.filename || part.name ? { name: part.filename || part.name } : {}),
136
+ ...(part.mime ? { mime: part.mime } : {}),
137
+ }));
138
+ return {
139
+ id: context.messageID,
140
+ text,
141
+ delivery: context.delivery === 'queue' ? 'queue' : 'steer',
142
+ ...(files.length ? { files } : {}),
143
+ };
144
+ };
145
+ const classifyHttpStatus = (status, { ok } = {}) => {
146
+ if (ok || isSuccessStatus(status)) return { ok: true, status };
147
+ // 409 conflict / busy: definitive rejection before admission — retry later,
148
+ // do not mark accepted or re-POST as a new success path.
149
+ if (status === 409) return { ok: false, kind: 'retry', code: 'session_busy' };
150
+ if (status === 408 || status === 429 || (Number.isInteger(status) && status >= 500)) {
151
+ return { ok: false, status, kind: 'ambiguous' };
152
+ }
153
+ if (Number.isInteger(status) && status >= 400 && status < 500) {
154
+ return { ok: false, status, kind: 'failed' };
155
+ }
156
+ return { ok: false, kind: 'ambiguous', code: 'malformed_result' };
157
+ };
158
+ const classifySwitchStatus = (status, { ok } = {}) => {
159
+ if (ok || isSuccessStatus(status)) return { ok: true, status };
160
+ if (status === 409) return { ok: false, kind: 'retry', code: 'session_busy' };
161
+ if (status === 408 || status === 429 || (Number.isInteger(status) && status >= 500)) {
162
+ return { ok: false, status, kind: 'ambiguous', code: 'config_switch_ambiguous' };
163
+ }
164
+ if (Number.isInteger(status) && status >= 400 && status < 500) {
165
+ return { ok: false, status, kind: 'failed', code: 'config_switch_failed' };
166
+ }
167
+ return { ok: false, kind: 'ambiguous', code: 'config_switch_ambiguous' };
168
+ };
169
+ /**
170
+ * Apply captured per-item sendConfig before prompt on the session serial
171
+ * boundary. Failure returns without POSTing prompt (no accidental re-send
172
+ * under the wrong model/agent). Order: agent → model(+variant).
173
+ */
174
+ const applySendConfig = async (sessionID, directory, runtime, sendConfig, { signal } = {}) => {
175
+ if (!sendConfig || typeof sendConfig !== 'object') return { ok: true };
176
+ const headers = { 'Content-Type': 'application/json', Accept: 'application/json', ...authHeaders(runtime) };
177
+ if (typeof sendConfig.agent === 'string' && sendConfig.agent) {
178
+ const response = await fetch(openCodeUrl(`/api/session/${encodeURIComponent(sessionID)}/agent`, directory, runtime), {
179
+ method: 'POST',
180
+ headers,
181
+ body: JSON.stringify({ agent: sendConfig.agent }),
182
+ signal,
183
+ });
184
+ if (!response || typeof response !== 'object') {
185
+ return { ok: false, kind: 'ambiguous', code: 'config_switch_ambiguous' };
186
+ }
187
+ const classified = classifySwitchStatus(response.status, { ok: response.ok });
188
+ if (!classified.ok) return classified;
189
+ }
190
+ if (typeof sendConfig.providerID === 'string' && sendConfig.providerID
191
+ && typeof sendConfig.modelID === 'string' && sendConfig.modelID) {
192
+ const model = {
193
+ id: sendConfig.modelID,
194
+ providerID: sendConfig.providerID,
195
+ ...(typeof sendConfig.variant === 'string' && sendConfig.variant
196
+ ? { variant: sendConfig.variant }
197
+ : {}),
198
+ };
199
+ const response = await fetch(openCodeUrl(`/api/session/${encodeURIComponent(sessionID)}/model`, directory, runtime), {
200
+ method: 'POST',
201
+ headers,
202
+ body: JSON.stringify({ model }),
203
+ signal,
204
+ });
205
+ if (!response || typeof response !== 'object') {
206
+ return { ok: false, kind: 'ambiguous', code: 'config_switch_ambiguous' };
207
+ }
208
+ const classified = classifySwitchStatus(response.status, { ok: response.ok });
209
+ if (!classified.ok) return classified;
210
+ }
211
+ return { ok: true };
212
+ };
87
213
  const send = async (context, { signal } = {}) => {
88
214
  if (!isCurrent(context.runtime)) return { ok: false, kind: 'retry', code: 'runtime_stale' };
89
215
  try {
90
- const config = context.sendConfig ?? context;
91
- const result = await client(context.runtime).session.promptAsync({
92
- sessionID: context.scope?.sessionID ?? context.sessionID,
93
- directory: context.scope?.directory ?? context.directory,
94
- messageID: context.messageID,
95
- model: { providerID: config.providerID, modelID: config.modelID },
96
- ...(config.agent ? { agent: config.agent } : {}),
97
- ...(config.variant ? { variant: config.variant } : {}),
98
- parts: context.parts ?? await materializeAttachments(context, { signal }),
99
- }, { signal });
216
+ const parts = context.parts ?? await materializeAttachments(context, { signal });
217
+ const sessionID = context.scope?.sessionID ?? context.sessionID;
218
+ const directory = context.scope?.directory ?? context.directory;
219
+ const sendConfig = context.sendConfig && typeof context.sendConfig === 'object'
220
+ ? context.sendConfig
221
+ : null;
222
+ // Serial session boundary: switch captured config, then prompt once.
223
+ if (sendConfig) {
224
+ const switched = await applySendConfig(sessionID, directory, context.runtime, sendConfig, { signal });
225
+ if (!switched.ok) return switched;
226
+ }
227
+ const response = await fetch(openCodeUrl(`/api/session/${encodeURIComponent(sessionID)}/prompt`, directory, context.runtime), {
228
+ method: 'POST',
229
+ headers: { 'Content-Type': 'application/json', Accept: 'application/json', ...authHeaders(context.runtime) },
230
+ body: JSON.stringify(promptBodyFromContext({ ...context, parts })),
231
+ signal,
232
+ });
100
233
  // Only an explicit 2xx (incl. empty 200/202/204) with no error is success.
101
234
  // undefined/malformed results must not be treated as accepted POSTs.
102
- if (!result || typeof result !== 'object') return { ok: false, kind: 'ambiguous', code: 'malformed_result' };
103
- const status = safeStatus(result);
104
- if (result.error) {
105
- return {
106
- ok: false,
107
- status,
108
- kind: status === 408 || status === 429 || (Number.isInteger(status) && status >= 500) || status === undefined
109
- ? 'ambiguous'
110
- : Number.isInteger(status) && status >= 400 && status < 500
111
- ? 'failed'
112
- : 'ambiguous',
113
- };
114
- }
115
- if (isSuccessStatus(status)) return { ok: true, status };
116
- if (Number.isInteger(status) && status >= 400 && status < 500 && status !== 408 && status !== 429) {
117
- return { ok: false, status, kind: 'failed' };
118
- }
119
- if (status === 408 || status === 429 || (Number.isInteger(status) && status >= 500)) {
120
- return { ok: false, status, kind: 'ambiguous' };
121
- }
122
- // Missing status without error is ambiguous (empty/malformed transport), not ok.
123
- return { ok: false, kind: 'ambiguous', code: 'malformed_result' };
235
+ if (!response || typeof response !== 'object') return { ok: false, kind: 'ambiguous', code: 'malformed_result' };
236
+ return classifyHttpStatus(response.status, { ok: response.ok });
124
237
  } catch (error) {
125
- if (error?.name === 'AbortError') return { ok: false, kind: 'ambiguous', code: 'aborted' };
238
+ if (isAbortError(error)) return { ok: false, kind: 'ambiguous', code: 'aborted' };
239
+ if (isSessionBusyError(error)) return { ok: false, kind: 'retry', code: 'session_busy' };
126
240
  return { ok: false, kind: 'ambiguous', code: 'transport' };
127
241
  }
128
242
  };
129
- const findViaBoundedMessages = async (api, scope, messageID, { signal } = {}) => {
130
- const result = await api.session.messages({ sessionID: scope.sessionID, directory: scope.directory, limit: 100 }, { signal });
131
- if (result?.error) return { unavailable: true };
132
- const found = (result?.data ?? []).some((message) => messageIdentity(message) === messageID);
133
- if (found) return { found: true };
134
- if (result?.data?.length >= 100 || result?.nextCursor || result?.hasMore) return { unavailable: true };
135
- return { found: false };
136
- };
137
- const findViaLegacySessionMessage = async (api, scope, messageID, { signal } = {}) => {
138
- const legacy = api?.session?.message;
139
- if (typeof legacy !== 'function') return null;
243
+ const findViaInbox = async (scope, messageID, { signal, runtime } = {}) => {
140
244
  try {
141
- const result = await legacy.call(api.session, { sessionID: scope.sessionID, messageID, directory: scope.directory }, { signal });
142
- const status = safeStatus(result);
143
- if (result?.error) {
144
- if (status === 404) return { found: false };
145
- if (isNotFoundOrUnsupported(status)) return null;
146
- return { unavailable: true };
147
- }
148
- const record = result?.data ?? result;
149
- const id = messageIdentity(record);
150
- if (id === messageID || Boolean(record?.info ?? record?.id)) return { found: true };
151
- return { found: false };
245
+ const list = await client(runtime).session.inbox.list({ sessionID: scope.sessionID }, { signal });
246
+ if (!Array.isArray(list)) throw Object.assign(new Error('upstream'), { code: 'upstream' });
247
+ return { found: list.some((item) => inboxItemIdentity(item) === messageID) };
152
248
  } catch (error) {
153
- if (isUnsupportedLookupError(error)) return null;
154
- return { unavailable: true };
249
+ if (isNotFoundError(error)) return { found: false };
250
+ throw error;
155
251
  }
156
252
  };
157
- const findViaExactV2Message = async (api, scope, messageID, { signal } = {}) => {
158
- // Prefer client.v2.session.message when the 1.18 SDK surface exposes it.
159
- const exact = api?.v2?.session?.message;
160
- if (typeof exact !== 'function') return { kind: 'unsupported' };
253
+ // Prefer client.v2.session.message when the 1.18 SDK surface exposes it.
254
+ // Ticket 12: after prompt admission, reconcile only asks inbox + projection.
255
+ const findViaProjection = async (scope, messageID, { signal, runtime } = {}) => {
161
256
  try {
162
- const result = await exact.call(api.v2.session, { sessionID: scope.sessionID, messageID, directory: scope.directory }, { signal });
163
- const status = safeStatus(result);
164
- if (result?.error) {
165
- if (status === 404) return { kind: 'result', value: { found: false } };
166
- if (isNotFoundOrUnsupported(status)) return { kind: 'unsupported' };
167
- return { kind: 'result', value: { unavailable: true } };
168
- }
169
- const record = result?.data ?? result;
257
+ const record = await client(runtime).session.message({ sessionID: scope.sessionID, messageID }, { signal });
170
258
  const id = messageIdentity(record);
171
- if (id === messageID || Boolean(record?.info ?? record?.id)) return { kind: 'result', value: { found: true } };
172
- return { kind: 'result', value: { found: false } };
259
+ if (id === messageID || Boolean(record?.info ?? record?.id)) return { found: true };
260
+ return { found: false };
173
261
  } catch (error) {
174
- if (isUnsupportedLookupError(error)) return { kind: 'unsupported' };
175
- return { kind: 'result', value: { unavailable: true } };
262
+ if (isNotFoundError(error)) return { found: false };
263
+ throw error;
176
264
  }
177
265
  };
178
266
  const findMessage = async (scope, messageID, { signal, runtime } = {}) => {
@@ -182,13 +270,12 @@ export const createOpenCodeMessageQueueAdapter = ({
182
270
  if (exact?.unavailable) return { unavailable: true };
183
271
  return { found: Boolean(exact?.found ?? exact?.data ?? exact?.id) };
184
272
  }
185
- const api = client(runtime);
186
- const v2 = await findViaExactV2Message(api, scope, messageID, { signal });
187
- if (v2.kind === 'result') return v2.value;
188
- // Missing/unsupported v2 endpoint (or 405/501): try legacy client.session.message, then bounded list.
189
- const legacy = await findViaLegacySessionMessage(api, scope, messageID, { signal });
190
- if (legacy) return legacy;
191
- return findViaBoundedMessages(api, scope, messageID, { signal });
273
+ const inbox = await findViaInbox(scope, messageID, { signal, runtime });
274
+ if (inbox.unavailable) return { unavailable: true };
275
+ if (inbox.found) return { found: true };
276
+ // Must await so projection rejections stay inside this try/catch
277
+ // (bare return of a Promise lets rejections escape as unhandled).
278
+ return await findViaProjection(scope, messageID, { signal, runtime });
192
279
  } catch { return { unavailable: true }; }
193
280
  };
194
281
  const observeSessionEvent = (scope, phase, runtime = captureRuntime()) => turnGate.observeEvent(turnKey(scope, runtime), phase);