@@ -2143,13 +2143,9 @@ export async function dispatchReplyFromConfig(
21432143 return ! reply . hasMedia && ! hasExecApprovalPayload ( payload ) ;
21442144 } ;
21452145 // Durable inter-tool commentary lane: with verbose progress on, preamble
2146- // items become standalone progress messages (like tool summaries) instead of
2147- // living only in ephemeral channel drafts. The latest text per item id is
2148- // buffered so producers that re-emit snapshots for the same item send one
2149- // message; the buffer flushes when the producer moves on (a different item,
2150- // a tool event, a block reply, or the final reply). The delivery helpers
2151- // reference gates declared later in this scope; they only run once the
2152- // resolver is executing, well after scope initialization completes.
2146+ // items become standalone progress messages like tool summaries. The latest
2147+ // text per item id is buffered (snapshot producers re-emit the same item)
2148+ // and flushed when the producer moves on, always before the final reply.
21532149 let pendingCommentaryProgress : { itemId ?: string ; text : string } | null = null ;
21542150 const deliverCommentaryProgressMessage = async ( text : string ) => {
21552151 if ( ! shouldSendToolSummaries ( ) || shouldSuppressProgressDelivery ( ) ) {
@@ -2217,8 +2213,7 @@ export async function dispatchReplyFromConfig(
22172213 }
22182214 } ;
22192215 throwIfFinalDeliveryAborted ( ) ;
2220- // Drain any trailing commentary before the final reply so the durable
2221- // progress lane completes ahead of the answer.
2216+ // Trailing commentary must land ahead of the final answer.
22222217 await flushPendingCommentaryProgress ( ) ;
22232218 throwIfFinalDeliveryAborted ( ) ;
22242219 const sourceReplyTranscriptMirror =
@@ -2663,31 +2658,34 @@ export async function dispatchReplyFromConfig(
26632658 // classification in the CLI runners is wired once at run start, so a
26642659 // mid-run verbose toggle cannot move inter-tool commentary between lanes.
26652660 const deliverStandaloneCommentaryProgress = shouldEmitVerboseProgress ( ) ;
2666- const coreOwnedOnItemEvent = ( ( ) => {
2667- const forwardItemEvent = wrapProgressCallback ( params . replyOptions ?. onItemEvent , {
2668- forwardWhenSourceDeliverySuppressed : true ,
2669- requiresToolSummaryVisibility : true ,
2670- waitForDirectBlockReplyDelivery : true ,
2671- onForward : ( payload ) => {
2672- if ( hasFailedProgressStatus ( payload ) ) {
2673- markVisibleToolErrorProgress ( ) ;
2674- }
2675- } ,
2676- } ) ;
2677- return async ( payload : Parameters < NonNullable < GetReplyOptions [ "onItemEvent" ] > > [ 0 ] ) => {
2678- if ( isDispatchOperationAborted ( ) ) {
2679- return ;
2661+ const forwardItemEvent = wrapProgressCallback ( params . replyOptions ?. onItemEvent , {
2662+ forwardWhenSourceDeliverySuppressed : true ,
2663+ requiresToolSummaryVisibility : true ,
2664+ waitForDirectBlockReplyDelivery : true ,
2665+ onForward : ( payload ) => {
2666+ if ( hasFailedProgressStatus ( payload ) ) {
2667+ markVisibleToolErrorProgress ( ) ;
26802668 }
2681- if ( ! forwardItemEvent ) {
2682- // The wrapped forwarder marks progress itself when present.
2683- markProgress ( ) ;
2684- }
2685- if ( payload . kind === "preamble" ) {
2686- await noteCommentaryProgress ( payload ) ;
2687- }
2688- await forwardItemEvent ?.( payload ) ;
2689- } ;
2690- } ) ( ) ;
2669+ } ,
2670+ } ) ;
2671+ // Item-event presence gates CLI commentary classification downstream, so
2672+ // the handler exists exactly when verbose buffers it or a channel consumes it.
2673+ const onItemEvent =
2674+ deliverStandaloneCommentaryProgress || forwardItemEvent
2675+ ? async ( payload : Parameters < NonNullable < GetReplyOptions [ "onItemEvent" ] > > [ 0 ] ) => {
2676+ if ( isDispatchOperationAborted ( ) ) {
2677+ return ;
2678+ }
2679+ if ( ! forwardItemEvent ) {
2680+ // The wrapped forwarder marks progress itself when present.
2681+ markProgress ( ) ;
2682+ }
2683+ if ( deliverStandaloneCommentaryProgress && payload . kind === "preamble" ) {
2684+ await noteCommentaryProgress ( payload ) ;
2685+ }
2686+ await forwardItemEvent ?.( payload ) ;
2687+ }
2688+ : undefined ;
26912689 // Let draft-rendering channels yield their ephemeral commentary lines while
26922690 // the durable verbose commentary lane is delivering the same content.
26932691 params . replyOptions ?. onVerboseProgressVisibility ?.(
@@ -2732,26 +2730,13 @@ export async function dispatchReplyFromConfig(
27322730 requiresToolSummaryVisibility : true ,
27332731 waitForDirectBlockReplyDelivery : true ,
27342732 onForward : async ( ) => {
2735- // A tool is starting: the preceding commentary block is final, so
2736- // flush it ahead of the tool's own progress rendering.
2733+ // Commentary precedes the tool that follows it.
27372734 await flushPendingCommentaryProgress ( ) ;
27382735 } ,
27392736 } ) ,
2740- onItemEvent : deliverStandaloneCommentaryProgress
2741- ? coreOwnedOnItemEvent
2742- : wrapProgressCallback ( params . replyOptions ?. onItemEvent , {
2743- forwardWhenSourceDeliverySuppressed : true ,
2744- requiresToolSummaryVisibility : true ,
2745- waitForDirectBlockReplyDelivery : true ,
2746- onForward : ( payload ) => {
2747- if ( hasFailedProgressStatus ( payload ) ) {
2748- markVisibleToolErrorProgress ( ) ;
2749- }
2750- } ,
2751- } ) ,
2752- commentaryProgressEnabled : deliverStandaloneCommentaryProgress
2753- ? true
2754- : params . replyOptions ?. commentaryProgressEnabled ,
2737+ onItemEvent,
2738+ commentaryProgressEnabled :
2739+ deliverStandaloneCommentaryProgress || params . replyOptions ?. commentaryProgressEnabled ,
27552740 onCommandOutput : wrapProgressCallback ( params . replyOptions ?. onCommandOutput , {
27562741 forwardWhenSourceDeliverySuppressed : true ,
27572742 requiresToolSummaryVisibility : true ,
@@ -2783,8 +2768,7 @@ export async function dispatchReplyFromConfig(
27832768 return ;
27842769 }
27852770 markInboundDedupeReplayUnsafe ( ) ;
2786- // The tool finished: any buffered commentary preceded this tool
2787- // call, so it must land before the tool summary.
2771+ // Buffered commentary preceded this tool; land it before the summary.
27882772 await flushPendingCommentaryProgress ( ) ;
27892773 if ( ! suppressAutomaticSourceDelivery && shouldSendToolSummaries ( ) ) {
27902774 await onToolResultFromReplyOptions ?.( payload ) ;
@@ -2943,8 +2927,7 @@ export async function dispatchReplyFromConfig(
29432927 ) {
29442928 markInboundDedupeReplayUnsafe ( ) ;
29452929 }
2946- // Assistant text is moving on: buffered commentary preceded this
2947- // block, so deliver it first to keep the lanes in order.
2930+ // Buffered commentary preceded this block; deliver it first.
29482931 await flushPendingCommentaryProgress ( ) ;
29492932 if ( suppressDelivery ) {
29502933 return ;
@@ -3097,8 +3080,8 @@ export async function dispatchReplyFromConfig(
30973080 }
30983081
30993082 const replies = replyResult ? ( Array . isArray ( replyResult ) ? replyResult : [ replyResult ] ) : [ ] ;
3100- // Backstop: a run can end without a visible final reply (silent or
3101- // streaming-delivered turns) ; trailing commentary must still land.
3083+ // Backstop: silent/streaming-delivered turns end without a visible final
3084+ // reply ; trailing commentary must still land.
31023085 await flushPendingCommentaryProgress ( ) ;
31033086 const beforeAgentRunBlocked = replies . some (
31043087 ( reply ) => getReplyPayloadMetadata ( reply ) ?. beforeAgentRunBlocked === true ,
0 commit comments