( group: GroupAccumulator, )
| 688 | } |
| 689 | |
| 690 | function createCollapsedGroup( |
| 691 | group: GroupAccumulator, |
| 692 | ): CollapsedReadSearchGroup { |
| 693 | const firstMsg = group.messages[0]! |
| 694 | // When file-path-based reads exist, use unique file count (Set.size) only. |
| 695 | // Adding bash operation count on top would double-count — e.g. Read(README.md) |
| 696 | // followed by Bash(wc -l README.md) should still show as 1 file, not 2. |
| 697 | // Fall back to operation count only when there are no file-path reads (bash-only). |
| 698 | const totalReadCount = |
| 699 | group.readFilePaths.size > 0 |
| 700 | ? group.readFilePaths.size |
| 701 | : group.readOperationCount |
| 702 | // memoryReadFilePaths ⊆ readFilePaths (both populated from Read tool calls), |
| 703 | // so this count is safe to subtract from totalReadCount at readCount below. |
| 704 | // Absorbed relevant_memories attachments are NOT in readFilePaths — added |
| 705 | // separately after the subtraction so readCount stays correct. |
| 706 | const toolMemoryReadCount = group.memoryReadFilePaths.size |
| 707 | const memoryReadCount = |
| 708 | toolMemoryReadCount + (group.relevantMemories?.length ?? 0) |
| 709 | // Non-memory read file paths: exclude memory and team memory paths |
| 710 | const teamMemReadPaths = feature('TEAMMEM') |
| 711 | ? group.teamMemoryReadFilePaths |
| 712 | : undefined |
| 713 | const nonMemReadFilePaths = [...group.readFilePaths].filter( |
| 714 | p => |
| 715 | !group.memoryReadFilePaths.has(p) && !(teamMemReadPaths?.has(p) ?? false), |
| 716 | ) |
| 717 | const teamMemSearchCount = feature('TEAMMEM') |
| 718 | ? (group.teamMemorySearchCount ?? 0) |
| 719 | : 0 |
| 720 | const teamMemReadCount = feature('TEAMMEM') |
| 721 | ? (group.teamMemoryReadFilePaths?.size ?? 0) |
| 722 | : 0 |
| 723 | const teamMemWriteCount = feature('TEAMMEM') |
| 724 | ? (group.teamMemoryWriteCount ?? 0) |
| 725 | : 0 |
| 726 | const result: CollapsedReadSearchGroup = { |
| 727 | type: 'collapsed_read_search', |
| 728 | // Subtract memory + team memory counts so regular counts only reflect non-memory operations |
| 729 | searchCount: Math.max( |
| 730 | 0, |
| 731 | group.searchCount - group.memorySearchCount - teamMemSearchCount, |
| 732 | ), |
| 733 | readCount: Math.max( |
| 734 | 0, |
| 735 | totalReadCount - toolMemoryReadCount - teamMemReadCount, |
| 736 | ), |
| 737 | listCount: group.listCount, |
| 738 | // REPL operations are intentionally not collapsed (see isCollapsible: false at line 32), |
| 739 | // so replCount in collapsed groups is always 0. The replCount field is kept for |
| 740 | // sub-agent progress display in AgentTool/UI.tsx which has a separate code path. |
| 741 | replCount: 0, |
| 742 | memorySearchCount: group.memorySearchCount, |
| 743 | memoryReadCount, |
| 744 | memoryWriteCount: group.memoryWriteCount, |
| 745 | readFilePaths: nonMemReadFilePaths, |
| 746 | searchArgs: group.nonMemSearchArgs, |
| 747 | latestDisplayHint: group.latestDisplayHint, |
no test coverage detected