mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-05-01 05:00:46 +00:00
refactor(background): replace prefix/separator protocol with typed notification queue
Replace the stringly-typed \x00__BG_NOTIFY__\x00 prefix/separator encoding with a typed notification path using SendMessageType.Notification. - Add SendMessageType.Notification to the enum - Change BackgroundNotificationCallback to emit (displayText, modelText) - Move notification queue from AppContainer into useGeminiStream (mirrors the cron queue pattern): register on registry, queue structured items, drain on idle via submitQuery - prepareQueryForGemini short-circuits for Notification type (skips slash commands, shell mode, @-commands, prompt history logging) - Remove BACKGROUND_NOTIFICATION_PREFIX/SEPARATOR constants
This commit is contained in:
parent
9f836e1f03
commit
f5067bc24e
5 changed files with 70 additions and 96 deletions
|
|
@ -41,8 +41,6 @@ import {
|
|||
ApiCancelEvent,
|
||||
isSupportedImageMimeType,
|
||||
getUnsupportedImageFormatWarning,
|
||||
BACKGROUND_NOTIFICATION_PREFIX,
|
||||
BACKGROUND_NOTIFICATION_SEPARATOR,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
import { type Part, type PartListUnion, FinishReason } from '@google/genai';
|
||||
import type {
|
||||
|
|
@ -487,6 +485,7 @@ export const useGeminiStream = (
|
|||
userMessageTimestamp: number,
|
||||
abortSignal: AbortSignal,
|
||||
prompt_id: string,
|
||||
submitType: SendMessageType,
|
||||
): Promise<{
|
||||
queryToSend: PartListUnion | null;
|
||||
shouldProceed: boolean;
|
||||
|
|
@ -503,42 +502,23 @@ export const useGeminiStream = (
|
|||
if (typeof query === 'string') {
|
||||
const trimmedQuery = query.trim();
|
||||
|
||||
// Background agent notifications carry a prefix so the UI can
|
||||
// render them differently from user-typed messages.
|
||||
const isNotification = trimmedQuery.startsWith(
|
||||
BACKGROUND_NOTIFICATION_PREFIX,
|
||||
);
|
||||
let displayText: string;
|
||||
let modelText: string;
|
||||
if (isNotification) {
|
||||
const body = trimmedQuery.slice(
|
||||
BACKGROUND_NOTIFICATION_PREFIX.length,
|
||||
// Notification messages (e.g. background agent completions) are
|
||||
// pre-processed by the notification drain loop which already
|
||||
// added the display item to history. Just pass the model text
|
||||
// through to the API.
|
||||
if (submitType === SendMessageType.Notification) {
|
||||
onDebugMessage(
|
||||
`Received notification (${trimmedQuery.length} chars)`,
|
||||
);
|
||||
const sepIdx = body.indexOf(BACKGROUND_NOTIFICATION_SEPARATOR);
|
||||
if (sepIdx !== -1) {
|
||||
displayText = body.slice(0, sepIdx);
|
||||
modelText = body.slice(
|
||||
sepIdx + BACKGROUND_NOTIFICATION_SEPARATOR.length,
|
||||
);
|
||||
} else {
|
||||
displayText = body;
|
||||
modelText = body;
|
||||
}
|
||||
} else {
|
||||
displayText = trimmedQuery;
|
||||
modelText = trimmedQuery;
|
||||
return { queryToSend: trimmedQuery, shouldProceed: true };
|
||||
}
|
||||
|
||||
onDebugMessage(`Received user query (${displayText.length} chars)`);
|
||||
// Don't log notifications as user messages — they pollute
|
||||
// the prompt history shown when pressing up-arrow.
|
||||
if (!isNotification) {
|
||||
await logger?.logMessage(MessageSenderType.USER, displayText);
|
||||
}
|
||||
onDebugMessage(`Received user query (${trimmedQuery.length} chars)`);
|
||||
await logger?.logMessage(MessageSenderType.USER, trimmedQuery);
|
||||
|
||||
// Handle UI-only commands first
|
||||
const slashCommandResult = isSlashCommand(displayText)
|
||||
? await handleSlashCommand(displayText)
|
||||
const slashCommandResult = isSlashCommand(trimmedQuery)
|
||||
? await handleSlashCommand(trimmedQuery)
|
||||
: false;
|
||||
|
||||
if (slashCommandResult) {
|
||||
|
|
@ -575,26 +555,21 @@ export const useGeminiStream = (
|
|||
}
|
||||
}
|
||||
|
||||
if (shellModeActive && handleShellCommand(displayText, abortSignal)) {
|
||||
if (shellModeActive && handleShellCommand(trimmedQuery, abortSignal)) {
|
||||
return { queryToSend: null, shouldProceed: false };
|
||||
}
|
||||
|
||||
localQueryToSendToGemini = modelText;
|
||||
localQueryToSendToGemini = trimmedQuery;
|
||||
|
||||
addItem(
|
||||
isNotification
|
||||
? {
|
||||
type: 'notification' as const,
|
||||
text: displayText,
|
||||
}
|
||||
: { type: MessageType.USER, text: modelText },
|
||||
{ type: MessageType.USER, text: trimmedQuery },
|
||||
userMessageTimestamp,
|
||||
);
|
||||
|
||||
// Handle @-commands (which might involve tool calls)
|
||||
if (isAtCommand(modelText)) {
|
||||
if (isAtCommand(trimmedQuery)) {
|
||||
const atCommandResult = await handleAtCommand({
|
||||
query: modelText,
|
||||
query: trimmedQuery,
|
||||
config,
|
||||
onDebugMessage,
|
||||
messageId: userMessageTimestamp,
|
||||
|
|
@ -1274,6 +1249,7 @@ export const useGeminiStream = (
|
|||
userMessageTimestamp,
|
||||
abortSignal,
|
||||
prompt_id!,
|
||||
submitType,
|
||||
);
|
||||
|
||||
if (!shouldProceed || queryToSend === null) {
|
||||
|
|
@ -1810,6 +1786,40 @@ export const useGeminiStream = (
|
|||
}
|
||||
}, [streamingState, submitQuery, cronTrigger]);
|
||||
|
||||
// ─── Background agent notification queue ───────────────────
|
||||
const notificationQueueRef = useRef<
|
||||
Array<{ displayText: string; modelText: string }>
|
||||
>([]);
|
||||
const [notificationTrigger, setNotificationTrigger] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const registry = config.getBackgroundTaskRegistry();
|
||||
registry.setNotificationCallback(
|
||||
(displayText: string, modelText: string) => {
|
||||
notificationQueueRef.current.push({ displayText, modelText });
|
||||
setNotificationTrigger((n) => n + 1);
|
||||
},
|
||||
);
|
||||
return () => {
|
||||
registry.setNotificationCallback(() => {});
|
||||
};
|
||||
}, [config]);
|
||||
|
||||
// When idle, drain the notification queue one item at a time
|
||||
useEffect(() => {
|
||||
if (
|
||||
streamingState === StreamingState.Idle &&
|
||||
notificationQueueRef.current.length > 0
|
||||
) {
|
||||
const item = notificationQueueRef.current.shift()!;
|
||||
addItem(
|
||||
{ type: 'notification' as const, text: item.displayText },
|
||||
Date.now(),
|
||||
);
|
||||
submitQuery(item.modelText, SendMessageType.Notification);
|
||||
}
|
||||
}, [streamingState, submitQuery, notificationTrigger, addItem]);
|
||||
|
||||
return {
|
||||
streamingState,
|
||||
submitQuery,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue