@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
@@ -7,20 +7,21 @@
7
7
  * execution semantics.
8
8
  *
9
9
  * Evidence bounds (ticket 11):
10
- * - MIN_VERIFIED: pin / installed verification (`2.0.12`)
10
+ * - MIN_VERIFIED: credential API availability (`2.0.20`)
11
11
  * Any acceptable OpenCode 2.x at or above MIN_VERIFIED may execute. Below-min
12
12
  * 2.x and 1.x are refused with upgrade guidance; there is no upper version cap.
13
13
  */
14
14
 
15
15
  import {
16
16
  PINNED_OPENCODE2_VERSION,
17
+ MINIMUM_OPENCODE2_VERSION,
17
18
  compareOpenCode2Versions,
18
19
  isAcceptableOpenCode2HealthVersion,
19
20
  isOpenCode1xVersion,
20
21
  } from './opencode2-pin.js';
21
22
 
22
23
  /** Lowest version with documented OpenChamber + OpenCode 2 contract tests. */
23
- export const RUNTIME_CONTRACT_MIN_VERIFIED = PINNED_OPENCODE2_VERSION;
24
+ export const RUNTIME_CONTRACT_MIN_VERIFIED = MINIMUM_OPENCODE2_VERSION;
24
25
 
25
26
  /**
26
27
  * Optional / core capabilities and the minimum serve version that admits them.
@@ -13,14 +13,14 @@ import {
13
13
  } from './runtime-contract.js';
14
14
 
15
15
  describe('runtime contract admission (ticket 11)', () => {
16
- it('records minimum verified bound from pin', () => {
17
- expect(RUNTIME_CONTRACT_MIN_VERIFIED).toBe('2.0.12');
16
+ it('records the credential API floor independently of the install pin', () => {
17
+ expect(RUNTIME_CONTRACT_MIN_VERIFIED).toBe('2.0.20');
18
18
  });
19
19
 
20
20
  it('classifies version bands with a 2.x floor only', () => {
21
- expect(classifyRuntimeVersionBand('2.0.12')).toBe('verified');
22
- expect(classifyRuntimeVersionBand('2.0.14')).toBe('verified');
23
- expect(classifyRuntimeVersionBand('2.0.15')).toBe('verified');
21
+ expect(classifyRuntimeVersionBand('2.0.19')).toBe('below-min');
22
+ expect(classifyRuntimeVersionBand('2.0.20')).toBe('verified');
23
+ expect(classifyRuntimeVersionBand('2.0.23')).toBe('verified');
24
24
  expect(classifyRuntimeVersionBand('2.1.0')).toBe('verified');
25
25
  expect(classifyRuntimeVersionBand('2.0.5')).toBe('below-min');
26
26
  expect(classifyRuntimeVersionBand('1.18.18')).toBe('1x');
@@ -30,8 +30,8 @@ describe('runtime contract admission (ticket 11)', () => {
30
30
 
31
31
  it('admits verified serve for core protocol and optional caps', () => {
32
32
  const result = evaluateRuntimeContract({
33
- serveVersion: '2.0.12',
34
- cliVersion: '2.0.12',
33
+ serveVersion: '2.0.20',
34
+ cliVersion: '2.0.20',
35
35
  reachable: true,
36
36
  authenticated: true,
37
37
  healthOk: true,
@@ -65,7 +65,7 @@ describe('runtime contract admission (ticket 11)', () => {
65
65
 
66
66
  it('admits newer 2.x above the documented floor for execution', () => {
67
67
  const result = evaluateRuntimeContract({
68
- serveVersion: '2.0.15',
68
+ serveVersion: '2.0.23',
69
69
  reachable: true,
70
70
  authenticated: true,
71
71
  healthOk: true,
@@ -105,12 +105,12 @@ describe('runtime contract admission (ticket 11)', () => {
105
105
  expect(evaluateRuntimeContract({
106
106
  reachable: true,
107
107
  authenticated: false,
108
- serveVersion: '2.0.12',
108
+ serveVersion: '2.0.20',
109
109
  healthOk: true,
110
110
  }).phase).toBe('unauthenticated');
111
111
 
112
112
  const migration = evaluateRuntimeContract({
113
- serveVersion: '2.0.12',
113
+ serveVersion: '2.0.20',
114
114
  reachable: true,
115
115
  authenticated: true,
116
116
  healthOk: true,
@@ -122,7 +122,7 @@ describe('runtime contract admission (ticket 11)', () => {
122
122
  expect(migration.migrationExecutable).toBe(false);
123
123
 
124
124
  const mismatch = evaluateRuntimeContract({
125
- serveVersion: '2.0.12',
125
+ serveVersion: '2.0.20',
126
126
  cliVersion: '2.0.14',
127
127
  reachable: true,
128
128
  authenticated: true,
@@ -179,7 +179,7 @@ describe('runtime contract admission (ticket 11)', () => {
179
179
  migrationAdmitTranscript: true,
180
180
  });
181
181
  const body = runtimeContractExecutionBlockedBody(contract);
182
- expect(body.error).toContain('2.0.12');
182
+ expect(body.error).toContain('2.0.20');
183
183
  expect(body.error).toMatch(/Settings → About/i);
184
184
  expect(formatRuntimeContractBlockedError(contract)).toBe(body.error);
185
185
  });
@@ -2,18 +2,18 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import os from 'os';
4
4
  import yaml from 'yaml';
5
- import { parse as parseJsonc } from 'jsonc-parser';
5
+ import {
6
+ getGlobalConfigDirectory, readConfigFile, isPlainObject, readConfigLayers,
7
+ readConfig, getConfigForPath, writeConfig, getJsonEntrySource, getJsonWriteTarget,
8
+ } from './config-files.js';
6
9
 
7
10
  // ============== PATH CONSTANTS ==============
8
11
 
9
- const OPENCODE_CONFIG_DIR = path.join(os.homedir(), '.config', 'opencode');
12
+ const OPENCODE_CONFIG_DIR = getGlobalConfigDirectory();
10
13
  const AGENT_DIR = path.join(OPENCODE_CONFIG_DIR, 'agents');
11
14
  const COMMAND_DIR = path.join(OPENCODE_CONFIG_DIR, 'commands');
12
15
  const SKILL_DIR = path.join(OPENCODE_CONFIG_DIR, 'skills');
13
- const CONFIG_FILE = path.join(OPENCODE_CONFIG_DIR, 'config.json');
14
- const CUSTOM_CONFIG_FILE = process.env.OPENCODE_CONFIG
15
- ? path.resolve(process.env.OPENCODE_CONFIG)
16
- : null;
16
+ const CONFIG_FILE = path.join(OPENCODE_CONFIG_DIR, 'opencode.jsonc');
17
17
  const PROMPT_FILE_PATTERN = /^\{file:(.+)\}$/i;
18
18
 
19
19
  // ============== SCOPE TYPE CONSTANTS ==============
@@ -89,175 +89,6 @@ function writeMdFile(filePath, frontmatter, body) {
89
89
 
90
90
  // ============== CONFIG FILE OPERATIONS ==============
91
91
 
92
- function getProjectConfigCandidates(workingDirectory) {
93
- if (!workingDirectory) return [];
94
- return [
95
- path.join(workingDirectory, 'opencode.json'),
96
- path.join(workingDirectory, 'opencode.jsonc'),
97
- path.join(workingDirectory, '.opencode', 'opencode.json'),
98
- path.join(workingDirectory, '.opencode', 'opencode.jsonc'),
99
- ];
100
- }
101
-
102
- function getProjectConfigPath(workingDirectory) {
103
- if (!workingDirectory) return null;
104
-
105
- const candidates = getProjectConfigCandidates(workingDirectory);
106
-
107
- for (const candidate of candidates) {
108
- if (fs.existsSync(candidate)) {
109
- return candidate;
110
- }
111
- }
112
-
113
- return candidates[0];
114
- }
115
-
116
- function getConfigPaths(workingDirectory) {
117
- return {
118
- userPaths: [
119
- path.join(OPENCODE_CONFIG_DIR, 'config.json'),
120
- path.join(OPENCODE_CONFIG_DIR, 'opencode.json'),
121
- path.join(OPENCODE_CONFIG_DIR, 'opencode.jsonc'),
122
- ],
123
- projectPath: getProjectConfigPath(workingDirectory),
124
- customPath: CUSTOM_CONFIG_FILE
125
- };
126
- }
127
-
128
- function getPrimaryUserConfigPath(userPaths) {
129
- for (const userPath of userPaths) {
130
- if (fs.existsSync(userPath)) {
131
- return userPath;
132
- }
133
- }
134
-
135
- return CONFIG_FILE;
136
- }
137
-
138
- function readConfigFile(filePath) {
139
- if (!filePath || !fs.existsSync(filePath)) {
140
- return {};
141
- }
142
- try {
143
- const content = fs.readFileSync(filePath, 'utf8');
144
- const normalized = content.trim();
145
- if (!normalized) {
146
- return {};
147
- }
148
- return parseJsonc(normalized, [], { allowTrailingComma: true });
149
- } catch (error) {
150
- console.error(`Failed to read config file: ${filePath}`, error);
151
- throw new Error('Failed to read OpenCode configuration');
152
- }
153
- }
154
-
155
- function isPlainObject(value) {
156
- return value && typeof value === 'object' && !Array.isArray(value);
157
- }
158
-
159
- function mergeConfigs(base, override) {
160
- if (!isPlainObject(base) || !isPlainObject(override)) {
161
- return override;
162
- }
163
- const result = { ...base };
164
- for (const [key, value] of Object.entries(override)) {
165
- if (key in result) {
166
- const baseValue = result[key];
167
- if (isPlainObject(baseValue) && isPlainObject(value)) {
168
- result[key] = mergeConfigs(baseValue, value);
169
- } else {
170
- result[key] = value;
171
- }
172
- } else {
173
- result[key] = value;
174
- }
175
- }
176
- return result;
177
- }
178
-
179
- function readConfigLayers(workingDirectory) {
180
- const { userPaths, projectPath, customPath } = getConfigPaths(workingDirectory);
181
- const userPath = getPrimaryUserConfigPath(userPaths);
182
- const userConfig = readConfigFile(userPath);
183
- const projectConfig = readConfigFile(projectPath);
184
- const customConfig = readConfigFile(customPath);
185
- const mergedConfig = mergeConfigs(mergeConfigs(userConfig, projectConfig), customConfig);
186
-
187
- return {
188
- userConfig,
189
- projectConfig,
190
- customConfig,
191
- mergedConfig,
192
- paths: { userPath, projectPath, customPath }
193
- };
194
- }
195
-
196
- function readConfig(workingDirectory) {
197
- return readConfigLayers(workingDirectory).mergedConfig;
198
- }
199
-
200
- function getConfigForPath(layers, targetPath) {
201
- if (!targetPath) {
202
- return layers.userConfig;
203
- }
204
- if (layers.paths.customPath && targetPath === layers.paths.customPath) {
205
- return layers.customConfig;
206
- }
207
- if (layers.paths.projectPath && targetPath === layers.paths.projectPath) {
208
- return layers.projectConfig;
209
- }
210
- return layers.userConfig;
211
- }
212
-
213
- function writeConfig(config, filePath = CONFIG_FILE) {
214
- try {
215
- if (fs.existsSync(filePath)) {
216
- const backupFile = `${filePath}.openchamber.backup`;
217
- fs.copyFileSync(filePath, backupFile);
218
- console.log(`Created config backup: ${backupFile}`);
219
- }
220
-
221
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
222
- fs.writeFileSync(filePath, JSON.stringify(config, null, 2), 'utf8');
223
- console.log(`Successfully wrote config file: ${filePath}`);
224
- } catch (error) {
225
- console.error(`Failed to write config file: ${filePath}`, error);
226
- throw new Error('Failed to write OpenCode configuration');
227
- }
228
- }
229
-
230
- function getJsonEntrySource(layers, sectionKey, entryName) {
231
- const { userConfig, projectConfig, customConfig, paths } = layers;
232
- const customSection = customConfig?.[sectionKey]?.[entryName];
233
- if (customSection !== undefined) {
234
- return { section: customSection, config: customConfig, path: paths.customPath, exists: true };
235
- }
236
-
237
- const projectSection = projectConfig?.[sectionKey]?.[entryName];
238
- if (projectSection !== undefined) {
239
- return { section: projectSection, config: projectConfig, path: paths.projectPath, exists: true };
240
- }
241
-
242
- const userSection = userConfig?.[sectionKey]?.[entryName];
243
- if (userSection !== undefined) {
244
- return { section: userSection, config: userConfig, path: paths.userPath, exists: true };
245
- }
246
-
247
- return { section: null, config: null, path: null, exists: false };
248
- }
249
-
250
- function getJsonWriteTarget(layers, preferredScope) {
251
- const { userConfig, projectConfig, customConfig, paths } = layers;
252
- if (paths.customPath) {
253
- return { config: customConfig, path: paths.customPath };
254
- }
255
- if (preferredScope === AGENT_SCOPE.PROJECT && paths.projectPath) {
256
- return { config: projectConfig, path: paths.projectPath };
257
- }
258
- return { config: userConfig, path: paths.userPath };
259
- }
260
-
261
92
  // ============== GIT/WORKTREE HELPERS ==============
262
93
 
263
94
  function getAncestors(startDir, stopDir) {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Startup upgrade screen for OpenCode below 2.0.15.
2
+ * Startup upgrade screen for OpenCode below the runtime contract minimum.
3
3
  *
4
4
  * This module does not own process lifecycle. It classifies the version the
5
5
  * existing startup path already detected, and installs through the owned-cache
@@ -9,6 +9,7 @@
9
9
 
10
10
  import {
11
11
  PINNED_OPENCODE2_VERSION,
12
+ MINIMUM_OPENCODE2_VERSION,
12
13
  isAcceptableOpenCode2HealthVersion,
13
14
  isOpenCode1xVersion,
14
15
  isOpenCode2VersionAtLeast,
@@ -16,7 +17,7 @@ import {
16
17
  } from './opencode2-pin.js';
17
18
 
18
19
  /** Oldest OpenCode this OpenChamber build will enter a session against. */
19
- export const REQUIRED_OPENCODE_VERSION = '2.0.15';
20
+ export const REQUIRED_OPENCODE_VERSION = MINIMUM_OPENCODE2_VERSION;
20
21
 
21
22
  const normalizeVersion = (value) => {
22
23
  if (typeof value !== 'string') return null;
@@ -68,14 +69,6 @@ export function platformSupportsUpgradeScreenInstall(platform = process.platform
68
69
  }
69
70
  }
70
71
 
71
- const pickDetectedVersion = (serveVersion, cliVersion) => {
72
- const serve = normalizeVersion(serveVersion);
73
- const cli = normalizeVersion(cliVersion);
74
- if (serve && !isUpgradeScreenSatisfied(serve)) return serve;
75
- if (serve) return serve;
76
- return cli;
77
- };
78
-
79
72
  /**
80
73
  * @param {{
81
74
  * version?: string | null,
@@ -156,7 +149,7 @@ async function readUpgradeScreenStatus(deps) {
156
149
  ? await Promise.resolve(deps.readCliVersion()).catch(() => null)
157
150
  : null;
158
151
  return describeUpgradeScreen({
159
- version: pickDetectedVersion(serveVersion, cliVersion),
152
+ version: serveVersion || (typeof deps.readServeVersion === 'function' ? null : cliVersion),
160
153
  installation,
161
154
  platformCanInstall: typeof deps.platformCanInstall === 'boolean'
162
155
  ? deps.platformCanInstall
@@ -11,26 +11,26 @@ import {
11
11
  } from './upgrade-screen.js';
12
12
 
13
13
  describe('upgrade screen version gate', () => {
14
- it('uses 2.0.15 even when the runtime pin is older', () => {
15
- expect(REQUIRED_OPENCODE_VERSION).toBe('2.0.15');
16
- expect(resolveUpgradeScreenTarget('2.0.12')).toBe('2.0.15');
17
- expect(resolveUpgradeScreenTarget('2.0.16')).toBe('2.0.16');
14
+ it('uses the floor even when the runtime pin is older', () => {
15
+ expect(REQUIRED_OPENCODE_VERSION).toBe('2.0.20');
16
+ expect(resolveUpgradeScreenTarget('2.0.12')).toBe('2.0.20');
17
+ expect(resolveUpgradeScreenTarget('2.0.23')).toBe('2.0.23');
18
18
  });
19
19
 
20
- it('does not open the gate at or above 2.0.15', () => {
20
+ it('does not open the gate at or above 2.0.20', () => {
21
21
  expect(describeUpgradeScreen({
22
- version: '2.0.15',
22
+ version: '2.0.20',
23
23
  installation: 'managed',
24
24
  platformCanInstall: true,
25
- })).toMatchObject({ state: 'compatible', canInstall: false, minimumVersion: '2.0.15' });
25
+ })).toMatchObject({ state: 'compatible', canInstall: false, minimumVersion: '2.0.20' });
26
26
  expect(describeUpgradeScreen({
27
- version: 'v2.0.16',
27
+ version: 'v2.0.23',
28
28
  installation: 'managed',
29
29
  platformCanInstall: true,
30
30
  }).state).toBe('compatible');
31
31
  });
32
32
 
33
- it('opens the gate below 2.0.15 and offers install only when this runtime can switch', () => {
33
+ it('opens the gate below 2.0.20 and offers install only when this runtime can switch', () => {
34
34
  expect(describeUpgradeScreen({
35
35
  version: '2.0.14',
36
36
  installation: 'managed',
@@ -68,17 +68,17 @@ describe('upgrade screen version gate', () => {
68
68
 
69
69
  it('does not report an unverified install as upgraded', () => {
70
70
  expect(evaluateUpgradeScreenInstallResult({
71
- targetVersion: '2.0.15',
71
+ targetVersion: '2.0.23',
72
72
  serveVersion: '2.0.14',
73
73
  })).toMatchObject({ ok: false, upgraded: false, version: '2.0.14' });
74
74
  expect(evaluateUpgradeScreenInstallResult({
75
- targetVersion: '2.0.15',
75
+ targetVersion: '2.0.23',
76
76
  serveVersion: null,
77
77
  }).upgraded).toBe(false);
78
78
  expect(evaluateUpgradeScreenInstallResult({
79
- targetVersion: '2.0.15',
80
- serveVersion: '2.0.15',
81
- })).toMatchObject({ ok: true, upgraded: true, version: '2.0.15' });
79
+ targetVersion: '2.0.23',
80
+ serveVersion: '2.0.23',
81
+ })).toMatchObject({ ok: true, upgraded: true, version: '2.0.23' });
82
82
  });
83
83
  });
84
84
 
@@ -94,7 +94,7 @@ describe('installRequiredOpenCode', () => {
94
94
  calls.push('install');
95
95
  return '/cache/opencode';
96
96
  }),
97
- readBinaryVersion: vi.fn(() => '2.0.15'),
97
+ readBinaryVersion: vi.fn(() => '2.0.23'),
98
98
  persistBinary: vi.fn(async () => {
99
99
  calls.push('persist');
100
100
  }),
@@ -114,20 +114,20 @@ describe('installRequiredOpenCode', () => {
114
114
  return { deps, calls };
115
115
  };
116
116
 
117
- it('installs 2.0.15 and only succeeds after the running serve is verified', async () => {
117
+ it('installs 2.0.23 and only succeeds after the running serve is verified', async () => {
118
118
  const { deps, calls } = createDeps({
119
119
  readServeVersion: vi.fn()
120
120
  .mockResolvedValueOnce('2.0.14')
121
- .mockResolvedValueOnce('2.0.15'),
121
+ .mockResolvedValueOnce('2.0.23'),
122
122
  });
123
123
 
124
124
  await expect(installRequiredOpenCode(deps)).resolves.toMatchObject({
125
125
  ok: true,
126
126
  upgraded: true,
127
- version: '2.0.15',
128
- targetVersion: '2.0.15',
127
+ version: '2.0.23',
128
+ targetVersion: '2.0.23',
129
129
  });
130
- expect(deps.install).toHaveBeenCalledWith({ version: '2.0.15' });
130
+ expect(deps.install).toHaveBeenCalledWith({ version: '2.0.23' });
131
131
  expect(calls).toEqual(['install', 'persist', 'force', 'restart', 'ready']);
132
132
  });
133
133
 
@@ -137,7 +137,7 @@ describe('installRequiredOpenCode', () => {
137
137
  .mockResolvedValueOnce('1.18.30')
138
138
  .mockResolvedValueOnce('1.18.30'),
139
139
  readCliVersion: () => '1.18.30',
140
- readBinaryVersion: () => '2.0.15',
140
+ readBinaryVersion: () => '2.0.23',
141
141
  });
142
142
 
143
143
  await expect(installRequiredOpenCode(deps)).rejects.toThrow(/still 1\.18\.30/);
@@ -161,11 +161,11 @@ describe('installRequiredOpenCode', () => {
161
161
  });
162
162
 
163
163
  describe('upgrade screen routes', () => {
164
- it('hides the gate for 2.0.15 and reports install failure without upgraded:true', async () => {
164
+ it('hides the gate for 2.0.20 and reports install failure without upgraded:true', async () => {
165
165
  const app = express();
166
166
  app.use(express.json());
167
167
  const readServeVersion = vi.fn()
168
- .mockResolvedValueOnce('2.0.15')
168
+ .mockResolvedValueOnce('2.0.20')
169
169
  .mockResolvedValueOnce('2.0.12')
170
170
  .mockResolvedValueOnce('2.0.12');
171
171
  registerUpgradeScreenRoutes(app, {
@@ -176,14 +176,14 @@ describe('upgrade screen routes', () => {
176
176
  install: vi.fn(async () => {
177
177
  throw new Error('registry unreachable');
178
178
  }),
179
- readBinaryVersion: () => '2.0.15',
179
+ readBinaryVersion: () => '2.0.23',
180
180
  persistBinary: vi.fn(),
181
181
  forceBinary: vi.fn(),
182
182
  restart: vi.fn(),
183
183
  });
184
184
 
185
185
  const compatible = await request(app).get('/api/opencode/compatibility').expect(200);
186
- expect(compatible.body).toMatchObject({ state: 'compatible', version: '2.0.15', minimumVersion: '2.0.15' });
186
+ expect(compatible.body).toMatchObject({ state: 'compatible', version: '2.0.20', minimumVersion: '2.0.20' });
187
187
 
188
188
  const failed = await request(app).post('/api/opencode/install-required').expect(500);
189
189
  expect(failed.body).toMatchObject({
@@ -0,0 +1,40 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { MINIMUM_OPENCODE2_VERSION, PINNED_OPENCODE2_VERSION } from './opencode2-pin.js';
3
+ import { evaluateRuntimeContract } from './runtime-contract.js';
4
+ import { describeUpgradeScreen, installRequiredOpenCode, resolveUpgradeScreenTarget } from './upgrade-screen.js';
5
+ import { ensurePinnedOpenCode2Cli } from './ensure-cli.js';
6
+
7
+ describe('v2 version alignment', () => {
8
+ it.each(['1.18.30', '2.0.19', null, 'unknown'])('blocks execution and startup for %s', (version) => {
9
+ expect(evaluateRuntimeContract({ serveVersion: version, cliVersion: '2.0.23', reachable: true, authenticated: true }).executionAllowed).toBe(false);
10
+ expect(describeUpgradeScreen({ version }).state).not.toBe('compatible');
11
+ });
12
+ it.each(['2.0.20', '2.0.23', '2.1.0'])('admits running %s independently of CLI pin', (version) => {
13
+ expect(evaluateRuntimeContract({ serveVersion: version, cliVersion: '2.0.15', reachable: true, authenticated: true })).toMatchObject({ executionAllowed: true, versionMismatch: true, minVerifiedVersion: '2.0.20' });
14
+ expect(describeUpgradeScreen({ version }).state).toBe('compatible');
15
+ });
16
+ it('installs the audited pin on first install and preserves newer discovered CLIs', async () => {
17
+ expect(MINIMUM_OPENCODE2_VERSION).toBe('2.0.20');
18
+ expect(PINNED_OPENCODE2_VERSION).toBe('2.0.23');
19
+ expect(resolveUpgradeScreenTarget()).toBe('2.0.23');
20
+ const install = vi.fn(async () => '/owned/opencode');
21
+ await expect(ensurePinnedOpenCode2Cli({ readVersion: (path) => path === '/owned/opencode' ? '2.0.23' : '', install })).resolves.toMatchObject({ version: '2.0.23', installed: true });
22
+ expect(install).toHaveBeenCalledWith(expect.objectContaining({ version: '2.0.23' }));
23
+ install.mockClear();
24
+ await expect(ensurePinnedOpenCode2Cli({ discoveredPath: '/global/opencode', readVersion: () => '2.1.0', install })).resolves.toMatchObject({ version: '2.1.0', installed: false });
25
+ expect(install).not.toHaveBeenCalled();
26
+ });
27
+ it('does not infer a successful installation from an unreadable binary', async () => {
28
+ await expect(ensurePinnedOpenCode2Cli({ readVersion: () => '', install: async () => '/owned/opencode' })).rejects.toThrow('got unknown');
29
+ });
30
+ it('verifies the live service after explicit install, and rolls selection back on failure', async () => {
31
+ const persistBinary = vi.fn();
32
+ const restart = vi.fn();
33
+ const deps = { resolveOwnership: async () => ({ ownership: 'shared-service' }), readServeVersion: vi.fn().mockResolvedValueOnce('2.0.19').mockResolvedValueOnce('2.0.23'), readCliVersion: () => '2.0.23', platformCanInstall: true, install: async () => '/owned/opencode', readBinaryVersion: () => '2.0.23', persistBinary, restart, isSharedService: () => true };
34
+ await expect(installRequiredOpenCode(deps)).resolves.toMatchObject({ upgraded: true, version: '2.0.23' });
35
+ expect(restart).toHaveBeenCalledWith({ binaryPath: '/owned/opencode' });
36
+ deps.readServeVersion.mockResolvedValue('2.0.19');
37
+ await expect(installRequiredOpenCode(deps)).rejects.toMatchObject({ code: 'UPGRADE_SCREEN_NOT_VERIFIED' });
38
+ expect(persistBinary).toHaveBeenLastCalledWith('');
39
+ });
40
+ });
@@ -38,6 +38,15 @@ These provider IDs are currently dispatchable via `fetchQuotaForProvider(provide
38
38
  - `providers/openai.js` exists for logic parity/reuse but is intentionally not registered for dispatcher ID routing.
39
39
 
40
40
  ## Response contract
41
+ Provider credentials are read asynchronously from the current OpenCode service via
42
+ `opencode/auth.js`; only the selected credential is used. Account switching and
43
+ logout are observed on the next request, without file fallback or persisted secret
44
+ cache. Credential-list transport/auth/schema failure rejects the configured-provider
45
+ list instead of returning an authoritative empty list; per-provider quota requests
46
+ return `ok: false` with a sanitized error. VS Code uses the same credential adapter.
47
+ OpenChamber-owned quota cookies and Grok/Antigravity-specific stores retain their
48
+ separate explicit sources; they are not legacy OpenCode `auth.json` fallbacks.
49
+
41
50
  All providers should return results via shared helpers to preserve API shape:
42
51
  - Required fields: `providerId`, `providerName`, `ok`, `configured`, `usage`, `fetchedAt`
43
52
  - Optional field: `error`
@@ -0,0 +1,29 @@
1
+ import { afterEach, expect, it, vi } from 'vitest';
2
+ import { configureOpenCodeCredentials } from '../opencode/auth.js';
3
+ import { fetchQuota as fetchCodexQuota } from './providers/codex.js';
4
+ import { fetchQuotaForProvider, listConfiguredQuotaProviders } from './providers/index.js';
5
+
6
+ afterEach(() => { configureOpenCodeCredentials(null); vi.unstubAllGlobals(); });
7
+
8
+ it('quota requests follow the current active account, then stop sending after logout', async () => {
9
+ let access = 'first-account';
10
+ configureOpenCodeCredentials(() => ({ credential: { list: async () => access ? [{ id: 'selected', integrationID: 'openai', active: true, value: { type: 'oauth', access, refresh: 'refresh', expires: 123 } }] : [] } }));
11
+ const fetchMock = vi.fn(async () => Response.json({ rate_limit: { primary_window: { used_percent: 20 } } }));
12
+ vi.stubGlobal('fetch', fetchMock);
13
+ await fetchCodexQuota();
14
+ expect(fetchMock.mock.calls[0][1].headers.Authorization).toBe('Bearer first-account');
15
+ access = 'second-account';
16
+ await fetchCodexQuota();
17
+ expect(fetchMock.mock.calls[1][1].headers.Authorization).toBe('Bearer second-account');
18
+ access = '';
19
+ await expect(fetchCodexQuota()).resolves.toMatchObject({ ok: false, configured: false });
20
+ expect(fetchMock).toHaveBeenCalledTimes(2);
21
+ });
22
+
23
+ it('quota source failure remains an error with no upstream secret in the response', async () => {
24
+ configureOpenCodeCredentials(() => ({ credential: { list: async () => { throw new Error('secret-from-upstream'); } } }));
25
+ await expect(listConfiguredQuotaProviders()).rejects.toThrow('Unable to read OpenCode credentials');
26
+ const result = await fetchQuotaForProvider('codex');
27
+ expect(result).toMatchObject({ ok: false, error: 'Unable to read OpenCode credentials' });
28
+ expect(JSON.stringify(result)).not.toContain('secret-from-upstream');
29
+ });
@@ -1,4 +1,4 @@
1
- import { readAuthFile } from '../../opencode/auth.js';
1
+ import { readOpenCodeCredentials } from '../../opencode/auth.js';
2
2
  import {
3
3
  getAuthEntry,
4
4
  normalizeAuthEntry,
@@ -12,14 +12,14 @@ export const providerId = 'claude';
12
12
  export const providerName = 'Claude';
13
13
  const aliases = ['anthropic', 'claude'];
14
14
 
15
- export const isConfigured = () => {
16
- const auth = readAuthFile();
15
+ export const isConfigured = async () => {
16
+ const auth = await readOpenCodeCredentials();
17
17
  const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
18
18
  return Boolean(entry?.access || entry?.token);
19
19
  };
20
20
 
21
21
  export const fetchQuota = async () => {
22
- const auth = readAuthFile();
22
+ const auth = await readOpenCodeCredentials();
23
23
  const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
24
24
  const accessToken = entry?.access ?? entry?.token;
25
25
 
@@ -1,4 +1,4 @@
1
- import { readAuthFile } from '../../opencode/auth.js';
1
+ import { readOpenCodeCredentials } from '../../opencode/auth.js';
2
2
  import {
3
3
  getAuthEntry,
4
4
  normalizeAuthEntry,
@@ -14,14 +14,14 @@ export const providerId = 'codex';
14
14
  export const providerName = 'Codex';
15
15
  const aliases = ['openai', 'codex', 'chatgpt'];
16
16
 
17
- export const isConfigured = () => {
18
- const auth = readAuthFile();
17
+ export const isConfigured = async () => {
18
+ const auth = await readOpenCodeCredentials();
19
19
  const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
20
20
  return Boolean(entry?.access || entry?.token);
21
21
  };
22
22
 
23
23
  export const fetchQuota = async () => {
24
- const auth = readAuthFile();
24
+ const auth = await readOpenCodeCredentials();
25
25
  const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
26
26
  const accessToken = entry?.access ?? entry?.token;
27
27
  const accountId = entry?.accountId;
@@ -1,7 +1,7 @@
1
1
  import { afterEach, describe, expect, it, vi } from 'vitest';
2
2
 
3
3
  vi.mock('../../opencode/auth.js', () => ({
4
- readAuthFile: () => ({ openai: { access: 'test-token' } }),
4
+ readOpenCodeCredentials: async () => ({ openai: { access: 'test-token' } }),
5
5
  }));
6
6
 
7
7
  import { fetchQuota } from './codex.js';
@@ -1,4 +1,4 @@
1
- import { readAuthFile } from '../../opencode/auth.js';
1
+ import { readOpenCodeCredentials } from '../../opencode/auth.js';
2
2
  import {
3
3
  getAuthEntry,
4
4
  normalizeAuthEntry,
@@ -42,14 +42,14 @@ export const providerId = 'github-copilot';
42
42
  export const providerName = 'GitHub Copilot';
43
43
  const aliases = ['github-copilot', 'copilot'];
44
44
 
45
- export const isConfigured = () => {
46
- const auth = readAuthFile();
45
+ export const isConfigured = async () => {
46
+ const auth = await readOpenCodeCredentials();
47
47
  const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
48
48
  return Boolean(entry?.access || entry?.token);
49
49
  };
50
50
 
51
51
  export const fetchQuota = async () => {
52
- const auth = readAuthFile();
52
+ const auth = await readOpenCodeCredentials();
53
53
  const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
54
54
  const accessToken = entry?.access ?? entry?.token;
55
55
 
@@ -107,7 +107,7 @@ export const providerIdAddon = 'github-copilot-addon';
107
107
  export const providerNameAddon = 'GitHub Copilot Add-on';
108
108
 
109
109
  export const fetchQuotaAddon = async () => {
110
- const auth = readAuthFile();
110
+ const auth = await readOpenCodeCredentials();
111
111
  const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
112
112
  const accessToken = entry?.access ?? entry?.token;
113
113