mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-28 19:52:02 +00:00
Merge remote-tracking branch 'origin/main' into feature/status-line-customization
# Conflicts: # packages/cli/src/ui/components/Footer.tsx
This commit is contained in:
commit
51964fa4b9
49 changed files with 1414 additions and 2370 deletions
|
|
@ -143,6 +143,7 @@ describe('useShellCommandProcessor', () => {
|
|||
status: ToolCallStatus.Executing,
|
||||
}),
|
||||
],
|
||||
isUserInitiated: true,
|
||||
});
|
||||
const tmpFile = path.join(os.tmpdir(), 'shell_pwd_abcdef.tmp');
|
||||
const wrappedCommand = `{ ls -l; }; __code=$?; pwd > "${tmpFile}"; exit $__code`;
|
||||
|
|
|
|||
|
|
@ -131,6 +131,7 @@ export const useShellCommandProcessor = (
|
|||
setPendingHistoryItem({
|
||||
type: 'tool_group',
|
||||
tools: [initialToolDisplay],
|
||||
isUserInitiated: true,
|
||||
});
|
||||
|
||||
let executionPid: number | undefined;
|
||||
|
|
@ -304,6 +305,7 @@ export const useShellCommandProcessor = (
|
|||
{
|
||||
type: 'tool_group',
|
||||
tools: [finalToolDisplay],
|
||||
isUserInitiated: true,
|
||||
} as HistoryItemWithoutId,
|
||||
userMessageTimestamp,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -173,6 +173,7 @@ export const useGeminiStream = (
|
|||
setShellInputFocused: (value: boolean) => void,
|
||||
terminalWidth: number,
|
||||
terminalHeight: number,
|
||||
midTurnDrainRef?: React.RefObject<(() => string[]) | null>,
|
||||
) => {
|
||||
const [initError, setInitError] = useState<string | null>(null);
|
||||
const abortControllerRef = useRef<AbortController | null>(null);
|
||||
|
|
@ -1572,6 +1573,23 @@ export const useGeminiStream = (
|
|||
return;
|
||||
}
|
||||
|
||||
// Mid-turn queue drain: inject queued user messages alongside tool
|
||||
// results so the model sees them in the next API call.
|
||||
// Skip if the turn was cancelled — messages stay in queue for next turn.
|
||||
const drained =
|
||||
turnCancelledRef.current || abortControllerRef.current?.signal.aborted
|
||||
? []
|
||||
: (midTurnDrainRef?.current?.() ?? []);
|
||||
if (drained.length > 0) {
|
||||
for (const msg of drained) {
|
||||
responsesToSend.push({
|
||||
text: `\n[User message received during tool execution]: ${msg}`,
|
||||
});
|
||||
// Record in UI history so the transcript stays complete.
|
||||
addItem({ type: MessageType.USER, text: msg }, Date.now());
|
||||
}
|
||||
}
|
||||
|
||||
submitQuery(responsesToSend, SendMessageType.ToolResult, prompt_ids[0]);
|
||||
},
|
||||
[
|
||||
|
|
@ -1582,6 +1600,8 @@ export const useGeminiStream = (
|
|||
performMemoryRefresh,
|
||||
modelSwitchedFromQuotaError,
|
||||
config,
|
||||
midTurnDrainRef,
|
||||
addItem,
|
||||
],
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { StreamingState } from '../types.js';
|
||||
|
||||
export interface UseMessageQueueOptions {
|
||||
|
|
@ -18,6 +18,12 @@ export interface UseMessageQueueReturn {
|
|||
addMessage: (message: string) => void;
|
||||
clearQueue: () => void;
|
||||
getQueuedMessagesText: () => string;
|
||||
/**
|
||||
* Atomically drain all queued messages. Returns the drained messages
|
||||
* and clears both the synchronous ref and React state. Safe to call
|
||||
* from non-React contexts (e.g., tool completion callbacks).
|
||||
*/
|
||||
drainQueue: () => string[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -31,17 +37,22 @@ export function useMessageQueue({
|
|||
submitQuery,
|
||||
}: UseMessageQueueOptions): UseMessageQueueReturn {
|
||||
const [messageQueue, setMessageQueue] = useState<string[]>([]);
|
||||
// Synchronous ref mirrors React state so non-React callbacks (e.g.,
|
||||
// mid-turn drain in handleCompletedTools) always see the latest queue.
|
||||
const queueRef = useRef<string[]>([]);
|
||||
|
||||
// Add a message to the queue
|
||||
const addMessage = useCallback((message: string) => {
|
||||
const trimmedMessage = message.trim();
|
||||
if (trimmedMessage.length > 0) {
|
||||
setMessageQueue((prev) => [...prev, trimmedMessage]);
|
||||
queueRef.current = [...queueRef.current, trimmedMessage];
|
||||
setMessageQueue(queueRef.current);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Clear the entire queue
|
||||
const clearQueue = useCallback(() => {
|
||||
queueRef.current = [];
|
||||
setMessageQueue([]);
|
||||
}, []);
|
||||
|
||||
|
|
@ -51,6 +62,15 @@ export function useMessageQueue({
|
|||
return messageQueue.join('\n\n');
|
||||
}, [messageQueue]);
|
||||
|
||||
// Atomically drain all queued messages (synchronous, safe from callbacks).
|
||||
const drainQueue = useCallback((): string[] => {
|
||||
const drained = queueRef.current;
|
||||
if (drained.length === 0) return [];
|
||||
queueRef.current = [];
|
||||
setMessageQueue([]);
|
||||
return drained;
|
||||
}, []);
|
||||
|
||||
// Process queued messages when streaming becomes idle
|
||||
useEffect(() => {
|
||||
if (
|
||||
|
|
@ -61,15 +81,22 @@ export function useMessageQueue({
|
|||
// Combine all messages with double newlines for clarity
|
||||
const combinedMessage = messageQueue.join('\n\n');
|
||||
// Clear the queue and submit
|
||||
setMessageQueue([]);
|
||||
clearQueue();
|
||||
submitQuery(combinedMessage);
|
||||
}
|
||||
}, [isConfigInitialized, streamingState, messageQueue, submitQuery]);
|
||||
}, [
|
||||
isConfigInitialized,
|
||||
streamingState,
|
||||
messageQueue,
|
||||
submitQuery,
|
||||
clearQueue,
|
||||
]);
|
||||
|
||||
return {
|
||||
messageQueue,
|
||||
addMessage,
|
||||
clearQueue,
|
||||
getQueuedMessagesText,
|
||||
drainQueue,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue