@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
@@ -0,0 +1,93 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import path from 'node:path';
3
+ import { describe, expect, it, vi } from 'vitest';
4
+
5
+ import {
6
+ buildSharedServiceStartEnv,
7
+ isSharedServiceEnabled,
8
+ parseSharedServiceRegistration,
9
+ readSharedServiceRegistration,
10
+ resolveSharedServiceRegistrationPath,
11
+ startSharedOpenCodeService,
12
+ } from './shared-service.js';
13
+
14
+ const fakeChild = () => {
15
+ const child = new EventEmitter();
16
+ child.stderr = new EventEmitter();
17
+ child.kill = vi.fn();
18
+ return child;
19
+ };
20
+
21
+ describe('shared OpenCode service registration', () => {
22
+ it('resolves the stable-channel registration under the XDG state root', () => {
23
+ expect(resolveSharedServiceRegistrationPath({}, '/home/u')).toBe(path.join('/home/u', '.local', 'state', 'opencode', 'service.json'));
24
+ expect(resolveSharedServiceRegistrationPath({ XDG_STATE_HOME: '/xdg' }, '/home/u')).toBe(path.join('/xdg', 'opencode', 'service.json'));
25
+ });
26
+
27
+ it('parses a registration and rejects malformed ones', () => {
28
+ expect(parseSharedServiceRegistration(JSON.stringify({
29
+ id: 'a', version: '2.0.12', url: 'http://127.0.0.1:49374', pid: 42, password: 'pw',
30
+ }))).toEqual({ origin: 'http://127.0.0.1:49374', port: 49374, pid: 42, password: 'pw', version: '2.0.12' });
31
+ expect(parseSharedServiceRegistration(JSON.stringify({ url: 'http://127.0.0.1:49374' }))?.password).toBeNull();
32
+ expect(parseSharedServiceRegistration('not json')).toBeNull();
33
+ expect(parseSharedServiceRegistration(JSON.stringify({ url: 'ftp://x:1' }))).toBeNull();
34
+ expect(parseSharedServiceRegistration(JSON.stringify({ url: 'http://127.0.0.1' }))).toBeNull();
35
+ });
36
+
37
+ it('treats a missing registration file as absent, not as a service', async () => {
38
+ const fsLike = { readFile: vi.fn(async () => { throw Object.assign(new Error('nope'), { code: 'ENOENT' }); }) };
39
+ await expect(readSharedServiceRegistration({ file: '/x/service.json', fsLike })).resolves.toBeNull();
40
+ });
41
+
42
+ it('is enabled by default and can be opted out', () => {
43
+ expect(isSharedServiceEnabled({})).toBe(true);
44
+ expect(isSharedServiceEnabled({ OPENCHAMBER_OPENCODE_SHARED_SERVICE: '0' })).toBe(false);
45
+ expect(isSharedServiceEnabled({ OPENCHAMBER_OPENCODE_SHARED_SERVICE: 'false' })).toBe(false);
46
+ });
47
+ });
48
+
49
+ describe('shared OpenCode service start', () => {
50
+ it('never hands OpenChamber credentials to the long-lived service', () => {
51
+ expect(buildSharedServiceStartEnv({
52
+ PATH: '/bin',
53
+ HOME: '/home/u',
54
+ OPENCODE_SERVER_PASSWORD: 'managed',
55
+ OPENCODE_PASSWORD: 'managed',
56
+ OPENCHAMBER_UI_PASSWORD: 'ui',
57
+ OPENCHAMBER_DATA_DIR: '/data',
58
+ })).toEqual({ PATH: '/bin', HOME: '/home/u' });
59
+ });
60
+
61
+ it('runs `service start` and resolves on a clean exit', async () => {
62
+ const child = fakeChild();
63
+ const spawnImpl = vi.fn(() => {
64
+ queueMicrotask(() => child.emit('exit', 0));
65
+ return child;
66
+ });
67
+ await startSharedOpenCodeService({ binary: '/bin/opencode', args: ['--x'], env: { PATH: '/bin' }, spawnImpl });
68
+ expect(spawnImpl).toHaveBeenCalledWith('/bin/opencode', ['--x', 'service', 'start'], expect.objectContaining({ env: { PATH: '/bin' } }));
69
+ });
70
+
71
+ it('surfaces the CLI error when `service start` fails', async () => {
72
+ const child = fakeChild();
73
+ const spawnImpl = vi.fn(() => {
74
+ queueMicrotask(() => {
75
+ child.stderr.emit('data', 'Background service failed to start');
76
+ child.emit('exit', 1);
77
+ });
78
+ return child;
79
+ });
80
+ await expect(startSharedOpenCodeService({ binary: 'opencode', env: {}, spawnImpl }))
81
+ .rejects.toThrow(/exited with code 1: Background service failed to start/);
82
+ });
83
+
84
+ it('replaces the registered service only for an explicit upgrade', async () => {
85
+ const child = fakeChild();
86
+ const spawnImpl = vi.fn(() => {
87
+ queueMicrotask(() => child.emit('exit', 0));
88
+ return child;
89
+ });
90
+ await startSharedOpenCodeService({ binary: '/cache/opencode', env: {}, replace: true, spawnImpl });
91
+ expect(spawnImpl).toHaveBeenCalledWith('/cache/opencode', ['service', 'restart'], expect.objectContaining({ windowsHide: true }));
92
+ });
93
+ });
@@ -371,9 +371,9 @@ function addSkillFromMdFile(skillsMap, skillMdPath, scope, source) {
371
371
  return;
372
372
  }
373
373
 
374
- const name = typeof parsed.frontmatter?.name === 'string'
375
- ? parsed.frontmatter.name.trim()
376
- : '';
374
+ const name = path.basename(skillMdPath) === 'SKILL.md'
375
+ ? path.basename(path.dirname(skillMdPath))
376
+ : path.basename(skillMdPath, '.md');
377
377
  const description = typeof parsed.frontmatter?.description === 'string'
378
378
  ? parsed.frontmatter.description
379
379
  : '';
@@ -1,3 +1,5 @@
1
+ import { forceCloseHttpServerConnections } from './http-server-connections.js';
2
+
1
3
  export const createGracefulShutdownRuntime = (dependencies) => {
2
4
  const {
3
5
  process,
@@ -22,7 +24,6 @@ export const createGracefulShutdownRuntime = (dependencies) => {
22
24
  getOpenCodePort,
23
25
  getOpenCodeProcess,
24
26
  setOpenCodeProcess,
25
- killProcessOnPort,
26
27
  waitForPortRelease,
27
28
  getServer,
28
29
  getUiAuthController,
@@ -78,12 +79,14 @@ export const createGracefulShutdownRuntime = (dependencies) => {
78
79
  }
79
80
 
80
81
  if (!shouldSkipOpenCodeStop()) {
81
- const portToKill = getOpenCodePort();
82
+ const portToRelease = getOpenCodePort();
82
83
  const openCodeProcess = getOpenCodeProcess();
83
84
 
84
85
  if (openCodeProcess) {
85
86
  console.log('Stopping OpenCode process...');
86
87
  try {
88
+ // close() owns SIGTERM→SIGKILL on this child handle. Never re-kill a
89
+ // numeric pid captured before close — it may already be recycled.
87
90
  await openCodeProcess.close();
88
91
  } catch (error) {
89
92
  console.warn('Error closing OpenCode process:', error);
@@ -91,9 +94,9 @@ export const createGracefulShutdownRuntime = (dependencies) => {
91
94
  setOpenCodeProcess(null);
92
95
  }
93
96
 
94
- killProcessOnPort(portToKill);
95
- if (!(await waitForPortRelease(portToKill, 5000))) {
96
- console.warn(`Timed out waiting for OpenCode port ${portToKill} to be released during shutdown`);
97
+ // Probe only after owned close. Do not mass-kill by port (clients / external).
98
+ if (!(await waitForPortRelease(portToRelease, 5000))) {
99
+ console.warn(`Timed out waiting for OpenCode port ${portToRelease} to be released during shutdown`);
97
100
  }
98
101
  } else {
99
102
  console.log('Skipping OpenCode shutdown (external server)');
@@ -105,12 +108,18 @@ export const createGracefulShutdownRuntime = (dependencies) => {
105
108
  try {
106
109
  await Promise.race([
107
110
  new Promise((resolve) => {
111
+ // Stop accepting first. closeAllConnections drops plain HTTP sockets
112
+ // but leaves upgraded WS/SSE; destroy tracked server-owned leftovers
113
+ // so the close callback can fire without waiting SHUTDOWN_TIMEOUT.
108
114
  server.close(() => {
109
115
  console.log('HTTP server closed');
110
116
  resolve();
111
117
  });
112
118
  if (options.forceCloseConnections === true) {
113
- server.closeAllConnections?.();
119
+ const { destroyedTracked } = forceCloseHttpServerConnections(server);
120
+ if (destroyedTracked > 0) {
121
+ console.log(`Forced close of ${destroyedTracked} remaining server socket(s)`);
122
+ }
114
123
  }
115
124
  }),
116
125
  new Promise((resolve) => {
@@ -22,7 +22,6 @@ const createRuntime = (server, shutdownTimeoutMs = 1000, overrides = {}) => crea
22
22
  getOpenCodePort: () => null,
23
23
  getOpenCodeProcess: () => null,
24
24
  setOpenCodeProcess: vi.fn(),
25
- killProcessOnPort: vi.fn(),
26
25
  waitForPortRelease: vi.fn(async () => true),
27
26
  getServer: () => server,
28
27
  getUiAuthController: () => null,
@@ -96,4 +95,53 @@ describe('graceful shutdown runtime', () => {
96
95
  expect(scheduledTasksRuntime.stop).toHaveBeenCalledOnce();
97
96
  expect(runHistoryStore.close).not.toHaveBeenCalled();
98
97
  });
98
+
99
+ it('awaits managed OpenCode close with zero extra kill, then probes port release for 5s', async () => {
100
+ const close = vi.fn(async () => undefined);
101
+ const waitForPortRelease = vi.fn(async (_port, timeoutMs) => {
102
+ expect(timeoutMs).toBe(5000);
103
+ return true;
104
+ });
105
+ const setOpenCodeProcess = vi.fn();
106
+ const server = {
107
+ close: vi.fn((callback) => {
108
+ callback();
109
+ }),
110
+ };
111
+
112
+ const runtime = createRuntime(server, 1000, {
113
+ shouldSkipOpenCodeStop: () => false,
114
+ getOpenCodePort: () => 45678,
115
+ getOpenCodeProcess: () => ({ close, pid: 99 }),
116
+ setOpenCodeProcess,
117
+ waitForPortRelease,
118
+ });
119
+ await runtime.gracefulShutdown({ exitProcess: false });
120
+
121
+ expect(close).toHaveBeenCalledOnce();
122
+ expect(setOpenCodeProcess).toHaveBeenCalledWith(null);
123
+ expect(waitForPortRelease).toHaveBeenCalledWith(45678, 5000);
124
+ });
125
+
126
+ it('does not kill anything when OpenCode close already finished and a later server stage fails', async () => {
127
+ const close = vi.fn(async () => undefined);
128
+ const waitForPortRelease = vi.fn(async () => true);
129
+ const server = {
130
+ close: vi.fn(() => {
131
+ throw new Error('server close blew up after opencode stop');
132
+ }),
133
+ };
134
+
135
+ const runtime = createRuntime(server, 1000, {
136
+ shouldSkipOpenCodeStop: () => false,
137
+ getOpenCodePort: () => 45678,
138
+ getOpenCodeProcess: () => ({ close, pid: 99 }),
139
+ setOpenCodeProcess: vi.fn(),
140
+ waitForPortRelease,
141
+ });
142
+
143
+ await expect(runtime.gracefulShutdown({ exitProcess: false })).rejects.toThrow(/server close blew up/);
144
+ expect(close).toHaveBeenCalledOnce();
145
+ expect(waitForPortRelease).toHaveBeenCalledWith(45678, 5000);
146
+ });
99
147
  });
@@ -1,4 +1,4 @@
1
- import { createOpencodeClient } from '@opencode-ai/sdk/v2';
1
+ import { OpenCode } from '@opencode/client';
2
2
 
3
3
  const summarizeDescription = (value) => {
4
4
  const normalized = typeof value === 'string' ? value.replace(/\s+/gu, ' ').trim() : '';
@@ -16,8 +16,6 @@ export const registerSkillRoutes = (app, dependencies) => {
16
16
  readSettingsFromDisk,
17
17
  sanitizeSkillCatalogs,
18
18
  isUnsafeSkillRelativePath,
19
- refreshOpenCodeAfterConfigChange,
20
- clientReloadDelayMs,
21
19
  buildOpenCodeUrl,
22
20
  getOpenCodeAuthHeaders,
23
21
  getOpenCodePort,
@@ -127,25 +125,26 @@ export const registerSkillRoutes = (app, dependencies) => {
127
125
  }
128
126
 
129
127
  try {
130
- const client = createOpencodeClient({
128
+ const client = OpenCode.make({
131
129
  baseUrl: buildOpenCodeUrl('/', '').replace(/\/$/, ''),
132
- directory: workingDirectory || undefined,
133
130
  headers: getOpenCodeAuthHeaders(),
134
- fetch: (request) => fetch(request, { signal: AbortSignal.timeout(8_000) }),
131
+ fetch: globalThis.fetch,
135
132
  });
136
133
 
137
- const response = await client.app.skills(
138
- workingDirectory ? { directory: workingDirectory } : undefined,
134
+ const response = await client.skill.list(
135
+ workingDirectory ? { location: { directory: workingDirectory } } : undefined,
136
+ { signal: AbortSignal.timeout(8_000) },
139
137
  );
140
138
  const payload = response?.data;
141
139
  if (!Array.isArray(payload)) {
142
- return [];
140
+ throw new Error('OpenCode skill catalog is unavailable');
143
141
  }
144
142
 
145
143
  return payload
146
144
  .map((item) => {
147
- const name = typeof item?.name === 'string' ? item.name.trim() : '';
148
- const location = typeof item?.location === 'string' ? item.location : '';
145
+ // OpenChamber's name is the invocation key, not the V2 display label.
146
+ const name = typeof item?.id === 'string' ? item.id : '';
147
+ const location = typeof item?.path === 'string' ? item.path : '';
149
148
  const description = typeof item?.description === 'string' ? item.description : '';
150
149
  const content = typeof item?.content === 'string' ? item.content : '';
151
150
  if (!name || !location) {
@@ -177,7 +176,7 @@ export const registerSkillRoutes = (app, dependencies) => {
177
176
  .filter(Boolean);
178
177
  } catch (error) {
179
178
  console.error('Failed to list OpenCode skills:', error);
180
- return [];
179
+ throw error;
181
180
  }
182
181
  };
183
182
 
@@ -462,19 +461,14 @@ export const registerSkillRoutes = (app, dependencies) => {
462
461
 
463
462
  const installed = result.installed || [];
464
463
  const skipped = result.skipped || [];
465
- const requiresReload = installed.length > 0;
466
-
467
- if (requiresReload) {
468
- await refreshOpenCodeAfterConfigChange('skills install');
469
- }
470
464
 
471
465
  return res.json({
472
466
  ok: true,
473
467
  installed,
474
468
  skipped,
475
- requiresReload,
476
- message: requiresReload ? 'Skills installed successfully. Reloading interface…' : 'No skills were installed',
477
- reloadDelayMs: requiresReload ? clientReloadDelayMs : undefined,
469
+ requiresReload: false,
470
+ application: 'watch',
471
+ message: installed.length > 0 ? 'Skills installed successfully.' : 'No skills were installed',
478
472
  });
479
473
  }
480
474
 
@@ -513,19 +507,14 @@ export const registerSkillRoutes = (app, dependencies) => {
513
507
 
514
508
  const installed = result.installed || [];
515
509
  const skipped = result.skipped || [];
516
- const requiresReload = installed.length > 0;
517
-
518
- if (requiresReload) {
519
- await refreshOpenCodeAfterConfigChange('skills install');
520
- }
521
510
 
522
511
  res.json({
523
512
  ok: true,
524
513
  installed,
525
514
  skipped,
526
- requiresReload,
527
- message: requiresReload ? 'Skills installed successfully. Reloading interface…' : 'No skills were installed',
528
- reloadDelayMs: requiresReload ? clientReloadDelayMs : undefined,
515
+ requiresReload: false,
516
+ application: 'watch',
517
+ message: installed.length > 0 ? 'Skills installed successfully.' : 'No skills were installed',
529
518
  });
530
519
  } catch (error) {
531
520
  console.error('Failed to install skills:', error);
@@ -606,13 +595,12 @@ export const registerSkillRoutes = (app, dependencies) => {
606
595
  console.log('[Server] Scope:', scope, 'Working directory:', directory);
607
596
 
608
597
  createSkill(skillName, { ...config, source: skillSource }, directory, scope);
609
- await refreshOpenCodeAfterConfigChange('skill creation');
610
598
 
611
599
  res.json({
612
600
  success: true,
613
- requiresReload: true,
614
- message: `Skill ${skillName} created successfully. Reloading interface…`,
615
- reloadDelayMs: clientReloadDelayMs,
601
+ requiresReload: false,
602
+ application: 'watch',
603
+ message: `Skill ${skillName} created successfully.`,
616
604
  });
617
605
  } catch (error) {
618
606
  console.error('Failed to create skill:', error);
@@ -633,13 +621,12 @@ export const registerSkillRoutes = (app, dependencies) => {
633
621
  console.log('[Server] Working directory:', directory);
634
622
 
635
623
  updateSkill(skillName, updates, directory, updates?.targetPath);
636
- await refreshOpenCodeAfterConfigChange('skill update');
637
624
 
638
625
  res.json({
639
626
  success: true,
640
- requiresReload: true,
641
- message: `Skill ${skillName} updated successfully. Reloading interface…`,
642
- reloadDelayMs: clientReloadDelayMs,
627
+ requiresReload: false,
628
+ application: 'watch',
629
+ message: `Skill ${skillName} updated successfully.`,
643
630
  });
644
631
  } catch (error) {
645
632
  console.error('[Server] Failed to update skill:', error);
@@ -725,13 +712,12 @@ export const registerSkillRoutes = (app, dependencies) => {
725
712
  }
726
713
 
727
714
  deleteSkill(skillName, directory);
728
- await refreshOpenCodeAfterConfigChange('skill deletion');
729
715
 
730
716
  res.json({
731
717
  success: true,
732
- requiresReload: true,
733
- message: `Skill ${skillName} deleted successfully. Reloading interface…`,
734
- reloadDelayMs: clientReloadDelayMs,
718
+ requiresReload: false,
719
+ application: 'watch',
720
+ message: `Skill ${skillName} deleted successfully.`,
735
721
  });
736
722
  } catch (error) {
737
723
  console.error('Failed to delete skill:', error);
@@ -2,18 +2,51 @@ import { describe, expect, it, vi } from 'vitest';
2
2
  import express from 'express';
3
3
  import request from 'supertest';
4
4
 
5
- vi.mock('@opencode-ai/sdk/v2', () => ({ createOpencodeClient: vi.fn() }));
5
+ vi.mock('@opencode/client', () => ({ OpenCode: { make: vi.fn() } }));
6
6
 
7
- const { createOpencodeClient } = await import('@opencode-ai/sdk/v2');
7
+ const { OpenCode } = await import('@opencode/client');
8
8
  const { registerSkillRoutes } = await import('./skill-routes.js');
9
9
 
10
10
  describe('skill summary route', () => {
11
+ it('preserves authentication and directory through the real V2 SDK transport', async () => {
12
+ const { OpenCode: RealOpenCode } = await vi.importActual('@opencode/client');
13
+ const upstreamRequests = [];
14
+ const originalFetch = globalThis.fetch;
15
+ globalThis.fetch = async (url, init) => {
16
+ upstreamRequests.push(new Request(url, init));
17
+ return Response.json({ location: { directory: '/repo' }, data: [{
18
+ id: 'agent-tracker-api', name: 'Runmark', path: '/skills/agent-tracker-api/SKILL.md',
19
+ description: 'Query Runmark API', content: 'Instructions',
20
+ }] });
21
+ };
22
+ OpenCode.make.mockImplementation(RealOpenCode.make);
23
+ try {
24
+ const app = express();
25
+ registerSkillRoutes(app, {
26
+ fs: { existsSync: () => false }, path: await import('node:path'), os: await import('node:os'),
27
+ resolveOptionalProjectDirectory: async () => ({ directory: '/repo' }),
28
+ buildOpenCodeUrl: () => 'http://opencode-upstream:4096/',
29
+ getOpenCodeAuthHeaders: () => ({ Authorization: 'Basic example' }), getOpenCodePort: () => 4096,
30
+ SKILL_SCOPE: { PROJECT: 'project', USER: 'user' }, discoverSkills: () => [], mergeDiscoveredSkills: (skills) => skills,
31
+ });
32
+ const response = await request(app).get('/api/config/skills?summary=true').expect(200);
33
+ expect(response.body.skills[0].name).toBe('agent-tracker-api');
34
+ expect(upstreamRequests).toHaveLength(1);
35
+ expect(upstreamRequests[0].headers.get('authorization')).toBe('Basic example');
36
+ expect(upstreamRequests[0].url).toContain(encodeURIComponent('/repo'));
37
+ } finally {
38
+ globalThis.fetch = originalFetch;
39
+ OpenCode.make.mockReset();
40
+ }
41
+ });
42
+
11
43
  it('returns compact normalized skill data without content or sources', async () => {
12
- createOpencodeClient.mockReturnValue({
13
- app: {
14
- skills: vi.fn(async () => ({ data: [{
15
- name: 'skill',
16
- location: '/repo/.opencode/skills/skill/SKILL.md',
44
+ OpenCode.make.mockReturnValue({
45
+ skill: {
46
+ list: vi.fn(async () => ({ data: [{
47
+ id: 'skill',
48
+ name: 'Skill Display Name',
49
+ path: '/repo/.opencode/skills/skill/SKILL.md',
17
50
  description: ` ${'😀'.repeat(161)}\nnext `,
18
51
  content: 'secret skill content',
19
52
  }] })),
@@ -47,5 +80,36 @@ describe('skill summary route', () => {
47
80
  });
48
81
  expect(response.body.skills[0]).not.toHaveProperty('content');
49
82
  expect(response.body.skills[0]).not.toHaveProperty('sources');
83
+ expect(OpenCode.make).toHaveBeenCalledWith(expect.objectContaining({
84
+ baseUrl: 'http://opencode-upstream:4096',
85
+ headers: { Authorization: 'Basic example' },
86
+ }));
87
+ expect(OpenCode.make.mock.calls[0][0].directory).toBeUndefined();
88
+ });
89
+
90
+ it('returns 500 when OpenCode skill list fails instead of an empty success', async () => {
91
+ OpenCode.make.mockReturnValue({
92
+ skill: {
93
+ list: vi.fn(async () => { throw new Error('upstream sentinel'); }),
94
+ },
95
+ });
96
+ const app = express();
97
+ registerSkillRoutes(app, {
98
+ fs: { existsSync: () => false },
99
+ path: await import('node:path'),
100
+ os: await import('node:os'),
101
+ resolveProjectDirectory: async () => ({ directory: '/repo' }),
102
+ resolveOptionalProjectDirectory: async () => ({ directory: '/repo' }),
103
+ buildOpenCodeUrl: () => 'http://opencode-upstream:4096/',
104
+ getOpenCodeAuthHeaders: () => ({ Authorization: 'Basic example' }),
105
+ getOpenCodePort: () => 4096,
106
+ SKILL_SCOPE: { PROJECT: 'project', USER: 'user' },
107
+ discoverSkills: () => [],
108
+ mergeDiscoveredSkills: (primary) => primary,
109
+ });
110
+
111
+ const response = await request(app).get('/api/config/skills?summary=true&directory=%2Frepo').expect(500);
112
+ expect(JSON.stringify(response.body)).not.toContain('sentinel');
113
+ expect(response.body).toEqual({ error: 'Failed to list skills' });
50
114
  });
51
115
  });
@@ -3,8 +3,23 @@ import fsPromises from 'fs/promises';
3
3
  import os from 'os';
4
4
  import path from 'path';
5
5
  import { getSkillSources, mergeDiscoveredSkills } from './skills.js';
6
+ import { addSkillFromMdFile } from './shared.js';
6
7
 
7
8
  describe('skills', () => {
9
+ it('uses the path-derived id even when frontmatter has a different display name', async () => {
10
+ const root = await fsPromises.mkdtemp(path.join(os.tmpdir(), 'oc-skill-id-'));
11
+ try {
12
+ const dir = path.join(root, 'release');
13
+ await fsPromises.mkdir(dir);
14
+ const file = path.join(dir, 'SKILL.md');
15
+ await fsPromises.writeFile(file, '---\nname: Git Release\ndescription: Release notes\n---\nInstructions');
16
+ const skills = new Map();
17
+ addSkillFromMdFile(skills, file, 'project', 'opencode');
18
+ expect([...skills.keys()]).toEqual(['release']);
19
+ } finally {
20
+ await fsPromises.rm(root, { recursive: true, force: true });
21
+ }
22
+ });
8
23
  it('merges locally discovered skills missing from OpenCode live discovery', () => {
9
24
  const merged = mergeDiscoveredSkills(
10
25
  [
@@ -23,6 +23,8 @@ export const createStartupPipelineRuntime = (dependencies) => {
23
23
  getOpenCodeAuthHeaders,
24
24
  globalEventHub,
25
25
  processForwardedEventPayload,
26
+ projectOutboundSessionPayload = null,
27
+ ensureSessionMetadataReady = null,
26
28
  messageStreamWsClients,
27
29
  triggerHealthCheck,
28
30
  upstreamStallTimeoutMs,
@@ -83,6 +85,8 @@ export const createStartupPipelineRuntime = (dependencies) => {
83
85
  getOpenCodeAuthHeaders,
84
86
  globalEventHub,
85
87
  processForwardedEventPayload,
88
+ projectOutboundSessionPayload,
89
+ ensureSessionMetadataReady,
86
90
  wsClients: messageStreamWsClients,
87
91
  triggerHealthCheck,
88
92
  upstreamStallTimeoutMs,
@@ -47,8 +47,10 @@ export const createStaticRoutesRuntime = (dependencies) => {
47
47
  normalizePwaOrientation,
48
48
  });
49
49
 
50
+ // `root` limits send's dotfile check to `index.html`; an absolute path
51
+ // would 404 whenever the install lives under a dot directory (~/.bun, ~/.local).
50
52
  app.get(/^(?!\/api|.*\.(js|css|svg|png|jpg|jpeg|gif|ico|woff|woff2|ttf|eot|map)).*$/, (_req, res) => {
51
- res.sendFile(path.join(distPath, 'index.html'));
53
+ res.sendFile('index.html', { root: distPath });
52
54
  });
53
55
  return;
54
56
  }
@@ -1,4 +1,7 @@
1
1
  import { describe, expect, it } from 'bun:test';
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
2
5
  import express from 'express';
3
6
  import request from 'supertest';
4
7
  import { createStaticRoutesRuntime } from './static-routes-runtime.js';
@@ -57,4 +60,34 @@ describe('static routes runtime', () => {
57
60
  expect(auth.body).not.toEqual({ ok: true, mode: 'api-only', message: 'OpenChamber is running in API-only mode' });
58
61
  expect(health.body).not.toEqual({ ok: true, mode: 'api-only', message: 'OpenChamber is running in API-only mode' });
59
62
  });
63
+
64
+ it('serves index.html for deep links when dist lives under a dot directory', async () => {
65
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'openchamber-static-'));
66
+ const distPath = path.join(root, '.bun', 'install', 'dist');
67
+ fs.mkdirSync(distPath, { recursive: true });
68
+ fs.writeFileSync(path.join(distPath, 'index.html'), '<!doctype html><title>OpenChamber</title>');
69
+ try {
70
+ const app = express();
71
+ createStaticRoutesRuntime({
72
+ fs,
73
+ path,
74
+ process: { env: { OPENCHAMBER_DIST_DIR: distPath } },
75
+ __dirname: '/server',
76
+ express,
77
+ resolveProjectDirectory: () => '',
78
+ buildOpenCodeUrl: () => '',
79
+ getOpenCodeAuthHeaders: () => ({}),
80
+ readSettingsFromDiskMigrated: async () => ({}),
81
+ normalizePwaAppName: (value) => value,
82
+ normalizePwaOrientation: (value) => value,
83
+ }).registerStaticRoutes(app);
84
+
85
+ const response = await request(app).get('/session/ses_abc');
86
+
87
+ expect(response.status).toBe(200);
88
+ expect(response.text).toContain('<title>OpenChamber</title>');
89
+ } finally {
90
+ fs.rmSync(root, { recursive: true, force: true });
91
+ }
92
+ });
60
93
  });