@google/gemini-cli 0.18.0-preview.1 → 0.19.0-nightly.20251122.42c2e1b21

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 (203) hide show
  1. package/dist/google-gemini-cli-0.19.0-nightly.20251120.8e531dc02.tgz +0 -0
  2. package/dist/index.js +8 -6
  3. package/dist/index.js.map +1 -1
  4. package/dist/package.json +3 -3
  5. package/dist/src/commands/extensions/examples/mcp-server/package.json +1 -1
  6. package/dist/src/config/auth.js +4 -0
  7. package/dist/src/config/auth.js.map +1 -1
  8. package/dist/src/config/auth.test.js +61 -37
  9. package/dist/src/config/auth.test.js.map +1 -1
  10. package/dist/src/config/config.integration.test.js +81 -198
  11. package/dist/src/config/config.integration.test.js.map +1 -1
  12. package/dist/src/config/config.js +2 -6
  13. package/dist/src/config/config.js.map +1 -1
  14. package/dist/src/config/config.test.js +196 -299
  15. package/dist/src/config/config.test.js.map +1 -1
  16. package/dist/src/config/extension.test.js +109 -133
  17. package/dist/src/config/extension.test.js.map +1 -1
  18. package/dist/src/config/extensions/consent.test.js +152 -0
  19. package/dist/src/config/extensions/consent.test.js.map +1 -0
  20. package/dist/src/config/extensions/extensionEnablement.test.js +82 -15
  21. package/dist/src/config/extensions/extensionEnablement.test.js.map +1 -1
  22. package/dist/src/config/extensions/extensionSettings.test.js +105 -1
  23. package/dist/src/config/extensions/extensionSettings.test.js.map +1 -1
  24. package/dist/src/config/extensions/github.d.ts +1 -0
  25. package/dist/src/config/extensions/github.js +1 -1
  26. package/dist/src/config/extensions/github.js.map +1 -1
  27. package/dist/src/config/extensions/github.test.js +197 -318
  28. package/dist/src/config/extensions/github.test.js.map +1 -1
  29. package/dist/src/config/extensions/storage.test.d.ts +6 -0
  30. package/dist/src/config/extensions/storage.test.js +64 -0
  31. package/dist/src/config/extensions/storage.test.js.map +1 -0
  32. package/dist/src/config/extensions/update.test.js +154 -263
  33. package/dist/src/config/extensions/update.test.js.map +1 -1
  34. package/dist/src/config/extensions/variables.test.js +87 -1
  35. package/dist/src/config/extensions/variables.test.js.map +1 -1
  36. package/dist/src/config/sandboxConfig.d.ts +1 -1
  37. package/dist/src/config/sandboxConfig.js.map +1 -1
  38. package/dist/src/config/sandboxConfig.test.d.ts +6 -0
  39. package/dist/src/config/sandboxConfig.test.js +178 -0
  40. package/dist/src/config/sandboxConfig.test.js.map +1 -0
  41. package/dist/src/config/settingPaths.test.d.ts +6 -0
  42. package/dist/src/config/settingPaths.test.js +22 -0
  43. package/dist/src/config/settingPaths.test.js.map +1 -0
  44. package/dist/src/config/settings.test.js +164 -226
  45. package/dist/src/config/settings.test.js.map +1 -1
  46. package/dist/src/config/settingsSchema.d.ts +10 -10
  47. package/dist/src/config/settingsSchema.js +10 -10
  48. package/dist/src/config/settingsSchema.js.map +1 -1
  49. package/dist/src/config/settingsSchema.test.js +0 -6
  50. package/dist/src/config/settingsSchema.test.js.map +1 -1
  51. package/dist/src/gemini.d.ts +1 -1
  52. package/dist/src/gemini.js +5 -7
  53. package/dist/src/gemini.js.map +1 -1
  54. package/dist/src/gemini.test.js +18 -21
  55. package/dist/src/gemini.test.js.map +1 -1
  56. package/dist/src/generated/git-commit.d.ts +2 -2
  57. package/dist/src/generated/git-commit.js +2 -2
  58. package/dist/src/generated/git-commit.js.map +1 -1
  59. package/dist/src/services/BuiltinCommandLoader.js +1 -1
  60. package/dist/src/services/BuiltinCommandLoader.js.map +1 -1
  61. package/dist/src/services/BuiltinCommandLoader.test.js +0 -22
  62. package/dist/src/services/BuiltinCommandLoader.test.js.map +1 -1
  63. package/dist/src/test-utils/mockCommandContext.js +1 -1
  64. package/dist/src/test-utils/render.js +1 -1
  65. package/dist/src/test-utils/render.js.map +1 -1
  66. package/dist/src/ui/AppContainer.js +13 -11
  67. package/dist/src/ui/AppContainer.js.map +1 -1
  68. package/dist/src/ui/AppContainer.test.js +10 -16
  69. package/dist/src/ui/AppContainer.test.js.map +1 -1
  70. package/dist/src/ui/auth/AuthDialog.js +17 -10
  71. package/dist/src/ui/auth/AuthDialog.js.map +1 -1
  72. package/dist/src/ui/auth/AuthDialog.test.js +5 -2
  73. package/dist/src/ui/auth/AuthDialog.test.js.map +1 -1
  74. package/dist/src/ui/components/AppHeader.js +3 -17
  75. package/dist/src/ui/components/AppHeader.js.map +1 -1
  76. package/dist/src/ui/components/AppHeader.test.js +10 -4
  77. package/dist/src/ui/components/AppHeader.test.js.map +1 -1
  78. package/dist/src/ui/components/DebugProfiler.js +1 -1
  79. package/dist/src/ui/components/DebugProfiler.js.map +1 -1
  80. package/dist/src/ui/components/DialogManager.js +6 -1
  81. package/dist/src/ui/components/DialogManager.js.map +1 -1
  82. package/dist/src/ui/components/InputPrompt.js +1 -1
  83. package/dist/src/ui/components/InputPrompt.js.map +1 -1
  84. package/dist/src/ui/components/LoadingIndicator.js +6 -1
  85. package/dist/src/ui/components/LoadingIndicator.js.map +1 -1
  86. package/dist/src/ui/components/ModelDialog.test.js +1 -1
  87. package/dist/src/ui/components/ModelDialog.test.js.map +1 -1
  88. package/dist/src/ui/components/SessionBrowser.d.ts +98 -0
  89. package/dist/src/ui/components/SessionBrowser.js +457 -0
  90. package/dist/src/ui/components/SessionBrowser.js.map +1 -0
  91. package/dist/src/ui/components/SessionBrowser.test.d.ts +6 -0
  92. package/dist/src/ui/components/SessionBrowser.test.js +245 -0
  93. package/dist/src/ui/components/SessionBrowser.test.js.map +1 -0
  94. package/dist/src/ui/components/messages/ShellToolMessage.js +2 -2
  95. package/dist/src/ui/components/messages/ShellToolMessage.js.map +1 -1
  96. package/dist/src/ui/components/messages/ToolMessage.d.ts +5 -0
  97. package/dist/src/ui/components/messages/ToolMessage.js +32 -3
  98. package/dist/src/ui/components/messages/ToolMessage.js.map +1 -1
  99. package/dist/src/ui/components/shared/text-buffer.js +20 -4
  100. package/dist/src/ui/components/shared/text-buffer.js.map +1 -1
  101. package/dist/src/ui/components/shared/text-buffer.test.js +20 -0
  102. package/dist/src/ui/components/shared/text-buffer.test.js.map +1 -1
  103. package/dist/src/ui/constants.d.ts +1 -0
  104. package/dist/src/ui/constants.js +1 -0
  105. package/dist/src/ui/constants.js.map +1 -1
  106. package/dist/src/ui/hooks/shellCommandProcessor.d.ts +1 -0
  107. package/dist/src/ui/hooks/shellCommandProcessor.js +3 -1
  108. package/dist/src/ui/hooks/shellCommandProcessor.js.map +1 -1
  109. package/dist/src/ui/hooks/useAlternateBuffer.js +1 -1
  110. package/dist/src/ui/hooks/useAlternateBuffer.js.map +1 -1
  111. package/dist/src/ui/hooks/useBanner.d.ts +14 -0
  112. package/dist/src/ui/hooks/useBanner.js +48 -0
  113. package/dist/src/ui/hooks/useBanner.js.map +1 -0
  114. package/dist/src/ui/hooks/useBanner.test.d.ts +6 -0
  115. package/dist/src/ui/hooks/useBanner.test.js +92 -0
  116. package/dist/src/ui/hooks/useBanner.test.js.map +1 -0
  117. package/dist/src/ui/hooks/useBracketedPaste.js +3 -5
  118. package/dist/src/ui/hooks/useBracketedPaste.js.map +1 -1
  119. package/dist/src/ui/hooks/useGeminiStream.d.ts +1 -0
  120. package/dist/src/ui/hooks/useGeminiStream.js +6 -4
  121. package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
  122. package/dist/src/ui/hooks/useGeminiStream.test.js +1 -1
  123. package/dist/src/ui/hooks/useGeminiStream.test.js.map +1 -1
  124. package/dist/src/ui/hooks/useInactivityTimer.d.ts +14 -0
  125. package/dist/src/ui/hooks/useInactivityTimer.js +30 -0
  126. package/dist/src/ui/hooks/useInactivityTimer.js.map +1 -0
  127. package/dist/src/ui/hooks/useLoadingIndicator.d.ts +1 -1
  128. package/dist/src/ui/hooks/useLoadingIndicator.js +2 -2
  129. package/dist/src/ui/hooks/useLoadingIndicator.js.map +1 -1
  130. package/dist/src/ui/hooks/useLoadingIndicator.test.js +16 -5
  131. package/dist/src/ui/hooks/useLoadingIndicator.test.js.map +1 -1
  132. package/dist/src/ui/hooks/usePhraseCycler.d.ts +4 -1
  133. package/dist/src/ui/hooks/usePhraseCycler.js +52 -43
  134. package/dist/src/ui/hooks/usePhraseCycler.js.map +1 -1
  135. package/dist/src/ui/hooks/usePhraseCycler.test.js +52 -3
  136. package/dist/src/ui/hooks/usePhraseCycler.test.js.map +1 -1
  137. package/dist/src/ui/hooks/useReactToolScheduler.d.ts +2 -1
  138. package/dist/src/ui/hooks/useReactToolScheduler.js +3 -0
  139. package/dist/src/ui/hooks/useReactToolScheduler.js.map +1 -1
  140. package/dist/src/ui/hooks/useSessionBrowser.d.ts +18 -1
  141. package/dist/src/ui/hooks/useSessionBrowser.js +59 -0
  142. package/dist/src/ui/hooks/useSessionBrowser.js.map +1 -1
  143. package/dist/src/ui/hooks/useSessionBrowser.test.js +154 -526
  144. package/dist/src/ui/hooks/useSessionBrowser.test.js.map +1 -1
  145. package/dist/src/ui/hooks/useSlashCompletion.test.js +1 -1
  146. package/dist/src/ui/hooks/useSlashCompletion.test.js.map +1 -1
  147. package/dist/src/ui/hooks/useToolScheduler.test.js +1 -1
  148. package/dist/src/ui/hooks/useToolScheduler.test.js.map +1 -1
  149. package/dist/src/ui/privacy/CloudFreePrivacyNotice.test.d.ts +6 -0
  150. package/dist/src/ui/privacy/CloudFreePrivacyNotice.test.js +121 -0
  151. package/dist/src/ui/privacy/CloudFreePrivacyNotice.test.js.map +1 -0
  152. package/dist/src/ui/privacy/CloudPaidPrivacyNotice.test.d.ts +6 -0
  153. package/dist/src/ui/privacy/CloudPaidPrivacyNotice.test.js +34 -0
  154. package/dist/src/ui/privacy/CloudPaidPrivacyNotice.test.js.map +1 -0
  155. package/dist/src/ui/privacy/GeminiPrivacyNotice.test.d.ts +6 -0
  156. package/dist/src/ui/privacy/GeminiPrivacyNotice.test.js +34 -0
  157. package/dist/src/ui/privacy/GeminiPrivacyNotice.test.js.map +1 -0
  158. package/dist/src/ui/privacy/PrivacyNotice.test.d.ts +6 -0
  159. package/dist/src/ui/privacy/PrivacyNotice.test.js +62 -0
  160. package/dist/src/ui/privacy/PrivacyNotice.test.js.map +1 -0
  161. package/dist/src/ui/types.js +1 -1
  162. package/dist/src/ui/utils/bracketedPaste.d.ts +7 -0
  163. package/dist/src/ui/utils/bracketedPaste.js +15 -0
  164. package/dist/src/ui/utils/bracketedPaste.js.map +1 -0
  165. package/dist/src/ui/utils/kittyProtocolDetector.js +3 -4
  166. package/dist/src/ui/utils/kittyProtocolDetector.js.map +1 -1
  167. package/dist/src/ui/utils/mouse.d.ts +2 -2
  168. package/dist/src/ui/utils/mouse.js +2 -11
  169. package/dist/src/ui/utils/mouse.js.map +1 -1
  170. package/dist/src/utils/persistentState.d.ts +1 -1
  171. package/dist/src/utils/sessionCleanup.test.js +38 -0
  172. package/dist/src/utils/sessionCleanup.test.js.map +1 -1
  173. package/dist/src/utils/sessionUtils.d.ts +49 -4
  174. package/dist/src/utils/sessionUtils.js +100 -25
  175. package/dist/src/utils/sessionUtils.js.map +1 -1
  176. package/dist/src/utils/sessionUtils.test.js +46 -3
  177. package/dist/src/utils/sessionUtils.test.js.map +1 -1
  178. package/dist/src/utils/sessions.js +4 -1
  179. package/dist/src/utils/sessions.js.map +1 -1
  180. package/dist/src/utils/sessions.test.js +42 -0
  181. package/dist/src/utils/sessions.test.js.map +1 -1
  182. package/dist/src/zed-integration/connection.test.d.ts +6 -0
  183. package/dist/src/zed-integration/connection.test.js +175 -0
  184. package/dist/src/zed-integration/connection.test.js.map +1 -0
  185. package/dist/src/zed-integration/fileSystemService.test.d.ts +6 -0
  186. package/dist/src/zed-integration/fileSystemService.test.js +98 -0
  187. package/dist/src/zed-integration/fileSystemService.test.js.map +1 -0
  188. package/dist/src/zed-integration/schema.d.ts +30 -30
  189. package/dist/src/zed-integration/zedIntegration.d.ts +31 -1
  190. package/dist/src/zed-integration/zedIntegration.js +5 -2
  191. package/dist/src/zed-integration/zedIntegration.js.map +1 -1
  192. package/dist/src/zed-integration/zedIntegration.test.d.ts +6 -0
  193. package/dist/src/zed-integration/zedIntegration.test.js +619 -0
  194. package/dist/src/zed-integration/zedIntegration.test.js.map +1 -0
  195. package/dist/tsconfig.tsbuildinfo +1 -1
  196. package/package.json +4 -4
  197. package/dist/google-gemini-cli-0.18.0-preview.0.tgz +0 -0
  198. package/dist/src/utils/stdio.d.ts +0 -32
  199. package/dist/src/utils/stdio.js +0 -85
  200. package/dist/src/utils/stdio.js.map +0 -1
  201. package/dist/src/utils/stdio.test.js +0 -47
  202. package/dist/src/utils/stdio.test.js.map +0 -1
  203. /package/dist/src/{utils/stdio.test.d.ts → config/extensions/consent.test.d.ts} +0 -0
@@ -104,76 +104,42 @@ describe('Settings Loading and Merging', () => {
104
104
  expect(settings.workspace.settings).toEqual({});
105
105
  expect(settings.merged).toEqual({});
106
106
  });
107
- it('should load system settings if only system file exists', () => {
108
- mockFsExistsSync.mockImplementation((p) => p === getSystemSettingsPath());
109
- const systemSettingsContent = {
110
- ui: {
111
- theme: 'system-default',
112
- },
113
- tools: {
114
- sandbox: false,
115
- },
116
- };
117
- fs.readFileSync.mockImplementation((p) => {
118
- if (p === getSystemSettingsPath())
119
- return JSON.stringify(systemSettingsContent);
120
- return '{}';
121
- });
122
- const settings = loadSettings(MOCK_WORKSPACE_DIR);
123
- expect(fs.readFileSync).toHaveBeenCalledWith(getSystemSettingsPath(), 'utf-8');
124
- expect(settings.system.settings).toEqual(systemSettingsContent);
125
- expect(settings.user.settings).toEqual({});
126
- expect(settings.workspace.settings).toEqual({});
127
- expect(settings.merged).toEqual({
128
- ...systemSettingsContent,
129
- });
130
- });
131
- it('should load user settings if only user file exists', () => {
132
- const expectedUserSettingsPath = USER_SETTINGS_PATH; // Use the path actually resolved by the (mocked) module
133
- mockFsExistsSync.mockImplementation((p) => p === expectedUserSettingsPath);
134
- const userSettingsContent = {
135
- ui: {
136
- theme: 'dark',
137
- },
138
- context: {
139
- fileName: 'USER_CONTEXT.md',
107
+ it.each([
108
+ {
109
+ scope: 'system',
110
+ path: getSystemSettingsPath(),
111
+ content: {
112
+ ui: { theme: 'system-default' },
113
+ tools: { sandbox: false },
114
+ },
115
+ },
116
+ {
117
+ scope: 'user',
118
+ path: USER_SETTINGS_PATH,
119
+ content: {
120
+ ui: { theme: 'dark' },
121
+ context: { fileName: 'USER_CONTEXT.md' },
122
+ },
123
+ },
124
+ {
125
+ scope: 'workspace',
126
+ path: MOCK_WORKSPACE_SETTINGS_PATH,
127
+ content: {
128
+ tools: { sandbox: true },
129
+ context: { fileName: 'WORKSPACE_CONTEXT.md' },
140
130
  },
141
- };
131
+ },
132
+ ])('should load $scope settings if only $scope file exists', ({ scope, path, content }) => {
133
+ mockFsExistsSync.mockImplementation((p) => p === path);
142
134
  fs.readFileSync.mockImplementation((p) => {
143
- if (p === expectedUserSettingsPath)
144
- return JSON.stringify(userSettingsContent);
135
+ if (p === path)
136
+ return JSON.stringify(content);
145
137
  return '{}';
146
138
  });
147
139
  const settings = loadSettings(MOCK_WORKSPACE_DIR);
148
- expect(fs.readFileSync).toHaveBeenCalledWith(expectedUserSettingsPath, 'utf-8');
149
- expect(settings.user.settings).toEqual(userSettingsContent);
150
- expect(settings.workspace.settings).toEqual({});
151
- expect(settings.merged).toEqual({
152
- ...userSettingsContent,
153
- });
154
- });
155
- it('should load workspace settings if only workspace file exists', () => {
156
- mockFsExistsSync.mockImplementation((p) => p === MOCK_WORKSPACE_SETTINGS_PATH);
157
- const workspaceSettingsContent = {
158
- tools: {
159
- sandbox: true,
160
- },
161
- context: {
162
- fileName: 'WORKSPACE_CONTEXT.md',
163
- },
164
- };
165
- fs.readFileSync.mockImplementation((p) => {
166
- if (p === MOCK_WORKSPACE_SETTINGS_PATH)
167
- return JSON.stringify(workspaceSettingsContent);
168
- return '';
169
- });
170
- const settings = loadSettings(MOCK_WORKSPACE_DIR);
171
- expect(fs.readFileSync).toHaveBeenCalledWith(MOCK_WORKSPACE_SETTINGS_PATH, 'utf-8');
172
- expect(settings.user.settings).toEqual({});
173
- expect(settings.workspace.settings).toEqual(workspaceSettingsContent);
174
- expect(settings.merged).toEqual({
175
- ...workspaceSettingsContent,
176
- });
140
+ expect(fs.readFileSync).toHaveBeenCalledWith(path, 'utf-8');
141
+ expect(settings[scope].settings).toEqual(content);
142
+ expect(settings.merged).toEqual(content);
177
143
  });
178
144
  it('should merge system, user and workspace settings, with system taking precedence over workspace, and workspace over user', () => {
179
145
  mockFsExistsSync.mockImplementation((p) => p === getSystemSettingsPath() ||
@@ -523,64 +489,55 @@ describe('Settings Loading and Merging', () => {
523
489
  const settings = loadSettings(MOCK_WORKSPACE_DIR);
524
490
  expect(settings.merged.security?.disableYoloMode).toBe(true); // System setting should be used
525
491
  });
526
- it('should handle contextFileName correctly when only in user settings', () => {
527
- mockFsExistsSync.mockImplementation((p) => p === USER_SETTINGS_PATH);
528
- const userSettingsContent = { context: { fileName: 'CUSTOM.md' } };
529
- fs.readFileSync.mockImplementation((p) => {
530
- if (p === USER_SETTINGS_PATH)
531
- return JSON.stringify(userSettingsContent);
532
- return '';
533
- });
534
- const settings = loadSettings(MOCK_WORKSPACE_DIR);
535
- expect(settings.merged.context?.fileName).toBe('CUSTOM.md');
536
- });
537
- it('should handle contextFileName correctly when only in workspace settings', () => {
538
- mockFsExistsSync.mockImplementation((p) => p === MOCK_WORKSPACE_SETTINGS_PATH);
539
- const workspaceSettingsContent = {
540
- context: { fileName: 'PROJECT_SPECIFIC.md' },
541
- };
492
+ it.each([
493
+ {
494
+ description: 'contextFileName in user settings',
495
+ path: USER_SETTINGS_PATH,
496
+ content: { context: { fileName: 'CUSTOM.md' } },
497
+ expected: { key: 'context.fileName', value: 'CUSTOM.md' },
498
+ },
499
+ {
500
+ description: 'contextFileName in workspace settings',
501
+ path: MOCK_WORKSPACE_SETTINGS_PATH,
502
+ content: { context: { fileName: 'PROJECT_SPECIFIC.md' } },
503
+ expected: { key: 'context.fileName', value: 'PROJECT_SPECIFIC.md' },
504
+ },
505
+ {
506
+ description: 'excludedProjectEnvVars in user settings',
507
+ path: USER_SETTINGS_PATH,
508
+ content: {
509
+ advanced: { excludedEnvVars: ['DEBUG', 'NODE_ENV', 'CUSTOM_VAR'] },
510
+ },
511
+ expected: {
512
+ key: 'advanced.excludedEnvVars',
513
+ value: ['DEBUG', 'NODE_ENV', 'CUSTOM_VAR'],
514
+ },
515
+ },
516
+ {
517
+ description: 'excludedProjectEnvVars in workspace settings',
518
+ path: MOCK_WORKSPACE_SETTINGS_PATH,
519
+ content: {
520
+ advanced: { excludedEnvVars: ['WORKSPACE_DEBUG', 'WORKSPACE_VAR'] },
521
+ },
522
+ expected: {
523
+ key: 'advanced.excludedEnvVars',
524
+ value: ['WORKSPACE_DEBUG', 'WORKSPACE_VAR'],
525
+ },
526
+ },
527
+ ])('should handle $description correctly', ({ path, content, expected }) => {
528
+ mockFsExistsSync.mockImplementation((p) => p === path);
542
529
  fs.readFileSync.mockImplementation((p) => {
543
- if (p === MOCK_WORKSPACE_SETTINGS_PATH)
544
- return JSON.stringify(workspaceSettingsContent);
545
- return '';
546
- });
547
- const settings = loadSettings(MOCK_WORKSPACE_DIR);
548
- expect(settings.merged.context?.fileName).toBe('PROJECT_SPECIFIC.md');
549
- });
550
- it('should handle excludedProjectEnvVars correctly when only in user settings', () => {
551
- mockFsExistsSync.mockImplementation((p) => p === USER_SETTINGS_PATH);
552
- const userSettingsContent = {
553
- general: {},
554
- advanced: { excludedEnvVars: ['DEBUG', 'NODE_ENV', 'CUSTOM_VAR'] },
555
- };
556
- fs.readFileSync.mockImplementation((p) => {
557
- if (p === USER_SETTINGS_PATH)
558
- return JSON.stringify(userSettingsContent);
559
- return '';
560
- });
561
- const settings = loadSettings(MOCK_WORKSPACE_DIR);
562
- expect(settings.merged.advanced?.excludedEnvVars).toEqual([
563
- 'DEBUG',
564
- 'NODE_ENV',
565
- 'CUSTOM_VAR',
566
- ]);
567
- });
568
- it('should handle excludedProjectEnvVars correctly when only in workspace settings', () => {
569
- mockFsExistsSync.mockImplementation((p) => p === MOCK_WORKSPACE_SETTINGS_PATH);
570
- const workspaceSettingsContent = {
571
- general: {},
572
- advanced: { excludedEnvVars: ['WORKSPACE_DEBUG', 'WORKSPACE_VAR'] },
573
- };
574
- fs.readFileSync.mockImplementation((p) => {
575
- if (p === MOCK_WORKSPACE_SETTINGS_PATH)
576
- return JSON.stringify(workspaceSettingsContent);
577
- return '';
530
+ if (p === path)
531
+ return JSON.stringify(content);
532
+ return '{}';
578
533
  });
579
534
  const settings = loadSettings(MOCK_WORKSPACE_DIR);
580
- expect(settings.merged.advanced?.excludedEnvVars).toEqual([
581
- 'WORKSPACE_DEBUG',
582
- 'WORKSPACE_VAR',
583
- ]);
535
+ const keys = expected.key.split('.');
536
+ let result = settings.merged;
537
+ for (const key of keys) {
538
+ result = result[key];
539
+ }
540
+ expect(result).toEqual(expected.value);
584
541
  });
585
542
  it('should merge excludedProjectEnvVars with workspace taking precedence over user', () => {
586
543
  mockFsExistsSync.mockImplementation((p) => p === USER_SETTINGS_PATH || p === MOCK_WORKSPACE_SETTINGS_PATH);
@@ -631,27 +588,28 @@ describe('Settings Loading and Merging', () => {
631
588
  const settings = loadSettings(MOCK_WORKSPACE_DIR);
632
589
  expect(settings.merged.context?.fileName).toBeUndefined();
633
590
  });
634
- it('should load telemetry setting from user settings', () => {
635
- mockFsExistsSync.mockImplementation((p) => p === USER_SETTINGS_PATH);
636
- const userSettingsContent = { telemetry: { enabled: true } };
637
- fs.readFileSync.mockImplementation((p) => {
638
- if (p === USER_SETTINGS_PATH)
639
- return JSON.stringify(userSettingsContent);
640
- return '{}';
641
- });
642
- const settings = loadSettings(MOCK_WORKSPACE_DIR);
643
- expect(settings.merged.telemetry?.enabled).toBe(true);
644
- });
645
- it('should load telemetry setting from workspace settings', () => {
646
- mockFsExistsSync.mockImplementation((p) => p === MOCK_WORKSPACE_SETTINGS_PATH);
647
- const workspaceSettingsContent = { telemetry: { enabled: false } };
591
+ it.each([
592
+ {
593
+ scope: 'user',
594
+ path: USER_SETTINGS_PATH,
595
+ content: { telemetry: { enabled: true } },
596
+ expected: true,
597
+ },
598
+ {
599
+ scope: 'workspace',
600
+ path: MOCK_WORKSPACE_SETTINGS_PATH,
601
+ content: { telemetry: { enabled: false } },
602
+ expected: false,
603
+ },
604
+ ])('should load telemetry setting from $scope settings', ({ path, content, expected }) => {
605
+ mockFsExistsSync.mockImplementation((p) => p === path);
648
606
  fs.readFileSync.mockImplementation((p) => {
649
- if (p === MOCK_WORKSPACE_SETTINGS_PATH)
650
- return JSON.stringify(workspaceSettingsContent);
607
+ if (p === path)
608
+ return JSON.stringify(content);
651
609
  return '{}';
652
610
  });
653
611
  const settings = loadSettings(MOCK_WORKSPACE_DIR);
654
- expect(settings.merged.telemetry?.enabled).toBe(false);
612
+ expect(settings.merged.telemetry?.enabled).toBe(expected);
655
613
  });
656
614
  it('should prioritize workspace telemetry setting over user setting', () => {
657
615
  mockFsExistsSync.mockReturnValue(true);
@@ -730,51 +688,52 @@ describe('Settings Loading and Merging', () => {
730
688
  },
731
689
  });
732
690
  });
733
- it('should handle MCP servers when only in user settings', () => {
734
- mockFsExistsSync.mockImplementation((p) => p === USER_SETTINGS_PATH);
735
- const userSettingsContent = {
736
- mcpServers: {
691
+ it.each([
692
+ {
693
+ scope: 'user',
694
+ path: USER_SETTINGS_PATH,
695
+ content: {
696
+ mcpServers: {
697
+ 'user-only-server': {
698
+ command: 'user-only-command',
699
+ description: 'User only server',
700
+ },
701
+ },
702
+ },
703
+ expected: {
737
704
  'user-only-server': {
738
705
  command: 'user-only-command',
739
706
  description: 'User only server',
740
707
  },
741
708
  },
742
- };
743
- fs.readFileSync.mockImplementation((p) => {
744
- if (p === USER_SETTINGS_PATH)
745
- return JSON.stringify(userSettingsContent);
746
- return '';
747
- });
748
- const settings = loadSettings(MOCK_WORKSPACE_DIR);
749
- expect(settings.merged.mcpServers).toEqual({
750
- 'user-only-server': {
751
- command: 'user-only-command',
752
- description: 'User only server',
709
+ },
710
+ {
711
+ scope: 'workspace',
712
+ path: MOCK_WORKSPACE_SETTINGS_PATH,
713
+ content: {
714
+ mcpServers: {
715
+ 'workspace-only-server': {
716
+ command: 'workspace-only-command',
717
+ description: 'Workspace only server',
718
+ },
719
+ },
753
720
  },
754
- });
755
- });
756
- it('should handle MCP servers when only in workspace settings', () => {
757
- mockFsExistsSync.mockImplementation((p) => p === MOCK_WORKSPACE_SETTINGS_PATH);
758
- const workspaceSettingsContent = {
759
- mcpServers: {
721
+ expected: {
760
722
  'workspace-only-server': {
761
723
  command: 'workspace-only-command',
762
724
  description: 'Workspace only server',
763
725
  },
764
726
  },
765
- };
727
+ },
728
+ ])('should handle MCP servers when only in $scope settings', ({ path, content, expected }) => {
729
+ mockFsExistsSync.mockImplementation((p) => p === path);
766
730
  fs.readFileSync.mockImplementation((p) => {
767
- if (p === MOCK_WORKSPACE_SETTINGS_PATH)
768
- return JSON.stringify(workspaceSettingsContent);
769
- return '';
731
+ if (p === path)
732
+ return JSON.stringify(content);
733
+ return '{}';
770
734
  });
771
735
  const settings = loadSettings(MOCK_WORKSPACE_DIR);
772
- expect(settings.merged.mcpServers).toEqual({
773
- 'workspace-only-server': {
774
- command: 'workspace-only-command',
775
- description: 'Workspace only server',
776
- },
777
- });
736
+ expect(settings.merged.mcpServers).toEqual(expected);
778
737
  });
779
738
  it('should have mcpServers as undefined if not in any settings file', () => {
780
739
  mockFsExistsSync.mockReturnValue(false); // No settings files exist
@@ -877,65 +836,44 @@ describe('Settings Loading and Merging', () => {
877
836
  excluded: ['workspace-excluded'],
878
837
  });
879
838
  });
880
- it('should merge compressionThreshold settings, with workspace taking precedence', () => {
881
- mockFsExistsSync.mockReturnValue(true);
882
- const userSettingsContent = {
883
- general: {},
884
- model: { compressionThreshold: 0.5 },
885
- };
886
- const workspaceSettingsContent = {
887
- general: {},
888
- model: { compressionThreshold: 0.8 },
889
- };
890
- fs.readFileSync.mockImplementation((p) => {
891
- if (p === USER_SETTINGS_PATH)
892
- return JSON.stringify(userSettingsContent);
893
- if (p === MOCK_WORKSPACE_SETTINGS_PATH)
894
- return JSON.stringify(workspaceSettingsContent);
895
- return '{}';
896
- });
897
- const settings = loadSettings(MOCK_WORKSPACE_DIR);
898
- expect(settings.user.settings.model?.compressionThreshold).toEqual(0.5);
899
- expect(settings.workspace.settings.model?.compressionThreshold).toEqual(0.8);
900
- expect(settings.merged.model?.compressionThreshold).toEqual(0.8);
901
- });
902
- it('should merge output format settings, with workspace taking precedence', () => {
903
- mockFsExistsSync.mockReturnValue(true);
904
- const userSettingsContent = {
905
- output: { format: 'text' },
906
- };
907
- const workspaceSettingsContent = {
908
- output: { format: 'json' },
909
- };
910
- fs.readFileSync.mockImplementation((p) => {
911
- if (p === USER_SETTINGS_PATH)
912
- return JSON.stringify(userSettingsContent);
913
- if (p === MOCK_WORKSPACE_SETTINGS_PATH)
914
- return JSON.stringify(workspaceSettingsContent);
915
- return '{}';
916
- });
917
- const settings = loadSettings(MOCK_WORKSPACE_DIR);
918
- expect(settings.merged.output?.format).toBe('json');
919
- });
920
- it('should handle compressionThreshold when only in user settings', () => {
921
- mockFsExistsSync.mockImplementation((p) => p === USER_SETTINGS_PATH);
922
- const userSettingsContent = {
923
- general: {},
924
- model: { compressionThreshold: 0.5 },
925
- };
926
- fs.readFileSync.mockImplementation((p) => {
927
- if (p === USER_SETTINGS_PATH)
928
- return JSON.stringify(userSettingsContent);
929
- return '{}';
839
+ describe('compressionThreshold settings', () => {
840
+ it.each([
841
+ {
842
+ description: 'should be taken from user settings if only present there',
843
+ userContent: { model: { compressionThreshold: 0.5 } },
844
+ workspaceContent: {},
845
+ expected: 0.5,
846
+ },
847
+ {
848
+ description: 'should be taken from workspace settings if only present there',
849
+ userContent: {},
850
+ workspaceContent: { model: { compressionThreshold: 0.8 } },
851
+ expected: 0.8,
852
+ },
853
+ {
854
+ description: 'should prioritize workspace settings over user settings',
855
+ userContent: { model: { compressionThreshold: 0.5 } },
856
+ workspaceContent: { model: { compressionThreshold: 0.8 } },
857
+ expected: 0.8,
858
+ },
859
+ {
860
+ description: 'should be undefined if not in any settings file',
861
+ userContent: {},
862
+ workspaceContent: {},
863
+ expected: undefined,
864
+ },
865
+ ])('$description', ({ userContent, workspaceContent, expected }) => {
866
+ mockFsExistsSync.mockReturnValue(true);
867
+ fs.readFileSync.mockImplementation((p) => {
868
+ if (p === USER_SETTINGS_PATH)
869
+ return JSON.stringify(userContent);
870
+ if (p === MOCK_WORKSPACE_SETTINGS_PATH)
871
+ return JSON.stringify(workspaceContent);
872
+ return '{}';
873
+ });
874
+ const settings = loadSettings(MOCK_WORKSPACE_DIR);
875
+ expect(settings.merged.model?.compressionThreshold).toEqual(expected);
930
876
  });
931
- const settings = loadSettings(MOCK_WORKSPACE_DIR);
932
- expect(settings.merged.model?.compressionThreshold).toEqual(0.5);
933
- });
934
- it('should have model as undefined if not in any settings file', () => {
935
- mockFsExistsSync.mockReturnValue(false); // No settings files exist
936
- fs.readFileSync.mockReturnValue('{}');
937
- const settings = loadSettings(MOCK_WORKSPACE_DIR);
938
- expect(settings.merged.model).toBeUndefined();
939
877
  });
940
878
  it('should use user compressionThreshold if workspace does not define it', () => {
941
879
  mockFsExistsSync.mockReturnValue(true);