diff --git a/packages/cli/src/ui/hooks/useReactToolScheduler.ts b/packages/cli/src/ui/hooks/useReactToolScheduler.ts index 22988fef..307a90cf 100644 --- a/packages/cli/src/ui/hooks/useReactToolScheduler.ts +++ b/packages/cli/src/ui/hooks/useReactToolScheduler.ts @@ -138,7 +138,6 @@ export function useReactToolScheduler( outputUpdateHandler, onAllToolCallsComplete: allToolCallsCompleteHandler, onToolCallsUpdate: toolCallsUpdateHandler, - approvalMode: config.getApprovalMode(), getPreferredEditor, config, }), diff --git a/packages/core/src/core/coreToolScheduler.test.ts b/packages/core/src/core/coreToolScheduler.test.ts index 7b6a130c..80651a14 100644 --- a/packages/core/src/core/coreToolScheduler.test.ts +++ b/packages/core/src/core/coreToolScheduler.test.ts @@ -20,6 +20,7 @@ import { ToolResult, Config, Icon, + ApprovalMode, } from '../index.js'; import { Part, PartListUnion } from '@google/genai'; @@ -126,6 +127,7 @@ describe('CoreToolScheduler', () => { getSessionId: () => 'test-session-id', getUsageStatisticsEnabled: () => true, getDebugMode: () => false, + getApprovalMode: () => ApprovalMode.DEFAULT, } as unknown as Config; const scheduler = new CoreToolScheduler({ @@ -194,6 +196,7 @@ describe('CoreToolScheduler with payload', () => { getSessionId: () => 'test-session-id', getUsageStatisticsEnabled: () => true, getDebugMode: () => false, + getApprovalMode: () => ApprovalMode.DEFAULT, } as unknown as Config; const scheduler = new CoreToolScheduler({ @@ -470,6 +473,7 @@ describe('CoreToolScheduler edit cancellation', () => { getSessionId: () => 'test-session-id', getUsageStatisticsEnabled: () => true, getDebugMode: () => false, + getApprovalMode: () => ApprovalMode.DEFAULT, } as unknown as Config; const scheduler = new CoreToolScheduler({ @@ -527,3 +531,85 @@ describe('CoreToolScheduler edit cancellation', () => { expect(cancelledCall.response.resultDisplay.fileName).toBe('test.txt'); }); }); + +describe('CoreToolScheduler YOLO mode', () => { + it('should execute tool requiring confirmation directly without waiting', async () => { + // Arrange + const mockTool = new MockTool(); + // This tool would normally require confirmation. + mockTool.shouldConfirm = true; + + const toolRegistry = { + getTool: () => mockTool, + getToolByName: () => mockTool, + // Other properties are not needed for this test but are included for type consistency. + getFunctionDeclarations: () => [], + tools: new Map(), + discovery: {} as any, + registerTool: () => {}, + getToolByDisplayName: () => mockTool, + getTools: () => [], + discoverTools: async () => {}, + getAllTools: () => [], + getToolsByServer: () => [], + }; + + const onAllToolCallsComplete = vi.fn(); + const onToolCallsUpdate = vi.fn(); + + // Configure the scheduler for YOLO mode. + const mockConfig = { + getSessionId: () => 'test-session-id', + getUsageStatisticsEnabled: () => true, + getDebugMode: () => false, + getApprovalMode: () => ApprovalMode.YOLO, + } as unknown as Config; + + const scheduler = new CoreToolScheduler({ + config: mockConfig, + toolRegistry: Promise.resolve(toolRegistry as any), + onAllToolCallsComplete, + onToolCallsUpdate, + getPreferredEditor: () => 'vscode', + }); + + const abortController = new AbortController(); + const request = { + callId: '1', + name: 'mockTool', + args: { param: 'value' }, + isClientInitiated: false, + prompt_id: 'prompt-id-yolo', + }; + + // Act + await scheduler.schedule([request], abortController.signal); + + // Assert + // 1. The tool's execute method was called directly. + expect(mockTool.executeFn).toHaveBeenCalledWith({ param: 'value' }); + + // 2. The tool call status never entered 'awaiting_approval'. + const statusUpdates = onToolCallsUpdate.mock.calls + .map((call) => (call[0][0] as ToolCall)?.status) + .filter(Boolean); + expect(statusUpdates).not.toContain('awaiting_approval'); + expect(statusUpdates).toEqual([ + 'validating', + 'scheduled', + 'executing', + 'success', + ]); + + // 3. The final callback indicates the tool call was successful. + expect(onAllToolCallsComplete).toHaveBeenCalled(); + const completedCalls = onAllToolCallsComplete.mock + .calls[0][0] as ToolCall[]; + expect(completedCalls).toHaveLength(1); + const completedCall = completedCalls[0]; + expect(completedCall.status).toBe('success'); + if (completedCall.status === 'success') { + expect(completedCall.response.resultDisplay).toBe('Tool executed'); + } + }); +}); diff --git a/packages/core/src/core/coreToolScheduler.ts b/packages/core/src/core/coreToolScheduler.ts index 0d7d5923..af078faa 100644 --- a/packages/core/src/core/coreToolScheduler.ts +++ b/packages/core/src/core/coreToolScheduler.ts @@ -219,7 +219,6 @@ interface CoreToolSchedulerOptions { outputUpdateHandler?: OutputUpdateHandler; onAllToolCallsComplete?: AllToolCallsCompleteHandler; onToolCallsUpdate?: ToolCallsUpdateHandler; - approvalMode?: ApprovalMode; getPreferredEditor: () => EditorType | undefined; config: Config; } @@ -230,7 +229,6 @@ export class CoreToolScheduler { private outputUpdateHandler?: OutputUpdateHandler; private onAllToolCallsComplete?: AllToolCallsCompleteHandler; private onToolCallsUpdate?: ToolCallsUpdateHandler; - private approvalMode: ApprovalMode; private getPreferredEditor: () => EditorType | undefined; private config: Config; @@ -240,7 +238,6 @@ export class CoreToolScheduler { this.outputUpdateHandler = options.outputUpdateHandler; this.onAllToolCallsComplete = options.onAllToolCallsComplete; this.onToolCallsUpdate = options.onToolCallsUpdate; - this.approvalMode = options.approvalMode ?? ApprovalMode.DEFAULT; this.getPreferredEditor = options.getPreferredEditor; } @@ -462,7 +459,7 @@ export class CoreToolScheduler { const { request: reqInfo, tool: toolInstance } = toolCall; try { - if (this.approvalMode === ApprovalMode.YOLO) { + if (this.config.getApprovalMode() === ApprovalMode.YOLO) { this.setStatusInternal(reqInfo.callId, 'scheduled'); } else { const confirmationDetails = await toolInstance.shouldConfirmExecute(