@vantran-se/openchamber-web 1.24.3 → 2.0.1

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 (367) hide show
  1. package/README.md +2 -8
  2. package/bin/lib/commands-connect-url.js +3 -8
  3. package/bin/lib/commands-tunnel.js +3 -0
  4. package/dist/assets/{CodeMirrorCommentWidgets-BjJHrzmU.js → CodeMirrorCommentWidgets-ntXIbhV2.js} +1 -1
  5. package/dist/assets/DiffView-CjxUovbM.js +4 -0
  6. package/dist/assets/FilesView-DFWD0RQR.js +17 -0
  7. package/dist/assets/{GitView-CDOJy5Ay.js → GitView-BWa7e5vw.js} +4 -4
  8. package/dist/assets/{GuestAttachDialog-D3S0Rjic.js → GuestAttachDialog-DdOuyZzp.js} +1 -1
  9. package/dist/assets/GuestSurfacePane-DDfzid3U.js +1 -0
  10. package/dist/assets/{JsonTreeView-D0C9nU7K.js → JsonTreeView-3sGwqexh.js} +1 -1
  11. package/dist/assets/LinearIssuesView-C6dpIdjw.js +1 -0
  12. package/dist/assets/MarkdownImageGallery-Q7YR_tl4.js +1 -0
  13. package/dist/assets/MarkdownRendererImpl-DxDV56bL.js +12 -0
  14. package/dist/assets/OnboardingScreen-rK4Stg__.js +1 -0
  15. package/dist/assets/{PatchDiff-DnEhjXcC.js → PatchDiff-DS-IMU1j.js} +1 -1
  16. package/dist/assets/{PierreDiffViewer-DyxZVVVR.js → PierreDiffViewer-DW23KWKZ.js} +1 -1
  17. package/dist/assets/PlanView-BCk_VobP.js +5 -0
  18. package/dist/assets/PluginPane-BPP1dbck.js +3 -0
  19. package/dist/assets/SettingsView-kMmrpn43.js +29 -0
  20. package/dist/assets/SettingsWindow-BpvD6IYQ.js +1 -0
  21. package/dist/assets/SubmoduleDiffSummary-BLttSS1X.js +1 -0
  22. package/dist/assets/ToolOutputDialog-CtqDqbyr.js +15 -0
  23. package/dist/assets/ToolPartDiffPreview-5FcKdyRy.js +8 -0
  24. package/dist/assets/{VirtualizedFileDiff-0pyr_mHY.js → VirtualizedFileDiff-C763hNCR.js} +4 -4
  25. package/dist/assets/{WalkthroughView-yNj4nOP5.js → WalkthroughView-lap5btCF.js} +2 -2
  26. package/dist/assets/appThemeRegistry-u4zw1l0B.js +1 -0
  27. package/dist/assets/{commitAndPush-DvJKa3Ks.js → commitAndPush-CwGJfJG-.js} +1 -1
  28. package/dist/assets/{computeVirtualFileMetrics-DxTBMuu-.js → computeVirtualFileMetrics-DW5jjX4n.js} +1 -1
  29. package/dist/assets/de-CndEg67T.js +10 -0
  30. package/dist/assets/es-DglvPlaC.js +16 -0
  31. package/dist/assets/fr-DejPCpbS.js +14 -0
  32. package/dist/assets/index-C2r64QfD.js +1 -0
  33. package/dist/assets/index-CLcM8HKx.css +1 -0
  34. package/dist/assets/index-CvLgnuNI.js +41 -0
  35. package/dist/assets/{isDiffPlainText-D7FkBMP6.js → isDiffPlainText-D47a6hMP.js} +1 -1
  36. package/dist/assets/{isFilePlainText-CL2vsrIZ.js → isFilePlainText-B-mGgeKl.js} +1 -1
  37. package/dist/assets/ja-CVpQawCd.js +16 -0
  38. package/dist/assets/ko-Bw446h9j.js +16 -0
  39. package/dist/assets/main-Bz4WWXbr.js +2 -0
  40. package/dist/assets/main-oreqDsOV.js +896 -0
  41. package/dist/assets/{markdownCore-Dh7WEsdM.js → markdownCore-D2e5sp9y.js} +4 -4
  42. package/dist/assets/miniChat-D1v2gsBI.js +2 -0
  43. package/dist/assets/mobile-DBj3lCf2.js +2 -0
  44. package/dist/assets/{patchFileDiff-Bsba8KFp.js → patchFileDiff-BpeUOV91.js} +3 -3
  45. package/dist/assets/pl-BYNlPdhm.js +16 -0
  46. package/dist/assets/pt-BR-CL-Yfv9H.js +16 -0
  47. package/dist/assets/renderElectronMiniChatApp-DVtfEB2z.js +2 -0
  48. package/dist/assets/renderMobileApp-DFp9-xbG.js +25 -0
  49. package/dist/assets/runtimeEndpointReset-CsIpUep1.js +27 -0
  50. package/dist/assets/scheduledTasksApi-B5OHVpPM.js +1 -0
  51. package/dist/assets/{shikiHighlight-8_3csmr6.js → shikiHighlight-DNm2Z_Jc.js} +1 -1
  52. package/dist/assets/{textMateThemeFromAppTheme-CHo2fCgW.js → textMateThemeFromAppTheme-DGF2kG7g.js} +1 -1
  53. package/dist/assets/tr-DzmbxLy-.js +7 -0
  54. package/dist/assets/uk-C7vQnwro.js +16 -0
  55. package/dist/assets/useAppFontEffects-Ci3FEaPf.js +7337 -0
  56. package/dist/assets/{useInlineCommentController-DkQ_gqpn.js → useInlineCommentController-CuUuakrt.js} +2 -2
  57. package/dist/assets/useRootScrollLock-CQIsyERq.js +2 -0
  58. package/dist/assets/{vendor-codemirror-language-data-CdzUmUyZ.js → vendor-codemirror-language-data-CQa7_1cr.js} +2 -2
  59. package/dist/assets/vendor-cron-parser-CjvYVigk.js +2 -0
  60. package/dist/assets/vendor-opencode-client-_BAHQo4R.js +12 -0
  61. package/dist/assets/{vendor-remixicon-react-BDmGmFCA.js → vendor-remixicon-react-D6dZ0UwF.js} +1 -1
  62. package/dist/assets/{vendor-zod-CgAdXWZO.js → vendor-zod-Gea9uBlY.js} +1 -1
  63. package/dist/assets/{web-components-B8IcVQAA.js → web-components-B6tKQyJN.js} +2 -2
  64. package/dist/assets/zh-CN-Cl0srS-z.js +16 -0
  65. package/dist/assets/zh-TW-DtII5bDb.js +16 -0
  66. package/dist/index.html +13 -7
  67. package/dist/mini-chat.html +5 -5
  68. package/dist/mobile.html +5 -5
  69. package/package.json +6 -5
  70. package/server/index.d.ts +1 -0
  71. package/server/index.js +246 -91
  72. package/server/lib/agent-tool/DOCUMENTATION.md +32 -15
  73. package/server/lib/agent-tool/runtime.js +135 -54
  74. package/server/lib/agent-tool/runtime.test.js +155 -87
  75. package/server/lib/browser-control/DOCUMENTATION.md +37 -2
  76. package/server/lib/browser-control/provider.js +217 -0
  77. package/server/lib/browser-control/provider.test.js +251 -0
  78. package/server/lib/context-obligatory/runtime.js +61 -50
  79. package/server/lib/context-obligatory/runtime.test.js +62 -162
  80. package/server/lib/event-stream/DOCUMENTATION.md +12 -1
  81. package/server/lib/event-stream/delta-coalescer.js +0 -0
  82. package/server/lib/event-stream/delta-coalescer.test.js +68 -57
  83. package/server/lib/event-stream/directory-ws-bridge.js +1 -1
  84. package/server/lib/event-stream/global-hub.js +10 -1
  85. package/server/lib/event-stream/global-hub.test.js +27 -26
  86. package/server/lib/event-stream/index.js +6 -0
  87. package/server/lib/event-stream/protocol.js +26 -19
  88. package/server/lib/event-stream/protocol.test.js +18 -7
  89. package/server/lib/event-stream/rebind.test.js +5 -5
  90. package/server/lib/event-stream/runtime.test.js +17 -15
  91. package/server/lib/event-stream/translate-v2.js +313 -0
  92. package/server/lib/event-stream/translate-v2.test.js +209 -0
  93. package/server/lib/event-stream/upstream-reader.test.js +16 -16
  94. package/server/lib/fs/DOCUMENTATION.md +4 -2
  95. package/server/lib/fs/byte-range.js +35 -0
  96. package/server/lib/fs/byte-range.test.js +31 -0
  97. package/server/lib/fs/routes.js +47 -42
  98. package/server/lib/fs/routes.test.js +123 -18
  99. package/server/lib/git/service.js +12 -0
  100. package/server/lib/github/DOCUMENTATION.md +1 -1
  101. package/server/lib/github/pr-status.js +22 -2
  102. package/server/lib/github/pr-status.test.js +27 -14
  103. package/server/lib/guests/DOCUMENTATION.md +13 -0
  104. package/server/lib/guests/catalog.js +12 -0
  105. package/server/lib/guests/catalog.test.js +33 -0
  106. package/server/lib/guests/routes.js +10 -1
  107. package/server/lib/guests/service.js +192 -14
  108. package/server/lib/guests/service.test.js +201 -1
  109. package/server/lib/guests/surface.js +630 -0
  110. package/server/lib/guests/surface.test.js +486 -0
  111. package/server/lib/markdown-image-grants/routes.js +18 -5
  112. package/server/lib/markdown-image-grants/routes.test.js +9 -6
  113. package/server/lib/message-queue/DOCUMENTATION.md +33 -22
  114. package/server/lib/message-queue/runtime.js +155 -100
  115. package/server/lib/message-queue/runtime.test.js +168 -82
  116. package/server/lib/notifications/DOCUMENTATION.md +28 -0
  117. package/server/lib/notifications/control-stream.http.test.js +68 -0
  118. package/server/lib/notifications/emitter-runtime.js +15 -0
  119. package/server/lib/notifications/emitter-runtime.test.js +32 -0
  120. package/server/lib/notifications/push-runtime.js +5 -1
  121. package/server/lib/notifications/push-runtime.test.js +2 -2
  122. package/server/lib/notifications/routes.js +1 -1
  123. package/server/lib/notifications/runtime.js +115 -26
  124. package/server/lib/notifications/template-runtime.js +13 -15
  125. package/server/lib/notifications/template-runtime.test.js +16 -9
  126. package/server/lib/notifications/trigger-runtime.test.js +187 -0
  127. package/server/lib/openchamber-control/DOCUMENTATION.md +10 -0
  128. package/server/lib/openchamber-control/actions.js +3 -2
  129. package/server/lib/openchamber-control/error.js +1 -1
  130. package/server/lib/openchamber-control/file-open.js +59 -0
  131. package/server/lib/openchamber-control/file-open.test.js +57 -0
  132. package/server/lib/openchamber-control/routes.test.js +12 -0
  133. package/server/lib/openchamber-control/service.js +102 -49
  134. package/server/lib/openchamber-control/service.test.js +84 -44
  135. package/server/lib/openchamber-sessions/archive-store.js +224 -0
  136. package/server/lib/openchamber-sessions/archive-store.test.js +187 -0
  137. package/server/lib/openchamber-sessions/opencode-client.js +22 -0
  138. package/server/lib/openchamber-sessions/routes.js +398 -328
  139. package/server/lib/openchamber-sessions/routes.test.js +777 -740
  140. package/server/lib/openchamber-sessions/session-metadata-store.js +285 -0
  141. package/server/lib/openchamber-sessions/session-metadata-store.test.js +259 -0
  142. package/server/lib/opencode/DOCUMENTATION.md +449 -29
  143. package/server/lib/opencode/agents.js +233 -327
  144. package/server/lib/opencode/auth.js +41 -44
  145. package/server/lib/opencode/auth.test.js +92 -0
  146. package/server/lib/opencode/cli-upgrade.d.ts +4 -0
  147. package/server/lib/opencode/cli-upgrade.js +18 -0
  148. package/server/lib/opencode/cli-upgrade.test.js +36 -0
  149. package/server/lib/opencode/commands.js +164 -145
  150. package/server/lib/opencode/compatibility.d.ts +16 -0
  151. package/server/lib/opencode/compatibility.js +88 -0
  152. package/server/lib/opencode/compatibility.test.js +62 -0
  153. package/server/lib/opencode/config-entity-routes.js +58 -29
  154. package/server/lib/opencode/config-entity-v2.test.js +290 -0
  155. package/server/lib/opencode/config-mutation-response.js +17 -7
  156. package/server/lib/opencode/config-mutation-response.test.js +4 -7
  157. package/server/lib/opencode/config-paths.test.js +2 -2
  158. package/server/lib/opencode/config-v2.d.ts +183 -0
  159. package/server/lib/opencode/config-v2.js +801 -0
  160. package/server/lib/opencode/config-v2.test.js +466 -0
  161. package/server/lib/opencode/credential-db.js +144 -0
  162. package/server/lib/opencode/credential-db.test.js +104 -0
  163. package/server/lib/opencode/env-runtime.js +48 -9
  164. package/server/lib/opencode/env-runtime.test.js +45 -0
  165. package/server/lib/opencode/feature-routes-runtime.js +15 -5
  166. package/server/lib/opencode/lifecycle-process.test.js +7 -4
  167. package/server/lib/opencode/lifecycle.js +85 -30
  168. package/server/lib/opencode/lifecycle.test.js +80 -8
  169. package/server/lib/opencode/login-shell-env.js +27 -0
  170. package/server/lib/opencode/managed-config-file.js +119 -0
  171. package/server/lib/opencode/managed-config-file.test.js +152 -0
  172. package/server/lib/opencode/managed-plugin-config.js +33 -13
  173. package/server/lib/opencode/mcp.js +43 -149
  174. package/server/lib/opencode/network-runtime.js +6 -7
  175. package/server/lib/opencode/openchamber-routes.test.js +2 -5
  176. package/server/lib/opencode/plugin-routes.js +3 -3
  177. package/server/lib/opencode/plugin-routes.test.js +6 -24
  178. package/server/lib/opencode/plugins.js +91 -40
  179. package/server/lib/opencode/plugins.test.js +28 -15
  180. package/server/lib/opencode/providers.js +63 -83
  181. package/server/lib/opencode/providers.test.js +128 -59
  182. package/server/lib/opencode/proxy-agent-wiring.test.js +4 -2
  183. package/server/lib/opencode/proxy.js +179 -129
  184. package/server/lib/opencode/pwa-manifest-routes.js +4 -2
  185. package/server/lib/opencode/pwa-manifest-routes.test.js +3 -2
  186. package/server/lib/opencode/response-envelope.js +14 -0
  187. package/server/lib/opencode/response-envelope.test.js +23 -0
  188. package/server/lib/opencode/routes-upgrade.test.js +73 -116
  189. package/server/lib/opencode/routes.js +121 -428
  190. package/server/lib/opencode/server-utils-runtime.js +10 -4
  191. package/server/lib/opencode/session-activity.js +78 -0
  192. package/server/lib/opencode/session-runtime.js +21 -10
  193. package/server/lib/opencode/session-runtime.test.js +9 -9
  194. package/server/lib/opencode/settings-helpers.js +9 -9
  195. package/server/lib/opencode/settings-helpers.test.js +7 -12
  196. package/server/lib/opencode/settings-registry.json +6 -8
  197. package/server/lib/opencode/settings-runtime.js +18 -1
  198. package/server/lib/opencode/settings-runtime.test.js +31 -2
  199. package/server/lib/opencode/shared.js +54 -22
  200. package/server/lib/opencode/shared.test.js +8 -4
  201. package/server/lib/opencode/shutdown-runtime.js +54 -7
  202. package/server/lib/opencode/shutdown-runtime.test.js +224 -30
  203. package/server/lib/opencode/skill-routes.js +23 -24
  204. package/server/lib/opencode/skills.js +7 -5
  205. package/server/lib/opencode/tunnel-wiring-runtime.js +2 -1
  206. package/server/lib/opencode/upgrade-capability.test.js +1 -1
  207. package/server/lib/opencode/v1-migration-topup.js +288 -0
  208. package/server/lib/opencode/v1-migration-topup.test.js +266 -0
  209. package/server/lib/opencode/v2-install.d.ts +8 -0
  210. package/server/lib/opencode/v2-install.js +158 -0
  211. package/server/lib/opencode/v2-install.test.js +124 -0
  212. package/server/lib/opencode/watcher.js +10 -3
  213. package/server/lib/opencode/watcher.test.js +17 -21
  214. package/server/lib/opencode/websearch-config.js +66 -0
  215. package/server/lib/package-manager.js +55 -68
  216. package/server/lib/package-manager.test.js +241 -75
  217. package/server/lib/permission-auto-accept/runtime.js +38 -22
  218. package/server/lib/permission-auto-accept/runtime.test.js +39 -17
  219. package/server/lib/quota/DOCUMENTATION.md +4 -0
  220. package/server/lib/quota/providers/xai.js +4 -4
  221. package/server/lib/relay/tunnel-host.js +4 -1
  222. package/server/lib/relay/tunnel-host.test.js +7 -0
  223. package/server/lib/routing/DOCUMENTATION.md +52 -21
  224. package/server/lib/routing/defaults.js +23 -6
  225. package/server/lib/routing/jev.js +17 -7
  226. package/server/lib/routing/routes.http.test.js +68 -33
  227. package/server/lib/routing/routes.js +65 -22
  228. package/server/lib/routing/runtime.js +145 -48
  229. package/server/lib/routing/runtime.test.js +79 -55
  230. package/server/lib/scheduled-tasks/DOCUMENTATION.md +4 -0
  231. package/server/lib/scheduled-tasks/issue-2710-double-execution.test.js +51 -46
  232. package/server/lib/scheduled-tasks/runtime.js +81 -74
  233. package/server/lib/scheduled-tasks/runtime.test.js +53 -1
  234. package/server/lib/session-assist/DOCUMENTATION.md +8 -3
  235. package/server/lib/session-assist/context.js +122 -46
  236. package/server/lib/session-assist/context.test.js +117 -49
  237. package/server/lib/session-assist/runtime.js +52 -22
  238. package/server/lib/session-assist/runtime.test.js +145 -173
  239. package/server/lib/session-goal/DOCUMENTATION.md +23 -15
  240. package/server/lib/session-goal/create.js +9 -12
  241. package/server/lib/session-goal/create.test.js +27 -28
  242. package/server/lib/session-goal/runtime.js +164 -77
  243. package/server/lib/session-goal/runtime.test.js +271 -519
  244. package/server/lib/session-knowledge/DOCUMENTATION.md +5 -0
  245. package/server/lib/session-knowledge/runtime.js +59 -41
  246. package/server/lib/session-knowledge/runtime.test.js +90 -20
  247. package/server/lib/small-model/DOCUMENTATION.md +154 -208
  248. package/server/lib/small-model/client.js +113 -0
  249. package/server/lib/small-model/index.js +296 -158
  250. package/server/lib/small-model/index.test.js +423 -256
  251. package/server/lib/small-model/routes.js +1 -1
  252. package/server/lib/spaces/DOCUMENTATION.md +807 -0
  253. package/server/lib/spaces/code-in-bait.js +185 -0
  254. package/server/lib/spaces/code-in.js +641 -0
  255. package/server/lib/spaces/code-in.test.js +1048 -0
  256. package/server/lib/spaces/errors.js +8 -0
  257. package/server/lib/spaces/exec-http.js +94 -0
  258. package/server/lib/spaces/gatekeeper-channel.js +202 -0
  259. package/server/lib/spaces/gatekeeper-channel.test.js +215 -0
  260. package/server/lib/spaces/gatekeeper-program.cjs +914 -0
  261. package/server/lib/spaces/gatekeeper-program.test.js +1391 -0
  262. package/server/lib/spaces/hardening.js +457 -0
  263. package/server/lib/spaces/hardening.test.js +368 -0
  264. package/server/lib/spaces/host-git.js +127 -0
  265. package/server/lib/spaces/host-git.test.js +137 -0
  266. package/server/lib/spaces/labels.js +234 -0
  267. package/server/lib/spaces/labels.test.js +219 -0
  268. package/server/lib/spaces/layout.js +184 -0
  269. package/server/lib/spaces/layout.test.js +30 -0
  270. package/server/lib/spaces/manager.js +82 -0
  271. package/server/lib/spaces/manager.test.js +114 -0
  272. package/server/lib/spaces/places/code-in.docker.live.test.js +216 -0
  273. package/server/lib/spaces/places/contract-suite.js +156 -0
  274. package/server/lib/spaces/places/contract.docker.live.test.js +7 -0
  275. package/server/lib/spaces/places/contract.test.js +6 -0
  276. package/server/lib/spaces/places/docker-engine.js +126 -0
  277. package/server/lib/spaces/places/docker-live-support.js +202 -0
  278. package/server/lib/spaces/places/docker-tools.js +186 -0
  279. package/server/lib/spaces/places/docker.js +587 -0
  280. package/server/lib/spaces/places/docker.test.js +1610 -0
  281. package/server/lib/spaces/places/escape-suite.js +1018 -0
  282. package/server/lib/spaces/places/escape.docker.live.test.js +7 -0
  283. package/server/lib/spaces/places/fake-docker.js +375 -0
  284. package/server/lib/spaces/places/memory-place.js +103 -0
  285. package/server/lib/spaces/places/packed.docker.live.test.js +77 -0
  286. package/server/lib/spaces/places/registry.js +43 -0
  287. package/server/lib/spaces/places/registry.test.js +50 -0
  288. package/server/lib/spaces/places/server.docker.live.test.js +225 -0
  289. package/server/lib/spaces/run-command.js +261 -0
  290. package/server/lib/spaces/run-command.test.js +396 -0
  291. package/server/lib/spaces/space-server.js +190 -0
  292. package/server/lib/spaces/space-server.test.js +335 -0
  293. package/server/lib/spaces/tools-filler.js +82 -0
  294. package/server/lib/spaces/tools-filler.test.js +148 -0
  295. package/server/lib/spaces/tools-pack.js +49 -0
  296. package/server/lib/spaces/tools-pack.test.js +57 -0
  297. package/server/lib/spaces/tools.js +138 -0
  298. package/server/lib/spaces/tools.test.js +153 -0
  299. package/server/lib/tts/openai-sdk.js +11 -0
  300. package/server/lib/tts/service.js +7 -5
  301. package/server/lib/tts/stt.js +3 -2
  302. package/server/lib/tunnels/DOCUMENTATION.md +2 -0
  303. package/server/lib/tunnels/routes.js +7 -0
  304. package/server/lib/tunnels/routes.test.js +55 -0
  305. package/server/lib/ui-auth/ui-auth.js +38 -2
  306. package/server/lib/ui-auth/ui-auth.test.js +42 -0
  307. package/server/lib/ui-auth/ui-passkeys.js +12 -10
  308. package/server/lib/walkthrough/DOCUMENTATION.md +10 -1
  309. package/server/lib/walkthrough/pull-request.js +61 -8
  310. package/server/lib/walkthrough/pull-request.test.js +54 -1
  311. package/server/lib/walkthrough/routes.js +20 -0
  312. package/server/lib/walkthrough/routes.test.js +19 -0
  313. package/server/opencode-proxy.test.js +190 -166
  314. package/server/opencode-worktree-readiness.test.js +5 -4
  315. package/dist/assets/DiffView-COVQhWGd.js +0 -4
  316. package/dist/assets/FilesView-B1V5JxFX.js +0 -13
  317. package/dist/assets/LinearIssuesView-D7FKg1XJ.js +0 -1
  318. package/dist/assets/MarkdownImageGallery-BrFjAVEf.js +0 -1
  319. package/dist/assets/MarkdownRendererImpl-DWepTafh.js +0 -12
  320. package/dist/assets/OnboardingScreen-Dyxxiqdt.js +0 -1
  321. package/dist/assets/PlanView-LW2z_EAP.js +0 -5
  322. package/dist/assets/PluginPane-B4tKno2h.js +0 -3
  323. package/dist/assets/SettingsView-DEaMulN9.js +0 -29
  324. package/dist/assets/SettingsWindow-FLDJXOgQ.js +0 -1
  325. package/dist/assets/SubmoduleDiffSummary-CvVwD-Vi.js +0 -1
  326. package/dist/assets/ToolOutputDialog-BiP0x2Rw.js +0 -15
  327. package/dist/assets/ToolPartDiffPreview-CkR4msKP.js +0 -8
  328. package/dist/assets/appThemeRegistry-BuUKhbYQ.js +0 -1
  329. package/dist/assets/de-DgxgFyGG.js +0 -10
  330. package/dist/assets/es-BFsZj5F_.js +0 -16
  331. package/dist/assets/fr-jdZ5KKQi.js +0 -14
  332. package/dist/assets/index-CKHjPKbq.js +0 -1
  333. package/dist/assets/index-Cbci4i1W.js +0 -40
  334. package/dist/assets/index-DqaMWNrC.css +0 -1
  335. package/dist/assets/ja-C5kKrhO4.js +0 -16
  336. package/dist/assets/ko-DXDTT_0O.js +0 -16
  337. package/dist/assets/main-CZALtVek.js +0 -896
  338. package/dist/assets/main-CgXMBNvG.js +0 -2
  339. package/dist/assets/miniChat-CwTmDixi.js +0 -2
  340. package/dist/assets/mobile-CLf2vCtO.js +0 -2
  341. package/dist/assets/pl-D-7x4Y8Q.js +0 -16
  342. package/dist/assets/pt-BR-CSBZrHa8.js +0 -16
  343. package/dist/assets/renderElectronMiniChatApp-C5gAFBk0.js +0 -2
  344. package/dist/assets/renderMobileApp-V4hYd32H.js +0 -25
  345. package/dist/assets/runtimeEndpointReset-DhlHCn-2.js +0 -25
  346. package/dist/assets/scheduledTasksApi-DzjbLd8P.js +0 -1
  347. package/dist/assets/tr-Dr-oJ22L.js +0 -7
  348. package/dist/assets/uk-9Nx83UcI.js +0 -16
  349. package/dist/assets/useAppFontEffects-khZXpFL_.js +0 -7333
  350. package/dist/assets/useRootScrollLock-DdvHf9Pt.js +0 -2
  351. package/dist/assets/vendor-cron-parser-CUcP7OfX.js +0 -2
  352. package/dist/assets/vendor-opencode-sdk-BJ73u-bH.js +0 -7
  353. package/dist/assets/zh-CN-Bxy_Hj3r.js +0 -16
  354. package/dist/assets/zh-TW-0BB4gpHM.js +0 -16
  355. package/server/lib/opencode/mcp-oauth-callback.test.js +0 -111
  356. package/server/lib/routing/feature-flag.js +0 -11
  357. package/server/lib/small-model/call.js +0 -851
  358. package/server/lib/small-model/call.test.js +0 -1292
  359. package/server/lib/small-model/catalog.js +0 -13
  360. package/server/lib/small-model/resolve.js +0 -131
  361. package/server/lib/small-model/resolve.test.js +0 -197
  362. package/server/lib/small-model/runtime-providers.js +0 -164
  363. package/server/lib/small-model/runtime-providers.test.js +0 -118
  364. package/server/lib/system-prompt/DOCUMENTATION.md +0 -36
  365. package/server/lib/system-prompt/runtime.js +0 -49
  366. package/server/lib/system-prompt/runtime.test.js +0 -76
  367. package/server/lib/walkthrough/reproduce-2607.test.js +0 -148
@@ -1,40 +1,57 @@
1
1
  import { describe, expect, it } from 'vitest';
2
- import { excerpt, loadAssistContext } from './context.js';
2
+ import { excerpt, loadAssistContext, newestContentId } from './context.js';
3
3
  import { buildAssistPrompt } from './prompt.js';
4
4
 
5
- const record = (id, role, text, extra = {}, parts = []) => ({
6
- info: { id, role, parentID: 'user', finish: 'stop', time: { completed: 1 }, providerID: 'provider', modelID: 'model', ...extra },
7
- parts: [...(text ? [{ type: 'text', text }] : []), ...parts],
5
+ // v2 message records are flat and a page lists them newest first.
6
+ const user = (id, text, extra = {}) => ({ id, type: 'user', text, ...extra });
7
+ const assistant = (id, text, extra = {}) => ({
8
+ id,
9
+ type: 'assistant',
10
+ content: text ? [{ type: 'text', text }] : [],
11
+ finish: 'stop',
12
+ time: { completed: 1 },
13
+ model: { providerID: 'provider', id: 'model' },
14
+ ...extra,
8
15
  });
9
- const pair = (id) => [record(`u${id}`, 'user', `request ${id}`), record(`a${id}`, 'assistant', `answer ${id}`, { parentID: `u${id}` })];
10
- const page = (data, cursor = '') => ({ data, response: { headers: new Headers({ 'x-next-cursor': cursor }) } });
16
+ const synthetic = (id, text, extra = {}) => ({ id, type: 'synthetic', text, ...extra });
17
+ // OpenCode closes every turn with an idle marker; a switch is appended when
18
+ // the user changes model or agent between turns.
19
+ const idle = (id, outcome = 'succeeded') => ({ id, type: 'idle', outcome, time: { created: 2 } });
20
+ const modelSwitch = (id) => ({ id, type: 'model-switched', model: { providerID: 'p', id: 'm' } });
21
+
22
+ const pair = (id) => [user(`u${id}`, `request ${id}`), assistant(`a${id}`, `answer ${id}`)];
23
+
24
+ /** `data` is given oldest-first here and reversed, the way OpenCode serves it. */
25
+ const page = (data, next = null) => ({ data: [...data].reverse(), cursor: next ? { next } : {} });
11
26
  const load = (readPage) => loadAssistContext({ readPage, signal: new AbortController().signal });
12
27
 
13
28
  describe('session assist context', () => {
14
29
  it('stops paging at three human turns and excludes tools and injected instructions', async () => {
15
30
  let calls = 0;
16
31
  const records = [...pair(1), ...pair(2), ...pair(3), ...pair(4)];
17
- records.at(-1).parts.push({ type: 'tool', state: { output: 'TOOL_PAYLOAD' } }, { type: 'text', synthetic: true, text: 'INJECTED_PROMPT' });
18
- const context = await load(async ({ limit, before }) => {
32
+ records.at(-1).content.push(
33
+ { type: 'tool', tool: 'bash', state: { output: 'TOOL_PAYLOAD' } },
34
+ );
35
+ const context = await load(async ({ limit, cursor }) => {
19
36
  calls++;
20
37
  expect(limit).toBe(50);
21
- expect(before).toBeUndefined();
38
+ expect(cursor).toBeUndefined();
22
39
  return page(records, 'more');
23
40
  });
24
41
  expect(calls).toBe(1);
25
42
  expect(context.turns.map((t) => t.user.id)).toEqual(['u2', 'u3', 'u4']);
26
43
  expect(context.last.text).toBe('answer 4');
27
44
  expect(JSON.stringify(context)).not.toContain('TOOL_PAYLOAD');
28
- expect(JSON.stringify(context)).not.toContain('INJECTED_PROMPT');
29
45
  });
30
46
 
31
- it('finds the real user across a page boundary and compaction continuations', async () => {
32
- const summary = record('summary', 'assistant', 'INTERNAL SUMMARY', { summary: true });
33
- const continuation = record('continuation', 'user', '', {}, [{ type: 'text', synthetic: true, text: 'Continue' }]);
34
- const final = record('final', 'assistant', 'All requested work done', { parentID: 'continuation' });
35
- const context = await load(async ({ before }) => before
36
- ? page([...pair(1), ...pair(2), record('human', 'user', 'Finish the requested work')])
37
- : page([summary, continuation, final], 'older'));
47
+ it('finds the real user across a page boundary and past a compaction', async () => {
48
+ // v2 gives compaction its own message role instead of an assistant message
49
+ // flagged `summary`.
50
+ const compaction = { id: 'summary', type: 'compaction', status: 'completed', summary: 'INTERNAL SUMMARY' };
51
+ const final = assistant('final', 'All requested work done');
52
+ const context = await load(async ({ cursor }) => (cursor
53
+ ? page([...pair(1), ...pair(2), user('human', 'Finish the requested work')])
54
+ : page([compaction, final], 'older')));
38
55
  expect(context.last.id).toBe('final');
39
56
  expect(context.turns.at(-1).user.id).toBe('human');
40
57
  expect(context.turns.at(-1).assistant.id).toBe('final');
@@ -43,8 +60,8 @@ describe('session assist context', () => {
43
60
 
44
61
  it('retains interrupted requests as progress, not as completed turns', async () => {
45
62
  const context = await load(async () => page([
46
- record('u1', 'user', 'Implement both fixes'),
47
- record('a1', 'assistant', 'First fix done', { finish: 'tool-calls', parentID: 'u1' }),
63
+ user('u1', 'Implement both fixes'),
64
+ assistant('a1', 'First fix done', { finish: 'tool-calls' }),
48
65
  ...pair(2),
49
66
  ]));
50
67
  expect(context.turns[0].complete).toBe(false);
@@ -52,22 +69,31 @@ describe('session assist context', () => {
52
69
  expect(context.turns[1].complete).toBe(true);
53
70
  });
54
71
 
55
- it('keeps synthetic annotations and separates author language from quoted material', async () => {
56
- const user = record('u', 'user', '', {}, [{
57
- type: 'text', synthetic: true, text: 'Serialized attachment',
72
+ it('folds an attached synthetic into the user message it belongs to', async () => {
73
+ // v1 carried an attachment as a synthetic part of the user message; v2
74
+ // sends it as its own turn just before the prompt.
75
+ const attachment = synthetic('s', 'Serialized attachment', {
58
76
  metadata: { openchamberContext: { kind: 'chat-quote', quote: 'English quoted source', text: 'Виправ саме це' } },
59
- }]);
60
- const context = await load(async () => page([user, record('a', 'assistant', 'Done', { parentID: 'u' })]));
77
+ });
78
+ const context = await load(async () => page([attachment, user('u', 'Fix it'), assistant('a', 'Done')]));
79
+ expect(context.turns).toHaveLength(1);
61
80
  expect(context.turns[0].user.text).toContain('> English quoted source');
62
81
  expect(context.turns[0].user.text).toContain('User comment:\nВиправ саме це');
63
- expect(context.turns[0].user.authored).toBe('Виправ саме це');
82
+ expect(context.turns[0].user.text).toContain('Fix it');
83
+ expect(context.turns[0].user.authored).toContain('Виправ саме це');
64
84
  });
65
85
 
66
86
  it('retains the comment after a large quote and the conclusion after a long answer', async () => {
67
- const user = record('u', 'user', '', {}, [{ type: 'text', synthetic: true, text: 'attachment', metadata: {
68
- openchamberContext: { kind: 'browser-annotation', prompt: 'x'.repeat(30_000), text: 'USER_REQUEST_END' },
69
- } }]);
70
- const context = await load(async () => page([user, record('a', 'assistant', `START${'x'.repeat(30_000)}CONCLUSION`, { parentID: 'u' })]));
87
+ const attachment = synthetic('s', 'attachment', {
88
+ metadata: {
89
+ openchamberContext: { kind: 'browser-annotation', prompt: 'x'.repeat(30_000), text: 'USER_REQUEST_END' },
90
+ },
91
+ });
92
+ const context = await load(async () => page([
93
+ attachment,
94
+ user('u', 'Look at this'),
95
+ assistant('a', `START${'x'.repeat(30_000)}CONCLUSION`),
96
+ ]));
71
97
  expect(context.turns[0].user.text).toContain('USER_REQUEST_END');
72
98
  expect(context.last.text).toMatch(/^START/);
73
99
  expect(context.last.text).toMatch(/CONCLUSION$/);
@@ -75,10 +101,10 @@ describe('session assist context', () => {
75
101
  });
76
102
 
77
103
  it('fails explicitly on malformed annotation text instead of losing the comment', async () => {
78
- const user = record('u', 'user', '', {}, [{ type: 'text', synthetic: true, text: 'attachment', metadata: {
79
- openchamberContext: { kind: 'chat-quote', quote: 'source', text: 42 },
80
- } }]);
81
- await expect(load(async () => page([user, record('a', 'assistant', 'done')]))).rejects.toThrow();
104
+ const attachment = synthetic('s', 'attachment', {
105
+ metadata: { openchamberContext: { kind: 'chat-quote', quote: 'source', text: 42 } },
106
+ });
107
+ await expect(load(async () => page([attachment, user('u', 'Fix'), assistant('a', 'done')]))).rejects.toThrow();
82
108
  });
83
109
 
84
110
  it('does not treat a failed or repeated page as complete history', async () => {
@@ -93,24 +119,68 @@ describe('session assist context', () => {
93
119
 
94
120
  it('bounds retrieval and refuses to invent a user for an orphaned answer', async () => {
95
121
  let calls = 0;
96
- const context = await load(async () => page([record(`a${++calls}`, 'assistant', 'answer')], `cursor${calls}`));
122
+ const context = await load(async () => page([assistant(`a${++calls}`, 'answer')], `cursor${calls}`));
97
123
  expect(calls).toBe(8);
98
124
  expect(context).toBeNull();
99
125
  });
100
126
 
101
- it('skips unfinished, failed, summary, and user tails', async () => {
127
+ it('looks past the idle marker that closes a finished turn', async () => {
128
+ // The ordinary v2 transcript: answer, then `idle`, possibly followed by a
129
+ // switch the user made afterwards. The answer is still the last message.
130
+ for (const tail of [[idle('i')], [idle('i'), modelSwitch('m')], [modelSwitch('m'), idle('i')]]) {
131
+ const context = await load(async () => page([...pair(1), ...tail]));
132
+ expect(context?.last.id).toBe('a1');
133
+ expect(context.turns).toHaveLength(1);
134
+ expect(context.turns[0].assistant.id).toBe('a1');
135
+ }
136
+ });
137
+
138
+ it('keeps an attachment glued to its prompt across a switch, and a turn across an idle', async () => {
139
+ const attachment = synthetic('s', 'Attached note');
140
+ const context = await load(async () => page([
141
+ ...pair(1), idle('i1'),
142
+ attachment, modelSwitch('m'), user('u2', 'Use the note'), assistant('a2', 'Used'), idle('i2'),
143
+ ]));
144
+ expect(context.turns.map((turn) => turn.user.id)).toEqual(['u1', 'u2']);
145
+ expect(context.turns[1].user.text).toBe('Attached note\n\nUse the note');
146
+ expect(context.turns[0].complete).toBe(true);
147
+ });
148
+
149
+ it('treats a failed or interrupted idle as evidence the turn did not finish', async () => {
150
+ for (const outcome of ['failed', 'interrupted']) {
151
+ expect(await load(async () => page([...pair(1), idle('i', outcome)]))).toBeNull();
152
+ }
153
+ });
154
+
155
+ it('keeps paging when a page holds only service records', async () => {
156
+ const context = await load(async ({ cursor }) => (cursor
157
+ ? page([...pair(1)])
158
+ : page([idle('i1'), modelSwitch('m'), idle('i2')], 'older')));
159
+ expect(context?.last.id).toBe('a1');
160
+ });
161
+
162
+ it('names the newest content record of a newest-first page', () => {
163
+ expect(newestContentId([idle('i'), modelSwitch('m'), assistant('a', 'x'), user('u', 'y')])).toBe('a');
164
+ expect(newestContentId([idle('i', 'failed'), assistant('a', 'x')])).toBe('i');
165
+ expect(newestContentId([idle('i')])).toBeNull();
166
+ expect(newestContentId(undefined)).toBeNull();
167
+ });
168
+
169
+ it('skips unfinished, failed, compaction, and user tails', async () => {
102
170
  for (const tail of [
103
- record('tail', 'user', 'New request'),
104
- record('tail', 'assistant', 'Working', { finish: 'tool-calls' }),
105
- record('tail', 'assistant', 'Failed', { error: { name: 'error' } }),
106
- record('tail', 'assistant', 'Summary', { summary: true }),
171
+ user('tail', 'New request'),
172
+ assistant('tail', 'Working', { finish: 'tool-calls' }),
173
+ assistant('tail', 'Failed', { error: { type: 'APIError', message: 'boom' } }),
174
+ { id: 'tail', type: 'compaction', status: 'completed', summary: 'Summary' },
107
175
  ]) expect(await load(async () => page([...pair(1), tail]))).toBeNull();
108
176
  });
109
177
 
110
178
  it('keeps both sides of the newest exchange within a small model budget', async () => {
111
179
  const context = await load(async () => page([
112
- ...pair(1), ...pair(2), record('latest', 'user', `REQUEST_START${'u'.repeat(8000)}REQUEST_END`),
113
- record('answer', 'assistant', `ANSWER_START${'a'.repeat(16000)}ANSWER_END`, { parentID: 'latest' }),
180
+ ...pair(1),
181
+ ...pair(2),
182
+ user('latest', `REQUEST_START${'u'.repeat(8000)}REQUEST_END`),
183
+ assistant('answer', `ANSWER_START${'a'.repeat(16000)}ANSWER_END`),
114
184
  ]));
115
185
  for (const budget of [1_000, 4_000, 14_000, 32_000, 1_000_000]) {
116
186
  const prompt = buildAssistPrompt(context.turns, { recap: true, suggestion: true }, budget);
@@ -118,15 +188,13 @@ describe('session assist context', () => {
118
188
  for (const marker of ['REQUEST_START', 'REQUEST_END', 'ANSWER_START', 'ANSWER_END']) expect(prompt.text).toContain(marker);
119
189
  }
120
190
  expect(buildAssistPrompt(context.turns, { recap: true }, 500)).toBeNull();
121
- expect(excerpt('long text', 0)).toBe('');
122
- expect(excerpt('long text', 2)).toBe('lo');
123
- for (let limit = 0; limit < 60; limit++) expect(excerpt('x'.repeat(100), limit).length).toBeLessThanOrEqual(limit);
124
191
  });
125
192
 
126
- it('does not assign an older parent\'s late answer to a newer user request', async () => {
127
- expect(await load(async () => page([
128
- ...pair(1), record('new-user', 'user', 'A different request'),
129
- record('late-answer', 'assistant', 'Old work done', { parentID: 'u1' }),
130
- ]))).toBeNull();
193
+ it('excerpts from both ends so the start and the end both survive', () => {
194
+ expect(excerpt('abcdef', 10)).toBe('abcdef');
195
+ const trimmed = excerpt(`START${'x'.repeat(500)}END`, 100);
196
+ expect(trimmed.length).toBeLessThanOrEqual(100);
197
+ expect(trimmed).toMatch(/^START/);
198
+ expect(trimmed).toMatch(/END$/);
131
199
  });
132
200
  });
@@ -3,9 +3,9 @@
3
3
  import fs from 'fs';
4
4
  import os from 'os';
5
5
  import path from 'path';
6
- import { createOpencodeClient } from '@opencode-ai/sdk/v2';
6
+ import { OpenCode } from '@opencode/client';
7
7
  import { readMergedSettingsSync } from '../opencode/settings-files.js';
8
- import { loadAssistContext } from './context.js';
8
+ import { loadAssistContext, newestContentId } from './context.js';
9
9
  import { buildAssistPrompt, buildAssistSystemPrompt } from './prompt.js';
10
10
 
11
11
  const OPENCHAMBER_SETTINGS_FILE = path.join(
@@ -28,6 +28,8 @@ const RECAP_CHAR_LIMIT = 320;
28
28
  const SUGGESTION_CHAR_LIMIT = 500;
29
29
  const FETCH_TIMEOUT_MS = 5_000;
30
30
  const GENERATION_TIMEOUT_MS = 120_000;
31
+ // Enough records to look past the idle marker and a couple of switches.
32
+ const TAIL_RECHECK_LIMIT = 8;
31
33
  const QUIET_FAILURE_CODES = new Set(['context-too-small', 'output-exhausted']);
32
34
 
33
35
  const extractJsonObject = (value) => {
@@ -77,12 +79,22 @@ const extractUserMessage = (payload) => {
77
79
  };
78
80
  };
79
81
 
82
+ /**
83
+ * The recap and the suggestion live in OpenChamber's own session metadata
84
+ * store: OpenCode 2.x accepts session metadata only when a session is created.
85
+ * `persistSessionAssist(sessionID, directory, assist)` writes it; without that
86
+ * seam the runtime stays inert rather than generating text it cannot save.
87
+ */
80
88
  export const createSessionAssistRuntime = ({
81
89
  buildOpenCodeUrl,
82
90
  getOpenCodeAuthHeaders,
83
91
  getSmallModelService,
84
92
  getTargets = getSessionAssistTargets,
85
93
  quietMs = IDLE_QUIET_MS,
94
+ persistSessionAssist = null,
95
+ // Archive state is OpenChamber's own in v2 (no OpenCode route sets it), so
96
+ // the runtime asks rather than reading `time.archived` off the record.
97
+ isSessionArchived = async () => false,
86
98
  }) => {
87
99
  const timers = new Map();
88
100
  const inflight = new Map();
@@ -107,20 +119,32 @@ export const createSessionAssistRuntime = ({
107
119
  const targets = getTargets();
108
120
  if (!targets.recap && !targets.suggestion) return;
109
121
  const baseUrl = buildOpenCodeUrl('/', '').replace(/\/$/, '');
110
- const client = createOpencodeClient({ baseUrl, headers: getOpenCodeAuthHeaders(), throwOnError: true });
122
+ const client = OpenCode.make({
123
+ baseUrl,
124
+ headers: {
125
+ ...getOpenCodeAuthHeaders(),
126
+ // v2 scopes by header and rejects non-ASCII header values.
127
+ ...(directory ? { 'x-opencode-directory': encodeURIComponent(directory) } : {}),
128
+ },
129
+ });
111
130
  const requestOptions = () => ({ signal: AbortSignal.any([signal, AbortSignal.timeout(FETCH_TIMEOUT_MS)]) });
112
131
  const checkCurrent = () => {
113
132
  signal.throwIfAborted();
114
133
  if (buildOpenCodeUrl('/', '').replace(/\/$/, '') !== baseUrl) throw new Error('Session assist runtime changed');
115
134
  };
116
- const { data: session } = await client.session.get({ sessionID: sessionId, directory }, requestOptions());
135
+ const session = await client.session.get({ sessionID: sessionId }, requestOptions());
117
136
  checkCurrent();
118
137
  // Reverted history is not the active conversation. A new prompt clears
119
138
  // the revert boundary before its next idle event.
120
- if (session?.id !== sessionId || session.parentID || session.revert?.messageID || session.time?.archived) return;
139
+ if (session?.id !== sessionId || session.parentID || session.revert?.messageID) return;
140
+ // An archived session is put away: no recap or suggestion is generated for it.
141
+ if (await isSessionArchived(sessionId)) return;
121
142
  const context = await loadAssistContext({
122
143
  signal,
123
- readPage: (page) => client.session.messages({ sessionID: sessionId, directory, ...page }, requestOptions()),
144
+ readPage: ({ limit, cursor }) => client.message.list(
145
+ { sessionID: sessionId, limit, ...(cursor ? { cursor } : { order: 'desc' }) },
146
+ requestOptions(),
147
+ ),
124
148
  });
125
149
  checkCurrent();
126
150
  if (!context) return;
@@ -160,30 +184,28 @@ export const createSessionAssistRuntime = ({
160
184
  if (recap && scriptMismatch(recap)) recap = '';
161
185
  if (suggestion && scriptMismatch(suggestion)) suggestion = '';
162
186
  if (!recap && !suggestion) return;
163
- const { data: latest } = await client.session.messages({ sessionID: sessionId, directory, limit: 1 }, requestOptions());
187
+ // v2 lists newest first. The answer is followed by its `idle` marker and
188
+ // possibly a model or agent switch, so read a short tail and compare the
189
+ // newest content record rather than the newest record.
190
+ const latestPage = await client.message.list({ sessionID: sessionId, limit: TAIL_RECHECK_LIMIT, order: 'desc' }, requestOptions());
164
191
  checkCurrent();
165
- if (latest?.at(-1)?.info.id !== last.id) return;
192
+ if (newestContentId(latestPage?.data) !== last.id) return;
166
193
  // Never fall back to the pre-generation metadata snapshot after a failed
167
194
  // fresh read: doing so overwrites dismissals and unrelated metadata.
168
- const { data: freshSession } = await client.session.get({ sessionID: sessionId, directory }, requestOptions());
195
+ const freshSession = await client.session.get({ sessionID: sessionId }, requestOptions());
169
196
  checkCurrent();
170
- if (freshSession?.id !== sessionId || freshSession.revert?.messageID || freshSession.time?.archived || freshSession.directory !== session.directory) return;
197
+ if (freshSession?.id !== sessionId || freshSession.revert?.messageID || freshSession.location?.directory !== session.location?.directory) return;
198
+ if (await isSessionArchived(sessionId)) return;
171
199
  const enabled = getTargets();
172
200
  if (!enabled.recap) recap = '';
173
201
  if (!enabled.suggestion) suggestion = '';
174
202
  if (!recap && !suggestion) return;
175
- const currentMetadata = freshSession.metadata ?? {};
176
- const currentNamespace = currentMetadata.openchamber ?? {};
177
- await client.session.update({
178
- sessionID: sessionId, directory,
179
- metadata: {
180
- ...currentMetadata,
181
- openchamber: {
182
- ...currentNamespace,
183
- assist: { recap, suggestion, forMessageID: last.id, generatedAt: Date.now() },
184
- },
185
- },
186
- }, requestOptions());
203
+ await persistSessionAssist(sessionId, directory, {
204
+ recap,
205
+ suggestion,
206
+ forMessageID: last.id,
207
+ generatedAt: Date.now(),
208
+ });
187
209
  };
188
210
 
189
211
  const startGeneration = (sessionId, directory, armedAt) => {
@@ -219,8 +241,16 @@ export const createSessionAssistRuntime = ({
219
241
  timers.set(sessionId, { timer, armedAt });
220
242
  };
221
243
 
244
+ let parkedNoticeLogged = false;
222
245
  const processPayload = (payload, directoryHint = '') => {
223
246
  if (stopped) return;
247
+ if (typeof persistSessionAssist !== 'function') {
248
+ if (!parkedNoticeLogged) {
249
+ parkedNoticeLogged = true;
250
+ console.log('[session-assist] parked: no session metadata store is wired, so a recap could not be saved');
251
+ }
252
+ return;
253
+ }
224
254
  const status = extractSessionStatus(payload);
225
255
  if (status) {
226
256
  if (status.type === 'idle') armTimer(status.sessionId, status.directory || directoryHint);