@@ -636,7 +636,7 @@ def count_lines_by_section(self) -> Dict[str, int]:
636636
637637 # --- markdown rendering with filter ---
638638 def to_markdown (self , section_filter : Optional [Dict [str , bool ]] = None ,
639- clean_content : bool = False , output_cap : int = 0 , msg_cap : int = 0 ) -> str :
639+ clean_content : bool = False , output_cap : int = 0 , user_cap : int = 0 , agent_cap : int = 0 , reasoning_cap : int = 0 ) -> str :
640640 if section_filter is None :
641641 section_filter = {s [0 ]: True for s in SECTION_DEFS }
642642
@@ -651,13 +651,19 @@ def _cap_text(text: str) -> str:
651651 return f'... ({ len (lines ) - output_cap } lines trimmed) ...\n ' + '\n ' .join (kept )
652652
653653 keep_indices = set (range (len (self .data )))
654- if msg_cap > 0 :
655- chat_msg_count = 0
656- for i in range (len (self .data ) - 1 , - 1 , - 1 ):
657- if self .data [i ]['type' ] in {'user_message' , 'agent_message' , 'agent_reasoning' , 'reasoning' }:
658- if chat_msg_count >= msg_cap :
659- keep_indices .remove (i )
660- chat_msg_count += 1
654+ counts = {'user_message' : 0 , 'agent_message' : 0 , 'reasoning_group' : 0 }
655+
656+ for i in range (len (self .data ) - 1 , - 1 , - 1 ):
657+ itype = self .data [i ]['type' ]
658+ if itype == 'user_message' and user_cap > 0 :
659+ if counts ['user_message' ] >= user_cap : keep_indices .remove (i )
660+ counts ['user_message' ] += 1
661+ elif itype == 'agent_message' and agent_cap > 0 :
662+ if counts ['agent_message' ] >= agent_cap : keep_indices .remove (i )
663+ counts ['agent_message' ] += 1
664+ elif itype in ('agent_reasoning' , 'reasoning' ) and reasoning_cap > 0 :
665+ if counts ['reasoning_group' ] >= reasoning_cap : keep_indices .remove (i )
666+ counts ['reasoning_group' ] += 1
661667
662668 md : List [str ] = []
663669 md .append (f"# { self .title } \n " )
@@ -797,15 +803,15 @@ def read_key() -> str:
797803CAP_STEPS = [0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 15 , 20 , 30 , 50 , 100 , 200 , 500 ]
798804MSG_CAP_STEPS = [0 , 5 , 10 , 20 , 50 ] + list (range (70 , 511 , 20 ))
799805
800- def interactive_filter (parsers : List [SessionParser ]) -> Tuple [Dict [str , bool ], bool , int , int ]:
806+ def interactive_filter (parsers : List [SessionParser ]) -> Tuple [Dict [str , bool ], bool , int , int , int , int ]:
801807 """
802808 Full-screen interactive filter.
803- Returns (section_filter, clean_content, output_cap, msg_cap ).
809+ Returns (section_filter, clean_content, output_cap, user_cap, agent_cap, reasoning_cap ).
804810 """
805811 _line_cache = {}
806812
807- def get_lines_for_state (cap_out : int , cap_msg : int , cc : bool ) -> Dict [str , int ]:
808- cache_key = (cap_out , cap_msg , cc )
813+ def get_lines_for_state (cap_out : int , cap_user : int , cap_agent : int , cap_reason : int , cc : bool ) -> Dict [str , int ]:
814+ cache_key = (cap_out , cap_user , cap_agent , cap_reason , cc )
809815 if cache_key in _line_cache :
810816 return _line_cache [cache_key ]
811817
@@ -815,13 +821,19 @@ def get_lines_for_state(cap_out: int, cap_msg: int, cc: bool) -> Dict[str, int]:
815821
816822 for parser in parsers :
817823 keep_indices = set (range (len (parser .data )))
818- if cap_msg > 0 :
819- chat_msg_count = 0
820- for i in range (len (parser .data ) - 1 , - 1 , - 1 ):
821- if parser .data [i ]['type' ] in {'user_message' , 'agent_message' , 'agent_reasoning' , 'reasoning' }:
822- if chat_msg_count >= cap_msg :
823- keep_indices .remove (i )
824- chat_msg_count += 1
824+ chat_counts = {'u' : 0 , 'a' : 0 , 'r' : 0 }
825+
826+ for i in range (len (parser .data ) - 1 , - 1 , - 1 ):
827+ itype = parser .data [i ]['type' ]
828+ if itype == 'user_message' and cap_user > 0 :
829+ if chat_counts ['u' ] >= cap_user : keep_indices .remove (i )
830+ chat_counts ['u' ] += 1
831+ elif itype == 'agent_message' and cap_agent > 0 :
832+ if chat_counts ['a' ] >= cap_agent : keep_indices .remove (i )
833+ chat_counts ['a' ] += 1
834+ elif itype in ('agent_reasoning' , 'reasoning' ) and cap_reason > 0 :
835+ if chat_counts ['r' ] >= cap_reason : keep_indices .remove (i )
836+ chat_counts ['r' ] += 1
825837
826838 for i , item in enumerate (parser .data ):
827839 if i not in keep_indices : continue
@@ -867,19 +879,29 @@ def get_lines_for_state(cap_out: int, cap_msg: int, cc: bool) -> Dict[str, int]:
867879
868880 fstate : Dict [str , bool ] = {s [0 ]: s [3 ] for s in SECTION_DEFS }
869881 clean_content = False
882+
870883 output_cap = 8
871884 cap_idx = CAP_STEPS .index (8 )
872- msg_cap = 0
873- msg_idx = MSG_CAP_STEPS .index (0 )
885+
886+ user_cap = 0
887+ u_idx = 0
888+
889+ agent_cap = 0
890+ a_idx = 0
891+
892+ reason_cap = 0
893+ r_idx = 0
874894
875895 cursor = 0
876896 ROW_CLEAN = len (SECTION_DEFS )
877897 ROW_CAP = len (SECTION_DEFS ) + 1
878- ROW_MSG = len (SECTION_DEFS ) + 2
879- num_items = len (SECTION_DEFS ) + 3
898+ ROW_USER = len (SECTION_DEFS ) + 2
899+ ROW_AGENT = len (SECTION_DEFS ) + 3
900+ ROW_REASON = len (SECTION_DEFS ) + 4
901+ num_items = len (SECTION_DEFS ) + 5
880902
881903 while True :
882- agg_lines = get_lines_for_state (output_cap , msg_cap , clean_content )
904+ agg_lines = get_lines_for_state (output_cap , user_cap , agent_cap , reason_cap , clean_content )
883905 total_lines = sum (agg_lines .get (s [0 ], 0 ) for s in SECTION_DEFS )
884906 selected_lines = sum (agg_lines .get (s [0 ], 0 ) for s in SECTION_DEFS if fstate .get (s [0 ], False ))
885907 pct = (selected_lines / total_lines * 100 ) if total_lines > 0 else 0
@@ -932,13 +954,29 @@ def get_lines_for_state(cap_out: int, cap_msg: int, cc: bool) -> Dict[str, int]:
932954 hint = f' { Style .DIM } ◀▶{ Style .RESET } ' if cap_cur else ''
933955 print (f' { cap_arrow } { cap_st } 💻 Terminal Output Cap{ Style .RESET } { Style .DIM } (max lines/block){ Style .RESET } { cap_label } { hint } ' )
934956
935- # Msg Cap
936- msg_cur = (cursor == ROW_MSG )
937- msg_arrow = f'{ Style .BOLD } { Style .YELLOW } ▸{ Style .RESET } ' if msg_cur else ' '
938- msg_st = f'{ Style .BOLD } ' if msg_cur else Style .DIM
939- msg_label = f'{ Style .DIM } ALL{ Style .RESET } ' if msg_cap == 0 else f'{ Style .YELLOW } Last { msg_cap } { Style .RESET } '
940- msg_hint = f' { Style .DIM } ◀▶{ Style .RESET } ' if msg_cur else ''
941- print (f' { msg_arrow } { msg_st } 🕒 Chat Message Cap{ Style .RESET } { Style .DIM } (blocks for 👤🤖🧠){ Style .RESET } { msg_label } { msg_hint } ' )
957+ # User Cap
958+ u_cur = (cursor == ROW_USER )
959+ u_arrow = f'{ Style .BOLD } { Style .YELLOW } ▸{ Style .RESET } ' if u_cur else ' '
960+ u_st = f'{ Style .BOLD } ' if u_cur else Style .DIM
961+ u_label = f'{ Style .DIM } ALL{ Style .RESET } ' if user_cap == 0 else f'{ Style .YELLOW } Last { user_cap } { Style .RESET } '
962+ u_hint = f' { Style .DIM } ◀▶{ Style .RESET } ' if u_cur else ''
963+ print (f' { u_arrow } { u_st } 👤 User Message Cap{ Style .RESET } { Style .DIM } (blocks){ Style .RESET } { u_label } { u_hint } ' )
964+
965+ # Agent Cap
966+ a_cur = (cursor == ROW_AGENT )
967+ a_arrow = f'{ Style .BOLD } { Style .YELLOW } ▸{ Style .RESET } ' if a_cur else ' '
968+ a_st = f'{ Style .BOLD } ' if a_cur else Style .DIM
969+ a_label = f'{ Style .DIM } ALL{ Style .RESET } ' if agent_cap == 0 else f'{ Style .YELLOW } Last { agent_cap } { Style .RESET } '
970+ a_hint = f' { Style .DIM } ◀▶{ Style .RESET } ' if a_cur else ''
971+ print (f' { a_arrow } { a_st } 🤖 Agent Message Cap{ Style .RESET } { Style .DIM } (blocks){ Style .RESET } { a_label } { a_hint } ' )
972+
973+ # Reason Cap
974+ r_cur = (cursor == ROW_REASON )
975+ r_arrow = f'{ Style .BOLD } { Style .YELLOW } ▸{ Style .RESET } ' if r_cur else ' '
976+ r_st = f'{ Style .BOLD } ' if r_cur else Style .DIM
977+ r_label = f'{ Style .DIM } ALL{ Style .RESET } ' if reason_cap == 0 else f'{ Style .YELLOW } Last { reason_cap } { Style .RESET } '
978+ r_hint = f' { Style .DIM } ◀▶{ Style .RESET } ' if r_cur else ''
979+ print (f' { r_arrow } { r_st } 🧠 Agent Reasoning Cap{ Style .RESET } { Style .DIM } (blocks){ Style .RESET } { r_label } { r_hint } ' )
942980
943981 print (f'\n { Style .DIM } { "━" * 62 } { Style .RESET } ' )
944982 bar_w = 30
@@ -962,16 +1000,28 @@ def get_lines_for_state(cap_out: int, cap_msg: int, cc: bool) -> Dict[str, int]:
9621000 if cursor == ROW_CAP :
9631001 cap_idx = max (0 , cap_idx - 1 )
9641002 output_cap = CAP_STEPS [cap_idx ]
965- elif cursor == ROW_MSG :
966- msg_idx = max (0 , msg_idx - 1 )
967- msg_cap = MSG_CAP_STEPS [msg_idx ]
1003+ elif cursor == ROW_USER :
1004+ u_idx = max (0 , u_idx - 1 )
1005+ user_cap = MSG_CAP_STEPS [u_idx ]
1006+ elif cursor == ROW_AGENT :
1007+ a_idx = max (0 , a_idx - 1 )
1008+ agent_cap = MSG_CAP_STEPS [a_idx ]
1009+ elif cursor == ROW_REASON :
1010+ r_idx = max (0 , r_idx - 1 )
1011+ reason_cap = MSG_CAP_STEPS [r_idx ]
9681012 elif key == 'RIGHT' :
9691013 if cursor == ROW_CAP :
9701014 cap_idx = min (len (CAP_STEPS ) - 1 , cap_idx + 1 )
9711015 output_cap = CAP_STEPS [cap_idx ]
972- elif cursor == ROW_MSG :
973- msg_idx = min (len (MSG_CAP_STEPS ) - 1 , msg_idx + 1 )
974- msg_cap = MSG_CAP_STEPS [msg_idx ]
1016+ elif cursor == ROW_USER :
1017+ u_idx = min (len (MSG_CAP_STEPS ) - 1 , u_idx + 1 )
1018+ user_cap = MSG_CAP_STEPS [u_idx ]
1019+ elif cursor == ROW_AGENT :
1020+ a_idx = min (len (MSG_CAP_STEPS ) - 1 , a_idx + 1 )
1021+ agent_cap = MSG_CAP_STEPS [a_idx ]
1022+ elif cursor == ROW_REASON :
1023+ r_idx = min (len (MSG_CAP_STEPS ) - 1 , r_idx + 1 )
1024+ reason_cap = MSG_CAP_STEPS [r_idx ]
9751025 elif key == 'A' :
9761026 for s in SECTION_DEFS : fstate [s [0 ]] = True
9771027 elif key == 'N' :
@@ -983,8 +1033,12 @@ def get_lines_for_state(cap_out: int, cap_msg: int, cc: bool) -> Dict[str, int]:
9831033 clean_content = False
9841034 output_cap = 8
9851035 cap_idx = CAP_STEPS .index (8 )
986- msg_cap = 0
987- msg_idx = MSG_CAP_STEPS .index (0 )
1036+ user_cap = 0
1037+ u_idx = 0
1038+ agent_cap = 0
1039+ a_idx = 0
1040+ reason_cap = 0
1041+ r_idx = 0
9881042 elif key == 'Q' or key == 'ESC' :
9891043 break
9901044 elif key .isdigit ():
@@ -997,7 +1051,7 @@ def get_lines_for_state(cap_out: int, cap_msg: int, cc: bool) -> Dict[str, int]:
9971051 for s in SECTION_DEFS : fstate [s [0 ]] = s [0 ] in pkeys
9981052 clean_content = pclean
9991053
1000- return fstate , clean_content , output_cap , msg_cap
1054+ return fstate , clean_content , output_cap , user_cap , agent_cap , reason_cap
10011055
10021056# ──────────────────────────────────────────────────────────────
10031057# Session List & Main Loop
@@ -1127,7 +1181,7 @@ def process_conversion(indices_str: str, files: List[Path]):
11271181 return
11281182
11291183 # Interactive filter
1130- section_filter , clean_content , output_cap , msg_cap = interactive_filter (parsers )
1184+ section_filter , clean_content , output_cap , user_cap , agent_cap , reason_cap = interactive_filter (parsers )
11311185
11321186 # Check anything is selected
11331187 if not any (section_filter .values ()):
@@ -1164,7 +1218,9 @@ def process_conversion(indices_str: str, files: List[Path]):
11641218 section_filter = section_filter ,
11651219 clean_content = clean_content ,
11661220 output_cap = output_cap ,
1167- msg_cap = msg_cap ,
1221+ user_cap = user_cap ,
1222+ agent_cap = agent_cap ,
1223+ reasoning_cap = reason_cap ,
11681224 )
11691225
11701226 date_prefix = datetime .fromtimestamp (
0 commit comments