@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
@@ -1,47 +1,72 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import {
4
+ getAncestors,
5
+ findWorktreeRoot,
4
6
  CONFIG_FILE,
5
7
  AGENT_DIR,
8
+ OPENCODE_CONFIG_DIR,
6
9
  AGENT_SCOPE,
7
10
  ensureDirs,
8
11
  parseMdFile,
9
12
  writeMdFile,
10
13
  readConfigLayers,
11
- readConfigFile,
12
14
  writeConfig,
13
15
  getJsonEntrySource,
14
- getJsonWriteTarget,
15
16
  isPromptFileReference,
16
17
  resolvePromptFilePath,
17
18
  writePromptFile,
18
19
  } from './shared.js';
20
+ import {
21
+ toAgentEntity,
22
+ fromAgentEntity,
23
+ isLegacyAgentFrontmatter,
24
+ writeSectionEntry,
25
+ deleteSectionEntry,
26
+ effectiveAgentRules,
27
+ readGlobalPermissionRules,
28
+ normalizePermissionRules,
29
+ parseModelSelection,
30
+ formatModelSelection,
31
+ isRecord,
32
+ } from './config-v2.js';
19
33
 
20
34
  // ============== AGENT SCOPE HELPERS ==============
35
+ //
36
+ // OpenCode 2 discovers agents from `agent/`, `agents/`, `mode/` and `modes/`.
37
+ // OpenChamber reads every one of those and WRITES only to `agents/`. An agent
38
+ // that already lives in a v1 directory is rewritten in place (v2 fields, same
39
+ // path) so no file silently moves out from under the user.
21
40
 
22
- /**
23
- * Ensure project-level agent directory exists
24
- */
41
+ const USER_AGENT_DIRS = ['agents', 'agent', 'modes', 'mode'].map((name) => path.join(OPENCODE_CONFIG_DIR, name));
42
+ const PROJECT_AGENT_DIR_NAMES = ['agents', 'agent', 'modes', 'mode'];
43
+
44
+ /** Ensure the v2 project agent directory exists. */
25
45
  function ensureProjectAgentDir(workingDirectory) {
26
46
  const projectAgentDir = path.join(workingDirectory, '.opencode', 'agents');
27
47
  if (!fs.existsSync(projectAgentDir)) {
28
48
  fs.mkdirSync(projectAgentDir, { recursive: true });
29
49
  }
30
- const legacyProjectAgentDir = path.join(workingDirectory, '.opencode', 'agent');
31
- if (!fs.existsSync(legacyProjectAgentDir)) {
32
- fs.mkdirSync(legacyProjectAgentDir, { recursive: true });
33
- }
34
50
  return projectAgentDir;
35
51
  }
36
52
 
37
53
  /**
38
- * Get project-level agent path
54
+ * Project-level agent path: an existing v1 file keeps its path, otherwise the
55
+ * v2 `agents/` location.
39
56
  */
40
57
  function getProjectAgentPath(workingDirectory, agentName) {
41
- const pluralPath = path.join(workingDirectory, '.opencode', 'agents', `${agentName}.md`);
42
- const legacyPath = path.join(workingDirectory, '.opencode', 'agent', `${agentName}.md`);
43
- if (fs.existsSync(legacyPath) && !fs.existsSync(pluralPath)) return legacyPath;
44
- return pluralPath;
58
+ const preferred = path.join(workingDirectory, '.opencode', 'agents', `${agentName}.md`);
59
+ // OpenCode 2 discovers `.opencode` from the working directory up to the
60
+ // project root, so a definition in a parent directory of a monorepo
61
+ // package counts; nested ids (`team/reviewer`) map onto the path.
62
+ const worktreeRoot = findWorktreeRoot(workingDirectory) || path.resolve(workingDirectory);
63
+ for (const base of getAncestors(workingDirectory, worktreeRoot)) {
64
+ for (const dirName of PROJECT_AGENT_DIR_NAMES) {
65
+ const candidate = path.join(base, '.opencode', dirName, `${agentName}.md`);
66
+ if (fs.existsSync(candidate)) return candidate;
67
+ }
68
+ }
69
+ return preferred;
45
70
  }
46
71
 
47
72
  /**
@@ -59,9 +84,7 @@ function buildUserAgentIndex(cache) {
59
84
  if (cache.userAgentIndexReady) return;
60
85
  cache.userAgentIndexReady = true;
61
86
 
62
- if (!fs.existsSync(AGENT_DIR)) return;
63
-
64
- const dirsToVisit = [AGENT_DIR];
87
+ const dirsToVisit = USER_AGENT_DIRS.filter((dir) => fs.existsSync(dir));
65
88
  while (dirsToVisit.length > 0) {
66
89
  const dir = dirsToVisit.pop();
67
90
  let entries;
@@ -102,24 +125,22 @@ function getIndexedUserAgentPath(agentName, cache) {
102
125
  }
103
126
 
104
127
  /**
105
- * Get user-level agent path — walks subfolders to support grouped layouts.
106
- * e.g. ~/.config/opencode/agents/business/ceo-diginno.md
128
+ * User-level agent path — walks subfolders to support grouped layouts
129
+ * (e.g. `~/.config/opencode/agents/business/ceo.md`) and the v1 `agent/` dir.
130
+ * New agents land flat in the v2 `agents/` directory.
107
131
  */
108
132
  function getUserAgentPath(agentName, lookupCache = null) {
109
- // 1. Check flat path first (legacy / newly created agents)
110
- const pluralPath = path.join(AGENT_DIR, `${agentName}.md`);
111
- if (fs.existsSync(pluralPath)) return pluralPath;
112
-
113
- const legacyPath = path.join(AGENT_DIR, '..', 'agent', `${agentName}.md`);
114
- if (fs.existsSync(legacyPath)) return legacyPath;
133
+ const preferred = path.join(AGENT_DIR, `${agentName}.md`);
134
+ for (const dir of USER_AGENT_DIRS) {
135
+ const candidate = path.join(dir, `${agentName}.md`);
136
+ if (fs.existsSync(candidate)) return candidate;
137
+ }
115
138
 
116
- // 2. Lookup subfolders for grouped layout
117
139
  const cache = lookupCache || createAgentLookupCache();
118
140
  const found = getIndexedUserAgentPath(agentName, cache);
119
141
  if (found) return found;
120
142
 
121
- // 3. Return expected flat path as default (for new agent creation)
122
- return pluralPath;
143
+ return preferred;
123
144
  }
124
145
 
125
146
  /**
@@ -133,12 +154,12 @@ function getAgentScope(agentName, workingDirectory, lookupCache = null) {
133
154
  return { scope: AGENT_SCOPE.PROJECT, path: projectPath };
134
155
  }
135
156
  }
136
-
157
+
137
158
  const userPath = getUserAgentPath(agentName, lookupCache);
138
159
  if (fs.existsSync(userPath)) {
139
160
  return { scope: AGENT_SCOPE.USER, path: userPath };
140
161
  }
141
-
162
+
142
163
  return { scope: null, path: null };
143
164
  }
144
165
 
@@ -157,86 +178,41 @@ function getAgentWritePath(agentName, workingDirectory, requestedScope, lookupCa
157
178
  if (scope === AGENT_SCOPE.PROJECT && workingDirectory) {
158
179
  return {
159
180
  scope: AGENT_SCOPE.PROJECT,
160
- path: getProjectAgentPath(workingDirectory, agentName)
181
+ path: getProjectAgentPath(workingDirectory, agentName),
161
182
  };
162
183
  }
163
184
 
164
185
  return {
165
186
  scope: AGENT_SCOPE.USER,
166
- path: getUserAgentPath(agentName, lookupCache)
187
+ path: getUserAgentPath(agentName, lookupCache),
167
188
  };
168
189
  }
169
190
 
170
- /**
171
- * Detect where an agent's permission field is currently defined
172
- * Priority: project .md > user .md > project JSON > user JSON
173
- * Returns: { source: 'md'|'json'|null, scope: 'project'|'user'|null, path: string|null }
174
- */
175
- function getAgentPermissionSource(agentName, workingDirectory, lookupCache = null) {
176
- // Check project-level .md first
177
- if (workingDirectory) {
178
- const projectMdPath = getProjectAgentPath(workingDirectory, agentName);
179
- if (fs.existsSync(projectMdPath)) {
180
- const { frontmatter } = parseMdFile(projectMdPath);
181
- if (frontmatter.permission !== undefined) {
182
- return { source: 'md', scope: AGENT_SCOPE.PROJECT, path: projectMdPath };
183
- }
184
- }
185
- }
186
-
187
- // Check user-level .md
188
- const userMdPath = getUserAgentPath(agentName, lookupCache);
189
- if (fs.existsSync(userMdPath)) {
190
- const { frontmatter } = parseMdFile(userMdPath);
191
- if (frontmatter.permission !== undefined) {
192
- return { source: 'md', scope: AGENT_SCOPE.USER, path: userMdPath };
193
- }
194
- }
195
-
196
- // Check JSON layers in effective override order. readConfigLayers merges
197
- // user -> project -> custom, so custom wins over project, project over user.
198
- const layers = readConfigLayers(workingDirectory);
199
-
200
- const customJsonPermission = layers.customConfig?.agent?.[agentName]?.permission;
201
- if (customJsonPermission !== undefined && layers.paths.customPath) {
202
- return { source: 'json', scope: 'custom', path: layers.paths.customPath };
203
- }
204
-
205
- const projectJsonPermission = layers.projectConfig?.agent?.[agentName]?.permission;
206
- if (projectJsonPermission !== undefined && layers.paths.projectPath) {
207
- return { source: 'json', scope: AGENT_SCOPE.PROJECT, path: layers.paths.projectPath };
208
- }
209
-
210
- const userJsonPermission = layers.userConfig?.agent?.[agentName]?.permission;
211
- if (userJsonPermission !== undefined) {
212
- return { source: 'json', scope: AGENT_SCOPE.USER, path: layers.paths.userPath };
213
- }
191
+ // ============== READ ==============
214
192
 
215
- return { source: null, scope: null, path: null };
216
- }
217
-
218
- function applyAgentPermission(target, newPermission) {
219
- if (newPermission == null) {
220
- delete target.permission;
221
- } else {
222
- target.permission = newPermission;
223
- }
193
+ function readMdAgent(mdPath) {
194
+ const { frontmatter, body } = parseMdFile(mdPath);
195
+ return {
196
+ entity: toAgentEntity(frontmatter, body),
197
+ frontmatter,
198
+ body,
199
+ legacy: isLegacyAgentFrontmatter(frontmatter),
200
+ };
224
201
  }
225
202
 
226
203
  function getAgentSources(agentName, workingDirectory, lookupCache = createAgentLookupCache()) {
227
204
  const projectPath = workingDirectory ? getProjectAgentPath(workingDirectory, agentName) : null;
228
- const projectExists = projectPath && fs.existsSync(projectPath);
205
+ const projectExists = Boolean(projectPath) && fs.existsSync(projectPath);
229
206
 
230
207
  const userPath = getUserAgentPath(agentName, lookupCache);
231
208
  const userExists = fs.existsSync(userPath);
232
209
 
233
210
  const mdPath = projectExists ? projectPath : (userExists ? userPath : null);
234
- const mdExists = !!mdPath;
211
+ const mdExists = Boolean(mdPath);
235
212
  const mdScope = projectExists ? AGENT_SCOPE.PROJECT : (userExists ? AGENT_SCOPE.USER : null);
236
213
 
237
214
  const layers = readConfigLayers(workingDirectory);
238
- const jsonSource = getJsonEntrySource(layers, 'agent', agentName);
239
- const jsonSection = jsonSource.section;
215
+ const jsonSource = getJsonEntrySource(layers, 'agents', agentName);
240
216
  const jsonPath = jsonSource.path || layers.paths.customPath || layers.paths.projectPath || layers.paths.userPath;
241
217
  const jsonScope = jsonSource.path === layers.paths.projectPath ? AGENT_SCOPE.PROJECT : AGENT_SCOPE.USER;
242
218
 
@@ -245,79 +221,119 @@ function getAgentSources(agentName, workingDirectory, lookupCache = createAgentL
245
221
  exists: mdExists,
246
222
  path: mdPath,
247
223
  scope: mdScope,
248
- fields: []
224
+ legacy: false,
225
+ fields: [],
249
226
  },
250
227
  json: {
251
228
  exists: jsonSource.exists,
252
229
  path: jsonPath,
253
230
  scope: jsonSource.exists ? jsonScope : null,
254
- fields: []
231
+ sectionKey: jsonSource.sectionKey,
232
+ legacy: Boolean(jsonSource.legacy),
233
+ fields: [],
255
234
  },
256
235
  projectMd: {
257
236
  exists: projectExists,
258
- path: projectPath
237
+ path: projectPath,
259
238
  },
260
239
  userMd: {
261
240
  exists: userExists,
262
- path: userPath
263
- }
241
+ path: userPath,
242
+ },
264
243
  };
265
244
 
266
245
  if (mdExists) {
267
- const { frontmatter, body } = parseMdFile(mdPath);
268
- sources.md.fields = Object.keys(frontmatter);
269
- if (body) {
270
- sources.md.fields.push('prompt');
271
- }
246
+ const md = readMdAgent(mdPath);
247
+ sources.md.legacy = md.legacy;
248
+ sources.md.fields = Object.keys(md.entity);
272
249
  }
273
250
 
274
- if (jsonSection) {
275
- sources.json.fields = Object.keys(jsonSection);
251
+ if (jsonSource.exists) {
252
+ sources.json.fields = Object.keys(toAgentEntity(jsonSource.section));
276
253
  }
277
254
 
278
255
  return sources;
279
256
  }
280
257
 
258
+ /**
259
+ * Canonical v2 agent entity plus where it came from. `config.system` is the
260
+ * markdown body for .md agents; `config.permissions` is always the ordered v2
261
+ * rule array, even when the file still uses a v1 `permission` map.
262
+ */
281
263
  function getAgentConfig(agentName, workingDirectory, lookupCache = createAgentLookupCache()) {
282
264
  const projectPath = workingDirectory ? getProjectAgentPath(workingDirectory, agentName) : null;
283
- const projectExists = projectPath && fs.existsSync(projectPath);
265
+ const projectExists = Boolean(projectPath) && fs.existsSync(projectPath);
284
266
 
285
267
  const userPath = getUserAgentPath(agentName, lookupCache);
286
268
  const userExists = fs.existsSync(userPath);
287
269
 
288
270
  if (projectExists || userExists) {
289
271
  const mdPath = projectExists ? projectPath : userPath;
290
- const { frontmatter, body } = parseMdFile(mdPath);
291
-
272
+ const md = readMdAgent(mdPath);
292
273
  return {
293
274
  source: 'md',
294
275
  scope: projectExists ? AGENT_SCOPE.PROJECT : AGENT_SCOPE.USER,
295
- config: {
296
- ...frontmatter,
297
- ...(typeof body === 'string' && body.length > 0 ? { prompt: body } : {}),
298
- },
276
+ path: mdPath,
277
+ legacy: md.legacy,
278
+ config: md.entity,
299
279
  };
300
280
  }
301
281
 
302
282
  const layers = readConfigLayers(workingDirectory);
303
- const jsonSource = getJsonEntrySource(layers, 'agent', agentName);
283
+ const jsonSource = getJsonEntrySource(layers, 'agents', agentName);
304
284
 
305
- if (jsonSource.exists && jsonSource.section) {
285
+ if (jsonSource.exists) {
306
286
  const scope = jsonSource.path === layers.paths.projectPath ? AGENT_SCOPE.PROJECT : AGENT_SCOPE.USER;
307
287
  return {
308
288
  source: 'json',
309
289
  scope,
310
- config: { ...jsonSource.section },
290
+ path: jsonSource.path,
291
+ legacy: Boolean(jsonSource.legacy),
292
+ config: toAgentEntity(jsonSource.section),
311
293
  };
312
294
  }
313
295
 
314
296
  return {
315
297
  source: 'none',
316
298
  scope: null,
299
+ path: null,
300
+ legacy: false,
317
301
  config: {},
318
302
  };
319
303
  }
320
304
 
305
+ /**
306
+ * The permission rules that apply to an agent, in evaluation order (global
307
+ * rules first, agent rules last; last match wins). Answers the editor question
308
+ * "what applies to this agent".
309
+ */
310
+ function getAgentPermissions(agentName, workingDirectory, lookupCache = createAgentLookupCache()) {
311
+ const layers = readConfigLayers(workingDirectory);
312
+ const global = [
313
+ ...readGlobalPermissionRules(layers.userConfig),
314
+ ...readGlobalPermissionRules(layers.projectConfig),
315
+ ...readGlobalPermissionRules(layers.customConfig),
316
+ ];
317
+ const agent = getAgentConfig(agentName, workingDirectory, lookupCache);
318
+ return {
319
+ global,
320
+ agent: agent.config.permissions ?? [],
321
+ effective: effectiveAgentRules(global, agent.config.permissions ?? []),
322
+ source: agent.source,
323
+ path: agent.path,
324
+ };
325
+ }
326
+
327
+ // ============== WRITE ==============
328
+
329
+ function writeAgentMd(targetPath, entity) {
330
+ const { fields, system } = fromAgentEntity(entity);
331
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
332
+ // Native keys only: a single legacy key routes the whole file through
333
+ // OpenCode's v1 decoder, which would drop the `permissions` array.
334
+ writeMdFile(targetPath, fields, system);
335
+ }
336
+
321
337
  function createAgent(agentName, config, workingDirectory, scope) {
322
338
  ensureDirs();
323
339
  const lookupCache = createAgentLookupCache();
@@ -334,8 +350,7 @@ function createAgent(agentName, config, workingDirectory, scope) {
334
350
  }
335
351
 
336
352
  const layers = readConfigLayers(workingDirectory);
337
- const jsonSource = getJsonEntrySource(layers, 'agent', agentName);
338
- if (jsonSource.exists) {
353
+ if (getJsonEntrySource(layers, 'agents', agentName).exists) {
339
354
  throw new Error(`Agent ${agentName} already exists in opencode.json`);
340
355
  }
341
356
 
@@ -351,233 +366,122 @@ function createAgent(agentName, config, workingDirectory, scope) {
351
366
  targetScope = AGENT_SCOPE.USER;
352
367
  }
353
368
 
354
- const { prompt, scope: _scopeFromConfig, ...rawFrontmatter } = config;
355
- const frontmatter = Object.fromEntries(
356
- Object.entries(rawFrontmatter).filter(([, value]) => value !== null && value !== undefined)
357
- );
358
-
359
- writeMdFile(targetPath, frontmatter, prompt || '');
369
+ const { scope: _ignoredScope, ...entity } = isRecord(config) ? config : {};
370
+ writeAgentMd(targetPath, entity);
360
371
  console.log(`Created new agent: ${agentName} (scope: ${targetScope}, path: ${targetPath})`);
372
+ return { scope: targetScope, path: targetPath };
361
373
  }
362
374
 
363
- function updateAgent(agentName, updates, workingDirectory) {
364
- ensureDirs();
365
- const lookupCache = createAgentLookupCache();
375
+ // v1 agent fields that do not exist at the top level of a v2 entity any more.
376
+ // A client clearing one of them names the v1 field, so deletion has to reach
377
+ // the v2 location instead of removing a key that was never there.
378
+ const AGENT_REQUEST_BODY_FIELDS = ['temperature', 'top_p'];
366
379
 
367
- const { scope, path: mdPath } = getAgentWritePath(agentName, workingDirectory, undefined, lookupCache);
368
- const mdExists = mdPath && fs.existsSync(mdPath);
369
-
370
- const layers = readConfigLayers(workingDirectory);
371
- const jsonSource = getJsonEntrySource(layers, 'agent', agentName);
372
- const jsonSection = jsonSource.section;
373
- const hasJsonFields = jsonSource.exists && jsonSection && Object.keys(jsonSection).length > 0;
374
- const jsonTarget = jsonSource.exists
375
- ? { config: jsonSource.config, path: jsonSource.path }
376
- : getJsonWriteTarget(layers, AGENT_SCOPE.USER);
377
- let config = jsonTarget.config || {};
378
-
379
- const isBuiltinOverride = !mdExists && !hasJsonFields;
380
+ function deleteRequestBodyField(entity, key) {
381
+ if (!isRecord(entity.request) || !isRecord(entity.request.body)) return;
382
+ delete entity.request.body[key];
383
+ if (Object.keys(entity.request.body).length === 0) delete entity.request.body;
384
+ if (Object.keys(entity.request).length === 0) delete entity.request;
385
+ }
380
386
 
381
- let targetPath = mdPath;
382
- let targetScope = scope;
387
+ /** Drop the `#variant` suffix, keeping the provider and model. */
388
+ function stripModelVariant(entity) {
389
+ const parsed = parseModelSelection(entity.model);
390
+ if (!parsed) return;
391
+ const stripped = formatModelSelection({ providerID: parsed.providerID, modelID: parsed.modelID });
392
+ if (stripped) entity.model = stripped;
393
+ }
383
394
 
384
- if (!mdExists && isBuiltinOverride) {
385
- targetPath = getUserAgentPath(agentName, lookupCache);
386
- targetScope = AGENT_SCOPE.USER;
395
+ function deleteAgentField(entity, field) {
396
+ if (field === 'permission' || field === 'permissions') {
397
+ delete entity.permissions;
398
+ return;
387
399
  }
400
+ if (field === 'variant') {
401
+ stripModelVariant(entity);
402
+ return;
403
+ }
404
+ if (AGENT_REQUEST_BODY_FIELDS.includes(field)) {
405
+ deleteRequestBodyField(entity, field);
406
+ return;
407
+ }
408
+ delete entity[field === 'prompt' ? 'system' : field];
409
+ }
388
410
 
389
- let mdData = mdExists ? parseMdFile(mdPath) : (isBuiltinOverride ? { frontmatter: {}, body: '' } : null);
390
-
391
- let mdModified = false;
392
- let jsonModified = false;
393
- const creatingNewMd = isBuiltinOverride;
394
-
395
- for (const [field, value] of Object.entries(updates)) {
396
- // Skip undefined values — they would overwrite existing frontmatter fields with nothing
397
- if (value === undefined) continue;
398
-
399
- if (field === 'prompt') {
400
- if (value === null) {
401
- if (mdExists || creatingNewMd) {
402
- if (mdData) {
403
- mdData.body = '';
404
- mdModified = true;
405
- }
406
- continue;
407
- }
408
-
409
- if (isPromptFileReference(jsonSection?.prompt)) {
410
- const promptFilePath = resolvePromptFilePath(jsonSection.prompt);
411
- if (!promptFilePath) {
412
- throw new Error(`Invalid prompt file reference for agent ${agentName}`);
413
- }
414
- writePromptFile(promptFilePath, '');
415
- continue;
416
- }
417
-
418
- if (config.agent?.[agentName]) {
419
- delete config.agent[agentName].prompt;
420
-
421
- if (Object.keys(config.agent[agentName]).length === 0) {
422
- delete config.agent[agentName];
423
- }
424
- if (Object.keys(config.agent).length === 0) {
425
- delete config.agent;
426
- }
427
-
428
- jsonModified = true;
429
- }
430
- continue;
431
- }
432
-
433
- const normalizedValue = typeof value === 'string' ? value : (value == null ? '' : String(value));
434
-
435
- if (mdExists || creatingNewMd) {
436
- if (mdData) {
437
- mdData.body = normalizedValue;
438
- mdModified = true;
439
- }
440
- continue;
441
- } else if (isPromptFileReference(jsonSection?.prompt)) {
442
- const promptFilePath = resolvePromptFilePath(jsonSection.prompt);
443
- if (!promptFilePath) {
444
- throw new Error(`Invalid prompt file reference for agent ${agentName}`);
445
- }
446
- writePromptFile(promptFilePath, normalizedValue);
447
- continue;
448
- } else if (isPromptFileReference(normalizedValue)) {
449
- if (!config.agent) config.agent = {};
450
- if (!config.agent[agentName]) config.agent[agentName] = {};
451
- config.agent[agentName].prompt = normalizedValue;
452
- jsonModified = true;
453
- continue;
454
- }
455
-
456
- if (!config.agent) config.agent = {};
457
- if (!config.agent[agentName]) config.agent[agentName] = {};
458
- config.agent[agentName].prompt = normalizedValue;
459
- jsonModified = true;
411
+ /**
412
+ * Merge a partial update into a canonical entity. `null` removes a field,
413
+ * `undefined` leaves it alone.
414
+ */
415
+ function applyAgentUpdates(entity, updates) {
416
+ // `request` is copied so clearing one overlay field cannot mutate the entity
417
+ // the caller still holds.
418
+ const next = { ...entity };
419
+ if (isRecord(next.request)) {
420
+ const request = { ...next.request };
421
+ if (isRecord(request.body)) request.body = { ...request.body };
422
+ if (isRecord(request.headers)) request.headers = { ...request.headers };
423
+ next.request = request;
424
+ }
425
+ for (const [field, value] of Object.entries(isRecord(updates) ? updates : {})) {
426
+ if (field === 'scope' || value === undefined) continue;
427
+ if (value === null) {
428
+ deleteAgentField(next, field);
460
429
  continue;
461
430
  }
462
-
463
- if (field === 'permission') {
464
- const permissionSource = getAgentPermissionSource(agentName, workingDirectory, lookupCache);
465
- // The client edits the complete source permission map; persist it verbatim.
466
- // (The old non-wildcard re-merge resurrected rules the user deleted.)
467
- const newPermission = value && typeof value === 'object' && Object.keys(value).length === 0 ? null : value;
468
-
469
- if (permissionSource.source === 'md') {
470
- if (mdData && permissionSource.path === targetPath) {
471
- applyAgentPermission(mdData.frontmatter, newPermission);
472
- mdModified = true;
473
- } else {
474
- const existingMdData = parseMdFile(permissionSource.path);
475
- applyAgentPermission(existingMdData.frontmatter, newPermission);
476
- writeMdFile(permissionSource.path, existingMdData.frontmatter, existingMdData.body);
477
- console.log(`Updated permission in .md file: ${permissionSource.path}`);
478
- }
479
- } else if (permissionSource.source === 'json') {
480
- if (permissionSource.path === (jsonTarget.path || CONFIG_FILE)) {
481
- if (!config.agent) config.agent = {};
482
- if (!config.agent[agentName]) config.agent[agentName] = {};
483
- applyAgentPermission(config.agent[agentName], newPermission);
484
- jsonModified = true;
485
- } else {
486
- const existingConfig = readConfigFile(permissionSource.path);
487
- if (!existingConfig.agent) existingConfig.agent = {};
488
- if (!existingConfig.agent[agentName]) existingConfig.agent[agentName] = {};
489
- applyAgentPermission(existingConfig.agent[agentName], newPermission);
490
- writeConfig(existingConfig, permissionSource.path);
491
- console.log(`Updated permission in JSON: ${permissionSource.path}`);
492
- }
493
- } else {
494
- if (mdExists && mdData) {
495
- applyAgentPermission(mdData.frontmatter, newPermission);
496
- mdModified = true;
497
- } else if (hasJsonFields) {
498
- if (!config.agent) config.agent = {};
499
- if (!config.agent[agentName]) config.agent[agentName] = {};
500
- applyAgentPermission(config.agent[agentName], newPermission);
501
- jsonModified = true;
502
- } else {
503
- const writeTarget = getJsonWriteTarget(layers, AGENT_SCOPE.USER);
504
- if (!writeTarget.config.agent) writeTarget.config.agent = {};
505
- if (!writeTarget.config.agent[agentName]) writeTarget.config.agent[agentName] = {};
506
- applyAgentPermission(writeTarget.config.agent[agentName], newPermission);
507
- writeConfig(writeTarget.config, writeTarget.path);
508
- console.log(`Created permission in JSON: ${writeTarget.path}`);
509
- }
510
- }
431
+ if (field === 'permission' || field === 'permissions') {
432
+ const rules = normalizePermissionRules(value);
433
+ if (rules.length === 0) delete next.permissions;
434
+ else next.permissions = rules;
511
435
  continue;
512
436
  }
437
+ // `prompt` is the v1 spelling of `system`; accept it so older clients keep working.
438
+ next[field === 'prompt' ? 'system' : field] = value;
439
+ }
440
+ return toAgentEntity(next);
441
+ }
513
442
 
514
- const inMd = mdData?.frontmatter?.[field] !== undefined;
515
- const inJson = jsonSection?.[field] !== undefined;
516
-
517
- if (value === null) {
518
- if (mdData && inMd) {
519
- delete mdData.frontmatter[field];
520
- mdModified = true;
521
- }
522
-
523
- if (inJson && config.agent?.[agentName]) {
524
- delete config.agent[agentName][field];
525
-
526
- if (Object.keys(config.agent[agentName]).length === 0) {
527
- delete config.agent[agentName];
528
- }
529
- if (Object.keys(config.agent).length === 0) {
530
- delete config.agent;
531
- }
532
-
533
- jsonModified = true;
534
- }
535
-
536
- continue;
537
- }
443
+ function updateAgent(agentName, updates, workingDirectory) {
444
+ ensureDirs();
445
+ const lookupCache = createAgentLookupCache();
538
446
 
539
- if (inJson) {
540
- if (!config.agent) config.agent = {};
541
- if (!config.agent[agentName]) config.agent[agentName] = {};
542
- config.agent[agentName][field] = value;
543
- jsonModified = true;
544
- } else if (inMd || creatingNewMd) {
545
- if (mdData) {
546
- mdData.frontmatter[field] = value;
547
- mdModified = true;
447
+ const current = getAgentConfig(agentName, workingDirectory, lookupCache);
448
+ const entity = applyAgentUpdates(current.config, updates);
449
+
450
+ if (current.source === 'md') {
451
+ writeAgentMd(current.path, entity);
452
+ console.log(`Updated agent: ${agentName} (md: ${current.path})`);
453
+ return { source: 'md', scope: current.scope, path: current.path };
454
+ }
455
+
456
+ if (current.source === 'json') {
457
+ const layers = readConfigLayers(workingDirectory);
458
+ const jsonSource = getJsonEntrySource(layers, 'agents', agentName);
459
+ const config = jsonSource.config || {};
460
+ const rawSystem = jsonSource.section?.system ?? jsonSource.section?.prompt;
461
+ // `{file:...}` substitution still works in OpenCode 2, so an agent whose
462
+ // system prompt lives in a file keeps the reference and the file is edited.
463
+ if (isPromptFileReference(rawSystem)) {
464
+ const promptFilePath = resolvePromptFilePath(rawSystem);
465
+ if (!promptFilePath) {
466
+ throw new Error(`Invalid prompt file reference for agent ${agentName}`);
548
467
  }
549
- } else {
550
- if ((mdExists || creatingNewMd) && mdData) {
551
- mdData.frontmatter[field] = value;
552
- mdModified = true;
553
- } else {
554
- if (!config.agent) config.agent = {};
555
- if (!config.agent[agentName]) config.agent[agentName] = {};
556
- config.agent[agentName][field] = value;
557
- jsonModified = true;
468
+ if (entity.system !== current.config.system) {
469
+ writePromptFile(promptFilePath, entity.system ?? '');
558
470
  }
471
+ entity.system = rawSystem;
559
472
  }
560
- }
561
-
562
- if (mdModified && mdData) {
563
- writeMdFile(targetPath, mdData.frontmatter, mdData.body);
564
- }
565
-
566
- if (jsonModified) {
567
- writeConfig(config, jsonTarget.path || CONFIG_FILE);
568
- }
569
-
570
- console.log(`Updated agent: ${agentName} (scope: ${targetScope}, md: ${mdModified}, json: ${jsonModified})`);
571
- }
572
-
573
- function deleteJsonAgentEntry(config, agentName) {
574
- const agentMap = config.agent;
575
- if (!agentMap || typeof agentMap !== 'object' || Array.isArray(agentMap) || !agentMap[agentName]) return false;
576
- delete agentMap[agentName];
577
- if (Object.keys(agentMap).length === 0) {
578
- delete config.agent;
579
- }
580
- return true;
473
+ writeSectionEntry(config, 'agents', agentName, entity);
474
+ const targetPath = jsonSource.path || CONFIG_FILE;
475
+ writeConfig(config, targetPath);
476
+ console.log(`Updated agent: ${agentName} (json: ${targetPath})`);
477
+ return { source: 'json', scope: current.scope, path: targetPath };
478
+ }
479
+
480
+ // Built-in override: materialize a user-level v2 markdown agent.
481
+ const { scope, path: targetPath } = getAgentWritePath(agentName, workingDirectory, AGENT_SCOPE.USER, lookupCache);
482
+ writeAgentMd(targetPath, entity);
483
+ console.log(`Created agent override: ${agentName} (scope: ${scope}, path: ${targetPath})`);
484
+ return { source: 'md', scope, path: targetPath };
581
485
  }
582
486
 
583
487
  function deleteAgent(agentName, workingDirectory, scope) {
@@ -605,7 +509,7 @@ function deleteAgent(agentName, workingDirectory, scope) {
605
509
  const layers = readConfigLayers(workingDirectory);
606
510
 
607
511
  if (requestedScope === AGENT_SCOPE.PROJECT) {
608
- if (layers.paths.projectPath && deleteJsonAgentEntry(layers.projectConfig, agentName)) {
512
+ if (layers.paths.projectPath && deleteSectionEntry(layers.projectConfig, 'agents', agentName)) {
609
513
  writeConfig(layers.projectConfig, layers.paths.projectPath);
610
514
  console.log(`Removed project-level agent from opencode.json: ${agentName}`);
611
515
  return;
@@ -616,7 +520,7 @@ function deleteAgent(agentName, workingDirectory, scope) {
616
520
  if (requestedScope === AGENT_SCOPE.USER) {
617
521
  const userJsonPath = layers.paths.customPath || layers.paths.userPath;
618
522
  const userJsonConfig = layers.paths.customPath ? layers.customConfig : layers.userConfig;
619
- if (userJsonPath && deleteJsonAgentEntry(userJsonConfig, agentName)) {
523
+ if (userJsonPath && deleteSectionEntry(userJsonConfig, 'agents', agentName)) {
620
524
  writeConfig(userJsonConfig, userJsonPath);
621
525
  console.log(`Removed user-level agent from opencode.json: ${agentName}`);
622
526
  return;
@@ -624,8 +528,9 @@ function deleteAgent(agentName, workingDirectory, scope) {
624
528
  throw new Error(`User agent ${agentName} not found`);
625
529
  }
626
530
 
627
- const jsonSource = getJsonEntrySource(layers, 'agent', agentName);
628
- if (jsonSource.exists && jsonSource.config && jsonSource.path && deleteJsonAgentEntry(jsonSource.config, agentName)) {
531
+ const jsonSource = getJsonEntrySource(layers, 'agents', agentName);
532
+ if (jsonSource.exists && jsonSource.config && jsonSource.path
533
+ && deleteSectionEntry(jsonSource.config, 'agents', agentName)) {
629
534
  writeConfig(jsonSource.config, jsonSource.path);
630
535
  console.log(`Removed agent from opencode.json: ${agentName}`);
631
536
  return;
@@ -637,6 +542,7 @@ function deleteAgent(agentName, workingDirectory, scope) {
637
542
  export {
638
543
  getAgentSources,
639
544
  getAgentConfig,
545
+ getAgentPermissions,
640
546
  createAgent,
641
547
  updateAgent,
642
548
  deleteAgent,