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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (311) hide show
  1. package/README.md +13 -7
  2. package/bin/cli-opencode.test.js +208 -0
  3. package/bin/cli.js +62 -9
  4. package/bin/lib/cli-startup.js +105 -2
  5. package/bin/lib/cli-startup.test.js +179 -0
  6. package/dist/assets/{AgentAvatar-CRaBJXKs.js → AgentAvatar-B_JQoreg.js} +1 -1
  7. package/dist/assets/AssistantView-ClQAfGOT.js +3 -0
  8. package/dist/assets/{DiagramView-D1LGK_s2.js → DiagramView-BNnda8qO.js} +1 -1
  9. package/dist/assets/{FadeInOnReveal-B0kJo9Ii.js → FadeInOnReveal-VYQTSAkr.js} +1 -1
  10. package/dist/assets/{Icon-BgdfPhNs.js → Icon-BiEQEcih.js} +1 -1
  11. package/dist/assets/MarkdownRenderer-CJHSsWin.js +2 -0
  12. package/dist/assets/{MarkdownRendererImpl-DfoCJ1Mq.js → MarkdownRendererImpl-DwrHFGUp.js} +3 -3
  13. package/dist/assets/{MarkstreamRendererImpl-D2dsW5Lg.js → MarkstreamRendererImpl-CSnbQMu3.js} +1 -1
  14. package/dist/assets/MobileDetailNavigation-BXr6AGLT.js +1 -0
  15. package/dist/assets/{MobileShareBridge-2Yjn6wXg.js → MobileShareBridge-D7OE9OP_.js} +2 -2
  16. package/dist/assets/MobileSurface-D2jUkX9l.js +2 -0
  17. package/dist/assets/{MultiRunWindow-tTbMFowS.js → MultiRunWindow-DF3iIFCd.js} +1 -1
  18. package/dist/assets/{OnboardingScreen-CD2KtlF3.js → OnboardingScreen-BjHmlwlw.js} +1 -1
  19. package/dist/assets/OverlayScrollbar-DLtWO_Gp.js +2 -0
  20. package/dist/assets/SettingsView-BRxELjow.js +25 -0
  21. package/dist/assets/{SettingsWindow-zIa76s4t.js → SettingsWindow-BI5swspb.js} +1 -1
  22. package/dist/assets/{TerminalView-CDdzF4yi.js → TerminalView-BU3x8-fr.js} +1 -1
  23. package/dist/assets/{ToolOutputDialog-DNqA4uBl.js → ToolOutputDialog-BEWhBcRd.js} +2 -2
  24. package/dist/assets/appThemeRegistry-4MR0F7Jz.js +1 -0
  25. package/dist/assets/{button-CI7Q-Zw_.js → button-DKuz3L8l.js} +1 -1
  26. package/dist/assets/collapsible-DXbDynN4.js +3 -0
  27. package/dist/assets/context-menu-CqolsLCm.js +1 -0
  28. package/dist/assets/{desktopHosts-B1ixEylr.js → desktopHosts-CoDkOnyS.js} +1 -1
  29. package/dist/assets/{desktopRelayRestore-4JmFUw8r.js → desktopRelayRestore-DDlVKyOE.js} +1 -1
  30. package/dist/assets/{diagram-DdLO8HZ9.js → diagram-D7T8HQgj.js} +1 -1
  31. package/dist/assets/{dialog-B6pnIYmp.js → dialog-DQYl3W_o.js} +1 -1
  32. package/dist/assets/es-CN3E_zXj.js +15 -0
  33. package/dist/assets/{fileMentionTouchSelection-DNeiqns-.js → fileMentionTouchSelection-BZ6ovBYH.js} +1 -1
  34. package/dist/assets/{fileQueries-DQc8EJUf.js → fileQueries-JrpcsOCs.js} +1 -1
  35. package/dist/assets/fr-9zZvQZD2.js +13 -0
  36. package/dist/assets/gitApi-CM42qUed.js +10 -0
  37. package/dist/assets/{imageSource-BlAJJUEI.js → imageSource-CIl0FL10.js} +1 -1
  38. package/dist/assets/{input-D-__fwRi.js → input-CzQy8UhV.js} +1 -1
  39. package/dist/assets/insertionBoundaries-CtkZ9720.js +1 -0
  40. package/dist/assets/ja-Dnjk_82N.js +15 -0
  41. package/dist/assets/ko-BxCEE99C.js +15 -0
  42. package/dist/assets/main-BILgBI3B.js +169 -0
  43. package/dist/assets/main-BeveiW6F.js +2 -0
  44. package/dist/assets/markdown-worker-bS5OgGO3.js +2 -0
  45. package/dist/assets/{markdownHeightCache-BhxqJOvf.js → markdownHeightCache-Cj1I5NDw.js} +1 -1
  46. package/dist/assets/{markdownTheme-DOfGg-A7.js → markdownTheme-CKac3wKC.js} +1 -1
  47. package/dist/assets/mcpQueries-D5mQymgM.js +1 -0
  48. package/dist/assets/miniChat-BgrzM3u7.js +2 -0
  49. package/dist/assets/mobile-DtYoQE8C.js +2 -0
  50. package/dist/assets/multirun-jy8dEI96.js +4 -0
  51. package/dist/assets/{persistence-Ce3AnpEr.js → persistence-C9LQpiOS.js} +2 -2
  52. package/dist/assets/pl-CNNsWnPM.js +15 -0
  53. package/dist/assets/platform-BQghHRfp.js +6 -0
  54. package/dist/assets/pt-BR-BZB2DJMp.js +15 -0
  55. package/dist/assets/renderElectronMiniChatApp-BGrrmeME.js +2 -0
  56. package/dist/assets/renderMobileApp-u3c4GtFC.js +28 -0
  57. package/dist/assets/{runtimeConfig-CMkwoQ_X.js → runtimeConfig-BIGBRGQI.js} +1 -1
  58. package/dist/assets/{runtimeEndpointReset-ChOxt-J7.js → runtimeEndpointReset-D2savkuw.js} +12 -12
  59. package/dist/assets/{sessionChangeSummary-NnxcqCTI.js → sessionChangeSummary-eIbpSfrO.js} +1 -1
  60. package/dist/assets/{sessionLookup-BrU3rFHJ.js → sessionLookup-myHe5ltu.js} +1 -1
  61. package/dist/assets/{sortable-tabs-strip-Fnfhf3K9.js → sortable-tabs-strip-BQtmHx9r.js} +1 -1
  62. package/dist/assets/src-CLN7Z-zx.css +2 -0
  63. package/dist/assets/store-DLCiZaA6.js +15 -0
  64. package/dist/assets/{terminalApi-xjqssKke.js → terminalApi-BxcdiduX.js} +1 -1
  65. package/dist/assets/{textarea-bHYxZtfH.js → textarea-CakKvqFw.js} +1 -1
  66. package/dist/assets/{tooltip-0T5jv0cu.js → tooltip-Cx5gTbdY.js} +1 -1
  67. package/dist/assets/uk-rwQrxeYM.js +15 -0
  68. package/dist/assets/{url-_z7evzdK.js → url-CDHnjlsm.js} +1 -1
  69. package/dist/assets/{useAppFontEffects-C02YFyAv.js → useAppFontEffects-CNeo0aZs.js} +59 -45
  70. package/dist/assets/{useAssistantUIStore-CMutVh9B.js → useAssistantUIStore-asNG6K7n.js} +1 -1
  71. package/dist/assets/useDesktopWindowControlsLayout-DBJ56qIo.js +1 -0
  72. package/dist/assets/useEffectiveDirectory-B4LvR8Gt.js +1 -0
  73. package/dist/assets/{useFontPreferences-B1JYyzpK.js → useFontPreferences-CUnQ4TVm.js} +1 -1
  74. package/dist/assets/useMobileNavigationStore-ZHJPxlHt.js +414 -0
  75. package/dist/assets/{useRuntimeAPIs-DSLXJybW.js → useRuntimeAPIs-DwLtO0c_.js} +1 -1
  76. package/dist/assets/useSessionAutoCleanup-BIVmrnYU.js +2 -0
  77. package/dist/assets/{useThemeSystem-DMzWVJaW.js → useThemeSystem-BlNUWH47.js} +1 -1
  78. package/dist/assets/useUIStore-CQYKJnrg.js +1 -0
  79. package/dist/assets/{useWorkerHighlightedLines-C9z0ML1z.js → useWorkerHighlightedLines-CJZbJDW3.js} +2 -2
  80. package/dist/assets/{vendor-base-ui-CnHfUmDN.js → vendor-base-ui-CDjyofeD.js} +1 -1
  81. package/dist/assets/{vendor-cmdk-CeAGfrLo.js → vendor-cmdk-BFH0UpXX.js} +1 -1
  82. package/dist/assets/{vendor-dnd-kit-accessibility-BY5LhhUB.js → vendor-dnd-kit-accessibility-BrR3AKvM.js} +1 -1
  83. package/dist/assets/{vendor-dnd-kit-core-CvWxUPdb.js → vendor-dnd-kit-core-CNfdvk6H.js} +1 -1
  84. package/dist/assets/{vendor-dnd-kit-sortable-hHdP9lLb.js → vendor-dnd-kit-sortable-DQUaTeGj.js} +1 -1
  85. package/dist/assets/{vendor-framer-motion-DFy_BSVd.js → vendor-framer-motion-4dogsUk3.js} +1 -1
  86. package/dist/assets/{vendor-legendapp-list-WE32lwNk.js → vendor-legendapp-list-BU7MdPZR.js} +1 -1
  87. package/dist/assets/{vendor-markstream-react-DX55XLGO.js → vendor-markstream-react-qHC2FykU.js} +3 -3
  88. package/dist/assets/vendor-opencode-client-BOe2qpQb.js +12 -0
  89. package/dist/assets/{vendor-pierre-diffs-DX7XTPG3.js → vendor-pierre-diffs-CUfqC003.js} +1 -1
  90. package/dist/assets/{vendor-react-DLPElpoQ.js → vendor-react-C94A0AHl.js} +1 -1
  91. package/dist/assets/{vendor-react-drawio-ECtEwvbc.js → vendor-react-drawio-FVlWqKVE.js} +1 -1
  92. package/dist/assets/{vendor-reactuses-core-BnW6GxJt.js → vendor-reactuses-core-C2A3EjUi.js} +1 -1
  93. package/dist/assets/{vendor-sonner-DVzioF3d.js → vendor-sonner-DNqHmUQs.js} +1 -1
  94. package/dist/assets/{vendor-stream-diffs-DWy3xSmp.js → vendor-stream-diffs-CwbKsmlv.js} +2 -2
  95. package/dist/assets/{vendor-tanstack-react-query-D7pys_c9.js → vendor-tanstack-react-query-D0iFK57C.js} +1 -1
  96. package/dist/assets/{vendor-tanstack-react-virtual-tlDgH6PG.js → vendor-tanstack-react-virtual-B2kV6cnT.js} +1 -1
  97. package/dist/assets/{vendor-yaml-DIcAK3EC.js → vendor-yaml-C9KlJAlj.js} +37 -37
  98. package/dist/assets/{vendor-zustand-B4dlGEM2.js → vendor-zustand-BqLKf6ZY.js} +1 -1
  99. package/dist/assets/zh-CN-BvmBDnkD.js +15 -0
  100. package/dist/assets/zh-TW-9wcHTX0y.js +15 -0
  101. package/dist/index.html +13 -13
  102. package/dist/mini-chat.html +13 -13
  103. package/dist/mobile.html +13 -13
  104. package/package.json +2 -2
  105. package/server/index.js +392 -20
  106. package/server/lib/assistants/DOCUMENTATION.md +26 -21
  107. package/server/lib/assistants/assign.js +2 -1
  108. package/server/lib/assistants/contact-store.js +37 -19
  109. package/server/lib/assistants/contracts.js +11 -1
  110. package/server/lib/assistants/projection-history.test.js +454 -0
  111. package/server/lib/assistants/routes.js +11 -5
  112. package/server/lib/assistants/service.js +850 -294
  113. package/server/lib/assistants/service.test.js +588 -274
  114. package/server/lib/conversations/DOCUMENTATION.md +5 -5
  115. package/server/lib/conversations/service.js +87 -57
  116. package/server/lib/conversations/service.test.js +132 -100
  117. package/server/lib/conversations/validation.js +11 -1
  118. package/server/lib/event-stream/directory-ws-bridge.js +13 -2
  119. package/server/lib/event-stream/global-hub.js +51 -5
  120. package/server/lib/event-stream/runtime.js +57 -20
  121. package/server/lib/event-stream/runtime.test.js +112 -0
  122. package/server/lib/llm/DOCUMENTATION.md +95 -84
  123. package/server/lib/llm/catalog.js +111 -45
  124. package/server/lib/llm/catalog.test.js +121 -59
  125. package/server/lib/llm/completions.js +41 -12
  126. package/server/lib/llm/completions.test.js +45 -4
  127. package/server/lib/llm/generate.js +516 -453
  128. package/server/lib/llm/generate.test.js +705 -484
  129. package/server/lib/llm/routes.js +8 -4
  130. package/server/lib/message-queue/DOCUMENTATION.md +5 -5
  131. package/server/lib/message-queue/opencode-adapter.js +191 -104
  132. package/server/lib/message-queue/opencode-adapter.test.js +328 -58
  133. package/server/lib/message-queue/runtime.js +15 -2
  134. package/server/lib/message-queue/runtime.test.js +6 -6
  135. package/server/lib/message-queue/worker.js +7 -1
  136. package/server/lib/notifications/runtime.js +5 -0
  137. package/server/lib/opencode/DOCUMENTATION.md +103 -16
  138. package/server/lib/opencode/agent-document.d.ts +28 -0
  139. package/server/lib/opencode/agent-document.js +280 -0
  140. package/server/lib/opencode/agent-document.test.js +137 -0
  141. package/server/lib/opencode/agents.js +96 -0
  142. package/server/lib/opencode/auth-state-runtime.js +5 -0
  143. package/server/lib/opencode/bootstrap-runtime.js +2 -0
  144. package/server/lib/opencode/config-entity-routes.js +111 -102
  145. package/server/lib/opencode/config-entity-routes.test.js +123 -10
  146. package/server/lib/opencode/config-hot-reload.integration.test.js +105 -0
  147. package/server/lib/opencode/ensure-cli.js +238 -0
  148. package/server/lib/opencode/ensure-cli.test.js +119 -0
  149. package/server/lib/opencode/env-runtime.js +208 -111
  150. package/server/lib/opencode/env-runtime.test.js +211 -14
  151. package/server/lib/opencode/feature-routes-runtime.js +80 -5
  152. package/server/lib/opencode/feature-routes-runtime.test.js +18 -0
  153. package/server/lib/opencode/http-server-connections.js +98 -0
  154. package/server/lib/opencode/http-server-connections.test.js +321 -0
  155. package/server/lib/opencode/lifecycle.js +539 -95
  156. package/server/lib/opencode/lifecycle.test.js +698 -6
  157. package/server/lib/opencode/managed-capabilities-runtime.js +15 -2
  158. package/server/lib/opencode/managed-capabilities-runtime.test.js +1 -0
  159. package/server/lib/opencode/network-runtime.js +28 -16
  160. package/server/lib/opencode/network-runtime.test.js +68 -4
  161. package/server/lib/opencode/openchamber-routes.js +7 -1
  162. package/server/lib/opencode/opencode2-pin.js +139 -0
  163. package/server/lib/opencode/opencode2-pin.test.js +88 -0
  164. package/server/lib/opencode/opencode2-sidecar.e2e.test.js +326 -0
  165. package/server/lib/opencode/owned-runtime-upgrade.js +431 -0
  166. package/server/lib/opencode/owned-runtime-upgrade.test.js +160 -0
  167. package/server/lib/opencode/plugin-compatibility.js +15 -0
  168. package/server/lib/opencode/plugin-compatibility.test.js +21 -0
  169. package/server/lib/opencode/plugin-routes.js +3 -18
  170. package/server/lib/opencode/plugin-routes.test.js +9 -9
  171. package/server/lib/opencode/plugins.js +4 -3
  172. package/server/lib/opencode/plugins.test.js +12 -0
  173. package/server/lib/opencode/provider-catalog.js +58 -12
  174. package/server/lib/opencode/provider-catalog.test.js +48 -0
  175. package/server/lib/opencode/proxy.js +368 -28
  176. package/server/lib/opencode/proxy.test.js +338 -0
  177. package/server/lib/opencode/routes.js +303 -76
  178. package/server/lib/opencode/routes.test.js +315 -50
  179. package/server/lib/opencode/runtime-contract.js +401 -0
  180. package/server/lib/opencode/runtime-contract.test.js +214 -0
  181. package/server/lib/opencode/server-opencode-fetch.js +242 -0
  182. package/server/lib/opencode/server-opencode-fetch.test.js +130 -0
  183. package/server/lib/opencode/server-utils-runtime.js +35 -0
  184. package/server/lib/opencode/shared-service.integration.test.js +66 -0
  185. package/server/lib/opencode/shared-service.js +147 -0
  186. package/server/lib/opencode/shared-service.test.js +93 -0
  187. package/server/lib/opencode/shared.js +3 -3
  188. package/server/lib/opencode/shutdown-runtime.js +15 -6
  189. package/server/lib/opencode/shutdown-runtime.test.js +49 -1
  190. package/server/lib/opencode/skill-routes.js +26 -40
  191. package/server/lib/opencode/skill-routes.test.js +71 -7
  192. package/server/lib/opencode/skills.test.js +15 -0
  193. package/server/lib/opencode/startup-pipeline-runtime.js +4 -0
  194. package/server/lib/opencode/static-routes-runtime.js +3 -1
  195. package/server/lib/opencode/static-routes-runtime.test.js +33 -0
  196. package/server/lib/opencode/v1-migration-gate.js +157 -0
  197. package/server/lib/opencode/v1-migration-gate.test.js +111 -0
  198. package/server/lib/opencode/v2-client.js +60 -0
  199. package/server/lib/opencode/v2-plugin-host-shim/index.mjs +155 -0
  200. package/server/lib/opencode/v2-plugin-host-shim/package.json +4 -0
  201. package/server/lib/opencode/watcher.js +2 -1
  202. package/server/lib/opencode/watcher.test.js +1 -1
  203. package/server/lib/permission-auto-accept/DOCUMENTATION.md +5 -5
  204. package/server/lib/permission-auto-accept/runtime.js +14 -7
  205. package/server/lib/permission-auto-accept/runtime.test.js +27 -12
  206. package/server/lib/relay/reasoning-projection.e2e.test.ts +22 -7
  207. package/server/lib/scheduled-tasks/DOCUMENTATION.md +79 -39
  208. package/server/lib/scheduled-tasks/managed-tool-route.js +31 -1
  209. package/server/lib/scheduled-tasks/managed-tool-route.test.js +23 -0
  210. package/server/lib/scheduled-tasks/routes.js +27 -0
  211. package/server/lib/scheduled-tasks/routes.test.js +94 -0
  212. package/server/lib/scheduled-tasks/runtime.js +381 -310
  213. package/server/lib/scheduled-tasks/runtime.test.js +600 -309
  214. package/server/lib/session-goal/DOCUMENTATION.md +173 -22
  215. package/server/lib/session-goal/capability.js +57 -0
  216. package/server/lib/session-goal/capability.test.js +33 -0
  217. package/server/lib/session-goal/routes.js +240 -2
  218. package/server/lib/session-goal/routes.test.js +211 -0
  219. package/server/lib/session-goal/runtime.js +1617 -113
  220. package/server/lib/session-goal/runtime.test.js +1500 -70
  221. package/server/lib/session-index/DOCUMENTATION.md +18 -15
  222. package/server/lib/session-index/event-ingest.js +89 -16
  223. package/server/lib/session-index/event-ingest.test.js +73 -0
  224. package/server/lib/session-index/host-authority-snapshot.js +92 -0
  225. package/server/lib/session-index/routes.host-archive.test.js +201 -0
  226. package/server/lib/session-index/routes.js +63 -10
  227. package/server/lib/session-index/routes.test.js +8 -8
  228. package/server/lib/session-index/service.js +7 -1
  229. package/server/lib/session-index/sync-runtime.js +171 -41
  230. package/server/lib/session-index/sync-runtime.test.js +164 -9
  231. package/server/lib/session-metadata/DOCUMENTATION.md +86 -0
  232. package/server/lib/session-metadata/routes.js +193 -0
  233. package/server/lib/session-metadata/routes.test.js +156 -0
  234. package/server/lib/session-metadata/session-archive.d.ts +73 -0
  235. package/server/lib/session-metadata/session-archive.js +654 -0
  236. package/server/lib/session-metadata/session-archive.test.js +359 -0
  237. package/server/lib/session-metadata/session-metadata-store.d.ts +36 -0
  238. package/server/lib/session-metadata/session-metadata-store.js +375 -0
  239. package/server/lib/session-metadata/session-metadata-store.test.js +379 -0
  240. package/server/lib/session-metadata/session-projection.d.ts +41 -0
  241. package/server/lib/session-metadata/session-projection.js +255 -0
  242. package/server/lib/session-metadata/session-projection.test.js +64 -0
  243. package/server/lib/session-metadata/system-session.js +55 -0
  244. package/server/lib/session-metadata/system-session.test.js +55 -0
  245. package/server/lib/session-title/DOCUMENTATION.md +40 -7
  246. package/server/lib/session-title/native-runtime.test.js +167 -0
  247. package/server/lib/session-title/runtime.js +67 -42
  248. package/server/lib/session-title/session-access.js +46 -0
  249. package/server/lib/session-title/session-access.test.js +91 -0
  250. package/server/lib/session-turn-pages/DOCUMENTATION.md +12 -11
  251. package/server/lib/session-turn-pages/reconcile.service.js +1 -1
  252. package/server/lib/session-turn-pages/reconcile.service.test.js +16 -0
  253. package/server/lib/session-turn-pages/routes.js +123 -75
  254. package/server/lib/session-turn-pages/routes.test.js +566 -0
  255. package/server/lib/session-turn-pages/service.js +6 -2
  256. package/server/lib/session-turn-pages/service.test.js +34 -0
  257. package/server/lib/session-turn-pages/session-message-projection.d.ts +13 -0
  258. package/server/lib/session-turn-pages/session-message-projection.js +281 -0
  259. package/server/lib/small-model/DOCUMENTATION.md +101 -146
  260. package/server/lib/small-model/catalog.js +84 -54
  261. package/server/lib/small-model/catalog.test.js +111 -9
  262. package/server/lib/small-model/custom-api.js +68 -0
  263. package/server/lib/small-model/custom-api.test.js +64 -0
  264. package/server/lib/small-model/index.js +125 -241
  265. package/server/lib/small-model/resolve.js +35 -120
  266. package/server/lib/small-model/resolve.test.js +20 -110
  267. package/server/lib/small-model/routes.js +1 -3
  268. package/server/lib/small-model/summary-settings.test.js +157 -253
  269. package/server/opencode-proxy.test.js +63 -12
  270. package/dist/assets/AssistantView-BUveAgc4.js +0 -3
  271. package/dist/assets/MarkdownRenderer-BbrbDH6-.js +0 -2
  272. package/dist/assets/MobileDetailNavigation-B0aqKXd7.js +0 -1
  273. package/dist/assets/MobileSurface-DW7WfPsy.js +0 -2
  274. package/dist/assets/OverlayScrollbar-DrA0Vsg5.js +0 -2
  275. package/dist/assets/SettingsView-PkXIavr7.js +0 -25
  276. package/dist/assets/appThemeRegistry-D5Yxg4YF.js +0 -1
  277. package/dist/assets/collapsible-D5Phaykh.js +0 -1
  278. package/dist/assets/context-menu-CubSDW2g.js +0 -1
  279. package/dist/assets/es-DA3MqOo0.js +0 -15
  280. package/dist/assets/fr-Dbz0CdIA.js +0 -13
  281. package/dist/assets/gitApi-DXyC4MiV.js +0 -7
  282. package/dist/assets/insertionBoundaries-KlOVrHHt.js +0 -1
  283. package/dist/assets/ja-DumanVQA.js +0 -15
  284. package/dist/assets/ko-C0IkiUjQ.js +0 -15
  285. package/dist/assets/main-BPS5ylib.js +0 -2
  286. package/dist/assets/main-Cpd9VWYs.js +0 -169
  287. package/dist/assets/markdown-worker-CptoICmq.js +0 -2
  288. package/dist/assets/miniChat-B8_KLWiY.js +0 -2
  289. package/dist/assets/mobile-CtktKrXn.js +0 -2
  290. package/dist/assets/multirun-CYfP9Cfm.js +0 -4
  291. package/dist/assets/pl-B9No6MQp.js +0 -15
  292. package/dist/assets/platform-0w_Q9_ji.js +0 -2
  293. package/dist/assets/projectMeta-S1hJ7oBp.js +0 -3
  294. package/dist/assets/pt-BR-DyQ0WTDh.js +0 -15
  295. package/dist/assets/renderElectronMiniChatApp-iK14LWm8.js +0 -2
  296. package/dist/assets/renderMobileApp-DORLPWnb.js +0 -28
  297. package/dist/assets/src-rS3zCcsC.css +0 -2
  298. package/dist/assets/store-5B_LDnLM.js +0 -15
  299. package/dist/assets/uk-C_1KkqyP.js +0 -15
  300. package/dist/assets/useDesktopWindowControlsLayout-Dnr-jX0C.js +0 -1
  301. package/dist/assets/useEffectiveDirectory-CZLLyarc.js +0 -1
  302. package/dist/assets/useMobileNavigationStore-CoCiqNgm.js +0 -413
  303. package/dist/assets/useSessionAutoCleanup-xQyETQkG.js +0 -2
  304. package/dist/assets/useUIStore-Bxv3f23A.js +0 -1
  305. package/dist/assets/vendor-opencode-sdk-C07HQypa.js +0 -7
  306. package/dist/assets/zh-CN-uWILgBdt.js +0 -15
  307. package/dist/assets/zh-TW-MYfdvhbD.js +0 -15
  308. package/server/lib/small-model/call.js +0 -427
  309. package/server/lib/small-model/call.test.js +0 -382
  310. package/server/lib/small-model/opencode-session.js +0 -339
  311. package/server/lib/small-model/opencode-session.test.js +0 -177
@@ -1,10 +1,43 @@
1
1
  # Session Goal
2
2
 
3
3
  Server-side control loop that keeps a session working toward a user-defined
4
- objective stored under `metadata.openchamber.goal`, with the small model as
5
- an independent progress auditor. Built on OpenChamber's backend-driven
6
- architecture: the loop lives in
7
- the web server and survives UI disconnects.
4
+ objective. On OpenCode 2.x the goal record lives in the OpenChamber session
5
+ metadata store (`packages/web/server/lib/session-metadata/`), folded back onto
6
+ `session.metadata` by the proxy. Capability is **open** (`supported: true`):
7
+ manual UI, goal routes, and scheduled-task goal create all write the same
8
+ store. The small model is an independent progress auditor. Built on
9
+ OpenChamber's backend-driven architecture: the loop lives in the web server
10
+ and survives UI disconnects.
11
+
12
+ ## v2 storage (OpenChamber session metadata store)
13
+
14
+ OpenCode 2.x accepts session `metadata` only at create time — there is no
15
+ PATCH. Goal progress therefore belongs in the Host
16
+ `packages/web/server/lib/session-metadata/` store (merge-patched under
17
+ `openchamber.goal`), not an OpenCode `PATCH /session/:id`.
18
+
19
+ `createSessionGoalRuntime` accepts optional seams:
20
+
21
+ | Seam | Role |
22
+ |---|---|
23
+ | `readSessionMetadata` | Read Host metadata for the session |
24
+ | `persistSessionGoal` | Full goal replace (routes / scheduled create) |
25
+ | `mutateSessionMetadata` | Preferred conditional commit: `decide(committed) → { ok, patch }` on the store lock |
26
+
27
+ When `mutateSessionMetadata` is present, runtime goal commits use it exclusively
28
+ (no OpenCode PATCH). When only `readSessionMetadata` + `persistSessionGoal` are
29
+ present, the runtime falls back to read-then-persist. When neither store path is
30
+ wired, the legacy OpenCode PATCH path remains (tests / unwired servers).
31
+
32
+ Those seams, the proxy overlay, and `PUT /api/openchamber/sessions/:id/metadata`
33
+ are wired in `server/index.js`. A UI metadata write notifies the runtime with
34
+ a synthetic `session.updated` so create/resume can arm the loop. Metadata PUT
35
+ normalizes `executionGeneration` for goal patches inside the store mutate lock.
36
+
37
+ **Capability is open** (`supported === true`). Scheduled-task goal create and
38
+ `POST /api/goals/:sessionId` persist an active goal into the same store as
39
+ manual UI metadata writes; after first persist they notify the runtime with a
40
+ synthetic `session.updated` so the loop can arm.
8
41
 
9
42
  ## Goal payload (`metadata.openchamber.goal`)
10
43
 
@@ -18,14 +51,52 @@ the web server and survives UI disconnects.
18
51
  tokensUsed, // tokensCommitted + current segment (snapshot - baseline)
19
52
  tokensBaseline, // segment start snapshot (pre-goal turn; 0 after compaction)
20
53
  tokensCommitted, // closed segments' total (one segment per compaction)
21
- turnsUsed, // auto-continuations sent (capped at MAX_AUTO_TURNS)
54
+ turnsUsed, // auto-continuation attempts committed (capped at MAX_AUTO_TURNS)
22
55
  blockedStreak, // consecutive blocked audit verdicts
23
56
  auditFailStreak, // consecutive failed/unavailable audit calls
24
57
  note, // latest audit progress note, <= 280 chars
25
58
  statusReason, // why settled; 'resumed' is a kickoff signal from UI
26
59
  lastAccountedMessageID, // incremental accounting cursor
60
+ executionGeneration, // execution permit generation (see pause boundary)
61
+ pendingContinuation, // durable admission (ticket 10); null when idle
27
62
  createdAt, updatedAt
28
63
  }
64
+
65
+ ### Durable continuation admission (`pendingContinuation`)
66
+
67
+ Minimal Host-metadata field (not a parallel store). Written only through the
68
+ same conditional goal owner as other goal commits:
69
+
70
+ ```
71
+ {
72
+ messageID, // msg_goalc_<goalId>_<generation>_<turnsUsed>
73
+ goalId, generation, turnsUsed,
74
+ phase, // reserved | transport | uncertain | accepted
75
+ text, // fixed prompt payload for legal same-id re-dispatch
76
+ providerID, modelID, variant?, agent?,
77
+ error?, at
78
+ }
79
+ ```
80
+
81
+ Rules:
82
+
83
+ 1. **Reserve before send** — persist fixed `messageID` + prompt/selection
84
+ identity with `phase: reserved` **without** bumping `turnsUsed`.
85
+ 2. **Accept commit** — `turnsUsed` increment and `pendingContinuation: null`
86
+ share one conditional write after upstream accept (or reconcile `found`).
87
+ 3. **Selection / pre-prompt drop** — pause or switchAgent/switchModel failure
88
+ clears reserved pending; **0 count**.
89
+ 4. **Uncertain transport** — keep durable identity; pause auto-continue with a
90
+ user-visible `statusReason` (reuses blocked/pause reason UI). Reconcile via
91
+ official `session.inbox.list` / `session.message.get` (or projected list);
92
+ fetch failure is **unavailable**, never authoritative empty.
93
+ 5. **Already received** — count once, clear pending; do not invent a new id.
94
+ 6. **pause → resume / goal replace** — reserved pending cleared on pause;
95
+ transport/uncertain/accepted identity retained (no claim of upstream revoke).
96
+ Stale `goalId` / superseded `generation` never overwrite newer user ops;
97
+ found-under-stale-gen still counts once.
98
+ 7. **Reboot** — restore pending from metadata, reconcile first, then same-id
99
+ fixed-payload re-dispatch only when still absent and generation matches.
29
100
  ```
30
101
 
31
102
  The UI writes goals (create/edit/pause/resume/clear) by patching this
@@ -38,8 +109,49 @@ content, since "Implement this plan: X" alone gives the audit nothing to
38
109
  judge against. The armed send also attaches a synthetic system-reminder
39
110
  part telling the agent goal mode is active and that each turn should end
40
111
  with a factual done/verified/remaining statement for the independent audit.
41
- Freshness/stale-write protection is by `id`: every runtime write re-reads the
42
- session and drops the write when the stored goal id no longer matches.
112
+
113
+ ### Execution generation and pause/commit boundary
114
+
115
+ `executionGeneration` is the durable execution permit. It starts at `0` on
116
+ create and advances on:
117
+
118
+ - pause (user, abort, question) while status was `active`
119
+ - resume (`paused`/`blocked`/… → `active`)
120
+ - execution-condition edits (`tokenBudget`, `objective`, `objectiveFile`)
121
+
122
+ Settle paths (`active` → `complete` / `blocked` / `budgetLimited`) keep the
123
+ same generation. Writers go through `withGoalExecutionGeneration` (metadata
124
+ PUT, `persistSessionGoal` Host wiring) or the runtime's `advanceGeneration`
125
+ flag so UI patches that omit the field still bump correctly.
126
+
127
+ Runtime commits (continue accounting, settle, pause) validate **goal id +
128
+ expected generation + applicable status** on one serial boundary when the
129
+ Host `mutateSessionMetadata` seam is wired. A late audit that returns
130
+ `continue` / `complete` / `blocked` after pause cannot overwrite the paused
131
+ snapshot or start a new `session.prompt` continuation.
132
+
133
+ Local (in-process) ordering:
134
+
135
+ 1. Pause / stop / `session.updated` paused → bump dispatch epoch + abort
136
+ in-flight audit controller + clear idle timer (**before** awaiting durable
137
+ write).
138
+ 2. Tick captures epoch at start; re-checks before continue commit and before
139
+ dispatch.
140
+ 3. `sendContinuation` re-validates **epoch + shutdown recovery + stopped** after
141
+ every await (`switchAgent` / `switchModel` / generation re-read) and again
142
+ immediately before `session.prompt`. It also re-reads durable goal
143
+ generation/status at the prompt boundary. A pause or shutdown that lands
144
+ during selection must drop with `uncertain: false` and **zero** prompt.
145
+ 4. Continuation identity `{ goalId, generation, turnsUsed }` and
146
+ `phase: transport` are recorded **only at the actual prompt boundary** —
147
+ not before selection. Selection failures (agent/model switch) must **not**
148
+ be reported as prompt-uncertain. Uncertain applies only after prompt left
149
+ the process and transport failed; the identity is kept for reconcile. The
150
+ runtime does **not** claim full revoke of a request already handed upstream.
151
+ Explicit stop of the upstream turn remains the existing abort/stop operation.
152
+
153
+ Public seams for tests / controlled async: `runTick`, `pauseGoal`,
154
+ `getDispatchEpoch`, `getDispatchedContinuation`.
43
155
 
44
156
  ## File-backed objectives
45
157
 
@@ -53,11 +165,16 @@ before touching the filesystem). Rationale: metadata rides every
53
165
  `session.updated`, so multi-KB objectives must not live there.
54
166
 
55
167
  - `objectives.js` — write/read/delete, 5000-char clamp.
56
- - `routes.js` — `PUT/GET/DELETE /api/goals/objective/:sessionId`
57
- (OpenChamber-owned, registered before the generic proxy; JSON parsing via
58
- the `/api/goals` family in core-routes). The UI writes the file BEFORE
59
- patching the goal metadata and falls back to an inline objective when the
60
- write fails; `clearSessionGoal` deletes the file best-effort.
168
+ - `capability.js` — `getSessionGoalCapability()` / `assertSessionGoalSupported()`
169
+ (single source; currently `{ supported: true }`).
170
+ - `routes.js` — `GET /api/goals/capability`, create/resume endpoints,
171
+ `PUT/GET/DELETE /api/goals/objective/:sessionId` (OpenChamber-owned,
172
+ registered before the generic proxy; JSON parsing via the `/api/goals`
173
+ family in core-routes). Create / resume require an injected
174
+ `persistSessionGoal` (503 when missing); objective PUT writes the file when
175
+ supported. The UI writes the file BEFORE patching the goal metadata and
176
+ falls back to an inline objective when the write fails;
177
+ `clearSessionGoal` deletes the file best-effort.
61
178
  - The tick resolves the effective objective fresh on every cycle (the file
62
179
  is live-editable mid-goal) and falls back to the inline `objective` when
63
180
  the file is unreadable — a goal never dies because a file went away.
@@ -139,11 +256,23 @@ before touching the filesystem). Rationale: metadata rides every
139
256
  audit unavailable") — resumable, and settling resets the streak so
140
257
  Resume gets fresh tolerance. A dead small model can never drive the
141
258
  loop blind to the turn cap;
142
- - continue: persist accounting + `turnsUsed` first (a crash after the
143
- write just waits for the next idle tick; the reverse could double-send),
144
- re-check the tail, then `POST /session/:id/prompt_async` with the
145
- continuation prompt using the last assistant message's
146
- provider/model/agent — the goal spends the session's own subscription.
259
+ - continue (ticket 10 durable admission): persist accounting +
260
+ `pendingContinuation` (`phase: reserved`, fixed `msg_goalc_…` id +
261
+ prompt/selection payload) **without** bumping `turnsUsed`; re-check
262
+ tail; then `session.switchAgent` / `session.switchModel` +
263
+ `session.prompt` with that stable id. Accept → one conditional commit
264
+ of `turnsUsed + 1` and clear pending. Selection/pre-prompt pause or
265
+ failure clears reserved pending (0 count). Uncertain transport keeps
266
+ durable identity and pauses auto-continue with a readable
267
+ `statusReason`; next tick / reboot reconciles via inbox / exact
268
+ message (fail ≠ empty) before any new id. Already-received counts
269
+ once. Synthetic is metadata-only (`goalContinuation`).
270
+ - **Shutdown recovery (ticket 07):** `session.execution.interrupted` with
271
+ `reason: shutdown` clears the idle timer and sets a **local** recovery
272
+ pending marker (not a parallel durable store). Auto-continue / tick stay
273
+ gated until authoritative `session.status` idle or a non-shutdown
274
+ execution terminal clears the marker. Goal status itself is unchanged.
275
+
147
276
  4. Settling (`complete`/`blocked`/`budgetLimited`) fires the injected
148
277
  `emitGoalNotification` so the user hears about it even with the UI closed.
149
278
  The same settle also notifies a registered Assistant contact reporter
@@ -183,13 +312,35 @@ sees only that final turn, so the report is its evidence.
183
312
  (edit/pause/resume/complete/clear).
184
313
  - Sidebar glyph next to the date in `SessionNodeItem`.
185
314
 
315
+ ## Capability (Host goal state)
316
+
317
+ `capability.js` is the single source for whether Host goal state is available:
318
+
319
+ ```
320
+ getSessionGoalCapability() → { supported: true }
321
+ ```
322
+
323
+ Goal records live in the OpenChamber session-metadata store (not OpenCode
324
+ SessionInfo). Routes, scheduled tasks, and the proxy overlay share that store.
325
+
326
+ | Surface | While supported |
327
+ |---|---|
328
+ | `GET /api/goals/capability` | `{ supported: true }` |
329
+ | `POST /api/goals/:sessionId` (create) | Write objective + persist active goal (503 if persist missing) |
330
+ | `POST /api/goals/:sessionId/resume` | Set status active / statusReason resumed (404 if no goal) |
331
+ | `PUT /api/goals/objective/:sessionId` | Writes the objective file |
332
+ | `GET` / `DELETE` objective | Allowed |
333
+ | Scheduled `goalEnabled` create/edit | Allowed |
334
+ | Existing `goalEnabled` task run | Persist goal after session create; fail before create if persist unwired |
335
+
186
336
  ## Scheduled goals
187
337
 
188
- Scheduled tasks can run as goals: `execution.goalEnabled` (+ optional
189
- `execution.goalTokenBudget`) on a task makes the scheduled-tasks runtime
190
- stamp `metadata.openchamber.goal` onto the fresh session (objective = the
191
- expanded task prompt) and attach the goal-mode intro part to the prompt.
192
- The loop here picks it up from session events like any other goal.
338
+ Scheduled tasks with `execution.goalEnabled` (+ optional
339
+ `execution.goalTokenBudget`) write the objective file, persist an active goal
340
+ into the Host store (same shape as manual create), notify the goal runtime to
341
+ arm, and attach the goal-mode intro to the prompt. Without
342
+ `persistSessionGoal` injected, runs fail **before** session create (no
343
+ create/distill/prompt side effects).
193
344
 
194
345
  ## Limitations
195
346
 
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Session-goal Host capability for the current OpenCode surface.
3
+ *
4
+ * Goal records live in the OpenChamber session-metadata store
5
+ * (`packages/web/server/lib/session-metadata/`), not OpenCode SessionInfo.
6
+ * When the store + scheduled-task / route persist seams are wired, advertise
7
+ * supported so create·resume·edit paths write trackable goal state.
8
+ *
9
+ * `SESSION_GOAL_UNSUPPORTED_REASON` remains for structured refusals if a
10
+ * future surface must advertise unsupported again.
11
+ */
12
+
13
+ export const SESSION_GOAL_UNSUPPORTED_REASON = 'v2_goal_state_unavailable';
14
+
15
+ /**
16
+ * @returns {{ supported: false, reason: typeof SESSION_GOAL_UNSUPPORTED_REASON }
17
+ * | { supported: true, reason?: undefined }}
18
+ */
19
+ export function getSessionGoalCapability() {
20
+ return {
21
+ supported: true,
22
+ };
23
+ }
24
+
25
+ export function isSessionGoalSupported() {
26
+ return getSessionGoalCapability().supported === true;
27
+ }
28
+
29
+ /**
30
+ * Human-readable refusal for APIs / run history when goal state is unavailable.
31
+ * @param {{ supported: boolean, reason?: string }} [capability]
32
+ */
33
+ export function sessionGoalUnavailableMessage(capability = getSessionGoalCapability()) {
34
+ const reason = typeof capability?.reason === 'string' && capability.reason.trim()
35
+ ? capability.reason.trim()
36
+ : SESSION_GOAL_UNSUPPORTED_REASON;
37
+ return `session goal is unavailable (${reason})`;
38
+ }
39
+
40
+ /**
41
+ * Throw a structured refusal for create / edit / resume / objective-write paths.
42
+ * @param {{ operation?: string }} [options]
43
+ */
44
+ export function assertSessionGoalSupported(options = {}) {
45
+ const capability = getSessionGoalCapability();
46
+ if (capability.supported) {
47
+ return capability;
48
+ }
49
+ const operation = typeof options.operation === 'string' && options.operation.trim()
50
+ ? options.operation.trim()
51
+ : 'goal';
52
+ const error = new Error(`${sessionGoalUnavailableMessage(capability)}: cannot ${operation}`);
53
+ error.statusCode = 501;
54
+ error.code = capability.reason || SESSION_GOAL_UNSUPPORTED_REASON;
55
+ error.capability = capability;
56
+ throw error;
57
+ }
@@ -0,0 +1,33 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import {
3
+ SESSION_GOAL_UNSUPPORTED_REASON,
4
+ assertSessionGoalSupported,
5
+ getSessionGoalCapability,
6
+ isSessionGoalSupported,
7
+ sessionGoalUnavailableMessage,
8
+ } from './capability.js';
9
+
10
+ describe('session-goal capability', () => {
11
+ it('advertises supported Host goal state (session-metadata store)', () => {
12
+ expect(getSessionGoalCapability()).toEqual({
13
+ supported: true,
14
+ });
15
+ expect(SESSION_GOAL_UNSUPPORTED_REASON).toBe('v2_goal_state_unavailable');
16
+ expect(isSessionGoalSupported()).toBe(true);
17
+ });
18
+
19
+ it('formats a stable unavailable message for run history and APIs', () => {
20
+ expect(sessionGoalUnavailableMessage({
21
+ supported: false,
22
+ reason: SESSION_GOAL_UNSUPPORTED_REASON,
23
+ })).toBe(
24
+ 'session goal is unavailable (v2_goal_state_unavailable)',
25
+ );
26
+ });
27
+
28
+ it('assertSessionGoalSupported returns when supported', () => {
29
+ expect(assertSessionGoalSupported({ operation: 'create' })).toEqual({
30
+ supported: true,
31
+ });
32
+ });
33
+ });
@@ -1,12 +1,243 @@
1
+ import crypto from 'node:crypto';
2
+ import {
3
+ assertSessionGoalSupported,
4
+ getSessionGoalCapability,
5
+ isSessionGoalSupported,
6
+ sessionGoalUnavailableMessage,
7
+ } from './capability.js';
1
8
  import { deleteObjective, readObjective, writeObjective } from './objectives.js';
2
9
 
10
+ const asNonEmptyString = (value) => {
11
+ if (typeof value !== 'string') return null;
12
+ const trimmed = value.trim();
13
+ return trimmed.length > 0 ? trimmed : null;
14
+ };
15
+
16
+ const refuseGoalMutation = (res, operation) => {
17
+ try {
18
+ assertSessionGoalSupported({ operation });
19
+ return false;
20
+ } catch (error) {
21
+ const capability = error?.capability || getSessionGoalCapability();
22
+ res.status(Number(error?.statusCode) || 501).json({
23
+ error: error?.message || sessionGoalUnavailableMessage(capability),
24
+ reason: capability.reason,
25
+ capability,
26
+ operation,
27
+ });
28
+ return true;
29
+ }
30
+ };
31
+
32
+ const resolveTokenBudget = (value) => {
33
+ if (Number.isFinite(value) && value > 0) {
34
+ return Math.floor(value);
35
+ }
36
+ return null;
37
+ };
38
+
39
+ /**
40
+ * Build an active goal record accepted by parseGoalMetadata.
41
+ * File-backed: objectiveFile true (objective may be empty).
42
+ * Inline fallback: objective text required.
43
+ */
44
+ const buildActiveGoalRecord = ({
45
+ objective = '',
46
+ objectiveFile = false,
47
+ tokenBudget = null,
48
+ statusReason = '',
49
+ id = null,
50
+ } = {}) => {
51
+ const now = Date.now();
52
+ const inline = typeof objective === 'string' ? objective.trim() : '';
53
+ return {
54
+ id: typeof id === 'string' && id ? id : crypto.randomUUID(),
55
+ status: 'active',
56
+ objective: objectiveFile ? '' : inline,
57
+ objectiveFile: objectiveFile === true,
58
+ tokenBudget: resolveTokenBudget(tokenBudget),
59
+ tokensUsed: 0,
60
+ turnsUsed: 0,
61
+ blockedStreak: 0,
62
+ note: '',
63
+ statusReason: typeof statusReason === 'string' ? statusReason : '',
64
+ lastAccountedMessageID: '',
65
+ // Fresh goal starts a new execution generation (0). Resume / pause bump it.
66
+ executionGeneration: 0,
67
+ createdAt: now,
68
+ updatedAt: now,
69
+ };
70
+ };
71
+
3
72
  // OpenChamber-owned routes for file-backed goal objectives, keyed by session
4
73
  // id (one goal per session; a new goal overwrites the old file). The UI
5
74
  // writes the objective file before stamping the goal metadata (which only
6
75
  // carries an `objectiveFile: true` flag), reads it back for display, and
7
76
  // deletes it when the goal is removed.
8
- export function registerSessionGoalRoutes(app) {
77
+ //
78
+ // Host goal state lives in the session-metadata store. Create / resume need
79
+ // an injected persistSessionGoal; without it they return 503 rather than
80
+ // pretending success. GET remains readable for leftover files.
81
+ export function registerSessionGoalRoutes(app, deps = {}) {
82
+ const {
83
+ persistSessionGoal = null,
84
+ readSessionMetadata = null,
85
+ /** After create/resume persist — arm the goal loop (synthetic session.updated). */
86
+ onGoalPersisted = null,
87
+ } = deps;
88
+
89
+ const notifyGoalPersisted = (sessionID, directory, persisted, goal) => {
90
+ if (typeof onGoalPersisted !== 'function') return;
91
+ try {
92
+ const metadata = persisted
93
+ && typeof persisted === 'object'
94
+ && !Array.isArray(persisted)
95
+ && persisted.openchamber
96
+ ? persisted
97
+ : { openchamber: { goal } };
98
+ onGoalPersisted({ sessionID, directory: directory || '', metadata });
99
+ } catch (error) {
100
+ console.warn('[session-goal] onGoalPersisted failed:', error?.message || error);
101
+ }
102
+ };
103
+
104
+ app.get('/api/goals/capability', (_req, res) => {
105
+ res.json(getSessionGoalCapability());
106
+ });
107
+
108
+ app.post('/api/goals/:sessionId', async (req, res) => {
109
+ if (refuseGoalMutation(res, 'create')) return;
110
+ if (typeof persistSessionGoal !== 'function') {
111
+ res.status(503).json({
112
+ error: 'session goal persist is not configured',
113
+ operation: 'create',
114
+ capability: getSessionGoalCapability(),
115
+ });
116
+ return;
117
+ }
118
+
119
+ const sessionId = asNonEmptyString(req.params?.sessionId);
120
+ if (!sessionId) {
121
+ res.status(400).json({ error: 'a session id is required' });
122
+ return;
123
+ }
124
+
125
+ const body = req.body && typeof req.body === 'object' ? req.body : {};
126
+ const directory = asNonEmptyString(body.directory) || '';
127
+ const objectiveText = typeof body.objective === 'string' ? body.objective : '';
128
+ const tokenBudget = body.tokenBudget ?? body.goalTokenBudget ?? null;
129
+
130
+ let objectiveFile = false;
131
+ let inlineObjective = objectiveText;
132
+ try {
133
+ await writeObjective(sessionId, objectiveText);
134
+ objectiveFile = true;
135
+ inlineObjective = '';
136
+ } catch (error) {
137
+ console.warn('[session-goal] objective file write failed, using inline objective:', error?.message || error);
138
+ if (!asNonEmptyString(inlineObjective)) {
139
+ res.status(400).json({ error: 'objective is required when the objective file cannot be written' });
140
+ return;
141
+ }
142
+ }
143
+
144
+ const goal = buildActiveGoalRecord({
145
+ objective: inlineObjective,
146
+ objectiveFile,
147
+ tokenBudget,
148
+ });
149
+
150
+ let persisted;
151
+ try {
152
+ persisted = await persistSessionGoal(sessionId, directory, goal);
153
+ } catch (error) {
154
+ const message = error?.message || 'Failed to persist session goal';
155
+ console.error('[session-goal] create persist failed:', message);
156
+ res.status(500).json({ error: message, operation: 'create' });
157
+ return;
158
+ }
159
+
160
+ notifyGoalPersisted(sessionId, directory, persisted, goal);
161
+ res.json({ ok: true, goal });
162
+ });
163
+
164
+ app.post('/api/goals/:sessionId/resume', async (req, res) => {
165
+ if (refuseGoalMutation(res, 'resume')) return;
166
+ if (typeof persistSessionGoal !== 'function') {
167
+ res.status(503).json({
168
+ error: 'session goal persist is not configured',
169
+ operation: 'resume',
170
+ capability: getSessionGoalCapability(),
171
+ });
172
+ return;
173
+ }
174
+ if (typeof readSessionMetadata !== 'function') {
175
+ res.status(503).json({
176
+ error: 'session goal metadata read is not configured',
177
+ operation: 'resume',
178
+ capability: getSessionGoalCapability(),
179
+ });
180
+ return;
181
+ }
182
+
183
+ const sessionId = asNonEmptyString(req.params?.sessionId);
184
+ if (!sessionId) {
185
+ res.status(400).json({ error: 'a session id is required' });
186
+ return;
187
+ }
188
+
189
+ const body = req.body && typeof req.body === 'object' ? req.body : {};
190
+ const directory = asNonEmptyString(body.directory) || '';
191
+
192
+ let metadata;
193
+ try {
194
+ metadata = await readSessionMetadata(sessionId);
195
+ } catch (error) {
196
+ const message = error?.message || 'Failed to read session metadata';
197
+ console.error('[session-goal] resume read failed:', message);
198
+ res.status(503).json({ error: message, operation: 'resume' });
199
+ return;
200
+ }
201
+
202
+ const currentGoal = metadata?.openchamber?.goal;
203
+ if (!currentGoal || typeof currentGoal !== 'object') {
204
+ res.status(404).json({ error: 'goal not found', operation: 'resume' });
205
+ return;
206
+ }
207
+
208
+ const now = Date.now();
209
+ const priorGeneration = Number.isFinite(currentGoal.executionGeneration)
210
+ && currentGoal.executionGeneration >= 0
211
+ ? Math.floor(currentGoal.executionGeneration)
212
+ : 0;
213
+ const goal = {
214
+ ...currentGoal,
215
+ status: 'active',
216
+ statusReason: 'resumed',
217
+ // Resume opens a new execution generation so late audits from the prior
218
+ // active period cannot commit continue/settle.
219
+ executionGeneration: priorGeneration + 1,
220
+ updatedAt: now,
221
+ };
222
+
223
+ let persisted;
224
+ try {
225
+ persisted = await persistSessionGoal(sessionId, directory, goal);
226
+ } catch (error) {
227
+ const message = error?.message || 'Failed to persist session goal';
228
+ console.error('[session-goal] resume persist failed:', message);
229
+ res.status(500).json({ error: message, operation: 'resume' });
230
+ return;
231
+ }
232
+
233
+ notifyGoalPersisted(sessionId, directory, persisted, goal);
234
+ res.json({ ok: true, goal });
235
+ });
236
+
9
237
  app.put('/api/goals/objective/:sessionId', async (req, res) => {
238
+ // Objective write is part of manual goal create — refuse while unsupported
239
+ // so create cannot partial-succeed on the file alone.
240
+ if (refuseGoalMutation(res, 'create')) return;
10
241
  try {
11
242
  const { content } = req.body || {};
12
243
  await writeObjective(req.params.sessionId, content);
@@ -30,7 +261,14 @@ export function registerSessionGoalRoutes(app) {
30
261
  });
31
262
 
32
263
  app.delete('/api/goals/objective/:sessionId', async (req, res) => {
264
+ // Clear is allowed even when create is unsupported so leftover files from
265
+ // prior experiments can be removed without implying goal Host support.
33
266
  await deleteObjective(req.params.sessionId);
34
- res.json({ ok: true });
267
+ res.json({
268
+ ok: true,
269
+ ...(isSessionGoalSupported() ? {} : {
270
+ capability: getSessionGoalCapability(),
271
+ }),
272
+ });
35
273
  });
36
274
  }