mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-05-05 07:10:55 +00:00
Merge pull request #2763 from QwenLM/fix/2754-allow-webfetch-in-plan-mode
fix: allow web fetch approvals in plan mode
This commit is contained in:
commit
311f971ba7
4 changed files with 170 additions and 37 deletions
|
|
@ -2480,6 +2480,70 @@ describe('CoreToolScheduler plan mode with ask_user_question', () => {
|
|||
}
|
||||
});
|
||||
|
||||
it('should allow info confirmation tools in plan mode after approval', async () => {
|
||||
const onConfirmSpy = vi.fn().mockResolvedValue(undefined);
|
||||
const infoTool = new MockTool({
|
||||
name: 'web_fetch',
|
||||
getDefaultPermission: async () => 'ask',
|
||||
getConfirmationDetails: async () => ({
|
||||
type: 'info' as const,
|
||||
title: 'Confirm Web Fetch',
|
||||
prompt: 'Fetch https://example.com/docs',
|
||||
urls: ['https://example.com/docs'],
|
||||
onConfirm: onConfirmSpy,
|
||||
}),
|
||||
execute: async () => ({
|
||||
llmContent: 'Fetched docs',
|
||||
returnDisplay: 'Fetched docs',
|
||||
}),
|
||||
});
|
||||
const onAllToolCallsComplete = vi.fn();
|
||||
const onToolCallsUpdate = vi.fn();
|
||||
const scheduler = createPlanModeScheduler(
|
||||
infoTool,
|
||||
onAllToolCallsComplete,
|
||||
onToolCallsUpdate,
|
||||
);
|
||||
|
||||
const abortController = new AbortController();
|
||||
const request = {
|
||||
callId: '1',
|
||||
name: 'web_fetch',
|
||||
args: {
|
||||
url: 'https://example.com/docs',
|
||||
prompt: 'Summarize the API docs',
|
||||
},
|
||||
isClientInitiated: false,
|
||||
prompt_id: 'prompt-plan-info',
|
||||
};
|
||||
|
||||
await scheduler.schedule([request], abortController.signal);
|
||||
|
||||
const awaitingCall = (await waitForStatus(
|
||||
onToolCallsUpdate,
|
||||
'awaiting_approval',
|
||||
)) as WaitingToolCall;
|
||||
|
||||
expect(awaitingCall.confirmationDetails.type).toBe('info');
|
||||
|
||||
await awaitingCall.confirmationDetails.onConfirm(
|
||||
ToolConfirmationOutcome.ProceedOnce,
|
||||
);
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(onAllToolCallsComplete).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
expect(onConfirmSpy).toHaveBeenCalledWith(
|
||||
ToolConfirmationOutcome.ProceedOnce,
|
||||
undefined,
|
||||
);
|
||||
|
||||
const completedCalls = onAllToolCallsComplete.mock
|
||||
.calls[0][0] as ToolCall[];
|
||||
expect(completedCalls[0].status).toBe('success');
|
||||
});
|
||||
|
||||
it('should handle user cancellation of ask_user_question in plan mode', async () => {
|
||||
const mockTool = createAskUserQuestionMockTool();
|
||||
const onAllToolCallsComplete = vi.fn();
|
||||
|
|
|
|||
|
|
@ -903,6 +903,7 @@ export class CoreToolScheduler {
|
|||
// it must bypass both YOLO auto-approve and plan-mode blocking.
|
||||
const isAskUserQuestionTool =
|
||||
reqInfo.name === ToolNames.ASK_USER_QUESTION;
|
||||
let confirmationDetails: ToolCallConfirmationDetails | undefined;
|
||||
|
||||
if (approvalMode === ApprovalMode.YOLO && !isAskUserQuestionTool) {
|
||||
this.setToolCallOutcome(
|
||||
|
|
@ -910,30 +911,33 @@ export class CoreToolScheduler {
|
|||
ToolConfirmationOutcome.ProceedAlways,
|
||||
);
|
||||
this.setStatusInternal(reqInfo.callId, 'scheduled');
|
||||
} else if (
|
||||
isPlanMode &&
|
||||
!isExitPlanModeTool &&
|
||||
!isAskUserQuestionTool
|
||||
) {
|
||||
this.setStatusInternal(reqInfo.callId, 'error', {
|
||||
callId: reqInfo.callId,
|
||||
responseParts: convertToFunctionResponse(
|
||||
reqInfo.name,
|
||||
reqInfo.callId,
|
||||
getPlanModeSystemReminder(),
|
||||
),
|
||||
resultDisplay: 'Plan mode blocked a non-read-only tool call.',
|
||||
error: undefined,
|
||||
errorType: undefined,
|
||||
});
|
||||
} else {
|
||||
// Get confirmation details from the tool
|
||||
const confirmationDetails =
|
||||
confirmationDetails =
|
||||
await invocation.getConfirmationDetails(signal);
|
||||
|
||||
// ── Centralised rule injection ──────────────────────────────────
|
||||
injectPermissionRulesIfMissing(confirmationDetails, pmCtx);
|
||||
|
||||
if (
|
||||
isPlanMode &&
|
||||
!isExitPlanModeTool &&
|
||||
!isAskUserQuestionTool &&
|
||||
confirmationDetails.type !== 'info'
|
||||
) {
|
||||
this.setStatusInternal(reqInfo.callId, 'error', {
|
||||
callId: reqInfo.callId,
|
||||
responseParts: convertToFunctionResponse(
|
||||
reqInfo.name,
|
||||
reqInfo.callId,
|
||||
getPlanModeSystemReminder(),
|
||||
),
|
||||
resultDisplay: 'Plan mode blocked a non-read-only tool call.',
|
||||
error: undefined,
|
||||
errorType: undefined,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// AUTO_EDIT mode: auto-approve edit-like and info tools
|
||||
if (
|
||||
approvalMode === ApprovalMode.AUTO_EDIT &&
|
||||
|
|
@ -990,14 +994,14 @@ export class CoreToolScheduler {
|
|||
if (resolution.status === 'accepted') {
|
||||
this.handleConfirmationResponse(
|
||||
reqInfo.callId,
|
||||
confirmationDetails.onConfirm,
|
||||
confirmationDetails!.onConfirm,
|
||||
ToolConfirmationOutcome.ProceedOnce,
|
||||
signal,
|
||||
);
|
||||
} else {
|
||||
this.handleConfirmationResponse(
|
||||
reqInfo.callId,
|
||||
confirmationDetails.onConfirm,
|
||||
confirmationDetails!.onConfirm,
|
||||
ToolConfirmationOutcome.Cancel,
|
||||
signal,
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue