66 "encoding/json"
77 "fmt"
88 "os"
9+ "regexp"
910 "strings"
1011 "time"
1112
@@ -45,6 +46,16 @@ type codexDeepHistoryParseResult struct {
4546 Stats codexDeepHistoryStats
4647}
4748
49+ type codexDeepMessageOrigin uint8
50+
51+ const (
52+ codexDeepMessageOriginEvent codexDeepMessageOrigin = 1 << iota
53+ codexDeepMessageOriginResponse
54+ codexDeepMessageDedupeWindow = 2 * time .Second
55+ )
56+
57+ var codexEmbeddedImageEnvelopePattern = regexp .MustCompile (`(?s)[\r\n]*<image\b[^>]*\bpath="([^"]+)"[^>]*>\s*\[input_image:\]\s*</image>` )
58+
4859func (m * Manager ) syncSessionFromLogSource (
4960 ctx context.Context ,
5061 session tables.WebSessionTable ,
@@ -185,6 +196,7 @@ func (m *Manager) parseCodexDeepHistoryWithStats(filePath string) (codexDeepHist
185196 items := make ([]HistoryItem , 0 , 256 )
186197 pendingTools := make (map [string ]int )
187198 pendingUserInputs := make (map [string ]int )
199+ messageOrigins := make (map [string ]codexDeepMessageOrigin )
188200 var orderIndex int64
189201 var stats codexDeepHistoryStats
190202
@@ -202,6 +214,30 @@ func (m *Manager) parseCodexDeepHistoryWithStats(filePath string) (codexDeepHist
202214 return len (items ) - 1
203215 }
204216
217+ appendMessage := func (item HistoryItem , origin codexDeepMessageOrigin ) int {
218+ itemTime := codexDeepMessageTime (item )
219+ for index := len (items ) - 1 ; index >= 0 ; index -- {
220+ candidate := items [index ]
221+ candidateTime := codexDeepMessageTime (candidate )
222+ if itemTime != nil && candidateTime != nil && itemTime .Sub (* candidateTime ) > codexDeepMessageDedupeWindow {
223+ break
224+ }
225+ candidateOrigin := messageOrigins [candidate .ID ]
226+ if candidateOrigin == 0 || candidateOrigin & origin != 0 {
227+ continue
228+ }
229+ if ! codexDeepMessagesMatch (candidate , item ) {
230+ continue
231+ }
232+ mergeCodexDeepMessage (& items [index ], item , candidateOrigin , origin )
233+ messageOrigins [candidate .ID ] = candidateOrigin | origin
234+ return index
235+ }
236+ index := appendItem (item )
237+ messageOrigins [item .ID ] = origin
238+ return index
239+ }
240+
205241 appendIfNotDuplicate := func (item HistoryItem ) {
206242 if item .ItemType == "context_compaction" && len (items ) > 0 {
207243 last := items [len (items )- 1 ]
@@ -242,6 +278,10 @@ func (m *Manager) parseCodexDeepHistoryWithStats(filePath string) (codexDeepHist
242278 }
243279 stats .observeEventMessage (payload , ts )
244280 for _ , item := range m .codexHistoryItemsFromEventMessage (payload , ts ) {
281+ if isCodexDeepMessage (item ) {
282+ appendMessage (item , codexDeepMessageOriginEvent )
283+ continue
284+ }
245285 appendIfNotDuplicate (item )
246286 }
247287 case "response_item" :
@@ -257,6 +297,7 @@ func (m *Manager) parseCodexDeepHistoryWithStats(filePath string) (codexDeepHist
257297 payload ,
258298 ts ,
259299 appendItem ,
300+ appendMessage ,
260301 pendingTools ,
261302 pendingUserInputs ,
262303 )
@@ -266,6 +307,7 @@ func (m *Manager) parseCodexDeepHistoryWithStats(filePath string) (codexDeepHist
266307 return codexDeepHistoryParseResult {}, err
267308 }
268309
310+ items = dedupeCodexDeepPlanMessages (items )
269311 for index := range items {
270312 items [index ].OrderIndex = int64 (index + 1 )
271313 }
@@ -275,6 +317,178 @@ func (m *Manager) parseCodexDeepHistoryWithStats(filePath string) (codexDeepHist
275317 }, nil
276318}
277319
320+ func isCodexDeepMessage (item HistoryItem ) bool {
321+ return item .Tool == nil && (item .Kind == "user" || item .Kind == "assistant" )
322+ }
323+
324+ func normalizedCodexDeepMessageText (text string ) string {
325+ return strings .TrimSpace (strings .ReplaceAll (text , "\r \n " , "\n " ))
326+ }
327+
328+ func codexDeepMessageTime (item HistoryItem ) * time.Time {
329+ if item .Timestamp != nil {
330+ return item .Timestamp
331+ }
332+ return item .ObservedAt
333+ }
334+
335+ func codexDeepMessageAttachmentsCompatible (left , right []HistoryAttachment ) bool {
336+ if len (left ) == 0 || len (right ) == 0 {
337+ return true
338+ }
339+ if len (left ) != len (right ) {
340+ return false
341+ }
342+ keys := make (map [string ]struct {}, len (left ))
343+ for _ , attachment := range left {
344+ key := firstNonEmpty (strings .TrimSpace (attachment .ID ), strings .TrimSpace (attachment .Path ))
345+ keys [key ] = struct {}{}
346+ }
347+ for _ , attachment := range right {
348+ key := firstNonEmpty (strings .TrimSpace (attachment .ID ), strings .TrimSpace (attachment .Path ))
349+ if _ , ok := keys [key ]; ! ok {
350+ return false
351+ }
352+ }
353+ return true
354+ }
355+
356+ func codexDeepMessagesMatch (left , right HistoryItem ) bool {
357+ if ! isCodexDeepMessage (left ) || ! isCodexDeepMessage (right ) || left .Kind != right .Kind {
358+ return false
359+ }
360+ if normalizedCodexDeepMessageText (left .Text ) != normalizedCodexDeepMessageText (right .Text ) {
361+ return false
362+ }
363+ if ! codexDeepMessageAttachmentsCompatible (left .Attachments , right .Attachments ) {
364+ return false
365+ }
366+ leftTime := codexDeepMessageTime (left )
367+ rightTime := codexDeepMessageTime (right )
368+ if leftTime == nil || rightTime == nil {
369+ return false
370+ }
371+ delta := leftTime .Sub (* rightTime )
372+ if delta < 0 {
373+ delta = - delta
374+ }
375+ return delta <= codexDeepMessageDedupeWindow
376+ }
377+
378+ func mergeHistoryAttachments (left , right []HistoryAttachment ) []HistoryAttachment {
379+ if len (left ) == 0 {
380+ return right
381+ }
382+ if len (right ) == 0 {
383+ return left
384+ }
385+ result := append ([]HistoryAttachment {}, left ... )
386+ seen := make (map [string ]struct {}, len (result ))
387+ for _ , attachment := range result {
388+ key := firstNonEmpty (strings .TrimSpace (attachment .ID ), strings .TrimSpace (attachment .Path ))
389+ seen [key ] = struct {}{}
390+ }
391+ for _ , attachment := range right {
392+ key := firstNonEmpty (strings .TrimSpace (attachment .ID ), strings .TrimSpace (attachment .Path ))
393+ if _ , ok := seen [key ]; ok {
394+ continue
395+ }
396+ seen [key ] = struct {}{}
397+ result = append (result , attachment )
398+ }
399+ return result
400+ }
401+
402+ func mergeCodexDeepMessage (
403+ target * HistoryItem ,
404+ source HistoryItem ,
405+ targetOrigin codexDeepMessageOrigin ,
406+ sourceOrigin codexDeepMessageOrigin ,
407+ ) {
408+ if target == nil {
409+ return
410+ }
411+ if sourceOrigin == codexDeepMessageOriginEvent {
412+ target .Text = source .Text
413+ target .Attachments = mergeHistoryAttachments (source .Attachments , target .Attachments )
414+ } else {
415+ target .Attachments = mergeHistoryAttachments (target .Attachments , source .Attachments )
416+ }
417+ if sourceOrigin == codexDeepMessageOriginResponse || targetOrigin != codexDeepMessageOriginResponse {
418+ if source .SourceTurnID != nil {
419+ target .SourceTurnID = source .SourceTurnID
420+ }
421+ if source .SourceItemID != nil {
422+ target .SourceItemID = source .SourceItemID
423+ }
424+ if source .Payload != nil {
425+ target .Payload = source .Payload
426+ }
427+ }
428+ target .Done = target .Done || source .Done
429+ if target .Timestamp == nil || source .Timestamp != nil && source .Timestamp .Before (* target .Timestamp ) {
430+ target .Timestamp = source .Timestamp
431+ }
432+ if target .ObservedAt == nil || source .ObservedAt != nil && source .ObservedAt .After (* target .ObservedAt ) {
433+ target .ObservedAt = source .ObservedAt
434+ }
435+ }
436+
437+ func codexProposedPlanText (text string ) (string , bool ) {
438+ trimmed := strings .TrimSpace (strings .ReplaceAll (text , "\r \n " , "\n " ))
439+ const openTag = "<proposed_plan>"
440+ const closeTag = "</proposed_plan>"
441+ if ! strings .HasPrefix (trimmed , openTag ) || ! strings .HasSuffix (trimmed , closeTag ) {
442+ return "" , false
443+ }
444+ return strings .TrimSpace (strings .TrimSuffix (strings .TrimPrefix (trimmed , openTag ), closeTag )), true
445+ }
446+
447+ func codexDeepItemsShareTurnOrTime (left , right HistoryItem ) bool {
448+ if left .SourceTurnID != nil && right .SourceTurnID != nil &&
449+ strings .TrimSpace (* left .SourceTurnID ) != "" && * left .SourceTurnID == * right .SourceTurnID {
450+ return true
451+ }
452+ leftTime := codexDeepMessageTime (left )
453+ rightTime := codexDeepMessageTime (right )
454+ if leftTime == nil || rightTime == nil {
455+ return false
456+ }
457+ delta := leftTime .Sub (* rightTime )
458+ if delta < 0 {
459+ delta = - delta
460+ }
461+ return delta <= codexDeepMessageDedupeWindow
462+ }
463+
464+ func dedupeCodexDeepPlanMessages (items []HistoryItem ) []HistoryItem {
465+ if len (items ) == 0 {
466+ return items
467+ }
468+ result := make ([]HistoryItem , 0 , len (items ))
469+ for index , item := range items {
470+ planText , proposed := codexProposedPlanText (item .Text )
471+ if item .Kind == "assistant" && proposed {
472+ duplicate := false
473+ for candidateIndex , candidate := range items {
474+ if candidateIndex == index || candidate .Tool == nil || candidate .Tool .Kind != "plan" {
475+ continue
476+ }
477+ if normalizedCodexDeepMessageText (candidate .Tool .Output ) == normalizedCodexDeepMessageText (planText ) &&
478+ codexDeepItemsShareTurnOrTime (candidate , item ) {
479+ duplicate = true
480+ break
481+ }
482+ }
483+ if duplicate {
484+ continue
485+ }
486+ }
487+ result = append (result , item )
488+ }
489+ return result
490+ }
491+
278492func latestHistoryItemTimestamp (items []HistoryItem ) * time.Time {
279493 var latest * time.Time
280494 for _ , item := range items {
@@ -635,13 +849,17 @@ func (m *Manager) applyCodexResponseItem(
635849 payload map [string ]any ,
636850 ts time.Time ,
637851 appendItem func (item HistoryItem ) int ,
852+ appendMessage func (item HistoryItem , origin codexDeepMessageOrigin ) int ,
638853 pendingTools map [string ]int ,
639854 pendingUserInputs map [string ]int ,
640855) {
641856 responseType := strings .TrimSpace (stringValue (payload ["type" ]))
642857 switch responseType {
643858 case "message" :
644- role := strings .TrimSpace (stringValue (payload ["role" ]))
859+ role := strings .ToLower (strings .TrimSpace (stringValue (payload ["role" ])))
860+ if role == "developer" || role == "system" {
861+ return
862+ }
645863 content := decodeRawArray (payload ["content" ])
646864 textParts := make ([]string , 0 , len (content ))
647865 for _ , block := range content {
@@ -653,35 +871,34 @@ func (m *Manager) applyCodexResponseItem(
653871 }
654872 }
655873 }
656- text := strings . TrimSpace (strings .Join (textParts , "\n " ))
874+ text , embeddedAttachments := m . normalizeCodexResponseMessage (strings .Join (textParts , "\n " ))
657875 if text == "" {
658876 return
659877 }
660878 item := HistoryItem {
661- ID : utils .NewID (),
662- ItemType : "message" ,
663- Text : text ,
664- Timestamp : ptr (ts ),
665- ObservedAt : ptr (ts ),
666- Payload : cloneMap (payload ),
667- Done : true ,
879+ ID : utils .NewID (),
880+ SourceTurnID : nilIfEmptyHistory (codexResponseItemTurnID (payload )),
881+ SourceItemID : nilIfEmptyHistory (stringValue (payload ["id" ])),
882+ ItemType : "message" ,
883+ Text : text ,
884+ Timestamp : ptr (ts ),
885+ ObservedAt : ptr (ts ),
886+ Attachments : embeddedAttachments ,
887+ Payload : cloneMap (payload ),
888+ Done : true ,
668889 }
669890 switch role {
670891 case "assistant" :
671892 item .Kind = "assistant"
672893 item .ItemType = "agent_message"
673- case "developer" , "system" :
674- item .Kind = "system"
675- item .ItemType = "system_message"
676- item .Level = "info"
677894 default :
678- if isHiddenCodexPrompt (text ) {
895+ if isHiddenCodexPrompt (text ) || isCodexInjectedHostContext ( text ) {
679896 return
680897 }
681898 item .Kind = "user"
682899 item .ItemType = "user_message"
683900 }
684- appendItem (item )
901+ appendMessage (item , codexDeepMessageOriginResponse )
685902 case "reasoning" :
686903 text := codexReasoningSummary (payload )
687904 if text == "" {
@@ -986,6 +1203,41 @@ func codexReasoningSummary(payload map[string]any) string {
9861203 return strings .TrimSpace (strings .Join (parts , "\n " ))
9871204}
9881205
1206+ func codexResponseItemTurnID (payload map [string ]any ) string {
1207+ metadata := decodeRawObject (payload ["internal_chat_message_metadata_passthrough" ])
1208+ return strings .TrimSpace (firstNonEmpty (
1209+ stringValue (metadata ["turn_id" ]),
1210+ stringValue (payload ["turn_id" ]),
1211+ ))
1212+ }
1213+
1214+ func isCodexInjectedHostContext (text string ) bool {
1215+ trimmed := strings .TrimSpace (text )
1216+ if strings .HasPrefix (trimmed , "# AGENTS.md instructions for " ) &&
1217+ strings .Contains (trimmed , "<INSTRUCTIONS>" ) &&
1218+ strings .Contains (trimmed , "</INSTRUCTIONS>" ) {
1219+ return true
1220+ }
1221+ return strings .HasPrefix (trimmed , "<environment_context>" ) &&
1222+ strings .HasSuffix (trimmed , "</environment_context>" )
1223+ }
1224+
1225+ func (m * Manager ) normalizeCodexResponseMessage (text string ) (string , []HistoryAttachment ) {
1226+ attachments := make ([]HistoryAttachment , 0 )
1227+ for _ , match := range codexEmbeddedImageEnvelopePattern .FindAllStringSubmatch (text , - 1 ) {
1228+ if len (match ) < 2 {
1229+ continue
1230+ }
1231+ attachment , err := m .registerExternalAttachment (strings .TrimSpace (match [1 ]))
1232+ if err != nil {
1233+ continue
1234+ }
1235+ attachments = mergeHistoryAttachments (attachments , []HistoryAttachment {attachment })
1236+ }
1237+ text = codexEmbeddedImageEnvelopePattern .ReplaceAllString (text , "" )
1238+ return strings .TrimSpace (text ), attachments
1239+ }
1240+
9891241func (m * Manager ) codexHistoryAttachments (payload map [string ]any ) []HistoryAttachment {
9901242 sources := append ([]string {}, extractCodexAttachmentSources (payload ["local_images" ])... )
9911243 sources = append (sources , extractCodexAttachmentSources (payload ["images" ])... )
0 commit comments