@@ -289,6 +289,10 @@ function hiddenTextBlock(text: string): ContentBlock {
289289 } as ContentBlock ;
290290}
291291
292+ function isManualCompactPrompt ( prompt : ContentBlock [ ] ) : boolean {
293+ return / ^ \/ c o m p a c t (?: \s | $ ) / . test ( promptBlocksToText ( prompt ) . trimStart ( ) ) ;
294+ }
295+
292296interface LocalSkillPromptContext {
293297 /** Set when the message is a bare `/skill` invocation the adapter should strip. */
294298 skillName ?: string ;
@@ -339,6 +343,7 @@ export class AgentServer {
339343 private rtkSavingsAttempted = false ;
340344 private questionRelayedToSlack = false ;
341345 private adapterEmittedTurnComplete = false ;
346+ private suppressAdapterTurnComplete = false ;
342347 private runUsage = new RunUsageAccumulator ( ) ;
343348 private detectedPrUrl : string | null = null ;
344349 // Reset per session. `evaluatedPrUrls` dedupes per URL; `prAttributionChain` serializes
@@ -364,6 +369,7 @@ export class AgentServer {
364369 private initializationPromise : Promise < void > | null = null ;
365370 private pendingEvents : Record < string , unknown > [ ] = [ ] ;
366371 private deliveredMessageIds = new Set < string > ( ) ;
372+ private pendingCompactContinuationMessageIds = new Set < string > ( ) ;
367373 private pendingPermissions = new Map <
368374 string ,
369375 {
@@ -909,18 +915,28 @@ export class AgentServer {
909915 typeof params . messageId === "string" && params . messageId
910916 ? params . messageId
911917 : undefined ;
918+ let retryCompactContinuation = false ;
912919 if ( messageId ) {
913920 if ( this . deliveredMessageIds . has ( messageId ) ) {
914- this . logger . info ( "Duplicate user_message delivery ignored" , {
915- messageId,
916- } ) ;
917- return { stopReason : "duplicate_delivery" , duplicate : true } ;
921+ if ( this . pendingCompactContinuationMessageIds . has ( messageId ) ) {
922+ retryCompactContinuation = true ;
923+ this . logger . info ( "Retrying pending compact continuation" , {
924+ messageId,
925+ } ) ;
926+ } else {
927+ this . logger . info ( "Duplicate user_message delivery ignored" , {
928+ messageId,
929+ } ) ;
930+ return { stopReason : "duplicate_delivery" , duplicate : true } ;
931+ }
932+ } else {
933+ this . deliveredMessageIds . add ( messageId ) ;
918934 }
919- this . deliveredMessageIds . add ( messageId ) ;
920935 if ( this . deliveredMessageIds . size > 500 ) {
921936 const oldest = this . deliveredMessageIds . values ( ) . next ( ) . value ;
922937 if ( oldest !== undefined ) {
923938 this . deliveredMessageIds . delete ( oldest ) ;
939+ this . pendingCompactContinuationMessageIds . delete ( oldest ) ;
924940 }
925941 }
926942 }
@@ -951,17 +967,53 @@ export class AgentServer {
951967 : { } ) ,
952968 } ;
953969
970+ const manualCompactPrompt = isManualCompactPrompt ( prompt ) ;
971+ const acpSessionId = this . session . acpSessionId ;
972+ const continueAfterCompaction = ( ) : Promise < PromptResponse > =>
973+ this . promptWithUpstreamRetry ( {
974+ sessionId : acpSessionId ,
975+ prompt : [
976+ hiddenTextBlock (
977+ "Compaction is complete. Continue working on the task from the compacted context, following the user's instructions from the /compact command." ,
978+ ) ,
979+ ] ,
980+ } ) ;
981+
982+ let compactCommandCompleted = retryCompactContinuation ;
954983 let result : PromptResponse ;
984+ this . suppressAdapterTurnComplete =
985+ manualCompactPrompt || retryCompactContinuation ;
955986 try {
956- result = await this . session . clientConnection . prompt ( {
957- sessionId : this . session . acpSessionId ,
958- prompt,
959- ...( Object . keys ( promptMeta ) . length > 0
960- ? { _meta : promptMeta }
961- : { } ) ,
962- } ) ;
987+ if ( retryCompactContinuation ) {
988+ result = await continueAfterCompaction ( ) ;
989+ if ( messageId ) {
990+ this . pendingCompactContinuationMessageIds . delete ( messageId ) ;
991+ }
992+ } else {
993+ result = await this . session . clientConnection . prompt ( {
994+ sessionId : this . session . acpSessionId ,
995+ prompt,
996+ ...( Object . keys ( promptMeta ) . length > 0
997+ ? { _meta : promptMeta }
998+ : { } ) ,
999+ } ) ;
1000+
1001+ if ( result . stopReason === "end_turn" && manualCompactPrompt ) {
1002+ compactCommandCompleted = true ;
1003+ if ( messageId ) {
1004+ this . pendingCompactContinuationMessageIds . add ( messageId ) ;
1005+ }
1006+ // `/compact` is an SDK-local command, so without a follow-up the
1007+ // cloud run reports completion before the model resumes the task.
1008+ this . recordTurnUsage ( result . usage ) ;
1009+ result = await continueAfterCompaction ( ) ;
1010+ if ( messageId ) {
1011+ this . pendingCompactContinuationMessageIds . delete ( messageId ) ;
1012+ }
1013+ }
1014+ }
9631015 } catch ( error ) {
964- if ( messageId ) {
1016+ if ( messageId && ! compactCommandCompleted ) {
9651017 this . deliveredMessageIds . delete ( messageId ) ;
9661018 }
9671019 await this . session . logWriter . flushAll ( ) ;
@@ -974,6 +1026,8 @@ export class AgentServer {
9741026 throw error ;
9751027 }
9761028 return { stopReason : "error_recoverable" } ;
1029+ } finally {
1030+ this . suppressAdapterTurnComplete = false ;
9771031 }
9781032
9791033 this . logger . debug ( "User message completed" , {
@@ -1299,16 +1353,8 @@ export class AgentServer {
12991353
13001354 // Tap both streams to broadcast all ACP messages via SSE (mimics local transport)
13011355 this . adapterEmittedTurnComplete = false ;
1302- const onAcpMessage = ( message : unknown ) => {
1303- if ( isTurnCompleteNotification ( message ) ) {
1304- this . adapterEmittedTurnComplete = true ;
1305- }
1306- this . broadcastEvent ( {
1307- type : "notification" ,
1308- timestamp : new Date ( ) . toISOString ( ) ,
1309- notification : message ,
1310- } ) ;
1311- } ;
1356+ const onAcpMessage = ( message : unknown ) =>
1357+ this . handleAcpTransportMessage ( message ) ;
13121358
13131359 const tappedReadable = createTappedReadableStream (
13141360 acpConnection . clientStreams . readable as ReadableStream < Uint8Array > ,
@@ -4045,6 +4091,20 @@ ${signedCommitInstructions}${prLinkInstructions}${shellEfficiencyInstructions}
40454091 } ) ;
40464092 }
40474093
4094+ private handleAcpTransportMessage ( message : unknown ) : void {
4095+ if ( isTurnCompleteNotification ( message ) ) {
4096+ if ( this . suppressAdapterTurnComplete ) {
4097+ return ;
4098+ }
4099+ this . adapterEmittedTurnComplete = true ;
4100+ }
4101+ this . broadcastEvent ( {
4102+ type : "notification" ,
4103+ timestamp : new Date ( ) . toISOString ( ) ,
4104+ notification : message ,
4105+ } ) ;
4106+ }
4107+
40484108 private broadcastTurnComplete ( stopReason : string ) : void {
40494109 if ( ! this . session ) return ;
40504110 if ( this . adapterEmittedTurnComplete ) {
0 commit comments