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

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 (328) 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-CzTtVMnI.js} +1 -1
  7. package/dist/assets/AssistantView-Cg1qQvVe.js +3 -0
  8. package/dist/assets/{DiagramView-D1LGK_s2.js → DiagramView-hOi72Wdx.js} +1 -1
  9. package/dist/assets/{FadeInOnReveal-B0kJo9Ii.js → FadeInOnReveal-DCqrQkkz.js} +1 -1
  10. package/dist/assets/Icon-B8fSTLdP.js +1 -0
  11. package/dist/assets/MarkdownRenderer-Cw74oVxV.js +2 -0
  12. package/dist/assets/{MarkdownRendererImpl-DfoCJ1Mq.js → MarkdownRendererImpl-YM7iRmzK.js} +3 -3
  13. package/dist/assets/MarkstreamRendererImpl-BDy_oGJQ.js +3 -0
  14. package/dist/assets/MobileDetailNavigation-iX_hb_Xt.js +1 -0
  15. package/dist/assets/{MobileShareBridge-2Yjn6wXg.js → MobileShareBridge-CAMUupwR.js} +2 -2
  16. package/dist/assets/MobileSurface-CHbdPcLH.js +2 -0
  17. package/dist/assets/{MultiRunWindow-tTbMFowS.js → MultiRunWindow-Ci1ODFFX.js} +1 -1
  18. package/dist/assets/{OnboardingScreen-CD2KtlF3.js → OnboardingScreen-O4cGdz5p.js} +1 -1
  19. package/dist/assets/OverlayScrollbar-CRfGtcmG.js +2 -0
  20. package/dist/assets/SettingsView-Dg8BtTV1.js +25 -0
  21. package/dist/assets/{SettingsWindow-zIa76s4t.js → SettingsWindow-B8d8qIQ7.js} +1 -1
  22. package/dist/assets/TerminalView-CMiw3s_c.js +8 -0
  23. package/dist/assets/{ToolOutputDialog-DNqA4uBl.js → ToolOutputDialog-ByaQYRTp.js} +2 -2
  24. package/dist/assets/appThemeRegistry-BGGHio8T.js +1 -0
  25. package/dist/assets/{assistant-attachment-cache-C0aRnFkj.js → assistant-attachment-cache-DphJleHr.js} +1 -1
  26. package/dist/assets/{button-CI7Q-Zw_.js → button-DIIrsMjW.js} +1 -1
  27. package/dist/assets/{collapsible-D5Phaykh.js → collapsible-CnvUeYst.js} +1 -1
  28. package/dist/assets/{context-menu-CubSDW2g.js → context-menu-BGBUpZEQ.js} +1 -1
  29. package/dist/assets/{desktopHosts-B1ixEylr.js → desktopHosts-JGFqhlaM.js} +1 -1
  30. package/dist/assets/{desktopRelayRestore-4JmFUw8r.js → desktopRelayRestore-CHEwrI93.js} +1 -1
  31. package/dist/assets/{diagram-DdLO8HZ9.js → diagram-rvn7_qBM.js} +1 -1
  32. package/dist/assets/{dialog-B6pnIYmp.js → dialog-CQ63sc9E.js} +1 -1
  33. package/dist/assets/es-Dsmc0A_3.js +15 -0
  34. package/dist/assets/fileMentionTouchSelection-BzzcWJCi.js +3 -0
  35. package/dist/assets/{fileQueries-DQc8EJUf.js → fileQueries-DN6gbLhM.js} +1 -1
  36. package/dist/assets/fr-C_ZlZDI2.js +13 -0
  37. package/dist/assets/gitApi-BF8vuoZc.js +10 -0
  38. package/dist/assets/{imageSource-BlAJJUEI.js → imageSource-BMztRu0c.js} +1 -1
  39. package/dist/assets/{input-D-__fwRi.js → input-BVu-iykl.js} +1 -1
  40. package/dist/assets/insertionBoundaries-EcAfGT2D.js +1 -0
  41. package/dist/assets/ja-BqqTaWTi.js +15 -0
  42. package/dist/assets/ko-DP2XPM37.js +15 -0
  43. package/dist/assets/main-B7t9lavz.js +2 -0
  44. package/dist/assets/main-D2BzpnUt.css +1 -0
  45. package/dist/assets/main-DWP-prNB.js +169 -0
  46. package/dist/assets/markdown-worker-bS5OgGO3.js +2 -0
  47. package/dist/assets/{markdownHeightCache-BhxqJOvf.js → markdownHeightCache-D9-B1wdY.js} +1 -1
  48. package/dist/assets/markdownTheme-DM5h1i18.js +1 -0
  49. package/dist/assets/mcpQueries-CfXrAkuU.js +1 -0
  50. package/dist/assets/miniChat-DgOrEQbN.js +2 -0
  51. package/dist/assets/mobile-Co_ZOHkZ.js +2 -0
  52. package/dist/assets/multirun-DelyUO6k.js +4 -0
  53. package/dist/assets/persistence-D0y7226-.js +3 -0
  54. package/dist/assets/pl-BCf5uLlK.js +15 -0
  55. package/dist/assets/platform-CurqO28D.js +6 -0
  56. package/dist/assets/{projectMeta-S1hJ7oBp.js → projectMeta-CPElzJ0n.js} +2 -2
  57. package/dist/assets/pt-BR-W09Q2pvn.js +15 -0
  58. package/dist/assets/{relay-image-stream-DM7atqJs.js → relay-image-stream-DrdVj99c.js} +1 -1
  59. package/dist/assets/renderElectronMiniChatApp-pOE93w86.js +2 -0
  60. package/dist/assets/renderMobileApp-F7ZSLcw8.js +28 -0
  61. package/dist/assets/runtime-fetch-Fs9ZV2V8.js +1 -0
  62. package/dist/assets/{runtimeConfig-CMkwoQ_X.js → runtimeConfig-DSS0KxG3.js} +1 -1
  63. package/dist/assets/runtimeEndpointReset-x2Wgx57R.js +149 -0
  64. package/dist/assets/sessionChangeSummary-BUGSYcHp.js +2 -0
  65. package/dist/assets/{sessionLookup-BrU3rFHJ.js → sessionLookup-Bss8M20i.js} +1 -1
  66. package/dist/assets/{sortable-tabs-strip-Fnfhf3K9.js → sortable-tabs-strip-CE7W7lJ9.js} +1 -1
  67. package/dist/assets/src-lS1keDVV.css +2 -0
  68. package/dist/assets/store-jaP79mqI.js +15 -0
  69. package/dist/assets/{terminalApi-xjqssKke.js → terminalApi-mDR2jXTo.js} +1 -1
  70. package/dist/assets/textarea-XD2oX_Ee.js +1 -0
  71. package/dist/assets/{tooltip-0T5jv0cu.js → tooltip-DYeQ_Hvy.js} +1 -1
  72. package/dist/assets/uk-BgFzz8m2.js +15 -0
  73. package/dist/assets/{url-_z7evzdK.js → url-CGbJbiJq.js} +1 -1
  74. package/dist/assets/{useAppFontEffects-C02YFyAv.js → useAppFontEffects-BAWHZ-t0.js} +64 -45
  75. package/dist/assets/{useAssistantUIStore-CMutVh9B.js → useAssistantUIStore-Bz4tRflF.js} +1 -1
  76. package/dist/assets/useDesktopWindowControlsLayout-hN_nLb7T.js +1 -0
  77. package/dist/assets/useEffectiveDirectory-Do9DQ453.js +1 -0
  78. package/dist/assets/{useFontPreferences-B1JYyzpK.js → useFontPreferences-Bjx8zkGM.js} +1 -1
  79. package/dist/assets/useMobileNavigationStore-BsY_gi_K.js +414 -0
  80. package/dist/assets/useSessionAutoCleanup-C4g5KmDf.js +2 -0
  81. package/dist/assets/useUIStore-syVmbuDA.js +1 -0
  82. package/dist/assets/{useWorkerHighlightedLines-C9z0ML1z.js → useWorkerHighlightedLines-B5QXxBok.js} +2 -2
  83. package/dist/assets/vendor-opencode-client-C12gfjag.js +12 -0
  84. package/dist/assets/{vendor-reactuses-core-BnW6GxJt.js → vendor-reactuses-core-Dq5ocTF8.js} +1 -1
  85. package/dist/assets/{vendor-yaml-DIcAK3EC.js → vendor-yaml-C9KlJAlj.js} +37 -37
  86. package/dist/assets/zh-CN-BhqSLgUY.js +15 -0
  87. package/dist/assets/zh-TW-BsaBgY-p.js +15 -0
  88. package/dist/index.html +12 -12
  89. package/dist/mini-chat.html +12 -12
  90. package/dist/mobile.html +12 -12
  91. package/package.json +3 -2
  92. package/server/index.js +423 -20
  93. package/server/lib/assistants/DOCUMENTATION.md +26 -21
  94. package/server/lib/assistants/assign.js +2 -1
  95. package/server/lib/assistants/contact-store.js +37 -19
  96. package/server/lib/assistants/contracts.js +11 -1
  97. package/server/lib/assistants/projection-history.test.js +454 -0
  98. package/server/lib/assistants/routes.js +11 -5
  99. package/server/lib/assistants/service.js +850 -294
  100. package/server/lib/assistants/service.test.js +588 -274
  101. package/server/lib/browser-provider/DOCUMENTATION.md +141 -0
  102. package/server/lib/browser-provider/contract.js +312 -0
  103. package/server/lib/browser-provider/host.js +421 -0
  104. package/server/lib/browser-provider/host.test.js +304 -0
  105. package/server/lib/browser-provider/routes.js +122 -0
  106. package/server/lib/browser-provider/routes.test.js +120 -0
  107. package/server/lib/browser-provider/selection-store.js +43 -0
  108. package/server/lib/browser-provider/selection-store.test.js +35 -0
  109. package/server/lib/conversations/DOCUMENTATION.md +5 -5
  110. package/server/lib/conversations/service.js +87 -57
  111. package/server/lib/conversations/service.test.js +132 -100
  112. package/server/lib/conversations/validation.js +11 -1
  113. package/server/lib/event-stream/DOCUMENTATION.md +3 -1
  114. package/server/lib/event-stream/directory-ws-bridge.js +13 -2
  115. package/server/lib/event-stream/global-hub.js +51 -5
  116. package/server/lib/event-stream/reasoning-projection.js +16 -1
  117. package/server/lib/event-stream/reasoning-projection.test.js +15 -0
  118. package/server/lib/event-stream/runtime.js +57 -20
  119. package/server/lib/event-stream/runtime.test.js +112 -0
  120. package/server/lib/llm/DOCUMENTATION.md +95 -84
  121. package/server/lib/llm/catalog.js +111 -45
  122. package/server/lib/llm/catalog.test.js +121 -59
  123. package/server/lib/llm/completions.js +41 -12
  124. package/server/lib/llm/completions.test.js +45 -4
  125. package/server/lib/llm/generate.js +516 -453
  126. package/server/lib/llm/generate.test.js +705 -484
  127. package/server/lib/llm/routes.js +8 -4
  128. package/server/lib/message-queue/DOCUMENTATION.md +5 -5
  129. package/server/lib/message-queue/opencode-adapter.js +191 -104
  130. package/server/lib/message-queue/opencode-adapter.test.js +328 -58
  131. package/server/lib/message-queue/runtime.js +15 -2
  132. package/server/lib/message-queue/runtime.test.js +6 -6
  133. package/server/lib/message-queue/worker.js +7 -1
  134. package/server/lib/notifications/DOCUMENTATION.md +3 -1
  135. package/server/lib/notifications/runtime.js +133 -31
  136. package/server/lib/notifications/runtime.test.js +103 -0
  137. package/server/lib/notifications/template-runtime.js +130 -38
  138. package/server/lib/notifications/template-runtime.test.js +96 -0
  139. package/server/lib/opencode/DOCUMENTATION.md +111 -16
  140. package/server/lib/opencode/agent-document.d.ts +28 -0
  141. package/server/lib/opencode/agent-document.js +280 -0
  142. package/server/lib/opencode/agent-document.test.js +137 -0
  143. package/server/lib/opencode/agents.js +96 -0
  144. package/server/lib/opencode/auth-state-runtime.js +5 -0
  145. package/server/lib/opencode/bootstrap-runtime.js +2 -0
  146. package/server/lib/opencode/config-entity-routes.js +117 -103
  147. package/server/lib/opencode/config-entity-routes.test.js +148 -10
  148. package/server/lib/opencode/config-hot-reload.integration.test.js +105 -0
  149. package/server/lib/opencode/ensure-cli.d.ts +8 -0
  150. package/server/lib/opencode/ensure-cli.js +238 -0
  151. package/server/lib/opencode/ensure-cli.test.js +119 -0
  152. package/server/lib/opencode/env-runtime.js +208 -111
  153. package/server/lib/opencode/env-runtime.test.js +211 -14
  154. package/server/lib/opencode/feature-routes-runtime.js +90 -5
  155. package/server/lib/opencode/feature-routes-runtime.test.js +25 -0
  156. package/server/lib/opencode/http-server-connections.js +98 -0
  157. package/server/lib/opencode/http-server-connections.test.js +321 -0
  158. package/server/lib/opencode/lifecycle.js +539 -95
  159. package/server/lib/opencode/lifecycle.test.js +698 -6
  160. package/server/lib/opencode/managed-capabilities-runtime.js +15 -2
  161. package/server/lib/opencode/managed-capabilities-runtime.test.js +1 -0
  162. package/server/lib/opencode/network-runtime.js +28 -16
  163. package/server/lib/opencode/network-runtime.test.js +68 -4
  164. package/server/lib/opencode/openchamber-routes.js +7 -1
  165. package/server/lib/opencode/opencode2-pin.js +139 -0
  166. package/server/lib/opencode/opencode2-pin.test.js +88 -0
  167. package/server/lib/opencode/opencode2-sidecar.e2e.test.js +326 -0
  168. package/server/lib/opencode/owned-runtime-upgrade.js +431 -0
  169. package/server/lib/opencode/owned-runtime-upgrade.test.js +160 -0
  170. package/server/lib/opencode/plugin-compatibility.js +15 -0
  171. package/server/lib/opencode/plugin-compatibility.test.js +21 -0
  172. package/server/lib/opencode/plugin-routes.js +3 -18
  173. package/server/lib/opencode/plugin-routes.test.js +9 -9
  174. package/server/lib/opencode/plugins.js +4 -3
  175. package/server/lib/opencode/plugins.test.js +12 -0
  176. package/server/lib/opencode/provider-catalog.js +58 -12
  177. package/server/lib/opencode/provider-catalog.test.js +48 -0
  178. package/server/lib/opencode/proxy.js +373 -28
  179. package/server/lib/opencode/proxy.test.js +353 -0
  180. package/server/lib/opencode/routes.js +333 -76
  181. package/server/lib/opencode/routes.test.js +315 -50
  182. package/server/lib/opencode/runtime-contract.js +401 -0
  183. package/server/lib/opencode/runtime-contract.test.js +214 -0
  184. package/server/lib/opencode/server-opencode-fetch.js +242 -0
  185. package/server/lib/opencode/server-opencode-fetch.test.js +130 -0
  186. package/server/lib/opencode/server-utils-runtime.js +35 -0
  187. package/server/lib/opencode/shared-service.integration.test.js +66 -0
  188. package/server/lib/opencode/shared-service.js +147 -0
  189. package/server/lib/opencode/shared-service.test.js +93 -0
  190. package/server/lib/opencode/shared.js +3 -3
  191. package/server/lib/opencode/shutdown-runtime.js +15 -6
  192. package/server/lib/opencode/shutdown-runtime.test.js +49 -1
  193. package/server/lib/opencode/skill-routes.js +26 -40
  194. package/server/lib/opencode/skill-routes.test.js +71 -7
  195. package/server/lib/opencode/skills.test.js +15 -0
  196. package/server/lib/opencode/startup-pipeline-runtime.js +4 -0
  197. package/server/lib/opencode/static-routes-runtime.js +3 -1
  198. package/server/lib/opencode/static-routes-runtime.test.js +33 -0
  199. package/server/lib/opencode/upgrade-screen.d.ts +30 -0
  200. package/server/lib/opencode/upgrade-screen.js +299 -0
  201. package/server/lib/opencode/upgrade-screen.test.js +196 -0
  202. package/server/lib/opencode/v1-migration-gate.js +157 -0
  203. package/server/lib/opencode/v1-migration-gate.test.js +111 -0
  204. package/server/lib/opencode/v2-client.js +60 -0
  205. package/server/lib/opencode/v2-plugin-host-shim/index.mjs +155 -0
  206. package/server/lib/opencode/v2-plugin-host-shim/package.json +4 -0
  207. package/server/lib/opencode/watcher.js +2 -1
  208. package/server/lib/opencode/watcher.test.js +1 -1
  209. package/server/lib/permission-auto-accept/DOCUMENTATION.md +5 -5
  210. package/server/lib/permission-auto-accept/runtime.js +14 -7
  211. package/server/lib/permission-auto-accept/runtime.test.js +27 -12
  212. package/server/lib/relay/reasoning-projection.e2e.test.ts +22 -7
  213. package/server/lib/scheduled-tasks/DOCUMENTATION.md +79 -39
  214. package/server/lib/scheduled-tasks/managed-tool-route.js +31 -1
  215. package/server/lib/scheduled-tasks/managed-tool-route.test.js +23 -0
  216. package/server/lib/scheduled-tasks/routes.js +27 -0
  217. package/server/lib/scheduled-tasks/routes.test.js +94 -0
  218. package/server/lib/scheduled-tasks/runtime.js +381 -310
  219. package/server/lib/scheduled-tasks/runtime.test.js +600 -309
  220. package/server/lib/session-goal/DOCUMENTATION.md +173 -22
  221. package/server/lib/session-goal/capability.js +57 -0
  222. package/server/lib/session-goal/capability.test.js +33 -0
  223. package/server/lib/session-goal/routes.js +240 -2
  224. package/server/lib/session-goal/routes.test.js +211 -0
  225. package/server/lib/session-goal/runtime.js +1617 -113
  226. package/server/lib/session-goal/runtime.test.js +1500 -70
  227. package/server/lib/session-index/DOCUMENTATION.md +18 -15
  228. package/server/lib/session-index/event-ingest.js +89 -16
  229. package/server/lib/session-index/event-ingest.test.js +73 -0
  230. package/server/lib/session-index/host-authority-snapshot.js +92 -0
  231. package/server/lib/session-index/routes.host-archive.test.js +201 -0
  232. package/server/lib/session-index/routes.js +63 -10
  233. package/server/lib/session-index/routes.test.js +8 -8
  234. package/server/lib/session-index/service.js +7 -1
  235. package/server/lib/session-index/sync-runtime.js +171 -41
  236. package/server/lib/session-index/sync-runtime.test.js +164 -9
  237. package/server/lib/session-metadata/DOCUMENTATION.md +96 -0
  238. package/server/lib/session-metadata/opencode-session-record.d.ts +57 -0
  239. package/server/lib/session-metadata/opencode-session-record.js +316 -0
  240. package/server/lib/session-metadata/opencode-session-record.test.js +242 -0
  241. package/server/lib/session-metadata/routes.js +197 -0
  242. package/server/lib/session-metadata/routes.test.js +156 -0
  243. package/server/lib/session-metadata/session-archive.d.ts +73 -0
  244. package/server/lib/session-metadata/session-archive.js +654 -0
  245. package/server/lib/session-metadata/session-archive.test.js +359 -0
  246. package/server/lib/session-metadata/session-metadata-store.d.ts +38 -0
  247. package/server/lib/session-metadata/session-metadata-store.js +422 -0
  248. package/server/lib/session-metadata/session-metadata-store.test.js +379 -0
  249. package/server/lib/session-metadata/session-projection.d.ts +41 -0
  250. package/server/lib/session-metadata/session-projection.js +279 -0
  251. package/server/lib/session-metadata/session-projection.test.js +64 -0
  252. package/server/lib/session-metadata/system-session.js +55 -0
  253. package/server/lib/session-metadata/system-session.test.js +55 -0
  254. package/server/lib/session-title/DOCUMENTATION.md +40 -7
  255. package/server/lib/session-title/native-runtime.test.js +167 -0
  256. package/server/lib/session-title/runtime.js +69 -44
  257. package/server/lib/session-title/session-access.js +46 -0
  258. package/server/lib/session-title/session-access.test.js +91 -0
  259. package/server/lib/session-turn-pages/DOCUMENTATION.md +12 -11
  260. package/server/lib/session-turn-pages/reconcile.service.js +1 -1
  261. package/server/lib/session-turn-pages/reconcile.service.test.js +16 -0
  262. package/server/lib/session-turn-pages/routes.js +123 -75
  263. package/server/lib/session-turn-pages/routes.test.js +566 -0
  264. package/server/lib/session-turn-pages/service.js +6 -2
  265. package/server/lib/session-turn-pages/service.test.js +34 -0
  266. package/server/lib/session-turn-pages/session-message-projection.d.ts +13 -0
  267. package/server/lib/session-turn-pages/session-message-projection.js +281 -0
  268. package/server/lib/small-model/DOCUMENTATION.md +101 -146
  269. package/server/lib/small-model/catalog.js +84 -54
  270. package/server/lib/small-model/catalog.test.js +111 -9
  271. package/server/lib/small-model/custom-api.js +68 -0
  272. package/server/lib/small-model/custom-api.test.js +64 -0
  273. package/server/lib/small-model/index.js +125 -241
  274. package/server/lib/small-model/resolve.js +35 -120
  275. package/server/lib/small-model/resolve.test.js +20 -110
  276. package/server/lib/small-model/routes.js +1 -3
  277. package/server/lib/small-model/summary-settings.test.js +157 -253
  278. package/server/opencode-proxy.test.js +63 -12
  279. package/dist/assets/AssistantView-BUveAgc4.js +0 -3
  280. package/dist/assets/Icon-BgdfPhNs.js +0 -1
  281. package/dist/assets/MarkdownRenderer-BbrbDH6-.js +0 -2
  282. package/dist/assets/MarkstreamRendererImpl-D2dsW5Lg.js +0 -3
  283. package/dist/assets/MobileDetailNavigation-B0aqKXd7.js +0 -1
  284. package/dist/assets/MobileSurface-DW7WfPsy.js +0 -2
  285. package/dist/assets/OverlayScrollbar-DrA0Vsg5.js +0 -2
  286. package/dist/assets/SettingsView-PkXIavr7.js +0 -25
  287. package/dist/assets/TerminalView-CDdzF4yi.js +0 -8
  288. package/dist/assets/appThemeRegistry-D5Yxg4YF.js +0 -1
  289. package/dist/assets/es-DA3MqOo0.js +0 -15
  290. package/dist/assets/fileMentionTouchSelection-DNeiqns-.js +0 -4
  291. package/dist/assets/fr-Dbz0CdIA.js +0 -13
  292. package/dist/assets/gitApi-DXyC4MiV.js +0 -7
  293. package/dist/assets/insertionBoundaries-KlOVrHHt.js +0 -1
  294. package/dist/assets/ja-DumanVQA.js +0 -15
  295. package/dist/assets/ko-C0IkiUjQ.js +0 -15
  296. package/dist/assets/main-BPS5ylib.js +0 -2
  297. package/dist/assets/main-Cpd9VWYs.js +0 -169
  298. package/dist/assets/main-qSeeJRW1.css +0 -1
  299. package/dist/assets/markdown-worker-CptoICmq.js +0 -2
  300. package/dist/assets/markdownTheme-DOfGg-A7.js +0 -1
  301. package/dist/assets/miniChat-B8_KLWiY.js +0 -2
  302. package/dist/assets/mobile-CtktKrXn.js +0 -2
  303. package/dist/assets/multirun-CYfP9Cfm.js +0 -4
  304. package/dist/assets/persistence-Ce3AnpEr.js +0 -3
  305. package/dist/assets/pl-B9No6MQp.js +0 -15
  306. package/dist/assets/platform-0w_Q9_ji.js +0 -2
  307. package/dist/assets/pt-BR-DyQ0WTDh.js +0 -15
  308. package/dist/assets/renderElectronMiniChatApp-iK14LWm8.js +0 -2
  309. package/dist/assets/renderMobileApp-DORLPWnb.js +0 -28
  310. package/dist/assets/runtime-fetch-zIalUnbb.js +0 -1
  311. package/dist/assets/runtimeEndpointReset-ChOxt-J7.js +0 -147
  312. package/dist/assets/sessionChangeSummary-NnxcqCTI.js +0 -2
  313. package/dist/assets/src-rS3zCcsC.css +0 -2
  314. package/dist/assets/store-5B_LDnLM.js +0 -15
  315. package/dist/assets/textarea-bHYxZtfH.js +0 -1
  316. package/dist/assets/uk-C_1KkqyP.js +0 -15
  317. package/dist/assets/useDesktopWindowControlsLayout-Dnr-jX0C.js +0 -1
  318. package/dist/assets/useEffectiveDirectory-CZLLyarc.js +0 -1
  319. package/dist/assets/useMobileNavigationStore-CoCiqNgm.js +0 -413
  320. package/dist/assets/useSessionAutoCleanup-xQyETQkG.js +0 -2
  321. package/dist/assets/useUIStore-Bxv3f23A.js +0 -1
  322. package/dist/assets/vendor-opencode-sdk-C07HQypa.js +0 -7
  323. package/dist/assets/zh-CN-uWILgBdt.js +0 -15
  324. package/dist/assets/zh-TW-MYfdvhbD.js +0 -15
  325. package/server/lib/small-model/call.js +0 -427
  326. package/server/lib/small-model/call.test.js +0 -382
  327. package/server/lib/small-model/opencode-session.js +0 -339
  328. package/server/lib/small-model/opencode-session.test.js +0 -177
@@ -1,15 +1,66 @@
1
- import { createOpencodeClient } from '@opencode-ai/sdk/v2';
1
+ import { OpenCode } from '@opencode/client';
2
2
 
3
3
  // --- error classification ---
4
4
 
5
5
  const isTransportError = (error) => {
6
6
  if (!error) return true;
7
+ if (error.reason === 'Transport') return true;
7
8
  if (error.name === 'TypeError' || error.name === 'FetchError' || error.name === 'AbortError') return true;
9
+ if (error.cause?.name === 'TypeError' || error.cause?.name === 'FetchError' || error.cause?.name === 'AbortError') return true;
8
10
  const code = error.cause?.code || error.code;
9
11
  if (code === 'ECONNREFUSED' || code === 'ENOTFOUND' || code === 'ECONNRESET' || code === 'ETIMEDOUT' || code === 'EPIPE') return true;
10
12
  return false;
11
13
  };
12
14
 
15
+ // v2 client throws raw data or ClientError; recover HTTP status when present.
16
+ const getHttpStatus = (error) => {
17
+ const candidates = [error?.cause?.status, error?.status, error?.response?.status];
18
+ for (const value of candidates) {
19
+ if (Number.isFinite(value)) return value;
20
+ }
21
+ return undefined;
22
+ };
23
+
24
+ const classifyPromptFailure = (error) => {
25
+ if (isTransportError(error)) {
26
+ return { ambiguous: true, status: undefined };
27
+ }
28
+ const status = getHttpStatus(error);
29
+ if (Number.isFinite(status)) {
30
+ return { ambiguous: isRetryableHttpStatus(status), status };
31
+ }
32
+ // Declared v2 HTTP errors (400/401/404/409) throw JSON without a status field.
33
+ return { ambiguous: false, status: 400 };
34
+ };
35
+
36
+ // Map sanitized parts onto the official v2 prompt body (text/files/agents).
37
+ const toV2PromptInput = (sanitizedInput, sessionID) => {
38
+ const text = sanitizedInput.parts
39
+ .filter((part) => part.type === 'text')
40
+ .map((part) => part.text)
41
+ .join('\n');
42
+ const files = sanitizedInput.parts
43
+ .filter((part) => part.type === 'file')
44
+ .map((part) => ({
45
+ uri: part.url,
46
+ ...(part.filename ? { name: part.filename } : {}),
47
+ }));
48
+ const agents = sanitizedInput.parts
49
+ .filter((part) => part.type === 'agent')
50
+ .map((part) => ({ name: part.name }));
51
+
52
+ return {
53
+ sessionID,
54
+ id: sanitizedInput.messageID,
55
+ text,
56
+ ...(files.length > 0 ? { files } : {}),
57
+ ...(agents.length > 0 ? { agents } : {}),
58
+ ...(sanitizedInput.skills?.length ? { skills: sanitizedInput.skills.map(({ id }) => ({ id, name: id })) } : {}),
59
+ ...(sanitizedInput.metadata ? { metadata: sanitizedInput.metadata } : {}),
60
+ delivery: 'steer',
61
+ };
62
+ };
63
+
13
64
  const isRetryableHttpStatus = (status) => {
14
65
  if (!Number.isFinite(status)) return true;
15
66
  return status === 408 || status === 429 || status >= 500;
@@ -84,7 +135,7 @@ export const createConversationsService = (deps) => {
84
135
 
85
136
  const baseUrl = buildOpenCodeUrl('/', '').replace(/\/$/, '');
86
137
  const authHeaders = getOpenCodeAuthHeaders();
87
- return createOpencodeClient({ baseUrl, headers: authHeaders });
138
+ return OpenCode.make({ baseUrl, headers: authHeaders });
88
139
  };
89
140
 
90
141
  const createAndPrompt = async ({ sanitizedInput } = {}) => {
@@ -99,19 +150,34 @@ export const createConversationsService = (deps) => {
99
150
  const client = clientOrError;
100
151
 
101
152
  // Phase 1: create session (30s timeout)
102
- let sessionResult;
103
- let upstreamStatus;
153
+ let session;
104
154
  const createT = timeoutSignal(CREATE_TIMEOUT_MS);
105
155
  try {
106
- sessionResult = await client.session.create({
107
- directory: sanitizedInput.directory,
156
+ session = await client.session.create({
157
+ location: { directory: sanitizedInput.directory },
108
158
  ...(sanitizedInput.title ? { title: sanitizedInput.title } : {}),
109
- ...(sanitizedInput.parentID ? { parentID: sanitizedInput.parentID } : {}),
110
- ...(sanitizedInput.metadata ? { metadata: sanitizedInput.metadata } : {}),
159
+ ...(sanitizedInput.agent ? { agent: sanitizedInput.agent } : {}),
160
+ ...(sanitizedInput.model ? {
161
+ model: {
162
+ id: sanitizedInput.model.modelID,
163
+ providerID: sanitizedInput.model.providerID,
164
+ ...(sanitizedInput.variant ? { variant: sanitizedInput.variant } : {}),
165
+ },
166
+ } : {}),
111
167
  }, {
112
168
  signal: createT.signal,
113
169
  });
114
170
  } catch (error) {
171
+ const status = getHttpStatus(error);
172
+ if (Number.isFinite(status) && !isTransportError(error)) {
173
+ logger.warn(`[conversations] session.create client error (${status})`);
174
+ return {
175
+ ok: false,
176
+ phase: 'create',
177
+ error: safeErrorMessage('create'),
178
+ status,
179
+ };
180
+ }
115
181
  logger.warn('[conversations] session.create transport error');
116
182
  return {
117
183
  ok: false,
@@ -122,19 +188,7 @@ export const createConversationsService = (deps) => {
122
188
  createT.clear();
123
189
  }
124
190
 
125
- upstreamStatus = sessionResult?.response?.status;
126
-
127
- if (sessionResult.error) {
128
- logger.warn(`[conversations] session.create SDK error (${upstreamStatus || 'no status'})`);
129
- return {
130
- ok: false,
131
- phase: 'create',
132
- error: safeErrorMessage('create'),
133
- ...(Number.isFinite(upstreamStatus) ? { status: upstreamStatus } : {}),
134
- };
135
- }
136
-
137
- const sessionID = sessionResult?.data?.id;
191
+ const sessionID = session?.id;
138
192
  if (!sessionID) {
139
193
  logger.warn('[conversations] session.create returned no session ID');
140
194
  return {
@@ -144,29 +198,17 @@ export const createConversationsService = (deps) => {
144
198
  };
145
199
  }
146
200
 
147
- const session = sessionResult?.data;
148
-
149
- // Phase 2: promptAsync (45s timeout, 204, does not wait for model completion)
201
+ // Phase 2: v2 prompt + delivery=steer (45s timeout). Host only orchestrates
202
+ // create + first prompt; it does not cache message body as conversation content.
150
203
  let promptResult;
151
204
  const promptT = timeoutSignal(PROMPT_TIMEOUT_MS);
152
205
  try {
153
- promptResult = await client.session.promptAsync({
154
- sessionID,
155
- directory: sanitizedInput.directory,
156
- messageID: sanitizedInput.messageID,
157
- model: {
158
- providerID: sanitizedInput.model.providerID,
159
- modelID: sanitizedInput.model.modelID,
160
- },
161
- ...(sanitizedInput.agent ? { agent: sanitizedInput.agent } : {}),
162
- ...(sanitizedInput.variant ? { variant: sanitizedInput.variant } : {}),
163
- parts: sanitizedInput.parts,
164
- }, {
206
+ promptResult = await client.session.prompt(toV2PromptInput(sanitizedInput, sessionID), {
165
207
  signal: promptT.signal,
166
208
  });
167
209
  } catch (error) {
168
- const ambiguous = isTransportError(error);
169
- logger.warn(`[conversations] promptAsync throw (ambiguous=${ambiguous})`);
210
+ const { ambiguous, status } = classifyPromptFailure(error);
211
+ logger.warn(`[conversations] prompt throw (ambiguous=${ambiguous})`);
170
212
  if (ambiguous) {
171
213
  safeMark(markUserMessageSent, sessionID, logger);
172
214
  }
@@ -177,37 +219,25 @@ export const createConversationsService = (deps) => {
177
219
  messageID: sanitizedInput.messageID,
178
220
  ambiguous,
179
221
  error: safeErrorMessage('prompt'),
222
+ ...(Number.isFinite(status) ? { status } : {}),
180
223
  };
181
224
  } finally {
182
225
  promptT.clear();
183
226
  }
184
227
 
185
228
  // SDK result shape { data, error, response }
186
- if (promptResult.error) {
187
- const httpStatus = promptResult.response?.status;
188
- const ambiguous = isAmbiguousPromptError(promptResult.error, promptResult.response);
189
- logger.warn(`[conversations] promptAsync SDK error (${httpStatus || 'no status'}, ambiguous=${ambiguous})`);
190
- if (ambiguous) {
191
- safeMark(markUserMessageSent, sessionID, logger);
192
- }
193
- return {
194
- ok: false,
195
- phase: 'prompt',
196
- session,
197
- messageID: sanitizedInput.messageID,
198
- ambiguous,
199
- error: safeErrorMessage('prompt'),
200
- ...(Number.isFinite(httpStatus) ? { status: httpStatus } : {}),
201
- };
202
- }
203
-
204
- // promptAsync 204 success
229
+ // prompt success — return inbox item when present; never persist parts/text.
205
230
  safeMark(markUserMessageSent, sessionID, logger);
206
231
 
232
+ const inbox = promptResult && typeof promptResult === 'object'
233
+ ? promptResult
234
+ : undefined;
235
+
207
236
  return {
208
237
  ok: true,
209
238
  session,
210
239
  messageID: sanitizedInput.messageID,
240
+ ...(inbox && (inbox.id || inbox.type) ? { inbox } : {}),
211
241
  };
212
242
  };
213
243
 
@@ -1,15 +1,29 @@
1
1
  import { describe, expect, it, vi, beforeEach } from 'vitest';
2
2
  import { validateConversationInput } from './validation.js';
3
3
 
4
- // Mock the SDK module before any imports
5
- vi.mock('@opencode-ai/sdk/v2', () => ({
6
- createOpencodeClient: vi.fn(),
4
+ // Mock the official v2 client before any imports
5
+ vi.mock('@opencode/client', () => ({
6
+ OpenCode: { make: vi.fn() },
7
7
  }));
8
8
 
9
9
  // Lazy import to allow vi.mock to take effect
10
10
  const { createConversationsService } = await import('./service.js');
11
11
 
12
- const mockCreateOpencodeClient = (await import('@opencode-ai/sdk/v2')).createOpencodeClient;
12
+ const mockMake = (await import('@opencode/client')).OpenCode.make;
13
+
14
+ const clientError = (reason, cause) => {
15
+ const error = new Error(reason);
16
+ error.name = 'ClientError';
17
+ error.reason = reason;
18
+ if (cause !== undefined) error.cause = cause;
19
+ return error;
20
+ };
21
+
22
+ const httpError = (status, message) => {
23
+ const error = new Error(message);
24
+ error.status = status;
25
+ return error;
26
+ };
13
27
 
14
28
  describe('conversations validation', () => {
15
29
  it('rejects non-object body', () => {
@@ -270,7 +284,7 @@ describe('conversations service', () => {
270
284
  mockBuildUrl.mockReturnValue('http://127.0.0.1:4096/');
271
285
  mockGetHeaders.mockReturnValue({ Authorization: 'Basic test' });
272
286
  mockWaitReady.mockResolvedValue(undefined);
273
- mockCreateOpencodeClient.mockReset();
287
+ mockMake.mockReset();
274
288
  });
275
289
 
276
290
  const baseInput = (overrides = {}) => ({
@@ -295,9 +309,9 @@ describe('conversations service', () => {
295
309
  // --- create failures ---
296
310
 
297
311
  it('returns create failure with safe error when SDK returns error', async () => {
298
- mockCreateOpencodeClient.mockReturnValue({
312
+ mockMake.mockReturnValue({
299
313
  session: {
300
- create: async () => ({ data: null, error: new Error('internal: port 4096 unreachable'), response: { status: 500 } }),
314
+ create: async () => { throw clientError('UnexpectedStatus', { status: 500 }); },
301
315
  },
302
316
  });
303
317
  const service = createService();
@@ -311,7 +325,7 @@ describe('conversations service', () => {
311
325
  });
312
326
 
313
327
  it('returns create failure on transport error during create', async () => {
314
- mockCreateOpencodeClient.mockReturnValue({
328
+ mockMake.mockReturnValue({
315
329
  session: {
316
330
  create: async () => { throw new Error('Connection refused'); },
317
331
  },
@@ -325,15 +339,19 @@ describe('conversations service', () => {
325
339
 
326
340
  // --- create: no agent/model ---
327
341
 
328
- it('does NOT pass agent/model/variant to session.create', async () => {
329
- mockCreateOpencodeClient.mockReturnValue({
342
+ it('passes agent/model/variant to session.create', async () => {
343
+ mockMake.mockReturnValue({
330
344
  session: {
331
345
  create: async (params) => {
332
- expect(params.agent).toBeUndefined();
333
- expect(params.model).toBeUndefined();
334
- return { data: { id: 'ses_ok' }, error: null, response: { status: 200 } };
346
+ expect(params.agent).toBe('builder');
347
+ expect(params.model).toEqual({
348
+ id: 'gpt-4o',
349
+ providerID: 'openai',
350
+ variant: 'v1',
351
+ });
352
+ return { id: 'ses_ok' };
335
353
  },
336
- promptAsync: async () => ({ data: undefined, error: null, response: { status: 204 } }),
354
+ prompt: async () => undefined,
337
355
  },
338
356
  });
339
357
  const service = createService();
@@ -343,42 +361,71 @@ describe('conversations service', () => {
343
361
  expect(result.ok).toBe(true);
344
362
  });
345
363
 
346
- // --- promptAsync usage ---
364
+ // --- prompt usage ---
347
365
 
348
- it('uses promptAsync (not prompt) and does not pass delivery', async () => {
349
- let promptAsyncCalled = false;
350
- mockCreateOpencodeClient.mockReturnValue({
366
+ it('uses prompt with delivery=steer and does not cache message body', async () => {
367
+ let promptCalled = false;
368
+ mockMake.mockReturnValue({
351
369
  session: {
352
- create: async () => ({ data: { id: 'ses_pa' }, error: null, response: { status: 200 } }),
353
- promptAsync: async (params) => {
354
- promptAsyncCalled = true;
355
- expect(params.delivery).toBeUndefined();
356
- return { data: undefined, error: null, response: { status: 204 } };
370
+ create: async () => ({ id: 'ses_pa' }),
371
+ prompt: async (params) => {
372
+ promptCalled = true;
373
+ expect(params.delivery).toBe('steer');
374
+ return {
375
+ id: 'msg_test',
376
+ sessionID: 'ses_pa',
377
+ type: 'user',
378
+ delivery: 'steer',
379
+ payload: { text: 'hello' },
380
+ timeCreated: 1,
381
+ };
357
382
  },
358
383
  },
359
384
  });
360
385
  const service = createService();
361
386
  const result = await service.createAndPrompt({ sanitizedInput: baseInput() });
362
- expect(promptAsyncCalled).toBe(true);
387
+ expect(promptCalled).toBe(true);
363
388
  expect(result.ok).toBe(true);
389
+ expect(result.inbox).toEqual(expect.objectContaining({ id: 'msg_test', delivery: 'steer' }));
390
+ expect(result.parts).toBeUndefined();
391
+ expect(result.text).toBeUndefined();
364
392
  });
365
393
 
366
394
  // --- success ---
367
395
 
368
- it('returns success when create + promptAsync succeed', async () => {
396
+ it('validates and forwards skill ids with the first prompt', async () => {
397
+ const validated = validateConversationInput({
398
+ input: { type: 'prompt' },
399
+ ...baseInput({ parts: [{ type: 'text', text: '[skill:release] prepare notes' }], skills: [{ id: 'release', text: 'untrusted body' }] }),
400
+ });
401
+ expect(validated.valid).toBe(true);
402
+ expect(validated.sanitized.skills).toEqual([{ id: 'release' }]);
403
+ const prompt = vi.fn(async () => undefined);
404
+ mockMake.mockReturnValue({ session: { create: async () => ({ id: 'ses_skill' }), prompt } });
405
+ const result = await createService().createAndPrompt({ sanitizedInput: validated.sanitized });
406
+ expect(result.ok).toBe(true);
407
+ expect(prompt.mock.calls[0][0]).toMatchObject({
408
+ text: '[skill:release] prepare notes', skills: [{ id: 'release', name: 'release' }], delivery: 'steer',
409
+ });
410
+ });
411
+
412
+ it('returns success when create + prompt succeed', async () => {
369
413
  const sessionData = { id: 'ses_created', title: 'Test' };
370
- mockCreateOpencodeClient.mockReturnValue({
414
+ mockMake.mockReturnValue({
371
415
  session: {
372
416
  create: async (params) => {
373
- expect(params.directory).toBe('/test');
374
- return { data: sessionData, error: null, response: { status: 200 } };
417
+ expect(params.location).toEqual({ directory: '/test' });
418
+ expect(params.model).toEqual({ id: 'gpt-4o', providerID: 'openai' });
419
+ return sessionData;
375
420
  },
376
- promptAsync: async (params) => {
421
+ prompt: async (params) => {
377
422
  expect(params.sessionID).toBe('ses_created');
378
- expect(params.messageID).toBe('msg_test');
379
- expect(params.model).toEqual({ providerID: 'openai', modelID: 'gpt-4o' });
380
- expect(params.parts).toEqual([{ type: 'text', text: 'hello' }]);
381
- return { data: undefined, error: null, response: { status: 204 } };
423
+ expect(params.id).toBe('msg_test');
424
+ expect(params.text).toBe('hello');
425
+ expect(params.delivery).toBe('steer');
426
+ expect(params.model).toBeUndefined();
427
+ expect(params.parts).toBeUndefined();
428
+ return undefined;
382
429
  },
383
430
  },
384
431
  });
@@ -390,14 +437,19 @@ describe('conversations service', () => {
390
437
  expect(mockMarkSent).toHaveBeenCalledWith('ses_created');
391
438
  });
392
439
 
393
- it('passes agent, variant to promptAsync', async () => {
394
- mockCreateOpencodeClient.mockReturnValue({
440
+ it('passes agent, variant to session.create and not prompt', async () => {
441
+ mockMake.mockReturnValue({
395
442
  session: {
396
- create: async () => ({ data: { id: 'ses_avd' }, error: null, response: { status: 200 } }),
397
- promptAsync: async (params) => {
443
+ create: async (params) => {
398
444
  expect(params.agent).toBe('builder');
399
- expect(params.variant).toBe('fast');
400
- return { data: undefined, error: null, response: { status: 204 } };
445
+ expect(params.model).toEqual({ id: 'gpt-4o', providerID: 'openai', variant: 'fast' });
446
+ return { id: 'ses_avd' };
447
+ },
448
+ prompt: async (params) => {
449
+ expect(params.agent).toBeUndefined();
450
+ expect(params.variant).toBeUndefined();
451
+ expect(params.delivery).toBe('steer');
452
+ return undefined;
401
453
  },
402
454
  },
403
455
  });
@@ -412,14 +464,10 @@ describe('conversations service', () => {
412
464
 
413
465
  it('returns prompt failure with ambiguous=false for 400 SDK error, safe error, no mark', async () => {
414
466
  const sessionData = { id: 'ses_400' };
415
- mockCreateOpencodeClient.mockReturnValue({
467
+ mockMake.mockReturnValue({
416
468
  session: {
417
- create: async () => ({ data: sessionData, error: null, response: { status: 200 } }),
418
- promptAsync: async () => ({
419
- data: null,
420
- error: new Error('path /foo/bar does not exist'),
421
- response: { status: 400 },
422
- }),
469
+ create: async () => sessionData,
470
+ prompt: async () => { throw httpError(400, 'path /foo/bar does not exist'); },
423
471
  },
424
472
  });
425
473
  const service = createService();
@@ -433,14 +481,10 @@ describe('conversations service', () => {
433
481
  });
434
482
 
435
483
  it('returns prompt failure with ambiguous=false for 404, no mark', async () => {
436
- mockCreateOpencodeClient.mockReturnValue({
484
+ mockMake.mockReturnValue({
437
485
  session: {
438
- create: async () => ({ data: { id: 'ses_404' }, error: null, response: { status: 200 } }),
439
- promptAsync: async () => ({
440
- data: null,
441
- error: new Error('Session not found'),
442
- response: { status: 404 },
443
- }),
486
+ create: async () => ({ id: 'ses_404' }),
487
+ prompt: async () => { throw httpError(404, 'Session not found'); },
444
488
  },
445
489
  });
446
490
  const service = createService();
@@ -456,14 +500,10 @@ describe('conversations service', () => {
456
500
 
457
501
  it('returns ambiguous=true for 503, safe error, marks activity', async () => {
458
502
  const sessionData = { id: 'ses_503' };
459
- mockCreateOpencodeClient.mockReturnValue({
503
+ mockMake.mockReturnValue({
460
504
  session: {
461
- create: async () => ({ data: sessionData, error: null, response: { status: 200 } }),
462
- promptAsync: async () => ({
463
- data: null,
464
- error: new Error('Service Unavailable'),
465
- response: { status: 503 },
466
- }),
505
+ create: async () => sessionData,
506
+ prompt: async () => { throw clientError('UnexpectedStatus', { status: 503 }); },
467
507
  },
468
508
  });
469
509
  const service = createService();
@@ -477,10 +517,10 @@ describe('conversations service', () => {
477
517
  });
478
518
 
479
519
  it('returns ambiguous=true for transport throw, marks activity', async () => {
480
- mockCreateOpencodeClient.mockReturnValue({
520
+ mockMake.mockReturnValue({
481
521
  session: {
482
- create: async () => ({ data: { id: 'ses_transport' }, error: null, response: { status: 200 } }),
483
- promptAsync: async () => { throw new TypeError('fetch failed'); },
522
+ create: async () => ({ id: 'ses_transport' }),
523
+ prompt: async () => { throw new TypeError('fetch failed'); },
484
524
  },
485
525
  });
486
526
  const service = createService();
@@ -493,14 +533,10 @@ describe('conversations service', () => {
493
533
  });
494
534
 
495
535
  it('returns ambiguous=true for 408, marks activity', async () => {
496
- mockCreateOpencodeClient.mockReturnValue({
536
+ mockMake.mockReturnValue({
497
537
  session: {
498
- create: async () => ({ data: { id: 'ses_408' }, error: null, response: { status: 200 } }),
499
- promptAsync: async () => ({
500
- data: null,
501
- error: new Error('Request Timeout'),
502
- response: { status: 408 },
503
- }),
538
+ create: async () => ({ id: 'ses_408' }),
539
+ prompt: async () => { throw clientError('UnexpectedStatus', { status: 408 }); },
504
540
  },
505
541
  });
506
542
  const service = createService();
@@ -511,14 +547,10 @@ describe('conversations service', () => {
511
547
  });
512
548
 
513
549
  it('returns ambiguous=true for 429, marks activity', async () => {
514
- mockCreateOpencodeClient.mockReturnValue({
550
+ mockMake.mockReturnValue({
515
551
  session: {
516
- create: async () => ({ data: { id: 'ses_429' }, error: null, response: { status: 200 } }),
517
- promptAsync: async () => ({
518
- data: null,
519
- error: new Error('Too Many Requests'),
520
- response: { status: 429 },
521
- }),
552
+ create: async () => ({ id: 'ses_429' }),
553
+ prompt: async () => { throw clientError('UnexpectedStatus', { status: 429 }); },
522
554
  },
523
555
  });
524
556
  const service = createService();
@@ -529,10 +561,10 @@ describe('conversations service', () => {
529
561
  });
530
562
 
531
563
  it('does NOT mark on non-transport, non-ambiguous throw', async () => {
532
- mockCreateOpencodeClient.mockReturnValue({
564
+ mockMake.mockReturnValue({
533
565
  session: {
534
- create: async () => ({ data: { id: 'ses_perm' }, error: null, response: { status: 200 } }),
535
- promptAsync: async () => { throw new Error('Some permanent failure'); },
566
+ create: async () => ({ id: 'ses_perm' }),
567
+ prompt: async () => { throw new Error('Some permanent failure'); },
536
568
  },
537
569
  });
538
570
  const service = createService();
@@ -552,10 +584,10 @@ describe('conversations service', () => {
552
584
  markUserMessageSent: throwingMark,
553
585
  waitForOpenCodeReady: mockWaitReady,
554
586
  });
555
- mockCreateOpencodeClient.mockReturnValue({
587
+ mockMake.mockReturnValue({
556
588
  session: {
557
- create: async () => ({ data: { id: 'ses_safe' }, error: null, response: { status: 200 } }),
558
- promptAsync: async () => ({ data: undefined, error: null, response: { status: 204 } }),
589
+ create: async () => ({ id: 'ses_safe' }),
590
+ prompt: async () => undefined,
559
591
  },
560
592
  });
561
593
  const result = await svc.createAndPrompt({ sanitizedInput: baseInput() });
@@ -568,13 +600,13 @@ describe('conversations service', () => {
568
600
  it('passes directory/headers through buildOpenCodeUrl and getOpenCodeAuthHeaders', async () => {
569
601
  mockBuildUrl.mockReturnValue('http://custom:9999/');
570
602
  mockGetHeaders.mockReturnValue({ Authorization: 'Bearer secret123' });
571
- mockCreateOpencodeClient.mockImplementation((config) => {
603
+ mockMake.mockImplementation((config) => {
572
604
  expect(config.baseUrl).toBe('http://custom:9999');
573
605
  expect(config.headers).toEqual({ Authorization: 'Bearer secret123' });
574
606
  return {
575
607
  session: {
576
- create: async () => ({ data: { id: 'ses_url' }, error: null, response: { status: 200 } }),
577
- promptAsync: async () => ({ data: undefined, error: null, response: { status: 204 } }),
608
+ create: async () => ({ id: 'ses_url' }),
609
+ prompt: async () => undefined,
578
610
  },
579
611
  };
580
612
  });
@@ -585,10 +617,10 @@ describe('conversations service', () => {
585
617
 
586
618
  it('awaits waitForOpenCodeReady before calling SDK', async () => {
587
619
  let createCalled = false;
588
- mockCreateOpencodeClient.mockReturnValue({
620
+ mockMake.mockReturnValue({
589
621
  session: {
590
- create: async () => { createCalled = true; return { data: { id: 'ses_w' }, error: null, response: { status: 200 } }; },
591
- promptAsync: async () => ({ data: undefined, error: null, response: { status: 204 } }),
622
+ create: async () => { createCalled = true; return { id: 'ses_w' }; },
623
+ prompt: async () => undefined,
592
624
  },
593
625
  });
594
626
  const service = createService();
@@ -600,16 +632,16 @@ describe('conversations service', () => {
600
632
  // --- internal timeouts ---
601
633
 
602
634
  it('uses internal timeout signal for session.create', async () => {
603
- mockCreateOpencodeClient.mockReturnValue({
635
+ mockMake.mockReturnValue({
604
636
  session: {
605
637
  create: async (_params, opts) => {
606
638
  expect(opts.signal).toBeDefined();
607
639
  expect(opts.signal.aborted).toBe(false);
608
- return { data: { id: 'ses_to' }, error: null, response: { status: 200 } };
640
+ return { id: 'ses_to' };
609
641
  },
610
- promptAsync: async (_params, opts) => {
642
+ prompt: async (_params, opts) => {
611
643
  expect(opts.signal).toBeDefined();
612
- return { data: undefined, error: null, response: { status: 204 } };
644
+ return undefined;
613
645
  },
614
646
  },
615
647
  });
@@ -619,7 +651,7 @@ describe('conversations service', () => {
619
651
  });
620
652
 
621
653
  it('maps AbortError from create to create failure', async () => {
622
- mockCreateOpencodeClient.mockReturnValue({
654
+ mockMake.mockReturnValue({
623
655
  session: {
624
656
  create: async () => { throw new DOMException('Aborted', 'AbortError'); },
625
657
  },
@@ -631,11 +663,11 @@ describe('conversations service', () => {
631
663
  expect(result.error).toBe('Failed to create session');
632
664
  });
633
665
 
634
- it('maps AbortError from promptAsync to ambiguous=true', async () => {
635
- mockCreateOpencodeClient.mockReturnValue({
666
+ it('maps AbortError from prompt to ambiguous=true', async () => {
667
+ mockMake.mockReturnValue({
636
668
  session: {
637
- create: async () => ({ data: { id: 'ses_pto' }, error: null, response: { status: 200 } }),
638
- promptAsync: async () => { throw new DOMException('Aborted', 'AbortError'); },
669
+ create: async () => ({ id: 'ses_pto' }),
670
+ prompt: async () => { throw new DOMException('Aborted', 'AbortError'); },
639
671
  },
640
672
  });
641
673
  const service = createService();
@@ -649,9 +681,9 @@ describe('conversations service', () => {
649
681
  // --- create failure no session ID ---
650
682
 
651
683
  it('returns create failure when no session ID in response', async () => {
652
- mockCreateOpencodeClient.mockReturnValue({
684
+ mockMake.mockReturnValue({
653
685
  session: {
654
- create: async () => ({ data: null, error: null, response: { status: 200 } }),
686
+ create: async () => null,
655
687
  },
656
688
  });
657
689
  const service = createService();
@@ -1,6 +1,6 @@
1
1
  const ALLOWED_TOP_LEVEL_KEYS = new Set([
2
2
  'input', 'directory', 'messageID', 'model', 'parts',
3
- 'title', 'parentID', 'agent', 'variant', 'metadata',
3
+ 'title', 'parentID', 'agent', 'variant', 'metadata', 'skills',
4
4
  ]);
5
5
  const ALLOWED_PART_TYPES = new Set(['text', 'file', 'agent']);
6
6
 
@@ -139,6 +139,15 @@ export const validateConversationInput = (body) => {
139
139
  }
140
140
 
141
141
  // Optional fields
142
+ let skills;
143
+ if (body.skills !== undefined) {
144
+ if (!Array.isArray(body.skills) || body.skills.some((skill) =>
145
+ !skill || typeof skill.id !== 'string' || !skill.id.trim())) {
146
+ errors.push('skills must be an array of non-empty skill ids');
147
+ } else {
148
+ skills = body.skills.map((skill) => ({ id: skill.id }));
149
+ }
150
+ }
142
151
  const title = asOptionalNonEmptyString(body.title);
143
152
  if (title === null) errors.push('title must be a non-empty string if provided');
144
153
 
@@ -168,6 +177,7 @@ export const validateConversationInput = (body) => {
168
177
  modelID: body.model.modelID.trim(),
169
178
  },
170
179
  parts: sanitizedParts,
180
+ ...(skills?.length ? { skills } : {}),
171
181
  title,
172
182
  parentID,
173
183
  agent,