@@ -53,6 +53,7 @@ import {
5353} from "./AcpExtensions" ;
5454import {
5555 createCollabAgentToolCallUpdate ,
56+ createCompletedContextCompactionUpdate ,
5657 createCommandExecutionCompleteUpdate ,
5758 createCommandExecutionUpdate ,
5859 createDynamicToolCallUpdate ,
@@ -110,6 +111,8 @@ export interface SessionState {
110111 sessionMcpServers ?: Array < string > ;
111112 terminalOutputMode : TerminalOutputMode ;
112113 currentGoal ?: ThreadGoalSnapshot | null ;
114+ sessionTitle : string | null ;
115+ sessionTitleSource : "unset" | "fallback" | "explicit" | "unknown" ;
113116}
114117
115118interface ActiveAuthState {
@@ -425,6 +428,8 @@ export class CodexAcpServer {
425428 currentModelSupportsFast : currentModelSupportsFast ,
426429 sessionMcpServers : sessionMcpServers ,
427430 terminalOutputMode : this . terminalOutputMode ,
431+ sessionTitle : null ,
432+ sessionTitleSource : "sessionId" in request ? "unknown" : "unset" ,
428433 } ;
429434 this . sessions . set ( sessionId , sessionState ) ;
430435 resumeSubscribed = false ;
@@ -972,6 +977,8 @@ export class CodexAcpServer {
972977 currentModelSupportsFast : currentModelSupportsFast ,
973978 sessionMcpServers : sessionMcpServers ,
974979 terminalOutputMode : this . terminalOutputMode ,
980+ sessionTitle : null ,
981+ sessionTitleSource : "unset" ,
975982 } ;
976983 this . sessions . set ( sessionId , sessionState ) ;
977984 subscribed = false ;
@@ -999,6 +1006,7 @@ export class CodexAcpServer {
9991006 private async streamThreadHistory ( sessionId : string , thread : Thread ) : Promise < void > {
10001007 const session = new ACPSessionConnection ( this . connection , sessionId ) ;
10011008 const sessionState = this . getSessionState ( sessionId ) ;
1009+ await this . publishThreadHistoryTitle ( session , sessionState , thread ) ;
10021010 const responseItemFallbackUpdates = await createResponseItemHistoryFallbackUpdates (
10031011 thread ,
10041012 sessionState . terminalOutputMode ,
@@ -1020,6 +1028,67 @@ export class CodexAcpServer {
10201028 }
10211029 }
10221030
1031+ private async publishThreadHistoryTitle (
1032+ session : ACPSessionConnection ,
1033+ sessionState : SessionState ,
1034+ thread : Thread ,
1035+ ) : Promise < void > {
1036+ const explicitTitle = this . normalizeSessionTitle ( thread . name ) ;
1037+ if ( explicitTitle ) {
1038+ sessionState . sessionTitle = explicitTitle ;
1039+ sessionState . sessionTitleSource = "explicit" ;
1040+ await session . update ( {
1041+ sessionUpdate : "session_info_update" ,
1042+ title : explicitTitle ,
1043+ } ) ;
1044+ return ;
1045+ }
1046+
1047+ const historyTitle = this . findFirstUserMessageTitle ( thread )
1048+ ?? this . normalizeSessionTitle ( thread . preview ) ;
1049+ await this . publishFallbackSessionTitle ( sessionState , historyTitle ) ;
1050+ }
1051+
1052+ private findFirstUserMessageTitle ( thread : Thread ) : string | null {
1053+ for ( const turn of thread . turns ) {
1054+ for ( const item of turn . items ) {
1055+ if ( item . type !== "userMessage" ) continue ;
1056+ const title = this . normalizeSessionTitle ( item . content
1057+ . filter ( ( input ) : input is Extract < UserInput , { type : "text" } > => input . type === "text" )
1058+ . map ( input => input . text )
1059+ . join ( " " ) ) ;
1060+ if ( title ) return title ;
1061+ }
1062+ }
1063+ return null ;
1064+ }
1065+
1066+ private async publishFallbackSessionTitle (
1067+ sessionState : SessionState ,
1068+ title : string | null ,
1069+ ) : Promise < void > {
1070+ if ( sessionState . sessionTitleSource !== "unset" || ! title ) return ;
1071+ sessionState . sessionTitle = title ;
1072+ sessionState . sessionTitleSource = "fallback" ;
1073+ const session = new ACPSessionConnection ( this . connection , sessionState . sessionId ) ;
1074+ await session . update ( {
1075+ sessionUpdate : "session_info_update" ,
1076+ title,
1077+ } ) ;
1078+ }
1079+
1080+ private createPromptFallbackTitle ( prompt : acp . ContentBlock [ ] ) : string | null {
1081+ return this . normalizeSessionTitle ( prompt
1082+ . filter ( ( block ) : block is Extract < acp . ContentBlock , { type : "text" } > => block . type === "text" )
1083+ . map ( block => block . text )
1084+ . join ( " " ) ) ;
1085+ }
1086+
1087+ private normalizeSessionTitle ( title : string | null | undefined ) : string | null {
1088+ const normalized = title ?. replace ( / \s + / g, " " ) . trim ( ) ?? "" ;
1089+ return normalized . length > 0 ? normalized : null ;
1090+ }
1091+
10231092 private async createHistoryUpdates ( item : ThreadItem , sessionState : SessionState ) : Promise < UpdateSessionEvent [ ] > {
10241093 switch ( item . type ) {
10251094 case "userMessage" :
@@ -1066,7 +1135,7 @@ export class CodexAcpServer {
10661135 case "exitedReviewMode" :
10671136 return [ this . createReviewModeUpdate ( item , false ) ] ;
10681137 case "contextCompaction" :
1069- return [ this . createContextCompactionUpdate ( ) ] ;
1138+ return [ createCompletedContextCompactionUpdate ( item ) ] ;
10701139 case "plan" :
10711140 return [ this . createPlanUpdate ( item ) ] ;
10721141 }
@@ -1119,16 +1188,6 @@ export class CodexAcpServer {
11191188 } ;
11201189 }
11211190
1122- private createContextCompactionUpdate ( ) : UpdateSessionEvent {
1123- return {
1124- sessionUpdate : "agent_message_chunk" ,
1125- content : {
1126- type : "text" ,
1127- text : "Context compacted." ,
1128- } ,
1129- } ;
1130- }
1131-
11321191 private createPlanUpdate (
11331192 item : ThreadItem & { type : "plan" }
11341193 ) : UpdateSessionEvent {
@@ -1636,6 +1695,11 @@ export class CodexAcpServer {
16361695 throw error ;
16371696 }
16381697
1698+ await this . publishFallbackSessionTitle (
1699+ sessionState ,
1700+ this . createPromptFallbackTitle ( params . prompt ) ,
1701+ ) ;
1702+
16391703 return {
16401704 stopReason : "end_turn" ,
16411705 usage : this . buildPromptUsage ( sessionState . lastTokenUsage ) ,
0 commit comments