From ee1fccbc89c4fc37fd4ac94f06718fba44514e02 Mon Sep 17 00:00:00 2001 From: luijait Date: Sat, 14 Jun 2025 18:40:00 +0200 Subject: [PATCH 1/3] Fix replay not matching numbers --- tools/replay.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/replay.py b/tools/replay.py index 107ff3c3..f902e840 100644 --- a/tools/replay.py +++ b/tools/replay.py @@ -250,7 +250,7 @@ def replay_conversation(messages: List[Dict], replay_delay: float = 0.5, usage: if role == "user": print(color(f"CAI> ", fg="cyan") + f"{content}") turn_counter += 1 - interaction_counter = 0 + # Don't reset interaction_counter to maintain numbering across user prompts # Handle assistant messages elif role == "assistant": From 80c5b5e1fe0e3c1b1c92a3714c2d7c270e669925 Mon Sep 17 00:00:00 2001 From: luijait Date: Sat, 14 Jun 2025 22:48:27 +0200 Subject: [PATCH 2/3] Fix virtualization CAI_STREAM=True errors with tool outputs --- src/cai/tools/common.py | 3 ++- src/cai/util.py | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/cai/tools/common.py b/src/cai/tools/common.py index 7bbb00df..e1aac760 100644 --- a/src/cai/tools/common.py +++ b/src/cai/tools/common.py @@ -744,7 +744,8 @@ async def _run_local_async(command, stdout=False, timeout=100, stream=False, cal # The SDK will handle ALL display when CAI_STREAM=false streaming_enabled = os.getenv("CAI_STREAM", "false").lower() == "true" - # Only display if we're in streaming mode AND parallel mode + # Only display panels if we're in streaming mode or parallel mode + # In streaming mode, the Live panels are handled by the streaming system if streaming_enabled and is_parallel: # Display the completed tool output from cai.util import cli_print_tool_output diff --git a/src/cai/util.py b/src/cai/util.py index be905abe..28276842 100644 --- a/src/cai/util.py +++ b/src/cai/util.py @@ -2825,9 +2825,10 @@ def cli_print_tool_output( # Check if we're in a container environment is_container = bool(os.getenv("CAI_ACTIVE_CONTAINER", "")) - # In parallel mode OR container mode, use static panels - if is_parallel or is_container: - # In parallel mode or container mode, use static panels to avoid Live context conflicts + # In parallel mode, use static panels + # For container mode, use Live panels to allow real-time updates + if is_parallel: + # In parallel mode, use static panels to avoid Live context conflicts # Check if we already printed this panel (shouldn't happen but be safe) if call_id not in _LIVE_STREAMING_PANELS: # For container mode with streaming, if this is the initial call but we already From 9000ee2ad41d8655528a8a2e13489596ebdccfa2 Mon Sep 17 00:00:00 2001 From: luijait Date: Sun, 15 Jun 2025 21:18:02 +0200 Subject: [PATCH 3/3] Fix duplicate output --- .../reconnaissance/generic_linux_command.py | 6 +- src/cai/util.py | 62 +++++++++++++++++-- 2 files changed, 63 insertions(+), 5 deletions(-) diff --git a/src/cai/tools/reconnaissance/generic_linux_command.py b/src/cai/tools/reconnaissance/generic_linux_command.py index eb0624d7..552e7b3f 100644 --- a/src/cai/tools/reconnaissance/generic_linux_command.py +++ b/src/cai/tools/reconnaissance/generic_linux_command.py @@ -133,11 +133,15 @@ async def generic_linux_command(command: str = "", else: timeout = 100 - # Tools always stream EXCEPT in parallel mode + # Tools always stream EXCEPT in parallel mode or when CAI_STREAM=False # In parallel mode, multiple agents run concurrently with Runner.run() # and streaming would create confusing overlapping outputs stream = True # Default to streaming + # Check if CAI_STREAM is explicitly set to False + if os.getenv("CAI_STREAM", "true").lower() == "false": + stream = False + # Simple heuristic: If CAI_PARALLEL > 1 AND we have a P agent ID, disable streaming # This is more reliable than trying to count active agents try: diff --git a/src/cai/util.py b/src/cai/util.py index 28276842..ddf92545 100644 --- a/src/cai/util.py +++ b/src/cai/util.py @@ -2901,7 +2901,11 @@ def cli_print_tool_output( # Use simple output _print_simple_tool_output(tool_name, args, output, execution_info, token_info) return - else: + + # Initialize is_first_display for later use + is_first_display = False + + if not streaming: # For non-streaming outputs, check if we've already seen this command streaming_enabled = os.getenv("CAI_STREAM", "false").lower() == "true" @@ -2928,11 +2932,11 @@ def cli_print_tool_output( if not output: return + # Check if this is first time display before adding to displayed commands + is_first_display = command_key not in cli_print_tool_output._displayed_commands + # Add to displayed commands since we're going to show it cli_print_tool_output._displayed_commands.add(command_key) - - # Track display time for better duplicate detection - cli_print_tool_output._command_display_times[command_key] = time.time() # For non-streaming updates with call_id, check if already seen # This _seen_calls logic is an additional layer for non-streaming calls that might have call_ids @@ -3067,12 +3071,62 @@ def cli_print_tool_output( title_align="left", ) + # When CAI_STREAM=false and this is the first display (not a duplicate), + # show a small command execution panel first + if not streaming_enabled and not streaming and is_first_display: + # Get agent name for the panel + agent_name = "" + if token_info and token_info.get("agent_name"): + agent_name = token_info.get("agent_name") + else: + agent_name = "Agent" + + # Extract the command from args + command_text = "" + if isinstance(display_args, dict): + if "command" in display_args: + command_text = display_args.get("command", "") + if "args" in display_args and display_args["args"]: + command_text += f" {display_args['args']}" + elif "full_command" in display_args: + command_text = display_args.get("full_command", "") + else: + # Fallback to string representation + command_text = str(display_args) + else: + command_text = str(display_args) + + # Create a small panel showing just the command being executed + command_panel = Panel( + f"[bold cyan]{command_text}[/bold cyan]", + title=f"[bold blue]{agent_name} - Executing Command[/bold blue]", + border_style="blue", + padding=(0, 1), + box=ROUNDED, + title_align="left", + width=None, # Auto width based on content + expand=False # Don't expand to full width + ) + + # Print the command panel + console.print(command_panel) + console.print() # Add spacing between panels + # Display the panel console.print(panel) + + # Track display time AFTER the panel is rendered + # This ensures accurate timing for duplicate detection + if not streaming and command_key: + cli_print_tool_output._command_display_times[command_key] = time.time() except (ImportError, Exception): # Fall back to simple output format without rich _print_simple_tool_output(tool_name, args, output, execution_info, token_info) + + # Also track display time for simple output + if not streaming and command_key: + cli_print_tool_output._command_display_times[command_key] = time.time() # Helper function to format tool arguments