@@ -33,6 +33,7 @@ import {CodexCommands} from "./CodexCommands";
3333import type { QuotaMeta } from "./QuotaMeta" ;
3434import { logger } from "./Logger" ;
3535import { sanitizeMcpServerName } from "./McpServerName" ;
36+ import { createResponseItemHistoryFallbackUpdates } from "./ResponseItemHistoryFallback" ;
3637import {
3738 type LegacyLoadSessionResponse ,
3839 type LegacyNewSessionResponse ,
@@ -44,6 +45,7 @@ import {
4445 LEGACY_SET_SESSION_MODEL_METHOD ,
4546} from "./AcpExtensions" ;
4647import {
48+ createCommandExecutionCompleteUpdate ,
4749 createCommandExecutionUpdate ,
4850 createDynamicToolCallUpdate ,
4951 createFileChangeUpdate ,
@@ -853,17 +855,29 @@ export class CodexAcpServer {
853855
854856 private async streamThreadHistory ( sessionId : string , thread : Thread ) : Promise < void > {
855857 const session = new ACPSessionConnection ( this . connection , sessionId ) ;
858+ const sessionState = this . getSessionState ( sessionId ) ;
859+ const responseItemFallbackUpdates = await createResponseItemHistoryFallbackUpdates (
860+ thread ,
861+ sessionState . terminalOutputMode ,
862+ ) ;
863+
864+ const threadUpdates : UpdateSessionEvent [ ] = [ ] ;
856865 for ( const turn of thread . turns ) {
857866 for ( const item of turn . items ) {
858- const updates = await this . createHistoryUpdates ( item ) ;
859- for ( const update of updates ) {
860- await session . update ( update ) ;
861- }
867+ const updates = await this . createHistoryUpdates ( item , sessionState ) ;
868+ threadUpdates . push ( ...updates ) ;
862869 }
863870 }
871+
872+ const updates = responseItemFallbackUpdates
873+ ? mergeHistoryUpdates ( responseItemFallbackUpdates , threadUpdates )
874+ : threadUpdates ;
875+ for ( const update of updates ) {
876+ await session . update ( update ) ;
877+ }
864878 }
865879
866- private async createHistoryUpdates ( item : ThreadItem ) : Promise < UpdateSessionEvent [ ] > {
880+ private async createHistoryUpdates ( item : ThreadItem , sessionState : SessionState ) : Promise < UpdateSessionEvent [ ] > {
867881 switch ( item . type ) {
868882 case "userMessage" :
869883 return this . createUserMessageUpdates ( item ) ;
@@ -880,8 +894,14 @@ export class CodexAcpServer {
880894 return this . createReasoningUpdates ( item ) ;
881895 case "fileChange" :
882896 return [ await createFileChangeUpdate ( item ) ] ;
883- case "commandExecution" :
884- return [ await createCommandExecutionUpdate ( item ) ] ;
897+ case "commandExecution" : {
898+ const updates = [ await createCommandExecutionUpdate ( item ) ] ;
899+ const completeUpdate = createCommandExecutionCompleteUpdate ( item , sessionState . terminalOutputMode ) ;
900+ if ( completeUpdate ) {
901+ updates . push ( completeUpdate ) ;
902+ }
903+ return updates ;
904+ }
885905 case "mcpToolCall" :
886906 return [ await createMcpToolCallUpdate ( item ) ] ;
887907 case "dynamicToolCall" :
@@ -1481,6 +1501,71 @@ export class CodexAcpServer {
14811501 }
14821502}
14831503
1504+ function mergeHistoryUpdates (
1505+ responseItemFallbackUpdates : UpdateSessionEvent [ ] ,
1506+ threadUpdates : UpdateSessionEvent [ ] ,
1507+ ) : UpdateSessionEvent [ ] {
1508+ const merged : UpdateSessionEvent [ ] = [ ] ;
1509+ const seen = new Set < string > ( ) ;
1510+ let fallbackIndex = 0 ;
1511+
1512+ const pushUpdate = ( update : UpdateSessionEvent ) => {
1513+ const key = historyUpdateKey ( update ) ;
1514+ if ( key && seen . has ( key ) ) {
1515+ return ;
1516+ }
1517+ if ( key ) {
1518+ seen . add ( key ) ;
1519+ }
1520+ merged . push ( update ) ;
1521+ } ;
1522+
1523+ const flushFallbackThrough = ( targetKey : string ) : boolean => {
1524+ const matchIndex = responseItemFallbackUpdates . findIndex ( ( update , index ) => (
1525+ index >= fallbackIndex && historyUpdateKey ( update ) === targetKey
1526+ ) ) ;
1527+ if ( matchIndex === - 1 ) {
1528+ return false ;
1529+ }
1530+
1531+ while ( fallbackIndex <= matchIndex ) {
1532+ pushUpdate ( responseItemFallbackUpdates [ fallbackIndex ] ! ) ;
1533+ fallbackIndex += 1 ;
1534+ }
1535+ return true ;
1536+ } ;
1537+
1538+ for ( const update of threadUpdates ) {
1539+ const key = historyUpdateKey ( update ) ;
1540+ if ( key && flushFallbackThrough ( key ) ) {
1541+ continue ;
1542+ }
1543+ pushUpdate ( update ) ;
1544+ }
1545+
1546+ while ( fallbackIndex < responseItemFallbackUpdates . length ) {
1547+ pushUpdate ( responseItemFallbackUpdates [ fallbackIndex ] ! ) ;
1548+ fallbackIndex += 1 ;
1549+ }
1550+
1551+ return merged ;
1552+ }
1553+
1554+ function historyUpdateKey ( update : UpdateSessionEvent ) : string | null {
1555+ switch ( update . sessionUpdate ) {
1556+ case "user_message_chunk" :
1557+ case "agent_message_chunk" :
1558+ case "agent_thought_chunk" :
1559+ return `${ update . sessionUpdate } :${ JSON . stringify ( update . content ) } ` ;
1560+ case "tool_call" :
1561+ return `tool_call:${ update . toolCallId } :start` ;
1562+ case "tool_call_update" :
1563+ return `tool_call:${ update . toolCallId } :update` ;
1564+ default :
1565+ return null ;
1566+ }
1567+ }
1568+
14841569function getRequestedMcpServerNames ( mcpServers : Array < acp . McpServer > ) : Array < string > {
14851570 return Array . from ( new Set ( mcpServers . map ( server => sanitizeMcpServerName ( server . name ) ) ) ) ;
14861571}
0 commit comments