@ai-devkit/agent-manager 0.26.0 → 0.26.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -37,9 +37,26 @@ interface CodexEventEntry {
37
37
  id?: string;
38
38
  cwd?: string;
39
39
  timestamp?: string;
40
+ role?: string;
41
+ content?: CodexContent[];
42
+ item?: CodexItem;
43
+ turn_id?: string;
44
+ internal_chat_message_metadata_passthrough?: {
45
+ turn_id?: string;
46
+ };
40
47
  };
41
48
  }
42
49
 
50
+ interface CodexContent {
51
+ type?: string;
52
+ text?: string;
53
+ }
54
+
55
+ interface CodexItem {
56
+ type?: string;
57
+ content?: string | CodexContent[];
58
+ }
59
+
43
60
  interface CodexSession {
44
61
  sessionId: string;
45
62
  projectPath: string;
@@ -502,7 +519,7 @@ export class CodexAdapter implements AgentAdapter {
502
519
  }
503
520
 
504
521
  const lastEntry = this.findLastEventEntry(entries);
505
- const lastPayloadType = lastEntry?.payload?.type;
522
+ const lastPayloadType = lastEntry ? this.normalizedPayloadType(lastEntry) : undefined;
506
523
 
507
524
  const lastActive =
508
525
  this.parseTimestamp(lastEntry?.timestamp) ||
@@ -596,15 +613,44 @@ export class CodexAdapter implements AgentAdapter {
596
613
 
597
614
  private extractSummary(entries: CodexEventEntry[]): string {
598
615
  for (let i = entries.length - 1; i >= 0; i--) {
599
- const message = entries[i]?.payload?.message;
600
- if (typeof message === 'string' && message.trim().length > 0) {
601
- return this.truncate(message.trim(), 120);
602
- }
616
+ const message = this.extractEntryText(entries[i]);
617
+ if (message) return this.truncate(message, 120);
603
618
  }
604
619
 
605
620
  return 'Codex session active';
606
621
  }
607
622
 
623
+ private normalizedPayloadType(entry: CodexEventEntry): string | undefined {
624
+ const payloadType = entry.payload?.type;
625
+
626
+ if (entry.type === 'response_item' && payloadType === 'message') {
627
+ if (entry.payload?.role === 'assistant') return 'agent_message';
628
+ if (entry.payload?.role === 'user') return 'user_message';
629
+ return payloadType;
630
+ }
631
+
632
+ if (entry.type === 'event_msg' && payloadType === 'item_completed') {
633
+ const itemType = entry.payload?.item?.type;
634
+ if (itemType === 'AgentMessage') return 'agent_message';
635
+ if (itemType === 'UserMessage') return 'user_message';
636
+ return itemType ?? payloadType;
637
+ }
638
+
639
+ return payloadType;
640
+ }
641
+
642
+ private extractEntryText(entry: CodexEventEntry | undefined): string {
643
+ if (!entry) return '';
644
+
645
+ const legacyMessage = entry.payload?.message;
646
+ if (typeof legacyMessage === 'string' && legacyMessage.trim().length > 0) {
647
+ return legacyMessage.trim();
648
+ }
649
+
650
+ const conversationMessage = this.toConversationMessage(entry, false);
651
+ return conversationMessage?.content.trim() ?? '';
652
+ }
653
+
608
654
  private truncate(value: string, maxLength: number): string {
609
655
  if (value.length <= maxLength) return value;
610
656
  return `${value.slice(0, maxLength - 3)}...`;
@@ -619,7 +665,8 @@ export class CodexAdapter implements AgentAdapter {
619
665
  /**
620
666
  * Read the full conversation from a Codex session JSONL file.
621
667
  *
622
- * Codex entries use payload.type to indicate message role and payload.message for content.
668
+ * Codex entries use either legacy payload.message fields or current
669
+ * response_item/event_msg content arrays.
623
670
  */
624
671
  getConversation(sessionFilePath: string, options?: { verbose?: boolean }): ConversationMessage[] {
625
672
  const verbose = options?.verbose ?? false;
@@ -628,45 +675,118 @@ export class CodexAdapter implements AgentAdapter {
628
675
  if (content === undefined) return [];
629
676
 
630
677
  const lines = content.trim().split('\n');
678
+ const entries: CodexEventEntry[] = [];
631
679
  const messages: ConversationMessage[] = [];
632
680
 
633
681
  for (const line of lines) {
634
- let entry: CodexEventEntry;
635
682
  try {
636
- entry = JSON.parse(line);
683
+ entries.push(JSON.parse(line));
637
684
  } catch {
638
685
  continue;
639
686
  }
687
+ }
640
688
 
641
- if (entry.type === 'session_meta') continue;
689
+ const responseItemMirrorKeys = new Set<string>();
690
+ for (const entry of entries) {
691
+ if (entry.type !== 'response_item') continue;
642
692
 
643
- const payloadType = entry.payload?.type;
644
- if (!payloadType) continue;
693
+ const message = this.toConversationMessage(entry, verbose);
694
+ const mirrorKey = message ? this.mirroredMessageKey(entry, message) : null;
695
+ if (mirrorKey) responseItemMirrorKeys.add(mirrorKey);
696
+ }
645
697
 
646
- let role: ConversationMessage['role'];
647
- if (payloadType === 'user_message') {
648
- role = 'user';
649
- } else if (payloadType === 'agent_message' || payloadType === 'task_complete') {
650
- role = 'assistant';
651
- } else if (verbose) {
652
- role = 'system';
653
- } else {
698
+ for (const entry of entries) {
699
+ const message = this.toConversationMessage(entry, verbose);
700
+ if (!message) continue;
701
+
702
+ const mirrorKey = this.mirroredMessageKey(entry, message);
703
+ if (
704
+ entry.type === 'event_msg' &&
705
+ mirrorKey &&
706
+ responseItemMirrorKeys.has(mirrorKey)
707
+ ) {
654
708
  continue;
655
709
  }
656
710
 
657
- const text = entry.payload?.message?.trim();
658
- if (!text) continue;
659
-
660
- messages.push({
661
- role,
662
- content: text,
663
- timestamp: entry.timestamp,
664
- });
711
+ messages.push(message);
665
712
  }
666
713
 
667
714
  return messages;
668
715
  }
669
716
 
717
+ private toConversationMessage(entry: CodexEventEntry, verbose: boolean): ConversationMessage | null {
718
+ if (entry.type === 'session_meta') return null;
719
+
720
+ const payloadType = entry.payload?.type;
721
+ if (entry.type === 'response_item' && payloadType === 'message') {
722
+ const role = this.mapCodexRole(entry.payload?.role, verbose);
723
+ const text = this.extractContentText(entry.payload?.content);
724
+ if (!role || !text) return null;
725
+
726
+ return { role, content: text, timestamp: entry.timestamp };
727
+ }
728
+
729
+ if (entry.type === 'event_msg' && payloadType === 'item_completed') {
730
+ const item = entry.payload?.item;
731
+ const role = this.mapCodexItemRole(item?.type, verbose);
732
+ const text = this.extractContentText(item?.content);
733
+ if (!role || !text) return null;
734
+
735
+ return { role, content: text, timestamp: entry.timestamp };
736
+ }
737
+
738
+ if (!payloadType) return null;
739
+
740
+ let role: ConversationMessage['role'];
741
+ if (payloadType === 'user_message') {
742
+ role = 'user';
743
+ } else if (payloadType === 'agent_message' || payloadType === 'task_complete') {
744
+ role = 'assistant';
745
+ } else if (verbose) {
746
+ role = 'system';
747
+ } else {
748
+ return null;
749
+ }
750
+
751
+ const text = entry.payload?.message?.trim();
752
+ if (!text) return null;
753
+
754
+ return { role, content: text, timestamp: entry.timestamp };
755
+ }
756
+
757
+ private mapCodexRole(role: string | undefined, verbose: boolean): ConversationMessage['role'] | null {
758
+ if (role === 'user') return 'user';
759
+ if (role === 'assistant') return 'assistant';
760
+ return verbose ? 'system' : null;
761
+ }
762
+
763
+ private mapCodexItemRole(itemType: string | undefined, verbose: boolean): ConversationMessage['role'] | null {
764
+ if (itemType === 'AgentMessage') return 'assistant';
765
+ if (itemType === 'UserMessage') return 'user';
766
+ return verbose ? 'system' : null;
767
+ }
768
+
769
+ private mirroredMessageKey(entry: CodexEventEntry, message: ConversationMessage): string | null {
770
+ const turnId =
771
+ entry.payload?.turn_id ||
772
+ entry.payload?.internal_chat_message_metadata_passthrough?.turn_id;
773
+
774
+ if (!turnId) return null;
775
+ return `${turnId}\0${message.role}\0${message.content}`;
776
+ }
777
+
778
+ private extractContentText(content: string | CodexContent[] | undefined): string {
779
+ if (typeof content === 'string') return content.trim();
780
+ if (!Array.isArray(content)) return '';
781
+
782
+ return content
783
+ .map((part) => part.text)
784
+ .filter((text): text is string => typeof text === 'string' && text.trim().length > 0)
785
+ .map((text) => text.trim())
786
+ .join('\n')
787
+ .trim();
788
+ }
789
+
670
790
  async listSessions(opts?: ListSessionsOptions): Promise<SessionSummary[]> {
671
791
  if (!isDirectory(this.codexSessionsDir)) return [];
672
792