diff --git a/packages/cli/src/ui/hooks/useGeminiStream.ts b/packages/cli/src/ui/hooks/useGeminiStream.ts index abff9283b..43ea32213 100644 --- a/packages/cli/src/ui/hooks/useGeminiStream.ts +++ b/packages/cli/src/ui/hooks/useGeminiStream.ts @@ -1178,6 +1178,7 @@ export const useGeminiStream = ( query: PartListUnion, submitType: SendMessageType = SendMessageType.UserQuery, prompt_id?: string, + metadata?: { notificationDisplayText?: string }, ) => { const allowConcurrentBtwDuringResponse = submitType === SendMessageType.UserQuery && @@ -1314,7 +1315,10 @@ export const useGeminiStream = ( finalQueryToSend, abortSignal, prompt_id!, - { type: submitType }, + { + type: submitType, + notificationDisplayText: metadata?.notificationDisplayText, + }, ); const processingStatus = await processGeminiStreamEvents( @@ -1816,7 +1820,9 @@ export const useGeminiStream = ( { type: 'notification' as const, text: item.displayText }, Date.now(), ); - submitQuery(item.modelText, SendMessageType.Notification); + submitQuery(item.modelText, SendMessageType.Notification, undefined, { + notificationDisplayText: item.displayText, + }); } }, [streamingState, submitQuery, notificationTrigger, addItem]); diff --git a/packages/cli/src/ui/utils/resumeHistoryUtils.ts b/packages/cli/src/ui/utils/resumeHistoryUtils.ts index aa30a0489..f8ee52017 100644 --- a/packages/cli/src/ui/utils/resumeHistoryUtils.ts +++ b/packages/cli/src/ui/utils/resumeHistoryUtils.ts @@ -256,6 +256,18 @@ function convertToHistoryItems( } switch (record.type) { case 'user': { + // Restore notification items (background agent completions) + if (record.subtype === 'notification') { + const payload = record.systemPayload as + | { displayText?: string } + | undefined; + const text = + payload?.displayText || + extractTextFromParts(record.message?.parts as Part[]) || + 'Background agent completed'; + items.push({ type: 'notification', text }); + break; + } if (pendingAtCommands.length > 0) { // Flush any pending tool group before user message if (currentToolGroup.length > 0) { diff --git a/packages/core/src/core/client.ts b/packages/core/src/core/client.ts index 63a1a62df..14e5b38f5 100644 --- a/packages/core/src/core/client.ts +++ b/packages/core/src/core/client.ts @@ -111,6 +111,8 @@ export interface SendMessageOptions { iterationCount: number; reasons: string[]; }; + /** Display text for notification messages (persisted for session resume). */ + notificationDisplayText?: string; } export class GeminiClient { @@ -551,6 +553,12 @@ export class GeminiClient { } } + if (messageType === SendMessageType.Notification) { + this.config + .getChatRecordingService() + ?.recordNotification(request, options?.notificationDisplayText); + } + if ( messageType === SendMessageType.UserQuery || messageType === SendMessageType.Cron diff --git a/packages/core/src/services/chatRecordingService.ts b/packages/core/src/services/chatRecordingService.ts index 0e021fa4c..c81541c03 100644 --- a/packages/core/src/services/chatRecordingService.ts +++ b/packages/core/src/services/chatRecordingService.ts @@ -57,7 +57,8 @@ export interface ChatRecord { | 'chat_compression' | 'slash_command' | 'ui_telemetry' - | 'at_command'; + | 'at_command' + | 'notification'; /** Working directory at time of message */ cwd: string; /** CLI version for compatibility tracking */ @@ -97,7 +98,12 @@ export interface ChatRecord { | ChatCompressionRecordPayload | SlashCommandRecordPayload | UiTelemetryRecordPayload - | AtCommandRecordPayload; + | AtCommandRecordPayload + | NotificationRecordPayload; +} + +export interface NotificationRecordPayload { + displayText: string; } /** @@ -294,6 +300,27 @@ export class ChatRecordingService { } } + /** + * Records a background agent notification. + * Stored as a user-role message (so the API history includes it on resume) + * with subtype 'notification' (so the UI can restore it as an info item). + */ + recordNotification(message: PartListUnion, displayText?: string): void { + try { + const record: ChatRecord = { + ...this.createBaseRecord('user'), + subtype: 'notification', + message: createUserContent(message), + systemPayload: displayText + ? ({ displayText } as NotificationRecordPayload) + : undefined, + }; + this.appendRecord(record); + } catch (error) { + debugLogger.error('Error saving notification:', error); + } + } + /** * Records an assistant turn with all available data. * Writes immediately to disk.