@google/gemini-cli 0.12.0-nightly.20251027.cb0947c5 → 0.12.0-preview.0

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 (168) hide show
  1. package/README.md +7 -5
  2. package/dist/package.json +2 -2
  3. package/dist/src/commands/extensions/disable.d.ts +1 -1
  4. package/dist/src/commands/extensions/disable.js +5 -4
  5. package/dist/src/commands/extensions/disable.js.map +1 -1
  6. package/dist/src/commands/extensions/enable.d.ts +1 -1
  7. package/dist/src/commands/extensions/enable.js +3 -2
  8. package/dist/src/commands/extensions/enable.js.map +1 -1
  9. package/dist/src/commands/extensions/install.js +2 -1
  10. package/dist/src/commands/extensions/install.js.map +1 -1
  11. package/dist/src/commands/extensions/install.test.js +1 -0
  12. package/dist/src/commands/extensions/install.test.js.map +1 -1
  13. package/dist/src/commands/extensions/link.js +2 -1
  14. package/dist/src/commands/extensions/link.js.map +1 -1
  15. package/dist/src/commands/extensions/list.js +2 -2
  16. package/dist/src/commands/extensions/list.js.map +1 -1
  17. package/dist/src/commands/extensions/uninstall.js +2 -1
  18. package/dist/src/commands/extensions/uninstall.js.map +1 -1
  19. package/dist/src/commands/extensions/update.js +2 -2
  20. package/dist/src/commands/extensions/update.js.map +1 -1
  21. package/dist/src/commands/mcp/list.js +2 -2
  22. package/dist/src/commands/mcp/list.js.map +1 -1
  23. package/dist/src/config/config.d.ts +5 -3
  24. package/dist/src/config/config.js +42 -9
  25. package/dist/src/config/config.js.map +1 -1
  26. package/dist/src/config/config.test.js +186 -161
  27. package/dist/src/config/config.test.js.map +1 -1
  28. package/dist/src/config/extension-manager.d.ts +23 -10
  29. package/dist/src/config/extension-manager.js +89 -62
  30. package/dist/src/config/extension-manager.js.map +1 -1
  31. package/dist/src/config/extension.test.js +158 -74
  32. package/dist/src/config/extension.test.js.map +1 -1
  33. package/dist/src/config/extensions/extensionSettings.d.ts +3 -3
  34. package/dist/src/config/extensions/extensionSettings.js +74 -24
  35. package/dist/src/config/extensions/extensionSettings.js.map +1 -1
  36. package/dist/src/config/extensions/extensionSettings.test.js +145 -24
  37. package/dist/src/config/extensions/extensionSettings.test.js.map +1 -1
  38. package/dist/src/config/extensions/github.js +3 -3
  39. package/dist/src/config/extensions/github.js.map +1 -1
  40. package/dist/src/config/extensions/github.test.js +1 -1
  41. package/dist/src/config/extensions/github.test.js.map +1 -1
  42. package/dist/src/config/extensions/update.js +7 -6
  43. package/dist/src/config/extensions/update.js.map +1 -1
  44. package/dist/src/config/extensions/update.test.js +54 -31
  45. package/dist/src/config/extensions/update.test.js.map +1 -1
  46. package/dist/src/config/keyBindings.js +1 -1
  47. package/dist/src/config/keyBindings.js.map +1 -1
  48. package/dist/src/config/policies/read-only.toml +56 -0
  49. package/dist/src/config/policies/write.toml +63 -0
  50. package/dist/src/config/policies/yolo.toml +31 -0
  51. package/dist/src/config/policy-engine.integration.test.js +41 -38
  52. package/dist/src/config/policy-engine.integration.test.js.map +1 -1
  53. package/dist/src/config/policy-toml-loader.d.ts +46 -0
  54. package/dist/src/config/policy-toml-loader.js +314 -0
  55. package/dist/src/config/policy-toml-loader.js.map +1 -0
  56. package/dist/src/config/policy-toml-loader.test.d.ts +6 -0
  57. package/dist/src/config/policy-toml-loader.test.js +626 -0
  58. package/dist/src/config/policy-toml-loader.test.js.map +1 -0
  59. package/dist/src/config/policy.d.ts +9 -2
  60. package/dist/src/config/policy.js +139 -110
  61. package/dist/src/config/policy.js.map +1 -1
  62. package/dist/src/config/policy.test.js +780 -82
  63. package/dist/src/config/policy.test.js.map +1 -1
  64. package/dist/src/config/settings.test.js +4 -4
  65. package/dist/src/config/settings.test.js.map +1 -1
  66. package/dist/src/gemini.js +6 -17
  67. package/dist/src/gemini.js.map +1 -1
  68. package/dist/src/gemini.test.js +1 -0
  69. package/dist/src/gemini.test.js.map +1 -1
  70. package/dist/src/generated/git-commit.d.ts +2 -2
  71. package/dist/src/generated/git-commit.js +2 -2
  72. package/dist/src/generated/git-commit.js.map +1 -1
  73. package/dist/src/test-utils/render.d.ts +12 -0
  74. package/dist/src/test-utils/render.js +28 -1
  75. package/dist/src/test-utils/render.js.map +1 -1
  76. package/dist/src/test-utils/render.test.d.ts +6 -0
  77. package/dist/src/test-utils/render.test.js +54 -0
  78. package/dist/src/test-utils/render.test.js.map +1 -0
  79. package/dist/src/ui/AppContainer.js +28 -22
  80. package/dist/src/ui/AppContainer.js.map +1 -1
  81. package/dist/src/ui/AppContainer.test.js +8 -0
  82. package/dist/src/ui/AppContainer.test.js.map +1 -1
  83. package/dist/src/ui/commands/directoryCommand.js +1 -1
  84. package/dist/src/ui/commands/directoryCommand.js.map +1 -1
  85. package/dist/src/ui/commands/extensionsCommand.js +45 -1
  86. package/dist/src/ui/commands/extensionsCommand.js.map +1 -1
  87. package/dist/src/ui/commands/extensionsCommand.test.js +64 -1
  88. package/dist/src/ui/commands/extensionsCommand.test.js.map +1 -1
  89. package/dist/src/ui/commands/memoryCommand.js +1 -1
  90. package/dist/src/ui/commands/memoryCommand.js.map +1 -1
  91. package/dist/src/ui/commands/memoryCommand.test.js +3 -1
  92. package/dist/src/ui/commands/memoryCommand.test.js.map +1 -1
  93. package/dist/src/ui/components/ConsoleSummaryDisplay.js +1 -1
  94. package/dist/src/ui/components/ConsoleSummaryDisplay.js.map +1 -1
  95. package/dist/src/ui/components/DetailedMessagesDisplay.js +1 -1
  96. package/dist/src/ui/components/DetailedMessagesDisplay.js.map +1 -1
  97. package/dist/src/ui/components/FolderTrustDialog.test.js +4 -5
  98. package/dist/src/ui/components/FolderTrustDialog.test.js.map +1 -1
  99. package/dist/src/ui/components/Footer.js +1 -1
  100. package/dist/src/ui/components/Footer.js.map +1 -1
  101. package/dist/src/ui/components/Footer.test.js +24 -0
  102. package/dist/src/ui/components/Footer.test.js.map +1 -1
  103. package/dist/src/ui/components/Help.test.js +0 -1
  104. package/dist/src/ui/components/Help.test.js.map +1 -1
  105. package/dist/src/ui/components/ModelDialog.test.js +5 -6
  106. package/dist/src/ui/components/ModelDialog.test.js.map +1 -1
  107. package/dist/src/ui/components/PermissionsModifyTrustDialog.test.js +11 -13
  108. package/dist/src/ui/components/PermissionsModifyTrustDialog.test.js.map +1 -1
  109. package/dist/src/ui/components/SettingsDialog.test.js +12 -14
  110. package/dist/src/ui/components/SettingsDialog.test.js.map +1 -1
  111. package/dist/src/ui/components/shared/BaseSelectionList.test.js +11 -13
  112. package/dist/src/ui/components/shared/BaseSelectionList.test.js.map +1 -1
  113. package/dist/src/ui/components/shared/text-buffer.test.js +2 -2
  114. package/dist/src/ui/components/shared/text-buffer.test.js.map +1 -1
  115. package/dist/src/ui/contexts/KeypressContext.test.js +6 -5
  116. package/dist/src/ui/contexts/KeypressContext.test.js.map +1 -1
  117. package/dist/src/ui/contexts/SessionContext.test.js +27 -14
  118. package/dist/src/ui/contexts/SessionContext.test.js.map +1 -1
  119. package/dist/src/ui/hooks/atCommandProcessor.js +2 -2
  120. package/dist/src/ui/hooks/atCommandProcessor.js.map +1 -1
  121. package/dist/src/ui/hooks/useAtCompletion.test.js +32 -23
  122. package/dist/src/ui/hooks/useAtCompletion.test.js.map +1 -1
  123. package/dist/src/ui/hooks/useAutoAcceptIndicator.test.js +2 -2
  124. package/dist/src/ui/hooks/useAutoAcceptIndicator.test.js.map +1 -1
  125. package/dist/src/ui/hooks/useExtensionUpdates.d.ts +1 -2
  126. package/dist/src/ui/hooks/useExtensionUpdates.js +2 -1
  127. package/dist/src/ui/hooks/useExtensionUpdates.js.map +1 -1
  128. package/dist/src/ui/hooks/useExtensionUpdates.test.js +14 -20
  129. package/dist/src/ui/hooks/useExtensionUpdates.test.js.map +1 -1
  130. package/dist/src/ui/hooks/useFlickerDetector.test.js +9 -6
  131. package/dist/src/ui/hooks/useFlickerDetector.test.js.map +1 -1
  132. package/dist/src/ui/hooks/useFolderTrust.test.js +45 -23
  133. package/dist/src/ui/hooks/useFolderTrust.test.js.map +1 -1
  134. package/dist/src/ui/hooks/useGeminiStream.js +7 -5
  135. package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
  136. package/dist/src/ui/hooks/useGeminiStream.test.js +42 -41
  137. package/dist/src/ui/hooks/useGeminiStream.test.js.map +1 -1
  138. package/dist/src/ui/hooks/useHistoryManager.test.js +2 -2
  139. package/dist/src/ui/hooks/useHistoryManager.test.js.map +1 -1
  140. package/dist/src/ui/hooks/useInputHistory.test.js +2 -2
  141. package/dist/src/ui/hooks/useInputHistory.test.js.map +1 -1
  142. package/dist/src/ui/hooks/useInputHistoryStore.test.js +2 -2
  143. package/dist/src/ui/hooks/useInputHistoryStore.test.js.map +1 -1
  144. package/dist/src/ui/hooks/usePermissionsModifyTrust.test.js +2 -3
  145. package/dist/src/ui/hooks/usePermissionsModifyTrust.test.js.map +1 -1
  146. package/dist/src/ui/hooks/usePhraseCycler.js +1 -1
  147. package/dist/src/ui/hooks/usePhraseCycler.js.map +1 -1
  148. package/dist/src/ui/hooks/usePhraseCycler.test.js +83 -111
  149. package/dist/src/ui/hooks/usePhraseCycler.test.js.map +1 -1
  150. package/dist/src/ui/hooks/useQuotaAndFallback.test.js +2 -2
  151. package/dist/src/ui/hooks/useQuotaAndFallback.test.js.map +1 -1
  152. package/dist/src/ui/hooks/useReactToolScheduler.test.js +1 -2
  153. package/dist/src/ui/hooks/useReactToolScheduler.test.js.map +1 -1
  154. package/dist/src/ui/hooks/useReverseSearchCompletion.test.js +2 -2
  155. package/dist/src/ui/hooks/useReverseSearchCompletion.test.js.map +1 -1
  156. package/dist/src/ui/hooks/useShellHistory.test.js +40 -17
  157. package/dist/src/ui/hooks/useShellHistory.test.js.map +1 -1
  158. package/dist/src/ui/hooks/useSlashCompletion.test.js +54 -49
  159. package/dist/src/ui/hooks/useSlashCompletion.test.js.map +1 -1
  160. package/dist/src/ui/hooks/useToolScheduler.test.js +48 -42
  161. package/dist/src/ui/hooks/useToolScheduler.test.js.map +1 -1
  162. package/dist/src/ui/keyMatchers.test.js +3 -3
  163. package/dist/src/ui/keyMatchers.test.js.map +1 -1
  164. package/dist/src/zed-integration/zedIntegration.d.ts +2 -2
  165. package/dist/src/zed-integration/zedIntegration.js +4 -6
  166. package/dist/src/zed-integration/zedIntegration.js.map +1 -1
  167. package/dist/tsconfig.tsbuildinfo +1 -1
  168. package/package.json +3 -3
@@ -3,12 +3,11 @@
3
3
  * Copyright 2025 Google LLC
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
- /** @vitest-environment jsdom */
7
6
  import { vi } from 'vitest';
8
7
  import * as fs from 'node:fs';
9
8
  import * as os from 'node:os';
10
9
  import * as path from 'node:path';
11
- import { ExtensionUninstallEvent, ExtensionDisableEvent, ExtensionEnableEvent, } from '@google/gemini-cli-core';
10
+ import { ExtensionUninstallEvent, ExtensionDisableEvent, ExtensionEnableEvent, KeychainTokenStorage, } from '@google/gemini-cli-core';
12
11
  import { loadSettings, SettingScope } from './settings.js';
13
12
  import { isWorkspaceTrusted } from './trustedFolders.js';
14
13
  import { createExtension } from '../test-utils/createExtension.js';
@@ -43,11 +42,12 @@ vi.mock('simple-git', () => ({
43
42
  return mockGit;
44
43
  }),
45
44
  }));
45
+ const mockHomedir = vi.hoisted(() => vi.fn(() => '/tmp/mock-home'));
46
46
  vi.mock('os', async (importOriginal) => {
47
47
  const mockedOs = await importOriginal();
48
48
  return {
49
49
  ...mockedOs,
50
- homedir: vi.fn(),
50
+ homedir: mockHomedir,
51
51
  };
52
52
  });
53
53
  vi.mock('./trustedFolders.js', async (importOriginal) => {
@@ -75,6 +75,13 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
75
75
  ExtensionInstallEvent: vi.fn(),
76
76
  ExtensionUninstallEvent: vi.fn(),
77
77
  ExtensionDisableEvent: vi.fn(),
78
+ KeychainTokenStorage: vi.fn().mockImplementation(() => ({
79
+ getSecret: vi.fn(),
80
+ setSecret: vi.fn(),
81
+ deleteSecret: vi.fn(),
82
+ listSecrets: vi.fn(),
83
+ isAvailable: vi.fn().mockResolvedValue(true),
84
+ })),
78
85
  };
79
86
  });
80
87
  vi.mock('child_process', async (importOriginal) => {
@@ -91,7 +98,29 @@ describe('extension tests', () => {
91
98
  let extensionManager;
92
99
  let mockRequestConsent;
93
100
  let mockPromptForSettings;
101
+ let mockKeychainStorage;
102
+ let keychainData;
94
103
  beforeEach(() => {
104
+ vi.clearAllMocks();
105
+ keychainData = {};
106
+ mockKeychainStorage = {
107
+ getSecret: vi
108
+ .fn()
109
+ .mockImplementation(async (key) => keychainData[key] || null),
110
+ setSecret: vi
111
+ .fn()
112
+ .mockImplementation(async (key, value) => {
113
+ keychainData[key] = value;
114
+ }),
115
+ deleteSecret: vi.fn().mockImplementation(async (key) => {
116
+ delete keychainData[key];
117
+ }),
118
+ listSecrets: vi
119
+ .fn()
120
+ .mockImplementation(async () => Object.keys(keychainData)),
121
+ isAvailable: vi.fn().mockResolvedValue(true),
122
+ };
123
+ KeychainTokenStorage.mockImplementation(() => mockKeychainStorage);
95
124
  tempHomeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gemini-cli-test-home-'));
96
125
  tempWorkspaceDir = fs.mkdtempSync(path.join(tempHomeDir, 'gemini-cli-test-workspace-'));
97
126
  userExtensionsDir = path.join(tempHomeDir, EXTENSIONS_DIRECTORY_NAME);
@@ -110,7 +139,7 @@ describe('extension tests', () => {
110
139
  workspaceDir: tempWorkspaceDir,
111
140
  requestConsent: mockRequestConsent,
112
141
  requestSetting: mockPromptForSettings,
113
- loadedSettings: loadSettings(tempWorkspaceDir),
142
+ settings: loadSettings(tempWorkspaceDir).merged,
114
143
  });
115
144
  });
116
145
  afterEach(() => {
@@ -119,7 +148,7 @@ describe('extension tests', () => {
119
148
  vi.restoreAllMocks();
120
149
  });
121
150
  describe('loadExtensions', () => {
122
- it('should include extension path in loaded extension', () => {
151
+ it('should include extension path in loaded extension', async () => {
123
152
  const extensionDir = path.join(userExtensionsDir, 'test-extension');
124
153
  fs.mkdirSync(extensionDir, { recursive: true });
125
154
  createExtension({
@@ -127,12 +156,12 @@ describe('extension tests', () => {
127
156
  name: 'test-extension',
128
157
  version: '1.0.0',
129
158
  });
130
- const extensions = extensionManager.loadExtensions();
159
+ const extensions = await extensionManager.loadExtensions();
131
160
  expect(extensions).toHaveLength(1);
132
161
  expect(extensions[0].path).toBe(extensionDir);
133
162
  expect(extensions[0].name).toBe('test-extension');
134
163
  });
135
- it('should load context file path when GEMINI.md is present', () => {
164
+ it('should load context file path when GEMINI.md is present', async () => {
136
165
  createExtension({
137
166
  extensionsDir: userExtensionsDir,
138
167
  name: 'ext1',
@@ -144,7 +173,7 @@ describe('extension tests', () => {
144
173
  name: 'ext2',
145
174
  version: '2.0.0',
146
175
  });
147
- const extensions = extensionManager.loadExtensions();
176
+ const extensions = await extensionManager.loadExtensions();
148
177
  expect(extensions).toHaveLength(2);
149
178
  const ext1 = extensions.find((e) => e.name === 'ext1');
150
179
  const ext2 = extensions.find((e) => e.name === 'ext2');
@@ -153,7 +182,7 @@ describe('extension tests', () => {
153
182
  ]);
154
183
  expect(ext2?.contextFiles).toEqual([]);
155
184
  });
156
- it('should load context file path from the extension config', () => {
185
+ it('should load context file path from the extension config', async () => {
157
186
  createExtension({
158
187
  extensionsDir: userExtensionsDir,
159
188
  name: 'ext1',
@@ -161,14 +190,14 @@ describe('extension tests', () => {
161
190
  addContextFile: false,
162
191
  contextFileName: 'my-context-file.md',
163
192
  });
164
- const extensions = extensionManager.loadExtensions();
193
+ const extensions = await extensionManager.loadExtensions();
165
194
  expect(extensions).toHaveLength(1);
166
195
  const ext1 = extensions.find((e) => e.name === 'ext1');
167
196
  expect(ext1?.contextFiles).toEqual([
168
197
  path.join(userExtensionsDir, 'ext1', 'my-context-file.md'),
169
198
  ]);
170
199
  });
171
- it('should annotate disabled extensions', () => {
200
+ it('should annotate disabled extensions', async () => {
172
201
  createExtension({
173
202
  extensionsDir: userExtensionsDir,
174
203
  name: 'disabled-extension',
@@ -179,15 +208,16 @@ describe('extension tests', () => {
179
208
  name: 'enabled-extension',
180
209
  version: '2.0.0',
181
210
  });
182
- extensionManager.disableExtension('disabled-extension', SettingScope.User);
183
- const extensions = extensionManager.loadExtensions();
211
+ await extensionManager.loadExtensions();
212
+ await extensionManager.disableExtension('disabled-extension', SettingScope.User);
213
+ const extensions = extensionManager.getExtensions();
184
214
  expect(extensions).toHaveLength(2);
185
215
  expect(extensions[0].name).toBe('disabled-extension');
186
216
  expect(extensions[0].isActive).toBe(false);
187
217
  expect(extensions[1].name).toBe('enabled-extension');
188
218
  expect(extensions[1].isActive).toBe(true);
189
219
  });
190
- it('should hydrate variables', () => {
220
+ it('should hydrate variables', async () => {
191
221
  createExtension({
192
222
  extensionsDir: userExtensionsDir,
193
223
  name: 'test-extension',
@@ -200,7 +230,7 @@ describe('extension tests', () => {
200
230
  },
201
231
  },
202
232
  });
203
- const extensions = extensionManager.loadExtensions();
233
+ const extensions = await extensionManager.loadExtensions();
204
234
  expect(extensions).toHaveLength(1);
205
235
  const expectedCwd = path.join(userExtensionsDir, 'test-extension', 'server');
206
236
  expect(extensions[0].mcpServers?.['test-server'].cwd).toBe(expectedCwd);
@@ -213,12 +243,13 @@ describe('extension tests', () => {
213
243
  contextFileName: 'context.md',
214
244
  });
215
245
  fs.writeFileSync(path.join(sourceExtDir, 'context.md'), 'linked context');
216
- const extensionName = await extensionManager.installOrUpdateExtension({
246
+ await extensionManager.loadExtensions();
247
+ const extension = await extensionManager.installOrUpdateExtension({
217
248
  source: sourceExtDir,
218
249
  type: 'link',
219
250
  });
220
- expect(extensionName).toEqual('my-linked-extension');
221
- const extensions = extensionManager.loadExtensions();
251
+ expect(extension.name).toEqual('my-linked-extension');
252
+ const extensions = extensionManager.getExtensions();
222
253
  expect(extensions).toHaveLength(1);
223
254
  const linkedExt = extensions[0];
224
255
  expect(linkedExt.name).toBe('my-linked-extension');
@@ -244,18 +275,19 @@ describe('extension tests', () => {
244
275
  },
245
276
  },
246
277
  });
278
+ await extensionManager.loadExtensions();
247
279
  await extensionManager.installOrUpdateExtension({
248
280
  source: sourceExtDir,
249
281
  type: 'link',
250
282
  });
251
- const extensions = extensionManager.loadExtensions();
283
+ const extensions = extensionManager.getExtensions();
252
284
  expect(extensions).toHaveLength(1);
253
285
  expect(extensions[0].mcpServers?.['test-server'].cwd).toBe(path.join(sourceExtDir, 'server'));
254
286
  expect(extensions[0].mcpServers?.['test-server'].args).toEqual([
255
287
  path.join(sourceExtDir, 'server', 'index.js'),
256
288
  ]);
257
289
  });
258
- it('should resolve environment variables in extension configuration', () => {
290
+ it('should resolve environment variables in extension configuration', async () => {
259
291
  process.env['TEST_API_KEY'] = 'test-api-key-123';
260
292
  process.env['TEST_DB_URL'] = 'postgresql://localhost:5432/testdb';
261
293
  try {
@@ -281,7 +313,7 @@ describe('extension tests', () => {
281
313
  },
282
314
  };
283
315
  fs.writeFileSync(configPath, JSON.stringify(extensionConfig));
284
- const extensions = extensionManager.loadExtensions();
316
+ const extensions = await extensionManager.loadExtensions();
285
317
  expect(extensions).toHaveLength(1);
286
318
  const extension = extensions[0];
287
319
  expect(extension.name).toBe('test-extension');
@@ -298,7 +330,7 @@ describe('extension tests', () => {
298
330
  delete process.env['TEST_DB_URL'];
299
331
  }
300
332
  });
301
- it('should resolve environment variables from an extension .env file', () => {
333
+ it('should resolve environment variables from an extension .env file', async () => {
302
334
  const extDir = createExtension({
303
335
  extensionsDir: userExtensionsDir,
304
336
  name: 'test-extension',
@@ -313,10 +345,17 @@ describe('extension tests', () => {
313
345
  },
314
346
  },
315
347
  },
348
+ settings: [
349
+ {
350
+ name: 'My API Key',
351
+ description: 'API key for testing.',
352
+ envVar: 'MY_API_KEY',
353
+ },
354
+ ],
316
355
  });
317
356
  const envFilePath = path.join(extDir, '.env');
318
357
  fs.writeFileSync(envFilePath, 'MY_API_KEY=test-key-from-file\n');
319
- const extensions = extensionManager.loadExtensions();
358
+ const extensions = await extensionManager.loadExtensions();
320
359
  expect(extensions).toHaveLength(1);
321
360
  const extension = extensions[0];
322
361
  const serverConfig = extension.mcpServers['test-server'];
@@ -324,7 +363,7 @@ describe('extension tests', () => {
324
363
  expect(serverConfig.env['API_KEY']).toBe('test-key-from-file');
325
364
  expect(serverConfig.env['STATIC_VALUE']).toBe('no-substitution');
326
365
  });
327
- it('should handle missing environment variables gracefully', () => {
366
+ it('should handle missing environment variables gracefully', async () => {
328
367
  const userExtensionsDir = path.join(tempHomeDir, EXTENSIONS_DIRECTORY_NAME);
329
368
  fs.mkdirSync(userExtensionsDir, { recursive: true });
330
369
  const extDir = path.join(userExtensionsDir, 'test-extension');
@@ -344,7 +383,7 @@ describe('extension tests', () => {
344
383
  },
345
384
  };
346
385
  fs.writeFileSync(path.join(extDir, EXTENSIONS_CONFIG_FILENAME), JSON.stringify(extensionConfig));
347
- const extensions = extensionManager.loadExtensions();
386
+ const extensions = await extensionManager.loadExtensions();
348
387
  expect(extensions).toHaveLength(1);
349
388
  const extension = extensions[0];
350
389
  const serverConfig = extension.mcpServers['test-server'];
@@ -352,7 +391,7 @@ describe('extension tests', () => {
352
391
  expect(serverConfig.env['MISSING_VAR']).toBe('$UNDEFINED_ENV_VAR');
353
392
  expect(serverConfig.env['MISSING_VAR_BRACES']).toBe('${ALSO_UNDEFINED}');
354
393
  });
355
- it('should skip extensions with invalid JSON and log a warning', () => {
394
+ it('should skip extensions with invalid JSON and log a warning', async () => {
356
395
  const consoleSpy = vi
357
396
  .spyOn(console, 'error')
358
397
  .mockImplementation(() => { });
@@ -367,13 +406,13 @@ describe('extension tests', () => {
367
406
  fs.mkdirSync(badExtDir);
368
407
  const badConfigPath = path.join(badExtDir, EXTENSIONS_CONFIG_FILENAME);
369
408
  fs.writeFileSync(badConfigPath, '{ "name": "bad-ext"'); // Malformed
370
- const extensions = extensionManager.loadExtensions();
409
+ const extensions = await extensionManager.loadExtensions();
371
410
  expect(extensions).toHaveLength(1);
372
411
  expect(extensions[0].name).toBe('good-ext');
373
412
  expect(consoleSpy).toHaveBeenCalledExactlyOnceWith(expect.stringContaining(`Warning: Skipping extension in ${badExtDir}: Failed to load extension config from ${badConfigPath}`));
374
413
  consoleSpy.mockRestore();
375
414
  });
376
- it('should skip extensions with missing name and log a warning', () => {
415
+ it('should skip extensions with missing name and log a warning', async () => {
377
416
  const consoleSpy = vi
378
417
  .spyOn(console, 'error')
379
418
  .mockImplementation(() => { });
@@ -388,13 +427,13 @@ describe('extension tests', () => {
388
427
  fs.mkdirSync(badExtDir);
389
428
  const badConfigPath = path.join(badExtDir, EXTENSIONS_CONFIG_FILENAME);
390
429
  fs.writeFileSync(badConfigPath, JSON.stringify({ version: '1.0.0' }));
391
- const extensions = extensionManager.loadExtensions();
430
+ const extensions = await extensionManager.loadExtensions();
392
431
  expect(extensions).toHaveLength(1);
393
432
  expect(extensions[0].name).toBe('good-ext');
394
433
  expect(consoleSpy).toHaveBeenCalledExactlyOnceWith(expect.stringContaining(`Warning: Skipping extension in ${badExtDir}: Failed to load extension config from ${badConfigPath}: Invalid configuration in ${badConfigPath}: missing "name"`));
395
434
  consoleSpy.mockRestore();
396
435
  });
397
- it('should filter trust out of mcp servers', () => {
436
+ it('should filter trust out of mcp servers', async () => {
398
437
  createExtension({
399
438
  extensionsDir: userExtensionsDir,
400
439
  name: 'test-extension',
@@ -407,27 +446,28 @@ describe('extension tests', () => {
407
446
  },
408
447
  },
409
448
  });
410
- const extensions = extensionManager.loadExtensions();
449
+ const extensions = await extensionManager.loadExtensions();
411
450
  expect(extensions).toHaveLength(1);
412
451
  expect(extensions[0].mcpServers?.['test-server'].trust).toBeUndefined();
413
452
  });
414
- it('should throw an error for invalid extension names', () => {
453
+ it('should throw an error for invalid extension names', async () => {
415
454
  const consoleSpy = vi
416
455
  .spyOn(console, 'error')
417
456
  .mockImplementation(() => { });
418
- const badExtDir = createExtension({
457
+ createExtension({
419
458
  extensionsDir: userExtensionsDir,
420
459
  name: 'bad_name',
421
460
  version: '1.0.0',
422
461
  });
423
- const extension = extensionManager.loadExtension(badExtDir);
424
- expect(extension).toBeNull();
462
+ const extensions = await extensionManager.loadExtensions();
463
+ const extension = extensions.find((e) => e.name === 'bad_name');
464
+ expect(extension).toBeUndefined();
425
465
  expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Invalid extension name: "bad_name"'));
426
466
  consoleSpy.mockRestore();
427
467
  });
428
468
  describe('id generation', () => {
429
- it('should generate id from source for non-github git urls', () => {
430
- const extensionDir = createExtension({
469
+ it('should generate id from source for non-github git urls', async () => {
470
+ createExtension({
431
471
  extensionsDir: userExtensionsDir,
432
472
  name: 'my-ext',
433
473
  version: '1.0.0',
@@ -436,11 +476,12 @@ describe('extension tests', () => {
436
476
  source: 'http://somehost.com/foo/bar',
437
477
  },
438
478
  });
439
- const extension = extensionManager.loadExtension(extensionDir);
479
+ const extensions = await extensionManager.loadExtensions();
480
+ const extension = extensions.find((e) => e.name === 'my-ext');
440
481
  expect(extension?.id).toBe(hashValue('http://somehost.com/foo/bar'));
441
482
  });
442
- it('should generate id from owner/repo for github http urls', () => {
443
- const extensionDir = createExtension({
483
+ it('should generate id from owner/repo for github http urls', async () => {
484
+ createExtension({
444
485
  extensionsDir: userExtensionsDir,
445
486
  name: 'my-ext',
446
487
  version: '1.0.0',
@@ -449,11 +490,12 @@ describe('extension tests', () => {
449
490
  source: 'http://github.com/foo/bar',
450
491
  },
451
492
  });
452
- const extension = extensionManager.loadExtension(extensionDir);
493
+ const extensions = await extensionManager.loadExtensions();
494
+ const extension = extensions.find((e) => e.name === 'my-ext');
453
495
  expect(extension?.id).toBe(hashValue('https://github.com/foo/bar'));
454
496
  });
455
- it('should generate id from owner/repo for github ssh urls', () => {
456
- const extensionDir = createExtension({
497
+ it('should generate id from owner/repo for github ssh urls', async () => {
498
+ createExtension({
457
499
  extensionsDir: userExtensionsDir,
458
500
  name: 'my-ext',
459
501
  version: '1.0.0',
@@ -462,11 +504,12 @@ describe('extension tests', () => {
462
504
  source: 'git@github.com:foo/bar',
463
505
  },
464
506
  });
465
- const extension = extensionManager.loadExtension(extensionDir);
507
+ const extensions = await extensionManager.loadExtensions();
508
+ const extension = extensions.find((e) => e.name === 'my-ext');
466
509
  expect(extension?.id).toBe(hashValue('https://github.com/foo/bar'));
467
510
  });
468
- it('should generate id from source for github-release extension', () => {
469
- const extensionDir = createExtension({
511
+ it('should generate id from source for github-release extension', async () => {
512
+ createExtension({
470
513
  extensionsDir: userExtensionsDir,
471
514
  name: 'my-ext',
472
515
  version: '1.0.0',
@@ -475,11 +518,12 @@ describe('extension tests', () => {
475
518
  source: 'https://github.com/foo/bar',
476
519
  },
477
520
  });
478
- const extension = extensionManager.loadExtension(extensionDir);
521
+ const extensions = await extensionManager.loadExtensions();
522
+ const extension = extensions.find((e) => e.name === 'my-ext');
479
523
  expect(extension?.id).toBe(hashValue('https://github.com/foo/bar'));
480
524
  });
481
- it('should generate id from the original source for local extension', () => {
482
- const extensionDir = createExtension({
525
+ it('should generate id from the original source for local extension', async () => {
526
+ createExtension({
483
527
  extensionsDir: userExtensionsDir,
484
528
  name: 'local-ext-name',
485
529
  version: '1.0.0',
@@ -488,7 +532,8 @@ describe('extension tests', () => {
488
532
  source: '/some/path',
489
533
  },
490
534
  });
491
- const extension = extensionManager.loadExtension(extensionDir);
535
+ const extensions = await extensionManager.loadExtensions();
536
+ const extension = extensions.find((e) => e.name === 'local-ext-name');
492
537
  expect(extension?.id).toBe(hashValue('/some/path'));
493
538
  });
494
539
  it('should generate id from the original source for linked extensions', async () => {
@@ -498,20 +543,24 @@ describe('extension tests', () => {
498
543
  name: 'link-ext-name',
499
544
  version: '1.0.0',
500
545
  });
501
- const extensionName = await extensionManager.installOrUpdateExtension({
546
+ await extensionManager.loadExtensions();
547
+ await extensionManager.installOrUpdateExtension({
502
548
  type: 'link',
503
549
  source: actualExtensionDir,
504
550
  });
505
- const extension = extensionManager.loadExtension(new ExtensionStorage(extensionName).getExtensionDir());
551
+ const extension = extensionManager
552
+ .getExtensions()
553
+ .find((e) => e.name === 'link-ext-name');
506
554
  expect(extension?.id).toBe(hashValue(actualExtensionDir));
507
555
  });
508
- it('should generate id from name for extension with no install metadata', () => {
509
- const extensionDir = createExtension({
556
+ it('should generate id from name for extension with no install metadata', async () => {
557
+ createExtension({
510
558
  extensionsDir: userExtensionsDir,
511
559
  name: 'no-meta-name',
512
560
  version: '1.0.0',
513
561
  });
514
- const extension = extensionManager.loadExtension(extensionDir);
562
+ const extensions = await extensionManager.loadExtensions();
563
+ const extension = extensions.find((e) => e.name === 'no-meta-name');
515
564
  expect(extension?.id).toBe(hashValue('no-meta-name'));
516
565
  });
517
566
  });
@@ -525,6 +574,7 @@ describe('extension tests', () => {
525
574
  });
526
575
  const targetExtDir = path.join(userExtensionsDir, 'my-local-extension');
527
576
  const metadataPath = path.join(targetExtDir, INSTALL_METADATA_FILENAME);
577
+ await extensionManager.loadExtensions();
528
578
  await extensionManager.installOrUpdateExtension({
529
579
  source: sourceExtDir,
530
580
  type: 'local',
@@ -544,6 +594,7 @@ describe('extension tests', () => {
544
594
  name: 'my-local-extension',
545
595
  version: '1.0.0',
546
596
  });
597
+ await extensionManager.loadExtensions();
547
598
  await extensionManager.installOrUpdateExtension({
548
599
  source: sourceExtDir,
549
600
  type: 'local',
@@ -605,6 +656,7 @@ describe('extension tests', () => {
605
656
  failureReason: 'no release data',
606
657
  type: 'github-release',
607
658
  });
659
+ await extensionManager.loadExtensions();
608
660
  await extensionManager.installOrUpdateExtension({
609
661
  source: gitUrl,
610
662
  type: 'git',
@@ -626,6 +678,7 @@ describe('extension tests', () => {
626
678
  const targetExtDir = path.join(userExtensionsDir, 'my-linked-extension');
627
679
  const metadataPath = path.join(targetExtDir, INSTALL_METADATA_FILENAME);
628
680
  const configPath = path.join(targetExtDir, EXTENSIONS_CONFIG_FILENAME);
681
+ await extensionManager.loadExtensions();
629
682
  await extensionManager.installOrUpdateExtension({
630
683
  source: sourceExtDir,
631
684
  type: 'link',
@@ -648,6 +701,7 @@ describe('extension tests', () => {
648
701
  name: 'my-local-extension',
649
702
  version: '1.1.0',
650
703
  });
704
+ await extensionManager.loadExtensions();
651
705
  if (isUpdate) {
652
706
  await extensionManager.installOrUpdateExtension({
653
707
  source: sourceExtDir,
@@ -709,10 +763,13 @@ describe('extension tests', () => {
709
763
  },
710
764
  },
711
765
  });
766
+ await extensionManager.loadExtensions();
712
767
  await expect(extensionManager.installOrUpdateExtension({
713
768
  source: sourceExtDir,
714
769
  type: 'local',
715
- })).resolves.toBe('my-local-extension');
770
+ })).resolves.toMatchObject({
771
+ name: 'my-local-extension',
772
+ });
716
773
  expect(mockRequestConsent).toHaveBeenCalledWith(`Installing extension "my-local-extension".
717
774
  ${INSTALL_WARNING_MESSAGE}
718
775
  This extension will run the following MCP servers:
@@ -731,10 +788,11 @@ This extension will run the following MCP servers:
731
788
  },
732
789
  },
733
790
  });
791
+ await extensionManager.loadExtensions();
734
792
  await expect(extensionManager.installOrUpdateExtension({
735
793
  source: sourceExtDir,
736
794
  type: 'local',
737
- })).resolves.toBe('my-local-extension');
795
+ })).resolves.toMatchObject({ name: 'my-local-extension' });
738
796
  });
739
797
  it('should cancel installation if user declines prompt for local extension with mcp servers', async () => {
740
798
  const sourceExtDir = createExtension({
@@ -749,6 +807,7 @@ This extension will run the following MCP servers:
749
807
  },
750
808
  });
751
809
  mockRequestConsent.mockResolvedValue(false);
810
+ await extensionManager.loadExtensions();
752
811
  await expect(extensionManager.installOrUpdateExtension({
753
812
  source: sourceExtDir,
754
813
  type: 'local',
@@ -762,6 +821,7 @@ This extension will run the following MCP servers:
762
821
  });
763
822
  const targetExtDir = path.join(userExtensionsDir, 'my-local-extension');
764
823
  const metadataPath = path.join(targetExtDir, INSTALL_METADATA_FILENAME);
824
+ await extensionManager.loadExtensions();
765
825
  await extensionManager.installOrUpdateExtension({
766
826
  source: sourceExtDir,
767
827
  type: 'local',
@@ -789,6 +849,7 @@ This extension will run the following MCP servers:
789
849
  },
790
850
  },
791
851
  });
852
+ await extensionManager.loadExtensions();
792
853
  // Install it with hard coded consent first.
793
854
  await extensionManager.installOrUpdateExtension({
794
855
  source: sourceExtDir,
@@ -798,7 +859,7 @@ This extension will run the following MCP servers:
798
859
  // Now update it without changing anything.
799
860
  await expect(extensionManager.installOrUpdateExtension({ source: sourceExtDir, type: 'local' },
800
861
  // Provide its own existing config as the previous config.
801
- await extensionManager.loadExtensionConfig(sourceExtDir))).resolves.toBe('my-local-extension');
862
+ await extensionManager.loadExtensionConfig(sourceExtDir))).resolves.toMatchObject({ name: 'my-local-extension' });
802
863
  // Still only called once
803
864
  expect(mockRequestConsent).toHaveBeenCalledOnce();
804
865
  });
@@ -815,6 +876,7 @@ This extension will run the following MCP servers:
815
876
  },
816
877
  ],
817
878
  });
879
+ await extensionManager.loadExtensions();
818
880
  await extensionManager.installOrUpdateExtension({
819
881
  source: sourceExtDir,
820
882
  type: 'local',
@@ -838,8 +900,9 @@ This extension will run the following MCP servers:
838
900
  workspaceDir: tempWorkspaceDir,
839
901
  requestConsent: mockRequestConsent,
840
902
  requestSetting: null,
841
- loadedSettings: loadSettings(tempWorkspaceDir),
903
+ settings: loadSettings(tempWorkspaceDir).merged,
842
904
  });
905
+ await extensionManager.loadExtensions();
843
906
  await extensionManager.installOrUpdateExtension({
844
907
  source: sourceExtDir,
845
908
  type: 'local',
@@ -860,6 +923,7 @@ This extension will run the following MCP servers:
860
923
  ],
861
924
  });
862
925
  mockPromptForSettings.mockResolvedValueOnce('old-api-key');
926
+ await extensionManager.loadExtensions();
863
927
  // Install it so it exists in the userExtensionsDir
864
928
  await extensionManager.installOrUpdateExtension({
865
929
  source: oldSourceExtDir,
@@ -913,6 +977,7 @@ This extension will run the following MCP servers:
913
977
  },
914
978
  ],
915
979
  });
980
+ await extensionManager.loadExtensions();
916
981
  await extensionManager.installOrUpdateExtension({
917
982
  source: oldSourceExtDir,
918
983
  type: 'local',
@@ -977,6 +1042,7 @@ This extension will run the following MCP servers:
977
1042
  version: '1.0.0',
978
1043
  });
979
1044
  vi.spyOn(ExtensionStorage, 'createTmpDir').mockResolvedValue(join(tempDir, extensionName));
1045
+ await extensionManager.loadExtensions();
980
1046
  await extensionManager.installOrUpdateExtension({
981
1047
  source: gitUrl,
982
1048
  type: 'github-release',
@@ -998,6 +1064,7 @@ This extension will run the following MCP servers:
998
1064
  errorMessage: 'download failed',
999
1065
  type: 'github-release',
1000
1066
  });
1067
+ await extensionManager.loadExtensions();
1001
1068
  await extensionManager.installOrUpdateExtension({ source: gitUrl, type: 'github-release' });
1002
1069
  // It gets called once to ask for a git clone, and once to consent to
1003
1070
  // the actual extension features.
@@ -1015,6 +1082,7 @@ This extension will run the following MCP servers:
1015
1082
  type: 'github-release',
1016
1083
  });
1017
1084
  mockRequestConsent.mockResolvedValue(false);
1085
+ await extensionManager.loadExtensions();
1018
1086
  await expect(extensionManager.installOrUpdateExtension({
1019
1087
  source: gitUrl,
1020
1088
  type: 'github-release',
@@ -1028,6 +1096,7 @@ This extension will run the following MCP servers:
1028
1096
  failureReason: 'no release data',
1029
1097
  type: 'github-release',
1030
1098
  });
1099
+ await extensionManager.loadExtensions();
1031
1100
  await extensionManager.installOrUpdateExtension({
1032
1101
  source: gitUrl,
1033
1102
  type: 'git',
@@ -1047,6 +1116,7 @@ This extension will run the following MCP servers:
1047
1116
  errorMessage: 'No release data found',
1048
1117
  type: 'github-release',
1049
1118
  });
1119
+ await extensionManager.loadExtensions();
1050
1120
  await extensionManager.installOrUpdateExtension({ source: gitUrl, type: 'github-release' });
1051
1121
  expect(mockRequestConsent).toHaveBeenCalledWith(expect.stringContaining('Would you like to attempt to install via "git clone" instead?'));
1052
1122
  expect(mockGit.clone).toHaveBeenCalled();
@@ -1060,6 +1130,7 @@ This extension will run the following MCP servers:
1060
1130
  name: 'my-local-extension',
1061
1131
  version: '1.0.0',
1062
1132
  });
1133
+ await extensionManager.loadExtensions();
1063
1134
  await extensionManager.uninstallExtension('my-local-extension', false);
1064
1135
  expect(fs.existsSync(sourceExtDir)).toBe(false);
1065
1136
  });
@@ -1074,12 +1145,14 @@ This extension will run the following MCP servers:
1074
1145
  name: 'other-extension',
1075
1146
  version: '1.0.0',
1076
1147
  });
1148
+ await extensionManager.loadExtensions();
1077
1149
  await extensionManager.uninstallExtension('my-local-extension', false);
1078
1150
  expect(fs.existsSync(sourceExtDir)).toBe(false);
1079
- expect(extensionManager.loadExtensions()).toHaveLength(1);
1151
+ expect(extensionManager.getExtensions()).toHaveLength(1);
1080
1152
  expect(fs.existsSync(otherExtDir)).toBe(true);
1081
1153
  });
1082
1154
  it('should throw an error if the extension does not exist', async () => {
1155
+ await extensionManager.loadExtensions();
1083
1156
  await expect(extensionManager.uninstallExtension('nonexistent-extension', false)).rejects.toThrow('Extension not found.');
1084
1157
  });
1085
1158
  describe.each([true, false])('with isUpdate: %s', (isUpdate) => {
@@ -1093,6 +1166,7 @@ This extension will run the following MCP servers:
1093
1166
  type: 'local',
1094
1167
  },
1095
1168
  });
1169
+ await extensionManager.loadExtensions();
1096
1170
  await extensionManager.uninstallExtension('my-local-extension', isUpdate);
1097
1171
  if (isUpdate) {
1098
1172
  expect(mockLogExtensionUninstall).not.toHaveBeenCalled();
@@ -1111,6 +1185,7 @@ This extension will run the following MCP servers:
1111
1185
  });
1112
1186
  const enablementManager = new ExtensionEnablementManager();
1113
1187
  enablementManager.enable('test-extension', true, '/some/scope');
1188
+ await extensionManager.loadExtensions();
1114
1189
  await extensionManager.uninstallExtension('test-extension', isUpdate);
1115
1190
  const config = enablementManager.readConfig()['test-extension'];
1116
1191
  if (isUpdate) {
@@ -1133,6 +1208,7 @@ This extension will run the following MCP servers:
1133
1208
  type: 'git',
1134
1209
  },
1135
1210
  });
1211
+ await extensionManager.loadExtensions();
1136
1212
  await extensionManager.uninstallExtension(gitUrl, false);
1137
1213
  expect(fs.existsSync(sourceExtDir)).toBe(false);
1138
1214
  expect(mockLogExtensionUninstall).toHaveBeenCalled();
@@ -1145,28 +1221,31 @@ This extension will run the following MCP servers:
1145
1221
  version: '1.0.0',
1146
1222
  // No installMetadata provided
1147
1223
  });
1224
+ await extensionManager.loadExtensions();
1148
1225
  await expect(extensionManager.uninstallExtension('https://github.com/google/no-metadata-extension', false)).rejects.toThrow('Extension not found.');
1149
1226
  });
1150
1227
  });
1151
1228
  describe('disableExtension', () => {
1152
- it('should disable an extension at the user scope', () => {
1229
+ it('should disable an extension at the user scope', async () => {
1153
1230
  createExtension({
1154
1231
  extensionsDir: userExtensionsDir,
1155
1232
  name: 'my-extension',
1156
1233
  version: '1.0.0',
1157
1234
  });
1235
+ await extensionManager.loadExtensions();
1158
1236
  extensionManager.disableExtension('my-extension', SettingScope.User);
1159
1237
  expect(isEnabled({
1160
1238
  name: 'my-extension',
1161
1239
  enabledForPath: tempWorkspaceDir,
1162
1240
  })).toBe(false);
1163
1241
  });
1164
- it('should disable an extension at the workspace scope', () => {
1242
+ it('should disable an extension at the workspace scope', async () => {
1165
1243
  createExtension({
1166
1244
  extensionsDir: userExtensionsDir,
1167
1245
  name: 'my-extension',
1168
1246
  version: '1.0.0',
1169
1247
  });
1248
+ await extensionManager.loadExtensions();
1170
1249
  extensionManager.disableExtension('my-extension', SettingScope.Workspace);
1171
1250
  expect(isEnabled({
1172
1251
  name: 'my-extension',
@@ -1177,12 +1256,13 @@ This extension will run the following MCP servers:
1177
1256
  enabledForPath: tempWorkspaceDir,
1178
1257
  })).toBe(false);
1179
1258
  });
1180
- it('should handle disabling the same extension twice', () => {
1259
+ it('should handle disabling the same extension twice', async () => {
1181
1260
  createExtension({
1182
1261
  extensionsDir: userExtensionsDir,
1183
1262
  name: 'my-extension',
1184
1263
  version: '1.0.0',
1185
1264
  });
1265
+ await extensionManager.loadExtensions();
1186
1266
  extensionManager.disableExtension('my-extension', SettingScope.User);
1187
1267
  extensionManager.disableExtension('my-extension', SettingScope.User);
1188
1268
  expect(isEnabled({
@@ -1190,10 +1270,10 @@ This extension will run the following MCP servers:
1190
1270
  enabledForPath: tempWorkspaceDir,
1191
1271
  })).toBe(false);
1192
1272
  });
1193
- it('should throw an error if you request system scope', () => {
1194
- expect(() => extensionManager.disableExtension('my-extension', SettingScope.System)).toThrow('System and SystemDefaults scopes are not supported.');
1273
+ it('should throw an error if you request system scope', async () => {
1274
+ await expect(async () => await extensionManager.disableExtension('my-extension', SettingScope.System)).rejects.toThrow('System and SystemDefaults scopes are not supported.');
1195
1275
  });
1196
- it('should log a disable event', () => {
1276
+ it('should log a disable event', async () => {
1197
1277
  createExtension({
1198
1278
  extensionsDir: userExtensionsDir,
1199
1279
  name: 'ext1',
@@ -1203,6 +1283,7 @@ This extension will run the following MCP servers:
1203
1283
  type: 'local',
1204
1284
  },
1205
1285
  });
1286
+ await extensionManager.loadExtensions();
1206
1287
  extensionManager.disableExtension('ext1', SettingScope.Workspace);
1207
1288
  expect(mockLogExtensionDisable).toHaveBeenCalled();
1208
1289
  expect(ExtensionDisableEvent).toHaveBeenCalledWith(hashValue('ext1'), hashValue(userExtensionsDir), SettingScope.Workspace);
@@ -1213,38 +1294,40 @@ This extension will run the following MCP servers:
1213
1294
  vi.restoreAllMocks();
1214
1295
  });
1215
1296
  const getActiveExtensions = () => {
1216
- const extensions = extensionManager.loadExtensions();
1297
+ const extensions = extensionManager.getExtensions();
1217
1298
  return extensions.filter((e) => e.isActive);
1218
1299
  };
1219
- it('should enable an extension at the user scope', () => {
1300
+ it('should enable an extension at the user scope', async () => {
1220
1301
  createExtension({
1221
1302
  extensionsDir: userExtensionsDir,
1222
1303
  name: 'ext1',
1223
1304
  version: '1.0.0',
1224
1305
  });
1306
+ await extensionManager.loadExtensions();
1225
1307
  extensionManager.disableExtension('ext1', SettingScope.User);
1226
1308
  let activeExtensions = getActiveExtensions();
1227
1309
  expect(activeExtensions).toHaveLength(0);
1228
- extensionManager.enableExtension('ext1', SettingScope.User);
1229
- activeExtensions = getActiveExtensions();
1310
+ await extensionManager.enableExtension('ext1', SettingScope.User);
1311
+ activeExtensions = await getActiveExtensions();
1230
1312
  expect(activeExtensions).toHaveLength(1);
1231
1313
  expect(activeExtensions[0].name).toBe('ext1');
1232
1314
  });
1233
- it('should enable an extension at the workspace scope', () => {
1315
+ it('should enable an extension at the workspace scope', async () => {
1234
1316
  createExtension({
1235
1317
  extensionsDir: userExtensionsDir,
1236
1318
  name: 'ext1',
1237
1319
  version: '1.0.0',
1238
1320
  });
1321
+ await extensionManager.loadExtensions();
1239
1322
  extensionManager.disableExtension('ext1', SettingScope.Workspace);
1240
1323
  let activeExtensions = getActiveExtensions();
1241
1324
  expect(activeExtensions).toHaveLength(0);
1242
- extensionManager.enableExtension('ext1', SettingScope.Workspace);
1243
- activeExtensions = getActiveExtensions();
1325
+ await extensionManager.enableExtension('ext1', SettingScope.Workspace);
1326
+ activeExtensions = await getActiveExtensions();
1244
1327
  expect(activeExtensions).toHaveLength(1);
1245
1328
  expect(activeExtensions[0].name).toBe('ext1');
1246
1329
  });
1247
- it('should log an enable event', () => {
1330
+ it('should log an enable event', async () => {
1248
1331
  createExtension({
1249
1332
  extensionsDir: userExtensionsDir,
1250
1333
  name: 'ext1',
@@ -1254,6 +1337,7 @@ This extension will run the following MCP servers:
1254
1337
  type: 'local',
1255
1338
  },
1256
1339
  });
1340
+ await extensionManager.loadExtensions();
1257
1341
  extensionManager.disableExtension('ext1', SettingScope.Workspace);
1258
1342
  extensionManager.enableExtension('ext1', SettingScope.Workspace);
1259
1343
  expect(mockLogExtensionEnable).toHaveBeenCalled();