@vantran-se/openchamber-web 1.24.4 → 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-B4a0JkUz.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-Byu2DY7s.js → GitView-BWa7e5vw.js} +4 -4
  8. package/dist/assets/{GuestAttachDialog-B808E0-d.js → GuestAttachDialog-DdOuyZzp.js} +1 -1
  9. package/dist/assets/GuestSurfacePane-DDfzid3U.js +1 -0
  10. package/dist/assets/{JsonTreeView-VW_7czo3.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-DCjGhlYV.js → PatchDiff-DS-IMU1j.js} +1 -1
  16. package/dist/assets/{PierreDiffViewer-CEDZNkU2.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-NU0FHFhT.js → VirtualizedFileDiff-C763hNCR.js} +4 -4
  25. package/dist/assets/{WalkthroughView-BZyjhqHv.js → WalkthroughView-lap5btCF.js} +2 -2
  26. package/dist/assets/appThemeRegistry-u4zw1l0B.js +1 -0
  27. package/dist/assets/{commitAndPush-BDftrjr6.js → commitAndPush-CwGJfJG-.js} +1 -1
  28. package/dist/assets/{computeVirtualFileMetrics-DlWlEM3x.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-CO4e5J_S.js → isDiffPlainText-D47a6hMP.js} +1 -1
  36. package/dist/assets/{isFilePlainText-ue6kCou7.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-CK48LG0z.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-D7QVlovD.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-BPu3JiQs.js → shikiHighlight-DNm2Z_Jc.js} +1 -1
  52. package/dist/assets/{textMateThemeFromAppTheme-DWqRPo9A.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-CgS0fnw_.js → useInlineCommentController-CuUuakrt.js} +2 -2
  57. package/dist/assets/useRootScrollLock-CQIsyERq.js +2 -0
  58. package/dist/assets/{vendor-codemirror-language-data-Bx2EHc1O.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-DxX8bZ1o.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-B2CD9wC8.js +0 -4
  316. package/dist/assets/FilesView-C4vMFclc.js +0 -13
  317. package/dist/assets/LinearIssuesView-EzGNCGrd.js +0 -1
  318. package/dist/assets/MarkdownImageGallery-B2YUDFaB.js +0 -1
  319. package/dist/assets/MarkdownRendererImpl-B4ikIzR3.js +0 -12
  320. package/dist/assets/OnboardingScreen-CQHBSTEi.js +0 -1
  321. package/dist/assets/PlanView-D3x-pTkZ.js +0 -5
  322. package/dist/assets/PluginPane-DEcgDQ9h.js +0 -3
  323. package/dist/assets/SettingsView-CSinuH-1.js +0 -29
  324. package/dist/assets/SettingsWindow-B5CHoJFZ.js +0 -1
  325. package/dist/assets/SubmoduleDiffSummary-oxeACCWs.js +0 -1
  326. package/dist/assets/ToolOutputDialog-tLNWmfcW.js +0 -15
  327. package/dist/assets/ToolPartDiffPreview-CflzmK-x.js +0 -8
  328. package/dist/assets/appThemeRegistry-DLicw4dH.js +0 -1
  329. package/dist/assets/de-Bro6_zyl.js +0 -10
  330. package/dist/assets/es-Bb9XQZvd.js +0 -16
  331. package/dist/assets/fr-Dgfzn-IL.js +0 -14
  332. package/dist/assets/index-CtQNmqUL.js +0 -1
  333. package/dist/assets/index-DqaMWNrC.css +0 -1
  334. package/dist/assets/index-Dqc6USl3.js +0 -40
  335. package/dist/assets/ja-BYsakGFh.js +0 -16
  336. package/dist/assets/ko-BSaQz9aN.js +0 -16
  337. package/dist/assets/main-B62_pjL5.js +0 -2
  338. package/dist/assets/main-BEdcOI2P.js +0 -896
  339. package/dist/assets/miniChat-LT8Rqofr.js +0 -2
  340. package/dist/assets/mobile-CqarEynh.js +0 -2
  341. package/dist/assets/pl-OIviPdoj.js +0 -16
  342. package/dist/assets/pt-BR-Cltg-E6s.js +0 -16
  343. package/dist/assets/renderElectronMiniChatApp-COyLTtZx.js +0 -2
  344. package/dist/assets/renderMobileApp-BC1MHg6g.js +0 -25
  345. package/dist/assets/runtimeEndpointReset-DdrazOVH.js +0 -25
  346. package/dist/assets/scheduledTasksApi-B2SWMGEU.js +0 -1
  347. package/dist/assets/tr-CQI-KPre.js +0 -7
  348. package/dist/assets/uk-CqW7R4DA.js +0 -16
  349. package/dist/assets/useAppFontEffects-ne84JBVL.js +0 -7333
  350. package/dist/assets/useRootScrollLock-Cpf1qoGp.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-CVZG_o_J.js +0 -16
  354. package/dist/assets/zh-TW-D5ZxzCZp.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
@@ -102,7 +102,8 @@ const createRuntime = (overrides = {}, stateOverrides = {}, envOverrides = {}) =
102
102
  waitForReady: vi.fn(async () => true),
103
103
  normalizeApiPrefix: vi.fn(() => ''),
104
104
  applyOpencodeBinaryFromSettings: vi.fn(async () => null),
105
- ensureOpencodeCliEnv: vi.fn(),
105
+ checkOpenCodeBinary: async () => '2.0.14',
106
+ ensureOpencodeCliEnv: vi.fn(),
106
107
  ensureLocalOpenCodeServerPassword: vi.fn(async () => 'password'),
107
108
  resolveManagedOpenCodeLaunchSpec: vi.fn((binary) => ({ binary, args: [], wrapperType: null })),
108
109
  setOpenCodePort: vi.fn((port) => {
@@ -114,6 +115,8 @@ const createRuntime = (overrides = {}, stateOverrides = {}, envOverrides = {}) =
114
115
  clearResolvedOpenCodeBinary: vi.fn(),
115
116
  buildAugmentedPath: vi.fn(() => '/home/user/.bun/bin:/usr/local/bin:/usr/bin'),
116
117
  buildManagedOpenCodePath: vi.fn(() => '/home/user/.bun/bin:/usr/local/bin:/usr/bin'),
118
+ // Never let a test touch the real `~/.local/share/opencode/opencode.db`.
119
+ topUpV1SessionMigration: vi.fn(() => ({ status: 'skipped', missing: 0, revisited: 0, reason: 'no-database' })),
117
120
  getManagedOpenCodeShellEnvSnapshot: vi.fn(() => ({
118
121
  PATH: '/home/user/.bun/bin:/usr/local/bin:/usr/bin',
119
122
  SHELL_ONLY: 'yes',
@@ -158,7 +161,7 @@ describe('OpenCode lifecycle', () => {
158
161
  it('records an authoritative ready terminal event for external startup', async () => {
159
162
  globalThis.fetch = vi.fn(async () => ({
160
163
  ok: true,
161
- json: async () => ({ healthy: true }),
164
+ json: async () => ({ version: '2.0.15', pid: 1, urls: [], paths: { tmp: '/tmp' } }),
162
165
  }));
163
166
  const runtime = createRuntime({
164
167
  env: {
@@ -190,7 +193,7 @@ describe('OpenCode lifecycle', () => {
190
193
  it('recovers an external OPENCODE_HOST connection using its configured endpoint', async () => {
191
194
  const fetchMock = vi.fn(async () => ({
192
195
  ok: true,
193
- json: async () => ({ healthy: true }),
196
+ json: async () => ({ version: '2.0.15', pid: 1, urls: [], paths: { tmp: '/tmp' } }),
194
197
  }));
195
198
  globalThis.fetch = fetchMock;
196
199
  const runtime = createRuntime({}, {
@@ -206,7 +209,7 @@ describe('OpenCode lifecycle', () => {
206
209
  await runtime.restartOpenCode();
207
210
 
208
211
  expect(fetchMock).toHaveBeenCalledWith(
209
- 'http://seamus:4095/global/health',
212
+ 'http://seamus:4095/api/info',
210
213
  expect.objectContaining({ method: 'GET' }),
211
214
  );
212
215
  expect(runtime.testState.openCodePort).toBe(4095);
@@ -240,7 +243,7 @@ describe('OpenCode lifecycle', () => {
240
243
  it('warms recently used directories after a successful bootstrap', async () => {
241
244
  const fetchMock = vi.fn(async () => ({
242
245
  ok: true,
243
- json: async () => ({ healthy: true }),
246
+ json: async () => ({ version: '2.0.15', pid: 1, urls: [], paths: { tmp: '/tmp' } }),
244
247
  }));
245
248
  globalThis.fetch = fetchMock;
246
249
  const runtime = createRuntime({
@@ -260,10 +263,10 @@ describe('OpenCode lifecycle', () => {
260
263
 
261
264
  const warmupUrls = fetchMock.mock.calls
262
265
  .map(([url]) => String(url))
263
- .filter((url) => url.includes('/session/status'));
266
+ .filter((url) => url.includes('/api/session?'));
264
267
  expect(warmupUrls).toEqual([
265
- 'http://127.0.0.1:45678/session/status?directory=%2Ftmp%2Fworktree-a',
266
- 'http://127.0.0.1:45678/session/status?directory=%2Ftmp%2Fproject-b',
268
+ 'http://127.0.0.1:45678/api/session?directory=%2Ftmp%2Fworktree-a&limit=1',
269
+ 'http://127.0.0.1:45678/api/session?directory=%2Ftmp%2Fproject-b&limit=1',
267
270
  ]);
268
271
  });
269
272
 
@@ -876,6 +879,28 @@ describe('OpenCode lifecycle', () => {
876
879
  expect(spawnMock).toHaveBeenCalledTimes(2);
877
880
  await server.close();
878
881
  });
882
+
883
+ it('tops up the v1 session migration before spawning managed OpenCode', async () => {
884
+ const calls = [];
885
+ const topUpV1SessionMigration = vi.fn(() => {
886
+ calls.push('top-up');
887
+ return { status: 'scheduled', missing: 3, revisited: 0 };
888
+ });
889
+ spawnMock.mockImplementation(() => {
890
+ calls.push('spawn');
891
+ const child = createMockChild();
892
+ queueMicrotask(() => child.stdout.emit('data', 'opencode server listening on http://127.0.0.1:45678\n'));
893
+ return child;
894
+ });
895
+ globalThis.fetch = vi.fn(async () => ({ ok: false }));
896
+
897
+ const runtime = createRuntime({ topUpV1SessionMigration });
898
+ await runtime.startOpenCode();
899
+
900
+ expect(topUpV1SessionMigration).toHaveBeenCalledTimes(1);
901
+ expect(calls).toEqual(['top-up', 'spawn']);
902
+ });
903
+
879
904
  });
880
905
 
881
906
  describe('killProcessOnPort on Windows', () => {
@@ -944,3 +969,50 @@ describe('killProcessOnPort on Windows', () => {
944
969
  expect(spawnSyncMock).not.toHaveBeenCalledWith('taskkill', expect.anything(), expect.anything());
945
970
  });
946
971
  });
972
+
973
+ it('shares the managed CLI preflight with desktop while startup is pending', async () => {
974
+ let finish;
975
+ let entered;
976
+ const checking = new Promise(resolve => { entered = resolve; });
977
+ const check = new Promise(resolve => { finish = resolve; });
978
+ let checks = 0;
979
+ const runtime = createRuntime({
980
+ checkOpenCodeBinary: () => { checks += 1; entered(); return check; },
981
+ ensureOpencodeCliEnv: () => '/tmp/opencode',
982
+ });
983
+ spawnMock.mockImplementation(() => {
984
+ const child = createMockChild();
985
+ queueMicrotask(() => child.stdout.emit('data', 'opencode server listening on http://127.0.0.1:45678\n'));
986
+ return child;
987
+ });
988
+ expect(await runtime.getManagedOpenCodePreflight()).toBe(false);
989
+ const starting = runtime.startOpenCode();
990
+ await checking;
991
+ const desktopVerdict = runtime.getManagedOpenCodePreflight();
992
+ expect(runtime.testState.isOpenCodeReady).toBe(false);
993
+ finish('2.0.15');
994
+ expect(await desktopVerdict).toBe(true);
995
+ expect(checks).toBe(1);
996
+ const server = await starting;
997
+ try {
998
+ expect(await runtime.getManagedOpenCodePreflight()).toBe(true);
999
+ runtime.testState.isExternalOpenCode = true;
1000
+ expect(await runtime.getManagedOpenCodePreflight()).toBe(false);
1001
+ runtime.testState.isExternalOpenCode = false;
1002
+ runtime.testState.isShuttingDown = true;
1003
+ expect(await runtime.getManagedOpenCodePreflight()).toBe(false);
1004
+ } finally {
1005
+ await server.close();
1006
+ }
1007
+ });
1008
+
1009
+ it('a rejected CLI preflight never permits desktop bootstrap', async () => {
1010
+ const runtime = createRuntime({
1011
+ checkOpenCodeBinary: async () => {
1012
+ throw Object.assign(new Error('Unsupported CLI'), { code: 'OPENCODE_BINARY_INVALID' });
1013
+ },
1014
+ });
1015
+ await expect(runtime.startOpenCode()).rejects.toThrow('Unsupported CLI');
1016
+ expect(await runtime.getManagedOpenCodePreflight()).toBe(false);
1017
+ expect(spawnMock).not.toHaveBeenCalled();
1018
+ });
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Handoff of a login-shell environment snapshot from an embedding host.
3
+ *
4
+ * The server snapshots the user's login shell (`$SHELL -lic 'env -0'`) at
5
+ * import time to learn the PATH a Finder- or Dock-launched process never
6
+ * inherits. Desktop already runs the same probe asynchronously while its
7
+ * window comes up; running it again here would block the Electron main
8
+ * thread for as long as the user's shell startup files take, a second time.
9
+ * A host that has a snapshot hands it over before importing the server, and
10
+ * the server uses it instead of probing. `null` means the host probed and got
11
+ * nothing: the server does not retry, because that retry would be
12
+ * synchronous and bounded only by the probe timeout.
13
+ *
14
+ * Kept in this module rather than in the environment on purpose: the
15
+ * snapshot is the user's full shell environment, secrets included, and
16
+ * process.env is inherited by every child the server spawns.
17
+ */
18
+
19
+ let provided;
20
+
21
+ // `snapshot` is the parsed `env -0` map, or null when the host's probe failed.
22
+ export const provideLoginShellEnvSnapshot = (snapshot) => {
23
+ provided = snapshot ? { ...snapshot } : null;
24
+ };
25
+
26
+ // `undefined` when no host provided one, so the server probes itself.
27
+ export const providedLoginShellEnvSnapshot = () => provided;
@@ -0,0 +1,119 @@
1
+ import { appendManagedPlugin } from './managed-plugin-config.js';
2
+
3
+ export const MANAGED_CONFIG_FILE_NAME = 'opencode.managed.json';
4
+
5
+ /**
6
+ * The managed OpenCode config layer.
7
+ *
8
+ * OpenCode 2 watches every config source it reads, including the file named by
9
+ * the `OPENCODE_CONFIG` environment variable, and reloads its plugin list when
10
+ * that file changes. Writing OpenChamber's own plugin entries into a file we
11
+ * own therefore makes toggling a managed tool take effect in the running
12
+ * process — where `OPENCODE_CONFIG_CONTENT` (an environment variable) could
13
+ * only ever change across a restart.
14
+ *
15
+ * The file is OpenChamber-owned and holds nothing but `plugins`. It sits above
16
+ * the user's global `opencode.json` and below their project config, so it never
17
+ * shadows a project-level choice.
18
+ *
19
+ * When the user's own environment already sets `OPENCODE_CONFIG`, that file is
20
+ * theirs: OpenChamber does not hijack it and falls back to merging its plugins
21
+ * into `OPENCODE_CONFIG_CONTENT`, which keeps the old restart requirement for
22
+ * managed-tool toggles.
23
+ *
24
+ * @param {object} dependencies
25
+ * @param {typeof import('node:fs/promises')} dependencies.fsPromises
26
+ * @param {typeof import('node:path')} dependencies.path
27
+ * @param {string} dependencies.dataDir OpenChamber data directory
28
+ * @param {NodeJS.ProcessEnv} [dependencies.env] environment the managed child inherits
29
+ * @param {object|null} [dependencies.agentToolRuntime]
30
+ * @param {() => Promise<object|null>} dependencies.readSettings
31
+ * @param {() => boolean} dependencies.isAgentMemoryAvailable
32
+ */
33
+ export const createManagedConfigRuntime = ({
34
+ fsPromises,
35
+ path,
36
+ dataDir,
37
+ env = process.env,
38
+ agentToolRuntime = null,
39
+ readSettings,
40
+ isAgentMemoryAvailable,
41
+ }) => {
42
+ const filePath = path.join(dataDir, MANAGED_CONFIG_FILE_NAME);
43
+
44
+ /** The user owns `OPENCODE_CONFIG` when their environment already set it. */
45
+ const ownsConfigFile = () => (env.OPENCODE_CONFIG ?? '').trim().length === 0;
46
+
47
+ const writeConfigFile = async (pluginDirectories) => {
48
+ await fsPromises.mkdir(dataDir, { recursive: true });
49
+ const text = `${JSON.stringify({ plugins: pluginDirectories }, null, 2)}\n`;
50
+ // Temp + rename: OpenCode reacts to the write immediately, and a partial
51
+ // read would make it drop every managed plugin until the next change.
52
+ const tmp = `${filePath}.tmp-${process.pid}-${Date.now()}`;
53
+ try {
54
+ await fsPromises.writeFile(tmp, text, { encoding: 'utf8', mode: 0o600 });
55
+ await fsPromises.rename(tmp, filePath);
56
+ } catch (error) {
57
+ await fsPromises.rm(tmp, { force: true }).catch(() => {});
58
+ throw error;
59
+ }
60
+ };
61
+
62
+ /**
63
+ * Materialize the plugins the current settings ask for and return their
64
+ * directories in load order. Directories exist on disk before the config
65
+ * file names them.
66
+ */
67
+ const materializeEnabledPlugins = async () => {
68
+ const settings = await Promise.resolve(readSettings()).catch(() => null);
69
+ const includeControl = settings?.agentControlToolEnabled !== false;
70
+ const includeWeb = settings?.agentWebToolEnabled !== false;
71
+ const includeMemory = isAgentMemoryAvailable() && settings?.agentMemoryToolEnabled === true;
72
+
73
+ const directories = [];
74
+ if (agentToolRuntime && (includeControl || includeWeb || includeMemory)) {
75
+ directories.push(await agentToolRuntime.materializePlugin({ includeControl, includeWeb, includeMemory }));
76
+ }
77
+ return directories;
78
+ };
79
+
80
+ /**
81
+ * Environment for a managed OpenCode child: the callback URL and per-child
82
+ * token are always present, so a tool enabled later in the session can still
83
+ * call back without a restart.
84
+ */
85
+ const buildManagedChildEnv = async () => {
86
+ const directories = await materializeEnabledPlugins();
87
+ const childEnv = agentToolRuntime ? agentToolRuntime.createChildEnv() : {};
88
+ if (ownsConfigFile()) {
89
+ await writeConfigFile(directories);
90
+ return { ...childEnv, OPENCODE_CONFIG: filePath };
91
+ }
92
+ if (directories.length === 0) return childEnv;
93
+ let content = env.OPENCODE_CONFIG_CONTENT;
94
+ for (const directory of directories) {
95
+ content = appendManagedPlugin(content, directory, 'managed plugin');
96
+ }
97
+ return { ...childEnv, OPENCODE_CONFIG_CONTENT: content };
98
+ };
99
+
100
+ /**
101
+ * Rewrite the managed config file after a managed-plugin setting changed, so
102
+ * the running OpenCode picks the new plugin list up. A no-op when the user
103
+ * owns `OPENCODE_CONFIG`; those installs still need a restart.
104
+ *
105
+ * @returns {Promise<{updated: boolean, reason?: string}>}
106
+ */
107
+ const refreshManagedConfigFile = async () => {
108
+ if (!ownsConfigFile()) return { updated: false, reason: 'external-config' };
109
+ await writeConfigFile(await materializeEnabledPlugins());
110
+ return { updated: true };
111
+ };
112
+
113
+ return {
114
+ filePath,
115
+ ownsConfigFile,
116
+ buildManagedChildEnv,
117
+ refreshManagedConfigFile,
118
+ };
119
+ };
@@ -0,0 +1,152 @@
1
+ import fs from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { afterEach, describe, expect, it, vi } from 'vitest';
5
+
6
+ import { createManagedConfigRuntime, MANAGED_CONFIG_FILE_NAME } from './managed-config-file.js';
7
+
8
+ const temporaryDirectories = [];
9
+
10
+ afterEach(async () => {
11
+ await Promise.all(temporaryDirectories.splice(0).map((directory) => fs.rm(directory, { recursive: true, force: true })));
12
+ });
13
+
14
+ const createHarness = async ({ settings = {}, env = {}, memoryAvailable = true } = {}) => {
15
+ const dataDir = await fs.mkdtemp(path.join(os.tmpdir(), 'openchamber-managed-config-'));
16
+ temporaryDirectories.push(dataDir);
17
+ const agentToolRuntime = {
18
+ pluginDirectory: path.join(dataDir, 'agent-tool', 'openchamber-agent-tool'),
19
+ materializePlugin: vi.fn(async (flags) => {
20
+ await fs.mkdir(agentToolRuntime.pluginDirectory, { recursive: true });
21
+ await fs.writeFile(path.join(agentToolRuntime.pluginDirectory, 'flags.json'), JSON.stringify(flags));
22
+ return agentToolRuntime.pluginDirectory;
23
+ }),
24
+ createChildEnv: vi.fn(() => ({
25
+ OPENCHAMBER_AGENT_TOOL_URL: 'http://127.0.0.1:3901/api/openchamber/agent-tool',
26
+ OPENCHAMBER_AGENT_TOOL_TOKEN: 'token',
27
+ })),
28
+ };
29
+ const current = { settings };
30
+ const runtime = createManagedConfigRuntime({
31
+ fsPromises: fs,
32
+ path,
33
+ dataDir,
34
+ env,
35
+ agentToolRuntime,
36
+ readSettings: () => current.settings,
37
+ isAgentMemoryAvailable: () => memoryAvailable,
38
+ });
39
+ const readConfigFile = async () => JSON.parse(await fs.readFile(path.join(dataDir, MANAGED_CONFIG_FILE_NAME), 'utf8'));
40
+ return { dataDir, runtime, agentToolRuntime, current, readConfigFile };
41
+ };
42
+
43
+ describe('managed OpenCode config file', () => {
44
+ it('points the child at a config file listing the enabled plugins', async () => {
45
+ const { dataDir, runtime, readConfigFile } = await createHarness({
46
+ settings: {},
47
+ });
48
+
49
+ const childEnv = await runtime.buildManagedChildEnv();
50
+
51
+ expect(childEnv.OPENCODE_CONFIG).toBe(path.join(dataDir, MANAGED_CONFIG_FILE_NAME));
52
+ expect(childEnv.OPENCODE_CONFIG_CONTENT).toBeUndefined();
53
+ expect(childEnv.OPENCHAMBER_AGENT_TOOL_TOKEN).toBe('token');
54
+ expect(await readConfigFile()).toEqual({
55
+ plugins: [path.join(dataDir, 'agent-tool', 'openchamber-agent-tool')],
56
+ });
57
+ });
58
+
59
+ it('materializes every listed plugin directory before naming it', async () => {
60
+ const { runtime, readConfigFile } = await createHarness({ settings: {} });
61
+
62
+ await runtime.buildManagedChildEnv();
63
+
64
+ for (const directory of (await readConfigFile()).plugins) {
65
+ expect((await fs.stat(directory)).isDirectory()).toBe(true);
66
+ }
67
+ });
68
+
69
+ it('keeps the callback token in the child env while every tool is off', async () => {
70
+ const { runtime, agentToolRuntime, readConfigFile } = await createHarness({
71
+ settings: { agentControlToolEnabled: false, agentWebToolEnabled: false, agentMemoryToolEnabled: false },
72
+ });
73
+
74
+ const childEnv = await runtime.buildManagedChildEnv();
75
+
76
+ expect(agentToolRuntime.materializePlugin).not.toHaveBeenCalled();
77
+ expect(await readConfigFile()).toEqual({ plugins: [] });
78
+ // A tool switched on later reaches a process that can already call back.
79
+ expect(childEnv.OPENCHAMBER_AGENT_TOOL_TOKEN).toBe('token');
80
+ });
81
+
82
+ it('rewrites the file when a tool is turned off, without a new token', async () => {
83
+ const { dataDir, runtime, current, agentToolRuntime, readConfigFile } = await createHarness({
84
+ settings: {},
85
+ });
86
+ await runtime.buildManagedChildEnv();
87
+
88
+ current.settings = { agentControlToolEnabled: false, agentWebToolEnabled: false };
89
+ expect(await runtime.refreshManagedConfigFile()).toEqual({ updated: true });
90
+
91
+ const { plugins } = await readConfigFile();
92
+ expect(plugins).toEqual([]);
93
+ expect(agentToolRuntime.createChildEnv).toHaveBeenCalledTimes(1);
94
+ });
95
+
96
+ it('turns a tool back on without a restart', async () => {
97
+ const { runtime, current, agentToolRuntime, readConfigFile } = await createHarness({
98
+ settings: { agentControlToolEnabled: false, agentWebToolEnabled: false },
99
+ });
100
+ await runtime.buildManagedChildEnv();
101
+
102
+ current.settings = { agentControlToolEnabled: false, agentWebToolEnabled: true };
103
+ await runtime.refreshManagedConfigFile();
104
+
105
+ expect((await readConfigFile()).plugins).toHaveLength(1);
106
+ expect(agentToolRuntime.materializePlugin).toHaveBeenCalledWith({
107
+ includeControl: false,
108
+ includeWeb: true,
109
+ includeMemory: false,
110
+ });
111
+ });
112
+
113
+ it('leaves a config file the user owns alone and keeps the restart requirement', async () => {
114
+ const { dataDir, runtime } = await createHarness({
115
+ settings: {},
116
+ env: { OPENCODE_CONFIG: '/home/user/opencode.json', OPENCODE_CONFIG_CONTENT: '{"model":"test/model"}' },
117
+ });
118
+
119
+ const childEnv = await runtime.buildManagedChildEnv();
120
+
121
+ expect(childEnv.OPENCODE_CONFIG).toBeUndefined();
122
+ expect(JSON.parse(childEnv.OPENCODE_CONFIG_CONTENT)).toEqual({
123
+ model: 'test/model',
124
+ plugins: [path.join(dataDir, 'agent-tool', 'openchamber-agent-tool')],
125
+ });
126
+ await expect(fs.stat(path.join(dataDir, MANAGED_CONFIG_FILE_NAME))).rejects.toThrow();
127
+ expect(await runtime.refreshManagedConfigFile()).toEqual({ updated: false, reason: 'external-config' });
128
+ });
129
+
130
+ it('leaves no temp file behind and never publishes a partial list', async () => {
131
+ const { dataDir, runtime } = await createHarness({ settings: {} });
132
+
133
+ await runtime.buildManagedChildEnv();
134
+ await runtime.refreshManagedConfigFile();
135
+
136
+ const entries = await fs.readdir(dataDir);
137
+ expect(entries.filter((entry) => entry.includes('.tmp-'))).toEqual([]);
138
+ expect(entries).toContain(MANAGED_CONFIG_FILE_NAME);
139
+ });
140
+
141
+ it('ignores the memory tool while the feature is unavailable', async () => {
142
+ const { runtime, agentToolRuntime, readConfigFile } = await createHarness({
143
+ settings: { agentControlToolEnabled: false, agentWebToolEnabled: false, agentMemoryToolEnabled: true },
144
+ memoryAvailable: false,
145
+ });
146
+
147
+ await runtime.buildManagedChildEnv();
148
+
149
+ expect(agentToolRuntime.materializePlugin).not.toHaveBeenCalled();
150
+ expect(await readConfigFile()).toEqual({ plugins: [] });
151
+ });
152
+ });
@@ -2,34 +2,54 @@ import { parse as parseJsonc } from 'jsonc-parser';
2
2
 
3
3
  const isJsonObject = (value) => value !== null && value !== undefined && Object.getPrototypeOf(value) === Object.prototype;
4
4
 
5
+ const matchesEntry = (value, pluginDirectory) => (
6
+ value === pluginDirectory || (Array.isArray(value) && value[0] === pluginDirectory)
7
+ );
8
+
5
9
  /**
6
- * Append a managed OpenChamber plugin to the `plugin` list of an
10
+ * Append a managed OpenChamber plugin to the `plugins` list of an
7
11
  * `OPENCODE_CONFIG_CONTENT` value.
8
12
  *
9
- * Existing entries are preserved; an earlier entry for the same URL is dropped
10
- * so a restart never registers the same plugin twice. Every managed plugin
11
- * (agent tools, system prompt optimizer) goes through this one
12
- * merge so they compose in any order.
13
+ * OpenCode 2 loads a configured plugin from a DIRECTORY that carries a
14
+ * `package.json` resolving an entrypoint; a path to a single `.js` file is
15
+ * rejected with "configured plugin path must be a directory". So the value
16
+ * merged here is an absolute directory path, not a `file://` URL.
17
+ *
18
+ * Existing entries are preserved and an earlier entry for the same directory is
19
+ * dropped, so a restart never registers the same plugin twice. A legacy
20
+ * `plugin` key is folded into `plugins` and removed: OpenCode still decodes it,
21
+ * but keeping two keys would mean two places to dedupe against.
22
+ *
23
+ * This is the FALLBACK path only, taken when the user's own environment owns
24
+ * `OPENCODE_CONFIG`. Otherwise managed plugins are published through the
25
+ * watched config file in `managed-config-file.js`, which is what makes a
26
+ * managed-tool toggle apply without a restart.
13
27
  *
14
28
  * @param {string | undefined} rawConfig the current `OPENCODE_CONFIG_CONTENT`, JSONC or unset
15
- * @param {string} pluginUrl `file://` URL of the materialized plugin
29
+ * @param {string} pluginDirectory absolute path of the materialized plugin directory
16
30
  * @param {string} purpose short noun phrase for the error message, e.g. "managed tool"
17
31
  * @returns {string} the merged config as JSON
18
32
  */
19
- export const appendManagedPlugin = (rawConfig, pluginUrl, purpose) => {
33
+ export const appendManagedPlugin = (rawConfig, pluginDirectory, purpose) => {
20
34
  const errors = [];
21
35
  const text = (rawConfig ?? '').trim();
22
36
  const parsed = text ? parseJsonc(text, errors, { allowTrailingComma: true }) : {};
23
37
  if (errors.length > 0 || !isJsonObject(parsed)) {
24
38
  throw new Error(`OPENCODE_CONFIG_CONTENT must contain a valid JSON object before OpenChamber can inject its ${purpose}`);
25
39
  }
26
- if (parsed.plugin !== undefined && !Array.isArray(parsed.plugin)) {
27
- throw new Error(`OPENCODE_CONFIG_CONTENT plugin must be an array before OpenChamber can inject its ${purpose}`);
40
+ for (const key of ['plugin', 'plugins']) {
41
+ if (parsed[key] !== undefined && !Array.isArray(parsed[key])) {
42
+ throw new Error(`OPENCODE_CONFIG_CONTENT ${key} must be an array before OpenChamber can inject its ${purpose}`);
43
+ }
28
44
  }
29
- const configured = Array.isArray(parsed.plugin) ? parsed.plugin : [];
30
- parsed.plugin = [
31
- ...configured.filter((value) => value !== pluginUrl && (!Array.isArray(value) || value[0] !== pluginUrl)),
32
- pluginUrl,
45
+ const configured = [
46
+ ...(Array.isArray(parsed.plugin) ? parsed.plugin : []),
47
+ ...(Array.isArray(parsed.plugins) ? parsed.plugins : []),
48
+ ];
49
+ delete parsed.plugin;
50
+ parsed.plugins = [
51
+ ...configured.filter((value) => !matchesEntry(value, pluginDirectory)),
52
+ pluginDirectory,
33
53
  ];
34
54
  return JSON.stringify(parsed);
35
55
  };