@vantran-se/openchamber-web 2.0.4 → 2.0.5

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 (243) hide show
  1. package/dist/assets/{CodeMirrorCommentWidgets-DZF8ntaq.js → CodeMirrorCommentWidgets-DCAQTLMM.js} +1 -1
  2. package/dist/assets/DiffView-uzNpHBlV.js +4 -0
  3. package/dist/assets/FilesView-Bkq0sjNx.js +17 -0
  4. package/dist/assets/{GitView-DwQwqDvu.js → GitView-BmibZC7n.js} +4 -4
  5. package/dist/assets/{GuestAttachDialog-CoXM0gh_.js → GuestAttachDialog-D7TJG1rt.js} +1 -1
  6. package/dist/assets/GuestSurfacePane-PAcYaFzP.js +1 -0
  7. package/dist/assets/{JsonTreeView-Cq_27v0y.js → JsonTreeView-BNT2yUVu.js} +1 -1
  8. package/dist/assets/{LinearIssuesView-CsSSRMhn.js → LinearIssuesView-D3Azksju.js} +1 -1
  9. package/dist/assets/MarkdownImageGallery-BGY6tuiK.js +1 -0
  10. package/dist/assets/MarkdownRendererImpl-DJmhQ37f.js +12 -0
  11. package/dist/assets/{OnboardingScreen-ClatMQAX.js → OnboardingScreen-D_41H2qC.js} +1 -1
  12. package/dist/assets/{PatchDiff-DOwlrIK9.js → PatchDiff-C0RSqa9W.js} +1 -1
  13. package/dist/assets/{PierreDiffViewer-CrcGqqD_.js → PierreDiffViewer-BBE7tTY2.js} +3 -3
  14. package/dist/assets/{PlanView-BGLsMgQk.js → PlanView-Bqc-xuDe.js} +4 -4
  15. package/dist/assets/PluginPane-DQ0k41Q-.js +3 -0
  16. package/dist/assets/SettingsView-BAiJU5hJ.js +30 -0
  17. package/dist/assets/{SettingsWindow-B0BAy-f9.js → SettingsWindow-DuC4ecLw.js} +1 -1
  18. package/dist/assets/{SubmoduleDiffSummary-fcdDzCSW.js → SubmoduleDiffSummary-0HgmcMXz.js} +1 -1
  19. package/dist/assets/{ToolOutputDialog-B7xaUz2H.js → ToolOutputDialog-DdHkQJEE.js} +6 -6
  20. package/dist/assets/{ToolPartDiffPreview-CkWCaUmZ.js → ToolPartDiffPreview-DWdN0r5K.js} +2 -2
  21. package/dist/assets/{VirtualizedFileDiff-DONC4eI2.js → VirtualizedFileDiff-ClDezf2d.js} +1 -1
  22. package/dist/assets/{WalkthroughView-CXDImOXW.js → WalkthroughView-BJicp0RC.js} +2 -2
  23. package/dist/assets/{appThemeRegistry-Br2TLSlb.js → appThemeRegistry-CiuyzfSW.js} +1 -1
  24. package/dist/assets/changesTree-CePHbFcj.js +1 -0
  25. package/dist/assets/commitAndPush-DTKb6IwT.js +1 -0
  26. package/dist/assets/{computeVirtualFileMetrics-_-20umCs.js → computeVirtualFileMetrics-kM_zJNMt.js} +2 -2
  27. package/dist/assets/de-BMnfXm-G.js +10 -0
  28. package/dist/assets/es-DKEvCCL6.js +16 -0
  29. package/dist/assets/fr-D9fgE2xE.js +14 -0
  30. package/dist/assets/index-DUke1nme.css +1 -0
  31. package/dist/assets/{index-OKDYjcMF.js → index-DWB8bR_g.js} +1 -1
  32. package/dist/assets/index-Di6zUe5h.js +41 -0
  33. package/dist/assets/{isDiffPlainText-D77M6q1f.js → isDiffPlainText-DOoIsGqs.js} +1 -1
  34. package/dist/assets/{isFilePlainText-C54B4Mns.js → isFilePlainText-bt5VHvcd.js} +1 -1
  35. package/dist/assets/ja-BEXad-KW.js +16 -0
  36. package/dist/assets/ko-DGrQ0lEe.js +16 -0
  37. package/dist/assets/{main-CGEvLMd2.js → main-BpLQbZC2.js} +2 -2
  38. package/dist/assets/main-CI9DtHJv.js +896 -0
  39. package/dist/assets/{markdown-shiki.worker-CahLvHIq.js → markdown-shiki.worker-Bg8SoAJJ.js} +8 -5
  40. package/dist/assets/markdownCore-DK2KK5RW.js +7 -0
  41. package/dist/assets/{miniChat-BSxkRQLj.js → miniChat-CIWaR8wG.js} +2 -2
  42. package/dist/assets/{mobile-C5KRTNOB.js → mobile-CnWKEYIP.js} +2 -2
  43. package/dist/assets/patchFileDiff-Cqn0C0az.js +13 -0
  44. package/dist/assets/pl-BlhkmR5b.js +16 -0
  45. package/dist/assets/pt-BR-Bs96Ew9I.js +16 -0
  46. package/dist/assets/{renderElectronMiniChatApp-CnkAQV6o.js → renderElectronMiniChatApp-D9t0-9nV.js} +2 -2
  47. package/dist/assets/renderMobileApp-CW7fVs_s.js +25 -0
  48. package/dist/assets/runtimeEndpointReset-D0PcCb3K.js +27 -0
  49. package/dist/assets/scheduledTasksApi-Dahv3ws0.js +1 -0
  50. package/dist/assets/{shikiHighlight-4O0ZM14D.js → shikiHighlight-l-MbaNS4.js} +1 -1
  51. package/dist/assets/{textMateThemeFromAppTheme-yNq7b0DZ.js → textMateThemeFromAppTheme-C-kGcOpD.js} +1 -1
  52. package/dist/assets/tr-CehUK1M9.js +7 -0
  53. package/dist/assets/uk-Bno5WGNz.js +16 -0
  54. package/dist/assets/{useAppFontEffects-Bzyszv2N.js → useAppFontEffects-DDjuwVqi.js} +160 -156
  55. package/dist/assets/{useInlineCommentController-zOf6ORAq.js → useInlineCommentController-Clg8EO3A.js} +2 -2
  56. package/dist/assets/{useRootScrollLock-DCaZ4BWi.js → useRootScrollLock-nDcRrgJ1.js} +2 -2
  57. package/dist/assets/{vendor-codemirror-language-data-6cEFPNUR.js → vendor-codemirror-language-data-oKF0MAwd.js} +2 -2
  58. package/dist/assets/vendor-opencode-client-DSZl7jSi.js +12 -0
  59. package/dist/assets/vendor-zod-BSoePkpP.js +39 -0
  60. package/dist/assets/{web-components-BEo4-ISu.js → web-components-Bc1WPd4i.js} +1 -1
  61. package/dist/assets/zh-CN-DnAweIWo.js +16 -0
  62. package/dist/assets/zh-TW-BbBBK25p.js +16 -0
  63. package/dist/index.html +5 -5
  64. package/dist/mini-chat.html +5 -5
  65. package/dist/mobile.html +5 -5
  66. package/package.json +3 -3
  67. package/server/index.js +82 -0
  68. package/server/lib/agent-tool/DOCUMENTATION.md +132 -47
  69. package/server/lib/agent-tool/runtime.js +34 -15
  70. package/server/lib/agent-tool/runtime.test.js +15 -0
  71. package/server/lib/browser-control/DOCUMENTATION.md +14 -2
  72. package/server/lib/event-stream/global-hub.js +23 -1
  73. package/server/lib/event-stream/global-hub.test.js +35 -0
  74. package/server/lib/event-stream/global-ws-bridge.js +3 -1
  75. package/server/lib/event-stream/upstream-reader.js +3 -2
  76. package/server/lib/git/DOCUMENTATION.md +1 -1
  77. package/server/lib/git/service.js +22 -0
  78. package/server/lib/git/service.test.js +8 -0
  79. package/server/lib/guests/DOCUMENTATION.md +4 -3
  80. package/server/lib/guests/catalog.js +19 -1
  81. package/server/lib/guests/catalog.test.js +22 -0
  82. package/server/lib/guests/html-styles.js +5 -3
  83. package/server/lib/guests/html-styles.test.js +3 -1
  84. package/server/lib/guests/routes.js +11 -3
  85. package/server/lib/guests/service.js +5 -0
  86. package/server/lib/guests/surface.js +55 -9
  87. package/server/lib/guests/surface.test.js +56 -6
  88. package/server/lib/notifications/DOCUMENTATION.md +10 -0
  89. package/server/lib/notifications/emit-route.js +118 -0
  90. package/server/lib/notifications/emit-route.test.js +112 -0
  91. package/server/lib/openchamber-control/actions.js +16 -2
  92. package/server/lib/openchamber-control/service.js +23 -0
  93. package/server/lib/openchamber-control/service.test.js +31 -0
  94. package/server/lib/openchamber-sessions/fork-inheritance.js +103 -0
  95. package/server/lib/openchamber-sessions/fork-inheritance.test.js +69 -0
  96. package/server/lib/openchamber-sessions/routes.js +11 -0
  97. package/server/lib/openchamber-sessions/routes.test.js +18 -0
  98. package/server/lib/opencode/bootstrap-runtime.js +14 -1
  99. package/server/lib/opencode/compatibility.js +6 -3
  100. package/server/lib/opencode/compatibility.test.js +8 -0
  101. package/server/lib/opencode/config-entity-v2.test.js +27 -0
  102. package/server/lib/opencode/config-paths.test.js +12 -0
  103. package/server/lib/opencode/config-v2.d.ts +2 -0
  104. package/server/lib/opencode/config-v2.js +42 -0
  105. package/server/lib/opencode/config-v2.test.js +17 -0
  106. package/server/lib/opencode/core-routes.js +20 -4
  107. package/server/lib/opencode/core-routes.test.js +41 -0
  108. package/server/lib/opencode/env-runtime.js +1 -8
  109. package/server/lib/opencode/feature-routes-runtime.js +2 -1
  110. package/server/lib/opencode/lifecycle.js +30 -2
  111. package/server/lib/opencode/lifecycle.test.js +27 -0
  112. package/server/lib/opencode/managed-config-file.js +18 -6
  113. package/server/lib/opencode/managed-config-file.test.js +23 -7
  114. package/server/lib/opencode/mcp.js +7 -2
  115. package/server/lib/opencode/npm-registry-config.d.ts +1 -0
  116. package/server/lib/opencode/npm-registry-config.js +123 -0
  117. package/server/lib/opencode/npm-registry-config.test.js +167 -0
  118. package/server/lib/opencode/npm-registry.js +5 -6
  119. package/server/lib/opencode/npm-registry.test.js +30 -0
  120. package/server/lib/opencode/openchamber-routes.js +29 -11
  121. package/server/lib/opencode/openchamber-routes.test.js +18 -0
  122. package/server/lib/opencode/project-directory-runtime.js +8 -0
  123. package/server/lib/opencode/project-directory-runtime.test.js +13 -0
  124. package/server/lib/opencode/provider-source-route.test.js +59 -0
  125. package/server/lib/opencode/providers.js +3 -0
  126. package/server/lib/opencode/providers.test.js +62 -0
  127. package/server/lib/opencode/proxy.js +38 -2
  128. package/server/lib/opencode/routes-upgrade.test.js +1 -1
  129. package/server/lib/opencode/routes.js +36 -1
  130. package/server/lib/opencode/server-utils-runtime.js +5 -0
  131. package/server/lib/opencode/settings-helpers.js +10 -0
  132. package/server/lib/opencode/settings-helpers.test.js +2 -2
  133. package/server/lib/opencode/settings-registry.json +17 -0
  134. package/server/lib/opencode/settings-runtime.js +1 -0
  135. package/server/lib/opencode/shared.js +49 -4
  136. package/server/lib/opencode/shared.test.js +78 -1
  137. package/server/lib/opencode/shutdown-runtime.js +3 -0
  138. package/server/lib/opencode/skill-routes.js +28 -12
  139. package/server/lib/opencode/skill-routes.test.js +121 -1
  140. package/server/lib/opencode/upgrade-capability.js +0 -14
  141. package/server/lib/opencode/v1-migration-topup.js +21 -15
  142. package/server/lib/opencode/v1-migration-topup.test.js +14 -6
  143. package/server/lib/opencode/v2-install.d.ts +1 -0
  144. package/server/lib/opencode/v2-install.js +8 -7
  145. package/server/lib/opencode/v2-install.test.js +13 -4
  146. package/server/lib/opencode/watcher.js +3 -1
  147. package/server/lib/opencode/websearch-config.js +13 -1
  148. package/server/lib/quota/providers/kimi.js +1 -1
  149. package/server/lib/realtime-proxy.js +5 -1
  150. package/server/lib/realtime-proxy.test.js +29 -0
  151. package/server/lib/relay/tunnel-host.js +4 -1
  152. package/server/lib/relay/tunnel-host.test.js +11 -0
  153. package/server/lib/session-assist/DOCUMENTATION.md +17 -5
  154. package/server/lib/session-assist/runtime.js +19 -2
  155. package/server/lib/session-assist/runtime.test.js +8 -0
  156. package/server/lib/session-goal/DOCUMENTATION.md +11 -0
  157. package/server/lib/small-model/DOCUMENTATION.md +11 -10
  158. package/server/lib/small-model/index.js +27 -32
  159. package/server/lib/small-model/index.test.js +28 -16
  160. package/server/lib/spaces/DOCUMENTATION.md +404 -32
  161. package/server/lib/spaces/code-in-bait.js +145 -6
  162. package/server/lib/spaces/code-in.js +10 -150
  163. package/server/lib/spaces/code-in.test.js +7 -83
  164. package/server/lib/spaces/code-out.js +1819 -0
  165. package/server/lib/spaces/code-out.test.js +3945 -0
  166. package/server/lib/spaces/code-transfer.js +173 -0
  167. package/server/lib/spaces/command-stream.test.js +181 -0
  168. package/server/lib/spaces/dispatcher.js +670 -0
  169. package/server/lib/spaces/dispatcher.test.js +582 -0
  170. package/server/lib/spaces/gatekeeper-channel.js +61 -7
  171. package/server/lib/spaces/gatekeeper-channel.test.js +43 -7
  172. package/server/lib/spaces/gatekeeper-program.cjs +68 -14
  173. package/server/lib/spaces/gatekeeper-program.test.js +118 -3
  174. package/server/lib/spaces/hardening.js +20 -4
  175. package/server/lib/spaces/hardening.test.js +18 -3
  176. package/server/lib/spaces/host-git.js +7 -4
  177. package/server/lib/spaces/host-git.test.js +6 -2
  178. package/server/lib/spaces/host.js +310 -0
  179. package/server/lib/spaces/host.test.js +273 -0
  180. package/server/lib/spaces/journey.js +552 -0
  181. package/server/lib/spaces/journey.test.js +589 -0
  182. package/server/lib/spaces/layout.js +42 -8
  183. package/server/lib/spaces/manager.js +4 -3
  184. package/server/lib/spaces/manager.test.js +6 -0
  185. package/server/lib/spaces/places/code-out.docker.live.test.js +412 -0
  186. package/server/lib/spaces/places/contract-suite.js +36 -0
  187. package/server/lib/spaces/places/dispatcher.docker.live.test.js +296 -0
  188. package/server/lib/spaces/places/docker-live-support.js +27 -4
  189. package/server/lib/spaces/places/docker.js +33 -5
  190. package/server/lib/spaces/places/docker.test.js +42 -3
  191. package/server/lib/spaces/places/escape-suite.js +118 -14
  192. package/server/lib/spaces/places/fake-docker.js +3 -1
  193. package/server/lib/spaces/places/grants.docker.live.test.js +197 -0
  194. package/server/lib/spaces/places/memory-place.js +24 -1
  195. package/server/lib/spaces/places/registry.js +1 -1
  196. package/server/lib/spaces/places/server.docker.live.test.js +69 -33
  197. package/server/lib/spaces/places/sessions.docker.live.test.js +222 -0
  198. package/server/lib/spaces/routes.js +216 -0
  199. package/server/lib/spaces/routes.test.js +213 -0
  200. package/server/lib/spaces/run-command.js +261 -8
  201. package/server/lib/spaces/run-command.test.js +193 -3
  202. package/server/lib/spaces/space-events.js +153 -0
  203. package/server/lib/spaces/space-events.test.js +160 -0
  204. package/server/lib/spaces/space-opencode.js +52 -0
  205. package/server/lib/spaces/space-opencode.test.js +41 -0
  206. package/server/lib/spaces/space-records.js +150 -0
  207. package/server/lib/spaces/space-records.test.js +113 -0
  208. package/server/lib/spaces/space-sessions.js +210 -0
  209. package/server/lib/spaces/space-sessions.test.js +161 -0
  210. package/server/lib/spaces/websocket.js +329 -0
  211. package/server/lib/spaces/websocket.test.js +345 -0
  212. package/server/lib/ui-auth/ui-auth.js +8 -2
  213. package/server/lib/ui-auth/ui-auth.test.js +29 -0
  214. package/server/opencode-proxy.test.js +113 -0
  215. package/dist/assets/DiffView-D2p4Ar0H.js +0 -4
  216. package/dist/assets/FilesView-BeTtbsip.js +0 -17
  217. package/dist/assets/GuestSurfacePane-DIJvkkM-.js +0 -1
  218. package/dist/assets/MarkdownImageGallery-CY0dWTaE.js +0 -1
  219. package/dist/assets/MarkdownRendererImpl-ILdXzLrt.js +0 -12
  220. package/dist/assets/PluginPane-B39JEUL8.js +0 -3
  221. package/dist/assets/SettingsView-13B2_G-A.js +0 -29
  222. package/dist/assets/commitAndPush-CcI4ol7c.js +0 -1
  223. package/dist/assets/de-BtYnUgII.js +0 -10
  224. package/dist/assets/es-crP5qPye.js +0 -16
  225. package/dist/assets/fr-CLSRuALk.js +0 -14
  226. package/dist/assets/index-CLcM8HKx.css +0 -1
  227. package/dist/assets/index-CvLgnuNI.js +0 -41
  228. package/dist/assets/ja-SZ1-ucLV.js +0 -16
  229. package/dist/assets/ko-HthbT_Ve.js +0 -16
  230. package/dist/assets/main-BJklC7Cl.js +0 -896
  231. package/dist/assets/markdownCore-BzQuETm0.js +0 -5
  232. package/dist/assets/patchFileDiff-DGw2KryM.js +0 -13
  233. package/dist/assets/pl-C8eYfjL1.js +0 -16
  234. package/dist/assets/pt-BR-cYOrZdxS.js +0 -16
  235. package/dist/assets/renderMobileApp-CMxjwisj.js +0 -25
  236. package/dist/assets/runtimeEndpointReset-C45jrFq4.js +0 -27
  237. package/dist/assets/scheduledTasksApi-CkK1F9s2.js +0 -1
  238. package/dist/assets/tr-B4IQ5t-T.js +0 -7
  239. package/dist/assets/uk-DCoXG6ve.js +0 -16
  240. package/dist/assets/vendor-opencode-client-_BAHQo4R.js +0 -12
  241. package/dist/assets/vendor-zod-Gea9uBlY.js +0 -39
  242. package/dist/assets/zh-CN-BXBoxsFk.js +0 -16
  243. package/dist/assets/zh-TW-C6oelpnL.js +0 -16
package/dist/index.html CHANGED
@@ -515,17 +515,17 @@
515
515
  pointer-events: none;
516
516
  }
517
517
  </style>
518
- <script type="module" crossorigin src="/assets/main-CGEvLMd2.js"></script>
518
+ <script type="module" crossorigin src="/assets/main-BpLQbZC2.js"></script>
519
519
  <link rel="modulepreload" crossorigin href="/assets/vendor-vite-runtime-DD1OZmLC.js">
520
- <link rel="modulepreload" crossorigin href="/assets/vendor-zod-Gea9uBlY.js">
520
+ <link rel="modulepreload" crossorigin href="/assets/vendor-zod-BSoePkpP.js">
521
521
  <link rel="modulepreload" crossorigin href="/assets/vendor-scheduler-7OC5HNn7.js">
522
522
  <link rel="modulepreload" crossorigin href="/assets/vendor-react-BUCujlKS.js">
523
523
  <link rel="modulepreload" crossorigin href="/assets/vendor-zustand-BAGL70zf.js">
524
- <link rel="modulepreload" crossorigin href="/assets/vendor-opencode-client-_BAHQo4R.js">
525
- <link rel="modulepreload" crossorigin href="/assets/index-CvLgnuNI.js">
524
+ <link rel="modulepreload" crossorigin href="/assets/vendor-opencode-client-DSZl7jSi.js">
525
+ <link rel="modulepreload" crossorigin href="/assets/index-Di6zUe5h.js">
526
526
  <link rel="modulepreload" crossorigin href="/assets/contextPanelEmbeddedChat-CirXEPJq.js">
527
527
  <link rel="modulepreload" crossorigin href="/assets/vendor-katex-CHD9BdDO.js">
528
- <link rel="stylesheet" crossorigin href="/assets/index-CLcM8HKx.css">
528
+ <link rel="stylesheet" crossorigin href="/assets/index-DUke1nme.css">
529
529
  <link rel="stylesheet" crossorigin href="/assets/vendor-katex-Cp579UoJ.css">
530
530
  </head>
531
531
  <body class="h-full bg-background text-foreground">
@@ -74,16 +74,16 @@
74
74
  }
75
75
  </style>
76
76
 
77
- <script type="module" crossorigin src="/assets/miniChat-BSxkRQLj.js"></script>
77
+ <script type="module" crossorigin src="/assets/miniChat-CIWaR8wG.js"></script>
78
78
  <link rel="modulepreload" crossorigin href="/assets/vendor-vite-runtime-DD1OZmLC.js">
79
- <link rel="modulepreload" crossorigin href="/assets/vendor-zod-Gea9uBlY.js">
79
+ <link rel="modulepreload" crossorigin href="/assets/vendor-zod-BSoePkpP.js">
80
80
  <link rel="modulepreload" crossorigin href="/assets/vendor-scheduler-7OC5HNn7.js">
81
81
  <link rel="modulepreload" crossorigin href="/assets/vendor-react-BUCujlKS.js">
82
82
  <link rel="modulepreload" crossorigin href="/assets/vendor-zustand-BAGL70zf.js">
83
- <link rel="modulepreload" crossorigin href="/assets/vendor-opencode-client-_BAHQo4R.js">
84
- <link rel="modulepreload" crossorigin href="/assets/index-CvLgnuNI.js">
83
+ <link rel="modulepreload" crossorigin href="/assets/vendor-opencode-client-DSZl7jSi.js">
84
+ <link rel="modulepreload" crossorigin href="/assets/index-Di6zUe5h.js">
85
85
  <link rel="modulepreload" crossorigin href="/assets/vendor-katex-CHD9BdDO.js">
86
- <link rel="stylesheet" crossorigin href="/assets/index-CLcM8HKx.css">
86
+ <link rel="stylesheet" crossorigin href="/assets/index-DUke1nme.css">
87
87
  <link rel="stylesheet" crossorigin href="/assets/vendor-katex-Cp579UoJ.css">
88
88
  </head>
89
89
  <body class="h-full bg-background text-foreground">
package/dist/mobile.html CHANGED
@@ -63,16 +63,16 @@
63
63
  })();
64
64
  </script>
65
65
 
66
- <script type="module" crossorigin src="/assets/mobile-C5KRTNOB.js"></script>
66
+ <script type="module" crossorigin src="/assets/mobile-CnWKEYIP.js"></script>
67
67
  <link rel="modulepreload" crossorigin href="/assets/vendor-vite-runtime-DD1OZmLC.js">
68
- <link rel="modulepreload" crossorigin href="/assets/vendor-zod-Gea9uBlY.js">
68
+ <link rel="modulepreload" crossorigin href="/assets/vendor-zod-BSoePkpP.js">
69
69
  <link rel="modulepreload" crossorigin href="/assets/vendor-scheduler-7OC5HNn7.js">
70
70
  <link rel="modulepreload" crossorigin href="/assets/vendor-react-BUCujlKS.js">
71
71
  <link rel="modulepreload" crossorigin href="/assets/vendor-zustand-BAGL70zf.js">
72
- <link rel="modulepreload" crossorigin href="/assets/vendor-opencode-client-_BAHQo4R.js">
73
- <link rel="modulepreload" crossorigin href="/assets/index-CvLgnuNI.js">
72
+ <link rel="modulepreload" crossorigin href="/assets/vendor-opencode-client-DSZl7jSi.js">
73
+ <link rel="modulepreload" crossorigin href="/assets/index-Di6zUe5h.js">
74
74
  <link rel="modulepreload" crossorigin href="/assets/vendor-katex-CHD9BdDO.js">
75
- <link rel="stylesheet" crossorigin href="/assets/index-CLcM8HKx.css">
75
+ <link rel="stylesheet" crossorigin href="/assets/index-DUke1nme.css">
76
76
  <link rel="stylesheet" crossorigin href="/assets/vendor-katex-Cp579UoJ.css">
77
77
  </head>
78
78
  <body class="h-full bg-background text-foreground">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vantran-se/openchamber-web",
3
- "version": "2.0.4",
3
+ "version": "2.0.5",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./server/index.js",
@@ -27,8 +27,8 @@
27
27
  "@clack/prompts": "^1.1.0",
28
28
  "@octokit/rest": "^22.0.1",
29
29
  "@openchamber/sdk": "2.0.0",
30
- "@opencode/client": "2.0.15",
31
- "@opencode/schema": "2.0.15",
30
+ "@opencode/client": "2.0.18",
31
+ "@opencode/schema": "2.0.18",
32
32
  "@simplewebauthn/server": "13.3.3",
33
33
  "bun-pty": "^0.4.5",
34
34
  "compression": "^1.8.1",
package/server/index.js CHANGED
@@ -118,6 +118,8 @@ import { createAgentMemoryRuntime } from './lib/agent-memory/runtime.js';
118
118
  import { createAgentMemoryActions } from './lib/agent-memory/actions.js';
119
119
  import { createMemoryProjectResolver } from './lib/agent-memory/project-resolution.js';
120
120
  import { isAgentMemoryFeatureAvailable } from './lib/agent-memory/feature-flag.js';
121
+ import { createSpacesHost } from './lib/spaces/host.js';
122
+ import { createSwitchController, registerSpaceRoutes } from './lib/spaces/routes.js';
121
123
  import { resolvePrimaryWorktreeRoot } from './lib/git/service.js';
122
124
  import { createRemoteClientAuthRuntime } from './lib/client-auth/remote-clients.js';
123
125
  import { createClientPairingRuntime } from './lib/client-auth/pairing.js';
@@ -135,6 +137,7 @@ import { createOpenChamberSessionService } from './lib/openchamber-sessions/rout
135
137
  import { createSessionMetadataStore, createOpenCodeSessionMetadata } from './lib/openchamber-sessions/session-metadata-store.js';
136
138
  import { createScheduledTaskService } from './lib/scheduled-tasks/service.js';
137
139
  import { createOpenChamberControlService } from './lib/openchamber-control/service.js';
140
+ import { createPluginNotificationEmitter } from './lib/notifications/emit-route.js';
138
141
  import { OpenChamberControlError } from './lib/openchamber-control/error.js';
139
142
  import { createFileOpenRequester } from './lib/openchamber-control/file-open.js';
140
143
  import { applyConnectAttemptTimeout } from './lib/network-defaults.js';
@@ -392,6 +395,8 @@ const projectDirectoryRuntime = createProjectDirectoryRuntime({
392
395
  normalizeDirectoryPath,
393
396
  getReadSettingsFromDiskMigrated: () => readSettingsFromDiskMigrated,
394
397
  sanitizeProjects,
398
+ // A space's directory never runs on the host, whatever route carries it.
399
+ refuseDirectory: (candidate) => spacesHost?.refuseDirectory(candidate) ?? null,
395
400
  });
396
401
 
397
402
  const resolveDirectoryCandidate = (...args) => projectDirectoryRuntime.resolveDirectoryCandidate(...args);
@@ -635,6 +640,9 @@ let openCodeNotReadySince = 0;
635
640
  let isExternalOpenCode = false;
636
641
  let exitOnShutdown = true;
637
642
  let uiAuthController = null;
643
+ // The isolated-spaces host: the place, the manager and the dispatcher. Null while the feature's
644
+ // switch is off, and then nothing of the feature runs, see docs/isolated-spaces/DESIGN.md.
645
+ let spacesHost = null;
638
646
  let activeTunnelController = null;
639
647
  let globalWatcherStartPromise = null;
640
648
  const tunnelProviderRegistry = createTunnelProviderRegistry([
@@ -1131,6 +1139,10 @@ const serverUtilsRuntime = createServerUtilsRuntime({
1131
1139
  // down, while the proxy is registered later still.
1132
1140
  getArchivedSessions: () => openChamberSessionService.archiveStore.getAll(),
1133
1141
  getStoredSessionMetadata: () => sessionMetadataStore.listUnmigrated(),
1142
+ // Isolated spaces: with the switch on, the session list carries every space's sessions and
1143
+ // the global SSE stream their events. Called, not captured: the host is made in `main`.
1144
+ getMergeSpaceSessionList: () => (spacesHost ? (payload) => spacesHost.mergeSessionList(payload) : null),
1145
+ getSpaceEventHub: () => (spacesHost ? globalMessageStreamHub : null),
1134
1146
  fs,
1135
1147
  os,
1136
1148
  path,
@@ -1592,6 +1604,12 @@ const fileOpenRequester = createFileOpenRequester({
1592
1604
  },
1593
1605
  });
1594
1606
 
1607
+ const pluginNotificationEmitter = createPluginNotificationEmitter({
1608
+ readSettingsFromDiskMigrated,
1609
+ emitDesktopNotification,
1610
+ broadcastUiNotification,
1611
+ });
1612
+
1595
1613
  const openChamberControlService = createOpenChamberControlService({
1596
1614
  readSettingsFromDiskMigrated,
1597
1615
  sanitizeProjects,
@@ -1602,6 +1620,15 @@ const openChamberControlService = createOpenChamberControlService({
1602
1620
  scheduledTaskService,
1603
1621
  browserControl: browserControlRouter,
1604
1622
  fileOpen: fileOpenRequester,
1623
+ // The tool is off by default; a plugin generated before it was switched off
1624
+ // must not keep paging the user.
1625
+ notifyUser: async (input) => {
1626
+ const settings = await readSettingsFromDiskMigrated().catch(() => null);
1627
+ if (settings?.agentNotifyToolEnabled !== true) {
1628
+ return { status: 403, body: { error: 'The notify tool is turned off in OpenChamber settings' } };
1629
+ }
1630
+ return pluginNotificationEmitter.emit(input);
1631
+ },
1605
1632
  agentMemoryActions: createAgentMemoryActions({
1606
1633
  agentMemoryRuntime,
1607
1634
  createError: (message, status) => new OpenChamberControlError(message, status),
@@ -1711,6 +1738,7 @@ const gracefulShutdownRuntime = createGracefulShutdownRuntime({
1711
1738
  getDictationRuntime: () => dictationRuntime,
1712
1739
  getRelayService: () => relayServiceInstance,
1713
1740
  getRelayReconcileTimer: () => relayReconcileTimer,
1741
+ getSpacesHost: () => spacesHost,
1714
1742
  });
1715
1743
 
1716
1744
  const gracefulShutdown = (...args) => gracefulShutdownRuntime.gracefulShutdown(...args);
@@ -1907,6 +1935,31 @@ async function main(options = {}) {
1907
1935
  // but do not hold server listen or managed OpenCode startup on `say -v "?"`.
1908
1936
  const sayTTSCapability = detectSayTtsCapability(process);
1909
1937
 
1938
+ // The isolated-spaces switch, read here at start and changed live through its route below.
1939
+ // While it is off the feature has no place, no manager, no route and runs no `docker`.
1940
+ const buildSpacesHost = () => createSpacesHost({
1941
+ dataDir: OPENCHAMBER_DATA_DIR,
1942
+ dockerPath: searchPathFor('docker', buildAugmentedPath()) ?? 'docker',
1943
+ gitPath: searchPathFor('git', buildAugmentedPath()) ?? 'git',
1944
+ // git starts `docker exec` itself when code moves in or out, so its PATH must find docker.
1945
+ hostEnvironment: { ...process.env, PATH: buildAugmentedPath() },
1946
+ // So the session list can say which registered project each space was made for.
1947
+ listProjectDirectories: async () => {
1948
+ const settings = await readSettingsFromDiskMigrated();
1949
+ return sanitizeProjects(settings?.projects || []).map((project) => project.path);
1950
+ },
1951
+ });
1952
+ const startupSettings = await readSettingsFromDiskMigrated().catch(() => null);
1953
+ if (startupSettings?.isolatedSpacesEnabled === true) {
1954
+ try {
1955
+ spacesHost = buildSpacesHost();
1956
+ } catch (error) {
1957
+ // The feature is absent then, and the rest of the server starts as with the switch off.
1958
+ console.error(`[spaces] isolated spaces are unavailable this start: ${error?.code ?? ''} ${error?.message ?? error}`.trim());
1959
+ spacesHost = null;
1960
+ }
1961
+ }
1962
+
1910
1963
  const app = express();
1911
1964
  const serverStartedAt = new Date().toISOString();
1912
1965
  const packagedClientOrigins = new Set([
@@ -2058,6 +2111,7 @@ async function main(options = {}) {
2058
2111
  isUiVisible,
2059
2112
  getUiNotificationClients: () => uiNotificationClients,
2060
2113
  writeSseEvent,
2114
+ pluginNotificationEmitter,
2061
2115
  sessionRuntime,
2062
2116
  setPushInitialized,
2063
2117
  fs,
@@ -2073,8 +2127,35 @@ async function main(options = {}) {
2073
2127
  setAutoAcceptSession,
2074
2128
  agentToolRuntime,
2075
2129
  desktopUpdater,
2130
+ skipBodyParsing: (req) => spacesHost?.skipsBodyParsing(req) === true,
2076
2131
  });
2077
2132
  uiAuthController = bootstrapResult.uiAuthController;
2133
+ // After the API auth gate, before every route that reads a directory, before the OpenCode proxy.
2134
+ // The slot is mounted once and reads the host at call time, so the switch can turn the feature
2135
+ // on and off live: with no host it passes every request on and no upgrade is taken.
2136
+ app.use((req, res, next) => (spacesHost ? spacesHost.middleware(req, res, next) : next()));
2137
+ server.on('upgrade', (...args) => { spacesHost?.upgradeHandler(...args); });
2138
+ const startSpacesHost = (host) => {
2139
+ host.prepareUpgrades({ uiAuthController, isRequestOriginAllowed });
2140
+ // Every space's events join the host's hub, and the host asks each space for its live status.
2141
+ void host.startEvents(globalMessageStreamHub).catch((error) => {
2142
+ console.warn(`[spaces] could not follow the spaces: ${error?.message ?? error}`);
2143
+ });
2144
+ };
2145
+ if (spacesHost) startSpacesHost(spacesHost);
2146
+ const spacesSwitch = createSwitchController({
2147
+ getHost: () => spacesHost,
2148
+ setHost: (host) => { spacesHost = host; },
2149
+ buildHost: buildSpacesHost,
2150
+ startHost: startSpacesHost,
2151
+ persist: (enabled) => persistSettings({ isolatedSpacesEnabled: enabled }),
2152
+ });
2153
+ registerSpaceRoutes(app, {
2154
+ getJourney: () => spacesHost?.journey ?? null,
2155
+ getPlaces: () => spacesHost?.places() ?? [],
2156
+ readSwitch: spacesSwitch.readSwitch,
2157
+ setSwitch: spacesSwitch.setSwitch,
2158
+ });
2078
2159
  realtimeProxyRuntime = attachRealtimeProxy({
2079
2160
  app,
2080
2161
  server,
@@ -2168,6 +2249,7 @@ async function main(options = {}) {
2168
2249
  guestSurfaceRuntime?.endForGuest(event.guestId);
2169
2250
  return browserControlRouter.handleGuestDeactivated(event);
2170
2251
  },
2252
+ surfaceViewerHeaders: (guestId, viewerId) => guestSurfaceRuntime?.viewerHeaders(guestId, viewerId) ?? null,
2171
2253
  builtInExtensionsDir: options.builtInExtensionsDir,
2172
2254
  openchamberUserConfigRoot: OPENCHAMBER_USER_CONFIG_ROOT,
2173
2255
  managedChatsRoot: OPENCHAMBER_CHATS_DIR,
@@ -1,55 +1,121 @@
1
- # Managed OpenChamber agent tools
1
+ # Managed OpenChamber Agent Tool
2
2
 
3
3
  ## Purpose
4
4
 
5
- This module exposes OpenChamber actions as typed OpenCode custom tools for Electron Desktop's managed OpenCode child.
6
-
7
- The main tools are:
8
-
9
- - `openchamber` for projects, sessions, worktrees, scheduled tasks, and `file.open`
10
- - `openchamber_web` for inspecting and interacting with the page in OpenChamber's browser panel
11
-
12
- Their persisted settings control whether each tool is present. Tool definitions include only the actions and inputs enabled for that tool.
13
-
14
- ## Runtime boundary
15
-
16
- Electron Desktop owns the managed integration:
17
-
18
- 1. `materializePlugin()` writes the generated plugin under `<openchamber-data-dir>/agent-tool/`.
19
- 2. The managed OpenCode config lists that plugin directory.
20
- 3. `createChildEnv()` gives the owned OpenCode child a random callback token and loopback callback URL.
21
- 4. The plugin calls `POST /api/openchamber/agent-tool` with its typed input and OpenCode session ID.
22
- 5. OpenChamber resolves the session directory and delegates the fixed action allowlist to the shared control service.
23
-
24
- The callback token stays in the managed child environment. It is never persisted, logged, returned to the UI, or written into the generated plugin.
25
-
26
- Ordinary Web does not install a plugin, publish callback registration, or advertise managed OpenChamber tools to the shared local OpenCode service. This keeps shared Web limited to sessions and live events. A valid `OPENCODE_HOST` is also outside managed-tool ownership, so OpenChamber does not guarantee tools on that endpoint.
27
-
28
- VS Code keeps its separate lifecycle. Hosted and Capacitor clients do not run tools themselves. They may connect to a Desktop-owned backend that has managed tools, but an ordinary Web backend does not add them.
29
-
30
- ## Input contract
31
-
32
- The plugin accepts action inputs inside `parameters` or beside `action`. An explicit `parameters` object wins when both forms provide the same field.
33
-
34
- Each action definition has a short presentation title and a separate agent-facing description. The generated schema uses descriptions only for required inputs, defaults, or behavior that a field name cannot explain.
35
-
36
- The schema uses one shared parameter object with `oneOf` and no `enum`. Some OpenAI-compatible gateways reject a node that combines both and return an empty completion.
37
-
38
- Session dispatch does not wait by default. Optional switches such as `worktree`, `goal`, `agent`, `variant`, and `wait` state their defaults and tell the agent not to invent them.
5
+ This module exposes OpenChamber to agents as typed OpenCode custom tools. There
6
+ are two, because controlling sessions and driving a page are separate intents
7
+ the user can want independently:
8
+
9
+ - `openchamber` — projects, sessions, worktrees, scheduled tasks, and showing
10
+ a file to the user (`file.open`). Enabled while the persisted
11
+ `agentControlToolEnabled` setting is not `false`.
12
+ - `openchamber_web` — looking at and interacting with the page in OpenChamber's
13
+ browser panel. Enabled while `agentWebToolEnabled` is not `false`.
14
+ - `openchamber_notify` — `notify.send`, a notification to the user through
15
+ `lib/notifications/emit-route.js` (same limits and rate window as
16
+ `POST /api/notifications/emit`). Off by default: enabled only while
17
+ `agentNotifyToolEnabled` is `true`, and the control service refuses the
18
+ action when the setting is off, so a stale plugin cannot keep paging.
19
+
20
+ Both default to on, are toggled in Settings → General → OpenCode CLI, and take
21
+ effect in the running OpenCode within a couple of seconds — OpenChamber rewrites
22
+ the managed config file OpenCode watches (see
23
+ `lib/opencode/managed-config-file.js`). Installs where the user's own
24
+ environment sets `OPENCODE_CONFIG` fall back to `OPENCODE_CONFIG_CONTENT` and
25
+ still need a restart for a toggle. Each tool carries only its own actions and
26
+ only the parameters those actions use, so turning one off removes its inputs
27
+ from the schema rather than leaving them visible. The plugin is injected only
28
+ when OpenChamber launches and owns the OpenCode process, and not at all when
29
+ both settings are `false`.
30
+
31
+ - The plugin accepts the action's inputs either inside `parameters` or beside
32
+ `action`, because models produce both shapes; an explicit `parameters` object
33
+ wins on a conflict. Rejecting the flattened shape turned a call that plainly
34
+ carried a `url` into "url is required", which reads as a broken tool rather
35
+ than a malformed call.
36
+
37
+ ## Runtime flow
38
+
39
+ 1. The OpenChamber HTTP listener binds and publishes its authoritative port.
40
+ 2. `materializePlugin()` writes the plugin under
41
+ `<openchamber-data-dir>/agent-tool/` and returns its directory; the managed
42
+ config layer lists that directory in `<data-dir>/opencode.managed.json` and
43
+ rewrites the file whenever a tool setting changes.
44
+ 3. `createChildEnv()` adds a random per-child token and the callback URL to the
45
+ managed OpenCode child environment. They are present even while every tool
46
+ is off, so a tool switched on later can call back without a restart. The URL
47
+ points at loopback, except when the listener is bound to one concrete
48
+ address (`--host <ip>`): that socket does not answer on loopback, so the URL
49
+ uses the bound address instead.
50
+ 4. The plugin calls `POST /api/openchamber/agent-tool` with its typed input and
51
+ the session id OpenCode gives the tool; OpenChamber resolves the session's
52
+ directory on its own side.
53
+ 5. The route delegates the fixed action allowlist directly to the shared
54
+ OpenChamber control service. The CLI uses the same service through its
55
+ authenticated HTTP adapter, so Goal Mode ordering, wait behavior,
56
+ partial-failure reporting, and scheduled-task contracts have one owner.
57
+ 6. Each action definition owns a short presentation title and a separate
58
+ agent-facing description. The generated schema uses the description to state
59
+ required inputs or one non-obvious behavior, while completed calls use the
60
+ short title in native tool metadata.
61
+
62
+ ## Agent context budget
63
+
64
+ - The tool exposes one shared parameter object rather than repeating parameters
65
+ in a large per-action union. Action descriptions carry only required inputs,
66
+ defaults, or one non-obvious semantic detail.
67
+ - The action schema carries `oneOf` and no `enum`. A node combining `enum` and
68
+ `oneOf` is valid JSON Schema, but some OpenAI-compatible gateways reject it
69
+ and answer with an empty completion instead of an error.
70
+ - Obvious fields rely on their names and JSON types. Parameter descriptions are
71
+ reserved for formats, dependencies, scope, and behavior that cannot be safely
72
+ inferred from the field name.
73
+ - Session dispatches do not wait by default. Agents are told to set `wait` only
74
+ when the user asks or the next step requires the completed result.
75
+ - The tool exposes only agent-relevant actions
76
+ (`OPENCHAMBER_AGENT_TOOL_ACTIONS`): `schedule.status` stays CLI-only because
77
+ `schedule.list` already returns scheduler status, and enable/disable are one
78
+ `schedule.toggle` action driven by the `disabled` boolean.
79
+ - The tool description frames intent: created sessions and scheduled tasks are
80
+ user-facing work the user follows up with. The agent must not decide on its
81
+ own to delegate parts of its current task, but an explicit user request to
82
+ create, send, or schedule always wins, even when it relates to the current
83
+ task (strict models otherwise read the old unconditional "never delegate" as
84
+ a hard ban and refused user-requested sends).
85
+ - Optional behavior switches (`worktree`, `goal`, `agent`, `variant`, `wait`)
86
+ state their default and an explicit "only when the user asks" rule so agents
87
+ do not invent worktrees, goal mode, or waits the user never requested.
88
+ - Detailed combination rules are enforced by the shared control service and
89
+ returned as actionable usage errors only after an invalid call. Per-action
90
+ examples and a repeated per-action parameter schema are intentionally omitted.
39
91
 
40
92
  ## Security invariants
41
93
 
42
- - The callback accepts same-machine requests only and requires the current managed-child bearer token with a timing-safe comparison.
43
- - A concrete listener address may accept a source equal to that address. A wildcard listener remains loopback-only.
44
- - The plugin adds its callback host to `NO_PROXY` and `no_proxy` in the managed child so an environment proxy cannot receive the callback token.
45
- - Inputs map to fixed tool, action, and parameter allowlists. There is no arbitrary CLI, shell, route, or URL forwarding.
46
- - Project-path registration and session or worktree deletion are not exposed.
47
- - A dropped callback request aborts its action.
48
- - OpenCode 2 does not give plugin tools an abort signal. The server therefore tracks actions by session and aborts them when the event stream reports an aborted `session.idle` transition.
94
+ - The callback accepts same-machine requests only and requires the current
95
+ per-child bearer token using a timing-safe comparison. Same-machine means a
96
+ loopback source, or, for a listener bound to one concrete address, a source
97
+ equal to that address: the OS sources a local connection to `<ip>` from
98
+ `<ip>`. A wildcard bind keeps the loopback-only rule, and another machine on
99
+ the network always arrives with its own address.
100
+ - The token is never persisted, logged, returned to the UI, or written into
101
+ the materialized plugin.
102
+ - The plugin adds the callback host to `NO_PROXY`/`no_proxy` inside the managed
103
+ child when it loads. Without that, an `HTTP_PROXY` in the child's environment
104
+ would receive a non-loopback callback, token included, because `fetch` has no
105
+ per-request way to skip the environment proxy.
106
+ - Inputs map to a fixed action and parameter allowlist. There is no arbitrary
107
+ CLI, shell, route, or URL forwarding.
108
+ - Session/worktree deletion and project-path registration are not exposed.
109
+ - A cancelled turn aborts the session's in-flight actions. OpenCode 2 gives a
110
+ plugin tool no abort signal and the plugin's request stays open, so the
111
+ server tracks in-flight actions per session and `abortSession(sessionID)` is
112
+ called from the event dispatch in `server/index.js` when the stream reports
113
+ `session.idle` with `aborted: true`; the abort signal then reaches the
114
+ shared service as before. A dropped HTTP request still aborts as well.
49
115
 
50
116
  ## Result contract
51
117
 
52
- Completed calls return JSON:
118
+ Every completed call returns JSON:
53
119
 
54
120
  ```json
55
121
  {
@@ -60,10 +126,29 @@ Completed calls return JSON:
60
126
  }
61
127
  ```
62
128
 
63
- Command and operational failures use the same envelope with `ok: false` and an `error` object. OpenCode-level cancellation can still produce a native tool error.
129
+ Command and operational failures use the same envelope with `ok: false` and
130
+ an `error` object. OpenCode-level cancellation can still produce a native tool
131
+ error state.
132
+
133
+ ## Runtime parity
134
+
135
+ - Web and Desktop managed OpenCode: injected automatically.
136
+ - External OpenCode selected with `OPENCODE_HOST` or skip-start: not injected,
137
+ because OpenChamber does not control that process environment.
138
+ - VS Code: not injected; the extension owns a separate OpenCode lifecycle.
139
+ - Hosted and Capacitor mobile clients use the server's managed OpenCode tool
140
+ when connected to such a server; no tool runs in the client runtime.
64
141
 
65
- ## Action resolution
142
+ ## The calling tool is part of the request
66
143
 
67
- Each generated tool sends its own name with every callback. Action resolution stays within that tool's allowlist. For example, a bare `open` requested by a memory tool cannot drive the browser.
144
+ Each generated tool sends its own name with every callback. Models routinely
145
+ drop the namespace their tool's name appears to supply — `openchamber_memory`
146
+ asked for `memory.read` gets called as `read` — and resolving the bare name
147
+ inside the calling tool's action set makes that unambiguous even where it is not
148
+ globally (`delete` belongs to both schedule and memory).
68
149
 
69
- When an action name is invalid, the response lists the actions available to the calling tool. This lets the model correct the call without guessing across tool namespaces.
150
+ Resolution never reaches outside the tool that asked: `open` from the memory
151
+ tool fails rather than driving the browser. An unresolvable action answers with
152
+ the actions that tool actually has, because an error that only says
153
+ "unsupported" leaves the model to guess a second wrong name — which is exactly
154
+ what happened before this existed.
@@ -3,6 +3,8 @@ import {
3
3
  OPENCHAMBER_AGENT_TOOL_ACTIONS,
4
4
  OPENCHAMBER_MEMORY_ACTION_DEFINITIONS,
5
5
  OPENCHAMBER_MEMORY_ACTIONS,
6
+ OPENCHAMBER_NOTIFY_ACTION_DEFINITIONS,
7
+ OPENCHAMBER_NOTIFY_ACTIONS,
6
8
  resolveAgentToolAction,
7
9
  OPENCHAMBER_WEB_ACTION_DEFINITIONS,
8
10
  OPENCHAMBER_WEB_ACTIONS,
@@ -12,12 +14,18 @@ const TOOL_SCHEMA_VERSION = 1;
12
14
  const PLUGIN_ID = 'openchamber-agent-tool';
13
15
  // Everything either managed tool may ask for; the agent allowlist stays
14
16
  // narrower than the full control surface.
15
- const ACTIONS = new Set([...OPENCHAMBER_AGENT_TOOL_ACTIONS, ...OPENCHAMBER_WEB_ACTIONS, ...OPENCHAMBER_MEMORY_ACTIONS]);
17
+ const ACTIONS = new Set([
18
+ ...OPENCHAMBER_AGENT_TOOL_ACTIONS,
19
+ ...OPENCHAMBER_WEB_ACTIONS,
20
+ ...OPENCHAMBER_MEMORY_ACTIONS,
21
+ ...OPENCHAMBER_NOTIFY_ACTIONS,
22
+ ]);
16
23
  const AGENT_TOOL_ACTION_TITLES = Object.fromEntries(
17
24
  [
18
25
  ...OPENCHAMBER_AGENT_TOOL_ACTION_DEFINITIONS,
19
26
  ...OPENCHAMBER_WEB_ACTION_DEFINITIONS,
20
27
  ...OPENCHAMBER_MEMORY_ACTION_DEFINITIONS,
28
+ ...OPENCHAMBER_NOTIFY_ACTION_DEFINITIONS,
21
29
  ].map(({ action, title }) => [action, title]),
22
30
  );
23
31
 
@@ -28,7 +36,7 @@ const AGENT_TOOL_ACTION_TITLES = Object.fromEntries(
28
36
  * in the other tool's schema, which is both misleading and paid for in context
29
37
  * on every call.
30
38
  */
31
- const WEB_PARAMETER_NAMES = ['url', 'selector', 'text', 'value', 'submit', 'direction', 'viewport', 'label'];
39
+ const WEB_PARAMETER_NAMES = ['url', 'selector', 'text', 'value', 'submit', 'direction', 'viewport', 'label', 'tabId'];
32
40
  // `title` is shared with the control tool, so it is not listed here — only the
33
41
  // names memory alone introduces are kept out of the other schemas.
34
42
  const MEMORY_ONLY_PARAMETER_NAMES = ['body', 'scope', 'memoryId', 'type'];
@@ -87,6 +95,7 @@ const ALL_PARAMETER_PROPERTIES = {
87
95
  direction: { type: 'string', enum: ['up', 'down', 'top', 'bottom'], description: 'Scroll direction for browser.scroll' },
88
96
  viewport: { type: 'string', enum: ['mobile', 'tablet', 'desktop', 'fill'], description: 'Page layout size; snapshots report which one is in effect' },
89
97
  label: { type: 'string', description: 'Short name for a browser.capture image, such as before-fix' },
98
+ tabId: { type: 'string', description: 'Browser tab to act on, an id from the tabs a browser.snapshot lists. Omit to use the tab the user is looking at' },
90
99
  body: { type: 'string', description: 'Full text of the memory; state it so it still makes sense in a session that has none of this conversation' },
91
100
  scope: { type: 'string', enum: ['global', 'project', 'both'], description: 'global is about the user and applies everywhere; project is about this codebase. both is only valid for memory.list' },
92
101
  memoryId: { type: 'string', description: 'Memory ID from a memory.list or memory.read result' },
@@ -108,12 +117,22 @@ const MEMORY_PARAMETER_PROPERTIES = {
108
117
  ...MEMORY_PARAMETER_OVERRIDES,
109
118
  };
110
119
 
120
+ // Its own names, not the shared map: `title` and `body` mean something else in
121
+ // the control and memory tools.
122
+ const NOTIFY_PARAMETER_PROPERTIES = {
123
+ title: { type: 'string', description: 'Short headline the user reads first, up to 120 characters' },
124
+ body: { type: 'string', description: 'One or two sentences of detail, up to 500 characters' },
125
+ showWhenFocused: { type: 'boolean', description: 'Show it even while the user is looking at OpenChamber. Only for something that cannot wait' },
126
+ };
127
+
111
128
  const CONTROL_TOOL_DESCRIPTION = "Control OpenChamber projects, sessions, and scheduled tasks on the user's behalf. Sessions and scheduled tasks you create are for the user to follow and interact with. Do not decide on your own to hand parts of your current task to another session; when the user asks you to create a session, send a prompt to one, or schedule a task, do it, including when the work relates to your current task. Use one action per call. Scope with projectId or directory; omit both to use the current session directory. Session dispatches return immediately by default and you receive no notification when a dispatched session finishes, so never promise to report back on it; the user follows it in OpenChamber; a dispatched session needs no follow-up from you. If the user later asks how it went, use session.messages (add wait to block until it is idle, lastAssistant for just the final answer) — session.send always sends a NEW prompt and never just waits. Set wait only when the user asks or the next step requires the completed result. Session and worktree deletion are unavailable.";
112
129
 
113
130
  const WEB_TOOL_DESCRIPTION = "Look at and interact with a web page in OpenChamber's browser panel, so you can check your own work rather than describing what you expect. Use one action per call. Open a page, snapshot it to read its text and its interactive elements, then click, type or scroll using the selectors the snapshot returned; snapshots also report any errors the page logged. Pass a selector to browser.snapshot to read one part of a long page. browser.inspect returns computed styles when the question is how something renders. Set viewport to check a layout at mobile, tablet or desktop size. The page runs with the user's real logins, so treat what you see as their live session.";
114
131
 
115
132
  const MEMORY_TOOL_DESCRIPTION = "Keep what you learn across sessions, so the user does not have to explain the same thing twice. Use one action per call. The session already lists the titles of what is stored. A title is an abbreviation, not the memory: read the entry with memory.read once before acting on it (it then stays in your context; do not re-read it on later turns), because titles leave out the conditions and exceptions that decide how the memory applies, and the ones that look self-explanatory hide them most often. Save something only when it will still be true in a later session — a stable preference, a project convention, a decision and its reason, or a hard-won pointer. Do not save one-off task state, anything you can read from the code, secrets or credentials, or anything the user asked you not to keep; when the user explicitly asks you to remember something, save it, unless it is a secret or credential. Choose the scope deliberately: global is about the user and reaches every project, so put a project's conventions in project scope. Save in the moment, without asking first, when the user corrects how you work or states a preference, confirms that a non-obvious approach worked, or when you learn a project fact that took real effort to find. One fact per entry. The user can review and remove what you save, so save when it fits and mention it briefly.";
116
133
 
134
+ const NOTIFY_TOOL_DESCRIPTION = "Send the user a notification through OpenChamber, so they learn about something without watching the session. Use it when you finish work that took long enough for the user to step away, when you are blocked on something only the user can resolve, or when the user asked to be told about something. Do not use it for routine progress, for every finished step, or to repeat what your reply already says to a user who is present. Keep the title short and put detail in the body.";
135
+
117
136
  const asNonEmptyString = (value) => {
118
137
  if (typeof value !== 'string') return null;
119
138
  const trimmed = value.trim();
@@ -245,12 +264,7 @@ const createToolEntry = ({ name, description, definitions, parameters }) => Stri
245
264
  })
246
265
  `;
247
266
 
248
-
249
- export const createPluginSource = ({
250
- includeControl,
251
- includeWeb,
252
- includeMemory,
253
- }) => {
267
+ export const createPluginSource = ({ includeControl, includeWeb, includeMemory, includeNotify }) => {
254
268
  const entries = [];
255
269
  if (includeControl) {
256
270
  entries.push(createToolEntry({
@@ -276,6 +290,14 @@ export const createPluginSource = ({
276
290
  parameters: MEMORY_PARAMETER_PROPERTIES,
277
291
  }));
278
292
  }
293
+ if (includeNotify) {
294
+ entries.push(createToolEntry({
295
+ name: 'openchamber_notify',
296
+ description: NOTIFY_TOOL_DESCRIPTION,
297
+ definitions: OPENCHAMBER_NOTIFY_ACTION_DEFINITIONS,
298
+ parameters: NOTIFY_PARAMETER_PROPERTIES,
299
+ }));
300
+ }
279
301
 
280
302
  // The callback carries a bearer token over plain HTTP. With a proxy in the
281
303
  // OpenCode environment, fetch could hand a non-loopback callback and token to
@@ -337,17 +359,13 @@ export const createAgentToolRuntime = (dependencies) => {
337
359
  * change while it runs, so the source on disk always matches the settings —
338
360
  * the running OpenCode reloads the directory it already has configured.
339
361
  */
340
- const materializePlugin = async ({ includeControl = true, includeWeb = true, includeMemory = true } = {}) => {
341
- if (!includeControl && !includeWeb && !includeMemory) {
362
+ const materializePlugin = async ({ includeControl = true, includeWeb = true, includeMemory = true, includeNotify = false } = {}) => {
363
+ if (!includeControl && !includeWeb && !includeMemory && !includeNotify) {
342
364
  throw new Error('At least one OpenChamber managed tool must be enabled to inject the plugin');
343
365
  }
344
366
  await fsPromises.mkdir(pluginDirectory, { recursive: true });
345
367
  await fsPromises.writeFile(pluginManifestPath, AGENT_TOOL_PLUGIN_PACKAGE_JSON, { mode: 0o600 });
346
- await fsPromises.writeFile(pluginPath, createPluginSource({
347
- includeControl,
348
- includeWeb,
349
- includeMemory,
350
- }), { mode: 0o600 });
368
+ await fsPromises.writeFile(pluginPath, createPluginSource({ includeControl, includeWeb, includeMemory, includeNotify }), { mode: 0o600 });
351
369
  return pluginDirectory;
352
370
  };
353
371
 
@@ -503,6 +521,7 @@ export const createAgentToolRuntime = (dependencies) => {
503
521
  pluginDirectory,
504
522
  materializePlugin,
505
523
  createChildEnv,
524
+ authorizeRequest: authorizeManagedChild,
506
525
  registerRoutes,
507
526
  execute,
508
527
  abortSession,
@@ -249,6 +249,21 @@ describe('managed agent tool runtime', () => {
249
249
  expect(Object.keys(tool)).toEqual(['openchamber_memory']);
250
250
  });
251
251
 
252
+ it('exposes notify as its own tool only when switched on', async () => {
253
+ const { runtime, dataDir } = await createRuntime();
254
+ await prepareManagedEnv(runtime, { includeControl: true, includeWeb: false, includeMemory: false, includeNotify: true });
255
+ const tool = await loadTools(dataDir, 'notify');
256
+
257
+ expect(Object.keys(tool)).toEqual(['openchamber', 'openchamber_notify']);
258
+ expect(Object.keys(tool.openchamber_notify.input.properties.parameters.properties).sort())
259
+ .toEqual(['body', 'showWhenFocused', 'title']);
260
+ expect(Object.keys(tool.openchamber.input.properties.parameters.properties)).not.toContain('showWhenFocused');
261
+
262
+ const { runtime: plain, dataDir: plainDir } = await createRuntime();
263
+ await prepareManagedEnv(plain, { includeControl: true, includeWeb: false, includeMemory: false });
264
+ expect(Object.keys(await loadTools(plainDir, 'nonotify'))).toEqual(['openchamber']);
265
+ });
266
+
252
267
  it('refuses to inject a plugin with no tools in it', async () => {
253
268
  const { runtime } = await createRuntime();
254
269
  let failed = false;
@@ -46,8 +46,20 @@ itself; it can only ask and wait.
46
46
  `browser.*` actions of the `openchamber_web` tool onto the router's
47
47
  `request()` (same signature as the broker) and owns their parameter
48
48
  validation.
49
- - The client half is `packages/ui/src/lib/browser/controlClient.ts`, which
50
- registers the mounted browser pane as the one responder.
49
+ - The client half is `packages/ui/src/lib/browser/controlClient.ts`. Every
50
+ mounted browser tab registers its pane under its context-panel tab id. An
51
+ action with `tabId` runs in that tab; without one it runs in the browser tab
52
+ the user last had in front of them (`setShownBrowserTab`, set by
53
+ `ContextPanel`), never in whichever pane registered last, and never switches
54
+ the user to the tab it acts in. `browser.open` without `tabId` never
55
+ navigates an existing tab: the registered opener (`ContextPanel`,
56
+ `useUIStore.openAgentBrowserTab`) makes a new background tab and the answer
57
+ carries its `tabId`. `browser.snapshot` answers carry `tabs`
58
+ (`id`, `title`, `url`, `active`). A client without the named tab waits
59
+ briefly, so the client that has it claims first, then claims and answers
60
+ "no such tab". `tabId` is validated and passed through by
61
+ `../openchamber-control/service.js` for every action, so an extension
62
+ provider receives it untouched (`BrowserTabTarget` in `@openchamber/sdk`).
51
63
 
52
64
  ## Invariants
53
65