feat(core): display declined confirmation code diff (#4440)
This commit is contained in:
parent
f650be2c3a
commit
73d5d988f5
|
@ -407,3 +407,123 @@ describe('convertToFunctionResponse', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('CoreToolScheduler edit cancellation', () => {
|
||||||
|
it('should preserve diff when an edit is cancelled', async () => {
|
||||||
|
class MockEditTool extends BaseTool<Record<string, unknown>, ToolResult> {
|
||||||
|
constructor() {
|
||||||
|
super(
|
||||||
|
'mockEditTool',
|
||||||
|
'mockEditTool',
|
||||||
|
'A mock edit tool',
|
||||||
|
Icon.Pencil,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async shouldConfirmExecute(
|
||||||
|
_params: Record<string, unknown>,
|
||||||
|
_abortSignal: AbortSignal,
|
||||||
|
): Promise<ToolCallConfirmationDetails | false> {
|
||||||
|
return {
|
||||||
|
type: 'edit',
|
||||||
|
title: 'Confirm Edit',
|
||||||
|
fileName: 'test.txt',
|
||||||
|
fileDiff:
|
||||||
|
'--- test.txt\n+++ test.txt\n@@ -1,1 +1,1 @@\n-old content\n+new content',
|
||||||
|
originalContent: 'old content',
|
||||||
|
newContent: 'new content',
|
||||||
|
onConfirm: async () => {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(
|
||||||
|
_params: Record<string, unknown>,
|
||||||
|
_abortSignal: AbortSignal,
|
||||||
|
): Promise<ToolResult> {
|
||||||
|
return {
|
||||||
|
llmContent: 'Edited successfully',
|
||||||
|
returnDisplay: 'Edited successfully',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockEditTool = new MockEditTool();
|
||||||
|
const toolRegistry = {
|
||||||
|
getTool: () => mockEditTool,
|
||||||
|
getFunctionDeclarations: () => [],
|
||||||
|
tools: new Map(),
|
||||||
|
discovery: {} as any,
|
||||||
|
registerTool: () => {},
|
||||||
|
getToolByName: () => mockEditTool,
|
||||||
|
getToolByDisplayName: () => mockEditTool,
|
||||||
|
getTools: () => [],
|
||||||
|
discoverTools: async () => {},
|
||||||
|
getAllTools: () => [],
|
||||||
|
getToolsByServer: () => [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const onAllToolCallsComplete = vi.fn();
|
||||||
|
const onToolCallsUpdate = vi.fn();
|
||||||
|
|
||||||
|
const mockConfig = {
|
||||||
|
getSessionId: () => 'test-session-id',
|
||||||
|
getUsageStatisticsEnabled: () => true,
|
||||||
|
getDebugMode: () => false,
|
||||||
|
} 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: 'mockEditTool',
|
||||||
|
args: {},
|
||||||
|
isClientInitiated: false,
|
||||||
|
prompt_id: 'prompt-id-1',
|
||||||
|
};
|
||||||
|
|
||||||
|
await scheduler.schedule([request], abortController.signal);
|
||||||
|
|
||||||
|
// Wait for the tool to reach awaiting_approval state
|
||||||
|
const awaitingCall = onToolCallsUpdate.mock.calls.find(
|
||||||
|
(call) => call[0][0].status === 'awaiting_approval',
|
||||||
|
)?.[0][0];
|
||||||
|
|
||||||
|
expect(awaitingCall).toBeDefined();
|
||||||
|
|
||||||
|
// Cancel the edit
|
||||||
|
const confirmationDetails = await mockEditTool.shouldConfirmExecute(
|
||||||
|
{},
|
||||||
|
abortController.signal,
|
||||||
|
);
|
||||||
|
if (confirmationDetails) {
|
||||||
|
await scheduler.handleConfirmationResponse(
|
||||||
|
'1',
|
||||||
|
confirmationDetails.onConfirm,
|
||||||
|
ToolConfirmationOutcome.Cancel,
|
||||||
|
abortController.signal,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(onAllToolCallsComplete).toHaveBeenCalled();
|
||||||
|
const completedCalls = onAllToolCallsComplete.mock
|
||||||
|
.calls[0][0] as ToolCall[];
|
||||||
|
|
||||||
|
expect(completedCalls[0].status).toBe('cancelled');
|
||||||
|
|
||||||
|
// Check that the diff is preserved
|
||||||
|
const cancelledCall = completedCalls[0] as any;
|
||||||
|
expect(cancelledCall.response.resultDisplay).toBeDefined();
|
||||||
|
expect(cancelledCall.response.resultDisplay.fileDiff).toBe(
|
||||||
|
'--- test.txt\n+++ test.txt\n@@ -1,1 +1,1 @@\n-old content\n+new content',
|
||||||
|
);
|
||||||
|
expect(cancelledCall.response.resultDisplay.fileName).toBe('test.txt');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
Tool,
|
Tool,
|
||||||
ToolCallConfirmationDetails,
|
ToolCallConfirmationDetails,
|
||||||
ToolResult,
|
ToolResult,
|
||||||
|
ToolResultDisplay,
|
||||||
ToolRegistry,
|
ToolRegistry,
|
||||||
ApprovalMode,
|
ApprovalMode,
|
||||||
EditorType,
|
EditorType,
|
||||||
|
@ -335,6 +336,22 @@ export class CoreToolScheduler {
|
||||||
const durationMs = existingStartTime
|
const durationMs = existingStartTime
|
||||||
? Date.now() - existingStartTime
|
? Date.now() - existingStartTime
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
// Preserve diff for cancelled edit operations
|
||||||
|
let resultDisplay: ToolResultDisplay | undefined = undefined;
|
||||||
|
if (currentCall.status === 'awaiting_approval') {
|
||||||
|
const waitingCall = currentCall as WaitingToolCall;
|
||||||
|
if (waitingCall.confirmationDetails.type === 'edit') {
|
||||||
|
resultDisplay = {
|
||||||
|
fileDiff: waitingCall.confirmationDetails.fileDiff,
|
||||||
|
fileName: waitingCall.confirmationDetails.fileName,
|
||||||
|
originalContent:
|
||||||
|
waitingCall.confirmationDetails.originalContent,
|
||||||
|
newContent: waitingCall.confirmationDetails.newContent,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
request: currentCall.request,
|
request: currentCall.request,
|
||||||
tool: toolInstance,
|
tool: toolInstance,
|
||||||
|
@ -350,7 +367,7 @@ export class CoreToolScheduler {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
resultDisplay: undefined,
|
resultDisplay,
|
||||||
error: undefined,
|
error: undefined,
|
||||||
},
|
},
|
||||||
durationMs,
|
durationMs,
|
||||||
|
|
Loading…
Reference in New Issue