From 7671b347ef6f897136a301666d30ecef2aeda8bc Mon Sep 17 00:00:00 2001 From: Anjali Sridhar Date: Tue, 29 Jul 2025 01:31:38 -0700 Subject: [PATCH 01/33] wip --- packages/cli/src/nonInteractiveCli.test.ts | 372 ++++++++------------- packages/cli/src/nonInteractiveCli.ts | 63 +--- 2 files changed, 152 insertions(+), 283 deletions(-) diff --git a/packages/cli/src/nonInteractiveCli.test.ts b/packages/cli/src/nonInteractiveCli.test.ts index 8b0419f1e65..bd79c41f13e 100644 --- a/packages/cli/src/nonInteractiveCli.test.ts +++ b/packages/cli/src/nonInteractiveCli.test.ts @@ -4,196 +4,166 @@ * SPDX-License-Identifier: Apache-2.0 */ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { + Config, + executeToolCall, + ToolRegistry, + shutdownTelemetry, + GeminiEventType, + ServerGeminiStreamEvent, +} from '@google/gemini-cli-core'; +import { Part } from '@google/genai'; import { runNonInteractive } from './nonInteractiveCli.js'; -import { Config, GeminiClient, ToolRegistry } from '@google/gemini-cli-core'; -import { GenerateContentResponse, Part, FunctionCall } from '@google/genai'; +import { vi } from 'vitest'; -// Mock dependencies -vi.mock('@google/gemini-cli-core', async () => { - const actualCore = await vi.importActual< - typeof import('@google/gemini-cli-core') - >('@google/gemini-cli-core'); +// Mock core modules +vi.mock('@google/gemini-cli-core', async (importOriginal) => { + const original = + await importOriginal(); return { - ...actualCore, - GeminiClient: vi.fn(), - ToolRegistry: vi.fn(), + ...original, executeToolCall: vi.fn(), + shutdownTelemetry: vi.fn(), }; }); describe('runNonInteractive', () => { let mockConfig: Config; - let mockGeminiClient: GeminiClient; let mockToolRegistry: ToolRegistry; - let mockChat: { - sendMessageStream: ReturnType; + let mockCoreExecuteToolCall: vi.Mock; + let mockShutdownTelemetry: vi.Mock; + let consoleErrorSpy: vi.SpyInstance; + let processExitSpy: vi.SpyInstance; + let processStdoutSpy: vi.SpyInstance; + let mockGeminiClient: { + sendMessageStream: vi.Mock; }; - let mockProcessStdoutWrite: ReturnType; - let mockProcessExit: ReturnType; beforeEach(() => { - vi.resetAllMocks(); - mockChat = { - sendMessageStream: vi.fn(), - }; - mockGeminiClient = { - getChat: vi.fn().mockResolvedValue(mockChat), - } as unknown as GeminiClient; + mockCoreExecuteToolCall = vi.mocked(executeToolCall); + mockShutdownTelemetry = vi.mocked(shutdownTelemetry); + + consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + processExitSpy = vi + .spyOn(process, 'exit') + .mockImplementation((() => {}) as (code?: number) => never); + processStdoutSpy = vi + .spyOn(process.stdout, 'write') + .mockImplementation(() => true); + mockToolRegistry = { - getFunctionDeclarations: vi.fn().mockReturnValue([]), getTool: vi.fn(), + getFunctionDeclarations: vi.fn().mockReturnValue([]), } as unknown as ToolRegistry; - vi.mocked(GeminiClient).mockImplementation(() => mockGeminiClient); - vi.mocked(ToolRegistry).mockImplementation(() => mockToolRegistry); + mockGeminiClient = { + sendMessageStream: vi.fn(), + }; mockConfig = { - getToolRegistry: vi.fn().mockReturnValue(mockToolRegistry), + initialize: vi.fn().mockResolvedValue(undefined), getGeminiClient: vi.fn().mockReturnValue(mockGeminiClient), - getContentGeneratorConfig: vi.fn().mockReturnValue({}), + getToolRegistry: vi.fn().mockResolvedValue(mockToolRegistry), getMaxSessionTurns: vi.fn().mockReturnValue(10), - initialize: vi.fn(), + getIdeMode: vi.fn().mockReturnValue(false), + getFullContext: vi.fn().mockReturnValue(false), + getContentGeneratorConfig: vi.fn().mockReturnValue({}), } as unknown as Config; - - mockProcessStdoutWrite = vi.fn().mockImplementation(() => true); - process.stdout.write = mockProcessStdoutWrite as any; // Use any to bypass strict signature matching for mock - mockProcessExit = vi - .fn() - .mockImplementation((_code?: number) => undefined as never); - process.exit = mockProcessExit as any; // Use any for process.exit mock }); afterEach(() => { vi.restoreAllMocks(); - // Restore original process methods if they were globally patched - // This might require storing the original methods before patching them in beforeEach }); + async function* createStreamFromEvents( + events: ServerGeminiStreamEvent[], + ): AsyncGenerator { + for (const event of events) { + yield event; + } + } + it('should process input and write text output', async () => { - const inputStream = (async function* () { - yield { - candidates: [{ content: { parts: [{ text: 'Hello' }] } }], - } as GenerateContentResponse; - yield { - candidates: [{ content: { parts: [{ text: ' World' }] } }], - } as GenerateContentResponse; - })(); - mockChat.sendMessageStream.mockResolvedValue(inputStream); + const events: ServerGeminiStreamEvent[] = [ + { type: GeminiEventType.Content, value: 'Hello' }, + { type: GeminiEventType.Content, value: ' World' }, + ]; + mockGeminiClient.sendMessageStream.mockReturnValue( + createStreamFromEvents(events), + ); await runNonInteractive(mockConfig, 'Test input', 'prompt-id-1'); - expect(mockChat.sendMessageStream).toHaveBeenCalledWith( - { - message: [{ text: 'Test input' }], - config: { - abortSignal: expect.any(AbortSignal), - tools: [{ functionDeclarations: [] }], - }, - }, - expect.any(String), + expect(mockGeminiClient.sendMessageStream).toHaveBeenCalledWith( + [{ text: 'Test input' }], + expect.any(AbortSignal), + 'prompt-id-1', ); - expect(mockProcessStdoutWrite).toHaveBeenCalledWith('Hello'); - expect(mockProcessStdoutWrite).toHaveBeenCalledWith(' World'); - expect(mockProcessStdoutWrite).toHaveBeenCalledWith('\n'); + expect(processStdoutSpy).toHaveBeenCalledWith('Hello'); + expect(processStdoutSpy).toHaveBeenCalledWith(' World'); + expect(processStdoutSpy).toHaveBeenCalledWith('\n'); + expect(mockShutdownTelemetry).toHaveBeenCalled(); }); it('should handle a single tool call and respond', async () => { - const functionCall: FunctionCall = { - id: 'fc1', - name: 'testTool', - args: { p: 'v' }, - }; - const toolResponsePart: Part = { - functionResponse: { + const toolCallEvent: ServerGeminiStreamEvent = { + type: GeminiEventType.ToolCallRequest, + value: { + callId: 'tool-1', name: 'testTool', - id: 'fc1', - response: { result: 'tool success' }, + args: { arg1: 'value1' }, + isClientInitiated: false, + prompt_id: 'prompt-id-2', }, }; + const toolResponse: Part[] = [{ text: 'Tool response' }]; + mockCoreExecuteToolCall.mockResolvedValue({ responseParts: toolResponse }); - const { executeToolCall: mockCoreExecuteToolCall } = await import( - '@google/gemini-cli-core' - ); - vi.mocked(mockCoreExecuteToolCall).mockResolvedValue({ - callId: 'fc1', - responseParts: [toolResponsePart], - resultDisplay: 'Tool success display', - error: undefined, - }); + const firstCallEvents: ServerGeminiStreamEvent[] = [toolCallEvent]; + const secondCallEvents: ServerGeminiStreamEvent[] = [ + { type: GeminiEventType.Content, value: 'Final answer' }, + ]; - const stream1 = (async function* () { - yield { functionCalls: [functionCall] } as GenerateContentResponse; - })(); - const stream2 = (async function* () { - yield { - candidates: [{ content: { parts: [{ text: 'Final answer' }] } }], - } as GenerateContentResponse; - })(); - mockChat.sendMessageStream - .mockResolvedValueOnce(stream1) - .mockResolvedValueOnce(stream2); + mockGeminiClient.sendMessageStream + .mockReturnValueOnce(createStreamFromEvents(firstCallEvents)) + .mockReturnValueOnce(createStreamFromEvents(secondCallEvents)); await runNonInteractive(mockConfig, 'Use a tool', 'prompt-id-2'); - expect(mockChat.sendMessageStream).toHaveBeenCalledTimes(2); + expect(mockGeminiClient.sendMessageStream).toHaveBeenCalledTimes(2); expect(mockCoreExecuteToolCall).toHaveBeenCalledWith( mockConfig, - expect.objectContaining({ callId: 'fc1', name: 'testTool' }), + expect.objectContaining({ name: 'testTool' }), mockToolRegistry, expect.any(AbortSignal), ); - expect(mockChat.sendMessageStream).toHaveBeenLastCalledWith( - expect.objectContaining({ - message: [toolResponsePart], - }), - expect.any(String), + expect(mockGeminiClient.sendMessageStream).toHaveBeenNthCalledWith( + 2, + [{ text: 'Tool response' }], + expect.any(AbortSignal), + 'prompt-id-2', ); - expect(mockProcessStdoutWrite).toHaveBeenCalledWith('Final answer'); + expect(processStdoutSpy).toHaveBeenCalledWith('Final answer'); + expect(processStdoutSpy).toHaveBeenCalledWith('\n'); }); it('should handle error during tool execution', async () => { - const functionCall: FunctionCall = { - id: 'fcError', - name: 'errorTool', - args: {}, - }; - const errorResponsePart: Part = { - functionResponse: { + const toolCallEvent: ServerGeminiStreamEvent = { + type: GeminiEventType.ToolCallRequest, + value: { + callId: 'tool-1', name: 'errorTool', - id: 'fcError', - response: { error: 'Tool failed' }, + args: {}, + isClientInitiated: false, + prompt_id: 'prompt-id-3', }, }; - - const { executeToolCall: mockCoreExecuteToolCall } = await import( - '@google/gemini-cli-core' - ); - vi.mocked(mockCoreExecuteToolCall).mockResolvedValue({ - callId: 'fcError', - responseParts: [errorResponsePart], - resultDisplay: 'Tool execution failed badly', - error: new Error('Tool failed'), + mockCoreExecuteToolCall.mockResolvedValue({ + error: new Error('Tool execution failed badly'), }); - - const stream1 = (async function* () { - yield { functionCalls: [functionCall] } as GenerateContentResponse; - })(); - - const stream2 = (async function* () { - yield { - candidates: [ - { content: { parts: [{ text: 'Could not complete request.' }] } }, - ], - } as GenerateContentResponse; - })(); - mockChat.sendMessageStream - .mockResolvedValueOnce(stream1) - .mockResolvedValueOnce(stream2); - const consoleErrorSpy = vi - .spyOn(console, 'error') - .mockImplementation(() => {}); + mockGeminiClient.sendMessageStream.mockReturnValue( + createStreamFromEvents([toolCallEvent]), + ); await runNonInteractive(mockConfig, 'Trigger tool error', 'prompt-id-3'); @@ -201,75 +171,48 @@ describe('runNonInteractive', () => { expect(consoleErrorSpy).toHaveBeenCalledWith( 'Error executing tool errorTool: Tool execution failed badly', ); - expect(mockChat.sendMessageStream).toHaveBeenLastCalledWith( - expect.objectContaining({ - message: [errorResponsePart], - }), - expect.any(String), - ); - expect(mockProcessStdoutWrite).toHaveBeenCalledWith( - 'Could not complete request.', - ); + expect(processExitSpy).toHaveBeenCalledWith(1); }); it('should exit with error if sendMessageStream throws initially', async () => { const apiError = new Error('API connection failed'); - mockChat.sendMessageStream.mockRejectedValue(apiError); - const consoleErrorSpy = vi - .spyOn(console, 'error') - .mockImplementation(() => {}); + mockGeminiClient.sendMessageStream.mockImplementation(() => { + throw apiError; + }); await runNonInteractive(mockConfig, 'Initial fail', 'prompt-id-4'); expect(consoleErrorSpy).toHaveBeenCalledWith( '[API Error: API connection failed]', ); + expect(processExitSpy).toHaveBeenCalledWith(1); }); it('should not exit if a tool is not found, and should send error back to model', async () => { - const functionCall: FunctionCall = { - id: 'fcNotFound', - name: 'nonexistentTool', - args: {}, - }; - const errorResponsePart: Part = { - functionResponse: { + const toolCallEvent: ServerGeminiStreamEvent = { + type: GeminiEventType.ToolCallRequest, + value: { + callId: 'tool-1', name: 'nonexistentTool', - id: 'fcNotFound', - response: { error: 'Tool "nonexistentTool" not found in registry.' }, + args: {}, + isClientInitiated: false, + prompt_id: 'prompt-id-5', }, }; - - const { executeToolCall: mockCoreExecuteToolCall } = await import( - '@google/gemini-cli-core' - ); - vi.mocked(mockCoreExecuteToolCall).mockResolvedValue({ - callId: 'fcNotFound', - responseParts: [errorResponsePart], - resultDisplay: 'Tool "nonexistentTool" not found in registry.', + mockCoreExecuteToolCall.mockResolvedValue({ error: new Error('Tool "nonexistentTool" not found in registry.'), + resultDisplay: 'Tool "nonexistentTool" not found in registry.', }); + const finalResponse: ServerGeminiStreamEvent[] = [ + { + type: GeminiEventType.Content, + value: "Sorry, I can't find that tool.", + }, + ]; - const stream1 = (async function* () { - yield { functionCalls: [functionCall] } as GenerateContentResponse; - })(); - const stream2 = (async function* () { - yield { - candidates: [ - { - content: { - parts: [{ text: 'Unfortunately the tool does not exist.' }], - }, - }, - ], - } as GenerateContentResponse; - })(); - mockChat.sendMessageStream - .mockResolvedValueOnce(stream1) - .mockResolvedValueOnce(stream2); - const consoleErrorSpy = vi - .spyOn(console, 'error') - .mockImplementation(() => {}); + mockGeminiClient.sendMessageStream + .mockReturnValueOnce(createStreamFromEvents([toolCallEvent])) + .mockReturnValueOnce(createStreamFromEvents(finalResponse)); await runNonInteractive( mockConfig, @@ -277,68 +220,23 @@ describe('runNonInteractive', () => { 'prompt-id-5', ); + expect(mockCoreExecuteToolCall).toHaveBeenCalled(); expect(consoleErrorSpy).toHaveBeenCalledWith( 'Error executing tool nonexistentTool: Tool "nonexistentTool" not found in registry.', ); - - expect(mockProcessExit).not.toHaveBeenCalled(); - - expect(mockChat.sendMessageStream).toHaveBeenCalledTimes(2); - expect(mockChat.sendMessageStream).toHaveBeenLastCalledWith( - expect.objectContaining({ - message: [errorResponsePart], - }), - expect.any(String), - ); - - expect(mockProcessStdoutWrite).toHaveBeenCalledWith( - 'Unfortunately the tool does not exist.', + expect(processExitSpy).not.toHaveBeenCalled(); + expect(mockGeminiClient.sendMessageStream).toHaveBeenCalledTimes(2); + expect(processStdoutSpy).toHaveBeenCalledWith( + "Sorry, I can't find that tool.", ); }); it('should exit when max session turns are exceeded', async () => { - const functionCall: FunctionCall = { - id: 'fcLoop', - name: 'loopTool', - args: {}, - }; - const toolResponsePart: Part = { - functionResponse: { - name: 'loopTool', - id: 'fcLoop', - response: { result: 'still looping' }, - }, - }; - - // Config with a max turn of 1 - vi.mocked(mockConfig.getMaxSessionTurns).mockReturnValue(1); - - const { executeToolCall: mockCoreExecuteToolCall } = await import( - '@google/gemini-cli-core' - ); - vi.mocked(mockCoreExecuteToolCall).mockResolvedValue({ - callId: 'fcLoop', - responseParts: [toolResponsePart], - resultDisplay: 'Still looping', - error: undefined, - }); - - const stream = (async function* () { - yield { functionCalls: [functionCall] } as GenerateContentResponse; - })(); - - mockChat.sendMessageStream.mockResolvedValue(stream); - const consoleErrorSpy = vi - .spyOn(console, 'error') - .mockImplementation(() => {}); - - await runNonInteractive(mockConfig, 'Trigger loop'); - - expect(mockChat.sendMessageStream).toHaveBeenCalledTimes(1); + vi.mocked(mockConfig.getMaxSessionTurns).mockReturnValue(0); + await runNonInteractive(mockConfig, 'Trigger loop', 'prompt-id-6'); + expect(mockGeminiClient.sendMessageStream).toHaveBeenCalledTimes(1); expect(consoleErrorSpy).toHaveBeenCalledWith( - ` - Reached max session turns for this session. Increase the number of turns by specifying maxSessionTurns in settings.json.`, + '\n Reached max session turns for this session. Increase the number of turns by specifying maxSessionTurns in settings.json.', ); - expect(mockProcessExit).not.toHaveBeenCalled(); }); }); diff --git a/packages/cli/src/nonInteractiveCli.ts b/packages/cli/src/nonInteractiveCli.ts index 7bc0f6aa480..e228f7084dd 100644 --- a/packages/cli/src/nonInteractiveCli.ts +++ b/packages/cli/src/nonInteractiveCli.ts @@ -11,38 +11,12 @@ import { ToolRegistry, shutdownTelemetry, isTelemetrySdkInitialized, + GeminiEventType, } from '@google/gemini-cli-core'; -import { - Content, - Part, - FunctionCall, - GenerateContentResponse, -} from '@google/genai'; +import { Content, Part, FunctionCall } from '@google/genai'; import { parseAndFormatApiError } from './ui/utils/errorParsing.js'; -function getResponseText(response: GenerateContentResponse): string | null { - if (response.candidates && response.candidates.length > 0) { - const candidate = response.candidates[0]; - if ( - candidate.content && - candidate.content.parts && - candidate.content.parts.length > 0 - ) { - // We are running in headless mode so we don't need to return thoughts to STDOUT. - const thoughtPart = candidate.content.parts[0]; - if (thoughtPart?.thought) { - return null; - } - return candidate.content.parts - .filter((part) => part.text) - .map((part) => part.text) - .join(''); - } - } - return null; -} - export async function runNonInteractive( config: Config, input: string, @@ -60,7 +34,6 @@ export async function runNonInteractive( const geminiClient = config.getGeminiClient(); const toolRegistry: ToolRegistry = await config.getToolRegistry(); - const chat = await geminiClient.getChat(); const abortController = new AbortController(); let currentMessages: Content[] = [{ role: 'user', parts: [{ text: input }] }]; let turnCount = 0; @@ -78,30 +51,28 @@ export async function runNonInteractive( } const functionCalls: FunctionCall[] = []; - const responseStream = await chat.sendMessageStream( - { - message: currentMessages[0]?.parts || [], // Ensure parts are always provided - config: { - abortSignal: abortController.signal, - tools: [ - { functionDeclarations: toolRegistry.getFunctionDeclarations() }, - ], - }, - }, + const responseStream = geminiClient.sendMessageStream( + currentMessages[0]?.parts || [], + abortController.signal, prompt_id, ); - for await (const resp of responseStream) { + for await (const event of responseStream) { if (abortController.signal.aborted) { console.error('Operation cancelled.'); return; } - const textPart = getResponseText(resp); - if (textPart) { - process.stdout.write(textPart); - } - if (resp.functionCalls) { - functionCalls.push(...resp.functionCalls); + + if (event.type === GeminiEventType.Content) { + process.stdout.write(event.value); + } else if (event.type === GeminiEventType.ToolCallRequest) { + const toolCallRequest = event.value; + const fc: FunctionCall = { + name: toolCallRequest.name, + args: toolCallRequest.args, + id: toolCallRequest.callId, + }; + functionCalls.push(fc); } } From d26577aeea54c9fd6f146bfee4e6a492881661da Mon Sep 17 00:00:00 2001 From: Anjali Sridhar Date: Tue, 29 Jul 2025 23:48:40 -0700 Subject: [PATCH 02/33] wip --- packages/cli/src/nonInteractiveCli.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/cli/src/nonInteractiveCli.test.ts b/packages/cli/src/nonInteractiveCli.test.ts index bd79c41f13e..9f2287252b6 100644 --- a/packages/cli/src/nonInteractiveCli.test.ts +++ b/packages/cli/src/nonInteractiveCli.test.ts @@ -234,7 +234,6 @@ describe('runNonInteractive', () => { it('should exit when max session turns are exceeded', async () => { vi.mocked(mockConfig.getMaxSessionTurns).mockReturnValue(0); await runNonInteractive(mockConfig, 'Trigger loop', 'prompt-id-6'); - expect(mockGeminiClient.sendMessageStream).toHaveBeenCalledTimes(1); expect(consoleErrorSpy).toHaveBeenCalledWith( '\n Reached max session turns for this session. Increase the number of turns by specifying maxSessionTurns in settings.json.', ); From 17bb9f16a268c3518edf08aa22a44f7db5e1556c Mon Sep 17 00:00:00 2001 From: Anjali Sridhar Date: Tue, 29 Jul 2025 23:57:22 -0700 Subject: [PATCH 03/33] wip --- packages/cli/src/nonInteractiveCli.test.ts | 1 + packages/cli/src/nonInteractiveCli.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/nonInteractiveCli.test.ts b/packages/cli/src/nonInteractiveCli.test.ts index 9f2287252b6..a0fc6f9f60a 100644 --- a/packages/cli/src/nonInteractiveCli.test.ts +++ b/packages/cli/src/nonInteractiveCli.test.ts @@ -24,6 +24,7 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => { ...original, executeToolCall: vi.fn(), shutdownTelemetry: vi.fn(), + isTelemetrySdkInitialized: vi.fn().mockReturnValue(true), }; }); diff --git a/packages/cli/src/nonInteractiveCli.ts b/packages/cli/src/nonInteractiveCli.ts index e228f7084dd..1d0a7f3d11a 100644 --- a/packages/cli/src/nonInteractiveCli.ts +++ b/packages/cli/src/nonInteractiveCli.ts @@ -41,7 +41,7 @@ export async function runNonInteractive( while (true) { turnCount++; if ( - config.getMaxSessionTurns() > 0 && + config.getMaxSessionTurns() >= 0 && turnCount > config.getMaxSessionTurns() ) { console.error( From 3e89e1ba4832ef37fa0fa5dfe45d6d7287f1c8e6 Mon Sep 17 00:00:00 2001 From: Anjali Sridhar Date: Thu, 31 Jul 2025 21:38:39 -0700 Subject: [PATCH 04/33] wip --- packages/cli/src/nonInteractiveCli.ts | 40 +++++++++++++++++++++++++++ packages/core/src/core/client.ts | 2 +- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/nonInteractiveCli.ts b/packages/cli/src/nonInteractiveCli.ts index 1d0a7f3d11a..25cd40efa0a 100644 --- a/packages/cli/src/nonInteractiveCli.ts +++ b/packages/cli/src/nonInteractiveCli.ts @@ -16,6 +16,42 @@ import { import { Content, Part, FunctionCall } from '@google/genai'; import { parseAndFormatApiError } from './ui/utils/errorParsing.js'; +import { ToolCallResponseInfo } from '@google/gemini-cli-core'; + +const DEBUG = process.env.GEMINI_DEBUG === 'true'; + + +function logToolCallRequest( + config: Config, + requestInfo: ToolCallRequestInfo, +) { + process.stdout.write( + `Tool call request: [${requestInfo.callId}] ${requestInfo.name} => ${JSON.stringify(requestInfo.args)}\n`, + ); +} + +function logToolCallResult( + config: Config, + requestInfo: ToolCallRequestInfo, + toolResponse: ToolCallResponseInfo, +) { + const status = toolResponse.error ? 'ERROR' : 'OK'; + if (toolResponse.error) { + process.stdout.write( + `Tool call status: ${status} ${requestInfo.name} => ${toolResponse.error.message}\n`, + ); + } else { + if (toolResponse.resultDisplay) { + process.stdout.write( + `Tool call status: ${status} ${requestInfo.name} => ${toolResponse.resultDisplay}\n`, + ); + } else { + process.stdout.write( + `Tool call status: ${status} ${requestInfo.name}\n`, + ); + } + } +} export async function runNonInteractive( config: Config, @@ -23,6 +59,7 @@ export async function runNonInteractive( prompt_id: string, ): Promise { await config.initialize(); + process.stdout.write(`User: ${input}\n`); // Handle EPIPE errors when the output is piped to a command that closes early. process.stdout.on('error', (err: NodeJS.ErrnoException) => { if (err.code === 'EPIPE') { @@ -67,6 +104,7 @@ export async function runNonInteractive( process.stdout.write(event.value); } else if (event.type === GeminiEventType.ToolCallRequest) { const toolCallRequest = event.value; + logToolCallRequest(config, toolCallRequest); const fc: FunctionCall = { name: toolCallRequest.name, args: toolCallRequest.args, @@ -96,6 +134,8 @@ export async function runNonInteractive( abortController.signal, ); + logToolCallResult(config, requestInfo, toolResponse); + if (toolResponse.error) { const isToolNotFound = toolResponse.error.message.includes( 'not found in registry', diff --git a/packages/core/src/core/client.ts b/packages/core/src/core/client.ts index 5b26e32c53a..3dba67a5b80 100644 --- a/packages/core/src/core/client.ts +++ b/packages/core/src/core/client.ts @@ -386,7 +386,7 @@ export class GeminiClient { } const turn = new Turn(this.getChat(), prompt_id); - + console.log(this.getChat().getHistory(false)); const loopDetected = await this.loopDetector.turnStarted(signal); if (loopDetected) { yield { type: GeminiEventType.LoopDetected }; From d2271a0cd9971948bb167bff78396dc8f20a8a82 Mon Sep 17 00:00:00 2001 From: Anjali Sridhar Date: Thu, 31 Jul 2025 22:29:36 -0700 Subject: [PATCH 05/33] wip, tool v1 --- packages/core/src/config/config.ts | 2 + packages/core/src/core/prompts.ts | 7 +- packages/core/src/index.ts | 1 + packages/core/src/tools/tools.ts | 1 + packages/core/src/tools/write-todos.ts | 260 +++++++++++++++++++++++++ 5 files changed, 270 insertions(+), 1 deletion(-) create mode 100644 packages/core/src/tools/write-todos.ts diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index c92fb6238b5..baf10a5cecc 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -47,6 +47,7 @@ import { ClearcutLogger } from '../telemetry/clearcut-logger/clearcut-logger.js' import { shouldAttemptBrowserLaunch } from '../utils/browser.js'; import { MCPOAuthConfig } from '../mcp/oauth-provider.js'; import { IdeClient } from '../ide/ide-client.js'; +import { WriteTodosTool } from '../tools/write-todos.js'; // Re-export OAuth config type export type { MCPOAuthConfig }; @@ -641,6 +642,7 @@ export class Config { registerCoreTool(ShellTool, this); registerCoreTool(MemoryTool); registerCoreTool(WebSearchTool, this); + registerCoreTool(WriteTodosTool, this); await registry.discoverAllTools(); return registry; diff --git a/packages/core/src/core/prompts.ts b/packages/core/src/core/prompts.ts index b97264d7e07..a69535435a3 100644 --- a/packages/core/src/core/prompts.ts +++ b/packages/core/src/core/prompts.ts @@ -18,6 +18,7 @@ import { WriteFileTool } from '../tools/write-file.js'; import process from 'node:process'; import { isGitRepository } from '../utils/gitUtils.js'; import { MemoryTool, GEMINI_CONFIG_DIR } from '../tools/memoryTool.js'; +import { WriteTodosTool } from '../tools/write-todos.js'; export function getCoreSystemPrompt(userMemory?: string): string { // if GEMINI_SYSTEM_MD is set (and not 0|false), override system prompt from file @@ -62,6 +63,10 @@ You are an interactive CLI agent specializing in software engineering tasks. You - **Path Construction:** Before using any file system tool (e.g., ${ReadFileTool.Name}' or '${WriteFileTool.Name}'), you must construct the full absolute path for the file_path argument. Always combine the absolute path of the project's root directory with the file's path relative to the root. For example, if the project root is /path/to/project/ and the file is foo/bar/baz.txt, the final path you must use is /path/to/project/foo/bar/baz.txt. If the user provides a relative path, you must resolve it against the root directory to create an absolute path. - **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. + +YOU MUST create and maintain a TODO list using '${WriteTodosTool.Name}' tool to make an initial plan, keep track of it and show the user the current execution state. + + # Primary Workflows ## Software Engineering Tasks @@ -324,7 +329,7 @@ The structure MUST be as follows: - Build Command: \`npm run build\` - Testing: Tests are run with \`npm test\`. Test files must end in \`.test.ts\`. - API Endpoint: The primary API endpoint is \`https://api.example.com/v2\`. - + --> diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index ecc408fea37..c1216e28ef2 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -73,6 +73,7 @@ export * from './tools/web-search.js'; export * from './tools/read-many-files.js'; export * from './tools/mcp-client.js'; export * from './tools/mcp-tool.js'; +export * from './tools/write-todos.js'; // MCP OAuth export { MCPOAuthProvider } from './mcp/oauth-provider.js'; diff --git a/packages/core/src/tools/tools.ts b/packages/core/src/tools/tools.ts index 0d7b402a8bb..ede71b90bb5 100644 --- a/packages/core/src/tools/tools.ts +++ b/packages/core/src/tools/tools.ts @@ -297,6 +297,7 @@ export enum Icon { Pencil = 'pencil', Regex = 'regex', Terminal = 'terminal', + Checklist = 'checklist', } export interface ToolLocation { diff --git a/packages/core/src/tools/write-todos.ts b/packages/core/src/tools/write-todos.ts new file mode 100644 index 00000000000..2843cc1dfe4 --- /dev/null +++ b/packages/core/src/tools/write-todos.ts @@ -0,0 +1,260 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { BaseTool, Icon, ToolResult } from './tools.js'; +import { SchemaValidator } from '../utils/schemaValidator.js'; +import { Type } from '@google/genai'; + +export const WRITE_TODOS_DESCRIPTION = `Use this tool to create and manage a structured task list for your current work session. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user. +It also helps the user understand the progress of the task and overall progress of their requests. + +## When to Use This Tool +Use this tool proactively in these scenarios: + +1. Complex multi-step tasks - When a task requires 3 or more distinct steps or actions +2. Non-trivial and complex tasks - Tasks that require careful planning or multiple operations +3. User explicitly requests todo list - When the user directly asks you to use the todo list +4. User provides multiple tasks - When users provide a list of things to be done (numbered or comma-separated) +5. After receiving new instructions - Immediately capture user requirements as todos +6. When you start working on a task - Mark it as in_progress BEFORE beginning work. Ideally you should only have one todo as in_progress at a time +7. After completing a task - Mark it as completed and add any new follow-up tasks discovered during implementation + +## When NOT to Use This Tool + +Skip using this tool when: +1. There is only a single, straightforward task +2. The task is trivial and tracking it provides no organizational benefit +3. The task can be completed in less than 3 trivial steps +4. The task is purely conversational or informational + +NOTE that you should not use this tool if there is only one trivial task to do. In this case you are better off just doing the task directly. + +## Examples of When to Use the Todo List + + +User: I want to add a dark mode toggle to the application settings. Make sure you run the tests and build when you're done! +Assistant: I'll help add a dark mode toggle to your application settings. Let me create a todo list to track this implementation. +*Creates todo list with the following items:* +1. Create dark mode toggle component in Settings page +2. Add dark mode state management (context/store) +3. Implement CSS-in-JS styles for dark theme +4. Update existing components to support theme switching +5. Run tests and build process, addressing any failures or errors that occur +*Begins working on the first task* + + +The assistant used the todo list because: +1. Adding dark mode is a multi-step feature requiring UI, state management, and styling changes +2. The user explicitly requested tests and build be run afterward +3. The assistant inferred that tests and build need to pass by adding "Ensure tests and build succeed" as the final task + + + + + +User: Help me rename the function getCwd to getCurrentWorkingDirectory across my project +Assistant: Let me first search through your codebase to find all occurrences of 'getCwd'. +*Uses search tools to locate all instances of getCwd in the codebase* +Assistant: I've found 15 instances of 'getCwd' across 8 different files. Let me create a todo list to track these changes. +*Creates todo list with specific items for each file that needs updating* + + +The assistant used the todo list because: +1. First, the assistant searched to understand the scope of the task +2. Upon finding multiple occurrences across different files, it determined this was a complex task with multiple steps +3. The todo list helps ensure every instance is tracked and updated systematically +4. This approach prevents missing any occurrences and maintains consistency + + + +## Examples of When NOT to Use the Todo List + + +User: How do I print 'Hello World' in Python? +Assistant: In Python, you can print "Hello World" with this simple code: + +\`\`\`python +print("Hello World") +\`\`\` +This will output the text "Hello World" to the console when executed. + + +The assistant did not use the todo list because this is a single, trivial task that can be completed in one step. There's no need to track multiple tasks or steps for such a straightforward request. + + + + +User: What time is it in Tokyo right now? +Assistant: I'll check the current time in Tokyo for you. + +*Searches for current time in Tokyo* + +The current time in Tokyo, Japan is [current time]. Tokyo is in the Japan Standard Time (JST) zone, which is UTC+9. + + +The assistant did not use the todo list because this is a single information lookup with immediate results. There are no multiple steps to track or organize, making the todo list unnecessary for this straightforward request. + + + +## Task States and Management + +1. **Task States**: Use these states to track progress: + - pending: Task not yet started + - in_progress: Currently working on (limit to ONE task at a time) + - completed: Task finished successfully + +2. **Task Management**: + - Update task status in real-time as you work + - Mark tasks complete IMMEDIATELY after finishing (don't batch completions) + - Only have ONE task in_progress at any time + - Complete current tasks before starting new ones + - Remove tasks that are no longer relevant from the list entirely + +3. **Task Completion Requirements**: + - ONLY mark a task as completed when you have FULLY accomplished it + - If you encounter errors, blockers, or cannot finish, keep the task as in_progress + - When blocked, create a new task describing what needs to be resolved + - Never mark a task as completed if: + - There are unresolved issues or errors + - Work is partial or incomplete + - You encountered blockers that prevent completion + - You couldn't find necessary resources or dependencies + - Quality standards haven't been met + +4. **Task Breakdown**: + - Create specific, actionable items + - Break complex tasks into smaller, manageable steps + - Use clear, descriptive task names + +When in doubt, use this tool. Being proactive with task management demonstrates attentiveness and ensures you complete all requirements successfully.`; +export type TodoStatus = 'pending' | 'in_progress' | 'completed'; + +export interface Todo { + description: string; + status: TodoStatus; +} + +export interface WriteTodosToolParams { + /** + * The full list of todos. This will overwrite any existing list. + */ + todos: Todo[]; +} + +export class WriteTodosTool extends BaseTool { + static readonly Name: string = 'write_todos_list'; + + // In-memory store for the session's todos. + private static todos: Todo[] = []; + + constructor() { + super( + WriteTodosTool.Name, + 'Write Todos', + WRITE_TODOS_DESCRIPTION, + Icon.Checklist, + { + properties: { + todos: { + type: Type.ARRAY, + description: + 'The complete list of todo items. This will replace the existing list.', + items: { + type: Type.OBJECT, + description: 'A single todo item.', + properties: { + description: { + type: Type.STRING, + description: 'The description of the task.', + }, + status: { + type: Type.STRING, + description: 'The current status of the task.', + enum: ['pending', 'in_progress', 'completed'], + }, + }, + required: ['description', 'status'], + }, + }, + }, + required: ['todos'], + type: Type.OBJECT, + }, + ); + } + + /** + * Static method to get the current list of todos. + * NOTE: This is intended for testing purposes. + * @returns The current list of todos. + */ + static getTodos(): Todo[] { + return WriteTodosTool.todos; + } + + /** + * Static method to reset the list of todos. + * NOTE: This is intended for testing purposes. + */ + static resetTodos(): void { + WriteTodosTool.todos = []; + } + + validateToolParams(params: WriteTodosToolParams): string | null { + const errors = SchemaValidator.validate(this.schema.parameters, params); + if (errors) { + return errors; + } + + const inProgressCount = params.todos.filter( + (todo) => todo.status === 'in_progress', + ).length; + if (inProgressCount > 1) { + return 'Invalid parameters: Only one task can be "in_progress" at a time.'; + } + + return null; + } + + getDescription(params: WriteTodosToolParams): string { + const count = params.todos?.length ?? 0; + if (count === 0) { + return 'Cleared todo list'; + } + return `Set ${count} todo(s)`; + } + + async execute( + params: WriteTodosToolParams, + _signal: AbortSignal, + ): Promise { + const validationError = this.validateToolParams(params); + if (validationError) { + return { + llmContent: `Error: Invalid parameters provided. Reason: ${validationError}`, + returnDisplay: validationError, + }; + } + + WriteTodosTool.todos = params.todos; + + const todoListString = WriteTodosTool.todos + .map( + (todo, index) => `${index + 1}. [${todo.status}] ${todo.description}`, + ) + .join('\n'); + + const llmContent = + WriteTodosTool.todos.length > 0 + ? `Successfully updated the todo list. The current list is now:\n${todoListString}` + : 'Successfully cleared the todo list.'; + + return { + llmContent, + returnDisplay: 'Todo list updated.', + }; + } +} From 8cd0bee6a6e28983213a0a0099c1559eccffa02c Mon Sep 17 00:00:00 2001 From: Anjali Sridhar Date: Thu, 31 Jul 2025 23:06:35 -0700 Subject: [PATCH 06/33] wip --- packages/core/src/core/prompts.ts | 1 + packages/core/src/tools/write-todos.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/src/core/prompts.ts b/packages/core/src/core/prompts.ts index a69535435a3..f2e94a3a0f8 100644 --- a/packages/core/src/core/prompts.ts +++ b/packages/core/src/core/prompts.ts @@ -65,6 +65,7 @@ You are an interactive CLI agent specializing in software engineering tasks. You YOU MUST create and maintain a TODO list using '${WriteTodosTool.Name}' tool to make an initial plan, keep track of it and show the user the current execution state. +Breakdown the task as explained by the '${WriteTodosTool.Name}' tool description to discrete concrete executable steps. # Primary Workflows diff --git a/packages/core/src/tools/write-todos.ts b/packages/core/src/tools/write-todos.ts index 2843cc1dfe4..d658ae824f6 100644 --- a/packages/core/src/tools/write-todos.ts +++ b/packages/core/src/tools/write-todos.ts @@ -254,7 +254,7 @@ export class WriteTodosTool extends BaseTool { return { llmContent, - returnDisplay: 'Todo list updated.', + returnDisplay: llmContent, }; } } From a6afa06339222376c1e6d99d9afa145b8e1db791 Mon Sep 17 00:00:00 2001 From: Anjali Sridhar Date: Thu, 31 Jul 2025 23:08:18 -0700 Subject: [PATCH 07/33] wip --- packages/core/src/tools/write-todos.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/tools/write-todos.ts b/packages/core/src/tools/write-todos.ts index d658ae824f6..67edda9fbc0 100644 --- a/packages/core/src/tools/write-todos.ts +++ b/packages/core/src/tools/write-todos.ts @@ -7,7 +7,7 @@ import { BaseTool, Icon, ToolResult } from './tools.js'; import { SchemaValidator } from '../utils/schemaValidator.js'; import { Type } from '@google/genai'; - +// Adapted from langchain/deepagents for experimentation export const WRITE_TODOS_DESCRIPTION = `Use this tool to create and manage a structured task list for your current work session. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user. It also helps the user understand the progress of the task and overall progress of their requests. From 825624cc26be13b4802435e8be51e67a17b52913 Mon Sep 17 00:00:00 2001 From: Anjali Sridhar Date: Fri, 1 Aug 2025 15:30:03 -0700 Subject: [PATCH 08/33] wip --- packages/cli/src/nonInteractiveCli.ts | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/cli/src/nonInteractiveCli.ts b/packages/cli/src/nonInteractiveCli.ts index 25cd40efa0a..e7bc396955e 100644 --- a/packages/cli/src/nonInteractiveCli.ts +++ b/packages/cli/src/nonInteractiveCli.ts @@ -20,13 +20,22 @@ import { ToolCallResponseInfo } from '@google/gemini-cli-core'; const DEBUG = process.env.GEMINI_DEBUG === 'true'; +function logModelResponse(response: Content) { + response.parts?.map((part) => { + if (part.thought) { + process.stdout.write('Gemini Thoughts 💭: ' + part.text + '\n'); + } else { + process.stdout.write('Gemini 🤖: ' + part.text + '\n'); + } + }); +} function logToolCallRequest( config: Config, requestInfo: ToolCallRequestInfo, ) { process.stdout.write( - `Tool call request: [${requestInfo.callId}] ${requestInfo.name} => ${JSON.stringify(requestInfo.args)}\n`, + `Tool call request:🔨 [${requestInfo.name}] => ${JSON.stringify(requestInfo.args)}\n`, ); } @@ -38,16 +47,16 @@ function logToolCallResult( const status = toolResponse.error ? 'ERROR' : 'OK'; if (toolResponse.error) { process.stdout.write( - `Tool call status: ${status} ${requestInfo.name} => ${toolResponse.error.message}\n`, + `Tool call status:❌ ${status} ${requestInfo.name} => ${toolResponse.error.message}\n`, ); } else { if (toolResponse.resultDisplay) { process.stdout.write( - `Tool call status: ${status} ${requestInfo.name} => ${toolResponse.resultDisplay}\n`, + `Tool call status:✅ ${status} ${requestInfo.name} => ${toolResponse.resultDisplay}\n`, ); } else { process.stdout.write( - `Tool call status: ${status} ${requestInfo.name}\n`, + `Tool call status:✅ ${status} ${requestInfo.name}\n`, ); } } @@ -59,7 +68,6 @@ export async function runNonInteractive( prompt_id: string, ): Promise { await config.initialize(); - process.stdout.write(`User: ${input}\n`); // Handle EPIPE errors when the output is piped to a command that closes early. process.stdout.on('error', (err: NodeJS.ErrnoException) => { if (err.code === 'EPIPE') { @@ -73,6 +81,7 @@ export async function runNonInteractive( const abortController = new AbortController(); let currentMessages: Content[] = [{ role: 'user', parts: [{ text: input }] }]; + process.stdout.write(`User 🖥️: ${input}\n`); let turnCount = 0; try { while (true) { @@ -101,6 +110,7 @@ export async function runNonInteractive( } if (event.type === GeminiEventType.Content) { + logModelResponse(JSON.parse(event.value)); process.stdout.write(event.value); } else if (event.type === GeminiEventType.ToolCallRequest) { const toolCallRequest = event.value; From fb5aa8dfe15eedee34295489bb87190e7d7093a4 Mon Sep 17 00:00:00 2001 From: Anjali Sridhar Date: Mon, 4 Aug 2025 15:58:00 -0700 Subject: [PATCH 09/33] wip --- packages/cli/src/nonInteractiveCli.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/nonInteractiveCli.ts b/packages/cli/src/nonInteractiveCli.ts index e7bc396955e..d417cb5fb2b 100644 --- a/packages/cli/src/nonInteractiveCli.ts +++ b/packages/cli/src/nonInteractiveCli.ts @@ -111,7 +111,7 @@ export async function runNonInteractive( if (event.type === GeminiEventType.Content) { logModelResponse(JSON.parse(event.value)); - process.stdout.write(event.value); + // process.stdout.write(event.value); } else if (event.type === GeminiEventType.ToolCallRequest) { const toolCallRequest = event.value; logToolCallRequest(config, toolCallRequest); From b8d227e2a70567327dd2605a9b50e61c9e1b976b Mon Sep 17 00:00:00 2001 From: Anjali Sridhar Date: Tue, 5 Aug 2025 16:48:19 -0700 Subject: [PATCH 10/33] wip --- packages/cli/src/nonInteractiveCli.test.ts | 9 +- packages/cli/src/nonInteractiveCli.ts | 42 +++---- .../src/extension.test.ts | 115 ++++++++++++++++++ 3 files changed, 135 insertions(+), 31 deletions(-) create mode 100644 packages/vscode-ide-companion/src/extension.test.ts diff --git a/packages/cli/src/nonInteractiveCli.test.ts b/packages/cli/src/nonInteractiveCli.test.ts index 938eb4e723a..b0466eefefb 100644 --- a/packages/cli/src/nonInteractiveCli.test.ts +++ b/packages/cli/src/nonInteractiveCli.test.ts @@ -101,9 +101,7 @@ describe('runNonInteractive', () => { expect.any(AbortSignal), 'prompt-id-1', ); - expect(processStdoutSpy).toHaveBeenCalledWith('Hello'); - expect(processStdoutSpy).toHaveBeenCalledWith(' World'); - expect(processStdoutSpy).toHaveBeenCalledWith('\n'); + expect(processStdoutSpy).toHaveBeenCalledWith('Gemini 🤖: Hello World\n'); expect(mockShutdownTelemetry).toHaveBeenCalled(); }); @@ -145,8 +143,7 @@ describe('runNonInteractive', () => { expect.any(AbortSignal), 'prompt-id-2', ); - expect(processStdoutSpy).toHaveBeenCalledWith('Final answer'); - expect(processStdoutSpy).toHaveBeenCalledWith('\n'); + expect(processStdoutSpy).toHaveBeenCalledWith('Gemini 🤖: Final answer\n'); }); it('should handle error during tool execution', async () => { @@ -230,7 +227,7 @@ describe('runNonInteractive', () => { expect(processExitSpy).not.toHaveBeenCalled(); expect(mockGeminiClient.sendMessageStream).toHaveBeenCalledTimes(2); expect(processStdoutSpy).toHaveBeenCalledWith( - "Sorry, I can't find that tool.", + "Gemini 🤖: Sorry, I can't find that tool.\n", ); }); diff --git a/packages/cli/src/nonInteractiveCli.ts b/packages/cli/src/nonInteractiveCli.ts index a6840fbd9df..70e4856a969 100644 --- a/packages/cli/src/nonInteractiveCli.ts +++ b/packages/cli/src/nonInteractiveCli.ts @@ -21,25 +21,6 @@ import { ToolCallResponseInfo } from '@google/gemini-cli-core'; const DEBUG = process.env.GEMINI_DEBUG === 'true'; -function logModelResponse(response: Content) { - response.parts?.map((part) => { - if (part.thought) { - process.stdout.write('Gemini Thoughts 💭: ' + part.text + '\n'); - } else { - process.stdout.write('Gemini 🤖: ' + part.text + '\n'); - } - }); -} - -function logToolCallRequest( - config: Config, - requestInfo: ToolCallRequestInfo, -) { - process.stdout.write( - `Tool call request:🔨 [${requestInfo.name}] => ${JSON.stringify(requestInfo.args)}\n`, - ); -} - function logToolCallResult( config: Config, requestInfo: ToolCallRequestInfo, @@ -48,16 +29,16 @@ function logToolCallResult( const status = toolResponse.error ? 'ERROR' : 'OK'; if (toolResponse.error) { process.stdout.write( - `Tool call status:❌ ${status} ${requestInfo.name} => ${toolResponse.error.message}\n`, + `\nTool call status:❌ ${status} ${requestInfo.name} => ${toolResponse.error.message}\n`, ); } else { if (toolResponse.resultDisplay) { process.stdout.write( - `Tool call status:✅ ${status} ${requestInfo.name} => ${toolResponse.resultDisplay}\n`, + `\nTool call status:✅ ${status} ${requestInfo.name} => ${toolResponse.resultDisplay}\n`, ); } else { process.stdout.write( - `Tool call status:✅ ${status} ${requestInfo.name}\n`, + `\nTool call status:✅ ${status} ${requestInfo.name}\n`, ); } } @@ -104,6 +85,9 @@ export async function runNonInteractive( prompt_id, ); + let thoughts = ""; + let response = ""; + let toolCalls = []; for await (const event of responseStream) { if (abortController.signal.aborted) { console.error('Operation cancelled.'); @@ -111,19 +95,27 @@ export async function runNonInteractive( } if (event.type === GeminiEventType.Content) { - logModelResponse(JSON.parse(event.value)); - // process.stdout.write(event.value); + response += event.value; + } else if (event.type === GeminiEventType.Thought) { + thoughts += event.value.description; } else if (event.type === GeminiEventType.ToolCallRequest) { const toolCallRequest = event.value; - logToolCallRequest(config, toolCallRequest); const fc: FunctionCall = { name: toolCallRequest.name, args: toolCallRequest.args, id: toolCallRequest.callId, }; functionCalls.push(fc); + toolCalls.push(toolCallRequest); } } + process.stdout.write(`\nGemini 🤖: ${response}\n`); + process.stdout.write(`\nThought 💭: ${thoughts}\n`); + for (const toolCall of toolCalls) { + process.stdout.write( + `\nTool call request:🔨 [${toolCall.name}] => ${JSON.stringify(toolCall.args)}\n`, + ); + } if (functionCalls.length > 0) { const toolResponseParts: Part[] = []; diff --git a/packages/vscode-ide-companion/src/extension.test.ts b/packages/vscode-ide-companion/src/extension.test.ts new file mode 100644 index 00000000000..bedb438d1dc --- /dev/null +++ b/packages/vscode-ide-companion/src/extension.test.ts @@ -0,0 +1,115 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as vscode from 'vscode'; +import { activate, deactivate } from './extension'; +import { IDEServer } from './ide-server'; +import { DiffManager } from './diff-manager'; + +vi.mock('vscode', () => ({ + window: { + createOutputChannel: vi.fn().mockReturnValue({ + appendLine: vi.fn(), + dispose: vi.fn(), + }), + showTextDocument: vi.fn(), + activeTextEditor: undefined, + }, + workspace: { + workspaceFolders: [], + onDidCloseTextDocument: vi.fn(), + registerTextDocumentContentProvider: vi.fn(), + onDidChangeWorkspaceFolders: vi.fn(), + }, + commands: { + registerCommand: vi.fn(), + }, + Uri: { + joinPath: vi.fn(), + }, + ExtensionContext: { + subscriptions: [], + environmentVariableCollection: { + replace: vi.fn(), + }, + }, +})); + +vi.mock('./ide-server'); +vi.mock('./diff-manager'); + +describe('extension', () => { + let context: vscode.ExtensionContext; + let mockIDEServer: IDEServer; + + beforeEach(() => { + context = { + subscriptions: [], + environmentVariableCollection: { + replace: vi.fn(), + }, + extensionUri: { fsPath: '/path/to/extension' } as vscode.Uri, + } as any; + mockIDEServer = new IDEServer(vi.fn(), new DiffManager({} as any, {} as any)); + (IDEServer as vi.Mock).mockImplementation(() => mockIDEServer); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('activate', () => { + it('should activate the extension', async () => { + await activate(context); + expect(vscode.window.createOutputChannel).toHaveBeenCalledWith( + 'Gemini CLI IDE Companion', + ); + expect(context.subscriptions.length).toBeGreaterThan(0); + expect(mockIDEServer.start).toHaveBeenCalledWith(context); + }); + + it('should update workspace path on activation', async () => { + (vscode.workspace.workspaceFolders as any) = [ + { uri: { fsPath: '/path/to/workspace' } }, + ]; + await activate(context); + expect( + context.environmentVariableCollection.replace, + ).toHaveBeenCalledWith( + 'GEMINI_CLI_IDE_WORKSPACE_PATH', + '/path/to/workspace', + ); + }); + + it('should register commands', async () => { + await activate(context); + expect(vscode.commands.registerCommand).toHaveBeenCalledWith( + 'gemini.diff.accept', + expect.any(Function), + ); + expect(vscode.commands.registerCommand).toHaveBeenCalledWith( + 'gemini.diff.cancel', + expect.any(Function), + ); + expect(vscode.commands.registerCommand).toHaveBeenCalledWith( + 'gemini-cli.runGeminiCLI', + expect.any(Function), + ); + expect(vscode.commands.registerCommand).toHaveBeenCalledWith( + 'gemini-cli.showNotices', + expect.any(Function), + ); + }); + }); + + describe('deactivate', () => { + it('should deactivate the extension', async () => { + await activate(context); + await deactivate(); + expect(mockIDEServer.stop).toHaveBeenCalled(); + }); + }); +}); From 2af022040f98366c0c6b2fc495f2e499e725965b Mon Sep 17 00:00:00 2001 From: Anjali Sridhar Date: Tue, 5 Aug 2025 16:48:59 -0700 Subject: [PATCH 11/33] wip --- .../src/extension.test.ts | 115 ------------------ 1 file changed, 115 deletions(-) delete mode 100644 packages/vscode-ide-companion/src/extension.test.ts diff --git a/packages/vscode-ide-companion/src/extension.test.ts b/packages/vscode-ide-companion/src/extension.test.ts deleted file mode 100644 index bedb438d1dc..00000000000 --- a/packages/vscode-ide-companion/src/extension.test.ts +++ /dev/null @@ -1,115 +0,0 @@ -/** - * @license - * Copyright 2025 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode'; -import { activate, deactivate } from './extension'; -import { IDEServer } from './ide-server'; -import { DiffManager } from './diff-manager'; - -vi.mock('vscode', () => ({ - window: { - createOutputChannel: vi.fn().mockReturnValue({ - appendLine: vi.fn(), - dispose: vi.fn(), - }), - showTextDocument: vi.fn(), - activeTextEditor: undefined, - }, - workspace: { - workspaceFolders: [], - onDidCloseTextDocument: vi.fn(), - registerTextDocumentContentProvider: vi.fn(), - onDidChangeWorkspaceFolders: vi.fn(), - }, - commands: { - registerCommand: vi.fn(), - }, - Uri: { - joinPath: vi.fn(), - }, - ExtensionContext: { - subscriptions: [], - environmentVariableCollection: { - replace: vi.fn(), - }, - }, -})); - -vi.mock('./ide-server'); -vi.mock('./diff-manager'); - -describe('extension', () => { - let context: vscode.ExtensionContext; - let mockIDEServer: IDEServer; - - beforeEach(() => { - context = { - subscriptions: [], - environmentVariableCollection: { - replace: vi.fn(), - }, - extensionUri: { fsPath: '/path/to/extension' } as vscode.Uri, - } as any; - mockIDEServer = new IDEServer(vi.fn(), new DiffManager({} as any, {} as any)); - (IDEServer as vi.Mock).mockImplementation(() => mockIDEServer); - }); - - afterEach(() => { - vi.clearAllMocks(); - }); - - describe('activate', () => { - it('should activate the extension', async () => { - await activate(context); - expect(vscode.window.createOutputChannel).toHaveBeenCalledWith( - 'Gemini CLI IDE Companion', - ); - expect(context.subscriptions.length).toBeGreaterThan(0); - expect(mockIDEServer.start).toHaveBeenCalledWith(context); - }); - - it('should update workspace path on activation', async () => { - (vscode.workspace.workspaceFolders as any) = [ - { uri: { fsPath: '/path/to/workspace' } }, - ]; - await activate(context); - expect( - context.environmentVariableCollection.replace, - ).toHaveBeenCalledWith( - 'GEMINI_CLI_IDE_WORKSPACE_PATH', - '/path/to/workspace', - ); - }); - - it('should register commands', async () => { - await activate(context); - expect(vscode.commands.registerCommand).toHaveBeenCalledWith( - 'gemini.diff.accept', - expect.any(Function), - ); - expect(vscode.commands.registerCommand).toHaveBeenCalledWith( - 'gemini.diff.cancel', - expect.any(Function), - ); - expect(vscode.commands.registerCommand).toHaveBeenCalledWith( - 'gemini-cli.runGeminiCLI', - expect.any(Function), - ); - expect(vscode.commands.registerCommand).toHaveBeenCalledWith( - 'gemini-cli.showNotices', - expect.any(Function), - ); - }); - }); - - describe('deactivate', () => { - it('should deactivate the extension', async () => { - await activate(context); - await deactivate(); - expect(mockIDEServer.stop).toHaveBeenCalled(); - }); - }); -}); From 241c866db2e5c55a1e4dd5614c82de44c33b7768 Mon Sep 17 00:00:00 2001 From: Anjali Sridhar Date: Tue, 5 Aug 2025 17:18:19 -0700 Subject: [PATCH 12/33] wip --- packages/cli/src/nonInteractiveCli.test.ts | 10 +++++++--- packages/cli/src/nonInteractiveCli.ts | 14 +++++--------- packages/core/src/core/client.ts | 1 - 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/packages/cli/src/nonInteractiveCli.test.ts b/packages/cli/src/nonInteractiveCli.test.ts index b0466eefefb..cb2370925d1 100644 --- a/packages/cli/src/nonInteractiveCli.test.ts +++ b/packages/cli/src/nonInteractiveCli.test.ts @@ -101,7 +101,9 @@ describe('runNonInteractive', () => { expect.any(AbortSignal), 'prompt-id-1', ); - expect(processStdoutSpy).toHaveBeenCalledWith('Gemini 🤖: Hello World\n'); + expect(processStdoutSpy).toHaveBeenCalledWith( + expect.stringContaining('Hello World'), + ); expect(mockShutdownTelemetry).toHaveBeenCalled(); }); @@ -143,7 +145,9 @@ describe('runNonInteractive', () => { expect.any(AbortSignal), 'prompt-id-2', ); - expect(processStdoutSpy).toHaveBeenCalledWith('Gemini 🤖: Final answer\n'); + expect(processStdoutSpy).toHaveBeenCalledWith( + expect.stringContaining('Final answer'), + ); }); it('should handle error during tool execution', async () => { @@ -227,7 +231,7 @@ describe('runNonInteractive', () => { expect(processExitSpy).not.toHaveBeenCalled(); expect(mockGeminiClient.sendMessageStream).toHaveBeenCalledTimes(2); expect(processStdoutSpy).toHaveBeenCalledWith( - "Gemini 🤖: Sorry, I can't find that tool.\n", + expect.stringContaining("Sorry, I can't find that tool."), ); }); diff --git a/packages/cli/src/nonInteractiveCli.ts b/packages/cli/src/nonInteractiveCli.ts index 70e4856a969..e6e6456fac3 100644 --- a/packages/cli/src/nonInteractiveCli.ts +++ b/packages/cli/src/nonInteractiveCli.ts @@ -7,6 +7,7 @@ import { Config, ToolCallRequestInfo, + ToolCallResponseInfo, executeToolCall, ToolRegistry, shutdownTelemetry, @@ -17,9 +18,6 @@ import { import { Content, Part, FunctionCall } from '@google/genai'; import { parseAndFormatApiError } from './ui/utils/errorParsing.js'; -import { ToolCallResponseInfo } from '@google/gemini-cli-core'; - -const DEBUG = process.env.GEMINI_DEBUG === 'true'; function logToolCallResult( config: Config, @@ -37,9 +35,7 @@ function logToolCallResult( `\nTool call status:✅ ${status} ${requestInfo.name} => ${toolResponse.resultDisplay}\n`, ); } else { - process.stdout.write( - `\nTool call status:✅ ${status} ${requestInfo.name}\n`, - ); + process.stdout.write(`Tool call status:✅ ${status} ${requestInfo.name}`); } } } @@ -85,9 +81,9 @@ export async function runNonInteractive( prompt_id, ); - let thoughts = ""; - let response = ""; - let toolCalls = []; + let thoughts = ''; + let response = ''; + const toolCalls = []; for await (const event of responseStream) { if (abortController.signal.aborted) { console.error('Operation cancelled.'); diff --git a/packages/core/src/core/client.ts b/packages/core/src/core/client.ts index 07ce967a57d..7421de7d4f0 100644 --- a/packages/core/src/core/client.ts +++ b/packages/core/src/core/client.ts @@ -421,7 +421,6 @@ export class GeminiClient { } const turn = new Turn(this.getChat(), prompt_id); - console.log(this.getChat().getHistory(false)); const loopDetected = await this.loopDetector.turnStarted(signal); if (loopDetected) { yield { type: GeminiEventType.LoopDetected }; From 7b4694325293f4ff27ec03311f7e17962020bab6 Mon Sep 17 00:00:00 2001 From: Anjali Sridhar Date: Tue, 5 Aug 2025 17:21:09 -0700 Subject: [PATCH 13/33] wip --- packages/core/src/core/client.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/core/client.ts b/packages/core/src/core/client.ts index 7421de7d4f0..3b6b57f9b3e 100644 --- a/packages/core/src/core/client.ts +++ b/packages/core/src/core/client.ts @@ -421,6 +421,7 @@ export class GeminiClient { } const turn = new Turn(this.getChat(), prompt_id); + const loopDetected = await this.loopDetector.turnStarted(signal); if (loopDetected) { yield { type: GeminiEventType.LoopDetected }; From 64868ac322e0269d8c9707ea26f84f8038792a6f Mon Sep 17 00:00:00 2001 From: Anjali Sridhar Date: Tue, 5 Aug 2025 17:22:09 -0700 Subject: [PATCH 14/33] wip --- packages/cli/src/nonInteractiveCli.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/nonInteractiveCli.ts b/packages/cli/src/nonInteractiveCli.ts index e6e6456fac3..967a73ced52 100644 --- a/packages/cli/src/nonInteractiveCli.ts +++ b/packages/cli/src/nonInteractiveCli.ts @@ -105,8 +105,12 @@ export async function runNonInteractive( toolCalls.push(toolCallRequest); } } - process.stdout.write(`\nGemini 🤖: ${response}\n`); - process.stdout.write(`\nThought 💭: ${thoughts}\n`); + if (thoughts) { + process.stdout.write(`\nThought 💭: ${thoughts}\n`); + } + if (response) { + process.stdout.write(`\nGemini 🤖: ${response}\n`); + } for (const toolCall of toolCalls) { process.stdout.write( `\nTool call request:🔨 [${toolCall.name}] => ${JSON.stringify(toolCall.args)}\n`, From e54f04e6df1bfb5c4d973cfe76cdc04ff740025c Mon Sep 17 00:00:00 2001 From: Anjali Sridhar Date: Tue, 5 Aug 2025 17:26:32 -0700 Subject: [PATCH 15/33] wip --- packages/cli/src/nonInteractiveCli.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/nonInteractiveCli.ts b/packages/cli/src/nonInteractiveCli.ts index 967a73ced52..6d094a525a7 100644 --- a/packages/cli/src/nonInteractiveCli.ts +++ b/packages/cli/src/nonInteractiveCli.ts @@ -35,7 +35,7 @@ function logToolCallResult( `\nTool call status:✅ ${status} ${requestInfo.name} => ${toolResponse.resultDisplay}\n`, ); } else { - process.stdout.write(`Tool call status:✅ ${status} ${requestInfo.name}`); + process.stdout.write(`\nTool call status:✅ ${status} ${requestInfo.name}\n`); } } } From 90e5a76f26c319150595ca8e914326ce2edfd3bb Mon Sep 17 00:00:00 2001 From: Anjali Sridhar Date: Tue, 5 Aug 2025 18:13:39 -0700 Subject: [PATCH 16/33] wip --- packages/cli/src/nonInteractiveCli.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/nonInteractiveCli.ts b/packages/cli/src/nonInteractiveCli.ts index 85cf998a9a3..69123eceb32 100644 --- a/packages/cli/src/nonInteractiveCli.ts +++ b/packages/cli/src/nonInteractiveCli.ts @@ -36,7 +36,9 @@ function logToolCallResult( `\nTool call status:✅ ${status} ${requestInfo.name} => ${toolResponse.resultDisplay}\n`, ); } else { - process.stdout.write(`\nTool call status:✅ ${status} ${requestInfo.name}\n`); + process.stdout.write( + `\nTool call status:✅ ${status} ${requestInfo.name}\n`, + ); } } } From 4941b10b722dad106a4e4da6dbde2b477f9655ac Mon Sep 17 00:00:00 2001 From: Anjali Sridhar Date: Wed, 13 Aug 2025 10:09:49 -0700 Subject: [PATCH 17/33] wip --- packages/core/src/tools/write-todos.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/core/src/tools/write-todos.ts b/packages/core/src/tools/write-todos.ts index 67edda9fbc0..036c071a3cb 100644 --- a/packages/core/src/tools/write-todos.ts +++ b/packages/core/src/tools/write-todos.ts @@ -11,9 +11,7 @@ import { Type } from '@google/genai'; export const WRITE_TODOS_DESCRIPTION = `Use this tool to create and manage a structured task list for your current work session. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user. It also helps the user understand the progress of the task and overall progress of their requests. -## When to Use This Tool -Use this tool proactively in these scenarios: - +Use this tool when: 1. Complex multi-step tasks - When a task requires 3 or more distinct steps or actions 2. Non-trivial and complex tasks - Tasks that require careful planning or multiple operations 3. User explicitly requests todo list - When the user directly asks you to use the todo list From 9073656a7fdd97b01bccf2164f3ebcda7f682f06 Mon Sep 17 00:00:00 2001 From: Anjali Sridhar Date: Fri, 29 Aug 2025 00:30:56 -0700 Subject: [PATCH 18/33] wip --- packages/core/src/tools/write-todos.ts | 29 -------------------------- 1 file changed, 29 deletions(-) diff --git a/packages/core/src/tools/write-todos.ts b/packages/core/src/tools/write-todos.ts index 036c071a3cb..8a7481c3424 100644 --- a/packages/core/src/tools/write-todos.ts +++ b/packages/core/src/tools/write-todos.ts @@ -97,35 +97,6 @@ The assistant did not use the todo list because this is a single information loo -## Task States and Management - -1. **Task States**: Use these states to track progress: - - pending: Task not yet started - - in_progress: Currently working on (limit to ONE task at a time) - - completed: Task finished successfully - -2. **Task Management**: - - Update task status in real-time as you work - - Mark tasks complete IMMEDIATELY after finishing (don't batch completions) - - Only have ONE task in_progress at any time - - Complete current tasks before starting new ones - - Remove tasks that are no longer relevant from the list entirely - -3. **Task Completion Requirements**: - - ONLY mark a task as completed when you have FULLY accomplished it - - If you encounter errors, blockers, or cannot finish, keep the task as in_progress - - When blocked, create a new task describing what needs to be resolved - - Never mark a task as completed if: - - There are unresolved issues or errors - - Work is partial or incomplete - - You encountered blockers that prevent completion - - You couldn't find necessary resources or dependencies - - Quality standards haven't been met - -4. **Task Breakdown**: - - Create specific, actionable items - - Break complex tasks into smaller, manageable steps - - Use clear, descriptive task names When in doubt, use this tool. Being proactive with task management demonstrates attentiveness and ensures you complete all requirements successfully.`; export type TodoStatus = 'pending' | 'in_progress' | 'completed'; From e170f5a92aeeb348e8b87f15834a328b7a0d7939 Mon Sep 17 00:00:00 2001 From: Anjali Sridhar Date: Tue, 2 Sep 2025 14:02:11 -0700 Subject: [PATCH 19/33] wip --- packages/core/src/tools/write-todos.ts | 99 +++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 3 deletions(-) diff --git a/packages/core/src/tools/write-todos.ts b/packages/core/src/tools/write-todos.ts index 7d729985df2..174922993a8 100644 --- a/packages/core/src/tools/write-todos.ts +++ b/packages/core/src/tools/write-todos.ts @@ -12,7 +12,9 @@ import { Type } from '@google/genai'; export const WRITE_TODOS_DESCRIPTION = `Use this tool to create and manage a structured task list for your current work session. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user. It also helps the user understand the progress of the task and overall progress of their requests. -Use this tool when: +## When to Use This Tool +Use this tool proactively in these scenarios: + 1. Complex multi-step tasks - When a task requires 3 or more distinct steps or actions 2. Non-trivial and complex tasks - Tasks that require careful planning or multiple operations 3. User explicitly requests todo list - When the user directly asks you to use the todo list @@ -52,6 +54,27 @@ The assistant used the todo list because: + +User: Help me plan a comprehensive marketing campaign for our new product launch, including social media, email marketing, and press releases. +Assistant: I'll help you plan a comprehensive marketing campaign for your product launch. Let me create a todo list to organize all the components. +*Creates todo list with the following items:* +1. Define target audience and key messaging +2. Create social media content calendar and posting schedule +3. Design email marketing sequence and automation flows +4. Draft press release and identify media contacts +5. Set up tracking and analytics for campaign performance +6. Create visual assets and brand materials +7. Coordinate launch timeline and deliverables +*Begins working on the first task* + + +The assistant used the todo list because: +1. Marketing campaign planning involves multiple distinct channels and activities +2. Each component requires careful coordination and planning +3. The systematic approach ensures all aspects of the launch are covered +4. Progress tracking helps maintain timeline and deliverables + + User: Help me rename the function getCwd to getCurrentWorkingDirectory across my project @@ -69,15 +92,30 @@ The assistant used the todo list because: + +User: I need help organizing my home office: decluttering the desk, setting up a filing system, organizing digital files, and creating a daily workflow. +Assistant: I'll help you organize your home office systematically. Let me create a todo list to track each area of organization. +*Creates todo list with items like: 1) Sort and declutter desk items, 2) Set up physical filing system for documents, 3) Organize digital files and folders, 4) Create daily workflow and productivity systems, 5) Establish maintenance routines* +Let's start with decluttering your desk space to create a clean foundation for the other organizational systems. + + +The assistant used the todo list because: +1. The user provided multiple distinct organizational tasks +2. Each area requires different approaches and careful planning +3. Home organization benefits from systematic, step-by-step completion +4. Progress tracking ensures all areas are addressed thoroughly + + + ## Examples of When NOT to Use the Todo List User: How do I print 'Hello World' in Python? Assistant: In Python, you can print "Hello World" with this simple code: -\`\`\`python +python print("Hello World") -\`\`\` + This will output the text "Hello World" to the console when executed. @@ -85,6 +123,32 @@ The assistant did not use the todo list because this is a single, trivial task t + +User: What's the best way to water houseplants? +Assistant: The best way to water houseplants depends on the specific plant type, but here are some general guidelines: + +- Check soil moisture by inserting your finger about 1-2 inches into the soil +- Water thoroughly when the top inch feels dry, allowing water to drain from the bottom +- Use room temperature water and water in the morning when possible +- Avoid overwatering, which is more harmful than underwatering for most plants + +Different plants have different needs, so it's important to research your specific varieties for optimal care. + + +The assistant did not use the todo list because this is an informational request with no actual tasks to complete. The user is simply asking for advice and explanation, not for the assistant to perform multiple steps or activities. + + + + +User: Can you add a comment to the calculateTotal function to explain what it does? +Assistant: Sure, let me add a comment to the calculateTotal function to explain what it does. +* Uses the Edit tool to add a comment to the calculateTotal function * + + +The assistant did not use the todo list because this is a single, straightforward task confined to one location. Adding a comment doesn't require tracking multiple steps or systematic organization. + + + User: What time is it in Tokyo right now? Assistant: I'll check the current time in Tokyo for you. @@ -98,6 +162,35 @@ The assistant did not use the todo list because this is a single information loo +## Task States and Management + +1. **Task States**: Use these states to track progress: + - pending: Task not yet started + - in_progress: Currently working on (limit to ONE task at a time) + - completed: Task finished successfully + +2. **Task Management**: + - Update task status in real-time as you work + - Mark tasks complete IMMEDIATELY after finishing (don't batch completions) + - Only have ONE task in_progress at any time + - Complete current tasks before starting new ones + - Remove tasks that are no longer relevant from the list entirely + +3. **Task Completion Requirements**: + - ONLY mark a task as completed when you have FULLY accomplished it + - If you encounter errors, blockers, or cannot finish, keep the task as in_progress + - When blocked, create a new task describing what needs to be resolved + - Never mark a task as completed if: + - There are unresolved issues or errors + - Work is partial or incomplete + - You encountered blockers that prevent completion + - You couldn't find necessary resources or dependencies + - Quality standards haven't been met + +4. **Task Breakdown**: + - Create specific, actionable items + - Break complex tasks into smaller, manageable steps + - Use clear, descriptive task names When in doubt, use this tool. Being proactive with task management demonstrates attentiveness and ensures you complete all requirements successfully.`; export type TodoStatus = 'pending' | 'in_progress' | 'completed'; From 48c64a3fe3992f2d132afa2f70e1cd53bec86980 Mon Sep 17 00:00:00 2001 From: Anjali Sridhar Date: Wed, 3 Sep 2025 11:06:09 -0700 Subject: [PATCH 20/33] wip --- packages/core/src/tools/write-todos.ts | 72 +++-- packages/vscode-ide-companion/NOTICES.txt | 326 ++++++++++++---------- 2 files changed, 224 insertions(+), 174 deletions(-) diff --git a/packages/core/src/tools/write-todos.ts b/packages/core/src/tools/write-todos.ts index 174922993a8..cbd45183d57 100644 --- a/packages/core/src/tools/write-todos.ts +++ b/packages/core/src/tools/write-todos.ts @@ -259,31 +259,24 @@ export class WriteTodosTool extends BaseDeclarativeTool< WRITE_TODOS_DESCRIPTION, Kind.Other, { - properties: { - todos: { - type: Type.ARRAY, - description: - 'The complete list of todo items. This will replace the existing list.', - items: { - type: Type.OBJECT, - description: 'A single todo item.', - properties: { - description: { - type: Type.STRING, - description: 'The description of the task.', - }, - status: { - type: Type.STRING, - description: 'The current status of the task.', - enum: ['pending', 'in_progress', 'completed'], - }, - }, - required: ['description', 'status'], + type: Type.ARRAY, + description: 'The complete list of todo items. This will replace the existing list.', + items: { + type: Type.OBJECT, + description: 'A single todo item.', + properties: { + description: { + type: Type.STRING, + description: 'The description of the task.', + }, + status: { + type: Type.STRING, + description: 'The current status of the task.', + enum: ['pending', 'in_progress', 'completed'], }, }, + required: ['description', 'status'], }, - required: ['todos'], - type: Type.OBJECT, } ); } @@ -306,11 +299,32 @@ export class WriteTodosTool extends BaseDeclarativeTool< } protected override validateToolParamValues( - params: WriteTodosToolParams + params: WriteTodosToolParams | Todo[] ): string | null { - const inProgressCount = params.todos.filter( - (todo) => todo.status === 'in_progress' + // Handle both direct array input and object with todos array + const todos = Array.isArray(params) ? params : + (params && Array.isArray(params.todos) ? params.todos : null); + + if (!todos) { + return 'Input must be an array of todo items or an object with a todos array'; + } + + for (const todo of todos) { + if (typeof todo !== 'object' || todo === null) { + return 'Each todo item must be an object'; + } + if (typeof todo.description !== 'string' || !todo.description.trim()) { + return 'Each todo must have a non-empty description string'; + } + if (!['pending', 'in_progress', 'completed'].includes(todo.status)) { + return 'Each todo must have a valid status (pending, in_progress, or completed)'; + } + } + + const inProgressCount = todos.filter( + (todo: Todo) => todo.status === 'in_progress' ).length; + if (inProgressCount > 1) { return 'Invalid parameters: Only one task can be "in_progress" at a time.'; } @@ -319,9 +333,11 @@ export class WriteTodosTool extends BaseDeclarativeTool< } protected createInvocation( - params: WriteTodosToolParams + params: WriteTodosToolParams | Todo[] ): ToolInvocation { - return new WriteTodosToolInvocation(params); + // Ensure the input is properly formatted + const todos = Array.isArray(params) ? params : + (params && Array.isArray(params.todos) ? params.todos : []); + return new WriteTodosToolInvocation({ todos }); } } - diff --git a/packages/vscode-ide-companion/NOTICES.txt b/packages/vscode-ide-companion/NOTICES.txt index 5b6c821c6a0..e7a85bb876e 100644 --- a/packages/vscode-ide-companion/NOTICES.txt +++ b/packages/vscode-ide-companion/NOTICES.txt @@ -460,7 +460,7 @@ SOFTWARE. ============================================================ -express@5.1.0 +express@4.21.2 (No repository found) (The MIT License) @@ -490,7 +490,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ============================================================ -accepts@2.0.0 +accepts@1.3.8 (No repository found) (The MIT License) @@ -577,7 +577,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ============================================================ -negotiator@1.0.0 +negotiator@0.6.3 (No repository found) (The MIT License) @@ -607,7 +607,34 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ============================================================ -body-parser@2.2.0 +array-flatten@1.1.1 +(git://github.com/blakeembrey/array-flatten.git) + +The MIT License (MIT) + +Copyright (c) 2014 Blake Embrey (hello@blakeembrey.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +============================================================ +body-parser@1.20.3 (No repository found) (The MIT License) @@ -718,14 +745,42 @@ SOFTWARE. ============================================================ -http-errors@2.0.0 +depd@2.0.0 +(No repository found) + +(The MIT License) + +Copyright (c) 2014-2018 Douglas Christopher Wilson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +============================================================ +destroy@1.2.0 (No repository found) The MIT License (MIT) Copyright (c) 2014 Jonathan Ong me@jongleberry.com -Copyright (c) 2016 Douglas Christopher Wilson doug@somethingdoug.com +Copyright (c) 2015-2022 Douglas Christopher Wilson doug@somethingdoug.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -747,31 +802,32 @@ THE SOFTWARE. ============================================================ -depd@2.0.0 +http-errors@2.0.0 (No repository found) -(The MIT License) -Copyright (c) 2014-2018 Douglas Christopher Wilson +The MIT License (MIT) -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -'Software'), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: +Copyright (c) 2014 Jonathan Ong me@jongleberry.com +Copyright (c) 2016 Douglas Christopher Wilson doug@somethingdoug.com -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. ============================================================ @@ -983,7 +1039,7 @@ THE SOFTWARE. ============================================================ -qs@6.14.0 +qs@6.13.0 (https://github.com/ljharb/qs.git) BSD 3-Clause License @@ -1559,7 +1615,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ============================================================ -type-is@2.0.1 +type-is@1.6.18 (No repository found) (The MIT License) @@ -1588,12 +1644,12 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ============================================================ -media-typer@1.1.0 +media-typer@0.3.0 (No repository found) (The MIT License) -Copyright (c) 2014-2017 Douglas Christopher Wilson +Copyright (c) 2014 Douglas Christopher Wilson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -1616,7 +1672,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ============================================================ -content-disposition@1.0.0 +content-disposition@0.5.4 (No repository found) (The MIT License) @@ -1701,12 +1757,18 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ============================================================ -cookie-signature@1.2.2 +cookie-signature@1.0.6 (https://github.com/visionmedia/node-cookie-signature.git) +License text not found. + +============================================================ +encodeurl@2.0.0 +(No repository found) + (The MIT License) -Copyright (c) 2012–2024 LearnBoost and other contributors; +Copyright (c) 2016 Douglas Christopher Wilson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -1729,12 +1791,14 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ============================================================ -encodeurl@2.0.0 +escape-html@1.0.3 (No repository found) (The MIT License) -Copyright (c) 2016 Douglas Christopher Wilson +Copyright (c) 2012-2013 TJ Holowaychuk +Copyright (c) 2015 Andreas Lubbe +Copyright (c) 2015 Tiancheng "Timothy" Gu Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -1757,14 +1821,12 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ============================================================ -escape-html@1.0.3 +etag@1.8.1 (No repository found) (The MIT License) -Copyright (c) 2012-2013 TJ Holowaychuk -Copyright (c) 2015 Andreas Lubbe -Copyright (c) 2015 Tiancheng "Timothy" Gu +Copyright (c) 2014-2016 Douglas Christopher Wilson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -1787,12 +1849,12 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ============================================================ -etag@1.8.1 +finalhandler@1.3.1 (No repository found) (The MIT License) -Copyright (c) 2014-2016 Douglas Christopher Wilson +Copyright (c) 2014-2022 Douglas Christopher Wilson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -1815,12 +1877,14 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ============================================================ -finalhandler@2.1.0 +parseurl@1.3.3 (No repository found) + (The MIT License) -Copyright (c) 2014-2022 Douglas Christopher Wilson +Copyright (c) 2014 Jonathan Ong +Copyright (c) 2014-2017 Douglas Christopher Wilson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -1843,14 +1907,13 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ============================================================ -parseurl@1.3.3 +fresh@0.5.2 (No repository found) - (The MIT License) -Copyright (c) 2014 Jonathan Ong -Copyright (c) 2014-2017 Douglas Christopher Wilson +Copyright (c) 2012 TJ Holowaychuk +Copyright (c) 2016-2017 Douglas Christopher Wilson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -1873,13 +1936,13 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ============================================================ -fresh@2.0.0 +merge-descriptors@1.0.3 (No repository found) (The MIT License) -Copyright (c) 2012 TJ Holowaychuk -Copyright (c) 2016-2017 Douglas Christopher Wilson +Copyright (c) 2013 Jonathan Ong +Copyright (c) 2015 Douglas Christopher Wilson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -1902,62 +1965,60 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ============================================================ -merge-descriptors@2.0.0 +methods@1.1.2 (No repository found) -MIT License - -Copyright (c) Jonathan Ong -Copyright (c) Douglas Christopher Wilson -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +(The MIT License) -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +Copyright (c) 2013-2014 TJ Holowaychuk +Copyright (c) 2015-2016 Douglas Christopher Wilson +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: -============================================================ -once@1.4.0 -(git://github.com/isaacs/once) +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. -The ISC License +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -Copyright (c) Isaac Z. Schlueter and Contributors - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR -IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ============================================================ -wrappy@1.0.2 -(https://github.com/npm/wrappy) +path-to-regexp@0.1.12 +(https://github.com/pillarjs/path-to-regexp.git) -The ISC License +The MIT License (MIT) -Copyright (c) Isaac Z. Schlueter and Contributors +Copyright (c) 2014 Blake Embrey (hello@blakeembrey.com) -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR -IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. ============================================================ @@ -2071,12 +2132,12 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ============================================================ -router@2.2.0 +send@0.19.0 (No repository found) (The MIT License) -Copyright (c) 2013 Roman Shtylman +Copyright (c) 2012 TJ Holowaychuk Copyright (c) 2014-2022 Douglas Christopher Wilson Permission is hereby granted, free of charge, to any person obtaining @@ -2100,36 +2161,12 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ============================================================ -is-promise@4.0.0 -(https://github.com/then/is-promise.git) - -Copyright (c) 2014 Forbes Lindesay - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -============================================================ -path-to-regexp@8.2.0 -(https://github.com/pillarjs/path-to-regexp.git) +mime@3.0.0 +(https://github.com/broofa/mime) The MIT License (MIT) -Copyright (c) 2014 Blake Embrey (hello@blakeembrey.com) +Copyright (c) 2010 Benjamin Thomas, Robert Kieffer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -2151,13 +2188,15 @@ THE SOFTWARE. ============================================================ -send@1.2.0 +serve-static@1.16.2 (No repository found) (The MIT License) -Copyright (c) 2012 TJ Holowaychuk -Copyright (c) 2014-2022 Douglas Christopher Wilson +Copyright (c) 2010 Sencha Inc. +Copyright (c) 2011 LearnBoost +Copyright (c) 2011 TJ Holowaychuk +Copyright (c) 2014-2016 Douglas Christopher Wilson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -2180,34 +2219,29 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ============================================================ -serve-static@2.2.0 -(No repository found) +utils-merge@1.0.1 +(git://github.com/jaredhanson/utils-merge.git) -(The MIT License) +The MIT License (MIT) -Copyright (c) 2010 Sencha Inc. -Copyright (c) 2011 LearnBoost -Copyright (c) 2011 TJ Holowaychuk -Copyright (c) 2014-2016 Douglas Christopher Wilson +Copyright (c) 2013-2017 Jared Hanson -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -'Software'), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ============================================================ From 9977987452026f37daefe44aa2f9ec19ffdfeab5 Mon Sep 17 00:00:00 2001 From: Anjali Sridhar Date: Wed, 10 Sep 2025 09:18:28 -0700 Subject: [PATCH 21/33] wip --- packages/core/src/tools/write-todos.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/tools/write-todos.ts b/packages/core/src/tools/write-todos.ts index cbd45183d57..f3686ee2fc5 100644 --- a/packages/core/src/tools/write-todos.ts +++ b/packages/core/src/tools/write-todos.ts @@ -223,7 +223,7 @@ class WriteTodosToolInvocation extends BaseToolInvocation< _signal: AbortSignal, _updateOutput?: (output: string) => void ): Promise { - WriteTodosTool.todos = this.params.todos; + WriteTodosTool.todos = this.params.todos ?? []; const todoListString = WriteTodosTool.todos .map( From 69fa62bc717fcf034bdd896ec4cb4cbdfef400a2 Mon Sep 17 00:00:00 2001 From: Anjali Sridhar Date: Sat, 13 Sep 2025 05:42:49 -0700 Subject: [PATCH 22/33] wip --- packages/cli/src/nonInteractiveCli.test.ts | 13 +- packages/cli/src/nonInteractiveCli.ts | 25 +- .../core/__snapshots__/prompts.test.ts.snap | 45 +++ packages/core/src/core/prompts.ts | 2 +- packages/core/src/tools/write-todos.test.ts | 109 +++++++ packages/core/src/tools/write-todos.ts | 301 +++++------------- 6 files changed, 245 insertions(+), 250 deletions(-) create mode 100644 packages/core/src/tools/write-todos.test.ts diff --git a/packages/cli/src/nonInteractiveCli.test.ts b/packages/cli/src/nonInteractiveCli.test.ts index 1cb214893c0..bf90bdc6cc3 100644 --- a/packages/cli/src/nonInteractiveCli.test.ts +++ b/packages/cli/src/nonInteractiveCli.test.ts @@ -134,9 +134,9 @@ describe('runNonInteractive', () => { expect.any(AbortSignal), 'prompt-id-1', ); - expect(processStdoutSpy).toHaveBeenCalledWith( - expect.stringContaining('Hello World'), - ); + expect(processStdoutSpy).toHaveBeenCalledWith('Hello'); + expect(processStdoutSpy).toHaveBeenCalledWith(' World'); + expect(processStdoutSpy).toHaveBeenCalledWith('\n'); expect(mockShutdownTelemetry).toHaveBeenCalled(); }); @@ -181,9 +181,8 @@ describe('runNonInteractive', () => { expect.any(AbortSignal), 'prompt-id-2', ); - expect(processStdoutSpy).toHaveBeenCalledWith( - expect.stringContaining('Final answer'), - ); + expect(processStdoutSpy).toHaveBeenCalledWith('Final answer'); + expect(processStdoutSpy).toHaveBeenCalledWith('\n'); }); it('should handle error during tool execution and should send error back to the model', async () => { @@ -305,7 +304,7 @@ describe('runNonInteractive', () => { ); expect(mockGeminiClient.sendMessageStream).toHaveBeenCalledTimes(2); expect(processStdoutSpy).toHaveBeenCalledWith( - expect.stringContaining("Sorry, I can't find that tool."), + "Sorry, I can't find that tool.", ); }); diff --git a/packages/cli/src/nonInteractiveCli.ts b/packages/cli/src/nonInteractiveCli.ts index 4db882fbf1f..ff33bd86ec6 100644 --- a/packages/cli/src/nonInteractiveCli.ts +++ b/packages/cli/src/nonInteractiveCli.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type { Config, ToolCallRequestInfo, ToolCallResponseInfo } from '@google/gemini-cli-core'; +import type { Config, ToolCallRequestInfo } from '@google/gemini-cli-core'; import { executeToolCall, shutdownTelemetry, @@ -20,29 +20,6 @@ import type { Content, Part } from '@google/genai'; import { ConsolePatcher } from './ui/utils/ConsolePatcher.js'; import { handleAtCommand } from './ui/hooks/atCommandProcessor.js'; -function logToolCallResult( - config: Config, - requestInfo: ToolCallRequestInfo, - toolResponse: ToolCallResponseInfo, -) { - const status = toolResponse.error ? 'ERROR' : 'OK'; - if (toolResponse.error) { - process.stdout.write( - `\nTool call status:❌ ${status} ${requestInfo.name} => ${toolResponse.error.message}\n`, - ); - } else { - if (toolResponse.resultDisplay) { - process.stdout.write( - `\nTool call status:✅ ${status} ${requestInfo.name} => ${toolResponse.resultDisplay}\n`, - ); - } else { - process.stdout.write( - `\nTool call status:✅ ${status} ${requestInfo.name}\n`, - ); - } - } -} - export async function runNonInteractive( config: Config, input: string, diff --git a/packages/core/src/core/__snapshots__/prompts.test.ts.snap b/packages/core/src/core/__snapshots__/prompts.test.ts.snap index 47674d6ca1f..0e824dce857 100644 --- a/packages/core/src/core/__snapshots__/prompts.test.ts.snap +++ b/packages/core/src/core/__snapshots__/prompts.test.ts.snap @@ -16,6 +16,11 @@ exports[`Core System Prompt (prompts.ts) > should append userMemory with separat - **Path Construction:** Before using any file system tool (e.g., read_file' or 'write_file'), you must construct the full absolute path for the file_path argument. Always combine the absolute path of the project's root directory with the file's path relative to the root. For example, if the project root is /path/to/project/ and the file is foo/bar/baz.txt, the final path you must use is /path/to/project/foo/bar/baz.txt. If the user provides a relative path, you must resolve it against the root directory to create an absolute path. - **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. + +YOU MUST create and maintain a TODO list using 'write_todos_list' tool to make an initial plan, keep track of it and show the user the current execution state. +Breakdown the task as explained by the 'write_todos_list' tool description to discrete concrete executable steps. This should be used for every task given by the user. + + # Primary Workflows ## Software Engineering Tasks @@ -197,6 +202,11 @@ exports[`Core System Prompt (prompts.ts) > should include git instructions when - **Path Construction:** Before using any file system tool (e.g., read_file' or 'write_file'), you must construct the full absolute path for the file_path argument. Always combine the absolute path of the project's root directory with the file's path relative to the root. For example, if the project root is /path/to/project/ and the file is foo/bar/baz.txt, the final path you must use is /path/to/project/foo/bar/baz.txt. If the user provides a relative path, you must resolve it against the root directory to create an absolute path. - **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. + +YOU MUST create and maintain a TODO list using 'write_todos_list' tool to make an initial plan, keep track of it and show the user the current execution state. +Breakdown the task as explained by the 'write_todos_list' tool description to discrete concrete executable steps. This should be used for every task given by the user. + + # Primary Workflows ## Software Engineering Tasks @@ -388,6 +398,11 @@ exports[`Core System Prompt (prompts.ts) > should include non-sandbox instructio - **Path Construction:** Before using any file system tool (e.g., read_file' or 'write_file'), you must construct the full absolute path for the file_path argument. Always combine the absolute path of the project's root directory with the file's path relative to the root. For example, if the project root is /path/to/project/ and the file is foo/bar/baz.txt, the final path you must use is /path/to/project/foo/bar/baz.txt. If the user provides a relative path, you must resolve it against the root directory to create an absolute path. - **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. + +YOU MUST create and maintain a TODO list using 'write_todos_list' tool to make an initial plan, keep track of it and show the user the current execution state. +Breakdown the task as explained by the 'write_todos_list' tool description to discrete concrete executable steps. This should be used for every task given by the user. + + # Primary Workflows ## Software Engineering Tasks @@ -564,6 +579,11 @@ exports[`Core System Prompt (prompts.ts) > should include sandbox-specific instr - **Path Construction:** Before using any file system tool (e.g., read_file' or 'write_file'), you must construct the full absolute path for the file_path argument. Always combine the absolute path of the project's root directory with the file's path relative to the root. For example, if the project root is /path/to/project/ and the file is foo/bar/baz.txt, the final path you must use is /path/to/project/foo/bar/baz.txt. If the user provides a relative path, you must resolve it against the root directory to create an absolute path. - **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. + +YOU MUST create and maintain a TODO list using 'write_todos_list' tool to make an initial plan, keep track of it and show the user the current execution state. +Breakdown the task as explained by the 'write_todos_list' tool description to discrete concrete executable steps. This should be used for every task given by the user. + + # Primary Workflows ## Software Engineering Tasks @@ -740,6 +760,11 @@ exports[`Core System Prompt (prompts.ts) > should include seatbelt-specific inst - **Path Construction:** Before using any file system tool (e.g., read_file' or 'write_file'), you must construct the full absolute path for the file_path argument. Always combine the absolute path of the project's root directory with the file's path relative to the root. For example, if the project root is /path/to/project/ and the file is foo/bar/baz.txt, the final path you must use is /path/to/project/foo/bar/baz.txt. If the user provides a relative path, you must resolve it against the root directory to create an absolute path. - **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. + +YOU MUST create and maintain a TODO list using 'write_todos_list' tool to make an initial plan, keep track of it and show the user the current execution state. +Breakdown the task as explained by the 'write_todos_list' tool description to discrete concrete executable steps. This should be used for every task given by the user. + + # Primary Workflows ## Software Engineering Tasks @@ -916,6 +941,11 @@ exports[`Core System Prompt (prompts.ts) > should not include git instructions w - **Path Construction:** Before using any file system tool (e.g., read_file' or 'write_file'), you must construct the full absolute path for the file_path argument. Always combine the absolute path of the project's root directory with the file's path relative to the root. For example, if the project root is /path/to/project/ and the file is foo/bar/baz.txt, the final path you must use is /path/to/project/foo/bar/baz.txt. If the user provides a relative path, you must resolve it against the root directory to create an absolute path. - **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. + +YOU MUST create and maintain a TODO list using 'write_todos_list' tool to make an initial plan, keep track of it and show the user the current execution state. +Breakdown the task as explained by the 'write_todos_list' tool description to discrete concrete executable steps. This should be used for every task given by the user. + + # Primary Workflows ## Software Engineering Tasks @@ -1092,6 +1122,11 @@ exports[`Core System Prompt (prompts.ts) > should return the base prompt when no - **Path Construction:** Before using any file system tool (e.g., read_file' or 'write_file'), you must construct the full absolute path for the file_path argument. Always combine the absolute path of the project's root directory with the file's path relative to the root. For example, if the project root is /path/to/project/ and the file is foo/bar/baz.txt, the final path you must use is /path/to/project/foo/bar/baz.txt. If the user provides a relative path, you must resolve it against the root directory to create an absolute path. - **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. + +YOU MUST create and maintain a TODO list using 'write_todos_list' tool to make an initial plan, keep track of it and show the user the current execution state. +Breakdown the task as explained by the 'write_todos_list' tool description to discrete concrete executable steps. This should be used for every task given by the user. + + # Primary Workflows ## Software Engineering Tasks @@ -1268,6 +1303,11 @@ exports[`Core System Prompt (prompts.ts) > should return the base prompt when us - **Path Construction:** Before using any file system tool (e.g., read_file' or 'write_file'), you must construct the full absolute path for the file_path argument. Always combine the absolute path of the project's root directory with the file's path relative to the root. For example, if the project root is /path/to/project/ and the file is foo/bar/baz.txt, the final path you must use is /path/to/project/foo/bar/baz.txt. If the user provides a relative path, you must resolve it against the root directory to create an absolute path. - **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. + +YOU MUST create and maintain a TODO list using 'write_todos_list' tool to make an initial plan, keep track of it and show the user the current execution state. +Breakdown the task as explained by the 'write_todos_list' tool description to discrete concrete executable steps. This should be used for every task given by the user. + + # Primary Workflows ## Software Engineering Tasks @@ -1444,6 +1484,11 @@ exports[`Core System Prompt (prompts.ts) > should return the base prompt when us - **Path Construction:** Before using any file system tool (e.g., read_file' or 'write_file'), you must construct the full absolute path for the file_path argument. Always combine the absolute path of the project's root directory with the file's path relative to the root. For example, if the project root is /path/to/project/ and the file is foo/bar/baz.txt, the final path you must use is /path/to/project/foo/bar/baz.txt. If the user provides a relative path, you must resolve it against the root directory to create an absolute path. - **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. + +YOU MUST create and maintain a TODO list using 'write_todos_list' tool to make an initial plan, keep track of it and show the user the current execution state. +Breakdown the task as explained by the 'write_todos_list' tool description to discrete concrete executable steps. This should be used for every task given by the user. + + # Primary Workflows ## Software Engineering Tasks diff --git a/packages/core/src/core/prompts.ts b/packages/core/src/core/prompts.ts index 165a873a832..b57e770ef1d 100644 --- a/packages/core/src/core/prompts.ts +++ b/packages/core/src/core/prompts.ts @@ -114,7 +114,7 @@ You are an interactive CLI agent specializing in software engineering tasks. You YOU MUST create and maintain a TODO list using '${WriteTodosTool.Name}' tool to make an initial plan, keep track of it and show the user the current execution state. -Breakdown the task as explained by the '${WriteTodosTool.Name}' tool description to discrete concrete executable steps. +Breakdown the task as explained by the '${WriteTodosTool.Name}' tool description to discrete concrete executable steps. This should be used for every task given by the user. # Primary Workflows diff --git a/packages/core/src/tools/write-todos.test.ts b/packages/core/src/tools/write-todos.test.ts new file mode 100644 index 00000000000..affb18d0d89 --- /dev/null +++ b/packages/core/src/tools/write-todos.test.ts @@ -0,0 +1,109 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { describe, expect, it } from 'vitest'; +import { WriteTodosTool, type WriteTodosToolParams } from './write-todos.js'; + +describe('WriteTodosTool', () => { + const tool = new WriteTodosTool(); + const signal = new AbortController().signal; + + describe('validation', () => { + it('should not throw for valid parameters', async () => { + const params: WriteTodosToolParams = { + todos: [ + { description: 'Task 1', status: 'pending' }, + { description: 'Task 2', status: 'in_progress' }, + { description: 'Task 3', status: 'completed' }, + ], + }; + await expect(tool.buildAndExecute(params, signal)).resolves.toBeDefined(); + }); + + it('should not throw for an empty list', async () => { + const params: WriteTodosToolParams = { + todos: [], + }; + await expect(tool.buildAndExecute(params, signal)).resolves.toBeDefined(); + }); + + it('should throw an error if todos is not an array', async () => { + const params = { + todos: 'not-an-array', + } as unknown as WriteTodosToolParams; + await expect(tool.buildAndExecute(params, signal)).rejects.toThrow( + 'params/todos must be array', + ); + }); + + it('should throw an error if a todo item is not an object', async () => { + const params = { + todos: ['not-an-object'], + } as unknown as WriteTodosToolParams; + await expect(tool.buildAndExecute(params, signal)).rejects.toThrow( + 'params/todos/0 must be object', + ); + }); + + it('should throw an error if a todo description is missing or empty', async () => { + const params: WriteTodosToolParams = { + todos: [{ description: ' ', status: 'pending' }], + }; + await expect(tool.buildAndExecute(params, signal)).rejects.toThrow( + 'Each todo must have a non-empty description string', + ); + }); + + it('should throw an error if a todo status is invalid', async () => { + const params = { + todos: [{ description: 'Task 1', status: 'invalid-status' }], + } as unknown as WriteTodosToolParams; + await expect(tool.buildAndExecute(params, signal)).rejects.toThrow( + 'params/todos/0/status must be equal to one of the allowed values', + ); + }); + + it('should throw an error if more than one task is in_progress', async () => { + const params: WriteTodosToolParams = { + todos: [ + { description: 'Task 1', status: 'in_progress' }, + { description: 'Task 2', status: 'in_progress' }, + ], + }; + await expect(tool.buildAndExecute(params, signal)).rejects.toThrow( + 'Invalid parameters: Only one task can be "in_progress" at a time.', + ); + }); + }); + + describe('execute', () => { + it('should return a success message for clearing the list', async () => { + const params: WriteTodosToolParams = { + todos: [], + }; + const result = await tool.buildAndExecute(params, signal); + expect(result.llmContent).toBe('Successfully cleared the todo list.'); + expect(result.returnDisplay).toBe('Successfully cleared the todo list.'); + }); + + it('should return a formatted todo list on success', async () => { + const params: WriteTodosToolParams = { + todos: [ + { description: 'First task', status: 'completed' }, + { description: 'Second task', status: 'in_progress' }, + { description: 'Third task', status: 'pending' }, + ], + }; + const result = await tool.buildAndExecute(params, signal); + const expectedOutput = `Successfully updated the todo list. The current list is now: +1. [completed] First task +2. [in_progress] Second task +3. [pending] Third task`; + expect(result.llmContent).toBe(expectedOutput); + expect(result.returnDisplay).toBe(expectedOutput); + }); + }); +}); diff --git a/packages/core/src/tools/write-todos.ts b/packages/core/src/tools/write-todos.ts index f3686ee2fc5..9c9b8bb5627 100644 --- a/packages/core/src/tools/write-todos.ts +++ b/packages/core/src/tools/write-todos.ts @@ -5,195 +5,80 @@ */ import type { ToolInvocation } from './tools.js'; -import { BaseDeclarativeTool, BaseToolInvocation, Kind, type ToolResult } from './tools.js'; -import { Type } from '@google/genai'; +import { + BaseDeclarativeTool, + BaseToolInvocation, + Kind, + type ToolResult, +} from './tools.js'; -// Adapted from langchain/deepagents for experimentation -export const WRITE_TODOS_DESCRIPTION = `Use this tool to create and manage a structured task list for your current work session. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user. -It also helps the user understand the progress of the task and overall progress of their requests. +// Insprired by langchain/deepagents. +export const WRITE_TODOS_DESCRIPTION = `This tool can help you list out the current subtasks that are required to be completed for a given user request. The list of subtasks helps you keep track of the current task, organize complex queries and help ensure that you don't miss any steps. With this list, the user can also see the current progress you are making in executing a given task. -## When to Use This Tool -Use this tool proactively in these scenarios: +Depending on the task complexity, you should first divide a given task in subtasks and then use this tool to list out the subtasks that are required to be completed for a given user request. +Each of the subtasks should be clear and distinct. -1. Complex multi-step tasks - When a task requires 3 or more distinct steps or actions -2. Non-trivial and complex tasks - Tasks that require careful planning or multiple operations -3. User explicitly requests todo list - When the user directly asks you to use the todo list -4. User provides multiple tasks - When users provide a list of things to be done (numbered or comma-separated) -5. After receiving new instructions - Immediately capture user requirements as todos -6. When you start working on a task - Mark it as in_progress BEFORE beginning work. Ideally you should only have one todo as in_progress at a time -7. After completing a task - Mark it as completed and add any new follow-up tasks discovered during implementation +Use this tool for complex queries that require multiple steps. If you find that the request is actually complex after you have started executing the user task, create a todo list and use it. If execution of the user task requires multiple steps, planning and generally is higher complexity than a simple Q&A, use this tool. -## When NOT to Use This Tool +DO NOT use this tool for simple tasks that can be completed in less than 2 steps. If the user query is simple and straightforward, do not use the tool. If you can respond with an answer in a single turn then this tool is not required. -Skip using this tool when: -1. There is only a single, straightforward task -2. The task is trivial and tracking it provides no organizational benefit -3. The task can be completed in less than 3 trivial steps -4. The task is purely conversational or informational +## Task state definitions -NOTE that you should not use this tool if there is only one trivial task to do. In this case you are better off just doing the task directly. +- pending: Work has not begun on a given subtask. +- in_progress: Marked just prior to beginning work on a given subtask. You should only have one subtask as in_progress at a time. +- completed: Subtask was succesfully completed with no errors or issues. If the subtask required more more steps to complete, update the todo list. If more information is required from the user and you are in interactive mode, add that as a new subtask for request more information. All steps should be identified as completed only when they are completed. +- cancelled: As you update the todo list, some tasks are not required anymore due to the dynamic nature of the task. In this case, mark the subtasks as cancelled so that the user still sees them in the list and knows that they are not required anymore. -## Examples of When to Use the Todo List - -User: I want to add a dark mode toggle to the application settings. Make sure you run the tests and build when you're done! -Assistant: I'll help add a dark mode toggle to your application settings. Let me create a todo list to track this implementation. -*Creates todo list with the following items:* -1. Create dark mode toggle component in Settings page -2. Add dark mode state management (context/store) -3. Implement CSS-in-JS styles for dark theme -4. Update existing components to support theme switching -5. Run tests and build process, addressing any failures or errors that occur -*Begins working on the first task* +## Methodology for using this tool +1. Use this too as soon as you receive a user request based on the complexity of the task. +2. Keep track of every subtask that you take my update this list +3. Mark a subtask as in_progress before you begin working on it. You should only have one subtask as in_progress at a time. +4. Update the subtask list as you proceed in executing the task. The subtask list is not static and should reflect the current progress you are making. +5. Mark a subtask as completed when you have completed it. +6. Mark a subtask as cancelled if you update the todo list as you proceed in executing the task. +7. You must update the todo list as soon as you start, stop or cancel a subtask. Don't batch or wait to update the todo list. - -The assistant used the todo list because: -1. Adding dark mode is a multi-step feature requiring UI, state management, and styling changes -2. The user explicitly requested tests and build be run afterward -3. The assistant inferred that tests and build need to pass by adding "Ensure tests and build succeed" as the final task - - - - -User: Help me plan a comprehensive marketing campaign for our new product launch, including social media, email marketing, and press releases. -Assistant: I'll help you plan a comprehensive marketing campaign for your product launch. Let me create a todo list to organize all the components. -*Creates todo list with the following items:* -1. Define target audience and key messaging -2. Create social media content calendar and posting schedule -3. Design email marketing sequence and automation flows -4. Draft press release and identify media contacts -5. Set up tracking and analytics for campaign performance -6. Create visual assets and brand materials -7. Coordinate launch timeline and deliverables -*Begins working on the first task* - -The assistant used the todo list because: -1. Marketing campaign planning involves multiple distinct channels and activities -2. Each component requires careful coordination and planning -3. The systematic approach ensures all aspects of the launch are covered -4. Progress tracking helps maintain timeline and deliverables - - +## Examples of When to Use the Todo List -User: Help me rename the function getCwd to getCurrentWorkingDirectory across my project -Assistant: Let me first search through your codebase to find all occurrences of 'getCwd'. -*Uses search tools to locate all instances of getCwd in the codebase* -Assistant: I've found 15 instances of 'getCwd' across 8 different files. Let me create a todo list to track these changes. -*Creates todo list with specific items for each file that needs updating* +User request: Create a website with a React for creating fancy logos using gemini-2.5-flash-image + +ToDo list created by the agent: +1. Initialize a new React project environment (e.g., using Vite). +2. Design and build the core UI components: a text input (prompt field) for the logo description, selection controls for style parameters (if the API supports them), and an image preview area. +3. Implement state management (e.g., React Context or Zustand) to manage the user's input prompt, the API loading status (pending, success, error), and the resulting image data. +4. Create an API service module within the React app (using "fetch" or "axios") to securely format and send the prompt data via an HTTP POST request to the specified "gemini-2.5-flash-image" (Gemini model) endpoint. +5. Implement asynchronous logic to handle the API call: show a loading indicator while the request is pending, retrieve the generated image (e.g., as a URL or base64 string) upon success, and display any errors. +6. Display the returned "fancy logo" from the API response in the preview area component. +7. Add functionality (e.g., a "Download" button) to allow the user to save the generated image file. +8. Deploy the application to a web server or hosting platform. -The assistant used the todo list because: -1. First, the assistant searched to understand the scope of the task -2. Upon finding multiple occurrences across different files, it determined this was a complex task with multiple steps -3. The todo list helps ensure every instance is tracked and updated systematically -4. This approach prevents missing any occurrences and maintains consistency +The agent used the todo list to break the task into distinct, manageable steps: +1. Building an entire interactive web application from scratch is a highly complex, multi-stage process involving setup, UI development, logic integration, and deployment. +2. The agent inferred the core functionality required for a "logo creator," such as UI controls for customization (Task 3) and an export feature (Task 7), which must be tracked as distinct goals. +3. The agent rightly inferred the requirement of a API service model for interacting with the image model endpoint. - -User: I need help organizing my home office: decluttering the desk, setting up a filing system, organizing digital files, and creating a daily workflow. -Assistant: I'll help you organize your home office systematically. Let me create a todo list to track each area of organization. -*Creates todo list with items like: 1) Sort and declutter desk items, 2) Set up physical filing system for documents, 3) Organize digital files and folders, 4) Create daily workflow and productivity systems, 5) Establish maintenance routines* -Let's start with decluttering your desk space to create a clean foundation for the other organizational systems. - - -The assistant used the todo list because: -1. The user provided multiple distinct organizational tasks -2. Each area requires different approaches and careful planning -3. Home organization benefits from systematic, step-by-step completion -4. Progress tracking ensures all areas are addressed thoroughly - - ## Examples of When NOT to Use the Todo List -User: How do I print 'Hello World' in Python? -Assistant: In Python, you can print "Hello World" with this simple code: +User request: Ensure that the test passes. -python -print("Hello World") - -This will output the text "Hello World" to the console when executed. +Agent: + -The assistant did not use the todo list because this is a single, trivial task that can be completed in one step. There's no need to track multiple tasks or steps for such a straightforward request. +The agent did not use the todo list because this task could be completed by a tight loop of execute test->edit->execute test. +`; - -User: What's the best way to water houseplants? -Assistant: The best way to water houseplants depends on the specific plant type, but here are some general guidelines: - -- Check soil moisture by inserting your finger about 1-2 inches into the soil -- Water thoroughly when the top inch feels dry, allowing water to drain from the bottom -- Use room temperature water and water in the morning when possible -- Avoid overwatering, which is more harmful than underwatering for most plants - -Different plants have different needs, so it's important to research your specific varieties for optimal care. - - -The assistant did not use the todo list because this is an informational request with no actual tasks to complete. The user is simply asking for advice and explanation, not for the assistant to perform multiple steps or activities. - - - - -User: Can you add a comment to the calculateTotal function to explain what it does? -Assistant: Sure, let me add a comment to the calculateTotal function to explain what it does. -* Uses the Edit tool to add a comment to the calculateTotal function * - - -The assistant did not use the todo list because this is a single, straightforward task confined to one location. Adding a comment doesn't require tracking multiple steps or systematic organization. - - - - -User: What time is it in Tokyo right now? -Assistant: I'll check the current time in Tokyo for you. - -*Searches for current time in Tokyo* - -The current time in Tokyo, Japan is [current time]. Tokyo is in the Japan Standard Time (JST) zone, which is UTC+9. - - -The assistant did not use the todo list because this is a single information lookup with immediate results. There are no multiple steps to track or organize, making the todo list unnecessary for this straightforward request. - - - -## Task States and Management - -1. **Task States**: Use these states to track progress: - - pending: Task not yet started - - in_progress: Currently working on (limit to ONE task at a time) - - completed: Task finished successfully - -2. **Task Management**: - - Update task status in real-time as you work - - Mark tasks complete IMMEDIATELY after finishing (don't batch completions) - - Only have ONE task in_progress at any time - - Complete current tasks before starting new ones - - Remove tasks that are no longer relevant from the list entirely - -3. **Task Completion Requirements**: - - ONLY mark a task as completed when you have FULLY accomplished it - - If you encounter errors, blockers, or cannot finish, keep the task as in_progress - - When blocked, create a new task describing what needs to be resolved - - Never mark a task as completed if: - - There are unresolved issues or errors - - Work is partial or incomplete - - You encountered blockers that prevent completion - - You couldn't find necessary resources or dependencies - - Quality standards haven't been met - -4. **Task Breakdown**: - - Create specific, actionable items - - Break complex tasks into smaller, manageable steps - - Use clear, descriptive task names - -When in doubt, use this tool. Being proactive with task management demonstrates attentiveness and ensures you complete all requirements successfully.`; -export type TodoStatus = 'pending' | 'in_progress' | 'completed'; +export type TodoStatus = 'pending' | 'in_progress' | 'completed' | 'cancelled'; export interface Todo { description: string; @@ -221,18 +106,17 @@ class WriteTodosToolInvocation extends BaseToolInvocation< async execute( _signal: AbortSignal, - _updateOutput?: (output: string) => void + _updateOutput?: (output: string) => void, ): Promise { - WriteTodosTool.todos = this.params.todos ?? []; - - const todoListString = WriteTodosTool.todos + const todos = this.params.todos ?? []; + const todoListString = todos .map( - (todo, index) => `${index + 1}. [${todo.status}] ${todo.description}` + (todo, index) => `${index + 1}. [${todo.status}] ${todo.description}`, ) .join('\n'); const llmContent = - WriteTodosTool.todos.length > 0 + todos.length > 0 ? `Successfully updated the todo list. The current list is now:\n${todoListString}` : 'Successfully cleared the todo list.'; @@ -249,9 +133,6 @@ export class WriteTodosTool extends BaseDeclarativeTool< > { static readonly Name: string = 'write_todos_list'; - // In-memory store for the session's todos. - static todos: Todo[] = []; - constructor() { super( WriteTodosTool.Name, @@ -259,54 +140,41 @@ export class WriteTodosTool extends BaseDeclarativeTool< WRITE_TODOS_DESCRIPTION, Kind.Other, { - type: Type.ARRAY, - description: 'The complete list of todo items. This will replace the existing list.', - items: { - type: Type.OBJECT, - description: 'A single todo item.', - properties: { - description: { - type: Type.STRING, - description: 'The description of the task.', - }, - status: { - type: Type.STRING, - description: 'The current status of the task.', - enum: ['pending', 'in_progress', 'completed'], + type: 'object', + properties: { + todos: { + type: 'array', + description: + 'The complete list of todo items. This will replace the existing list.', + items: { + type: 'object', + description: 'A single todo item.', + properties: { + description: { + type: 'string', + description: 'The description of the task.', + }, + status: { + type: 'string', + description: 'The current status of the task.', + enum: ['pending', 'in_progress', 'completed'], + }, + }, + required: ['description', 'status'], }, }, - required: ['description', 'status'], }, - } + required: ['todos'], + }, ); } - /** - * Static method to get the current list of todos. - * NOTE: This is intended for testing purposes. - * @returns The current list of todos. - */ - static getTodos(): Todo[] { - return WriteTodosTool.todos; - } - - /** - * Static method to reset the list of todos. - * NOTE: This is intended for testing purposes. - */ - static resetTodos(): void { - WriteTodosTool.todos = []; - } - protected override validateToolParamValues( - params: WriteTodosToolParams | Todo[] + params: WriteTodosToolParams, ): string | null { - // Handle both direct array input and object with todos array - const todos = Array.isArray(params) ? params : - (params && Array.isArray(params.todos) ? params.todos : null); - - if (!todos) { - return 'Input must be an array of todo items or an object with a todos array'; + const todos = params?.todos; + if (!params || !Array.isArray(todos)) { + return '`todos` parameter must be an array'; } for (const todo of todos) { @@ -322,9 +190,9 @@ export class WriteTodosTool extends BaseDeclarativeTool< } const inProgressCount = todos.filter( - (todo: Todo) => todo.status === 'in_progress' + (todo: Todo) => todo.status === 'in_progress', ).length; - + if (inProgressCount > 1) { return 'Invalid parameters: Only one task can be "in_progress" at a time.'; } @@ -333,11 +201,8 @@ export class WriteTodosTool extends BaseDeclarativeTool< } protected createInvocation( - params: WriteTodosToolParams | Todo[] + params: WriteTodosToolParams, ): ToolInvocation { - // Ensure the input is properly formatted - const todos = Array.isArray(params) ? params : - (params && Array.isArray(params.todos) ? params.todos : []); - return new WriteTodosToolInvocation({ todos }); + return new WriteTodosToolInvocation(params); } } From 02cfccdff00e9b895dbd6999129f47a765193790 Mon Sep 17 00:00:00 2001 From: Anjali Sridhar Date: Wed, 17 Sep 2025 14:44:16 -0700 Subject: [PATCH 23/33] wip --- .../core/__snapshots__/prompts.test.ts.snap | 36 ------------------- packages/core/src/core/prompts.ts | 5 --- 2 files changed, 41 deletions(-) diff --git a/packages/core/src/core/__snapshots__/prompts.test.ts.snap b/packages/core/src/core/__snapshots__/prompts.test.ts.snap index 0e824dce857..456f5dc0a18 100644 --- a/packages/core/src/core/__snapshots__/prompts.test.ts.snap +++ b/packages/core/src/core/__snapshots__/prompts.test.ts.snap @@ -17,10 +17,6 @@ exports[`Core System Prompt (prompts.ts) > should append userMemory with separat - **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. -YOU MUST create and maintain a TODO list using 'write_todos_list' tool to make an initial plan, keep track of it and show the user the current execution state. -Breakdown the task as explained by the 'write_todos_list' tool description to discrete concrete executable steps. This should be used for every task given by the user. - - # Primary Workflows ## Software Engineering Tasks @@ -203,10 +199,6 @@ exports[`Core System Prompt (prompts.ts) > should include git instructions when - **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. -YOU MUST create and maintain a TODO list using 'write_todos_list' tool to make an initial plan, keep track of it and show the user the current execution state. -Breakdown the task as explained by the 'write_todos_list' tool description to discrete concrete executable steps. This should be used for every task given by the user. - - # Primary Workflows ## Software Engineering Tasks @@ -399,10 +391,6 @@ exports[`Core System Prompt (prompts.ts) > should include non-sandbox instructio - **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. -YOU MUST create and maintain a TODO list using 'write_todos_list' tool to make an initial plan, keep track of it and show the user the current execution state. -Breakdown the task as explained by the 'write_todos_list' tool description to discrete concrete executable steps. This should be used for every task given by the user. - - # Primary Workflows ## Software Engineering Tasks @@ -580,10 +568,6 @@ exports[`Core System Prompt (prompts.ts) > should include sandbox-specific instr - **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. -YOU MUST create and maintain a TODO list using 'write_todos_list' tool to make an initial plan, keep track of it and show the user the current execution state. -Breakdown the task as explained by the 'write_todos_list' tool description to discrete concrete executable steps. This should be used for every task given by the user. - - # Primary Workflows ## Software Engineering Tasks @@ -761,10 +745,6 @@ exports[`Core System Prompt (prompts.ts) > should include seatbelt-specific inst - **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. -YOU MUST create and maintain a TODO list using 'write_todos_list' tool to make an initial plan, keep track of it and show the user the current execution state. -Breakdown the task as explained by the 'write_todos_list' tool description to discrete concrete executable steps. This should be used for every task given by the user. - - # Primary Workflows ## Software Engineering Tasks @@ -942,10 +922,6 @@ exports[`Core System Prompt (prompts.ts) > should not include git instructions w - **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. -YOU MUST create and maintain a TODO list using 'write_todos_list' tool to make an initial plan, keep track of it and show the user the current execution state. -Breakdown the task as explained by the 'write_todos_list' tool description to discrete concrete executable steps. This should be used for every task given by the user. - - # Primary Workflows ## Software Engineering Tasks @@ -1123,10 +1099,6 @@ exports[`Core System Prompt (prompts.ts) > should return the base prompt when no - **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. -YOU MUST create and maintain a TODO list using 'write_todos_list' tool to make an initial plan, keep track of it and show the user the current execution state. -Breakdown the task as explained by the 'write_todos_list' tool description to discrete concrete executable steps. This should be used for every task given by the user. - - # Primary Workflows ## Software Engineering Tasks @@ -1304,10 +1276,6 @@ exports[`Core System Prompt (prompts.ts) > should return the base prompt when us - **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. -YOU MUST create and maintain a TODO list using 'write_todos_list' tool to make an initial plan, keep track of it and show the user the current execution state. -Breakdown the task as explained by the 'write_todos_list' tool description to discrete concrete executable steps. This should be used for every task given by the user. - - # Primary Workflows ## Software Engineering Tasks @@ -1485,10 +1453,6 @@ exports[`Core System Prompt (prompts.ts) > should return the base prompt when us - **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. -YOU MUST create and maintain a TODO list using 'write_todos_list' tool to make an initial plan, keep track of it and show the user the current execution state. -Breakdown the task as explained by the 'write_todos_list' tool description to discrete concrete executable steps. This should be used for every task given by the user. - - # Primary Workflows ## Software Engineering Tasks diff --git a/packages/core/src/core/prompts.ts b/packages/core/src/core/prompts.ts index b57e770ef1d..b2bf6334c76 100644 --- a/packages/core/src/core/prompts.ts +++ b/packages/core/src/core/prompts.ts @@ -18,7 +18,6 @@ import { WriteFileTool } from '../tools/write-file.js'; import process from 'node:process'; import { isGitRepository } from '../utils/gitUtils.js'; import { MemoryTool, GEMINI_CONFIG_DIR } from '../tools/memoryTool.js'; -import { WriteTodosTool } from '../tools/write-todos.js'; export function resolvePathFromEnv(envVar?: string): { isSwitch: boolean; @@ -113,10 +112,6 @@ You are an interactive CLI agent specializing in software engineering tasks. You - **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. -YOU MUST create and maintain a TODO list using '${WriteTodosTool.Name}' tool to make an initial plan, keep track of it and show the user the current execution state. -Breakdown the task as explained by the '${WriteTodosTool.Name}' tool description to discrete concrete executable steps. This should be used for every task given by the user. - - # Primary Workflows ## Software Engineering Tasks From 53d69355f16d859c2a9179fe08c6bbdde145bd36 Mon Sep 17 00:00:00 2001 From: Anjali Sridhar Date: Thu, 18 Sep 2025 15:02:51 -0700 Subject: [PATCH 24/33] wip --- packages/cli/src/config/config.ts | 6 ++++++ packages/cli/src/config/settingsSchema.ts | 9 +++++++++ packages/cli/src/gemini.test.tsx | 1 + packages/core/src/config/config.ts | 11 ++++++++++- 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index e2b8add3cfe..036a47792bf 100755 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -81,6 +81,7 @@ export interface CliArgs { includeDirectories: string[] | undefined; screenReader: boolean | undefined; useSmartEdit: boolean | undefined; + useWriteTodos: boolean | undefined; promptWords: string[] | undefined; outputFormat: string | undefined; } @@ -231,6 +232,10 @@ export async function parseArguments(settings: Settings): Promise { description: 'Enable screen reader mode for accessibility.', default: false, }) + .option('use-write-todos', { + type: 'boolean', + description: 'Enable the write_todos_list tool.', + }) .option('output-format', { alias: 'o', type: 'string', @@ -641,6 +646,7 @@ export async function loadCliConfig( enableToolOutputTruncation: settings.tools?.enableToolOutputTruncation, eventEmitter: appEvents, useSmartEdit: argv.useSmartEdit ?? settings.useSmartEdit, + useWriteTodos: argv.useWriteTodos ?? settings.useWriteTodos, output: { format: (argv.outputFormat ?? settings.output?.format) as OutputFormat, }, diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts index 4a4b3902e31..f0464608b23 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -824,6 +824,15 @@ const SETTINGS_SCHEMA = { description: 'Enable the smart-edit tool instead of the replace tool.', showInDialog: false, }, + useWriteTodos: { + type: 'boolean', + label: 'Use Write Todos', + category: 'Advanced', + requiresRestart: false, + default: false, + description: 'Enable the write_todos_list tool.', + showInDialog: false, + }, security: { type: 'object', label: 'Security', diff --git a/packages/cli/src/gemini.test.tsx b/packages/cli/src/gemini.test.tsx index ac9fee6d991..7e6df99ca91 100644 --- a/packages/cli/src/gemini.test.tsx +++ b/packages/cli/src/gemini.test.tsx @@ -257,6 +257,7 @@ describe('gemini.tsx main function kitty protocol', () => { includeDirectories: undefined, screenReader: undefined, useSmartEdit: undefined, + useWriteTodos: undefined, promptWords: undefined, outputFormat: undefined, }); diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index cf9e3a89326..7ec97eb97ee 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -241,6 +241,7 @@ export interface ConfigParameters { enableToolOutputTruncation?: boolean; eventEmitter?: EventEmitter; useSmartEdit?: boolean; + useWriteTodos?: boolean; policyEngineConfig?: PolicyEngineConfig; output?: OutputSettings; useModelRouter?: boolean; @@ -328,6 +329,7 @@ export class Config { private readonly fileExclusions: FileExclusions; private readonly eventEmitter?: EventEmitter; private readonly useSmartEdit: boolean; + private readonly useWriteTodos: boolean; private readonly messageBus: MessageBus; private readonly policyEngine: PolicyEngine; private readonly outputSettings: OutputSettings; @@ -417,6 +419,7 @@ export class Config { this.enableToolOutputTruncation = params.enableToolOutputTruncation ?? false; this.useSmartEdit = params.useSmartEdit ?? true; + this.useWriteTodos = params.useWriteTodos ?? false; this.useModelRouter = params.useModelRouter ?? false; this.extensionManagement = params.extensionManagement ?? true; this.storage = new Storage(this.targetDir); @@ -943,6 +946,10 @@ export class Config { return this.useSmartEdit; } + getUseWriteTodos(): boolean { + return this.useWriteTodos; + } + getOutputFormat(): OutputFormat { return this.outputSettings?.format ? this.outputSettings.format @@ -1043,7 +1050,9 @@ export class Config { registerCoreTool(ShellTool, this); registerCoreTool(MemoryTool); registerCoreTool(WebSearchTool, this); - registerCoreTool(WriteTodosTool, this); + if (this.getUseWriteTodos()) { + registerCoreTool(WriteTodosTool, this); + } await registry.discoverAllTools(); return registry; From 4a423e9995975cb143b2355e9b560ea056fcd87f Mon Sep 17 00:00:00 2001 From: Anjali Sridhar Date: Thu, 18 Sep 2025 22:45:56 -0700 Subject: [PATCH 25/33] wip --- .../core/src/core/__snapshots__/prompts.test.ts.snap | 9 --------- packages/core/src/core/prompts.ts | 3 +-- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/packages/core/src/core/__snapshots__/prompts.test.ts.snap b/packages/core/src/core/__snapshots__/prompts.test.ts.snap index 456f5dc0a18..47674d6ca1f 100644 --- a/packages/core/src/core/__snapshots__/prompts.test.ts.snap +++ b/packages/core/src/core/__snapshots__/prompts.test.ts.snap @@ -16,7 +16,6 @@ exports[`Core System Prompt (prompts.ts) > should append userMemory with separat - **Path Construction:** Before using any file system tool (e.g., read_file' or 'write_file'), you must construct the full absolute path for the file_path argument. Always combine the absolute path of the project's root directory with the file's path relative to the root. For example, if the project root is /path/to/project/ and the file is foo/bar/baz.txt, the final path you must use is /path/to/project/foo/bar/baz.txt. If the user provides a relative path, you must resolve it against the root directory to create an absolute path. - **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. - # Primary Workflows ## Software Engineering Tasks @@ -198,7 +197,6 @@ exports[`Core System Prompt (prompts.ts) > should include git instructions when - **Path Construction:** Before using any file system tool (e.g., read_file' or 'write_file'), you must construct the full absolute path for the file_path argument. Always combine the absolute path of the project's root directory with the file's path relative to the root. For example, if the project root is /path/to/project/ and the file is foo/bar/baz.txt, the final path you must use is /path/to/project/foo/bar/baz.txt. If the user provides a relative path, you must resolve it against the root directory to create an absolute path. - **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. - # Primary Workflows ## Software Engineering Tasks @@ -390,7 +388,6 @@ exports[`Core System Prompt (prompts.ts) > should include non-sandbox instructio - **Path Construction:** Before using any file system tool (e.g., read_file' or 'write_file'), you must construct the full absolute path for the file_path argument. Always combine the absolute path of the project's root directory with the file's path relative to the root. For example, if the project root is /path/to/project/ and the file is foo/bar/baz.txt, the final path you must use is /path/to/project/foo/bar/baz.txt. If the user provides a relative path, you must resolve it against the root directory to create an absolute path. - **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. - # Primary Workflows ## Software Engineering Tasks @@ -567,7 +564,6 @@ exports[`Core System Prompt (prompts.ts) > should include sandbox-specific instr - **Path Construction:** Before using any file system tool (e.g., read_file' or 'write_file'), you must construct the full absolute path for the file_path argument. Always combine the absolute path of the project's root directory with the file's path relative to the root. For example, if the project root is /path/to/project/ and the file is foo/bar/baz.txt, the final path you must use is /path/to/project/foo/bar/baz.txt. If the user provides a relative path, you must resolve it against the root directory to create an absolute path. - **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. - # Primary Workflows ## Software Engineering Tasks @@ -744,7 +740,6 @@ exports[`Core System Prompt (prompts.ts) > should include seatbelt-specific inst - **Path Construction:** Before using any file system tool (e.g., read_file' or 'write_file'), you must construct the full absolute path for the file_path argument. Always combine the absolute path of the project's root directory with the file's path relative to the root. For example, if the project root is /path/to/project/ and the file is foo/bar/baz.txt, the final path you must use is /path/to/project/foo/bar/baz.txt. If the user provides a relative path, you must resolve it against the root directory to create an absolute path. - **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. - # Primary Workflows ## Software Engineering Tasks @@ -921,7 +916,6 @@ exports[`Core System Prompt (prompts.ts) > should not include git instructions w - **Path Construction:** Before using any file system tool (e.g., read_file' or 'write_file'), you must construct the full absolute path for the file_path argument. Always combine the absolute path of the project's root directory with the file's path relative to the root. For example, if the project root is /path/to/project/ and the file is foo/bar/baz.txt, the final path you must use is /path/to/project/foo/bar/baz.txt. If the user provides a relative path, you must resolve it against the root directory to create an absolute path. - **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. - # Primary Workflows ## Software Engineering Tasks @@ -1098,7 +1092,6 @@ exports[`Core System Prompt (prompts.ts) > should return the base prompt when no - **Path Construction:** Before using any file system tool (e.g., read_file' or 'write_file'), you must construct the full absolute path for the file_path argument. Always combine the absolute path of the project's root directory with the file's path relative to the root. For example, if the project root is /path/to/project/ and the file is foo/bar/baz.txt, the final path you must use is /path/to/project/foo/bar/baz.txt. If the user provides a relative path, you must resolve it against the root directory to create an absolute path. - **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. - # Primary Workflows ## Software Engineering Tasks @@ -1275,7 +1268,6 @@ exports[`Core System Prompt (prompts.ts) > should return the base prompt when us - **Path Construction:** Before using any file system tool (e.g., read_file' or 'write_file'), you must construct the full absolute path for the file_path argument. Always combine the absolute path of the project's root directory with the file's path relative to the root. For example, if the project root is /path/to/project/ and the file is foo/bar/baz.txt, the final path you must use is /path/to/project/foo/bar/baz.txt. If the user provides a relative path, you must resolve it against the root directory to create an absolute path. - **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. - # Primary Workflows ## Software Engineering Tasks @@ -1452,7 +1444,6 @@ exports[`Core System Prompt (prompts.ts) > should return the base prompt when us - **Path Construction:** Before using any file system tool (e.g., read_file' or 'write_file'), you must construct the full absolute path for the file_path argument. Always combine the absolute path of the project's root directory with the file's path relative to the root. For example, if the project root is /path/to/project/ and the file is foo/bar/baz.txt, the final path you must use is /path/to/project/foo/bar/baz.txt. If the user provides a relative path, you must resolve it against the root directory to create an absolute path. - **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. - # Primary Workflows ## Software Engineering Tasks diff --git a/packages/core/src/core/prompts.ts b/packages/core/src/core/prompts.ts index b2bf6334c76..3de97bec77f 100644 --- a/packages/core/src/core/prompts.ts +++ b/packages/core/src/core/prompts.ts @@ -111,7 +111,6 @@ You are an interactive CLI agent specializing in software engineering tasks. You - **Path Construction:** Before using any file system tool (e.g., ${ReadFileTool.Name}' or '${WriteFileTool.Name}'), you must construct the full absolute path for the file_path argument. Always combine the absolute path of the project's root directory with the file's path relative to the root. For example, if the project root is /path/to/project/ and the file is foo/bar/baz.txt, the final path you must use is /path/to/project/foo/bar/baz.txt. If the user provides a relative path, you must resolve it against the root directory to create an absolute path. - **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. - # Primary Workflows ## Software Engineering Tasks @@ -368,7 +367,7 @@ The structure MUST be as follows: - Build Command: \`npm run build\` - Testing: Tests are run with \`npm test\`. Test files must end in \`.test.ts\`. - API Endpoint: The primary API endpoint is \`https://api.example.com/v2\`. - + --> From 46bda4c8678d77ea84f847aca50d5aaeea5132d0 Mon Sep 17 00:00:00 2001 From: Anjali Sridhar Date: Fri, 19 Sep 2025 13:20:51 -0700 Subject: [PATCH 26/33] wip --- packages/core/src/tools/write-todos.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/core/src/tools/write-todos.ts b/packages/core/src/tools/write-todos.ts index 9c9b8bb5627..70e46522a41 100644 --- a/packages/core/src/tools/write-todos.ts +++ b/packages/core/src/tools/write-todos.ts @@ -15,7 +15,7 @@ import { // Insprired by langchain/deepagents. export const WRITE_TODOS_DESCRIPTION = `This tool can help you list out the current subtasks that are required to be completed for a given user request. The list of subtasks helps you keep track of the current task, organize complex queries and help ensure that you don't miss any steps. With this list, the user can also see the current progress you are making in executing a given task. -Depending on the task complexity, you should first divide a given task in subtasks and then use this tool to list out the subtasks that are required to be completed for a given user request. +Depending on the task complexity, you should first divide a given task into subtasks and then use this tool to list out the subtasks that are required to be completed for a given user request. Each of the subtasks should be clear and distinct. Use this tool for complex queries that require multiple steps. If you find that the request is actually complex after you have started executing the user task, create a todo list and use it. If execution of the user task requires multiple steps, planning and generally is higher complexity than a simple Q&A, use this tool. @@ -26,17 +26,17 @@ DO NOT use this tool for simple tasks that can be completed in less than 2 steps - pending: Work has not begun on a given subtask. - in_progress: Marked just prior to beginning work on a given subtask. You should only have one subtask as in_progress at a time. -- completed: Subtask was succesfully completed with no errors or issues. If the subtask required more more steps to complete, update the todo list. If more information is required from the user and you are in interactive mode, add that as a new subtask for request more information. All steps should be identified as completed only when they are completed. -- cancelled: As you update the todo list, some tasks are not required anymore due to the dynamic nature of the task. In this case, mark the subtasks as cancelled so that the user still sees them in the list and knows that they are not required anymore. +- completed: Subtask was succesfully completed with no errors or issues. If the subtask required more steps to complete, update the todo list with the subtasks. All steps should be identified as completed only when they are completed. +- cancelled: As you update the todo list, some tasks are not required anymore due to the dynamic nature of the task. In this case, mark the subtasks as cancelled. ## Methodology for using this tool -1. Use this too as soon as you receive a user request based on the complexity of the task. -2. Keep track of every subtask that you take my update this list +1. Use this todo list list as soon as you receive a user request based on the complexity of the task. +2. Keep track of every subtask that you update the list with. 3. Mark a subtask as in_progress before you begin working on it. You should only have one subtask as in_progress at a time. -4. Update the subtask list as you proceed in executing the task. The subtask list is not static and should reflect the current progress you are making. +4. Update the subtask list as you proceed in executing the task. The subtask list is not static and should reflect your progress and current plans, which may evolve as you acquire new information. 5. Mark a subtask as completed when you have completed it. -6. Mark a subtask as cancelled if you update the todo list as you proceed in executing the task. +6. Mark a subtask as cancelled if the subtask is no longer needed. 7. You must update the todo list as soon as you start, stop or cancel a subtask. Don't batch or wait to update the todo list. @@ -59,7 +59,7 @@ ToDo list created by the agent: The agent used the todo list to break the task into distinct, manageable steps: 1. Building an entire interactive web application from scratch is a highly complex, multi-stage process involving setup, UI development, logic integration, and deployment. 2. The agent inferred the core functionality required for a "logo creator," such as UI controls for customization (Task 3) and an export feature (Task 7), which must be tracked as distinct goals. -3. The agent rightly inferred the requirement of a API service model for interacting with the image model endpoint. +3. The agent rightly inferred the requirement of an API service model for interacting with the image model endpoint. From a66eb629d8b5bf24085ed5bc1e1ed1df6d2022e9 Mon Sep 17 00:00:00 2001 From: joshualitt Date: Fri, 19 Sep 2025 11:08:41 -0700 Subject: [PATCH 27/33] Revert "feat(third_party) Port `get-ripgrep`." (#8923) --- .gitignore | 1 - esbuild.config.js | 4 - npm-shrinkwrap.json | 115 ++++++------ package.json | 1 - packages/core/package.json | 2 +- packages/core/src/tools/ripGrep.test.ts | 4 +- packages/core/src/tools/ripGrep.ts | 2 +- packages/core/tsconfig.json | 3 +- scripts/build_package.js | 4 +- scripts/clean.js | 1 - third_party/get-ripgrep/package.json | 30 +-- ...{downloadRipGrep.ts => downloadRipGrep.js} | 61 ++++--- .../get-ripgrep/src/downloadRipGrep.test.ts | 172 ------------------ third_party/get-ripgrep/src/index.js | 17 ++ third_party/get-ripgrep/src/index.ts | 7 - third_party/get-ripgrep/tsconfig.json | 13 -- 16 files changed, 129 insertions(+), 308 deletions(-) rename third_party/get-ripgrep/src/{downloadRipGrep.ts => downloadRipGrep.js} (61%) delete mode 100644 third_party/get-ripgrep/src/downloadRipGrep.test.ts create mode 100644 third_party/get-ripgrep/src/index.js delete mode 100644 third_party/get-ripgrep/src/index.ts delete mode 100644 third_party/get-ripgrep/tsconfig.json diff --git a/.gitignore b/.gitignore index 84a90e86da2..d44f7ad3496 100644 --- a/.gitignore +++ b/.gitignore @@ -40,7 +40,6 @@ bundle # Test report files junit.xml packages/*/coverage/ -third_party/*/coverage/ # Generated files packages/cli/src/generated/ diff --git a/esbuild.config.js b/esbuild.config.js index d6df6e319af..3f1644a63e7 100644 --- a/esbuild.config.js +++ b/esbuild.config.js @@ -41,10 +41,6 @@ esbuild format: 'esm', external, alias: { - 'get-ripgrep': path.resolve( - __dirname, - 'third_party/get-ripgrep/src/index.ts', - ), 'is-in-ci': path.resolve( __dirname, 'packages/cli/src/patches/is-in-ci.ts', diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 684a3a7910c..9ab80d68386 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -8,7 +8,6 @@ "name": "@google/gemini-cli", "version": "0.7.0-nightly.20250918.2722473a", "workspaces": [ - "third_party/get-ripgrep", "packages/*" ], "dependencies": { @@ -398,13 +397,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", - "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", + "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.4" + "@babel/types": "^7.27.3" }, "bin": { "parser": "bin/babel-parser.js" @@ -424,9 +423,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", - "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", + "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1834,15 +1833,41 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/@joshua.litt/get-ripgrep": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@joshua.litt/get-ripgrep/-/get-ripgrep-0.0.2.tgz", + "integrity": "sha512-cSHA+H+HEkOXeiCxrNvGj/pgv2Y0bfp4GbH3R87zr7Vob2pDUZV3BkUL9ucHMoDFID4GteSy5z5niN/lF9QeuQ==", + "dependencies": { + "@lvce-editor/verror": "^1.6.0", + "execa": "^9.5.2", + "extract-zip": "^2.0.1", + "fs-extra": "^11.3.0", + "got": "^14.4.5", + "path-exists": "^5.0.0", + "xdg-basedir": "^5.1.0" + } + }, + "node_modules/@joshua.litt/get-ripgrep/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -1855,6 +1880,16 @@ "node": ">=6.0.0" } }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", @@ -1863,9 +1898,9 @@ "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1955,6 +1990,12 @@ "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", "license": "MIT" }, + "node_modules/@lvce-editor/verror": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@lvce-editor/verror/-/verror-1.7.0.tgz", + "integrity": "sha512-+LGuAEIC2L7pbvkyAQVWM2Go0dAy+UWEui28g07zNtZsCBhm+gusBK8PNwLJLV5Jay+TyUYuwLIbJdjLLzqEBg==", + "license": "MIT" + }, "node_modules/@lydell/node-pty": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@lydell/node-pty/-/node-pty-1.1.0.tgz", @@ -8712,10 +8753,6 @@ "node": ">= 0.4" } }, - "node_modules/get-ripgrep": { - "resolved": "third_party/get-ripgrep", - "link": true - }, "node_modules/get-stream": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", @@ -17055,6 +17092,7 @@ "@google-cloud/opentelemetry-cloud-monitoring-exporter": "^0.21.0", "@google-cloud/opentelemetry-cloud-trace-exporter": "^3.0.0", "@google/genai": "1.16.0", + "@joshua.litt/get-ripgrep": "^0.0.2", "@modelcontextprotocol/sdk": "^1.11.0", "@opentelemetry/api": "^1.9.0", "@opentelemetry/exporter-logs-otlp-grpc": "^0.203.0", @@ -17078,7 +17116,6 @@ "fast-uri": "^3.0.6", "fdir": "^6.4.6", "fzf": "^0.5.2", - "get-ripgrep": "file:../../third_party/get-ripgrep", "glob": "^10.4.5", "google-auth-library": "^9.11.0", "html-to-text": "^9.0.5", @@ -17455,44 +17492,6 @@ "engines": { "node": ">= 0.6" } - }, - "third_party/get-ripgrep": { - "version": "0.0.0-dev", - "license": "MIT", - "dependencies": { - "execa": "^9.5.2", - "extract-zip": "^2.0.1", - "fs-extra": "^11.3.0", - "got": "^14.4.5", - "path-exists": "^5.0.0", - "xdg-basedir": "^5.1.0" - }, - "devDependencies": { - "@types/fs-extra": "^11.0.4", - "@types/node": "^22.13.0", - "prettier": "^3.4.2", - "typescript": "^5.7.3", - "vitest": "^3.1.1" - } - }, - "third_party/get-ripgrep/node_modules/@types/node": { - "version": "22.18.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.6.tgz", - "integrity": "sha512-r8uszLPpeIWbNKtvWRt/DbVi5zbqZyj1PTmhRMqBMvDnaz1QpmSKujUtJLrqGZeoM8v72MfYggDceY4K1itzWQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "third_party/get-ripgrep/node_modules/path-exists": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } } } } diff --git a/package.json b/package.json index 5ba588a5103..133e7a4d081 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,6 @@ }, "type": "module", "workspaces": [ - "third_party/get-ripgrep", "packages/*" ], "private": "true", diff --git a/packages/core/package.json b/packages/core/package.json index ce33c325ebf..4556679efbb 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -24,6 +24,7 @@ "@google-cloud/opentelemetry-cloud-monitoring-exporter": "^0.21.0", "@google-cloud/opentelemetry-cloud-trace-exporter": "^3.0.0", "@google-cloud/logging": "^11.2.1", + "@joshua.litt/get-ripgrep": "^0.0.2", "@modelcontextprotocol/sdk": "^1.11.0", "@opentelemetry/api": "^1.9.0", "@opentelemetry/exporter-logs-otlp-grpc": "^0.203.0", @@ -47,7 +48,6 @@ "fast-uri": "^3.0.6", "fdir": "^6.4.6", "fzf": "^0.5.2", - "get-ripgrep": "file:../../third_party/get-ripgrep", "glob": "^10.4.5", "google-auth-library": "^9.11.0", "html-to-text": "^9.0.5", diff --git a/packages/core/src/tools/ripGrep.test.ts b/packages/core/src/tools/ripGrep.test.ts index 89a96314234..7c47275b497 100644 --- a/packages/core/src/tools/ripGrep.test.ts +++ b/packages/core/src/tools/ripGrep.test.ts @@ -22,11 +22,11 @@ import type { Config } from '../config/config.js'; import { createMockWorkspaceContext } from '../test-utils/mockWorkspaceContext.js'; import type { ChildProcess } from 'node:child_process'; import { spawn } from 'node:child_process'; -import { downloadRipGrep } from 'get-ripgrep'; +import { downloadRipGrep } from '@joshua.litt/get-ripgrep'; import { fileExists } from '../utils/fileUtils.js'; // Mock dependencies for canUseRipgrep -vi.mock('get-ripgrep', () => ({ +vi.mock('@joshua.litt/get-ripgrep', () => ({ downloadRipGrep: vi.fn(), })); vi.mock('../utils/fileUtils.js', async (importOriginal) => { diff --git a/packages/core/src/tools/ripGrep.ts b/packages/core/src/tools/ripGrep.ts index 9b8cd6a0eb1..269fb379930 100644 --- a/packages/core/src/tools/ripGrep.ts +++ b/packages/core/src/tools/ripGrep.ts @@ -8,7 +8,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { EOL } from 'node:os'; import { spawn } from 'node:child_process'; -import { downloadRipGrep } from 'get-ripgrep'; +import { downloadRipGrep } from '@joshua.litt/get-ripgrep'; import type { ToolInvocation, ToolResult } from './tools.js'; import { BaseDeclarativeTool, BaseToolInvocation, Kind } from './tools.js'; import { SchemaValidator } from '../utils/schemaValidator.js'; diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index c44f208142c..b788af471a2 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -7,6 +7,5 @@ "types": ["node", "vitest/globals"] }, "include": ["index.ts", "src/**/*.ts", "src/**/*.json"], - "exclude": ["node_modules", "dist"], - "references": [{ "path": "../../third_party/get-ripgrep" }] + "exclude": ["node_modules", "dist"] } diff --git a/scripts/build_package.js b/scripts/build_package.js index ab2d8f47bd0..73f73861e95 100644 --- a/scripts/build_package.js +++ b/scripts/build_package.js @@ -21,9 +21,7 @@ import { execSync } from 'node:child_process'; import { writeFileSync } from 'node:fs'; import { join } from 'node:path'; -if ( - !(process.cwd().includes('packages') || process.cwd().includes('third_party')) -) { +if (!process.cwd().includes('packages')) { console.error('must be invoked from a package directory'); process.exit(1); } diff --git a/scripts/clean.js b/scripts/clean.js index a66108c3128..88493a63c15 100644 --- a/scripts/clean.js +++ b/scripts/clean.js @@ -43,7 +43,6 @@ for (const workspace of rootPackageJson.workspaces) { for (const pkgPath of packages) { const pkgDir = dirname(join(root, pkgPath)); rmSync(join(pkgDir, 'dist'), RMRF_OPTIONS); - rmSync(join(pkgDir, 'tsconfig.tsbuildinfo'), RMRF_OPTIONS); } } diff --git a/third_party/get-ripgrep/package.json b/third_party/get-ripgrep/package.json index be69f8a30f1..80e01ae7ac8 100644 --- a/third_party/get-ripgrep/package.json +++ b/third_party/get-ripgrep/package.json @@ -1,20 +1,19 @@ { - "name": "get-ripgrep", + "name": "@lvce-editor/ripgrep", "version": "0.0.0-dev", - "description": "A module for downloading ripgrep at runtime a Node project", - "main": "dist/index.js", - "files": [ - "dist" - ], + "description": "A module for using ripgrep in a Node project", + "main": "src/index.js", + "typings": "src/index.d.ts", "type": "module", + "repository": { + "type": "git", + "url": "https://github.com/lvce-editor/ripgrep" + }, "scripts": { - "build": "node ../../scripts/build_package.js", - "prepublishOnly": "npm run build", - "format": "prettier --write .", - "lint": "eslint . --ext .ts,.tsx", - "test": "vitest run", - "test:ci": "vitest run --coverage", - "typecheck": "tsc --noEmit" + "postinstall": "node ./src/postinstall.js", + "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js", + "test:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch", + "format": "prettier --write ." }, "keywords": [ "lvce-editor", @@ -23,17 +22,20 @@ "author": "Lvce Editor", "license": "MIT", "dependencies": { + "@lvce-editor/verror": "^1.6.0", "execa": "^9.5.2", "extract-zip": "^2.0.1", "fs-extra": "^11.3.0", "got": "^14.4.5", "path-exists": "^5.0.0", + "tempy": "^3.1.0", "xdg-basedir": "^5.1.0" }, "devDependencies": { "@types/fs-extra": "^11.0.4", + "@types/jest": "^29.5.14", "@types/node": "^22.13.0", - "vitest": "^3.1.1", + "jest": "^29.7.0", "prettier": "^3.4.2", "typescript": "^5.7.3" }, diff --git a/third_party/get-ripgrep/src/downloadRipGrep.ts b/third_party/get-ripgrep/src/downloadRipGrep.js similarity index 61% rename from third_party/get-ripgrep/src/downloadRipGrep.ts rename to third_party/get-ripgrep/src/downloadRipGrep.js index 88ceecf2ffb..906b2a9e2e5 100644 --- a/third_party/get-ripgrep/src/downloadRipGrep.ts +++ b/third_party/get-ripgrep/src/downloadRipGrep.js @@ -4,6 +4,7 @@ * Copyright 2023 Lvce Editor * SPDX-License-Identifier: MIT */ +import { VError } from '@lvce-editor/verror' import { execa } from 'execa' import extractZip from 'extract-zip' import fsExtra from 'fs-extra' @@ -12,19 +13,22 @@ import * as os from 'node:os' import { dirname, join } from 'node:path' import { pathExists } from 'path-exists' import { pipeline } from 'node:stream/promises' +import { temporaryFile } from 'tempy' import { fileURLToPath } from 'node:url' import { xdgCache } from 'xdg-basedir' -import path from 'path' + +const { mkdir, createWriteStream, move } = fsExtra const __dirname = dirname(fileURLToPath(import.meta.url)) const REPOSITORY = `microsoft/ripgrep-prebuilt` -const VERSION = process.env['RIPGREP_VERSION'] || 'v13.0.0-10' +const VERSION = process.env.RIPGREP_VERSION || 'v13.0.0-10' +console.log({ VERSION }) const BIN_PATH = join(__dirname, '../bin') const getTarget = () => { - const arch = process.env['npm_config_arch'] || os.arch() - const platform = process.env['platform'] || os.platform() + const arch = process.env.npm_config_arch || os.arch() + const platform = process.env.platform || os.platform() switch (platform) { case 'darwin': switch (arch) { @@ -59,60 +63,61 @@ const getTarget = () => { return 'i686-unknown-linux-musl.tar.gz' } default: - throw new Error('Unknown platform: ' + platform) + throw new VError('Unknown platform: ' + platform) } } -async function downloadFile(url: string, outFile: string): Promise { - let tmpDir = undefined +export const downloadFile = async (url, outFile) => { try { - tmpDir = await fsExtra.mkdtemp('download-ripgrep') - const tmpFile = path.join(tmpDir, 'tmp-file') - await pipeline(got.stream(url), fsExtra.createWriteStream(tmpFile)) - await fsExtra.mkdir(dirname(outFile), { recursive: true }) - await fsExtra.move(tmpFile, outFile) + const tmpFile = temporaryFile() + await pipeline(got.stream(url), createWriteStream(tmpFile)) + await mkdir(dirname(outFile), { recursive: true }) + await move(tmpFile, outFile) } catch (error) { - throw new Error(`Failed to download "${url}": ${error}`) - } finally { - if (tmpDir) { - await fsExtra.rm(tmpDir, { recursive: true, force: true }) - } + throw new VError(error, `Failed to download "${url}"`) } } -async function unzip(inFile: string, outDir: string): Promise { +/** + * @param {string} inFile + * @param {string} outDir + */ +const unzip = async (inFile, outDir) => { try { - await fsExtra.mkdir(outDir, { recursive: true }) + await mkdir(outDir, { recursive: true }) await extractZip(inFile, { dir: outDir }) } catch (error) { - throw new Error(`Failed to unzip "${inFile}": ${error}`) + throw new VError(error, `Failed to unzip "${inFile}"`) } } -async function untarGz(inFile: string, outDir: string): Promise { +/** + * @param {string} inFile + * @param {string} outDir + */ +const untarGz = async (inFile, outDir) => { try { - await fsExtra.mkdir(outDir, { recursive: true }) + await mkdir(outDir, { recursive: true }) await execa('tar', ['xvf', inFile, '-C', outDir]) } catch (error) { - throw new Error(`Failed to extract "${inFile}": ${error}`) + throw new VError(error, `Failed to extract "${inFile}"`) } } -export async function downloadRipGrep(overrideBinPath?: string): Promise { +export const downloadRipGrep = async () => { const target = getTarget() const url = `https://github.com/${REPOSITORY}/releases/download/${VERSION}/ripgrep-${VERSION}-${target}` const downloadPath = `${xdgCache}/vscode-ripgrep/ripgrep-${VERSION}-${target}` - const binPath = overrideBinPath ?? BIN_PATH if (!(await pathExists(downloadPath))) { await downloadFile(url, downloadPath) } else { console.info(`File ${downloadPath} has been cached`) } if (downloadPath.endsWith('.tar.gz')) { - await untarGz(downloadPath, binPath) + await untarGz(downloadPath, BIN_PATH) } else if (downloadPath.endsWith('.zip')) { - await unzip(downloadPath, binPath) + await unzip(downloadPath, BIN_PATH) } else { - throw new Error(`Invalid downloadPath ${downloadPath}`) + throw new VError(`Invalid downloadPath ${downloadPath}`) } } diff --git a/third_party/get-ripgrep/src/downloadRipGrep.test.ts b/third_party/get-ripgrep/src/downloadRipGrep.test.ts deleted file mode 100644 index 31869944f8e..00000000000 --- a/third_party/get-ripgrep/src/downloadRipGrep.test.ts +++ /dev/null @@ -1,172 +0,0 @@ -/** - * @license - * Copyright 2025 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { Writable, Readable } from 'node:stream' -import fs from 'node:fs' -import { downloadRipGrep } from './downloadRipGrep.js' -import { execa } from 'execa' -import extractZip from 'extract-zip' -import fsExtra from 'fs-extra' -import got from 'got' -import * as os from 'node:os' -import { pathExists } from 'path-exists' -import { pipeline } from 'node:stream/promises' - -// Mock dependencies before any imports -vi.mock('execa', () => ({ - execa: vi.fn(), -})) - -vi.mock('extract-zip', () => ({ - default: vi.fn(), -})) - -vi.mock('fs-extra', () => ({ - default: { - mkdir: vi.fn(), - createWriteStream: vi.fn(() => new Writable()), - move: vi.fn(), - mkdtemp: vi.fn(async (prefix) => `${prefix}`), - rm: vi.fn(), - }, -})) - -vi.mock('got', () => ({ - default: { - stream: vi.fn(), - }, -})) - -vi.mock('node:os', () => ({ - platform: vi.fn(), - arch: vi.fn(), -})) - -vi.mock('path-exists', () => ({ - pathExists: vi.fn(), -})) - -vi.mock('node:stream/promises', () => ({ - pipeline: vi.fn(), -})) - -vi.mock('xdg-basedir', () => ({ - xdgCache: `./mocked/cache`, -})) - -describe('downloadRipGrep', () => { - beforeEach(() => { - vi.resetAllMocks() - // Mock got.stream to return a real, empty readable stream - const mockStream = new Readable({ - read() { - this.push(null) // Signal end of stream - }, - }) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - vi.mocked(got.stream).mockReturnValue(mockStream as any) - vi.mocked(pipeline).mockResolvedValue(undefined) - }) - - afterEach(() => { - vi.restoreAllMocks() - fs.rmSync('./test/bin', { recursive: true, force: true }) - fs.rmSync('./mocked', { recursive: true, force: true }) - }) - - const testMatrix = [ - { - platform: 'darwin', - arch: 'arm64', - target: 'aarch64-apple-darwin.tar.gz', - }, - { platform: 'darwin', arch: 'x64', target: 'x86_64-apple-darwin.tar.gz' }, - { platform: 'win32', arch: 'x64', target: 'x86_64-pc-windows-msvc.zip' }, - { platform: 'win32', arch: 'arm', target: 'aarch64-pc-windows-msvc.zip' }, - { - platform: 'linux', - arch: 'x64', - target: 'x86_64-unknown-linux-musl.tar.gz', - }, - { - platform: 'linux', - arch: 'arm64', - target: 'aarch64-unknown-linux-gnu.tar.gz', - }, - ] - - for (const { platform, arch, target } of testMatrix) { - it(`should download and extract for ${platform}-${arch}`, async () => { - vi.mocked(os.platform).mockReturnValue(platform as NodeJS.Platform) - vi.mocked(os.arch).mockReturnValue(arch) - vi.mocked(pathExists).mockResolvedValue(false) - - const binPath = './test/bin' - await downloadRipGrep(binPath) - - const version = 'v13.0.0-10' - const expectedUrl = `https://github.com/microsoft/ripgrep-prebuilt/releases/download/${version}/ripgrep-${version}-${target}` - const expectedDownloadPath = `./mocked/cache/vscode-ripgrep/ripgrep-${version}-${target}` - - // Check download - expect(got.stream).toHaveBeenCalledWith(expectedUrl) - expect(pipeline).toHaveBeenCalled() - - // Check extraction - if (target.endsWith('.tar.gz')) { - expect(execa).toHaveBeenCalledWith('tar', [ - 'xvf', - expectedDownloadPath, - '-C', - binPath, - ]) - expect(extractZip).not.toHaveBeenCalled() - } else if (target.endsWith('.zip')) { - expect(extractZip).toHaveBeenCalledWith(expectedDownloadPath, { - dir: binPath, - }) - expect(execa).not.toHaveBeenCalled() - } - }) - } - - it('should use the cached file if it exists', async () => { - vi.mocked(os.platform).mockReturnValue('linux') - vi.mocked(os.arch).mockReturnValue('x64') - vi.mocked(pathExists).mockResolvedValue(true) - - const binPath = './test/bin' - await downloadRipGrep(binPath) - - expect(got.stream).not.toHaveBeenCalled() - expect(pipeline).not.toHaveBeenCalled() - expect(execa).toHaveBeenCalled() // Still extracts - }) - - it('should throw an error for an unknown platform', async () => { - vi.mocked(os.platform).mockReturnValue('sunos' as NodeJS.Platform) // an unsupported platform - vi.mocked(os.arch).mockReturnValue('x64') - - await expect(downloadRipGrep('./test/bin')).rejects.toThrow( - 'Unknown platform: sunos', - ) - }) - - it('should clean up temporary files on successful download', async () => { - vi.mocked(os.platform).mockReturnValue('linux') - vi.mocked(os.arch).mockReturnValue('x64') - vi.mocked(pathExists).mockResolvedValue(false) - - await downloadRipGrep('./test/bin') - - expect(fsExtra.mkdtemp).toHaveBeenCalledWith('download-ripgrep') - expect(fsExtra.rm).toHaveBeenCalledWith('download-ripgrep', { - recursive: true, - force: true, - }) - }) -}) diff --git a/third_party/get-ripgrep/src/index.js b/third_party/get-ripgrep/src/index.js new file mode 100644 index 00000000000..8fc965e98fe --- /dev/null +++ b/third_party/get-ripgrep/src/index.js @@ -0,0 +1,17 @@ +/* eslint-disable */ +/** + * @license + * Copyright 2023 Lvce Editor + * SPDX-License-Identifier: MIT + */ +import { dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' + +const __dirname = dirname(fileURLToPath(import.meta.url)) + +export const rgPath = join( + __dirname, + '..', + 'bin', + `rg${process.platform === 'win32' ? '.exe' : ''}`, +) diff --git a/third_party/get-ripgrep/src/index.ts b/third_party/get-ripgrep/src/index.ts deleted file mode 100644 index f436ed49ebb..00000000000 --- a/third_party/get-ripgrep/src/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* eslint-disable */ -/** - * @license - * Copyright 2023 Lvce Editor - * SPDX-License-Identifier: MIT - */ -export { downloadRipGrep } from './downloadRipGrep.js' diff --git a/third_party/get-ripgrep/tsconfig.json b/third_party/get-ripgrep/tsconfig.json deleted file mode 100644 index 8147e5a05b0..00000000000 --- a/third_party/get-ripgrep/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "dist", - "lib": ["DOM", "DOM.Iterable", "ES2021"], - "composite": true, - "types": ["node", "vitest/globals"], - "rootDir": "src", - "declaration": true - }, - "include": ["src/**/*.ts", "src/**/*.test.ts"], - "exclude": ["node_modules", "dist"] -} From 4de9aca498608e13bf77cfb1fe30998c8d72d54a Mon Sep 17 00:00:00 2001 From: Tommaso Sciortino Date: Fri, 19 Sep 2025 11:22:01 -0700 Subject: [PATCH 28/33] Rollback shrinkwrap (#8926) --- .github/CODEOWNERS | 4 ++-- .github/actions/publish-release/action.yml | 10 ++-------- .github/workflows/e2e.yml | 2 +- .github/workflows/release-promote.yml | 3 --- .prettierignore | 1 - npm-shrinkwrap.json => package-lock.json | 0 package.json | 3 +-- packages/cli/package.json | 3 +-- .../vscode-ide-companion/scripts/generate-notices.js | 4 ++-- scripts/check-lockfile.js | 4 ++-- scripts/prepare-package.js | 1 - scripts/version.js | 2 +- 12 files changed, 12 insertions(+), 25 deletions(-) rename npm-shrinkwrap.json => package-lock.json (100%) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a4263466eca..b3d94bfd5f2 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,10 +4,10 @@ # Require reviews from the release approvers for critical files. # These patterns override the rule above. /package.json @google-gemini/gemini-cli-askmode-approvers -/npm-shrinkwrap.json @google-gemini/gemini-cli-askmode-approvers +/package-lock.json @google-gemini/gemini-cli-askmode-approvers /GEMINI.md @google-gemini/gemini-cli-askmode-approvers /SECURITY.md @google-gemini/gemini-cli-askmode-approvers /LICENSE @google-gemini/gemini-cli-askmode-approvers /.github/workflows/ @google-gemini/gemini-cli-askmode-approvers /packages/cli/package.json @google-gemini/gemini-cli-askmode-approvers -/packages/core/package.json @google-gemini/gemini-cli-askmode-approvers \ No newline at end of file +/packages/core/package.json @google-gemini/gemini-cli-askmode-approvers diff --git a/.github/actions/publish-release/action.yml b/.github/actions/publish-release/action.yml index 0dd3c751719..2f90ba2b10e 100644 --- a/.github/actions/publish-release/action.yml +++ b/.github/actions/publish-release/action.yml @@ -69,14 +69,8 @@ runs: BRANCH_NAME: '${{ steps.release_branch.outputs.BRANCH_NAME }}' DRY_RUN: '${{ inputs.dry-run }}' RELEASE_TAG: '${{ inputs.release-tag }}' - run: | - git add package.json packages/*/package.json - if [ -f npm-shrinkwrap.json ]; then - git add npm-shrinkwrap.json - fi - if [ -f package-lock.json ]; then - git add package-lock.json - fi + run: |- + git add package.json package-lock.json packages/*/package.json git commit -m "chore(release): ${RELEASE_TAG}" if [[ "${DRY_RUN}" == "false" ]]; then echo "Pushing release branch to remote..." diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 34f0ed795d8..e95cdcdc96e 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -45,7 +45,7 @@ jobs: run: 'npm run build' - name: 'Archive build artifacts' - run: 'tar -cvf build-artifacts.tar bundle/ node_modules/ packages/ package.json npm-shrinkwrap.json' + run: 'tar -cvf build-artifacts.tar bundle/ node_modules/ packages/ package.json' - name: 'Upload build artifacts' uses: 'actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02' diff --git a/.github/workflows/release-promote.yml b/.github/workflows/release-promote.yml index ef8e3f680f7..df664749218 100644 --- a/.github/workflows/release-promote.yml +++ b/.github/workflows/release-promote.yml @@ -303,9 +303,6 @@ jobs: DRY_RUN: '${{ github.event.inputs.dry_run }}' run: |- git add package.json packages/*/package.json - if [ -f npm-shrinkwrap.json ]; then - git add npm-shrinkwrap.json - fi if [ -f package-lock.json ]; then git add package-lock.json fi diff --git a/.prettierignore b/.prettierignore index fbf5c453d61..f4330b7e68a 100644 --- a/.prettierignore +++ b/.prettierignore @@ -17,5 +17,4 @@ eslint.config.js **/generated gha-creds-*.json junit.xml -npm-shrinkwrap.json Thumbs.db diff --git a/npm-shrinkwrap.json b/package-lock.json similarity index 100% rename from npm-shrinkwrap.json rename to package-lock.json diff --git a/package.json b/package.json index 133e7a4d081..db9ac46bacd 100644 --- a/package.json +++ b/package.json @@ -59,8 +59,7 @@ "files": [ "bundle/", "README.md", - "LICENSE", - "npm-shrinkwrap.json" + "LICENSE" ], "devDependencies": { "@types/marked": "^5.0.2", diff --git a/packages/cli/package.json b/packages/cli/package.json index 22e374c9207..2ce5e550d86 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -22,8 +22,7 @@ "typecheck": "tsc --noEmit" }, "files": [ - "dist", - "npm-shrinkwrap.json" + "dist" ], "config": { "sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.7.0-nightly.20250918.2722473a" diff --git a/packages/vscode-ide-companion/scripts/generate-notices.js b/packages/vscode-ide-companion/scripts/generate-notices.js index 9f7901f0f66..b6c3341fac2 100644 --- a/packages/vscode-ide-companion/scripts/generate-notices.js +++ b/packages/vscode-ide-companion/scripts/generate-notices.js @@ -94,7 +94,7 @@ function collectDependencies(packageName, packageLock, dependenciesMap) { const packageInfo = packageLock.packages[`node_modules/${packageName}`]; if (!packageInfo) { console.warn( - `Warning: Could not find package info for ${packageName} in npm-shrinkwrap.json.`, + `Warning: Could not find package info for ${packageName} in package-lock.json.`, ); return; } @@ -114,7 +114,7 @@ async function main() { const packageJsonContent = await fs.readFile(packageJsonPath, 'utf-8'); const packageJson = JSON.parse(packageJsonContent); - const packageLockJsonPath = path.join(projectRoot, 'npm-shrinkwrap.json'); + const packageLockJsonPath = path.join(projectRoot, 'package-lock.json'); const packageLockJsonContent = await fs.readFile( packageLockJsonPath, 'utf-8', diff --git a/scripts/check-lockfile.js b/scripts/check-lockfile.js index 6346ffd90f6..7cda57a7d63 100644 --- a/scripts/check-lockfile.js +++ b/scripts/check-lockfile.js @@ -10,7 +10,7 @@ import { fileURLToPath } from 'node:url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const root = join(__dirname, '..'); -const lockfilePath = join(root, 'npm-shrinkwrap.json'); +const lockfilePath = join(root, 'package-lock.json'); function readJsonFile(filePath) { try { @@ -64,7 +64,7 @@ for (const [location, details] of Object.entries(packages)) { if (invalidPackages.length > 0) { console.error( - '\nError: The following dependencies in npm-shrinkwrap.json are missing the "resolved" or "integrity" field:', + '\nError: The following dependencies in package-lock.json are missing the "resolved" or "integrity" field:', ); invalidPackages.forEach((pkg) => console.error(`- ${pkg}`)); process.exitCode = 1; diff --git a/scripts/prepare-package.js b/scripts/prepare-package.js index 313e61293bd..ff1dc137ffb 100644 --- a/scripts/prepare-package.js +++ b/scripts/prepare-package.js @@ -46,7 +46,6 @@ copyFiles('core', { copyFiles('cli', { 'README.md': 'README.md', LICENSE: 'LICENSE', - 'npm-shrinkwrap.json': 'npm-shrinkwrap.json', }); console.log('Successfully prepared all packages.'); diff --git a/scripts/version.js b/scripts/version.js index 38c45fe2e4d..24b687f4466 100644 --- a/scripts/version.js +++ b/scripts/version.js @@ -75,7 +75,7 @@ if (cliPackageJson.config?.sandboxImageUri) { writeJson(cliPackageJsonPath, cliPackageJson); } -// 6. Run `npm install` to update npm-shrinkwrap.json. +// 6. Run `npm install` to update package-lock.json. run('npm install'); console.log(`Successfully bumped versions to v${newVersion}.`); From 502da6f8bb672478a97215781310a7b65229e3cb Mon Sep 17 00:00:00 2001 From: matt korwel Date: Fri, 19 Sep 2025 11:43:39 -0700 Subject: [PATCH 29/33] Release: Ensure Tag Modification works (#8931) Co-authored-by: gemini-cli-robot --- .github/workflows/release-change-tags.yml | 26 +++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release-change-tags.yml b/.github/workflows/release-change-tags.yml index b594d99d173..88d0f9912f4 100644 --- a/.github/workflows/release-change-tags.yml +++ b/.github/workflows/release-change-tags.yml @@ -8,14 +8,19 @@ on: required: true type: 'string' channel: - description: 'The npm dist-tag to apply (e.g., preview, stable).' + description: 'The npm dist-tag to apply (e.g., latest, preview, nightly).' required: true type: 'choice' options: - - 'stable' + - 'latest' - 'preview' - 'nightly' - dry_run: + ref: + description: 'The branch, tag, or SHA to run from.' + required: false + type: 'string' + default: 'main' + dry-run: description: 'Whether to run in dry-run mode.' required: false type: 'boolean' @@ -28,6 +33,12 @@ jobs: packages: 'write' issues: 'write' steps: + - name: 'Checkout repository' + uses: 'actions/checkout@v4' + with: + ref: '${{ github.event.inputs.ref }}' + fetch-depth: 0 + - name: 'Setup Node.js' uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' with: @@ -36,20 +47,23 @@ jobs: scope: '@google' - name: 'Change tag for @google/gemini-cli-core' - if: 'github.event.inputs.dry_run == false' + if: |- + ${{ github.event.inputs.dry-run == 'false' }} env: NODE_AUTH_TOKEN: '${{ secrets.WOMBAT_TOKEN_CORE }}' run: | npm dist-tag add @google/gemini-cli-core@${{ github.event.inputs.version }} ${{ github.event.inputs.channel }} - name: 'Change tag for @google/gemini-cli' - if: 'github.event.inputs.dry_run == false' + if: |- + ${{ github.event.inputs.dry-run == 'false' }} env: NODE_AUTH_TOKEN: '${{ secrets.WOMBAT_TOKEN_CLI }}' run: | npm dist-tag add @google/gemini-cli@${{ github.event.inputs.version }} ${{ github.event.inputs.channel }} - name: 'Log dry run' - if: 'github.event.inputs.dry_run == true' + if: |- + ${{ github.event.inputs.dry-run == 'true' }} run: | echo "Dry run: Would have added tag '${{ github.event.inputs.channel }}' to version '${{ github.event.inputs.version }}' for @google/gemini-cli and @google/gemini-cli-core." From 5238f638416a59da10c27ec18a5ecf9a0b42880d Mon Sep 17 00:00:00 2001 From: Jacob MacDonald Date: Fri, 19 Sep 2025 11:55:30 -0700 Subject: [PATCH 30/33] Update extension-releasing.md to have more info (#8927) --- docs/extension-releasing.md | 57 ++++++++++++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/docs/extension-releasing.md b/docs/extension-releasing.md index a623a4bfc8f..86e212c653d 100644 --- a/docs/extension-releasing.md +++ b/docs/extension-releasing.md @@ -1,10 +1,53 @@ # Extension Releasing -Gemini CLI extensions can be distributed as pre-built binaries through GitHub Releases. This provides a faster and more reliable installation experience for users, as it avoids the need to clone the repository and build the extension from source. +There are two primary ways of releasing extensions to users: -## Asset naming convention +- [Git repository](#releasing-through-a-git-repository) +- [Github Releases](#releasing-through-github-releases) -To ensure Gemini CLI can automatically find the correct release asset for each platform, you should follow this naming convention. The CLI will search for assets in the following order: +Git repository releases tend to be the simplest and most flexible approach, while GitHub releases can be more efficient on initial install as they are shipped as single archives instead of requiring a git clone which downloads each file individually. Github releases may also contain platform specific archives if you need to ship platform specific binary files. + +## Releasing through a git repository + +This is the most flexible and simple option. All you need to do us create a publicly accessible git repo (such as a public github repository) and then users can install your extension using `gemini extensions install `, or for a GitHub repository they can use the simplified `gemini extensions install /` format. They can optionally depend on a specific ref (branch/tag/commit) using the `--ref=` argument, this defaults to the default branch. + +Whenever commits are pushed to the ref that a user depends on, they will be prompted to update the extension. Note that this also allows for easy rollbacks, the HEAD commit is always treated as the latest version regardless of the actual version in the `gemini-extension.json` file. + +### Managing release channels using a git repository + +Users can depend on any ref from your git repo, such as a branch or tag, which allows you to manage multiple release channels. + +For instance, you can maintain a `stable` branch, which users can install this way `gemini extensions install --ref=stable`. Or, you could make this the default by treating your default branch as your stable release branch, and doing development in a different branch (for instance called `dev`). You can maintain as many branches or tags as you like, providing maximum flexibility for you and your users. + +Note that these `ref` arguments can be tags, branches, or even specific commits, which allows users to depend on a specific version of your extension. It is up to you how you want to manage your tags and branches. + +### Example releasing flow using a git repo + +While there are many options for how you want to manage releases using a git flow, we recommend treating your default branch as your "stable" release branch. This means that the default behavior for `gemini extensions install ` is to be on the stable release branch. + +Lets say you want to maintain three standard release channels, `stable`, `preview`, and `dev`. You would do all your standard development in the `dev` branch. When you are ready to do a preview release, you merge that branch into your `preview` branch. When you are ready to promote your preview branch to stable, you merge `preview` into your stable branch (which might be your default branch or a different branch). + +You can also cherry pick changes from one branch into another using `git cherry-pick`, but do note that this will result in your branches having a slightly divergent history from each other, unless you force push changes to your branches on each release to restore the history to a clean slate (which may not be possible for the default branch depending on your repository settings). If you plan on doing cherry picks, you may want to avoid having your default branch be the stable branch to avoid force-pushing to the default branch which should generally be avoided. + +## Releasing through Github releases + +Gemini CLI extensions can be distributed through [GitHub Releases](https://docs.github.com/en/repositories/releasing-projects-on-github/about-releases). This provides a faster and more reliable initial installation experience for users, as it avoids the need to clone the repository. + +Each release includes at least one archive file, which contains the full contents of the repo at the tag that it was linked to. Releases may also include [pre-built archives](#custom-pre-built-archives) if your extension requires some build step or has platform specific binaries attached to it. + +When checking for updates, gemini will just look for the latest release on github (you must mark it as such when creating the release), unless the user installed a specific release by passing `--ref=`. We do not at this time support opting in to pre-release releases or semver. + +### Custom pre-built archives + +Custom archives must be attached directly to the github release as assets and must be fully self-contained. This means they should include the entire extension, see [archive structure](#archive-structure). + +If your extension is platform-independent, you can provide a single generic asset. In this case, there should be only one asset attached to the release. + +Custom archives may also be used if you want to develop your extension within a larger repository, you can build an archive which has a different layout from the repo itself (for instance it might just be an archive of a subdirectory containing the extension). + +#### Platform specific archives + +To ensure Gemini CLI can automatically find the correct release asset for each platform, you must follow this naming convention. The CLI will search for assets in the following order: 1. **Platform and Architecture-Specific:** `{platform}.{arch}.{name}.{extension}` 2. **Platform-Specific:** `{platform}.{name}.{extension}` @@ -27,13 +70,13 @@ To ensure Gemini CLI can automatically find the correct release asset for each p - `linux.x64.my-tool.tar.gz` - `win32.my-tool.zip` -If your extension is platform-independent, you can provide a single generic asset. In this case, there should be only one asset attached to the release. +#### Archive structure -## Archive structure +Archives must be fully contained extensions and have all the standard requirements - specifically the `gemini-extension.json` file must be at the root of the archive. -The `gemini-extension.json` file must be at the root of the archive. +The rest of the layout should look exactly the same as a typical extension, see [extensions.md](extension.md). -## Example GitHub Actions workflow +#### Example GitHub Actions workflow Here is an example of a GitHub Actions workflow that builds and releases a Gemini CLI extension for multiple platforms: From 336343962b2a03822063cb5308fdb615962e3865 Mon Sep 17 00:00:00 2001 From: Tommaso Sciortino Date: Fri, 19 Sep 2025 12:13:33 -0700 Subject: [PATCH 31/33] Add skip_github_release option to Manual Release. (#8932) --- .github/actions/publish-release/action.yml | 7 ++++++- .github/workflows/release-manual.yml | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/actions/publish-release/action.yml b/.github/actions/publish-release/action.yml index 2f90ba2b10e..1561db07f92 100644 --- a/.github/actions/publish-release/action.yml +++ b/.github/actions/publish-release/action.yml @@ -27,6 +27,11 @@ inputs: previous-tag: description: 'The previous tag to use for generating release notes.' required: true + skip-github-release: + description: 'Whether to skip creating a GitHub release.' + type: 'boolean' + required: false + default: false working-directory: description: 'The working directory to run the steps in.' required: false @@ -132,7 +137,7 @@ runs: - name: '🎉 Create GitHub Release' working-directory: '${{ inputs.working-directory }}' - if: "${{ inputs.dry-run == 'false' }}" + if: "${{ inputs.dry-run == 'false' && inputs.skip-github-release == 'false' }}" env: GITHUB_TOKEN: '${{ inputs.github-token }}' shell: 'bash' diff --git a/.github/workflows/release-manual.yml b/.github/workflows/release-manual.yml index 0b7a6321237..56e0f8c919f 100644 --- a/.github/workflows/release-manual.yml +++ b/.github/workflows/release-manual.yml @@ -31,6 +31,11 @@ on: required: false type: 'boolean' default: false + skip_github_release: + description: 'Select to skip creating a GitHub release and create a npm release only.' + required: false + type: 'boolean' + default: false jobs: release: @@ -80,3 +85,4 @@ jobs: github-token: '${{ secrets.GITHUB_TOKEN }}' dry-run: '${{ github.event.inputs.dry_run }}' previous-tag: '${{ steps.release_info.outputs.PREVIOUS_TAG }}' + skip-github-release: '${{ github.event.inputs.skip_github_release }}' From 35e2f88bd3eb913861e872b8ec0c6c3a04a7e907 Mon Sep 17 00:00:00 2001 From: Shreya Keshive Date: Fri, 19 Sep 2025 16:12:12 -0400 Subject: [PATCH 32/33] Add few more license file names to generate-notices script (#8939) --- packages/vscode-ide-companion/scripts/generate-notices.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/vscode-ide-companion/scripts/generate-notices.js b/packages/vscode-ide-companion/scripts/generate-notices.js index b6c3341fac2..06f735ca81b 100644 --- a/packages/vscode-ide-companion/scripts/generate-notices.js +++ b/packages/vscode-ide-companion/scripts/generate-notices.js @@ -50,6 +50,8 @@ async function getDependencyLicense(depName, depVersion) { 'LICENSE.md', 'LICENSE.txt', 'LICENSE-MIT.txt', + 'license.md', + 'license', ].filter(Boolean); let licenseFile; From a2e8e3f905e8542a44d4c21d404337c3492cc004 Mon Sep 17 00:00:00 2001 From: Anjali Sridhar Date: Sat, 20 Sep 2025 05:45:36 -0700 Subject: [PATCH 33/33] wip --- packages/cli/src/config/config.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index d0509fc5f79..13344ac18d0 100755 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -261,10 +261,6 @@ export async function parseArguments(settings: Settings): Promise { description: 'Enable screen reader mode for accessibility.', default: false, }) - .option('use-write-todos', { - type: 'boolean', - description: 'Enable the write_todos_list tool.', - }) .option('output-format', { alias: 'o', type: 'string',