@google/gemini-cli 0.24.0-preview.0 → 0.25.0-nightly.20260112.15891721a

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 (299) hide show
  1. package/README.md +1 -1
  2. package/dist/google-gemini-cli-0.25.0-nightly.20260107.59a18e710.tgz +0 -0
  3. package/dist/package.json +3 -3
  4. package/dist/src/commands/extensions/configure.d.ts +13 -0
  5. package/dist/src/commands/extensions/configure.js +124 -0
  6. package/dist/src/commands/extensions/configure.js.map +1 -0
  7. package/dist/src/commands/extensions/configure.test.d.ts +1 -0
  8. package/dist/src/commands/extensions/configure.test.js +197 -0
  9. package/dist/src/commands/extensions/configure.test.js.map +1 -0
  10. package/dist/src/commands/extensions/examples/hooks/gemini-extension.json +4 -0
  11. package/dist/src/commands/extensions/examples/hooks/hooks/hooks.json +14 -0
  12. package/dist/src/commands/extensions/examples/hooks/scripts/on-start.js +8 -0
  13. package/dist/src/commands/extensions/examples/mcp-server/README.md +35 -0
  14. package/dist/src/commands/extensions/examples/mcp-server/example.js +36 -22
  15. package/dist/src/commands/extensions/examples/mcp-server/gemini-extension.json +1 -1
  16. package/dist/src/commands/extensions/examples/mcp-server/package.json +0 -7
  17. package/dist/src/commands/extensions/examples/skills/gemini-extension.json +4 -0
  18. package/dist/src/commands/extensions/examples/skills/skills/greeter/SKILL.md +7 -0
  19. package/dist/src/commands/extensions/utils.d.ts +1 -0
  20. package/dist/src/commands/extensions/utils.js +5 -1
  21. package/dist/src/commands/extensions/utils.js.map +1 -1
  22. package/dist/src/commands/extensions.js +2 -2
  23. package/dist/src/commands/extensions.js.map +1 -1
  24. package/dist/src/commands/skills/disable.d.ts +2 -2
  25. package/dist/src/commands/skills/disable.js +8 -10
  26. package/dist/src/commands/skills/disable.js.map +1 -1
  27. package/dist/src/commands/skills/disable.test.js +7 -7
  28. package/dist/src/commands/skills/disable.test.js.map +1 -1
  29. package/dist/src/commands/skills/enable.d.ts +0 -2
  30. package/dist/src/commands/skills/enable.js +9 -21
  31. package/dist/src/commands/skills/enable.js.map +1 -1
  32. package/dist/src/commands/skills/enable.test.js +39 -12
  33. package/dist/src/commands/skills/enable.test.js.map +1 -1
  34. package/dist/src/commands/skills/list.d.ts +3 -1
  35. package/dist/src/commands/skills/list.js +20 -6
  36. package/dist/src/commands/skills/list.js.map +1 -1
  37. package/dist/src/commands/skills/list.test.js +37 -3
  38. package/dist/src/commands/skills/list.test.js.map +1 -1
  39. package/dist/src/config/config.js +6 -4
  40. package/dist/src/config/config.js.map +1 -1
  41. package/dist/src/config/config.test.js +33 -0
  42. package/dist/src/config/config.test.js.map +1 -1
  43. package/dist/src/config/extension-manager-scope.test.js +144 -0
  44. package/dist/src/config/extension-manager-scope.test.js.map +1 -0
  45. package/dist/src/config/extension-manager-skills.test.js +91 -43
  46. package/dist/src/config/extension-manager-skills.test.js.map +1 -1
  47. package/dist/src/config/extension-manager.js +57 -15
  48. package/dist/src/config/extension-manager.js.map +1 -1
  49. package/dist/src/config/extension.test.js +65 -0
  50. package/dist/src/config/extension.test.js.map +1 -1
  51. package/dist/src/config/extensions/extensionSettings.d.ts +5 -4
  52. package/dist/src/config/extensions/extensionSettings.js +20 -14
  53. package/dist/src/config/extensions/extensionSettings.js.map +1 -1
  54. package/dist/src/config/extensions/extensionSettings.test.js +8 -8
  55. package/dist/src/config/extensions/extensionSettings.test.js.map +1 -1
  56. package/dist/src/config/extensions/extensionUpdates.test.js +5 -21
  57. package/dist/src/config/extensions/extensionUpdates.test.js.map +1 -1
  58. package/dist/src/config/extensions/storage.js +2 -2
  59. package/dist/src/config/extensions/storage.js.map +1 -1
  60. package/dist/src/config/settings.d.ts +4 -6
  61. package/dist/src/config/settings.js +42 -238
  62. package/dist/src/config/settings.js.map +1 -1
  63. package/dist/src/config/settings.test.js +148 -684
  64. package/dist/src/config/settings.test.js.map +1 -1
  65. package/dist/src/config/settingsSchema.d.ts +20 -10
  66. package/dist/src/config/settingsSchema.js +23 -11
  67. package/dist/src/config/settingsSchema.js.map +1 -1
  68. package/dist/src/config/settings_validation_warning.test.js +10 -5
  69. package/dist/src/config/settings_validation_warning.test.js.map +1 -1
  70. package/dist/src/config/trustedFolders.js +1 -2
  71. package/dist/src/config/trustedFolders.js.map +1 -1
  72. package/dist/src/config/trustedFolders.test.js +7 -0
  73. package/dist/src/config/trustedFolders.test.js.map +1 -1
  74. package/dist/src/gemini.js +60 -66
  75. package/dist/src/gemini.js.map +1 -1
  76. package/dist/src/gemini.test.js +36 -83
  77. package/dist/src/gemini.test.js.map +1 -1
  78. package/dist/src/gemini_cleanup.test.js +2 -0
  79. package/dist/src/gemini_cleanup.test.js.map +1 -1
  80. package/dist/src/generated/git-commit.d.ts +2 -2
  81. package/dist/src/generated/git-commit.js +2 -2
  82. package/dist/src/generated/git-commit.js.map +1 -1
  83. package/dist/src/nonInteractiveCli.js +2 -2
  84. package/dist/src/nonInteractiveCli.js.map +1 -1
  85. package/dist/src/nonInteractiveCli.test.js +2 -1
  86. package/dist/src/nonInteractiveCli.test.js.map +1 -1
  87. package/dist/src/services/BuiltinCommandLoader.js +29 -6
  88. package/dist/src/services/BuiltinCommandLoader.js.map +1 -1
  89. package/dist/src/services/BuiltinCommandLoader.test.js +55 -1
  90. package/dist/src/services/BuiltinCommandLoader.test.js.map +1 -1
  91. package/dist/src/services/prompt-processors/shellProcessor.js +13 -11
  92. package/dist/src/services/prompt-processors/shellProcessor.js.map +1 -1
  93. package/dist/src/services/prompt-processors/shellProcessor.test.js +93 -61
  94. package/dist/src/services/prompt-processors/shellProcessor.test.js.map +1 -1
  95. package/dist/src/test-utils/render.js +2 -2
  96. package/dist/src/test-utils/render.js.map +1 -1
  97. package/dist/src/ui/App.test.js +2 -2
  98. package/dist/src/ui/App.test.js.map +1 -1
  99. package/dist/src/ui/AppContainer.js +22 -30
  100. package/dist/src/ui/AppContainer.js.map +1 -1
  101. package/dist/src/ui/commands/agentsCommand.d.ts +7 -0
  102. package/dist/src/ui/commands/agentsCommand.js +75 -0
  103. package/dist/src/ui/commands/agentsCommand.js.map +1 -0
  104. package/dist/src/ui/commands/agentsCommand.test.js +91 -0
  105. package/dist/src/ui/commands/agentsCommand.test.js.map +1 -0
  106. package/dist/src/ui/commands/bugCommand.js +27 -4
  107. package/dist/src/ui/commands/bugCommand.js.map +1 -1
  108. package/dist/src/ui/commands/bugCommand.test.js +70 -2
  109. package/dist/src/ui/commands/bugCommand.test.js.map +1 -1
  110. package/dist/src/ui/commands/chatCommand.d.ts +1 -2
  111. package/dist/src/ui/commands/chatCommand.js +38 -30
  112. package/dist/src/ui/commands/chatCommand.js.map +1 -1
  113. package/dist/src/ui/commands/chatCommand.test.js +79 -52
  114. package/dist/src/ui/commands/chatCommand.test.js.map +1 -1
  115. package/dist/src/ui/commands/clearCommand.js +7 -11
  116. package/dist/src/ui/commands/clearCommand.js.map +1 -1
  117. package/dist/src/ui/commands/clearCommand.test.js +4 -0
  118. package/dist/src/ui/commands/clearCommand.test.js.map +1 -1
  119. package/dist/src/ui/commands/extensionsCommand.js +70 -2
  120. package/dist/src/ui/commands/extensionsCommand.js.map +1 -1
  121. package/dist/src/ui/commands/extensionsCommand.test.js +70 -3
  122. package/dist/src/ui/commands/extensionsCommand.test.js.map +1 -1
  123. package/dist/src/ui/commands/hooksCommand.js +153 -18
  124. package/dist/src/ui/commands/hooksCommand.js.map +1 -1
  125. package/dist/src/ui/commands/hooksCommand.test.js +186 -15
  126. package/dist/src/ui/commands/hooksCommand.test.js.map +1 -1
  127. package/dist/src/ui/commands/skillsCommand.js +27 -29
  128. package/dist/src/ui/commands/skillsCommand.js.map +1 -1
  129. package/dist/src/ui/commands/skillsCommand.test.js +84 -2
  130. package/dist/src/ui/commands/skillsCommand.test.js.map +1 -1
  131. package/dist/src/ui/commands/types.d.ts +2 -1
  132. package/dist/src/ui/commands/types.js +1 -0
  133. package/dist/src/ui/commands/types.js.map +1 -1
  134. package/dist/src/ui/components/Composer.test.js +1 -0
  135. package/dist/src/ui/components/Composer.test.js.map +1 -1
  136. package/dist/src/ui/components/DialogManager.js +1 -1
  137. package/dist/src/ui/components/DialogManager.js.map +1 -1
  138. package/dist/src/ui/components/HistoryItemDisplay.js +2 -1
  139. package/dist/src/ui/components/HistoryItemDisplay.js.map +1 -1
  140. package/dist/src/ui/components/HistoryItemDisplay.test.js +21 -0
  141. package/dist/src/ui/components/HistoryItemDisplay.test.js.map +1 -1
  142. package/dist/src/ui/components/InputPrompt.test.js +0 -2
  143. package/dist/src/ui/components/InputPrompt.test.js.map +1 -1
  144. package/dist/src/ui/components/MainContent.js +6 -1
  145. package/dist/src/ui/components/MainContent.js.map +1 -1
  146. package/dist/src/ui/components/Notifications.js +2 -3
  147. package/dist/src/ui/components/Notifications.js.map +1 -1
  148. package/dist/src/ui/components/Notifications.test.js +1 -0
  149. package/dist/src/ui/components/Notifications.test.js.map +1 -1
  150. package/dist/src/ui/components/ProQuotaDialog.d.ts +1 -3
  151. package/dist/src/ui/components/ProQuotaDialog.js +8 -21
  152. package/dist/src/ui/components/ProQuotaDialog.js.map +1 -1
  153. package/dist/src/ui/components/ProQuotaDialog.test.js +31 -11
  154. package/dist/src/ui/components/ProQuotaDialog.test.js.map +1 -1
  155. package/dist/src/ui/components/SettingsDialog.test.js +1 -3
  156. package/dist/src/ui/components/SettingsDialog.test.js.map +1 -1
  157. package/dist/src/ui/components/StatusDisplay.js +2 -2
  158. package/dist/src/ui/components/StatusDisplay.js.map +1 -1
  159. package/dist/src/ui/components/StatusDisplay.test.js +1 -0
  160. package/dist/src/ui/components/StatusDisplay.test.js.map +1 -1
  161. package/dist/src/ui/components/SuggestionsDisplay.js +7 -2
  162. package/dist/src/ui/components/SuggestionsDisplay.js.map +1 -1
  163. package/dist/src/ui/components/ThemeDialog.test.js +1 -1
  164. package/dist/src/ui/components/ThemeDialog.test.js.map +1 -1
  165. package/dist/src/ui/components/messages/ToolMessage.test.js +1 -0
  166. package/dist/src/ui/components/messages/ToolMessage.test.js.map +1 -1
  167. package/dist/src/ui/components/views/AgentsStatus.d.ts +13 -0
  168. package/dist/src/ui/components/views/AgentsStatus.js +23 -0
  169. package/dist/src/ui/components/views/AgentsStatus.js.map +1 -0
  170. package/dist/src/ui/components/views/ExtensionsList.js +2 -1
  171. package/dist/src/ui/components/views/ExtensionsList.js.map +1 -1
  172. package/dist/src/ui/components/views/ExtensionsList.test.js +12 -1
  173. package/dist/src/ui/components/views/ExtensionsList.test.js.map +1 -1
  174. package/dist/src/ui/components/views/HooksList.js +25 -16
  175. package/dist/src/ui/components/views/HooksList.js.map +1 -1
  176. package/dist/src/ui/components/views/SkillsList.js +9 -7
  177. package/dist/src/ui/components/views/SkillsList.js.map +1 -1
  178. package/dist/src/ui/components/views/SkillsList.test.js +15 -0
  179. package/dist/src/ui/components/views/SkillsList.test.js.map +1 -1
  180. package/dist/src/ui/contexts/KeypressContext.js +5 -28
  181. package/dist/src/ui/contexts/KeypressContext.js.map +1 -1
  182. package/dist/src/ui/contexts/KeypressContext.test.js +28 -8
  183. package/dist/src/ui/contexts/KeypressContext.test.js.map +1 -1
  184. package/dist/src/ui/hooks/atCommandProcessor.js +15 -1
  185. package/dist/src/ui/hooks/atCommandProcessor.js.map +1 -1
  186. package/dist/src/ui/hooks/atCommandProcessor_agents.test.js +183 -0
  187. package/dist/src/ui/hooks/atCommandProcessor_agents.test.js.map +1 -0
  188. package/dist/src/ui/hooks/slashCommandProcessor.test.js +1 -0
  189. package/dist/src/ui/hooks/slashCommandProcessor.test.js.map +1 -1
  190. package/dist/src/ui/hooks/useAtCompletion.js +31 -0
  191. package/dist/src/ui/hooks/useAtCompletion.js.map +1 -1
  192. package/dist/src/ui/hooks/useAtCompletion_agents.test.d.ts +6 -0
  193. package/dist/src/ui/hooks/useAtCompletion_agents.test.js +85 -0
  194. package/dist/src/ui/hooks/useAtCompletion_agents.test.js.map +1 -0
  195. package/dist/src/ui/hooks/useCommandCompletion.js +12 -8
  196. package/dist/src/ui/hooks/useCommandCompletion.js.map +1 -1
  197. package/dist/src/ui/hooks/useCommandCompletion.test.js +81 -0
  198. package/dist/src/ui/hooks/useCommandCompletion.test.js.map +1 -1
  199. package/dist/src/ui/hooks/useExtensionUpdates.test.js +7 -0
  200. package/dist/src/ui/hooks/useExtensionUpdates.test.js.map +1 -1
  201. package/dist/src/ui/hooks/useGeminiStream.js +6 -6
  202. package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
  203. package/dist/src/ui/hooks/useGeminiStream.test.js +45 -2
  204. package/dist/src/ui/hooks/useGeminiStream.test.js.map +1 -1
  205. package/dist/src/ui/hooks/usePermissionsModifyTrust.test.js +10 -3
  206. package/dist/src/ui/hooks/usePermissionsModifyTrust.test.js.map +1 -1
  207. package/dist/src/ui/hooks/useQuotaAndFallback.js +15 -10
  208. package/dist/src/ui/hooks/useQuotaAndFallback.js.map +1 -1
  209. package/dist/src/ui/hooks/useQuotaAndFallback.test.js +13 -8
  210. package/dist/src/ui/hooks/useQuotaAndFallback.test.js.map +1 -1
  211. package/dist/src/ui/hooks/useRewind.d.ts +14 -0
  212. package/dist/src/ui/hooks/useRewind.js +31 -0
  213. package/dist/src/ui/hooks/useRewind.js.map +1 -0
  214. package/dist/src/ui/hooks/useRewind.test.d.ts +6 -0
  215. package/dist/src/ui/hooks/useRewind.test.js +100 -0
  216. package/dist/src/ui/hooks/useRewind.test.js.map +1 -0
  217. package/dist/src/ui/hooks/useToolScheduler.test.js +10 -2
  218. package/dist/src/ui/hooks/useToolScheduler.test.js.map +1 -1
  219. package/dist/src/ui/themes/theme-manager.js +2 -3
  220. package/dist/src/ui/themes/theme-manager.js.map +1 -1
  221. package/dist/src/ui/themes/theme-manager.test.js +7 -0
  222. package/dist/src/ui/themes/theme-manager.test.js.map +1 -1
  223. package/dist/src/ui/types.d.ts +8 -2
  224. package/dist/src/ui/types.js +1 -0
  225. package/dist/src/ui/types.js.map +1 -1
  226. package/dist/src/ui/utils/CodeColorizer.test.js +1 -1
  227. package/dist/src/ui/utils/CodeColorizer.test.js.map +1 -1
  228. package/dist/src/ui/utils/MarkdownDisplay.test.js +1 -1
  229. package/dist/src/ui/utils/MarkdownDisplay.test.js.map +1 -1
  230. package/dist/src/ui/utils/directoryUtils.js +4 -4
  231. package/dist/src/ui/utils/directoryUtils.js.map +1 -1
  232. package/dist/src/ui/utils/directoryUtils.test.js +1 -0
  233. package/dist/src/ui/utils/directoryUtils.test.js.map +1 -1
  234. package/dist/src/ui/utils/historyExportUtils.d.ts +21 -0
  235. package/dist/src/ui/utils/historyExportUtils.js +59 -0
  236. package/dist/src/ui/utils/historyExportUtils.js.map +1 -0
  237. package/dist/src/ui/utils/rewindFileOps.d.ts +47 -0
  238. package/dist/src/ui/utils/rewindFileOps.js +190 -0
  239. package/dist/src/ui/utils/rewindFileOps.js.map +1 -0
  240. package/dist/src/ui/utils/rewindFileOps.test.d.ts +6 -0
  241. package/dist/src/ui/utils/rewindFileOps.test.js +375 -0
  242. package/dist/src/ui/utils/rewindFileOps.test.js.map +1 -0
  243. package/dist/src/ui/utils/terminalCapabilityManager.d.ts +3 -10
  244. package/dist/src/ui/utils/terminalCapabilityManager.js +11 -35
  245. package/dist/src/ui/utils/terminalCapabilityManager.js.map +1 -1
  246. package/dist/src/ui/utils/terminalCapabilityManager.test.js +13 -34
  247. package/dist/src/ui/utils/terminalCapabilityManager.test.js.map +1 -1
  248. package/dist/src/ui/utils/terminalSetup.d.ts +1 -1
  249. package/dist/src/ui/utils/terminalSetup.js +19 -4
  250. package/dist/src/ui/utils/terminalSetup.js.map +1 -1
  251. package/dist/src/ui/utils/terminalSetup.test.js +7 -0
  252. package/dist/src/ui/utils/terminalSetup.test.js.map +1 -1
  253. package/dist/src/ui/utils/textUtils.js +9 -1
  254. package/dist/src/ui/utils/textUtils.js.map +1 -1
  255. package/dist/src/ui/utils/textUtils.test.js +12 -1
  256. package/dist/src/ui/utils/textUtils.test.js.map +1 -1
  257. package/dist/src/utils/activityLogger.d.ts +47 -0
  258. package/dist/src/utils/activityLogger.js +297 -0
  259. package/dist/src/utils/activityLogger.js.map +1 -0
  260. package/dist/src/utils/cleanup.d.ts +5 -0
  261. package/dist/src/utils/cleanup.js +17 -0
  262. package/dist/src/utils/cleanup.js.map +1 -1
  263. package/dist/src/utils/cleanup.test.js +21 -31
  264. package/dist/src/utils/cleanup.test.js.map +1 -1
  265. package/dist/src/utils/resolvePath.js +3 -3
  266. package/dist/src/utils/resolvePath.js.map +1 -1
  267. package/dist/src/utils/resolvePath.test.js +3 -0
  268. package/dist/src/utils/resolvePath.test.js.map +1 -1
  269. package/dist/src/utils/sandbox.js +17 -10
  270. package/dist/src/utils/sandbox.js.map +1 -1
  271. package/dist/src/utils/sandbox.test.js +17 -5
  272. package/dist/src/utils/sandbox.test.js.map +1 -1
  273. package/dist/src/utils/skillSettings.d.ts +33 -0
  274. package/dist/src/utils/skillSettings.js +101 -0
  275. package/dist/src/utils/skillSettings.js.map +1 -0
  276. package/dist/src/utils/skillUtils.d.ts +16 -0
  277. package/dist/src/utils/skillUtils.js +51 -0
  278. package/dist/src/utils/skillUtils.js.map +1 -0
  279. package/dist/src/utils/userStartupWarnings.d.ts +2 -1
  280. package/dist/src/utils/userStartupWarnings.js +17 -7
  281. package/dist/src/utils/userStartupWarnings.js.map +1 -1
  282. package/dist/src/utils/userStartupWarnings.test.js +37 -6
  283. package/dist/src/utils/userStartupWarnings.test.js.map +1 -1
  284. package/dist/tsconfig.tsbuildinfo +1 -1
  285. package/package.json +4 -4
  286. package/dist/src/commands/extensions/examples/mcp-server/example.js.map +0 -1
  287. package/dist/src/commands/extensions/examples/mcp-server/example.test.js +0 -111
  288. package/dist/src/commands/extensions/examples/mcp-server/example.test.js.map +0 -1
  289. package/dist/src/commands/extensions/examples/mcp-server/example.test.ts +0 -135
  290. package/dist/src/commands/extensions/examples/mcp-server/example.ts +0 -60
  291. package/dist/src/commands/extensions/examples/mcp-server/tsconfig.json +0 -13
  292. package/dist/src/commands/extensions/settings.d.ts +0 -7
  293. package/dist/src/commands/extensions/settings.js +0 -115
  294. package/dist/src/commands/extensions/settings.js.map +0 -1
  295. package/dist/src/commands/extensions/settings.test.js +0 -170
  296. package/dist/src/commands/extensions/settings.test.js.map +0 -1
  297. /package/dist/src/{commands/extensions/examples/mcp-server/example.d.ts → config/extension-manager-scope.test.d.ts} +0 -0
  298. /package/dist/src/{commands/extensions/examples/mcp-server/example.test.d.ts → ui/commands/agentsCommand.test.d.ts} +0 -0
  299. /package/dist/src/{commands/extensions/settings.test.d.ts → ui/hooks/atCommandProcessor_agents.test.d.ts} +0 -0
@@ -37,16 +37,15 @@ vi.mock('./settingsSchema.js', async (importOriginal) => {
37
37
  };
38
38
  });
39
39
  // NOW import everything else, including the (now effectively re-exported) settings.js
40
- import path, * as pathActual from 'node:path'; // Restored for MOCK_WORKSPACE_SETTINGS_PATH
40
+ import * as pathActual from 'node:path'; // Restored for MOCK_WORKSPACE_SETTINGS_PATH
41
41
  import { describe, it, expect, vi, beforeEach, afterEach, } from 'vitest';
42
42
  import * as fs from 'node:fs'; // fs will be mocked separately
43
43
  import stripJsonComments from 'strip-json-comments'; // Will be mocked separately
44
44
  import { isWorkspaceTrusted } from './trustedFolders.js';
45
45
  // These imports will get the versions from the vi.mock('./settings.js', ...) factory.
46
46
  import { loadSettings, USER_SETTINGS_PATH, // This IS the mocked path.
47
- getSystemSettingsPath, getSystemDefaultsPath, migrateSettingsToV1, needsMigration, loadEnvironment, migrateDeprecatedSettings, SettingScope, saveSettings, getDefaultsFromSchema, } from './settings.js';
47
+ getSystemSettingsPath, getSystemDefaultsPath, saveSettings, getDefaultsFromSchema, } from './settings.js';
48
48
  import { FatalConfigError, GEMINI_DIR } from '@google/gemini-cli-core';
49
- import { ExtensionManager } from './extension-manager.js';
50
49
  import { updateSettingsFilePreservingFormat } from '../utils/commentJson.js';
51
50
  import { getSettingsSchema, MergeStrategy, } from './settingsSchema.js';
52
51
  const MOCK_WORKSPACE_DIR = '/mock/workspace';
@@ -212,136 +211,6 @@ describe('Settings Loading and Merging', () => {
212
211
  },
213
212
  });
214
213
  });
215
- it('should correctly migrate a complex legacy (v1) settings file', () => {
216
- mockFsExistsSync.mockImplementation((p) => p === USER_SETTINGS_PATH);
217
- const legacySettingsContent = {
218
- theme: 'legacy-dark',
219
- vimMode: true,
220
- contextFileName: 'LEGACY_CONTEXT.md',
221
- model: 'gemini-2.5-pro',
222
- mcpServers: {
223
- 'legacy-server-1': {
224
- command: 'npm',
225
- args: ['run', 'start:server1'],
226
- description: 'Legacy Server 1',
227
- },
228
- 'legacy-server-2': {
229
- command: 'node',
230
- args: ['server2.js'],
231
- description: 'Legacy Server 2',
232
- },
233
- },
234
- allowMCPServers: ['legacy-server-1'],
235
- someUnrecognizedSetting: 'should-be-preserved',
236
- };
237
- fs.readFileSync.mockImplementation((p) => {
238
- if (p === USER_SETTINGS_PATH)
239
- return JSON.stringify(legacySettingsContent);
240
- return '{}';
241
- });
242
- const settings = loadSettings(MOCK_WORKSPACE_DIR);
243
- expect(settings.merged).toMatchObject({
244
- ui: {
245
- theme: 'legacy-dark',
246
- },
247
- general: {
248
- vimMode: true,
249
- },
250
- context: {
251
- fileName: 'LEGACY_CONTEXT.md',
252
- },
253
- model: {
254
- name: 'gemini-2.5-pro',
255
- },
256
- mcpServers: {
257
- 'legacy-server-1': {
258
- command: 'npm',
259
- args: ['run', 'start:server1'],
260
- description: 'Legacy Server 1',
261
- },
262
- 'legacy-server-2': {
263
- command: 'node',
264
- args: ['server2.js'],
265
- description: 'Legacy Server 2',
266
- },
267
- },
268
- mcp: {
269
- allowed: ['legacy-server-1'],
270
- },
271
- someUnrecognizedSetting: 'should-be-preserved',
272
- });
273
- });
274
- it('should rewrite allowedTools to tools.allowed during migration', () => {
275
- mockFsExistsSync.mockImplementation((p) => p === USER_SETTINGS_PATH);
276
- const legacySettingsContent = {
277
- allowedTools: ['fs', 'shell'],
278
- };
279
- fs.readFileSync.mockImplementation((p) => {
280
- if (p === USER_SETTINGS_PATH)
281
- return JSON.stringify(legacySettingsContent);
282
- return '{}';
283
- });
284
- const settings = loadSettings(MOCK_WORKSPACE_DIR);
285
- expect(settings.merged.tools?.allowed).toEqual(['fs', 'shell']);
286
- expect(settings.merged['allowedTools']).toBeUndefined();
287
- });
288
- it('should allow V2 settings to override V1 settings when both are present (zombie setting fix)', () => {
289
- mockFsExistsSync.mockImplementation((p) => p === USER_SETTINGS_PATH);
290
- const mixedSettingsContent = {
291
- // V1 setting (migrates to ui.accessibility.screenReader = true)
292
- accessibility: {
293
- screenReader: true,
294
- },
295
- // V2 setting (explicitly set to false)
296
- ui: {
297
- accessibility: {
298
- screenReader: false,
299
- },
300
- },
301
- };
302
- fs.readFileSync.mockImplementation((p) => {
303
- if (p === USER_SETTINGS_PATH)
304
- return JSON.stringify(mixedSettingsContent);
305
- return '{}';
306
- });
307
- const settings = loadSettings(MOCK_WORKSPACE_DIR);
308
- // We expect the V2 setting (false) to win, NOT the migrated V1 setting (true)
309
- expect(settings.merged.ui?.accessibility?.screenReader).toBe(false);
310
- });
311
- it('should correctly merge and migrate legacy array properties from multiple scopes', () => {
312
- mockFsExistsSync.mockReturnValue(true);
313
- const legacyUserSettings = {
314
- includeDirectories: ['/user/dir'],
315
- excludeTools: ['user-tool'],
316
- excludedProjectEnvVars: ['USER_VAR'],
317
- };
318
- const legacyWorkspaceSettings = {
319
- includeDirectories: ['/workspace/dir'],
320
- excludeTools: ['workspace-tool'],
321
- excludedProjectEnvVars: ['WORKSPACE_VAR', 'USER_VAR'],
322
- };
323
- fs.readFileSync.mockImplementation((p) => {
324
- if (p === USER_SETTINGS_PATH)
325
- return JSON.stringify(legacyUserSettings);
326
- if (p === MOCK_WORKSPACE_SETTINGS_PATH)
327
- return JSON.stringify(legacyWorkspaceSettings);
328
- return '{}';
329
- });
330
- const settings = loadSettings(MOCK_WORKSPACE_DIR);
331
- // Verify includeDirectories are concatenated
332
- expect(settings.merged.context?.includeDirectories).toEqual([
333
- '/user/dir',
334
- '/workspace/dir',
335
- ]);
336
- // Verify excludeTools are concatenated and de-duped
337
- expect(settings.merged.tools?.exclude).toEqual([
338
- 'user-tool',
339
- 'workspace-tool',
340
- ]);
341
- // Verify excludedProjectEnvVars are concatenated and de-duped
342
- expect(settings.merged.advanced?.excludedEnvVars).toEqual(expect.arrayContaining(['USER_VAR', 'WORKSPACE_VAR']));
343
- expect(settings.merged.advanced?.excludedEnvVars).toHaveLength(4);
344
- });
345
214
  it('should merge all settings files with the correct precedence', () => {
346
215
  // Mock schema to test defaults application
347
216
  const mockSchema = {
@@ -1379,557 +1248,6 @@ describe('Settings Loading and Merging', () => {
1379
1248
  ]);
1380
1249
  });
1381
1250
  });
1382
- describe('with workspace trust', () => {
1383
- it('should merge workspace settings when workspace is trusted', () => {
1384
- mockFsExistsSync.mockReturnValue(true);
1385
- const userSettingsContent = {
1386
- ui: { theme: 'dark' },
1387
- tools: { sandbox: false },
1388
- };
1389
- const workspaceSettingsContent = {
1390
- tools: { sandbox: true },
1391
- context: { fileName: 'WORKSPACE.md' },
1392
- };
1393
- fs.readFileSync.mockImplementation((p) => {
1394
- if (p === USER_SETTINGS_PATH)
1395
- return JSON.stringify(userSettingsContent);
1396
- if (p === MOCK_WORKSPACE_SETTINGS_PATH)
1397
- return JSON.stringify(workspaceSettingsContent);
1398
- return '{}';
1399
- });
1400
- const settings = loadSettings(MOCK_WORKSPACE_DIR);
1401
- expect(settings.merged.tools?.sandbox).toBe(true);
1402
- expect(settings.merged.context?.fileName).toBe('WORKSPACE.md');
1403
- expect(settings.merged.ui?.theme).toBe('dark');
1404
- });
1405
- it('should NOT merge workspace settings when workspace is not trusted', () => {
1406
- vi.mocked(isWorkspaceTrusted).mockReturnValue({
1407
- isTrusted: false,
1408
- source: 'file',
1409
- });
1410
- mockFsExistsSync.mockReturnValue(true);
1411
- const userSettingsContent = {
1412
- ui: { theme: 'dark' },
1413
- tools: { sandbox: false },
1414
- context: { fileName: 'USER.md' },
1415
- };
1416
- const workspaceSettingsContent = {
1417
- tools: { sandbox: true },
1418
- context: { fileName: 'WORKSPACE.md' },
1419
- };
1420
- fs.readFileSync.mockImplementation((p) => {
1421
- if (p === USER_SETTINGS_PATH)
1422
- return JSON.stringify(userSettingsContent);
1423
- if (p === MOCK_WORKSPACE_SETTINGS_PATH)
1424
- return JSON.stringify(workspaceSettingsContent);
1425
- return '{}';
1426
- });
1427
- const settings = loadSettings(MOCK_WORKSPACE_DIR);
1428
- expect(settings.merged.tools?.sandbox).toBe(false); // User setting
1429
- expect(settings.merged.context?.fileName).toBe('USER.md'); // User setting
1430
- expect(settings.merged.ui?.theme).toBe('dark'); // User setting
1431
- });
1432
- });
1433
- describe('migrateSettingsToV1', () => {
1434
- it('should handle an empty object', () => {
1435
- const v2Settings = {};
1436
- const v1Settings = migrateSettingsToV1(v2Settings);
1437
- expect(v1Settings).toEqual({});
1438
- });
1439
- it('should migrate a simple v2 settings object to v1', () => {
1440
- const v2Settings = {
1441
- general: {
1442
- preferredEditor: 'vscode',
1443
- vimMode: true,
1444
- },
1445
- ui: {
1446
- theme: 'dark',
1447
- },
1448
- };
1449
- const v1Settings = migrateSettingsToV1(v2Settings);
1450
- expect(v1Settings).toEqual({
1451
- preferredEditor: 'vscode',
1452
- vimMode: true,
1453
- theme: 'dark',
1454
- });
1455
- });
1456
- it('should handle nested properties correctly', () => {
1457
- const v2Settings = {
1458
- security: {
1459
- folderTrust: {
1460
- enabled: true,
1461
- },
1462
- auth: {
1463
- selectedType: 'oauth',
1464
- },
1465
- },
1466
- advanced: {
1467
- autoConfigureMemory: true,
1468
- },
1469
- };
1470
- const v1Settings = migrateSettingsToV1(v2Settings);
1471
- expect(v1Settings).toEqual({
1472
- folderTrust: true,
1473
- selectedAuthType: 'oauth',
1474
- autoConfigureMaxOldSpaceSize: true,
1475
- });
1476
- });
1477
- it('should preserve mcpServers at the top level', () => {
1478
- const v2Settings = {
1479
- general: {
1480
- preferredEditor: 'vscode',
1481
- },
1482
- mcpServers: {
1483
- 'my-server': {
1484
- command: 'npm start',
1485
- },
1486
- },
1487
- };
1488
- const v1Settings = migrateSettingsToV1(v2Settings);
1489
- expect(v1Settings).toEqual({
1490
- preferredEditor: 'vscode',
1491
- mcpServers: {
1492
- 'my-server': {
1493
- command: 'npm start',
1494
- },
1495
- },
1496
- });
1497
- });
1498
- it('should carry over unrecognized top-level properties', () => {
1499
- const v2Settings = {
1500
- general: {
1501
- vimMode: false,
1502
- },
1503
- unrecognized: 'value',
1504
- another: {
1505
- nested: true,
1506
- },
1507
- };
1508
- const v1Settings = migrateSettingsToV1(v2Settings);
1509
- expect(v1Settings).toEqual({
1510
- vimMode: false,
1511
- unrecognized: 'value',
1512
- another: {
1513
- nested: true,
1514
- },
1515
- });
1516
- });
1517
- it('should handle a complex object with mixed properties', () => {
1518
- const v2Settings = {
1519
- general: {
1520
- disableAutoUpdate: true,
1521
- },
1522
- ui: {
1523
- hideBanner: true,
1524
- customThemes: {
1525
- myTheme: {},
1526
- },
1527
- },
1528
- model: {
1529
- name: 'gemini-pro',
1530
- },
1531
- mcpServers: {
1532
- 'server-1': {
1533
- command: 'node server.js',
1534
- },
1535
- },
1536
- unrecognized: {
1537
- should: 'be-preserved',
1538
- },
1539
- };
1540
- const v1Settings = migrateSettingsToV1(v2Settings);
1541
- expect(v1Settings).toEqual({
1542
- disableAutoUpdate: true,
1543
- hideBanner: true,
1544
- customThemes: {
1545
- myTheme: {},
1546
- },
1547
- model: 'gemini-pro',
1548
- mcpServers: {
1549
- 'server-1': {
1550
- command: 'node server.js',
1551
- },
1552
- },
1553
- unrecognized: {
1554
- should: 'be-preserved',
1555
- },
1556
- });
1557
- });
1558
- it('should not migrate a v1 settings object', () => {
1559
- const v1Settings = {
1560
- preferredEditor: 'vscode',
1561
- vimMode: true,
1562
- theme: 'dark',
1563
- };
1564
- const migratedSettings = migrateSettingsToV1(v1Settings);
1565
- expect(migratedSettings).toEqual({
1566
- preferredEditor: 'vscode',
1567
- vimMode: true,
1568
- theme: 'dark',
1569
- });
1570
- });
1571
- it('should migrate a full v2 settings object to v1', () => {
1572
- const v2Settings = {
1573
- general: {
1574
- preferredEditor: 'code',
1575
- vimMode: true,
1576
- },
1577
- ui: {
1578
- theme: 'dark',
1579
- },
1580
- privacy: {
1581
- usageStatisticsEnabled: false,
1582
- },
1583
- model: {
1584
- name: 'gemini-2.5-pro',
1585
- },
1586
- context: {
1587
- fileName: 'CONTEXT.md',
1588
- includeDirectories: ['/src'],
1589
- },
1590
- tools: {
1591
- sandbox: true,
1592
- exclude: ['toolA'],
1593
- },
1594
- mcp: {
1595
- allowed: ['server1'],
1596
- },
1597
- security: {
1598
- folderTrust: {
1599
- enabled: true,
1600
- },
1601
- },
1602
- advanced: {
1603
- dnsResolutionOrder: 'ipv4first',
1604
- excludedEnvVars: ['SECRET'],
1605
- },
1606
- mcpServers: {
1607
- 'my-server': {
1608
- command: 'npm start',
1609
- },
1610
- },
1611
- unrecognizedTopLevel: {
1612
- value: 'should be preserved',
1613
- },
1614
- };
1615
- const v1Settings = migrateSettingsToV1(v2Settings);
1616
- expect(v1Settings).toEqual({
1617
- preferredEditor: 'code',
1618
- vimMode: true,
1619
- theme: 'dark',
1620
- usageStatisticsEnabled: false,
1621
- model: 'gemini-2.5-pro',
1622
- contextFileName: 'CONTEXT.md',
1623
- includeDirectories: ['/src'],
1624
- sandbox: true,
1625
- excludeTools: ['toolA'],
1626
- allowMCPServers: ['server1'],
1627
- folderTrust: true,
1628
- dnsResolutionOrder: 'ipv4first',
1629
- excludedProjectEnvVars: ['SECRET'],
1630
- mcpServers: {
1631
- 'my-server': {
1632
- command: 'npm start',
1633
- },
1634
- },
1635
- unrecognizedTopLevel: {
1636
- value: 'should be preserved',
1637
- },
1638
- });
1639
- });
1640
- it('should handle partial v2 settings', () => {
1641
- const v2Settings = {
1642
- general: {
1643
- vimMode: false,
1644
- },
1645
- ui: {},
1646
- model: {
1647
- name: 'gemini-2.5-pro',
1648
- },
1649
- unrecognized: 'value',
1650
- };
1651
- const v1Settings = migrateSettingsToV1(v2Settings);
1652
- expect(v1Settings).toEqual({
1653
- vimMode: false,
1654
- model: 'gemini-2.5-pro',
1655
- unrecognized: 'value',
1656
- });
1657
- });
1658
- it('should handle settings with different data types', () => {
1659
- const v2Settings = {
1660
- general: {
1661
- vimMode: false,
1662
- },
1663
- model: {
1664
- maxSessionTurns: -1,
1665
- },
1666
- context: {
1667
- includeDirectories: [],
1668
- },
1669
- security: {
1670
- folderTrust: {
1671
- enabled: undefined,
1672
- },
1673
- },
1674
- };
1675
- const v1Settings = migrateSettingsToV1(v2Settings);
1676
- expect(v1Settings).toEqual({
1677
- vimMode: false,
1678
- maxSessionTurns: -1,
1679
- includeDirectories: [],
1680
- security: {
1681
- folderTrust: {
1682
- enabled: undefined,
1683
- },
1684
- },
1685
- });
1686
- });
1687
- it('should preserve unrecognized top-level keys', () => {
1688
- const v2Settings = {
1689
- general: {
1690
- vimMode: true,
1691
- },
1692
- customTopLevel: {
1693
- a: 1,
1694
- b: [2],
1695
- },
1696
- anotherOne: 'hello',
1697
- };
1698
- const v1Settings = migrateSettingsToV1(v2Settings);
1699
- expect(v1Settings).toEqual({
1700
- vimMode: true,
1701
- customTopLevel: {
1702
- a: 1,
1703
- b: [2],
1704
- },
1705
- anotherOne: 'hello',
1706
- });
1707
- });
1708
- it('should handle an empty v2 settings object', () => {
1709
- const v2Settings = {};
1710
- const v1Settings = migrateSettingsToV1(v2Settings);
1711
- expect(v1Settings).toEqual({});
1712
- });
1713
- it('should correctly handle mcpServers at the top level', () => {
1714
- const v2Settings = {
1715
- mcpServers: {
1716
- serverA: { command: 'a' },
1717
- },
1718
- mcp: {
1719
- allowed: ['serverA'],
1720
- },
1721
- };
1722
- const v1Settings = migrateSettingsToV1(v2Settings);
1723
- expect(v1Settings).toEqual({
1724
- mcpServers: {
1725
- serverA: { command: 'a' },
1726
- },
1727
- allowMCPServers: ['serverA'],
1728
- });
1729
- });
1730
- it('should correctly migrate customWittyPhrases', () => {
1731
- const v2Settings = {
1732
- ui: {
1733
- customWittyPhrases: ['test phrase'],
1734
- },
1735
- };
1736
- const v1Settings = migrateSettingsToV1(v2Settings);
1737
- expect(v1Settings).toEqual({
1738
- customWittyPhrases: ['test phrase'],
1739
- });
1740
- });
1741
- });
1742
- describe('loadEnvironment', () => {
1743
- function setup({ isFolderTrustEnabled = true, isWorkspaceTrustedValue = true, }) {
1744
- delete process.env['TESTTEST']; // reset
1745
- const geminiEnvPath = path.resolve(path.join(GEMINI_DIR, '.env'));
1746
- vi.mocked(isWorkspaceTrusted).mockReturnValue({
1747
- isTrusted: isWorkspaceTrustedValue,
1748
- source: 'file',
1749
- });
1750
- mockFsExistsSync.mockImplementation((p) => [USER_SETTINGS_PATH, geminiEnvPath].includes(p.toString()));
1751
- const userSettingsContent = {
1752
- ui: {
1753
- theme: 'dark',
1754
- },
1755
- security: {
1756
- folderTrust: {
1757
- enabled: isFolderTrustEnabled,
1758
- },
1759
- },
1760
- context: {
1761
- fileName: 'USER_CONTEXT.md',
1762
- },
1763
- };
1764
- fs.readFileSync.mockImplementation((p) => {
1765
- if (p === USER_SETTINGS_PATH)
1766
- return JSON.stringify(userSettingsContent);
1767
- if (p === geminiEnvPath)
1768
- return 'TESTTEST=1234';
1769
- return '{}';
1770
- });
1771
- }
1772
- it('sets environment variables from .env files', () => {
1773
- setup({ isFolderTrustEnabled: false, isWorkspaceTrustedValue: true });
1774
- loadEnvironment(loadSettings(MOCK_WORKSPACE_DIR).merged);
1775
- expect(process.env['TESTTEST']).toEqual('1234');
1776
- });
1777
- it('does not load env files from untrusted spaces', () => {
1778
- setup({ isFolderTrustEnabled: true, isWorkspaceTrustedValue: false });
1779
- loadEnvironment(loadSettings(MOCK_WORKSPACE_DIR).merged);
1780
- expect(process.env['TESTTEST']).not.toEqual('1234');
1781
- });
1782
- });
1783
- describe('needsMigration', () => {
1784
- it('should return false for an empty object', () => {
1785
- expect(needsMigration({})).toBe(false);
1786
- });
1787
- it('should return false for settings that are already in V2 format', () => {
1788
- const v2Settings = {
1789
- ui: {
1790
- theme: 'dark',
1791
- },
1792
- tools: {
1793
- sandbox: true,
1794
- },
1795
- };
1796
- expect(needsMigration(v2Settings)).toBe(false);
1797
- });
1798
- it('should return true for settings with a V1 key that needs to be moved', () => {
1799
- const v1Settings = {
1800
- theme: 'dark', // v1 key
1801
- };
1802
- expect(needsMigration(v1Settings)).toBe(true);
1803
- });
1804
- it('should return true for settings with a mix of V1 and V2 keys', () => {
1805
- const mixedSettings = {
1806
- theme: 'dark', // v1 key
1807
- tools: {
1808
- sandbox: true, // v2 key
1809
- },
1810
- };
1811
- expect(needsMigration(mixedSettings)).toBe(true);
1812
- });
1813
- it('should return false for settings with only V1 keys that are the same in V2', () => {
1814
- const v1Settings = {
1815
- mcpServers: {},
1816
- telemetry: {},
1817
- extensions: [],
1818
- };
1819
- expect(needsMigration(v1Settings)).toBe(false);
1820
- });
1821
- it('should return true for settings with a mix of V1 keys that are the same in V2 and V1 keys that need moving', () => {
1822
- const v1Settings = {
1823
- mcpServers: {}, // same in v2
1824
- theme: 'dark', // needs moving
1825
- };
1826
- expect(needsMigration(v1Settings)).toBe(true);
1827
- });
1828
- it('should return false for settings with unrecognized keys', () => {
1829
- const settings = {
1830
- someUnrecognizedKey: 'value',
1831
- };
1832
- expect(needsMigration(settings)).toBe(false);
1833
- });
1834
- it('should return false for settings with v2 keys and unrecognized keys', () => {
1835
- const settings = {
1836
- ui: { theme: 'dark' },
1837
- someUnrecognizedKey: 'value',
1838
- };
1839
- expect(needsMigration(settings)).toBe(false);
1840
- });
1841
- });
1842
- describe('migrateDeprecatedSettings', () => {
1843
- let mockFsExistsSync;
1844
- let mockFsReadFileSync;
1845
- beforeEach(() => {
1846
- vi.resetAllMocks();
1847
- mockFsExistsSync = vi.mocked(fs.existsSync);
1848
- mockFsExistsSync.mockReturnValue(true);
1849
- mockFsReadFileSync = vi.mocked(fs.readFileSync);
1850
- mockFsReadFileSync.mockReturnValue('{}');
1851
- vi.mocked(isWorkspaceTrusted).mockReturnValue({
1852
- isTrusted: true,
1853
- source: undefined,
1854
- });
1855
- });
1856
- afterEach(() => {
1857
- vi.restoreAllMocks();
1858
- });
1859
- it('should migrate disabled extensions from user and workspace settings', () => {
1860
- const userSettingsContent = {
1861
- extensions: {
1862
- disabled: ['user-ext-1', 'shared-ext'],
1863
- },
1864
- };
1865
- const workspaceSettingsContent = {
1866
- extensions: {
1867
- disabled: ['workspace-ext-1', 'shared-ext'],
1868
- },
1869
- };
1870
- mockFsReadFileSync.mockImplementation((p) => {
1871
- if (p === USER_SETTINGS_PATH)
1872
- return JSON.stringify(userSettingsContent);
1873
- if (p === MOCK_WORKSPACE_SETTINGS_PATH)
1874
- return JSON.stringify(workspaceSettingsContent);
1875
- return '{}';
1876
- });
1877
- const loadedSettings = loadSettings(MOCK_WORKSPACE_DIR);
1878
- const setValueSpy = vi.spyOn(loadedSettings, 'setValue');
1879
- const extensionManager = new ExtensionManager({
1880
- settings: loadedSettings.merged,
1881
- workspaceDir: MOCK_WORKSPACE_DIR,
1882
- requestConsent: vi.fn(),
1883
- requestSetting: vi.fn(),
1884
- });
1885
- const mockDisableExtension = vi.spyOn(extensionManager, 'disableExtension');
1886
- mockDisableExtension.mockImplementation(async () => { });
1887
- migrateDeprecatedSettings(loadedSettings, extensionManager);
1888
- // Check user settings migration
1889
- expect(mockDisableExtension).toHaveBeenCalledWith('user-ext-1', SettingScope.User);
1890
- expect(mockDisableExtension).toHaveBeenCalledWith('shared-ext', SettingScope.User);
1891
- // Check workspace settings migration
1892
- expect(mockDisableExtension).toHaveBeenCalledWith('workspace-ext-1', SettingScope.Workspace);
1893
- expect(mockDisableExtension).toHaveBeenCalledWith('shared-ext', SettingScope.Workspace);
1894
- // Check that setValue was called to remove the deprecated setting
1895
- expect(setValueSpy).toHaveBeenCalledWith(SettingScope.User, 'extensions', {
1896
- disabled: undefined,
1897
- });
1898
- expect(setValueSpy).toHaveBeenCalledWith(SettingScope.Workspace, 'extensions', {
1899
- disabled: undefined,
1900
- });
1901
- });
1902
- it('should not do anything if there are no deprecated settings', () => {
1903
- const userSettingsContent = {
1904
- extensions: {
1905
- enabled: ['user-ext-1'],
1906
- },
1907
- };
1908
- const workspaceSettingsContent = {
1909
- someOtherSetting: 'value',
1910
- };
1911
- mockFsReadFileSync.mockImplementation((p) => {
1912
- if (p === USER_SETTINGS_PATH)
1913
- return JSON.stringify(userSettingsContent);
1914
- if (p === MOCK_WORKSPACE_SETTINGS_PATH)
1915
- return JSON.stringify(workspaceSettingsContent);
1916
- return '{}';
1917
- });
1918
- const loadedSettings = loadSettings(MOCK_WORKSPACE_DIR);
1919
- const setValueSpy = vi.spyOn(loadedSettings, 'setValue');
1920
- const extensionManager = new ExtensionManager({
1921
- settings: loadedSettings.merged,
1922
- workspaceDir: MOCK_WORKSPACE_DIR,
1923
- requestConsent: vi.fn(),
1924
- requestSetting: vi.fn(),
1925
- });
1926
- const mockDisableExtension = vi.spyOn(extensionManager, 'disableExtension');
1927
- mockDisableExtension.mockImplementation(async () => { });
1928
- migrateDeprecatedSettings(loadedSettings, extensionManager);
1929
- expect(mockDisableExtension).not.toHaveBeenCalled();
1930
- expect(setValueSpy).not.toHaveBeenCalled();
1931
- });
1932
- });
1933
1251
  describe('saveSettings', () => {
1934
1252
  it('should save settings using updateSettingsFilePreservingFormat', () => {
1935
1253
  const mockUpdateSettings = vi.mocked(updateSettingsFilePreservingFormat);
@@ -1973,6 +1291,152 @@ describe('Settings Loading and Merging', () => {
1973
1291
  expect(mockCoreEvents.emitFeedback).toHaveBeenCalledWith('error', 'There was an error saving your latest settings changes.', error);
1974
1292
  });
1975
1293
  });
1294
+ describe('LoadedSettings and remote admin settings', () => {
1295
+ it('should prioritize remote admin settings over file-based admin settings', () => {
1296
+ mockFsExistsSync.mockReturnValue(true);
1297
+ const systemSettingsContent = {
1298
+ admin: {
1299
+ // These should be ignored
1300
+ secureModeEnabled: true,
1301
+ mcp: { enabled: false },
1302
+ extensions: { enabled: false },
1303
+ },
1304
+ // A non-admin setting to ensure it's still processed
1305
+ ui: { theme: 'system-theme' },
1306
+ };
1307
+ fs.readFileSync.mockImplementation((p) => {
1308
+ if (p === getSystemSettingsPath()) {
1309
+ return JSON.stringify(systemSettingsContent);
1310
+ }
1311
+ return '{}';
1312
+ });
1313
+ const loadedSettings = loadSettings(MOCK_WORKSPACE_DIR);
1314
+ // 1. Verify that on initial load, file-based admin settings are ignored
1315
+ // and schema defaults are used instead.
1316
+ expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(false); // default: false
1317
+ expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(true); // default: true
1318
+ expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(true); // default: true
1319
+ expect(loadedSettings.merged.ui?.theme).toBe('system-theme'); // non-admin setting should be loaded
1320
+ // 2. Now, set remote admin settings.
1321
+ loadedSettings.setRemoteAdminSettings({
1322
+ secureModeEnabled: true,
1323
+ mcpSetting: { mcpEnabled: false },
1324
+ cliFeatureSetting: { extensionsSetting: { extensionsEnabled: false } },
1325
+ });
1326
+ // 3. Verify that remote admin settings take precedence.
1327
+ expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(true);
1328
+ expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(false);
1329
+ expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(false);
1330
+ // non-admin setting should remain unchanged
1331
+ expect(loadedSettings.merged.ui?.theme).toBe('system-theme');
1332
+ });
1333
+ it('should set remote admin settings and recompute merged settings', () => {
1334
+ mockFsExistsSync.mockReturnValue(true);
1335
+ const systemSettingsContent = {
1336
+ admin: {
1337
+ secureModeEnabled: false,
1338
+ mcp: { enabled: false },
1339
+ extensions: { enabled: false },
1340
+ },
1341
+ ui: { theme: 'initial-theme' },
1342
+ };
1343
+ fs.readFileSync.mockImplementation((p) => {
1344
+ if (p === getSystemSettingsPath()) {
1345
+ return JSON.stringify(systemSettingsContent);
1346
+ }
1347
+ return '{}';
1348
+ });
1349
+ const loadedSettings = loadSettings(MOCK_WORKSPACE_DIR);
1350
+ // Ensure initial state from defaults (as file-based admin settings are ignored)
1351
+ expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(false);
1352
+ expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(true);
1353
+ expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(true);
1354
+ expect(loadedSettings.merged.ui?.theme).toBe('initial-theme');
1355
+ const newRemoteSettings = {
1356
+ secureModeEnabled: true,
1357
+ mcpSetting: { mcpEnabled: false },
1358
+ cliFeatureSetting: { extensionsSetting: { extensionsEnabled: false } },
1359
+ };
1360
+ loadedSettings.setRemoteAdminSettings(newRemoteSettings);
1361
+ // Verify that remote admin settings are applied
1362
+ expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(true);
1363
+ expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(false);
1364
+ expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(false);
1365
+ // Non-admin settings should remain untouched
1366
+ expect(loadedSettings.merged.ui?.theme).toBe('initial-theme');
1367
+ // Verify that calling setRemoteAdminSettings with partial data overwrites previous remote settings
1368
+ // and missing properties revert to schema defaults.
1369
+ loadedSettings.setRemoteAdminSettings({ secureModeEnabled: false });
1370
+ expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(false);
1371
+ expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(true); // Reverts to default: true
1372
+ expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(true); // Reverts to default: true
1373
+ });
1374
+ it('should correctly handle undefined remote admin settings', () => {
1375
+ mockFsExistsSync.mockReturnValue(true);
1376
+ const systemSettingsContent = {
1377
+ ui: { theme: 'initial-theme' },
1378
+ };
1379
+ fs.readFileSync.mockImplementation((p) => {
1380
+ if (p === getSystemSettingsPath()) {
1381
+ return JSON.stringify(systemSettingsContent);
1382
+ }
1383
+ return '{}';
1384
+ });
1385
+ const loadedSettings = loadSettings(MOCK_WORKSPACE_DIR);
1386
+ // Should have default admin settings
1387
+ expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(false);
1388
+ expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(true);
1389
+ expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(true);
1390
+ loadedSettings.setRemoteAdminSettings({}); // Set empty remote settings
1391
+ // Admin settings should revert to defaults because there are no remote overrides
1392
+ expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(false);
1393
+ expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(true);
1394
+ expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(true);
1395
+ });
1396
+ it('should correctly handle missing properties in remote admin settings', () => {
1397
+ mockFsExistsSync.mockReturnValue(true);
1398
+ const systemSettingsContent = {
1399
+ admin: {
1400
+ secureModeEnabled: true,
1401
+ },
1402
+ };
1403
+ fs.readFileSync.mockImplementation((p) => {
1404
+ if (p === getSystemSettingsPath()) {
1405
+ return JSON.stringify(systemSettingsContent);
1406
+ }
1407
+ return '{}';
1408
+ });
1409
+ const loadedSettings = loadSettings(MOCK_WORKSPACE_DIR);
1410
+ // Ensure initial state from defaults (as file-based admin settings are ignored)
1411
+ expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(false);
1412
+ expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(true);
1413
+ expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(true);
1414
+ // Set remote settings with only secureModeEnabled
1415
+ loadedSettings.setRemoteAdminSettings({
1416
+ secureModeEnabled: true,
1417
+ });
1418
+ // Verify secureModeEnabled is updated, others remain defaults
1419
+ expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(true);
1420
+ expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(true);
1421
+ expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(true);
1422
+ // Set remote settings with only mcpSetting.mcpEnabled
1423
+ loadedSettings.setRemoteAdminSettings({
1424
+ mcpSetting: { mcpEnabled: false },
1425
+ });
1426
+ // Verify mcpEnabled is updated, others remain defaults (secureModeEnabled reverts to default:false)
1427
+ expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(false);
1428
+ expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(false);
1429
+ expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(true);
1430
+ // Set remote settings with only cliFeatureSetting.extensionsSetting.extensionsEnabled
1431
+ loadedSettings.setRemoteAdminSettings({
1432
+ cliFeatureSetting: { extensionsSetting: { extensionsEnabled: false } },
1433
+ });
1434
+ // Verify extensionsEnabled is updated, others remain defaults
1435
+ expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(false);
1436
+ expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(true);
1437
+ expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(false);
1438
+ });
1439
+ });
1976
1440
  describe('getDefaultsFromSchema', () => {
1977
1441
  it('should extract defaults from a schema', () => {
1978
1442
  const mockSchema = {