release: Gemini CLI v0.35.2 (6 patches)

This commit is contained in:
delta-cloud-208e
2026-03-27 03:09:57 +00:00
parent 3b293c1332
commit 6e3f963d9b
6 changed files with 18290 additions and 4 deletions

View File

@@ -2,7 +2,7 @@
<!-- VERSION_BADGE:START -->
Patched Gemini CLI for use with custom API endpoints.
Latest: **v0.35.1** (13 patches).
Latest: **v0.35.2** (13 patches).
<!-- VERSION_BADGE:END -->
## npm Install (Recommended)

View File

@@ -15,7 +15,7 @@
"gemini-3.1-pro-low",
"gemini-3.1-flash-image"
],
"target_version": "0.35.1",
"target_version": "0.35.2",
"telemetry_enabled": false,
"npm_package": "@google/gemini-cli",
"npm_registry": "https://npm.sensey24.ru"

View File

@@ -963,7 +963,7 @@ def patch_compression_aliases(gemini_root, config):
changes += 1
if changes == 0:
if "'chat-compression-3-pro'" in content and "'chat-compression-3-flash'" in content:
if "DEFAULT_GEMINI_MODEL:\n return 'chat-compression-3-pro'" in content:
return True, "Already patched (chatCompressionService.js)"
return False, "No chatCompressionService.js patterns matched"

View File

@@ -1,6 +1,12 @@
{
"latest": "0.35.1",
"latest": "0.35.2",
"releases": [
{
"version": "0.35.2",
"date": "2026-03-27",
"patches": 6,
"status": "stable"
},
{
"version": "0.35.1",
"date": "2026-03-26",

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,825 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import * as fs from 'node:fs';
import * as path from 'node:path';
import { platform } from 'node:os';
import * as dotenv from 'dotenv';
import process from 'node:process';
import { CoreEvent, FatalConfigError, GEMINI_DIR, getErrorMessage, getFsErrorMessage, Storage, coreEvents, homedir, createCache, } from '@google/gemini-cli-core';
import stripJsonComments from 'strip-json-comments';
import { DefaultLight } from '../ui/themes/builtin/light/default-light.js';
import { DefaultDark } from '../ui/themes/builtin/dark/default-dark.js';
import { isWorkspaceTrusted } from './trustedFolders.js';
import { getSettingsSchema, } from './settingsSchema.js';
export { getSettingsSchema, };
import { resolveEnvVarsInObject } from '../utils/envVarResolver.js';
import { customDeepMerge } from '../utils/deepMerge.js';
import { updateSettingsFilePreservingFormat } from '../utils/commentJson.js';
import { validateSettings, formatValidationError, } from './settings-validation.js';
export function getMergeStrategyForPath(path) {
let current = undefined;
let currentSchema = getSettingsSchema();
let parent = undefined;
for (const key of path) {
if (!currentSchema || !currentSchema[key]) {
// Key not found in schema - check if parent has additionalProperties
if (parent?.additionalProperties?.mergeStrategy) {
return parent.additionalProperties.mergeStrategy;
}
return undefined;
}
parent = current;
current = currentSchema[key];
currentSchema = current.properties;
}
return current?.mergeStrategy;
}
export const USER_SETTINGS_PATH = Storage.getGlobalSettingsPath();
export const USER_SETTINGS_DIR = path.dirname(USER_SETTINGS_PATH);
export const DEFAULT_EXCLUDED_ENV_VARS = ['DEBUG', 'DEBUG_MODE'];
const AUTH_ENV_VAR_WHITELIST = [
'GEMINI_API_KEY',
'GOOGLE_API_KEY',
'GOOGLE_CLOUD_PROJECT',
'GOOGLE_CLOUD_LOCATION',
'GOOGLE_GEMINI_BASE_URL',
'GOOGLE_VERTEX_BASE_URL',
];
/**
* Sanitizes an environment variable value to prevent shell injection.
* Restricts values to a safe character set: alphanumeric, -, _, ., /
*/
export function sanitizeEnvVar(value) {
return value.replace(/[^a-zA-Z0-9\-_./:@]/g, '');
}
export function getSystemSettingsPath() {
if (process.env['GEMINI_CLI_SYSTEM_SETTINGS_PATH']) {
return process.env['GEMINI_CLI_SYSTEM_SETTINGS_PATH'];
}
if (platform() === 'darwin') {
return '/Library/Application Support/GeminiCli/settings.json';
}
else if (platform() === 'win32') {
return 'C:\\ProgramData\\gemini-cli\\settings.json';
}
else {
return '/etc/gemini-cli/settings.json';
}
}
export function getSystemDefaultsPath() {
if (process.env['GEMINI_CLI_SYSTEM_DEFAULTS_PATH']) {
return process.env['GEMINI_CLI_SYSTEM_DEFAULTS_PATH'];
}
return path.join(path.dirname(getSystemSettingsPath()), 'system-defaults.json');
}
export var SettingScope;
(function (SettingScope) {
SettingScope["User"] = "User";
SettingScope["Workspace"] = "Workspace";
SettingScope["System"] = "System";
SettingScope["SystemDefaults"] = "SystemDefaults";
// Note that this scope is not supported in the settings dialog at this time,
// it is only supported for extensions.
SettingScope["Session"] = "Session";
})(SettingScope || (SettingScope = {}));
/**
* The actual values of the loadable settings scopes.
*/
const _loadableSettingScopes = [
SettingScope.User,
SettingScope.Workspace,
SettingScope.System,
SettingScope.SystemDefaults,
];
/**
* A type guard function that checks if `scope` is a loadable settings scope,
* and allows promotion to the `LoadableSettingsScope` type based on the result.
*/
export function isLoadableSettingScope(scope) {
return _loadableSettingScopes.includes(scope);
}
function setNestedProperty(obj, path, value) {
const keys = path.split('.');
const lastKey = keys.pop();
if (!lastKey)
return;
let current = obj;
for (const key of keys) {
if (current[key] === undefined) {
current[key] = {};
}
const next = current[key];
if (typeof next === 'object' && next !== null) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
current = next;
}
else {
// This path is invalid, so we stop.
return;
}
}
current[lastKey] = value;
}
export function getDefaultsFromSchema(schema = getSettingsSchema()) {
const defaults = {};
for (const key in schema) {
const definition = schema[key];
if (definition.properties) {
defaults[key] = getDefaultsFromSchema(definition.properties);
}
else if (definition.default !== undefined) {
defaults[key] = definition.default;
}
}
return defaults;
}
export function mergeSettings(system, systemDefaults, user, workspace, isTrusted) {
const safeWorkspace = isTrusted ? workspace : {};
const schemaDefaults = getDefaultsFromSchema();
// Settings are merged with the following precedence (last one wins for
// single values):
// 1. Schema Defaults (Built-in)
// 2. System Defaults
// 3. User Settings
// 4. Workspace Settings
// 5. System Settings (as overrides)
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return customDeepMerge(getMergeStrategyForPath, schemaDefaults, systemDefaults, user, safeWorkspace, system);
}
/**
* Creates a fully populated MergedSettings object for testing purposes.
* It merges the provided overrides with the default settings from the schema.
*
* @param overrides Partial settings to override the defaults.
* @returns A complete MergedSettings object.
*/
export function createTestMergedSettings(overrides = {}) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return customDeepMerge(getMergeStrategyForPath, getDefaultsFromSchema(), overrides);
}
export class LoadedSettings {
constructor(system, systemDefaults, user, workspace, isTrusted, errors = []) {
this.system = system;
this.systemDefaults = systemDefaults;
this.user = user;
this._workspaceFile = workspace;
this.isTrusted = isTrusted;
this.workspace = isTrusted
? workspace
: this.createEmptyWorkspace(workspace);
this.errors = errors;
this._merged = this.computeMergedSettings();
this._snapshot = this.computeSnapshot();
}
system;
systemDefaults;
user;
workspace;
isTrusted;
errors;
_workspaceFile;
_merged;
_snapshot;
_remoteAdminSettings;
get merged() {
return this._merged;
}
setTrusted(isTrusted) {
if (this.isTrusted === isTrusted) {
return;
}
this.isTrusted = isTrusted;
this.workspace = isTrusted
? this._workspaceFile
: this.createEmptyWorkspace(this._workspaceFile);
this._merged = this.computeMergedSettings();
coreEvents.emitSettingsChanged();
}
createEmptyWorkspace(workspace) {
return {
...workspace,
settings: {},
originalSettings: {},
};
}
computeMergedSettings() {
const merged = mergeSettings(this.system.settings, this.systemDefaults.settings, this.user.settings, this.workspace.settings, this.isTrusted);
// Remote admin settings always take precedence and file-based admin settings
// are ignored.
const adminSettingSchema = getSettingsSchema().admin;
if (adminSettingSchema?.properties) {
const adminSchema = adminSettingSchema.properties;
const adminDefaults = getDefaultsFromSchema(adminSchema);
// The final admin settings are the defaults overridden by remote settings.
// Any admin settings from files are ignored.
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
merged.admin = customDeepMerge((path) => getMergeStrategyForPath(['admin', ...path]), adminDefaults, this._remoteAdminSettings?.admin ?? {});
}
return merged;
}
computeSnapshot() {
const cloneSettingsFile = (file) => ({
path: file.path,
rawJson: file.rawJson,
settings: structuredClone(file.settings),
originalSettings: structuredClone(file.originalSettings),
});
return {
system: cloneSettingsFile(this.system),
systemDefaults: cloneSettingsFile(this.systemDefaults),
user: cloneSettingsFile(this.user),
workspace: cloneSettingsFile(this.workspace),
isTrusted: this.isTrusted,
errors: [...this.errors],
merged: structuredClone(this._merged),
};
}
// Passing this along with getSnapshot to useSyncExternalStore allows for idiomatic reactivity on settings changes
// React will pass a listener fn into this subscribe fn
// that listener fn will perform an object identity check on the snapshot and trigger a React re render if the snapshot has changed
subscribe(listener) {
coreEvents.on(CoreEvent.SettingsChanged, listener);
return () => coreEvents.off(CoreEvent.SettingsChanged, listener);
}
getSnapshot() {
return this._snapshot;
}
forScope(scope) {
switch (scope) {
case SettingScope.User:
return this.user;
case SettingScope.Workspace:
return this.workspace;
case SettingScope.System:
return this.system;
case SettingScope.SystemDefaults:
return this.systemDefaults;
default:
throw new Error(`Invalid scope: ${scope}`);
}
}
isPersistable(settingsFile) {
return !settingsFile.readOnly;
}
setValue(scope, key, value) {
const settingsFile = this.forScope(scope);
// Clone value to prevent reference sharing
const valueToSet = typeof value === 'object' && value !== null
? structuredClone(value)
: value;
setNestedProperty(settingsFile.settings, key, valueToSet);
if (this.isPersistable(settingsFile)) {
// Use a fresh clone for originalSettings to ensure total independence
setNestedProperty(settingsFile.originalSettings, key, structuredClone(valueToSet));
saveSettings(settingsFile);
}
this._merged = this.computeMergedSettings();
this._snapshot = this.computeSnapshot();
coreEvents.emitSettingsChanged();
}
setRemoteAdminSettings(remoteSettings) {
const admin = {};
const { strictModeDisabled, mcpSetting, cliFeatureSetting } = remoteSettings;
if (Object.keys(remoteSettings).length === 0) {
this._remoteAdminSettings = { admin };
this._merged = this.computeMergedSettings();
return;
}
admin.secureModeEnabled = !strictModeDisabled;
admin.mcp = {
enabled: mcpSetting?.mcpEnabled,
config: mcpSetting?.mcpConfig?.mcpServers,
};
admin.extensions = {
enabled: cliFeatureSetting?.extensionsSetting?.extensionsEnabled,
};
admin.skills = {
enabled: cliFeatureSetting?.unmanagedCapabilitiesEnabled,
};
this._remoteAdminSettings = { admin };
this._merged = this.computeMergedSettings();
}
}
function findEnvFile(startDir) {
let currentDir = path.resolve(startDir);
while (true) {
// prefer gemini-specific .env under GEMINI_DIR
const geminiEnvPath = path.join(currentDir, GEMINI_DIR, '.env');
if (fs.existsSync(geminiEnvPath)) {
return geminiEnvPath;
}
const envPath = path.join(currentDir, '.env');
if (fs.existsSync(envPath)) {
return envPath;
}
const parentDir = path.dirname(currentDir);
if (parentDir === currentDir || !parentDir) {
// check .env under home as fallback, again preferring gemini-specific .env
const homeGeminiEnvPath = path.join(homedir(), GEMINI_DIR, '.env');
if (fs.existsSync(homeGeminiEnvPath)) {
return homeGeminiEnvPath;
}
const homeEnvPath = path.join(homedir(), '.env');
if (fs.existsSync(homeEnvPath)) {
return homeEnvPath;
}
return null;
}
currentDir = parentDir;
}
}
export function setUpCloudShellEnvironment(envFilePath, isTrusted, isSandboxed) {
// Special handling for GOOGLE_CLOUD_PROJECT in Cloud Shell:
// Because GOOGLE_CLOUD_PROJECT in Cloud Shell tracks the project
// set by the user using "gcloud config set project" we do not want to
// use its value. So, unless the user overrides GOOGLE_CLOUD_PROJECT in
// one of the .env files, we set the Cloud Shell-specific default here.
let value = 'cloudshell-gca';
if (envFilePath && fs.existsSync(envFilePath)) {
const envFileContent = fs.readFileSync(envFilePath);
const parsedEnv = dotenv.parse(envFileContent);
if (parsedEnv['GOOGLE_CLOUD_PROJECT']) {
// .env file takes precedence in Cloud Shell
value = parsedEnv['GOOGLE_CLOUD_PROJECT'];
if (!isTrusted && isSandboxed) {
value = sanitizeEnvVar(value);
}
}
}
process.env['GOOGLE_CLOUD_PROJECT'] = value;
}
export function loadEnvironment(settings, workspaceDir, isWorkspaceTrustedFn = isWorkspaceTrusted) {
const envFilePath = findEnvFile(workspaceDir);
const trustResult = isWorkspaceTrustedFn(settings, workspaceDir);
const isTrusted = trustResult.isTrusted ?? false;
// Check settings OR check process.argv directly since this might be called
// before arguments are fully parsed. This is a best-effort sniffing approach
// that happens early in the CLI lifecycle. It is designed to detect the
// sandbox flag before the full command-line parser is initialized to ensure
// security constraints are applied when loading environment variables.
const args = process.argv.slice(2);
const doubleDashIndex = args.indexOf('--');
const relevantArgs = doubleDashIndex === -1 ? args : args.slice(0, doubleDashIndex);
const isSandboxed = !!settings.tools?.sandbox ||
relevantArgs.includes('-s') ||
relevantArgs.includes('--sandbox');
// Cloud Shell environment variable handling
if (process.env['CLOUD_SHELL'] === 'true') {
setUpCloudShellEnvironment(envFilePath, isTrusted, isSandboxed);
}
if (envFilePath) {
// Manually parse and load environment variables to handle exclusions correctly.
// This avoids modifying environment variables that were already set from the shell.
try {
const envFileContent = fs.readFileSync(envFilePath, 'utf-8');
const parsedEnv = dotenv.parse(envFileContent);
const excludedVars = settings?.advanced?.excludedEnvVars || DEFAULT_EXCLUDED_ENV_VARS;
const isProjectEnvFile = !envFilePath.includes(GEMINI_DIR);
for (const key in parsedEnv) {
if (Object.hasOwn(parsedEnv, key)) {
let value = parsedEnv[key];
// If the workspace is untrusted but we are sandboxed, only allow whitelisted variables.
if (!isTrusted && isSandboxed) {
if (!AUTH_ENV_VAR_WHITELIST.includes(key)) {
continue;
}
// Sanitize the value for untrusted sources
value = sanitizeEnvVar(value);
}
// If it's a project .env file, skip loading excluded variables.
if (isProjectEnvFile && excludedVars.includes(key)) {
continue;
}
// Load variable only if it's not already set in the environment.
if (!Object.hasOwn(process.env, key)) {
process.env[key] = value;
}
}
}
}
catch (_e) {
// Errors are ignored to match the behavior of `dotenv.config({ quiet: true })`.
}
}
}
// Cache to store the results of loadSettings to avoid redundant disk I/O.
const settingsCache = createCache({
storage: 'map',
defaultTtl: 10000, // 10 seconds
});
/**
* Resets the settings cache. Used exclusively for test isolation.
* @internal
*/
export function resetSettingsCacheForTesting() {
settingsCache.clear();
}
/**
* Loads settings from user and workspace directories.
* Project settings override user settings.
*/
export function loadSettings(workspaceDir = process.cwd()) {
const normalizedWorkspaceDir = path.resolve(workspaceDir);
return settingsCache.getOrCreate(normalizedWorkspaceDir, () => _doLoadSettings(normalizedWorkspaceDir));
}
/**
* Internal implementation of the settings loading logic.
*/
function _doLoadSettings(workspaceDir) {
let systemSettings = {};
let systemDefaultSettings = {};
let userSettings = {};
let workspaceSettings = {};
const settingsErrors = [];
const systemSettingsPath = getSystemSettingsPath();
const systemDefaultsPath = getSystemDefaultsPath();
const storage = new Storage(workspaceDir);
const workspaceSettingsPath = storage.getWorkspaceSettingsPath();
const load = (filePath) => {
try {
if (fs.existsSync(filePath)) {
const content = fs.readFileSync(filePath, 'utf-8');
const rawSettings = JSON.parse(stripJsonComments(content));
if (typeof rawSettings !== 'object' ||
rawSettings === null ||
Array.isArray(rawSettings)) {
settingsErrors.push({
message: 'Settings file is not a valid JSON object.',
path: filePath,
severity: 'error',
});
return { settings: {} };
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const settingsObject = rawSettings;
// Validate settings structure with Zod
const validationResult = validateSettings(settingsObject);
if (!validationResult.success && validationResult.error) {
const errorMessage = formatValidationError(validationResult.error, filePath);
settingsErrors.push({
message: errorMessage,
path: filePath,
severity: 'warning',
});
}
return { settings: settingsObject, rawJson: content };
}
}
catch (error) {
settingsErrors.push({
message: getErrorMessage(error),
path: filePath,
severity: 'error',
});
}
return { settings: {} };
};
const systemResult = load(systemSettingsPath);
const systemDefaultsResult = load(systemDefaultsPath);
const userResult = load(USER_SETTINGS_PATH);
let workspaceResult = {
settings: {},
rawJson: undefined,
};
if (!storage.isWorkspaceHomeDir()) {
workspaceResult = load(workspaceSettingsPath);
}
const systemOriginalSettings = structuredClone(systemResult.settings);
const systemDefaultsOriginalSettings = structuredClone(systemDefaultsResult.settings);
const userOriginalSettings = structuredClone(userResult.settings);
const workspaceOriginalSettings = structuredClone(workspaceResult.settings);
// Environment variables for runtime use
systemSettings = resolveEnvVarsInObject(systemResult.settings);
systemDefaultSettings = resolveEnvVarsInObject(systemDefaultsResult.settings);
userSettings = resolveEnvVarsInObject(userResult.settings);
workspaceSettings = resolveEnvVarsInObject(workspaceResult.settings);
// Support legacy theme names
if (userSettings.ui?.theme === 'VS') {
userSettings.ui.theme = DefaultLight.name;
}
else if (userSettings.ui?.theme === 'VS2015') {
userSettings.ui.theme = DefaultDark.name;
}
if (workspaceSettings.ui?.theme === 'VS') {
workspaceSettings.ui.theme = DefaultLight.name;
}
else if (workspaceSettings.ui?.theme === 'VS2015') {
workspaceSettings.ui.theme = DefaultDark.name;
}
// For the initial trust check, we can only use user and system settings.
const initialTrustCheckSettings = customDeepMerge(getMergeStrategyForPath, getDefaultsFromSchema(), systemDefaultSettings, userSettings, systemSettings);
const isTrusted = isWorkspaceTrusted(initialTrustCheckSettings, workspaceDir)
.isTrusted ?? false;
// Create a temporary merged settings object to pass to loadEnvironment.
const tempMergedSettings = mergeSettings(systemSettings, systemDefaultSettings, userSettings, workspaceSettings, isTrusted);
// loadEnvironment depends on settings so we have to create a temp version of
// the settings to avoid a cycle
loadEnvironment(tempMergedSettings, workspaceDir);
// Check for any fatal errors before proceeding
const fatalErrors = settingsErrors.filter((e) => e.severity === 'error');
if (fatalErrors.length > 0) {
const errorMessages = fatalErrors.map((error) => `Error in ${error.path}: ${error.message}`);
throw new FatalConfigError(`${errorMessages.join('\n')}\nPlease fix the configuration file(s) and try again.`);
}
const loadedSettings = new LoadedSettings({
path: systemSettingsPath,
settings: systemSettings,
originalSettings: systemOriginalSettings,
rawJson: systemResult.rawJson,
readOnly: true,
}, {
path: systemDefaultsPath,
settings: systemDefaultSettings,
originalSettings: systemDefaultsOriginalSettings,
rawJson: systemDefaultsResult.rawJson,
readOnly: true,
}, {
path: USER_SETTINGS_PATH,
settings: userSettings,
originalSettings: userOriginalSettings,
rawJson: userResult.rawJson,
readOnly: false,
}, {
path: storage.isWorkspaceHomeDir() ? '' : workspaceSettingsPath,
settings: workspaceSettings,
originalSettings: workspaceOriginalSettings,
rawJson: workspaceResult.rawJson,
readOnly: storage.isWorkspaceHomeDir(),
}, isTrusted, settingsErrors);
// Automatically migrate deprecated settings when loading.
migrateDeprecatedSettings(loadedSettings);
return loadedSettings;
}
/**
* Migrates deprecated settings to their new counterparts.
*
* Deprecated settings are removed from settings files by default.
*
* @returns true if any changes were made and need to be saved.
*/
export function migrateDeprecatedSettings(loadedSettings, removeDeprecated = true) {
let anyModified = false;
const systemWarnings = new Map();
/**
* Helper to migrate a boolean setting and track it if it's deprecated.
*/
const migrateBoolean = (settings, oldKey, newKey, prefix, foundDeprecated) => {
let modified = false;
const oldValue = settings[oldKey];
const newValue = settings[newKey];
if (typeof oldValue === 'boolean') {
if (foundDeprecated) {
foundDeprecated.push(prefix ? `${prefix}.${oldKey}` : oldKey);
}
if (typeof newValue === 'boolean') {
// Both exist, trust the new one
if (removeDeprecated) {
delete settings[oldKey];
modified = true;
}
}
else {
// Only old exists, migrate to new (inverted)
settings[newKey] = !oldValue;
if (removeDeprecated) {
delete settings[oldKey];
}
modified = true;
}
}
return modified;
};
const processScope = (scope) => {
const settingsFile = loadedSettings.forScope(scope);
const settings = settingsFile.settings;
const foundDeprecated = [];
// Migrate general settings
const generalSettings = settings.general;
if (generalSettings) {
const newGeneral = { ...generalSettings };
let modified = false;
modified =
migrateBoolean(newGeneral, 'disableAutoUpdate', 'enableAutoUpdate', 'general', foundDeprecated) || modified;
modified =
migrateBoolean(newGeneral, 'disableUpdateNag', 'enableAutoUpdateNotification', 'general', foundDeprecated) || modified;
if (modified) {
loadedSettings.setValue(scope, 'general', newGeneral);
if (!settingsFile.readOnly) {
anyModified = true;
}
}
}
// Migrate ui settings
const uiSettings = settings.ui;
if (uiSettings) {
const newUi = { ...uiSettings };
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const accessibilitySettings = newUi['accessibility'];
if (accessibilitySettings) {
const newAccessibility = { ...accessibilitySettings };
if (migrateBoolean(newAccessibility, 'disableLoadingPhrases', 'enableLoadingPhrases', 'ui.accessibility', foundDeprecated)) {
newUi['accessibility'] = newAccessibility;
loadedSettings.setValue(scope, 'ui', newUi);
if (!settingsFile.readOnly) {
anyModified = true;
}
}
// Migrate enableLoadingPhrases: false → loadingPhrases: 'off'
const enableLP = newAccessibility['enableLoadingPhrases'];
if (typeof enableLP === 'boolean' &&
newUi['loadingPhrases'] === undefined) {
if (!enableLP) {
newUi['loadingPhrases'] = 'off';
loadedSettings.setValue(scope, 'ui', newUi);
if (!settingsFile.readOnly) {
anyModified = true;
}
}
foundDeprecated.push('ui.accessibility.enableLoadingPhrases');
}
}
}
// Migrate context settings
const contextSettings = settings.context;
if (contextSettings) {
const newContext = { ...contextSettings };
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const fileFilteringSettings = newContext['fileFiltering'];
if (fileFilteringSettings) {
const newFileFiltering = { ...fileFilteringSettings };
if (migrateBoolean(newFileFiltering, 'disableFuzzySearch', 'enableFuzzySearch', 'context.fileFiltering', foundDeprecated)) {
newContext['fileFiltering'] = newFileFiltering;
loadedSettings.setValue(scope, 'context', newContext);
if (!settingsFile.readOnly) {
anyModified = true;
}
}
}
}
// Migrate tools settings
const toolsSettings = settings.tools;
if (toolsSettings) {
if (toolsSettings['approvalMode'] !== undefined) {
foundDeprecated.push('tools.approvalMode');
const generalSettings = settings.general || {};
const newGeneral = { ...generalSettings };
// Only set defaultApprovalMode if it's not already set
if (newGeneral['defaultApprovalMode'] === undefined) {
newGeneral['defaultApprovalMode'] = toolsSettings['approvalMode'];
loadedSettings.setValue(scope, 'general', newGeneral);
if (!settingsFile.readOnly) {
anyModified = true;
}
}
if (removeDeprecated) {
const newTools = { ...toolsSettings };
delete newTools['approvalMode'];
loadedSettings.setValue(scope, 'tools', newTools);
if (!settingsFile.readOnly) {
anyModified = true;
}
}
}
}
// Migrate experimental agent settings
const experimentalModified = migrateExperimentalSettings(settings, loadedSettings, scope, removeDeprecated, foundDeprecated);
if (experimentalModified) {
if (!settingsFile.readOnly) {
anyModified = true;
}
}
if (settingsFile.readOnly && foundDeprecated.length > 0) {
systemWarnings.set(scope, foundDeprecated);
}
};
processScope(SettingScope.User);
processScope(SettingScope.Workspace);
processScope(SettingScope.System);
processScope(SettingScope.SystemDefaults);
if (systemWarnings.size > 0) {
for (const [scope, flags] of systemWarnings) {
const scopeName = scope === SettingScope.SystemDefaults
? 'system default'
: scope.toLowerCase();
coreEvents.emitFeedback('warning', `The ${scopeName} configuration contains deprecated settings: [${flags.join(', ')}]. These could not be migrated automatically as system settings are read-only. Please update the system configuration manually.`);
}
}
return anyModified;
}
export function saveSettings(settingsFile) {
// Clear the entire cache on any save.
settingsCache.clear();
try {
// Ensure the directory exists
const dirPath = path.dirname(settingsFile.path);
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
const settingsToSave = settingsFile.originalSettings;
// Use the format-preserving update function
updateSettingsFilePreservingFormat(settingsFile.path, settingsToSave);
}
catch (error) {
const detailedErrorMessage = getFsErrorMessage(error);
coreEvents.emitFeedback('error', `Failed to save settings: ${detailedErrorMessage}`, error);
}
}
export function saveModelChange(loadedSettings, model) {
try {
loadedSettings.setValue(SettingScope.User, 'model.name', model);
}
catch (error) {
const detailedErrorMessage = getFsErrorMessage(error);
coreEvents.emitFeedback('error', `Failed to save preferred model: ${detailedErrorMessage}`, error);
}
}
function migrateExperimentalSettings(settings, loadedSettings, scope, removeDeprecated, foundDeprecated) {
const experimentalSettings = settings.experimental;
if (experimentalSettings) {
const agentsSettings = {
...settings.agents,
};
const agentsOverrides = {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
...(agentsSettings['overrides'] || {}),
};
let modified = false;
const migrateExperimental = (oldKey, migrateFn) => {
const old = experimentalSettings[oldKey];
if (old) {
foundDeprecated?.push(`experimental.${oldKey}`);
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
migrateFn(old);
modified = true;
}
};
// Migrate codebaseInvestigatorSettings -> agents.overrides.codebase_investigator
migrateExperimental('codebaseInvestigatorSettings', (old) => {
const override = {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
...agentsOverrides['codebase_investigator'],
};
if (old['enabled'] !== undefined)
override['enabled'] = old['enabled'];
const runConfig = {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
...override['runConfig'],
};
if (old['maxNumTurns'] !== undefined)
runConfig['maxTurns'] = old['maxNumTurns'];
if (old['maxTimeMinutes'] !== undefined)
runConfig['maxTimeMinutes'] = old['maxTimeMinutes'];
if (Object.keys(runConfig).length > 0)
override['runConfig'] = runConfig;
if (old['model'] !== undefined || old['thinkingBudget'] !== undefined) {
const modelConfig = {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
...override['modelConfig'],
};
if (old['model'] !== undefined)
modelConfig['model'] = old['model'];
if (old['thinkingBudget'] !== undefined) {
const generateContentConfig = {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
...modelConfig['generateContentConfig'],
};
const thinkingConfig = {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
...generateContentConfig['thinkingConfig'],
};
thinkingConfig['thinkingBudget'] = old['thinkingBudget'];
generateContentConfig['thinkingConfig'] = thinkingConfig;
modelConfig['generateContentConfig'] = generateContentConfig;
}
override['modelConfig'] = modelConfig;
}
agentsOverrides['codebase_investigator'] = override;
});
// Migrate cliHelpAgentSettings -> agents.overrides.cli_help
migrateExperimental('cliHelpAgentSettings', (old) => {
const override = {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
...agentsOverrides['cli_help'],
};
if (old['enabled'] !== undefined)
override['enabled'] = old['enabled'];
agentsOverrides['cli_help'] = override;
});
if (modified) {
agentsSettings['overrides'] = agentsOverrides;
loadedSettings.setValue(scope, 'agents', agentsSettings);
if (removeDeprecated) {
const newExperimental = { ...experimentalSettings };
delete newExperimental['codebaseInvestigatorSettings'];
delete newExperimental['cliHelpAgentSettings'];
loadedSettings.setValue(scope, 'experimental', newExperimental);
}
return true;
}
}
return false;
}
//# sourceMappingURL=settings.js.map