diff --git a/src/vs/platform/agentHost/common/state/protocol/actions.ts b/src/vs/platform/agentHost/common/state/protocol/actions.ts index 9a9b6cfb8719a..2dcd27477588d 100644 --- a/src/vs/platform/agentHost/common/state/protocol/actions.ts +++ b/src/vs/platform/agentHost/common/state/protocol/actions.ts @@ -282,6 +282,8 @@ export interface ISessionToolCallApprovedAction extends IToolCallActionBase { approved: true; /** How the tool was confirmed */ confirmed: ToolCallConfirmationReason; + /** If the user edited the tool input before approving, this is the new input */ + userEditedInput?: string; } /** diff --git a/src/vs/platform/agentHost/common/state/protocol/reducers.ts b/src/vs/platform/agentHost/common/state/protocol/reducers.ts index 128074285b2e7..c3e84af7e2e39 100644 --- a/src/vs/platform/agentHost/common/state/protocol/reducers.ts +++ b/src/vs/platform/agentHost/common/state/protocol/reducers.ts @@ -344,7 +344,7 @@ export function sessionReducer(state: ISessionState, action: ISessionAction, log status: ToolCallStatus.Running, ...base, invocationMessage: tc.invocationMessage, - toolInput: tc.toolInput, + toolInput: action.userEditedInput ?? tc.toolInput, confirmed: action.confirmed, }; } diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostSessionHandler.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostSessionHandler.ts index 3dc1a2ef8abab..2744b07ed36bb 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostSessionHandler.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostSessionHandler.ts @@ -1029,6 +1029,10 @@ export class AgentHostSessionHandler extends Disposable implements IChatSessionC const approved = reason.type !== ToolConfirmKind.Denied && reason.type !== ToolConfirmKind.Skipped; this._logService.info(`[AgentHost] Tool confirmation: toolCallId=${toolCallId}, approved=${approved}`); if (approved) { + // Check if the user edited the command in the confirmation UI + const toolSpecificData = invocation.toolSpecificData; + const userEditedInput = toolSpecificData?.kind === 'terminal' ? toolSpecificData.commandLine.userEdited : undefined; + const confirmAction = { type: ActionType.SessionToolCallConfirmed as const, session: session.toString(), @@ -1036,6 +1040,7 @@ export class AgentHostSessionHandler extends Disposable implements IChatSessionC toolCallId, approved: true as const, confirmed: ToolCallConfirmationReason.UserAction, + ...(userEditedInput ? { userEditedInput } : {}), }; const seq = this._clientState.applyOptimistic(confirmAction); this._config.connection.dispatchAction(confirmAction, this._clientState.clientId, seq); diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalConfirmationTool.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalConfirmationTool.ts index 0e92d061c5139..02a3ed6dd9439 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalConfirmationTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalConfirmationTool.ts @@ -7,6 +7,7 @@ import { CancellationToken } from '../../../../../../base/common/cancellation.js import { Codicon } from '../../../../../../base/common/codicons.js'; import { localize } from '../../../../../../nls.js'; import { CountTokensCallback, IPreparedToolInvocation, IToolData, IToolInvocation, IToolInvocationPreparationContext, IToolResult, ToolDataSource, ToolInvocationPresentation, ToolProgress } from '../../../../chat/common/tools/languageModelToolsService.js'; +import { IChatTerminalToolInvocationData } from '../../../../chat/common/chatService/chatService.js'; import { RunInTerminalTool } from './runInTerminalTool.js'; import { TerminalToolId } from './toolIds.js'; @@ -76,7 +77,17 @@ export class ConfirmTerminalCommandTool extends RunInTerminalTool { return preparedInvocation; } override async invoke(invocation: IToolInvocation, countTokens: CountTokensCallback, progress: ToolProgress, token: CancellationToken): Promise { - // This is a confirmation-only tool - just return success + // This is a confirmation-only tool - return the user-edited command if they modified it + const toolSpecificData = invocation.toolSpecificData as IChatTerminalToolInvocationData | undefined; + const userEdited = toolSpecificData?.commandLine.userEdited; + if (userEdited && userEdited !== toolSpecificData?.commandLine.original) { + return { + content: [{ + kind: 'text', + value: `The user approved the command but edited it to: \`${userEdited}\`. Use this exact command when executing.` + }] + }; + } return { content: [{ kind: 'text', diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts index df9449835f71b..d4c6faa7f1a45 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts @@ -2030,6 +2030,55 @@ suite('RunInTerminalTool', () => { const result = await confirmTool.prepareToolInvocation(context, CancellationToken.None); assertConfirmationRequired(result); }); + + test('should return user-edited command when user edits the command', async () => { + sandboxEnabled = false; + setAutoApprove({}); + + const { ConfirmTerminalCommandTool } = await import('../../browser/tools/runInTerminalConfirmationTool.js'); + const confirmTool = store.add(instantiationService.createInstance(ConfirmTerminalCommandTool)); + + const toolSpecificData: IChatTerminalToolInvocationData = { + kind: 'terminal', + commandLine: { + original: 'git commit -m "original message"', + userEdited: 'git commit -m "edited message"', + }, + language: 'sh', + }; + + const invocation = { + toolSpecificData, + } as any; + + const result = await confirmTool.invoke(invocation, async () => 0, { report: () => { } }, CancellationToken.None); + const value = (result.content[0] as { kind: 'text'; value: string }).value; + ok(value.includes('edited message')); + ok(value.includes('git commit -m "edited message"')); + }); + + test('should return yes when user does not edit the command', async () => { + sandboxEnabled = false; + setAutoApprove({}); + + const { ConfirmTerminalCommandTool } = await import('../../browser/tools/runInTerminalConfirmationTool.js'); + const confirmTool = store.add(instantiationService.createInstance(ConfirmTerminalCommandTool)); + + const toolSpecificData: IChatTerminalToolInvocationData = { + kind: 'terminal', + commandLine: { + original: 'echo hello', + }, + language: 'sh', + }; + + const invocation = { + toolSpecificData, + } as any; + + const result = await confirmTool.invoke(invocation, async () => 0, { report: () => { } }, CancellationToken.None); + strictEqual((result.content[0] as { kind: 'text'; value: string }).value, 'yes'); + }); }); });