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.

Files changed (268) hide show
  1. massgen/__init__.py +142 -8
  2. massgen/adapters/__init__.py +29 -0
  3. massgen/adapters/ag2_adapter.py +483 -0
  4. massgen/adapters/base.py +183 -0
  5. massgen/adapters/tests/__init__.py +0 -0
  6. massgen/adapters/tests/test_ag2_adapter.py +439 -0
  7. massgen/adapters/tests/test_agent_adapter.py +128 -0
  8. massgen/adapters/utils/__init__.py +2 -0
  9. massgen/adapters/utils/ag2_utils.py +236 -0
  10. massgen/adapters/utils/tests/__init__.py +0 -0
  11. massgen/adapters/utils/tests/test_ag2_utils.py +138 -0
  12. massgen/agent_config.py +329 -55
  13. massgen/api_params_handler/__init__.py +10 -0
  14. massgen/api_params_handler/_api_params_handler_base.py +99 -0
  15. massgen/api_params_handler/_chat_completions_api_params_handler.py +176 -0
  16. massgen/api_params_handler/_claude_api_params_handler.py +113 -0
  17. massgen/api_params_handler/_response_api_params_handler.py +130 -0
  18. massgen/backend/__init__.py +39 -4
  19. massgen/backend/azure_openai.py +385 -0
  20. massgen/backend/base.py +341 -69
  21. massgen/backend/base_with_mcp.py +1102 -0
  22. massgen/backend/capabilities.py +386 -0
  23. massgen/backend/chat_completions.py +577 -130
  24. massgen/backend/claude.py +1033 -537
  25. massgen/backend/claude_code.py +1203 -0
  26. massgen/backend/cli_base.py +209 -0
  27. massgen/backend/docs/BACKEND_ARCHITECTURE.md +126 -0
  28. massgen/backend/{CLAUDE_API_RESEARCH.md → docs/CLAUDE_API_RESEARCH.md} +18 -18
  29. massgen/backend/{GEMINI_API_DOCUMENTATION.md → docs/GEMINI_API_DOCUMENTATION.md} +9 -9
  30. massgen/backend/docs/Gemini MCP Integration Analysis.md +1050 -0
  31. massgen/backend/docs/MCP_IMPLEMENTATION_CLAUDE_BACKEND.md +177 -0
  32. massgen/backend/docs/MCP_INTEGRATION_RESPONSE_BACKEND.md +352 -0
  33. massgen/backend/docs/OPENAI_GPT5_MODELS.md +211 -0
  34. massgen/backend/{OPENAI_RESPONSES_API_FORMAT.md → docs/OPENAI_RESPONSE_API_TOOL_CALLS.md} +3 -3
  35. massgen/backend/docs/OPENAI_response_streaming.md +20654 -0
  36. massgen/backend/docs/inference_backend.md +257 -0
  37. massgen/backend/docs/permissions_and_context_files.md +1085 -0
  38. massgen/backend/external.py +126 -0
  39. massgen/backend/gemini.py +1850 -241
  40. massgen/backend/grok.py +40 -156
  41. massgen/backend/inference.py +156 -0
  42. massgen/backend/lmstudio.py +171 -0
  43. massgen/backend/response.py +1095 -322
  44. massgen/chat_agent.py +131 -113
  45. massgen/cli.py +1560 -275
  46. massgen/config_builder.py +2396 -0
  47. massgen/configs/BACKEND_CONFIGURATION.md +458 -0
  48. massgen/configs/README.md +559 -216
  49. massgen/configs/ag2/ag2_case_study.yaml +27 -0
  50. massgen/configs/ag2/ag2_coder.yaml +34 -0
  51. massgen/configs/ag2/ag2_coder_case_study.yaml +36 -0
  52. massgen/configs/ag2/ag2_gemini.yaml +27 -0
  53. massgen/configs/ag2/ag2_groupchat.yaml +108 -0
  54. massgen/configs/ag2/ag2_groupchat_gpt.yaml +118 -0
  55. massgen/configs/ag2/ag2_single_agent.yaml +21 -0
  56. massgen/configs/basic/multi/fast_timeout_example.yaml +37 -0
  57. massgen/configs/basic/multi/gemini_4o_claude.yaml +31 -0
  58. massgen/configs/basic/multi/gemini_gpt5nano_claude.yaml +36 -0
  59. massgen/configs/{gemini_4o_claude.yaml → basic/multi/geminicode_4o_claude.yaml} +3 -3
  60. massgen/configs/basic/multi/geminicode_gpt5nano_claude.yaml +36 -0
  61. massgen/configs/basic/multi/glm_gemini_claude.yaml +25 -0
  62. massgen/configs/basic/multi/gpt4o_audio_generation.yaml +30 -0
  63. massgen/configs/basic/multi/gpt4o_image_generation.yaml +31 -0
  64. massgen/configs/basic/multi/gpt5nano_glm_qwen.yaml +26 -0
  65. massgen/configs/basic/multi/gpt5nano_image_understanding.yaml +26 -0
  66. massgen/configs/{three_agents_default.yaml → basic/multi/three_agents_default.yaml} +8 -4
  67. massgen/configs/basic/multi/three_agents_opensource.yaml +27 -0
  68. massgen/configs/basic/multi/three_agents_vllm.yaml +20 -0
  69. massgen/configs/basic/multi/two_agents_gemini.yaml +19 -0
  70. massgen/configs/{two_agents.yaml → basic/multi/two_agents_gpt5.yaml} +14 -6
  71. massgen/configs/basic/multi/two_agents_opensource_lmstudio.yaml +31 -0
  72. massgen/configs/basic/multi/two_qwen_vllm_sglang.yaml +28 -0
  73. massgen/configs/{single_agent.yaml → basic/single/single_agent.yaml} +1 -1
  74. massgen/configs/{single_flash2.5.yaml → basic/single/single_flash2.5.yaml} +1 -2
  75. massgen/configs/basic/single/single_gemini2.5pro.yaml +16 -0
  76. massgen/configs/basic/single/single_gpt4o_audio_generation.yaml +22 -0
  77. massgen/configs/basic/single/single_gpt4o_image_generation.yaml +22 -0
  78. massgen/configs/basic/single/single_gpt4o_video_generation.yaml +24 -0
  79. massgen/configs/basic/single/single_gpt5nano.yaml +20 -0
  80. massgen/configs/basic/single/single_gpt5nano_file_search.yaml +18 -0
  81. massgen/configs/basic/single/single_gpt5nano_image_understanding.yaml +17 -0
  82. massgen/configs/basic/single/single_gptoss120b.yaml +15 -0
  83. massgen/configs/basic/single/single_openrouter_audio_understanding.yaml +15 -0
  84. massgen/configs/basic/single/single_qwen_video_understanding.yaml +15 -0
  85. massgen/configs/debug/code_execution/command_filtering_blacklist.yaml +29 -0
  86. massgen/configs/debug/code_execution/command_filtering_whitelist.yaml +28 -0
  87. massgen/configs/debug/code_execution/docker_verification.yaml +29 -0
  88. massgen/configs/debug/skip_coordination_test.yaml +27 -0
  89. massgen/configs/debug/test_sdk_migration.yaml +17 -0
  90. massgen/configs/docs/DISCORD_MCP_SETUP.md +208 -0
  91. massgen/configs/docs/TWITTER_MCP_ENESCINAR_SETUP.md +82 -0
  92. massgen/configs/providers/azure/azure_openai_multi.yaml +21 -0
  93. massgen/configs/providers/azure/azure_openai_single.yaml +19 -0
  94. massgen/configs/providers/claude/claude.yaml +14 -0
  95. massgen/configs/providers/gemini/gemini_gpt5nano.yaml +28 -0
  96. massgen/configs/providers/local/lmstudio.yaml +11 -0
  97. massgen/configs/providers/openai/gpt5.yaml +46 -0
  98. massgen/configs/providers/openai/gpt5_nano.yaml +46 -0
  99. massgen/configs/providers/others/grok_single_agent.yaml +19 -0
  100. massgen/configs/providers/others/zai_coding_team.yaml +108 -0
  101. massgen/configs/providers/others/zai_glm45.yaml +12 -0
  102. massgen/configs/{creative_team.yaml → teams/creative/creative_team.yaml} +16 -6
  103. massgen/configs/{travel_planning.yaml → teams/creative/travel_planning.yaml} +16 -6
  104. massgen/configs/{news_analysis.yaml → teams/research/news_analysis.yaml} +16 -6
  105. massgen/configs/{research_team.yaml → teams/research/research_team.yaml} +15 -7
  106. massgen/configs/{technical_analysis.yaml → teams/research/technical_analysis.yaml} +16 -6
  107. massgen/configs/tools/code-execution/basic_command_execution.yaml +25 -0
  108. massgen/configs/tools/code-execution/code_execution_use_case_simple.yaml +41 -0
  109. massgen/configs/tools/code-execution/docker_claude_code.yaml +32 -0
  110. massgen/configs/tools/code-execution/docker_multi_agent.yaml +32 -0
  111. massgen/configs/tools/code-execution/docker_simple.yaml +29 -0
  112. massgen/configs/tools/code-execution/docker_with_resource_limits.yaml +32 -0
  113. massgen/configs/tools/code-execution/multi_agent_playwright_automation.yaml +57 -0
  114. massgen/configs/tools/filesystem/cc_gpt5_gemini_filesystem.yaml +34 -0
  115. massgen/configs/tools/filesystem/claude_code_context_sharing.yaml +68 -0
  116. massgen/configs/tools/filesystem/claude_code_flash2.5.yaml +43 -0
  117. massgen/configs/tools/filesystem/claude_code_flash2.5_gptoss.yaml +49 -0
  118. massgen/configs/tools/filesystem/claude_code_gpt5nano.yaml +31 -0
  119. massgen/configs/tools/filesystem/claude_code_single.yaml +40 -0
  120. massgen/configs/tools/filesystem/fs_permissions_test.yaml +87 -0
  121. massgen/configs/tools/filesystem/gemini_gemini_workspace_cleanup.yaml +54 -0
  122. massgen/configs/tools/filesystem/gemini_gpt5_filesystem_casestudy.yaml +30 -0
  123. massgen/configs/tools/filesystem/gemini_gpt5nano_file_context_path.yaml +43 -0
  124. massgen/configs/tools/filesystem/gemini_gpt5nano_protected_paths.yaml +45 -0
  125. massgen/configs/tools/filesystem/gpt5mini_cc_fs_context_path.yaml +31 -0
  126. massgen/configs/tools/filesystem/grok4_gpt5_gemini_filesystem.yaml +32 -0
  127. massgen/configs/tools/filesystem/multiturn/grok4_gpt5_claude_code_filesystem_multiturn.yaml +58 -0
  128. massgen/configs/tools/filesystem/multiturn/grok4_gpt5_gemini_filesystem_multiturn.yaml +58 -0
  129. massgen/configs/tools/filesystem/multiturn/two_claude_code_filesystem_multiturn.yaml +47 -0
  130. massgen/configs/tools/filesystem/multiturn/two_gemini_flash_filesystem_multiturn.yaml +48 -0
  131. massgen/configs/tools/mcp/claude_code_discord_mcp_example.yaml +27 -0
  132. massgen/configs/tools/mcp/claude_code_simple_mcp.yaml +35 -0
  133. massgen/configs/tools/mcp/claude_code_twitter_mcp_example.yaml +32 -0
  134. massgen/configs/tools/mcp/claude_mcp_example.yaml +24 -0
  135. massgen/configs/tools/mcp/claude_mcp_test.yaml +27 -0
  136. massgen/configs/tools/mcp/five_agents_travel_mcp_test.yaml +157 -0
  137. massgen/configs/tools/mcp/five_agents_weather_mcp_test.yaml +103 -0
  138. massgen/configs/tools/mcp/gemini_mcp_example.yaml +24 -0
  139. massgen/configs/tools/mcp/gemini_mcp_filesystem_test.yaml +23 -0
  140. massgen/configs/tools/mcp/gemini_mcp_filesystem_test_sharing.yaml +23 -0
  141. massgen/configs/tools/mcp/gemini_mcp_filesystem_test_single_agent.yaml +17 -0
  142. massgen/configs/tools/mcp/gemini_mcp_filesystem_test_with_claude_code.yaml +24 -0
  143. massgen/configs/tools/mcp/gemini_mcp_test.yaml +27 -0
  144. massgen/configs/tools/mcp/gemini_notion_mcp.yaml +52 -0
  145. massgen/configs/tools/mcp/gpt5_nano_mcp_example.yaml +24 -0
  146. massgen/configs/tools/mcp/gpt5_nano_mcp_test.yaml +27 -0
  147. massgen/configs/tools/mcp/gpt5mini_claude_code_discord_mcp_example.yaml +38 -0
  148. massgen/configs/tools/mcp/gpt_oss_mcp_example.yaml +25 -0
  149. massgen/configs/tools/mcp/gpt_oss_mcp_test.yaml +28 -0
  150. massgen/configs/tools/mcp/grok3_mini_mcp_example.yaml +24 -0
  151. massgen/configs/tools/mcp/grok3_mini_mcp_test.yaml +27 -0
  152. massgen/configs/tools/mcp/multimcp_gemini.yaml +111 -0
  153. massgen/configs/tools/mcp/qwen_api_mcp_example.yaml +25 -0
  154. massgen/configs/tools/mcp/qwen_api_mcp_test.yaml +28 -0
  155. massgen/configs/tools/mcp/qwen_local_mcp_example.yaml +24 -0
  156. massgen/configs/tools/mcp/qwen_local_mcp_test.yaml +27 -0
  157. massgen/configs/tools/planning/five_agents_discord_mcp_planning_mode.yaml +140 -0
  158. massgen/configs/tools/planning/five_agents_filesystem_mcp_planning_mode.yaml +151 -0
  159. massgen/configs/tools/planning/five_agents_notion_mcp_planning_mode.yaml +151 -0
  160. massgen/configs/tools/planning/five_agents_twitter_mcp_planning_mode.yaml +155 -0
  161. massgen/configs/tools/planning/gpt5_mini_case_study_mcp_planning_mode.yaml +73 -0
  162. massgen/configs/tools/web-search/claude_streamable_http_test.yaml +43 -0
  163. massgen/configs/tools/web-search/gemini_streamable_http_test.yaml +43 -0
  164. massgen/configs/tools/web-search/gpt5_mini_streamable_http_test.yaml +43 -0
  165. massgen/configs/tools/web-search/gpt_oss_streamable_http_test.yaml +44 -0
  166. massgen/configs/tools/web-search/grok3_mini_streamable_http_test.yaml +43 -0
  167. massgen/configs/tools/web-search/qwen_api_streamable_http_test.yaml +44 -0
  168. massgen/configs/tools/web-search/qwen_local_streamable_http_test.yaml +43 -0
  169. massgen/coordination_tracker.py +708 -0
  170. massgen/docker/README.md +462 -0
  171. massgen/filesystem_manager/__init__.py +21 -0
  172. massgen/filesystem_manager/_base.py +9 -0
  173. massgen/filesystem_manager/_code_execution_server.py +545 -0
  174. massgen/filesystem_manager/_docker_manager.py +477 -0
  175. massgen/filesystem_manager/_file_operation_tracker.py +248 -0
  176. massgen/filesystem_manager/_filesystem_manager.py +813 -0
  177. massgen/filesystem_manager/_path_permission_manager.py +1261 -0
  178. massgen/filesystem_manager/_workspace_tools_server.py +1815 -0
  179. massgen/formatter/__init__.py +10 -0
  180. massgen/formatter/_chat_completions_formatter.py +284 -0
  181. massgen/formatter/_claude_formatter.py +235 -0
  182. massgen/formatter/_formatter_base.py +156 -0
  183. massgen/formatter/_response_formatter.py +263 -0
  184. massgen/frontend/__init__.py +1 -2
  185. massgen/frontend/coordination_ui.py +471 -286
  186. massgen/frontend/displays/base_display.py +56 -11
  187. massgen/frontend/displays/create_coordination_table.py +1956 -0
  188. massgen/frontend/displays/rich_terminal_display.py +1259 -619
  189. massgen/frontend/displays/simple_display.py +9 -4
  190. massgen/frontend/displays/terminal_display.py +27 -68
  191. massgen/logger_config.py +681 -0
  192. massgen/mcp_tools/README.md +232 -0
  193. massgen/mcp_tools/__init__.py +105 -0
  194. massgen/mcp_tools/backend_utils.py +1035 -0
  195. massgen/mcp_tools/circuit_breaker.py +195 -0
  196. massgen/mcp_tools/client.py +894 -0
  197. massgen/mcp_tools/config_validator.py +138 -0
  198. massgen/mcp_tools/docs/circuit_breaker.md +646 -0
  199. massgen/mcp_tools/docs/client.md +950 -0
  200. massgen/mcp_tools/docs/config_validator.md +478 -0
  201. massgen/mcp_tools/docs/exceptions.md +1165 -0
  202. massgen/mcp_tools/docs/security.md +854 -0
  203. massgen/mcp_tools/exceptions.py +338 -0
  204. massgen/mcp_tools/hooks.py +212 -0
  205. massgen/mcp_tools/security.py +780 -0
  206. massgen/message_templates.py +342 -64
  207. massgen/orchestrator.py +1515 -241
  208. massgen/stream_chunk/__init__.py +35 -0
  209. massgen/stream_chunk/base.py +92 -0
  210. massgen/stream_chunk/multimodal.py +237 -0
  211. massgen/stream_chunk/text.py +162 -0
  212. massgen/tests/mcp_test_server.py +150 -0
  213. massgen/tests/multi_turn_conversation_design.md +0 -8
  214. massgen/tests/test_azure_openai_backend.py +156 -0
  215. massgen/tests/test_backend_capabilities.py +262 -0
  216. massgen/tests/test_backend_event_loop_all.py +179 -0
  217. massgen/tests/test_chat_completions_refactor.py +142 -0
  218. massgen/tests/test_claude_backend.py +15 -28
  219. massgen/tests/test_claude_code.py +268 -0
  220. massgen/tests/test_claude_code_context_sharing.py +233 -0
  221. massgen/tests/test_claude_code_orchestrator.py +175 -0
  222. massgen/tests/test_cli_backends.py +180 -0
  223. massgen/tests/test_code_execution.py +679 -0
  224. massgen/tests/test_external_agent_backend.py +134 -0
  225. massgen/tests/test_final_presentation_fallback.py +237 -0
  226. massgen/tests/test_gemini_planning_mode.py +351 -0
  227. massgen/tests/test_grok_backend.py +7 -10
  228. massgen/tests/test_http_mcp_server.py +42 -0
  229. massgen/tests/test_integration_simple.py +198 -0
  230. massgen/tests/test_mcp_blocking.py +125 -0
  231. massgen/tests/test_message_context_building.py +29 -47
  232. massgen/tests/test_orchestrator_final_presentation.py +48 -0
  233. massgen/tests/test_path_permission_manager.py +2087 -0
  234. massgen/tests/test_rich_terminal_display.py +14 -13
  235. massgen/tests/test_timeout.py +133 -0
  236. massgen/tests/test_v3_3agents.py +11 -12
  237. massgen/tests/test_v3_simple.py +8 -13
  238. massgen/tests/test_v3_three_agents.py +11 -18
  239. massgen/tests/test_v3_two_agents.py +8 -13
  240. massgen/token_manager/__init__.py +7 -0
  241. massgen/token_manager/token_manager.py +400 -0
  242. massgen/utils.py +52 -16
  243. massgen/v1/agent.py +45 -91
  244. massgen/v1/agents.py +18 -53
  245. massgen/v1/backends/gemini.py +50 -153
  246. massgen/v1/backends/grok.py +21 -54
  247. massgen/v1/backends/oai.py +39 -111
  248. massgen/v1/cli.py +36 -93
  249. massgen/v1/config.py +8 -12
  250. massgen/v1/logging.py +43 -127
  251. massgen/v1/main.py +18 -32
  252. massgen/v1/orchestrator.py +68 -209
  253. massgen/v1/streaming_display.py +62 -163
  254. massgen/v1/tools.py +8 -12
  255. massgen/v1/types.py +9 -23
  256. massgen/v1/utils.py +5 -23
  257. massgen-0.1.0.dist-info/METADATA +1245 -0
  258. massgen-0.1.0.dist-info/RECORD +273 -0
  259. massgen-0.1.0.dist-info/entry_points.txt +2 -0
  260. massgen/frontend/logging/__init__.py +0 -9
  261. massgen/frontend/logging/realtime_logger.py +0 -197
  262. massgen-0.0.3.dist-info/METADATA +0 -568
  263. massgen-0.0.3.dist-info/RECORD +0 -76
  264. massgen-0.0.3.dist-info/entry_points.txt +0 -2
  265. /massgen/backend/{Function calling openai responses.md → docs/Function calling openai responses.md} +0 -0
  266. {massgen-0.0.3.dist-info → massgen-0.1.0.dist-info}/WHEEL +0 -0
  267. {massgen-0.0.3.dist-info → massgen-0.1.0.dist-info}/licenses/LICENSE +0 -0
  268. {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 sys
14
- import select
15
- import tty
16
- import termios
17
- import subprocess
11
+ import re
18
12
  import signal
19
- from pathlib import Path
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 typing import List, Optional, Dict, Any
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 = HEAVY = DOUBLE = None
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(kwargs.get("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("enable_syntax_highlighting", True)
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, self.terminal_size.width // self.num_agents - 1
139
+ 20,
140
+ self.terminal_size.width // self.num_agents - 1,
132
141
  )
133
142
  self.agent_panel_height = max(
134
- 10, self.terminal_size.height - 13
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("keyboard_interactive_mode", True)
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", False
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 = {"presentation", "status", "tool", "error"}
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", True
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", True
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", 3
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", True
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", 0.03
288
+ "flush_char_delay",
289
+ 0.03,
262
290
  ) # Delay between characters
263
291
  self._flush_word_delay = kwargs.get(
264
- "flush_word_delay", 0.08
292
+ "flush_word_delay",
293
+ 0.08,
265
294
  ) # Extra delay after punctuation
266
295
 
267
296
  # File-based output system
268
- self.output_dir = kwargs.get("output_dir", "agent_outputs")
269
- self.agent_files = {}
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(target=self._handle_terminal_resize, daemon=True).start()
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, self.terminal_size.width // self.num_agents - 1
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
- terminal_info["buffer_size"] = "large" # VSCode has good buffering
447
+ # VSCode has good buffering
448
+ terminal_info["buffer_size"] = "large"
417
449
  terminal_info["needs_flush_delay"] = True # Reduce flicker
418
- terminal_info["refresh_stabilization"] = True # Add stability delays
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
- terminal_info["performance_tier"] = "low" # Multiplexers are slower
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("SSH_CLIENT"):
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(self, user_override=None):
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 = {"high": 0.1, "medium": 0.2, "low": 0.5} # 100ms # 200ms # 500ms
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 = {"high": 0.01, "medium": 0.03, "low": 0.05} # 10ms # 30ms # 50ms
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
- current_time = time.time()
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(self._refresh_times)
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
- return self._fallback_to_simple_display()
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(self._create_layout(), console=self.console, **live_settings)
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
- return self._fallback_to_simple_display()
709
+ self._fallback_to_simple_display()
710
+ return None
661
711
 
662
712
  except Exception:
663
713
  # Any error in setup, use fallback
664
- return self._fallback_to_simple_display()
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
- settings["refresh_per_second"] = min(settings["refresh_per_second"], 3)
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
- settings["refresh_per_second"] = min(settings["refresh_per_second"], 8)
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
- settings["refresh_per_second"] = min(settings["refresh_per_second"], 5)
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
- settings["refresh_per_second"] = min(settings["refresh_per_second"], 6)
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
- "primary": "bright_cyan",
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 sometimes has issues with certain bright colors
904
- # Use more stable color palette
948
+ # VSCode terminal works well with our unified color scheme
949
+ # Keep the hex colors for consistent appearance
905
950
  vscode_adjustments = {
906
- "primary": "cyan", # Less bright than bright_cyan
907
- "secondary": "blue",
908
- "border": "cyan",
909
- "panel_style": "cyan",
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(self, question: str, log_filename: Optional[str] = None):
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", style=self.colors["header_style"]
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", style=self.colors["info"]
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()}", style=self.colors["secondary"]
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
- # Arrange layout
1017
- layout.split_column(
1018
- Layout(header, name="header", size=5),
1019
- Layout(agent_columns, name="main"),
1020
- Layout(footer, name="footer", size=8),
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, equal=False, expand=False, width=self.fixed_column_width
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[:80]}{'...' if len(self.question) > 80 else ''}",
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, equal=False, expand=False, width=self.fixed_column_width
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 ["iterm", "macos_terminal"]:
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, daemon=True
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, daemon=True
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, daemon=True
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(sys.stdin.fileno(), termios.TCSANOW, new_settings)
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 as e:
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 self._original_settings and sys.stdin.isatty():
1200
- termios.tcsetattr(
1201
- sys.stdin.fileno(), termios.TCSADRAIN, self._original_settings
1202
- )
1203
- self._original_settings = None
1204
- except:
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(["start", str(file_path)], check=False, shell=True)
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(["code", "--new-window", str(file_path)], check=False)
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(["open", str(self.system_status_file)], check=False)
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(["xdg-open", str(self.system_status_file)], check=False)
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)], check=False, shell=True
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 _open_system_status_in_vscode_new_window(self):
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)], check=False
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)], check=False
1491
+ ["open", "-a", "TextEdit", str(file_path)],
1492
+ check=False,
1336
1493
  )
1337
1494
  else:
1338
- subprocess.run([editor, str(file_path)], check=False)
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)], check=False, shell=True
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)], check=False
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)], check=False
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", style=self.colors["info"]
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, box=DOUBLE, border_style=self.colors["border"]
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 as e:
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(self, "_agent_keys"):
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
- "\nThis is a system inspection interface for diving into the multi-agent collaboration behind the scenes in MassGen. It lets you examine each agent’s original output and compare it to the final MassGen answer in terms of quality. You can explore the detailed communication, collaboration, voting, and decision-making process.\n",
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}: ", style=self.colors["warning"]
1684
+ f" {key}: ",
1685
+ style=self.colors["warning"],
1510
1686
  )
1511
1687
  options_text.append(
1512
- f"Inspect the original answer and working log of agent ", style=self.colors["text"]
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", style=self.colors["warning"]
1692
+ f"{agent_id}\n",
1693
+ style=self.colors["warning"],
1516
1694
  )
1517
1695
 
1518
1696
  options_text.append(
1519
- " s: Inpsect the orchestrator working log including the voting process\n", style=self.colors["warning"]
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 ({agent_id})\n", style=self.colors["success"]
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(" q: Quit Inspection\n", style=self.colors["info"])
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, self._stored_final_presentation
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 _redisplay_final_presentation(self):
1584
- """Redisplay the stored final presentation."""
1585
- if not self._stored_final_presentation or not self._stored_presentation_agent:
1586
- self.console.print(
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
- # Add separator
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", style=self.colors["header_style"]
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", style=self.colors["info"]
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, box=DOUBLE, border_style=self.colors["border"]
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("No activity yet...", style=self.colors["text"])
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), None
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
- # Truncate line if too long
1731
- if len(line) > self.max_line_length:
1732
- line = line[: self.max_line_length - 3] + "..."
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(line[3:], style=f"italic {self.colors['warning']}")
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(clean_line, style=f"bold {self.colors['success']}")
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, style=f"bold {self.colors['header_style']}"
2046
+ clean_header,
2047
+ style=f"bold {self.colors['header_style']}",
1790
2048
  )
1791
2049
  else:
1792
2050
  formatted.append(
1793
- clean_header, style=f"bold {self.colors['primary']}"
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("Web search starting...", style=self.colors["text"])
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 ["🔍 [Search Query]", "Search Query:", "[Search Query]"]
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
- if len(query) > 80:
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(f'"{query}"', style=f"italic {self.colors['text']}")
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, 60
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
- + ["🔍 ... (web search content truncated due to status update) ..."]
1947
- + web_search_lines[
1948
- -(self._max_web_search_lines - 2) :
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("⚡ Status updated - jumped to latest")
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("📊 Agent Status: ", style=self.colors["primary"])
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
- footer_content.append(" | ".join(status_parts), style=self.colors["text"])
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("📋 Recent Events:\n", style=self.colors["primary"])
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(f" • {event}\n", style=self.colors["text"])
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", style=self.colors["info"]
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-", style=self.colors["primary"]
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
- f"{len(self.agent_ids)} to open agent files in editor, 's' for system status",
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, agent_id: str, content: str, content_type: str = "thinking"
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(agent_id, content, content_type)
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, agent_id: str, content: str, content_type: str
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, timeout_flush
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(self, agent_id: str, content: str, content_type: str):
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 as e:
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 as e:
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(agent_id, "waiting")
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
- was_live = self.live is not None
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(), key=lambda x: x[1], reverse=True
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("\n🔍 Vote Details:\n", style=self.colors["primary"])
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(f" → {voted_for}:\n", style=self.colors["info"])
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', style=self.colors["text"]
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
- # Restart live display if it was active
2456
- if was_live:
2457
- self.live = Live(
2458
- self._create_layout(),
2459
- console=self.console,
2460
- refresh_per_second=self.refresh_rate,
2461
- vertical_overflow="ellipsis",
2462
- transient=False,
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
- self.live.start()
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 from winning agent with enhanced orchestrator query support."""
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
- # Stop live display for clean presentation output
2477
- was_live = self.live is not None
2478
- if self.live:
2479
- self.live.stop()
2480
- self.live = None
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
- # Create presentation header with orchestrator context
2483
- header_text = Text()
2484
- header_text.append(
2485
- f"🎤 Final Presentation from {selected_agent}",
2486
- style=self.colors["header_style"],
2487
- )
2488
- if vote_results and vote_results.get("vote_counts"):
2489
- vote_count = vote_results["vote_counts"].get(selected_agent, 0)
2490
- header_text.append(
2491
- f" (Selected with {vote_count} votes)", style=self.colors["info"]
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
- header_panel = Panel(
2495
- Align.center(header_text),
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
- self.console.print(header_panel)
2502
- self.console.print("=" * 60)
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
- # Enhanced streaming with orchestrator query awareness
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
- # Accumulate content
2523
- presentation_content += content
2524
-
2525
- # Enhanced formatting for orchestrator query responses
2526
- if chunk_type == "status":
2527
- # Status updates from orchestrator query
2528
- status_text = Text(f"🔄 {content}", style=self.colors["info"])
2529
- self.console.print(status_text)
2530
- elif "error" in chunk_type:
2531
- # Error handling in orchestrator query
2532
- error_text = Text(f"❌ {content}", style=self.colors["error"])
2533
- self.console.print(error_text)
2534
- else:
2535
- # Main presentation content with simple output
2536
- self.console.print(content, end="", highlight=False)
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
- error_text = Text(
2550
- f"❌ Error during final presentation: {e}", style=self.colors["error"]
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
- fallback_text = Text(
2564
- f"\n📋 Fallback to stored answer:\n{stored_answer}",
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
- # Restart live display if needed
2589
- if was_live:
2590
- time.sleep(0.5) # Brief pause before restarting live display
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("vote_results", {})
2616
- selected_agent = selected_agent or status.get("selected_agent")
2617
- except:
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}", style=self.colors["success"]
3143
+ f"🏆 Selected agent: {selected_agent}",
3144
+ style=self.colors["success"],
2641
3145
  )
2642
3146
  else:
2643
- selected_agent_text = Text(
2644
- "No agent selected", style=self.colors["warning"]
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}", style=self.colors["success"]
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}", style=self.colors["info"]
3194
+ f"\n🗳️ Vote results: {vote_summary}",
3195
+ style=self.colors["info"],
2673
3196
  )
2674
3197
 
2675
3198
  selection_panel = Panel(
2676
- selection_text, border_style=self.colors["info"], box=ROUNDED
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(selected_agent, vote_results)
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
- hasattr(self.orchestrator, "agent_states")
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
- agent_state["answer"]
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 ["⚡", "🔄", "✅", "🗳️", "❌", "voted", "🔧", "status"]
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, selected_agent: str, presentation_content: str
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(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=f"[italic]Final presentation content[/italic]",
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", style=self.colors["success"]
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, selected_agent: str, vote_results: Dict[str, Any] = None
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, vote_results
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, presentation_stream, vote_results
3619
+ selected_agent,
3620
+ presentation_stream,
3621
+ vote_results,
3015
3622
  )
3016
- except Exception as e:
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 as e:
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 as e2:
3649
+ except Exception:
3043
3650
  # Last resort: show stored content
3044
3651
  self._display_final_presentation_content(
3045
- selected_agent, "Unable to retrieve live presentation."
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, stored_answer
3665
+ selected_agent,
3666
+ stored_answer,
3057
3667
  )
3058
3668
  else:
3059
3669
  print("DEBUG: No stored answer found")
3060
3670
  else:
3061
- print(f"DEBUG: Agent {selected_agent} not found in agent_states")
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}", style=self.colors["error"]
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
- time.sleep(0.3) # Increased wait to ensure all vote statuses are displayed
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
- def _schedule_priority_update(self, agent_id: str):
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(self, agent_id: str, content_type: str, content: str):
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(self, agent_id: str, is_critical: bool = False):
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(delay, delayed_update)
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, self._process_update_batch
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, debounced_update
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(self._update_header_cache)
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(self._update_footer_cache)
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, update_id
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(agent_id)
3412
- except:
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, enabled: bool, char_delay: float = 0.03, word_delay: float = 0.08
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