@openchambery/web 2.0.0-beta.1 → 2.0.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (287) hide show
  1. package/README.md +15 -11
  2. package/bin/cli-opencode.test.js +4 -4
  3. package/bin/cli.js +1 -1
  4. package/bin/lib/cli-startup.js +1 -1
  5. package/bin/lib/cli-startup.test.js +18 -4
  6. package/dist/assets/{AgentAvatar-CLWWNu_Q.js → AgentAvatar-Q1SmFRx2.js} +1 -1
  7. package/dist/assets/AssistantView-CwXkiCAd.js +3 -0
  8. package/dist/assets/{DiagramView-xuSJTnv7.js → DiagramView-BW92schO.js} +1 -1
  9. package/dist/assets/{FadeInOnReveal-Ov0D96d0.js → FadeInOnReveal-BZmwl-zE.js} +1 -1
  10. package/dist/assets/{Icon-DD-BuMfZ.js → Icon-AQPvxMiU.js} +1 -1
  11. package/dist/assets/{MarkdownRenderer-bc_p1H7l.js → MarkdownRenderer-Bv05iDZZ.js} +2 -2
  12. package/dist/assets/{MarkdownRendererImpl-SKi4oG0c.js → MarkdownRendererImpl-C81xqYib.js} +3 -3
  13. package/dist/assets/MarkstreamRendererImpl-CXUCQuKJ.js +3 -0
  14. package/dist/assets/{MobileDetailNavigation-Bnx59U_J.js → MobileDetailNavigation-BMgIu7zc.js} +1 -1
  15. package/dist/assets/{MobileShareBridge-BQlP4J-F.js → MobileShareBridge-BOXGNHmC.js} +2 -2
  16. package/dist/assets/MobileSurface-xuc_z0_G.js +2 -0
  17. package/dist/assets/{MultiRunWindow-ChZGob0v.js → MultiRunWindow-B5At2Z95.js} +1 -1
  18. package/dist/assets/{OnboardingScreen-DtQJxZG-.js → OnboardingScreen-bsPLgz4r.js} +1 -1
  19. package/dist/assets/OverlayScrollbar-4wp4migh.js +2 -0
  20. package/dist/assets/SettingsView-CJ6tcH5_.js +25 -0
  21. package/dist/assets/{SettingsWindow-DfnsFa8y.js → SettingsWindow-CMtX2bMl.js} +1 -1
  22. package/dist/assets/{TerminalView-Dh9MWCpS.js → TerminalView-Dvr-ysTS.js} +1 -1
  23. package/dist/assets/{ToolOutputDialog-BejmjrRk.js → ToolOutputDialog-uH1ZZDSS.js} +2 -2
  24. package/dist/assets/appThemeRegistry-DK7q2juD.js +1 -0
  25. package/dist/assets/{assistant-attachment-cache-C0aRnFkj.js → assistant-attachment-cache-DphJleHr.js} +1 -1
  26. package/dist/assets/{button-D-rATN7w.js → button-CcNJUxUd.js} +1 -1
  27. package/dist/assets/collapsible-ZmZ6rLWs.js +3 -0
  28. package/dist/assets/context-menu-B2LXJgXL.js +1 -0
  29. package/dist/assets/{desktopHosts-B1ixEylr.js → desktopHosts-C6HrP0jq.js} +1 -1
  30. package/dist/assets/{desktopRelayRestore-4JmFUw8r.js → desktopRelayRestore-DdVBp45U.js} +1 -1
  31. package/dist/assets/{diagram-DQf7Y_uR.js → diagram-CxTs9hCQ.js} +1 -1
  32. package/dist/assets/{dialog-bfbvR4VH.js → dialog-KMVL9QKF.js} +1 -1
  33. package/dist/assets/es-CN3E_zXj.js +15 -0
  34. package/dist/assets/{fileMentionTouchSelection-BvrNCroG.js → fileMentionTouchSelection-7RIwlpkB.js} +1 -1
  35. package/dist/assets/{fileQueries-DQc8EJUf.js → fileQueries-CjdBRy8y.js} +1 -1
  36. package/dist/assets/fr-9zZvQZD2.js +13 -0
  37. package/dist/assets/gitApi-yuWg9BN4.js +10 -0
  38. package/dist/assets/{imageSource-BlAJJUEI.js → imageSource-9vQBq3zp.js} +1 -1
  39. package/dist/assets/{input-8r6F_Fza.js → input-CrIvk3HZ.js} +1 -1
  40. package/dist/assets/insertionBoundaries-BiUCSkx-.js +1 -0
  41. package/dist/assets/ja-Dnjk_82N.js +15 -0
  42. package/dist/assets/ko-BxCEE99C.js +15 -0
  43. package/dist/assets/{main-CCE_4y5F.js → main-C9mIBo77.js} +2 -2
  44. package/dist/assets/main-DiL6w6KF.js +169 -0
  45. package/dist/assets/markdown-worker-bS5OgGO3.js +2 -0
  46. package/dist/assets/{markdownHeightCache-1Qo6y6IJ.js → markdownHeightCache-BmXVY_gc.js} +1 -1
  47. package/dist/assets/{markdownTheme-DOfGg-A7.js → markdownTheme-CKac3wKC.js} +1 -1
  48. package/dist/assets/mcpQueries-C7jknHV8.js +1 -0
  49. package/dist/assets/miniChat-CwtUaElU.js +2 -0
  50. package/dist/assets/mobile-DB-6--ws.js +2 -0
  51. package/dist/assets/multirun-TAsxFI8e.js +4 -0
  52. package/dist/assets/{persistence-Ce3AnpEr.js → persistence-56kq8Z4V.js} +2 -2
  53. package/dist/assets/pl-CNNsWnPM.js +15 -0
  54. package/dist/assets/platform-C13fMVEs.js +6 -0
  55. package/dist/assets/pt-BR-BZB2DJMp.js +15 -0
  56. package/dist/assets/{relay-image-stream-DM7atqJs.js → relay-image-stream-DrdVj99c.js} +1 -1
  57. package/dist/assets/renderElectronMiniChatApp-CGj0MNRU.js +2 -0
  58. package/dist/assets/renderMobileApp-CGMMqbsO.js +28 -0
  59. package/dist/assets/runtime-fetch-Fs9ZV2V8.js +1 -0
  60. package/dist/assets/{runtimeConfig-BMvqa5we.js → runtimeConfig-BEANkvOd.js} +1 -1
  61. package/dist/assets/runtimeEndpointReset-A_ZBUJFS.js +147 -0
  62. package/dist/assets/sessionChangeSummary-qOLrP9fw.js +2 -0
  63. package/dist/assets/{sessionLookup-gh-hVRIc.js → sessionLookup-34XFmP9C.js} +1 -1
  64. package/dist/assets/{sortable-tabs-strip-CudoSgV4.js → sortable-tabs-strip-DyEV0Heg.js} +1 -1
  65. package/dist/assets/src-ubCWXZhR.css +2 -0
  66. package/dist/assets/store-DLCiZaA6.js +15 -0
  67. package/dist/assets/{terminalApi-DiF6JmBP.js → terminalApi-CXaBEoa_.js} +1 -1
  68. package/dist/assets/textarea-BBOlRI9O.js +1 -0
  69. package/dist/assets/{tooltip-CQvpK-UV.js → tooltip-CPI4fr1a.js} +1 -1
  70. package/dist/assets/uk-rwQrxeYM.js +15 -0
  71. package/dist/assets/{url-_z7evzdK.js → url-DRDQXjyX.js} +1 -1
  72. package/dist/assets/{useAppFontEffects-Cpmpo7Wb.js → useAppFontEffects-CiDe42I5.js} +57 -43
  73. package/dist/assets/{useAssistantUIStore-CMutVh9B.js → useAssistantUIStore-asNG6K7n.js} +1 -1
  74. package/dist/assets/useDesktopWindowControlsLayout-BlizAlOt.js +1 -0
  75. package/dist/assets/useEffectiveDirectory-C_CouL5v.js +1 -0
  76. package/dist/assets/{useFontPreferences-DcbXQOkc.js → useFontPreferences-B0oUZAiB.js} +1 -1
  77. package/dist/assets/useMobileNavigationStore-DfPClaW-.js +414 -0
  78. package/dist/assets/{useRuntimeAPIs-DSLXJybW.js → useRuntimeAPIs-DwLtO0c_.js} +1 -1
  79. package/dist/assets/useSessionAutoCleanup-DtrfbxAp.js +2 -0
  80. package/dist/assets/{useThemeSystem-DMzWVJaW.js → useThemeSystem-BlNUWH47.js} +1 -1
  81. package/dist/assets/useUIStore-CQYKJnrg.js +1 -0
  82. package/dist/assets/{useWorkerHighlightedLines-Bqn4AiBL.js → useWorkerHighlightedLines-CPNYz6Wf.js} +2 -2
  83. package/dist/assets/{vendor-base-ui-CnHfUmDN.js → vendor-base-ui-CDjyofeD.js} +1 -1
  84. package/dist/assets/{vendor-cmdk-CeAGfrLo.js → vendor-cmdk-BFH0UpXX.js} +1 -1
  85. package/dist/assets/{vendor-dnd-kit-accessibility-BY5LhhUB.js → vendor-dnd-kit-accessibility-BrR3AKvM.js} +1 -1
  86. package/dist/assets/{vendor-dnd-kit-core-CvWxUPdb.js → vendor-dnd-kit-core-CNfdvk6H.js} +1 -1
  87. package/dist/assets/{vendor-dnd-kit-sortable-hHdP9lLb.js → vendor-dnd-kit-sortable-DQUaTeGj.js} +1 -1
  88. package/dist/assets/{vendor-framer-motion-DFy_BSVd.js → vendor-framer-motion-4dogsUk3.js} +1 -1
  89. package/dist/assets/{vendor-legendapp-list-WE32lwNk.js → vendor-legendapp-list-BU7MdPZR.js} +1 -1
  90. package/dist/assets/{vendor-markstream-react-DX55XLGO.js → vendor-markstream-react-qHC2FykU.js} +3 -3
  91. package/dist/assets/{vendor-pierre-diffs-DX7XTPG3.js → vendor-pierre-diffs-CUfqC003.js} +1 -1
  92. package/dist/assets/{vendor-react-DLPElpoQ.js → vendor-react-C94A0AHl.js} +1 -1
  93. package/dist/assets/{vendor-react-drawio-ECtEwvbc.js → vendor-react-drawio-FVlWqKVE.js} +1 -1
  94. package/dist/assets/{vendor-reactuses-core-BnW6GxJt.js → vendor-reactuses-core-C2A3EjUi.js} +1 -1
  95. package/dist/assets/{vendor-sonner-DVzioF3d.js → vendor-sonner-DNqHmUQs.js} +1 -1
  96. package/dist/assets/{vendor-stream-diffs-DWy3xSmp.js → vendor-stream-diffs-CwbKsmlv.js} +2 -2
  97. package/dist/assets/{vendor-tanstack-react-query-D7pys_c9.js → vendor-tanstack-react-query-D0iFK57C.js} +1 -1
  98. package/dist/assets/{vendor-tanstack-react-virtual-tlDgH6PG.js → vendor-tanstack-react-virtual-B2kV6cnT.js} +1 -1
  99. package/dist/assets/{vendor-zustand-B4dlGEM2.js → vendor-zustand-BqLKf6ZY.js} +1 -1
  100. package/dist/assets/zh-CN-BvmBDnkD.js +15 -0
  101. package/dist/assets/zh-TW-9wcHTX0y.js +15 -0
  102. package/dist/index.html +13 -13
  103. package/dist/mini-chat.html +13 -13
  104. package/dist/mobile.html +13 -13
  105. package/package.json +1 -1
  106. package/server/index.js +327 -62
  107. package/server/lib/assistants/DOCUMENTATION.md +14 -9
  108. package/server/lib/assistants/contracts.js +11 -1
  109. package/server/lib/assistants/projection-history.test.js +265 -18
  110. package/server/lib/assistants/routes.js +6 -0
  111. package/server/lib/assistants/service.js +417 -231
  112. package/server/lib/assistants/service.test.js +392 -148
  113. package/server/lib/conversations/DOCUMENTATION.md +1 -1
  114. package/server/lib/conversations/service.js +1 -0
  115. package/server/lib/conversations/service.test.js +16 -0
  116. package/server/lib/conversations/validation.js +11 -1
  117. package/server/lib/event-stream/DOCUMENTATION.md +3 -1
  118. package/server/lib/event-stream/directory-ws-bridge.js +13 -2
  119. package/server/lib/event-stream/global-hub.js +49 -4
  120. package/server/lib/event-stream/reasoning-projection.js +16 -1
  121. package/server/lib/event-stream/reasoning-projection.test.js +15 -0
  122. package/server/lib/event-stream/runtime.js +57 -20
  123. package/server/lib/event-stream/runtime.test.js +112 -0
  124. package/server/lib/llm/DOCUMENTATION.md +33 -9
  125. package/server/lib/llm/catalog.js +1 -1
  126. package/server/lib/llm/completions.js +5 -1
  127. package/server/lib/llm/completions.test.js +3 -3
  128. package/server/lib/llm/generate.js +237 -145
  129. package/server/lib/llm/generate.test.js +201 -46
  130. package/server/lib/llm/routes.js +2 -0
  131. package/server/lib/message-queue/runtime.js +15 -2
  132. package/server/lib/notifications/runtime.js +23 -5
  133. package/server/lib/notifications/runtime.test.js +11 -0
  134. package/server/lib/opencode/DOCUMENTATION.md +85 -17
  135. package/server/lib/opencode/agent-document.d.ts +28 -0
  136. package/server/lib/opencode/agent-document.js +280 -0
  137. package/server/lib/opencode/agent-document.test.js +137 -0
  138. package/server/lib/opencode/agents.js +96 -0
  139. package/server/lib/opencode/auth-state-runtime.js +5 -0
  140. package/server/lib/opencode/bootstrap-runtime.js +2 -0
  141. package/server/lib/opencode/config-entity-routes.js +42 -99
  142. package/server/lib/opencode/config-entity-routes.test.js +107 -0
  143. package/server/lib/opencode/config-hot-reload.integration.test.js +105 -0
  144. package/server/lib/opencode/ensure-cli.js +1 -2
  145. package/server/lib/opencode/env-runtime.js +40 -26
  146. package/server/lib/opencode/env-runtime.test.js +10 -17
  147. package/server/lib/opencode/feature-routes-runtime.js +43 -4
  148. package/server/lib/opencode/feature-routes-runtime.test.js +18 -0
  149. package/server/lib/opencode/http-server-connections.js +98 -0
  150. package/server/lib/opencode/http-server-connections.test.js +321 -0
  151. package/server/lib/opencode/lifecycle.js +349 -39
  152. package/server/lib/opencode/lifecycle.test.js +322 -0
  153. package/server/lib/opencode/managed-capabilities-runtime.js +15 -2
  154. package/server/lib/opencode/managed-capabilities-runtime.test.js +1 -0
  155. package/server/lib/opencode/openchamber-routes.js +7 -1
  156. package/server/lib/opencode/opencode2-pin.js +4 -4
  157. package/server/lib/opencode/opencode2-pin.test.js +1 -0
  158. package/server/lib/opencode/opencode2-sidecar.e2e.test.js +127 -4
  159. package/server/lib/opencode/owned-runtime-upgrade.js +431 -0
  160. package/server/lib/opencode/owned-runtime-upgrade.test.js +160 -0
  161. package/server/lib/opencode/plugin-routes.js +3 -18
  162. package/server/lib/opencode/plugin-routes.test.js +9 -9
  163. package/server/lib/opencode/provider-catalog.js +58 -12
  164. package/server/lib/opencode/provider-catalog.test.js +48 -0
  165. package/server/lib/opencode/proxy.js +252 -46
  166. package/server/lib/opencode/proxy.test.js +235 -2
  167. package/server/lib/opencode/routes.js +296 -62
  168. package/server/lib/opencode/routes.test.js +278 -12
  169. package/server/lib/opencode/runtime-contract.js +401 -0
  170. package/server/lib/opencode/runtime-contract.test.js +214 -0
  171. package/server/lib/opencode/server-opencode-fetch.js +242 -0
  172. package/server/lib/opencode/server-opencode-fetch.test.js +130 -0
  173. package/server/lib/opencode/server-utils-runtime.js +33 -0
  174. package/server/lib/opencode/shared-service.integration.test.js +66 -0
  175. package/server/lib/opencode/shared-service.js +147 -0
  176. package/server/lib/opencode/shared-service.test.js +93 -0
  177. package/server/lib/opencode/shared.js +3 -3
  178. package/server/lib/opencode/shutdown-runtime.js +15 -6
  179. package/server/lib/opencode/shutdown-runtime.test.js +49 -1
  180. package/server/lib/opencode/skill-routes.js +20 -33
  181. package/server/lib/opencode/skill-routes.test.js +35 -2
  182. package/server/lib/opencode/skills.test.js +15 -0
  183. package/server/lib/opencode/startup-pipeline-runtime.js +4 -0
  184. package/server/lib/opencode/static-routes-runtime.js +3 -1
  185. package/server/lib/opencode/static-routes-runtime.test.js +33 -0
  186. package/server/lib/opencode/v2-client.js +14 -3
  187. package/server/lib/opencode/v2-plugin-host-shim/index.mjs +155 -0
  188. package/server/lib/opencode/v2-plugin-host-shim/package.json +4 -0
  189. package/server/lib/permission-auto-accept/DOCUMENTATION.md +5 -5
  190. package/server/lib/permission-auto-accept/runtime.js +14 -7
  191. package/server/lib/permission-auto-accept/runtime.test.js +27 -12
  192. package/server/lib/relay/reasoning-projection.e2e.test.ts +22 -7
  193. package/server/lib/scheduled-tasks/DOCUMENTATION.md +8 -0
  194. package/server/lib/scheduled-tasks/runtime.js +52 -4
  195. package/server/lib/scheduled-tasks/runtime.test.js +80 -1
  196. package/server/lib/session-goal/DOCUMENTATION.md +113 -13
  197. package/server/lib/session-goal/routes.js +9 -0
  198. package/server/lib/session-goal/runtime.js +1579 -117
  199. package/server/lib/session-goal/runtime.test.js +1453 -84
  200. package/server/lib/session-index/DOCUMENTATION.md +18 -15
  201. package/server/lib/session-index/event-ingest.js +89 -16
  202. package/server/lib/session-index/event-ingest.test.js +73 -0
  203. package/server/lib/session-index/host-authority-snapshot.js +92 -0
  204. package/server/lib/session-index/routes.host-archive.test.js +201 -0
  205. package/server/lib/session-index/routes.js +63 -10
  206. package/server/lib/session-index/routes.test.js +8 -8
  207. package/server/lib/session-index/service.js +7 -1
  208. package/server/lib/session-index/sync-runtime.js +161 -34
  209. package/server/lib/session-index/sync-runtime.test.js +155 -1
  210. package/server/lib/session-metadata/DOCUMENTATION.md +61 -16
  211. package/server/lib/session-metadata/routes.js +108 -9
  212. package/server/lib/session-metadata/routes.test.js +47 -2
  213. package/server/lib/session-metadata/session-archive.d.ts +73 -0
  214. package/server/lib/session-metadata/session-archive.js +654 -0
  215. package/server/lib/session-metadata/session-archive.test.js +359 -0
  216. package/server/lib/session-metadata/session-metadata-store.d.ts +36 -0
  217. package/server/lib/session-metadata/session-metadata-store.js +212 -72
  218. package/server/lib/session-metadata/session-metadata-store.test.js +205 -10
  219. package/server/lib/session-metadata/session-projection.d.ts +41 -0
  220. package/server/lib/session-metadata/session-projection.js +255 -0
  221. package/server/lib/session-metadata/session-projection.test.js +64 -0
  222. package/server/lib/session-metadata/system-session.js +55 -0
  223. package/server/lib/session-metadata/system-session.test.js +55 -0
  224. package/server/lib/session-title/DOCUMENTATION.md +40 -7
  225. package/server/lib/session-title/native-runtime.test.js +167 -0
  226. package/server/lib/session-title/runtime.js +67 -42
  227. package/server/lib/session-title/session-access.js +46 -0
  228. package/server/lib/session-title/session-access.test.js +91 -0
  229. package/server/lib/session-turn-pages/DOCUMENTATION.md +9 -8
  230. package/server/lib/session-turn-pages/routes.js +17 -102
  231. package/server/lib/session-turn-pages/routes.test.js +281 -0
  232. package/server/lib/session-turn-pages/session-message-projection.d.ts +13 -0
  233. package/server/lib/session-turn-pages/session-message-projection.js +281 -0
  234. package/server/lib/small-model/DOCUMENTATION.md +101 -147
  235. package/server/lib/small-model/catalog.js +31 -51
  236. package/server/lib/small-model/catalog.test.js +65 -10
  237. package/server/lib/small-model/custom-api.js +68 -0
  238. package/server/lib/small-model/custom-api.test.js +64 -0
  239. package/server/lib/small-model/index.js +124 -240
  240. package/server/lib/small-model/resolve.js +35 -120
  241. package/server/lib/small-model/resolve.test.js +20 -110
  242. package/server/lib/small-model/routes.js +1 -3
  243. package/server/lib/small-model/summary-settings.test.js +157 -253
  244. package/server/opencode-proxy.test.js +2 -0
  245. package/dist/assets/AssistantView-BTCkm8yk.js +0 -3
  246. package/dist/assets/MarkstreamRendererImpl-CnkodGJW.js +0 -3
  247. package/dist/assets/MobileSurface-BXLeiZ4d.js +0 -2
  248. package/dist/assets/OverlayScrollbar-pMwxaCca.js +0 -2
  249. package/dist/assets/SettingsView-B5I0KDTZ.js +0 -25
  250. package/dist/assets/appThemeRegistry-COGfxTFV.js +0 -1
  251. package/dist/assets/collapsible-BXRd5_Hr.js +0 -1
  252. package/dist/assets/context-menu-jGtH6fR9.js +0 -1
  253. package/dist/assets/es-c52LTHHe.js +0 -15
  254. package/dist/assets/fr-PisFbUE8.js +0 -13
  255. package/dist/assets/gitApi-ButwzQ0N.js +0 -9
  256. package/dist/assets/insertionBoundaries-DGs7S5zv.js +0 -1
  257. package/dist/assets/ja-C8Ikun3Z.js +0 -15
  258. package/dist/assets/ko-ClFys5o2.js +0 -15
  259. package/dist/assets/main-Dn9rpsgl.js +0 -169
  260. package/dist/assets/markdown-worker-CptoICmq.js +0 -2
  261. package/dist/assets/miniChat-BpjxVqgg.js +0 -2
  262. package/dist/assets/mobile-nMJCaNq1.js +0 -2
  263. package/dist/assets/multirun-BvSG94mW.js +0 -4
  264. package/dist/assets/pl-CnaKWN5F.js +0 -15
  265. package/dist/assets/platform-CWTIp02q.js +0 -4
  266. package/dist/assets/projectMeta-DOe49dM_.js +0 -3
  267. package/dist/assets/pt-BR-s8OpfRI6.js +0 -15
  268. package/dist/assets/renderElectronMiniChatApp-R6dKh79f.js +0 -2
  269. package/dist/assets/renderMobileApp-y-S8y8v_.js +0 -28
  270. package/dist/assets/runtime-fetch-zIalUnbb.js +0 -1
  271. package/dist/assets/runtimeEndpointReset-B6BxhvR0.js +0 -147
  272. package/dist/assets/sessionChangeSummary-VVBlEXuh.js +0 -2
  273. package/dist/assets/src-BzX5UI-c.css +0 -2
  274. package/dist/assets/store-DJczmczv.js +0 -15
  275. package/dist/assets/textarea-3XR8sUfy.js +0 -1
  276. package/dist/assets/uk-CaX0iqkB.js +0 -15
  277. package/dist/assets/useDesktopWindowControlsLayout-B1GX59-L.js +0 -1
  278. package/dist/assets/useEffectiveDirectory-CN6JoCFw.js +0 -1
  279. package/dist/assets/useMobileNavigationStore-BVjFtXSy.js +0 -414
  280. package/dist/assets/useSessionAutoCleanup-BLjymaFt.js +0 -2
  281. package/dist/assets/useUIStore-Bxv3f23A.js +0 -1
  282. package/dist/assets/zh-CN-20MYEjha.js +0 -15
  283. package/dist/assets/zh-TW-DiN04qTw.js +0 -15
  284. package/server/lib/small-model/call.js +0 -427
  285. package/server/lib/small-model/call.test.js +0 -382
  286. package/server/lib/small-model/opencode-session.js +0 -183
  287. package/server/lib/small-model/opencode-session.test.js +0 -147
@@ -1,7 +1,12 @@
1
1
  import { OpenCode } from '@opencode/client';
2
+ import { buildLlmSessionMetadata } from '../session-metadata/system-session.js';
3
+ import { ensureLlmTempDirectory } from './temp-directory.js';
2
4
 
3
5
  const LLM_AGENT_NAME = 'openchamber-llm';
6
+ const TEXT_AGENT_NAME = 'openchamber-text';
4
7
  const GENERATE_TIMEOUT_MS = 90_000;
8
+ const AGENT_READY_WAIT_MS = 5_000;
9
+ const AGENT_READY_POLL_MS = 250;
5
10
  /** Single attachment payload cap (decoded bytes). Composer UI allows larger drafts; gateway enforces this. */
6
11
  export const MAX_ATTACHMENT_BYTES = 20 * 1024 * 1024;
7
12
  const MAX_INLINE_FILE_CHARS = 100_000;
@@ -31,6 +36,21 @@ Report execution and failures from supplied tool results only.
31
36
  Do not call native OpenCode tools, MCP tools, or skill tools. Native permissions are denied for this generator session.
32
37
  `;
33
38
 
39
+ /** Neutral deny-all agent for text-only `session.generate`; the caller's instructions travel in the prompt. */
40
+ const TEXT_AGENT_MARKDOWN = `---
41
+ mode: primary
42
+ hidden: true
43
+ permissions:
44
+ - action: "*"
45
+ resource: "*"
46
+ effect: deny
47
+ ---
48
+
49
+ Follow the instructions in the request and respond in the requested format.
50
+ Native OpenCode, MCP, and skill tools are denied for this generator session.
51
+ When the request defines an application tool-call format (such as fenced JSON), write it as plain text in your reply; the application executes it.
52
+ `;
53
+
34
54
  const failGenerate = (message, code = 'upstream_error') => {
35
55
  const error = new Error(message);
36
56
  error.code = code;
@@ -267,26 +287,52 @@ export function subscribeThrowawayTextDeltas({ globalEventHub, sessionID, onText
267
287
  }
268
288
 
269
289
  /**
270
- * Verify the openchamber-llm agent ends with deny-all permissions.
290
+ * OpenCode loads a new location's config lazily: the first requests against a
291
+ * fresh temp directory can report no agents at all. Poll `agent.get` within a
292
+ * bounded window instead of failing on that first read.
293
+ */
294
+ const readGeneratorAgent = async ({ client, location, signal, agentID, waitMs, pollMs }) => {
295
+ const deadline = Date.now() + waitMs;
296
+ for (;;) {
297
+ try {
298
+ return await client.agent.get({
299
+ agentID,
300
+ ...(location ? { location } : {}),
301
+ }, { signal });
302
+ } catch (error) {
303
+ rethrowIfAborted(error, signal);
304
+ if (Date.now() + pollMs > deadline) throw error;
305
+ await new Promise((resolve) => setTimeout(resolve, pollMs));
306
+ }
307
+ }
308
+ };
309
+
310
+ /**
311
+ * Verify the generator agent ends with deny-all permissions.
271
312
  * Title alone is never treated as isolation. Failure must happen before prompt.
272
313
  */
273
- export async function assertLlmAgentDenyAll({ client, location, signal }) {
314
+ export async function assertLlmAgentDenyAll({
315
+ client,
316
+ location,
317
+ signal,
318
+ agentID = LLM_AGENT_NAME,
319
+ waitMs = AGENT_READY_WAIT_MS,
320
+ pollMs = AGENT_READY_POLL_MS,
321
+ }) {
274
322
  let result;
275
323
  try {
276
- result = await client.agent.get({
277
- agentID: LLM_AGENT_NAME,
278
- ...(location ? { location } : {}),
279
- }, { signal });
324
+ result = await readGeneratorAgent({ client, location, signal, agentID, waitMs, pollMs });
280
325
  } catch (error) {
326
+ rethrowIfAborted(error, signal);
281
327
  failGenerate(
282
- `OpenCode LLM agent is unavailable (${clientErrorMessage(error, 'agent.get failed')}); attachment generation blocked`,
328
+ `OpenCode LLM agent is unavailable (${clientErrorMessage(error, 'agent.get failed')}); generation blocked`,
283
329
  'llm_attachment_generation_unavailable',
284
330
  );
285
331
  }
286
332
  const permissions = result?.data?.permissions;
287
333
  if (!Array.isArray(permissions) || permissions.length === 0) {
288
334
  failGenerate(
289
- 'OpenCode LLM agent permissions are missing; attachment generation blocked',
335
+ 'OpenCode LLM agent permissions are missing; generation blocked',
290
336
  'llm_attachment_generation_unavailable',
291
337
  );
292
338
  }
@@ -298,7 +344,7 @@ export async function assertLlmAgentDenyAll({ client, location, signal }) {
298
344
  || last.effect !== 'deny'
299
345
  ) {
300
346
  failGenerate(
301
- 'OpenCode LLM agent final permission is not deny-all; attachment generation blocked',
347
+ 'OpenCode LLM agent final permission is not deny-all; generation blocked',
302
348
  'llm_attachment_generation_unavailable',
303
349
  );
304
350
  }
@@ -339,63 +385,57 @@ const makeClient = ({ baseUrl, headers, clientFactory }) => {
339
385
  return OpenCode.make({ baseUrl, headers });
340
386
  };
341
387
 
342
- async function generateViaTextApi({
343
- client,
344
- location,
345
- providerID,
346
- modelID,
347
- variant,
348
- prompt,
349
- signal,
350
- }) {
351
- let result;
388
+ const prepareGeneratorDirectory = async ({ ensureTempDirectory, agentName, agentMarkdown }) => {
389
+ let workingDirectory;
352
390
  try {
353
- result = await client.generate.text({
354
- ...(location ? { location } : {}),
355
- prompt,
356
- model: modelRef(providerID, modelID, variant),
357
- }, { signal });
391
+ workingDirectory = await ensureTempDirectory({ agentName, agentMarkdown });
358
392
  } catch (error) {
359
- rethrowIfAborted(error, signal);
360
- failGenerate(clientErrorMessage(error, 'OpenCode generate.text failed'));
393
+ failGenerate(
394
+ `LLM temp directory failed (${clientErrorMessage(error, 'ensure failed')})`,
395
+ 'llm_attachment_generation_unavailable',
396
+ );
361
397
  }
362
- const text = typeof result?.text === 'string'
363
- ? result.text.trim()
364
- : typeof result?.data?.text === 'string'
365
- ? result.data.text.trim()
366
- : '';
367
- if (!text) failGenerate('OpenCode generate.text returned no text');
368
- return { text, source: 'generate.text' };
369
- }
398
+ if (typeof workingDirectory !== 'string' || !workingDirectory.trim()) {
399
+ failGenerate(
400
+ 'LLM temp directory is empty; generation blocked',
401
+ 'llm_attachment_generation_unavailable',
402
+ );
403
+ }
404
+ return workingDirectory.trim();
405
+ };
370
406
 
371
- async function generateViaAttachmentSession({
407
+ /**
408
+ * Run `work(sessionID)` inside a throwaway OpenCode session owned by a
409
+ * verified deny-all agent. The session is always removed afterwards;
410
+ * cancellation interrupts it first.
411
+ */
412
+ async function withThrowawaySession({
372
413
  client,
373
414
  workingDirectory,
415
+ agentName,
374
416
  providerID,
375
417
  modelID,
376
418
  variant,
377
- system,
378
- prompt,
379
- imageFiles,
380
419
  signal,
381
- onTextDelta,
382
- globalEventHub,
383
- }) {
420
+ persistSessionMetadata,
421
+ onSystemSessionPersisted,
422
+ }, work) {
384
423
  const location = { directory: workingDirectory };
385
424
 
386
425
  // Permission isolation must be verified before any prompt. Never use title as isolation.
387
- await assertLlmAgentDenyAll({ client, location, signal });
426
+ await assertLlmAgentDenyAll({ client, location, signal, agentID: agentName });
388
427
 
389
428
  let sessionID = null;
390
- let unsubscribeDeltas = null;
391
429
  try {
392
430
  let created;
431
+ const isolationMetadata = buildLlmSessionMetadata();
393
432
  try {
394
433
  created = await client.session.create({
395
434
  title: '[openchamber-llm] generate',
396
- agent: LLM_AGENT_NAME,
435
+ agent: agentName,
397
436
  model: modelRef(providerID, modelID, variant),
398
437
  location,
438
+ metadata: isolationMetadata,
399
439
  }, { signal });
400
440
  } catch (error) {
401
441
  // Temp workspace invisible to OpenCode must fail explicitly.
@@ -408,7 +448,72 @@ async function generateViaAttachmentSession({
408
448
  if (!sessionID) {
409
449
  failGenerate('OpenCode LLM session create returned no id', 'llm_attachment_generation_unavailable');
410
450
  }
451
+ if (typeof persistSessionMetadata === 'function') {
452
+ try {
453
+ await persistSessionMetadata(sessionID, isolationMetadata);
454
+ onSystemSessionPersisted?.({
455
+ sessionID,
456
+ directory: workingDirectory,
457
+ metadata: isolationMetadata,
458
+ });
459
+ } catch (error) {
460
+ console.warn('[llm] failed to persist system session metadata:', error?.message || error);
461
+ }
462
+ }
463
+ return await work(sessionID);
464
+ } finally {
465
+ if (sessionID) {
466
+ if (signal?.aborted) {
467
+ try {
468
+ await client.session.interrupt({ sessionID, continue: false });
469
+ } catch {
470
+ // Best-effort interrupt on timeout/abort.
471
+ }
472
+ }
473
+ try {
474
+ await client.session.remove({ sessionID });
475
+ } catch (error) {
476
+ console.warn('[llm] failed to remove throwaway OpenCode session:', error?.message || error);
477
+ }
478
+ }
479
+ }
480
+ }
411
481
 
482
+ /**
483
+ * Text-only generation through `session.generate`: one model call with the
484
+ * session's provider context (so session-scoped providers such as OpenCode Go
485
+ * work), no tool loop, and no messages written to the session.
486
+ */
487
+ async function generateViaSessionGenerate({ client, prompt, signal, ...sessionOptions }) {
488
+ return withThrowawaySession({ client, signal, ...sessionOptions }, async (sessionID) => {
489
+ let result;
490
+ try {
491
+ result = await client.session.generate({ sessionID, prompt }, { signal });
492
+ } catch (error) {
493
+ rethrowIfAborted(error, signal);
494
+ failGenerate(clientErrorMessage(error, 'OpenCode session.generate failed'));
495
+ }
496
+ const text = typeof result?.text === 'string'
497
+ ? result.text.trim()
498
+ : typeof result?.data?.text === 'string'
499
+ ? result.data.text.trim()
500
+ : '';
501
+ if (!text) failGenerate('OpenCode session.generate returned no text');
502
+ return { text, source: 'session.generate' };
503
+ });
504
+ }
505
+
506
+ async function generateViaAttachmentSession({
507
+ client,
508
+ system,
509
+ prompt,
510
+ imageFiles,
511
+ signal,
512
+ onTextDelta,
513
+ globalEventHub,
514
+ ...sessionOptions
515
+ }) {
516
+ return withThrowawaySession({ client, signal, ...sessionOptions }, async (sessionID) => {
412
517
  if (system) {
413
518
  try {
414
519
  await client.session.instructions.entry.put({
@@ -421,89 +526,81 @@ async function generateViaAttachmentSession({
421
526
  }
422
527
  }
423
528
 
424
- unsubscribeDeltas = subscribeThrowawayTextDeltas({
529
+ const unsubscribeDeltas = subscribeThrowawayTextDeltas({
425
530
  globalEventHub,
426
531
  sessionID,
427
532
  onTextDelta,
428
533
  });
429
-
430
- const files = imageFiles.map((part) => ({
431
- uri: part.url,
432
- ...(part.filename ? { name: part.filename } : {}),
433
- }));
434
-
435
534
  try {
436
- await client.session.prompt({
437
- sessionID,
438
- text: prompt || '[attachment]',
439
- ...(files.length > 0 ? { files } : {}),
440
- delivery: 'steer',
441
- }, { signal });
442
- } catch (error) {
443
- rethrowIfAborted(error, signal);
444
- failGenerate(clientErrorMessage(error, 'session.prompt failed'));
535
+ return await promptAttachmentSession({ client, sessionID, prompt, imageFiles, signal });
536
+ } finally {
537
+ try {
538
+ unsubscribeDeltas?.();
539
+ } catch {
540
+ // Unsubscribe must not mask generate errors or block session remove.
541
+ }
445
542
  }
543
+ });
544
+ }
446
545
 
447
- try {
448
- await client.session.wait({ sessionID }, { signal });
449
- } catch (error) {
450
- rethrowIfAborted(error, signal);
451
- failGenerate(clientErrorMessage(error, 'session.wait failed'));
452
- }
546
+ async function promptAttachmentSession({ client, sessionID, prompt, imageFiles, signal }) {
547
+ const files = imageFiles.map((part) => ({
548
+ uri: part.url,
549
+ ...(part.filename ? { name: part.filename } : {}),
550
+ }));
453
551
 
454
- let listed;
455
- try {
456
- listed = await client.message.list({
457
- sessionID,
458
- limit: 20,
459
- order: 'desc',
460
- }, { signal });
461
- } catch (error) {
462
- rethrowIfAborted(error, signal);
463
- failGenerate(clientErrorMessage(error, 'message.list failed'));
464
- }
552
+ try {
553
+ await client.session.prompt({
554
+ sessionID,
555
+ text: prompt || '[attachment]',
556
+ ...(files.length > 0 ? { files } : {}),
557
+ delivery: 'steer',
558
+ }, { signal });
559
+ } catch (error) {
560
+ rethrowIfAborted(error, signal);
561
+ failGenerate(clientErrorMessage(error, 'session.prompt failed'));
562
+ }
465
563
 
466
- const messages = Array.isArray(listed?.data) ? listed.data : [];
467
- const text = assistantTextFromMessages(messages);
468
- if (!text.trim()) {
469
- failGenerate('OpenCode LLM generator returned no assistant text');
470
- }
471
- return { text: text.trim(), source: 'attachment-session' };
472
- } finally {
473
- try {
474
- unsubscribeDeltas?.();
475
- } catch {
476
- // Unsubscribe must not mask generate errors or block session remove.
477
- }
478
- unsubscribeDeltas = null;
479
- if (sessionID) {
480
- if (signal?.aborted) {
481
- try {
482
- await client.session.interrupt({ sessionID, continue: false });
483
- } catch {
484
- // Best-effort interrupt on timeout/abort.
485
- }
486
- }
487
- try {
488
- await client.session.remove({ sessionID });
489
- } catch (error) {
490
- console.warn('[llm] failed to remove throwaway OpenCode session:', error?.message || error);
491
- }
492
- }
564
+ try {
565
+ await client.session.wait({ sessionID }, { signal });
566
+ } catch (error) {
567
+ rethrowIfAborted(error, signal);
568
+ failGenerate(clientErrorMessage(error, 'session.wait failed'));
569
+ }
570
+
571
+ let listed;
572
+ try {
573
+ listed = await client.message.list({
574
+ sessionID,
575
+ limit: 20,
576
+ order: 'desc',
577
+ }, { signal });
578
+ } catch (error) {
579
+ rethrowIfAborted(error, signal);
580
+ failGenerate(clientErrorMessage(error, 'message.list failed'));
581
+ }
582
+
583
+ const messages = Array.isArray(listed?.data) ? listed.data : [];
584
+ const text = assistantTextFromMessages(messages);
585
+ if (!text.trim()) {
586
+ failGenerate('OpenCode LLM generator returned no assistant text');
493
587
  }
588
+ return { text: text.trim(), source: 'attachment-session' };
494
589
  }
495
590
 
496
591
  /**
497
592
  * Generate assistant text through OpenCode's connected providers via
498
593
  * `@opencode/client`.
499
594
  *
500
- * - Pure text: `generate.text` (no session).
501
- * - Vision image attachments: dedicated temp session after agent deny-all verify.
595
+ * Both paths run in a throwaway session owned by a verified deny-all agent in
596
+ * the LLM temp directory, and the session is removed afterwards.
597
+ * - Pure text: `session.generate` (single model call, nothing written).
598
+ * - Vision image attachments: `session.prompt` + wait + message list.
502
599
  * - Non-vision: keep `[image: …]` descriptions; never forward image bytes.
503
600
  *
504
601
  * Optional internal streaming: `onTextDelta` + `globalEventHub` forwards real
505
602
  * `session.text.delta` tokens on the attachment-session path only.
506
- * `generate.text` cannot emit live deltas (no fake typewriter).
603
+ * `session.generate` cannot emit live deltas (no fake typewriter).
507
604
  *
508
605
  * Optional `signal` from the contact continuation is combined with the generator
509
606
  * deadline. Cancellation still runs throwaway-session cleanup.
@@ -516,10 +613,12 @@ export async function generateOpenCodeText({
516
613
  messages,
517
614
  variant,
518
615
  clientFactory,
519
- ensureTempDirectory,
616
+ ensureTempDirectory = ensureLlmTempDirectory,
520
617
  forwardImageParts = false,
521
618
  onTextDelta = null,
522
619
  globalEventHub = null,
620
+ persistSessionMetadata = null,
621
+ onSystemSessionPersisted = null,
523
622
  signal: parentSignal = null,
524
623
  timeoutMs = GENERATE_TIMEOUT_MS,
525
624
  }) {
@@ -555,56 +654,47 @@ export async function generateOpenCodeText({
555
654
  }, deadlineMs);
556
655
  timeout.unref?.();
557
656
 
657
+ const sessionOptions = {
658
+ client,
659
+ providerID,
660
+ modelID,
661
+ variant,
662
+ signal: requestSignal,
663
+ persistSessionMetadata,
664
+ onSystemSessionPersisted,
665
+ };
666
+
558
667
  try {
559
668
  if (imageFiles.length > 0) {
560
- if (typeof ensureTempDirectory !== 'function') {
561
- failGenerate(
562
- 'LLM temp directory is required for attachment generation',
563
- 'llm_attachment_generation_unavailable',
564
- );
565
- }
566
- let workingDirectory;
567
- try {
568
- workingDirectory = await ensureTempDirectory({
569
- agentName: LLM_AGENT_NAME,
570
- agentMarkdown: AGENT_MARKDOWN,
571
- });
572
- } catch (error) {
573
- failGenerate(
574
- `LLM temp directory failed (${clientErrorMessage(error, 'ensure failed')})`,
575
- 'llm_attachment_generation_unavailable',
576
- );
577
- }
578
- if (typeof workingDirectory !== 'string' || !workingDirectory.trim()) {
579
- failGenerate(
580
- 'LLM temp directory is empty; attachment generation blocked',
581
- 'llm_attachment_generation_unavailable',
582
- );
583
- }
669
+ const workingDirectory = await prepareGeneratorDirectory({
670
+ ensureTempDirectory,
671
+ agentName: LLM_AGENT_NAME,
672
+ agentMarkdown: AGENT_MARKDOWN,
673
+ });
584
674
  return await generateViaAttachmentSession({
585
- client,
586
- workingDirectory: workingDirectory.trim(),
587
- providerID,
588
- modelID,
589
- variant,
675
+ ...sessionOptions,
676
+ workingDirectory,
677
+ agentName: LLM_AGENT_NAME,
590
678
  system: flattened.system,
591
679
  prompt: flattened.prompt,
592
680
  imageFiles,
593
- signal: requestSignal,
594
681
  onTextDelta,
595
682
  globalEventHub,
596
683
  });
597
684
  }
598
685
 
599
- // Text path: no session. System prompt is prepended into the single prompt string.
686
+ // Text path: the system prompt is prepended into the single prompt string.
687
+ const workingDirectory = await prepareGeneratorDirectory({
688
+ ensureTempDirectory,
689
+ agentName: TEXT_AGENT_NAME,
690
+ agentMarkdown: TEXT_AGENT_MARKDOWN,
691
+ });
600
692
  const prompt = [flattened.system, flattened.prompt].filter((part) => String(part || '').trim()).join('\n\n');
601
- return await generateViaTextApi({
602
- client,
603
- providerID,
604
- modelID,
605
- variant,
693
+ return await generateViaSessionGenerate({
694
+ ...sessionOptions,
695
+ workingDirectory,
696
+ agentName: TEXT_AGENT_NAME,
606
697
  prompt,
607
- signal: requestSignal,
608
698
  });
609
699
  } finally {
610
700
  clearTimeout(timeout);
@@ -625,6 +715,8 @@ export const _test = {
625
715
  assertValidAttachmentParts,
626
716
  LLM_AGENT_NAME,
627
717
  AGENT_MARKDOWN,
718
+ TEXT_AGENT_NAME,
719
+ TEXT_AGENT_MARKDOWN,
628
720
  MAX_ATTACHMENT_BYTES,
629
721
  GENERATE_TIMEOUT_MS,
630
722
  };