@machina.ai/cell-cli 1.64.0-rc3 → 1.66.0-rc1
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.
- package/dist/index.js +14 -9
- package/dist/index.js.map +1 -1
- package/dist/package.json +4 -4
- package/dist/src/acp/acpSession.js +9 -13
- package/dist/src/acp/acpSession.js.map +1 -1
- package/dist/src/acp/acpSession.test.js +88 -0
- package/dist/src/acp/acpSession.test.js.map +1 -1
- package/dist/src/commands/mcp/add.js +14 -6
- package/dist/src/commands/mcp/add.js.map +1 -1
- package/dist/src/commands/mcp/add.test.js +61 -0
- package/dist/src/commands/mcp/add.test.js.map +1 -1
- package/dist/src/commands/mcp/remove.js +14 -1
- package/dist/src/commands/mcp/remove.js.map +1 -1
- package/dist/src/commands/mcp/remove.test.js +63 -0
- package/dist/src/commands/mcp/remove.test.js.map +1 -1
- package/dist/src/config/config.js +5 -0
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/config.test.js +1 -0
- package/dist/src/config/config.test.js.map +1 -1
- package/dist/src/config/envDefaults.d.ts +20 -0
- package/dist/src/config/envDefaults.js +29 -0
- package/dist/src/config/envDefaults.js.map +1 -0
- package/dist/src/config/envDefaults.test.d.ts +6 -0
- package/dist/src/config/envDefaults.test.js +23 -0
- package/dist/src/config/envDefaults.test.js.map +1 -0
- package/dist/src/config/extension-manager.js +13 -4
- package/dist/src/config/extension-manager.js.map +1 -1
- package/dist/src/config/extension.test.js +117 -0
- package/dist/src/config/extension.test.js.map +1 -1
- package/dist/src/config/extensions/github.test.js +34 -0
- package/dist/src/config/extensions/github.test.js.map +1 -1
- package/dist/src/config/extensions/github_fetch.js +110 -18
- package/dist/src/config/extensions/github_fetch.js.map +1 -1
- package/dist/src/config/extensions/github_fetch.test.js +251 -36
- package/dist/src/config/extensions/github_fetch.test.js.map +1 -1
- package/dist/src/config/settings.d.ts +1 -0
- package/dist/src/config/settings.js +148 -58
- package/dist/src/config/settings.js.map +1 -1
- package/dist/src/config/settings.test.js +300 -0
- package/dist/src/config/settings.test.js.map +1 -1
- package/dist/src/config/settingsSchema.d.ts +1 -1
- package/dist/src/config/settingsSchema.js +6 -1
- package/dist/src/config/settingsSchema.js.map +1 -1
- package/dist/src/gemini.test.js +69 -0
- package/dist/src/gemini.test.js.map +1 -1
- package/dist/src/generated/git-commit.d.ts +2 -2
- package/dist/src/generated/git-commit.js +2 -2
- package/dist/src/interactiveCli.js +8 -3
- package/dist/src/interactiveCli.js.map +1 -1
- package/dist/src/services/CommandService.js +1 -1
- package/dist/src/services/CommandService.js.map +1 -1
- package/dist/src/services/CommandService.test.js +18 -1
- package/dist/src/services/CommandService.test.js.map +1 -1
- package/dist/src/services/SlashCommandConflictHandler.d.ts +1 -0
- package/dist/src/services/SlashCommandConflictHandler.js +18 -2
- package/dist/src/services/SlashCommandConflictHandler.js.map +1 -1
- package/dist/src/services/SlashCommandConflictHandler.test.js +85 -8
- package/dist/src/services/SlashCommandConflictHandler.test.js.map +1 -1
- package/dist/src/ui/AppContainer.js +254 -12
- package/dist/src/ui/AppContainer.js.map +1 -1
- package/dist/src/ui/AppContainer.test.js +362 -6
- package/dist/src/ui/AppContainer.test.js.map +1 -1
- package/dist/src/ui/auth/AuthDialog.js +21 -5
- package/dist/src/ui/auth/AuthDialog.js.map +1 -1
- package/dist/src/ui/auth/AuthDialog.test.js +83 -0
- package/dist/src/ui/auth/AuthDialog.test.js.map +1 -1
- package/dist/src/ui/auth/useAuth.js +7 -1
- package/dist/src/ui/auth/useAuth.js.map +1 -1
- package/dist/src/ui/auth/useAuth.test.js +9 -0
- package/dist/src/ui/auth/useAuth.test.js.map +1 -1
- package/dist/src/ui/commands/marketplaceCommand.js +92 -4
- package/dist/src/ui/commands/marketplaceCommand.js.map +1 -1
- package/dist/src/ui/commands/marketplaceCommand.test.js +98 -1
- package/dist/src/ui/commands/marketplaceCommand.test.js.map +1 -1
- package/dist/src/ui/components/AsciiArt.d.ts +6 -6
- package/dist/src/ui/components/AsciiArt.js +6 -6
- package/dist/src/ui/components/CliSpinner.d.ts +3 -2
- package/dist/src/ui/components/CliSpinner.js +27 -3
- package/dist/src/ui/components/CliSpinner.js.map +1 -1
- package/dist/src/ui/components/CliSpinner.test.js +42 -3
- package/dist/src/ui/components/CliSpinner.test.js.map +1 -1
- package/dist/src/ui/components/Composer.js +3 -3
- package/dist/src/ui/components/Composer.js.map +1 -1
- package/dist/src/ui/components/Composer.test.js +39 -1
- package/dist/src/ui/components/Composer.test.js.map +1 -1
- package/dist/src/ui/components/DetailedMessagesDisplay.js +5 -1
- package/dist/src/ui/components/DetailedMessagesDisplay.js.map +1 -1
- package/dist/src/ui/components/DetailedMessagesDisplay.test.js +19 -1
- package/dist/src/ui/components/DetailedMessagesDisplay.test.js.map +1 -1
- package/dist/src/ui/components/Footer.js +1 -1
- package/dist/src/ui/components/Footer.js.map +1 -1
- package/dist/src/ui/components/GeminiRespondingSpinner.d.ts +1 -1
- package/dist/src/ui/components/GeminiRespondingSpinner.js +9 -2
- package/dist/src/ui/components/GeminiRespondingSpinner.js.map +1 -1
- package/dist/src/ui/components/GeminiSpinner.d.ts +1 -1
- package/dist/src/ui/components/GeminiSpinner.js +18 -7
- package/dist/src/ui/components/GeminiSpinner.js.map +1 -1
- package/dist/src/ui/components/HistoryItemDisplay.js +5 -2
- package/dist/src/ui/components/HistoryItemDisplay.js.map +1 -1
- package/dist/src/ui/components/HistoryItemDisplay.test.js +21 -0
- package/dist/src/ui/components/HistoryItemDisplay.test.js.map +1 -1
- package/dist/src/ui/components/LoadingIndicator.d.ts +1 -1
- package/dist/src/ui/components/LoadingIndicator.js +4 -2
- package/dist/src/ui/components/LoadingIndicator.js.map +1 -1
- package/dist/src/ui/components/MainContent.js +47 -9
- package/dist/src/ui/components/MainContent.js.map +1 -1
- package/dist/src/ui/components/MainContent.test.js +87 -1
- package/dist/src/ui/components/MainContent.test.js.map +1 -1
- package/dist/src/ui/components/MemoryUsageDisplay.js +8 -4
- package/dist/src/ui/components/MemoryUsageDisplay.js.map +1 -1
- package/dist/src/ui/components/ModelDialog.js +11 -5
- package/dist/src/ui/components/ModelDialog.js.map +1 -1
- package/dist/src/ui/components/ModelDialog.test.js +18 -14
- package/dist/src/ui/components/ModelDialog.test.js.map +1 -1
- package/dist/src/ui/components/StatusRow.d.ts +7 -6
- package/dist/src/ui/components/StatusRow.js +24 -10
- package/dist/src/ui/components/StatusRow.js.map +1 -1
- package/dist/src/ui/components/ValidationDialog.js +13 -6
- package/dist/src/ui/components/ValidationDialog.js.map +1 -1
- package/dist/src/ui/components/ValidationDialog.test.js +40 -0
- package/dist/src/ui/components/ValidationDialog.test.js.map +1 -1
- package/dist/src/ui/components/messages/DenseToolMessage.js +17 -12
- package/dist/src/ui/components/messages/DenseToolMessage.js.map +1 -1
- package/dist/src/ui/components/messages/DenseToolMessage.test.js +71 -0
- package/dist/src/ui/components/messages/DenseToolMessage.test.js.map +1 -1
- package/dist/src/ui/components/messages/ToolResultDisplay.js +14 -11
- package/dist/src/ui/components/messages/ToolResultDisplay.js.map +1 -1
- package/dist/src/ui/components/messages/ToolResultDisplay.test.js +32 -0
- package/dist/src/ui/components/messages/ToolResultDisplay.test.js.map +1 -1
- package/dist/src/ui/components/shared/MaxSizedBox.js +8 -2
- package/dist/src/ui/components/shared/MaxSizedBox.js.map +1 -1
- package/dist/src/ui/components/shared/MaxSizedBox.test.js +23 -1
- package/dist/src/ui/components/shared/MaxSizedBox.test.js.map +1 -1
- package/dist/src/ui/contexts/ToolActionsContext.js +7 -1
- package/dist/src/ui/contexts/ToolActionsContext.js.map +1 -1
- package/dist/src/ui/contexts/ToolActionsContext.test.js +54 -1
- package/dist/src/ui/contexts/ToolActionsContext.test.js.map +1 -1
- package/dist/src/ui/hooks/atCommandProcessor.d.ts +2 -1
- package/dist/src/ui/hooks/atCommandProcessor.js +15 -15
- package/dist/src/ui/hooks/atCommandProcessor.js.map +1 -1
- package/dist/src/ui/hooks/useAgentStream.d.ts +9 -2
- package/dist/src/ui/hooks/useAgentStream.js +173 -8
- package/dist/src/ui/hooks/useAgentStream.js.map +1 -1
- package/dist/src/ui/hooks/useAgentStream.test.js +104 -1
- package/dist/src/ui/hooks/useAgentStream.test.js.map +1 -1
- package/dist/src/ui/hooks/useGeminiStream.d.ts +1 -1
- package/dist/src/ui/hooks/useGeminiStream.js +174 -59
- package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
- package/dist/src/ui/hooks/useGeminiStream.test.js +230 -3
- package/dist/src/ui/hooks/useGeminiStream.test.js.map +1 -1
- package/dist/src/ui/hooks/useLoadingIndicator.d.ts +2 -1
- package/dist/src/ui/hooks/useLoadingIndicator.js +3 -3
- package/dist/src/ui/hooks/useLoadingIndicator.js.map +1 -1
- package/dist/src/ui/hooks/useLoadingIndicator.test.js +23 -4
- package/dist/src/ui/hooks/useLoadingIndicator.test.js.map +1 -1
- package/dist/src/ui/hooks/useReverseSearchCompletion.js +10 -6
- package/dist/src/ui/hooks/useReverseSearchCompletion.js.map +1 -1
- package/dist/src/ui/hooks/useReverseSearchCompletion.test.js +49 -0
- package/dist/src/ui/hooks/useReverseSearchCompletion.test.js.map +1 -1
- package/dist/src/ui/key/keyBindings.js +19 -6
- package/dist/src/ui/key/keyBindings.js.map +1 -1
- package/dist/src/ui/key/keyBindings.test.js +106 -0
- package/dist/src/ui/key/keyBindings.test.js.map +1 -1
- package/dist/src/ui/utils/formatters.d.ts +2 -0
- package/dist/src/ui/utils/formatters.js +13 -5
- package/dist/src/ui/utils/formatters.js.map +1 -1
- package/dist/src/ui/utils/formatters.test.js +18 -0
- package/dist/src/ui/utils/formatters.test.js.map +1 -1
- package/dist/src/utils/activityLogger.js +109 -33
- package/dist/src/utils/activityLogger.js.map +1 -1
- package/dist/src/utils/activityLogger.test.js +441 -0
- package/dist/src/utils/activityLogger.test.js.map +1 -1
- package/dist/src/utils/handleAutoUpdate.d.ts +4 -1
- package/dist/src/utils/handleAutoUpdate.js +45 -3
- package/dist/src/utils/handleAutoUpdate.js.map +1 -1
- package/dist/src/utils/handleAutoUpdate.test.js +280 -2
- package/dist/src/utils/handleAutoUpdate.test.js.map +1 -1
- package/dist/src/utils/nodeVersion.d.ts +11 -0
- package/dist/src/utils/nodeVersion.js +42 -0
- package/dist/src/utils/nodeVersion.js.map +1 -0
- package/dist/src/utils/nodeVersion.test.d.ts +6 -0
- package/dist/src/utils/nodeVersion.test.js +35 -0
- package/dist/src/utils/nodeVersion.test.js.map +1 -0
- package/dist/src/utils/processUtils.d.ts +23 -0
- package/dist/src/utils/processUtils.js +72 -0
- package/dist/src/utils/processUtils.js.map +1 -1
- package/dist/src/utils/processUtils.test.js +95 -1
- package/dist/src/utils/processUtils.test.js.map +1 -1
- package/dist/src/utils/relaunch.js +8 -6
- package/dist/src/utils/relaunch.js.map +1 -1
- package/dist/src/utils/relaunch.test.js +107 -1
- package/dist/src/utils/relaunch.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
|
@@ -832,6 +832,28 @@ describe('Settings Loading and Merging', () => {
|
|
|
832
832
|
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
|
833
833
|
expect(settings.merged.telemetry?.enabled).toBe(false);
|
|
834
834
|
});
|
|
835
|
+
it('should load telemetry.otlpHeaders from settings', () => {
|
|
836
|
+
mockFsExistsSync.mockImplementation((p) => normalizePath(p) === normalizePath(USER_SETTINGS_PATH));
|
|
837
|
+
const userSettingsContent = {
|
|
838
|
+
telemetry: {
|
|
839
|
+
enabled: true,
|
|
840
|
+
otlpHeaders: {
|
|
841
|
+
Authorization: 'Bearer test-token-value',
|
|
842
|
+
'x-custom': 'static-val',
|
|
843
|
+
},
|
|
844
|
+
},
|
|
845
|
+
};
|
|
846
|
+
fs.readFileSync.mockImplementation((p) => {
|
|
847
|
+
if (normalizePath(p) === normalizePath(USER_SETTINGS_PATH))
|
|
848
|
+
return JSON.stringify(userSettingsContent);
|
|
849
|
+
return '{}';
|
|
850
|
+
});
|
|
851
|
+
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
|
852
|
+
expect(settings.merged.telemetry?.otlpHeaders).toEqual({
|
|
853
|
+
Authorization: 'Bearer test-token-value',
|
|
854
|
+
'x-custom': 'static-val',
|
|
855
|
+
});
|
|
856
|
+
});
|
|
835
857
|
it('should have telemetry as undefined if not in any settings file', () => {
|
|
836
858
|
mockFsExistsSync.mockReturnValue(false); // No settings files exist
|
|
837
859
|
fs.readFileSync.mockReturnValue('{}');
|
|
@@ -1794,6 +1816,93 @@ describe('Settings Loading and Merging', () => {
|
|
|
1794
1816
|
'USER.md',
|
|
1795
1817
|
]); // User setting
|
|
1796
1818
|
});
|
|
1819
|
+
it('should mark workspace scope as readOnly and block setValue when workspace is not trusted', () => {
|
|
1820
|
+
vi.spyOn(trustedFolders, 'isWorkspaceTrusted').mockReturnValue({
|
|
1821
|
+
isTrusted: false,
|
|
1822
|
+
source: 'file',
|
|
1823
|
+
});
|
|
1824
|
+
mockFsExistsSync.mockReturnValue(true);
|
|
1825
|
+
const workspaceSettingsContent = {
|
|
1826
|
+
ui: { theme: 'dark' },
|
|
1827
|
+
mcpServers: {
|
|
1828
|
+
existing: { command: 'node', args: ['server.js'] },
|
|
1829
|
+
},
|
|
1830
|
+
};
|
|
1831
|
+
fs.readFileSync.mockImplementation((p) => {
|
|
1832
|
+
if (normalizePath(p) === normalizePath(MOCK_WORKSPACE_SETTINGS_PATH))
|
|
1833
|
+
return JSON.stringify(workspaceSettingsContent);
|
|
1834
|
+
return '{}';
|
|
1835
|
+
});
|
|
1836
|
+
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
|
1837
|
+
expect(settings.workspace.readOnly).toBe(true);
|
|
1838
|
+
expect(settings.forScope(SettingScope.Workspace).readOnly).toBe(true);
|
|
1839
|
+
expect(() => {
|
|
1840
|
+
settings.setValue(SettingScope.Workspace, 'mcpServers', {
|
|
1841
|
+
newsrv: { command: 'echo' },
|
|
1842
|
+
});
|
|
1843
|
+
}).toThrow(/Cannot modify settings in an untrusted workspace/);
|
|
1844
|
+
expect(updateSettingsFilePreservingFormat).not.toHaveBeenCalled();
|
|
1845
|
+
});
|
|
1846
|
+
it('should restore workspace writability when setTrusted(true) is called and re-block when setTrusted(false) is called', () => {
|
|
1847
|
+
vi.spyOn(trustedFolders, 'isWorkspaceTrusted').mockReturnValue({
|
|
1848
|
+
isTrusted: false,
|
|
1849
|
+
source: 'file',
|
|
1850
|
+
});
|
|
1851
|
+
mockFsExistsSync.mockReturnValue(true);
|
|
1852
|
+
const workspaceSettingsContent = {
|
|
1853
|
+
ui: { theme: 'dark' },
|
|
1854
|
+
mcpServers: {
|
|
1855
|
+
existing: { command: 'node', args: ['server.js'] },
|
|
1856
|
+
},
|
|
1857
|
+
};
|
|
1858
|
+
fs.readFileSync.mockImplementation((p) => {
|
|
1859
|
+
if (normalizePath(p) === normalizePath(MOCK_WORKSPACE_SETTINGS_PATH))
|
|
1860
|
+
return JSON.stringify(workspaceSettingsContent);
|
|
1861
|
+
return '{}';
|
|
1862
|
+
});
|
|
1863
|
+
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
|
1864
|
+
expect(settings.workspace.readOnly).toBe(true);
|
|
1865
|
+
settings.setTrusted(true);
|
|
1866
|
+
expect(settings.workspace.readOnly).toBe(false);
|
|
1867
|
+
expect(settings.workspace.settings).toEqual(workspaceSettingsContent);
|
|
1868
|
+
settings.setValue(SettingScope.Workspace, 'mcpServers', {
|
|
1869
|
+
...workspaceSettingsContent.mcpServers,
|
|
1870
|
+
newsrv: { command: 'echo' },
|
|
1871
|
+
});
|
|
1872
|
+
expect(updateSettingsFilePreservingFormat).toHaveBeenCalledWith(MOCK_WORKSPACE_SETTINGS_PATH, expect.objectContaining({
|
|
1873
|
+
ui: { theme: 'dark' },
|
|
1874
|
+
mcpServers: {
|
|
1875
|
+
existing: { command: 'node', args: ['server.js'] },
|
|
1876
|
+
newsrv: { command: 'echo' },
|
|
1877
|
+
},
|
|
1878
|
+
}));
|
|
1879
|
+
vi.mocked(updateSettingsFilePreservingFormat).mockClear();
|
|
1880
|
+
settings.setTrusted(false);
|
|
1881
|
+
expect(settings.workspace.readOnly).toBe(true);
|
|
1882
|
+
expect(() => {
|
|
1883
|
+
settings.setValue(SettingScope.Workspace, 'ui.theme', 'light');
|
|
1884
|
+
}).toThrow(/Cannot modify settings in an untrusted workspace/);
|
|
1885
|
+
expect(updateSettingsFilePreservingFormat).not.toHaveBeenCalled();
|
|
1886
|
+
});
|
|
1887
|
+
it('should throw a home directory error when setValue is called on workspace scope in the home directory', () => {
|
|
1888
|
+
const isWorkspaceHomeDirSpy = vi
|
|
1889
|
+
.spyOn(Storage.prototype, 'isWorkspaceHomeDir')
|
|
1890
|
+
.mockReturnValue(true);
|
|
1891
|
+
try {
|
|
1892
|
+
const settings = loadSettings(path.resolve('/mock/home/user'));
|
|
1893
|
+
expect(settings.workspace.path).toBe('');
|
|
1894
|
+
expect(settings.workspace.readOnly).toBe(true);
|
|
1895
|
+
expect(() => {
|
|
1896
|
+
settings.setValue(SettingScope.Workspace, 'mcpServers', {
|
|
1897
|
+
newsrv: { command: 'echo' },
|
|
1898
|
+
});
|
|
1899
|
+
}).toThrow('Cannot modify workspace settings in the home directory. Please use user scope instead.');
|
|
1900
|
+
expect(updateSettingsFilePreservingFormat).not.toHaveBeenCalled();
|
|
1901
|
+
}
|
|
1902
|
+
finally {
|
|
1903
|
+
isWorkspaceHomeDirSpy.mockRestore();
|
|
1904
|
+
}
|
|
1905
|
+
});
|
|
1797
1906
|
});
|
|
1798
1907
|
describe('loadEnvironment', () => {
|
|
1799
1908
|
function setup({ isFolderTrustEnabled = true, isWorkspaceTrustedValue = true, }) {
|
|
@@ -1904,6 +2013,197 @@ describe('Settings Loading and Merging', () => {
|
|
|
1904
2013
|
}
|
|
1905
2014
|
});
|
|
1906
2015
|
});
|
|
2016
|
+
describe('resolving settings placeholders from .env files', () => {
|
|
2017
|
+
const WORKSPACE_ENV_PATH = path.resolve(path.join(MOCK_WORKSPACE_DIR, '.env'));
|
|
2018
|
+
/**
|
|
2019
|
+
* Sets up a workspace containing a project `.env` file alongside the
|
|
2020
|
+
* given user and workspace settings files.
|
|
2021
|
+
*/
|
|
2022
|
+
function setupEnvFileScenario({ envFileContent, userSettingsContent = {}, workspaceSettingsContent = {}, }) {
|
|
2023
|
+
mockFsExistsSync.mockImplementation((p) => [
|
|
2024
|
+
path.resolve(USER_SETTINGS_PATH),
|
|
2025
|
+
path.resolve(MOCK_WORKSPACE_SETTINGS_PATH),
|
|
2026
|
+
WORKSPACE_ENV_PATH,
|
|
2027
|
+
].includes(path.resolve(p.toString())));
|
|
2028
|
+
fs.readFileSync.mockImplementation((p) => {
|
|
2029
|
+
const normalizedP = path.resolve(p.toString());
|
|
2030
|
+
if (normalizedP === path.resolve(USER_SETTINGS_PATH)) {
|
|
2031
|
+
return JSON.stringify(userSettingsContent);
|
|
2032
|
+
}
|
|
2033
|
+
if (normalizedP === path.resolve(MOCK_WORKSPACE_SETTINGS_PATH)) {
|
|
2034
|
+
return JSON.stringify(workspaceSettingsContent);
|
|
2035
|
+
}
|
|
2036
|
+
if (normalizedP === WORKSPACE_ENV_PATH) {
|
|
2037
|
+
return envFileContent;
|
|
2038
|
+
}
|
|
2039
|
+
return '{}';
|
|
2040
|
+
});
|
|
2041
|
+
}
|
|
2042
|
+
it('should resolve placeholders in settings using variables defined only in a .env file', () => {
|
|
2043
|
+
setupEnvFileScenario({
|
|
2044
|
+
envFileContent: 'GITHUB_MCP_PAT=ghp_secretTokenHere\nWORKSPACE_API_TOKEN=ws-token',
|
|
2045
|
+
userSettingsContent: {
|
|
2046
|
+
mcpServers: {
|
|
2047
|
+
github: {
|
|
2048
|
+
httpUrl: 'https://api.githubcopilot.com/mcp/',
|
|
2049
|
+
headers: { Authorization: 'Bearer $GITHUB_MCP_PAT' },
|
|
2050
|
+
},
|
|
2051
|
+
},
|
|
2052
|
+
},
|
|
2053
|
+
workspaceSettingsContent: {
|
|
2054
|
+
mcpServers: {
|
|
2055
|
+
internal: {
|
|
2056
|
+
url: 'https://internal.example.com/sse?token=${WORKSPACE_API_TOKEN}',
|
|
2057
|
+
},
|
|
2058
|
+
},
|
|
2059
|
+
},
|
|
2060
|
+
});
|
|
2061
|
+
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
|
2062
|
+
expect(settings.merged.mcpServers?.['github']?.headers).toEqual({
|
|
2063
|
+
Authorization: 'Bearer ghp_secretTokenHere',
|
|
2064
|
+
});
|
|
2065
|
+
expect(settings.merged.mcpServers?.['internal']?.url).toBe('https://internal.example.com/sse?token=ws-token');
|
|
2066
|
+
expect(settings.user.settings.mcpServers?.['github']?.headers).toEqual({
|
|
2067
|
+
Authorization: 'Bearer ghp_secretTokenHere',
|
|
2068
|
+
});
|
|
2069
|
+
expect(settings.workspace.settings.mcpServers?.['internal']?.url).toBe('https://internal.example.com/sse?token=ws-token');
|
|
2070
|
+
});
|
|
2071
|
+
it('should keep the unexpanded placeholders in originalSettings', () => {
|
|
2072
|
+
setupEnvFileScenario({
|
|
2073
|
+
envFileContent: 'GITHUB_ORIGINAL_MCP_PAT=ghp_secretTokenHere',
|
|
2074
|
+
userSettingsContent: {
|
|
2075
|
+
mcpServers: {
|
|
2076
|
+
github: {
|
|
2077
|
+
headers: { Authorization: 'Bearer $GITHUB_ORIGINAL_MCP_PAT' },
|
|
2078
|
+
},
|
|
2079
|
+
},
|
|
2080
|
+
},
|
|
2081
|
+
});
|
|
2082
|
+
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
|
2083
|
+
expect(settings.user.originalSettings.mcpServers?.['github']?.headers).toEqual({ Authorization: 'Bearer $GITHUB_ORIGINAL_MCP_PAT' });
|
|
2084
|
+
});
|
|
2085
|
+
it('should validate settings after .env variables have been resolved', () => {
|
|
2086
|
+
setupEnvFileScenario({
|
|
2087
|
+
envFileContent: 'MAX_TURNS_FROM_ENV_FILE=15',
|
|
2088
|
+
userSettingsContent: {
|
|
2089
|
+
model: { maxSessionTurns: '$MAX_TURNS_FROM_ENV_FILE' },
|
|
2090
|
+
},
|
|
2091
|
+
});
|
|
2092
|
+
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
|
2093
|
+
expect(settings.merged.model.maxSessionTurns).toBe(15);
|
|
2094
|
+
expect(settings.errors).toHaveLength(0);
|
|
2095
|
+
});
|
|
2096
|
+
it('should record validation warnings only once when a .env variable expands to an invalid type', () => {
|
|
2097
|
+
setupEnvFileScenario({
|
|
2098
|
+
envFileContent: 'INVALID_MAX_TURNS_FROM_ENV_FILE=not-a-number',
|
|
2099
|
+
userSettingsContent: {
|
|
2100
|
+
model: { maxSessionTurns: '$INVALID_MAX_TURNS_FROM_ENV_FILE' },
|
|
2101
|
+
},
|
|
2102
|
+
});
|
|
2103
|
+
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
|
2104
|
+
expect(settings.errors).toHaveLength(1);
|
|
2105
|
+
expect(settings.errors[0]?.severity).toBe('warning');
|
|
2106
|
+
});
|
|
2107
|
+
it('should prefer variables from the shell environment over .env files', () => {
|
|
2108
|
+
vi.stubEnv('SHELL_PRECEDENCE_MCP_PAT', 'token-from-shell');
|
|
2109
|
+
setupEnvFileScenario({
|
|
2110
|
+
envFileContent: 'SHELL_PRECEDENCE_MCP_PAT=token-from-env-file',
|
|
2111
|
+
userSettingsContent: {
|
|
2112
|
+
mcpServers: {
|
|
2113
|
+
github: {
|
|
2114
|
+
headers: { Authorization: 'Bearer $SHELL_PRECEDENCE_MCP_PAT' },
|
|
2115
|
+
},
|
|
2116
|
+
},
|
|
2117
|
+
},
|
|
2118
|
+
});
|
|
2119
|
+
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
|
2120
|
+
expect(settings.merged.mcpServers?.['github']?.headers).toEqual({
|
|
2121
|
+
Authorization: 'Bearer token-from-shell',
|
|
2122
|
+
});
|
|
2123
|
+
});
|
|
2124
|
+
it('should not resolve placeholders from a .env file in an untrusted workspace', () => {
|
|
2125
|
+
vi.spyOn(trustedFolders, 'isWorkspaceTrusted').mockReturnValue({
|
|
2126
|
+
isTrusted: false,
|
|
2127
|
+
source: 'file',
|
|
2128
|
+
});
|
|
2129
|
+
setupEnvFileScenario({
|
|
2130
|
+
envFileContent: 'UNTRUSTED_MCP_PAT=ghp_secretTokenHere',
|
|
2131
|
+
userSettingsContent: {
|
|
2132
|
+
mcpServers: {
|
|
2133
|
+
github: {
|
|
2134
|
+
headers: { Authorization: 'Bearer $UNTRUSTED_MCP_PAT' },
|
|
2135
|
+
},
|
|
2136
|
+
},
|
|
2137
|
+
},
|
|
2138
|
+
});
|
|
2139
|
+
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
|
2140
|
+
expect(process.env['UNTRUSTED_MCP_PAT']).toBeUndefined();
|
|
2141
|
+
expect(settings.merged.mcpServers?.['github']?.headers).toEqual({
|
|
2142
|
+
Authorization: 'Bearer $UNTRUSTED_MCP_PAT',
|
|
2143
|
+
});
|
|
2144
|
+
});
|
|
2145
|
+
it('should not resolve or validate workspace settings in an untrusted workspace to avoid leaking shell secrets in validation errors', () => {
|
|
2146
|
+
vi.spyOn(trustedFolders, 'isWorkspaceTrusted').mockReturnValue({
|
|
2147
|
+
isTrusted: false,
|
|
2148
|
+
source: 'file',
|
|
2149
|
+
});
|
|
2150
|
+
vi.stubEnv('SECRET_TOKEN_FROM_SHELL', 'super-secret-shell-value');
|
|
2151
|
+
setupEnvFileScenario({
|
|
2152
|
+
envFileContent: '',
|
|
2153
|
+
workspaceSettingsContent: {
|
|
2154
|
+
model: { maxSessionTurns: '$SECRET_TOKEN_FROM_SHELL' },
|
|
2155
|
+
},
|
|
2156
|
+
});
|
|
2157
|
+
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
|
2158
|
+
expect(settings.errors).toEqual([]);
|
|
2159
|
+
expect(settings.workspace.settings).toEqual({});
|
|
2160
|
+
settings.setTrusted(true);
|
|
2161
|
+
expect(settings.workspace.settings).toEqual({
|
|
2162
|
+
model: { maxSessionTurns: 'super-secret-shell-value' },
|
|
2163
|
+
});
|
|
2164
|
+
expect(settings.errors).toHaveLength(1);
|
|
2165
|
+
});
|
|
2166
|
+
it('should normalize legacy theme names when an initially untrusted workspace is later trusted', () => {
|
|
2167
|
+
vi.spyOn(trustedFolders, 'isWorkspaceTrusted').mockReturnValue({
|
|
2168
|
+
isTrusted: false,
|
|
2169
|
+
source: 'file',
|
|
2170
|
+
});
|
|
2171
|
+
setupEnvFileScenario({
|
|
2172
|
+
envFileContent: '',
|
|
2173
|
+
workspaceSettingsContent: {
|
|
2174
|
+
ui: { theme: 'VS2015' },
|
|
2175
|
+
},
|
|
2176
|
+
});
|
|
2177
|
+
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
|
2178
|
+
expect(settings.workspace.settings).toEqual({});
|
|
2179
|
+
settings.setTrusted(true);
|
|
2180
|
+
expect(settings.workspace.settings.ui?.theme).toBe('Default');
|
|
2181
|
+
expect(settings.merged.ui.theme).toBe('Default');
|
|
2182
|
+
});
|
|
2183
|
+
it('should evaluate workspace trust with settings resolved from the shell environment even when other settings reference .env variables', () => {
|
|
2184
|
+
// The trust check gates whether a workspace .env file may be loaded, so
|
|
2185
|
+
// it must be evaluated against the environment as it exists *before*
|
|
2186
|
+
// any .env file is loaded, with placeholders resolved and coerced even if
|
|
2187
|
+
// another typed setting still holds an unexpanded .env placeholder.
|
|
2188
|
+
vi.stubEnv('FOLDER_TRUST_FROM_SHELL', 'false');
|
|
2189
|
+
setupEnvFileScenario({
|
|
2190
|
+
envFileContent: 'TRUST_CHECK_MAX_TURNS=25',
|
|
2191
|
+
userSettingsContent: {
|
|
2192
|
+
security: { folderTrust: { enabled: '$FOLDER_TRUST_FROM_SHELL' } },
|
|
2193
|
+
model: { maxSessionTurns: '$TRUST_CHECK_MAX_TURNS' },
|
|
2194
|
+
},
|
|
2195
|
+
});
|
|
2196
|
+
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
|
2197
|
+
expect(isWorkspaceTrusted).toHaveBeenCalledWith(expect.objectContaining({
|
|
2198
|
+
security: expect.objectContaining({
|
|
2199
|
+
folderTrust: expect.objectContaining({ enabled: false }),
|
|
2200
|
+
}),
|
|
2201
|
+
}), MOCK_WORKSPACE_DIR);
|
|
2202
|
+
expect(settings.merged.security.folderTrust.enabled).toBe(false);
|
|
2203
|
+
expect(settings.merged.model.maxSessionTurns).toBe(25);
|
|
2204
|
+
expect(settings.errors).toEqual([]);
|
|
2205
|
+
});
|
|
2206
|
+
});
|
|
1907
2207
|
describe('migrateDeprecatedSettings', () => {
|
|
1908
2208
|
let mockFsExistsSync;
|
|
1909
2209
|
let mockFsReadFileSync;
|