codepp 0.0.437__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.
Files changed (288) hide show
  1. code_puppy/__init__.py +10 -0
  2. code_puppy/__main__.py +10 -0
  3. code_puppy/agents/__init__.py +31 -0
  4. code_puppy/agents/agent_c_reviewer.py +155 -0
  5. code_puppy/agents/agent_code_puppy.py +117 -0
  6. code_puppy/agents/agent_code_reviewer.py +90 -0
  7. code_puppy/agents/agent_cpp_reviewer.py +132 -0
  8. code_puppy/agents/agent_creator_agent.py +638 -0
  9. code_puppy/agents/agent_golang_reviewer.py +151 -0
  10. code_puppy/agents/agent_helios.py +124 -0
  11. code_puppy/agents/agent_javascript_reviewer.py +160 -0
  12. code_puppy/agents/agent_manager.py +742 -0
  13. code_puppy/agents/agent_pack_leader.py +385 -0
  14. code_puppy/agents/agent_planning.py +165 -0
  15. code_puppy/agents/agent_python_programmer.py +169 -0
  16. code_puppy/agents/agent_python_reviewer.py +90 -0
  17. code_puppy/agents/agent_qa_expert.py +163 -0
  18. code_puppy/agents/agent_qa_kitten.py +208 -0
  19. code_puppy/agents/agent_scheduler.py +121 -0
  20. code_puppy/agents/agent_security_auditor.py +181 -0
  21. code_puppy/agents/agent_terminal_qa.py +323 -0
  22. code_puppy/agents/agent_typescript_reviewer.py +166 -0
  23. code_puppy/agents/base_agent.py +2156 -0
  24. code_puppy/agents/event_stream_handler.py +348 -0
  25. code_puppy/agents/json_agent.py +202 -0
  26. code_puppy/agents/pack/__init__.py +34 -0
  27. code_puppy/agents/pack/bloodhound.py +304 -0
  28. code_puppy/agents/pack/husky.py +327 -0
  29. code_puppy/agents/pack/retriever.py +393 -0
  30. code_puppy/agents/pack/shepherd.py +348 -0
  31. code_puppy/agents/pack/terrier.py +287 -0
  32. code_puppy/agents/pack/watchdog.py +367 -0
  33. code_puppy/agents/prompt_reviewer.py +145 -0
  34. code_puppy/agents/subagent_stream_handler.py +276 -0
  35. code_puppy/api/__init__.py +13 -0
  36. code_puppy/api/app.py +169 -0
  37. code_puppy/api/main.py +21 -0
  38. code_puppy/api/pty_manager.py +453 -0
  39. code_puppy/api/routers/__init__.py +12 -0
  40. code_puppy/api/routers/agents.py +36 -0
  41. code_puppy/api/routers/commands.py +217 -0
  42. code_puppy/api/routers/config.py +75 -0
  43. code_puppy/api/routers/sessions.py +234 -0
  44. code_puppy/api/templates/terminal.html +361 -0
  45. code_puppy/api/websocket.py +154 -0
  46. code_puppy/callbacks.py +692 -0
  47. code_puppy/chatgpt_codex_client.py +338 -0
  48. code_puppy/claude_cache_client.py +672 -0
  49. code_puppy/cli_runner.py +1073 -0
  50. code_puppy/command_line/__init__.py +1 -0
  51. code_puppy/command_line/add_model_menu.py +1092 -0
  52. code_puppy/command_line/agent_menu.py +662 -0
  53. code_puppy/command_line/attachments.py +395 -0
  54. code_puppy/command_line/autosave_menu.py +704 -0
  55. code_puppy/command_line/clipboard.py +527 -0
  56. code_puppy/command_line/colors_menu.py +532 -0
  57. code_puppy/command_line/command_handler.py +293 -0
  58. code_puppy/command_line/command_registry.py +150 -0
  59. code_puppy/command_line/config_commands.py +719 -0
  60. code_puppy/command_line/core_commands.py +867 -0
  61. code_puppy/command_line/diff_menu.py +865 -0
  62. code_puppy/command_line/file_path_completion.py +73 -0
  63. code_puppy/command_line/load_context_completion.py +52 -0
  64. code_puppy/command_line/mcp/__init__.py +10 -0
  65. code_puppy/command_line/mcp/base.py +32 -0
  66. code_puppy/command_line/mcp/catalog_server_installer.py +175 -0
  67. code_puppy/command_line/mcp/custom_server_form.py +688 -0
  68. code_puppy/command_line/mcp/custom_server_installer.py +195 -0
  69. code_puppy/command_line/mcp/edit_command.py +148 -0
  70. code_puppy/command_line/mcp/handler.py +138 -0
  71. code_puppy/command_line/mcp/help_command.py +147 -0
  72. code_puppy/command_line/mcp/install_command.py +214 -0
  73. code_puppy/command_line/mcp/install_menu.py +705 -0
  74. code_puppy/command_line/mcp/list_command.py +94 -0
  75. code_puppy/command_line/mcp/logs_command.py +235 -0
  76. code_puppy/command_line/mcp/remove_command.py +82 -0
  77. code_puppy/command_line/mcp/restart_command.py +100 -0
  78. code_puppy/command_line/mcp/search_command.py +123 -0
  79. code_puppy/command_line/mcp/start_all_command.py +135 -0
  80. code_puppy/command_line/mcp/start_command.py +117 -0
  81. code_puppy/command_line/mcp/status_command.py +184 -0
  82. code_puppy/command_line/mcp/stop_all_command.py +112 -0
  83. code_puppy/command_line/mcp/stop_command.py +80 -0
  84. code_puppy/command_line/mcp/test_command.py +107 -0
  85. code_puppy/command_line/mcp/utils.py +129 -0
  86. code_puppy/command_line/mcp/wizard_utils.py +334 -0
  87. code_puppy/command_line/mcp_completion.py +174 -0
  88. code_puppy/command_line/model_picker_completion.py +197 -0
  89. code_puppy/command_line/model_settings_menu.py +932 -0
  90. code_puppy/command_line/motd.py +96 -0
  91. code_puppy/command_line/onboarding_slides.py +179 -0
  92. code_puppy/command_line/onboarding_wizard.py +342 -0
  93. code_puppy/command_line/pin_command_completion.py +329 -0
  94. code_puppy/command_line/prompt_toolkit_completion.py +846 -0
  95. code_puppy/command_line/session_commands.py +302 -0
  96. code_puppy/command_line/shell_passthrough.py +145 -0
  97. code_puppy/command_line/skills_completion.py +160 -0
  98. code_puppy/command_line/uc_menu.py +893 -0
  99. code_puppy/command_line/utils.py +93 -0
  100. code_puppy/command_line/wiggum_state.py +78 -0
  101. code_puppy/config.py +1770 -0
  102. code_puppy/error_logging.py +134 -0
  103. code_puppy/gemini_code_assist.py +385 -0
  104. code_puppy/gemini_model.py +754 -0
  105. code_puppy/hook_engine/README.md +105 -0
  106. code_puppy/hook_engine/__init__.py +21 -0
  107. code_puppy/hook_engine/aliases.py +155 -0
  108. code_puppy/hook_engine/engine.py +221 -0
  109. code_puppy/hook_engine/executor.py +296 -0
  110. code_puppy/hook_engine/matcher.py +156 -0
  111. code_puppy/hook_engine/models.py +240 -0
  112. code_puppy/hook_engine/registry.py +106 -0
  113. code_puppy/hook_engine/validator.py +144 -0
  114. code_puppy/http_utils.py +361 -0
  115. code_puppy/keymap.py +128 -0
  116. code_puppy/main.py +10 -0
  117. code_puppy/mcp_/__init__.py +66 -0
  118. code_puppy/mcp_/async_lifecycle.py +286 -0
  119. code_puppy/mcp_/blocking_startup.py +469 -0
  120. code_puppy/mcp_/captured_stdio_server.py +275 -0
  121. code_puppy/mcp_/circuit_breaker.py +290 -0
  122. code_puppy/mcp_/config_wizard.py +507 -0
  123. code_puppy/mcp_/dashboard.py +308 -0
  124. code_puppy/mcp_/error_isolation.py +407 -0
  125. code_puppy/mcp_/examples/retry_example.py +226 -0
  126. code_puppy/mcp_/health_monitor.py +589 -0
  127. code_puppy/mcp_/managed_server.py +428 -0
  128. code_puppy/mcp_/manager.py +807 -0
  129. code_puppy/mcp_/mcp_logs.py +224 -0
  130. code_puppy/mcp_/registry.py +451 -0
  131. code_puppy/mcp_/retry_manager.py +337 -0
  132. code_puppy/mcp_/server_registry_catalog.py +1126 -0
  133. code_puppy/mcp_/status_tracker.py +355 -0
  134. code_puppy/mcp_/system_tools.py +209 -0
  135. code_puppy/mcp_prompts/__init__.py +1 -0
  136. code_puppy/mcp_prompts/hook_creator.py +103 -0
  137. code_puppy/messaging/__init__.py +255 -0
  138. code_puppy/messaging/bus.py +613 -0
  139. code_puppy/messaging/commands.py +167 -0
  140. code_puppy/messaging/markdown_patches.py +57 -0
  141. code_puppy/messaging/message_queue.py +361 -0
  142. code_puppy/messaging/messages.py +569 -0
  143. code_puppy/messaging/queue_console.py +271 -0
  144. code_puppy/messaging/renderers.py +311 -0
  145. code_puppy/messaging/rich_renderer.py +1158 -0
  146. code_puppy/messaging/spinner/__init__.py +83 -0
  147. code_puppy/messaging/spinner/console_spinner.py +240 -0
  148. code_puppy/messaging/spinner/spinner_base.py +95 -0
  149. code_puppy/messaging/subagent_console.py +460 -0
  150. code_puppy/model_factory.py +848 -0
  151. code_puppy/model_switching.py +63 -0
  152. code_puppy/model_utils.py +168 -0
  153. code_puppy/models.json +174 -0
  154. code_puppy/models_dev_api.json +1 -0
  155. code_puppy/models_dev_parser.py +592 -0
  156. code_puppy/plugins/__init__.py +186 -0
  157. code_puppy/plugins/agent_skills/__init__.py +22 -0
  158. code_puppy/plugins/agent_skills/config.py +175 -0
  159. code_puppy/plugins/agent_skills/discovery.py +136 -0
  160. code_puppy/plugins/agent_skills/downloader.py +392 -0
  161. code_puppy/plugins/agent_skills/installer.py +22 -0
  162. code_puppy/plugins/agent_skills/metadata.py +219 -0
  163. code_puppy/plugins/agent_skills/prompt_builder.py +60 -0
  164. code_puppy/plugins/agent_skills/register_callbacks.py +241 -0
  165. code_puppy/plugins/agent_skills/remote_catalog.py +322 -0
  166. code_puppy/plugins/agent_skills/skill_catalog.py +257 -0
  167. code_puppy/plugins/agent_skills/skills_install_menu.py +664 -0
  168. code_puppy/plugins/agent_skills/skills_menu.py +781 -0
  169. code_puppy/plugins/antigravity_oauth/__init__.py +10 -0
  170. code_puppy/plugins/antigravity_oauth/accounts.py +406 -0
  171. code_puppy/plugins/antigravity_oauth/antigravity_model.py +706 -0
  172. code_puppy/plugins/antigravity_oauth/config.py +42 -0
  173. code_puppy/plugins/antigravity_oauth/constants.py +133 -0
  174. code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
  175. code_puppy/plugins/antigravity_oauth/register_callbacks.py +518 -0
  176. code_puppy/plugins/antigravity_oauth/storage.py +288 -0
  177. code_puppy/plugins/antigravity_oauth/test_plugin.py +319 -0
  178. code_puppy/plugins/antigravity_oauth/token.py +167 -0
  179. code_puppy/plugins/antigravity_oauth/transport.py +863 -0
  180. code_puppy/plugins/antigravity_oauth/utils.py +168 -0
  181. code_puppy/plugins/chatgpt_oauth/__init__.py +8 -0
  182. code_puppy/plugins/chatgpt_oauth/config.py +52 -0
  183. code_puppy/plugins/chatgpt_oauth/oauth_flow.py +329 -0
  184. code_puppy/plugins/chatgpt_oauth/register_callbacks.py +176 -0
  185. code_puppy/plugins/chatgpt_oauth/test_plugin.py +301 -0
  186. code_puppy/plugins/chatgpt_oauth/utils.py +523 -0
  187. code_puppy/plugins/claude_code_hooks/__init__.py +1 -0
  188. code_puppy/plugins/claude_code_hooks/config.py +137 -0
  189. code_puppy/plugins/claude_code_hooks/register_callbacks.py +175 -0
  190. code_puppy/plugins/claude_code_oauth/README.md +167 -0
  191. code_puppy/plugins/claude_code_oauth/SETUP.md +93 -0
  192. code_puppy/plugins/claude_code_oauth/__init__.py +25 -0
  193. code_puppy/plugins/claude_code_oauth/config.py +52 -0
  194. code_puppy/plugins/claude_code_oauth/register_callbacks.py +453 -0
  195. code_puppy/plugins/claude_code_oauth/test_plugin.py +283 -0
  196. code_puppy/plugins/claude_code_oauth/token_refresh_heartbeat.py +241 -0
  197. code_puppy/plugins/claude_code_oauth/utils.py +640 -0
  198. code_puppy/plugins/customizable_commands/__init__.py +0 -0
  199. code_puppy/plugins/customizable_commands/register_callbacks.py +152 -0
  200. code_puppy/plugins/example_custom_command/README.md +280 -0
  201. code_puppy/plugins/example_custom_command/register_callbacks.py +51 -0
  202. code_puppy/plugins/file_permission_handler/__init__.py +4 -0
  203. code_puppy/plugins/file_permission_handler/register_callbacks.py +470 -0
  204. code_puppy/plugins/frontend_emitter/__init__.py +25 -0
  205. code_puppy/plugins/frontend_emitter/emitter.py +121 -0
  206. code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
  207. code_puppy/plugins/hook_creator/__init__.py +1 -0
  208. code_puppy/plugins/hook_creator/register_callbacks.py +33 -0
  209. code_puppy/plugins/hook_manager/__init__.py +1 -0
  210. code_puppy/plugins/hook_manager/config.py +290 -0
  211. code_puppy/plugins/hook_manager/hooks_menu.py +564 -0
  212. code_puppy/plugins/hook_manager/register_callbacks.py +227 -0
  213. code_puppy/plugins/oauth_puppy_html.py +228 -0
  214. code_puppy/plugins/scheduler/__init__.py +1 -0
  215. code_puppy/plugins/scheduler/register_callbacks.py +88 -0
  216. code_puppy/plugins/scheduler/scheduler_menu.py +522 -0
  217. code_puppy/plugins/scheduler/scheduler_wizard.py +341 -0
  218. code_puppy/plugins/shell_safety/__init__.py +6 -0
  219. code_puppy/plugins/shell_safety/agent_shell_safety.py +69 -0
  220. code_puppy/plugins/shell_safety/command_cache.py +156 -0
  221. code_puppy/plugins/shell_safety/register_callbacks.py +202 -0
  222. code_puppy/plugins/synthetic_status/__init__.py +1 -0
  223. code_puppy/plugins/synthetic_status/register_callbacks.py +132 -0
  224. code_puppy/plugins/synthetic_status/status_api.py +147 -0
  225. code_puppy/plugins/universal_constructor/__init__.py +13 -0
  226. code_puppy/plugins/universal_constructor/models.py +138 -0
  227. code_puppy/plugins/universal_constructor/register_callbacks.py +47 -0
  228. code_puppy/plugins/universal_constructor/registry.py +302 -0
  229. code_puppy/plugins/universal_constructor/sandbox.py +584 -0
  230. code_puppy/prompts/antigravity_system_prompt.md +1 -0
  231. code_puppy/pydantic_patches.py +356 -0
  232. code_puppy/reopenable_async_client.py +232 -0
  233. code_puppy/round_robin_model.py +150 -0
  234. code_puppy/scheduler/__init__.py +41 -0
  235. code_puppy/scheduler/__main__.py +9 -0
  236. code_puppy/scheduler/cli.py +118 -0
  237. code_puppy/scheduler/config.py +126 -0
  238. code_puppy/scheduler/daemon.py +280 -0
  239. code_puppy/scheduler/executor.py +155 -0
  240. code_puppy/scheduler/platform.py +19 -0
  241. code_puppy/scheduler/platform_unix.py +22 -0
  242. code_puppy/scheduler/platform_win.py +32 -0
  243. code_puppy/session_storage.py +338 -0
  244. code_puppy/status_display.py +257 -0
  245. code_puppy/summarization_agent.py +176 -0
  246. code_puppy/terminal_utils.py +418 -0
  247. code_puppy/tools/__init__.py +501 -0
  248. code_puppy/tools/agent_tools.py +603 -0
  249. code_puppy/tools/ask_user_question/__init__.py +26 -0
  250. code_puppy/tools/ask_user_question/constants.py +73 -0
  251. code_puppy/tools/ask_user_question/demo_tui.py +55 -0
  252. code_puppy/tools/ask_user_question/handler.py +232 -0
  253. code_puppy/tools/ask_user_question/models.py +304 -0
  254. code_puppy/tools/ask_user_question/registration.py +26 -0
  255. code_puppy/tools/ask_user_question/renderers.py +309 -0
  256. code_puppy/tools/ask_user_question/terminal_ui.py +329 -0
  257. code_puppy/tools/ask_user_question/theme.py +155 -0
  258. code_puppy/tools/ask_user_question/tui_loop.py +423 -0
  259. code_puppy/tools/browser/__init__.py +37 -0
  260. code_puppy/tools/browser/browser_control.py +289 -0
  261. code_puppy/tools/browser/browser_interactions.py +545 -0
  262. code_puppy/tools/browser/browser_locators.py +640 -0
  263. code_puppy/tools/browser/browser_manager.py +378 -0
  264. code_puppy/tools/browser/browser_navigation.py +251 -0
  265. code_puppy/tools/browser/browser_screenshot.py +179 -0
  266. code_puppy/tools/browser/browser_scripts.py +462 -0
  267. code_puppy/tools/browser/browser_workflows.py +221 -0
  268. code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
  269. code_puppy/tools/browser/terminal_command_tools.py +534 -0
  270. code_puppy/tools/browser/terminal_screenshot_tools.py +552 -0
  271. code_puppy/tools/browser/terminal_tools.py +525 -0
  272. code_puppy/tools/command_runner.py +1346 -0
  273. code_puppy/tools/common.py +1409 -0
  274. code_puppy/tools/display.py +84 -0
  275. code_puppy/tools/file_modifications.py +886 -0
  276. code_puppy/tools/file_operations.py +802 -0
  277. code_puppy/tools/scheduler_tools.py +412 -0
  278. code_puppy/tools/skills_tools.py +244 -0
  279. code_puppy/tools/subagent_context.py +158 -0
  280. code_puppy/tools/tools_content.py +51 -0
  281. code_puppy/tools/universal_constructor.py +889 -0
  282. code_puppy/uvx_detection.py +242 -0
  283. code_puppy/version_checker.py +82 -0
  284. codepp-0.0.437.dist-info/METADATA +766 -0
  285. codepp-0.0.437.dist-info/RECORD +288 -0
  286. codepp-0.0.437.dist-info/WHEEL +4 -0
  287. codepp-0.0.437.dist-info/entry_points.txt +3 -0
  288. codepp-0.0.437.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,271 @@
1
+ """
2
+ Queue-based console that mimics Rich Console but sends messages to a queue.
3
+
4
+ This allows tools to use the same Rich console interface while having
5
+ their output captured and routed through our message queue system.
6
+ """
7
+
8
+ import traceback
9
+ from typing import Any, Optional
10
+
11
+ from rich.console import Console
12
+ from rich.markdown import Markdown
13
+ from rich.table import Table
14
+ from rich.text import Text
15
+
16
+ from .message_queue import MessageQueue, MessageType, get_global_queue
17
+
18
+
19
+ class QueueConsole:
20
+ """
21
+ Console-like interface that sends messages to a queue instead of stdout.
22
+
23
+ This is designed to be a drop-in replacement for Rich Console that
24
+ routes messages through our queue system.
25
+ """
26
+
27
+ def __init__(
28
+ self,
29
+ queue: Optional[MessageQueue] = None,
30
+ fallback_console: Optional[Console] = None,
31
+ ):
32
+ self.queue = queue or get_global_queue()
33
+ self.fallback_console = fallback_console or Console()
34
+
35
+ def print(
36
+ self,
37
+ *values: Any,
38
+ sep: str = " ",
39
+ end: str = "\n",
40
+ style: Optional[str] = None,
41
+ highlight: bool = True,
42
+ **kwargs,
43
+ ):
44
+ """Print values to the message queue."""
45
+ # Handle Rich objects properly
46
+ if len(values) == 1 and hasattr(values[0], "__rich_console__"):
47
+ # Single Rich object - pass it through directly
48
+ content = values[0]
49
+ message_type = self._infer_message_type_from_rich_object(content, style)
50
+ else:
51
+ # Convert to string, but handle Rich objects properly
52
+ processed_values = []
53
+ for v in values:
54
+ if hasattr(v, "__rich_console__"):
55
+ # For Rich objects, try to extract their text content
56
+ from io import StringIO
57
+
58
+ from rich.console import Console
59
+
60
+ string_io = StringIO()
61
+ # Use markup=True to properly process rich styling
62
+ # Use a reasonable width to prevent wrapping issues
63
+ temp_console = Console(
64
+ file=string_io, width=80, legacy_windows=False, markup=True
65
+ )
66
+ temp_console.print(v)
67
+ processed_values.append(string_io.getvalue().rstrip("\n"))
68
+ else:
69
+ processed_values.append(str(v))
70
+
71
+ content = sep.join(processed_values) + end
72
+ message_type = self._infer_message_type(content, style)
73
+
74
+ # Create Rich Text object if style is provided and content is string
75
+ if style and isinstance(content, str):
76
+ content = Text(content, style=style)
77
+
78
+ # Emit to queue
79
+ self.queue.emit_simple(
80
+ message_type, content, style=style, highlight=highlight, **kwargs
81
+ )
82
+
83
+ def print_exception(
84
+ self,
85
+ *,
86
+ width: Optional[int] = None,
87
+ extra_lines: int = 3,
88
+ theme: Optional[str] = None,
89
+ word_wrap: bool = False,
90
+ show_locals: bool = False,
91
+ indent_guides: bool = True,
92
+ suppress: tuple = (),
93
+ max_frames: int = 100,
94
+ ):
95
+ """Print exception information to the queue."""
96
+ # Get the exception traceback
97
+ exc_text = traceback.format_exc()
98
+
99
+ # Emit as error message
100
+ self.queue.emit_simple(
101
+ MessageType.ERROR,
102
+ f"Exception:\n{exc_text}",
103
+ exception=True,
104
+ show_locals=show_locals,
105
+ )
106
+
107
+ def log(
108
+ self,
109
+ *values: Any,
110
+ sep: str = " ",
111
+ end: str = "\n",
112
+ style: Optional[str] = None,
113
+ justify: Optional[str] = None,
114
+ emoji: Optional[bool] = None,
115
+ markup: Optional[bool] = None,
116
+ highlight: Optional[bool] = None,
117
+ log_locals: bool = False,
118
+ ):
119
+ """Log a message (similar to print but with logging semantics)."""
120
+ content = sep.join(str(v) for v in values) + end
121
+
122
+ # Log messages are typically informational
123
+ message_type = MessageType.INFO
124
+ if style:
125
+ message_type = self._infer_message_type(content, style)
126
+
127
+ if style and isinstance(content, str):
128
+ content = Text(content, style=style)
129
+
130
+ self.queue.emit_simple(
131
+ message_type, content, log=True, style=style, log_locals=log_locals
132
+ )
133
+
134
+ def _infer_message_type_from_rich_object(
135
+ self, content: Any, style: Optional[str] = None
136
+ ) -> MessageType:
137
+ """Infer message type from Rich object type and style."""
138
+ if style:
139
+ style_lower = style.lower()
140
+ if "red" in style_lower or "error" in style_lower:
141
+ return MessageType.ERROR
142
+ elif "yellow" in style_lower or "warning" in style_lower:
143
+ return MessageType.WARNING
144
+ elif "green" in style_lower or "success" in style_lower:
145
+ return MessageType.SUCCESS
146
+ elif "blue" in style_lower:
147
+ return MessageType.INFO
148
+ elif "purple" in style_lower or "magenta" in style_lower:
149
+ return MessageType.AGENT_REASONING
150
+ elif "dim" in style_lower:
151
+ return MessageType.SYSTEM
152
+
153
+ # Infer from object type
154
+ if isinstance(content, Markdown):
155
+ return MessageType.AGENT_REASONING
156
+ elif isinstance(content, Table):
157
+ return MessageType.TOOL_OUTPUT
158
+ elif hasattr(content, "lexer_name"): # Syntax object
159
+ return MessageType.TOOL_OUTPUT
160
+
161
+ return MessageType.INFO
162
+
163
+ def _infer_message_type(
164
+ self, content: str, style: Optional[str] = None
165
+ ) -> MessageType:
166
+ """Infer message type from content and style."""
167
+ if style:
168
+ style_lower = style.lower()
169
+ if "red" in style_lower or "error" in style_lower:
170
+ return MessageType.ERROR
171
+ elif "yellow" in style_lower or "warning" in style_lower:
172
+ return MessageType.WARNING
173
+ elif "green" in style_lower or "success" in style_lower:
174
+ return MessageType.SUCCESS
175
+ elif "blue" in style_lower:
176
+ return MessageType.INFO
177
+ elif "purple" in style_lower or "magenta" in style_lower:
178
+ return MessageType.AGENT_REASONING
179
+ elif "dim" in style_lower:
180
+ return MessageType.SYSTEM
181
+
182
+ # Infer from content patterns
183
+ content_lower = content.lower()
184
+ if any(word in content_lower for word in ["error", "failed", "exception"]):
185
+ return MessageType.ERROR
186
+ elif any(word in content_lower for word in ["warning", "warn"]):
187
+ return MessageType.WARNING
188
+ elif any(word in content_lower for word in ["success", "completed", "done"]):
189
+ return MessageType.SUCCESS
190
+ elif any(word in content_lower for word in ["tool", "command", "running"]):
191
+ return MessageType.TOOL_OUTPUT
192
+
193
+ return MessageType.INFO
194
+
195
+ # Additional methods to maintain Rich Console compatibility
196
+ def rule(self, title: str = "", *, align: str = "center", style: str = "rule.line"):
197
+ """Print a horizontal rule."""
198
+ self.queue.emit_simple(
199
+ MessageType.SYSTEM,
200
+ f"─── {title} ───" if title else "─" * 40,
201
+ rule=True,
202
+ style=style,
203
+ )
204
+
205
+ def status(self, status: str, *, spinner: str = "dots"):
206
+ """Show a status message (simplified)."""
207
+ self.queue.emit_simple(
208
+ MessageType.INFO, f"⏳ {status}", status=True, spinner=spinner
209
+ )
210
+
211
+ def input(self, prompt: str = "") -> str:
212
+ """Get user input without spinner interference.
213
+
214
+ This method coordinates with the TUI to pause any running spinners
215
+ and properly display the user input prompt.
216
+ """
217
+ # Set the global flag that we're awaiting user input
218
+ from code_puppy.tools.command_runner import set_awaiting_user_input
219
+
220
+ set_awaiting_user_input(True)
221
+
222
+ # Emit the prompt as a system message so it shows in the TUI chat
223
+ if prompt:
224
+ self.queue.emit_simple(MessageType.SYSTEM, prompt, requires_user_input=True)
225
+
226
+ # Create a new, isolated console instance specifically for input
227
+ # This bypasses any spinner or queue system interference
228
+ input_console = Console(file=__import__("sys").stderr, force_terminal=True)
229
+
230
+ # Clear any spinner artifacts and position cursor properly
231
+ if prompt:
232
+ input_console.print(prompt, end="", style="bold cyan")
233
+
234
+ # Use regular input() which will read from stdin
235
+ # Since we printed the prompt to stderr, this should work cleanly
236
+ try:
237
+ user_response = input()
238
+
239
+ # Show the user's response in the chat as well
240
+ if user_response:
241
+ self.queue.emit_simple(
242
+ MessageType.INFO, f"User response: {user_response}"
243
+ )
244
+
245
+ return user_response
246
+ except (KeyboardInterrupt, EOFError):
247
+ # Handle interruption gracefully
248
+ input_console.print("\n[yellow]Input cancelled[/yellow]")
249
+ self.queue.emit_simple(MessageType.WARNING, "User input cancelled")
250
+ return ""
251
+ finally:
252
+ # Clear the global flag for awaiting user input
253
+ from code_puppy.tools.command_runner import set_awaiting_user_input
254
+
255
+ set_awaiting_user_input(False)
256
+
257
+ # File-like interface for compatibility
258
+ @property
259
+ def file(self):
260
+ """Get the current file (for compatibility)."""
261
+ return self.fallback_console.file
262
+
263
+ @file.setter
264
+ def file(self, value):
265
+ """Set the current file (for compatibility)."""
266
+ self.fallback_console.file = value
267
+
268
+
269
+ def get_queue_console(queue: Optional[MessageQueue] = None) -> QueueConsole:
270
+ """Get a QueueConsole instance."""
271
+ return QueueConsole(queue or get_global_queue())
@@ -0,0 +1,311 @@
1
+ """
2
+ Renderer implementations for different UI modes.
3
+
4
+ These renderers consume messages from the queue and display them
5
+ appropriately for their respective interfaces.
6
+ """
7
+
8
+ import asyncio
9
+ import threading
10
+ from abc import ABC, abstractmethod
11
+ from typing import Optional
12
+
13
+ from rich.console import Console
14
+ from rich.markdown import Markdown
15
+ from rich.markup import escape as escape_rich_markup
16
+
17
+ from .message_queue import MessageQueue, MessageType, UIMessage
18
+
19
+
20
+ class MessageRenderer(ABC):
21
+ """Base class for message renderers."""
22
+
23
+ def __init__(self, queue: MessageQueue):
24
+ self.queue = queue
25
+ self._running = False
26
+ self._task = None
27
+
28
+ @abstractmethod
29
+ async def render_message(self, message: UIMessage):
30
+ """Render a single message."""
31
+ pass
32
+
33
+ async def start(self):
34
+ """Start the renderer."""
35
+ if self._running:
36
+ return
37
+
38
+ self._running = True
39
+ # Mark the queue as having an active renderer
40
+ self.queue.mark_renderer_active()
41
+ self._task = asyncio.create_task(self._consume_messages())
42
+
43
+ async def stop(self):
44
+ """Stop the renderer."""
45
+ self._running = False
46
+ # Mark the queue as having no active renderer
47
+ self.queue.mark_renderer_inactive()
48
+ if self._task:
49
+ self._task.cancel()
50
+ try:
51
+ await self._task
52
+ except asyncio.CancelledError:
53
+ pass
54
+
55
+ async def _consume_messages(self):
56
+ """Consume messages from the queue."""
57
+ while self._running:
58
+ try:
59
+ message = await asyncio.wait_for(self.queue.get_async(), timeout=0.1)
60
+ await self.render_message(message)
61
+ except asyncio.TimeoutError:
62
+ continue
63
+ except asyncio.CancelledError:
64
+ break
65
+ except Exception as e:
66
+ # Log error but continue processing
67
+ # Note: Using sys.stderr - can't use messaging in renderer
68
+ import sys
69
+
70
+ sys.stderr.write(f"Error rendering message: {e}\n")
71
+
72
+
73
+ class InteractiveRenderer(MessageRenderer):
74
+ """Renderer for interactive CLI mode using Rich console.
75
+
76
+ Note: This async-based renderer is not currently used in the codebase.
77
+ Interactive mode currently uses SynchronousInteractiveRenderer instead.
78
+ A future refactoring might consolidate these renderers.
79
+ """
80
+
81
+ def __init__(self, queue: MessageQueue, console: Optional[Console] = None):
82
+ super().__init__(queue)
83
+ self.console = console or Console()
84
+
85
+ async def render_message(self, message: UIMessage):
86
+ """Render a message using Rich console."""
87
+ # Handle human input requests
88
+ if message.type == MessageType.HUMAN_INPUT_REQUEST:
89
+ await self._handle_human_input_request(message)
90
+ return
91
+
92
+ # Convert message type to appropriate Rich styling
93
+ if message.type == MessageType.ERROR:
94
+ style = "bold red"
95
+ elif message.type == MessageType.WARNING:
96
+ style = "yellow"
97
+ elif message.type == MessageType.SUCCESS:
98
+ style = "green"
99
+ elif message.type == MessageType.TOOL_OUTPUT:
100
+ style = "blue"
101
+ elif message.type == MessageType.AGENT_REASONING:
102
+ style = None
103
+ elif message.type == MessageType.PLANNED_NEXT_STEPS:
104
+ style = None
105
+ elif message.type == MessageType.AGENT_RESPONSE:
106
+ # Special handling for agent responses - they'll be rendered as markdown
107
+ style = None
108
+ elif message.type == MessageType.SYSTEM:
109
+ style = "dim"
110
+ else:
111
+ style = None
112
+
113
+ # Make version messages dim regardless of message type
114
+ if isinstance(message.content, str):
115
+ if (
116
+ "Current version:" in message.content
117
+ or "Latest version:" in message.content
118
+ ):
119
+ style = "dim"
120
+
121
+ # Render the content
122
+ if isinstance(message.content, str):
123
+ if message.type == MessageType.AGENT_RESPONSE:
124
+ # Render agent responses as markdown
125
+ try:
126
+ markdown = Markdown(message.content)
127
+ self.console.print(markdown)
128
+ except Exception:
129
+ # Fallback to plain text if markdown parsing fails
130
+ safe_content = escape_rich_markup(message.content)
131
+ self.console.print(safe_content)
132
+ elif style:
133
+ # Escape Rich markup to prevent crashes from malformed tags
134
+ safe_content = escape_rich_markup(message.content)
135
+ self.console.print(safe_content, style=style)
136
+ else:
137
+ safe_content = escape_rich_markup(message.content)
138
+ self.console.print(safe_content)
139
+ else:
140
+ # For complex Rich objects (Tables, Markdown, Text, etc.)
141
+ self.console.print(message.content)
142
+
143
+ # Ensure output is immediately flushed to the terminal
144
+ # This fixes the issue where messages don't appear until user input
145
+ if hasattr(self.console.file, "flush"):
146
+ self.console.file.flush()
147
+
148
+ async def _handle_human_input_request(self, message: UIMessage):
149
+ """Handle a human input request in async mode."""
150
+ # This renderer is not currently used in practice, but if it were:
151
+ # We would need async input handling here
152
+ # For now, just render as a system message
153
+ safe_content = escape_rich_markup(str(message.content))
154
+ self.console.print(f"[bold cyan]INPUT REQUESTED:[/bold cyan] {safe_content}")
155
+ if hasattr(self.console.file, "flush"):
156
+ self.console.file.flush()
157
+
158
+
159
+ class SynchronousInteractiveRenderer:
160
+ """
161
+ Synchronous renderer for interactive mode that doesn't require async.
162
+
163
+ This is useful for cases where we want immediate rendering without
164
+ the overhead of async message processing.
165
+
166
+ Note: As part of the messaging system refactoring, we're keeping this class for now
167
+ as it's essential for the interactive mode to function properly. Future refactoring
168
+ could replace this with a simpler implementation that leverages the unified message
169
+ queue system more effectively, or potentially convert interactive mode to use
170
+ async/await consistently and use InteractiveRenderer instead.
171
+
172
+ Current responsibilities:
173
+ - Consumes messages from the queue in a background thread
174
+ - Renders messages to the console in real-time without requiring async code
175
+ - Registers as a direct listener to the message queue for immediate processing
176
+ """
177
+
178
+ def __init__(self, queue: MessageQueue, console: Optional[Console] = None):
179
+ self.queue = queue
180
+ self.console = console or Console()
181
+ self._running = False
182
+ self._thread = None
183
+
184
+ def start(self):
185
+ """Start the synchronous renderer in a background thread."""
186
+ if self._running:
187
+ return
188
+
189
+ self._running = True
190
+ # Mark the queue as having an active renderer
191
+ self.queue.mark_renderer_active()
192
+ # Add ourselves as a listener for immediate processing
193
+ self.queue.add_listener(self._render_message)
194
+ self._thread = threading.Thread(target=self._consume_messages, daemon=True)
195
+ self._thread.start()
196
+
197
+ def stop(self):
198
+ """Stop the synchronous renderer."""
199
+ self._running = False
200
+ # Mark the queue as having no active renderer
201
+ self.queue.mark_renderer_inactive()
202
+ # Remove ourselves as a listener
203
+ self.queue.remove_listener(self._render_message)
204
+ if self._thread and self._thread.is_alive():
205
+ self._thread.join(timeout=1.0)
206
+
207
+ def _consume_messages(self):
208
+ """Consume messages synchronously."""
209
+ while self._running:
210
+ message = self.queue.get_nowait()
211
+ if message:
212
+ self._render_message(message)
213
+ else:
214
+ # No messages, sleep briefly
215
+ import time
216
+
217
+ time.sleep(0.01)
218
+
219
+ def _render_message(self, message: UIMessage):
220
+ """Render a message using Rich console."""
221
+ # Handle human input requests
222
+ if message.type == MessageType.HUMAN_INPUT_REQUEST:
223
+ self._handle_human_input_request(message)
224
+ return
225
+
226
+ # Convert message type to appropriate Rich styling
227
+ if message.type == MessageType.ERROR:
228
+ style = "bold red"
229
+ elif message.type == MessageType.WARNING:
230
+ style = "yellow"
231
+ elif message.type == MessageType.SUCCESS:
232
+ style = "green"
233
+ elif message.type == MessageType.TOOL_OUTPUT:
234
+ style = "blue"
235
+ elif message.type == MessageType.AGENT_REASONING:
236
+ style = None
237
+ elif message.type == MessageType.AGENT_RESPONSE:
238
+ # Special handling for agent responses - they'll be rendered as markdown
239
+ style = None
240
+ elif message.type == MessageType.SYSTEM:
241
+ style = "dim"
242
+ else:
243
+ style = None
244
+
245
+ # Make version messages dim regardless of message type
246
+ if isinstance(message.content, str):
247
+ if (
248
+ "Current version:" in message.content
249
+ or "Latest version:" in message.content
250
+ ):
251
+ style = "dim"
252
+
253
+ # Render the content
254
+ if isinstance(message.content, str):
255
+ if message.type == MessageType.AGENT_RESPONSE:
256
+ # Render agent responses as markdown
257
+ try:
258
+ markdown = Markdown(message.content)
259
+ self.console.print(markdown)
260
+ except Exception:
261
+ # Fallback to plain text if markdown parsing fails
262
+ safe_content = escape_rich_markup(message.content)
263
+ self.console.print(safe_content)
264
+ elif style:
265
+ # Escape Rich markup to prevent crashes from malformed tags
266
+ # in shell output or other user-provided content
267
+ safe_content = escape_rich_markup(message.content)
268
+ self.console.print(safe_content, style=style)
269
+ else:
270
+ safe_content = escape_rich_markup(message.content)
271
+ self.console.print(safe_content)
272
+ else:
273
+ # For complex Rich objects (Tables, Markdown, Text, etc.)
274
+ self.console.print(message.content)
275
+
276
+ # Ensure output is immediately flushed to the terminal
277
+ # This fixes the issue where messages don't appear until user input
278
+ if hasattr(self.console.file, "flush"):
279
+ self.console.file.flush()
280
+
281
+ def _handle_human_input_request(self, message: UIMessage):
282
+ """Handle a human input request in interactive mode."""
283
+ prompt_id = message.metadata.get("prompt_id") if message.metadata else None
284
+ if not prompt_id:
285
+ self.console.print(
286
+ "[bold red]Error: Invalid human input request[/bold red]"
287
+ )
288
+ return
289
+
290
+ # Display the prompt - escape to prevent markup injection
291
+ safe_content = escape_rich_markup(str(message.content))
292
+ self.console.print(f"[bold cyan]{safe_content}[/bold cyan]")
293
+ if hasattr(self.console.file, "flush"):
294
+ self.console.file.flush()
295
+
296
+ # Get user input
297
+ try:
298
+ # Use basic input for now - could be enhanced with prompt_toolkit later
299
+ response = input(">>> ")
300
+
301
+ # Provide the response back to the queue
302
+ from .message_queue import provide_prompt_response
303
+
304
+ provide_prompt_response(prompt_id, response)
305
+
306
+ except (EOFError, KeyboardInterrupt):
307
+ # Handle Ctrl+C or Ctrl+D
308
+ provide_prompt_response(prompt_id, "")
309
+ except Exception as e:
310
+ self.console.print(f"[bold red]Error getting input: {e}[/bold red]")
311
+ provide_prompt_response(prompt_id, "")