diff --git a/.github/workflows/gemini-automated-issue-triage.yml b/.github/workflows/gemini-automated-issue-triage.yml index 08b97db0a2..64609b5c3b 100644 --- a/.github/workflows/gemini-automated-issue-triage.yml +++ b/.github/workflows/gemini-automated-issue-triage.yml @@ -284,8 +284,21 @@ jobs: return; } } else { - core.setFailed(`Output is not valid JSON and does not contain a JSON markdown block.\nRaw output: ${rawOutput}`); - return; + // If no markdown block, try to find a raw JSON object in the output. + // The CLI may include debug/log lines (e.g. telemetry init, YOLO mode) + // before the actual JSON response. + const jsonObjectMatch = rawOutput.match(/(\{[\s\S]*"labels_to_set"[\s\S]*\})/); + if (jsonObjectMatch) { + try { + parsedLabels = JSON.parse(jsonObjectMatch[0]); + } catch (extractError) { + core.setFailed(`Found JSON-like content but failed to parse: ${extractError.message}\nRaw output: ${rawOutput}`); + return; + } + } else { + core.setFailed(`Output is not valid JSON and does not contain extractable JSON.\nRaw output: ${rawOutput}`); + return; + } } } diff --git a/docs/changelogs/preview.md b/docs/changelogs/preview.md index e8ac4a2dc9..4cb6a3824b 100644 --- a/docs/changelogs/preview.md +++ b/docs/changelogs/preview.md @@ -1,4 +1,4 @@ -# Preview release: v0.30.0-preview.1 +# Preview release: v0.30.0-preview.3 Released: February 19, 2026 @@ -311,4 +311,4 @@ npm install -g @google/gemini-cli@preview [#19008](https://github.com/google-gemini/gemini-cli/pull/19008) **Full changelog**: -https://github.com/google-gemini/gemini-cli/compare/v0.29.0-preview.5...v0.30.0-preview.1 +https://github.com/google-gemini/gemini-cli/compare/v0.29.0-preview.5...v0.30.0-preview.3 diff --git a/docs/cli/plan-mode.md b/docs/cli/plan-mode.md index b59b0c3198..03da2a6ac9 100644 --- a/docs/cli/plan-mode.md +++ b/docs/cli/plan-mode.md @@ -69,6 +69,7 @@ You can enter Plan Mode in three ways: 2. **Command:** Type `/plan` in the input box. 3. **Natural Language:** Ask the agent to "start a plan for...". The agent will then call the [`enter_plan_mode`] tool to switch modes. + - **Note:** This tool is not available when the CLI is in YOLO mode. ### The Planning Workflow diff --git a/docs/tools/planning.md b/docs/tools/planning.md index 686b27f058..458b172510 100644 --- a/docs/tools/planning.md +++ b/docs/tools/planning.md @@ -11,6 +11,8 @@ by the agent when you ask it to "start a plan" using natural language. In this mode, the agent is restricted to read-only tools to allow for safe exploration and planning. +> **Note:** This tool is not available when the CLI is in YOLO mode. + - **Tool name:** `enter_plan_mode` - **Display name:** Enter Plan Mode - **File:** `enter-plan-mode.ts` diff --git a/packages/core/src/config/config.test.ts b/packages/core/src/config/config.test.ts index 7235e72eb7..e08d503fab 100644 --- a/packages/core/src/config/config.test.ts +++ b/packages/core/src/config/config.test.ts @@ -1390,7 +1390,22 @@ describe('setApprovalMode with folder trust', () => { expect(updateSpy).toHaveBeenCalled(); }); - it('should not update system instruction when switching between non-Plan modes', () => { + it('should update system instruction when entering YOLO mode', () => { + const config = new Config(baseParams); + vi.spyOn(config, 'isTrustedFolder').mockReturnValue(true); + vi.spyOn(config, 'getToolRegistry').mockReturnValue({ + getTool: vi.fn().mockReturnValue(undefined), + unregisterTool: vi.fn(), + registerTool: vi.fn(), + } as Partial as ToolRegistry); + const updateSpy = vi.spyOn(config, 'updateSystemInstructionIfInitialized'); + + config.setApprovalMode(ApprovalMode.YOLO); + + expect(updateSpy).toHaveBeenCalled(); + }); + + it('should not update system instruction when switching between non-Plan/non-YOLO modes', () => { const config = new Config(baseParams); vi.spyOn(config, 'isTrustedFolder').mockReturnValue(true); const updateSpy = vi.spyOn(config, 'updateSystemInstructionIfInitialized'); @@ -2647,6 +2662,27 @@ describe('syncPlanModeTools', () => { expect(registeredTool).toBeUndefined(); }); + it('should NOT register EnterPlanModeTool when in YOLO mode, even if plan is enabled', async () => { + const config = new Config({ + ...baseParams, + approvalMode: ApprovalMode.YOLO, + plan: true, + }); + const registry = new ToolRegistry(config, config.getMessageBus()); + vi.spyOn(config, 'getToolRegistry').mockReturnValue(registry); + + const registerSpy = vi.spyOn(registry, 'registerTool'); + vi.spyOn(registry, 'getTool').mockReturnValue(undefined); + + config.syncPlanModeTools(); + + const { EnterPlanModeTool } = await import('../tools/enter-plan-mode.js'); + const registeredTool = registerSpy.mock.calls.find( + (call) => call[0] instanceof EnterPlanModeTool, + ); + expect(registeredTool).toBeUndefined(); + }); + it('should call geminiClient.setTools if initialized', async () => { const config = new Config(baseParams); const registry = new ToolRegistry(config, config.getMessageBus()); diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index 9ac6a7f37b..406fe20fdd 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -1792,7 +1792,11 @@ export class Config { const isPlanModeTransition = currentMode !== mode && (currentMode === ApprovalMode.PLAN || mode === ApprovalMode.PLAN); - if (isPlanModeTransition) { + const isYoloModeTransition = + currentMode !== mode && + (currentMode === ApprovalMode.YOLO || mode === ApprovalMode.YOLO); + + if (isPlanModeTransition || isYoloModeTransition) { this.syncPlanModeTools(); this.updateSystemInstructionIfInitialized(); } @@ -1802,8 +1806,13 @@ export class Config { * Synchronizes enter/exit plan mode tools based on current mode. */ syncPlanModeTools(): void { - const isPlanMode = this.getApprovalMode() === ApprovalMode.PLAN; const registry = this.getToolRegistry(); + if (!registry) { + return; + } + const approvalMode = this.getApprovalMode(); + const isPlanMode = approvalMode === ApprovalMode.PLAN; + const isYoloMode = approvalMode === ApprovalMode.YOLO; if (isPlanMode) { if (registry.getTool(ENTER_PLAN_MODE_TOOL_NAME)) { @@ -1816,7 +1825,7 @@ export class Config { if (registry.getTool(EXIT_PLAN_MODE_TOOL_NAME)) { registry.unregisterTool(EXIT_PLAN_MODE_TOOL_NAME); } - if (this.planEnabled) { + if (this.planEnabled && !isYoloMode) { if (!registry.getTool(ENTER_PLAN_MODE_TOOL_NAME)) { registry.registerTool(new EnterPlanModeTool(this, this.messageBus)); } diff --git a/packages/core/src/tools/definitions/__snapshots__/coreToolsModelSnapshots.test.ts.snap b/packages/core/src/tools/definitions/__snapshots__/coreToolsModelSnapshots.test.ts.snap index 8aa86f60a7..9767829f0e 100644 --- a/packages/core/src/tools/definitions/__snapshots__/coreToolsModelSnapshots.test.ts.snap +++ b/packages/core/src/tools/definitions/__snapshots__/coreToolsModelSnapshots.test.ts.snap @@ -1293,18 +1293,8 @@ Use this tool when the user's query implies needing the content of several files exports[`coreTools snapshots for specific models > Model: gemini-3-pro-preview > snapshot for tool: replace 1`] = ` { - "description": "Replaces text within a file. By default, replaces a single occurrence, but can replace multiple occurrences when \`expected_replacements\` is specified. This tool requires providing significant context around the change to ensure precise targeting. Always use the read_file tool to examine the file's current content before attempting a text replacement. - - The user has the ability to modify the \`new_string\` content. If modified, this will be stated in the response. - - Expectation for required parameters: - 1. \`old_string\` MUST be the exact literal text to replace (including all whitespace, indentation, newlines, and surrounding code etc.). - 2. \`new_string\` MUST be the exact literal text to replace \`old_string\` with (also including all whitespace, indentation, newlines, and surrounding code etc.). Ensure the resulting code is correct and idiomatic and that \`old_string\` and \`new_string\` are different. - 3. \`instruction\` is the detailed instruction of what needs to be changed. It is important to Make it specific and detailed so developers or large language models can understand what needs to be changed and perform the changes on their own if necessary. - 4. NEVER escape \`old_string\` or \`new_string\`, that would break the exact literal text requirement. - **Important:** If ANY of the above are not satisfied, the tool will fail. CRITICAL for \`old_string\`: Must uniquely identify the single instance to change. Include at least 3 lines of context BEFORE and AFTER the target text, matching whitespace and indentation precisely. If this string matches multiple locations, or does not match exactly, the tool will fail. - 5. Prefer to break down complex and long changes into multiple smaller atomic calls to this tool. Always check the content of the file after changes or not finding a string to match. - **Multiple replacements:** Set \`expected_replacements\` to the number of occurrences you want to replace. The tool will replace ALL occurrences that match \`old_string\` exactly. Ensure the number of replacements matches your expectation.", + "description": "Replaces text within a file. By default, replaces a single occurrence, but can replace multiple occurrences ONLY when \`expected_replacements\` is specified. This tool requires providing significant context around the change to ensure precise targeting. +The user has the ability to modify the \`new_string\` content. If modified, this will be stated in the response.", "name": "replace", "parametersJsonSchema": { "properties": { @@ -1318,29 +1308,15 @@ exports[`coreTools snapshots for specific models > Model: gemini-3-pro-preview > "type": "string", }, "instruction": { - "description": "A clear, semantic instruction for the code change, acting as a high-quality prompt for an expert LLM assistant. It must be self-contained and explain the goal of the change. - -A good instruction should concisely answer: -1. WHY is the change needed? (e.g., "To fix a bug where users can be null...") -2. WHERE should the change happen? (e.g., "...in the 'renderUserProfile' function...") -3. WHAT is the high-level change? (e.g., "...add a null check for the 'user' object...") -4. WHAT is the desired outcome? (e.g., "...so that it displays a loading spinner instead of crashing.") - -**GOOD Example:** "In the 'calculateTotal' function, correct the sales tax calculation by updating the 'taxRate' constant from 0.05 to 0.075 to reflect the new regional tax laws." - -**BAD Examples:** -- "Change the text." (Too vague) -- "Fix the bug." (Doesn't explain the bug or the fix) -- "Replace the line with this new line." (Brittle, just repeats the other parameters) -", + "description": "A clear, semantic instruction for the code change, acting as a high-quality prompt for an expert LLM assistant. It must be self-contained and explain the goal of the change.", "type": "string", }, "new_string": { - "description": "The exact literal text to replace \`old_string\` with, preferably unescaped. Provide the EXACT text. Ensure the resulting code is correct and idiomatic.", + "description": "The exact literal text to replace \`old_string\` with, unescaped. Provide the EXACT text. Ensure the resulting code is correct and idiomatic.", "type": "string", }, "old_string": { - "description": "The exact literal text to replace, preferably unescaped. For single replacements (default), include at least 3 lines of context BEFORE and AFTER the target text, matching whitespace and indentation precisely. If this string is not the exact literal text (i.e. you escaped it) or does not match exactly, the tool will fail.", + "description": "The exact literal text to replace, unescaped. If this string is not the exact literal text (i.e. you escaped it) or does not match exactly, the tool will fail.", "type": "string", }, }, @@ -1448,8 +1424,7 @@ exports[`coreTools snapshots for specific models > Model: gemini-3-pro-preview > exports[`coreTools snapshots for specific models > Model: gemini-3-pro-preview > snapshot for tool: write_file 1`] = ` { "description": "Writes content to a specified file in the local filesystem. - - The user has the ability to modify \`content\`. If modified, this will be stated in the response.", +The user has the ability to modify \`content\`. If modified, this will be stated in the response.", "name": "write_file", "parametersJsonSchema": { "properties": { diff --git a/packages/core/src/tools/definitions/model-family-sets/gemini-3.ts b/packages/core/src/tools/definitions/model-family-sets/gemini-3.ts index a532cac8ba..71e8aaec1c 100644 --- a/packages/core/src/tools/definitions/model-family-sets/gemini-3.ts +++ b/packages/core/src/tools/definitions/model-family-sets/gemini-3.ts @@ -64,8 +64,7 @@ export const GEMINI_3_SET: CoreToolSet = { write_file: { name: WRITE_FILE_TOOL_NAME, description: `Writes content to a specified file in the local filesystem. - - The user has the ability to modify \`content\`. If modified, this will be stated in the response.`, +The user has the ability to modify \`content\`. If modified, this will be stated in the response.`, parametersJsonSchema: { type: 'object', properties: { @@ -291,18 +290,8 @@ export const GEMINI_3_SET: CoreToolSet = { replace: { name: EDIT_TOOL_NAME, - description: `Replaces text within a file. By default, replaces a single occurrence, but can replace multiple occurrences when \`expected_replacements\` is specified. This tool requires providing significant context around the change to ensure precise targeting. Always use the ${READ_FILE_TOOL_NAME} tool to examine the file's current content before attempting a text replacement. - - The user has the ability to modify the \`new_string\` content. If modified, this will be stated in the response. - - Expectation for required parameters: - 1. \`old_string\` MUST be the exact literal text to replace (including all whitespace, indentation, newlines, and surrounding code etc.). - 2. \`new_string\` MUST be the exact literal text to replace \`old_string\` with (also including all whitespace, indentation, newlines, and surrounding code etc.). Ensure the resulting code is correct and idiomatic and that \`old_string\` and \`new_string\` are different. - 3. \`instruction\` is the detailed instruction of what needs to be changed. It is important to Make it specific and detailed so developers or large language models can understand what needs to be changed and perform the changes on their own if necessary. - 4. NEVER escape \`old_string\` or \`new_string\`, that would break the exact literal text requirement. - **Important:** If ANY of the above are not satisfied, the tool will fail. CRITICAL for \`old_string\`: Must uniquely identify the single instance to change. Include at least 3 lines of context BEFORE and AFTER the target text, matching whitespace and indentation precisely. If this string matches multiple locations, or does not match exactly, the tool will fail. - 5. Prefer to break down complex and long changes into multiple smaller atomic calls to this tool. Always check the content of the file after changes or not finding a string to match. - **Multiple replacements:** Set \`expected_replacements\` to the number of occurrences you want to replace. The tool will replace ALL occurrences that match \`old_string\` exactly. Ensure the number of replacements matches your expectation.`, + description: `Replaces text within a file. By default, replaces a single occurrence, but can replace multiple occurrences ONLY when \`expected_replacements\` is specified. This tool requires providing significant context around the change to ensure precise targeting. +The user has the ability to modify the \`new_string\` content. If modified, this will be stated in the response.`, parametersJsonSchema: { type: 'object', properties: { @@ -311,31 +300,17 @@ export const GEMINI_3_SET: CoreToolSet = { type: 'string', }, instruction: { - description: `A clear, semantic instruction for the code change, acting as a high-quality prompt for an expert LLM assistant. It must be self-contained and explain the goal of the change. - -A good instruction should concisely answer: -1. WHY is the change needed? (e.g., "To fix a bug where users can be null...") -2. WHERE should the change happen? (e.g., "...in the 'renderUserProfile' function...") -3. WHAT is the high-level change? (e.g., "...add a null check for the 'user' object...") -4. WHAT is the desired outcome? (e.g., "...so that it displays a loading spinner instead of crashing.") - -**GOOD Example:** "In the 'calculateTotal' function, correct the sales tax calculation by updating the 'taxRate' constant from 0.05 to 0.075 to reflect the new regional tax laws." - -**BAD Examples:** -- "Change the text." (Too vague) -- "Fix the bug." (Doesn't explain the bug or the fix) -- "Replace the line with this new line." (Brittle, just repeats the other parameters) -`, + description: `A clear, semantic instruction for the code change, acting as a high-quality prompt for an expert LLM assistant. It must be self-contained and explain the goal of the change.`, type: 'string', }, old_string: { description: - 'The exact literal text to replace, preferably unescaped. For single replacements (default), include at least 3 lines of context BEFORE and AFTER the target text, matching whitespace and indentation precisely. If this string is not the exact literal text (i.e. you escaped it) or does not match exactly, the tool will fail.', + 'The exact literal text to replace, unescaped. If this string is not the exact literal text (i.e. you escaped it) or does not match exactly, the tool will fail.', type: 'string', }, new_string: { description: - 'The exact literal text to replace `old_string` with, preferably unescaped. Provide the EXACT text. Ensure the resulting code is correct and idiomatic.', + 'The exact literal text to replace `old_string` with, unescaped. Provide the EXACT text. Ensure the resulting code is correct and idiomatic.', type: 'string', }, expected_replacements: {