@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
@@ -0,0 +1,155 @@
1
+ import { readdirSync, readFileSync, statSync } from "node:fs"
2
+ import { homedir } from "node:os"
3
+ import path from "node:path"
4
+ import { pathToFileURL } from "node:url"
5
+
6
+ const stripJsonc = (text) => text
7
+ .replace(/\/\*[\s\S]*?\*\//g, "")
8
+ .replace(/(^|[^:\\])\/\/.*$/gm, "$1")
9
+
10
+ const readConfigPlugins = () => {
11
+ const specs = []
12
+ const files = []
13
+ if (process.env.OPENCODE_CONFIG) files.push(process.env.OPENCODE_CONFIG)
14
+ const dirs = [process.env.OPENCODE_CONFIG_DIR, path.join(homedir(), ".config", "opencode")].filter(Boolean)
15
+ for (const dir of dirs) {
16
+ files.push(path.join(dir, "opencode.jsonc"), path.join(dir, "opencode.json"), path.join(dir, "config.json"))
17
+ }
18
+ for (const file of files) {
19
+ try {
20
+ const parsed = JSON.parse(stripJsonc(readFileSync(file, "utf8")))
21
+ const plugin = parsed?.plugin
22
+ const list = Array.isArray(plugin) ? plugin : plugin ? [plugin] : []
23
+ for (const entry of list) {
24
+ if (typeof entry === "string") specs.push(entry)
25
+ else if (Array.isArray(entry) && typeof entry[0] === "string") specs.push(entry[0])
26
+ }
27
+ } catch {
28
+ // Missing or invalid config files are skipped; OpenCode still loads the ones it can parse.
29
+ }
30
+ }
31
+ return [...new Set(specs)]
32
+ }
33
+
34
+ const packageName = (spec) => {
35
+ const slash = spec.startsWith("@") ? spec.indexOf("/", 1) : -1
36
+ const versionAt = spec.lastIndexOf("@")
37
+ if (versionAt > 0 && versionAt > slash) return spec.slice(0, versionAt)
38
+ return spec
39
+ }
40
+
41
+ const resolvePackageEntry = (spec) => {
42
+ const name = packageName(spec)
43
+ const roots = [
44
+ path.join(homedir(), ".cache", "opencode", "npm"),
45
+ path.join(homedir(), ".cache", "opencode", "packages"),
46
+ ]
47
+ const candidates = []
48
+ for (const root of roots) {
49
+ let entries = []
50
+ try {
51
+ entries = readdirSync(root)
52
+ } catch {
53
+ continue
54
+ }
55
+ for (const entry of entries) {
56
+ if (entry !== name && !entry.startsWith(`${name}@`)) continue
57
+ const base = path.join(root, entry)
58
+ const packageDirs = [path.join(base, "node_modules", name)]
59
+ try {
60
+ for (const child of readdirSync(base)) {
61
+ if (/^\d+$/.test(child)) packageDirs.push(path.join(base, child, "node_modules", name))
62
+ }
63
+ } catch {
64
+ // The cache entry may be the package directory itself.
65
+ }
66
+ for (const pkgDir of packageDirs) {
67
+ try {
68
+ const pkg = JSON.parse(readFileSync(path.join(pkgDir, "package.json"), "utf8"))
69
+ if (pkg.name !== name) continue
70
+ const exported = pkg.exports?.["."]
71
+ const rel = typeof exported === "string" ? exported : pkg.module || pkg.main || "index.mjs"
72
+ candidates.push({
73
+ dir: pkgDir,
74
+ entry: path.resolve(pkgDir, rel),
75
+ mtime: statSync(path.join(pkgDir, "package.json")).mtimeMs,
76
+ })
77
+ } catch {
78
+ // Not a readable package directory.
79
+ }
80
+ }
81
+ }
82
+ }
83
+ candidates.sort((left, right) => right.mtime - left.mtime)
84
+ return candidates[0] || null
85
+ }
86
+
87
+ const usesCatalog = (dir) => {
88
+ const stack = [dir]
89
+ let seen = 0
90
+ while (stack.length > 0 && seen < 80) {
91
+ const current = stack.pop()
92
+ let names = []
93
+ try {
94
+ names = readdirSync(current)
95
+ } catch {
96
+ continue
97
+ }
98
+ for (const name of names) {
99
+ if (name === "node_modules" || name === ".git") continue
100
+ const full = path.join(current, name)
101
+ let info
102
+ try {
103
+ info = statSync(full)
104
+ } catch {
105
+ continue
106
+ }
107
+ if (info.isDirectory()) {
108
+ stack.push(full)
109
+ continue
110
+ }
111
+ if (!/\.(?:mjs|cjs|js)$/.test(name) || info.size > 2_000_000) continue
112
+ seen += 1
113
+ try {
114
+ if (readFileSync(full, "utf8").includes(".catalog")) return true
115
+ } catch {
116
+ // Unreadable files are not catalog callers.
117
+ }
118
+ }
119
+ }
120
+ return false
121
+ }
122
+
123
+ const catalogFor = (ctx) => ({
124
+ // OpenCode 2.0.12 removed ctx.catalog. Model plugins still call
125
+ // draft.provider.update / draft.model.update inside catalog.transform.
126
+ transform: (fn) => ctx.provider?.transform?.((draft) => fn({
127
+ provider: {
128
+ update: (id, updater) => draft.update(id, updater),
129
+ add: (...args) => draft.add?.(...args),
130
+ },
131
+ model: {
132
+ update: (providerID, modelID, updater) => draft.models?.update?.(providerID, modelID, updater),
133
+ set: (providerID, modelID, value) => draft.models?.set?.(providerID, modelID, value),
134
+ },
135
+ })),
136
+ reload: () => ctx.provider?.reload?.() ?? ctx.model?.reload?.(),
137
+ })
138
+
139
+ export default {
140
+ id: "openchamber-v2-plugin-host-shim",
141
+ setup: async (ctx) => {
142
+ if (typeof ctx?.catalog?.transform === "function") return
143
+ const patched = { ...ctx, catalog: catalogFor(ctx) }
144
+ for (const spec of readConfigPlugins()) {
145
+ const resolved = resolvePackageEntry(spec)
146
+ if (!resolved || !usesCatalog(resolved.dir)) continue
147
+ try {
148
+ const mod = await import(pathToFileURL(resolved.entry).href)
149
+ if (typeof mod.default?.setup === "function") await mod.default.setup(patched)
150
+ } catch (error) {
151
+ console.error(`[openchamber] v2 plugin host shim skipped ${spec}: ${error instanceof Error ? error.message : String(error)}`)
152
+ }
153
+ }
154
+ },
155
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "type": "module",
3
+ "name": "openchamber-v2-plugin-host-shim"
4
+ }
@@ -8,13 +8,13 @@ This module owns the authoritative permission auto-accept policy for web, deskto
8
8
 
9
9
  `permissionAutoAccept.sessions` contains explicit per-session boolean policies.
10
10
 
11
- Policy inheritance uses the nearest explicit session value. A child `false` therefore overrides a parent `true`; descendants without an explicit value inherit from their nearest configured ancestor.
11
+ Policy inheritance uses the nearest explicit session value. A child `false` therefore overrides a parent `true`. Sessions with no explicit value, including a missing ancestor lookup, default to allow. A failed settings load still fails closed and does not approve.
12
12
 
13
13
  ## Runtime
14
14
 
15
- `createPermissionAutoAcceptRuntime` loads and serializes policy writes, subscribes to the global OpenCode event hub, caches session lineage, retries transient replies, and reconciles pending permissions after startup, reconnect, and policy enablement. Enabling Auto-Accept for a session immediately accepts matching pending requests and keeps handling future requests without requiring a connected UI.
15
+ `createPermissionAutoAcceptRuntime` loads and serializes policy writes, subscribes to the global OpenCode event hub, caches session lineage, retries transient replies, and reconciles pending permissions after startup, reconnect, and policy enablement. Pending requests are listed at `GET /permission/request` and approved with `POST /session/:sessionID/permission/:requestID/reply` body `{ decision: "always" }`. OpenCode 2.0.12 rejects the legacy `{ reply }` body and the unscoped `/permission/:id/reply` path.
16
16
 
17
- Unknown lineage and failed policy loads fail closed. A failed pending-permission fetch is distinct from an empty successful response and never clears policy state.
17
+ A failed settings load fails closed and does not approve. A failed pending-permission fetch is distinct from an empty successful response and never clears policy state.
18
18
 
19
19
  ## Routes
20
20
 
@@ -27,9 +27,9 @@ These are normal authenticated OpenChamber runtime routes. They must not be adde
27
27
 
28
28
  `packages/ui/src/stores/permissionStore.ts` is a projection of server policy and does not persist an independent policy. The server is the sole responder and the UI renders pending requests until the authoritative `permission.replied` event arrives.
29
29
 
30
- `ChatInput` derives control visibility from the persisted, directory-scoped `useConfigStore` agent snapshot. A selected agent with a final global `allow` or `deny` rule has no remaining prompt path and hides the control. Unknown snapshots, missing selected agents, and rules that can ask keep the control visible. This path issues no additional request.
30
+ `ChatInput` derives control visibility from the persisted, directory-scoped `useConfigStore` agent snapshot via `shouldShowPermissionAutoAcceptControl`. It reads the V2 wire `permissions` field when present (OpenCode `AgentInfo.permissions`), otherwise the legacy singular `permission` document. Rule normalization reuses `toPermissionRuleset` so both V2 `{ action, resource, effect }` and V1 `{ permission, pattern, action }` / tool-map shapes are recognized. A selected agent with a final global `allow` or `deny` rule has no remaining prompt path and hides the control. Unknown snapshots, missing selected agents, unparseable rules, and rules that can still ask keep the control visible. This path issues no additional request and does not change server auto-accept policy.
31
31
 
32
- VS Code retains its foreground-only implementation because it does not run the web server runtime.
32
+ VS Code retains its foreground-only implementation because it does not run the web server runtime (`isVSCodeRuntime()` always shows the control).
33
33
 
34
34
  ## Tests
35
35
 
@@ -123,6 +123,7 @@ export function createPermissionAutoAcceptRuntime({
123
123
 
124
124
  const isSessionAutoAccepting = async (sessionId, directory) => {
125
125
  await load();
126
+ if (!sessionId) return false;
126
127
  const seen = new Set();
127
128
  let current = sessionId;
128
129
  let currentDirectory = directory;
@@ -133,22 +134,23 @@ export function createPermissionAutoAcceptRuntime({
133
134
  try {
134
135
  info = await getSession(current, currentDirectory);
135
136
  } catch {
136
- return false;
137
+ // Lineage lookup failed. No explicit deny was found, so keep the default allow.
138
+ return true;
137
139
  }
138
140
  current = info?.parentID ?? null;
139
141
  currentDirectory = info?.directory ?? currentDirectory;
140
142
  }
141
- return false;
143
+ return true;
142
144
  };
143
145
 
144
146
  const replyOnce = async (permission, directory) => {
145
147
  if (!permission?.id || !permission?.sessionID) return false;
146
148
  await load();
147
149
  if (!(await isSessionAutoAccepting(permission.sessionID, directory))) return false;
148
- await request(`/permission/${encodeURIComponent(permission.id)}/reply`, {
150
+ await request(`/session/${encodeURIComponent(permission.sessionID)}/permission/${encodeURIComponent(permission.id)}/reply`, {
149
151
  directory,
150
152
  method: 'POST',
151
- body: { reply: 'once' },
153
+ body: { decision: 'always' },
152
154
  });
153
155
  return true;
154
156
  };
@@ -187,7 +189,7 @@ export function createPermissionAutoAcceptRuntime({
187
189
  for (const directory of scopes) {
188
190
  let payload;
189
191
  try {
190
- payload = await request('/permission', { directory });
192
+ payload = await request('/permission/request', { directory });
191
193
  } catch {
192
194
  continue;
193
195
  }
@@ -209,12 +211,17 @@ export function createPermissionAutoAcceptRuntime({
209
211
  const raw = event?.payload;
210
212
  const payload = raw?.payload && typeof raw.payload === 'object' ? raw.payload : raw;
211
213
  const directory = typeof event?.directory === 'string' && event.directory !== 'global' ? event.directory : undefined;
214
+ const body = payload?.properties && typeof payload.properties === 'object'
215
+ ? payload.properties
216
+ : payload?.data && typeof payload.data === 'object'
217
+ ? payload.data
218
+ : payload;
212
219
  if (payload?.type === 'session.created' || payload?.type === 'session.updated') {
213
- rememberSession(payload.properties?.info, directory);
220
+ rememberSession(body?.info ?? payload.properties?.info, directory);
214
221
  return;
215
222
  }
216
223
  if (payload?.type === 'permission.asked') {
217
- void processPermission(payload.properties, directory);
224
+ void processPermission(body, directory);
218
225
  }
219
226
  };
220
227
 
@@ -31,6 +31,18 @@ const flush = async () => {
31
31
  };
32
32
 
33
33
  describe('permission auto-accept runtime', () => {
34
+ it('approves a session with no explicit policy', async () => {
35
+ const fetchImpl = vi.fn(async (url, init = {}) => {
36
+ const path = new URL(url).pathname;
37
+ if (path === '/session/root') return Response.json({ id: 'root' });
38
+ if (init.method === 'POST') return Response.json({});
39
+ return new Response('', { status: 404 });
40
+ });
41
+ const { runtime } = createRuntime({ fetchImpl });
42
+ await expect(runtime.processPermission({ id: 'perm', sessionID: 'root' }, '/project')).resolves.toBe(true);
43
+ expect(fetchImpl.mock.calls.some(([url, init]) => new URL(url).pathname === '/session/root/permission/perm/reply' && JSON.parse(init.body).decision === 'always')).toBe(true);
44
+ });
45
+
34
46
  it('persists explicit session policies across runtime restarts', async () => {
35
47
  const first = createRuntime();
36
48
  await first.runtime.setSessionPolicy('root', true);
@@ -55,7 +67,7 @@ describe('permission auto-accept runtime', () => {
55
67
  it('fetches missing subagent lineage before replying', async () => {
56
68
  const fetchImpl = vi.fn(async (url, init = {}) => {
57
69
  const path = new URL(url).pathname;
58
- if (path === '/permission') return new Response('[]');
70
+ if (path === '/permission/request') return new Response('[]');
59
71
  if (path === '/session/child') return Response.json({ id: 'child', parentID: 'root', directory: '/project' });
60
72
  if (init.method === 'POST') return Response.json({});
61
73
  return new Response('', { status: 404 });
@@ -65,15 +77,15 @@ describe('permission auto-accept runtime', () => {
65
77
  fetchImpl,
66
78
  });
67
79
  await expect(runtime.processPermission({ id: 'perm', sessionID: 'child' }, '/project')).resolves.toBe(true);
68
- expect(fetchImpl.mock.calls.some(([url, init]) => new URL(url).pathname === '/permission/perm/reply' && init.method === 'POST')).toBe(true);
80
+ expect(fetchImpl.mock.calls.some(([url, init]) => new URL(url).pathname === '/session/child/permission/perm/reply' && init.method === 'POST' && JSON.parse(init.body).decision === 'always')).toBe(true);
69
81
  });
70
82
 
71
83
  it('retries a transient reply failure and deduplicates concurrent events', async () => {
72
84
  let replyAttempts = 0;
73
85
  const fetchImpl = vi.fn(async (url, init = {}) => {
74
86
  const path = new URL(url).pathname;
75
- if (path === '/permission') return new Response('[]');
76
- if (path === '/permission/perm/reply' && init.method === 'POST') {
87
+ if (path === '/permission/request') return new Response('[]');
88
+ if (path === '/session/root/permission/perm/reply' && init.method === 'POST') {
77
89
  replyAttempts += 1;
78
90
  return replyAttempts === 1 ? new Response('', { status: 503 }) : Response.json({});
79
91
  }
@@ -94,8 +106,8 @@ describe('permission auto-accept runtime', () => {
94
106
  it('reconciles pending permissions after reconnect', async () => {
95
107
  const fetchImpl = vi.fn(async (url, init = {}) => {
96
108
  const path = new URL(url).pathname;
97
- if (path === '/permission') return Response.json([{ id: 'pending', sessionID: 'root' }]);
98
- if (path === '/permission/pending/reply' && init.method === 'POST') return Response.json({});
109
+ if (path === '/permission/request') return Response.json([{ id: 'pending', sessionID: 'root' }]);
110
+ if (path === '/session/root/permission/pending/reply' && init.method === 'POST') return Response.json({});
99
111
  return Response.json({ id: 'root' });
100
112
  });
101
113
  const { connect } = createRuntime({
@@ -104,14 +116,14 @@ describe('permission auto-accept runtime', () => {
104
116
  });
105
117
  connect();
106
118
  await flush();
107
- expect(fetchImpl.mock.calls.some(([url]) => new URL(url).pathname === '/permission/pending/reply')).toBe(true);
119
+ expect(fetchImpl.mock.calls.some(([url]) => new URL(url).pathname === '/session/root/permission/pending/reply')).toBe(true);
108
120
  });
109
121
 
110
122
  it('accepts existing pending permissions when a session policy is enabled', async () => {
111
123
  const fetchImpl = vi.fn(async (url, init = {}) => {
112
124
  const parsed = new URL(url);
113
125
  const path = parsed.pathname;
114
- if (path === '/permission') {
126
+ if (path === '/permission/request') {
115
127
  return parsed.searchParams.get('directory') === '/project'
116
128
  ? Response.json([
117
129
  { id: 'root-pending', sessionID: 'root' },
@@ -119,19 +131,22 @@ describe('permission auto-accept runtime', () => {
119
131
  ])
120
132
  : Response.json([]);
121
133
  }
122
- if (path === '/permission/root-pending/reply' && init.method === 'POST') return Response.json({});
134
+ if (init.method === 'POST' && path.endsWith('/reply')) return Response.json({});
123
135
  if (path === '/session/other') return Response.json({ id: 'other' });
124
136
  return new Response('', { status: 404 });
125
137
  });
126
- const { runtime } = createRuntime({ fetchImpl });
138
+ const { runtime } = createRuntime({
139
+ stored: { permissionAutoAccept: { sessions: { other: false } } },
140
+ fetchImpl,
141
+ });
127
142
 
128
143
  await runtime.setSessionPolicy('root', true, '/project');
129
144
 
130
145
  const replyPaths = fetchImpl.mock.calls
131
146
  .filter(([, init]) => init?.method === 'POST')
132
147
  .map(([url]) => new URL(url).pathname);
133
- expect(replyPaths).toEqual(['/permission/root-pending/reply']);
148
+ expect(replyPaths).toEqual(['/session/root/permission/root-pending/reply']);
134
149
  expect(fetchImpl.mock.calls.some(([url]) => new URL(url).searchParams.get('directory') === '/project')).toBe(true);
135
- expect(await runtime.load()).toEqual({ sessions: { root: true } });
150
+ expect(await runtime.load()).toEqual({ sessions: { other: false, root: true } });
136
151
  });
137
152
  });
@@ -147,7 +147,11 @@ const startFakeOpenCode = async (): Promise<{
147
147
  next();
148
148
  });
149
149
 
150
- app.get('/global/event', (req, res) => {
150
+ // Production proxy forwards official v2 paths with `/api` restored
151
+ // (`toUpstreamOpenCodeApiPath`). Host mounts both `/api/global/event` and
152
+ // `/api/event`; fake upstream must match those upstream targets — not the
153
+ // pre-v2 bare `/global/event` or `/event`.
154
+ const writeSseFixture = (res: express.Response) => {
151
155
  res.writeHead(200, {
152
156
  'content-type': 'text/event-stream; charset=utf-8',
153
157
  'cache-control': 'no-cache',
@@ -243,9 +247,16 @@ const startFakeOpenCode = async (): Promise<{
243
247
  );
244
248
 
245
249
  res.end();
250
+ };
251
+
252
+ app.get('/api/global/event', (_req, res) => {
253
+ writeSseFixture(res);
254
+ });
255
+ app.get('/api/event', (_req, res) => {
256
+ writeSseFixture(res);
246
257
  });
247
258
 
248
- app.get('/session/:sessionID/message', (req, res) => {
259
+ app.get('/api/session/:sessionID/message', (_req, res) => {
249
260
  res.json([
250
261
  {
251
262
  info: {
@@ -513,11 +524,15 @@ describe('relay reasoning projection e2e (Host filter over private relay)', () =
513
524
  for (const url of upstream.observation.urls) {
514
525
  expect(url).not.toContain('includeReasoning');
515
526
  }
516
- // Sanity: upstream still saw the event + message routes.
517
- expect(upstream.observation.urls.some((u) => u.includes('/global/event'))).toBe(true);
518
- expect(upstream.observation.urls.some((u) => u.includes('/session/ses_1/message'))).toBe(
519
- true,
520
- );
527
+ // Sanity: upstream still saw official v2 event + message routes.
528
+ expect(
529
+ upstream.observation.urls.some(
530
+ (u) => u.includes('/api/global/event') || u.includes('/api/event'),
531
+ ),
532
+ ).toBe(true);
533
+ expect(
534
+ upstream.observation.urls.some((u) => u.includes('/api/session/ses_1/message')),
535
+ ).toBe(true);
521
536
 
522
537
  // Unauthenticated tunnel request still hits real auth gate (not open).
523
538
  const denied = await client.fetch('/api/global/event?includeReasoning=false', {
@@ -69,6 +69,14 @@ Server-owned scheduled task runtime and routes for OpenChamber-only automation.
69
69
  - `runNow(projectId, taskId)`
70
70
  - `observeSessionEvent(event)`
71
71
 
72
+ Isolation dependencies (required for sidebar hide):
73
+
74
+ - `persistSessionMetadata(sessionID, patch)` — Host store write. Missing
75
+ persist refuses the run **before** `session.create`. Persist failure after
76
+ create fails the run before prompt.
77
+ - `onSystemSessionPersisted({ sessionID, directory, metadata })` — drop the
78
+ session from the sidebar index immediately after Host commit.
79
+
72
80
  Dependencies include optional `runHistoryStore` with:
73
81
 
74
82
  - `startRun(record)`
@@ -8,6 +8,7 @@ import {
8
8
  isSessionGoalSupported,
9
9
  sessionGoalUnavailableMessage,
10
10
  } from '../session-goal/capability.js';
11
+ import { buildScheduledTaskMetadata } from '../session-metadata/system-session.js';
11
12
  import { SCHEDULED_SLOT_CLAIMED_CODE } from './run-history-store.js';
12
13
 
13
14
  const DEFAULT_GLOBAL_CONCURRENCY = 4;
@@ -481,12 +482,26 @@ const snapshotSessionOutcome = async ({
481
482
 
482
483
  const parseSessionEventPhase = (event) => {
483
484
  const payload = event?.payload?.payload ?? event?.payload;
484
- const properties = payload?.properties;
485
+ // Accept bridge `{ properties }` and native v2 `{ data }` envelopes.
486
+ const properties = (payload?.properties && typeof payload.properties === 'object')
487
+ ? payload.properties
488
+ : ((payload?.data && typeof payload.data === 'object') ? payload.data : null);
485
489
  const sessionID = typeof properties?.sessionID === 'string' ? properties.sessionID : '';
486
490
  if (!sessionID) {
487
491
  return { phase: '', sessionID: '' };
488
492
  }
489
- if (payload?.type === 'session.idle') {
493
+ // Ticket 07: shutdown interrupt is not an idle terminal — keep run open.
494
+ if (payload?.type === 'session.execution.interrupted' && properties?.reason === 'shutdown') {
495
+ return { phase: 'busy', sessionID };
496
+ }
497
+ if (payload?.type === 'session.idle' || payload?.type === 'session.execution.succeeded') {
498
+ return { phase: 'idle', sessionID };
499
+ }
500
+ if (payload?.type === 'session.execution.failed') {
501
+ return { phase: 'error', sessionID };
502
+ }
503
+ if (payload?.type === 'session.execution.interrupted') {
504
+ // user / superseded / inactivity — claim released; treat as idle for settlement.
490
505
  return { phase: 'idle', sessionID };
491
506
  }
492
507
  if (payload?.type === 'session.status') {
@@ -518,6 +533,14 @@ export const createScheduledTasksRuntime = (deps) => {
518
533
  maxRunDurationMs = DEFAULT_MAX_RUN_MS,
519
534
  /** Host store write: (sessionId, directory, goal) => Promise */
520
535
  persistSessionGoal = null,
536
+ /** Host store write: (sessionId, patch) => Promise — required for sidebar isolation */
537
+ persistSessionMetadata = null,
538
+ /**
539
+ * After Host isolation metadata is committed. Used to drop the session
540
+ * from the sidebar index immediately (do not wait for the next list sync).
541
+ * @type {null | ((event: { sessionID: string, directory?: string, metadata: object }) => void)}
542
+ */
543
+ onSystemSessionPersisted = null,
521
544
  /** Host store read: (sessionId) => Promise<metadata|null> */
522
545
  readSessionMetadata = null,
523
546
  /**
@@ -801,9 +824,10 @@ export const createScheduledTasksRuntime = (deps) => {
801
824
  + '\n</system-reminder>';
802
825
  };
803
826
 
804
- const buildSessionCreateInput = (task, projectPath, title) => ({
827
+ const buildSessionCreateInput = (task, projectPath, title, metadata) => ({
805
828
  title,
806
829
  location: { directory: projectPath },
830
+ metadata,
807
831
  ...(task.execution.agent ? { agent: task.execution.agent } : {}),
808
832
  model: {
809
833
  id: task.execution.modelID,
@@ -812,6 +836,18 @@ export const createScheduledTasksRuntime = (deps) => {
812
836
  },
813
837
  });
814
838
 
839
+ const persistScheduledTaskIsolation = async (sessionID, directory, metadata) => {
840
+ if (typeof persistSessionMetadata !== 'function') {
841
+ throw new Error('scheduled-task session metadata persist is not configured');
842
+ }
843
+ await persistSessionMetadata(sessionID, metadata);
844
+ try {
845
+ onSystemSessionPersisted?.({ sessionID, directory, metadata });
846
+ } catch {
847
+ // Index/live hide is compensatory; Host persist is the authority.
848
+ }
849
+ };
850
+
815
851
  const buildPromptText = (task, projectPath) => {
816
852
  const promptText = expandSnippets(task.execution.prompt, projectPath);
817
853
  if (!task.execution.goalEnabled) {
@@ -915,6 +951,7 @@ export const createScheduledTasksRuntime = (deps) => {
915
951
  note: '',
916
952
  statusReason: '',
917
953
  lastAccountedMessageID: '',
954
+ executionGeneration: 0,
918
955
  createdAt: now,
919
956
  updatedAt: now,
920
957
  };
@@ -1107,6 +1144,10 @@ export const createScheduledTasksRuntime = (deps) => {
1107
1144
  }
1108
1145
  }
1109
1146
 
1147
+ if (typeof persistSessionMetadata !== 'function') {
1148
+ throw new Error('scheduled-task session metadata persist is not configured');
1149
+ }
1150
+
1110
1151
  if (typeof waitForOpenCodeReady === 'function') {
1111
1152
  await waitForOpenCodeReady(10_000, 250);
1112
1153
  signal?.throwIfAborted?.();
@@ -1141,14 +1182,21 @@ export const createScheduledTasksRuntime = (deps) => {
1141
1182
  };
1142
1183
 
1143
1184
  try {
1185
+ const isolationMetadata = buildScheduledTaskMetadata({
1186
+ projectID,
1187
+ taskID: task.id,
1188
+ runID,
1189
+ name: task.name,
1190
+ });
1144
1191
  const session = await client.session.create(
1145
- buildSessionCreateInput(task, projectPath, title),
1192
+ buildSessionCreateInput(task, projectPath, title, isolationMetadata),
1146
1193
  requestOptions,
1147
1194
  );
1148
1195
  sessionID = session?.id;
1149
1196
  if (!sessionID) {
1150
1197
  throw new Error('failed to create session');
1151
1198
  }
1199
+ await persistScheduledTaskIsolation(sessionID, projectPath, isolationMetadata);
1152
1200
 
1153
1201
  if (signal) {
1154
1202
  if (signal.aborted) {