massgen 0.0.3__py3-none-any.whl → 0.1.0__py3-none-any.whl
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.
Potentially problematic release.
This version of massgen might be problematic. Click here for more details.
- massgen/__init__.py +142 -8
- massgen/adapters/__init__.py +29 -0
- massgen/adapters/ag2_adapter.py +483 -0
- massgen/adapters/base.py +183 -0
- massgen/adapters/tests/__init__.py +0 -0
- massgen/adapters/tests/test_ag2_adapter.py +439 -0
- massgen/adapters/tests/test_agent_adapter.py +128 -0
- massgen/adapters/utils/__init__.py +2 -0
- massgen/adapters/utils/ag2_utils.py +236 -0
- massgen/adapters/utils/tests/__init__.py +0 -0
- massgen/adapters/utils/tests/test_ag2_utils.py +138 -0
- massgen/agent_config.py +329 -55
- massgen/api_params_handler/__init__.py +10 -0
- massgen/api_params_handler/_api_params_handler_base.py +99 -0
- massgen/api_params_handler/_chat_completions_api_params_handler.py +176 -0
- massgen/api_params_handler/_claude_api_params_handler.py +113 -0
- massgen/api_params_handler/_response_api_params_handler.py +130 -0
- massgen/backend/__init__.py +39 -4
- massgen/backend/azure_openai.py +385 -0
- massgen/backend/base.py +341 -69
- massgen/backend/base_with_mcp.py +1102 -0
- massgen/backend/capabilities.py +386 -0
- massgen/backend/chat_completions.py +577 -130
- massgen/backend/claude.py +1033 -537
- massgen/backend/claude_code.py +1203 -0
- massgen/backend/cli_base.py +209 -0
- massgen/backend/docs/BACKEND_ARCHITECTURE.md +126 -0
- massgen/backend/{CLAUDE_API_RESEARCH.md → docs/CLAUDE_API_RESEARCH.md} +18 -18
- massgen/backend/{GEMINI_API_DOCUMENTATION.md → docs/GEMINI_API_DOCUMENTATION.md} +9 -9
- massgen/backend/docs/Gemini MCP Integration Analysis.md +1050 -0
- massgen/backend/docs/MCP_IMPLEMENTATION_CLAUDE_BACKEND.md +177 -0
- massgen/backend/docs/MCP_INTEGRATION_RESPONSE_BACKEND.md +352 -0
- massgen/backend/docs/OPENAI_GPT5_MODELS.md +211 -0
- massgen/backend/{OPENAI_RESPONSES_API_FORMAT.md → docs/OPENAI_RESPONSE_API_TOOL_CALLS.md} +3 -3
- massgen/backend/docs/OPENAI_response_streaming.md +20654 -0
- massgen/backend/docs/inference_backend.md +257 -0
- massgen/backend/docs/permissions_and_context_files.md +1085 -0
- massgen/backend/external.py +126 -0
- massgen/backend/gemini.py +1850 -241
- massgen/backend/grok.py +40 -156
- massgen/backend/inference.py +156 -0
- massgen/backend/lmstudio.py +171 -0
- massgen/backend/response.py +1095 -322
- massgen/chat_agent.py +131 -113
- massgen/cli.py +1560 -275
- massgen/config_builder.py +2396 -0
- massgen/configs/BACKEND_CONFIGURATION.md +458 -0
- massgen/configs/README.md +559 -216
- massgen/configs/ag2/ag2_case_study.yaml +27 -0
- massgen/configs/ag2/ag2_coder.yaml +34 -0
- massgen/configs/ag2/ag2_coder_case_study.yaml +36 -0
- massgen/configs/ag2/ag2_gemini.yaml +27 -0
- massgen/configs/ag2/ag2_groupchat.yaml +108 -0
- massgen/configs/ag2/ag2_groupchat_gpt.yaml +118 -0
- massgen/configs/ag2/ag2_single_agent.yaml +21 -0
- massgen/configs/basic/multi/fast_timeout_example.yaml +37 -0
- massgen/configs/basic/multi/gemini_4o_claude.yaml +31 -0
- massgen/configs/basic/multi/gemini_gpt5nano_claude.yaml +36 -0
- massgen/configs/{gemini_4o_claude.yaml → basic/multi/geminicode_4o_claude.yaml} +3 -3
- massgen/configs/basic/multi/geminicode_gpt5nano_claude.yaml +36 -0
- massgen/configs/basic/multi/glm_gemini_claude.yaml +25 -0
- massgen/configs/basic/multi/gpt4o_audio_generation.yaml +30 -0
- massgen/configs/basic/multi/gpt4o_image_generation.yaml +31 -0
- massgen/configs/basic/multi/gpt5nano_glm_qwen.yaml +26 -0
- massgen/configs/basic/multi/gpt5nano_image_understanding.yaml +26 -0
- massgen/configs/{three_agents_default.yaml → basic/multi/three_agents_default.yaml} +8 -4
- massgen/configs/basic/multi/three_agents_opensource.yaml +27 -0
- massgen/configs/basic/multi/three_agents_vllm.yaml +20 -0
- massgen/configs/basic/multi/two_agents_gemini.yaml +19 -0
- massgen/configs/{two_agents.yaml → basic/multi/two_agents_gpt5.yaml} +14 -6
- massgen/configs/basic/multi/two_agents_opensource_lmstudio.yaml +31 -0
- massgen/configs/basic/multi/two_qwen_vllm_sglang.yaml +28 -0
- massgen/configs/{single_agent.yaml → basic/single/single_agent.yaml} +1 -1
- massgen/configs/{single_flash2.5.yaml → basic/single/single_flash2.5.yaml} +1 -2
- massgen/configs/basic/single/single_gemini2.5pro.yaml +16 -0
- massgen/configs/basic/single/single_gpt4o_audio_generation.yaml +22 -0
- massgen/configs/basic/single/single_gpt4o_image_generation.yaml +22 -0
- massgen/configs/basic/single/single_gpt4o_video_generation.yaml +24 -0
- massgen/configs/basic/single/single_gpt5nano.yaml +20 -0
- massgen/configs/basic/single/single_gpt5nano_file_search.yaml +18 -0
- massgen/configs/basic/single/single_gpt5nano_image_understanding.yaml +17 -0
- massgen/configs/basic/single/single_gptoss120b.yaml +15 -0
- massgen/configs/basic/single/single_openrouter_audio_understanding.yaml +15 -0
- massgen/configs/basic/single/single_qwen_video_understanding.yaml +15 -0
- massgen/configs/debug/code_execution/command_filtering_blacklist.yaml +29 -0
- massgen/configs/debug/code_execution/command_filtering_whitelist.yaml +28 -0
- massgen/configs/debug/code_execution/docker_verification.yaml +29 -0
- massgen/configs/debug/skip_coordination_test.yaml +27 -0
- massgen/configs/debug/test_sdk_migration.yaml +17 -0
- massgen/configs/docs/DISCORD_MCP_SETUP.md +208 -0
- massgen/configs/docs/TWITTER_MCP_ENESCINAR_SETUP.md +82 -0
- massgen/configs/providers/azure/azure_openai_multi.yaml +21 -0
- massgen/configs/providers/azure/azure_openai_single.yaml +19 -0
- massgen/configs/providers/claude/claude.yaml +14 -0
- massgen/configs/providers/gemini/gemini_gpt5nano.yaml +28 -0
- massgen/configs/providers/local/lmstudio.yaml +11 -0
- massgen/configs/providers/openai/gpt5.yaml +46 -0
- massgen/configs/providers/openai/gpt5_nano.yaml +46 -0
- massgen/configs/providers/others/grok_single_agent.yaml +19 -0
- massgen/configs/providers/others/zai_coding_team.yaml +108 -0
- massgen/configs/providers/others/zai_glm45.yaml +12 -0
- massgen/configs/{creative_team.yaml → teams/creative/creative_team.yaml} +16 -6
- massgen/configs/{travel_planning.yaml → teams/creative/travel_planning.yaml} +16 -6
- massgen/configs/{news_analysis.yaml → teams/research/news_analysis.yaml} +16 -6
- massgen/configs/{research_team.yaml → teams/research/research_team.yaml} +15 -7
- massgen/configs/{technical_analysis.yaml → teams/research/technical_analysis.yaml} +16 -6
- massgen/configs/tools/code-execution/basic_command_execution.yaml +25 -0
- massgen/configs/tools/code-execution/code_execution_use_case_simple.yaml +41 -0
- massgen/configs/tools/code-execution/docker_claude_code.yaml +32 -0
- massgen/configs/tools/code-execution/docker_multi_agent.yaml +32 -0
- massgen/configs/tools/code-execution/docker_simple.yaml +29 -0
- massgen/configs/tools/code-execution/docker_with_resource_limits.yaml +32 -0
- massgen/configs/tools/code-execution/multi_agent_playwright_automation.yaml +57 -0
- massgen/configs/tools/filesystem/cc_gpt5_gemini_filesystem.yaml +34 -0
- massgen/configs/tools/filesystem/claude_code_context_sharing.yaml +68 -0
- massgen/configs/tools/filesystem/claude_code_flash2.5.yaml +43 -0
- massgen/configs/tools/filesystem/claude_code_flash2.5_gptoss.yaml +49 -0
- massgen/configs/tools/filesystem/claude_code_gpt5nano.yaml +31 -0
- massgen/configs/tools/filesystem/claude_code_single.yaml +40 -0
- massgen/configs/tools/filesystem/fs_permissions_test.yaml +87 -0
- massgen/configs/tools/filesystem/gemini_gemini_workspace_cleanup.yaml +54 -0
- massgen/configs/tools/filesystem/gemini_gpt5_filesystem_casestudy.yaml +30 -0
- massgen/configs/tools/filesystem/gemini_gpt5nano_file_context_path.yaml +43 -0
- massgen/configs/tools/filesystem/gemini_gpt5nano_protected_paths.yaml +45 -0
- massgen/configs/tools/filesystem/gpt5mini_cc_fs_context_path.yaml +31 -0
- massgen/configs/tools/filesystem/grok4_gpt5_gemini_filesystem.yaml +32 -0
- massgen/configs/tools/filesystem/multiturn/grok4_gpt5_claude_code_filesystem_multiturn.yaml +58 -0
- massgen/configs/tools/filesystem/multiturn/grok4_gpt5_gemini_filesystem_multiturn.yaml +58 -0
- massgen/configs/tools/filesystem/multiturn/two_claude_code_filesystem_multiturn.yaml +47 -0
- massgen/configs/tools/filesystem/multiturn/two_gemini_flash_filesystem_multiturn.yaml +48 -0
- massgen/configs/tools/mcp/claude_code_discord_mcp_example.yaml +27 -0
- massgen/configs/tools/mcp/claude_code_simple_mcp.yaml +35 -0
- massgen/configs/tools/mcp/claude_code_twitter_mcp_example.yaml +32 -0
- massgen/configs/tools/mcp/claude_mcp_example.yaml +24 -0
- massgen/configs/tools/mcp/claude_mcp_test.yaml +27 -0
- massgen/configs/tools/mcp/five_agents_travel_mcp_test.yaml +157 -0
- massgen/configs/tools/mcp/five_agents_weather_mcp_test.yaml +103 -0
- massgen/configs/tools/mcp/gemini_mcp_example.yaml +24 -0
- massgen/configs/tools/mcp/gemini_mcp_filesystem_test.yaml +23 -0
- massgen/configs/tools/mcp/gemini_mcp_filesystem_test_sharing.yaml +23 -0
- massgen/configs/tools/mcp/gemini_mcp_filesystem_test_single_agent.yaml +17 -0
- massgen/configs/tools/mcp/gemini_mcp_filesystem_test_with_claude_code.yaml +24 -0
- massgen/configs/tools/mcp/gemini_mcp_test.yaml +27 -0
- massgen/configs/tools/mcp/gemini_notion_mcp.yaml +52 -0
- massgen/configs/tools/mcp/gpt5_nano_mcp_example.yaml +24 -0
- massgen/configs/tools/mcp/gpt5_nano_mcp_test.yaml +27 -0
- massgen/configs/tools/mcp/gpt5mini_claude_code_discord_mcp_example.yaml +38 -0
- massgen/configs/tools/mcp/gpt_oss_mcp_example.yaml +25 -0
- massgen/configs/tools/mcp/gpt_oss_mcp_test.yaml +28 -0
- massgen/configs/tools/mcp/grok3_mini_mcp_example.yaml +24 -0
- massgen/configs/tools/mcp/grok3_mini_mcp_test.yaml +27 -0
- massgen/configs/tools/mcp/multimcp_gemini.yaml +111 -0
- massgen/configs/tools/mcp/qwen_api_mcp_example.yaml +25 -0
- massgen/configs/tools/mcp/qwen_api_mcp_test.yaml +28 -0
- massgen/configs/tools/mcp/qwen_local_mcp_example.yaml +24 -0
- massgen/configs/tools/mcp/qwen_local_mcp_test.yaml +27 -0
- massgen/configs/tools/planning/five_agents_discord_mcp_planning_mode.yaml +140 -0
- massgen/configs/tools/planning/five_agents_filesystem_mcp_planning_mode.yaml +151 -0
- massgen/configs/tools/planning/five_agents_notion_mcp_planning_mode.yaml +151 -0
- massgen/configs/tools/planning/five_agents_twitter_mcp_planning_mode.yaml +155 -0
- massgen/configs/tools/planning/gpt5_mini_case_study_mcp_planning_mode.yaml +73 -0
- massgen/configs/tools/web-search/claude_streamable_http_test.yaml +43 -0
- massgen/configs/tools/web-search/gemini_streamable_http_test.yaml +43 -0
- massgen/configs/tools/web-search/gpt5_mini_streamable_http_test.yaml +43 -0
- massgen/configs/tools/web-search/gpt_oss_streamable_http_test.yaml +44 -0
- massgen/configs/tools/web-search/grok3_mini_streamable_http_test.yaml +43 -0
- massgen/configs/tools/web-search/qwen_api_streamable_http_test.yaml +44 -0
- massgen/configs/tools/web-search/qwen_local_streamable_http_test.yaml +43 -0
- massgen/coordination_tracker.py +708 -0
- massgen/docker/README.md +462 -0
- massgen/filesystem_manager/__init__.py +21 -0
- massgen/filesystem_manager/_base.py +9 -0
- massgen/filesystem_manager/_code_execution_server.py +545 -0
- massgen/filesystem_manager/_docker_manager.py +477 -0
- massgen/filesystem_manager/_file_operation_tracker.py +248 -0
- massgen/filesystem_manager/_filesystem_manager.py +813 -0
- massgen/filesystem_manager/_path_permission_manager.py +1261 -0
- massgen/filesystem_manager/_workspace_tools_server.py +1815 -0
- massgen/formatter/__init__.py +10 -0
- massgen/formatter/_chat_completions_formatter.py +284 -0
- massgen/formatter/_claude_formatter.py +235 -0
- massgen/formatter/_formatter_base.py +156 -0
- massgen/formatter/_response_formatter.py +263 -0
- massgen/frontend/__init__.py +1 -2
- massgen/frontend/coordination_ui.py +471 -286
- massgen/frontend/displays/base_display.py +56 -11
- massgen/frontend/displays/create_coordination_table.py +1956 -0
- massgen/frontend/displays/rich_terminal_display.py +1259 -619
- massgen/frontend/displays/simple_display.py +9 -4
- massgen/frontend/displays/terminal_display.py +27 -68
- massgen/logger_config.py +681 -0
- massgen/mcp_tools/README.md +232 -0
- massgen/mcp_tools/__init__.py +105 -0
- massgen/mcp_tools/backend_utils.py +1035 -0
- massgen/mcp_tools/circuit_breaker.py +195 -0
- massgen/mcp_tools/client.py +894 -0
- massgen/mcp_tools/config_validator.py +138 -0
- massgen/mcp_tools/docs/circuit_breaker.md +646 -0
- massgen/mcp_tools/docs/client.md +950 -0
- massgen/mcp_tools/docs/config_validator.md +478 -0
- massgen/mcp_tools/docs/exceptions.md +1165 -0
- massgen/mcp_tools/docs/security.md +854 -0
- massgen/mcp_tools/exceptions.py +338 -0
- massgen/mcp_tools/hooks.py +212 -0
- massgen/mcp_tools/security.py +780 -0
- massgen/message_templates.py +342 -64
- massgen/orchestrator.py +1515 -241
- massgen/stream_chunk/__init__.py +35 -0
- massgen/stream_chunk/base.py +92 -0
- massgen/stream_chunk/multimodal.py +237 -0
- massgen/stream_chunk/text.py +162 -0
- massgen/tests/mcp_test_server.py +150 -0
- massgen/tests/multi_turn_conversation_design.md +0 -8
- massgen/tests/test_azure_openai_backend.py +156 -0
- massgen/tests/test_backend_capabilities.py +262 -0
- massgen/tests/test_backend_event_loop_all.py +179 -0
- massgen/tests/test_chat_completions_refactor.py +142 -0
- massgen/tests/test_claude_backend.py +15 -28
- massgen/tests/test_claude_code.py +268 -0
- massgen/tests/test_claude_code_context_sharing.py +233 -0
- massgen/tests/test_claude_code_orchestrator.py +175 -0
- massgen/tests/test_cli_backends.py +180 -0
- massgen/tests/test_code_execution.py +679 -0
- massgen/tests/test_external_agent_backend.py +134 -0
- massgen/tests/test_final_presentation_fallback.py +237 -0
- massgen/tests/test_gemini_planning_mode.py +351 -0
- massgen/tests/test_grok_backend.py +7 -10
- massgen/tests/test_http_mcp_server.py +42 -0
- massgen/tests/test_integration_simple.py +198 -0
- massgen/tests/test_mcp_blocking.py +125 -0
- massgen/tests/test_message_context_building.py +29 -47
- massgen/tests/test_orchestrator_final_presentation.py +48 -0
- massgen/tests/test_path_permission_manager.py +2087 -0
- massgen/tests/test_rich_terminal_display.py +14 -13
- massgen/tests/test_timeout.py +133 -0
- massgen/tests/test_v3_3agents.py +11 -12
- massgen/tests/test_v3_simple.py +8 -13
- massgen/tests/test_v3_three_agents.py +11 -18
- massgen/tests/test_v3_two_agents.py +8 -13
- massgen/token_manager/__init__.py +7 -0
- massgen/token_manager/token_manager.py +400 -0
- massgen/utils.py +52 -16
- massgen/v1/agent.py +45 -91
- massgen/v1/agents.py +18 -53
- massgen/v1/backends/gemini.py +50 -153
- massgen/v1/backends/grok.py +21 -54
- massgen/v1/backends/oai.py +39 -111
- massgen/v1/cli.py +36 -93
- massgen/v1/config.py +8 -12
- massgen/v1/logging.py +43 -127
- massgen/v1/main.py +18 -32
- massgen/v1/orchestrator.py +68 -209
- massgen/v1/streaming_display.py +62 -163
- massgen/v1/tools.py +8 -12
- massgen/v1/types.py +9 -23
- massgen/v1/utils.py +5 -23
- massgen-0.1.0.dist-info/METADATA +1245 -0
- massgen-0.1.0.dist-info/RECORD +273 -0
- massgen-0.1.0.dist-info/entry_points.txt +2 -0
- massgen/frontend/logging/__init__.py +0 -9
- massgen/frontend/logging/realtime_logger.py +0 -197
- massgen-0.0.3.dist-info/METADATA +0 -568
- massgen-0.0.3.dist-info/RECORD +0 -76
- massgen-0.0.3.dist-info/entry_points.txt +0 -2
- /massgen/backend/{Function calling openai responses.md → docs/Function calling openai responses.md} +0 -0
- {massgen-0.0.3.dist-info → massgen-0.1.0.dist-info}/WHEEL +0 -0
- {massgen-0.0.3.dist-info → massgen-0.1.0.dist-info}/licenses/LICENSE +0 -0
- {massgen-0.0.3.dist-info → massgen-0.1.0.dist-info}/top_level.txt +0 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
1
3
|
"""
|
|
2
4
|
Rich Terminal Display for MassGen Coordination
|
|
3
5
|
|
|
@@ -5,54 +7,56 @@ Enhanced terminal interface using Rich library with live updates,
|
|
|
5
7
|
beautiful formatting, code highlighting, and responsive layout.
|
|
6
8
|
"""
|
|
7
9
|
|
|
8
|
-
import re
|
|
9
|
-
import time
|
|
10
|
-
import threading
|
|
11
|
-
import asyncio
|
|
12
10
|
import os
|
|
13
|
-
import
|
|
14
|
-
import select
|
|
15
|
-
import tty
|
|
16
|
-
import termios
|
|
17
|
-
import subprocess
|
|
11
|
+
import re
|
|
18
12
|
import signal
|
|
19
|
-
|
|
13
|
+
import subprocess
|
|
14
|
+
import sys
|
|
15
|
+
import threading
|
|
16
|
+
import time
|
|
17
|
+
|
|
18
|
+
# Unix-specific imports (not available on Windows)
|
|
19
|
+
try:
|
|
20
|
+
import select
|
|
21
|
+
import termios
|
|
22
|
+
|
|
23
|
+
UNIX_TERMINAL_SUPPORT = True
|
|
24
|
+
except ImportError:
|
|
25
|
+
UNIX_TERMINAL_SUPPORT = False
|
|
20
26
|
from concurrent.futures import ThreadPoolExecutor
|
|
21
|
-
from
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from typing import Any, Dict, List, Optional
|
|
29
|
+
|
|
22
30
|
from .terminal_display import TerminalDisplay
|
|
23
31
|
|
|
24
32
|
try:
|
|
33
|
+
from rich.align import Align
|
|
34
|
+
from rich.box import DOUBLE, ROUNDED
|
|
35
|
+
from rich.columns import Columns
|
|
25
36
|
from rich.console import Console
|
|
37
|
+
from rich.layout import Layout
|
|
26
38
|
from rich.live import Live
|
|
27
39
|
from rich.panel import Panel
|
|
28
|
-
from rich.columns import Columns
|
|
29
|
-
from rich.table import Table
|
|
30
|
-
from rich.syntax import Syntax
|
|
31
40
|
from rich.text import Text
|
|
32
|
-
from rich.layout import Layout
|
|
33
|
-
from rich.align import Align
|
|
34
|
-
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
35
|
-
from rich.status import Status
|
|
36
|
-
from rich.box import ROUNDED, HEAVY, DOUBLE
|
|
37
41
|
|
|
38
42
|
RICH_AVAILABLE = True
|
|
39
43
|
except ImportError:
|
|
40
44
|
RICH_AVAILABLE = False
|
|
41
45
|
|
|
42
46
|
# Provide dummy classes when Rich is not available
|
|
43
|
-
class Layout:
|
|
47
|
+
class Layout: # type: ignore[no-redef]
|
|
44
48
|
pass
|
|
45
49
|
|
|
46
|
-
class Panel:
|
|
50
|
+
class Panel: # type: ignore[no-redef]
|
|
47
51
|
pass
|
|
48
52
|
|
|
49
|
-
class Console:
|
|
53
|
+
class Console: # type: ignore[no-redef]
|
|
50
54
|
pass
|
|
51
55
|
|
|
52
|
-
class Live:
|
|
56
|
+
class Live: # type: ignore[no-redef]
|
|
53
57
|
pass
|
|
54
58
|
|
|
55
|
-
class Columns:
|
|
59
|
+
class Columns: # type: ignore[no-redef]
|
|
56
60
|
pass
|
|
57
61
|
|
|
58
62
|
class Table:
|
|
@@ -61,10 +65,10 @@ except ImportError:
|
|
|
61
65
|
class Syntax:
|
|
62
66
|
pass
|
|
63
67
|
|
|
64
|
-
class Text:
|
|
68
|
+
class Text: # type: ignore[no-redef]
|
|
65
69
|
pass
|
|
66
70
|
|
|
67
|
-
class Align:
|
|
71
|
+
class Align: # type: ignore[no-redef]
|
|
68
72
|
pass
|
|
69
73
|
|
|
70
74
|
class Progress:
|
|
@@ -79,13 +83,13 @@ except ImportError:
|
|
|
79
83
|
class Status:
|
|
80
84
|
pass
|
|
81
85
|
|
|
82
|
-
ROUNDED =
|
|
86
|
+
ROUNDED = DOUBLE = None # type: ignore[assignment]
|
|
83
87
|
|
|
84
88
|
|
|
85
89
|
class RichTerminalDisplay(TerminalDisplay):
|
|
86
90
|
"""Enhanced terminal display using Rich library for beautiful formatting."""
|
|
87
91
|
|
|
88
|
-
def __init__(self, agent_ids: List[str], **kwargs):
|
|
92
|
+
def __init__(self, agent_ids: List[str], **kwargs: Any) -> None:
|
|
89
93
|
"""Initialize rich terminal display.
|
|
90
94
|
|
|
91
95
|
Args:
|
|
@@ -105,19 +109,23 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
105
109
|
"""
|
|
106
110
|
if not RICH_AVAILABLE:
|
|
107
111
|
raise ImportError(
|
|
108
|
-
"Rich library is required for RichTerminalDisplay. "
|
|
109
|
-
"Install with: pip install rich"
|
|
112
|
+
"Rich library is required for RichTerminalDisplay. " "Install with: pip install rich",
|
|
110
113
|
)
|
|
111
114
|
|
|
112
115
|
super().__init__(agent_ids, **kwargs)
|
|
113
116
|
|
|
114
117
|
# Terminal performance detection and adaptive refresh rate
|
|
115
118
|
self._terminal_performance = self._detect_terminal_performance()
|
|
116
|
-
self.refresh_rate = self._get_adaptive_refresh_rate(
|
|
119
|
+
self.refresh_rate = self._get_adaptive_refresh_rate(
|
|
120
|
+
kwargs.get("refresh_rate"),
|
|
121
|
+
)
|
|
117
122
|
|
|
118
123
|
# Rich-specific configuration
|
|
119
124
|
self.theme = kwargs.get("theme", "dark")
|
|
120
|
-
self.enable_syntax_highlighting = kwargs.get(
|
|
125
|
+
self.enable_syntax_highlighting = kwargs.get(
|
|
126
|
+
"enable_syntax_highlighting",
|
|
127
|
+
True,
|
|
128
|
+
)
|
|
121
129
|
self.max_content_lines = kwargs.get("max_content_lines", 8)
|
|
122
130
|
self.max_line_length = kwargs.get("max_line_length", 100)
|
|
123
131
|
self.show_timestamps = kwargs.get("show_timestamps", True)
|
|
@@ -128,10 +136,12 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
128
136
|
# Dynamic column width calculation - will be updated on resize
|
|
129
137
|
self.num_agents = len(agent_ids)
|
|
130
138
|
self.fixed_column_width = max(
|
|
131
|
-
20,
|
|
139
|
+
20,
|
|
140
|
+
self.terminal_size.width // self.num_agents - 1,
|
|
132
141
|
)
|
|
133
142
|
self.agent_panel_height = max(
|
|
134
|
-
10,
|
|
143
|
+
10,
|
|
144
|
+
self.terminal_size.height - 13,
|
|
135
145
|
) # Reserve space for header(5) + footer(8)
|
|
136
146
|
|
|
137
147
|
self.orchestrator = kwargs.get("orchestrator", None)
|
|
@@ -149,46 +159,54 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
149
159
|
self._full_refresh_interval = self._get_adaptive_full_refresh_interval()
|
|
150
160
|
|
|
151
161
|
# Performance monitoring
|
|
152
|
-
self._refresh_times = []
|
|
162
|
+
self._refresh_times: List[float] = []
|
|
153
163
|
self._dropped_frames = 0
|
|
154
164
|
self._performance_check_interval = 5.0 # Check performance every 5 seconds
|
|
155
165
|
|
|
156
166
|
# Async refresh components - more workers for faster updates
|
|
157
167
|
self._refresh_executor = ThreadPoolExecutor(
|
|
158
|
-
max_workers=min(len(agent_ids) * 2 + 8, 20)
|
|
168
|
+
max_workers=min(len(agent_ids) * 2 + 8, 20),
|
|
159
169
|
)
|
|
160
|
-
self._agent_panels_cache = {}
|
|
170
|
+
self._agent_panels_cache: Dict[str, Panel] = {}
|
|
161
171
|
self._header_cache = None
|
|
162
172
|
self._footer_cache = None
|
|
163
173
|
self._layout_update_lock = threading.Lock()
|
|
164
|
-
self._pending_updates = set()
|
|
174
|
+
self._pending_updates: set[str] = set()
|
|
165
175
|
self._shutdown_flag = False
|
|
166
176
|
|
|
167
177
|
# Priority update queue for critical status changes
|
|
168
|
-
self._priority_updates = set()
|
|
178
|
+
self._priority_updates: set[str] = set()
|
|
169
179
|
self._status_update_executor = ThreadPoolExecutor(max_workers=4)
|
|
170
180
|
|
|
171
181
|
# Theme configuration
|
|
172
182
|
self._setup_theme()
|
|
173
183
|
|
|
174
184
|
# Interactive mode variables
|
|
175
|
-
self._keyboard_interactive_mode = kwargs.get(
|
|
185
|
+
self._keyboard_interactive_mode = kwargs.get(
|
|
186
|
+
"keyboard_interactive_mode",
|
|
187
|
+
True,
|
|
188
|
+
)
|
|
176
189
|
self._safe_keyboard_mode = kwargs.get(
|
|
177
|
-
"safe_keyboard_mode",
|
|
190
|
+
"safe_keyboard_mode",
|
|
191
|
+
False,
|
|
178
192
|
) # Non-interfering keyboard mode
|
|
179
193
|
self._key_handler = None
|
|
180
194
|
self._input_thread = None
|
|
181
195
|
self._stop_input_thread = False
|
|
182
196
|
self._original_settings = None
|
|
183
|
-
self._agent_selector_active =
|
|
184
|
-
False # Flag to prevent duplicate agent selector calls
|
|
185
|
-
)
|
|
197
|
+
self._agent_selector_active = False # Flag to prevent duplicate agent selector calls
|
|
186
198
|
|
|
187
199
|
# Store final presentation for re-display
|
|
188
200
|
self._stored_final_presentation = None
|
|
189
201
|
self._stored_presentation_agent = None
|
|
190
202
|
self._stored_vote_results = None
|
|
191
203
|
|
|
204
|
+
# Final presentation display state
|
|
205
|
+
self._final_presentation_active = False
|
|
206
|
+
self._final_presentation_content = ""
|
|
207
|
+
self._final_presentation_agent = None
|
|
208
|
+
self._final_presentation_vote_results = None
|
|
209
|
+
|
|
192
210
|
# Code detection patterns
|
|
193
211
|
self.code_patterns = [
|
|
194
212
|
r"```(\w+)?\n(.*?)\n```", # Markdown code blocks
|
|
@@ -209,16 +227,21 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
209
227
|
self._last_content_hash = {agent_id: "" for agent_id in agent_ids}
|
|
210
228
|
|
|
211
229
|
# Adaptive debounce mechanism for updates
|
|
212
|
-
self._debounce_timers = {}
|
|
230
|
+
self._debounce_timers: Dict[str, threading.Timer] = {}
|
|
213
231
|
self._debounce_delay = self._get_adaptive_debounce_delay()
|
|
214
232
|
|
|
215
233
|
# Layered refresh strategy
|
|
216
|
-
self._critical_updates = set() # Status changes, errors, tool results
|
|
217
|
-
self._normal_updates = set() # Text content, thinking updates
|
|
218
|
-
self._decorative_updates = set() # Progress bars, timestamps
|
|
234
|
+
self._critical_updates: set[str] = set() # Status changes, errors, tool results
|
|
235
|
+
self._normal_updates: set[str] = set() # Text content, thinking updates
|
|
236
|
+
self._decorative_updates: set[str] = set() # Progress bars, timestamps
|
|
219
237
|
|
|
220
238
|
# Message filtering settings - tool content always important
|
|
221
|
-
self._important_content_types = {
|
|
239
|
+
self._important_content_types = {
|
|
240
|
+
"presentation",
|
|
241
|
+
"status",
|
|
242
|
+
"tool",
|
|
243
|
+
"error",
|
|
244
|
+
}
|
|
222
245
|
self._status_change_keywords = {
|
|
223
246
|
"completed",
|
|
224
247
|
"failed",
|
|
@@ -244,29 +267,42 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
244
267
|
|
|
245
268
|
# Status jump mechanism for web search interruption
|
|
246
269
|
self._status_jump_enabled = kwargs.get(
|
|
247
|
-
"enable_status_jump",
|
|
270
|
+
"enable_status_jump",
|
|
271
|
+
True,
|
|
248
272
|
) # Enable jumping to latest status
|
|
249
273
|
self._web_search_truncate_on_status_change = kwargs.get(
|
|
250
|
-
"truncate_web_search_on_status_change",
|
|
274
|
+
"truncate_web_search_on_status_change",
|
|
275
|
+
True,
|
|
251
276
|
) # Truncate web search content on status changes
|
|
252
277
|
self._max_web_search_lines = kwargs.get(
|
|
253
|
-
"max_web_search_lines_on_status_change",
|
|
278
|
+
"max_web_search_lines_on_status_change",
|
|
279
|
+
3,
|
|
254
280
|
) # Maximum lines to keep from web search when status changes
|
|
255
281
|
|
|
256
282
|
# Flush output configuration for final answer display
|
|
257
283
|
self._enable_flush_output = kwargs.get(
|
|
258
|
-
"enable_flush_output",
|
|
284
|
+
"enable_flush_output",
|
|
285
|
+
True,
|
|
259
286
|
) # Enable flush output for final answer
|
|
260
287
|
self._flush_char_delay = kwargs.get(
|
|
261
|
-
"flush_char_delay",
|
|
288
|
+
"flush_char_delay",
|
|
289
|
+
0.03,
|
|
262
290
|
) # Delay between characters
|
|
263
291
|
self._flush_word_delay = kwargs.get(
|
|
264
|
-
"flush_word_delay",
|
|
292
|
+
"flush_word_delay",
|
|
293
|
+
0.08,
|
|
265
294
|
) # Extra delay after punctuation
|
|
266
295
|
|
|
267
296
|
# File-based output system
|
|
268
|
-
|
|
269
|
-
|
|
297
|
+
# Use centralized log session directory
|
|
298
|
+
from massgen.logger_config import get_log_session_dir
|
|
299
|
+
|
|
300
|
+
log_session_dir = get_log_session_dir()
|
|
301
|
+
self.output_dir = kwargs.get(
|
|
302
|
+
"output_dir",
|
|
303
|
+
log_session_dir / "agent_outputs",
|
|
304
|
+
)
|
|
305
|
+
self.agent_files: Dict[str, Path] = {}
|
|
270
306
|
self.system_status_file = None
|
|
271
307
|
self._selected_agent = None
|
|
272
308
|
self._setup_agent_files()
|
|
@@ -282,7 +318,7 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
282
318
|
self._batch_timer = None
|
|
283
319
|
self._batch_timeout = self._get_adaptive_batch_timeout()
|
|
284
320
|
|
|
285
|
-
def _setup_resize_handler(self):
|
|
321
|
+
def _setup_resize_handler(self) -> None:
|
|
286
322
|
"""Setup SIGWINCH signal handler for terminal resize detection."""
|
|
287
323
|
if not sys.stdin.isatty():
|
|
288
324
|
return # Skip if not running in a terminal
|
|
@@ -294,12 +330,15 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
294
330
|
# SIGWINCH might not be available on all platforms
|
|
295
331
|
pass
|
|
296
332
|
|
|
297
|
-
def _handle_resize_signal(self, signum, frame):
|
|
333
|
+
def _handle_resize_signal(self, signum: int, frame: Any) -> None:
|
|
298
334
|
"""Handle SIGWINCH signal when terminal is resized."""
|
|
299
335
|
# Use a separate thread to handle resize to avoid signal handler restrictions
|
|
300
|
-
threading.Thread(
|
|
336
|
+
threading.Thread(
|
|
337
|
+
target=self._handle_terminal_resize,
|
|
338
|
+
daemon=True,
|
|
339
|
+
).start()
|
|
301
340
|
|
|
302
|
-
def _handle_terminal_resize(self):
|
|
341
|
+
def _handle_terminal_resize(self) -> None:
|
|
303
342
|
"""Handle terminal resize by recalculating layout and refreshing display."""
|
|
304
343
|
with self._resize_lock:
|
|
305
344
|
try:
|
|
@@ -313,13 +352,8 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
313
352
|
new_size = self.console.size
|
|
314
353
|
|
|
315
354
|
# Check if size actually changed
|
|
316
|
-
if
|
|
317
|
-
new_size.width != self.terminal_size.width
|
|
318
|
-
or new_size.height != self.terminal_size.height
|
|
319
|
-
):
|
|
320
|
-
|
|
355
|
+
if new_size.width != self.terminal_size.width or new_size.height != self.terminal_size.height:
|
|
321
356
|
# Update stored terminal size
|
|
322
|
-
old_size = self.terminal_size
|
|
323
357
|
self.terminal_size = new_size
|
|
324
358
|
|
|
325
359
|
# VSCode-specific post-resize delay
|
|
@@ -350,23 +384,24 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
350
384
|
# Silently handle errors to avoid disrupting the application
|
|
351
385
|
pass
|
|
352
386
|
|
|
353
|
-
def _recalculate_layout(self):
|
|
387
|
+
def _recalculate_layout(self) -> None:
|
|
354
388
|
"""Recalculate layout dimensions based on current terminal size."""
|
|
355
389
|
# Recalculate column width
|
|
356
390
|
self.fixed_column_width = max(
|
|
357
|
-
20,
|
|
391
|
+
20,
|
|
392
|
+
self.terminal_size.width // self.num_agents - 1,
|
|
358
393
|
)
|
|
359
394
|
|
|
360
395
|
# Recalculate panel height (reserve space for header and footer)
|
|
361
396
|
self.agent_panel_height = max(10, self.terminal_size.height - 13)
|
|
362
397
|
|
|
363
|
-
def _invalidate_display_cache(self):
|
|
398
|
+
def _invalidate_display_cache(self) -> None:
|
|
364
399
|
"""Invalidate all cached display components to force refresh."""
|
|
365
400
|
self._agent_panels_cache.clear()
|
|
366
401
|
self._header_cache = None
|
|
367
402
|
self._footer_cache = None
|
|
368
403
|
|
|
369
|
-
def _setup_agent_files(self):
|
|
404
|
+
def _setup_agent_files(self) -> None:
|
|
370
405
|
"""Setup individual txt files for each agent and system status file."""
|
|
371
406
|
# Create output directory if it doesn't exist
|
|
372
407
|
Path(self.output_dir).mkdir(parents=True, exist_ok=True)
|
|
@@ -381,10 +416,10 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
381
416
|
|
|
382
417
|
# Initialize system status file
|
|
383
418
|
self.system_status_file = Path(self.output_dir) / "system_status.txt"
|
|
384
|
-
with open(self.system_status_file, "w", encoding="utf-8") as f:
|
|
419
|
+
with open(str(self.system_status_file), "w", encoding="utf-8") as f:
|
|
385
420
|
f.write("=== SYSTEM STATUS LOG ===\n\n")
|
|
386
421
|
|
|
387
|
-
def _detect_terminal_performance(self):
|
|
422
|
+
def _detect_terminal_performance(self) -> Dict[str, Any]:
|
|
388
423
|
"""Detect terminal performance characteristics for adaptive refresh rates."""
|
|
389
424
|
terminal_info = {
|
|
390
425
|
"type": "unknown",
|
|
@@ -404,18 +439,16 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
404
439
|
terminal_info["performance_tier"] = "high"
|
|
405
440
|
terminal_info["type"] = "iterm"
|
|
406
441
|
terminal_info["supports_unicode"] = True
|
|
407
|
-
elif (
|
|
408
|
-
"vscode" in term_program
|
|
409
|
-
or "code" in term_program
|
|
410
|
-
or self._detect_vscode_terminal()
|
|
411
|
-
):
|
|
442
|
+
elif "vscode" in term_program or "code" in term_program or self._detect_vscode_terminal():
|
|
412
443
|
# VSCode integrated terminal - needs special handling for flaky behavior
|
|
413
444
|
terminal_info["performance_tier"] = "medium"
|
|
414
445
|
terminal_info["type"] = "vscode"
|
|
415
446
|
terminal_info["supports_unicode"] = True
|
|
416
|
-
|
|
447
|
+
# VSCode has good buffering
|
|
448
|
+
terminal_info["buffer_size"] = "large"
|
|
417
449
|
terminal_info["needs_flush_delay"] = True # Reduce flicker
|
|
418
|
-
|
|
450
|
+
# Add stability delays
|
|
451
|
+
terminal_info["refresh_stabilization"] = True
|
|
419
452
|
elif "apple_terminal" in term_program or term_program == "terminal":
|
|
420
453
|
terminal_info["performance_tier"] = "high"
|
|
421
454
|
terminal_info["type"] = "macos_terminal"
|
|
@@ -424,7 +457,8 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
424
457
|
terminal_info["performance_tier"] = "high"
|
|
425
458
|
terminal_info["type"] = "modern"
|
|
426
459
|
elif "screen" in term or "tmux" in term:
|
|
427
|
-
|
|
460
|
+
# Multiplexers are slower
|
|
461
|
+
terminal_info["performance_tier"] = "low"
|
|
428
462
|
terminal_info["type"] = "multiplexer"
|
|
429
463
|
elif "xterm" in term:
|
|
430
464
|
terminal_info["performance_tier"] = "medium"
|
|
@@ -435,7 +469,9 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
435
469
|
terminal_info["supports_unicode"] = False
|
|
436
470
|
|
|
437
471
|
# Check for SSH (typically slower)
|
|
438
|
-
if os.environ.get("SSH_CONNECTION") or os.environ.get(
|
|
472
|
+
if os.environ.get("SSH_CONNECTION") or os.environ.get(
|
|
473
|
+
"SSH_CLIENT",
|
|
474
|
+
):
|
|
439
475
|
if terminal_info["performance_tier"] == "high":
|
|
440
476
|
terminal_info["performance_tier"] = "medium"
|
|
441
477
|
elif terminal_info["performance_tier"] == "medium":
|
|
@@ -454,7 +490,7 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
454
490
|
|
|
455
491
|
return terminal_info
|
|
456
492
|
|
|
457
|
-
def _detect_vscode_terminal(self):
|
|
493
|
+
def _detect_vscode_terminal(self) -> bool:
|
|
458
494
|
"""Additional VSCode terminal detection using multiple indicators."""
|
|
459
495
|
try:
|
|
460
496
|
# Check for VSCode-specific environment variables
|
|
@@ -477,25 +513,24 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
477
513
|
|
|
478
514
|
current_process = psutil.Process()
|
|
479
515
|
parent = current_process.parent()
|
|
480
|
-
if parent and (
|
|
481
|
-
"code" in parent.name().lower() or "vscode" in parent.name().lower()
|
|
482
|
-
):
|
|
516
|
+
if parent and ("code" in parent.name().lower() or "vscode" in parent.name().lower()):
|
|
483
517
|
return True
|
|
484
518
|
except (ImportError, psutil.NoSuchProcess, psutil.AccessDenied):
|
|
485
519
|
pass
|
|
486
520
|
|
|
487
521
|
# Check for common VSCode terminal patterns in environment
|
|
488
522
|
term_program = os.environ.get("TERM_PROGRAM", "").lower()
|
|
489
|
-
if term_program and any(
|
|
490
|
-
pattern in term_program for pattern in ["code", "vscode"]
|
|
491
|
-
):
|
|
523
|
+
if term_program and any(pattern in term_program for pattern in ["code", "vscode"]):
|
|
492
524
|
return True
|
|
493
525
|
|
|
494
526
|
return False
|
|
495
527
|
except Exception:
|
|
496
528
|
return False
|
|
497
529
|
|
|
498
|
-
def _get_adaptive_refresh_rate(
|
|
530
|
+
def _get_adaptive_refresh_rate(
|
|
531
|
+
self,
|
|
532
|
+
user_override: Optional[int] = None,
|
|
533
|
+
) -> int:
|
|
499
534
|
"""Get adaptive refresh rate based on terminal performance."""
|
|
500
535
|
if user_override is not None:
|
|
501
536
|
return user_override
|
|
@@ -517,7 +552,7 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
517
552
|
|
|
518
553
|
return refresh_rates.get(perf_tier, 8)
|
|
519
554
|
|
|
520
|
-
def _get_adaptive_update_interval(self):
|
|
555
|
+
def _get_adaptive_update_interval(self) -> float:
|
|
521
556
|
"""Get adaptive update interval based on terminal performance."""
|
|
522
557
|
perf_tier = self._terminal_performance["performance_tier"]
|
|
523
558
|
|
|
@@ -529,20 +564,28 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
529
564
|
|
|
530
565
|
return intervals.get(perf_tier, 0.05)
|
|
531
566
|
|
|
532
|
-
def _get_adaptive_full_refresh_interval(self):
|
|
567
|
+
def _get_adaptive_full_refresh_interval(self) -> float:
|
|
533
568
|
"""Get adaptive full refresh interval based on terminal performance."""
|
|
534
569
|
perf_tier = self._terminal_performance["performance_tier"]
|
|
535
570
|
|
|
536
|
-
intervals = {
|
|
571
|
+
intervals = {
|
|
572
|
+
"high": 0.1,
|
|
573
|
+
"medium": 0.2,
|
|
574
|
+
"low": 0.5,
|
|
575
|
+
} # 100ms # 200ms # 500ms
|
|
537
576
|
|
|
538
577
|
return intervals.get(perf_tier, 0.2)
|
|
539
578
|
|
|
540
|
-
def _get_adaptive_debounce_delay(self):
|
|
579
|
+
def _get_adaptive_debounce_delay(self) -> float:
|
|
541
580
|
"""Get adaptive debounce delay based on terminal performance."""
|
|
542
581
|
perf_tier = self._terminal_performance["performance_tier"]
|
|
543
582
|
term_type = self._terminal_performance["type"]
|
|
544
583
|
|
|
545
|
-
delays = {
|
|
584
|
+
delays = {
|
|
585
|
+
"high": 0.01,
|
|
586
|
+
"medium": 0.03,
|
|
587
|
+
"low": 0.05,
|
|
588
|
+
} # 10ms # 30ms # 50ms
|
|
546
589
|
|
|
547
590
|
base_delay = delays.get(perf_tier, 0.03)
|
|
548
591
|
|
|
@@ -552,7 +595,7 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
552
595
|
|
|
553
596
|
return base_delay
|
|
554
597
|
|
|
555
|
-
def _get_adaptive_buffer_length(self):
|
|
598
|
+
def _get_adaptive_buffer_length(self) -> int:
|
|
556
599
|
"""Get adaptive buffer length based on terminal performance."""
|
|
557
600
|
perf_tier = self._terminal_performance["performance_tier"]
|
|
558
601
|
term_type = self._terminal_performance["type"]
|
|
@@ -571,7 +614,7 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
571
614
|
|
|
572
615
|
return base_length
|
|
573
616
|
|
|
574
|
-
def _get_adaptive_buffer_timeout(self):
|
|
617
|
+
def _get_adaptive_buffer_timeout(self) -> float:
|
|
575
618
|
"""Get adaptive buffer timeout based on terminal performance."""
|
|
576
619
|
perf_tier = self._terminal_performance["performance_tier"]
|
|
577
620
|
term_type = self._terminal_performance["type"]
|
|
@@ -590,7 +633,7 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
590
633
|
|
|
591
634
|
return base_timeout
|
|
592
635
|
|
|
593
|
-
def _get_adaptive_batch_timeout(self):
|
|
636
|
+
def _get_adaptive_batch_timeout(self) -> float:
|
|
594
637
|
"""Get adaptive batch timeout for update batching."""
|
|
595
638
|
perf_tier = self._terminal_performance["performance_tier"]
|
|
596
639
|
|
|
@@ -602,9 +645,9 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
602
645
|
|
|
603
646
|
return timeouts.get(perf_tier, 0.1)
|
|
604
647
|
|
|
605
|
-
def _monitor_performance(self):
|
|
648
|
+
def _monitor_performance(self) -> None:
|
|
606
649
|
"""Monitor refresh performance and adjust if needed."""
|
|
607
|
-
|
|
650
|
+
time.time()
|
|
608
651
|
|
|
609
652
|
# Clean old refresh time records (keep last 20)
|
|
610
653
|
if len(self._refresh_times) > 20:
|
|
@@ -612,7 +655,9 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
612
655
|
|
|
613
656
|
# Calculate average refresh time
|
|
614
657
|
if len(self._refresh_times) >= 5:
|
|
615
|
-
avg_refresh_time = sum(self._refresh_times) / len(
|
|
658
|
+
avg_refresh_time = sum(self._refresh_times) / len(
|
|
659
|
+
self._refresh_times,
|
|
660
|
+
)
|
|
616
661
|
expected_refresh_time = 1.0 / self.refresh_rate
|
|
617
662
|
|
|
618
663
|
# If refresh takes too long, downgrade performance
|
|
@@ -621,7 +666,6 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
621
666
|
|
|
622
667
|
# After 3 dropped frames, reduce refresh rate
|
|
623
668
|
if self._dropped_frames >= 3:
|
|
624
|
-
old_rate = self.refresh_rate
|
|
625
669
|
self.refresh_rate = max(2, int(self.refresh_rate * 0.7))
|
|
626
670
|
self._dropped_frames = 0
|
|
627
671
|
|
|
@@ -633,21 +677,26 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
633
677
|
if self.live and self.live.is_started:
|
|
634
678
|
try:
|
|
635
679
|
self.live.refresh_per_second = self.refresh_rate
|
|
636
|
-
except:
|
|
680
|
+
except Exception:
|
|
637
681
|
# If live display fails, fallback to simple mode
|
|
638
682
|
self._fallback_to_simple_display()
|
|
639
683
|
|
|
640
|
-
def _create_live_display_with_fallback(self):
|
|
684
|
+
def _create_live_display_with_fallback(self) -> Optional[Live]:
|
|
641
685
|
"""Create Live display with terminal compatibility checks and fallback."""
|
|
642
686
|
try:
|
|
643
687
|
# Test terminal capabilities
|
|
644
688
|
if not self._test_terminal_capabilities():
|
|
645
|
-
|
|
689
|
+
self._fallback_to_simple_display()
|
|
690
|
+
return None
|
|
646
691
|
|
|
647
692
|
# Create Live display with adaptive settings
|
|
648
693
|
live_settings = self._get_adaptive_live_settings()
|
|
649
694
|
|
|
650
|
-
live = Live(
|
|
695
|
+
live = Live(
|
|
696
|
+
self._create_layout(),
|
|
697
|
+
console=self.console,
|
|
698
|
+
**live_settings,
|
|
699
|
+
)
|
|
651
700
|
|
|
652
701
|
# Test if Live display works
|
|
653
702
|
try:
|
|
@@ -657,13 +706,15 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
657
706
|
return live
|
|
658
707
|
except Exception:
|
|
659
708
|
# Live display failed, try fallback
|
|
660
|
-
|
|
709
|
+
self._fallback_to_simple_display()
|
|
710
|
+
return None
|
|
661
711
|
|
|
662
712
|
except Exception:
|
|
663
713
|
# Any error in setup, use fallback
|
|
664
|
-
|
|
714
|
+
self._fallback_to_simple_display()
|
|
715
|
+
return None
|
|
665
716
|
|
|
666
|
-
def _test_terminal_capabilities(self):
|
|
717
|
+
def _test_terminal_capabilities(self) -> bool:
|
|
667
718
|
"""Test if terminal supports rich Live display features."""
|
|
668
719
|
try:
|
|
669
720
|
# Check if we're in a proper terminal
|
|
@@ -695,7 +746,7 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
695
746
|
except Exception:
|
|
696
747
|
return False
|
|
697
748
|
|
|
698
|
-
def _get_adaptive_live_settings(self):
|
|
749
|
+
def _get_adaptive_live_settings(self) -> Dict[str, Any]:
|
|
699
750
|
"""Get Live display settings adapted to terminal performance."""
|
|
700
751
|
perf_tier = self._terminal_performance["performance_tier"]
|
|
701
752
|
|
|
@@ -707,10 +758,14 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
707
758
|
|
|
708
759
|
# Adjust settings based on performance tier
|
|
709
760
|
if perf_tier == "low":
|
|
710
|
-
|
|
761
|
+
current_rate = settings["refresh_per_second"]
|
|
762
|
+
assert isinstance(current_rate, int)
|
|
763
|
+
settings["refresh_per_second"] = min(current_rate, 3)
|
|
711
764
|
settings["transient"] = True # Reduce memory usage
|
|
712
765
|
elif perf_tier == "medium":
|
|
713
|
-
|
|
766
|
+
current_rate = settings["refresh_per_second"]
|
|
767
|
+
assert isinstance(current_rate, int)
|
|
768
|
+
settings["refresh_per_second"] = min(current_rate, 8)
|
|
714
769
|
|
|
715
770
|
# Disable auto_refresh for multiplexers to prevent conflicts
|
|
716
771
|
if self._terminal_performance["type"] == "multiplexer":
|
|
@@ -719,7 +774,9 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
719
774
|
# macOS terminal-specific optimizations
|
|
720
775
|
if self._terminal_performance["type"] in ["iterm", "macos_terminal"]:
|
|
721
776
|
# Use more conservative refresh rates for macOS terminals to reduce flakiness
|
|
722
|
-
|
|
777
|
+
current_rate = settings["refresh_per_second"]
|
|
778
|
+
assert isinstance(current_rate, int)
|
|
779
|
+
settings["refresh_per_second"] = min(current_rate, 5)
|
|
723
780
|
# Enable transient mode to reduce flicker
|
|
724
781
|
settings["transient"] = False
|
|
725
782
|
# Ensure vertical overflow is handled gracefully
|
|
@@ -728,7 +785,9 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
728
785
|
# VSCode terminal-specific optimizations
|
|
729
786
|
if self._terminal_performance["type"] == "vscode":
|
|
730
787
|
# VSCode terminal needs very conservative refresh to prevent flaky behavior
|
|
731
|
-
|
|
788
|
+
current_rate = settings["refresh_per_second"]
|
|
789
|
+
assert isinstance(current_rate, int)
|
|
790
|
+
settings["refresh_per_second"] = min(current_rate, 6)
|
|
732
791
|
# Use transient mode to reduce rendering artifacts
|
|
733
792
|
settings["transient"] = False
|
|
734
793
|
# Handle overflow gracefully to prevent layout issues
|
|
@@ -738,26 +797,26 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
738
797
|
|
|
739
798
|
return settings
|
|
740
799
|
|
|
741
|
-
def _fallback_to_simple_display(self):
|
|
800
|
+
def _fallback_to_simple_display(self) -> None:
|
|
742
801
|
"""Fallback to simple console output when Live display is not supported."""
|
|
743
802
|
self._simple_display_mode = True
|
|
744
803
|
|
|
745
804
|
# Print a simple status message
|
|
746
805
|
try:
|
|
747
806
|
self.console.print(
|
|
748
|
-
"\n[yellow]Terminal compatibility: Using simple display mode[/yellow]"
|
|
807
|
+
"\n[yellow]Terminal compatibility: Using simple display mode[/yellow]",
|
|
749
808
|
)
|
|
750
809
|
self.console.print(
|
|
751
|
-
f"[dim]Monitoring {len(self.agent_ids)} agents...[/dim]\n"
|
|
810
|
+
f"[dim]Monitoring {len(self.agent_ids)} agents...[/dim]\n",
|
|
752
811
|
)
|
|
753
|
-
except:
|
|
812
|
+
except Exception:
|
|
754
813
|
# If even basic console fails, use plain print
|
|
755
814
|
print("\nUsing simple display mode...")
|
|
756
815
|
print(f"Monitoring {len(self.agent_ids)} agents...\n")
|
|
757
816
|
|
|
758
817
|
return None # No Live display
|
|
759
818
|
|
|
760
|
-
def _update_display_safe(self):
|
|
819
|
+
def _update_display_safe(self) -> None:
|
|
761
820
|
"""Safely update display with fallback support and terminal-specific synchronization."""
|
|
762
821
|
# Add extra synchronization for macOS terminals and VSCode to prevent race conditions
|
|
763
822
|
term_type = self._terminal_performance["type"]
|
|
@@ -765,7 +824,7 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
765
824
|
|
|
766
825
|
# VSCode-specific stabilization
|
|
767
826
|
if term_type == "vscode" and self._terminal_performance.get(
|
|
768
|
-
"refresh_stabilization"
|
|
827
|
+
"refresh_stabilization",
|
|
769
828
|
):
|
|
770
829
|
# Add small delay before refresh to let VSCode terminal stabilize
|
|
771
830
|
time.sleep(0.01)
|
|
@@ -775,19 +834,13 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
775
834
|
# For macOS terminals and VSCode, use more conservative locking
|
|
776
835
|
with self._layout_update_lock:
|
|
777
836
|
with self._lock: # Double locking for extra safety
|
|
778
|
-
if (
|
|
779
|
-
hasattr(self, "_simple_display_mode")
|
|
780
|
-
and self._simple_display_mode
|
|
781
|
-
):
|
|
837
|
+
if hasattr(self, "_simple_display_mode") and self._simple_display_mode:
|
|
782
838
|
self._update_simple_display()
|
|
783
839
|
else:
|
|
784
840
|
self._update_live_display_safe()
|
|
785
841
|
else:
|
|
786
842
|
with self._layout_update_lock:
|
|
787
|
-
if (
|
|
788
|
-
hasattr(self, "_simple_display_mode")
|
|
789
|
-
and self._simple_display_mode
|
|
790
|
-
):
|
|
843
|
+
if hasattr(self, "_simple_display_mode") and self._simple_display_mode:
|
|
791
844
|
self._update_simple_display()
|
|
792
845
|
else:
|
|
793
846
|
self._update_live_display()
|
|
@@ -797,12 +850,12 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
797
850
|
|
|
798
851
|
# VSCode-specific post-refresh stabilization
|
|
799
852
|
if term_type == "vscode" and self._terminal_performance.get(
|
|
800
|
-
"needs_flush_delay"
|
|
853
|
+
"needs_flush_delay",
|
|
801
854
|
):
|
|
802
855
|
# Small delay after refresh to prevent flicker
|
|
803
856
|
time.sleep(0.005)
|
|
804
857
|
|
|
805
|
-
def _update_simple_display(self):
|
|
858
|
+
def _update_simple_display(self) -> None:
|
|
806
859
|
"""Update display in simple mode without Live."""
|
|
807
860
|
try:
|
|
808
861
|
# Simple status update every few seconds
|
|
@@ -818,7 +871,7 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
818
871
|
|
|
819
872
|
try:
|
|
820
873
|
self.console.print(f"\r{status_line[:80]}", end="")
|
|
821
|
-
except:
|
|
874
|
+
except Exception:
|
|
822
875
|
print(f"\r{status_line[:80]}", end="")
|
|
823
876
|
|
|
824
877
|
self._last_simple_update = current_time
|
|
@@ -826,7 +879,7 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
826
879
|
except Exception:
|
|
827
880
|
pass
|
|
828
881
|
|
|
829
|
-
def _update_live_display(self):
|
|
882
|
+
def _update_live_display(self) -> None:
|
|
830
883
|
"""Update Live display mode."""
|
|
831
884
|
try:
|
|
832
885
|
if self.live:
|
|
@@ -835,7 +888,7 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
835
888
|
# If Live display fails, switch to simple mode
|
|
836
889
|
self._fallback_to_simple_display()
|
|
837
890
|
|
|
838
|
-
def _update_live_display_safe(self):
|
|
891
|
+
def _update_live_display_safe(self) -> None:
|
|
839
892
|
"""Update Live display mode with extra safety for macOS terminals."""
|
|
840
893
|
try:
|
|
841
894
|
if self.live and self.live.is_started:
|
|
@@ -855,33 +908,25 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
855
908
|
# If Live display fails, switch to simple mode
|
|
856
909
|
self._fallback_to_simple_display()
|
|
857
910
|
|
|
858
|
-
def _setup_theme(self):
|
|
911
|
+
def _setup_theme(self) -> None:
|
|
859
912
|
"""Setup color theme configuration."""
|
|
913
|
+
# Unified colors that work well in both dark and light modes
|
|
914
|
+
unified_colors = {
|
|
915
|
+
"primary": "#0066CC", # Deep blue - good contrast on both backgrounds
|
|
916
|
+
"secondary": "#4A90E2", # Medium blue - readable on both
|
|
917
|
+
"success": "#00AA44", # Deep green - visible on both
|
|
918
|
+
"warning": "#CC6600", # Orange-brown - works on both
|
|
919
|
+
"error": "#CC0000", # Deep red - strong contrast
|
|
920
|
+
"info": "#6633CC", # Purple - good on both backgrounds
|
|
921
|
+
"text": "default", # Use terminal's default text color
|
|
922
|
+
"border": "#4A90E2", # Medium blue for panels
|
|
923
|
+
"panel_style": "#4A90E2", # Consistent panel border color
|
|
924
|
+
"header_style": "bold #0066CC", # Bold deep blue headers
|
|
925
|
+
}
|
|
926
|
+
|
|
860
927
|
themes = {
|
|
861
|
-
"dark":
|
|
862
|
-
|
|
863
|
-
"secondary": "bright_blue",
|
|
864
|
-
"success": "bright_green",
|
|
865
|
-
"warning": "bright_yellow",
|
|
866
|
-
"error": "bright_red",
|
|
867
|
-
"info": "bright_magenta",
|
|
868
|
-
"text": "white",
|
|
869
|
-
"border": "blue",
|
|
870
|
-
"panel_style": "blue",
|
|
871
|
-
"header_style": "bold bright_cyan",
|
|
872
|
-
},
|
|
873
|
-
"light": {
|
|
874
|
-
"primary": "blue",
|
|
875
|
-
"secondary": "cyan",
|
|
876
|
-
"success": "green",
|
|
877
|
-
"warning": "yellow",
|
|
878
|
-
"error": "red",
|
|
879
|
-
"info": "magenta",
|
|
880
|
-
"text": "black",
|
|
881
|
-
"border": "blue",
|
|
882
|
-
"panel_style": "blue",
|
|
883
|
-
"header_style": "bold blue",
|
|
884
|
-
},
|
|
928
|
+
"dark": unified_colors.copy(),
|
|
929
|
+
"light": unified_colors.copy(),
|
|
885
930
|
"cyberpunk": {
|
|
886
931
|
"primary": "bright_magenta",
|
|
887
932
|
"secondary": "bright_cyan",
|
|
@@ -900,20 +945,20 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
900
945
|
|
|
901
946
|
# VSCode terminal-specific color adjustments
|
|
902
947
|
if self._terminal_performance["type"] == "vscode":
|
|
903
|
-
# VSCode terminal
|
|
904
|
-
#
|
|
948
|
+
# VSCode terminal works well with our unified color scheme
|
|
949
|
+
# Keep the hex colors for consistent appearance
|
|
905
950
|
vscode_adjustments = {
|
|
906
|
-
"primary": "
|
|
907
|
-
"secondary": "
|
|
908
|
-
"border": "
|
|
909
|
-
"panel_style": "
|
|
951
|
+
"primary": "#0066CC", # Deep blue - stable in VSCode
|
|
952
|
+
"secondary": "#4A90E2", # Medium blue
|
|
953
|
+
"border": "#4A90E2", # Consistent panel borders
|
|
954
|
+
"panel_style": "#4A90E2", # Unified panel style
|
|
910
955
|
}
|
|
911
956
|
self.colors.update(vscode_adjustments)
|
|
912
957
|
|
|
913
958
|
# Set up VSCode-safe emoji mapping for better compatibility
|
|
914
959
|
self._setup_vscode_emoji_fallbacks()
|
|
915
960
|
|
|
916
|
-
def _setup_vscode_emoji_fallbacks(self):
|
|
961
|
+
def _setup_vscode_emoji_fallbacks(self) -> None:
|
|
917
962
|
"""Setup emoji fallbacks for VSCode terminal compatibility."""
|
|
918
963
|
# VSCode terminal sometimes has issues with certain Unicode characters
|
|
919
964
|
# Provide ASCII fallbacks for better stability
|
|
@@ -940,15 +985,15 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
940
985
|
|
|
941
986
|
def _safe_emoji(self, emoji: str) -> str:
|
|
942
987
|
"""Get safe emoji for current terminal, with VSCode fallbacks."""
|
|
943
|
-
if
|
|
944
|
-
self._terminal_performance["type"] == "vscode"
|
|
945
|
-
and self._use_emoji_fallbacks
|
|
946
|
-
and emoji in self._emoji_fallbacks
|
|
947
|
-
):
|
|
988
|
+
if self._terminal_performance["type"] == "vscode" and self._use_emoji_fallbacks and emoji in self._emoji_fallbacks:
|
|
948
989
|
return self._emoji_fallbacks[emoji]
|
|
949
990
|
return emoji
|
|
950
991
|
|
|
951
|
-
def initialize(
|
|
992
|
+
def initialize(
|
|
993
|
+
self,
|
|
994
|
+
question: str,
|
|
995
|
+
log_filename: Optional[str] = None,
|
|
996
|
+
) -> None:
|
|
952
997
|
"""Initialize the rich display with question and optional log file."""
|
|
953
998
|
self.log_filename = log_filename
|
|
954
999
|
self.question = question
|
|
@@ -956,6 +1001,11 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
956
1001
|
# Clear screen
|
|
957
1002
|
self.console.clear()
|
|
958
1003
|
|
|
1004
|
+
# Suppress console logging to prevent interference with Live display
|
|
1005
|
+
from massgen.logger_config import suppress_console_logging
|
|
1006
|
+
|
|
1007
|
+
suppress_console_logging()
|
|
1008
|
+
|
|
959
1009
|
# Create initial layout
|
|
960
1010
|
self._create_initial_display()
|
|
961
1011
|
|
|
@@ -973,11 +1023,12 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
973
1023
|
|
|
974
1024
|
# Interactive mode is handled through input prompts
|
|
975
1025
|
|
|
976
|
-
def _create_initial_display(self):
|
|
1026
|
+
def _create_initial_display(self) -> None:
|
|
977
1027
|
"""Create the initial welcome display."""
|
|
978
1028
|
welcome_text = Text()
|
|
979
1029
|
welcome_text.append(
|
|
980
|
-
"🚀 MassGen Coordination Dashboard 🚀\n",
|
|
1030
|
+
"🚀 MassGen Coordination Dashboard 🚀\n",
|
|
1031
|
+
style=self.colors["header_style"],
|
|
981
1032
|
)
|
|
982
1033
|
welcome_text.append(
|
|
983
1034
|
f"Multi-Agent System with {len(self.agent_ids)} agents\n",
|
|
@@ -986,11 +1037,13 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
986
1037
|
|
|
987
1038
|
if self.log_filename:
|
|
988
1039
|
welcome_text.append(
|
|
989
|
-
f"📁 Log: {self.log_filename}\n",
|
|
1040
|
+
f"📁 Log: {self.log_filename}\n",
|
|
1041
|
+
style=self.colors["info"],
|
|
990
1042
|
)
|
|
991
1043
|
|
|
992
1044
|
welcome_text.append(
|
|
993
|
-
f"🎨 Theme: {self.theme.title()}",
|
|
1045
|
+
f"🎨 Theme: {self.theme.title()}",
|
|
1046
|
+
style=self.colors["secondary"],
|
|
994
1047
|
)
|
|
995
1048
|
|
|
996
1049
|
welcome_panel = Panel(
|
|
@@ -1013,12 +1066,23 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1013
1066
|
agent_columns = self._create_agent_columns_from_cache()
|
|
1014
1067
|
footer = self._footer_cache if self._footer_cache else self._create_footer()
|
|
1015
1068
|
|
|
1016
|
-
#
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1069
|
+
# Check if final presentation is active
|
|
1070
|
+
if self._final_presentation_active:
|
|
1071
|
+
# Create final presentation panel
|
|
1072
|
+
presentation_panel = self._create_final_presentation_panel()
|
|
1073
|
+
|
|
1074
|
+
# Arrange layout with ONLY presentation panel (hide header and agent columns for full width)
|
|
1075
|
+
layout.split_column(
|
|
1076
|
+
Layout(presentation_panel, name="presentation"),
|
|
1077
|
+
Layout(footer, name="footer", size=8),
|
|
1078
|
+
)
|
|
1079
|
+
else:
|
|
1080
|
+
# Arrange layout without final presentation
|
|
1081
|
+
layout.split_column(
|
|
1082
|
+
Layout(header, name="header", size=5),
|
|
1083
|
+
Layout(agent_columns, name="main"),
|
|
1084
|
+
Layout(footer, name="footer", size=8),
|
|
1085
|
+
)
|
|
1022
1086
|
|
|
1023
1087
|
return layout
|
|
1024
1088
|
|
|
@@ -1036,7 +1100,10 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1036
1100
|
|
|
1037
1101
|
# Use fixed column widths with equal=False to enforce exact sizing
|
|
1038
1102
|
return Columns(
|
|
1039
|
-
agent_panels,
|
|
1103
|
+
agent_panels,
|
|
1104
|
+
equal=False,
|
|
1105
|
+
expand=False,
|
|
1106
|
+
width=self.fixed_column_width,
|
|
1040
1107
|
)
|
|
1041
1108
|
|
|
1042
1109
|
def _create_header(self) -> Panel:
|
|
@@ -1049,7 +1116,7 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1049
1116
|
|
|
1050
1117
|
if hasattr(self, "question"):
|
|
1051
1118
|
header_text.append(
|
|
1052
|
-
f"\n💡 Question: {self.question
|
|
1119
|
+
f"\n💡 Question: {self.question}",
|
|
1053
1120
|
style=self.colors["info"],
|
|
1054
1121
|
)
|
|
1055
1122
|
|
|
@@ -1070,10 +1137,13 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1070
1137
|
|
|
1071
1138
|
# Use fixed column widths with equal=False to enforce exact sizing
|
|
1072
1139
|
return Columns(
|
|
1073
|
-
agent_panels,
|
|
1140
|
+
agent_panels,
|
|
1141
|
+
equal=False,
|
|
1142
|
+
expand=False,
|
|
1143
|
+
width=self.fixed_column_width,
|
|
1074
1144
|
)
|
|
1075
1145
|
|
|
1076
|
-
def _setup_keyboard_handler(self):
|
|
1146
|
+
def _setup_keyboard_handler(self) -> None:
|
|
1077
1147
|
"""Setup keyboard handler for interactive agent selection."""
|
|
1078
1148
|
try:
|
|
1079
1149
|
# Simple key mapping for agent selection
|
|
@@ -1089,7 +1159,7 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1089
1159
|
except ImportError:
|
|
1090
1160
|
self._keyboard_interactive_mode = False
|
|
1091
1161
|
|
|
1092
|
-
def _start_input_thread(self):
|
|
1162
|
+
def _start_input_thread(self) -> None:
|
|
1093
1163
|
"""Start background thread for keyboard input during Live mode."""
|
|
1094
1164
|
if not sys.stdin.isatty():
|
|
1095
1165
|
return # Can't handle input if not a TTY
|
|
@@ -1099,28 +1169,38 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1099
1169
|
# Choose input method based on safety requirements and terminal type
|
|
1100
1170
|
term_type = self._terminal_performance["type"]
|
|
1101
1171
|
|
|
1102
|
-
if self._safe_keyboard_mode or term_type in [
|
|
1172
|
+
if self._safe_keyboard_mode or term_type in [
|
|
1173
|
+
"iterm",
|
|
1174
|
+
"macos_terminal",
|
|
1175
|
+
]:
|
|
1103
1176
|
# Use completely safe method for macOS terminals to avoid conflicts
|
|
1104
1177
|
self._input_thread = threading.Thread(
|
|
1105
|
-
target=self._input_thread_worker_safe,
|
|
1178
|
+
target=self._input_thread_worker_safe,
|
|
1179
|
+
daemon=True,
|
|
1106
1180
|
)
|
|
1107
1181
|
self._input_thread.start()
|
|
1108
1182
|
else:
|
|
1109
1183
|
# Try improved method first, fallback to polling method if needed
|
|
1110
1184
|
try:
|
|
1111
1185
|
self._input_thread = threading.Thread(
|
|
1112
|
-
target=self._input_thread_worker_improved,
|
|
1186
|
+
target=self._input_thread_worker_improved,
|
|
1187
|
+
daemon=True,
|
|
1113
1188
|
)
|
|
1114
1189
|
self._input_thread.start()
|
|
1115
1190
|
except Exception:
|
|
1116
1191
|
# Fallback to simpler polling method
|
|
1117
1192
|
self._input_thread = threading.Thread(
|
|
1118
|
-
target=self._input_thread_worker_fallback,
|
|
1193
|
+
target=self._input_thread_worker_fallback,
|
|
1194
|
+
daemon=True,
|
|
1119
1195
|
)
|
|
1120
1196
|
self._input_thread.start()
|
|
1121
1197
|
|
|
1122
|
-
def _input_thread_worker_improved(self):
|
|
1198
|
+
def _input_thread_worker_improved(self) -> None:
|
|
1123
1199
|
"""Improved background thread worker that doesn't interfere with Rich rendering."""
|
|
1200
|
+
# Fall back to simple method if Unix terminal support is not available
|
|
1201
|
+
if not UNIX_TERMINAL_SUPPORT:
|
|
1202
|
+
return self._input_thread_worker_fallback()
|
|
1203
|
+
|
|
1124
1204
|
try:
|
|
1125
1205
|
# Save original terminal settings but don't change to raw mode
|
|
1126
1206
|
if sys.stdin.isatty():
|
|
@@ -1131,7 +1211,11 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1131
1211
|
new_settings[3] = new_settings[3] & ~(termios.ICANON | termios.ECHO)
|
|
1132
1212
|
new_settings[6][termios.VMIN] = 0 # Non-blocking
|
|
1133
1213
|
new_settings[6][termios.VTIME] = 1 # 100ms timeout
|
|
1134
|
-
termios.tcsetattr(
|
|
1214
|
+
termios.tcsetattr(
|
|
1215
|
+
sys.stdin.fileno(),
|
|
1216
|
+
termios.TCSANOW,
|
|
1217
|
+
new_settings,
|
|
1218
|
+
)
|
|
1135
1219
|
|
|
1136
1220
|
while not self._stop_input_thread:
|
|
1137
1221
|
try:
|
|
@@ -1146,25 +1230,25 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1146
1230
|
|
|
1147
1231
|
except (KeyboardInterrupt, EOFError):
|
|
1148
1232
|
pass
|
|
1149
|
-
except Exception
|
|
1233
|
+
except Exception:
|
|
1150
1234
|
# Handle errors gracefully
|
|
1151
1235
|
pass
|
|
1152
1236
|
finally:
|
|
1153
1237
|
# Restore terminal settings
|
|
1154
1238
|
self._restore_terminal_settings()
|
|
1155
1239
|
|
|
1156
|
-
def _input_thread_worker_fallback(self):
|
|
1240
|
+
def _input_thread_worker_fallback(self) -> None:
|
|
1157
1241
|
"""Fallback keyboard input method using simple polling without terminal mode changes."""
|
|
1158
1242
|
import time
|
|
1159
1243
|
|
|
1160
1244
|
# Show instructions to user
|
|
1161
1245
|
self.console.print(
|
|
1162
|
-
"\n[dim]Keyboard support active. Press keys during Live display:[/dim]"
|
|
1246
|
+
"\n[dim]Keyboard support active. Press keys during Live display:[/dim]",
|
|
1163
1247
|
)
|
|
1164
1248
|
self.console.print(
|
|
1165
1249
|
"[dim]1-{} to open agent files, 's' for system status, 'q' to quit[/dim]\n".format(
|
|
1166
|
-
len(self.agent_ids)
|
|
1167
|
-
)
|
|
1250
|
+
len(self.agent_ids),
|
|
1251
|
+
),
|
|
1168
1252
|
)
|
|
1169
1253
|
|
|
1170
1254
|
try:
|
|
@@ -1183,35 +1267,52 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1183
1267
|
# Handle any other errors gracefully
|
|
1184
1268
|
pass
|
|
1185
1269
|
|
|
1186
|
-
def _input_thread_worker_safe(self):
|
|
1270
|
+
def _input_thread_worker_safe(self) -> None:
|
|
1187
1271
|
"""Completely safe keyboard input that never changes terminal settings."""
|
|
1188
1272
|
# This method does nothing to avoid any interference with Rich rendering
|
|
1189
1273
|
# Keyboard functionality is disabled in safe mode to prevent rendering issues
|
|
1190
1274
|
try:
|
|
1191
1275
|
while not self._stop_input_thread:
|
|
1192
1276
|
time.sleep(0.5) # Just wait without doing anything
|
|
1193
|
-
except:
|
|
1277
|
+
except Exception:
|
|
1194
1278
|
pass
|
|
1195
1279
|
|
|
1196
|
-
def _restore_terminal_settings(self):
|
|
1280
|
+
def _restore_terminal_settings(self) -> None:
|
|
1197
1281
|
"""Restore original terminal settings."""
|
|
1198
1282
|
try:
|
|
1199
|
-
if
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1283
|
+
if UNIX_TERMINAL_SUPPORT and sys.stdin.isatty():
|
|
1284
|
+
if self._original_settings:
|
|
1285
|
+
# Restore the original settings
|
|
1286
|
+
termios.tcsetattr(
|
|
1287
|
+
sys.stdin.fileno(),
|
|
1288
|
+
termios.TCSADRAIN,
|
|
1289
|
+
self._original_settings,
|
|
1290
|
+
)
|
|
1291
|
+
self._original_settings = None
|
|
1292
|
+
else:
|
|
1293
|
+
# If we don't have original settings, at least ensure echo is on
|
|
1294
|
+
try:
|
|
1295
|
+
current = termios.tcgetattr(sys.stdin.fileno())
|
|
1296
|
+
# Enable echo and canonical mode
|
|
1297
|
+
current[3] = current[3] | termios.ECHO | termios.ICANON
|
|
1298
|
+
termios.tcsetattr(
|
|
1299
|
+
sys.stdin.fileno(),
|
|
1300
|
+
termios.TCSADRAIN,
|
|
1301
|
+
current,
|
|
1302
|
+
)
|
|
1303
|
+
except Exception:
|
|
1304
|
+
pass
|
|
1305
|
+
except Exception:
|
|
1205
1306
|
pass
|
|
1206
1307
|
|
|
1207
|
-
def _ensure_clean_keyboard_state(self):
|
|
1308
|
+
def _ensure_clean_keyboard_state(self) -> None:
|
|
1208
1309
|
"""Ensure clean keyboard state before starting agent selector."""
|
|
1209
1310
|
# Stop input thread completely
|
|
1210
1311
|
self._stop_input_thread = True
|
|
1211
1312
|
if self._input_thread and self._input_thread.is_alive():
|
|
1212
1313
|
try:
|
|
1213
1314
|
self._input_thread.join(timeout=0.5)
|
|
1214
|
-
except:
|
|
1315
|
+
except Exception:
|
|
1215
1316
|
pass
|
|
1216
1317
|
|
|
1217
1318
|
# Restore terminal settings to normal mode
|
|
@@ -1219,12 +1320,10 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1219
1320
|
|
|
1220
1321
|
# Clear any pending keyboard input from stdin buffer
|
|
1221
1322
|
try:
|
|
1222
|
-
if sys.stdin.isatty():
|
|
1223
|
-
import termios
|
|
1224
|
-
|
|
1323
|
+
if UNIX_TERMINAL_SUPPORT and sys.stdin.isatty():
|
|
1225
1324
|
# Flush input buffer to remove any pending keystrokes
|
|
1226
1325
|
termios.tcflush(sys.stdin.fileno(), termios.TCIFLUSH)
|
|
1227
|
-
except:
|
|
1326
|
+
except Exception:
|
|
1228
1327
|
pass
|
|
1229
1328
|
|
|
1230
1329
|
# Small delay to ensure all cleanup is complete
|
|
@@ -1232,19 +1331,21 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1232
1331
|
|
|
1233
1332
|
time.sleep(0.1)
|
|
1234
1333
|
|
|
1235
|
-
def _handle_key_press(self, key):
|
|
1334
|
+
def _handle_key_press(self, key: str) -> None:
|
|
1236
1335
|
"""Handle key press events for agent selection."""
|
|
1237
1336
|
if key in self._agent_keys:
|
|
1238
1337
|
agent_id = self._agent_keys[key]
|
|
1239
1338
|
self._open_agent_in_default_text_editor(agent_id)
|
|
1240
1339
|
elif key == "s":
|
|
1241
1340
|
self._open_system_status_in_default_text_editor()
|
|
1341
|
+
elif key == "f":
|
|
1342
|
+
self._open_final_presentation_in_default_text_editor()
|
|
1242
1343
|
elif key == "q":
|
|
1243
1344
|
# Quit the application - restore terminal and stop
|
|
1244
1345
|
self._stop_input_thread = True
|
|
1245
1346
|
self._restore_terminal_settings()
|
|
1246
1347
|
|
|
1247
|
-
def _open_agent_in_default_text_editor(self, agent_id: str):
|
|
1348
|
+
def _open_agent_in_default_text_editor(self, agent_id: str) -> None:
|
|
1248
1349
|
"""Open agent's txt file in default text editor."""
|
|
1249
1350
|
if agent_id not in self.agent_files:
|
|
1250
1351
|
return
|
|
@@ -1260,12 +1361,16 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1260
1361
|
elif sys.platform.startswith("linux"): # Linux
|
|
1261
1362
|
subprocess.run(["xdg-open", str(file_path)], check=False)
|
|
1262
1363
|
elif sys.platform == "win32": # Windows
|
|
1263
|
-
subprocess.run(
|
|
1364
|
+
subprocess.run(
|
|
1365
|
+
["start", str(file_path)],
|
|
1366
|
+
check=False,
|
|
1367
|
+
shell=True,
|
|
1368
|
+
)
|
|
1264
1369
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
1265
1370
|
# Fallback to external app method
|
|
1266
1371
|
self._open_agent_in_external_app(agent_id)
|
|
1267
1372
|
|
|
1268
|
-
def _open_agent_in_vscode_new_window(self, agent_id: str):
|
|
1373
|
+
def _open_agent_in_vscode_new_window(self, agent_id: str) -> None:
|
|
1269
1374
|
"""Open agent's txt file in a new VS Code window."""
|
|
1270
1375
|
if agent_id not in self.agent_files:
|
|
1271
1376
|
return
|
|
@@ -1276,12 +1381,15 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1276
1381
|
|
|
1277
1382
|
try:
|
|
1278
1383
|
# Force open in new VS Code window
|
|
1279
|
-
subprocess.run(
|
|
1384
|
+
subprocess.run(
|
|
1385
|
+
["code", "--new-window", str(file_path)],
|
|
1386
|
+
check=False,
|
|
1387
|
+
)
|
|
1280
1388
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
1281
1389
|
# Fallback to existing method if VS Code is not available
|
|
1282
1390
|
self._open_agent_in_external_app(agent_id)
|
|
1283
1391
|
|
|
1284
|
-
def _open_system_status_in_default_text_editor(self):
|
|
1392
|
+
def _open_system_status_in_default_text_editor(self) -> None:
|
|
1285
1393
|
"""Open system status file in default text editor."""
|
|
1286
1394
|
if not self.system_status_file or not self.system_status_file.exists():
|
|
1287
1395
|
return
|
|
@@ -1289,18 +1397,65 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1289
1397
|
try:
|
|
1290
1398
|
# Use system default application to open text files
|
|
1291
1399
|
if sys.platform == "darwin": # macOS
|
|
1292
|
-
subprocess.run(
|
|
1400
|
+
subprocess.run(
|
|
1401
|
+
["open", str(self.system_status_file)],
|
|
1402
|
+
check=False,
|
|
1403
|
+
)
|
|
1293
1404
|
elif sys.platform.startswith("linux"): # Linux
|
|
1294
|
-
subprocess.run(
|
|
1405
|
+
subprocess.run(
|
|
1406
|
+
["xdg-open", str(self.system_status_file)],
|
|
1407
|
+
check=False,
|
|
1408
|
+
)
|
|
1295
1409
|
elif sys.platform == "win32": # Windows
|
|
1296
1410
|
subprocess.run(
|
|
1297
|
-
["start", str(self.system_status_file)],
|
|
1411
|
+
["start", str(self.system_status_file)],
|
|
1412
|
+
check=False,
|
|
1413
|
+
shell=True,
|
|
1298
1414
|
)
|
|
1299
1415
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
1300
1416
|
# Fallback to external app method
|
|
1301
1417
|
self._open_system_status_in_external_app()
|
|
1302
1418
|
|
|
1303
|
-
def
|
|
1419
|
+
def _open_final_presentation_in_default_text_editor(self) -> None:
|
|
1420
|
+
"""Open final presentation file in default text editor."""
|
|
1421
|
+
# Check if we have an active final presentation file or stored one
|
|
1422
|
+
final_presentation_file = None
|
|
1423
|
+
|
|
1424
|
+
# Priority 1: Use active streaming file if available
|
|
1425
|
+
if hasattr(self, "_final_presentation_file_path") and self._final_presentation_file_path:
|
|
1426
|
+
final_presentation_file = self._final_presentation_file_path
|
|
1427
|
+
# Priority 2: Look for stored presentation agent's file
|
|
1428
|
+
elif hasattr(self, "_stored_presentation_agent") and self._stored_presentation_agent:
|
|
1429
|
+
agent_name = self._stored_presentation_agent
|
|
1430
|
+
final_presentation_file = self.output_dir / f"{agent_name}_final_presentation.txt"
|
|
1431
|
+
else:
|
|
1432
|
+
return
|
|
1433
|
+
|
|
1434
|
+
if not final_presentation_file.exists():
|
|
1435
|
+
return
|
|
1436
|
+
|
|
1437
|
+
try:
|
|
1438
|
+
# Use system default application to open text files
|
|
1439
|
+
if sys.platform == "darwin": # macOS
|
|
1440
|
+
subprocess.run(
|
|
1441
|
+
["open", str(final_presentation_file)],
|
|
1442
|
+
check=False,
|
|
1443
|
+
)
|
|
1444
|
+
elif sys.platform.startswith("linux"): # Linux
|
|
1445
|
+
subprocess.run(
|
|
1446
|
+
["xdg-open", str(final_presentation_file)],
|
|
1447
|
+
check=False,
|
|
1448
|
+
)
|
|
1449
|
+
elif sys.platform == "win32": # Windows
|
|
1450
|
+
subprocess.run(
|
|
1451
|
+
["start", str(final_presentation_file)],
|
|
1452
|
+
check=False,
|
|
1453
|
+
shell=True,
|
|
1454
|
+
)
|
|
1455
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
1456
|
+
pass
|
|
1457
|
+
|
|
1458
|
+
def _open_system_status_in_vscode_new_window(self) -> None:
|
|
1304
1459
|
"""Open system status file in a new VS Code window."""
|
|
1305
1460
|
if not self.system_status_file or not self.system_status_file.exists():
|
|
1306
1461
|
return
|
|
@@ -1308,13 +1463,14 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1308
1463
|
try:
|
|
1309
1464
|
# Force open in new VS Code window
|
|
1310
1465
|
subprocess.run(
|
|
1311
|
-
["code", "--new-window", str(self.system_status_file)],
|
|
1466
|
+
["code", "--new-window", str(self.system_status_file)],
|
|
1467
|
+
check=False,
|
|
1312
1468
|
)
|
|
1313
1469
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
1314
1470
|
# Fallback to existing method if VS Code is not available
|
|
1315
1471
|
self._open_system_status_in_external_app()
|
|
1316
1472
|
|
|
1317
|
-
def _open_agent_in_external_app(self, agent_id: str):
|
|
1473
|
+
def _open_agent_in_external_app(self, agent_id: str) -> None:
|
|
1318
1474
|
"""Open agent's txt file in external editor or terminal viewer."""
|
|
1319
1475
|
if agent_id not in self.agent_files:
|
|
1320
1476
|
return
|
|
@@ -1332,10 +1488,14 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1332
1488
|
try:
|
|
1333
1489
|
if editor == "open":
|
|
1334
1490
|
subprocess.run(
|
|
1335
|
-
["open", "-a", "TextEdit", str(file_path)],
|
|
1491
|
+
["open", "-a", "TextEdit", str(file_path)],
|
|
1492
|
+
check=False,
|
|
1336
1493
|
)
|
|
1337
1494
|
else:
|
|
1338
|
-
subprocess.run(
|
|
1495
|
+
subprocess.run(
|
|
1496
|
+
[editor, str(file_path)],
|
|
1497
|
+
check=False,
|
|
1498
|
+
)
|
|
1339
1499
|
break
|
|
1340
1500
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
1341
1501
|
continue
|
|
@@ -1354,7 +1514,9 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1354
1514
|
for editor in editors:
|
|
1355
1515
|
try:
|
|
1356
1516
|
subprocess.run(
|
|
1357
|
-
[editor, str(file_path)],
|
|
1517
|
+
[editor, str(file_path)],
|
|
1518
|
+
check=False,
|
|
1519
|
+
shell=True,
|
|
1358
1520
|
)
|
|
1359
1521
|
break
|
|
1360
1522
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
@@ -1364,7 +1526,7 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1364
1526
|
# If all else fails, show a message that the file exists
|
|
1365
1527
|
pass
|
|
1366
1528
|
|
|
1367
|
-
def _open_system_status_in_external_app(self):
|
|
1529
|
+
def _open_system_status_in_external_app(self) -> None:
|
|
1368
1530
|
"""Open system status file in external editor or terminal viewer."""
|
|
1369
1531
|
if not self.system_status_file or not self.system_status_file.exists():
|
|
1370
1532
|
return
|
|
@@ -1388,7 +1550,8 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1388
1550
|
)
|
|
1389
1551
|
else:
|
|
1390
1552
|
subprocess.run(
|
|
1391
|
-
[editor, str(self.system_status_file)],
|
|
1553
|
+
[editor, str(self.system_status_file)],
|
|
1554
|
+
check=False,
|
|
1392
1555
|
)
|
|
1393
1556
|
break
|
|
1394
1557
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
@@ -1399,7 +1562,8 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1399
1562
|
for editor in editors:
|
|
1400
1563
|
try:
|
|
1401
1564
|
subprocess.run(
|
|
1402
|
-
[editor, str(self.system_status_file)],
|
|
1565
|
+
[editor, str(self.system_status_file)],
|
|
1566
|
+
check=False,
|
|
1403
1567
|
)
|
|
1404
1568
|
break
|
|
1405
1569
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
@@ -1422,7 +1586,7 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1422
1586
|
# If all else fails, show a message that the file exists
|
|
1423
1587
|
pass
|
|
1424
1588
|
|
|
1425
|
-
def _show_agent_full_content(self, agent_id: str):
|
|
1589
|
+
def _show_agent_full_content(self, agent_id: str) -> None:
|
|
1426
1590
|
"""Display full content of selected agent from txt file."""
|
|
1427
1591
|
if agent_id not in self.agent_files:
|
|
1428
1592
|
return
|
|
@@ -1432,6 +1596,8 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1432
1596
|
if file_path.exists():
|
|
1433
1597
|
with open(file_path, "r", encoding="utf-8") as f:
|
|
1434
1598
|
content = f.read()
|
|
1599
|
+
if "[" in content:
|
|
1600
|
+
content = content.replace("[", r"\[")
|
|
1435
1601
|
|
|
1436
1602
|
# Add separator instead of clearing screen
|
|
1437
1603
|
self.console.print("\n" + "=" * 80 + "\n")
|
|
@@ -1443,11 +1609,14 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1443
1609
|
style=self.colors["header_style"],
|
|
1444
1610
|
)
|
|
1445
1611
|
header_text.append(
|
|
1446
|
-
"\nPress any key to return to main view",
|
|
1612
|
+
"\nPress any key to return to main view",
|
|
1613
|
+
style=self.colors["info"],
|
|
1447
1614
|
)
|
|
1448
1615
|
|
|
1449
1616
|
header_panel = Panel(
|
|
1450
|
-
header_text,
|
|
1617
|
+
header_text,
|
|
1618
|
+
box=DOUBLE,
|
|
1619
|
+
border_style=self.colors["border"],
|
|
1451
1620
|
)
|
|
1452
1621
|
|
|
1453
1622
|
# Create content panel
|
|
@@ -1467,14 +1636,17 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1467
1636
|
# Add separator instead of clearing screen
|
|
1468
1637
|
self.console.print("\n" + "=" * 80 + "\n")
|
|
1469
1638
|
|
|
1470
|
-
except Exception
|
|
1639
|
+
except Exception:
|
|
1471
1640
|
# Handle errors gracefully
|
|
1472
1641
|
pass
|
|
1473
1642
|
|
|
1474
|
-
def show_agent_selector(self):
|
|
1643
|
+
def show_agent_selector(self) -> None:
|
|
1475
1644
|
"""Show agent selector and handle user input."""
|
|
1476
1645
|
|
|
1477
|
-
if not self._keyboard_interactive_mode or not hasattr(
|
|
1646
|
+
if not self._keyboard_interactive_mode or not hasattr(
|
|
1647
|
+
self,
|
|
1648
|
+
"_agent_keys",
|
|
1649
|
+
):
|
|
1478
1650
|
return
|
|
1479
1651
|
|
|
1480
1652
|
# Prevent duplicate agent selector calls
|
|
@@ -1495,7 +1667,10 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1495
1667
|
|
|
1496
1668
|
options_text = Text()
|
|
1497
1669
|
options_text.append(
|
|
1498
|
-
"
|
|
1670
|
+
"This is a system inspection interface for diving into the multi-agent collaboration behind the "
|
|
1671
|
+
"scenes in MassGen. It lets you examine each agent's original output and compare it to the final "
|
|
1672
|
+
"MassGen answer in terms of quality. You can explore the detailed communication, collaboration, "
|
|
1673
|
+
"voting, and decision-making process.\n",
|
|
1499
1674
|
style=self.colors["text"],
|
|
1500
1675
|
)
|
|
1501
1676
|
|
|
@@ -1506,33 +1681,46 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1506
1681
|
|
|
1507
1682
|
for key, agent_id in self._agent_keys.items():
|
|
1508
1683
|
options_text.append(
|
|
1509
|
-
f" {key}: ",
|
|
1684
|
+
f" {key}: ",
|
|
1685
|
+
style=self.colors["warning"],
|
|
1510
1686
|
)
|
|
1511
1687
|
options_text.append(
|
|
1512
|
-
|
|
1688
|
+
"Inspect the original answer and working log of agent ",
|
|
1689
|
+
style=self.colors["text"],
|
|
1513
1690
|
)
|
|
1514
1691
|
options_text.append(
|
|
1515
|
-
f"{agent_id}\n",
|
|
1692
|
+
f"{agent_id}\n",
|
|
1693
|
+
style=self.colors["warning"],
|
|
1516
1694
|
)
|
|
1517
1695
|
|
|
1518
1696
|
options_text.append(
|
|
1519
|
-
" s:
|
|
1697
|
+
" s: Inspect the orchestrator working log including the voting process\n",
|
|
1698
|
+
style=self.colors["warning"],
|
|
1699
|
+
)
|
|
1700
|
+
|
|
1701
|
+
options_text.append(
|
|
1702
|
+
" r: Display coordination table to see the full history of agent interactions and decisions\n",
|
|
1703
|
+
style=self.colors["warning"],
|
|
1520
1704
|
)
|
|
1521
1705
|
|
|
1522
1706
|
# Add option to show final presentation if it's stored
|
|
1523
1707
|
if self._stored_final_presentation and self._stored_presentation_agent:
|
|
1524
1708
|
options_text.append(
|
|
1525
|
-
f" f: Show final presentation from Selected Agent ({
|
|
1709
|
+
f" f: Show final presentation from Selected Agent ({self._stored_presentation_agent})\n",
|
|
1710
|
+
style=self.colors["success"],
|
|
1526
1711
|
)
|
|
1527
1712
|
|
|
1528
|
-
options_text.append(
|
|
1713
|
+
options_text.append(
|
|
1714
|
+
" q: Quit Inspection\n",
|
|
1715
|
+
style=self.colors["info"],
|
|
1716
|
+
)
|
|
1529
1717
|
|
|
1530
1718
|
self.console.print(
|
|
1531
1719
|
Panel(
|
|
1532
1720
|
options_text,
|
|
1533
1721
|
title="[bold]Agent Selector[/bold]",
|
|
1534
1722
|
border_style=self.colors["border"],
|
|
1535
|
-
)
|
|
1723
|
+
),
|
|
1536
1724
|
)
|
|
1537
1725
|
|
|
1538
1726
|
# Get user input
|
|
@@ -1543,13 +1731,16 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1543
1731
|
self._show_agent_full_content(self._agent_keys[choice])
|
|
1544
1732
|
elif choice == "s":
|
|
1545
1733
|
self._show_system_status()
|
|
1734
|
+
elif choice == "r":
|
|
1735
|
+
self.display_coordination_table()
|
|
1546
1736
|
elif choice == "f" and self._stored_final_presentation:
|
|
1737
|
+
# Display the final presentation in the terminal
|
|
1547
1738
|
self._redisplay_final_presentation()
|
|
1548
1739
|
elif choice == "q":
|
|
1549
1740
|
break
|
|
1550
1741
|
else:
|
|
1551
1742
|
self.console.print(
|
|
1552
|
-
f"[{self.colors['error']}]Invalid choice. Please try again.[/{self.colors['error']}]"
|
|
1743
|
+
f"[{self.colors['error']}]Invalid choice. Please try again.[/{self.colors['error']}]",
|
|
1553
1744
|
)
|
|
1554
1745
|
except KeyboardInterrupt:
|
|
1555
1746
|
# Handle Ctrl+C gracefully
|
|
@@ -1558,11 +1749,11 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1558
1749
|
# Always reset the flag when exiting
|
|
1559
1750
|
self._agent_selector_active = True
|
|
1560
1751
|
|
|
1561
|
-
def _redisplay_final_presentation(self):
|
|
1752
|
+
def _redisplay_final_presentation(self) -> None:
|
|
1562
1753
|
"""Redisplay the stored final presentation."""
|
|
1563
1754
|
if not self._stored_final_presentation or not self._stored_presentation_agent:
|
|
1564
1755
|
self.console.print(
|
|
1565
|
-
f"[{self.colors['error']}]No final presentation stored.[/{self.colors['error']}]"
|
|
1756
|
+
f"[{self.colors['error']}]No final presentation stored.[/{self.colors['error']}]",
|
|
1566
1757
|
)
|
|
1567
1758
|
return
|
|
1568
1759
|
|
|
@@ -1571,7 +1762,8 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1571
1762
|
|
|
1572
1763
|
# Display the stored presentation
|
|
1573
1764
|
self._display_final_presentation_content(
|
|
1574
|
-
self._stored_presentation_agent,
|
|
1765
|
+
self._stored_presentation_agent,
|
|
1766
|
+
self._stored_final_presentation,
|
|
1575
1767
|
)
|
|
1576
1768
|
|
|
1577
1769
|
# Wait for user to continue
|
|
@@ -1580,39 +1772,24 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1580
1772
|
# Add separator
|
|
1581
1773
|
self.console.print("\n" + "=" * 80 + "\n")
|
|
1582
1774
|
|
|
1583
|
-
def
|
|
1584
|
-
"""
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
f"[{self.colors['error']}]No final presentation stored.[/{self.colors['error']}]"
|
|
1588
|
-
)
|
|
1589
|
-
return
|
|
1590
|
-
|
|
1591
|
-
# Add separator
|
|
1592
|
-
self.console.print("\n" + "=" * 80 + "\n")
|
|
1593
|
-
|
|
1594
|
-
# Display the stored presentation
|
|
1595
|
-
self._display_final_presentation_content(
|
|
1596
|
-
self._stored_presentation_agent, self._stored_final_presentation
|
|
1597
|
-
)
|
|
1598
|
-
|
|
1599
|
-
# Wait for user to continue
|
|
1600
|
-
input("\nPress Enter to return to agent selector...")
|
|
1775
|
+
def _show_coordination_rounds_table(self) -> None:
|
|
1776
|
+
"""Display the coordination rounds table with rich formatting."""
|
|
1777
|
+
# Use the improved coordination table display
|
|
1778
|
+
self.display_coordination_table()
|
|
1601
1779
|
|
|
1602
|
-
|
|
1603
|
-
self.console.print("\n" + "=" * 80 + "\n")
|
|
1604
|
-
|
|
1605
|
-
def _show_system_status(self):
|
|
1780
|
+
def _show_system_status(self) -> None:
|
|
1606
1781
|
"""Display system status from txt file."""
|
|
1607
1782
|
if not self.system_status_file or not self.system_status_file.exists():
|
|
1608
1783
|
self.console.print(
|
|
1609
|
-
f"[{self.colors['error']}]System status file not found.[/{self.colors['error']}]"
|
|
1784
|
+
f"[{self.colors['error']}]System status file not found.[/{self.colors['error']}]",
|
|
1610
1785
|
)
|
|
1611
1786
|
return
|
|
1612
1787
|
|
|
1613
1788
|
try:
|
|
1614
1789
|
with open(self.system_status_file, "r", encoding="utf-8") as f:
|
|
1615
1790
|
content = f.read()
|
|
1791
|
+
if "[" in content:
|
|
1792
|
+
content = content.replace("[", r"\[")
|
|
1616
1793
|
|
|
1617
1794
|
# Add separator instead of clearing screen
|
|
1618
1795
|
self.console.print("\n" + "=" * 80 + "\n")
|
|
@@ -1620,14 +1797,18 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1620
1797
|
# Create header
|
|
1621
1798
|
header_text = Text()
|
|
1622
1799
|
header_text.append(
|
|
1623
|
-
"📊 SYSTEM STATUS - Full Log",
|
|
1800
|
+
"📊 SYSTEM STATUS - Full Log",
|
|
1801
|
+
style=self.colors["header_style"],
|
|
1624
1802
|
)
|
|
1625
1803
|
header_text.append(
|
|
1626
|
-
"\nPress any key to return to agent selector",
|
|
1804
|
+
"\nPress any key to return to agent selector",
|
|
1805
|
+
style=self.colors["info"],
|
|
1627
1806
|
)
|
|
1628
1807
|
|
|
1629
1808
|
header_panel = Panel(
|
|
1630
|
-
header_text,
|
|
1809
|
+
header_text,
|
|
1810
|
+
box=DOUBLE,
|
|
1811
|
+
border_style=self.colors["border"],
|
|
1631
1812
|
)
|
|
1632
1813
|
|
|
1633
1814
|
# Create content panel
|
|
@@ -1649,7 +1830,7 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1649
1830
|
|
|
1650
1831
|
except Exception as e:
|
|
1651
1832
|
self.console.print(
|
|
1652
|
-
f"[{self.colors['error']}]Error reading system status file: {e}[/{self.colors['error']}]"
|
|
1833
|
+
f"[{self.colors['error']}]Error reading system status file: {e}[/{self.colors['error']}]",
|
|
1653
1834
|
)
|
|
1654
1835
|
|
|
1655
1836
|
def _create_agent_panel(self, agent_id: str) -> Panel:
|
|
@@ -1678,7 +1859,10 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1678
1859
|
|
|
1679
1860
|
max_lines = max(0, self.agent_panel_height - 3)
|
|
1680
1861
|
if not agent_content:
|
|
1681
|
-
content_text.append(
|
|
1862
|
+
content_text.append(
|
|
1863
|
+
"No activity yet...",
|
|
1864
|
+
style=self.colors["text"],
|
|
1865
|
+
)
|
|
1682
1866
|
else:
|
|
1683
1867
|
for line in agent_content[-max_lines:]:
|
|
1684
1868
|
formatted_line = self._format_content_line(line)
|
|
@@ -1700,7 +1884,8 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1700
1884
|
# Add interactive indicator if enabled
|
|
1701
1885
|
if self._keyboard_interactive_mode and hasattr(self, "_agent_keys"):
|
|
1702
1886
|
agent_key = next(
|
|
1703
|
-
(k for k, v in self._agent_keys.items() if v == agent_id),
|
|
1887
|
+
(k for k, v in self._agent_keys.items() if v == agent_id),
|
|
1888
|
+
None,
|
|
1704
1889
|
)
|
|
1705
1890
|
if agent_key:
|
|
1706
1891
|
title += f" [Press {agent_key}]"
|
|
@@ -1727,9 +1912,32 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1727
1912
|
if self._is_web_search_content(line):
|
|
1728
1913
|
return self._format_web_search_line(line)
|
|
1729
1914
|
|
|
1730
|
-
#
|
|
1731
|
-
|
|
1732
|
-
|
|
1915
|
+
# Wrap long lines instead of truncating
|
|
1916
|
+
is_error_message = any(
|
|
1917
|
+
error_indicator in line
|
|
1918
|
+
for error_indicator in [
|
|
1919
|
+
"❌ Error:",
|
|
1920
|
+
"Error:",
|
|
1921
|
+
"Exception:",
|
|
1922
|
+
"Traceback",
|
|
1923
|
+
"❌",
|
|
1924
|
+
]
|
|
1925
|
+
)
|
|
1926
|
+
if len(line) > self.max_line_length and not is_error_message:
|
|
1927
|
+
# Wrap the line at word boundaries
|
|
1928
|
+
wrapped_lines = []
|
|
1929
|
+
remaining = line
|
|
1930
|
+
while len(remaining) > self.max_line_length:
|
|
1931
|
+
# Find last space before max_line_length
|
|
1932
|
+
break_point = remaining[: self.max_line_length].rfind(" ")
|
|
1933
|
+
if break_point == -1: # No space found, break at max_line_length
|
|
1934
|
+
break_point = self.max_line_length
|
|
1935
|
+
wrapped_lines.append(remaining[:break_point])
|
|
1936
|
+
remaining = remaining[break_point:].lstrip()
|
|
1937
|
+
if remaining:
|
|
1938
|
+
wrapped_lines.append(remaining)
|
|
1939
|
+
# Join wrapped lines with newlines - Rich will handle the formatting
|
|
1940
|
+
line = "\n".join(wrapped_lines)
|
|
1733
1941
|
|
|
1734
1942
|
# Check for special prefixes and format accordingly
|
|
1735
1943
|
if line.startswith("→"):
|
|
@@ -1746,7 +1954,10 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1746
1954
|
if "jumped to latest" in line:
|
|
1747
1955
|
formatted.append(line[3:], style=f"bold {self.colors['info']}")
|
|
1748
1956
|
else:
|
|
1749
|
-
formatted.append(
|
|
1957
|
+
formatted.append(
|
|
1958
|
+
line[3:],
|
|
1959
|
+
style=f"italic {self.colors['warning']}",
|
|
1960
|
+
)
|
|
1750
1961
|
elif self._is_code_content(line):
|
|
1751
1962
|
# Code content - apply syntax highlighting
|
|
1752
1963
|
if self.enable_syntax_highlighting:
|
|
@@ -1754,11 +1965,54 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1754
1965
|
else:
|
|
1755
1966
|
formatted.append(line, style=f"bold {self.colors['info']}")
|
|
1756
1967
|
else:
|
|
1757
|
-
# Regular content
|
|
1968
|
+
# Regular content - escape to prevent markup interpretation
|
|
1758
1969
|
formatted.append(line, style=self.colors["text"])
|
|
1759
1970
|
|
|
1760
1971
|
return formatted
|
|
1761
1972
|
|
|
1973
|
+
def _create_final_presentation_panel(self) -> Panel:
|
|
1974
|
+
"""Create a panel for the final presentation display."""
|
|
1975
|
+
if not self._final_presentation_active:
|
|
1976
|
+
return None
|
|
1977
|
+
|
|
1978
|
+
# Create content text from accumulated presentation content
|
|
1979
|
+
content_text = Text()
|
|
1980
|
+
|
|
1981
|
+
if not self._final_presentation_content:
|
|
1982
|
+
content_text.append("No activity yet...", style=self.colors["text"])
|
|
1983
|
+
else:
|
|
1984
|
+
# Split content into lines and format each
|
|
1985
|
+
lines = self._final_presentation_content.split("\n")
|
|
1986
|
+
|
|
1987
|
+
# Calculate available lines based on terminal height minus footer (no header during presentation)
|
|
1988
|
+
# Footer: 8, some buffer: 5, separator: 3 = 16 total reserved
|
|
1989
|
+
available_height = max(10, self.terminal_size.height - 16)
|
|
1990
|
+
|
|
1991
|
+
# Show last N lines to fit in available space (auto-scroll to bottom)
|
|
1992
|
+
display_lines = lines[-available_height:] if len(lines) > available_height else lines
|
|
1993
|
+
|
|
1994
|
+
for line in display_lines:
|
|
1995
|
+
if line.strip():
|
|
1996
|
+
formatted_line = self._format_content_line(line)
|
|
1997
|
+
content_text.append(formatted_line)
|
|
1998
|
+
content_text.append("\n")
|
|
1999
|
+
|
|
2000
|
+
# Panel title with agent and vote info
|
|
2001
|
+
title = f"🎤 Final Presentation from {self._final_presentation_agent}"
|
|
2002
|
+
if self._final_presentation_vote_results and self._final_presentation_vote_results.get("vote_counts"):
|
|
2003
|
+
vote_count = self._final_presentation_vote_results["vote_counts"].get(self._final_presentation_agent, 0)
|
|
2004
|
+
title += f" (Selected with {vote_count} votes)"
|
|
2005
|
+
title += " [Press f]"
|
|
2006
|
+
|
|
2007
|
+
# Create panel without fixed height so bottom border is always visible
|
|
2008
|
+
return Panel(
|
|
2009
|
+
content_text,
|
|
2010
|
+
title=f"[{self.colors['success']}]{title}[/{self.colors['success']}]",
|
|
2011
|
+
border_style=self.colors["success"],
|
|
2012
|
+
box=DOUBLE,
|
|
2013
|
+
expand=True, # Full width
|
|
2014
|
+
)
|
|
2015
|
+
|
|
1762
2016
|
def _format_presentation_content(self, content: str) -> Text:
|
|
1763
2017
|
"""Format presentation content with enhanced styling for orchestrator queries."""
|
|
1764
2018
|
formatted = Text()
|
|
@@ -1775,7 +2029,10 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1775
2029
|
if line.startswith("**") and line.endswith("**"):
|
|
1776
2030
|
# Bold emphasis for important points
|
|
1777
2031
|
clean_line = line.strip("*").strip()
|
|
1778
|
-
formatted.append(
|
|
2032
|
+
formatted.append(
|
|
2033
|
+
clean_line,
|
|
2034
|
+
style=f"bold {self.colors['success']}",
|
|
2035
|
+
)
|
|
1779
2036
|
elif line.startswith("- ") or line.startswith("• "):
|
|
1780
2037
|
# Bullet points with enhanced styling
|
|
1781
2038
|
formatted.append(line[:2], style=self.colors["primary"])
|
|
@@ -1786,11 +2043,13 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1786
2043
|
clean_header = line.lstrip("# ").strip()
|
|
1787
2044
|
if header_level <= 2:
|
|
1788
2045
|
formatted.append(
|
|
1789
|
-
clean_header,
|
|
2046
|
+
clean_header,
|
|
2047
|
+
style=f"bold {self.colors['header_style']}",
|
|
1790
2048
|
)
|
|
1791
2049
|
else:
|
|
1792
2050
|
formatted.append(
|
|
1793
|
-
clean_header,
|
|
2051
|
+
clean_header,
|
|
2052
|
+
style=f"bold {self.colors['primary']}",
|
|
1794
2053
|
)
|
|
1795
2054
|
elif self._is_code_content(line):
|
|
1796
2055
|
# Code blocks in presentations
|
|
@@ -1825,7 +2084,10 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1825
2084
|
# Handle different types of web search lines
|
|
1826
2085
|
if "[Provider Tool: Web Search] Starting search" in line:
|
|
1827
2086
|
formatted.append("🔍 ", style=self.colors["info"])
|
|
1828
|
-
formatted.append(
|
|
2087
|
+
formatted.append(
|
|
2088
|
+
"Web search starting...",
|
|
2089
|
+
style=self.colors["text"],
|
|
2090
|
+
)
|
|
1829
2091
|
elif "[Provider Tool: Web Search] Searching" in line:
|
|
1830
2092
|
formatted.append("🔍 ", style=self.colors["warning"])
|
|
1831
2093
|
formatted.append("Searching...", style=self.colors["text"])
|
|
@@ -1834,7 +2096,11 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1834
2096
|
formatted.append("Search completed", style=self.colors["text"])
|
|
1835
2097
|
elif any(
|
|
1836
2098
|
pattern in line
|
|
1837
|
-
for pattern in [
|
|
2099
|
+
for pattern in [
|
|
2100
|
+
"🔍 [Search Query]",
|
|
2101
|
+
"Search Query:",
|
|
2102
|
+
"[Search Query]",
|
|
2103
|
+
]
|
|
1838
2104
|
):
|
|
1839
2105
|
# Extract and display search query with better formatting
|
|
1840
2106
|
# Try different patterns to extract the query
|
|
@@ -1855,17 +2121,19 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1855
2121
|
|
|
1856
2122
|
if query:
|
|
1857
2123
|
# Format the search query nicely
|
|
1858
|
-
|
|
1859
|
-
# For long queries, show beginning and end
|
|
1860
|
-
query = query[:60] + "..." + query[-17:]
|
|
2124
|
+
# Show full query without truncation
|
|
1861
2125
|
formatted.append("🔍 Search: ", style=self.colors["info"])
|
|
1862
|
-
formatted.append(
|
|
2126
|
+
formatted.append(
|
|
2127
|
+
f'"{query}"',
|
|
2128
|
+
style=f"italic {self.colors['text']}",
|
|
2129
|
+
)
|
|
1863
2130
|
else:
|
|
1864
2131
|
formatted.append("🔍 Search query", style=self.colors["info"])
|
|
1865
2132
|
else:
|
|
1866
2133
|
# For long web search results, truncate more aggressively
|
|
1867
2134
|
max_web_length = min(
|
|
1868
|
-
self.max_line_length // 2,
|
|
2135
|
+
self.max_line_length // 2,
|
|
2136
|
+
60,
|
|
1869
2137
|
) # Much shorter for web content
|
|
1870
2138
|
if len(line) > max_web_length:
|
|
1871
2139
|
# Try to find a natural break point
|
|
@@ -1892,12 +2160,7 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1892
2160
|
if len(content) > 1000 and self._is_web_search_content(content):
|
|
1893
2161
|
# Check if it contains mostly URLs and technical details
|
|
1894
2162
|
url_count = content.count("http")
|
|
1895
|
-
technical_indicators = (
|
|
1896
|
-
content.count("[")
|
|
1897
|
-
+ content.count("]")
|
|
1898
|
-
+ content.count("(")
|
|
1899
|
-
+ content.count(")")
|
|
1900
|
-
)
|
|
2163
|
+
technical_indicators = content.count("[") + content.count("]") + content.count("(") + content.count(")")
|
|
1901
2164
|
|
|
1902
2165
|
# If more than 50% seems to be technical metadata, filter it
|
|
1903
2166
|
if url_count > 5 or technical_indicators > len(content) * 0.1:
|
|
@@ -1921,7 +2184,7 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1921
2184
|
|
|
1922
2185
|
return False
|
|
1923
2186
|
|
|
1924
|
-
def _truncate_web_search_content(self, agent_id: str):
|
|
2187
|
+
def _truncate_web_search_content(self, agent_id: str) -> None:
|
|
1925
2188
|
"""Truncate web search content when important status updates occur."""
|
|
1926
2189
|
if agent_id not in self.agent_outputs or not self.agent_outputs[agent_id]:
|
|
1927
2190
|
return
|
|
@@ -1943,22 +2206,22 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1943
2206
|
# Keep only the first line (search start) and last few lines (search end/results)
|
|
1944
2207
|
truncated_web_search = (
|
|
1945
2208
|
web_search_lines[:1] # First line (search start)
|
|
1946
|
-
+ [
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
] # Last few lines
|
|
2209
|
+
+ [
|
|
2210
|
+
"🔍 ... (web search content truncated due to status update) ...",
|
|
2211
|
+
]
|
|
2212
|
+
+ web_search_lines[-(self._max_web_search_lines - 2) :] # Last few lines
|
|
1950
2213
|
)
|
|
1951
2214
|
|
|
1952
2215
|
# Reconstruct the content with truncated web search
|
|
1953
2216
|
# Keep recent non-web-search content and add truncated web search
|
|
1954
|
-
recent_non_web = non_web_search_lines[
|
|
1955
|
-
-(max(5, self.max_content_lines - len(truncated_web_search))) :
|
|
1956
|
-
]
|
|
2217
|
+
recent_non_web = non_web_search_lines[-(max(5, self.max_content_lines - len(truncated_web_search))) :]
|
|
1957
2218
|
self.agent_outputs[agent_id] = recent_non_web + truncated_web_search
|
|
1958
2219
|
|
|
1959
2220
|
# Add a status jump indicator only if content was actually truncated
|
|
1960
2221
|
if len(web_search_lines) > self._max_web_search_lines:
|
|
1961
|
-
self.agent_outputs[agent_id].append(
|
|
2222
|
+
self.agent_outputs[agent_id].append(
|
|
2223
|
+
"⚡ Status updated - jumped to latest",
|
|
2224
|
+
)
|
|
1962
2225
|
|
|
1963
2226
|
def _is_code_content(self, content: str) -> bool:
|
|
1964
2227
|
"""Check if content appears to be code."""
|
|
@@ -1978,22 +2241,16 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
1978
2241
|
return Text(content, style=f"bold {self.colors['info']}")
|
|
1979
2242
|
else:
|
|
1980
2243
|
return Text(content, style=f"bold {self.colors['info']}")
|
|
1981
|
-
except:
|
|
2244
|
+
except Exception:
|
|
1982
2245
|
return Text(content, style=f"bold {self.colors['info']}")
|
|
1983
2246
|
|
|
1984
2247
|
def _detect_language(self, content: str) -> Optional[str]:
|
|
1985
2248
|
"""Detect programming language from content."""
|
|
1986
2249
|
content_lower = content.lower()
|
|
1987
2250
|
|
|
1988
|
-
if any(
|
|
1989
|
-
keyword in content_lower
|
|
1990
|
-
for keyword in ["def ", "import ", "class ", "python"]
|
|
1991
|
-
):
|
|
2251
|
+
if any(keyword in content_lower for keyword in ["def ", "import ", "class ", "python"]):
|
|
1992
2252
|
return "python"
|
|
1993
|
-
elif any(
|
|
1994
|
-
keyword in content_lower
|
|
1995
|
-
for keyword in ["function", "var ", "let ", "const "]
|
|
1996
|
-
):
|
|
2253
|
+
elif any(keyword in content_lower for keyword in ["function", "var ", "let ", "const "]):
|
|
1997
2254
|
return "javascript"
|
|
1998
2255
|
elif any(keyword in content_lower for keyword in ["<", ">", "html", "div"]):
|
|
1999
2256
|
return "html"
|
|
@@ -2031,19 +2288,11 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2031
2288
|
def _get_backend_name(self, agent_id: str) -> str:
|
|
2032
2289
|
"""Get backend name for agent."""
|
|
2033
2290
|
try:
|
|
2034
|
-
if (
|
|
2035
|
-
hasattr(self, "orchestrator")
|
|
2036
|
-
and self.orchestrator
|
|
2037
|
-
and hasattr(self.orchestrator, "agents")
|
|
2038
|
-
):
|
|
2291
|
+
if hasattr(self, "orchestrator") and self.orchestrator and hasattr(self.orchestrator, "agents"):
|
|
2039
2292
|
agent = self.orchestrator.agents.get(agent_id)
|
|
2040
|
-
if (
|
|
2041
|
-
agent
|
|
2042
|
-
and hasattr(agent, "backend")
|
|
2043
|
-
and hasattr(agent.backend, "get_provider_name")
|
|
2044
|
-
):
|
|
2293
|
+
if agent and hasattr(agent, "backend") and hasattr(agent.backend, "get_provider_name"):
|
|
2045
2294
|
return agent.backend.get_provider_name()
|
|
2046
|
-
except:
|
|
2295
|
+
except Exception:
|
|
2047
2296
|
pass
|
|
2048
2297
|
return "Unknown"
|
|
2049
2298
|
|
|
@@ -2052,7 +2301,10 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2052
2301
|
footer_content = Text()
|
|
2053
2302
|
|
|
2054
2303
|
# Agent status summary
|
|
2055
|
-
footer_content.append(
|
|
2304
|
+
footer_content.append(
|
|
2305
|
+
"📊 Agent Status: ",
|
|
2306
|
+
style=self.colors["primary"],
|
|
2307
|
+
)
|
|
2056
2308
|
|
|
2057
2309
|
status_counts = {}
|
|
2058
2310
|
for status in self.agent_status.values():
|
|
@@ -2063,20 +2315,36 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2063
2315
|
emoji = self._get_status_emoji(status, status)
|
|
2064
2316
|
status_parts.append(f"{emoji} {status.title()}: {count}")
|
|
2065
2317
|
|
|
2066
|
-
|
|
2318
|
+
# Add final presentation status if active
|
|
2319
|
+
if self._final_presentation_active:
|
|
2320
|
+
status_parts.append("🎤 Final Presentation: Active")
|
|
2321
|
+
elif hasattr(self, "_stored_final_presentation") and self._stored_final_presentation:
|
|
2322
|
+
status_parts.append("🎤 Final Presentation: Complete")
|
|
2323
|
+
|
|
2324
|
+
footer_content.append(
|
|
2325
|
+
" | ".join(status_parts),
|
|
2326
|
+
style=self.colors["text"],
|
|
2327
|
+
)
|
|
2067
2328
|
footer_content.append("\n")
|
|
2068
2329
|
|
|
2069
2330
|
# Recent events
|
|
2070
2331
|
if self.orchestrator_events:
|
|
2071
|
-
footer_content.append(
|
|
2332
|
+
footer_content.append(
|
|
2333
|
+
"📋 Recent Events:\n",
|
|
2334
|
+
style=self.colors["primary"],
|
|
2335
|
+
)
|
|
2072
2336
|
recent_events = self.orchestrator_events[-3:] # Show last 3 events
|
|
2073
2337
|
for event in recent_events:
|
|
2074
|
-
footer_content.append(
|
|
2338
|
+
footer_content.append(
|
|
2339
|
+
f" • {event}\n",
|
|
2340
|
+
style=self.colors["text"],
|
|
2341
|
+
)
|
|
2075
2342
|
|
|
2076
2343
|
# Log file info
|
|
2077
2344
|
if self.log_filename:
|
|
2078
2345
|
footer_content.append(
|
|
2079
|
-
f"📁 Log: {self.log_filename}\n",
|
|
2346
|
+
f"📁 Log: {self.log_filename}\n",
|
|
2347
|
+
style=self.colors["info"],
|
|
2080
2348
|
)
|
|
2081
2349
|
|
|
2082
2350
|
# Interactive mode instructions
|
|
@@ -2092,10 +2360,17 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2092
2360
|
)
|
|
2093
2361
|
else:
|
|
2094
2362
|
footer_content.append(
|
|
2095
|
-
"🎮 Live Mode Hotkeys: Press 1-",
|
|
2363
|
+
"🎮 Live Mode Hotkeys: Press 1-",
|
|
2364
|
+
style=self.colors["primary"],
|
|
2096
2365
|
)
|
|
2366
|
+
hotkeys = f"{len(self.agent_ids)} to open agent files in editor, 's' for system status"
|
|
2367
|
+
|
|
2368
|
+
# Add 'f' key if final presentation is available
|
|
2369
|
+
if hasattr(self, "_stored_final_presentation") and self._stored_final_presentation:
|
|
2370
|
+
hotkeys += ", 'f' for final presentation"
|
|
2371
|
+
|
|
2097
2372
|
footer_content.append(
|
|
2098
|
-
|
|
2373
|
+
hotkeys,
|
|
2099
2374
|
style=self.colors["text"],
|
|
2100
2375
|
)
|
|
2101
2376
|
footer_content.append(
|
|
@@ -2111,8 +2386,11 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2111
2386
|
)
|
|
2112
2387
|
|
|
2113
2388
|
def update_agent_content(
|
|
2114
|
-
self,
|
|
2115
|
-
|
|
2389
|
+
self,
|
|
2390
|
+
agent_id: str,
|
|
2391
|
+
content: str,
|
|
2392
|
+
content_type: str = "thinking",
|
|
2393
|
+
) -> None:
|
|
2116
2394
|
"""Update content for a specific agent with rich formatting and file output."""
|
|
2117
2395
|
|
|
2118
2396
|
if agent_id not in self.agent_ids:
|
|
@@ -2131,18 +2409,10 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2131
2409
|
"status",
|
|
2132
2410
|
"presentation",
|
|
2133
2411
|
"tool",
|
|
2134
|
-
] or any(
|
|
2135
|
-
keyword in content.lower() for keyword in self._status_change_keywords
|
|
2136
|
-
)
|
|
2412
|
+
] or any(keyword in content.lower() for keyword in self._status_change_keywords)
|
|
2137
2413
|
|
|
2138
2414
|
# If status jump is enabled and this is a status change, truncate web search content
|
|
2139
|
-
if
|
|
2140
|
-
self._status_jump_enabled
|
|
2141
|
-
and is_status_change
|
|
2142
|
-
and self._web_search_truncate_on_status_change
|
|
2143
|
-
and self.agent_outputs[agent_id]
|
|
2144
|
-
):
|
|
2145
|
-
|
|
2415
|
+
if self._status_jump_enabled and is_status_change and self._web_search_truncate_on_status_change and self.agent_outputs[agent_id]:
|
|
2146
2416
|
self._truncate_web_search_content(agent_id)
|
|
2147
2417
|
|
|
2148
2418
|
# Enhanced filtering for web search content
|
|
@@ -2150,7 +2420,11 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2150
2420
|
return
|
|
2151
2421
|
|
|
2152
2422
|
# Process content with buffering for smoother text display
|
|
2153
|
-
self._process_content_with_buffering(
|
|
2423
|
+
self._process_content_with_buffering(
|
|
2424
|
+
agent_id,
|
|
2425
|
+
content,
|
|
2426
|
+
content_type,
|
|
2427
|
+
)
|
|
2154
2428
|
|
|
2155
2429
|
# Categorize updates by priority for layered refresh strategy
|
|
2156
2430
|
self._categorize_update(agent_id, content_type, content)
|
|
@@ -2161,14 +2435,15 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2161
2435
|
"status",
|
|
2162
2436
|
"presentation",
|
|
2163
2437
|
"error",
|
|
2164
|
-
] or any(
|
|
2165
|
-
keyword in content.lower() for keyword in self._status_change_keywords
|
|
2166
|
-
)
|
|
2438
|
+
] or any(keyword in content.lower() for keyword in self._status_change_keywords)
|
|
2167
2439
|
self._schedule_layered_update(agent_id, is_critical)
|
|
2168
2440
|
|
|
2169
2441
|
def _process_content_with_buffering(
|
|
2170
|
-
self,
|
|
2171
|
-
|
|
2442
|
+
self,
|
|
2443
|
+
agent_id: str,
|
|
2444
|
+
content: str,
|
|
2445
|
+
content_type: str,
|
|
2446
|
+
) -> None:
|
|
2172
2447
|
"""Process content with buffering to accumulate text chunks."""
|
|
2173
2448
|
# Cancel any existing buffer timer
|
|
2174
2449
|
if self._buffer_timers.get(agent_id):
|
|
@@ -2176,10 +2451,7 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2176
2451
|
self._buffer_timers[agent_id] = None
|
|
2177
2452
|
|
|
2178
2453
|
# Special handling for content that should be displayed immediately
|
|
2179
|
-
if
|
|
2180
|
-
content_type in ["tool", "status", "presentation", "error"]
|
|
2181
|
-
or "\n" in content
|
|
2182
|
-
):
|
|
2454
|
+
if content_type in ["tool", "status", "presentation", "error"] or "\n" in content:
|
|
2183
2455
|
# Flush any existing buffer first
|
|
2184
2456
|
self._flush_buffer(agent_id)
|
|
2185
2457
|
|
|
@@ -2206,7 +2478,7 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2206
2478
|
# Set a timer to flush the buffer if no more content arrives
|
|
2207
2479
|
self._set_buffer_timer(agent_id)
|
|
2208
2480
|
|
|
2209
|
-
def _flush_buffer(self, agent_id: str):
|
|
2481
|
+
def _flush_buffer(self, agent_id: str) -> None:
|
|
2210
2482
|
"""Flush the buffer for a specific agent."""
|
|
2211
2483
|
if agent_id in self._text_buffers and self._text_buffers[agent_id]:
|
|
2212
2484
|
buffer_content = self._text_buffers[agent_id].strip()
|
|
@@ -2219,7 +2491,7 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2219
2491
|
self._buffer_timers[agent_id].cancel()
|
|
2220
2492
|
self._buffer_timers[agent_id] = None
|
|
2221
2493
|
|
|
2222
|
-
def _set_buffer_timer(self, agent_id: str):
|
|
2494
|
+
def _set_buffer_timer(self, agent_id: str) -> None:
|
|
2223
2495
|
"""Set a timer to flush the buffer after a timeout."""
|
|
2224
2496
|
if self._shutdown_flag:
|
|
2225
2497
|
return
|
|
@@ -2228,7 +2500,7 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2228
2500
|
if self._buffer_timers.get(agent_id):
|
|
2229
2501
|
self._buffer_timers[agent_id].cancel()
|
|
2230
2502
|
|
|
2231
|
-
def timeout_flush():
|
|
2503
|
+
def timeout_flush() -> None:
|
|
2232
2504
|
with self._lock:
|
|
2233
2505
|
if agent_id in self._text_buffers and self._text_buffers[agent_id]:
|
|
2234
2506
|
self._flush_buffer(agent_id)
|
|
@@ -2237,15 +2509,25 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2237
2509
|
self._schedule_async_update(force_update=True)
|
|
2238
2510
|
|
|
2239
2511
|
self._buffer_timers[agent_id] = threading.Timer(
|
|
2240
|
-
self._buffer_timeout,
|
|
2512
|
+
self._buffer_timeout,
|
|
2513
|
+
timeout_flush,
|
|
2241
2514
|
)
|
|
2242
2515
|
self._buffer_timers[agent_id].start()
|
|
2243
2516
|
|
|
2244
|
-
def _write_to_agent_file(
|
|
2517
|
+
def _write_to_agent_file(
|
|
2518
|
+
self,
|
|
2519
|
+
agent_id: str,
|
|
2520
|
+
content: str,
|
|
2521
|
+
content_type: str,
|
|
2522
|
+
) -> None:
|
|
2245
2523
|
"""Write content to agent's individual txt file."""
|
|
2246
2524
|
if agent_id not in self.agent_files:
|
|
2247
2525
|
return
|
|
2248
2526
|
|
|
2527
|
+
# Skip debug content from txt files
|
|
2528
|
+
if content_type == "debug":
|
|
2529
|
+
return
|
|
2530
|
+
|
|
2249
2531
|
try:
|
|
2250
2532
|
file_path = self.agent_files[agent_id]
|
|
2251
2533
|
timestamp = time.strftime("%H:%M:%S")
|
|
@@ -2263,9 +2545,7 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2263
2545
|
|
|
2264
2546
|
if has_emoji:
|
|
2265
2547
|
# Format with newline and timestamp when emojis are present
|
|
2266
|
-
formatted_content =
|
|
2267
|
-
f"\n[{timestamp}] [{content_type.upper()}] {content}\n"
|
|
2268
|
-
)
|
|
2548
|
+
formatted_content = f"\n[{timestamp}] {content}\n"
|
|
2269
2549
|
else:
|
|
2270
2550
|
# Regular format without extra newline
|
|
2271
2551
|
formatted_content = f"{content}"
|
|
@@ -2274,11 +2554,11 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2274
2554
|
with open(file_path, "a", encoding="utf-8") as f:
|
|
2275
2555
|
f.write(formatted_content)
|
|
2276
2556
|
|
|
2277
|
-
except Exception
|
|
2557
|
+
except Exception:
|
|
2278
2558
|
# Handle file write errors gracefully
|
|
2279
2559
|
pass
|
|
2280
2560
|
|
|
2281
|
-
def _write_system_status(self):
|
|
2561
|
+
def _write_system_status(self) -> None:
|
|
2282
2562
|
"""Write current system status to system status file - shows orchestrator events chronologically by time."""
|
|
2283
2563
|
if not self.system_status_file:
|
|
2284
2564
|
return
|
|
@@ -2288,7 +2568,26 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2288
2568
|
with open(self.system_status_file, "w", encoding="utf-8") as f:
|
|
2289
2569
|
f.write("=== SYSTEM STATUS LOG ===\n\n")
|
|
2290
2570
|
|
|
2571
|
+
# Agent Status Summary
|
|
2572
|
+
f.write("📊 Agent Status:\n")
|
|
2573
|
+
status_counts = {}
|
|
2574
|
+
for status in self.agent_status.values():
|
|
2575
|
+
status_counts[status] = status_counts.get(status, 0) + 1
|
|
2576
|
+
|
|
2577
|
+
for status, count in status_counts.items():
|
|
2578
|
+
emoji = self._get_status_emoji(status, status)
|
|
2579
|
+
f.write(f" {emoji} {status.title()}: {count}\n")
|
|
2580
|
+
|
|
2581
|
+
# Final Presentation Status
|
|
2582
|
+
if self._final_presentation_active:
|
|
2583
|
+
f.write(" 🎤 Final Presentation: Active\n")
|
|
2584
|
+
elif hasattr(self, "_stored_final_presentation") and self._stored_final_presentation:
|
|
2585
|
+
f.write(" 🎤 Final Presentation: Complete\n")
|
|
2586
|
+
|
|
2587
|
+
f.write("\n")
|
|
2588
|
+
|
|
2291
2589
|
# Show all orchestrator events in chronological order by time
|
|
2590
|
+
f.write("📋 Orchestrator Events:\n")
|
|
2292
2591
|
if self.orchestrator_events:
|
|
2293
2592
|
for event in self.orchestrator_events:
|
|
2294
2593
|
f.write(f" • {event}\n")
|
|
@@ -2297,40 +2596,32 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2297
2596
|
|
|
2298
2597
|
f.write("\n")
|
|
2299
2598
|
|
|
2300
|
-
except Exception
|
|
2599
|
+
except Exception:
|
|
2301
2600
|
# Handle file write errors gracefully
|
|
2302
2601
|
pass
|
|
2303
2602
|
|
|
2304
|
-
def update_agent_status(self, agent_id: str, status: str):
|
|
2603
|
+
def update_agent_status(self, agent_id: str, status: str) -> None:
|
|
2305
2604
|
"""Update status for a specific agent with rich indicators."""
|
|
2306
2605
|
if agent_id not in self.agent_ids:
|
|
2307
2606
|
return
|
|
2308
2607
|
|
|
2309
2608
|
with self._lock:
|
|
2310
2609
|
old_status = self.agent_status.get(agent_id, "waiting")
|
|
2311
|
-
last_tracked_status = self._last_agent_status.get(
|
|
2610
|
+
last_tracked_status = self._last_agent_status.get(
|
|
2611
|
+
agent_id,
|
|
2612
|
+
"waiting",
|
|
2613
|
+
)
|
|
2312
2614
|
|
|
2313
2615
|
# Check if this is a vote-related status change
|
|
2314
2616
|
current_activity = self.agent_activity.get(agent_id, "")
|
|
2315
|
-
is_vote_status = (
|
|
2316
|
-
"voted" in status.lower() or "voted" in current_activity.lower()
|
|
2317
|
-
)
|
|
2617
|
+
is_vote_status = "voted" in status.lower() or "voted" in current_activity.lower()
|
|
2318
2618
|
|
|
2319
2619
|
# Force update for vote statuses or actual status changes
|
|
2320
|
-
should_update = (
|
|
2321
|
-
old_status != status and last_tracked_status != status
|
|
2322
|
-
) or is_vote_status
|
|
2620
|
+
should_update = (old_status != status and last_tracked_status != status) or is_vote_status
|
|
2323
2621
|
|
|
2324
2622
|
if should_update:
|
|
2325
2623
|
# Truncate web search content when status changes for immediate focus on new status
|
|
2326
|
-
if
|
|
2327
|
-
self._status_jump_enabled
|
|
2328
|
-
and self._web_search_truncate_on_status_change
|
|
2329
|
-
and old_status != status
|
|
2330
|
-
and agent_id in self.agent_outputs
|
|
2331
|
-
and self.agent_outputs[agent_id]
|
|
2332
|
-
):
|
|
2333
|
-
|
|
2624
|
+
if self._status_jump_enabled and self._web_search_truncate_on_status_change and old_status != status and agent_id in self.agent_outputs and self.agent_outputs[agent_id]:
|
|
2334
2625
|
self._truncate_web_search_content(agent_id)
|
|
2335
2626
|
|
|
2336
2627
|
super().update_agent_status(agent_id, status)
|
|
@@ -2349,7 +2640,7 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2349
2640
|
# Update the internal status but don't refresh display if already tracked
|
|
2350
2641
|
super().update_agent_status(agent_id, status)
|
|
2351
2642
|
|
|
2352
|
-
def add_orchestrator_event(self, event: str):
|
|
2643
|
+
def add_orchestrator_event(self, event: str) -> None:
|
|
2353
2644
|
"""Add an orchestrator coordination event with timestamp."""
|
|
2354
2645
|
with self._lock:
|
|
2355
2646
|
if self.show_timestamps:
|
|
@@ -2359,32 +2650,26 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2359
2650
|
formatted_event = event
|
|
2360
2651
|
|
|
2361
2652
|
# Check for duplicate events
|
|
2362
|
-
if (
|
|
2363
|
-
hasattr(self, "orchestrator_events")
|
|
2364
|
-
and self.orchestrator_events
|
|
2365
|
-
and self.orchestrator_events[-1] == formatted_event
|
|
2366
|
-
):
|
|
2653
|
+
if hasattr(self, "orchestrator_events") and self.orchestrator_events and self.orchestrator_events[-1] == formatted_event:
|
|
2367
2654
|
return # Skip duplicate events
|
|
2368
2655
|
|
|
2369
2656
|
super().add_orchestrator_event(formatted_event)
|
|
2370
2657
|
|
|
2371
2658
|
# Only update footer for important events that indicate real status changes
|
|
2372
|
-
if any(
|
|
2373
|
-
keyword in event.lower() for keyword in self._important_event_keywords
|
|
2374
|
-
):
|
|
2659
|
+
if any(keyword in event.lower() for keyword in self._important_event_keywords):
|
|
2375
2660
|
# Mark footer for async update
|
|
2376
2661
|
self._pending_updates.add("footer")
|
|
2377
2662
|
self._schedule_async_update(force_update=True)
|
|
2378
2663
|
# Write system status update for important events
|
|
2379
2664
|
self._write_system_status()
|
|
2380
2665
|
|
|
2381
|
-
def display_vote_results(self, vote_results: Dict[str, Any]):
|
|
2666
|
+
def display_vote_results(self, vote_results: Dict[str, Any]) -> None:
|
|
2382
2667
|
"""Display voting results in a formatted rich panel."""
|
|
2383
2668
|
if not vote_results or not vote_results.get("vote_counts"):
|
|
2384
2669
|
return
|
|
2385
2670
|
|
|
2386
2671
|
# Stop live display temporarily for clean voting results output
|
|
2387
|
-
|
|
2672
|
+
self.live is not None
|
|
2388
2673
|
if self.live:
|
|
2389
2674
|
self.live.stop()
|
|
2390
2675
|
self.live = None
|
|
@@ -2400,31 +2685,49 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2400
2685
|
# Vote count section
|
|
2401
2686
|
vote_content.append("📊 Vote Count:\n", style=self.colors["primary"])
|
|
2402
2687
|
for agent_id, count in sorted(
|
|
2403
|
-
vote_counts.items(),
|
|
2688
|
+
vote_counts.items(),
|
|
2689
|
+
key=lambda x: x[1],
|
|
2690
|
+
reverse=True,
|
|
2404
2691
|
):
|
|
2405
2692
|
winner_mark = "🏆" if agent_id == winner else " "
|
|
2406
2693
|
tie_mark = " (tie-broken)" if is_tie and agent_id == winner else ""
|
|
2407
2694
|
vote_content.append(
|
|
2408
2695
|
f" {winner_mark} {agent_id}: {count} vote{'s' if count != 1 else ''}{tie_mark}\n",
|
|
2409
|
-
style=(
|
|
2410
|
-
self.colors["success"]
|
|
2411
|
-
if agent_id == winner
|
|
2412
|
-
else self.colors["text"]
|
|
2413
|
-
),
|
|
2696
|
+
style=(self.colors["success"] if agent_id == winner else self.colors["text"]),
|
|
2414
2697
|
)
|
|
2415
2698
|
|
|
2416
2699
|
# Vote details section
|
|
2417
2700
|
if voter_details:
|
|
2418
|
-
vote_content.append(
|
|
2701
|
+
vote_content.append(
|
|
2702
|
+
"\n🔍 Vote Details:\n",
|
|
2703
|
+
style=self.colors["primary"],
|
|
2704
|
+
)
|
|
2419
2705
|
for voted_for, voters in voter_details.items():
|
|
2420
|
-
vote_content.append(
|
|
2706
|
+
vote_content.append(
|
|
2707
|
+
f" → {voted_for}:\n",
|
|
2708
|
+
style=self.colors["info"],
|
|
2709
|
+
)
|
|
2421
2710
|
for voter_info in voters:
|
|
2422
2711
|
voter = voter_info["voter"]
|
|
2423
2712
|
reason = voter_info["reason"]
|
|
2424
2713
|
vote_content.append(
|
|
2425
|
-
f' • {voter}: "{reason}"\n',
|
|
2714
|
+
f' • {voter}: "{reason}"\n',
|
|
2715
|
+
style=self.colors["text"],
|
|
2426
2716
|
)
|
|
2427
2717
|
|
|
2718
|
+
# Agent mapping section
|
|
2719
|
+
agent_mapping = vote_results.get("agent_mapping", {})
|
|
2720
|
+
if agent_mapping:
|
|
2721
|
+
vote_content.append(
|
|
2722
|
+
"\n🔀 Agent Mapping:\n",
|
|
2723
|
+
style=self.colors["primary"],
|
|
2724
|
+
)
|
|
2725
|
+
for anon_id, real_id in sorted(agent_mapping.items()):
|
|
2726
|
+
vote_content.append(
|
|
2727
|
+
f" {anon_id} → {real_id}\n",
|
|
2728
|
+
style=self.colors["info"],
|
|
2729
|
+
)
|
|
2730
|
+
|
|
2428
2731
|
# Tie-breaking info
|
|
2429
2732
|
if is_tie:
|
|
2430
2733
|
vote_content.append(
|
|
@@ -2450,68 +2753,214 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2450
2753
|
)
|
|
2451
2754
|
|
|
2452
2755
|
self.console.print(voting_panel)
|
|
2453
|
-
self.console.print()
|
|
2454
2756
|
|
|
2455
|
-
#
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2757
|
+
# Don't restart live display - leave it stopped to show static results
|
|
2758
|
+
# This prevents duplication from stop/restart cycles
|
|
2759
|
+
|
|
2760
|
+
def display_coordination_table(self) -> None:
|
|
2761
|
+
"""Display the coordination table showing the full coordination flow."""
|
|
2762
|
+
try:
|
|
2763
|
+
# Stop live display temporarily
|
|
2764
|
+
self.live is not None
|
|
2765
|
+
if self.live:
|
|
2766
|
+
self.live.stop()
|
|
2767
|
+
self.live = None
|
|
2768
|
+
|
|
2769
|
+
# Get coordination events from orchestrator
|
|
2770
|
+
if not hasattr(self, "orchestrator") or not self.orchestrator:
|
|
2771
|
+
print("No orchestrator available for table generation")
|
|
2772
|
+
return
|
|
2773
|
+
|
|
2774
|
+
tracker = getattr(self.orchestrator, "coordination_tracker", None)
|
|
2775
|
+
if not tracker:
|
|
2776
|
+
print("No coordination tracker available")
|
|
2777
|
+
return
|
|
2778
|
+
|
|
2779
|
+
# Get events data with session metadata
|
|
2780
|
+
events_data = [event.to_dict() for event in tracker.events]
|
|
2781
|
+
|
|
2782
|
+
# Create session data with metadata (same format as saved file)
|
|
2783
|
+
session_data = {
|
|
2784
|
+
"session_metadata": {
|
|
2785
|
+
"user_prompt": tracker.user_prompt,
|
|
2786
|
+
"agent_ids": tracker.agent_ids,
|
|
2787
|
+
"start_time": tracker.start_time,
|
|
2788
|
+
"end_time": tracker.end_time,
|
|
2789
|
+
"final_winner": tracker.final_winner,
|
|
2790
|
+
},
|
|
2791
|
+
"events": events_data,
|
|
2792
|
+
}
|
|
2793
|
+
|
|
2794
|
+
# Import and use the table generator
|
|
2795
|
+
from massgen.frontend.displays.create_coordination_table import (
|
|
2796
|
+
CoordinationTableBuilder,
|
|
2463
2797
|
)
|
|
2464
|
-
|
|
2798
|
+
|
|
2799
|
+
# Generate Rich event table with legend
|
|
2800
|
+
builder = CoordinationTableBuilder(session_data)
|
|
2801
|
+
result = builder.generate_rich_event_table()
|
|
2802
|
+
|
|
2803
|
+
if result:
|
|
2804
|
+
legend, rich_table = result # Unpack tuple
|
|
2805
|
+
|
|
2806
|
+
# Import console utilities for cross-platform display
|
|
2807
|
+
from rich.console import Console
|
|
2808
|
+
from rich.panel import Panel
|
|
2809
|
+
from rich.text import Text
|
|
2810
|
+
|
|
2811
|
+
from massgen.frontend.displays.create_coordination_table import (
|
|
2812
|
+
display_scrollable_content_macos,
|
|
2813
|
+
display_with_native_pager,
|
|
2814
|
+
get_optimal_display_method,
|
|
2815
|
+
)
|
|
2816
|
+
|
|
2817
|
+
# Create a temporary console for paging
|
|
2818
|
+
temp_console = Console()
|
|
2819
|
+
|
|
2820
|
+
# Create content to display
|
|
2821
|
+
content = []
|
|
2822
|
+
|
|
2823
|
+
# Add title
|
|
2824
|
+
title_text = Text()
|
|
2825
|
+
title_text.append(
|
|
2826
|
+
"📊 COORDINATION TABLE",
|
|
2827
|
+
style="bold bright_green",
|
|
2828
|
+
)
|
|
2829
|
+
title_text.append(
|
|
2830
|
+
"\n\nNavigation: ↑/↓ or j/k to scroll, q to quit",
|
|
2831
|
+
style="dim cyan",
|
|
2832
|
+
)
|
|
2833
|
+
|
|
2834
|
+
title_panel = Panel(
|
|
2835
|
+
title_text,
|
|
2836
|
+
border_style="bright_blue",
|
|
2837
|
+
padding=(1, 2),
|
|
2838
|
+
)
|
|
2839
|
+
|
|
2840
|
+
content.append(title_panel)
|
|
2841
|
+
content.append("") # Empty line
|
|
2842
|
+
|
|
2843
|
+
# Add table first
|
|
2844
|
+
content.append(rich_table)
|
|
2845
|
+
|
|
2846
|
+
# Add legend below the table if available
|
|
2847
|
+
if legend:
|
|
2848
|
+
content.append("") # Empty line
|
|
2849
|
+
content.append("") # Extra spacing
|
|
2850
|
+
content.append(legend)
|
|
2851
|
+
|
|
2852
|
+
# Choose display method based on platform
|
|
2853
|
+
display_method = get_optimal_display_method()
|
|
2854
|
+
|
|
2855
|
+
try:
|
|
2856
|
+
if display_method == "macos_simple":
|
|
2857
|
+
# Use macOS-compatible simple display
|
|
2858
|
+
display_scrollable_content_macos(
|
|
2859
|
+
temp_console,
|
|
2860
|
+
content,
|
|
2861
|
+
"📊 COORDINATION TABLE",
|
|
2862
|
+
)
|
|
2863
|
+
elif display_method == "native_pager":
|
|
2864
|
+
# Use system pager for better scrolling
|
|
2865
|
+
display_with_native_pager(
|
|
2866
|
+
temp_console,
|
|
2867
|
+
content,
|
|
2868
|
+
"📊 COORDINATION TABLE",
|
|
2869
|
+
)
|
|
2870
|
+
else:
|
|
2871
|
+
# Use Rich's pager as fallback
|
|
2872
|
+
with temp_console.pager(styles=True):
|
|
2873
|
+
for item in content:
|
|
2874
|
+
temp_console.print(item)
|
|
2875
|
+
except (KeyboardInterrupt, EOFError):
|
|
2876
|
+
pass # Handle user interruption gracefully
|
|
2877
|
+
|
|
2878
|
+
# Add separator instead of clearing screen
|
|
2879
|
+
self.console.print("\n" + "=" * 80 + "\n")
|
|
2880
|
+
else:
|
|
2881
|
+
# Fallback to event table text version if Rich not available
|
|
2882
|
+
table_content = builder.generate_event_table()
|
|
2883
|
+
table_panel = Panel(
|
|
2884
|
+
table_content,
|
|
2885
|
+
title="[bold bright_green]📊 COORDINATION TABLE[/bold bright_green]",
|
|
2886
|
+
border_style=self.colors["success"],
|
|
2887
|
+
box=DOUBLE,
|
|
2888
|
+
expand=False,
|
|
2889
|
+
)
|
|
2890
|
+
self.console.print("\n")
|
|
2891
|
+
self.console.print(table_panel)
|
|
2892
|
+
self.console.print()
|
|
2893
|
+
|
|
2894
|
+
# Don't restart live display - leave it stopped to show static results
|
|
2895
|
+
# This prevents duplication from stop/restart cycles
|
|
2896
|
+
|
|
2897
|
+
except Exception as e:
|
|
2898
|
+
print(f"Error displaying coordination table: {e}")
|
|
2899
|
+
import traceback
|
|
2900
|
+
|
|
2901
|
+
traceback.print_exc()
|
|
2465
2902
|
|
|
2466
2903
|
async def display_final_presentation(
|
|
2467
2904
|
self,
|
|
2468
2905
|
selected_agent: str,
|
|
2469
|
-
presentation_stream,
|
|
2470
|
-
vote_results: Dict[str, Any] = None,
|
|
2471
|
-
):
|
|
2472
|
-
"""Display final presentation
|
|
2906
|
+
presentation_stream: Any,
|
|
2907
|
+
vote_results: Optional[Dict[str, Any]] = None,
|
|
2908
|
+
) -> None:
|
|
2909
|
+
"""Display final presentation with streaming box followed by clean final answer box."""
|
|
2473
2910
|
if not selected_agent:
|
|
2474
2911
|
return ""
|
|
2475
2912
|
|
|
2476
|
-
#
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2913
|
+
# Initialize final presentation state
|
|
2914
|
+
self._final_presentation_active = True
|
|
2915
|
+
self._final_presentation_content = ""
|
|
2916
|
+
self._final_presentation_agent = selected_agent
|
|
2917
|
+
self._final_presentation_vote_results = vote_results
|
|
2918
|
+
self._final_presentation_file_path = None # Will be set after file is initialized
|
|
2481
2919
|
|
|
2482
|
-
#
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2920
|
+
# Add visual separator before starting live display to prevent content from being hidden
|
|
2921
|
+
self.console.print("\n")
|
|
2922
|
+
|
|
2923
|
+
# Keep live display running for streaming
|
|
2924
|
+
was_live = self.live is not None and self.live.is_started
|
|
2925
|
+
if not was_live:
|
|
2926
|
+
# Clear screen before creating new Live to prevent duplication
|
|
2927
|
+
self.console.clear()
|
|
2928
|
+
self.live = Live(
|
|
2929
|
+
self._create_layout(),
|
|
2930
|
+
console=self.console,
|
|
2931
|
+
refresh_per_second=self.refresh_rate,
|
|
2932
|
+
vertical_overflow="ellipsis",
|
|
2933
|
+
transient=False, # Keep visible after stopped
|
|
2492
2934
|
)
|
|
2935
|
+
self.live.start()
|
|
2493
2936
|
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
border_style=self.colors["success"],
|
|
2497
|
-
box=DOUBLE,
|
|
2498
|
-
title="[bold]Final Presentation[/bold]",
|
|
2499
|
-
)
|
|
2937
|
+
# Update footer cache to show "Final Presentation: Active"
|
|
2938
|
+
self._update_footer_cache()
|
|
2500
2939
|
|
|
2501
|
-
|
|
2502
|
-
self.
|
|
2940
|
+
# Initial update to show the streaming panel
|
|
2941
|
+
self._update_final_presentation_panel()
|
|
2503
2942
|
|
|
2504
2943
|
presentation_content = ""
|
|
2505
2944
|
chunk_count = 0
|
|
2506
2945
|
|
|
2946
|
+
# Initialize the final presentation file
|
|
2947
|
+
presentation_file_path = self._initialize_final_presentation_file(
|
|
2948
|
+
selected_agent,
|
|
2949
|
+
)
|
|
2950
|
+
self._final_presentation_file_path = presentation_file_path # Store for 'f' key access
|
|
2951
|
+
|
|
2507
2952
|
try:
|
|
2508
|
-
#
|
|
2953
|
+
# Stream presentation content into the live panel
|
|
2509
2954
|
async for chunk in presentation_stream:
|
|
2510
2955
|
chunk_count += 1
|
|
2511
2956
|
content = getattr(chunk, "content", "") or ""
|
|
2512
2957
|
chunk_type = getattr(chunk, "type", "")
|
|
2513
2958
|
source = getattr(chunk, "source", selected_agent)
|
|
2514
2959
|
|
|
2960
|
+
# Skip debug chunks from display but still log them
|
|
2961
|
+
if chunk_type == "debug":
|
|
2962
|
+
continue
|
|
2963
|
+
|
|
2515
2964
|
if content:
|
|
2516
2965
|
# Ensure content is a string
|
|
2517
2966
|
if isinstance(content, list):
|
|
@@ -2519,37 +2968,58 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2519
2968
|
elif not isinstance(content, str):
|
|
2520
2969
|
content = str(content)
|
|
2521
2970
|
|
|
2522
|
-
#
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2971
|
+
# Process reasoning content with shared logic
|
|
2972
|
+
processed_content = self.process_reasoning_content(
|
|
2973
|
+
chunk_type,
|
|
2974
|
+
content,
|
|
2975
|
+
source,
|
|
2976
|
+
)
|
|
2977
|
+
|
|
2978
|
+
# Accumulate content and update live display
|
|
2979
|
+
self._final_presentation_content += processed_content
|
|
2980
|
+
presentation_content += processed_content
|
|
2981
|
+
|
|
2982
|
+
# Add content to recent events (truncate to avoid flooding)
|
|
2983
|
+
if processed_content.strip():
|
|
2984
|
+
truncated_content = processed_content.strip()[:150]
|
|
2985
|
+
if len(processed_content.strip()) > 150:
|
|
2986
|
+
truncated_content += "..."
|
|
2987
|
+
self.add_orchestrator_event(f"🎤 {selected_agent}: {truncated_content}")
|
|
2988
|
+
|
|
2989
|
+
# Save chunk to file as it arrives
|
|
2990
|
+
self._append_to_final_presentation_file(
|
|
2991
|
+
presentation_file_path,
|
|
2992
|
+
processed_content,
|
|
2993
|
+
)
|
|
2994
|
+
|
|
2995
|
+
# Update the live streaming panel
|
|
2996
|
+
self._update_final_presentation_panel()
|
|
2997
|
+
|
|
2998
|
+
else:
|
|
2999
|
+
# Handle reasoning chunks with no content (like reasoning_summary_done)
|
|
3000
|
+
processed_content = self.process_reasoning_content(
|
|
3001
|
+
chunk_type,
|
|
3002
|
+
"",
|
|
3003
|
+
source,
|
|
3004
|
+
)
|
|
3005
|
+
if processed_content:
|
|
3006
|
+
self._final_presentation_content += processed_content
|
|
3007
|
+
presentation_content += processed_content
|
|
3008
|
+
self._append_to_final_presentation_file(
|
|
3009
|
+
presentation_file_path,
|
|
3010
|
+
processed_content,
|
|
3011
|
+
)
|
|
3012
|
+
self._update_final_presentation_panel()
|
|
2537
3013
|
|
|
2538
3014
|
# Handle orchestrator query completion signals
|
|
2539
3015
|
if chunk_type == "done":
|
|
2540
|
-
completion_text = Text(
|
|
2541
|
-
f"\n✅ Presentation completed by {source}",
|
|
2542
|
-
style=self.colors["success"],
|
|
2543
|
-
)
|
|
2544
|
-
self.console.print(completion_text)
|
|
2545
3016
|
break
|
|
2546
3017
|
|
|
2547
3018
|
except Exception as e:
|
|
2548
3019
|
# Enhanced error handling for orchestrator queries
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
)
|
|
2552
|
-
self.console.print(error_text)
|
|
3020
|
+
error_msg = f"\n❌ Error during final presentation: {e}\n"
|
|
3021
|
+
self._final_presentation_content += error_msg
|
|
3022
|
+
self._update_final_presentation_panel()
|
|
2553
3023
|
|
|
2554
3024
|
# Fallback: try to get content from agent's stored answer
|
|
2555
3025
|
if hasattr(self, "orchestrator") and self.orchestrator:
|
|
@@ -2557,40 +3027,68 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2557
3027
|
status = self.orchestrator.get_status()
|
|
2558
3028
|
if selected_agent in status.get("agent_states", {}):
|
|
2559
3029
|
stored_answer = status["agent_states"][selected_agent].get(
|
|
2560
|
-
"answer",
|
|
3030
|
+
"answer",
|
|
3031
|
+
"",
|
|
2561
3032
|
)
|
|
2562
3033
|
if stored_answer:
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
style=self.colors["text"],
|
|
2566
|
-
)
|
|
2567
|
-
self.console.print(fallback_text)
|
|
3034
|
+
fallback_msg = f"\n📋 Fallback to stored answer:\n{stored_answer}\n"
|
|
3035
|
+
self._final_presentation_content += fallback_msg
|
|
2568
3036
|
presentation_content = stored_answer
|
|
3037
|
+
self._update_final_presentation_panel()
|
|
2569
3038
|
except Exception:
|
|
2570
3039
|
pass
|
|
2571
3040
|
|
|
2572
|
-
self.console.print("\n" + "=" * 60)
|
|
2573
|
-
|
|
2574
|
-
# Show presentation statistics
|
|
2575
|
-
if chunk_count > 0:
|
|
2576
|
-
stats_text = Text(
|
|
2577
|
-
f"📊 Presentation processed {chunk_count} chunks",
|
|
2578
|
-
style=self.colors["info"],
|
|
2579
|
-
)
|
|
2580
|
-
self.console.print(stats_text)
|
|
2581
|
-
|
|
2582
3041
|
# Store the presentation content for later re-display
|
|
2583
3042
|
if presentation_content:
|
|
2584
3043
|
self._stored_final_presentation = presentation_content
|
|
2585
3044
|
self._stored_presentation_agent = selected_agent
|
|
2586
3045
|
self._stored_vote_results = vote_results
|
|
2587
3046
|
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
3047
|
+
# Update footer cache to show 'f' key
|
|
3048
|
+
self._update_footer_cache()
|
|
3049
|
+
|
|
3050
|
+
# Finalize the file
|
|
3051
|
+
self._finalize_final_presentation_file(presentation_file_path)
|
|
3052
|
+
|
|
3053
|
+
# Stop the live display (transient=True will clear it)
|
|
3054
|
+
if self.live and self.live.is_started:
|
|
3055
|
+
self.live.stop()
|
|
3056
|
+
self.live = None
|
|
3057
|
+
|
|
3058
|
+
# Deactivate the presentation panel
|
|
3059
|
+
self._final_presentation_active = False
|
|
3060
|
+
|
|
3061
|
+
# Update footer cache to reflect completion
|
|
3062
|
+
self._update_footer_cache()
|
|
3063
|
+
|
|
3064
|
+
# Print a summary box with completion stats
|
|
3065
|
+
stats_text = Text()
|
|
3066
|
+
stats_text.append("✅ Presentation completed by ", style="bold green")
|
|
3067
|
+
stats_text.append(selected_agent, style=f"bold {self.colors['success']}")
|
|
3068
|
+
if chunk_count > 0:
|
|
3069
|
+
stats_text.append(f" | 📊 {chunk_count} chunks processed", style="dim")
|
|
3070
|
+
|
|
3071
|
+
summary_panel = Panel(
|
|
3072
|
+
stats_text,
|
|
3073
|
+
border_style="green",
|
|
3074
|
+
box=ROUNDED,
|
|
3075
|
+
expand=True,
|
|
3076
|
+
)
|
|
3077
|
+
self.console.print(summary_panel)
|
|
2591
3078
|
|
|
2592
3079
|
return presentation_content
|
|
2593
3080
|
|
|
3081
|
+
def _format_multiline_content(self, content: str) -> Text:
|
|
3082
|
+
"""Format multiline content for display in a panel."""
|
|
3083
|
+
formatted = Text()
|
|
3084
|
+
lines = content.split("\n")
|
|
3085
|
+
for line in lines:
|
|
3086
|
+
if line.strip():
|
|
3087
|
+
formatted_line = self._format_content_line(line)
|
|
3088
|
+
formatted.append(formatted_line)
|
|
3089
|
+
formatted.append("\n")
|
|
3090
|
+
return formatted
|
|
3091
|
+
|
|
2594
3092
|
def show_final_answer(
|
|
2595
3093
|
self,
|
|
2596
3094
|
answer: str,
|
|
@@ -2612,9 +3110,14 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2612
3110
|
try:
|
|
2613
3111
|
if hasattr(self, "orchestrator") and self.orchestrator:
|
|
2614
3112
|
status = self.orchestrator.get_status()
|
|
2615
|
-
vote_results = vote_results or status.get(
|
|
2616
|
-
|
|
2617
|
-
|
|
3113
|
+
vote_results = vote_results or status.get(
|
|
3114
|
+
"vote_results",
|
|
3115
|
+
{},
|
|
3116
|
+
)
|
|
3117
|
+
selected_agent = selected_agent or status.get(
|
|
3118
|
+
"selected_agent",
|
|
3119
|
+
)
|
|
3120
|
+
except Exception:
|
|
2618
3121
|
pass
|
|
2619
3122
|
|
|
2620
3123
|
# Force update all agent final statuses first (show voting results in agent panels)
|
|
@@ -2637,12 +3140,34 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2637
3140
|
# Now display only the selected agent instead of the full answer
|
|
2638
3141
|
if selected_agent:
|
|
2639
3142
|
selected_agent_text = Text(
|
|
2640
|
-
f"🏆 Selected agent: {selected_agent}",
|
|
3143
|
+
f"🏆 Selected agent: {selected_agent}",
|
|
3144
|
+
style=self.colors["success"],
|
|
2641
3145
|
)
|
|
2642
3146
|
else:
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
)
|
|
3147
|
+
# Check if this is due to orchestrator timeout
|
|
3148
|
+
is_timeout = False
|
|
3149
|
+
if hasattr(self, "orchestrator") and self.orchestrator:
|
|
3150
|
+
is_timeout = getattr(
|
|
3151
|
+
self.orchestrator,
|
|
3152
|
+
"is_orchestrator_timeout",
|
|
3153
|
+
False,
|
|
3154
|
+
)
|
|
3155
|
+
|
|
3156
|
+
if is_timeout:
|
|
3157
|
+
selected_agent_text = Text()
|
|
3158
|
+
selected_agent_text.append(
|
|
3159
|
+
"No agent selected\n",
|
|
3160
|
+
style=self.colors["warning"],
|
|
3161
|
+
)
|
|
3162
|
+
selected_agent_text.append(
|
|
3163
|
+
"The orchestrator timed out before any agent could complete voting or provide an answer.",
|
|
3164
|
+
style=self.colors["warning"],
|
|
3165
|
+
)
|
|
3166
|
+
else:
|
|
3167
|
+
selected_agent_text = Text(
|
|
3168
|
+
"No agent selected",
|
|
3169
|
+
style=self.colors["warning"],
|
|
3170
|
+
)
|
|
2646
3171
|
|
|
2647
3172
|
final_panel = Panel(
|
|
2648
3173
|
Align.center(selected_agent_text),
|
|
@@ -2652,33 +3177,31 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2652
3177
|
expand=False,
|
|
2653
3178
|
)
|
|
2654
3179
|
|
|
2655
|
-
self.console.print("\n")
|
|
2656
3180
|
self.console.print(final_panel)
|
|
2657
3181
|
|
|
2658
3182
|
# Show which agent was selected
|
|
2659
3183
|
if selected_agent:
|
|
2660
3184
|
selection_text = Text()
|
|
2661
3185
|
selection_text.append(
|
|
2662
|
-
f"✅ Selected by: {selected_agent}",
|
|
3186
|
+
f"✅ Selected by: {selected_agent}",
|
|
3187
|
+
style=self.colors["success"],
|
|
2663
3188
|
)
|
|
2664
3189
|
if vote_results and vote_results.get("vote_counts"):
|
|
2665
3190
|
vote_summary = ", ".join(
|
|
2666
|
-
[
|
|
2667
|
-
f"{agent}: {count}"
|
|
2668
|
-
for agent, count in vote_results["vote_counts"].items()
|
|
2669
|
-
]
|
|
3191
|
+
[f"{agent}: {count}" for agent, count in vote_results["vote_counts"].items()],
|
|
2670
3192
|
)
|
|
2671
3193
|
selection_text.append(
|
|
2672
|
-
f"\n🗳️ Vote results: {vote_summary}",
|
|
3194
|
+
f"\n🗳️ Vote results: {vote_summary}",
|
|
3195
|
+
style=self.colors["info"],
|
|
2673
3196
|
)
|
|
2674
3197
|
|
|
2675
3198
|
selection_panel = Panel(
|
|
2676
|
-
selection_text,
|
|
3199
|
+
selection_text,
|
|
3200
|
+
border_style=self.colors["info"],
|
|
3201
|
+
box=ROUNDED,
|
|
2677
3202
|
)
|
|
2678
3203
|
self.console.print(selection_panel)
|
|
2679
3204
|
|
|
2680
|
-
self.console.print("\n")
|
|
2681
|
-
|
|
2682
3205
|
# Display selected agent's final provided answer directly without flush
|
|
2683
3206
|
# if selected_agent:
|
|
2684
3207
|
# selected_agent_answer = self._get_selected_agent_final_answer(selected_agent)
|
|
@@ -2707,7 +3230,10 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2707
3230
|
# Display final presentation immediately after voting results
|
|
2708
3231
|
if selected_agent and hasattr(self, "orchestrator") and self.orchestrator:
|
|
2709
3232
|
try:
|
|
2710
|
-
self._show_orchestrator_final_presentation(
|
|
3233
|
+
self._show_orchestrator_final_presentation(
|
|
3234
|
+
selected_agent,
|
|
3235
|
+
vote_results,
|
|
3236
|
+
)
|
|
2711
3237
|
# Add a small delay to ensure presentation completes before agent selector
|
|
2712
3238
|
time.sleep(1.0)
|
|
2713
3239
|
except Exception as e:
|
|
@@ -2719,17 +3245,13 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2719
3245
|
self.console.print(error_text)
|
|
2720
3246
|
|
|
2721
3247
|
# Show interactive options for viewing agent details (only if not in safe mode)
|
|
2722
|
-
if (
|
|
2723
|
-
self._keyboard_interactive_mode
|
|
2724
|
-
and hasattr(self, "_agent_keys")
|
|
2725
|
-
and not self._safe_keyboard_mode
|
|
2726
|
-
):
|
|
3248
|
+
if self._keyboard_interactive_mode and hasattr(self, "_agent_keys") and not self._safe_keyboard_mode:
|
|
2727
3249
|
self.show_agent_selector()
|
|
2728
3250
|
|
|
2729
|
-
def _display_answer_with_flush(self, answer: str):
|
|
3251
|
+
def _display_answer_with_flush(self, answer: str) -> None:
|
|
2730
3252
|
"""Display answer with flush output effect - streaming character by character."""
|
|
2731
|
-
import time
|
|
2732
3253
|
import sys
|
|
3254
|
+
import time
|
|
2733
3255
|
|
|
2734
3256
|
# Use configurable delays
|
|
2735
3257
|
char_delay = self._flush_char_delay
|
|
@@ -2787,39 +3309,20 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2787
3309
|
try:
|
|
2788
3310
|
if hasattr(self, "orchestrator") and self.orchestrator:
|
|
2789
3311
|
status = self.orchestrator.get_status()
|
|
2790
|
-
if (
|
|
2791
|
-
|
|
2792
|
-
and selected_agent in self.orchestrator.agent_states
|
|
2793
|
-
):
|
|
2794
|
-
stored_answer = self.orchestrator.agent_states[
|
|
2795
|
-
selected_agent
|
|
2796
|
-
].answer
|
|
3312
|
+
if hasattr(self.orchestrator, "agent_states") and selected_agent in self.orchestrator.agent_states:
|
|
3313
|
+
stored_answer = self.orchestrator.agent_states[selected_agent].answer
|
|
2797
3314
|
if stored_answer:
|
|
2798
3315
|
# Clean up the stored answer
|
|
2799
|
-
return (
|
|
2800
|
-
stored_answer.replace("\\", "\n").replace("**", "").strip()
|
|
2801
|
-
)
|
|
3316
|
+
return stored_answer.replace("\\", "\n").replace("**", "").strip()
|
|
2802
3317
|
|
|
2803
3318
|
# Alternative: try getting from status
|
|
2804
|
-
if
|
|
2805
|
-
"agent_states" in status
|
|
2806
|
-
and selected_agent in status["agent_states"]
|
|
2807
|
-
):
|
|
3319
|
+
if "agent_states" in status and selected_agent in status["agent_states"]:
|
|
2808
3320
|
agent_state = status["agent_states"][selected_agent]
|
|
2809
3321
|
if hasattr(agent_state, "answer") and agent_state.answer:
|
|
2810
|
-
return (
|
|
2811
|
-
agent_state.answer.replace("\\", "\n")
|
|
2812
|
-
.replace("**", "")
|
|
2813
|
-
.strip()
|
|
2814
|
-
)
|
|
3322
|
+
return agent_state.answer.replace("\\", "\n").replace("**", "").strip()
|
|
2815
3323
|
elif isinstance(agent_state, dict) and "answer" in agent_state:
|
|
2816
|
-
return (
|
|
2817
|
-
|
|
2818
|
-
.replace("\\", "\n")
|
|
2819
|
-
.replace("**", "")
|
|
2820
|
-
.strip()
|
|
2821
|
-
)
|
|
2822
|
-
except:
|
|
3324
|
+
return agent_state["answer"].replace("\\", "\n").replace("**", "").strip()
|
|
3325
|
+
except Exception:
|
|
2823
3326
|
pass
|
|
2824
3327
|
|
|
2825
3328
|
# Fallback: extract from agent outputs
|
|
@@ -2842,15 +3345,21 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2842
3345
|
# Skip status indicators and tool outputs
|
|
2843
3346
|
if any(
|
|
2844
3347
|
marker in line
|
|
2845
|
-
for marker in [
|
|
3348
|
+
for marker in [
|
|
3349
|
+
"⚡",
|
|
3350
|
+
"🔄",
|
|
3351
|
+
"✅",
|
|
3352
|
+
"🗳️",
|
|
3353
|
+
"❌",
|
|
3354
|
+
"voted",
|
|
3355
|
+
"🔧",
|
|
3356
|
+
"status",
|
|
3357
|
+
]
|
|
2846
3358
|
):
|
|
2847
3359
|
continue
|
|
2848
3360
|
|
|
2849
3361
|
# Stop at voting/coordination markers - we want the answer before voting
|
|
2850
|
-
if any(
|
|
2851
|
-
marker in line.lower()
|
|
2852
|
-
for marker in ["final coordinated", "coordination", "voting"]
|
|
2853
|
-
):
|
|
3362
|
+
if any(marker in line.lower() for marker in ["final coordinated", "coordination", "voting"]):
|
|
2854
3363
|
break
|
|
2855
3364
|
|
|
2856
3365
|
# Collect meaningful content
|
|
@@ -2913,14 +3422,7 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2913
3422
|
if not presentation_lines and agent_output:
|
|
2914
3423
|
# Get the last few non-status lines as potential presentation content
|
|
2915
3424
|
for line in reversed(agent_output[-10:]): # Look at last 10 lines
|
|
2916
|
-
if (
|
|
2917
|
-
line.strip()
|
|
2918
|
-
and not line.startswith("⚡")
|
|
2919
|
-
and not line.startswith("🔄")
|
|
2920
|
-
and not any(
|
|
2921
|
-
marker in line for marker in ["voted", "🗳️", "✅", "status"]
|
|
2922
|
-
)
|
|
2923
|
-
):
|
|
3425
|
+
if line.strip() and not line.startswith("⚡") and not line.startswith("🔄") and not any(marker in line for marker in ["voted", "🗳️", "✅", "status"]):
|
|
2924
3426
|
presentation_lines.insert(0, line.strip())
|
|
2925
3427
|
if len(presentation_lines) >= 5: # Limit to reasonable amount
|
|
2926
3428
|
break
|
|
@@ -2928,8 +3430,10 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2928
3430
|
return "\n".join(presentation_lines) if presentation_lines else ""
|
|
2929
3431
|
|
|
2930
3432
|
def _display_final_presentation_content(
|
|
2931
|
-
self,
|
|
2932
|
-
|
|
3433
|
+
self,
|
|
3434
|
+
selected_agent: str,
|
|
3435
|
+
presentation_content: str,
|
|
3436
|
+
) -> None:
|
|
2933
3437
|
"""Display the final presentation content in a formatted panel with orchestrator query enhancements."""
|
|
2934
3438
|
if not presentation_content.strip():
|
|
2935
3439
|
return
|
|
@@ -2959,7 +3463,9 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2959
3463
|
content_text = Text()
|
|
2960
3464
|
|
|
2961
3465
|
# Use the enhanced presentation content formatter
|
|
2962
|
-
formatted_content = self._format_presentation_content(
|
|
3466
|
+
formatted_content = self._format_presentation_content(
|
|
3467
|
+
presentation_content,
|
|
3468
|
+
)
|
|
2963
3469
|
content_text.append(formatted_content)
|
|
2964
3470
|
|
|
2965
3471
|
# Create content panel with orchestrator-specific styling
|
|
@@ -2968,7 +3474,7 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2968
3474
|
title=f"[bold]{selected_agent.upper()} Final Presentation[/bold]",
|
|
2969
3475
|
border_style=self.colors["primary"],
|
|
2970
3476
|
box=ROUNDED,
|
|
2971
|
-
subtitle=
|
|
3477
|
+
subtitle="[italic]Final presentation content[/italic]",
|
|
2972
3478
|
)
|
|
2973
3479
|
|
|
2974
3480
|
self.console.print(content_panel)
|
|
@@ -2977,7 +3483,8 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2977
3483
|
# Add presentation completion indicator
|
|
2978
3484
|
completion_text = Text()
|
|
2979
3485
|
completion_text.append(
|
|
2980
|
-
"✅ Final presentation completed successfully",
|
|
3486
|
+
"✅ Final presentation completed successfully",
|
|
3487
|
+
style=self.colors["success"],
|
|
2981
3488
|
)
|
|
2982
3489
|
completion_panel = Panel(
|
|
2983
3490
|
Align.center(completion_text),
|
|
@@ -2986,15 +3493,112 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
2986
3493
|
)
|
|
2987
3494
|
self.console.print(completion_panel)
|
|
2988
3495
|
|
|
3496
|
+
# Save final presentation to text file
|
|
3497
|
+
self._save_final_presentation_to_file(
|
|
3498
|
+
selected_agent,
|
|
3499
|
+
presentation_content,
|
|
3500
|
+
)
|
|
3501
|
+
|
|
3502
|
+
def _save_final_presentation_to_file(
|
|
3503
|
+
self,
|
|
3504
|
+
selected_agent: str,
|
|
3505
|
+
presentation_content: str,
|
|
3506
|
+
) -> None:
|
|
3507
|
+
"""Save the final presentation content to a text file in agent_outputs directory."""
|
|
3508
|
+
try:
|
|
3509
|
+
# Create filename without timestamp (already in parent directory)
|
|
3510
|
+
filename = f"final_presentation_{selected_agent}.txt"
|
|
3511
|
+
file_path = Path(self.output_dir) / filename
|
|
3512
|
+
|
|
3513
|
+
# Write the final presentation content
|
|
3514
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
|
3515
|
+
f.write(
|
|
3516
|
+
f"=== FINAL PRESENTATION FROM {selected_agent.upper()} ===\n",
|
|
3517
|
+
)
|
|
3518
|
+
f.write(
|
|
3519
|
+
f"Generated at: {time.strftime('%Y-%m-%d %H:%M:%S')}\n",
|
|
3520
|
+
)
|
|
3521
|
+
f.write("=" * 60 + "\n\n")
|
|
3522
|
+
f.write(presentation_content)
|
|
3523
|
+
f.write("\n\n" + "=" * 60 + "\n")
|
|
3524
|
+
f.write("End of Final Presentation\n")
|
|
3525
|
+
|
|
3526
|
+
# Also create a symlink to the latest presentation
|
|
3527
|
+
latest_link = Path(self.output_dir) / f"final_presentation_{selected_agent}_latest.txt"
|
|
3528
|
+
if latest_link.exists():
|
|
3529
|
+
latest_link.unlink()
|
|
3530
|
+
latest_link.symlink_to(filename)
|
|
3531
|
+
|
|
3532
|
+
except Exception:
|
|
3533
|
+
# Handle file write errors gracefully
|
|
3534
|
+
pass
|
|
3535
|
+
|
|
3536
|
+
def _initialize_final_presentation_file(self, selected_agent: str) -> Path:
|
|
3537
|
+
"""Initialize a new final presentation file and return the file path."""
|
|
3538
|
+
try:
|
|
3539
|
+
# Create filename without timestamp (already in parent directory)
|
|
3540
|
+
filename = f"final_presentation_{selected_agent}.txt"
|
|
3541
|
+
file_path = Path(self.output_dir) / filename
|
|
3542
|
+
|
|
3543
|
+
# Write the initial header
|
|
3544
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
|
3545
|
+
f.write(
|
|
3546
|
+
f"=== FINAL PRESENTATION FROM {selected_agent.upper()} ===\n",
|
|
3547
|
+
)
|
|
3548
|
+
f.write(
|
|
3549
|
+
f"Generated at: {time.strftime('%Y-%m-%d %H:%M:%S')}\n",
|
|
3550
|
+
)
|
|
3551
|
+
f.write("=" * 60 + "\n\n")
|
|
3552
|
+
|
|
3553
|
+
# Also create a symlink to the latest presentation
|
|
3554
|
+
latest_link = Path(self.output_dir) / f"final_presentation_{selected_agent}_latest.txt"
|
|
3555
|
+
if latest_link.exists():
|
|
3556
|
+
latest_link.unlink()
|
|
3557
|
+
latest_link.symlink_to(filename)
|
|
3558
|
+
|
|
3559
|
+
return file_path
|
|
3560
|
+
except Exception:
|
|
3561
|
+
# Handle file write errors gracefully
|
|
3562
|
+
return None
|
|
3563
|
+
|
|
3564
|
+
def _append_to_final_presentation_file(
|
|
3565
|
+
self,
|
|
3566
|
+
file_path: Path,
|
|
3567
|
+
content: str,
|
|
3568
|
+
) -> None:
|
|
3569
|
+
"""Append content to the final presentation file."""
|
|
3570
|
+
try:
|
|
3571
|
+
if file_path and file_path.exists():
|
|
3572
|
+
with open(file_path, "a", encoding="utf-8") as f:
|
|
3573
|
+
f.write(content)
|
|
3574
|
+
f.flush() # Explicitly flush to disk so text editors see updates immediately
|
|
3575
|
+
import os
|
|
3576
|
+
|
|
3577
|
+
os.fsync(f.fileno()) # Force OS to write to disk
|
|
3578
|
+
except Exception:
|
|
3579
|
+
# Handle file write errors gracefully
|
|
3580
|
+
pass
|
|
3581
|
+
|
|
3582
|
+
def _finalize_final_presentation_file(self, file_path: Path) -> None:
|
|
3583
|
+
"""Add closing content to the final presentation file."""
|
|
3584
|
+
try:
|
|
3585
|
+
if file_path and file_path.exists():
|
|
3586
|
+
with open(file_path, "a", encoding="utf-8") as f:
|
|
3587
|
+
f.write("\n\n" + "=" * 60 + "\n")
|
|
3588
|
+
f.write("End of Final Presentation\n")
|
|
3589
|
+
except Exception:
|
|
3590
|
+
# Handle file write errors gracefully
|
|
3591
|
+
pass
|
|
3592
|
+
|
|
2989
3593
|
def _show_orchestrator_final_presentation(
|
|
2990
|
-
self,
|
|
2991
|
-
|
|
3594
|
+
self,
|
|
3595
|
+
selected_agent: str,
|
|
3596
|
+
vote_results: Dict[str, Any] = None,
|
|
3597
|
+
) -> None:
|
|
2992
3598
|
"""Show the final presentation from the orchestrator for the selected agent."""
|
|
2993
3599
|
import time
|
|
2994
|
-
import traceback
|
|
2995
3600
|
|
|
2996
3601
|
try:
|
|
2997
|
-
|
|
2998
3602
|
if not hasattr(self, "orchestrator") or not self.orchestrator:
|
|
2999
3603
|
return
|
|
3000
3604
|
|
|
@@ -3002,18 +3606,21 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
3002
3606
|
if hasattr(self.orchestrator, "get_final_presentation"):
|
|
3003
3607
|
import asyncio
|
|
3004
3608
|
|
|
3005
|
-
async def _get_and_display_presentation():
|
|
3609
|
+
async def _get_and_display_presentation() -> None:
|
|
3006
3610
|
"""Helper to get and display presentation asynchronously."""
|
|
3007
3611
|
try:
|
|
3008
3612
|
presentation_stream = self.orchestrator.get_final_presentation(
|
|
3009
|
-
selected_agent,
|
|
3613
|
+
selected_agent,
|
|
3614
|
+
vote_results,
|
|
3010
3615
|
)
|
|
3011
3616
|
|
|
3012
3617
|
# Display the presentation
|
|
3013
3618
|
await self.display_final_presentation(
|
|
3014
|
-
selected_agent,
|
|
3619
|
+
selected_agent,
|
|
3620
|
+
presentation_stream,
|
|
3621
|
+
vote_results,
|
|
3015
3622
|
)
|
|
3016
|
-
except Exception
|
|
3623
|
+
except Exception:
|
|
3017
3624
|
raise
|
|
3018
3625
|
|
|
3019
3626
|
# Run the async function
|
|
@@ -3033,36 +3640,42 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
3033
3640
|
loop.run_until_complete(_get_and_display_presentation())
|
|
3034
3641
|
# Add explicit wait to ensure presentation is fully displayed
|
|
3035
3642
|
time.sleep(0.5)
|
|
3036
|
-
except Exception
|
|
3643
|
+
except Exception:
|
|
3037
3644
|
# If all else fails, try asyncio.run
|
|
3038
3645
|
try:
|
|
3039
3646
|
asyncio.run(_get_and_display_presentation())
|
|
3040
3647
|
# Add explicit wait to ensure presentation is fully displayed
|
|
3041
3648
|
time.sleep(0.5)
|
|
3042
|
-
except Exception
|
|
3649
|
+
except Exception:
|
|
3043
3650
|
# Last resort: show stored content
|
|
3044
3651
|
self._display_final_presentation_content(
|
|
3045
|
-
selected_agent,
|
|
3652
|
+
selected_agent,
|
|
3653
|
+
"Unable to retrieve live presentation.",
|
|
3046
3654
|
)
|
|
3047
3655
|
else:
|
|
3048
3656
|
# Fallback: try to get stored presentation content
|
|
3049
3657
|
status = self.orchestrator.get_status()
|
|
3050
3658
|
if selected_agent in status.get("agent_states", {}):
|
|
3051
3659
|
stored_answer = status["agent_states"][selected_agent].get(
|
|
3052
|
-
"answer",
|
|
3660
|
+
"answer",
|
|
3661
|
+
"",
|
|
3053
3662
|
)
|
|
3054
3663
|
if stored_answer:
|
|
3055
3664
|
self._display_final_presentation_content(
|
|
3056
|
-
selected_agent,
|
|
3665
|
+
selected_agent,
|
|
3666
|
+
stored_answer,
|
|
3057
3667
|
)
|
|
3058
3668
|
else:
|
|
3059
3669
|
print("DEBUG: No stored answer found")
|
|
3060
3670
|
else:
|
|
3061
|
-
print(
|
|
3671
|
+
print(
|
|
3672
|
+
f"DEBUG: Agent {selected_agent} not found in agent_states",
|
|
3673
|
+
)
|
|
3062
3674
|
except Exception as e:
|
|
3063
3675
|
# Handle errors gracefully
|
|
3064
3676
|
error_text = Text(
|
|
3065
|
-
f"❌ Error in final presentation: {e}",
|
|
3677
|
+
f"❌ Error in final presentation: {e}",
|
|
3678
|
+
style=self.colors["error"],
|
|
3066
3679
|
)
|
|
3067
3680
|
self.console.print(error_text)
|
|
3068
3681
|
|
|
@@ -3071,7 +3684,7 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
3071
3684
|
# error_text = Text(f"Unable to retrieve final presentation: {str(e)}", style=self.colors['warning'])
|
|
3072
3685
|
# self.console.print(error_text)
|
|
3073
3686
|
|
|
3074
|
-
def _force_display_final_vote_statuses(self):
|
|
3687
|
+
def _force_display_final_vote_statuses(self) -> None:
|
|
3075
3688
|
"""Force display update to show all agents' final vote statuses."""
|
|
3076
3689
|
with self._lock:
|
|
3077
3690
|
# Mark all agents for update to ensure final vote status is shown
|
|
@@ -3085,9 +3698,10 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
3085
3698
|
# Wait longer to ensure all updates are processed and displayed
|
|
3086
3699
|
import time
|
|
3087
3700
|
|
|
3088
|
-
|
|
3701
|
+
# Increased wait to ensure all vote statuses are displayed
|
|
3702
|
+
time.sleep(0.3)
|
|
3089
3703
|
|
|
3090
|
-
def _flush_all_buffers(self):
|
|
3704
|
+
def _flush_all_buffers(self) -> None:
|
|
3091
3705
|
"""Flush all text buffers to ensure no content is lost."""
|
|
3092
3706
|
for agent_id in self.agent_ids:
|
|
3093
3707
|
if agent_id in self._text_buffers and self._text_buffers[agent_id]:
|
|
@@ -3096,7 +3710,7 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
3096
3710
|
self.agent_outputs[agent_id].append(buffer_content)
|
|
3097
3711
|
self._text_buffers[agent_id] = ""
|
|
3098
3712
|
|
|
3099
|
-
def cleanup(self):
|
|
3713
|
+
def cleanup(self) -> None:
|
|
3100
3714
|
"""Clean up display resources."""
|
|
3101
3715
|
with self._lock:
|
|
3102
3716
|
# Flush any remaining buffered content
|
|
@@ -3117,13 +3731,13 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
3117
3731
|
if self._input_thread and self._input_thread.is_alive():
|
|
3118
3732
|
try:
|
|
3119
3733
|
self._input_thread.join(timeout=1.0)
|
|
3120
|
-
except:
|
|
3734
|
+
except Exception:
|
|
3121
3735
|
pass
|
|
3122
3736
|
|
|
3123
3737
|
# Restore terminal settings
|
|
3124
3738
|
try:
|
|
3125
3739
|
self._restore_terminal_settings()
|
|
3126
|
-
except:
|
|
3740
|
+
except Exception:
|
|
3127
3741
|
# Ignore errors during terminal restoration
|
|
3128
3742
|
pass
|
|
3129
3743
|
|
|
@@ -3141,7 +3755,7 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
3141
3755
|
if self._key_handler:
|
|
3142
3756
|
try:
|
|
3143
3757
|
self._key_handler.stop()
|
|
3144
|
-
except:
|
|
3758
|
+
except Exception:
|
|
3145
3759
|
pass
|
|
3146
3760
|
|
|
3147
3761
|
# Set shutdown flag to prevent new timers
|
|
@@ -3175,17 +3789,22 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
3175
3789
|
if file_path.exists():
|
|
3176
3790
|
with open(file_path, "a", encoding="utf-8") as f:
|
|
3177
3791
|
f.write(
|
|
3178
|
-
f"\n=== SESSION ENDED at {time.strftime('%Y-%m-%d %H:%M:%S')} ===\n"
|
|
3792
|
+
f"\n=== SESSION ENDED at {time.strftime('%Y-%m-%d %H:%M:%S')} ===\n",
|
|
3179
3793
|
)
|
|
3180
|
-
except:
|
|
3794
|
+
except Exception:
|
|
3181
3795
|
pass
|
|
3182
3796
|
|
|
3183
|
-
|
|
3797
|
+
# Restore console logging after Live display cleanup (outside lock)
|
|
3798
|
+
from massgen.logger_config import restore_console_logging
|
|
3799
|
+
|
|
3800
|
+
restore_console_logging()
|
|
3801
|
+
|
|
3802
|
+
def _schedule_priority_update(self, agent_id: str) -> None:
|
|
3184
3803
|
"""Schedule immediate priority update for critical agent status changes."""
|
|
3185
3804
|
if self._shutdown_flag:
|
|
3186
3805
|
return
|
|
3187
3806
|
|
|
3188
|
-
def priority_update():
|
|
3807
|
+
def priority_update() -> None:
|
|
3189
3808
|
try:
|
|
3190
3809
|
# Update the specific agent panel immediately
|
|
3191
3810
|
self._update_agent_panel_cache(agent_id)
|
|
@@ -3196,12 +3815,14 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
3196
3815
|
|
|
3197
3816
|
self._status_update_executor.submit(priority_update)
|
|
3198
3817
|
|
|
3199
|
-
def _categorize_update(
|
|
3818
|
+
def _categorize_update(
|
|
3819
|
+
self,
|
|
3820
|
+
agent_id: str,
|
|
3821
|
+
content_type: str,
|
|
3822
|
+
content: str,
|
|
3823
|
+
) -> None:
|
|
3200
3824
|
"""Categorize update by priority for layered refresh strategy."""
|
|
3201
|
-
if content_type in ["status", "error", "tool"] or any(
|
|
3202
|
-
keyword in content.lower()
|
|
3203
|
-
for keyword in ["error", "failed", "completed", "voted"]
|
|
3204
|
-
):
|
|
3825
|
+
if content_type in ["status", "error", "tool"] or any(keyword in content.lower() for keyword in ["error", "failed", "completed", "voted"]):
|
|
3205
3826
|
self._critical_updates.add(agent_id)
|
|
3206
3827
|
# Remove from other categories to avoid duplicate processing
|
|
3207
3828
|
self._normal_updates.discard(agent_id)
|
|
@@ -3212,13 +3833,14 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
3212
3833
|
self._decorative_updates.discard(agent_id)
|
|
3213
3834
|
else:
|
|
3214
3835
|
# Decorative updates (progress, timestamps, etc.)
|
|
3215
|
-
if
|
|
3216
|
-
agent_id not in self._critical_updates
|
|
3217
|
-
and agent_id not in self._normal_updates
|
|
3218
|
-
):
|
|
3836
|
+
if agent_id not in self._critical_updates and agent_id not in self._normal_updates:
|
|
3219
3837
|
self._decorative_updates.add(agent_id)
|
|
3220
3838
|
|
|
3221
|
-
def _schedule_layered_update(
|
|
3839
|
+
def _schedule_layered_update(
|
|
3840
|
+
self,
|
|
3841
|
+
agent_id: str,
|
|
3842
|
+
is_critical: bool = False,
|
|
3843
|
+
) -> None:
|
|
3222
3844
|
"""Schedule update using layered refresh strategy with intelligent batching."""
|
|
3223
3845
|
if is_critical:
|
|
3224
3846
|
# Critical updates: immediate processing, flush any pending batch
|
|
@@ -3237,11 +3859,11 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
3237
3859
|
# Lower performance: use batching
|
|
3238
3860
|
self._add_to_update_batch(agent_id)
|
|
3239
3861
|
|
|
3240
|
-
def _schedule_delayed_update(self):
|
|
3862
|
+
def _schedule_delayed_update(self) -> None:
|
|
3241
3863
|
"""Schedule delayed update for non-critical content."""
|
|
3242
3864
|
delay = self._debounce_delay * 2 # Double delay for non-critical updates
|
|
3243
3865
|
|
|
3244
|
-
def delayed_update():
|
|
3866
|
+
def delayed_update() -> None:
|
|
3245
3867
|
if self._pending_updates:
|
|
3246
3868
|
self._schedule_async_update(force_update=False)
|
|
3247
3869
|
|
|
@@ -3249,10 +3871,13 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
3249
3871
|
if "delayed" in self._debounce_timers:
|
|
3250
3872
|
self._debounce_timers["delayed"].cancel()
|
|
3251
3873
|
|
|
3252
|
-
self._debounce_timers["delayed"] = threading.Timer(
|
|
3874
|
+
self._debounce_timers["delayed"] = threading.Timer(
|
|
3875
|
+
delay,
|
|
3876
|
+
delayed_update,
|
|
3877
|
+
)
|
|
3253
3878
|
self._debounce_timers["delayed"].start()
|
|
3254
3879
|
|
|
3255
|
-
def _add_to_update_batch(self, agent_id: str):
|
|
3880
|
+
def _add_to_update_batch(self, agent_id: str) -> None:
|
|
3256
3881
|
"""Add update to batch for efficient processing."""
|
|
3257
3882
|
self._update_batch.add(agent_id)
|
|
3258
3883
|
|
|
@@ -3262,11 +3887,12 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
3262
3887
|
|
|
3263
3888
|
# Set new batch timer
|
|
3264
3889
|
self._batch_timer = threading.Timer(
|
|
3265
|
-
self._batch_timeout,
|
|
3890
|
+
self._batch_timeout,
|
|
3891
|
+
self._process_update_batch,
|
|
3266
3892
|
)
|
|
3267
3893
|
self._batch_timer.start()
|
|
3268
3894
|
|
|
3269
|
-
def _process_update_batch(self):
|
|
3895
|
+
def _process_update_batch(self) -> None:
|
|
3270
3896
|
"""Process accumulated batch of updates."""
|
|
3271
3897
|
if self._update_batch:
|
|
3272
3898
|
# Move batch to pending updates
|
|
@@ -3276,7 +3902,7 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
3276
3902
|
# Process batch
|
|
3277
3903
|
self._schedule_async_update(force_update=False)
|
|
3278
3904
|
|
|
3279
|
-
def _flush_update_batch(self):
|
|
3905
|
+
def _flush_update_batch(self) -> None:
|
|
3280
3906
|
"""Immediately flush any pending batch updates."""
|
|
3281
3907
|
if self._batch_timer:
|
|
3282
3908
|
self._batch_timer.cancel()
|
|
@@ -3314,7 +3940,7 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
3314
3940
|
self._debounce_timers["main"].cancel()
|
|
3315
3941
|
|
|
3316
3942
|
# Create new debounce timer
|
|
3317
|
-
def debounced_update():
|
|
3943
|
+
def debounced_update() -> None:
|
|
3318
3944
|
current_time = time.time()
|
|
3319
3945
|
time_since_last_update = current_time - self._last_update
|
|
3320
3946
|
|
|
@@ -3323,11 +3949,12 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
3323
3949
|
self._refresh_executor.submit(self._async_update_components)
|
|
3324
3950
|
|
|
3325
3951
|
self._debounce_timers["main"] = threading.Timer(
|
|
3326
|
-
self._debounce_delay,
|
|
3952
|
+
self._debounce_delay,
|
|
3953
|
+
debounced_update,
|
|
3327
3954
|
)
|
|
3328
3955
|
self._debounce_timers["main"].start()
|
|
3329
3956
|
|
|
3330
|
-
def _should_skip_frame(self):
|
|
3957
|
+
def _should_skip_frame(self) -> bool:
|
|
3331
3958
|
"""Determine if we should skip this frame update to maintain stability."""
|
|
3332
3959
|
# Skip frames more aggressively for macOS terminals
|
|
3333
3960
|
term_type = self._terminal_performance["type"]
|
|
@@ -3336,15 +3963,12 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
3336
3963
|
if self._dropped_frames > 1:
|
|
3337
3964
|
return True
|
|
3338
3965
|
# Skip if refresh executor is overloaded
|
|
3339
|
-
if (
|
|
3340
|
-
hasattr(self._refresh_executor, "_work_queue")
|
|
3341
|
-
and self._refresh_executor._work_queue.qsize() > 2
|
|
3342
|
-
):
|
|
3966
|
+
if hasattr(self._refresh_executor, "_work_queue") and self._refresh_executor._work_queue.qsize() > 2:
|
|
3343
3967
|
return True
|
|
3344
3968
|
|
|
3345
3969
|
return False
|
|
3346
3970
|
|
|
3347
|
-
def _async_update_components(self):
|
|
3971
|
+
def _async_update_components(self) -> None:
|
|
3348
3972
|
"""Asynchronously update only the components that have changed."""
|
|
3349
3973
|
start_time = time.time()
|
|
3350
3974
|
|
|
@@ -3364,14 +3988,19 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
3364
3988
|
|
|
3365
3989
|
for update_id in updates_to_process:
|
|
3366
3990
|
if update_id == "header":
|
|
3367
|
-
future = self._refresh_executor.submit(
|
|
3991
|
+
future = self._refresh_executor.submit(
|
|
3992
|
+
self._update_header_cache,
|
|
3993
|
+
)
|
|
3368
3994
|
futures.append(future)
|
|
3369
3995
|
elif update_id == "footer":
|
|
3370
|
-
future = self._refresh_executor.submit(
|
|
3996
|
+
future = self._refresh_executor.submit(
|
|
3997
|
+
self._update_footer_cache,
|
|
3998
|
+
)
|
|
3371
3999
|
futures.append(future)
|
|
3372
4000
|
elif update_id in self.agent_ids:
|
|
3373
4001
|
future = self._refresh_executor.submit(
|
|
3374
|
-
self._update_agent_panel_cache,
|
|
4002
|
+
self._update_agent_panel_cache,
|
|
4003
|
+
update_id,
|
|
3375
4004
|
)
|
|
3376
4005
|
futures.append(future)
|
|
3377
4006
|
|
|
@@ -3391,28 +4020,39 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
3391
4020
|
self._refresh_times.append(refresh_time)
|
|
3392
4021
|
self._monitor_performance()
|
|
3393
4022
|
|
|
3394
|
-
def _update_header_cache(self):
|
|
4023
|
+
def _update_header_cache(self) -> None:
|
|
3395
4024
|
"""Update the cached header panel."""
|
|
3396
4025
|
try:
|
|
3397
4026
|
self._header_cache = self._create_header()
|
|
3398
|
-
except:
|
|
4027
|
+
except Exception:
|
|
3399
4028
|
pass
|
|
3400
4029
|
|
|
3401
|
-
def _update_footer_cache(self):
|
|
4030
|
+
def _update_footer_cache(self) -> None:
|
|
3402
4031
|
"""Update the cached footer panel."""
|
|
3403
4032
|
try:
|
|
3404
4033
|
self._footer_cache = self._create_footer()
|
|
3405
|
-
except:
|
|
4034
|
+
except Exception:
|
|
3406
4035
|
pass
|
|
3407
4036
|
|
|
3408
4037
|
def _update_agent_panel_cache(self, agent_id: str):
|
|
3409
4038
|
"""Update the cached panel for a specific agent."""
|
|
3410
4039
|
try:
|
|
3411
|
-
self._agent_panels_cache[agent_id] = self._create_agent_panel(
|
|
3412
|
-
|
|
4040
|
+
self._agent_panels_cache[agent_id] = self._create_agent_panel(
|
|
4041
|
+
agent_id,
|
|
4042
|
+
)
|
|
4043
|
+
except Exception:
|
|
4044
|
+
pass
|
|
4045
|
+
|
|
4046
|
+
def _update_final_presentation_panel(self) -> None:
|
|
4047
|
+
"""Update the live display to show the latest final presentation content."""
|
|
4048
|
+
try:
|
|
4049
|
+
if self.live and self.live.is_started:
|
|
4050
|
+
with self._lock:
|
|
4051
|
+
self.live.update(self._create_layout())
|
|
4052
|
+
except Exception:
|
|
3413
4053
|
pass
|
|
3414
4054
|
|
|
3415
|
-
def _refresh_display(self):
|
|
4055
|
+
def _refresh_display(self) -> None:
|
|
3416
4056
|
"""Override parent's refresh method to use async updates."""
|
|
3417
4057
|
# Only refresh if there are actual pending updates
|
|
3418
4058
|
# This prevents unnecessary full refreshes
|
|
@@ -3430,10 +4070,7 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
3430
4070
|
return True
|
|
3431
4071
|
|
|
3432
4072
|
# Check for error indicators
|
|
3433
|
-
if any(
|
|
3434
|
-
keyword in content.lower()
|
|
3435
|
-
for keyword in ["error", "exception", "failed", "timeout"]
|
|
3436
|
-
):
|
|
4073
|
+
if any(keyword in content.lower() for keyword in ["error", "exception", "failed", "timeout"]):
|
|
3437
4074
|
return True
|
|
3438
4075
|
|
|
3439
4076
|
return False
|
|
@@ -3459,7 +4096,10 @@ class RichTerminalDisplay(TerminalDisplay):
|
|
|
3459
4096
|
self._max_web_search_lines = max_lines
|
|
3460
4097
|
|
|
3461
4098
|
def set_flush_output(
|
|
3462
|
-
self,
|
|
4099
|
+
self,
|
|
4100
|
+
enabled: bool,
|
|
4101
|
+
char_delay: float = 0.03,
|
|
4102
|
+
word_delay: float = 0.08,
|
|
3463
4103
|
):
|
|
3464
4104
|
"""Configure flush output settings for final answer display.
|
|
3465
4105
|
|