diff --git a/docs/cli/tutorials/shell-commands.md b/docs/cli/tutorials/shell-commands.md index 22e945407e..3eaaf2049e 100644 --- a/docs/cli/tutorials/shell-commands.md +++ b/docs/cli/tutorials/shell-commands.md @@ -17,9 +17,10 @@ prefix. **Example:** `!ls -la` -This executes `ls -la` immediately and prints the output to your terminal. The -AI doesn't "see" this output unless you paste it back into the chat or use it in -a prompt. +This executes `ls -la` immediately and prints the output to your terminal. +Gemini CLI also records the command and its output in the current session +context, so the model can reference it in follow-up prompts. Very large outputs +may be truncated. ### Scenario: Entering Shell mode diff --git a/packages/core/src/core/client.test.ts b/packages/core/src/core/client.test.ts index 059b72437f..4dee75b93b 100644 --- a/packages/core/src/core/client.test.ts +++ b/packages/core/src/core/client.test.ts @@ -728,6 +728,23 @@ describe('Gemini Client (client.ts)', () => { ); }); + it('yields UserCancelled when processTurn throws AbortError', async () => { + const abortError = new Error('Aborted'); + abortError.name = 'AbortError'; + vi.spyOn(client['loopDetector'], 'turnStarted').mockRejectedValueOnce( + abortError, + ); + + const stream = client.sendMessageStream( + [{ text: 'Hi' }], + new AbortController().signal, + 'prompt-id-abort-error', + ); + const events = await fromAsync(stream); + + expect(events).toEqual([{ type: GeminiEventType.UserCancelled }]); + }); + it.each([ { compressionStatus: diff --git a/packages/core/src/core/client.ts b/packages/core/src/core/client.ts index 14e2f42bc3..2ab75cf365 100644 --- a/packages/core/src/core/client.ts +++ b/packages/core/src/core/client.ts @@ -34,7 +34,7 @@ import { type RetryAvailabilityContext, } from '../utils/retry.js'; import type { ValidationRequiredError } from '../utils/googleQuotaErrors.js'; -import { getErrorMessage } from '../utils/errors.js'; +import { getErrorMessage, isAbortError } from '../utils/errors.js'; import { tokenLimit } from './tokenLimits.js'; import type { ChatRecordingService, @@ -957,6 +957,12 @@ export class GeminiClient { ); } } + } catch (error) { + if (signal?.aborted || isAbortError(error)) { + yield { type: GeminiEventType.UserCancelled }; + return turn; + } + throw error; } finally { const hookState = this.hookStateMap.get(prompt_id); if (hookState) { diff --git a/packages/core/src/tools/ripGrep.ts b/packages/core/src/tools/ripGrep.ts index 000b4f0071..18a1b0c133 100644 --- a/packages/core/src/tools/ripGrep.ts +++ b/packages/core/src/tools/ripGrep.ts @@ -54,7 +54,23 @@ async function resolveExistingRgPath(): Promise { } let ripgrepAcquisitionPromise: Promise | null = null; - +/** + * Ensures a ripgrep binary is available. + * + * NOTE: + * - The Gemini CLI currently prefers a managed ripgrep binary downloaded + * into its global bin directory. + * - Even if ripgrep is available on the system PATH, it is intentionally + * not used at this time. + * + * Preference for system-installed ripgrep is blocked on: + * - checksum verification of external binaries + * - internalization of the get-ripgrep dependency + * + * See: + * - feat(core): Prefer rg in system path (#11847) + * - Move get-ripgrep to third_party (#12099) + */ async function ensureRipgrepAvailable(): Promise { const existingPath = await resolveExistingRgPath(); if (existingPath) {