Compare commits

..

1 Commits

Author SHA1 Message Date
Dmitry Lyalin 50fda21093 feat: add experimental in-progress steering hints 2026-02-10 20:35:57 -05:00
65 changed files with 1979 additions and 586 deletions
+1 -1
View File
@@ -25,7 +25,7 @@ You are an expert at fixing behavioral evaluations.
the same scenario. We don't want to lose test fidelity by making the prompts too
direct (i.e.: easy).
- Your primary mechanism for improving the agent's behavior is to make changes to
tool instructions, system prompt (snippets.ts), and/or modules that contribute to the prompt.
tool instructions, prompt.ts, and/or modules that contribute to the prompt.
- If prompt and description changes are unsuccessful, use logs and debugging to
confirm that everything is working as expected.
- If unable to fix the test, you can make recommendations for architecture changes
+1 -1
View File
@@ -59,7 +59,7 @@ Other ways to start in Plan Mode:
You can enter Plan Mode in three ways:
1. **Keyboard Shortcut:** Press `Shift+Tab` to cycle through approval modes
(`Default` -> `Auto-Edit` -> `Plan`).
(`Default` -> `Plan` -> `Auto-Edit`).
2. **Command:** Type `/plan` in the input box.
3. **Natural Language:** Ask the agent to "start a plan for...".
+12 -1
View File
@@ -739,10 +739,21 @@ The MCP integration tracks several states:
cautiously and only for servers you completely control
- **Access tokens:** Be security-aware when configuring environment variables
containing API keys or tokens
- **Environment variable redaction:** By default, the Gemini CLI redacts
sensitive environment variables (such as `GEMINI_API_KEY`, `GOOGLE_API_KEY`,
and variables matching patterns like `*TOKEN*`, `*SECRET*`, `*PASSWORD*`) when
spawning MCP servers using the `stdio` transport. This prevents unintended
exposure of your credentials to third-party servers.
- **Explicit environment variables:** If you need to pass a specific environment
variable to an MCP server, you should define it explicitly in the `env`
property of the server configuration in `settings.json`.
- **Sandbox compatibility:** When using sandboxing, ensure MCP servers are
available within the sandbox environment
available within the sandbox environment.
- **Private data:** Using broadly scoped personal access tokens can lead to
information leakage between repositories.
- **Untrusted servers:** Be extremely cautious when adding MCP servers from
untrusted or third-party sources. Malicious servers could attempt to
exfiltrate data or perform unauthorized actions through the tools they expose.
### Performance and resource management
-110
View File
@@ -1,110 +0,0 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, expect } from 'vitest';
import { evalTest } from './test-helper.js';
describe('Edits location eval', () => {
/**
* Ensure that Gemini CLI always updates existing test files, if present,
* instead of creating a new one.
*/
evalTest('USUALLY_PASSES', {
name: 'should update existing test file instead of creating a new one',
files: {
'package.json': JSON.stringify(
{
name: 'test-location-repro',
version: '1.0.0',
scripts: {
test: 'vitest run',
},
devDependencies: {
vitest: '^1.0.0',
typescript: '^5.0.0',
},
},
null,
2,
),
'src/math.ts': `
export function add(a: number, b: number): number {
return a + b;
}
export function subtract(a: number, b: number): number {
return a - b;
}
export function multiply(a: number, b: number): number {
return a + b;
}
`,
'src/math.test.ts': `
import { expect, test } from 'vitest';
import { add, subtract } from './math';
test('add adds two numbers', () => {
expect(add(2, 3)).toBe(5);
});
test('subtract subtracts two numbers', () => {
expect(subtract(5, 3)).toBe(2);
});
`,
'src/utils.ts': `
export function capitalize(s: string): string {
return s.charAt(0).toUpperCase() + s.slice(1);
}
`,
'src/utils.test.ts': `
import { expect, test } from 'vitest';
import { capitalize } from './utils';
test('capitalize capitalizes the first letter', () => {
expect(capitalize('hello')).toBe('Hello');
});
`,
},
prompt: 'Fix the bug in src/math.ts. Do not run the code.',
timeout: 180000,
assert: async (rig) => {
const toolLogs = rig.readToolLogs();
const replaceCalls = toolLogs.filter(
(t) => t.toolRequest.name === 'replace',
);
const writeFileCalls = toolLogs.filter(
(t) => t.toolRequest.name === 'write_file',
);
expect(replaceCalls.length).toBeGreaterThan(0);
expect(
writeFileCalls.some((file) =>
file.toolRequest.args.includes('.test.ts'),
),
).toBe(false);
const targetFiles = replaceCalls.map((t) => {
try {
return JSON.parse(t.toolRequest.args).file_path;
} catch {
return null;
}
});
console.log('DEBUG: targetFiles', targetFiles);
expect(
new Set(targetFiles).size,
'Expected only two files changed',
).greaterThanOrEqual(2);
expect(targetFiles.some((f) => f?.endsWith('src/math.ts'))).toBe(true);
expect(targetFiles.some((f) => f?.endsWith('src/math.test.ts'))).toBe(
true,
);
},
});
});
+1 -22
View File
@@ -11,7 +11,6 @@ import * as os from 'node:os';
import { RipGrepTool } from '../packages/core/src/tools/ripGrep.js';
import { Config } from '../packages/core/src/config/config.js';
import { WorkspaceContext } from '../packages/core/src/utils/workspaceContext.js';
import { createMockMessageBus } from '../packages/core/src/test-utils/mock-message-bus.js';
// Mock Config to provide necessary context
class MockConfig {
@@ -67,7 +66,7 @@ describe('ripgrep-real-direct', () => {
await fs.writeFile(path.join(tempDir, 'file3.txt'), 'goodbye moon\n');
const config = new MockConfig(tempDir) as unknown as Config;
tool = new RipGrepTool(config, createMockMessageBus());
tool = new RipGrepTool(config);
});
afterAll(async () => {
@@ -109,24 +108,4 @@ describe('ripgrep-real-direct', () => {
expect(result.llmContent).toContain('script.js');
expect(result.llmContent).not.toContain('file1.txt');
});
it('should support context parameters', async () => {
// Create a file with multiple lines
await fs.writeFile(
path.join(tempDir, 'context.txt'),
'line1\nline2\nline3 match\nline4\nline5\n',
);
const invocation = tool.build({
pattern: 'match',
context: 1,
});
const result = await invocation.execute(new AbortController().signal);
expect(result.llmContent).toContain('Found 1 match');
expect(result.llmContent).toContain('context.txt');
expect(result.llmContent).toContain('L2- line2');
expect(result.llmContent).toContain('L3: line3 match');
expect(result.llmContent).toContain('L4- line4');
});
});
+867 -28
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli",
"version": "0.29.1",
"version": "0.29.0-nightly.20260203.71f46f116",
"engines": {
"node": ">=20.0.0"
},
@@ -14,7 +14,7 @@
"url": "git+https://github.com/google-gemini/gemini-cli.git"
},
"config": {
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.29.1"
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.29.0-nightly.20260203.71f46f116"
},
"scripts": {
"start": "cross-env NODE_ENV=development node scripts/start.js",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli-a2a-server",
"version": "0.29.1",
"version": "0.29.0-nightly.20260203.71f46f116",
"description": "Gemini CLI A2A Server",
"repository": {
"type": "git",
+13 -6
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli",
"version": "0.29.1",
"version": "0.29.0-nightly.20260203.71f46f116",
"description": "Gemini CLI",
"license": "Apache-2.0",
"repository": {
@@ -26,7 +26,7 @@
"dist"
],
"config": {
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.29.1"
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.29.0-nightly.20260203.71f46f116"
},
"dependencies": {
"@agentclientprotocol/sdk": "^0.12.0",
@@ -34,10 +34,8 @@
"@google/genai": "1.30.0",
"@iarna/toml": "^2.2.5",
"@modelcontextprotocol/sdk": "^1.23.0",
"ansi-escapes": "^7.3.0",
"@types/update-notifier": "^6.0.8",
"ansi-regex": "^6.2.2",
"chalk": "^4.1.2",
"cli-spinners": "^2.9.2",
"clipboardy": "^5.0.0",
"color-convert": "^2.0.1",
"command-exists": "^1.2.9",
@@ -58,6 +56,7 @@
"prompts": "^2.4.2",
"proper-lockfile": "^4.1.2",
"react": "^19.2.0",
"read-package-up": "^11.0.0",
"shell-quote": "^1.8.3",
"simple-git": "^3.28.0",
"string-width": "^8.1.0",
@@ -66,21 +65,29 @@
"tar": "^7.5.2",
"tinygradient": "^1.1.5",
"undici": "^7.10.0",
"wrap-ansi": "9.0.2",
"ws": "^8.16.0",
"yargs": "^17.7.2",
"zod": "^3.23.8"
},
"devDependencies": {
"@babel/runtime": "^7.27.6",
"@google/gemini-cli-test-utils": "file:../test-utils",
"@types/archiver": "^6.0.3",
"@types/command-exists": "^1.2.3",
"@types/hast": "^3.0.4",
"@types/dotenv": "^6.1.1",
"@types/node": "^20.11.24",
"@types/react": "^19.2.0",
"@types/react-dom": "^19.2.0",
"@types/semver": "^7.7.0",
"@types/shell-quote": "^1.7.5",
"@types/tar": "^6.1.13",
"@types/ws": "^8.5.10",
"@types/yargs": "^17.0.32",
"archiver": "^7.0.1",
"ink-testing-library": "^4.0.0",
"pretty-format": "^30.0.2",
"react-dom": "^19.2.0",
"typescript": "^5.3.3",
"vitest": "^3.1.1"
},
+7
View File
@@ -128,6 +128,13 @@ async function addMcpServer(
settings.setValue(settingsScope, 'mcpServers', mcpServers);
if (transport === 'stdio') {
debugLogger.warn(
'Security Warning: Running MCP servers with stdio transport can expose inherited environment variables. ' +
'While the Gemini CLI redacts common API keys and secrets by default, you should only run servers from trusted sources.',
);
}
if (isExistingServer) {
debugLogger.log(`MCP server "${name}" updated in ${scope} settings.`);
} else {
+3 -6
View File
@@ -445,11 +445,7 @@ export async function loadCliConfig(
process.env['VITEST'] === 'true'
? false
: (settings.security?.folderTrust?.enabled ?? false);
const trustedFolder =
isWorkspaceTrusted(settings, cwd, undefined, {
prompt: argv.prompt,
query: argv.query,
})?.isTrusted ?? false;
const trustedFolder = isWorkspaceTrusted(settings, cwd)?.isTrusted ?? false;
// Set the context filename in the server's memoryTool module BEFORE loading memory
// TODO(b/343434939): This is a bit of a hack. The contextFileName should ideally be passed
@@ -606,7 +602,8 @@ export async function loadCliConfig(
const interactive =
!!argv.promptInteractive ||
!!argv.experimentalAcp ||
(!isHeadlessMode({ prompt: argv.prompt, query: argv.query }) &&
(!isHeadlessMode({ prompt: argv.prompt }) &&
!argv.query &&
!argv.isCommand);
const allowedTools = argv.allowedTools || settings.tools?.allowed || [];
@@ -449,14 +449,6 @@ describe('Trusted Folders', () => {
false,
);
});
it('should return true for isPathTrusted when isHeadlessMode is true', async () => {
const geminiCore = await import('@google/gemini-cli-core');
vi.spyOn(geminiCore, 'isHeadlessMode').mockReturnValue(true);
const folders = loadTrustedFolders();
expect(folders.isPathTrusted('/any-untrusted-path')).toBe(true);
});
});
describe('Trusted Folders Caching', () => {
+3 -18
View File
@@ -17,7 +17,6 @@ import {
homedir,
isHeadlessMode,
coreEvents,
type HeadlessModeOptions,
} from '@google/gemini-cli-core';
import type { Settings } from './settings.js';
import stripJsonComments from 'strip-json-comments';
@@ -129,11 +128,7 @@ export class LoadedTrustedFolders {
isPathTrusted(
location: string,
config?: Record<string, TrustLevel>,
headlessOptions?: HeadlessModeOptions,
): boolean | undefined {
if (isHeadlessMode(headlessOptions)) {
return true;
}
const configToUse = config ?? this.user.config;
// Resolve location to its realpath for canonical comparison
@@ -338,7 +333,6 @@ export function isFolderTrustEnabled(settings: Settings): boolean {
function getWorkspaceTrustFromLocalConfig(
workspaceDir: string,
trustConfig?: Record<string, TrustLevel>,
headlessOptions?: HeadlessModeOptions,
): TrustResult {
const folders = loadTrustedFolders();
const configToUse = trustConfig ?? folders.user.config;
@@ -352,11 +346,7 @@ function getWorkspaceTrustFromLocalConfig(
);
}
const isTrusted = folders.isPathTrusted(
workspaceDir,
configToUse,
headlessOptions,
);
const isTrusted = folders.isPathTrusted(workspaceDir, configToUse);
return {
isTrusted,
source: isTrusted !== undefined ? 'file' : undefined,
@@ -367,9 +357,8 @@ export function isWorkspaceTrusted(
settings: Settings,
workspaceDir: string = process.cwd(),
trustConfig?: Record<string, TrustLevel>,
headlessOptions?: HeadlessModeOptions,
): TrustResult {
if (isHeadlessMode(headlessOptions)) {
if (isHeadlessMode()) {
return { isTrusted: true, source: undefined };
}
@@ -383,9 +372,5 @@ export function isWorkspaceTrusted(
}
// Fall back to the local user configuration
return getWorkspaceTrustFromLocalConfig(
workspaceDir,
trustConfig,
headlessOptions,
);
return getWorkspaceTrustFromLocalConfig(workspaceDir, trustConfig);
}
+6
View File
@@ -160,6 +160,8 @@ const baseMockUiState = {
proQuotaRequest: null,
validationRequest: null,
},
hintMode: false,
hintBuffer: '',
};
export const mockAppState: AppState = {
@@ -209,6 +211,10 @@ const mockUIActions: UIActions = {
setActiveBackgroundShellPid: vi.fn(),
setIsBackgroundShellListOpen: vi.fn(),
setAuthContext: vi.fn(),
onHintInput: vi.fn(),
onHintBackspace: vi.fn(),
onHintClear: vi.fn(),
onHintSubmit: vi.fn(),
handleRestart: vi.fn(),
handleNewAgentsSelect: vi.fn(),
};
+92 -1
View File
@@ -94,7 +94,11 @@ import { basename } from 'node:path';
import { computeTerminalTitle } from '../utils/windowTitle.js';
import { useTextBuffer } from './components/shared/text-buffer.js';
import { useLogger } from './hooks/useLogger.js';
import { useGeminiStream } from './hooks/useGeminiStream.js';
import {
buildUserSteeringHintPrompt,
generateSteeringAckMessage,
useGeminiStream,
} from './hooks/useGeminiStream.js';
import { type BackgroundShell } from './hooks/shellCommandProcessor.js';
import { useVim } from './hooks/vim.js';
import { type LoadableSettingScope, SettingScope } from '../config/settings.js';
@@ -963,6 +967,18 @@ Logging in with Google... Restarting Gemini CLI to continue.
}
}, [pendingRestorePrompt, inputHistory, historyManager.history]);
const consumePendingHints = useCallback(() => {
const userHints = config.consumeUserHints();
if (userHints.length === 0) {
return null;
}
return userHints.join('\n');
}, [config]);
const getUserHint = useCallback(
() => consumePendingHints(),
[consumePendingHints],
);
const {
streamingState,
submitQuery,
@@ -1001,6 +1017,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
terminalWidth,
terminalHeight,
embeddedShellFocused,
getUserHint,
);
toggleBackgroundShellRef.current = toggleBackgroundShell;
@@ -1103,10 +1120,38 @@ Logging in with Google... Restarting Gemini CLI to continue.
],
);
const handleHintSubmit = useCallback(
(hint: string) => {
const trimmed = hint.trim();
if (!trimmed) {
return;
}
config.addUserHint(trimmed);
// Render hints as regular user input so they look like normal commands.
historyManager.addItem({
type: MessageType.USER,
text: trimmed,
});
},
[config, historyManager],
);
const handleFinalSubmit = useCallback(
async (submittedValue: string) => {
const isSlash = isSlashCommand(submittedValue.trim());
const isIdle = streamingState === StreamingState.Idle;
const isAgentRunning =
streamingState === StreamingState.Responding ||
isToolExecuting([
...pendingSlashCommandHistoryItems,
...pendingGeminiHistoryItems,
]);
if (isAgentRunning && !isSlash) {
handleHintSubmit(submittedValue);
addInput(submittedValue);
return;
}
if (isSlash || (isIdle && isMcpReady)) {
if (!isSlash) {
@@ -1148,7 +1193,10 @@ Logging in with Google... Restarting Gemini CLI to continue.
isMcpReady,
streamingState,
messageQueue.length,
pendingSlashCommandHistoryItems,
pendingGeminiHistoryItems,
config,
handleHintSubmit,
],
);
@@ -1814,6 +1862,43 @@ Logging in with Google... Restarting Gemini CLI to continue.
[pendingSlashCommandHistoryItems, pendingGeminiHistoryItems],
);
useEffect(() => {
if (
!isConfigInitialized ||
streamingState !== StreamingState.Idle ||
!isMcpReady
) {
return;
}
const pendingHint = consumePendingHints();
if (!pendingHint) {
return;
}
const geminiClient = config.getGeminiClient();
void generateSteeringAckMessage(geminiClient, pendingHint).then(
(ackText) => {
historyManager.addItem({
type: 'info',
icon: '· ',
color: 'gray',
marginBottom: 1,
text: ackText,
} as Omit<HistoryItem, 'id'>);
},
);
void submitQuery([{ text: buildUserSteeringHintPrompt(pendingHint) }]);
}, [
config,
consumePendingHints,
historyManager,
isConfigInitialized,
isMcpReady,
streamingState,
submitQuery,
]);
const allToolCalls = useMemo(
() =>
pendingHistoryItems
@@ -1975,6 +2060,8 @@ Logging in with Google... Restarting Gemini CLI to continue.
isBackgroundShellListOpen,
adminSettingsChanged,
newAgents,
hintMode: false,
hintBuffer: '',
}),
[
isThemeDialogOpen,
@@ -2137,6 +2224,10 @@ Logging in with Google... Restarting Gemini CLI to continue.
setActiveBackgroundShellPid,
setIsBackgroundShellListOpen,
setAuthContext,
onHintInput: () => {},
onHintBackspace: () => {},
onHintClear: () => {},
onHintSubmit: () => {},
handleRestart: async () => {
if (process.send) {
const remoteSettings = config.getRemoteAdminSettings();
@@ -14,7 +14,9 @@ describe('ApprovalModeIndicator', () => {
const { lastFrame } = render(
<ApprovalModeIndicator approvalMode={ApprovalMode.AUTO_EDIT} />,
);
expect(lastFrame()).toMatchSnapshot();
const output = lastFrame();
expect(output).toContain('auto-accept edits');
expect(output).toContain('shift+tab to manual');
});
it('renders correctly for AUTO_EDIT mode with plan enabled', () => {
@@ -24,28 +26,35 @@ describe('ApprovalModeIndicator', () => {
isPlanEnabled={true}
/>,
);
expect(lastFrame()).toMatchSnapshot();
const output = lastFrame();
expect(output).toContain('auto-accept edits');
expect(output).toContain('shift+tab to manual');
});
it('renders correctly for PLAN mode', () => {
const { lastFrame } = render(
<ApprovalModeIndicator approvalMode={ApprovalMode.PLAN} />,
);
expect(lastFrame()).toMatchSnapshot();
const output = lastFrame();
expect(output).toContain('plan');
expect(output).toContain('shift+tab to accept edits');
});
it('renders correctly for YOLO mode', () => {
const { lastFrame } = render(
<ApprovalModeIndicator approvalMode={ApprovalMode.YOLO} />,
);
expect(lastFrame()).toMatchSnapshot();
const output = lastFrame();
expect(output).toContain('YOLO');
expect(output).toContain('ctrl+y');
});
it('renders correctly for DEFAULT mode', () => {
const { lastFrame } = render(
<ApprovalModeIndicator approvalMode={ApprovalMode.DEFAULT} />,
);
expect(lastFrame()).toMatchSnapshot();
const output = lastFrame();
expect(output).toContain('shift+tab to accept edits');
});
it('renders correctly for DEFAULT mode with plan enabled', () => {
@@ -55,6 +64,7 @@ describe('ApprovalModeIndicator', () => {
isPlanEnabled={true}
/>,
);
expect(lastFrame()).toMatchSnapshot();
const output = lastFrame();
expect(output).toContain('shift+tab to plan');
});
});
@@ -14,16 +14,6 @@ interface ApprovalModeIndicatorProps {
isPlanEnabled?: boolean;
}
export const APPROVAL_MODE_TEXT = {
AUTO_EDIT: 'auto-accept edits',
PLAN: 'plan',
YOLO: 'YOLO',
HINT_SWITCH_TO_PLAN_MODE: 'shift+tab to plan',
HINT_SWITCH_TO_MANUAL_MODE: 'shift+tab to manual',
HINT_SWITCH_TO_AUTO_EDIT_MODE: 'shift+tab to accept edits',
HINT_SWITCH_TO_YOLO_MODE: 'ctrl+y',
};
export const ApprovalModeIndicator: React.FC<ApprovalModeIndicatorProps> = ({
approvalMode,
isPlanEnabled,
@@ -35,26 +25,26 @@ export const ApprovalModeIndicator: React.FC<ApprovalModeIndicatorProps> = ({
switch (approvalMode) {
case ApprovalMode.AUTO_EDIT:
textColor = theme.status.warning;
textContent = APPROVAL_MODE_TEXT.AUTO_EDIT;
subText = isPlanEnabled
? APPROVAL_MODE_TEXT.HINT_SWITCH_TO_PLAN_MODE
: APPROVAL_MODE_TEXT.HINT_SWITCH_TO_MANUAL_MODE;
textContent = 'auto-accept edits';
subText = 'shift+tab to manual';
break;
case ApprovalMode.PLAN:
textColor = theme.status.success;
textContent = APPROVAL_MODE_TEXT.PLAN;
subText = APPROVAL_MODE_TEXT.HINT_SWITCH_TO_MANUAL_MODE;
textContent = 'plan';
subText = 'shift+tab to accept edits';
break;
case ApprovalMode.YOLO:
textColor = theme.status.error;
textContent = APPROVAL_MODE_TEXT.YOLO;
subText = APPROVAL_MODE_TEXT.HINT_SWITCH_TO_YOLO_MODE;
textContent = 'YOLO';
subText = 'ctrl+y';
break;
case ApprovalMode.DEFAULT:
default:
textColor = theme.text.accent;
textContent = '';
subText = APPROVAL_MODE_TEXT.HINT_SWITCH_TO_AUTO_EDIT_MODE;
subText = isPlanEnabled
? 'shift+tab to plan'
: 'shift+tab to accept edits';
break;
}
+2 -1
View File
@@ -71,7 +71,8 @@ export const Footer: React.FC = () => {
const justifyContent = hideCWD && hideModelInfo ? 'center' : 'space-between';
const displayVimMode = vimEnabled ? vimMode : undefined;
const showDebugProfiler = debugMode || isDevelopment;
const showDebugProfiler =
debugMode || (isDevelopment && settings.merged.general.devtools);
return (
<Box
@@ -96,6 +96,7 @@ describe('<Header />', () => {
},
background: {
primary: '',
hintMode: '',
diff: { added: '', removed: '' },
},
border: {
@@ -44,6 +44,18 @@ describe('<HistoryItemDisplay />', () => {
expect(lastFrame()).toContain('Hello');
});
it('renders HintMessage for "hint" type', () => {
const item: HistoryItem = {
...baseItem,
type: 'hint',
text: 'Try using ripgrep first',
};
const { lastFrame } = renderWithProviders(
<HistoryItemDisplay {...baseItem} item={item} />,
);
expect(lastFrame()).toContain('Try using ripgrep first');
});
it('renders UserMessage for "user" type with slash command', () => {
const item: HistoryItem = {
...baseItem,
@@ -35,6 +35,7 @@ import { ChatList } from './views/ChatList.js';
import { HooksList } from './views/HooksList.js';
import { ModelMessage } from './messages/ModelMessage.js';
import { ThinkingMessage } from './messages/ThinkingMessage.js';
import { HintMessage } from './messages/HintMessage.js';
import { getInlineThinkingMode } from '../utils/inlineThinkingMode.js';
import { useSettings } from '../contexts/SettingsContext.js';
@@ -71,6 +72,9 @@ export const HistoryItemDisplay: React.FC<HistoryItemDisplayProps> = ({
{itemForDisplay.type === 'thinking' && inlineThinkingMode !== 'off' && (
<ThinkingMessage thought={itemForDisplay.thought} />
)}
{itemForDisplay.type === 'hint' && (
<HintMessage text={itemForDisplay.text} />
)}
{itemForDisplay.type === 'user' && (
<UserMessage text={itemForDisplay.text} width={terminalWidth} />
)}
@@ -102,6 +106,7 @@ export const HistoryItemDisplay: React.FC<HistoryItemDisplayProps> = ({
text={itemForDisplay.text}
icon={itemForDisplay.icon}
color={itemForDisplay.color}
marginBottom={itemForDisplay.marginBottom}
/>
)}
{itemForDisplay.type === 'warning' && (
@@ -281,10 +281,7 @@ describe('InputPrompt', () => {
navigateDown: vi.fn(),
handleSubmit: vi.fn(),
};
mockedUseInputHistory.mockImplementation(({ onSubmit }) => {
mockInputHistory.handleSubmit = vi.fn((val) => onSubmit(val));
return mockInputHistory;
});
mockedUseInputHistory.mockReturnValue(mockInputHistory);
mockReverseSearchCompletion = {
suggestions: [],
@@ -4096,7 +4093,7 @@ describe('InputPrompt', () => {
beforeEach(() => {
props.userMessages = ['first message', 'second message'];
// Mock useInputHistory to actually call onChange
mockedUseInputHistory.mockImplementation(({ onChange, onSubmit }) => ({
mockedUseInputHistory.mockImplementation(({ onChange }) => ({
navigateUp: () => {
onChange('second message', 'start');
return true;
@@ -4105,7 +4102,7 @@ describe('InputPrompt', () => {
onChange('first message', 'end');
return true;
},
handleSubmit: vi.fn((val) => onSubmit(val)),
handleSubmit: vi.fn(),
}));
});
+27 -21
View File
@@ -337,6 +337,31 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
],
);
const handleSubmit = useCallback(
(submittedValue: string) => {
const trimmedMessage = submittedValue.trim();
const isSlash = isSlashCommand(trimmedMessage);
const isShell = shellModeActive;
if (
(isSlash || isShell) &&
streamingState === StreamingState.Responding
) {
setQueueErrorMessage(
`${isShell ? 'Shell' : 'Slash'} commands cannot be queued`,
);
return;
}
handleSubmitAndClear(trimmedMessage);
},
[
handleSubmitAndClear,
shellModeActive,
streamingState,
setQueueErrorMessage,
],
);
const customSetTextAndResetCompletionSignal = useCallback(
(newText: string, cursorPosition?: 'start' | 'end' | number) => {
buffer.setText(newText, cursorPosition);
@@ -356,26 +381,6 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
onChange: customSetTextAndResetCompletionSignal,
});
const handleSubmit = useCallback(
(submittedValue: string) => {
const trimmedMessage = submittedValue.trim();
const isSlash = isSlashCommand(trimmedMessage);
const isShell = shellModeActive;
if (
(isSlash || isShell) &&
streamingState === StreamingState.Responding
) {
setQueueErrorMessage(
`${isShell ? 'Shell' : 'Slash'} commands cannot be queued`,
);
return;
}
inputHistory.handleSubmit(trimmedMessage);
},
[inputHistory, shellModeActive, streamingState, setQueueErrorMessage],
);
// Effect to reset completion if history navigation just occurred and set the text
useEffect(() => {
if (suppressCompletion) {
@@ -853,7 +858,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
showSuggestions && activeSuggestionIndex > -1
? suggestions[activeSuggestionIndex].value
: buffer.text;
handleSubmit(textToSubmit);
handleSubmitAndClear(textToSubmit);
resetState();
setActive(false);
return true;
@@ -1150,6 +1155,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
setShellModeActive,
onClearScreen,
inputHistory,
handleSubmitAndClear,
handleSubmit,
shellHistory,
reverseSearchCompletion,
@@ -1,13 +0,0 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`ApprovalModeIndicator > renders correctly for AUTO_EDIT mode 1`] = `"auto-accept edits shift+tab to manual"`;
exports[`ApprovalModeIndicator > renders correctly for AUTO_EDIT mode with plan enabled 1`] = `"auto-accept edits shift+tab to plan"`;
exports[`ApprovalModeIndicator > renders correctly for DEFAULT mode 1`] = `"shift+tab to accept edits"`;
exports[`ApprovalModeIndicator > renders correctly for DEFAULT mode with plan enabled 1`] = `"shift+tab to accept edits"`;
exports[`ApprovalModeIndicator > renders correctly for PLAN mode 1`] = `"plan shift+tab to manual"`;
exports[`ApprovalModeIndicator > renders correctly for YOLO mode 1`] = `"YOLO ctrl+y"`;
@@ -13,19 +13,21 @@ interface InfoMessageProps {
text: string;
icon?: string;
color?: string;
marginBottom?: number;
}
export const InfoMessage: React.FC<InfoMessageProps> = ({
text,
icon,
color,
marginBottom,
}) => {
color ??= theme.status.warning;
const prefix = icon ?? ' ';
const prefixWidth = prefix.length;
return (
<Box flexDirection="row" marginTop={1}>
<Box flexDirection="row" marginTop={1} marginBottom={marginBottom ?? 0}>
<Box width={prefixWidth}>
<Text color={color}>{prefix}</Text>
</Box>
@@ -73,6 +73,10 @@ export interface UIActions {
setActiveBackgroundShellPid: (pid: number) => void;
setIsBackgroundShellListOpen: (isOpen: boolean) => void;
setAuthContext: (context: { requiresRestart?: boolean }) => void;
onHintInput: (char: string) => void;
onHintBackspace: () => void;
onHintClear: () => void;
onHintSubmit: (hint: string) => void;
handleRestart: () => void;
handleNewAgentsSelect: (choice: NewAgentsChoice) => Promise<void>;
}
@@ -173,6 +173,8 @@ export interface UIState {
isBackgroundShellListOpen: boolean;
adminSettingsChanged: boolean;
newAgents: AgentDefinition[] | null;
hintMode: boolean;
hintBuffer: string;
transientMessage: {
text: string;
type: TransientMessageType;
@@ -202,7 +202,7 @@ describe('useApprovalModeIndicator', () => {
);
expect(result.current).toBe(ApprovalMode.YOLO);
// Shift+Tab cycles back to AUTO_EDIT (from YOLO)
// Shift+Tab cycles back to DEFAULT (since PLAN is disabled by default in mock)
act(() => {
capturedUseKeypressHandler({
name: 'tab',
@@ -236,7 +236,7 @@ describe('useApprovalModeIndicator', () => {
expect(result.current).toBe(ApprovalMode.AUTO_EDIT);
});
it('should cycle through DEFAULT -> AUTO_EDIT -> PLAN -> DEFAULT when plan is enabled', () => {
it('should cycle through DEFAULT -> PLAN -> AUTO_EDIT -> DEFAULT when plan is enabled', () => {
mockConfigInstance.getApprovalMode.mockReturnValue(ApprovalMode.DEFAULT);
mockConfigInstance.isPlanEnabled.mockReturnValue(true);
renderHook(() =>
@@ -246,15 +246,7 @@ describe('useApprovalModeIndicator', () => {
}),
);
// DEFAULT -> AUTO_EDIT
act(() => {
capturedUseKeypressHandler({ name: 'tab', shift: true } as Key);
});
expect(mockConfigInstance.setApprovalMode).toHaveBeenCalledWith(
ApprovalMode.AUTO_EDIT,
);
// AUTO_EDIT -> PLAN
// DEFAULT -> PLAN
act(() => {
capturedUseKeypressHandler({ name: 'tab', shift: true } as Key);
});
@@ -262,7 +254,15 @@ describe('useApprovalModeIndicator', () => {
ApprovalMode.PLAN,
);
// PLAN -> DEFAULT
// PLAN -> AUTO_EDIT
act(() => {
capturedUseKeypressHandler({ name: 'tab', shift: true } as Key);
});
expect(mockConfigInstance.setApprovalMode).toHaveBeenCalledWith(
ApprovalMode.AUTO_EDIT,
);
// AUTO_EDIT -> DEFAULT
act(() => {
capturedUseKeypressHandler({ name: 'tab', shift: true } as Key);
});
@@ -72,14 +72,14 @@ export function useApprovalModeIndicator({
const currentMode = config.getApprovalMode();
switch (currentMode) {
case ApprovalMode.DEFAULT:
nextApprovalMode = config.isPlanEnabled()
? ApprovalMode.PLAN
: ApprovalMode.AUTO_EDIT;
break;
case ApprovalMode.PLAN:
nextApprovalMode = ApprovalMode.AUTO_EDIT;
break;
case ApprovalMode.AUTO_EDIT:
nextApprovalMode = config.isPlanEnabled()
? ApprovalMode.PLAN
: ApprovalMode.DEFAULT;
break;
case ApprovalMode.PLAN:
nextApprovalMode = ApprovalMode.DEFAULT;
break;
case ApprovalMode.YOLO:
@@ -56,6 +56,11 @@ const MockedGeminiClientClass = vi.hoisted(() =>
this.startChat = mockStartChat;
this.sendMessageStream = mockSendMessageStream;
this.addHistory = vi.fn();
this.generateContent = vi.fn().mockResolvedValue({
candidates: [
{ content: { parts: [{ text: 'Got it. Focusing on tests only.' }] } },
],
});
this.getCurrentSequenceModel = vi.fn().mockReturnValue('test-model');
this.getChat = vi.fn().mockReturnValue({
recordCompletedToolCalls: vi.fn(),
@@ -661,6 +666,113 @@ describe('useGeminiStream', () => {
);
});
it('should inject steering hint prompt for continuation', async () => {
const toolCallResponseParts: Part[] = [{ text: 'tool final response' }];
const completedToolCalls: TrackedToolCall[] = [
{
request: {
callId: 'call1',
name: 'tool1',
args: {},
isClientInitiated: false,
prompt_id: 'prompt-id-ack',
},
status: 'success',
responseSubmittedToGemini: false,
response: {
callId: 'call1',
responseParts: toolCallResponseParts,
errorType: undefined,
},
tool: {
displayName: 'MockTool',
},
invocation: {
getDescription: () => `Mock description`,
} as unknown as AnyToolInvocation,
} as TrackedCompletedToolCall,
];
mockSendMessageStream.mockReturnValue(
(async function* () {
yield {
type: ServerGeminiEventType.Content,
value: 'Applied the requested adjustment.',
};
})(),
);
let capturedOnComplete:
| ((completedTools: TrackedToolCall[]) => Promise<void>)
| null = null;
mockUseToolScheduler.mockImplementation((onComplete) => {
capturedOnComplete = onComplete;
return [
[],
mockScheduleToolCalls,
mockMarkToolsAsSubmitted,
vi.fn(),
mockCancelAllToolCalls,
0,
];
});
renderHookWithProviders(() =>
useGeminiStream(
new MockedGeminiClientClass(mockConfig),
[],
mockAddItem,
mockConfig,
mockLoadedSettings,
mockOnDebugMessage,
mockHandleSlashCommand,
false,
() => 'vscode' as EditorType,
() => {},
() => Promise.resolve(),
false,
() => {},
() => {},
() => {},
80,
24,
undefined,
() => 'focus on tests only',
),
);
await act(async () => {
if (capturedOnComplete) {
await new Promise((resolve) => setTimeout(resolve, 0));
await capturedOnComplete(completedToolCalls);
}
});
await waitFor(() => {
expect(mockSendMessageStream).toHaveBeenCalledTimes(1);
});
const sentParts = mockSendMessageStream.mock.calls[0][0] as Part[];
const injectedHintPart = sentParts[0] as { text?: string };
expect(injectedHintPart.text).toContain(
'User steering update: "focus on tests only"',
);
expect(injectedHintPart.text).toContain(
'Classify it as ADD_TASK, MODIFY_TASK, CANCEL_TASK, or EXTRA_CONTEXT.',
);
expect(injectedHintPart.text).toContain(
'Do not cancel/skip tasks unless the user explicitly cancels them.',
);
expect(
mockAddItem.mock.calls.some(
([item]) =>
item?.type === 'info' &&
typeof item.text === 'string' &&
item.text.includes('Got it. Focusing on tests only.'),
),
).toBe(true);
});
it('should handle all tool calls being cancelled', async () => {
const cancelledToolCalls: TrackedToolCall[] = [
{
+128 -1
View File
@@ -8,6 +8,7 @@ import { useState, useRef, useCallback, useEffect, useMemo } from 'react';
import {
GeminiEventType as ServerGeminiEventType,
getErrorMessage,
getResponseText,
isNodeError,
MessageSenderType,
logUserPrompt,
@@ -47,7 +48,12 @@ import type {
GeminiErrorEventValue,
RetryAttemptPayload,
} from '@google/gemini-cli-core';
import { type Part, type PartListUnion, FinishReason } from '@google/genai';
import {
type Content,
type Part,
type PartListUnion,
FinishReason,
} from '@google/genai';
import type {
HistoryItem,
HistoryItemThinking,
@@ -81,6 +87,7 @@ import path from 'node:path';
import { useSessionStats } from '../contexts/SessionContext.js';
import { useKeypress } from './useKeypress.js';
import type { LoadedSettings } from '../../config/settings.js';
import { theme } from '../semantic-colors.js';
type ToolResponseWithParts = ToolCallResponseInfo & {
llmContent?: PartListUnion;
@@ -98,6 +105,102 @@ enum StreamProcessingStatus {
Error,
}
const USER_STEERING_INSTRUCTION =
'Internal instruction: Re-evaluate the active plan using this user steering update. ' +
'Classify it as ADD_TASK, MODIFY_TASK, CANCEL_TASK, or EXTRA_CONTEXT. ' +
'Apply minimal-diff changes only to affected tasks and keep unaffected tasks active. ' +
'Do not cancel/skip tasks unless the user explicitly cancels them. ' +
'Acknowledge the steering briefly and state the course correction.';
export function buildUserSteeringHintPrompt(hintText: string): string {
const trimmedText = hintText.trim();
return `User steering update: "${trimmedText}"\n${USER_STEERING_INSTRUCTION}`;
}
const STEERING_ACK_INSTRUCTION =
'Write one short, friendly sentence acknowledging a user steering update for an in-progress task. ' +
'Be concrete when possible (e.g., mention skipped/cancelled item numbers). ' +
'Do not apologize, do not mention internal policy, and do not add extra steps.';
const STEERING_ACK_TIMEOUT_MS = 1200;
const STEERING_ACK_MAX_INPUT_CHARS = 320;
const STEERING_ACK_MAX_OUTPUT_CHARS = 90;
const STEERING_ACK_INPUT_TRUNCATION_SUFFIX = '\n...[truncated]';
function buildSteeringFallbackMessage(hintText: string): string {
const normalized = hintText.replace(/\s+/g, ' ').trim();
if (!normalized) {
return 'Understood. Adjusting the plan.';
}
if (normalized.length <= 64) {
return `Understood. ${normalized}`;
}
return `Understood. ${normalized.slice(0, 61)}...`;
}
export async function generateSteeringAckMessage(
geminiClient: GeminiClient,
hintText: string,
): Promise<string> {
const truncateSteeringAckInput = (input: string): string => {
if (input.length <= STEERING_ACK_MAX_INPUT_CHARS) {
return input;
}
if (
STEERING_ACK_MAX_INPUT_CHARS <=
STEERING_ACK_INPUT_TRUNCATION_SUFFIX.length
) {
return input.slice(0, STEERING_ACK_MAX_INPUT_CHARS);
}
return (
input.slice(
0,
STEERING_ACK_MAX_INPUT_CHARS -
STEERING_ACK_INPUT_TRUNCATION_SUFFIX.length,
) + STEERING_ACK_INPUT_TRUNCATION_SUFFIX
);
};
const fallbackText = buildSteeringFallbackMessage(hintText);
const safeHint = truncateSteeringAckInput(
hintText.replace(/\s+/g, ' ').trim(),
);
const contents: Content[] = [
{
role: 'user',
parts: [
{
text: `${STEERING_ACK_INSTRUCTION}\n\nUser input:\n"""${safeHint}"""`,
},
],
},
];
const abortController = new AbortController();
const timeout = setTimeout(
() => abortController.abort(),
STEERING_ACK_TIMEOUT_MS,
);
try {
const response = await geminiClient.generateContent(
{ model: 'flash-lite-helper' },
contents,
abortController.signal,
);
const responseText = getResponseText(response)?.replace(/\s+/g, ' ').trim();
if (!responseText) {
return fallbackText;
}
if (responseText.length > STEERING_ACK_MAX_OUTPUT_CHARS) {
return responseText.slice(0, STEERING_ACK_MAX_OUTPUT_CHARS).trimEnd();
}
return responseText;
} catch {
return fallbackText;
} finally {
clearTimeout(timeout);
}
}
function isShellToolData(data: unknown): data is ShellToolData {
if (typeof data !== 'object' || data === null) {
return false;
@@ -185,6 +288,7 @@ export const useGeminiStream = (
terminalWidth: number,
terminalHeight: number,
isShellFocused?: boolean,
getUserHint?: () => string | null,
) => {
const [initError, setInitError] = useState<string | null>(null);
const [retryStatus, setRetryStatus] = useState<RetryAttemptPayload | null>(
@@ -1561,6 +1665,28 @@ export const useGeminiStream = (
const responsesToSend: Part[] = geminiTools.flatMap(
(toolCall) => toolCall.response.responseParts,
);
if (getUserHint) {
const userHint = getUserHint();
if (userHint && userHint.trim().length > 0) {
const hintText = userHint.trim();
responsesToSend.unshift({
text: buildUserSteeringHintPrompt(hintText),
});
void generateSteeringAckMessage(geminiClient, hintText).then(
(ackText) => {
addItem({
type: 'info',
icon: '· ',
color: theme.text.secondary,
marginBottom: 1,
text: ackText,
} as Omit<HistoryItem, 'id'>);
},
);
}
}
const callIdsToMarkAsSubmitted = geminiTools.map(
(toolCall) => toolCall.request.callId,
);
@@ -1593,6 +1719,7 @@ export const useGeminiStream = (
modelSwitchedFromQuotaError,
addItem,
registerBackgroundShell,
getUserHint,
],
);
+1
View File
@@ -36,6 +36,7 @@ const noColorSemanticColors: SemanticColors = {
},
background: {
primary: '',
hintMode: '',
diff: {
added: '',
removed: '',
@@ -16,6 +16,7 @@ export interface SemanticColors {
};
background: {
primary: string;
hintMode: string;
diff: {
added: string;
removed: string;
@@ -48,6 +49,7 @@ export const lightSemanticColors: SemanticColors = {
},
background: {
primary: lightTheme.Background,
hintMode: '#E8E0F0',
diff: {
added: lightTheme.DiffAdded,
removed: lightTheme.DiffRemoved,
@@ -80,6 +82,7 @@ export const darkSemanticColors: SemanticColors = {
},
background: {
primary: darkTheme.Background,
hintMode: '#352A45',
diff: {
added: darkTheme.DiffAdded,
removed: darkTheme.DiffRemoved,
+2
View File
@@ -131,6 +131,7 @@ export class Theme {
},
background: {
primary: this.colors.Background,
hintMode: this.type === 'light' ? '#E8E0F0' : '#352A45',
diff: {
added: this.colors.DiffAdded,
removed: this.colors.DiffRemoved,
@@ -400,6 +401,7 @@ export function createCustomTheme(customTheme: CustomTheme): Theme {
},
background: {
primary: customTheme.background?.primary ?? colors.Background,
hintMode: 'magenta',
diff: {
added: customTheme.background?.diff?.added ?? colors.DiffAdded,
removed: customTheme.background?.diff?.removed ?? colors.DiffRemoved,
+7
View File
@@ -123,6 +123,7 @@ export type HistoryItemInfo = HistoryItemBase & {
text: string;
icon?: string;
color?: string;
marginBottom?: number;
};
export type HistoryItemError = HistoryItemBase & {
@@ -225,6 +226,11 @@ export type HistoryItemThinking = HistoryItemBase & {
thought: ThoughtSummary;
};
export type HistoryItemHint = HistoryItemBase & {
type: 'hint';
text: string;
};
export type HistoryItemChatList = HistoryItemBase & {
type: 'chat_list';
chats: ChatDetail[];
@@ -349,6 +355,7 @@ export type HistoryItemWithoutId =
| HistoryItemMcpStatus
| HistoryItemChatList
| HistoryItemThinking
| HistoryItemHint
| HistoryItemHooksList;
export type HistoryItem = HistoryItemWithoutId & { id: number };
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli-core",
"version": "0.29.1",
"version": "0.29.0-nightly.20260203.71f46f116",
"description": "Gemini CLI Core",
"license": "Apache-2.0",
"repository": {
+8 -1
View File
@@ -462,7 +462,14 @@ export class LocalAgentExecutor<TOutput extends z.ZodTypeAny> {
const query = this.definition.promptConfig.query
? templateString(this.definition.promptConfig.query, augmentedInputs)
: DEFAULT_QUERY_STRING;
let currentMessage: Content = { role: 'user', parts: [{ text: query }] };
const userHints = this.runtimeContext.peekUserHints();
const hintText = userHints.map((hint) => `- ${hint}`).join('\n');
let currentMessage: Content = hintText
? {
role: 'user',
parts: [{ text: `User hints:\n${hintText}` }, { text: query }],
}
: { role: 'user', parts: [{ text: query }] };
while (true) {
// Check for termination conditions like max turns.
+30 -2
View File
@@ -88,7 +88,10 @@ class SubAgentInvocation extends BaseToolInvocation<AgentInputs, ToolResult> {
override async shouldConfirmExecute(
abortSignal: AbortSignal,
): Promise<ToolCallConfirmationDetails | false> {
const invocation = this.buildSubInvocation(this.definition, this.params);
const invocation = this.buildSubInvocation(
this.definition,
this.withUserHints(this.params),
);
return invocation.shouldConfirmExecute(abortSignal);
}
@@ -107,11 +110,36 @@ class SubAgentInvocation extends BaseToolInvocation<AgentInputs, ToolResult> {
);
}
const invocation = this.buildSubInvocation(this.definition, this.params);
const invocation = this.buildSubInvocation(
this.definition,
this.withUserHints(this.params),
);
return invocation.execute(signal, updateOutput);
}
private withUserHints(agentArgs: AgentInputs): AgentInputs {
if (this.definition.kind !== 'remote') {
return agentArgs;
}
const userHints = this.config.peekUserHints();
if (userHints.length === 0) {
return agentArgs;
}
const hintText = userHints.map((hint) => `- ${hint}`).join('\n');
const query = agentArgs['query'];
if (typeof query !== 'string' || query.trim().length === 0) {
return agentArgs;
}
return {
...agentArgs,
query: `User hints:\n${hintText}\n\n${query}`,
};
}
private buildSubInvocation(
definition: AgentDefinition,
agentArgs: AgentInputs,
+25
View File
@@ -2583,4 +2583,29 @@ describe('syncPlanModeTools', () => {
expect(setToolsSpy).toHaveBeenCalled();
});
describe('user hints', () => {
it('stores trimmed hints and exposes them via peek/consume', () => {
const config = new Config(baseParams);
config.addUserHint(' first hint ');
config.addUserHint('second hint');
config.addUserHint(' ');
expect(config.peekUserHints()).toEqual(['first hint', 'second hint']);
expect(config.consumeUserHints()).toEqual(['first hint', 'second hint']);
expect(config.peekUserHints()).toEqual([]);
});
it('tracks the last hint timestamp', () => {
const config = new Config(baseParams);
expect(config.getLastUserHintAt()).toBeNull();
config.addUserHint('hint');
const timestamp = config.getLastUserHintAt();
expect(timestamp).not.toBeNull();
expect(typeof timestamp).toBe('number');
});
});
});
+25
View File
@@ -668,6 +668,8 @@ export class Config {
private remoteAdminSettings: AdminControlsSettings | undefined;
private latestApiRequest: GenerateContentParameters | undefined;
private lastModeSwitchTime: number = Date.now();
private pendingUserHints: string[] = [];
private lastUserHintAt: number | null = null;
private approvedPlanPath: string | undefined;
@@ -2486,6 +2488,29 @@ export class Config {
return this.hookSystem;
}
addUserHint(hint: string): void {
const trimmed = hint.trim();
if (trimmed.length === 0) {
return;
}
this.pendingUserHints.push(trimmed);
this.lastUserHintAt = Date.now();
}
peekUserHints(): string[] {
return [...this.pendingUserHints];
}
consumeUserHints(): string[] {
const hints = this.pendingUserHints;
this.pendingUserHints = [];
return hints;
}
getLastUserHintAt(): number | null {
return this.lastUserHintAt;
}
/**
* Get hooks configuration
*/
@@ -121,6 +121,19 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
},
},
},
'flash-lite-helper': {
extends: 'base',
modelConfig: {
model: 'gemini-2.5-flash-lite',
generateContentConfig: {
temperature: 0.2,
maxOutputTokens: 120,
thinkingConfig: {
thinkingBudget: 0,
},
},
},
},
'edit-corrector': {
extends: 'base',
modelConfig: {
@@ -11,6 +11,7 @@ exports[`Core System Prompt (prompts.ts) > ApprovalMode in System Prompt > Appro
- **Idiomatic Changes:** When editing, understand the local context (imports, functions/classes) to ensure your changes integrate naturally and idiomatically.
- **Comments:** Add code comments sparingly. Focus on *why* something is done, especially for complex logic, rather than *what* is done. Only add high-value comments if necessary for clarity or if requested by the user. Do not edit comments that are separate from the code you are changing. *NEVER* talk to the user or describe your changes through comments.
- **Proactiveness:** Fulfill the user's request thoroughly. When adding features or fixing bugs, this includes adding tests to ensure quality. Consider all created files, especially tests, to be permanent artifacts unless the user says otherwise.
- **User Hints:** During execution, the user may provide real-time hints (marked as "User hint:" or "User hints:"). Treat these as high-priority but scope-preserving course corrections: apply the minimal plan change needed, keep unaffected user tasks active, and never cancel/skip tasks unless cancellation is explicit for those tasks. Hints may add new tasks, modify one or more tasks, cancel specific tasks, or provide extra context only. If scope is ambiguous, ask for clarification before dropping work.
- **Confirm Ambiguity/Expansion:** Do not take significant actions beyond the clear scope of the request without confirming with the user. If the user implies a change (e.g., reports a bug) without explicitly asking for a fix, **ask for confirmation first**. If asked *how* to do something, explain first, don't just do it.
- **Explaining Changes:** After completing a code modification or file operation *do not* provide summaries unless asked.
- **Do Not revert changes:** Do not revert changes to the codebase unless asked to do so by the user. Only revert changes made by you if they have resulted in an error or if the user has explicitly asked you to revert the changes.
@@ -142,6 +143,7 @@ exports[`Core System Prompt (prompts.ts) > ApprovalMode in System Prompt > Appro
- **Idiomatic Changes:** When editing, understand the local context (imports, functions/classes) to ensure your changes integrate naturally and idiomatically.
- **Comments:** Add code comments sparingly. Focus on *why* something is done, especially for complex logic, rather than *what* is done. Only add high-value comments if necessary for clarity or if requested by the user. Do not edit comments that are separate from the code you are changing. *NEVER* talk to the user or describe your changes through comments.
- **Proactiveness:** Fulfill the user's request thoroughly. When adding features or fixing bugs, this includes adding tests to ensure quality. Consider all created files, especially tests, to be permanent artifacts unless the user says otherwise.
- **User Hints:** During execution, the user may provide real-time hints (marked as "User hint:" or "User hints:"). Treat these as high-priority but scope-preserving course corrections: apply the minimal plan change needed, keep unaffected user tasks active, and never cancel/skip tasks unless cancellation is explicit for those tasks. Hints may add new tasks, modify one or more tasks, cancel specific tasks, or provide extra context only. If scope is ambiguous, ask for clarification before dropping work.
- **Confirm Ambiguity/Expansion:** Do not take significant actions beyond the clear scope of the request without confirming with the user. If the user implies a change (e.g., reports a bug) without explicitly asking for a fix, **ask for confirmation first**. If asked *how* to do something, explain first, don't just do it.
- **Explaining Changes:** After completing a code modification or file operation *do not* provide summaries unless asked.
- **Do Not revert changes:** Do not revert changes to the codebase unless asked to do so by the user. Only revert changes made by you if they have resulted in an error or if the user has explicitly asked you to revert the changes.
@@ -278,6 +280,7 @@ exports[`Core System Prompt (prompts.ts) > ApprovalMode in System Prompt > shoul
- **Idiomatic Changes:** When editing, understand the local context (imports, functions/classes) to ensure your changes integrate naturally and idiomatically.
- **Comments:** Add code comments sparingly. Focus on *why* something is done, especially for complex logic, rather than *what* is done. Only add high-value comments if necessary for clarity or if requested by the user. Do not edit comments that are separate from the code you are changing. *NEVER* talk to the user or describe your changes through comments.
- **Proactiveness:** Fulfill the user's request thoroughly. When adding features or fixing bugs, this includes adding tests to ensure quality. Consider all created files, especially tests, to be permanent artifacts unless the user says otherwise.
- **User Hints:** During execution, the user may provide real-time hints (marked as "User hint:" or "User hints:"). Treat these as high-priority but scope-preserving course corrections: apply the minimal plan change needed, keep unaffected user tasks active, and never cancel/skip tasks unless cancellation is explicit for those tasks. Hints may add new tasks, modify one or more tasks, cancel specific tasks, or provide extra context only. If scope is ambiguous, ask for clarification before dropping work.
- **Confirm Ambiguity/Expansion:** Do not take significant actions beyond the clear scope of the request without confirming with the user. If the user implies a change (e.g., reports a bug) without explicitly asking for a fix, **ask for confirmation first**. If asked *how* to do something, explain first, don't just do it.
- **Explaining Changes:** After completing a code modification or file operation *do not* provide summaries unless asked.
- **Do Not revert changes:** Do not revert changes to the codebase unless asked to do so by the user. Only revert changes made by you if they have resulted in an error or if the user has explicitly asked you to revert the changes.
@@ -390,6 +393,7 @@ exports[`Core System Prompt (prompts.ts) > ApprovalMode in System Prompt > shoul
- **Idiomatic Changes:** When editing, understand the local context (imports, functions/classes) to ensure your changes integrate naturally and idiomatically.
- **Comments:** Add code comments sparingly. Focus on *why* something is done, especially for complex logic, rather than *what* is done. Only add high-value comments if necessary for clarity or if requested by the user. Do not edit comments that are separate from the code you are changing. *NEVER* talk to the user or describe your changes through comments.
- **Proactiveness:** Fulfill the user's request thoroughly. When adding features or fixing bugs, this includes adding tests to ensure quality. Consider all created files, especially tests, to be permanent artifacts unless the user says otherwise.
- **User Hints:** During execution, the user may provide real-time hints (marked as "User hint:" or "User hints:"). Treat these as high-priority but scope-preserving course corrections: apply the minimal plan change needed, keep unaffected user tasks active, and never cancel/skip tasks unless cancellation is explicit for those tasks. Hints may add new tasks, modify one or more tasks, cancel specific tasks, or provide extra context only. If scope is ambiguous, ask for clarification before dropping work.
- **Confirm Ambiguity/Expansion:** Do not take significant actions beyond the clear scope of the request without confirming with the user. If the user implies a change (e.g., reports a bug) without explicitly asking for a fix, **ask for confirmation first**. If asked *how* to do something, explain first, don't just do it.
- **Explaining Changes:** After completing a code modification or file operation *do not* provide summaries unless asked.
- **Do Not revert changes:** Do not revert changes to the codebase unless asked to do so by the user. Only revert changes made by you if they have resulted in an error or if the user has explicitly asked you to revert the changes.
@@ -526,7 +530,7 @@ exports[`Core System Prompt (prompts.ts) > should append userMemory with separat
- **Technical Integrity:** You are responsible for the entire lifecycle: implementation, testing, and validation. Within the scope of your changes, prioritize readability and long-term maintainability by consolidating logic into clean abstractions rather than threading state across unrelated layers. Align strictly with the requested architectural direction, ensuring the final implementation is focused and free of redundant "just-in-case" alternatives. Validation is not merely running tests; it is the exhaustive process of ensuring that every aspect of your change—behavioral, structural, and stylistic—is correct and fully compatible with the broader project. For bug fixes, you must empirically reproduce the failure with a new test case or reproduction script before applying the fix.
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a corresponding Directive is issued. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. For Directives, only clarify if critically underspecified; otherwise, work autonomously. You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.
- **Proactiveness:** When executing a Directive, persist through errors and obstacles by diagnosing failures in the execution phase and, if necessary, backtracking to the research or strategy phases to adjust your approach until a successful, verified outcome is achieved. Fulfill the user's request thoroughly, including adding tests when adding features or fixing bugs. Take reasonable liberties to fulfill broad goals while staying within the requested scope; however, prioritize simplicity and the removal of redundant logic over providing "just-in-case" alternatives that diverge from the established path.
- **Testing:** ALWAYS search for and update related tests after making a code change. You must add a new test case to the existing test file (if one exists) or create a new test file to verify your changes.
- **User Hints:** During execution, the user may provide real-time hints (marked as "User hint:" or "User hints:"). Treat these as high-priority but scope-preserving course corrections: apply the minimal plan change needed, keep unaffected user tasks active, and never cancel/skip tasks unless cancellation is explicit for those tasks. Hints may add new tasks, modify one or more tasks, cancel specific tasks, or provide extra context only. If scope is ambiguous, ask for clarification before dropping work.
- **Confirm Ambiguity/Expansion:** Do not take significant actions beyond the clear scope of the request without confirming with the user. If the user implies a change (e.g., reports a bug) without explicitly asking for a fix, **ask for confirmation first**. If asked *how* to do something, explain first, don't just do it.
- **Explaining Changes:** After completing a code modification or file operation *do not* provide summaries unless asked.
- **Do Not revert changes:** Do not revert changes to the codebase unless asked to do so by the user. Only revert changes made by you if they have resulted in an error or if the user has explicitly asked you to revert the changes.
@@ -653,7 +657,7 @@ exports[`Core System Prompt (prompts.ts) > should handle CodebaseInvestigator wi
- **Technical Integrity:** You are responsible for the entire lifecycle: implementation, testing, and validation. Within the scope of your changes, prioritize readability and long-term maintainability by consolidating logic into clean abstractions rather than threading state across unrelated layers. Align strictly with the requested architectural direction, ensuring the final implementation is focused and free of redundant "just-in-case" alternatives. Validation is not merely running tests; it is the exhaustive process of ensuring that every aspect of your change—behavioral, structural, and stylistic—is correct and fully compatible with the broader project. For bug fixes, you must empirically reproduce the failure with a new test case or reproduction script before applying the fix.
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a corresponding Directive is issued. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. For Directives, you must work autonomously as no further user input is available. You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.
- **Proactiveness:** When executing a Directive, persist through errors and obstacles by diagnosing failures in the execution phase and, if necessary, backtracking to the research or strategy phases to adjust your approach until a successful, verified outcome is achieved. Fulfill the user's request thoroughly, including adding tests when adding features or fixing bugs. Take reasonable liberties to fulfill broad goals while staying within the requested scope; however, prioritize simplicity and the removal of redundant logic over providing "just-in-case" alternatives that diverge from the established path.
- **Testing:** ALWAYS search for and update related tests after making a code change. You must add a new test case to the existing test file (if one exists) or create a new test file to verify your changes.
- **User Hints:** During execution, the user may provide real-time hints (marked as "User hint:" or "User hints:"). Treat these as high-priority but scope-preserving course corrections: apply the minimal plan change needed, keep unaffected user tasks active, and never cancel/skip tasks unless cancellation is explicit for those tasks. Hints may add new tasks, modify one or more tasks, cancel specific tasks, or provide extra context only. If scope is ambiguous, ask for clarification before dropping work.
- **Handle Ambiguity/Expansion:** Do not take significant actions beyond the clear scope of the request. If the user implies a change (e.g., reports a bug) without explicitly asking for a fix, do not perform it automatically.
- **Explaining Changes:** After completing a code modification or file operation *do not* provide summaries unless asked.
- **Do Not revert changes:** Do not revert changes to the codebase unless asked to do so by the user. Only revert changes made by you if they have resulted in an error or if the user has explicitly asked you to revert the changes.
@@ -745,7 +749,7 @@ exports[`Core System Prompt (prompts.ts) > should handle CodebaseInvestigator wi
- **Technical Integrity:** You are responsible for the entire lifecycle: implementation, testing, and validation. Within the scope of your changes, prioritize readability and long-term maintainability by consolidating logic into clean abstractions rather than threading state across unrelated layers. Align strictly with the requested architectural direction, ensuring the final implementation is focused and free of redundant "just-in-case" alternatives. Validation is not merely running tests; it is the exhaustive process of ensuring that every aspect of your change—behavioral, structural, and stylistic—is correct and fully compatible with the broader project. For bug fixes, you must empirically reproduce the failure with a new test case or reproduction script before applying the fix.
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a corresponding Directive is issued. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. For Directives, you must work autonomously as no further user input is available. You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.
- **Proactiveness:** When executing a Directive, persist through errors and obstacles by diagnosing failures in the execution phase and, if necessary, backtracking to the research or strategy phases to adjust your approach until a successful, verified outcome is achieved. Fulfill the user's request thoroughly, including adding tests when adding features or fixing bugs. Take reasonable liberties to fulfill broad goals while staying within the requested scope; however, prioritize simplicity and the removal of redundant logic over providing "just-in-case" alternatives that diverge from the established path.
- **Testing:** ALWAYS search for and update related tests after making a code change. You must add a new test case to the existing test file (if one exists) or create a new test file to verify your changes.
- **User Hints:** During execution, the user may provide real-time hints (marked as "User hint:" or "User hints:"). Treat these as high-priority but scope-preserving course corrections: apply the minimal plan change needed, keep unaffected user tasks active, and never cancel/skip tasks unless cancellation is explicit for those tasks. Hints may add new tasks, modify one or more tasks, cancel specific tasks, or provide extra context only. If scope is ambiguous, ask for clarification before dropping work.
- **Handle Ambiguity/Expansion:** Do not take significant actions beyond the clear scope of the request. If the user implies a change (e.g., reports a bug) without explicitly asking for a fix, do not perform it automatically.
- **Explaining Changes:** After completing a code modification or file operation *do not* provide summaries unless asked.
- **Do Not revert changes:** Do not revert changes to the codebase unless asked to do so by the user. Only revert changes made by you if they have resulted in an error or if the user has explicitly asked you to revert the changes.
@@ -832,6 +836,7 @@ exports[`Core System Prompt (prompts.ts) > should handle git instructions when i
- **Idiomatic Changes:** When editing, understand the local context (imports, functions/classes) to ensure your changes integrate naturally and idiomatically.
- **Comments:** Add code comments sparingly. Focus on *why* something is done, especially for complex logic, rather than *what* is done. Only add high-value comments if necessary for clarity or if requested by the user. Do not edit comments that are separate from the code you are changing. *NEVER* talk to the user or describe your changes through comments.
- **Proactiveness:** Fulfill the user's request thoroughly. When adding features or fixing bugs, this includes adding tests to ensure quality. Consider all created files, especially tests, to be permanent artifacts unless the user says otherwise.
- **User Hints:** During execution, the user may provide real-time hints (marked as "User hint:" or "User hints:"). Treat these as high-priority but scope-preserving course corrections: apply the minimal plan change needed, keep unaffected user tasks active, and never cancel/skip tasks unless cancellation is explicit for those tasks. Hints may add new tasks, modify one or more tasks, cancel specific tasks, or provide extra context only. If scope is ambiguous, ask for clarification before dropping work.
- **Confirm Ambiguity/Expansion:** Do not take significant actions beyond the clear scope of the request without confirming with the user. If the user implies a change (e.g., reports a bug) without explicitly asking for a fix, **ask for confirmation first**. If asked *how* to do something, explain first, don't just do it.
- **Explaining Changes:** After completing a code modification or file operation *do not* provide summaries unless asked.
- **Do Not revert changes:** Do not revert changes to the codebase unless asked to do so by the user. Only revert changes made by you if they have resulted in an error or if the user has explicitly asked you to revert the changes.
@@ -944,6 +949,7 @@ exports[`Core System Prompt (prompts.ts) > should handle git instructions when i
- **Idiomatic Changes:** When editing, understand the local context (imports, functions/classes) to ensure your changes integrate naturally and idiomatically.
- **Comments:** Add code comments sparingly. Focus on *why* something is done, especially for complex logic, rather than *what* is done. Only add high-value comments if necessary for clarity or if requested by the user. Do not edit comments that are separate from the code you are changing. *NEVER* talk to the user or describe your changes through comments.
- **Proactiveness:** Fulfill the user's request thoroughly. When adding features or fixing bugs, this includes adding tests to ensure quality. Consider all created files, especially tests, to be permanent artifacts unless the user says otherwise.
- **User Hints:** During execution, the user may provide real-time hints (marked as "User hint:" or "User hints:"). Treat these as high-priority but scope-preserving course corrections: apply the minimal plan change needed, keep unaffected user tasks active, and never cancel/skip tasks unless cancellation is explicit for those tasks. Hints may add new tasks, modify one or more tasks, cancel specific tasks, or provide extra context only. If scope is ambiguous, ask for clarification before dropping work.
- **Confirm Ambiguity/Expansion:** Do not take significant actions beyond the clear scope of the request without confirming with the user. If the user implies a change (e.g., reports a bug) without explicitly asking for a fix, **ask for confirmation first**. If asked *how* to do something, explain first, don't just do it.
- **Explaining Changes:** After completing a code modification or file operation *do not* provide summaries unless asked.
- **Do Not revert changes:** Do not revert changes to the codebase unless asked to do so by the user. Only revert changes made by you if they have resulted in an error or if the user has explicitly asked you to revert the changes.
@@ -1074,6 +1080,7 @@ exports[`Core System Prompt (prompts.ts) > should include approved plan instruct
- **Idiomatic Changes:** When editing, understand the local context (imports, functions/classes) to ensure your changes integrate naturally and idiomatically.
- **Comments:** Add code comments sparingly. Focus on *why* something is done, especially for complex logic, rather than *what* is done. Only add high-value comments if necessary for clarity or if requested by the user. Do not edit comments that are separate from the code you are changing. *NEVER* talk to the user or describe your changes through comments.
- **Proactiveness:** Fulfill the user's request thoroughly. When adding features or fixing bugs, this includes adding tests to ensure quality. Consider all created files, especially tests, to be permanent artifacts unless the user says otherwise.
- **User Hints:** During execution, the user may provide real-time hints (marked as "User hint:" or "User hints:"). Treat these as high-priority but scope-preserving course corrections: apply the minimal plan change needed, keep unaffected user tasks active, and never cancel/skip tasks unless cancellation is explicit for those tasks. Hints may add new tasks, modify one or more tasks, cancel specific tasks, or provide extra context only. If scope is ambiguous, ask for clarification before dropping work.
- **Confirm Ambiguity/Expansion:** Do not take significant actions beyond the clear scope of the request without confirming with the user. If the user implies a change (e.g., reports a bug) without explicitly asking for a fix, **ask for confirmation first**. If asked *how* to do something, explain first, don't just do it.
- **Explaining Changes:** After completing a code modification or file operation *do not* provide summaries unless asked.
- **Do Not revert changes:** Do not revert changes to the codebase unless asked to do so by the user. Only revert changes made by you if they have resulted in an error or if the user has explicitly asked you to revert the changes.
@@ -1176,6 +1183,7 @@ exports[`Core System Prompt (prompts.ts) > should include available_skills when
- **Idiomatic Changes:** When editing, understand the local context (imports, functions/classes) to ensure your changes integrate naturally and idiomatically.
- **Comments:** Add code comments sparingly. Focus on *why* something is done, especially for complex logic, rather than *what* is done. Only add high-value comments if necessary for clarity or if requested by the user. Do not edit comments that are separate from the code you are changing. *NEVER* talk to the user or describe your changes through comments.
- **Proactiveness:** Fulfill the user's request thoroughly. When adding features or fixing bugs, this includes adding tests to ensure quality. Consider all created files, especially tests, to be permanent artifacts unless the user says otherwise.
- **User Hints:** During execution, the user may provide real-time hints (marked as "User hint:" or "User hints:"). Treat these as high-priority but scope-preserving course corrections: apply the minimal plan change needed, keep unaffected user tasks active, and never cancel/skip tasks unless cancellation is explicit for those tasks. Hints may add new tasks, modify one or more tasks, cancel specific tasks, or provide extra context only. If scope is ambiguous, ask for clarification before dropping work.
- **Confirm Ambiguity/Expansion:** Do not take significant actions beyond the clear scope of the request without confirming with the user. If the user implies a change (e.g., reports a bug) without explicitly asking for a fix, **ask for confirmation first**. If asked *how* to do something, explain first, don't just do it.
- **Explaining Changes:** After completing a code modification or file operation *do not* provide summaries unless asked.
- **Do Not revert changes:** Do not revert changes to the codebase unless asked to do so by the user. Only revert changes made by you if they have resulted in an error or if the user has explicitly asked you to revert the changes.
@@ -1306,7 +1314,7 @@ exports[`Core System Prompt (prompts.ts) > should include correct sandbox instru
- **Technical Integrity:** You are responsible for the entire lifecycle: implementation, testing, and validation. Within the scope of your changes, prioritize readability and long-term maintainability by consolidating logic into clean abstractions rather than threading state across unrelated layers. Align strictly with the requested architectural direction, ensuring the final implementation is focused and free of redundant "just-in-case" alternatives. Validation is not merely running tests; it is the exhaustive process of ensuring that every aspect of your change—behavioral, structural, and stylistic—is correct and fully compatible with the broader project. For bug fixes, you must empirically reproduce the failure with a new test case or reproduction script before applying the fix.
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a corresponding Directive is issued. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. For Directives, only clarify if critically underspecified; otherwise, work autonomously. You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.
- **Proactiveness:** When executing a Directive, persist through errors and obstacles by diagnosing failures in the execution phase and, if necessary, backtracking to the research or strategy phases to adjust your approach until a successful, verified outcome is achieved. Fulfill the user's request thoroughly, including adding tests when adding features or fixing bugs. Take reasonable liberties to fulfill broad goals while staying within the requested scope; however, prioritize simplicity and the removal of redundant logic over providing "just-in-case" alternatives that diverge from the established path.
- **Testing:** ALWAYS search for and update related tests after making a code change. You must add a new test case to the existing test file (if one exists) or create a new test file to verify your changes.
- **User Hints:** During execution, the user may provide real-time hints (marked as "User hint:" or "User hints:"). Treat these as high-priority but scope-preserving course corrections: apply the minimal plan change needed, keep unaffected user tasks active, and never cancel/skip tasks unless cancellation is explicit for those tasks. Hints may add new tasks, modify one or more tasks, cancel specific tasks, or provide extra context only. If scope is ambiguous, ask for clarification before dropping work.
- **Confirm Ambiguity/Expansion:** Do not take significant actions beyond the clear scope of the request without confirming with the user. If the user implies a change (e.g., reports a bug) without explicitly asking for a fix, **ask for confirmation first**. If asked *how* to do something, explain first, don't just do it.
- **Explaining Changes:** After completing a code modification or file operation *do not* provide summaries unless asked.
- **Do Not revert changes:** Do not revert changes to the codebase unless asked to do so by the user. Only revert changes made by you if they have resulted in an error or if the user has explicitly asked you to revert the changes.
@@ -1420,7 +1428,7 @@ exports[`Core System Prompt (prompts.ts) > should include correct sandbox instru
- **Technical Integrity:** You are responsible for the entire lifecycle: implementation, testing, and validation. Within the scope of your changes, prioritize readability and long-term maintainability by consolidating logic into clean abstractions rather than threading state across unrelated layers. Align strictly with the requested architectural direction, ensuring the final implementation is focused and free of redundant "just-in-case" alternatives. Validation is not merely running tests; it is the exhaustive process of ensuring that every aspect of your change—behavioral, structural, and stylistic—is correct and fully compatible with the broader project. For bug fixes, you must empirically reproduce the failure with a new test case or reproduction script before applying the fix.
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a corresponding Directive is issued. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. For Directives, only clarify if critically underspecified; otherwise, work autonomously. You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.
- **Proactiveness:** When executing a Directive, persist through errors and obstacles by diagnosing failures in the execution phase and, if necessary, backtracking to the research or strategy phases to adjust your approach until a successful, verified outcome is achieved. Fulfill the user's request thoroughly, including adding tests when adding features or fixing bugs. Take reasonable liberties to fulfill broad goals while staying within the requested scope; however, prioritize simplicity and the removal of redundant logic over providing "just-in-case" alternatives that diverge from the established path.
- **Testing:** ALWAYS search for and update related tests after making a code change. You must add a new test case to the existing test file (if one exists) or create a new test file to verify your changes.
- **User Hints:** During execution, the user may provide real-time hints (marked as "User hint:" or "User hints:"). Treat these as high-priority but scope-preserving course corrections: apply the minimal plan change needed, keep unaffected user tasks active, and never cancel/skip tasks unless cancellation is explicit for those tasks. Hints may add new tasks, modify one or more tasks, cancel specific tasks, or provide extra context only. If scope is ambiguous, ask for clarification before dropping work.
- **Confirm Ambiguity/Expansion:** Do not take significant actions beyond the clear scope of the request without confirming with the user. If the user implies a change (e.g., reports a bug) without explicitly asking for a fix, **ask for confirmation first**. If asked *how* to do something, explain first, don't just do it.
- **Explaining Changes:** After completing a code modification or file operation *do not* provide summaries unless asked.
- **Do Not revert changes:** Do not revert changes to the codebase unless asked to do so by the user. Only revert changes made by you if they have resulted in an error or if the user has explicitly asked you to revert the changes.
@@ -1534,7 +1542,7 @@ exports[`Core System Prompt (prompts.ts) > should include correct sandbox instru
- **Technical Integrity:** You are responsible for the entire lifecycle: implementation, testing, and validation. Within the scope of your changes, prioritize readability and long-term maintainability by consolidating logic into clean abstractions rather than threading state across unrelated layers. Align strictly with the requested architectural direction, ensuring the final implementation is focused and free of redundant "just-in-case" alternatives. Validation is not merely running tests; it is the exhaustive process of ensuring that every aspect of your change—behavioral, structural, and stylistic—is correct and fully compatible with the broader project. For bug fixes, you must empirically reproduce the failure with a new test case or reproduction script before applying the fix.
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a corresponding Directive is issued. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. For Directives, only clarify if critically underspecified; otherwise, work autonomously. You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.
- **Proactiveness:** When executing a Directive, persist through errors and obstacles by diagnosing failures in the execution phase and, if necessary, backtracking to the research or strategy phases to adjust your approach until a successful, verified outcome is achieved. Fulfill the user's request thoroughly, including adding tests when adding features or fixing bugs. Take reasonable liberties to fulfill broad goals while staying within the requested scope; however, prioritize simplicity and the removal of redundant logic over providing "just-in-case" alternatives that diverge from the established path.
- **Testing:** ALWAYS search for and update related tests after making a code change. You must add a new test case to the existing test file (if one exists) or create a new test file to verify your changes.
- **User Hints:** During execution, the user may provide real-time hints (marked as "User hint:" or "User hints:"). Treat these as high-priority but scope-preserving course corrections: apply the minimal plan change needed, keep unaffected user tasks active, and never cancel/skip tasks unless cancellation is explicit for those tasks. Hints may add new tasks, modify one or more tasks, cancel specific tasks, or provide extra context only. If scope is ambiguous, ask for clarification before dropping work.
- **Confirm Ambiguity/Expansion:** Do not take significant actions beyond the clear scope of the request without confirming with the user. If the user implies a change (e.g., reports a bug) without explicitly asking for a fix, **ask for confirmation first**. If asked *how* to do something, explain first, don't just do it.
- **Explaining Changes:** After completing a code modification or file operation *do not* provide summaries unless asked.
- **Do Not revert changes:** Do not revert changes to the codebase unless asked to do so by the user. Only revert changes made by you if they have resulted in an error or if the user has explicitly asked you to revert the changes.
@@ -1644,7 +1652,7 @@ exports[`Core System Prompt (prompts.ts) > should include planning phase suggest
- **Technical Integrity:** You are responsible for the entire lifecycle: implementation, testing, and validation. Within the scope of your changes, prioritize readability and long-term maintainability by consolidating logic into clean abstractions rather than threading state across unrelated layers. Align strictly with the requested architectural direction, ensuring the final implementation is focused and free of redundant "just-in-case" alternatives. Validation is not merely running tests; it is the exhaustive process of ensuring that every aspect of your change—behavioral, structural, and stylistic—is correct and fully compatible with the broader project. For bug fixes, you must empirically reproduce the failure with a new test case or reproduction script before applying the fix.
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a corresponding Directive is issued. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. For Directives, only clarify if critically underspecified; otherwise, work autonomously. You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.
- **Proactiveness:** When executing a Directive, persist through errors and obstacles by diagnosing failures in the execution phase and, if necessary, backtracking to the research or strategy phases to adjust your approach until a successful, verified outcome is achieved. Fulfill the user's request thoroughly, including adding tests when adding features or fixing bugs. Take reasonable liberties to fulfill broad goals while staying within the requested scope; however, prioritize simplicity and the removal of redundant logic over providing "just-in-case" alternatives that diverge from the established path.
- **Testing:** ALWAYS search for and update related tests after making a code change. You must add a new test case to the existing test file (if one exists) or create a new test file to verify your changes.
- **User Hints:** During execution, the user may provide real-time hints (marked as "User hint:" or "User hints:"). Treat these as high-priority but scope-preserving course corrections: apply the minimal plan change needed, keep unaffected user tasks active, and never cancel/skip tasks unless cancellation is explicit for those tasks. Hints may add new tasks, modify one or more tasks, cancel specific tasks, or provide extra context only. If scope is ambiguous, ask for clarification before dropping work.
- **Confirm Ambiguity/Expansion:** Do not take significant actions beyond the clear scope of the request without confirming with the user. If the user implies a change (e.g., reports a bug) without explicitly asking for a fix, **ask for confirmation first**. If asked *how* to do something, explain first, don't just do it.
- **Explaining Changes:** After completing a code modification or file operation *do not* provide summaries unless asked.
- **Do Not revert changes:** Do not revert changes to the codebase unless asked to do so by the user. Only revert changes made by you if they have resulted in an error or if the user has explicitly asked you to revert the changes.
@@ -1754,7 +1762,7 @@ exports[`Core System Prompt (prompts.ts) > should include sub-agents in XML for
- **Technical Integrity:** You are responsible for the entire lifecycle: implementation, testing, and validation. Within the scope of your changes, prioritize readability and long-term maintainability by consolidating logic into clean abstractions rather than threading state across unrelated layers. Align strictly with the requested architectural direction, ensuring the final implementation is focused and free of redundant "just-in-case" alternatives. Validation is not merely running tests; it is the exhaustive process of ensuring that every aspect of your change—behavioral, structural, and stylistic—is correct and fully compatible with the broader project. For bug fixes, you must empirically reproduce the failure with a new test case or reproduction script before applying the fix.
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a corresponding Directive is issued. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. For Directives, only clarify if critically underspecified; otherwise, work autonomously. You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.
- **Proactiveness:** When executing a Directive, persist through errors and obstacles by diagnosing failures in the execution phase and, if necessary, backtracking to the research or strategy phases to adjust your approach until a successful, verified outcome is achieved. Fulfill the user's request thoroughly, including adding tests when adding features or fixing bugs. Take reasonable liberties to fulfill broad goals while staying within the requested scope; however, prioritize simplicity and the removal of redundant logic over providing "just-in-case" alternatives that diverge from the established path.
- **Testing:** ALWAYS search for and update related tests after making a code change. You must add a new test case to the existing test file (if one exists) or create a new test file to verify your changes.
- **User Hints:** During execution, the user may provide real-time hints (marked as "User hint:" or "User hints:"). Treat these as high-priority but scope-preserving course corrections: apply the minimal plan change needed, keep unaffected user tasks active, and never cancel/skip tasks unless cancellation is explicit for those tasks. Hints may add new tasks, modify one or more tasks, cancel specific tasks, or provide extra context only. If scope is ambiguous, ask for clarification before dropping work.
- **Confirm Ambiguity/Expansion:** Do not take significant actions beyond the clear scope of the request without confirming with the user. If the user implies a change (e.g., reports a bug) without explicitly asking for a fix, **ask for confirmation first**. If asked *how* to do something, explain first, don't just do it.
- **Explaining Changes:** After completing a code modification or file operation *do not* provide summaries unless asked.
- **Do Not revert changes:** Do not revert changes to the codebase unless asked to do so by the user. Only revert changes made by you if they have resulted in an error or if the user has explicitly asked you to revert the changes.
@@ -1859,6 +1867,7 @@ exports[`Core System Prompt (prompts.ts) > should match snapshot on Windows 1`]
- **Idiomatic Changes:** When editing, understand the local context (imports, functions/classes) to ensure your changes integrate naturally and idiomatically.
- **Comments:** Add code comments sparingly. Focus on *why* something is done, especially for complex logic, rather than *what* is done. Only add high-value comments if necessary for clarity or if requested by the user. Do not edit comments that are separate from the code you are changing. *NEVER* talk to the user or describe your changes through comments.
- **Proactiveness:** Fulfill the user's request thoroughly. When adding features or fixing bugs, this includes adding tests to ensure quality. Consider all created files, especially tests, to be permanent artifacts unless the user says otherwise.
- **User Hints:** During execution, the user may provide real-time hints (marked as "User hint:" or "User hints:"). Treat these as high-priority but scope-preserving course corrections: apply the minimal plan change needed, keep unaffected user tasks active, and never cancel/skip tasks unless cancellation is explicit for those tasks. Hints may add new tasks, modify one or more tasks, cancel specific tasks, or provide extra context only. If scope is ambiguous, ask for clarification before dropping work.
- **Confirm Ambiguity/Expansion:** Do not take significant actions beyond the clear scope of the request without confirming with the user. If the user implies a change (e.g., reports a bug) without explicitly asking for a fix, **ask for confirmation first**. If asked *how* to do something, explain first, don't just do it.
- **Explaining Changes:** After completing a code modification or file operation *do not* provide summaries unless asked.
- **Do Not revert changes:** Do not revert changes to the codebase unless asked to do so by the user. Only revert changes made by you if they have resulted in an error or if the user has explicitly asked you to revert the changes.
@@ -1972,6 +1981,7 @@ exports[`Core System Prompt (prompts.ts) > should render hierarchical memory wit
- **Comments:** Add code comments sparingly. Focus on *why* something is done, especially for complex logic, rather than *what* is done. Only add high-value comments if necessary for clarity or if requested by the user. Do not edit comments that are separate from the code you are changing. *NEVER* talk to the user or describe your changes through comments.
- **Proactiveness:** Fulfill the user's request thoroughly. When adding features or fixing bugs, this includes adding tests to ensure quality. Consider all created files, especially tests, to be permanent artifacts unless the user says otherwise.
- **Conflict Resolution:** Instructions are provided in hierarchical context tags: \`<global_context>\`, \`<extension_context>\`, and \`<project_context>\`. In case of contradictory instructions, follow this priority: \`<project_context>\` (highest) > \`<extension_context>\` > \`<global_context>\` (lowest).
- **User Hints:** During execution, the user may provide real-time hints (marked as "User hint:" or "User hints:"). Treat these as high-priority but scope-preserving course corrections: apply the minimal plan change needed, keep unaffected user tasks active, and never cancel/skip tasks unless cancellation is explicit for those tasks. Hints may add new tasks, modify one or more tasks, cancel specific tasks, or provide extra context only. If scope is ambiguous, ask for clarification before dropping work.
- **Confirm Ambiguity/Expansion:** Do not take significant actions beyond the clear scope of the request without confirming with the user. If the user implies a change (e.g., reports a bug) without explicitly asking for a fix, **ask for confirmation first**. If asked *how* to do something, explain first, don't just do it.
- **Explaining Changes:** After completing a code modification or file operation *do not* provide summaries unless asked.
- **Do Not revert changes:** Do not revert changes to the codebase unless asked to do so by the user. Only revert changes made by you if they have resulted in an error or if the user has explicitly asked you to revert the changes.
@@ -2103,7 +2113,7 @@ exports[`Core System Prompt (prompts.ts) > should return the base prompt when us
- **Technical Integrity:** You are responsible for the entire lifecycle: implementation, testing, and validation. Within the scope of your changes, prioritize readability and long-term maintainability by consolidating logic into clean abstractions rather than threading state across unrelated layers. Align strictly with the requested architectural direction, ensuring the final implementation is focused and free of redundant "just-in-case" alternatives. Validation is not merely running tests; it is the exhaustive process of ensuring that every aspect of your change—behavioral, structural, and stylistic—is correct and fully compatible with the broader project. For bug fixes, you must empirically reproduce the failure with a new test case or reproduction script before applying the fix.
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a corresponding Directive is issued. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. For Directives, only clarify if critically underspecified; otherwise, work autonomously. You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.
- **Proactiveness:** When executing a Directive, persist through errors and obstacles by diagnosing failures in the execution phase and, if necessary, backtracking to the research or strategy phases to adjust your approach until a successful, verified outcome is achieved. Fulfill the user's request thoroughly, including adding tests when adding features or fixing bugs. Take reasonable liberties to fulfill broad goals while staying within the requested scope; however, prioritize simplicity and the removal of redundant logic over providing "just-in-case" alternatives that diverge from the established path.
- **Testing:** ALWAYS search for and update related tests after making a code change. You must add a new test case to the existing test file (if one exists) or create a new test file to verify your changes.
- **User Hints:** During execution, the user may provide real-time hints (marked as "User hint:" or "User hints:"). Treat these as high-priority but scope-preserving course corrections: apply the minimal plan change needed, keep unaffected user tasks active, and never cancel/skip tasks unless cancellation is explicit for those tasks. Hints may add new tasks, modify one or more tasks, cancel specific tasks, or provide extra context only. If scope is ambiguous, ask for clarification before dropping work.
- **Confirm Ambiguity/Expansion:** Do not take significant actions beyond the clear scope of the request without confirming with the user. If the user implies a change (e.g., reports a bug) without explicitly asking for a fix, **ask for confirmation first**. If asked *how* to do something, explain first, don't just do it.
- **Explaining Changes:** After completing a code modification or file operation *do not* provide summaries unless asked.
- **Do Not revert changes:** Do not revert changes to the codebase unless asked to do so by the user. Only revert changes made by you if they have resulted in an error or if the user has explicitly asked you to revert the changes.
@@ -2213,7 +2223,7 @@ exports[`Core System Prompt (prompts.ts) > should return the base prompt when us
- **Technical Integrity:** You are responsible for the entire lifecycle: implementation, testing, and validation. Within the scope of your changes, prioritize readability and long-term maintainability by consolidating logic into clean abstractions rather than threading state across unrelated layers. Align strictly with the requested architectural direction, ensuring the final implementation is focused and free of redundant "just-in-case" alternatives. Validation is not merely running tests; it is the exhaustive process of ensuring that every aspect of your change—behavioral, structural, and stylistic—is correct and fully compatible with the broader project. For bug fixes, you must empirically reproduce the failure with a new test case or reproduction script before applying the fix.
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a corresponding Directive is issued. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. For Directives, only clarify if critically underspecified; otherwise, work autonomously. You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.
- **Proactiveness:** When executing a Directive, persist through errors and obstacles by diagnosing failures in the execution phase and, if necessary, backtracking to the research or strategy phases to adjust your approach until a successful, verified outcome is achieved. Fulfill the user's request thoroughly, including adding tests when adding features or fixing bugs. Take reasonable liberties to fulfill broad goals while staying within the requested scope; however, prioritize simplicity and the removal of redundant logic over providing "just-in-case" alternatives that diverge from the established path.
- **Testing:** ALWAYS search for and update related tests after making a code change. You must add a new test case to the existing test file (if one exists) or create a new test file to verify your changes.
- **User Hints:** During execution, the user may provide real-time hints (marked as "User hint:" or "User hints:"). Treat these as high-priority but scope-preserving course corrections: apply the minimal plan change needed, keep unaffected user tasks active, and never cancel/skip tasks unless cancellation is explicit for those tasks. Hints may add new tasks, modify one or more tasks, cancel specific tasks, or provide extra context only. If scope is ambiguous, ask for clarification before dropping work.
- **Confirm Ambiguity/Expansion:** Do not take significant actions beyond the clear scope of the request without confirming with the user. If the user implies a change (e.g., reports a bug) without explicitly asking for a fix, **ask for confirmation first**. If asked *how* to do something, explain first, don't just do it.
- **Explaining Changes:** After completing a code modification or file operation *do not* provide summaries unless asked.
- **Do Not revert changes:** Do not revert changes to the codebase unless asked to do so by the user. Only revert changes made by you if they have resulted in an error or if the user has explicitly asked you to revert the changes.
@@ -2318,6 +2328,7 @@ exports[`Core System Prompt (prompts.ts) > should return the interactive avoidan
- **Idiomatic Changes:** When editing, understand the local context (imports, functions/classes) to ensure your changes integrate naturally and idiomatically.
- **Comments:** Add code comments sparingly. Focus on *why* something is done, especially for complex logic, rather than *what* is done. Only add high-value comments if necessary for clarity or if requested by the user. Do not edit comments that are separate from the code you are changing. *NEVER* talk to the user or describe your changes through comments.
- **Proactiveness:** Fulfill the user's request thoroughly. When adding features or fixing bugs, this includes adding tests to ensure quality. Consider all created files, especially tests, to be permanent artifacts unless the user says otherwise.
- **User Hints:** During execution, the user may provide real-time hints (marked as "User hint:" or "User hints:"). Treat these as high-priority but scope-preserving course corrections: apply the minimal plan change needed, keep unaffected user tasks active, and never cancel/skip tasks unless cancellation is explicit for those tasks. Hints may add new tasks, modify one or more tasks, cancel specific tasks, or provide extra context only. If scope is ambiguous, ask for clarification before dropping work.
- **Handle Ambiguity/Expansion:** Do not take significant actions beyond the clear scope of the request. If the user implies a change (e.g., reports a bug) without explicitly asking for a fix, do not perform it automatically.
- **Explaining Changes:** After completing a code modification or file operation *do not* provide summaries unless asked.
- **Do Not revert changes:** Do not revert changes to the codebase unless asked to do so by the user. Only revert changes made by you if they have resulted in an error or if the user has explicitly asked you to revert the changes.
@@ -2434,7 +2445,7 @@ exports[`Core System Prompt (prompts.ts) > should use chatty system prompt for p
- **Technical Integrity:** You are responsible for the entire lifecycle: implementation, testing, and validation. Within the scope of your changes, prioritize readability and long-term maintainability by consolidating logic into clean abstractions rather than threading state across unrelated layers. Align strictly with the requested architectural direction, ensuring the final implementation is focused and free of redundant "just-in-case" alternatives. Validation is not merely running tests; it is the exhaustive process of ensuring that every aspect of your change—behavioral, structural, and stylistic—is correct and fully compatible with the broader project. For bug fixes, you must empirically reproduce the failure with a new test case or reproduction script before applying the fix.
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a corresponding Directive is issued. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. For Directives, only clarify if critically underspecified; otherwise, work autonomously. You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.
- **Proactiveness:** When executing a Directive, persist through errors and obstacles by diagnosing failures in the execution phase and, if necessary, backtracking to the research or strategy phases to adjust your approach until a successful, verified outcome is achieved. Fulfill the user's request thoroughly, including adding tests when adding features or fixing bugs. Take reasonable liberties to fulfill broad goals while staying within the requested scope; however, prioritize simplicity and the removal of redundant logic over providing "just-in-case" alternatives that diverge from the established path.
- **Testing:** ALWAYS search for and update related tests after making a code change. You must add a new test case to the existing test file (if one exists) or create a new test file to verify your changes.
- **User Hints:** During execution, the user may provide real-time hints (marked as "User hint:" or "User hints:"). Treat these as high-priority but scope-preserving course corrections: apply the minimal plan change needed, keep unaffected user tasks active, and never cancel/skip tasks unless cancellation is explicit for those tasks. Hints may add new tasks, modify one or more tasks, cancel specific tasks, or provide extra context only. If scope is ambiguous, ask for clarification before dropping work.
- **Confirm Ambiguity/Expansion:** Do not take significant actions beyond the clear scope of the request without confirming with the user. If the user implies a change (e.g., reports a bug) without explicitly asking for a fix, **ask for confirmation first**. If asked *how* to do something, explain first, don't just do it.
- **Explaining Changes:** After completing a code modification or file operation *do not* provide summaries unless asked.
- **Do Not revert changes:** Do not revert changes to the codebase unless asked to do so by the user. Only revert changes made by you if they have resulted in an error or if the user has explicitly asked you to revert the changes.
@@ -2544,7 +2555,7 @@ exports[`Core System Prompt (prompts.ts) > should use chatty system prompt for p
- **Technical Integrity:** You are responsible for the entire lifecycle: implementation, testing, and validation. Within the scope of your changes, prioritize readability and long-term maintainability by consolidating logic into clean abstractions rather than threading state across unrelated layers. Align strictly with the requested architectural direction, ensuring the final implementation is focused and free of redundant "just-in-case" alternatives. Validation is not merely running tests; it is the exhaustive process of ensuring that every aspect of your change—behavioral, structural, and stylistic—is correct and fully compatible with the broader project. For bug fixes, you must empirically reproduce the failure with a new test case or reproduction script before applying the fix.
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a corresponding Directive is issued. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. For Directives, only clarify if critically underspecified; otherwise, work autonomously. You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.
- **Proactiveness:** When executing a Directive, persist through errors and obstacles by diagnosing failures in the execution phase and, if necessary, backtracking to the research or strategy phases to adjust your approach until a successful, verified outcome is achieved. Fulfill the user's request thoroughly, including adding tests when adding features or fixing bugs. Take reasonable liberties to fulfill broad goals while staying within the requested scope; however, prioritize simplicity and the removal of redundant logic over providing "just-in-case" alternatives that diverge from the established path.
- **Testing:** ALWAYS search for and update related tests after making a code change. You must add a new test case to the existing test file (if one exists) or create a new test file to verify your changes.
- **User Hints:** During execution, the user may provide real-time hints (marked as "User hint:" or "User hints:"). Treat these as high-priority but scope-preserving course corrections: apply the minimal plan change needed, keep unaffected user tasks active, and never cancel/skip tasks unless cancellation is explicit for those tasks. Hints may add new tasks, modify one or more tasks, cancel specific tasks, or provide extra context only. If scope is ambiguous, ask for clarification before dropping work.
- **Confirm Ambiguity/Expansion:** Do not take significant actions beyond the clear scope of the request without confirming with the user. If the user implies a change (e.g., reports a bug) without explicitly asking for a fix, **ask for confirmation first**. If asked *how* to do something, explain first, don't just do it.
- **Explaining Changes:** After completing a code modification or file operation *do not* provide summaries unless asked.
- **Do Not revert changes:** Do not revert changes to the codebase unless asked to do so by the user. Only revert changes made by you if they have resulted in an error or if the user has explicitly asked you to revert the changes.
@@ -2649,6 +2660,7 @@ exports[`Core System Prompt (prompts.ts) > should use legacy system prompt for n
- **Idiomatic Changes:** When editing, understand the local context (imports, functions/classes) to ensure your changes integrate naturally and idiomatically.
- **Comments:** Add code comments sparingly. Focus on *why* something is done, especially for complex logic, rather than *what* is done. Only add high-value comments if necessary for clarity or if requested by the user. Do not edit comments that are separate from the code you are changing. *NEVER* talk to the user or describe your changes through comments.
- **Proactiveness:** Fulfill the user's request thoroughly. When adding features or fixing bugs, this includes adding tests to ensure quality. Consider all created files, especially tests, to be permanent artifacts unless the user says otherwise.
- **User Hints:** During execution, the user may provide real-time hints (marked as "User hint:" or "User hints:"). Treat these as high-priority but scope-preserving course corrections: apply the minimal plan change needed, keep unaffected user tasks active, and never cancel/skip tasks unless cancellation is explicit for those tasks. Hints may add new tasks, modify one or more tasks, cancel specific tasks, or provide extra context only. If scope is ambiguous, ask for clarification before dropping work.
- **Confirm Ambiguity/Expansion:** Do not take significant actions beyond the clear scope of the request without confirming with the user. If the user implies a change (e.g., reports a bug) without explicitly asking for a fix, **ask for confirmation first**. If asked *how* to do something, explain first, don't just do it.
- **Explaining Changes:** After completing a code modification or file operation *do not* provide summaries unless asked.
- **Do Not revert changes:** Do not revert changes to the codebase unless asked to do so by the user. Only revert changes made by you if they have resulted in an error or if the user has explicitly asked you to revert the changes.
+2
View File
@@ -198,6 +198,7 @@ describe('Core System Prompt (prompts.ts)', () => {
expect(prompt).not.toContain('No sub-agents are currently available.');
expect(prompt).toContain('# Core Mandates');
expect(prompt).toContain('- **Conventions:**');
expect(prompt).toContain('- **User Hints:**');
expect(prompt).toContain('# Outside of Sandbox');
expect(prompt).toContain('# Final Reminder');
expect(prompt).toMatchSnapshot();
@@ -207,6 +208,7 @@ describe('Core System Prompt (prompts.ts)', () => {
vi.mocked(mockConfig.getActiveModel).mockReturnValue(PREVIEW_GEMINI_MODEL);
const prompt = getCoreSystemPrompt(mockConfig);
expect(prompt).toContain('You are Gemini CLI, an interactive CLI agent'); // Check for core content
expect(prompt).toContain('- **User Hints:**');
expect(prompt).toContain('No Chitchat:');
expect(prompt).toMatchSnapshot();
});
+1
View File
@@ -88,6 +88,7 @@ export * from './utils/formatters.js';
export * from './utils/generateContentResponseUtilities.js';
export * from './utils/filesearch/fileSearch.js';
export * from './utils/errorParsing.js';
export * from './utils/flashLiteHelper.js';
export * from './utils/workspaceContext.js';
export * from './utils/environmentContext.js';
export * from './utils/ignorePatterns.js';
@@ -159,6 +159,7 @@ export function renderCoreMandates(options?: CoreMandatesOptions): string {
- **Idiomatic Changes:** When editing, understand the local context (imports, functions/classes) to ensure your changes integrate naturally and idiomatically.
- **Comments:** Add code comments sparingly. Focus on *why* something is done, especially for complex logic, rather than *what* is done. Only add high-value comments if necessary for clarity or if requested by the user. Do not edit comments that are separate from the code you are changing. *NEVER* talk to the user or describe your changes through comments.
- **Proactiveness:** Fulfill the user's request thoroughly. When adding features or fixing bugs, this includes adding tests to ensure quality. Consider all created files, especially tests, to be permanent artifacts unless the user says otherwise.${mandateConflictResolution(options.hasHierarchicalMemory)}
- **User Hints:** During execution, the user may provide real-time hints (marked as "User hint:" or "User hints:"). Treat these as high-priority but scope-preserving course corrections: apply the minimal plan change needed, keep unaffected user tasks active, and never cancel/skip tasks unless cancellation is explicit for those tasks. Hints may add new tasks, modify one or more tasks, cancel specific tasks, or provide extra context only. If scope is ambiguous, ask for clarification before dropping work.
- ${mandateConfirm(options.interactive)}
- **Explaining Changes:** After completing a code modification or file operation *do not* provide summaries unless asked.
- **Do Not revert changes:** Do not revert changes to the codebase unless asked to do so by the user. Only revert changes made by you if they have resulted in an error or if the user has explicitly asked you to revert the changes.${mandateSkillGuidance(options.hasSkills)}${mandateExplainBeforeActing(options.isGemini3)}${mandateContinueWork(options.interactive)}
+2 -2
View File
@@ -170,8 +170,8 @@ export function renderCoreMandates(options?: CoreMandatesOptions): string {
- **Libraries/Frameworks:** NEVER assume a library/framework is available. Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', etc.) before employing it.
- **Technical Integrity:** You are responsible for the entire lifecycle: implementation, testing, and validation. Within the scope of your changes, prioritize readability and long-term maintainability by consolidating logic into clean abstractions rather than threading state across unrelated layers. Align strictly with the requested architectural direction, ensuring the final implementation is focused and free of redundant "just-in-case" alternatives. Validation is not merely running tests; it is the exhaustive process of ensuring that every aspect of your changebehavioral, structural, and stylisticis correct and fully compatible with the broader project. For bug fixes, you must empirically reproduce the failure with a new test case or reproduction script before applying the fix.
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a corresponding Directive is issued. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. ${options.interactive ? 'For Directives, only clarify if critically underspecified; otherwise, work autonomously.' : 'For Directives, you must work autonomously as no further user input is available.'} You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.
- **Proactiveness:** When executing a Directive, persist through errors and obstacles by diagnosing failures in the execution phase and, if necessary, backtracking to the research or strategy phases to adjust your approach until a successful, verified outcome is achieved. Fulfill the user's request thoroughly, including adding tests when adding features or fixing bugs. Take reasonable liberties to fulfill broad goals while staying within the requested scope; however, prioritize simplicity and the removal of redundant logic over providing "just-in-case" alternatives that diverge from the established path.
- **Testing:** ALWAYS search for and update related tests after making a code change. You must add a new test case to the existing test file (if one exists) or create a new test file to verify your changes.${mandateConflictResolution(options.hasHierarchicalMemory)}
- **Proactiveness:** When executing a Directive, persist through errors and obstacles by diagnosing failures in the execution phase and, if necessary, backtracking to the research or strategy phases to adjust your approach until a successful, verified outcome is achieved. Fulfill the user's request thoroughly, including adding tests when adding features or fixing bugs. Take reasonable liberties to fulfill broad goals while staying within the requested scope; however, prioritize simplicity and the removal of redundant logic over providing "just-in-case" alternatives that diverge from the established path.${mandateConflictResolution(options.hasHierarchicalMemory)}
- **User Hints:** During execution, the user may provide real-time hints (marked as "User hint:" or "User hints:"). Treat these as high-priority but scope-preserving course corrections: apply the minimal plan change needed, keep unaffected user tasks active, and never cancel/skip tasks unless cancellation is explicit for those tasks. Hints may add new tasks, modify one or more tasks, cancel specific tasks, or provide extra context only. If scope is ambiguous, ask for clarification before dropping work.
- ${mandateConfirm(options.interactive)}
- **Explaining Changes:** After completing a code modification or file operation *do not* provide summaries unless asked.
- **Do Not revert changes:** Do not revert changes to the codebase unless asked to do so by the user. Only revert changes made by you if they have resulted in an error or if the user has explicitly asked you to revert the changes.${mandateSkillGuidance(options.hasSkills)}
@@ -46,6 +46,9 @@ describe('sanitizeEnvironment', () => {
CLIENT_ID: 'sensitive-id',
DB_URI: 'sensitive-uri',
DATABASE_URL: 'sensitive-url',
GEMINI_API_KEY: 'sensitive-gemini-key',
GOOGLE_API_KEY: 'sensitive-google-key',
GOOGLE_APPLICATION_CREDENTIALS: '/path/to/creds.json',
SAFE_VAR: 'is-safe',
};
const sanitized = sanitizeEnvironment(env, EMPTY_OPTIONS);
@@ -103,6 +103,9 @@ export const NEVER_ALLOWED_ENVIRONMENT_VARIABLES: ReadonlySet<string> = new Set(
'GOOGLE_CLOUD_PROJECT',
'GOOGLE_CLOUD_ACCOUNT',
'FIREBASE_PROJECT_ID',
'GEMINI_API_KEY',
'GOOGLE_API_KEY',
'GOOGLE_APPLICATION_CREDENTIALS',
],
);
@@ -126,6 +126,17 @@
}
}
},
"flash-lite-helper": {
"model": "gemini-2.5-flash-lite",
"generateContentConfig": {
"temperature": 0.2,
"topP": 1,
"maxOutputTokens": 120,
"thinkingConfig": {
"thinkingBudget": 0
}
}
},
"edit-corrector": {
"model": "gemini-2.5-flash-lite",
"generateContentConfig": {
@@ -126,6 +126,17 @@
}
}
},
"flash-lite-helper": {
"model": "gemini-2.5-flash-lite",
"generateContentConfig": {
"temperature": 0.2,
"topP": 1,
"maxOutputTokens": 120,
"thinkingConfig": {
"thinkingBudget": 0
}
}
},
"edit-corrector": {
"model": "gemini-2.5-flash-lite",
"generateContentConfig": {
+2 -51
View File
@@ -187,7 +187,7 @@ describe('loggers', () => {
});
describe('logCliConfiguration', () => {
it('should log the cli configuration', async () => {
it('should log the cli configuration', () => {
const mockConfig = {
getSessionId: () => 'test-session-id',
getModel: () => 'test-model',
@@ -226,14 +226,11 @@ describe('loggers', () => {
}),
}),
isInteractive: () => false,
getExperiments: () => undefined,
getExperimentsAsync: async () => undefined,
} as unknown as Config;
const startSessionEvent = new StartSessionEvent(mockConfig);
logCliConfiguration(mockConfig, startSessionEvent);
await new Promise(process.nextTick);
expect(mockLogger.emit).toHaveBeenCalledWith({
body: 'CLI configuration loaded.',
attributes: {
@@ -274,8 +271,6 @@ describe('loggers', () => {
getTelemetryLogPromptsEnabled: () => true,
getUsageStatisticsEnabled: () => true,
isInteractive: () => false,
getExperiments: () => undefined,
getExperimentsAsync: async () => undefined,
} as unknown as Config;
it('should log a user prompt', () => {
@@ -313,8 +308,6 @@ describe('loggers', () => {
getTargetDir: () => 'target-dir',
getUsageStatisticsEnabled: () => true,
isInteractive: () => false,
getExperiments: () => undefined,
getExperimentsAsync: async () => undefined,
} as unknown as Config;
const event = new UserPromptEvent(
11,
@@ -350,8 +343,6 @@ describe('loggers', () => {
getTelemetryEnabled: () => true,
getTelemetryLogPromptsEnabled: () => true,
isInteractive: () => false,
getExperiments: () => undefined,
getExperimentsAsync: async () => undefined,
} as Config;
const mockMetrics = {
@@ -528,8 +519,6 @@ describe('loggers', () => {
getTelemetryEnabled: () => true,
getTelemetryLogPromptsEnabled: () => true,
isInteractive: () => false,
getExperiments: () => undefined,
getExperimentsAsync: async () => undefined,
} as Config;
const mockMetrics = {
@@ -662,8 +651,6 @@ describe('loggers', () => {
getTelemetryEnabled: () => true,
getTelemetryLogPromptsEnabled: () => true,
isInteractive: () => false,
getExperiments: () => undefined,
getExperimentsAsync: async () => undefined,
getContentGeneratorConfig: () => ({
authType: AuthType.LOGIN_WITH_GOOGLE,
}),
@@ -740,8 +727,6 @@ describe('loggers', () => {
getTelemetryEnabled: () => true,
getTelemetryLogPromptsEnabled: () => true, // Enabled
isInteractive: () => false,
getExperiments: () => undefined,
getExperimentsAsync: async () => undefined,
getContentGeneratorConfig: () => ({
authType: AuthType.USE_GEMINI,
}),
@@ -829,8 +814,6 @@ describe('loggers', () => {
getTelemetryEnabled: () => true,
getTelemetryLogPromptsEnabled: () => false, // Disabled
isInteractive: () => false,
getExperiments: () => undefined,
getExperimentsAsync: async () => undefined,
getContentGeneratorConfig: () => ({
authType: AuthType.USE_VERTEX_AI,
}),
@@ -884,8 +867,6 @@ describe('loggers', () => {
getTelemetryEnabled: () => true,
getTelemetryLogPromptsEnabled: () => true,
isInteractive: () => false,
getExperiments: () => undefined,
getExperimentsAsync: async () => undefined,
getUsageStatisticsEnabled: () => true,
getContentGeneratorConfig: () => ({
authType: AuthType.USE_GEMINI,
@@ -922,8 +903,6 @@ describe('loggers', () => {
getSessionId: () => 'test-session-id',
getUsageStatisticsEnabled: () => true,
isInteractive: () => false,
getExperiments: () => undefined,
getExperimentsAsync: async () => undefined,
} as unknown as Config;
it('should log flash fallback event', () => {
@@ -951,8 +930,6 @@ describe('loggers', () => {
getSessionId: () => 'test-session-id',
getUsageStatisticsEnabled: () => true,
isInteractive: () => false,
getExperiments: () => undefined,
getExperimentsAsync: async () => undefined,
} as unknown as Config;
beforeEach(() => {
@@ -1047,8 +1024,6 @@ describe('loggers', () => {
getTelemetryEnabled: () => true,
getTelemetryLogPromptsEnabled: () => true,
isInteractive: () => false,
getExperiments: () => undefined,
getExperimentsAsync: async () => undefined,
} as Config;
const mockMetrics = {
@@ -1620,8 +1595,6 @@ describe('loggers', () => {
getTelemetryEnabled: () => true,
getTelemetryLogPromptsEnabled: () => true,
isInteractive: () => false,
getExperiments: () => undefined,
getExperimentsAsync: async () => undefined,
} as Config;
const mockMetrics = {
@@ -1682,8 +1655,6 @@ describe('loggers', () => {
getSessionId: () => 'test-session-id',
getUsageStatisticsEnabled: () => true,
isInteractive: () => false,
getExperiments: () => undefined,
getExperimentsAsync: async () => undefined,
} as unknown as Config;
it('should log a tool output truncated event', () => {
@@ -1721,8 +1692,6 @@ describe('loggers', () => {
getSessionId: () => 'test-session-id',
getUsageStatisticsEnabled: () => true,
isInteractive: () => false,
getExperiments: () => undefined,
getExperimentsAsync: async () => undefined,
} as unknown as Config;
beforeEach(() => {
@@ -1823,8 +1792,6 @@ describe('loggers', () => {
getUsageStatisticsEnabled: () => true,
getContentGeneratorConfig: () => null,
isInteractive: () => false,
getExperiments: () => undefined,
getExperimentsAsync: async () => undefined,
} as unknown as Config;
beforeEach(() => {
@@ -1875,8 +1842,6 @@ describe('loggers', () => {
getUsageStatisticsEnabled: () => true,
getContentGeneratorConfig: () => null,
isInteractive: () => false,
getExperiments: () => undefined,
getExperimentsAsync: async () => undefined,
} as unknown as Config;
beforeEach(() => {
@@ -1929,8 +1894,6 @@ describe('loggers', () => {
getUsageStatisticsEnabled: () => true,
getContentGeneratorConfig: () => null,
isInteractive: () => false,
getExperiments: () => undefined,
getExperimentsAsync: async () => undefined,
} as unknown as Config;
beforeEach(() => {
@@ -1975,8 +1938,6 @@ describe('loggers', () => {
getSessionId: () => 'test-session-id',
getUsageStatisticsEnabled: () => true,
isInteractive: () => false,
getExperiments: () => undefined,
getExperimentsAsync: async () => undefined,
} as unknown as Config;
beforeEach(() => {
@@ -2022,8 +1983,6 @@ describe('loggers', () => {
getSessionId: () => 'test-session-id',
getUsageStatisticsEnabled: () => true,
isInteractive: () => false,
getExperiments: () => undefined,
getExperimentsAsync: async () => undefined,
} as unknown as Config;
beforeEach(() => {
@@ -2069,8 +2028,6 @@ describe('loggers', () => {
getSessionId: () => 'test-session-id',
getUsageStatisticsEnabled: () => true,
isInteractive: () => false,
getExperiments: () => undefined,
getExperimentsAsync: async () => undefined,
} as unknown as Config;
beforeEach(() => {
@@ -2107,8 +2064,6 @@ describe('loggers', () => {
getSessionId: () => 'test-session-id',
getUsageStatisticsEnabled: () => true,
isInteractive: () => false,
getExperiments: () => undefined,
getExperimentsAsync: async () => undefined,
} as unknown as Config;
beforeEach(() => {
@@ -2160,8 +2115,6 @@ describe('loggers', () => {
getSessionId: () => 'test-session-id',
getUsageStatisticsEnabled: () => true,
isInteractive: () => false,
getExperiments: () => undefined,
getExperimentsAsync: async () => undefined,
} as unknown as Config;
beforeEach(() => {
@@ -2197,8 +2150,6 @@ describe('loggers', () => {
getSessionId: () => 'test-session-id',
getUsageStatisticsEnabled: () => true,
isInteractive: () => false,
getExperiments: () => undefined,
getExperimentsAsync: async () => undefined,
getTelemetryLogPromptsEnabled: () => false,
} as unknown as Config;
@@ -2254,7 +2205,7 @@ describe('loggers', () => {
});
describe('Telemetry Buffering', () => {
it('should buffer events when SDK is not initialized', async () => {
it('should buffer events when SDK is not initialized', () => {
vi.spyOn(sdk, 'isTelemetrySdkInitialized').mockReturnValue(false);
const bufferSpy = vi
.spyOn(sdk, 'bufferTelemetryEvent')
+12 -29
View File
@@ -81,7 +81,6 @@ import { bufferTelemetryEvent } from './sdk.js';
import type { UiEvent } from './uiTelemetry.js';
import { uiTelemetryService } from './uiTelemetry.js';
import { ClearcutLogger } from './clearcut-logger/clearcut-logger.js';
import { debugLogger } from '../utils/debugLogger.js';
export function logCliConfiguration(
config: Config,
@@ -89,20 +88,12 @@ export function logCliConfiguration(
): void {
void ClearcutLogger.getInstance(config)?.logStartSessionEvent(event);
bufferTelemetryEvent(() => {
// Wait for experiments to load before emitting so we capture experimentIds
void config
.getExperimentsAsync()
.then(() => {
const logger = logs.getLogger(SERVICE_NAME);
const logRecord: LogRecord = {
body: event.toLogBody(),
attributes: event.toOpenTelemetryAttributes(config),
};
logger.emit(logRecord);
})
.catch((e: unknown) => {
debugLogger.error('Failed to log telemetry event', e);
});
const logger = logs.getLogger(SERVICE_NAME);
const logRecord: LogRecord = {
body: event.toLogBody(),
attributes: event.toOpenTelemetryAttributes(config),
};
logger.emit(logRecord);
});
}
@@ -789,19 +780,11 @@ export function logStartupStats(
event: StartupStatsEvent,
): void {
bufferTelemetryEvent(() => {
// Wait for experiments to load before emitting so we capture experimentIds
void config
.getExperimentsAsync()
.then(() => {
const logger = logs.getLogger(SERVICE_NAME);
const logRecord: LogRecord = {
body: event.toLogBody(),
attributes: event.toOpenTelemetryAttributes(config),
};
logger.emit(logRecord);
})
.catch((e: unknown) => {
debugLogger.error('Failed to log telemetry event', e);
});
const logger = logs.getLogger(SERVICE_NAME);
const logRecord: LogRecord = {
body: event.toLogBody(),
attributes: event.toOpenTelemetryAttributes(config),
};
logger.emit(logRecord);
});
}
@@ -26,8 +26,6 @@ function createMockConfig(logPromptsEnabled: boolean): Config {
return {
getTelemetryLogPromptsEnabled: () => logPromptsEnabled,
getSessionId: () => 'test-session-id',
getExperiments: () => undefined,
getExperimentsAsync: async () => undefined,
getModel: () => 'gemini-1.5-flash',
isInteractive: () => true,
getUserEmail: () => undefined,
-3
View File
@@ -75,8 +75,6 @@ describe('Telemetry SDK', () => {
getSessionId: () => 'test-session',
getTelemetryUseCliAuth: () => false,
isInteractive: () => false,
getExperiments: () => undefined,
getExperimentsAsync: async () => undefined,
} as unknown as Config;
});
@@ -355,7 +353,6 @@ describe('Telemetry SDK', () => {
expect(callback).not.toHaveBeenCalled();
await initializeTelemetry(mockConfig);
await new Promise((resolve) => setTimeout(resolve, 10));
expect(callback).toHaveBeenCalled();
});
});
@@ -14,15 +14,10 @@ const installationManager = new InstallationManager();
export function getCommonAttributes(config: Config): Attributes {
const email = userAccountManager.getCachedGoogleAccount();
const experiments = config.getExperiments();
return {
'session.id': config.getSessionId(),
'installation.id': installationManager.getInstallationId(),
interactive: config.isInteractive(),
...(email && { 'user.email': email }),
...(experiments &&
experiments.experimentIds.length > 0 && {
'experiments.ids': experiments.experimentIds,
}),
};
}
+79 -10
View File
@@ -901,9 +901,9 @@ describe('mcp-client', () => {
vi.mocked(ClientLib.Client).mockReturnValue(
mockedClient as unknown as ClientLib.Client,
);
vi.spyOn(SdkClientStdioLib, 'StdioClientTransport').mockReturnValue(
{} as SdkClientStdioLib.StdioClientTransport,
);
vi.spyOn(SdkClientStdioLib, 'StdioClientTransport').mockReturnValue({
close: vi.fn(),
} as unknown as SdkClientStdioLib.StdioClientTransport);
const mockedToolRegistry = {
registerTool: vi.fn(),
unregisterTool: vi.fn(),
@@ -1623,7 +1623,7 @@ describe('mcp-client', () => {
{
command: 'test-command',
args: ['--foo', 'bar'],
env: { FOO: 'bar' },
env: { GEMINI_CLI_FOO: 'bar' },
cwd: 'test/cwd',
},
false,
@@ -1634,11 +1634,80 @@ describe('mcp-client', () => {
command: 'test-command',
args: ['--foo', 'bar'],
cwd: 'test/cwd',
env: expect.objectContaining({ FOO: 'bar' }),
env: expect.objectContaining({ GEMINI_CLI_FOO: 'bar' }),
stderr: 'pipe',
});
});
it('should redact sensitive environment variables for command transport', async () => {
const mockedTransport = vi
.spyOn(SdkClientStdioLib, 'StdioClientTransport')
.mockReturnValue({} as SdkClientStdioLib.StdioClientTransport);
const originalEnv = process.env;
process.env = {
...originalEnv,
GEMINI_API_KEY: 'sensitive-key',
GEMINI_CLI_SAFE_VAR: 'safe-value',
};
// Ensure strict sanitization is not triggered for this test
delete process.env['GITHUB_SHA'];
delete process.env['SURFACE'];
try {
await createTransport(
'test-server',
{
command: 'test-command',
},
false,
EMPTY_CONFIG,
);
const callArgs = mockedTransport.mock.calls[0][0];
expect(callArgs.env).toBeDefined();
expect(callArgs.env!['GEMINI_CLI_SAFE_VAR']).toBe('safe-value');
expect(callArgs.env!['GEMINI_API_KEY']).toBeUndefined();
} finally {
process.env = originalEnv;
}
});
it('should include extension settings in environment', async () => {
const mockedTransport = vi
.spyOn(SdkClientStdioLib, 'StdioClientTransport')
.mockReturnValue({} as SdkClientStdioLib.StdioClientTransport);
await createTransport(
'test-server',
{
command: 'test-command',
extension: {
name: 'test-ext',
resolvedSettings: [
{
envVar: 'GEMINI_CLI_EXT_VAR',
value: 'ext-value',
sensitive: false,
name: 'ext-setting',
},
],
version: '',
isActive: false,
path: '',
contextFiles: [],
id: '',
},
},
false,
EMPTY_CONFIG,
);
const callArgs = mockedTransport.mock.calls[0][0];
expect(callArgs.env).toBeDefined();
expect(callArgs.env!['GEMINI_CLI_EXT_VAR']).toBe('ext-value');
});
it('should exclude extension settings with undefined values from environment', async () => {
const mockedTransport = vi
.spyOn(SdkClientStdioLib, 'StdioClientTransport')
@@ -1971,7 +2040,7 @@ describe('connectToMcpServer with OAuth', () => {
EMPTY_CONFIG,
);
expect(client).toBe(mockedClient);
expect(client.client).toBe(mockedClient);
expect(mockedClient.connect).toHaveBeenCalledTimes(2);
expect(mockAuthProvider.authenticate).toHaveBeenCalledOnce();
@@ -2017,7 +2086,7 @@ describe('connectToMcpServer with OAuth', () => {
EMPTY_CONFIG,
);
expect(client).toBe(mockedClient);
expect(client.client).toBe(mockedClient);
expect(mockedClient.connect).toHaveBeenCalledTimes(2);
expect(mockAuthProvider.authenticate).toHaveBeenCalledOnce();
expect(OAuthUtils.discoverOAuthConfig).toHaveBeenCalledWith(serverUrl);
@@ -2112,7 +2181,7 @@ describe('connectToMcpServer - HTTP→SSE fallback', () => {
EMPTY_CONFIG,
);
expect(client).toBe(mockedClient);
expect(client.client).toBe(mockedClient);
// First HTTP attempt fails, second SSE attempt succeeds
expect(mockedClient.connect).toHaveBeenCalledTimes(2);
});
@@ -2153,7 +2222,7 @@ describe('connectToMcpServer - HTTP→SSE fallback', () => {
EMPTY_CONFIG,
);
expect(client).toBe(mockedClient);
expect(client.client).toBe(mockedClient);
expect(mockedClient.connect).toHaveBeenCalledTimes(2);
});
});
@@ -2238,7 +2307,7 @@ describe('connectToMcpServer - OAuth with transport fallback', () => {
EMPTY_CONFIG,
);
expect(client).toBe(mockedClient);
expect(client.client).toBe(mockedClient);
expect(mockedClient.connect).toHaveBeenCalledTimes(3);
expect(mockAuthProvider.authenticate).toHaveBeenCalledOnce();
});
+87 -22
View File
@@ -34,7 +34,11 @@ import {
} from '@modelcontextprotocol/sdk/types.js';
import { ApprovalMode, PolicyDecision } from '../policy/types.js';
import { parse } from 'shell-quote';
import type { Config, MCPServerConfig } from '../config/config.js';
import type {
Config,
GeminiCLIExtension,
MCPServerConfig,
} from '../config/config.js';
import { AuthProviderType } from '../config/config.js';
import { GoogleCredentialProvider } from '../mcp/google-auth-provider.js';
import { ServiceAccountImpersonationProvider } from '../mcp/sa-impersonation-provider.js';
@@ -142,7 +146,7 @@ export class McpClient {
}
this.updateStatus(MCPServerStatus.CONNECTING);
try {
this.client = await connectToMcpServer(
const { client, transport } = await connectToMcpServer(
this.clientVersion,
this.serverName,
this.serverConfig,
@@ -150,11 +154,13 @@ export class McpClient {
this.workspaceContext,
this.cliConfig.sanitizationConfig,
);
this.client = client;
this.transport = transport;
this.registerNotificationHandlers();
const originalOnError = this.client.onerror;
this.client.onerror = (error) => {
this.client.onerror = async (error) => {
if (this.status !== MCPServerStatus.CONNECTED) {
return;
}
@@ -165,6 +171,14 @@ export class McpClient {
error,
);
this.updateStatus(MCPServerStatus.DISCONNECTED);
// Close transport to prevent memory leaks
if (this.transport) {
try {
await this.transport.close();
} catch {
// Ignore errors when closing transport on error
}
}
};
this.updateStatus(MCPServerStatus.CONNECTED);
} catch (error) {
@@ -913,8 +927,9 @@ export async function connectAndDiscover(
updateMCPServerStatus(mcpServerName, MCPServerStatus.CONNECTING);
let mcpClient: Client | undefined;
let transport: Transport | undefined;
try {
mcpClient = await connectToMcpServer(
const result = await connectToMcpServer(
clientVersion,
mcpServerName,
mcpServerConfig,
@@ -922,10 +937,20 @@ export async function connectAndDiscover(
workspaceContext,
cliConfig.sanitizationConfig,
);
mcpClient = result.client;
transport = result.transport;
mcpClient.onerror = (error) => {
mcpClient.onerror = async (error) => {
coreEvents.emitFeedback('error', `MCP ERROR (${mcpServerName}):`, error);
updateMCPServerStatus(mcpServerName, MCPServerStatus.DISCONNECTED);
// Close transport to prevent memory leaks
if (transport) {
try {
await transport.close();
} catch {
// Ignore errors when closing transport on error
}
}
};
// Attempt to discover both prompts and tools
@@ -1323,16 +1348,18 @@ function createSSETransportWithAuth(
* @param client The MCP client to connect
* @param config The MCP server configuration
* @param accessToken Optional OAuth access token for authentication
* @returns The transport used for connection
*/
async function connectWithSSETransport(
client: Client,
config: MCPServerConfig,
accessToken?: string | null,
): Promise<void> {
): Promise<Transport> {
const transport = createSSETransportWithAuth(config, accessToken);
await client.connect(transport, {
timeout: config.timeout ?? MCP_DEFAULT_TIMEOUT_MSEC,
});
return transport;
}
/**
@@ -1362,6 +1389,7 @@ async function showAuthRequiredMessage(serverName: string): Promise<never> {
* @param config The MCP server configuration
* @param accessToken The OAuth access token to use
* @param httpReturned404 Whether the HTTP transport returned 404 (indicating SSE-only server)
* @returns The transport used for connection
*/
async function retryWithOAuth(
client: Client,
@@ -1369,17 +1397,21 @@ async function retryWithOAuth(
config: MCPServerConfig,
accessToken: string,
httpReturned404: boolean,
): Promise<void> {
): Promise<Transport> {
if (httpReturned404) {
// HTTP returned 404, only try SSE
debugLogger.log(
`Retrying SSE connection to '${serverName}' with OAuth token...`,
);
await connectWithSSETransport(client, config, accessToken);
const transport = await connectWithSSETransport(
client,
config,
accessToken,
);
debugLogger.log(
`Successfully connected to '${serverName}' using SSE with OAuth.`,
);
return;
return transport;
}
// HTTP returned 401, try HTTP with OAuth first
@@ -1403,6 +1435,7 @@ async function retryWithOAuth(
debugLogger.log(
`Successfully connected to '${serverName}' using HTTP with OAuth.`,
);
return httpTransport;
} catch (httpError) {
await httpTransport.close();
@@ -1414,10 +1447,15 @@ async function retryWithOAuth(
!config.httpUrl
) {
debugLogger.log(`HTTP with OAuth returned 404, trying SSE with OAuth...`);
await connectWithSSETransport(client, config, accessToken);
const sseTransport = await connectWithSSETransport(
client,
config,
accessToken,
);
debugLogger.log(
`Successfully connected to '${serverName}' using SSE with OAuth.`,
);
return sseTransport;
} else {
throw httpError;
}
@@ -1431,7 +1469,7 @@ async function retryWithOAuth(
*
* @param mcpServerName The name of the MCP server, used for logging and identification.
* @param mcpServerConfig The configuration specifying how to connect to the server.
* @returns A promise that resolves to a connected MCP `Client` instance.
* @returns A promise that resolves to a connected MCP `Client` instance and its transport.
* @throws An error if the connection fails or the configuration is invalid.
*/
export async function connectToMcpServer(
@@ -1441,7 +1479,7 @@ export async function connectToMcpServer(
debugMode: boolean,
workspaceContext: WorkspaceContext,
sanitizationConfig: EnvironmentSanitizationConfig,
): Promise<Client> {
): Promise<{ client: Client; transport: Transport }> {
const mcpClient = new Client(
{
name: 'gemini-cli-mcp-client',
@@ -1513,7 +1551,7 @@ export async function connectToMcpServer(
await mcpClient.connect(transport, {
timeout: mcpServerConfig.timeout ?? MCP_DEFAULT_TIMEOUT_MSEC,
});
return mcpClient;
return { client: mcpClient, transport };
} catch (error) {
await transport.close();
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
@@ -1545,7 +1583,7 @@ export async function connectToMcpServer(
try {
// Try SSE with stored OAuth token if available
// This ensures that SSE fallback works for authenticated servers
await connectWithSSETransport(
const sseTransport = await connectWithSSETransport(
mcpClient,
mcpServerConfig,
await getStoredOAuthToken(mcpServerName),
@@ -1554,7 +1592,7 @@ export async function connectToMcpServer(
debugLogger.log(
`MCP server '${mcpServerName}': Successfully connected using SSE transport.`,
);
return mcpClient;
return { client: mcpClient, transport: sseTransport };
} catch (sseFallbackError) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
sseError = sseFallbackError as Error;
@@ -1662,14 +1700,14 @@ export async function connectToMcpServer(
);
}
await retryWithOAuth(
const oauthTransport = await retryWithOAuth(
mcpClient,
mcpServerName,
mcpServerConfig,
accessToken,
httpReturned404,
);
return mcpClient;
return { client: mcpClient, transport: oauthTransport };
} else {
throw new Error(
`Failed to handle automatic OAuth for server '${mcpServerName}'`,
@@ -1750,7 +1788,7 @@ export async function connectToMcpServer(
timeout: mcpServerConfig.timeout ?? MCP_DEFAULT_TIMEOUT_MSEC,
});
// Connection successful with OAuth
return mcpClient;
return { client: mcpClient, transport: oauthTransport };
} else {
throw new Error(
`OAuth configuration failed for '${mcpServerName}'. Please authenticate manually with /mcp auth ${mcpServerName}`,
@@ -1898,10 +1936,23 @@ export async function createTransport(
command: mcpServerConfig.command,
args: mcpServerConfig.args || [],
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
env: {
...sanitizeEnvironment(process.env, sanitizationConfig),
...(mcpServerConfig.env || {}),
} as Record<string, string>,
env: sanitizeEnvironment(
{
...process.env,
...getExtensionEnvironment(mcpServerConfig.extension),
...(mcpServerConfig.env || {}),
},
{
...sanitizationConfig,
allowedEnvironmentVariables: [
...(sanitizationConfig.allowedEnvironmentVariables ?? []),
...(mcpServerConfig.extension?.resolvedSettings?.map(
(s) => s.envVar,
) ?? []),
],
enableEnvironmentVariableRedaction: true,
},
) as Record<string, string>,
cwd: mcpServerConfig.cwd,
stderr: 'pipe',
});
@@ -1976,3 +2027,17 @@ export function isEnabled(
)
);
}
function getExtensionEnvironment(
extension?: GeminiCLIExtension,
): Record<string, string> {
const env: Record<string, string> = {};
if (extension?.resolvedSettings) {
for (const setting of extension.resolvedSettings) {
if (setting.value) {
env[setting.envVar] = setting.value;
}
}
}
return env;
}
+42 -69
View File
@@ -1408,45 +1408,42 @@ describe('RipGrepTool', () => {
expect(result.llmContent).toContain('L1: HELLO world');
});
it('should handle fixed_strings parameter', async () => {
mockSpawn.mockImplementationOnce(
createMockSpawn({
outputData:
JSON.stringify({
type: 'match',
data: {
path: { text: 'fileA.txt' },
line_number: 1,
lines: { text: 'hello.world\n' },
},
}) + '\n',
exitCode: 0,
}),
);
it.each([
{
name: 'fixed_strings parameter',
params: { pattern: 'hello.world', fixed_strings: true },
mockOutput: {
path: { text: 'fileA.txt' },
line_number: 1,
lines: { text: 'hello.world\n' },
},
expectedArgs: ['--fixed-strings'],
expectedPattern: 'hello.world',
},
])(
'should handle $name',
async ({ params, mockOutput, expectedArgs, expectedPattern }) => {
mockSpawn.mockImplementationOnce(
createMockSpawn({
outputData:
JSON.stringify({ type: 'match', data: mockOutput }) + '\n',
exitCode: 0,
}),
);
const invocation = grepTool.build({
pattern: 'hello.world',
fixed_strings: true,
});
const result = await invocation.execute(abortSignal);
const invocation = grepTool.build(params);
const result = await invocation.execute(abortSignal);
expect(mockSpawn).toHaveBeenLastCalledWith(
expect.anything(),
expect.arrayContaining(['--fixed-strings']),
expect.anything(),
);
expect(result.llmContent).toContain(
'Found 1 match for pattern "hello.world"',
);
});
it('should allow invalid regex patterns when fixed_strings is true', () => {
const params: RipGrepToolParams = {
pattern: '[[',
fixed_strings: true,
};
expect(grepTool.validateToolParams(params)).toBeNull();
});
expect(mockSpawn).toHaveBeenLastCalledWith(
expect.anything(),
expect.arrayContaining(expectedArgs),
expect.anything(),
);
expect(result.llmContent).toContain(
`Found 1 match for pattern "${expectedPattern}"`,
);
},
);
it('should handle no_ignore parameter', async () => {
mockSpawn.mockImplementationOnce(
@@ -1684,42 +1681,19 @@ describe('RipGrepTool', () => {
mockSpawn.mockImplementationOnce(
createMockSpawn({
outputData:
JSON.stringify({
type: 'context',
data: {
path: { text: 'fileA.txt' },
line_number: 1,
lines: { text: 'hello world\n' },
},
}) +
'\n' +
JSON.stringify({
type: 'match',
data: {
path: { text: 'fileA.txt' },
line_number: 2,
lines: { text: 'second line with world\n' },
lines_before: [{ text: 'hello world\n' }],
lines_after: [
{ text: 'third line\n' },
{ text: 'fourth line\n' },
],
},
}) +
'\n' +
JSON.stringify({
type: 'context',
data: {
path: { text: 'fileA.txt' },
line_number: 3,
lines: { text: 'third line\n' },
},
}) +
'\n' +
JSON.stringify({
type: 'context',
data: {
path: { text: 'fileA.txt' },
line_number: 4,
lines: { text: 'fourth line\n' },
},
}) +
'\n',
}) + '\n',
exitCode: 0,
}),
);
@@ -1747,10 +1721,9 @@ describe('RipGrepTool', () => {
);
expect(result.llmContent).toContain('Found 1 match for pattern "world"');
expect(result.llmContent).toContain('File: fileA.txt');
expect(result.llmContent).toContain('L1- hello world');
expect(result.llmContent).toContain('L2: second line with world');
expect(result.llmContent).toContain('L3- third line');
expect(result.llmContent).toContain('L4- fourth line');
// Note: Ripgrep JSON output for context lines doesn't include line numbers for context lines directly
// The current parsing only extracts the matched line, so we only assert on that.
});
});
+16 -25
View File
@@ -140,7 +140,6 @@ interface GrepMatch {
filePath: string;
lineNumber: number;
line: string;
isContext?: boolean;
}
class GrepToolInvocation extends BaseToolInvocation<
@@ -268,6 +267,8 @@ class GrepToolInvocation extends BaseToolInvocation<
return { llmContent: noMatchMsg, returnDisplay: `No matches found` };
}
const wasTruncated = allMatches.length >= totalMaxMatches;
const matchesByFile = allMatches.reduce(
(acc, match) => {
const fileKey = match.filePath;
@@ -281,19 +282,16 @@ class GrepToolInvocation extends BaseToolInvocation<
{} as Record<string, GrepMatch[]>,
);
const matchesOnly = allMatches.filter((m) => !m.isContext);
const matchCount = matchesOnly.length;
const matchCount = allMatches.length;
const matchTerm = matchCount === 1 ? 'match' : 'matches';
const wasTruncated = matchCount >= totalMaxMatches;
let llmContent = `Found ${matchCount} ${matchTerm} for pattern "${this.params.pattern}" ${searchLocationDescription}${this.params.include ? ` (filter: "${this.params.include}")` : ''}${wasTruncated ? ` (results limited to ${totalMaxMatches} matches for performance)` : ''}:\n---\n`;
for (const filePath in matchesByFile) {
llmContent += `File: ${filePath}\n`;
matchesByFile[filePath].forEach((match) => {
const separator = match.isContext ? '-' : ':';
llmContent += `L${match.lineNumber}${separator} ${match.line}\n`;
const trimmedLine = match.line.trim();
llmContent += `L${match.lineNumber}: ${trimmedLine}\n`;
});
llmContent += '---\n';
}
@@ -404,15 +402,11 @@ class GrepToolInvocation extends BaseToolInvocation<
allowedExitCodes: [0, 1],
});
let matchesFound = 0;
for await (const line of generator) {
const match = this.parseRipgrepJsonLine(line, absolutePath);
if (match) {
results.push(match);
if (!match.isContext) {
matchesFound++;
}
if (matchesFound >= maxMatches) {
if (results.length >= maxMatches) {
break;
}
}
@@ -431,11 +425,11 @@ class GrepToolInvocation extends BaseToolInvocation<
): GrepMatch | null {
try {
const json = JSON.parse(line);
if (json.type === 'match' || json.type === 'context') {
const data = json.data;
if (json.type === 'match') {
const match = json.data;
// Defensive check: ensure text properties exist (skips binary/invalid encoding)
if (data.path?.text && data.lines?.text) {
const absoluteFilePath = path.resolve(basePath, data.path.text);
if (match.path?.text && match.lines?.text) {
const absoluteFilePath = path.resolve(basePath, match.path.text);
const relativeCheck = path.relative(basePath, absoluteFilePath);
if (
relativeCheck === '..' ||
@@ -449,9 +443,8 @@ class GrepToolInvocation extends BaseToolInvocation<
return {
filePath: relativeFilePath || path.basename(absoluteFilePath),
lineNumber: data.line_number,
line: data.lines.text.trimEnd(),
isContext: json.type === 'context',
lineNumber: match.line_number,
line: match.lines.text.trimEnd(),
};
}
}
@@ -580,12 +573,10 @@ export class RipGrepTool extends BaseDeclarativeTool<
protected override validateToolParamValues(
params: RipGrepToolParams,
): string | null {
if (!params.fixed_strings) {
try {
new RegExp(params.pattern);
} catch (error) {
return `Invalid regular expression pattern provided: ${params.pattern}. Error: ${getErrorMessage(error)}`;
}
try {
new RegExp(params.pattern);
} catch (error) {
return `Invalid regular expression pattern provided: ${params.pattern}. Error: ${getErrorMessage(error)}`;
}
// Only validate path if one is provided
@@ -0,0 +1,107 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, vi } from 'vitest';
import type { GeminiClient } from '../core/client.js';
import {
DEFAULT_FLASH_LITE_MODEL_CONFIG_KEY,
generateFlashLiteText,
truncateFlashLiteInput,
} from './flashLiteHelper.js';
describe('truncateFlashLiteInput', () => {
it('returns input as-is when below limit', () => {
expect(truncateFlashLiteInput('hello', 10)).toBe('hello');
});
it('truncates and appends suffix when above limit', () => {
const input = 'abcdefghijklmnopqrstuvwxyz';
const result = truncateFlashLiteInput(input, 20);
expect(result.length).toBe(20);
expect(result).toContain('...[truncated]');
});
});
describe('generateFlashLiteText', () => {
const abortSignal = new AbortController().signal;
it('uses the default flash-lite helper model config and returns response text', async () => {
const geminiClient = {
generateContent: vi.fn().mockResolvedValue({
candidates: [
{ content: { parts: [{ text: ' Got it. Skipping #2. ' }] } },
],
}),
} as unknown as GeminiClient;
const result = await generateFlashLiteText(geminiClient, {
instruction: 'Write a short acknowledgement sentence.',
input: 'skip #2',
fallbackText: 'Got it.',
abortSignal,
});
expect(result).toBe('Got it. Skipping #2.');
expect(geminiClient.generateContent).toHaveBeenCalledWith(
DEFAULT_FLASH_LITE_MODEL_CONFIG_KEY,
expect.any(Array),
abortSignal,
);
});
it('returns fallback text when response text is empty', async () => {
const geminiClient = {
generateContent: vi.fn().mockResolvedValue({}),
} as unknown as GeminiClient;
const result = await generateFlashLiteText(geminiClient, {
instruction: 'Return one sentence.',
input: 'cancel task 2',
fallbackText: 'Understood. Cancelling task 2.',
abortSignal,
});
expect(result).toBe('Understood. Cancelling task 2.');
});
it('returns fallback text when generation throws', async () => {
const geminiClient = {
generateContent: vi.fn().mockRejectedValue(new Error('boom')),
} as unknown as GeminiClient;
const result = await generateFlashLiteText(geminiClient, {
instruction: 'Return one sentence.',
input: 'cancel task 2',
fallbackText: 'Understood.',
abortSignal,
});
expect(result).toBe('Understood.');
});
it('truncates the input before sending to the model', async () => {
const geminiClient = {
generateContent: vi.fn().mockResolvedValue({
candidates: [{ content: { parts: [{ text: 'Ack.' }] } }],
}),
} as unknown as GeminiClient;
const longInput = 'x'.repeat(200);
await generateFlashLiteText(geminiClient, {
instruction: 'Return one sentence.',
input: longInput,
fallbackText: 'Understood.',
abortSignal,
maxInputChars: 64,
});
const [, contents] = (
geminiClient.generateContent as ReturnType<typeof vi.fn>
).mock.calls[0];
const promptText = contents[0].parts[0].text as string;
expect(promptText).toContain('...[truncated]');
});
});
@@ -0,0 +1,89 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import type { Content } from '@google/genai';
import type { GeminiClient } from '../core/client.js';
import type { ModelConfigKey } from '../services/modelConfigService.js';
import { debugLogger } from './debugLogger.js';
import { getResponseText } from './partUtils.js';
export const DEFAULT_FLASH_LITE_MODEL_CONFIG_KEY: ModelConfigKey = {
model: 'flash-lite-helper',
};
export const DEFAULT_FLASH_LITE_MAX_INPUT_CHARS = 1200;
export const DEFAULT_FLASH_LITE_MAX_OUTPUT_CHARS = 180;
const INPUT_TRUNCATION_SUFFIX = '\n...[truncated]';
export interface GenerateFlashLiteTextOptions {
instruction: string;
input: string;
fallbackText: string;
abortSignal: AbortSignal;
modelConfigKey?: ModelConfigKey;
maxInputChars?: number;
maxOutputChars?: number;
}
export function truncateFlashLiteInput(
input: string,
maxInputChars: number = DEFAULT_FLASH_LITE_MAX_INPUT_CHARS,
): string {
if (maxInputChars <= INPUT_TRUNCATION_SUFFIX.length) {
return input.slice(0, Math.max(maxInputChars, 0));
}
if (input.length <= maxInputChars) {
return input;
}
const keepChars = maxInputChars - INPUT_TRUNCATION_SUFFIX.length;
return input.slice(0, keepChars) + INPUT_TRUNCATION_SUFFIX;
}
export async function generateFlashLiteText(
geminiClient: GeminiClient,
options: GenerateFlashLiteTextOptions,
): Promise<string> {
const {
instruction,
input,
fallbackText,
abortSignal,
modelConfigKey = DEFAULT_FLASH_LITE_MODEL_CONFIG_KEY,
maxInputChars = DEFAULT_FLASH_LITE_MAX_INPUT_CHARS,
maxOutputChars = DEFAULT_FLASH_LITE_MAX_OUTPUT_CHARS,
} = options;
const safeInstruction = instruction.trim();
if (!safeInstruction) {
return fallbackText;
}
const safeInput = truncateFlashLiteInput(input.trim(), maxInputChars);
const prompt = `${safeInstruction}\n\nUser input:\n"""${safeInput}"""`;
const contents: Content[] = [{ role: 'user', parts: [{ text: prompt }] }];
try {
const response = await geminiClient.generateContent(
modelConfigKey,
contents,
abortSignal,
);
const responseText = getResponseText(response)?.replace(/\s+/g, ' ').trim();
if (!responseText) {
return fallbackText;
}
if (maxOutputChars > 0 && responseText.length > maxOutputChars) {
return responseText.slice(0, maxOutputChars).trimEnd();
}
return responseText;
} catch (error) {
debugLogger.debug(
`[FlashLiteHelper] Generation failed: ${error instanceof Error ? error.message : String(error)}`,
);
return fallbackText;
}
}
+8 -44
View File
@@ -99,50 +99,16 @@ describe('isHeadlessMode', () => {
expect(isHeadlessMode({ prompt: true })).toBe(true);
});
it('should return true if query is provided', () => {
expect(isHeadlessMode({ query: 'test query' })).toBe(true);
});
it('should return true if -p or --prompt is in process.argv as a fallback', () => {
const originalArgv = process.argv;
process.argv = ['node', 'index.js', '-p', 'hello'];
try {
expect(isHeadlessMode()).toBe(true);
} finally {
process.argv = originalArgv;
}
process.argv = ['node', 'index.js', '--prompt', 'hello'];
try {
expect(isHeadlessMode()).toBe(true);
} finally {
process.argv = originalArgv;
}
});
it('should return false if -y or --yolo is in process.argv as a fallback', () => {
const originalArgv = process.argv;
process.argv = ['node', 'index.js', '-y'];
try {
expect(isHeadlessMode()).toBe(false);
} finally {
process.argv = originalArgv;
}
process.argv = ['node', 'index.js', '--yolo'];
try {
expect(isHeadlessMode()).toBe(false);
} finally {
process.argv = originalArgv;
}
it('should return false if query is provided but it is still a TTY', () => {
// Note: per current logic, query alone doesn't force headless if TTY
// This matches the existing behavior in packages/cli/src/config/config.ts
expect(isHeadlessMode({ query: 'test query' })).toBe(false);
});
it('should handle undefined process.stdout gracefully', () => {
const originalStdout = process.stdout;
Object.defineProperty(process, 'stdout', {
value: undefined,
configurable: true,
});
// @ts-expect-error - testing edge case
delete process.stdout;
try {
expect(isHeadlessMode()).toBe(false);
@@ -156,10 +122,8 @@ describe('isHeadlessMode', () => {
it('should handle undefined process.stdin gracefully', () => {
const originalStdin = process.stdin;
Object.defineProperty(process, 'stdin', {
value: undefined,
configurable: true,
});
// @ts-expect-error - testing edge case
delete process.stdin;
try {
expect(isHeadlessMode()).toBe(false);
+12 -16
View File
@@ -28,22 +28,18 @@ export interface HeadlessModeOptions {
* @returns true if the environment is considered headless.
*/
export function isHeadlessMode(options?: HeadlessModeOptions): boolean {
if (process.env['GEMINI_CLI_INTEGRATION_TEST'] !== 'true') {
const isCI =
process.env['CI'] === 'true' || process.env['GITHUB_ACTIONS'] === 'true';
if (isCI) {
return true;
}
if (process.env['GEMINI_CLI_INTEGRATION_TEST'] === 'true') {
return (
!!options?.prompt ||
(!!process.stdin && !process.stdin.isTTY) ||
(!!process.stdout && !process.stdout.isTTY)
);
}
const isNotTTY =
return (
process.env['CI'] === 'true' ||
process.env['GITHUB_ACTIONS'] === 'true' ||
!!options?.prompt ||
(!!process.stdin && !process.stdin.isTTY) ||
(!!process.stdout && !process.stdout.isTTY);
if (isNotTTY || !!options?.prompt || !!options?.query) {
return true;
}
// Fallback: check process.argv for flags that imply headless mode.
return process.argv.some((arg) => arg === '-p' || arg === '--prompt');
(!!process.stdout && !process.stdout.isTTY)
);
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli-test-utils",
"version": "0.29.1",
"version": "0.29.0-nightly.20260203.71f46f116",
"private": true,
"main": "src/index.ts",
"license": "Apache-2.0",
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "gemini-cli-vscode-ide-companion",
"displayName": "Gemini CLI Companion",
"description": "Enable Gemini CLI with direct access to your IDE workspace.",
"version": "0.29.1",
"version": "0.29.0-nightly.20260203.71f46f116",
"publisher": "google",
"icon": "assets/icon.png",
"repository": {