import { STATUS_TAG, SUMMARY_TAG, TASK_NOTIFICATION_TAG, } from '../constants/xml.js' import { BACKGROUND_BASH_SUMMARY_PREFIX } from '../tasks/LocalShellTask/LocalShellTask.js' import type { NormalizedUserMessage, RenderableMessage, } from '../types/message.js' import { isFullscreenEnvEnabled } from './fullscreen.js' import { extractTag } from './messages.js' function isCompletedBackgroundBash( msg: RenderableMessage, ): msg is NormalizedUserMessage { if (msg.type !== 'user') return false const content = msg.message.content[0] if (content?.type !== 'text') return false if (!content.text.includes(`<${TASK_NOTIFICATION_TAG}`)) return false // Only collapse successful completions — failed/killed stay visible individually. if (extractTag(content.text, STATUS_TAG) !== 'completed') return false // The prefix constant distinguishes bash-kind LocalShellTask completions from // agent/workflow/monitor notifications. Monitor-kind completions have their // own summary wording and deliberately don't collapse here. return ( extractTag(content.text, SUMMARY_TAG)?.startsWith( BACKGROUND_BASH_SUMMARY_PREFIX, ) ?? false ) } /** * Collapses consecutive completed-background-bash task-notifications into a * single synthetic "N background commands completed" notification. Failed/killed * tasks and agent/workflow notifications are left alone. Monitor stream * events (enqueueStreamEvent) have no tag and never match. * * Pass-through in verbose mode so ctrl+O shows each completion. */ export function collapseBackgroundBashNotifications( messages: RenderableMessage[], verbose: boolean, ): RenderableMessage[] { if (!isFullscreenEnvEnabled()) return messages if (verbose) return messages const result: RenderableMessage[] = [] let i = 0 while (i < messages.length) { const msg = messages[i]! if (isCompletedBackgroundBash(msg)) { let count = 0 while (i < messages.length && isCompletedBackgroundBash(messages[i]!)) { count++ i++ } if (count === 1) { result.push(msg) } else { // Synthesize a task-notification that UserAgentNotificationMessage // already knows how to render — no new renderer needed. result.push({ ...msg, message: { role: 'user', content: [ { type: 'text', text: `<${TASK_NOTIFICATION_TAG}><${STATUS_TAG}>completed<${SUMMARY_TAG}>${count} background commands completed`, }, ], }, }) } } else { result.push(msg) i++ } } return result }