@@ -109,10 +109,12 @@ import {
109109} from "./provider-request-error-classifier.js" ;
110110import type { FollowupRun } from "./queue.js" ;
111111import { createBlockReplyDeliveryHandler } from "./reply-delivery.js" ;
112+ import type { ReplyDispatchKind } from "./reply-dispatcher.types.js" ;
112113import type { ReplyMediaContext } from "./reply-media-paths.js" ;
113114import { createReplyMediaContext } from "./reply-media-paths.runtime.js" ;
114115import type { ReplyOperation } from "./reply-run-registry.js" ;
115116import { isReplyProfilerEnabled } from "./reply-timing-tracker.js" ;
117+ import { isRoutableChannel , routeReply } from "./route-reply.runtime.js" ;
116118import type { TypingSignaler } from "./typing-mode.js" ;
117119
118120// Maximum number of LiveSessionModelSwitchError retries before surfacing a
@@ -1564,9 +1566,6 @@ export async function runAgentTurnWithFallback(params: {
15641566 const shouldNotifyUserAboutCompaction =
15651567 runtimeConfig ?. agents ?. defaults ?. compaction ?. notifyUser === true ;
15661568 const sendCompactionNotice = async ( phase : "start" | "end" | "incomplete" ) => {
1567- if ( ! params . opts ?. onBlockReply ) {
1568- return ;
1569- }
15701569 const text =
15711570 phase === "start"
15721571 ? "🧹 Compacting context..."
@@ -1579,12 +1578,51 @@ export async function runAgentTurnWithFallback(params: {
15791578 replyToCurrent : true ,
15801579 isCompactionNotice : true ,
15811580 } ) ;
1581+
1582+ // Primary path: deliver through the active turn's streaming callback.
1583+ if ( params . opts ?. onBlockReply ) {
1584+ try {
1585+ await params . opts . onBlockReply ( noticePayload ) ;
1586+ } catch ( err ) {
1587+ logVerbose ( `compaction ${ phase } notice delivery failed (non-fatal): ${ String ( err ) } ` ) ;
1588+ }
1589+ return ;
1590+ }
1591+
1592+ // Fallback path: deliver through the canonical session outbound path when
1593+ // no active streaming turn is available (e.g. async / preflight compactions).
1594+ // Only send when notifyUser is enabled and the channel is routable.
1595+ if ( ! shouldNotifyUserAboutCompaction ) {
1596+ return ;
1597+ }
1598+ const channel = params . sessionCtx . Surface ?? params . sessionCtx . Provider ;
1599+ if ( ! channel || ! isRoutableChannel ( channel ) ) {
1600+ return ;
1601+ }
1602+ const to = params . sessionCtx . OriginatingTo ?? params . sessionCtx . To ;
1603+ if ( ! to ) {
1604+ return ;
1605+ }
1606+
15821607 try {
1583- await params . opts . onBlockReply ( noticePayload ) ;
1608+ const replyKind : ReplyDispatchKind = "block" ;
1609+ await routeReply ( {
1610+ payload : noticePayload ,
1611+ channel,
1612+ to,
1613+ sessionKey : params . sessionKey ,
1614+ accountId : params . sessionCtx . AccountId ,
1615+ requesterSenderId : params . sessionCtx . SenderId ,
1616+ requesterSenderName : params . sessionCtx . SenderName ,
1617+ requesterSenderUsername : params . sessionCtx . SenderUsername ,
1618+ requesterSenderE164 : params . sessionCtx . SenderE164 ,
1619+ threadId : params . followupRun . originatingThreadId ?? params . sessionCtx . MessageThreadId ,
1620+ cfg : runtimeConfig ,
1621+ replyKind,
1622+ runId : params . followupRun . run . runId ,
1623+ } ) ;
15841624 } catch ( err ) {
1585- // Non-critical notice delivery failure should not bubble out of the
1586- // fire-and-forget event handler.
1587- logVerbose ( `compaction ${ phase } notice delivery failed (non-fatal): ${ String ( err ) } ` ) ;
1625+ logVerbose ( `compaction ${ phase } notice route-reply failed (non-fatal): ${ String ( err ) } ` ) ;
15881626 }
15891627 } ;
15901628 const readCompactionHookMessages = ( value : unknown ) : string [ ] => {
0 commit comments