Merge remote-tracking branch 'origin/main' into fix/pr2371-btw-complete

# Conflicts:
#	packages/cli/src/ui/AppContainer.tsx
#	packages/cli/src/ui/hooks/useGeminiStream.ts
#	packages/cli/src/ui/layouts/DefaultAppLayout.tsx
#	packages/cli/src/ui/types.ts
#	packages/core/src/core/client.test.ts
This commit is contained in:
yiliang114 2026-03-20 00:55:29 +08:00
commit bd77eef46f
406 changed files with 55514 additions and 6431 deletions

View file

@ -19,14 +19,17 @@ import type {
} from '@qwen-code/qwen-code-core';
import {
GeminiEventType as ServerGeminiEventType,
SendMessageType,
createDebugLogger,
getErrorMessage,
isNodeError,
MessageSenderType,
logUserPrompt,
logUserRetry,
GitService,
UnauthorizedError,
UserPromptEvent,
UserRetryEvent,
logConversationFinishedEvent,
ConversationFinishedEvent,
ApprovalMode,
@ -431,6 +434,12 @@ export const useGeminiStream = (
isSubmittingQueryRef.current = false;
abortControllerRef.current?.abort();
// Report cancellation to arena status reporter (if in arena mode).
// This is needed because cancellation during tool execution won't
// flow through sendMessageStream where the inline reportCancelled()
// lives — tools get cancelled and handleCompletedTools returns early.
config.getArenaAgentClient()?.reportCancelled();
// Log API cancellation
const prompt_id = config.getSessionId() + '########' + getPromptCount();
const cancellationEvent = new ApiCancelEvent(
@ -1086,11 +1095,11 @@ export const useGeminiStream = (
const submitQuery = useCallback(
async (
query: PartListUnion,
options?: { isContinuation: boolean; skipPreparation?: boolean },
submitType: SendMessageType = SendMessageType.UserQuery,
prompt_id?: string,
) => {
const allowConcurrentBtwDuringResponse =
!options?.isContinuation &&
submitType === SendMessageType.UserQuery &&
streamingState === StreamingState.Responding &&
typeof query === 'string' &&
isBtwCommand(query);
@ -1099,7 +1108,7 @@ export const useGeminiStream = (
// which are part of the same logical flow (tool responses)
if (
isSubmittingQueryRef.current &&
!options?.isContinuation &&
submitType !== SendMessageType.ToolResult &&
!allowConcurrentBtwDuringResponse
) {
return;
@ -1108,7 +1117,7 @@ export const useGeminiStream = (
if (
(streamingState === StreamingState.Responding ||
streamingState === StreamingState.WaitingForConfirmation) &&
!options?.isContinuation &&
submitType !== SendMessageType.ToolResult &&
!allowConcurrentBtwDuringResponse
)
return;
@ -1119,7 +1128,10 @@ export const useGeminiStream = (
const userMessageTimestamp = Date.now();
// Reset quota error flag when starting a new query (not a continuation)
if (!options?.isContinuation && !allowConcurrentBtwDuringResponse) {
if (
submitType !== SendMessageType.ToolResult &&
!allowConcurrentBtwDuringResponse
) {
setModelSwitchedFromQuotaError(false);
// Commit any pending retry error to history (without hint) since the
// user is starting a new conversation turn.
@ -1148,14 +1160,15 @@ export const useGeminiStream = (
}
return promptIdContext.run(prompt_id, async () => {
const { queryToSend, shouldProceed } = options?.skipPreparation
? { queryToSend: query, shouldProceed: true }
: await prepareQueryForGemini(
query,
userMessageTimestamp,
abortSignal,
prompt_id!,
);
const { queryToSend, shouldProceed } =
submitType === SendMessageType.Retry
? { queryToSend: query, shouldProceed: true }
: await prepareQueryForGemini(
query,
userMessageTimestamp,
abortSignal,
prompt_id!,
);
if (!shouldProceed || queryToSend === null) {
isSubmittingQueryRef.current = false;
@ -1163,7 +1176,7 @@ export const useGeminiStream = (
}
// Check image format support for non-continuations
if (!options?.isContinuation) {
if (submitType === SendMessageType.UserQuery) {
const formatCheck = checkImageFormatsSupport(queryToSend);
if (formatCheck.hasUnsupportedFormats) {
addItem(
@ -1180,7 +1193,7 @@ export const useGeminiStream = (
lastPromptRef.current = finalQueryToSend;
lastPromptErroredRef.current = false;
if (!options?.isContinuation) {
if (submitType === SendMessageType.UserQuery) {
// trigger new prompt event for session stats in CLI
startNewPrompt();
@ -1201,6 +1214,10 @@ export const useGeminiStream = (
setThought(null);
}
if (submitType === SendMessageType.Retry) {
logUserRetry(config, new UserRetryEvent(prompt_id));
}
setIsResponding(true);
setInitError(null);
@ -1209,7 +1226,7 @@ export const useGeminiStream = (
finalQueryToSend,
abortSignal,
prompt_id!,
options,
{ type: submitType },
);
const processingStatus = await processGeminiStreamEvents(
@ -1297,7 +1314,7 @@ export const useGeminiStream = (
*
* When conditions are met:
* - Clears any pending auto-retry countdown to avoid duplicate retries
* - Re-submits the last query with skipPreparation: true for faster retry
* - Re-submits the last query with isRetry: true, reusing the same prompt_id
*
* This function is exposed via UIActionsContext and triggered by InputPrompt
* when the user presses Ctrl+Y (bound to Command.RETRY_LAST in keyBindings.ts).
@ -1324,10 +1341,7 @@ export const useGeminiStream = (
clearRetryCountdown();
await submitQuery(lastPrompt, {
isContinuation: false,
skipPreparation: true,
});
await submitQuery(lastPrompt, SendMessageType.Retry);
}, [streamingState, addItem, clearRetryCountdown, submitQuery]);
const handleApprovalModeChange = useCallback(
@ -1446,6 +1460,9 @@ export const useGeminiStream = (
role: 'user',
parts: combinedParts,
});
// Report cancellation to arena (safety net — cancelOngoingRequest
config.getArenaAgentClient()?.reportCancelled();
}
const callIdsToMarkAsSubmitted = geminiTools.map(
@ -1473,13 +1490,7 @@ export const useGeminiStream = (
return;
}
submitQuery(
responsesToSend,
{
isContinuation: true,
},
prompt_ids[0],
);
submitQuery(responsesToSend, SendMessageType.ToolResult, prompt_ids[0]);
},
[
isResponding,
@ -1488,6 +1499,7 @@ export const useGeminiStream = (
geminiClient,
performMemoryRefresh,
modelSwitchedFromQuotaError,
config,
],
);