mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-13 05:12:55 -07:00
foo
foo checkpoint
This commit is contained in:
@@ -73,6 +73,10 @@ powerful tool for developers.
|
||||
and `packages/core` (Backend logic).
|
||||
- **Imports:** Use specific imports and avoid restricted relative imports
|
||||
between packages (enforced by ESLint).
|
||||
- **Linting:** Never suppress the `@typescript-eslint/no-unnecessary-condition`
|
||||
warning (e.g., via `eslint-disable`). This rule ensures that optional chaining
|
||||
and truthiness checks are only used when truly necessary according to the type
|
||||
system, keeping the code clean and predictable.
|
||||
- **License Headers:** For all new source code files (`.ts`, `.tsx`, `.js`),
|
||||
include the Apache-2.0 license header with the current year. (e.g.,
|
||||
`Copyright 2026 Google LLC`). This is enforced by ESLint.
|
||||
|
||||
@@ -202,6 +202,7 @@ export default tseslint.config(
|
||||
'@typescript-eslint/no-unsafe-type-assertion': 'error',
|
||||
'@typescript-eslint/no-unsafe-assignment': 'error',
|
||||
'@typescript-eslint/no-unsafe-return': 'error',
|
||||
'@typescript-eslint/no-unnecessary-condition': 'error',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -377,6 +378,213 @@ export default tseslint.config(
|
||||
},
|
||||
// Prettier config must be last
|
||||
prettierConfig,
|
||||
{
|
||||
// Legacy files with many @typescript-eslint/no-unnecessary-condition issues
|
||||
files: [
|
||||
'packages/a2a-server/src/agent/executor.ts',
|
||||
'packages/a2a-server/src/agent/task.ts',
|
||||
'packages/a2a-server/src/config/settings.ts',
|
||||
'packages/a2a-server/src/http/app.ts',
|
||||
'packages/cli/src/commands/extensions/configure.ts',
|
||||
'packages/cli/src/config/config.ts',
|
||||
'packages/cli/src/config/extension-manager.ts',
|
||||
'packages/cli/src/config/extensions/extensionEnablement.ts',
|
||||
'packages/cli/src/config/extensions/github.ts',
|
||||
'packages/cli/src/config/mcp/mcpServerEnablement.ts',
|
||||
'packages/cli/src/config/settings-validation.ts',
|
||||
'packages/cli/src/config/settings.ts',
|
||||
'packages/cli/src/config/trustedFolders.ts',
|
||||
'packages/cli/src/nonInteractiveCli.ts',
|
||||
'packages/cli/src/services/McpPromptLoader.ts',
|
||||
'packages/cli/src/test-utils/AppRig.tsx',
|
||||
'packages/cli/src/test-utils/mockConfig.ts',
|
||||
'packages/cli/src/test-utils/render.tsx',
|
||||
'packages/cli/src/ui/AppContainer.tsx',
|
||||
'packages/cli/src/ui/commands/agentsCommand.ts',
|
||||
'packages/cli/src/ui/commands/chatCommand.ts',
|
||||
'packages/cli/src/ui/commands/directoryCommand.tsx',
|
||||
'packages/cli/src/ui/commands/hooksCommand.ts',
|
||||
'packages/cli/src/ui/commands/mcpCommand.ts',
|
||||
'packages/cli/src/ui/commands/restoreCommand.ts',
|
||||
'packages/cli/src/ui/commands/rewindCommand.tsx',
|
||||
'packages/cli/src/ui/commands/setupGithubCommand.ts',
|
||||
'packages/cli/src/ui/commands/skillsCommand.ts',
|
||||
'packages/cli/src/ui/commands/statsCommand.ts',
|
||||
'packages/cli/src/ui/components/AskUserDialog.tsx',
|
||||
'packages/cli/src/ui/components/ColorsDisplay.tsx',
|
||||
'packages/cli/src/ui/components/Composer.tsx',
|
||||
'packages/cli/src/ui/components/ContextUsageDisplay.tsx',
|
||||
'packages/cli/src/ui/components/DetailedMessagesDisplay.tsx',
|
||||
'packages/cli/src/ui/components/DialogManager.tsx',
|
||||
'packages/cli/src/ui/components/ExitPlanModeDialog.tsx',
|
||||
'packages/cli/src/ui/components/FolderTrustDialog.tsx',
|
||||
'packages/cli/src/ui/components/HooksDialog.tsx',
|
||||
'packages/cli/src/ui/components/IdeTrustChangeDialog.tsx',
|
||||
'packages/cli/src/ui/components/ModelStatsDisplay.tsx',
|
||||
'packages/cli/src/ui/components/MultiFolderTrustDialog.tsx',
|
||||
'packages/cli/src/ui/components/Notifications.tsx',
|
||||
'packages/cli/src/ui/components/QuotaDisplay.tsx',
|
||||
'packages/cli/src/ui/components/RewindViewer.tsx',
|
||||
'packages/cli/src/ui/components/SessionBrowser.tsx',
|
||||
'packages/cli/src/ui/components/SettingsDialog.tsx',
|
||||
'packages/cli/src/ui/components/ShowMoreLines.tsx',
|
||||
'packages/cli/src/ui/components/StatsDisplay.tsx',
|
||||
'packages/cli/src/ui/components/ThemeDialog.tsx',
|
||||
'packages/cli/src/ui/components/UserIdentity.tsx',
|
||||
'packages/cli/src/ui/components/messages/SubagentProgressDisplay.tsx',
|
||||
'packages/cli/src/ui/components/messages/Todo.tsx',
|
||||
'packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx',
|
||||
'packages/cli/src/ui/components/messages/ToolGroupMessage.tsx',
|
||||
'packages/cli/src/ui/components/shared/BaseSettingsDialog.tsx',
|
||||
'packages/cli/src/ui/components/shared/EnumSelector.tsx',
|
||||
'packages/cli/src/ui/components/shared/MaxSizedBox.tsx',
|
||||
'packages/cli/src/ui/components/shared/Scrollable.tsx',
|
||||
'packages/cli/src/ui/components/shared/VirtualizedList.tsx',
|
||||
'packages/cli/src/ui/components/shared/text-buffer.ts',
|
||||
'packages/cli/src/ui/components/triage/TriageDuplicates.tsx',
|
||||
'packages/cli/src/ui/components/triage/TriageIssues.tsx',
|
||||
'packages/cli/src/ui/components/views/McpStatus.tsx',
|
||||
'packages/cli/src/ui/contexts/KeypressContext.tsx',
|
||||
'packages/cli/src/ui/contexts/ScrollProvider.tsx',
|
||||
'packages/cli/src/ui/contexts/SessionContext.tsx',
|
||||
'packages/cli/src/ui/hooks/slashCommandProcessor.ts',
|
||||
'packages/cli/src/ui/hooks/useAtCompletion.ts',
|
||||
'packages/cli/src/ui/hooks/useCommandCompletion.tsx',
|
||||
'packages/cli/src/ui/hooks/useConsoleMessages.ts',
|
||||
'packages/cli/src/ui/hooks/useExtensionUpdates.ts',
|
||||
'packages/cli/src/ui/hooks/useGeminiStream.ts',
|
||||
'packages/cli/src/ui/hooks/useIncludeDirsTrust.tsx',
|
||||
'packages/cli/src/ui/hooks/useInputHistory.ts',
|
||||
'packages/cli/src/ui/hooks/useInputHistoryStore.ts',
|
||||
'packages/cli/src/ui/hooks/usePermissionsModifyTrust.ts',
|
||||
'packages/cli/src/ui/hooks/usePromptCompletion.ts',
|
||||
'packages/cli/src/ui/hooks/useQuotaAndFallback.ts',
|
||||
'packages/cli/src/ui/hooks/useSelectionList.ts',
|
||||
'packages/cli/src/ui/hooks/useShellCompletion.ts',
|
||||
'packages/cli/src/ui/hooks/useSlashCompletion.ts',
|
||||
'packages/cli/src/ui/hooks/useThemeCommand.ts',
|
||||
'packages/cli/src/ui/hooks/useToolScheduler.ts',
|
||||
'packages/cli/src/ui/hooks/vim.ts',
|
||||
'packages/cli/src/ui/themes/theme-manager.ts',
|
||||
'packages/cli/src/ui/utils/CodeColorizer.tsx',
|
||||
'packages/cli/src/ui/utils/MarkdownDisplay.tsx',
|
||||
'packages/cli/src/ui/utils/borderStyles.ts',
|
||||
'packages/cli/src/ui/utils/clipboardUtils.ts',
|
||||
'packages/cli/src/ui/utils/highlight.ts',
|
||||
'packages/cli/src/ui/utils/inlineThinkingMode.ts',
|
||||
'packages/cli/src/ui/utils/keybindingUtils.ts',
|
||||
'packages/cli/src/ui/utils/terminalCapabilityManager.ts',
|
||||
'packages/cli/src/ui/utils/terminalSetup.ts',
|
||||
'packages/cli/src/ui/utils/terminalUtils.ts',
|
||||
'packages/cli/src/utils/activityLogger.ts',
|
||||
'packages/cli/src/utils/commentJson.ts',
|
||||
'packages/cli/src/utils/deepMerge.ts',
|
||||
'packages/cli/src/utils/devtoolsService.ts',
|
||||
'packages/cli/src/utils/envVarResolver.ts',
|
||||
'packages/cli/src/utils/sandbox.ts',
|
||||
'packages/cli/src/utils/sessionUtils.ts',
|
||||
'packages/cli/src/utils/settingsUtils.ts',
|
||||
'packages/cli/src/zed-integration/zedIntegration.ts',
|
||||
'packages/core/src/agents/a2a-client-manager.ts',
|
||||
'packages/core/src/agents/a2aUtils.ts',
|
||||
'packages/core/src/agents/acknowledgedAgents.ts',
|
||||
'packages/core/src/agents/browser/browserManager.ts',
|
||||
'packages/core/src/agents/browser/mcpToolWrapper.ts',
|
||||
'packages/core/src/agents/local-executor.ts',
|
||||
'packages/core/src/agents/local-invocation.ts',
|
||||
'packages/core/src/agents/registry.ts',
|
||||
'packages/core/src/agents/subagent-tool.ts',
|
||||
'packages/core/src/availability/modelAvailabilityService.ts',
|
||||
'packages/core/src/availability/policyHelpers.ts',
|
||||
'packages/core/src/billing/billing.ts',
|
||||
'packages/core/src/code_assist/admin/mcpUtils.ts',
|
||||
'packages/core/src/code_assist/converter.ts',
|
||||
'packages/core/src/code_assist/oauth2.ts',
|
||||
'packages/core/src/code_assist/server.ts',
|
||||
'packages/core/src/code_assist/setup.ts',
|
||||
'packages/core/src/code_assist/telemetry.ts',
|
||||
'packages/core/src/commands/memory.ts',
|
||||
'packages/core/src/config/config.ts',
|
||||
'packages/core/src/confirmation-bus/message-bus.ts',
|
||||
'packages/core/src/core/baseLlmClient.ts',
|
||||
'packages/core/src/core/contentGenerator.ts',
|
||||
'packages/core/src/core/coreToolHookTriggers.ts',
|
||||
'packages/core/src/core/fakeContentGenerator.ts',
|
||||
'packages/core/src/core/geminiChat.ts',
|
||||
'packages/core/src/core/logger.ts',
|
||||
'packages/core/src/core/loggingContentGenerator.ts',
|
||||
'packages/core/src/core/turn.ts',
|
||||
'packages/core/src/hooks/hookRegistry.ts',
|
||||
'packages/core/src/hooks/hookRunner.ts',
|
||||
'packages/core/src/hooks/trustedHooks.ts',
|
||||
'packages/core/src/hooks/types.ts',
|
||||
'packages/core/src/ide/ide-client.ts',
|
||||
'packages/core/src/ide/ide-connection-utils.ts',
|
||||
'packages/core/src/ide/process-utils.ts',
|
||||
'packages/core/src/mcp/oauth-provider.ts',
|
||||
'packages/core/src/mcp/token-storage/base-token-storage.ts',
|
||||
'packages/core/src/policy/config.ts',
|
||||
'packages/core/src/prompts/mcp-prompts.ts',
|
||||
'packages/core/src/prompts/promptProvider.ts',
|
||||
'packages/core/src/routing/strategies/classifierStrategy.ts',
|
||||
'packages/core/src/routing/strategies/defaultStrategy.ts',
|
||||
'packages/core/src/routing/strategies/fallbackStrategy.ts',
|
||||
'packages/core/src/routing/strategies/numericalClassifierStrategy.ts',
|
||||
'packages/core/src/routing/strategies/overrideStrategy.ts',
|
||||
'packages/core/src/safety/checker-runner.ts',
|
||||
'packages/core/src/safety/conseca/conseca.ts',
|
||||
'packages/core/src/safety/conseca/policy-enforcer.ts',
|
||||
'packages/core/src/safety/conseca/policy-generator.ts',
|
||||
'packages/core/src/safety/context-builder.ts',
|
||||
'packages/core/src/scheduler/confirmation.ts',
|
||||
'packages/core/src/scheduler/scheduler.ts',
|
||||
'packages/core/src/scheduler/state-manager.ts',
|
||||
'packages/core/src/scheduler/tool-executor.ts',
|
||||
'packages/core/src/services/environmentSanitization.ts',
|
||||
'packages/core/src/services/modelConfigService.ts',
|
||||
'packages/core/src/services/sessionSummaryUtils.ts',
|
||||
'packages/core/src/services/shellExecutionService.ts',
|
||||
'packages/core/src/services/toolOutputMaskingService.ts',
|
||||
'packages/core/src/skills/skillLoader.ts',
|
||||
'packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts',
|
||||
'packages/core/src/telemetry/startupProfiler.ts',
|
||||
'packages/core/src/telemetry/telemetryAttributes.ts',
|
||||
'packages/core/src/telemetry/types.ts',
|
||||
'packages/core/src/telemetry/uiTelemetry.ts',
|
||||
'packages/core/src/tools/grep-utils.ts',
|
||||
'packages/core/src/tools/mcp-client.ts',
|
||||
'packages/core/src/tools/shell.ts',
|
||||
'packages/core/src/utils/bfsFileSearch.ts',
|
||||
'packages/core/src/utils/editCorrector.ts',
|
||||
'packages/core/src/utils/errors.ts',
|
||||
'packages/core/src/utils/fileDiffUtils.ts',
|
||||
'packages/core/src/utils/fileUtils.ts',
|
||||
'packages/core/src/utils/filesearch/crawler.ts',
|
||||
'packages/core/src/utils/filesearch/fileSearch.ts',
|
||||
'packages/core/src/utils/filesearch/result-cache.ts',
|
||||
'packages/core/src/utils/headless.ts',
|
||||
'packages/core/src/utils/ignoreFileParser.ts',
|
||||
'packages/core/src/utils/ignorePatterns.ts',
|
||||
'packages/core/src/utils/llm-edit-fixer.ts',
|
||||
'packages/core/src/utils/nextSpeakerChecker.ts',
|
||||
'packages/core/src/utils/partUtils.ts',
|
||||
'packages/core/src/utils/retry.ts',
|
||||
'packages/core/src/utils/safeJsonStringify.ts',
|
||||
'packages/core/src/utils/sessionUtils.ts',
|
||||
'packages/core/src/utils/shell-utils.ts',
|
||||
'packages/sdk/src/session.ts',
|
||||
],
|
||||
rules: {
|
||||
'@typescript-eslint/no-unnecessary-condition': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
// Legacy files with many @typescript-eslint/no-unnecessary-type-assertion issues
|
||||
files: ['packages/core/src/core/client.ts', 'packages/core/src/core/geminiChat.ts'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-unnecessary-type-assertion': 'off',
|
||||
},
|
||||
},
|
||||
// extra settings for scripts that we run directly with node
|
||||
{
|
||||
files: ['./integration-tests/**/*.js'],
|
||||
|
||||
@@ -793,7 +793,7 @@ export class Task {
|
||||
) {
|
||||
errorEvent = event;
|
||||
}
|
||||
const errorMessage = errorEvent?.value?.error
|
||||
const errorMessage = errorEvent?.value.error
|
||||
? getErrorMessage(errorEvent.value.error)
|
||||
: 'Unknown error from LLM stream';
|
||||
logger.error(
|
||||
@@ -802,7 +802,7 @@ export class Task {
|
||||
);
|
||||
|
||||
let errMessage = `Unknown error from LLM stream: ${JSON.stringify(event)}`;
|
||||
if (errorEvent?.value?.error) {
|
||||
if (errorEvent?.value.error) {
|
||||
errMessage = parseAndFormatApiError(errorEvent.value.error);
|
||||
}
|
||||
this.cancelPendingTools(`LLM stream error: ${errorMessage}`);
|
||||
|
||||
@@ -109,9 +109,9 @@ export async function loadConfig(
|
||||
};
|
||||
|
||||
const fileService = new FileDiscoveryService(workspaceDir, {
|
||||
respectGitIgnore: configParams?.fileFiltering?.respectGitIgnore,
|
||||
respectGeminiIgnore: configParams?.fileFiltering?.respectGeminiIgnore,
|
||||
customIgnoreFilePaths: configParams?.fileFiltering?.customIgnoreFilePaths,
|
||||
respectGitIgnore: configParams.fileFiltering?.respectGitIgnore,
|
||||
respectGeminiIgnore: configParams.fileFiltering?.respectGeminiIgnore,
|
||||
customIgnoreFilePaths: configParams.fileFiltering?.customIgnoreFilePaths,
|
||||
});
|
||||
const { memoryContent, fileCount, filePaths } =
|
||||
await loadServerHierarchicalMemory(
|
||||
@@ -212,7 +212,7 @@ export function loadEnvironment(): void {
|
||||
|
||||
function findEnvFile(startDir: string): string | null {
|
||||
let currentDir = path.resolve(startDir);
|
||||
while (true) {
|
||||
for (;;) {
|
||||
// prefer gemini-specific .env under GEMINI_DIR
|
||||
const geminiEnvPath = path.join(currentDir, GEMINI_DIR, '.env');
|
||||
if (fs.existsSync(geminiEnvPath)) {
|
||||
|
||||
@@ -148,7 +148,7 @@ function isPersistedStateMetadata(
|
||||
export function getPersistedState(
|
||||
metadata: PersistedTaskMetadata,
|
||||
): PersistedStateMetadata | undefined {
|
||||
const state = metadata?.[METADATA_KEY];
|
||||
const state = metadata[METADATA_KEY];
|
||||
if (isPersistedStateMetadata(state)) {
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ export function assertUniqueFinalEventIsLast(
|
||||
expect(finalEvent.metadata?.['coderAgent']).toMatchObject({
|
||||
kind: 'state-change',
|
||||
});
|
||||
expect(finalEvent.status?.state).toBe('input-required');
|
||||
expect(finalEvent.status.state).toBe('input-required');
|
||||
expect(finalEvent.final).toBe(true);
|
||||
|
||||
// There is only one event with final and its the last
|
||||
|
||||
@@ -45,7 +45,7 @@ export const configureCommand: CommandModule<object, ConfigureArgs> = {
|
||||
const { name, setting, scope } = args;
|
||||
const settings = loadSettings(process.cwd()).merged;
|
||||
|
||||
if (!(settings.experimental?.extensionConfig ?? true)) {
|
||||
if (!(settings.experimental.extensionConfig ?? true)) {
|
||||
coreEvents.emitFeedback(
|
||||
'error',
|
||||
'Extension configuration is currently disabled. Enable it by setting "experimental.extensionConfig" to true.',
|
||||
|
||||
@@ -82,7 +82,7 @@ export async function handleUpdate(args: UpdateArgs) {
|
||||
extensionManager,
|
||||
updateState,
|
||||
() => {},
|
||||
settings.experimental?.extensionReloading,
|
||||
settings.experimental.extensionReloading,
|
||||
))!;
|
||||
if (
|
||||
updatedExtensionInfo.originalVersion !==
|
||||
|
||||
@@ -99,12 +99,6 @@ export async function configureSpecificSetting(
|
||||
const extensionConfig = await extensionManager.loadExtensionConfig(
|
||||
extension.path,
|
||||
);
|
||||
if (!extensionConfig) {
|
||||
logger.error(
|
||||
`Could not find configuration for extension "${extensionName}".`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await updateSetting(
|
||||
extensionConfig,
|
||||
@@ -137,11 +131,7 @@ export async function configureExtension(
|
||||
const extensionConfig = await extensionManager.loadExtensionConfig(
|
||||
extension.path,
|
||||
);
|
||||
if (
|
||||
!extensionConfig ||
|
||||
!extensionConfig.settings ||
|
||||
extensionConfig.settings.length === 0
|
||||
) {
|
||||
if (!extensionConfig.settings || extensionConfig.settings.length === 0) {
|
||||
logger.log(`Extension "${extensionName}" has no settings to configure.`);
|
||||
return;
|
||||
}
|
||||
@@ -175,11 +165,7 @@ export async function configureAllExtensions(
|
||||
const extensionConfig = await extensionManager.loadExtensionConfig(
|
||||
extension.path,
|
||||
);
|
||||
if (
|
||||
extensionConfig &&
|
||||
extensionConfig.settings &&
|
||||
extensionConfig.settings.length > 0
|
||||
) {
|
||||
if (extensionConfig.settings && extensionConfig.settings.length > 0) {
|
||||
logger.log(`\nConfiguring settings for "${extension.name}"...`);
|
||||
await configureExtensionSettings(
|
||||
extensionConfig,
|
||||
@@ -208,7 +194,7 @@ export async function configureExtensionSettings(
|
||||
process.cwd(),
|
||||
);
|
||||
|
||||
let workspaceSettings: Record<string, string> = {};
|
||||
let workspaceSettings: Record<string, string | undefined> = {};
|
||||
if (scope === ExtensionSettingScope.USER) {
|
||||
workspaceSettings = await getScopedEnvContents(
|
||||
extensionConfig,
|
||||
|
||||
@@ -236,10 +236,7 @@ export async function handleMigrateFromClaude() {
|
||||
const settings = loadSettings(workingDir);
|
||||
|
||||
// Merge migrated hooks with existing hooks
|
||||
const existingHooks = (settings.merged?.hooks || {}) as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
const existingHooks = settings.merged.hooks as Record<string, unknown>;
|
||||
const mergedHooks = { ...existingHooks, ...migratedHooks };
|
||||
|
||||
// Update settings (setValue automatically saves)
|
||||
|
||||
@@ -117,7 +117,7 @@ async function addMcpServer(
|
||||
const existingSettings = settings.forScope(settingsScope).settings;
|
||||
const mcpServers = existingSettings.mcpServers || {};
|
||||
|
||||
const isExistingServer = !!mcpServers[name];
|
||||
const isExistingServer = Object.hasOwn(mcpServers, name);
|
||||
if (isExistingServer) {
|
||||
debugLogger.log(
|
||||
`MCP server "${name}" is already configured within ${scope} settings.`,
|
||||
@@ -211,11 +211,12 @@ export const addCommand: CommandModule = {
|
||||
})
|
||||
.middleware((argv) => {
|
||||
// Handle -- separator args as server args if present
|
||||
if (argv['--']) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
const dashArgs = argv['--'] as string[] | undefined;
|
||||
if (dashArgs && dashArgs.length > 0) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
const existingArgs = (argv['args'] as Array<string | number>) || [];
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
argv['args'] = [...existingArgs, ...(argv['--'] as string[])];
|
||||
const existingArgs = argv['args'] as Array<string | number>;
|
||||
argv['args'] = [...existingArgs, ...dashArgs];
|
||||
}
|
||||
}),
|
||||
handler: async (argv) => {
|
||||
|
||||
@@ -43,9 +43,9 @@ async function handleEnable(args: Args): Promise<void> {
|
||||
}
|
||||
|
||||
const result = await canLoadServer(name, {
|
||||
adminMcpEnabled: settings.merged.admin?.mcp?.enabled ?? true,
|
||||
allowedList: settings.merged.mcp?.allowed,
|
||||
excludedList: settings.merged.mcp?.excluded,
|
||||
adminMcpEnabled: settings.merged.admin.mcp.enabled,
|
||||
allowedList: settings.merged.mcp.allowed,
|
||||
excludedList: settings.merged.mcp.excluded,
|
||||
});
|
||||
|
||||
if (
|
||||
|
||||
@@ -39,10 +39,12 @@ export async function getMcpServersFromConfig(
|
||||
requestSetting: promptForSetting,
|
||||
});
|
||||
const extensions = await extensionManager.loadExtensions();
|
||||
const mcpServers = { ...settings.mcpServers };
|
||||
const mcpServers: Record<string, MCPServerConfig> = {
|
||||
...settings.mcpServers,
|
||||
};
|
||||
for (const extension of extensions) {
|
||||
Object.entries(extension.mcpServers || {}).forEach(([key, server]) => {
|
||||
if (mcpServers[key]) {
|
||||
if (key in mcpServers) {
|
||||
return;
|
||||
}
|
||||
mcpServers[key] = {
|
||||
@@ -52,7 +54,7 @@ export async function getMcpServersFromConfig(
|
||||
});
|
||||
}
|
||||
|
||||
const adminAllowlist = settings.admin?.mcp?.config;
|
||||
const adminAllowlist = settings.admin.mcp.config;
|
||||
const filteredResult = applyAdminAllowlist(mcpServers, adminAllowlist);
|
||||
|
||||
return filteredResult;
|
||||
|
||||
@@ -24,7 +24,7 @@ async function removeMcpServer(
|
||||
const existingSettings = settings.forScope(settingsScope).settings;
|
||||
const mcpServers = existingSettings.mcpServers || {};
|
||||
|
||||
if (!mcpServers[name]) {
|
||||
if (!(name in mcpServers)) {
|
||||
debugLogger.log(`Server "${name}" not found in ${scope} settings.`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -327,11 +327,11 @@ export async function parseArguments(
|
||||
return true;
|
||||
});
|
||||
|
||||
if (settings.experimental?.extensionManagement) {
|
||||
if (settings.experimental.extensionManagement) {
|
||||
yargsInstance.command(extensionsCommand);
|
||||
}
|
||||
|
||||
if (settings.skills?.enabled ?? true) {
|
||||
if (settings.skills.enabled) {
|
||||
yargsInstance.command(skillsCommand);
|
||||
}
|
||||
// Register hooks command if hooks are enabled
|
||||
@@ -456,16 +456,16 @@ export async function loadCliConfig(
|
||||
process.env['GEMINI_SANDBOX'] = 'true';
|
||||
}
|
||||
|
||||
const memoryImportFormat = settings.context?.importFormat || 'tree';
|
||||
const includeDirectoryTree = settings.context?.includeDirectoryTree ?? true;
|
||||
const memoryImportFormat = settings.context.importFormat || 'tree';
|
||||
const includeDirectoryTree = settings.context.includeDirectoryTree;
|
||||
|
||||
const ideMode = settings.ide?.enabled ?? false;
|
||||
const ideMode = settings.ide.enabled;
|
||||
|
||||
const folderTrust =
|
||||
process.env['GEMINI_CLI_INTEGRATION_TEST'] === 'true' ||
|
||||
process.env['VITEST'] === 'true'
|
||||
? false
|
||||
: (settings.security?.folderTrust?.enabled ?? false);
|
||||
: settings.security.folderTrust.enabled;
|
||||
const trustedFolder =
|
||||
isWorkspaceTrusted(settings, cwd, undefined, {
|
||||
prompt: argv.prompt,
|
||||
@@ -476,7 +476,7 @@ export async function loadCliConfig(
|
||||
// TODO(b/343434939): This is a bit of a hack. The contextFileName should ideally be passed
|
||||
// directly to the Config constructor in core, and have core handle setGeminiMdFilename.
|
||||
// However, loadHierarchicalGeminiMemory is called *before* createServerConfig.
|
||||
if (settings.context?.fileName) {
|
||||
if (settings.context.fileName) {
|
||||
setServerGeminiMdFilename(settings.context.fileName);
|
||||
} else {
|
||||
// Reset to default if not provided in settings.
|
||||
@@ -487,15 +487,15 @@ export async function loadCliConfig(
|
||||
|
||||
const memoryFileFiltering = {
|
||||
...DEFAULT_MEMORY_FILE_FILTERING_OPTIONS,
|
||||
...settings.context?.fileFiltering,
|
||||
...settings.context.fileFiltering,
|
||||
};
|
||||
|
||||
const fileFiltering = {
|
||||
...DEFAULT_FILE_FILTERING_OPTIONS,
|
||||
...settings.context?.fileFiltering,
|
||||
...settings.context.fileFiltering,
|
||||
};
|
||||
|
||||
const includeDirectories = (settings.context?.includeDirectories || [])
|
||||
const includeDirectories = settings.context.includeDirectories
|
||||
.map(resolvePath)
|
||||
.concat((argv.includeDirectories || []).map(resolvePath));
|
||||
|
||||
@@ -515,7 +515,7 @@ export async function loadCliConfig(
|
||||
.getExtensions()
|
||||
.find((ext) => ext.isActive && ext.plan?.directory)?.plan;
|
||||
|
||||
const experimentalJitContext = settings.experimental?.jitContext ?? false;
|
||||
const experimentalJitContext = settings.experimental.jitContext;
|
||||
|
||||
let memoryContent: string | HierarchicalMemory = '';
|
||||
let fileCount = 0;
|
||||
@@ -525,7 +525,7 @@ export async function loadCliConfig(
|
||||
// Call the (now wrapper) loadHierarchicalGeminiMemory which calls the server's version
|
||||
const result = await loadServerHierarchicalMemory(
|
||||
cwd,
|
||||
settings.context?.loadMemoryFromIncludeDirectories || false
|
||||
settings.context.loadMemoryFromIncludeDirectories
|
||||
? includeDirectories
|
||||
: [],
|
||||
debugMode,
|
||||
@@ -534,7 +534,7 @@ export async function loadCliConfig(
|
||||
trustedFolder,
|
||||
memoryImportFormat,
|
||||
memoryFileFiltering,
|
||||
settings.context?.discoveryMaxDirs,
|
||||
settings.context.discoveryMaxDirs,
|
||||
);
|
||||
memoryContent = result.memoryContent;
|
||||
fileCount = result.fileCount;
|
||||
@@ -548,8 +548,8 @@ export async function loadCliConfig(
|
||||
const rawApprovalMode =
|
||||
argv.approvalMode ||
|
||||
(argv.yolo ? 'yolo' : undefined) ||
|
||||
((settings.general?.defaultApprovalMode as string) !== 'yolo'
|
||||
? settings.general?.defaultApprovalMode
|
||||
((settings.general.defaultApprovalMode as string) !== 'yolo'
|
||||
? settings.general.defaultApprovalMode
|
||||
: undefined);
|
||||
|
||||
if (rawApprovalMode) {
|
||||
@@ -561,7 +561,7 @@ export async function loadCliConfig(
|
||||
approvalMode = ApprovalMode.AUTO_EDIT;
|
||||
break;
|
||||
case 'plan':
|
||||
if (!(settings.experimental?.plan ?? false)) {
|
||||
if (!settings.experimental.plan) {
|
||||
debugLogger.warn(
|
||||
'Approval mode "plan" is only available when experimental.plan is enabled. Falling back to "default".',
|
||||
);
|
||||
@@ -583,9 +583,9 @@ export async function loadCliConfig(
|
||||
}
|
||||
|
||||
// Override approval mode if disableYoloMode is set.
|
||||
if (settings.security?.disableYoloMode || settings.admin?.secureModeEnabled) {
|
||||
if (settings.security.disableYoloMode || settings.admin.secureModeEnabled) {
|
||||
if (approvalMode === ApprovalMode.YOLO) {
|
||||
if (settings.admin?.secureModeEnabled) {
|
||||
if (settings.admin.secureModeEnabled) {
|
||||
debugLogger.error(
|
||||
'YOLO mode is disabled by "secureModeEnabled" setting.',
|
||||
);
|
||||
@@ -636,7 +636,7 @@ export async function loadCliConfig(
|
||||
(!isHeadlessMode({ prompt: argv.prompt, query: argv.query }) &&
|
||||
!argv.isCommand);
|
||||
|
||||
const allowedTools = argv.allowedTools || settings.tools?.allowed || [];
|
||||
const allowedTools = argv.allowedTools || settings.tools.allowed || [];
|
||||
const allowedToolsSet = new Set(allowedTools);
|
||||
|
||||
// In non-interactive mode, exclude tools that require a prompt.
|
||||
@@ -694,7 +694,7 @@ export async function loadCliConfig(
|
||||
},
|
||||
mcp: {
|
||||
...settings.mcp,
|
||||
allowed: argv.allowedMcpServerNames ?? settings.mcp?.allowed,
|
||||
allowed: argv.allowedMcpServerNames ?? settings.mcp.allowed,
|
||||
},
|
||||
policyPaths: argv.policy,
|
||||
};
|
||||
@@ -715,7 +715,7 @@ export async function loadCliConfig(
|
||||
|
||||
const defaultModel = PREVIEW_GEMINI_MODEL_AUTO;
|
||||
const specifiedModel =
|
||||
argv.model || process.env['GEMINI_MODEL'] || settings.model?.name;
|
||||
argv.model || process.env['GEMINI_MODEL'] || settings.model.name;
|
||||
|
||||
const resolvedModel =
|
||||
specifiedModel === GEMINI_MODEL_ALIAS_AUTO
|
||||
@@ -729,9 +729,9 @@ export async function loadCliConfig(
|
||||
|
||||
const ptyInfo = await getPty();
|
||||
|
||||
const mcpEnabled = settings.admin?.mcp?.enabled ?? true;
|
||||
const extensionsEnabled = settings.admin?.extensions?.enabled ?? true;
|
||||
const adminSkillsEnabled = settings.admin?.skills?.enabled ?? true;
|
||||
const mcpEnabled = settings.admin.mcp.enabled;
|
||||
const extensionsEnabled = settings.admin.extensions.enabled;
|
||||
const adminSkillsEnabled = settings.admin.skills.enabled;
|
||||
|
||||
// Create MCP enablement manager and callbacks
|
||||
const mcpEnablementManager = McpServerEnablementManager.getInstance();
|
||||
@@ -739,8 +739,8 @@ export async function loadCliConfig(
|
||||
? mcpEnablementManager.getEnablementCallbacks()
|
||||
: undefined;
|
||||
|
||||
const adminAllowlist = settings.admin?.mcp?.config;
|
||||
let mcpServerCommand = mcpEnabled ? settings.mcp?.serverCommand : undefined;
|
||||
const adminAllowlist = settings.admin.mcp.config;
|
||||
let mcpServerCommand = mcpEnabled ? settings.mcp.serverCommand : undefined;
|
||||
let mcpServers = mcpEnabled ? settings.mcpServers : {};
|
||||
|
||||
if (mcpEnabled && adminAllowlist && Object.keys(adminAllowlist).length > 0) {
|
||||
@@ -748,7 +748,7 @@ export async function loadCliConfig(
|
||||
mcpServers = result.mcpServers;
|
||||
mcpServerCommand = undefined;
|
||||
|
||||
if (result.blockedServerNames && result.blockedServerNames.length > 0) {
|
||||
if (result.blockedServerNames.length > 0) {
|
||||
const message = getAdminBlockedMcpServersMessage(
|
||||
result.blockedServerNames,
|
||||
undefined,
|
||||
@@ -766,17 +766,17 @@ export async function loadCliConfig(
|
||||
includeDirectoryTree,
|
||||
includeDirectories,
|
||||
loadMemoryFromIncludeDirectories:
|
||||
settings.context?.loadMemoryFromIncludeDirectories || false,
|
||||
settings.context.loadMemoryFromIncludeDirectories,
|
||||
debugMode,
|
||||
question,
|
||||
|
||||
coreTools: settings.tools?.core || undefined,
|
||||
coreTools: settings.tools.core || undefined,
|
||||
allowedTools: allowedTools.length > 0 ? allowedTools : undefined,
|
||||
policyEngineConfig,
|
||||
policyUpdateConfirmationRequest,
|
||||
excludeTools,
|
||||
toolDiscoveryCommand: settings.tools?.discoveryCommand,
|
||||
toolCallCommand: settings.tools?.callCommand,
|
||||
toolDiscoveryCommand: settings.tools.discoveryCommand,
|
||||
toolCallCommand: settings.tools.callCommand,
|
||||
mcpServerCommand,
|
||||
mcpServers,
|
||||
mcpEnablementCallbacks,
|
||||
@@ -785,32 +785,32 @@ export async function loadCliConfig(
|
||||
agents: settings.agents,
|
||||
adminSkillsEnabled,
|
||||
allowedMcpServers: mcpEnabled
|
||||
? (argv.allowedMcpServerNames ?? settings.mcp?.allowed)
|
||||
? (argv.allowedMcpServerNames ?? settings.mcp.allowed)
|
||||
: undefined,
|
||||
blockedMcpServers: mcpEnabled
|
||||
? argv.allowedMcpServerNames
|
||||
? undefined
|
||||
: settings.mcp?.excluded
|
||||
: settings.mcp.excluded
|
||||
: undefined,
|
||||
blockedEnvironmentVariables:
|
||||
settings.security?.environmentVariableRedaction?.blocked,
|
||||
settings.security.environmentVariableRedaction.blocked,
|
||||
enableEnvironmentVariableRedaction:
|
||||
settings.security?.environmentVariableRedaction?.enabled,
|
||||
settings.security.environmentVariableRedaction.enabled,
|
||||
userMemory: memoryContent,
|
||||
geminiMdFileCount: fileCount,
|
||||
geminiMdFilePaths: filePaths,
|
||||
approvalMode,
|
||||
disableYoloMode:
|
||||
settings.security?.disableYoloMode || settings.admin?.secureModeEnabled,
|
||||
showMemoryUsage: settings.ui?.showMemoryUsage || false,
|
||||
settings.security.disableYoloMode || settings.admin.secureModeEnabled,
|
||||
showMemoryUsage: settings.ui.showMemoryUsage,
|
||||
accessibility: {
|
||||
...settings.ui?.accessibility,
|
||||
...settings.ui.accessibility,
|
||||
screenReader,
|
||||
},
|
||||
telemetry: telemetrySettings,
|
||||
usageStatisticsEnabled: settings.privacy?.usageStatisticsEnabled,
|
||||
usageStatisticsEnabled: settings.privacy.usageStatisticsEnabled,
|
||||
fileFiltering,
|
||||
checkpointing: settings.general?.checkpointing?.enabled,
|
||||
checkpointing: settings.general.checkpointing.enabled,
|
||||
proxy:
|
||||
process.env['HTTPS_PROXY'] ||
|
||||
process.env['https_proxy'] ||
|
||||
|
||||
@@ -154,11 +154,8 @@ export class ExtensionManager extends ExtensionLoader {
|
||||
installMetadata: ExtensionInstallMetadata,
|
||||
previousExtensionConfig?: ExtensionConfig,
|
||||
): Promise<GeminiCLIExtension> {
|
||||
if (
|
||||
this.settings.security?.allowedExtensions &&
|
||||
this.settings.security?.allowedExtensions.length > 0
|
||||
) {
|
||||
const extensionAllowed = this.settings.security?.allowedExtensions.some(
|
||||
if (this.settings.security.allowedExtensions.length > 0) {
|
||||
const extensionAllowed = this.settings.security.allowedExtensions.some(
|
||||
(pattern) => {
|
||||
try {
|
||||
return new RegExp(pattern).test(installMetadata.source);
|
||||
@@ -312,7 +309,7 @@ Would you like to attempt to install via "git clone" instead?`,
|
||||
const destinationPath = new ExtensionStorage(
|
||||
newExtensionName,
|
||||
).getExtensionDir();
|
||||
let previousSettings: Record<string, string> | undefined;
|
||||
let previousSettings: Record<string, string | undefined> | undefined;
|
||||
if (isUpdate) {
|
||||
previousSettings = await getEnvContents(
|
||||
previousExtensionConfig,
|
||||
@@ -626,19 +623,16 @@ Would you like to attempt to install via "git clone" instead?`,
|
||||
|
||||
const installMetadata = loadInstallMetadata(extensionDir);
|
||||
let effectiveExtensionPath = extensionDir;
|
||||
if (
|
||||
this.settings.security?.allowedExtensions &&
|
||||
this.settings.security?.allowedExtensions.length > 0
|
||||
) {
|
||||
if (this.settings.security.allowedExtensions.length > 0) {
|
||||
if (!installMetadata?.source) {
|
||||
throw new Error(
|
||||
`Failed to load extension ${extensionDir}. The ${INSTALL_METADATA_FILENAME} file is missing or misconfigured.`,
|
||||
);
|
||||
}
|
||||
const extensionAllowed = this.settings.security?.allowedExtensions.some(
|
||||
const extensionAllowed = this.settings.security.allowedExtensions.some(
|
||||
(pattern) => {
|
||||
try {
|
||||
return new RegExp(pattern).test(installMetadata?.source);
|
||||
return new RegExp(pattern).test(installMetadata.source);
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`Invalid regex pattern in allowedExtensions setting: "${pattern}. Error: ${getErrorMessage(e)}`,
|
||||
@@ -672,8 +666,8 @@ Would you like to attempt to install via "git clone" instead?`,
|
||||
|
||||
const extensionId = getExtensionId(config, installMetadata);
|
||||
|
||||
let userSettings: Record<string, string> = {};
|
||||
let workspaceSettings: Record<string, string> = {};
|
||||
let userSettings: Record<string, string | undefined> = {};
|
||||
let workspaceSettings: Record<string, string | undefined> = {};
|
||||
|
||||
if (this.settings.experimental.extensionConfig) {
|
||||
userSettings = await getScopedEnvContents(
|
||||
|
||||
@@ -166,7 +166,7 @@ export class ExtensionEnablementManager {
|
||||
const extensionConfig = config[extensionName];
|
||||
// Extensions are enabled by default.
|
||||
let enabled = true;
|
||||
const allOverrides = extensionConfig?.overrides ?? [];
|
||||
const allOverrides = extensionConfig.overrides ?? [];
|
||||
for (const rule of allOverrides) {
|
||||
const override = Override.fromFileRule(rule);
|
||||
if (override.matchesPath(ensureLeadingAndTrailingSlash(currentPath))) {
|
||||
|
||||
@@ -64,7 +64,7 @@ export async function maybePromptForSettings(
|
||||
extensionId: string,
|
||||
requestSetting: (setting: ExtensionSetting) => Promise<string>,
|
||||
previousExtensionConfig?: ExtensionConfig,
|
||||
previousSettings?: Record<string, string>,
|
||||
previousSettings?: Record<string, string | undefined>,
|
||||
): Promise<void> {
|
||||
const { name: extensionName, settings } = extensionConfig;
|
||||
if (
|
||||
@@ -92,7 +92,9 @@ export async function maybePromptForSettings(
|
||||
previousExtensionConfig?.settings ?? [],
|
||||
);
|
||||
|
||||
const allSettings: Record<string, string> = { ...previousSettings };
|
||||
const allSettings: Record<string, string | undefined> = {
|
||||
...previousSettings,
|
||||
};
|
||||
|
||||
for (const removedEnvSetting of settingsChanges.removeEnv) {
|
||||
delete allSettings[removedEnvSetting.envVar];
|
||||
@@ -174,13 +176,13 @@ export async function getScopedEnvContents(
|
||||
extensionId: string,
|
||||
scope: ExtensionSettingScope,
|
||||
workspaceDir?: string,
|
||||
): Promise<Record<string, string>> {
|
||||
): Promise<Record<string, string | undefined>> {
|
||||
const { name: extensionName } = extensionConfig;
|
||||
const keychain = new KeychainTokenStorage(
|
||||
getKeychainStorageName(extensionName, extensionId, scope, workspaceDir),
|
||||
);
|
||||
const envFilePath = getEnvFilePath(extensionName, scope, workspaceDir);
|
||||
let customEnv: Record<string, string> = {};
|
||||
let customEnv: Record<string, string | undefined> = {};
|
||||
if (fsSync.existsSync(envFilePath)) {
|
||||
const stat = fsSync.statSync(envFilePath);
|
||||
if (!stat.isDirectory()) {
|
||||
@@ -206,7 +208,7 @@ export async function getEnvContents(
|
||||
extensionConfig: ExtensionConfig,
|
||||
extensionId: string,
|
||||
workspaceDir: string,
|
||||
): Promise<Record<string, string>> {
|
||||
): Promise<Record<string, string | undefined>> {
|
||||
if (!extensionConfig.settings || extensionConfig.settings.length === 0) {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
|
||||
@@ -109,16 +109,16 @@ export function tryParseGithubUrl(source: string): GithubRepoInfo | null {
|
||||
if (!parsedUrl) {
|
||||
throw new Error(`Invalid repo URL: ${source}`);
|
||||
}
|
||||
if (parsedUrl?.host !== 'github.com') {
|
||||
if (parsedUrl.host !== 'github.com') {
|
||||
return null;
|
||||
}
|
||||
// The pathname should be "/owner/repo".
|
||||
const parts = parsedUrl?.pathname
|
||||
const parts = parsedUrl.pathname
|
||||
.split('/')
|
||||
// Remove the empty segments, fixes trailing and leading slashes
|
||||
.filter((part) => part !== '');
|
||||
|
||||
if (parts?.length !== 2) {
|
||||
if (parts.length !== 2) {
|
||||
throw new Error(
|
||||
`Invalid GitHub repository source: ${source}. Expected "owner/repo" or a github repo uri.`,
|
||||
);
|
||||
|
||||
@@ -48,7 +48,7 @@ export async function updateExtension(
|
||||
`Extension ${extension.name} cannot be updated, type is unknown.`,
|
||||
);
|
||||
}
|
||||
if (installMetadata?.type === 'link') {
|
||||
if (installMetadata.type === 'link') {
|
||||
dispatchExtensionStateUpdate({
|
||||
type: 'SET_STATE',
|
||||
payload: { name: extension.name, state: ExtensionUpdateState.UP_TO_DATE },
|
||||
|
||||
@@ -226,7 +226,7 @@ export class McpServerEnablementManager {
|
||||
async isFileEnabled(serverName: string): Promise<boolean> {
|
||||
const config = await this.readConfig();
|
||||
const state = config[normalizeServerId(serverName)];
|
||||
return state?.enabled ?? true;
|
||||
return state.enabled ?? true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -45,7 +45,7 @@ function getSandboxCommand(
|
||||
const environmentConfiguredSandbox =
|
||||
process.env['GEMINI_SANDBOX']?.toLowerCase().trim() ?? '';
|
||||
sandbox =
|
||||
environmentConfiguredSandbox?.length > 0
|
||||
environmentConfiguredSandbox.length > 0
|
||||
? environmentConfiguredSandbox
|
||||
: sandbox;
|
||||
if (sandbox === '1' || sandbox === 'true') sandbox = true;
|
||||
|
||||
@@ -460,7 +460,7 @@ describe('Settings Loading and Merging', () => {
|
||||
);
|
||||
|
||||
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||
expect(settings.merged.security?.folderTrust?.enabled).toBe(false); // Workspace setting should be used
|
||||
expect(settings.merged.security.folderTrust?.enabled).toBe(false); // Workspace setting should be used
|
||||
});
|
||||
|
||||
it('should use system folderTrust over user setting', () => {
|
||||
@@ -500,7 +500,7 @@ describe('Settings Loading and Merging', () => {
|
||||
);
|
||||
|
||||
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||
expect(settings.merged.security?.folderTrust?.enabled).toBe(true); // System setting should be used
|
||||
expect(settings.merged.security.folderTrust?.enabled).toBe(true); // System setting should be used
|
||||
});
|
||||
|
||||
it('should not allow user or workspace to override system disableYoloMode', () => {
|
||||
@@ -534,7 +534,7 @@ describe('Settings Loading and Merging', () => {
|
||||
);
|
||||
|
||||
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||
expect(settings.merged.security?.disableYoloMode).toBe(true); // System setting should be used
|
||||
expect(settings.merged.security.disableYoloMode).toBe(true); // System setting should be used
|
||||
});
|
||||
|
||||
it.each([
|
||||
@@ -630,7 +630,7 @@ describe('Settings Loading and Merging', () => {
|
||||
'WORKSPACE_DEBUG',
|
||||
'WORKSPACE_VAR',
|
||||
]);
|
||||
expect(settings.merged.advanced?.excludedEnvVars).toEqual([
|
||||
expect(settings.merged.advanced.excludedEnvVars).toEqual([
|
||||
'DEBUG',
|
||||
'DEBUG_MODE',
|
||||
'NODE_ENV',
|
||||
@@ -658,7 +658,7 @@ describe('Settings Loading and Merging', () => {
|
||||
);
|
||||
|
||||
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||
expect(settings.merged.context?.fileName).toBeUndefined();
|
||||
expect(settings.merged.context.fileName).toBeUndefined();
|
||||
});
|
||||
|
||||
it.each([
|
||||
@@ -991,7 +991,7 @@ describe('Settings Loading and Merging', () => {
|
||||
);
|
||||
|
||||
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||
expect(settings.merged.model?.compressionThreshold).toEqual(expected);
|
||||
expect(settings.merged.model.compressionThreshold).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1018,7 +1018,7 @@ describe('Settings Loading and Merging', () => {
|
||||
|
||||
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||
|
||||
expect(settings.merged.model?.compressionThreshold).toEqual(0.5);
|
||||
expect(settings.merged.model.compressionThreshold).toEqual(0.5);
|
||||
});
|
||||
|
||||
it('should merge includeDirectories from all scopes', () => {
|
||||
@@ -1052,7 +1052,7 @@ describe('Settings Loading and Merging', () => {
|
||||
|
||||
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||
|
||||
expect(settings.merged.context?.includeDirectories).toEqual([
|
||||
expect(settings.merged.context.includeDirectories).toEqual([
|
||||
'/system/defaults/dir',
|
||||
'/user/dir1',
|
||||
'/user/dir2',
|
||||
@@ -1247,7 +1247,7 @@ describe('Settings Loading and Merging', () => {
|
||||
expect((settings.merged as TestSettings)['workspaceOnly']).toBe(
|
||||
'workspace_value',
|
||||
);
|
||||
expect(settings.merged.ui?.theme).toBe('light'); // workspace overrides user
|
||||
expect(settings.merged.ui.theme).toBe('light'); // workspace overrides user
|
||||
|
||||
delete process.env['SYSTEM_VAR'];
|
||||
delete process.env['USER_VAR'];
|
||||
@@ -1275,7 +1275,7 @@ describe('Settings Loading and Merging', () => {
|
||||
);
|
||||
|
||||
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||
expect(settings.merged.advanced?.dnsResolutionOrder).toBe('verbatim');
|
||||
expect(settings.merged.advanced.dnsResolutionOrder).toBe('verbatim');
|
||||
});
|
||||
|
||||
it('should use user dnsResolutionOrder if workspace is not defined', () => {
|
||||
@@ -1294,7 +1294,7 @@ describe('Settings Loading and Merging', () => {
|
||||
);
|
||||
|
||||
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||
expect(settings.merged.advanced?.dnsResolutionOrder).toBe('verbatim');
|
||||
expect(settings.merged.advanced.dnsResolutionOrder).toBe('verbatim');
|
||||
});
|
||||
|
||||
it('should leave unresolved environment variables as is', () => {
|
||||
@@ -1599,7 +1599,7 @@ describe('Settings Loading and Merging', () => {
|
||||
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||
|
||||
// Verify the settings were loaded correctly
|
||||
expect(settings.merged.advanced?.excludedEnvVars).toEqual([
|
||||
expect(settings.merged.advanced.excludedEnvVars).toEqual([
|
||||
'DEBUG',
|
||||
'DEBUG_MODE',
|
||||
]);
|
||||
@@ -1637,7 +1637,7 @@ describe('Settings Loading and Merging', () => {
|
||||
'NODE_ENV',
|
||||
'DEBUG',
|
||||
]);
|
||||
expect(settings.merged.advanced?.excludedEnvVars).toEqual([
|
||||
expect(settings.merged.advanced.excludedEnvVars).toEqual([
|
||||
'DEBUG',
|
||||
'DEBUG_MODE',
|
||||
'NODE_ENV',
|
||||
@@ -1677,7 +1677,7 @@ describe('Settings Loading and Merging', () => {
|
||||
'WORKSPACE_DEBUG',
|
||||
'WORKSPACE_VAR',
|
||||
]);
|
||||
expect(settings.merged.advanced?.excludedEnvVars).toEqual([
|
||||
expect(settings.merged.advanced.excludedEnvVars).toEqual([
|
||||
'DEBUG',
|
||||
'DEBUG_MODE',
|
||||
'NODE_ENV',
|
||||
@@ -1711,9 +1711,9 @@ describe('Settings Loading and Merging', () => {
|
||||
);
|
||||
|
||||
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||
expect(settings.merged.tools?.sandbox).toBe(true);
|
||||
expect(settings.merged.context?.fileName).toBe('WORKSPACE.md');
|
||||
expect(settings.merged.ui?.theme).toBe('dark');
|
||||
expect(settings.merged.tools.sandbox).toBe(true);
|
||||
expect(settings.merged.context.fileName).toBe('WORKSPACE.md');
|
||||
expect(settings.merged.ui.theme).toBe('dark');
|
||||
});
|
||||
|
||||
it('should NOT merge workspace settings when workspace is not trusted', () => {
|
||||
@@ -1744,9 +1744,9 @@ describe('Settings Loading and Merging', () => {
|
||||
|
||||
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||
|
||||
expect(settings.merged.tools?.sandbox).toBe(false); // User setting
|
||||
expect(settings.merged.context?.fileName).toBe('USER.md'); // User setting
|
||||
expect(settings.merged.ui?.theme).toBe('dark'); // User setting
|
||||
expect(settings.merged.tools.sandbox).toBe(false); // User setting
|
||||
expect(settings.merged.context.fileName).toBe('USER.md'); // User setting
|
||||
expect(settings.merged.ui.theme).toBe('dark'); // User setting
|
||||
});
|
||||
|
||||
it('should NOT merge workspace settings when workspace trust is undefined', () => {
|
||||
@@ -1777,8 +1777,8 @@ describe('Settings Loading and Merging', () => {
|
||||
|
||||
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||
|
||||
expect(settings.merged.tools?.sandbox).toBe(false); // User setting
|
||||
expect(settings.merged.context?.fileName).toBe('USER.md'); // User setting
|
||||
expect(settings.merged.tools.sandbox).toBe(false); // User setting
|
||||
expect(settings.merged.context.fileName).toBe('USER.md'); // User setting
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2229,7 +2229,7 @@ describe('Settings Loading and Merging', () => {
|
||||
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||
|
||||
// Verify it was migrated in the merged settings
|
||||
expect(settings.merged.general?.enableAutoUpdate).toBe(false);
|
||||
expect(settings.merged.general.enableAutoUpdate).toBe(false);
|
||||
|
||||
// Verify it was saved back to disk (via setValue calling updateSettingsFilePreservingFormat)
|
||||
expect(updateSettingsFilePreservingFormat).toHaveBeenCalledWith(
|
||||
@@ -2289,7 +2289,7 @@ describe('Settings Loading and Merging', () => {
|
||||
).toBe(true);
|
||||
|
||||
// Merged should also reflect it (system overrides defaults, but both are migrated)
|
||||
expect(settings.merged.general?.enableAutoUpdateNotification).toBe(false);
|
||||
expect(settings.merged.general.enableAutoUpdateNotification).toBe(false);
|
||||
|
||||
// Verify it was NOT saved back to disk
|
||||
expect(updateSettingsFilePreservingFormat).not.toHaveBeenCalledWith(
|
||||
@@ -2487,10 +2487,10 @@ describe('Settings Loading and Merging', () => {
|
||||
|
||||
// 1. Verify that on initial load, file-based admin settings are ignored
|
||||
// and schema defaults are used instead.
|
||||
expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(false); // default: false
|
||||
expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(true); // default: true
|
||||
expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(true); // default: true
|
||||
expect(loadedSettings.merged.ui?.theme).toBe('system-theme'); // non-admin setting should be loaded
|
||||
expect(loadedSettings.merged.admin.secureModeEnabled).toBe(false); // default: false
|
||||
expect(loadedSettings.merged.admin.mcp?.enabled).toBe(true); // default: true
|
||||
expect(loadedSettings.merged.admin.extensions?.enabled).toBe(true); // default: true
|
||||
expect(loadedSettings.merged.ui.theme).toBe('system-theme'); // non-admin setting should be loaded
|
||||
|
||||
// 2. Now, set remote admin settings.
|
||||
loadedSettings.setRemoteAdminSettings({
|
||||
@@ -2503,11 +2503,11 @@ describe('Settings Loading and Merging', () => {
|
||||
});
|
||||
|
||||
// 3. Verify that remote admin settings take precedence.
|
||||
expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(true);
|
||||
expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(false);
|
||||
expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(false);
|
||||
expect(loadedSettings.merged.admin.secureModeEnabled).toBe(true);
|
||||
expect(loadedSettings.merged.admin.mcp?.enabled).toBe(false);
|
||||
expect(loadedSettings.merged.admin.extensions?.enabled).toBe(false);
|
||||
// non-admin setting should remain unchanged
|
||||
expect(loadedSettings.merged.ui?.theme).toBe('system-theme');
|
||||
expect(loadedSettings.merged.ui.theme).toBe('system-theme');
|
||||
});
|
||||
|
||||
it('should set remote admin settings and recompute merged settings', () => {
|
||||
@@ -2532,10 +2532,10 @@ describe('Settings Loading and Merging', () => {
|
||||
|
||||
const loadedSettings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||
// Ensure initial state from defaults (as file-based admin settings are ignored)
|
||||
expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(false);
|
||||
expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(true);
|
||||
expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(true);
|
||||
expect(loadedSettings.merged.ui?.theme).toBe('initial-theme');
|
||||
expect(loadedSettings.merged.admin.secureModeEnabled).toBe(false);
|
||||
expect(loadedSettings.merged.admin.mcp?.enabled).toBe(true);
|
||||
expect(loadedSettings.merged.admin.extensions?.enabled).toBe(true);
|
||||
expect(loadedSettings.merged.ui.theme).toBe('initial-theme');
|
||||
|
||||
const newRemoteSettings = {
|
||||
strictModeDisabled: false,
|
||||
@@ -2549,11 +2549,11 @@ describe('Settings Loading and Merging', () => {
|
||||
loadedSettings.setRemoteAdminSettings(newRemoteSettings);
|
||||
|
||||
// Verify that remote admin settings are applied
|
||||
expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(true);
|
||||
expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(false);
|
||||
expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(false);
|
||||
expect(loadedSettings.merged.admin.secureModeEnabled).toBe(true);
|
||||
expect(loadedSettings.merged.admin.mcp?.enabled).toBe(false);
|
||||
expect(loadedSettings.merged.admin.extensions?.enabled).toBe(false);
|
||||
// Non-admin settings should remain untouched
|
||||
expect(loadedSettings.merged.ui?.theme).toBe('initial-theme');
|
||||
expect(loadedSettings.merged.ui.theme).toBe('initial-theme');
|
||||
});
|
||||
|
||||
it('should correctly handle undefined remote admin settings', () => {
|
||||
@@ -2573,16 +2573,16 @@ describe('Settings Loading and Merging', () => {
|
||||
|
||||
const loadedSettings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||
// Should have default admin settings
|
||||
expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(false);
|
||||
expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(true);
|
||||
expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(true);
|
||||
expect(loadedSettings.merged.admin.secureModeEnabled).toBe(false);
|
||||
expect(loadedSettings.merged.admin.mcp?.enabled).toBe(true);
|
||||
expect(loadedSettings.merged.admin.extensions?.enabled).toBe(true);
|
||||
|
||||
loadedSettings.setRemoteAdminSettings({}); // Set empty remote settings
|
||||
|
||||
// Admin settings should revert to defaults because there are no remote overrides
|
||||
expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(false);
|
||||
expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(true);
|
||||
expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(true);
|
||||
expect(loadedSettings.merged.admin.secureModeEnabled).toBe(false);
|
||||
expect(loadedSettings.merged.admin.mcp?.enabled).toBe(true);
|
||||
expect(loadedSettings.merged.admin.extensions?.enabled).toBe(true);
|
||||
});
|
||||
|
||||
it('should un-nest MCP configuration from remote settings', () => {
|
||||
@@ -2604,7 +2604,7 @@ describe('Settings Loading and Merging', () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(loadedSettings.merged.admin?.mcp?.config).toEqual(mcpServers);
|
||||
expect(loadedSettings.merged.admin.mcp?.config).toEqual(mcpServers);
|
||||
});
|
||||
|
||||
it('should set skills based on unmanagedCapabilitiesEnabled', () => {
|
||||
@@ -2630,9 +2630,9 @@ describe('Settings Loading and Merging', () => {
|
||||
loadedSettings.setRemoteAdminSettings({});
|
||||
|
||||
// Should default to schema defaults (standard defaults)
|
||||
expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(false);
|
||||
expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(true);
|
||||
expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(true);
|
||||
expect(loadedSettings.merged.admin.secureModeEnabled).toBe(false);
|
||||
expect(loadedSettings.merged.admin.mcp?.enabled).toBe(true);
|
||||
expect(loadedSettings.merged.admin.extensions?.enabled).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -54,24 +54,20 @@ import {
|
||||
export function getMergeStrategyForPath(
|
||||
path: string[],
|
||||
): MergeStrategy | undefined {
|
||||
let current: SettingDefinition | undefined = undefined;
|
||||
let currentSchema: SettingsSchema | undefined = getSettingsSchema();
|
||||
let parent: SettingDefinition | undefined = undefined;
|
||||
|
||||
for (const key of path) {
|
||||
if (!currentSchema || !currentSchema[key]) {
|
||||
const current: SettingDefinition | undefined = currentSchema?.[key];
|
||||
if (!current) {
|
||||
// Key not found in schema - check if parent has additionalProperties
|
||||
if (parent?.additionalProperties?.mergeStrategy) {
|
||||
return parent.additionalProperties.mergeStrategy;
|
||||
}
|
||||
return undefined;
|
||||
return parent?.additionalProperties?.mergeStrategy;
|
||||
}
|
||||
parent = current;
|
||||
current = currentSchema[key];
|
||||
currentSchema = current.properties;
|
||||
}
|
||||
|
||||
return current?.mergeStrategy;
|
||||
return parent?.mergeStrategy;
|
||||
}
|
||||
|
||||
export const USER_SETTINGS_PATH = Storage.getGlobalSettingsPath();
|
||||
@@ -372,19 +368,17 @@ export class LoadedSettings {
|
||||
// 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);
|
||||
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: string[]) => getMergeStrategyForPath(['admin', ...path]),
|
||||
adminDefaults,
|
||||
this._remoteAdminSettings?.admin ?? {},
|
||||
) as MergedSettings['admin'];
|
||||
}
|
||||
// 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: string[]) => getMergeStrategyForPath(['admin', ...path]),
|
||||
adminDefaults,
|
||||
this._remoteAdminSettings?.admin ?? {},
|
||||
) as MergedSettings['admin'];
|
||||
return merged;
|
||||
}
|
||||
|
||||
@@ -493,7 +487,7 @@ export class LoadedSettings {
|
||||
|
||||
function findEnvFile(startDir: string): string | null {
|
||||
let currentDir = path.resolve(startDir);
|
||||
while (true) {
|
||||
for (;;) {
|
||||
// prefer gemini-specific .env under GEMINI_DIR
|
||||
const geminiEnvPath = path.join(currentDir, GEMINI_DIR, '.env');
|
||||
if (fs.existsSync(geminiEnvPath)) {
|
||||
@@ -583,7 +577,7 @@ export function loadEnvironment(
|
||||
const parsedEnv = dotenv.parse(envFileContent);
|
||||
|
||||
const excludedVars =
|
||||
settings?.advanced?.excludedEnvVars || DEFAULT_EXCLUDED_ENV_VARS;
|
||||
settings.advanced?.excludedEnvVars || DEFAULT_EXCLUDED_ENV_VARS;
|
||||
const isProjectEnvFile = !envFilePath.includes(GEMINI_DIR);
|
||||
|
||||
for (const key in parsedEnv) {
|
||||
@@ -1085,7 +1079,7 @@ function migrateExperimentalSettings(
|
||||
};
|
||||
const agentsOverrides = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
...((agentsSettings['overrides'] as Record<string, unknown>) || {}),
|
||||
...(agentsSettings['overrides'] as Record<string, unknown>),
|
||||
};
|
||||
let modified = false;
|
||||
|
||||
|
||||
@@ -195,7 +195,7 @@ describe('Settings Repro', () => {
|
||||
// If it doesn't throw, check if it merged correctly.
|
||||
// The model.compressionThreshold should be present.
|
||||
// And model.name should probably be undefined or default, but certainly NOT { compressionThreshold: 0.8 }
|
||||
expect(settings.merged.model?.compressionThreshold).toBe(0.8);
|
||||
expect(typeof settings.merged.model?.name).not.toBe('object');
|
||||
expect(settings.merged.model.compressionThreshold).toBe(0.8);
|
||||
expect(typeof settings.merged.model.name).not.toBe('object');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -33,7 +33,7 @@ export async function runDeferredCommand(settings: MergedSettings) {
|
||||
const adminSettings = settings.admin;
|
||||
const commandName = deferredCommand.commandName;
|
||||
|
||||
if (commandName === 'mcp' && adminSettings?.mcp?.enabled === false) {
|
||||
if (commandName === 'mcp' && adminSettings.mcp.enabled === false) {
|
||||
coreEvents.emitFeedback(
|
||||
'error',
|
||||
getAdminErrorMessage('MCP', undefined /* config */),
|
||||
@@ -44,7 +44,7 @@ export async function runDeferredCommand(settings: MergedSettings) {
|
||||
|
||||
if (
|
||||
commandName === 'extensions' &&
|
||||
adminSettings?.extensions?.enabled === false
|
||||
adminSettings.extensions.enabled === false
|
||||
) {
|
||||
coreEvents.emitFeedback(
|
||||
'error',
|
||||
@@ -54,7 +54,7 @@ export async function runDeferredCommand(settings: MergedSettings) {
|
||||
process.exit(ExitCodes.FATAL_CONFIG_ERROR);
|
||||
}
|
||||
|
||||
if (commandName === 'skills' && adminSettings?.skills?.enabled === false) {
|
||||
if (commandName === 'skills' && adminSettings.skills.enabled === false) {
|
||||
coreEvents.emitFeedback(
|
||||
'error',
|
||||
getAdminErrorMessage('Agent skills', undefined /* config */),
|
||||
|
||||
@@ -378,7 +378,7 @@ export async function main() {
|
||||
|
||||
if (
|
||||
(argv.allowedTools && argv.allowedTools.length > 0) ||
|
||||
(settings.merged.tools?.allowed && settings.merged.tools.allowed.length > 0)
|
||||
(settings.merged.tools.allowed && settings.merged.tools.allowed.length > 0)
|
||||
) {
|
||||
coreEvents.emitFeedback(
|
||||
'warning',
|
||||
@@ -387,7 +387,7 @@ export async function main() {
|
||||
}
|
||||
|
||||
if (
|
||||
settings.merged.tools?.exclude &&
|
||||
settings.merged.tools.exclude &&
|
||||
settings.merged.tools.exclude.length > 0
|
||||
) {
|
||||
coreEvents.emitFeedback(
|
||||
@@ -748,7 +748,7 @@ export async function main() {
|
||||
? SessionStartSource.Resume
|
||||
: SessionStartSource.Startup;
|
||||
|
||||
const hookSystem = config?.getHookSystem();
|
||||
const hookSystem = config.getHookSystem();
|
||||
if (hookSystem) {
|
||||
const result = await hookSystem.fireSessionStartEvent(sessionStartSource);
|
||||
|
||||
@@ -784,7 +784,7 @@ export async function main() {
|
||||
new UserPromptEvent(
|
||||
input.length,
|
||||
prompt_id,
|
||||
config.getContentGeneratorConfig()?.authType,
|
||||
config.getContentGeneratorConfig().authType,
|
||||
input,
|
||||
),
|
||||
);
|
||||
@@ -881,7 +881,7 @@ function setupAdminControlsListener() {
|
||||
type?: string;
|
||||
settings?: AdminControlsSettings;
|
||||
};
|
||||
if (message?.type === 'admin-settings' && message.settings) {
|
||||
if (message.type === 'admin-settings' && message.settings) {
|
||||
if (config) {
|
||||
config.setRemoteAdminSettings(message.settings);
|
||||
} else {
|
||||
|
||||
@@ -135,7 +135,7 @@ export async function runNonInteractive({
|
||||
key: { name?: string; ctrl?: boolean },
|
||||
) => {
|
||||
// Detect Ctrl+C: either ctrl+c key combo or raw character code 3
|
||||
if ((key && key.ctrl && key.name === 'c') || str === '\u0003') {
|
||||
if ((key.ctrl && key.name === 'c') || str === '\u0003') {
|
||||
// Only handle once
|
||||
if (isAborting) {
|
||||
return;
|
||||
@@ -289,7 +289,7 @@ export async function runNonInteractive({
|
||||
let currentMessages: Content[] = [{ role: 'user', parts: query }];
|
||||
|
||||
let turnCount = 0;
|
||||
while (true) {
|
||||
for (;;) {
|
||||
turnCount++;
|
||||
if (
|
||||
config.getMaxSessionTurns() >= 0 &&
|
||||
@@ -461,8 +461,8 @@ export async function runNonInteractive({
|
||||
(tc) => tc.response.errorType === ToolErrorType.STOP_EXECUTION,
|
||||
);
|
||||
|
||||
if (stopExecutionTool && stopExecutionTool.response.error) {
|
||||
const stopMessage = `Agent execution stopped: ${stopExecutionTool.response.error.message}`;
|
||||
if (stopExecutionTool) {
|
||||
const stopMessage = `Agent execution stopped: ${stopExecutionTool.response.error?.message}`;
|
||||
|
||||
if (config.getOutputFormat() === OutputFormat.TEXT) {
|
||||
process.stderr.write(`${stopMessage}\n`);
|
||||
|
||||
@@ -56,14 +56,14 @@ export const handleSlashCommand = async (
|
||||
if (commandToExecute.action) {
|
||||
// Not used by custom commands but may be in the future.
|
||||
const sessionStats: SessionStatsState = {
|
||||
sessionId: config?.getSessionId(),
|
||||
sessionId: config.getSessionId(),
|
||||
sessionStartTime: new Date(),
|
||||
metrics: uiTelemetryService.getMetrics(),
|
||||
lastPromptTokenCount: 0,
|
||||
promptCount: 1,
|
||||
};
|
||||
|
||||
const logger = new Logger(config?.getSessionId() || '', config?.storage);
|
||||
const logger = new Logger(config.getSessionId() || '', config.storage);
|
||||
|
||||
const context: CommandContext = {
|
||||
services: {
|
||||
|
||||
@@ -158,7 +158,7 @@ export class BuiltinCommandLoader implements ICommandLoader {
|
||||
themeCommand,
|
||||
toolsCommand,
|
||||
...(this.config?.isSkillsSupportEnabled()
|
||||
? this.config?.getSkillManager()?.isAdminEnabled() === false
|
||||
? this.config.getSkillManager().isAdminEnabled() === false
|
||||
? [
|
||||
{
|
||||
name: 'skills',
|
||||
|
||||
@@ -126,7 +126,7 @@ export class FileCommandLoader implements ICommandLoader {
|
||||
if (
|
||||
!signal.aborted &&
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
(error as { code?: string })?.code !== 'ENOENT'
|
||||
(error as { code?: string }).code !== 'ENOENT'
|
||||
) {
|
||||
coreEvents.emitFeedback(
|
||||
'error',
|
||||
|
||||
@@ -62,7 +62,7 @@ export class McpPromptLoader implements ICommandLoader {
|
||||
let helpMessage = `Arguments for "${prompt.name}":\n\n`;
|
||||
if (prompt.arguments && prompt.arguments.length > 0) {
|
||||
helpMessage += `You can provide arguments by name (e.g., --argName="value") or by position.\n\n`;
|
||||
helpMessage += `e.g., ${prompt.name} ${prompt.arguments?.map((_) => `"foo"`)} is equivalent to ${prompt.name} ${prompt.arguments?.map((arg) => `--${arg.name}="foo"`)}\n\n`;
|
||||
helpMessage += `e.g., ${prompt.name} ${prompt.arguments.map((_) => `"foo"`)} is equivalent to ${prompt.name} ${prompt.arguments.map((arg) => `--${arg.name}="foo"`)}\n\n`;
|
||||
}
|
||||
for (const arg of prompt.arguments) {
|
||||
helpMessage += ` --${arg.name}\n`;
|
||||
@@ -123,7 +123,7 @@ export class McpPromptLoader implements ICommandLoader {
|
||||
};
|
||||
}
|
||||
|
||||
const maybeContent = result.messages?.[0]?.content;
|
||||
const maybeContent = result.messages[0]?.content;
|
||||
if (maybeContent.type !== 'text') {
|
||||
return {
|
||||
type: 'message',
|
||||
|
||||
@@ -119,7 +119,7 @@ export class ShellProcessor implements IPromptProcessor {
|
||||
|
||||
if (!command) continue;
|
||||
|
||||
if (context.session.sessionShellAllowlist?.has(command)) {
|
||||
if (context.session.sessionShellAllowlist.has(command)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -312,7 +312,7 @@ export class AppRig {
|
||||
const details = call.confirmationDetails;
|
||||
const title = 'title' in details ? details.title : '';
|
||||
const toolDisplayName =
|
||||
call.tool?.displayName || title.replace(/^Confirm:\s*/, '');
|
||||
call.tool.displayName || title.replace(/^Confirm:\s*/, '');
|
||||
if (!this.pendingConfirmations.has(call.correlationId)) {
|
||||
this.pendingConfirmations.set(call.correlationId, {
|
||||
toolName: call.request.name,
|
||||
@@ -469,7 +469,7 @@ export class AppRig {
|
||||
} = options;
|
||||
const start = Date.now();
|
||||
|
||||
while (true) {
|
||||
for (;;) {
|
||||
if (await predicate()) return;
|
||||
|
||||
if (Date.now() - start > timeout) {
|
||||
@@ -622,7 +622,7 @@ export class AppRig {
|
||||
onConfirmation?: (confirmation: PendingConfirmation) => void | boolean,
|
||||
timeout = 60000,
|
||||
) {
|
||||
while (true) {
|
||||
for (;;) {
|
||||
const event = await this.waitForNextEvent(timeout);
|
||||
if (event.type === 'idle') {
|
||||
break;
|
||||
@@ -710,7 +710,7 @@ export class AppRig {
|
||||
if (this.config) {
|
||||
const recordingService = this.config
|
||||
.getGeminiClient()
|
||||
?.getChatRecordingService();
|
||||
.getChatRecordingService();
|
||||
if (recordingService) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion
|
||||
(recordingService as any).conversationFile = null;
|
||||
|
||||
@@ -18,7 +18,7 @@ export async function waitFor(
|
||||
): Promise<void> {
|
||||
const startTime = Date.now();
|
||||
|
||||
while (true) {
|
||||
for (;;) {
|
||||
try {
|
||||
await assertion();
|
||||
return;
|
||||
|
||||
@@ -47,7 +47,7 @@ export const generateSvgForTerminal = (terminal: Terminal): string => {
|
||||
const b = v[c % 6];
|
||||
const g = v[Math.floor(c / 6) % 6];
|
||||
const r = v[Math.floor(c / 36) % 6];
|
||||
return `#${[r, g, b].map((x) => x?.toString(16).padStart(2, '0')).join('')}`;
|
||||
return `#${[r, g, b].map((x) => x.toString(16).padStart(2, '0')).join('')}`;
|
||||
} else if (colorCode >= 232 && colorCode <= 255) {
|
||||
const gray = 8 + (colorCode - 232) * 10;
|
||||
const hex = gray.toString(16).padStart(2, '0');
|
||||
|
||||
@@ -73,12 +73,8 @@ export function IdeIntegrationNudge({
|
||||
];
|
||||
|
||||
const installText = isExtensionPreInstalled
|
||||
? `If you select Yes, the CLI will have access to your open files and display diffs directly in ${
|
||||
ideName ?? 'your editor'
|
||||
}.`
|
||||
: `If you select Yes, we'll install an extension that allows the CLI to access your open files and display diffs directly in ${
|
||||
ideName ?? 'your editor'
|
||||
}.`;
|
||||
? `If you select Yes, the CLI will have access to your open files and display diffs directly in ${ideName}.`
|
||||
: `If you select Yes, we'll install an extension that allows the CLI to access your open files and display diffs directly in ${ideName}.`;
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -92,7 +88,7 @@ export function IdeIntegrationNudge({
|
||||
<Box marginBottom={1} flexDirection="column">
|
||||
<Text>
|
||||
<Text color={theme.status.warning}>{'> '}</Text>
|
||||
{`Do you want to connect ${ideName ?? 'your editor'} to Gemini CLI?`}
|
||||
{`Do you want to connect ${ideName} to Gemini CLI?`}
|
||||
</Text>
|
||||
<Text color={theme.text.secondary}>{installText}</Text>
|
||||
</Box>
|
||||
|
||||
@@ -44,7 +44,7 @@ export function ApiAuthDialog({
|
||||
|
||||
const buffer = useTextBuffer({
|
||||
initialText: initialApiKey || '',
|
||||
initialCursorOffset: initialApiKey?.length || 0,
|
||||
initialCursorOffset: initialApiKey.length || 0,
|
||||
viewport: {
|
||||
width: viewportWidth,
|
||||
height: 4,
|
||||
|
||||
@@ -68,5 +68,5 @@ async function getIdeClientName(context: CommandContext) {
|
||||
return '';
|
||||
}
|
||||
const ideClient = await IdeClient.getInstance();
|
||||
return ideClient?.getDetectedIdeDisplayName() ?? '';
|
||||
return ideClient.getDetectedIdeDisplayName() ?? '';
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ async function enableAction(
|
||||
const allAgents = agentRegistry.getAllAgentNames();
|
||||
const overrides = settings.merged.agents.overrides;
|
||||
const disabledAgents = Object.keys(overrides).filter(
|
||||
(name) => overrides[name]?.enabled === false,
|
||||
(name) => overrides[name].enabled === false,
|
||||
);
|
||||
|
||||
if (allAgents.includes(agentName) && !disabledAgents.includes(agentName)) {
|
||||
@@ -167,7 +167,7 @@ async function disableAction(
|
||||
const allAgents = agentRegistry.getAllAgentNames();
|
||||
const overrides = settings.merged.agents.overrides;
|
||||
const disabledAgents = Object.keys(overrides).filter(
|
||||
(name) => overrides[name]?.enabled === false,
|
||||
(name) => overrides[name].enabled === false,
|
||||
);
|
||||
|
||||
if (disabledAgents.includes(agentName)) {
|
||||
@@ -271,7 +271,7 @@ function completeAgentsToEnable(context: CommandContext, partialArg: string) {
|
||||
|
||||
const overrides = settings.merged.agents.overrides;
|
||||
const disabledAgents = Object.entries(overrides)
|
||||
.filter(([_, override]) => override?.enabled === false)
|
||||
.filter(([_, override]) => override.enabled === false)
|
||||
.map(([name]) => name);
|
||||
|
||||
return disabledAgents.filter((name) => name.startsWith(partialArg));
|
||||
@@ -291,7 +291,7 @@ function completeAllAgents(context: CommandContext, partialArg: string) {
|
||||
if (!config) return [];
|
||||
|
||||
const agentRegistry = config.getAgentRegistry();
|
||||
const allAgents = agentRegistry?.getAllDiscoveredAgentNames() ?? [];
|
||||
const allAgents = agentRegistry.getAllDiscoveredAgentNames() ?? [];
|
||||
return allAgents.filter((name: string) => name.startsWith(partialArg));
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ const authLogoutCommand: SlashCommand = {
|
||||
undefined,
|
||||
);
|
||||
// Strip thoughts from history instead of clearing completely
|
||||
context.services.config?.getGeminiClient()?.stripThoughtsFromHistory();
|
||||
context.services.config?.getGeminiClient().stripThoughtsFromHistory();
|
||||
// Return logout action to signal explicit state change
|
||||
return {
|
||||
type: 'logout',
|
||||
|
||||
@@ -54,7 +54,7 @@ export const bugCommand: SlashCommand = {
|
||||
const kittyProtocol = terminalCapabilityManager.isKittyProtocolEnabled()
|
||||
? 'Supported'
|
||||
: 'Unsupported';
|
||||
const authType = config?.getContentGeneratorConfig()?.authType || 'Unknown';
|
||||
const authType = config?.getContentGeneratorConfig().authType || 'Unknown';
|
||||
|
||||
let info = `
|
||||
* **CLI Version:** ${cliVersion}
|
||||
@@ -73,13 +73,13 @@ export const bugCommand: SlashCommand = {
|
||||
info += `* **IDE Client:** ${ideClient}\n`;
|
||||
}
|
||||
|
||||
const chat = config?.getGeminiClient()?.getChat();
|
||||
const chat = config?.getGeminiClient().getChat();
|
||||
const history = chat?.getHistory() || [];
|
||||
let historyFileMessage = '';
|
||||
let problemValue = bugDescription;
|
||||
|
||||
if (history.length > INITIAL_HISTORY_LENGTH) {
|
||||
const tempDir = config?.storage?.getProjectTempDir();
|
||||
const tempDir = config?.storage.getProjectTempDir();
|
||||
if (tempDir) {
|
||||
const historyFileName = `bug-report-history-${Date.now()}.json`;
|
||||
const historyFilePath = path.join(tempDir, historyFileName);
|
||||
|
||||
@@ -34,7 +34,7 @@ const getSavedChatTags = async (
|
||||
mtSortDesc: boolean,
|
||||
): Promise<ChatDetail[]> => {
|
||||
const cfg = context.services.config;
|
||||
const geminiDir = cfg?.storage?.getProjectTempDir();
|
||||
const geminiDir = cfg?.storage.getProjectTempDir();
|
||||
if (!geminiDir) {
|
||||
return [];
|
||||
}
|
||||
@@ -123,7 +123,7 @@ const saveCommand: SlashCommand = {
|
||||
}
|
||||
}
|
||||
|
||||
const chat = config?.getGeminiClient()?.getChat();
|
||||
const chat = config?.getGeminiClient().getChat();
|
||||
if (!chat) {
|
||||
return {
|
||||
type: 'message',
|
||||
@@ -134,7 +134,7 @@ const saveCommand: SlashCommand = {
|
||||
|
||||
const history = chat.getHistory();
|
||||
if (history.length > INITIAL_HISTORY_LENGTH) {
|
||||
const authType = config?.getContentGeneratorConfig()?.authType;
|
||||
const authType = config?.getContentGeneratorConfig().authType;
|
||||
await logger.saveCheckpoint({ history, authType }, tag);
|
||||
return {
|
||||
type: 'message',
|
||||
@@ -183,7 +183,7 @@ const resumeCommand: SlashCommand = {
|
||||
};
|
||||
}
|
||||
|
||||
const currentAuthType = config?.getContentGeneratorConfig()?.authType;
|
||||
const currentAuthType = config?.getContentGeneratorConfig().authType;
|
||||
if (
|
||||
checkpoint.authType &&
|
||||
currentAuthType &&
|
||||
@@ -296,7 +296,7 @@ const shareCommand: SlashCommand = {
|
||||
};
|
||||
}
|
||||
|
||||
const chat = context.services.config?.getGeminiClient()?.getChat();
|
||||
const chat = context.services.config?.getGeminiClient().getChat();
|
||||
if (!chat) {
|
||||
return {
|
||||
type: 'message',
|
||||
|
||||
@@ -25,7 +25,7 @@ export const clearCommand: SlashCommand = {
|
||||
const config = context.services.config;
|
||||
const chatRecordingService = context.services.config
|
||||
?.getGeminiClient()
|
||||
?.getChat()
|
||||
.getChat()
|
||||
.getChatRecordingService();
|
||||
|
||||
// Fire SessionEnd hook before clearing
|
||||
|
||||
@@ -43,7 +43,7 @@ export const compressCommand: SlashCommand = {
|
||||
const promptId = `compress-${Date.now()}`;
|
||||
const compressed = await context.services.config
|
||||
?.getGeminiClient()
|
||||
?.tryCompressChat(promptId, true);
|
||||
.tryCompressChat(promptId, true);
|
||||
if (compressed) {
|
||||
ui.addItem(
|
||||
{
|
||||
|
||||
@@ -15,7 +15,7 @@ export const copyCommand: SlashCommand = {
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: true,
|
||||
action: async (context, _args): Promise<SlashCommandActionReturn | void> => {
|
||||
const chat = context.services.config?.getGeminiClient()?.getChat();
|
||||
const chat = context.services.config?.getGeminiClient().getChat();
|
||||
const history = chat?.getHistory();
|
||||
|
||||
// Get the last message from the AI (model role)
|
||||
|
||||
@@ -236,7 +236,7 @@ async function restartAction(
|
||||
if (failures.length < extensionsToRestart.length) {
|
||||
try {
|
||||
await context.services.config?.reloadSkills();
|
||||
await context.services.config?.getAgentRegistry()?.reload();
|
||||
await context.services.config?.getAgentRegistry().reload();
|
||||
} catch (error) {
|
||||
context.ui.addItem({
|
||||
type: MessageType.ERROR,
|
||||
@@ -271,7 +271,7 @@ async function exploreAction(
|
||||
context: CommandContext,
|
||||
): Promise<SlashCommandActionReturn | void> {
|
||||
const settings = context.services.settings.merged;
|
||||
const useRegistryUI = settings.experimental?.extensionRegistry;
|
||||
const useRegistryUI = settings.experimental.extensionRegistry;
|
||||
|
||||
if (useRegistryUI) {
|
||||
const extensionManager = context.services.config?.getExtensionLoader();
|
||||
|
||||
@@ -45,7 +45,7 @@ function getIdeStatusMessage(ideClient: IdeClient): {
|
||||
};
|
||||
default: {
|
||||
let content = `🔴 Disconnected`;
|
||||
if (connection?.details) {
|
||||
if (connection.details) {
|
||||
content += `: ${connection.details}`;
|
||||
}
|
||||
return {
|
||||
@@ -107,7 +107,7 @@ async function getIdeStatusMessageWithFiles(ideClient: IdeClient): Promise<{
|
||||
};
|
||||
default: {
|
||||
let content = `🔴 Disconnected`;
|
||||
if (connection?.details) {
|
||||
if (connection.details) {
|
||||
content += `: ${connection.details}`;
|
||||
}
|
||||
return {
|
||||
|
||||
@@ -139,7 +139,7 @@ const authCommand: SlashCommand = {
|
||||
}
|
||||
// Update the client with the new tools
|
||||
const geminiClient = config.getGeminiClient();
|
||||
if (geminiClient?.isInitialized()) {
|
||||
if (geminiClient.isInitialized()) {
|
||||
await geminiClient.setTools();
|
||||
}
|
||||
|
||||
@@ -361,7 +361,7 @@ const refreshCommand: SlashCommand = {
|
||||
|
||||
// Update the client with the new tools
|
||||
const geminiClient = config.getGeminiClient();
|
||||
if (geminiClient?.isInitialized()) {
|
||||
if (geminiClient.isInitialized()) {
|
||||
await geminiClient.setTools();
|
||||
}
|
||||
|
||||
@@ -419,9 +419,9 @@ async function handleEnableDisable(
|
||||
if (enable) {
|
||||
const settings = loadSettings();
|
||||
const result = await canLoadServer(name, {
|
||||
adminMcpEnabled: settings.merged.admin?.mcp?.enabled ?? true,
|
||||
allowedList: settings.merged.mcp?.allowed,
|
||||
excludedList: settings.merged.mcp?.excluded,
|
||||
adminMcpEnabled: settings.merged.admin.mcp.enabled ?? true,
|
||||
allowedList: settings.merged.mcp.allowed,
|
||||
excludedList: settings.merged.mcp.excluded,
|
||||
});
|
||||
if (
|
||||
!result.allowed &&
|
||||
@@ -465,7 +465,7 @@ async function handleEnableDisable(
|
||||
);
|
||||
await mcpClientManager.restart();
|
||||
}
|
||||
if (config.getGeminiClient()?.isInitialized())
|
||||
if (config.getGeminiClient().isInitialized())
|
||||
await config.getGeminiClient().setTools();
|
||||
context.ui.reloadCommands();
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ async function restoreAction(
|
||||
} else if (action.type === 'load_history' && loadHistory) {
|
||||
loadHistory(action.history);
|
||||
if (action.clientHistory) {
|
||||
config?.getGeminiClient()?.setHistory(action.clientHistory);
|
||||
config?.getGeminiClient().setHistory(action.clientHistory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,7 +227,7 @@ export const setupGithubCommand: SlashCommand = {
|
||||
}
|
||||
|
||||
// Get the latest release tag from GitHub
|
||||
const proxy = context?.services?.config?.getProxy();
|
||||
const proxy = context.services.config?.getProxy();
|
||||
const releaseTag = await getLatestGitHubRelease(proxy);
|
||||
const readmeUrl = `https://github.com/google-github-actions/run-gemini-cli/blob/${releaseTag}/README.md#quick-start`;
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ export const AboutBox: React.FC<AboutBoxProps> = ({
|
||||
<Text color={theme.text.primary}>{cliVersion}</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
{GIT_COMMIT_INFO && !['N/A'].includes(GIT_COMMIT_INFO) && (
|
||||
{!['N/A'].includes(GIT_COMMIT_INFO) && (
|
||||
<Box flexDirection="row">
|
||||
<Box width="35%">
|
||||
<Text bold color={theme.text.link}>
|
||||
|
||||
@@ -161,25 +161,25 @@ function getFieldDefaultFromDefinition(
|
||||
return !definition.experimental; // Experimental agents default to disabled
|
||||
}
|
||||
if (field.key === 'model') {
|
||||
return definition.modelConfig?.model ?? 'inherit';
|
||||
return definition.modelConfig.model ?? 'inherit';
|
||||
}
|
||||
if (field.key === 'temperature') {
|
||||
return definition.modelConfig?.generateContentConfig?.temperature;
|
||||
return definition.modelConfig.generateContentConfig?.temperature;
|
||||
}
|
||||
if (field.key === 'topP') {
|
||||
return definition.modelConfig?.generateContentConfig?.topP;
|
||||
return definition.modelConfig.generateContentConfig?.topP;
|
||||
}
|
||||
if (field.key === 'topK') {
|
||||
return definition.modelConfig?.generateContentConfig?.topK;
|
||||
return definition.modelConfig.generateContentConfig?.topK;
|
||||
}
|
||||
if (field.key === 'maxOutputTokens') {
|
||||
return definition.modelConfig?.generateContentConfig?.maxOutputTokens;
|
||||
return definition.modelConfig.generateContentConfig?.maxOutputTokens;
|
||||
}
|
||||
if (field.key === 'maxTimeMinutes') {
|
||||
return definition.runConfig?.maxTimeMinutes;
|
||||
return definition.runConfig.maxTimeMinutes;
|
||||
}
|
||||
if (field.key === 'maxTurns') {
|
||||
return definition.runConfig?.maxTurns;
|
||||
return definition.runConfig.maxTurns;
|
||||
}
|
||||
|
||||
return field.defaultValue;
|
||||
|
||||
@@ -1083,7 +1083,7 @@ export const AskUserDialog: React.FC<AskUserDialogProps> = ({
|
||||
const currentQuestion = questions[currentQuestionIndex];
|
||||
|
||||
const effectiveQuestion = useMemo(() => {
|
||||
if (currentQuestion?.type === 'yesno') {
|
||||
if (currentQuestion.type === 'yesno') {
|
||||
return {
|
||||
...currentQuestion,
|
||||
options: [
|
||||
|
||||
@@ -13,7 +13,7 @@ export type SpinnerProps = ComponentProps<typeof Spinner>;
|
||||
|
||||
export const CliSpinner = (props: SpinnerProps) => {
|
||||
const settings = useSettings();
|
||||
const shouldShow = settings.merged.ui?.showSpinner !== false;
|
||||
const shouldShow = settings.merged.ui.showSpinner !== false;
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldShow) {
|
||||
|
||||
@@ -79,7 +79,7 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
hasPendingToolConfirmation ||
|
||||
Boolean(uiState.commandConfirmationRequest) ||
|
||||
Boolean(uiState.authConsentRequest) ||
|
||||
(uiState.confirmUpdateExtensionRequests?.length ?? 0) > 0 ||
|
||||
(uiState.confirmUpdateExtensionRequests.length ?? 0) > 0 ||
|
||||
Boolean(uiState.loopDetectionConfirmationRequest) ||
|
||||
Boolean(uiState.quota.proQuotaRequest) ||
|
||||
Boolean(uiState.quota.validationRequest) ||
|
||||
|
||||
@@ -40,7 +40,7 @@ export const DetailedMessagesDisplay: React.FC<
|
||||
if (textWidth <= 0) {
|
||||
return 1;
|
||||
}
|
||||
const lines = Math.ceil((msg.content?.length || 1) / textWidth);
|
||||
const lines = Math.ceil((msg.content.length || 1) / textWidth);
|
||||
return Math.max(1, lines);
|
||||
},
|
||||
[width, messages],
|
||||
|
||||
@@ -255,7 +255,7 @@ export const DialogManager = ({
|
||||
onClose={uiActions.closeAgentConfigDialog}
|
||||
onSave={async () => {
|
||||
// Reload agent registry to pick up changes
|
||||
const agentRegistry = config?.getAgentRegistry();
|
||||
const agentRegistry = config.getAgentRegistry();
|
||||
if (agentRegistry) {
|
||||
await agentRegistry.reload();
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ const DOUBLE_TAB_CLEAN_UI_TOGGLE_WINDOW_MS = 350;
|
||||
* Returns true if a toggle action was performed or hint was shown, false otherwise.
|
||||
*/
|
||||
export function tryTogglePasteExpansion(buffer: TextBuffer): boolean {
|
||||
if (!buffer.pastedContent || Object.keys(buffer.pastedContent).length === 0) {
|
||||
if (Object.keys(buffer.pastedContent).length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -344,13 +344,11 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
const handleSubmitAndClear = useCallback(
|
||||
(submittedValue: string) => {
|
||||
let processedValue = submittedValue;
|
||||
if (buffer.pastedContent) {
|
||||
// Replace placeholders like [Pasted Text: 6 lines] with actual content
|
||||
processedValue = processedValue.replace(
|
||||
PASTED_TEXT_PLACEHOLDER_REGEX,
|
||||
(match) => buffer.pastedContent[match] || match,
|
||||
);
|
||||
}
|
||||
// Replace placeholders like [Pasted Text: 6 lines] with actual content
|
||||
processedValue = processedValue.replace(
|
||||
PASTED_TEXT_PLACEHOLDER_REGEX,
|
||||
(match) => buffer.pastedContent[match] || match,
|
||||
);
|
||||
|
||||
if (shellModeActive) {
|
||||
shellHistory.addCommandToHistory(processedValue);
|
||||
@@ -487,7 +485,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.experimental?.useOSC52Paste) {
|
||||
if (settings.experimental.useOSC52Paste) {
|
||||
stdout.write('\x1b]52;c;?\x07');
|
||||
} else {
|
||||
const textToInsert = await clipboardy.read();
|
||||
@@ -1537,7 +1535,6 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
const absoluteVisualIdx =
|
||||
scrollVisualRow + visualIdxInRenderedSet;
|
||||
const mapEntry = buffer.visualToLogicalMap[absoluteVisualIdx];
|
||||
if (!mapEntry) return null;
|
||||
|
||||
const cursorVisualRow =
|
||||
cursorVisualRowAbsolute - scrollVisualRow;
|
||||
|
||||
@@ -57,7 +57,7 @@ export const LoadingIndicator: React.FC<LoadingIndicatorProps> = ({
|
||||
: currentLoadingPhrase;
|
||||
const hasThoughtIndicator =
|
||||
currentLoadingPhrase !== INTERACTIVE_SHELL_WAITING_PHRASE &&
|
||||
Boolean(thought?.subject?.trim());
|
||||
Boolean(thought?.subject.trim());
|
||||
const thinkingIndicator = hasThoughtIndicator ? '💬 ' : '';
|
||||
|
||||
const cancelAndTimerContent =
|
||||
|
||||
@@ -118,7 +118,7 @@ export const MainContent = () => {
|
||||
isExpandable={true}
|
||||
/>
|
||||
))}
|
||||
{showConfirmationQueue && confirmingTool && (
|
||||
{showConfirmationQueue && (
|
||||
<ToolConfirmationQueue confirmingTool={confirmingTool} />
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@@ -42,7 +42,7 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
|
||||
const preferredModel = config?.getModel() || DEFAULT_GEMINI_MODEL_AUTO;
|
||||
|
||||
const shouldShowPreviewModels = config?.getHasAccessToPreviewModel();
|
||||
const useGemini31 = config?.getGemini31LaunchedSync?.() ?? false;
|
||||
const useGemini31 = config?.getGemini31LaunchedSync() ?? false;
|
||||
const selectedAuthType = settings.merged.security.auth.selectedType;
|
||||
const useCustomToolModel =
|
||||
useGemini31 && selectedAuthType === AuthType.USE_GEMINI;
|
||||
|
||||
@@ -254,7 +254,7 @@ export const ModelStatsDisplay: React.FC<ModelStatsDisplayProps> = ({
|
||||
isSubtle: true,
|
||||
};
|
||||
activeModels.forEach(([name, metrics]) => {
|
||||
const roleMetrics = metrics.roles?.[role];
|
||||
const roleMetrics = metrics.roles[role];
|
||||
if (roleMetrics) {
|
||||
row[name] = getValue(roleMetrics);
|
||||
} else {
|
||||
|
||||
@@ -201,7 +201,7 @@ const findTextMatches = (
|
||||
const lowerContent = m.toLowerCase();
|
||||
let startIndex = 0;
|
||||
|
||||
while (true) {
|
||||
for (;;) {
|
||||
const matchIndex = lowerContent.indexOf(lowerQuery, startIndex);
|
||||
if (matchIndex === -1) break;
|
||||
|
||||
|
||||
@@ -416,7 +416,7 @@ export const StatsDisplay: React.FC<StatsDisplayProps> = ({
|
||||
const computed = computeSessionStats(metrics);
|
||||
const settings = useSettings();
|
||||
const config = useConfig();
|
||||
const useGemini3_1 = config.getGemini31LaunchedSync?.() ?? false;
|
||||
const useGemini3_1 = config.getGemini31LaunchedSync() ?? false;
|
||||
const useCustomToolModel =
|
||||
useGemini3_1 &&
|
||||
config.getContentGeneratorConfig().authType === AuthType.USE_GEMINI;
|
||||
|
||||
@@ -63,8 +63,8 @@ export const ToolConfirmationQueue: React.FC<ToolConfirmationQueueProps> = ({
|
||||
: Math.floor(terminalHeight * 0.5);
|
||||
|
||||
const isRoutine =
|
||||
tool.confirmationDetails?.type === 'ask_user' ||
|
||||
tool.confirmationDetails?.type === 'exit_plan_mode';
|
||||
tool.confirmationDetails.type === 'ask_user' ||
|
||||
tool.confirmationDetails.type === 'exit_plan_mode';
|
||||
const borderColor = isRoutine ? theme.status.success : theme.status.warning;
|
||||
const hideToolIdentity = isRoutine;
|
||||
|
||||
|
||||
@@ -404,7 +404,7 @@ export function BaseSettingsDialog({
|
||||
}
|
||||
|
||||
// Number keys for quick edit on number fields
|
||||
if (currentItem?.type === 'number' && /^[0-9]$/.test(key.sequence)) {
|
||||
if (currentItem.type === 'number' && /^[0-9]$/.test(key.sequence)) {
|
||||
startEditing(currentItem.key, key.sequence);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2041,7 +2041,7 @@ function textBufferReducerLogic(
|
||||
if (visualToLogicalMap[newVisualRow]) {
|
||||
const [logRow, logicalStartCol] = visualToLogicalMap[newVisualRow];
|
||||
const transformedToLogicalMap =
|
||||
visualLayout.transformedToLogicalMaps?.[logRow] ?? [];
|
||||
visualLayout.transformedToLogicalMaps[logRow] ?? [];
|
||||
let transformedStartCol = 0;
|
||||
while (
|
||||
transformedStartCol < transformedToLogicalMap.length &&
|
||||
@@ -3324,12 +3324,11 @@ export function useTextBuffer({
|
||||
|
||||
if (visualToLogicalMap[clampedVisRow]) {
|
||||
const [logRow] = visualToLogicalMap[clampedVisRow];
|
||||
const transformedToLogicalMap =
|
||||
transformedToLogicalMaps?.[logRow] ?? [];
|
||||
const transformedToLogicalMap = transformedToLogicalMaps[logRow] ?? [];
|
||||
|
||||
// Where does this visual line begin within the transformed line?
|
||||
const startColInTransformed =
|
||||
visualToTransformedMap?.[clampedVisRow] ?? 0;
|
||||
visualToTransformedMap[clampedVisRow] ?? 0;
|
||||
|
||||
// Handle wide characters: convert visual X position to character offset
|
||||
const codePoints = toCodePoints(visualLine);
|
||||
@@ -3397,11 +3396,10 @@ export function useTextBuffer({
|
||||
}
|
||||
|
||||
const [logRow] = visualToLogicalMap[clampedVisRow];
|
||||
const transformedToLogicalMap = transformedToLogicalMaps?.[logRow] ?? [];
|
||||
const transformedToLogicalMap = transformedToLogicalMaps[logRow] ?? [];
|
||||
|
||||
// Where does this visual line begin within the transformed line?
|
||||
const startColInTransformed =
|
||||
visualToTransformedMap?.[clampedVisRow] ?? 0;
|
||||
const startColInTransformed = visualToTransformedMap[clampedVisRow] ?? 0;
|
||||
|
||||
// Handle wide characters: convert visual X position to character offset
|
||||
const codePoints = toCodePoints(visualLine);
|
||||
|
||||
@@ -208,7 +208,7 @@ I am triaging a GitHub issue labeled as 'possible-duplicate'. I need to decide i
|
||||
<target_issue>
|
||||
ID: #${issue.number}
|
||||
Title: ${issue.title}
|
||||
Author: ${issue.author?.login}
|
||||
Author: ${issue.author.login}
|
||||
Reactions: ${getReactionCount(issue)}
|
||||
Body:
|
||||
${issue.body.slice(0, 8000)}
|
||||
@@ -221,7 +221,7 @@ ${candidates
|
||||
<candidate>
|
||||
ID: #${c.number}
|
||||
Title: ${c.title}
|
||||
Author: ${c.author?.login}
|
||||
Author: ${c.author.login}
|
||||
Reactions: ${getReactionCount(c)}
|
||||
Body:
|
||||
${c.body.slice(0, 4000)}
|
||||
@@ -815,7 +815,7 @@ Return a JSON object with:
|
||||
</Box>
|
||||
<Text bold>{selectedCandidate.title}</Text>
|
||||
<Text color="gray">
|
||||
Author: {selectedCandidate.author?.login} | 👍{' '}
|
||||
Author: {selectedCandidate.author.login} | 👍{' '}
|
||||
{getReactionCount(selectedCandidate)}
|
||||
</Text>
|
||||
<Text color="gray">{selectedCandidate.url}</Text>
|
||||
@@ -874,7 +874,7 @@ Return a JSON object with:
|
||||
- {currentIssue.title}
|
||||
</Text>
|
||||
<Text color="gray">
|
||||
Author: {currentIssue.author?.login} | 👍{' '}
|
||||
Author: {currentIssue.author.login} | 👍{' '}
|
||||
{getReactionCount(currentIssue)}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
@@ -183,7 +183,7 @@ I am triaging GitHub issues for the Gemini CLI project. I need to identify issue
|
||||
<issue>
|
||||
ID: #${issue.number}
|
||||
Title: ${issue.title}
|
||||
Author: ${issue.author?.login}
|
||||
Author: ${issue.author.login}
|
||||
Labels: ${issue.labels.map((l) => l.name).join(', ')}
|
||||
Body:
|
||||
${issue.body.slice(0, 8000)}
|
||||
@@ -566,7 +566,7 @@ Return a JSON object with:
|
||||
- {currentIssue.title}
|
||||
</Text>
|
||||
<Text color="gray">
|
||||
Author: {currentIssue.author?.login} | 👍{' '}
|
||||
Author: {currentIssue.author.login} | 👍{' '}
|
||||
{getReactionCount(currentIssue)}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
@@ -214,7 +214,7 @@ function bufferBackslashEnter(
|
||||
keypressHandler: KeypressHandler,
|
||||
): KeypressHandler {
|
||||
const bufferer = (function* (): Generator<void, void, Key | null> {
|
||||
while (true) {
|
||||
for (;;) {
|
||||
const key = yield;
|
||||
|
||||
if (key == null) {
|
||||
@@ -260,7 +260,7 @@ function bufferBackslashEnter(
|
||||
*/
|
||||
function bufferPaste(keypressHandler: KeypressHandler): KeypressHandler {
|
||||
const bufferer = (function* (): Generator<void, void, Key | null> {
|
||||
while (true) {
|
||||
for (;;) {
|
||||
let key = yield;
|
||||
|
||||
if (key === null) {
|
||||
@@ -271,7 +271,7 @@ function bufferPaste(keypressHandler: KeypressHandler): KeypressHandler {
|
||||
}
|
||||
|
||||
let buffer = '';
|
||||
while (true) {
|
||||
for (;;) {
|
||||
const timeoutId = setTimeout(() => bufferer.next(null), PASTE_TIMEOUT);
|
||||
key = yield;
|
||||
clearTimeout(timeoutId);
|
||||
@@ -340,7 +340,7 @@ function* emitKeys(
|
||||
const lcAll = process.env['LC_ALL'] || '';
|
||||
const isGreek = lang.startsWith('el') || lcAll.startsWith('el');
|
||||
|
||||
while (true) {
|
||||
for (;;) {
|
||||
let ch = yield;
|
||||
let sequence = ch;
|
||||
let escaped = false;
|
||||
@@ -376,7 +376,7 @@ function* emitKeys(
|
||||
let buffer = '';
|
||||
|
||||
// Read until BEL, `ESC \`, or timeout (empty string)
|
||||
while (true) {
|
||||
for (;;) {
|
||||
const next = yield;
|
||||
if (next === '' || next === '\u0007') {
|
||||
break;
|
||||
|
||||
@@ -113,7 +113,7 @@ describe('SettingsContext', () => {
|
||||
it('should trigger re-renders when settings change (external event)', () => {
|
||||
const { result } = renderHook(() => useSettingsStore(), { wrapper });
|
||||
|
||||
expect(result.current.settings.merged.ui?.theme).toBe('default-theme');
|
||||
expect(result.current.settings.merged.ui.theme).toBe('default-theme');
|
||||
|
||||
const newSnapshot = {
|
||||
...mockSnapshot,
|
||||
@@ -128,7 +128,7 @@ describe('SettingsContext', () => {
|
||||
listeners.forEach((l) => l());
|
||||
});
|
||||
|
||||
expect(result.current.settings.merged.ui?.theme).toBe('new-theme');
|
||||
expect(result.current.settings.merged.ui.theme).toBe('new-theme');
|
||||
});
|
||||
|
||||
it('should call store.setValue when setSetting is called', () => {
|
||||
|
||||
@@ -119,7 +119,7 @@ function categorizeAtCommands(
|
||||
const resourceParts: AtCommandPart[] = [];
|
||||
const fileParts: AtCommandPart[] = [];
|
||||
|
||||
const agentRegistry = config.getAgentRegistry?.();
|
||||
const agentRegistry = config.getAgentRegistry();
|
||||
const resourceRegistry = config.getResourceRegistry();
|
||||
|
||||
for (const part of commandParts) {
|
||||
@@ -129,7 +129,7 @@ function categorizeAtCommands(
|
||||
|
||||
const name = part.content.substring(1);
|
||||
|
||||
if (agentRegistry?.getDefinition(name)) {
|
||||
if (agentRegistry.getDefinition(name)) {
|
||||
agentParts.push(part);
|
||||
} else if (resourceRegistry.findResourceByUri(name)) {
|
||||
resourceParts.push(part);
|
||||
|
||||
@@ -273,10 +273,6 @@ export const useSlashCommandProcessor = (
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
|
||||
const listener = () => {
|
||||
reloadCommands();
|
||||
};
|
||||
@@ -535,7 +531,7 @@ export const useSlashCommandProcessor = (
|
||||
}
|
||||
}
|
||||
case 'load_history': {
|
||||
config?.getGeminiClient()?.setHistory(result.clientHistory);
|
||||
config?.getGeminiClient().setHistory(result.clientHistory);
|
||||
fullCommandContext.ui.clear();
|
||||
result.history.forEach((item, index) => {
|
||||
fullCommandContext.ui.addItem(item, index);
|
||||
@@ -688,7 +684,7 @@ export const useSlashCommandProcessor = (
|
||||
command: resolvedCommandPath[0],
|
||||
subcommand,
|
||||
status: SlashCommandStatus.ERROR,
|
||||
extension_id: commandToExecute?.extensionId,
|
||||
extension_id: commandToExecute.extensionId,
|
||||
});
|
||||
logSlashCommand(config, event);
|
||||
}
|
||||
@@ -706,7 +702,7 @@ export const useSlashCommandProcessor = (
|
||||
command: resolvedCommandPath[0],
|
||||
subcommand,
|
||||
status: SlashCommandStatus.SUCCESS,
|
||||
extension_id: commandToExecute?.extensionId,
|
||||
extension_id: commandToExecute.extensionId,
|
||||
});
|
||||
logSlashCommand(config, event);
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ interface ResourceSuggestionCandidate {
|
||||
function buildResourceCandidates(
|
||||
config?: Config,
|
||||
): ResourceSuggestionCandidate[] {
|
||||
const registry = config?.getResourceRegistry?.();
|
||||
const registry = config?.getResourceRegistry();
|
||||
if (!registry) {
|
||||
return [];
|
||||
}
|
||||
@@ -137,7 +137,7 @@ function buildResourceCandidates(
|
||||
}
|
||||
|
||||
function buildAgentCandidates(config?: Config): Suggestion[] {
|
||||
const registry = config?.getAgentRegistry?.();
|
||||
const registry = config?.getAgentRegistry();
|
||||
if (!registry) {
|
||||
return [];
|
||||
}
|
||||
@@ -232,7 +232,7 @@ export function useAtCompletion(props: UseAtCompletionProps): void {
|
||||
}, [cwd, config]);
|
||||
|
||||
useEffect(() => {
|
||||
const workspaceContext = config?.getWorkspaceContext?.();
|
||||
const workspaceContext = config?.getWorkspaceContext();
|
||||
if (!workspaceContext) return;
|
||||
|
||||
const unsubscribe =
|
||||
@@ -274,9 +274,9 @@ export function useAtCompletion(props: UseAtCompletionProps): void {
|
||||
const initialize = async () => {
|
||||
const currentEpoch = initEpoch.current;
|
||||
try {
|
||||
const directories = config
|
||||
?.getWorkspaceContext?.()
|
||||
?.getDirectories() ?? [cwd];
|
||||
const directories = config?.getWorkspaceContext().getDirectories() ?? [
|
||||
cwd,
|
||||
];
|
||||
|
||||
const initPromises: Array<Promise<void>> = [];
|
||||
|
||||
@@ -296,7 +296,7 @@ export function useAtCompletion(props: UseAtCompletionProps): void {
|
||||
config?.getEnableRecursiveFileSearch() ?? true,
|
||||
enableFuzzySearch:
|
||||
config?.getFileFilteringEnableFuzzySearch() ?? true,
|
||||
maxFiles: config?.getFileFilteringOptions()?.maxFileCount,
|
||||
maxFiles: config?.getFileFilteringOptions().maxFileCount,
|
||||
});
|
||||
|
||||
initPromises.push(
|
||||
@@ -342,7 +342,7 @@ export function useAtCompletion(props: UseAtCompletionProps): void {
|
||||
}, 200);
|
||||
|
||||
const timeoutMs =
|
||||
config?.getFileFilteringOptions()?.searchTimeout ??
|
||||
config?.getFileFilteringOptions().searchTimeout ??
|
||||
DEFAULT_SEARCH_TIMEOUT_MS;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
@@ -358,9 +358,9 @@ export function useAtCompletion(props: UseAtCompletionProps): void {
|
||||
})();
|
||||
|
||||
try {
|
||||
const directories = config
|
||||
?.getWorkspaceContext?.()
|
||||
?.getDirectories() ?? [cwd];
|
||||
const directories = config?.getWorkspaceContext().getDirectories() ?? [
|
||||
cwd,
|
||||
];
|
||||
const cwdRealpath = directories[0];
|
||||
|
||||
const allSearchPromises = [...fileSearchMap.current.entries()].map(
|
||||
|
||||
@@ -41,13 +41,13 @@ export const useFocus = (): {
|
||||
};
|
||||
|
||||
// Enable focus reporting
|
||||
stdout?.write(ENABLE_FOCUS_REPORTING);
|
||||
stdin?.on('data', handleData);
|
||||
stdout.write(ENABLE_FOCUS_REPORTING);
|
||||
stdin.on('data', handleData);
|
||||
|
||||
return () => {
|
||||
// Disable focus reporting on cleanup
|
||||
stdout?.write(DISABLE_FOCUS_REPORTING);
|
||||
stdin?.removeListener('data', handleData);
|
||||
stdout.write(DISABLE_FOCUS_REPORTING);
|
||||
stdin.removeListener('data', handleData);
|
||||
};
|
||||
}, [stdin, stdout]);
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ export const useFolderTrust = (
|
||||
const [isRestarting, setIsRestarting] = useState(false);
|
||||
const startupMessageSent = useRef(false);
|
||||
|
||||
const folderTrust = settings.merged.security.folderTrust.enabled ?? true;
|
||||
const folderTrust = settings.merged.security.folderTrust.enabled;
|
||||
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
@@ -68,13 +68,11 @@ export const useFolderTrust = (
|
||||
};
|
||||
|
||||
if (isHeadlessMode()) {
|
||||
if (isMounted) {
|
||||
setIsTrusted(trusted);
|
||||
setIsFolderTrustDialogOpen(false);
|
||||
onTrustChange(true);
|
||||
showUntrustedMessage();
|
||||
}
|
||||
} else if (isMounted) {
|
||||
setIsTrusted(trusted);
|
||||
setIsFolderTrustDialogOpen(false);
|
||||
onTrustChange(true);
|
||||
showUntrustedMessage();
|
||||
} else {
|
||||
setIsTrusted(trusted);
|
||||
setIsFolderTrustDialogOpen(trusted === undefined);
|
||||
onTrustChange(trusted);
|
||||
@@ -95,7 +93,6 @@ export const useFolderTrust = (
|
||||
};
|
||||
|
||||
const trustLevel = trustLevelMap[choice];
|
||||
if (!trustLevel) return;
|
||||
|
||||
const cwd = process.cwd();
|
||||
const trustedFolders = loadTrustedFolders();
|
||||
|
||||
@@ -968,7 +968,7 @@ export const useGeminiStream = (
|
||||
type: MessageType.ERROR,
|
||||
text: parseAndFormatApiError(
|
||||
eventValue.error,
|
||||
config.getContentGeneratorConfig()?.authType,
|
||||
config.getContentGeneratorConfig().authType,
|
||||
undefined,
|
||||
config.getModel(),
|
||||
DEFAULT_GEMINI_FLASH_MODEL,
|
||||
@@ -1407,7 +1407,7 @@ export const useGeminiStream = (
|
||||
new UserPromptEvent(
|
||||
promptText.length,
|
||||
prompt_id!,
|
||||
config.getContentGeneratorConfig()?.authType,
|
||||
config.getContentGeneratorConfig().authType,
|
||||
promptText,
|
||||
),
|
||||
);
|
||||
@@ -1499,7 +1499,7 @@ export const useGeminiStream = (
|
||||
type: MessageType.ERROR,
|
||||
text: parseAndFormatApiError(
|
||||
getErrorMessage(error) || 'Unknown error',
|
||||
config.getContentGeneratorConfig()?.authType,
|
||||
config.getContentGeneratorConfig().authType,
|
||||
undefined,
|
||||
config.getModel(),
|
||||
DEFAULT_GEMINI_FLASH_MODEL,
|
||||
@@ -1625,7 +1625,7 @@ export const useGeminiStream = (
|
||||
| TrackedCompletedToolCall
|
||||
| TrackedCancelledToolCall;
|
||||
return (
|
||||
completedOrCancelledCall.response?.responseParts !== undefined
|
||||
completedOrCancelledCall.response.responseParts !== undefined
|
||||
);
|
||||
}
|
||||
return false;
|
||||
@@ -1653,7 +1653,7 @@ export const useGeminiStream = (
|
||||
const isShell = t.request.name === 'run_shell_command';
|
||||
// Access result from the tracked tool call response
|
||||
const response = t.response as ToolResponseWithParts;
|
||||
const rawData = response?.data;
|
||||
const rawData = response.data;
|
||||
const data = isShellToolData(rawData) ? rawData : undefined;
|
||||
|
||||
// Use data.pid for shell commands moved to the background.
|
||||
@@ -1661,9 +1661,9 @@ export const useGeminiStream = (
|
||||
|
||||
if (isShell && pid) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
const command = (data?.['command'] as string) ?? 'shell';
|
||||
const command = (data['command'] as string) ?? 'shell';
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
const initialOutput = (data?.['initialOutput'] as string) ?? '';
|
||||
const initialOutput = (data['initialOutput'] as string) ?? '';
|
||||
|
||||
registerBackgroundShell(pid, command, initialOutput);
|
||||
}
|
||||
|
||||
@@ -86,21 +86,21 @@ export function useHistory({
|
||||
switch (itemData.type) {
|
||||
case 'compression':
|
||||
case 'info':
|
||||
chatRecordingService?.recordMessage({
|
||||
chatRecordingService.recordMessage({
|
||||
model: undefined,
|
||||
type: 'info',
|
||||
content: itemData.text ?? '',
|
||||
});
|
||||
break;
|
||||
case 'warning':
|
||||
chatRecordingService?.recordMessage({
|
||||
chatRecordingService.recordMessage({
|
||||
model: undefined,
|
||||
type: 'warning',
|
||||
content: itemData.text ?? '',
|
||||
});
|
||||
break;
|
||||
case 'error':
|
||||
chatRecordingService?.recordMessage({
|
||||
chatRecordingService.recordMessage({
|
||||
model: undefined,
|
||||
type: 'error',
|
||||
content: itemData.text ?? '',
|
||||
|
||||
@@ -138,7 +138,7 @@ export function useQuotaAndFallback({
|
||||
: null,
|
||||
`/stats model for usage details`,
|
||||
`/model to switch models.`,
|
||||
contentGeneratorConfig?.authType === AuthType.LOGIN_WITH_GOOGLE
|
||||
contentGeneratorConfig.authType === AuthType.LOGIN_WITH_GOOGLE
|
||||
? `/auth to switch to API key.`
|
||||
: null,
|
||||
].filter(Boolean);
|
||||
@@ -194,7 +194,7 @@ export function useQuotaAndFallback({
|
||||
message,
|
||||
isTerminalQuotaError,
|
||||
isModelNotFoundError,
|
||||
authType: contentGeneratorConfig?.authType,
|
||||
authType: contentGeneratorConfig.authType,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
@@ -104,7 +104,7 @@ export const useSessionBrowser = (
|
||||
try {
|
||||
const chatRecordingService = config
|
||||
.getGeminiClient()
|
||||
?.getChatRecordingService();
|
||||
.getChatRecordingService();
|
||||
if (chatRecordingService) {
|
||||
chatRecordingService.deleteSession(session.file);
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ export function useSessionResume({
|
||||
}
|
||||
|
||||
// Give the history to the Gemini client.
|
||||
await config.getGeminiClient()?.resumeChat(clientHistory, resumedData);
|
||||
await config.getGeminiClient().resumeChat(clientHistory, resumedData);
|
||||
} catch (error) {
|
||||
coreEvents.emitFeedback(
|
||||
'error',
|
||||
|
||||
@@ -55,8 +55,7 @@ export const useTurnActivityMonitor = (
|
||||
pendingToolCalls.some((tc) => {
|
||||
if (tc.request.name !== 'run_shell_command') return false;
|
||||
|
||||
const command =
|
||||
(tc.request.args as { command?: string })?.command || '';
|
||||
const command = (tc.request.args as { command?: string }).command || '';
|
||||
return hasRedirection(command);
|
||||
}),
|
||||
[pendingToolCalls],
|
||||
|
||||
@@ -22,7 +22,7 @@ const PrivacyNoticeText = ({
|
||||
config: Config;
|
||||
onExit: () => void;
|
||||
}) => {
|
||||
const authType = config.getContentGeneratorConfig()?.authType;
|
||||
const authType = config.getContentGeneratorConfig().authType;
|
||||
|
||||
switch (authType) {
|
||||
case AuthType.USE_GEMINI:
|
||||
|
||||
@@ -353,7 +353,7 @@ export class Theme {
|
||||
this._colorMap = Object.freeze(this._buildColorMap(rawMappings)); // Build and freeze the map
|
||||
|
||||
// Determine the default foreground color
|
||||
const rawDefaultColor = rawMappings['hljs']?.color;
|
||||
const rawDefaultColor = rawMappings['hljs'].color;
|
||||
this.defaultColor =
|
||||
(rawDefaultColor ? Theme._resolveColor(rawDefaultColor) : undefined) ??
|
||||
''; // Default to empty string if not found or resolvable
|
||||
@@ -394,7 +394,7 @@ export class Theme {
|
||||
}
|
||||
|
||||
const style = hljsTheme[key];
|
||||
if (style?.color) {
|
||||
if (style.color) {
|
||||
const resolvedColor = Theme._resolveColor(style.color);
|
||||
if (resolvedColor !== undefined) {
|
||||
// Use the original key from the hljsTheme (e.g., 'hljs-keyword')
|
||||
|
||||
@@ -41,7 +41,7 @@ function renderHastNode(
|
||||
if (node.type === 'element') {
|
||||
const nodeClasses: string[] =
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
(node.properties?.['className'] as string[]) || [];
|
||||
(node.properties['className'] as string[]) || [];
|
||||
let elementColor: string | undefined = undefined;
|
||||
|
||||
// Find color defined specifically for this element's class
|
||||
@@ -59,7 +59,7 @@ function renderHastNode(
|
||||
|
||||
// Recursively render children, passing the determined color down
|
||||
// Ensure child type matches expected HAST structure (ElementContent is common)
|
||||
const children = node.children?.map(
|
||||
const children = node.children.map(
|
||||
(child: ElementContent, index: number) => (
|
||||
<React.Fragment key={index}>
|
||||
{renderHastNode(child, theme, colorToPassDown)}
|
||||
@@ -81,7 +81,7 @@ function renderHastNode(
|
||||
|
||||
// Pass down the initial inheritedColor (likely undefined from the top call)
|
||||
// Ensure child type matches expected HAST structure (RootContent is common)
|
||||
return node.children?.map((child: RootContent, index: number) => (
|
||||
return node.children.map((child: RootContent, index: number) => (
|
||||
<React.Fragment key={index}>
|
||||
{renderHastNode(child, theme, inheritedColor)}
|
||||
</React.Fragment>
|
||||
|
||||
@@ -124,18 +124,18 @@ const getStdioTty = (): TtyTarget => {
|
||||
// On Windows, prioritize stdout to prevent shell-specific formatting (e.g., PowerShell's
|
||||
// red stderr) from corrupting the raw escape sequence payload.
|
||||
if (process.platform === 'win32') {
|
||||
if (process.stdout?.isTTY)
|
||||
if (process.stdout.isTTY)
|
||||
return { stream: process.stdout, closeAfter: false };
|
||||
if (process.stderr?.isTTY)
|
||||
if (process.stderr.isTTY)
|
||||
return { stream: process.stderr, closeAfter: false };
|
||||
return null;
|
||||
}
|
||||
|
||||
// On non-Windows platforms, prioritize stderr to avoid polluting stdout,
|
||||
// preserving it for potential redirection or piping.
|
||||
if (process.stderr?.isTTY)
|
||||
if (process.stderr.isTTY)
|
||||
return { stream: process.stderr, closeAfter: false };
|
||||
if (process.stdout?.isTTY)
|
||||
if (process.stdout.isTTY)
|
||||
return { stream: process.stdout, closeAfter: false };
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -11,5 +11,5 @@ export type InlineThinkingMode = 'off' | 'full';
|
||||
export function getInlineThinkingMode(
|
||||
settings: LoadedSettings,
|
||||
): InlineThinkingMode {
|
||||
return settings.merged.ui?.inlineThinkingMode ?? 'off';
|
||||
return settings.merged.ui.inlineThinkingMode ?? 'off';
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ const TERMINAL_CLEANUP_SEQUENCE = '\x1b[<u\x1b[>4;0m\x1b[?2004l';
|
||||
|
||||
export function cleanupTerminalOnExit() {
|
||||
try {
|
||||
if (process.stdout?.fd !== undefined) {
|
||||
if (process.stdout.fd !== undefined) {
|
||||
fs.writeSync(process.stdout.fd, TERMINAL_CLEANUP_SEQUENCE);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -78,10 +78,8 @@ const TERMINAL_DATA: Record<SupportedTerminal, TerminalData> = {
|
||||
/**
|
||||
* Maps a supported terminal ID to its display name and config folder name.
|
||||
*/
|
||||
function getSupportedTerminalData(
|
||||
terminal: SupportedTerminal,
|
||||
): TerminalData | null {
|
||||
return TERMINAL_DATA[terminal] || null;
|
||||
function getSupportedTerminalData(terminal: SupportedTerminal): TerminalData {
|
||||
return TERMINAL_DATA[terminal];
|
||||
}
|
||||
|
||||
type Keybinding = {
|
||||
|
||||
@@ -340,7 +340,7 @@ export class ActivityLogger extends EventEmitter {
|
||||
|
||||
const readStream = async () => {
|
||||
try {
|
||||
while (true) {
|
||||
for (;;) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ export async function runExitCleanup() {
|
||||
}
|
||||
|
||||
async function drainStdin() {
|
||||
if (!process.stdin?.isTTY) return;
|
||||
if (!process.stdin.isTTY) return;
|
||||
// Resume stdin and attach a no-op listener to drain the buffer.
|
||||
// We use removeAllListeners to ensure we don't trigger other handlers.
|
||||
process.stdin
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
*/
|
||||
export function resolveEnvVarsInString(
|
||||
value: string,
|
||||
customEnv?: Record<string, string>,
|
||||
customEnv?: Record<string, string | undefined>,
|
||||
): string {
|
||||
const envVarRegex = /\$(?:(\w+)|{([^}]+)})/g; // Find $VAR_NAME or ${VAR_NAME}
|
||||
return value.replace(envVarRegex, (match, varName1, varName2) => {
|
||||
@@ -56,7 +56,7 @@ export function resolveEnvVarsInString(
|
||||
*/
|
||||
export function resolveEnvVarsInObject<T>(
|
||||
obj: T,
|
||||
customEnv?: Record<string, string>,
|
||||
customEnv?: Record<string, string | undefined>,
|
||||
): T {
|
||||
return resolveEnvVarsInObjectInternal(obj, new WeakSet(), customEnv);
|
||||
}
|
||||
@@ -71,7 +71,7 @@ export function resolveEnvVarsInObject<T>(
|
||||
function resolveEnvVarsInObjectInternal<T>(
|
||||
obj: T,
|
||||
visited: WeakSet<object>,
|
||||
customEnv?: Record<string, string>,
|
||||
customEnv?: Record<string, string | undefined>,
|
||||
): T {
|
||||
if (
|
||||
obj === null ||
|
||||
|
||||
@@ -75,7 +75,7 @@ export function handleError(
|
||||
): never {
|
||||
const errorMessage = parseAndFormatApiError(
|
||||
error,
|
||||
config.getContentGeneratorConfig()?.authType,
|
||||
config.getContentGeneratorConfig().authType,
|
||||
);
|
||||
|
||||
if (config.getOutputFormat() === OutputFormat.STREAM_JSON) {
|
||||
|
||||
@@ -43,7 +43,7 @@ export function getInstallationInfo(
|
||||
try {
|
||||
// Normalize path separators to forward slashes for consistent matching.
|
||||
const realPath = fs.realpathSync(cliPath).replace(/\\/g, '/');
|
||||
const normalizedProjectRoot = projectRoot?.replace(/\\/g, '/');
|
||||
const normalizedProjectRoot = projectRoot.replace(/\\/g, '/');
|
||||
const isGit = isGitRepository(process.cwd());
|
||||
|
||||
// Check for local git clone first
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
} from '@google/gemini-cli-core';
|
||||
|
||||
export async function relaunchOnExitCode(runner: () => Promise<number>) {
|
||||
while (true) {
|
||||
for (;;) {
|
||||
try {
|
||||
const exitCode = await runner();
|
||||
|
||||
|
||||
@@ -195,8 +195,8 @@ export async function start_sandbox(
|
||||
stdio: 'inherit',
|
||||
});
|
||||
return await new Promise((resolve, reject) => {
|
||||
sandboxProcess?.on('error', reject);
|
||||
sandboxProcess?.on('close', (code) => {
|
||||
sandboxProcess.on('error', reject);
|
||||
sandboxProcess.on('close', (code) => {
|
||||
process.stdin.resume();
|
||||
resolve(code ?? 1);
|
||||
});
|
||||
@@ -707,7 +707,7 @@ export async function start_sandbox(
|
||||
reject(err);
|
||||
});
|
||||
|
||||
sandboxProcess?.on('close', (code, signal) => {
|
||||
sandboxProcess.on('close', (code, signal) => {
|
||||
process.stdin.resume();
|
||||
if (code !== 0 && code !== null) {
|
||||
debugLogger.log(
|
||||
|
||||
@@ -75,11 +75,11 @@ export function getSettingDefinition(
|
||||
}
|
||||
|
||||
export function requiresRestart(key: string): boolean {
|
||||
return getFlattenedSchema()[key]?.requiresRestart ?? false;
|
||||
return getFlattenedSchema()[key].requiresRestart ?? false;
|
||||
}
|
||||
|
||||
export function getDefaultValue(key: string): SettingsValue {
|
||||
return getFlattenedSchema()[key]?.default;
|
||||
return getFlattenedSchema()[key].default;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -208,11 +208,11 @@ export function isValidSettingKey(key: string): boolean {
|
||||
}
|
||||
|
||||
export function getSettingCategory(key: string): string | undefined {
|
||||
return getFlattenedSchema()[key]?.category;
|
||||
return getFlattenedSchema()[key].category;
|
||||
}
|
||||
|
||||
export function shouldShowInDialog(key: string): boolean {
|
||||
return getFlattenedSchema()[key]?.showInDialog ?? true; // Default to true for backward compatibility
|
||||
return getFlattenedSchema()[key].showInDialog ?? true; // Default to true for backward compatibility
|
||||
}
|
||||
|
||||
export function getDialogSettingKeys(): string[] {
|
||||
@@ -285,7 +285,7 @@ export function getDisplayValue(
|
||||
let valueString = String(value);
|
||||
|
||||
if (definition?.type === 'enum' && definition.options) {
|
||||
const option = definition.options?.find((option) => option.value === value);
|
||||
const option = definition.options.find((option) => option.value === value);
|
||||
valueString = option?.label ?? `${value}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -1231,7 +1231,7 @@ describe('Session', () => {
|
||||
streamStarted(true);
|
||||
const reader = stream.getReader();
|
||||
try {
|
||||
while (true) {
|
||||
for (;;) {
|
||||
process.stdout.write('TEST: waiting for read\n');
|
||||
const { done, value } = await reader.read();
|
||||
process.stdout.write(`TEST: read returned done=${done}\n`);
|
||||
|
||||
@@ -657,11 +657,11 @@ export class Session {
|
||||
try {
|
||||
const model = resolveModel(
|
||||
this.config.getModel(),
|
||||
(await this.config.getGemini31Launched?.()) ?? false,
|
||||
(await this.config.getGemini31Launched()) ?? false,
|
||||
);
|
||||
const responseStream = await chat.sendMessageStream(
|
||||
{ model },
|
||||
nextMessage?.parts ?? [],
|
||||
nextMessage.parts ?? [],
|
||||
promptId,
|
||||
pendingSend.signal,
|
||||
LlmRole.MAIN,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user