@openchambery/web 2.0.0-beta.25 → 2.0.0-beta.28

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 (175) hide show
  1. package/dist/assets/{AgentAvatar-FyTdEOMr.js → AgentAvatar-CqD6Pfom.js} +1 -1
  2. package/dist/assets/{AssistantView-CUqIFFyH.js → AssistantView-BW9f9vF2.js} +3 -3
  3. package/dist/assets/{DiagramView-DhEXKsIH.js → DiagramView-BP3WDmFe.js} +1 -1
  4. package/dist/assets/{FadeInOnReveal-DHDffzHs.js → FadeInOnReveal-ezW_e-yz.js} +1 -1
  5. package/dist/assets/{Icon-D7NdMmbi.js → Icon-ChOAx1jX.js} +1 -1
  6. package/dist/assets/{MarkdownRenderer-CyacU8IE.js → MarkdownRenderer-BBHZa1Ts.js} +2 -2
  7. package/dist/assets/{MarkdownRendererImpl-DgO-73sa.js → MarkdownRendererImpl-DfxEsttZ.js} +2 -2
  8. package/dist/assets/{MarkstreamRendererImpl-CZPmsOrD.js → MarkstreamRendererImpl-6k-n071l.js} +1 -1
  9. package/dist/assets/{MobileDetailNavigation-BGKiZT4X.js → MobileDetailNavigation-s48GkQY9.js} +1 -1
  10. package/dist/assets/{MobileShareBridge-BFkZ-if6.js → MobileShareBridge-DAgShkFE.js} +2 -2
  11. package/dist/assets/MobileSurface-DZug2f4O.js +2 -0
  12. package/dist/assets/{MultiRunWindow-BZLr1op3.js → MultiRunWindow-CIhHw9mo.js} +1 -1
  13. package/dist/assets/{OnboardingScreen-DMbPMwlp.js → OnboardingScreen-ozxwbzDw.js} +1 -1
  14. package/dist/assets/{OverlayScrollbar-mI9WVa7E.js → OverlayScrollbar-BL-QnnrN.js} +1 -1
  15. package/dist/assets/SettingsView-DmUdWSji.js +25 -0
  16. package/dist/assets/{SettingsWindow-BMISHpTN.js → SettingsWindow-CuIZwGhM.js} +1 -1
  17. package/dist/assets/{TerminalView-DKCcoHzi.js → TerminalView-BIJ-ILgl.js} +1 -1
  18. package/dist/assets/{ToolOutputDialog-ByptQgFf.js → ToolOutputDialog-B6KIxVUo.js} +1 -1
  19. package/dist/assets/{appThemeRegistry-b59_2rLn.js → appThemeRegistry-BuMGUGUK.js} +1 -1
  20. package/dist/assets/{button-e8fYWIMM.js → button-DoDvdeG-.js} +1 -1
  21. package/dist/assets/{collapsible-B6wLXHi3.js → collapsible-CZkyFVK0.js} +1 -1
  22. package/dist/assets/{context-menu-Bq7W1d7y.js → context-menu-DTjON-Ta.js} +1 -1
  23. package/dist/assets/{desktopHosts-C8vf9vRY.js → desktopHosts-CP1YHQ4Z.js} +1 -1
  24. package/dist/assets/{desktopRelayRestore-DDJtljeu.js → desktopRelayRestore-BeOGO7oA.js} +1 -1
  25. package/dist/assets/{diagram-BBL-LfsC.js → diagram-DUi801df.js} +1 -1
  26. package/dist/assets/{dialog-MRsJpHzu.js → dialog-DGzGqOhG.js} +1 -1
  27. package/dist/assets/{es-DvPwAj0y.js → es-DCjl24dV.js} +3 -3
  28. package/dist/assets/fileMentionTouchSelection-C3A6XKRM.js +2 -0
  29. package/dist/assets/{fileQueries-CBu-ytXS.js → fileQueries-CAH8-p27.js} +1 -1
  30. package/dist/assets/{fr-XQsoaNKY.js → fr-DfJ5j0t5.js} +3 -3
  31. package/dist/assets/{gitApi-CYFnq-nn.js → gitApi-B1Nyw0PD.js} +3 -3
  32. package/dist/assets/{input-DbOe1mdT.js → input-Ct2uaiDc.js} +1 -1
  33. package/dist/assets/insertionBoundaries-BypTPUfp.js +1 -0
  34. package/dist/assets/{ja-DslzO96s.js → ja-B_9_w2Xf.js} +3 -3
  35. package/dist/assets/{ko-C_9XOAL0.js → ko-DlGbo3kV.js} +3 -3
  36. package/dist/assets/main-BREqmQUm.js +169 -0
  37. package/dist/assets/{main-DOpUCN5f.js → main-BodZWzei.js} +2 -2
  38. package/dist/assets/main-qSeeJRW1.css +1 -0
  39. package/dist/assets/{markdownHeightCache-B5VSLl79.js → markdownHeightCache-CMoWScss.js} +1 -1
  40. package/dist/assets/{mcpQueries-Bxq_6P-n.js → mcpQueries-nScpWEqu.js} +1 -1
  41. package/dist/assets/{miniChat-CBpEIoJn.js → miniChat-Ca-kdWf0.js} +2 -2
  42. package/dist/assets/{mobile-CiFeGY2g.js → mobile-BWi6F8Lj.js} +2 -2
  43. package/dist/assets/multirun-BdSrR6jN.js +3 -0
  44. package/dist/assets/persistence-B3_CPgm1.js +3 -0
  45. package/dist/assets/{pl-Dst9Wk53.js → pl-DjUsS8Ei.js} +3 -3
  46. package/dist/assets/platform-Bxdc4SbV.js +6 -0
  47. package/dist/assets/projectMeta-CIKLMweH.js +1 -0
  48. package/dist/assets/{pt-BR-Dq394BPb.js → pt-BR-B8sV7qo1.js} +3 -3
  49. package/dist/assets/{renderElectronMiniChatApp-DePrSFtn.js → renderElectronMiniChatApp-DtYCIW4n.js} +2 -2
  50. package/dist/assets/renderMobileApp-D47vojsP.js +28 -0
  51. package/dist/assets/{runtimeConfig-CgFmzQWA.js → runtimeConfig-u9DrZmkS.js} +1 -1
  52. package/dist/assets/runtimeEndpointReset-Bn4vJ-gl.js +148 -0
  53. package/dist/assets/runtimeEndpointReset-D1_Y5I4F.css +1 -0
  54. package/dist/assets/{sessionChangeSummary-Bg9UCEjA.js → sessionChangeSummary-C0OxmHed.js} +2 -2
  55. package/dist/assets/{sessionLookup-4MZgvAtH.js → sessionLookup-D1NHRgWf.js} +1 -1
  56. package/dist/assets/{sortable-tabs-strip-B0sZ7mm6.js → sortable-tabs-strip-Cp5i5hI-.js} +1 -1
  57. package/dist/assets/{store-D8fmTWK3.js → store-BTqPn8Uq.js} +3 -3
  58. package/dist/assets/{terminalApi-CJyWCuc8.js → terminalApi-CqyW3_W3.js} +1 -1
  59. package/dist/assets/{textarea-B5GMntHk.js → textarea-jEbc6gqf.js} +1 -1
  60. package/dist/assets/{tooltip-C64SdRvR.js → tooltip-DKYT7xYe.js} +1 -1
  61. package/dist/assets/{uk-8XAR75Qh.js → uk-Bh7HXyLp.js} +3 -3
  62. package/dist/assets/{url-DF1NFwt_.js → url-hhmiocZY.js} +1 -1
  63. package/dist/assets/{useAppFontEffects-Ykf_fh2W.js → useAppFontEffects-BBX1KjLV.js} +53 -53
  64. package/dist/assets/{useDesktopWindowControlsLayout-kH_CrZfG.js → useDesktopWindowControlsLayout-uKYzUX-m.js} +1 -1
  65. package/dist/assets/{useEffectiveDirectory-CpsCUPDZ.js → useEffectiveDirectory-BOolveMp.js} +1 -1
  66. package/dist/assets/{useFontPreferences-CrteQiAp.js → useFontPreferences-BMJQP6t6.js} +1 -1
  67. package/dist/assets/useMobileNavigationStore-CC8QT7VC.js +415 -0
  68. package/dist/assets/useSessionAutoCleanup-CydMAlLf.js +2 -0
  69. package/dist/assets/{useWorkerHighlightedLines-N119bHfO.js → useWorkerHighlightedLines-BF1ClmMe.js} +1 -1
  70. package/dist/assets/vendor-opencode-client-MF2GBrFx.js +12 -0
  71. package/dist/assets/{zh-CN-B-ILfrqC.js → zh-CN-Uzia0gk4.js} +3 -3
  72. package/dist/assets/{zh-TW-5HeTVP4z.js → zh-TW-CiI7DLdB.js} +3 -3
  73. package/dist/index.html +8 -8
  74. package/dist/mini-chat.html +8 -8
  75. package/dist/mobile.html +8 -8
  76. package/package.json +3 -3
  77. package/server/index.js +2 -20
  78. package/server/lib/git/DOCUMENTATION.md +1 -0
  79. package/server/lib/git/routes.js +7 -4
  80. package/server/lib/git/routes.test.js +34 -0
  81. package/server/lib/git/service.js +3 -1
  82. package/server/lib/llm/DOCUMENTATION.md +6 -4
  83. package/server/lib/llm/generate.js +8 -9
  84. package/server/lib/llm/generate.test.js +33 -6
  85. package/server/lib/opencode/DOCUMENTATION.md +24 -20
  86. package/server/lib/opencode/agents.js +13 -22
  87. package/server/lib/opencode/auth-state-runtime.js +3 -3
  88. package/server/lib/opencode/auth-state-runtime.test.js +59 -0
  89. package/server/lib/opencode/auth.d.ts +13 -0
  90. package/server/lib/opencode/auth.js +74 -64
  91. package/server/lib/opencode/auth.test.js +57 -0
  92. package/server/lib/opencode/commands.js +1 -1
  93. package/server/lib/opencode/config-entities-layering.test.js +72 -0
  94. package/server/lib/opencode/config-entity-routes.js +9 -122
  95. package/server/lib/opencode/config-entity-routes.test.js +7 -11
  96. package/server/lib/opencode/config-files.d.ts +17 -0
  97. package/server/lib/opencode/config-files.js +187 -0
  98. package/server/lib/opencode/config-files.test.js +201 -0
  99. package/server/lib/opencode/core-routes.js +0 -1
  100. package/server/lib/opencode/core-routes.test.js +0 -15
  101. package/server/lib/opencode/credential-boundary.d.ts +1 -0
  102. package/server/lib/opencode/credential-boundary.js +11 -0
  103. package/server/lib/opencode/ensure-cli.js +4 -1
  104. package/server/lib/opencode/env-config.js +4 -1
  105. package/server/lib/opencode/env-config.test.js +28 -0
  106. package/server/lib/opencode/env-runtime.test.js +2 -1
  107. package/server/lib/opencode/feature-routes-runtime.js +9 -7
  108. package/server/lib/opencode/hmr-state-runtime.js +4 -4
  109. package/server/lib/opencode/lifecycle.js +1 -49
  110. package/server/lib/opencode/lifecycle.test.js +30 -4
  111. package/server/lib/opencode/location-release.d.ts +8 -0
  112. package/server/lib/opencode/location-release.js +32 -0
  113. package/server/lib/opencode/location-release.test.js +43 -0
  114. package/server/lib/opencode/mcp.js +1 -1
  115. package/server/lib/opencode/opencode2-pin.js +2 -1
  116. package/server/lib/opencode/owned-runtime-upgrade.test.js +5 -5
  117. package/server/lib/opencode/plugins.js +12 -60
  118. package/server/lib/opencode/providers.js +6 -3
  119. package/server/lib/opencode/proxy.js +7 -0
  120. package/server/lib/opencode/proxy.test.js +13 -0
  121. package/server/lib/opencode/routes.js +5 -5
  122. package/server/lib/opencode/routes.test.js +18 -18
  123. package/server/lib/opencode/runtime-contract.js +3 -2
  124. package/server/lib/opencode/runtime-contract.test.js +12 -12
  125. package/server/lib/opencode/shared.js +6 -175
  126. package/server/lib/opencode/upgrade-screen.js +4 -11
  127. package/server/lib/opencode/upgrade-screen.test.js +25 -25
  128. package/server/lib/opencode/version-alignment.test.js +40 -0
  129. package/server/lib/quota/DOCUMENTATION.md +9 -0
  130. package/server/lib/quota/provider-credentials.test.js +29 -0
  131. package/server/lib/quota/providers/claude.js +4 -4
  132. package/server/lib/quota/providers/codex.js +4 -4
  133. package/server/lib/quota/providers/codex.test.js +1 -1
  134. package/server/lib/quota/providers/copilot.js +5 -5
  135. package/server/lib/quota/providers/google/auth.js +3 -3
  136. package/server/lib/quota/providers/google/index.js +2 -2
  137. package/server/lib/quota/providers/index.js +3 -7
  138. package/server/lib/quota/providers/index.test.js +2 -2
  139. package/server/lib/quota/providers/kimi.js +4 -4
  140. package/server/lib/quota/providers/minimax-shared.js +4 -4
  141. package/server/lib/quota/providers/nanogpt.js +4 -4
  142. package/server/lib/quota/providers/openai.js +4 -4
  143. package/server/lib/quota/providers/openrouter.js +4 -4
  144. package/server/lib/quota/providers/wafer.js +4 -4
  145. package/server/lib/quota/providers/zai.js +4 -4
  146. package/server/lib/quota/providers/zhipuai-coding-plan.js +6 -20
  147. package/server/lib/quota/routes.js +1 -1
  148. package/server/lib/scheduled-tasks/DOCUMENTATION.md +1 -0
  149. package/server/lib/scheduled-tasks/runtime.js +2 -3
  150. package/server/lib/session-index/DOCUMENTATION.md +7 -0
  151. package/server/lib/session-index/event-ingest.js +13 -0
  152. package/server/lib/session-index/event-ingest.test.js +7 -0
  153. package/server/lib/session-index/service.js +45 -0
  154. package/server/lib/session-index/service.test.js +47 -0
  155. package/server/lib/tts/DOCUMENTATION.md +7 -5
  156. package/server/lib/tts/credential-source.test.js +23 -0
  157. package/server/lib/tts/routes.js +2 -2
  158. package/server/lib/tts/service.js +16 -37
  159. package/dist/assets/MobileSurface-CiQ9yAML.js +0 -2
  160. package/dist/assets/SettingsView-Bo1Xj4mZ.js +0 -25
  161. package/dist/assets/fileMentionTouchSelection-CMG5yrcJ.js +0 -3
  162. package/dist/assets/insertionBoundaries-C5b2f_mO.js +0 -1
  163. package/dist/assets/main-Bz3MB9dk.js +0 -169
  164. package/dist/assets/main-D2BzpnUt.css +0 -1
  165. package/dist/assets/multirun-DnD1iWqj.js +0 -4
  166. package/dist/assets/persistence-CMWO3jFo.js +0 -3
  167. package/dist/assets/platform-B0p43squ.js +0 -6
  168. package/dist/assets/projectMeta-B1ShmM7W.js +0 -3
  169. package/dist/assets/renderMobileApp-CiGHeia2.js +0 -28
  170. package/dist/assets/runtimeEndpointReset-Dw46SzSQ.js +0 -149
  171. package/dist/assets/useMobileNavigationStore-CBo6pohX.js +0 -414
  172. package/dist/assets/useSessionAutoCleanup-BuFi27vR.js +0 -2
  173. package/dist/assets/vendor-opencode-client-C12gfjag.js +0 -12
  174. package/server/lib/opencode/snippets.js +0 -242
  175. package/server/lib/opencode/snippets.test.js +0 -97
package/dist/index.html CHANGED
@@ -552,7 +552,7 @@
552
552
  opacity: 0.72;
553
553
  }
554
554
  </style>
555
- <script type="module" crossorigin src="/assets/main-DOpUCN5f.js"></script>
555
+ <script type="module" crossorigin src="/assets/main-BodZWzei.js"></script>
556
556
  <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-C0FnF6B9.js">
557
557
  <link rel="modulepreload" crossorigin href="/assets/runtime-auth-D66yq6ie.js">
558
558
  <link rel="modulepreload" crossorigin href="/assets/runtime-switch-BJM2k_uH.js">
@@ -563,16 +563,16 @@
563
563
  <link rel="modulepreload" crossorigin href="/assets/vendor-react-DLPElpoQ.js">
564
564
  <link rel="modulepreload" crossorigin href="/assets/vendor-pathe-vBmvQE8f.js">
565
565
  <link rel="modulepreload" crossorigin href="/assets/vendor-tanstack-query-core-BWz0ose6.js">
566
- <link rel="modulepreload" crossorigin href="/assets/persistence-CMWO3jFo.js">
567
- <link rel="modulepreload" crossorigin href="/assets/desktopHosts-C8vf9vRY.js">
568
- <link rel="modulepreload" crossorigin href="/assets/desktopRelayRestore-DDJtljeu.js">
569
- <link rel="modulepreload" crossorigin href="/assets/vendor-opencode-client-C12gfjag.js">
566
+ <link rel="modulepreload" crossorigin href="/assets/persistence-B3_CPgm1.js">
567
+ <link rel="modulepreload" crossorigin href="/assets/desktopHosts-CP1YHQ4Z.js">
568
+ <link rel="modulepreload" crossorigin href="/assets/desktopRelayRestore-BeOGO7oA.js">
569
+ <link rel="modulepreload" crossorigin href="/assets/vendor-opencode-client-MF2GBrFx.js">
570
570
  <link rel="modulepreload" crossorigin href="/assets/vendor-aparajita-capacitor-secure-storage-CjQdwfjS.js">
571
- <link rel="modulepreload" crossorigin href="/assets/platform-B0p43squ.js">
572
- <link rel="modulepreload" crossorigin href="/assets/terminalApi-CJyWCuc8.js">
571
+ <link rel="modulepreload" crossorigin href="/assets/platform-Bxdc4SbV.js">
572
+ <link rel="modulepreload" crossorigin href="/assets/terminalApi-CqyW3_W3.js">
573
573
  <link rel="modulepreload" crossorigin href="/assets/types-BuiqMmh_.js">
574
574
  <link rel="modulepreload" crossorigin href="/assets/capgoAdapter-DCowZ2ab.js">
575
- <link rel="modulepreload" crossorigin href="/assets/runtimeConfig-CgFmzQWA.js">
575
+ <link rel="modulepreload" crossorigin href="/assets/runtimeConfig-u9DrZmkS.js">
576
576
  <link rel="stylesheet" crossorigin href="/assets/src-OzbnJsph.css">
577
577
  </head>
578
578
  <body class="h-full bg-background text-foreground">
@@ -74,7 +74,7 @@
74
74
  }
75
75
  </style>
76
76
 
77
- <script type="module" crossorigin src="/assets/miniChat-CBpEIoJn.js"></script>
77
+ <script type="module" crossorigin src="/assets/miniChat-Ca-kdWf0.js"></script>
78
78
  <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-C0FnF6B9.js">
79
79
  <link rel="modulepreload" crossorigin href="/assets/runtime-auth-D66yq6ie.js">
80
80
  <link rel="modulepreload" crossorigin href="/assets/runtime-switch-BJM2k_uH.js">
@@ -85,16 +85,16 @@
85
85
  <link rel="modulepreload" crossorigin href="/assets/vendor-react-DLPElpoQ.js">
86
86
  <link rel="modulepreload" crossorigin href="/assets/vendor-pathe-vBmvQE8f.js">
87
87
  <link rel="modulepreload" crossorigin href="/assets/vendor-tanstack-query-core-BWz0ose6.js">
88
- <link rel="modulepreload" crossorigin href="/assets/persistence-CMWO3jFo.js">
89
- <link rel="modulepreload" crossorigin href="/assets/desktopHosts-C8vf9vRY.js">
90
- <link rel="modulepreload" crossorigin href="/assets/desktopRelayRestore-DDJtljeu.js">
91
- <link rel="modulepreload" crossorigin href="/assets/vendor-opencode-client-C12gfjag.js">
88
+ <link rel="modulepreload" crossorigin href="/assets/persistence-B3_CPgm1.js">
89
+ <link rel="modulepreload" crossorigin href="/assets/desktopHosts-CP1YHQ4Z.js">
90
+ <link rel="modulepreload" crossorigin href="/assets/desktopRelayRestore-BeOGO7oA.js">
91
+ <link rel="modulepreload" crossorigin href="/assets/vendor-opencode-client-MF2GBrFx.js">
92
92
  <link rel="modulepreload" crossorigin href="/assets/vendor-aparajita-capacitor-secure-storage-CjQdwfjS.js">
93
- <link rel="modulepreload" crossorigin href="/assets/platform-B0p43squ.js">
94
- <link rel="modulepreload" crossorigin href="/assets/terminalApi-CJyWCuc8.js">
93
+ <link rel="modulepreload" crossorigin href="/assets/platform-Bxdc4SbV.js">
94
+ <link rel="modulepreload" crossorigin href="/assets/terminalApi-CqyW3_W3.js">
95
95
  <link rel="modulepreload" crossorigin href="/assets/types-BuiqMmh_.js">
96
96
  <link rel="modulepreload" crossorigin href="/assets/capgoAdapter-DCowZ2ab.js">
97
- <link rel="modulepreload" crossorigin href="/assets/runtimeConfig-CgFmzQWA.js">
97
+ <link rel="modulepreload" crossorigin href="/assets/runtimeConfig-u9DrZmkS.js">
98
98
  <link rel="stylesheet" crossorigin href="/assets/src-OzbnJsph.css">
99
99
  </head>
100
100
  <body class="h-full bg-background text-foreground">
package/dist/mobile.html CHANGED
@@ -63,7 +63,7 @@
63
63
  })();
64
64
  </script>
65
65
 
66
- <script type="module" crossorigin src="/assets/mobile-CiFeGY2g.js"></script>
66
+ <script type="module" crossorigin src="/assets/mobile-BWi6F8Lj.js"></script>
67
67
  <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-C0FnF6B9.js">
68
68
  <link rel="modulepreload" crossorigin href="/assets/runtime-auth-D66yq6ie.js">
69
69
  <link rel="modulepreload" crossorigin href="/assets/runtime-switch-BJM2k_uH.js">
@@ -74,16 +74,16 @@
74
74
  <link rel="modulepreload" crossorigin href="/assets/vendor-react-DLPElpoQ.js">
75
75
  <link rel="modulepreload" crossorigin href="/assets/vendor-pathe-vBmvQE8f.js">
76
76
  <link rel="modulepreload" crossorigin href="/assets/vendor-tanstack-query-core-BWz0ose6.js">
77
- <link rel="modulepreload" crossorigin href="/assets/persistence-CMWO3jFo.js">
78
- <link rel="modulepreload" crossorigin href="/assets/desktopHosts-C8vf9vRY.js">
79
- <link rel="modulepreload" crossorigin href="/assets/desktopRelayRestore-DDJtljeu.js">
80
- <link rel="modulepreload" crossorigin href="/assets/vendor-opencode-client-C12gfjag.js">
77
+ <link rel="modulepreload" crossorigin href="/assets/persistence-B3_CPgm1.js">
78
+ <link rel="modulepreload" crossorigin href="/assets/desktopHosts-CP1YHQ4Z.js">
79
+ <link rel="modulepreload" crossorigin href="/assets/desktopRelayRestore-BeOGO7oA.js">
80
+ <link rel="modulepreload" crossorigin href="/assets/vendor-opencode-client-MF2GBrFx.js">
81
81
  <link rel="modulepreload" crossorigin href="/assets/vendor-aparajita-capacitor-secure-storage-CjQdwfjS.js">
82
- <link rel="modulepreload" crossorigin href="/assets/platform-B0p43squ.js">
83
- <link rel="modulepreload" crossorigin href="/assets/terminalApi-CJyWCuc8.js">
82
+ <link rel="modulepreload" crossorigin href="/assets/platform-Bxdc4SbV.js">
83
+ <link rel="modulepreload" crossorigin href="/assets/terminalApi-CqyW3_W3.js">
84
84
  <link rel="modulepreload" crossorigin href="/assets/types-BuiqMmh_.js">
85
85
  <link rel="modulepreload" crossorigin href="/assets/capgoAdapter-DCowZ2ab.js">
86
- <link rel="modulepreload" crossorigin href="/assets/runtimeConfig-CgFmzQWA.js">
86
+ <link rel="modulepreload" crossorigin href="/assets/runtimeConfig-u9DrZmkS.js">
87
87
  <link rel="stylesheet" crossorigin href="/assets/src-OzbnJsph.css">
88
88
  </head>
89
89
  <body class="h-full bg-background text-foreground">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openchambery/web",
3
- "version": "2.0.0-beta.25",
3
+ "version": "2.0.0-beta.28",
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
  "@earendil-works/pi-agent-core": "0.84.4",
29
29
  "@octokit/rest": "^22.0.1",
30
- "@opencode/client": "2.0.15",
31
- "@opencode/schema": "2.0.15",
30
+ "@opencode/client": "2.0.23",
31
+ "@opencode/schema": "2.0.23",
32
32
  "@simplewebauthn/server": "13.3.1",
33
33
  "adm-zip": "^0.5.16",
34
34
  "better-sqlite3": "^12.10.0",
package/server/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import 'reflect-metadata';
2
2
  import express from 'express';
3
+ import { configureOpenCodeCredentials, openCodeCredentialSource } from './lib/opencode/auth.js';
3
4
  import compression from 'compression';
4
5
  import path from 'path';
5
6
  import { spawn, spawnSync } from 'child_process';
@@ -601,6 +602,7 @@ const waitForReady = (...args) => openCodeNetworkRuntime.waitForReady(...args);
601
602
  const normalizeApiPrefix = (...args) => openCodeNetworkRuntime.normalizeApiPrefix(...args);
602
603
  const setDetectedOpenCodeApiPrefix = (...args) => openCodeNetworkRuntime.setDetectedOpenCodeApiPrefix(...args);
603
604
  const buildOpenCodeUrl = (...args) => openCodeNetworkRuntime.buildOpenCodeUrl(...args);
605
+ configureOpenCodeCredentials(openCodeCredentialSource({ buildOpenCodeUrl, getOpenCodeAuthHeaders }));
604
606
  const ensureOpenCodeApiPrefix = (...args) => openCodeNetworkRuntime.ensureOpenCodeApiPrefix(...args);
605
607
  const scheduleOpenCodeApiDetection = (...args) => openCodeNetworkRuntime.scheduleOpenCodeApiDetection(...args);
606
608
 
@@ -1293,26 +1295,6 @@ const openCodeLifecycleRuntime = createOpenCodeLifecycleRuntime({
1293
1295
  getManagedOpenCodeShellEnvSnapshot: getLoginShellEnvSnapshot,
1294
1296
  managedCapabilitiesRuntime,
1295
1297
  getActiveSessionCount,
1296
- // Most-recently-used directories first: OpenCode initializes each directory
1297
- // lazily on first request (seconds on large session stores), so the
1298
- // lifecycle warms these right after readiness — before the UI's first
1299
- // interactive request would otherwise pay that cost.
1300
- getWarmupDirectories: async () => {
1301
- const settings = await readSettingsFromDiskMigrated().catch(() => null);
1302
- if (!settings) return [];
1303
- const directories = [];
1304
- if (typeof settings.lastDirectory === 'string' && settings.lastDirectory) {
1305
- directories.push(settings.lastDirectory);
1306
- }
1307
- const projects = Array.isArray(settings.projects) ? [...settings.projects] : [];
1308
- projects.sort((a, b) => (b?.lastOpenedAt ?? 0) - (a?.lastOpenedAt ?? 0));
1309
- for (const project of projects) {
1310
- if (typeof project?.path === 'string' && project.path) {
1311
- directories.push(project.path);
1312
- }
1313
- }
1314
- return [...new Set(directories)];
1315
- },
1316
1298
  });
1317
1299
 
1318
1300
  const restartOpenCode = (...args) => openCodeLifecycleRuntime.restartOpenCode(...args);
@@ -54,6 +54,7 @@ The following functions are exported and used by the web server:
54
54
  - `validateWorktreeCreate(directory, input)`: Validate worktree creation parameters (mode, branchName, startRef, upstream config).
55
55
  - `createWorktree(directory, input)`: Create a new worktree (supports 'new' and 'existing' modes, upstream setup).
56
56
  - `removeWorktree(directory, input)`: Remove a worktree (optionally delete local branch).
57
+ - `removeWorktree(directory, input, { beforeRemove })`: optional host hook after primary-workspace/catalog validation, immediately before removal of a matched worktree or managed orphan. The hook never runs for unrelated orphan paths. Production hosts use the official v2 `debug.location.evict` with the validated target and current upstream URL/auth; the 2s budget returns `released`, `failed`, `timeout`, or `unavailable`. Eviction is best effort and never counts as successful Git removal. DELETE responses include `locationRelease` when attempted. Shared-service explicit removal retains the existing force-delete semantics for that target; it is not automatic idle cleanup and does not interrupt unrelated sessions. An eviction acknowledgement is cache detachment, not a guarantee that active borrowers released OS handles. Queue rollback and topology publication remain tied to the Git result.
57
58
  - `isLinkedWorktree(directory)`: Check if directory is a linked worktree (not primary).
58
59
 
59
60
  ### Commit and Remote Operations
@@ -1,4 +1,4 @@
1
- export function registerGitRoutes(app, { messageQueueService = null, createRequestID = () => globalThis.crypto.randomUUID(), broadcastWorktreeTopologyChanged = () => {} } = {}) {
1
+ export function registerGitRoutes(app, { messageQueueService = null, createRequestID = () => globalThis.crypto.randomUUID(), broadcastWorktreeTopologyChanged = () => {}, releaseWorktreeLocation } = {}) {
2
2
  const normalizeWorktreePath = (value) => typeof value === 'string' ? value.trim().replace(/\\/g, '/').replace(/\/+$/, '') : '';
3
3
  const lifecycleOperations = new Map();
4
4
  const acquireWorktreeLifecycleOperation = (runtimeKey, directory) => {
@@ -1282,11 +1282,14 @@ export function registerGitRoutes(app, { messageQueueService = null, createReque
1282
1282
  }
1283
1283
 
1284
1284
  let result;
1285
+ let locationRelease;
1285
1286
  try {
1286
1287
  result = await removeWorktree(directory, {
1287
1288
  directory: worktreeDirectory,
1288
1289
  deleteLocalBranch: req.body?.deleteLocalBranch === true,
1289
- });
1290
+ }, ...(releaseWorktreeLocation ? [{ beforeRemove: async (target) => {
1291
+ locationRelease = await releaseWorktreeLocation(target);
1292
+ } }] : []));
1290
1293
  } catch (error) {
1291
1294
  if (messageQueueService) {
1292
1295
  try {
@@ -1328,7 +1331,7 @@ export function registerGitRoutes(app, { messageQueueService = null, createReque
1328
1331
  console.warn('Failed to roll back message queue worktree deletion');
1329
1332
  }
1330
1333
  }
1331
- return res.json({ success: false });
1334
+ return res.json({ success: false, ...(locationRelease ? { locationRelease } : {}) });
1332
1335
  }
1333
1336
 
1334
1337
  notifyWorktreeTopologyChanged(directory, worktreeDirectory, 'removed');
@@ -1346,7 +1349,7 @@ export function registerGitRoutes(app, { messageQueueService = null, createReque
1346
1349
  return res.status(500).json({ error: 'Failed to commit worktree queue deletion' });
1347
1350
  }
1348
1351
  }
1349
- res.json({ success: true });
1352
+ res.json({ success: true, ...(locationRelease ? { locationRelease } : {}) });
1350
1353
  } finally {
1351
1354
  releaseWorktreeLifecycleOperation(operation);
1352
1355
  }
@@ -69,6 +69,40 @@ const createMockResponse = () => {
69
69
  };
70
70
  };
71
71
 
72
+ describe('worktree eviction best effort', () => {
73
+ it.each(['released', 'failed', 'timeout', 'unavailable'])('preserves successful deletion with release outcome %s', async (state) => {
74
+ const { app, getRoute } = createRouteRegistry();
75
+ const releaseWorktreeLocation = vi.fn(async () => ({ state }));
76
+ const broadcastWorktreeTopologyChanged = vi.fn();
77
+ registerGitRoutes(app, { releaseWorktreeLocation, broadcastWorktreeTopologyChanged });
78
+ gitLibraries.removeWorktree.mockImplementation(async (_root, _input, options) => {
79
+ await options.beforeRemove('/validated/worktree');
80
+ return true;
81
+ });
82
+ const response = createMockResponse();
83
+ await getRoute('DELETE', '/api/git/worktrees')({ query: { directory: '/repo' }, body: { directory: '/requested/worktree' } }, response);
84
+ expect(releaseWorktreeLocation).toHaveBeenCalledWith('/validated/worktree');
85
+ expect(response.body).toEqual({ success: true, locationRelease: { state } });
86
+ expect(broadcastWorktreeTopologyChanged).toHaveBeenCalledTimes(1);
87
+ });
88
+ it('does not publish topology when removal fails after successful release', async () => {
89
+ const { app, getRoute } = createRouteRegistry();
90
+ const broadcastWorktreeTopologyChanged = vi.fn();
91
+ registerGitRoutes(app, { releaseWorktreeLocation: async () => ({ state: 'released' }), broadcastWorktreeTopologyChanged });
92
+ gitLibraries.removeWorktree.mockImplementation(async (_root, _input, options) => {
93
+ await options.beforeRemove('/repo/worktree');
94
+ throw new Error('deletion failed');
95
+ });
96
+ const response = createMockResponse();
97
+ const log = vi.spyOn(console, 'error').mockImplementation(() => {});
98
+ try {
99
+ await getRoute('DELETE', '/api/git/worktrees')({ query: { directory: '/repo' }, body: { directory: '/repo/worktree' } }, response);
100
+ } finally { log.mockRestore(); }
101
+ expect(response.statusCode).toBe(500);
102
+ expect(broadcastWorktreeTopologyChanged).not.toHaveBeenCalled();
103
+ });
104
+ });
105
+
72
106
  describe('git routes batch discover', () => {
73
107
  beforeEach(() => {
74
108
  gitLibraries.isGitRepository.mockReset();
@@ -3851,7 +3851,7 @@ export async function getWorktreeBootstrapStatus(directory) {
3851
3851
  };
3852
3852
  }
3853
3853
 
3854
- export async function removeWorktree(directory, input = {}) {
3854
+ export async function removeWorktree(directory, input = {}, { beforeRemove } = {}) {
3855
3855
  const targetDirectory = normalizeDirectoryPath(input?.directory);
3856
3856
  if (!targetDirectory) {
3857
3857
  throw new Error('Worktree directory is required');
@@ -3887,6 +3887,7 @@ export async function removeWorktree(directory, input = {}) {
3887
3887
 
3888
3888
  const targetExists = await checkPathExists(targetDirectory);
3889
3889
  if (targetExists && isManagedOrphan) {
3890
+ await beforeRemove?.(targetDirectory);
3890
3891
  await fsp.rm(targetDirectory, { recursive: true, force: true });
3891
3892
  }
3892
3893
 
@@ -3901,6 +3902,7 @@ export async function removeWorktree(directory, input = {}) {
3901
3902
  return true;
3902
3903
  }
3903
3904
 
3905
+ await beforeRemove?.(matchedEntry.worktree);
3904
3906
  await runGitCommandOrThrow(
3905
3907
  context.primaryWorktree,
3906
3908
  ['worktree', 'remove', '--force', matchedEntry.worktree],
@@ -39,8 +39,8 @@ call Anthropic/OpenAI/plugin SDKs, and does not use the `openai` npm package.
39
39
  `openchamber-text` agent (same temp root, same deny-all verification and
40
40
  `session.create` / `session.remove` lifecycle as the attachment path below):
41
41
 
42
- 1. Ensure `.opencode/agent/openchamber-text.md` and verify its final
43
- permission rule is deny-all before creating the session.
42
+ 1. Ensure `.opencode/agent/openchamber-text.md` and verify its last catch-all
43
+ permission rule is deny-all, with only deny rules after it, before creating the session.
44
44
  2. `session.create({ location, agent: 'openchamber-text', model, metadata })`.
45
45
  3. `session.generate({ sessionID, prompt })` → `{ text }`. One model call with
46
46
  the session's provider context, no tool loop, no messages written. System
@@ -62,8 +62,10 @@ data-URL file parts:
62
62
 
63
63
  1. Ensure the throwaway temp directory agent markdown (`openchamber-llm`,
64
64
  permissions array ending in `action:* resource:* effect:deny`).
65
- 2. `agent.get({ agentID: 'openchamber-llm', location })` and require the **final**
66
- permissions rule to be deny-all. Failure → `llm_attachment_generation_unavailable`
65
+ 2. `agent.get({ agentID: 'openchamber-llm', location })` and require the **last catch-all**
66
+ permissions rule to be deny-all, with only deny rules after it. This accepts
67
+ OpenCode's appended `browser` deny while rejecting trailing allow/ask rules.
68
+ Failure → `llm_attachment_generation_unavailable`
67
69
  **before** any `session.prompt`. Title is never treated as permission isolation.
68
70
  3. `session.create({ location, agent, model: { id, providerID, variant? },
69
71
  metadata: { openchamber: { llm: { purpose: 'chat-completions' } } } })`
@@ -18,7 +18,7 @@ const positiveMs = (value, fallback) => (
18
18
 
19
19
  /**
20
20
  * Agent frontmatter for official client agent permissions (action/resource/effect).
21
- * The final rule must be deny-all; assertLlmAgentDenyAll verifies that before prompt.
21
+ * The final catch-all must deny, with only denies after it; verified before prompt.
22
22
  */
23
23
  const AGENT_MARKDOWN = `---
24
24
  mode: primary
@@ -308,7 +308,7 @@ const readGeneratorAgent = async ({ client, location, signal, agentID, waitMs, p
308
308
  };
309
309
 
310
310
  /**
311
- * Verify the generator agent ends with deny-all permissions.
311
+ * Verify the generator agent has a deny-all rule followed only by denies.
312
312
  * Title alone is never treated as isolation. Failure must happen before prompt.
313
313
  */
314
314
  export async function assertLlmAgentDenyAll({
@@ -336,13 +336,12 @@ export async function assertLlmAgentDenyAll({
336
336
  'llm_attachment_generation_unavailable',
337
337
  );
338
338
  }
339
- const last = permissions[permissions.length - 1];
340
- if (
341
- !last
342
- || last.action !== '*'
343
- || last.resource !== '*'
344
- || last.effect !== 'deny'
345
- ) {
339
+ const lastCatchAll = permissions.findLastIndex(
340
+ (rule) => rule?.action === '*' && rule?.resource === '*',
341
+ );
342
+ const isolated = lastCatchAll > -1
343
+ && permissions.slice(lastCatchAll).every((rule) => rule?.effect === 'deny');
344
+ if (!isolated) {
346
345
  failGenerate(
347
346
  'OpenCode LLM agent final permission is not deny-all; generation blocked',
348
347
  'llm_attachment_generation_unavailable',
@@ -127,8 +127,16 @@ function textSessionClient({ generate = vi.fn(async () => ({ text: 'reply' })),
127
127
  }
128
128
 
129
129
  describe('generateOpenCodeText — text path', () => {
130
- it('runs session.generate in a verified deny-all throwaway session and removes it', async () => {
131
- const { client, agentGet, create, generate, remove, prompt } = textSessionClient()
130
+ it.each([
131
+ { name: 'deny-all alone', suffix: [] },
132
+ { name: 'appended browser deny', suffix: [{ action: 'browser', resource: '*', effect: 'deny' }] },
133
+ ])('runs session.generate in a verified throwaway session ($name) and removes it', async ({ suffix }) => {
134
+ const { client, agentGet, create, generate, remove, prompt } = textSessionClient({
135
+ agentGet: vi.fn(async () => ({
136
+ ...denyAllTextAgent,
137
+ data: { ...denyAllTextAgent.data, permissions: [...denyAllTextAgent.data.permissions, ...suffix] },
138
+ })),
139
+ })
132
140
  const ensureTempDirectory = vi.fn(async ({ agentName, agentMarkdown }) => {
133
141
  expect(agentName).toBe(_test.TEXT_AGENT_NAME)
134
142
  expect(agentMarkdown).toBe(_test.TEXT_AGENT_MARKDOWN)
@@ -188,10 +196,23 @@ describe('generateOpenCodeText — text path', () => {
188
196
  }
189
197
  })
190
198
 
191
- it('blocks before creating a session when the text agent is not deny-all', async () => {
199
+ it.each([
200
+ { name: 'no catch-all', permissions: [{ action: 'browser', resource: '*', effect: 'deny' }] },
201
+ ...['allow', 'ask'].flatMap((effect) => ['*', 'edit'].map((action) => ({
202
+ name: `trailing ${action} ${effect} before browser deny`,
203
+ permissions: [
204
+ ...denyAllTextAgent.data.permissions,
205
+ { action, resource: '*', effect },
206
+ { action: 'browser', resource: '*', effect: 'deny' },
207
+ ],
208
+ }))),
209
+ { name: 'missing permissions', permissions: undefined },
210
+ { name: 'empty permissions', permissions: [] },
211
+ { name: 'invalid trailing rule', permissions: [...denyAllTextAgent.data.permissions, null] },
212
+ ])('blocks before creating a session: $name', async ({ permissions }) => {
192
213
  const { client, create, generate } = textSessionClient({
193
214
  agentGet: vi.fn(async () => ({
194
- data: { id: 'openchamber-text', permissions: [{ action: 'bash', resource: '*', effect: 'allow' }] },
215
+ data: { id: 'openchamber-text', permissions },
195
216
  })),
196
217
  })
197
218
  await expect(generateOpenCodeText({
@@ -316,8 +337,14 @@ describe('generateOpenCodeText — text path', () => {
316
337
  })
317
338
 
318
339
  describe('generateOpenCodeText — attachment session path', () => {
319
- it('verifies deny-all before prompt, prompts with files, waits, lists, and removes', async () => {
320
- const agentGet = vi.fn(async () => denyAllAgent)
340
+ it.each([
341
+ { name: 'deny-all alone', suffix: [] },
342
+ { name: 'appended browser deny', suffix: [{ action: 'browser', resource: '*', effect: 'deny' }] },
343
+ ])('verifies deny-all ($name) before prompt, prompts with files, waits, lists, and removes', async ({ suffix }) => {
344
+ const agentGet = vi.fn(async () => ({
345
+ ...denyAllAgent,
346
+ data: { ...denyAllAgent.data, permissions: [...denyAllAgent.data.permissions, ...suffix] },
347
+ }))
321
348
  const create = vi.fn(async () => ({ id: 'ses_tmp' }))
322
349
  const put = vi.fn(async () => undefined)
323
350
  const prompt = vi.fn(async () => ({ id: 'inbox_1', type: 'user' }))
@@ -5,7 +5,7 @@ This module provides OpenCode server integration utilities for the web server ru
5
5
 
6
6
  ## Entrypoints and structure
7
7
  - `packages/web/server/lib/opencode/index.js`: public entrypoint (currently baseline placeholder).
8
- - `packages/web/server/lib/opencode/auth.js`: provider authentication file operations.
8
+ - `packages/web/server/lib/opencode/auth.js`: host-only authoritative provider credential API reads and deletion.
9
9
  - `packages/web/server/lib/opencode/auth-state-runtime.js`: managed OpenCode server auth password/header runtime.
10
10
  - `packages/web/server/lib/opencode/cli-options.js`: CLI/environment option parsing for server startup arguments.
11
11
  - `packages/web/server/lib/opencode/cli-entry-runtime.js`: CLI entrypoint runtime that detects direct execution, forces `OPENCHAMBER_RUNTIME=web` unless already `ssh-remote` (so `dev:server` cannot inherit a leftover desktop env and host a relay, while SSH-managed remotes may), parses CLI options, and starts server bootstrap.
@@ -15,7 +15,7 @@ This module provides OpenCode server integration utilities for the web server ru
15
15
  - `packages/web/server/lib/opencode/managed-capabilities-runtime.js`: managed-child scheduled-task resources, config injection, rotating bridge identity, and bridge authorization.
16
16
  - `packages/web/server/lib/opencode/env-runtime.js`: OpenCode CLI/binary resolution and shell environment runtime.
17
17
  - `packages/web/server/lib/opencode/ensure-cli.js`: detect local `opencode` version and install the pinned official V2 into the OpenChamber data dir when missing or too old. Never uses a packaged/bundled binary.
18
- - `packages/web/server/lib/opencode/upgrade-screen.js`: startup gate for a detected OpenCode below `2.0.15`. It does not own process lifecycle. `GET /api/opencode/compatibility` classifies the version already detected by startup. `POST /api/opencode/install-required` installs `max(pin, 2.0.15)` through `installPinnedOpenCode2Cli`, points the managed binary at that cache, and restarts through the existing lifecycle hooks. Success requires the running serve to report `2.0.15` or newer. External serves are not replaced. Failure returns `upgraded: false` and the error; it does not claim the previous process was restored.
18
+ - `packages/web/server/lib/opencode/upgrade-screen.js`: startup gate for the running OpenCode below `2.0.20`. Client/schema dependencies and the default CLI install target are pinned to `2.0.23`; the compatible execution floor is independently `2.0.20` (credential API). An unreadable running version is unavailable even when the local CLI is compatible. `POST /api/opencode/install-required` installs the pin into the owned cache and uses existing lifecycle hooks. Success requires the running service to meet both the floor and selected target. External services are not replaced; compatible newer shared services are not automatically downgraded. Failure restores binary selection and reports `upgraded: false`, without claiming process restoration.
19
19
  - `packages/web/server/lib/opencode/env-config.js`: OpenCode-related environment variable parsing and validation (host/port/hostname).
20
20
  - `packages/web/server/lib/opencode/hmr-state-runtime.js`: HMR-persistent runtime state initialization, auth-state bootstrap, and HMR sync helpers.
21
21
  - `packages/web/server/lib/opencode/bootstrap-runtime.js`: base app bootstrap runtime for status/auth/tts/notification/OpenChamber route wiring.
@@ -23,7 +23,6 @@ This module provides OpenCode server integration utilities for the web server ru
23
23
  - `packages/web/server/lib/opencode/project-directory-runtime.js`: request-scoped and settings-backed project directory resolution/validation runtime.
24
24
  - `packages/web/server/lib/opencode/config-entity-routes.js`: route registration for agent/command/MCP config orchestration and reload semantics.
25
25
  - `packages/web/server/lib/opencode/provider-catalog.js`: fail-closed projection for the safe provider catalog response.
26
- - `packages/web/server/lib/opencode/snippets.js`: opencode-snippets-compatible snippet file CRUD, discovery, and hashtag expansion.
27
26
  - `packages/web/server/lib/opencode/cli-options.js`: CLI/environment option parsing for server startup arguments.
28
27
  - `packages/web/server/lib/opencode/core-routes.js`: server status/system routes, auth/access guard routes, and settings utility route registration.
29
28
  - `packages/web/server/lib/opencode/shutdown-runtime.js`: graceful shutdown orchestration runtime for watcher/session/terminal/process/server teardown.
@@ -55,13 +54,12 @@ This module provides OpenCode server integration utilities for the web server ru
55
54
  - `packages/web/server/lib/ui-auth/ui-passkeys.js`: UI passkey storage and WebAuthn registration/authentication helpers (outside OpenCode module).
56
55
 
57
56
  ## Public exports (auth.js)
58
- - `readAuthFile()`: Reads and parses `~/.local/share/opencode/auth.json`.
59
- - `writeAuthFile(auth)`: Writes auth file with automatic backup.
60
- - `removeProviderAuth(providerId)`: Removes a provider's auth entry.
61
- - `getProviderAuth(providerId)`: Returns auth for a specific provider or null.
62
- - `listProviderAuths()`: Returns list of provider IDs with configured auth.
63
- - `AUTH_FILE`: Auth file path constant.
64
- - `OPENCODE_DATA_DIR`: OpenCode data directory path constant.
57
+ - `configureOpenCodeCredentials(resolveClient)`: host composition injects a call-time SDK client resolver; `null` detaches it. Web/Electron share server wiring; VS Code wires its manager in extension activation. Mobile clients use their host.
58
+ - `openCodeCredentialSource({ buildOpenCodeUrl, getOpenCodeAuthHeaders })`: resolves the current endpoint/auth at each read rather than capturing a previous runtime.
59
+ - `readOpenCodeCredentials()`: async SDK `credential.list()` read of the running service; validates entries and projects only active accounts into the quota/TTS credential shape. OAuth `metadata.accountID` becomes `accountId`. No local `auth.json`, local environment, or local provider-config fallback; external service environment-only keys are not exposed by the credential API.
60
+ - `getProviderAuth(providerId)`: async selected stored credential or authoritative `null`; network/auth/schema failures reject with a sanitized error, never empty success.
61
+ - `removeProviderAuth(providerId)`: deletes every stored account for that integration through SDK `credential.remove`, preserving unrelated integrations. Partial failure rejects explicitly; retry lists remaining accounts. Auth-only disconnect requires no process restart.
62
+ - Credential values remain host-only. `credential-boundary.js` blocks raw credential list/creation responses (GET/HEAD/POST `/api/credential`, including encoded/trailing-slash forms) in Web proxy and VS Code bridge because v2 replies contain keys. Provider-source responses expose only existence; errors never include upstream credential bodies/headers. Explicit legacy config-sync file transfer is not a current-service credential source or login operation.
65
63
 
66
64
  ## Public exports (shared.js)
67
65
  - `OPENCODE_CONFIG_DIR`, `AGENT_DIR`, `COMMAND_DIR`, `SKILL_DIR`, `CONFIG_FILE`, `CUSTOM_CONFIG_FILE`: Path constants.
@@ -89,9 +87,9 @@ This module provides OpenCode server integration utilities for the web server ru
89
87
  - `PUT /api/behavior/agents-md`
90
88
  - `POST /api/opencode/upgrade` (ticket 12): installs the remote latest or explicitly targeted `@opencode/cli` into the **owned cache** at `~/.config/openchamber/opencode-cli/<version>/`. Managed owned-cache processes restart on that binary; official shared services use that exact binary's `service restart`, then adopt its registration again. Success requires a fresh running-serve version probe and runtime contract verification. Arbitrary external servers, private processes using global CLI, and private bundled binaries return HTTP 409; user installs are never overwritten. Concurrent upgrades are single-flight (`operation` state). Active sessions require `confirmActiveTasks`/`force`. Failure preserves cached binaries, restores the previous binary selection, and reports the failed phase; it does not claim the old process was restored. Shared-service recovery can subsequently re-ensure the registered service.
91
89
  - `GET /api/opencode/upgrade-status`: returns `currentVersion`/`serveVersion`/`cliVersion`, `targetVersion`/`latestVersion` (official npm `@opencode/cli` latest stable 2.x release), `supplySource`, `ownership`, `canManage`, `management`, `guidance`, `contract`, and `operation`. Official shared services report `ownership: 'shared-service'`, `processOwnership: 'shared'`, and `canManage: true`. `available` is true only when `canManage` and target > current; unmanaged global/external runtimes report `available: null` plus manual guidance. `update-discovery.js` shares one in-flight registry request per host, caches successful checks for five minutes and failed checks for thirty seconds, with a five-second request deadline. Failed registry checks return HTTP 500 with `available: null`, never a false up-to-date result. An upgrade without an explicit target resolves this same remote release. Shared Web/Electron/hosted-mobile/Capacitor clients check after startup, every five minutes while visible, on focus/reconnect, and on runtime changes; query results and dismissed notifications are scoped to the host. Successful upgrade already includes process restart and live version verification, so no second reload is needed.
92
- - `GET /api/opencode/compatibility`: startup upgrade-screen status. `state` is `compatible` at OpenCode `2.0.15` or newer, `incompatible` when a detected version is older or not 2.x, and `unavailable` when no version could be read. `canInstall` is true only for a local (managed or bundled) runtime this platform can install into the owned cache. External serves are reported and not replaced.
93
- - `POST /api/opencode/install-required`: installs the upgrade-screen target (`2.0.15`, or the pin when the pin is newer) into the owned cache and restarts through the existing lifecycle. `upgraded: true` only after the running serve verifies at or above that target. Failure is `upgraded: false` plus `error`. Concurrent installs are single-flight.
94
- - `GET /api/opencode/contract` (ticket 11): runtime contract admission for the **running serve** (not health alone). Dimensions: reachable / authenticated / protocolCompatible / executionAllowed / capabilities / migration / CLI↔serve mismatch. Floor `2.0.12` (acceptable OpenCode 2.x at or above); below-min 2.x and 1.x block execution with upgrade guidance; no upper version cap.
90
+ - `GET /api/opencode/compatibility`: startup upgrade-screen status. `compatible` requires the running OpenCode 2.x at `2.0.20` or newer; older/invalid versions are `incompatible`; unreadable running versions are `unavailable`. External serves are not replaced.
91
+ - `POST /api/opencode/install-required`: installs the pin (`2.0.23`, never below the `2.0.20` floor) into the owned cache and restarts through the existing lifecycle. `upgraded: true` requires live version verification. Concurrent installs are single-flight; failures remain errors.
92
+ - `GET /api/opencode/contract`: runtime contract admission for the **running serve**. Reachability, auth, execution, migration, and CLI↔serve mismatch are separate. The `2.0.20` floor is independent of the `2.0.23` install pin; compatible newer 2.x services remain admitted.
95
93
  - `GET /api/opencode/health`: probes managed OpenCode `GET /api/info` (Basic auth, 4s timeout). `healthy` is reachability/version shape via `evaluateOpenCodeHealthBody` (admits official 2.x `ServerInfo` and classic `{ healthy: true, version }`; rejects 1.x / missing / `healthy: false`). Response also includes `executionAllowed`, `protocolCompatible`, and `contract` so clients do not treat health success as full execution semantics. Upstream non-OK responses pass through status with `{ healthy: false, error, contract }`; transport failures return HTTP 503.
96
94
  - `GET /api/opencode/version`: serve + CLI versions, mismatch flag, and contract snapshot
97
95
  - `POST /api/opencode/directory`
@@ -130,7 +128,7 @@ This module provides OpenCode server integration utilities for the web server ru
130
128
  - `resolveOpenCode2UpgradeTarget(target)` / `rejectOpenCode1xUpgradeTarget(target)`: upgrade targets default to the pin and refuse 1.x.
131
129
 
132
130
  ## Public exports (runtime-contract.js)
133
- - Ticket 11 pure admission matrix. `RUNTIME_CONTRACT_MIN_VERIFIED` (`2.0.12`) is the documented 2.x floor; capabilities include `core.protocol`, `session.prompt`, `session.queuedInput`, `session.background`, `session.generationFallback`, `migration.v1Read`.
131
+ - Pure admission matrix. `RUNTIME_CONTRACT_MIN_VERIFIED` (`2.0.20`) is the documented 2.x floor, shared with the startup gate through `MINIMUM_OPENCODE2_VERSION`; the VS Code sidecar mirrors this floor. Capabilities include `core.protocol`, `session.prompt`, `session.queuedInput`, `session.background`, `session.generationFallback`, `migration.v1Read`.
134
132
  - `evaluateRuntimeContract(input)`: separate reachable / auth / protocol / execution / migration / CLI↔serve mismatch reasons for UI and diagnostics. **`executionAllowed` requires acceptable OpenCode 2.x at or above the floor** (`versionBand === 'verified'`).
135
133
  - `formatRuntimeContractBlockedError(contract)` / `runtimeContractExecutionBlockedBody(contract)`: user-facing upgrade guidance on HTTP 409 execution blocks (below-min / 1.x), not raw JSON dumps.
136
134
  - `isRuntimeContractExecutionPath` / `isRuntimeContractDiagnosticPath` / `isRuntimeContractStopPath` / `shouldBlockRuntimeContractExecution` / `createRevokedRuntimeContract`: proxy + Host transport gate helpers. **Execution paths require `executionAllowed === true` on the current contract**; null/pending/false all block (no silent bypass). Unit/DI factories may pass `{ allowMissingContract: true }` only when no lifecycle is wired. Interrupt/abort and reads stay available when execution is limited. Health success is not full execution semantics.
@@ -155,7 +153,7 @@ This module provides OpenCode server integration utilities for the web server ru
155
153
  - `fetchV1MigrationGate({ url, headers, signal, fetchImpl })`: GET-only status poll used by lifecycle.
156
154
 
157
155
  ## Public exports (lifecycle.js)
158
- - `createOpenCodeLifecycleRuntime(dependencies)`: creates lifecycle runtime for managed/external OpenCode process orchestration. Bootstrap reuses a healthy v2 `opencode serve` when present; otherwise starts managed `opencode serve`. Before managed spawn, `ensurePinnedOpenCode2CliEnv()` detects the local CLI version and installs the pinned official V2 when missing or too old (skipped for external skip-start). Startup accepts both `server listening on http://127.0.0.1:PORT` and the legacy `opencode server listening on …` line. Readiness/health probes `GET /api/info` first, then `/global/health`, with Basic auth (username `opencode`). A body is admitted only when `evaluateOpenCodeHealthBody` accepts the version (rejects 1.x and missing/unknown). After health ok, `startOpenCode` / `waitForOpenCodeReady` poll `GET /api/experimental/migration/v1` (no POST; backfill is owned by opencode2). Each readiness attempt uses one abort timer that covers **both** health and migration so a hung migration cannot outrun the per-attempt budget. External instances are a read-only mount: restart/refresh only re-probe health + V1 migration (never kill, spawn, clear port, or flip `isExternalOpenCode`); health monitoring skips them; upgrade routes return 409. After readiness admits, lifecycle warms recent directories via optional `getWarmupDirectories` (sequential best-effort `GET /api/session?directory=&limit=1`). External skip-start / auto-detect attach leave `isOpenCodeReady` false until the gate admits. `isOpenCodeReady` means transcript may be fetched only when the gate admits (`completed`, or no V1 library such as HTTP 404). `required` / `running` / `error` keep the ready gate closed; `error` is retried. The last gate result is stored on `state.v1Migration` and published on the OpenChamber `/health` snapshot so UI can render `phase` and running `progress` (`label` / `numerator` / `denominator`) plus `userNotice` (reuse message ids; in-progress tools become interrupted; V1 subtasks do not appear in v2).
156
+ - `createOpenCodeLifecycleRuntime(dependencies)`: creates lifecycle runtime for managed/external OpenCode process orchestration. Bootstrap reuses a healthy v2 `opencode serve` when present; otherwise starts managed `opencode serve`. Before managed spawn, `ensurePinnedOpenCode2CliEnv()` detects the local CLI version and installs the pinned official V2 when missing or too old (skipped for external skip-start). Startup accepts both `server listening on http://127.0.0.1:PORT` and the legacy `opencode server listening on …` line. Readiness/health probes `GET /api/info` first, then `/global/health`, with Basic auth (username `opencode`). A body is admitted only when `evaluateOpenCodeHealthBody` accepts the version (rejects 1.x and missing/unknown). After health ok, `startOpenCode` / `waitForOpenCodeReady` poll `GET /api/experimental/migration/v1` (no POST; backfill is owned by opencode2). Each readiness attempt uses one abort timer that covers **both** health and migration so a hung migration cannot outrun the per-attempt budget. External instances are a read-only mount: restart/refresh only re-probe health + V1 migration (never kill, spawn, clear port, or flip `isExternalOpenCode`); health monitoring skips them; upgrade routes return 409. Startup does not prewarm persisted project directories: v2 session lists are storage-scoped and do not initialize locations, while location-scoped reads must follow actual consumer demand. External skip-start / auto-detect attach leave `isOpenCodeReady` false until the gate admits. `isOpenCodeReady` means transcript may be fetched only when the gate admits (`completed`, or no V1 library such as HTTP 404). `required` / `running` / `error` keep the ready gate closed; `error` is retried. The last gate result is stored on `state.v1Migration` and published on the OpenChamber `/health` snapshot so UI can render `phase` and running `progress` (`label` / `numerator` / `denominator`) plus `userNotice` (reuse message ids; in-progress tools become interrupted; V1 subtasks do not appear in v2).
159
157
  - Shared official service (default for Web and Electron hosts, including mobile clients connected to those hosts): when `sharedService` is injected and neither an OpenCode port nor host is configured, bootstrap runs `opencode service start` before HMR/private-process reuse or any port-4096 probe. It attaches to the registered stable-channel service, adopts its password/base URL/port, and marks it external for shutdown. Closing OpenChamber leaves that service running; no managed capabilities (scheduled-task plugin, bridge token, `v2-plugin-host-shim`) are injected. Attach failure clears URL/port/password, reports an unavailable integration and permits explicit retry, without silently spawning a private server. Explicit host/port and shared-service opt-out retain their existing behavior; VS Code retains its extension-owned sidecar lifecycle.
160
158
  - While attached, ordinary `restartOpenCode()` re-runs `service start` and adopts the latest registration before re-probing; a failed ensure propagates instead of probing stale credentials as success. Upgrade-only `restartOpenCode({ binaryPath })` bypasses settings rediscovery and runs the verified target's `service restart`. Configuration refresh uses `POST /api/location/reload` instead of process replacement (returns `{ reloaded: true, external: true }`). Health monitoring uses registration-PID liveness. Normal attachment/recovery does not version-pin or downgrade a healthy registered service; explicit in-app upgrade is the version-replacement operation.
161
159
  - Returned API:
@@ -210,6 +208,7 @@ This module provides OpenCode server integration utilities for the web server ru
210
208
 
211
209
  ## Public exports (env-config.js)
212
210
  - `resolveOpenCodeEnvConfig(options?)`: resolves and validates OpenCode host/port/hostname environment configuration.
211
+ - `OPENCODE_HOST` still requires an explicit port; HTTP `:80` and HTTPS `:443` retain their effective port even when URL normalization removes it. Host precedence, IPv6 and path/query/hash validation are unchanged.
213
212
  - Returned object fields:
214
213
  - `configuredOpenCodePort`
215
214
  - `configuredOpenCodeHost`
@@ -333,7 +332,6 @@ Failures of either required list still return HTTP 502 for the caller to retry.
333
332
  - Commands: batched metadata via `POST /api/config/commands/metadata`; `{ catalog: true }` returns the compact autocomplete catalog without templates, plus CRUD at `/api/config/commands/:name`
334
333
  - Global raw configs: `GET /api/config/global` discovers existing config targets; `GET/PUT /api/config/global/:target` reads and writes `opencode`, `oh-my-opencode-slim`, and `oh-my-openagent` JSON or JSONC files
335
334
  - MCP servers: `/api/config/mcp` and `/api/config/mcp/:name`
336
- - Snippets: `/api/config/snippets`, `/api/config/snippets/:name`, and `/api/config/snippets/expand`
337
335
 
338
336
  ### Catalog / OpenChamber-owned API change checklist
339
337
 
@@ -346,6 +344,7 @@ When adding or changing Host HTTP APIs that mobile/desktop clients reach over Pr
346
344
  5. Coordinate `partial` semantics between Host projection, VS Code projection (`packages/vscode/src/provider-catalog-runtime.ts`), and `packages/ui/src/lib/configCatalogParser.ts`. Reserve `partial: true` for structural incompleteness (dropped/truncated providers, models, defaults, variants). Soft metadata stripping must stay non-partial; a false `partial: true` blocks UI refresh of an existing complete snapshot and can leave model pickers on stale catalogs after OpenCode provider updates.
347
345
 
348
346
  ## Public exports (auth-state-runtime.js)
347
+ - Initial user auth prefers `OPENCODE_PASSWORD` over `OPENCODE_SERVER_PASSWORD`; HMR preserves the captured choice and active password. Private child environments set both names to the active header password after merging shell/host env. Registered shared-service auth is authoritative (including no password), uses username `opencode`, and never falls back to private env credentials.
349
348
  - `createOpenCodeAuthStateRuntime(dependencies)`: creates runtime for managed OpenCode auth password state and request headers.
350
349
  - Returned API:
351
350
  - `getOpenCodeAuthHeaders()`
@@ -513,16 +512,21 @@ The V2 skill catalog uses `id` as the existing OpenChamber `name` invocation key
513
512
  reuse the existing UI-password / CORS path, register before the generic proxy,
514
513
  and return 501 when the service is disabled. Logs never include message
515
514
  bodies, parts, or tokens.
516
- - Provider auth: `~/.local/share/opencode/auth.json`.
517
- - User config: `~/.config/opencode/opencode.json` or `~/.config/opencode/opencode.jsonc`.
518
- - Project config: `<workingDirectory>/.opencode/opencode.json` or `opencode.json`.
515
+ - Provider auth: running OpenCode v2 credential API (no direct access to legacy auth files or OpenCode database).
516
+ - `config-files.js` owns physical JSON/JSONC discovery, layer provenance and structural writes for Web/Electron and the VS Code extension. Global directory: `OPENCODE_CONFIG_DIR`, otherwise `$XDG_CONFIG_HOME/opencode` or `~/.config/opencode`.
517
+ - Global documents load `opencode.json` then `opencode.jsonc`; V2 does not load legacy `config.json`. Both are retained as separate writable documents. `userConfig` / `projectConfig` are merged read views; mutations must use `getConfigForPath`, `getJsonEntrySource`, or `getJsonWriteTarget`, never persist a merged view into one file.
518
+ - Project documents load from filesystem root to the working directory, all direct JSON/JSONC files first, then all `.opencode` files. `documents` records low-to-high precedence; `userLayers` / `projectLayers` retain physical sources. Custom files sit above global files and below project documents, matching V2 `ConfigDiscovery.names` and `Config.load`.
519
519
  - Custom config: `OPENCODE_CONFIG` env var path.
520
+ - Raw global editors in Web and VS Code edit the explicit custom file when set, otherwise the highest-precedence existing global document (JSONC wins). New global documents default to `opencode.jsonc`. Raw content is one physical document, never a synthetic merged serialization.
521
+ - Missing, whitespace-only and comment-only files are empty layers. Malformed/non-object JSONC and IO failures throw; partial parser results never become empty success. Both raw and structured writers validate the existing document before replacement.
522
+ - Structured writes diff object fields, preserve untouched comments/formatting (including BOM/CRLF), and remove properties with adjacent separator commas without consuming surrounding comments. Arrays are replaced as owned values. A failed round-trip aborts instead of falling back to whole-file serialization.
523
+ - Writes stage a same-directory temporary file, preserve file mode, create `.openchamber.backup`, then atomically rename; failed staging/backup/rename leaves the original intact and cleans the temporary file. Existing symlinks are resolved first: staging/rename target the real file, the link remains unchanged, and the backup stays beside the supplied config path. Dangling symlinks fail before staging; existing target permissions are restored on the staged file regardless of umask. Deleting an entity removes it from its selected source document; lower-precedence definitions may become visible afterward.
520
524
  - Rate limit config: `OPENCHAMBER_RATE_LIMIT_MAX_ATTEMPTS`, `OPENCHAMBER_RATE_LIMIT_NO_IP_MAX_ATTEMPTS` env vars.
521
525
 
522
526
  ## Notes for contributors
523
527
  - This module serves as foundation for OpenCode-related server utilities.
524
528
  - Route ownership moved to module-level `routes.js`; `index.js` wires dependencies only.
525
529
  - All file writes include automatic backup before modification.
526
- - Config merging follows priority: custom > project > user.
530
+ - Config merging follows priority: project `.opencode` > project direct > custom > global JSONC > global JSON.
527
531
  - UI auth uses scrypt for password hashing with constant-time comparison.
528
532
  - Tunnel auth treats `host.docker.internal` as local-only when the socket remote IP is private/loopback.