code-puppy 0.0.169__py3-none-any.whl → 0.0.366__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 (243) hide show
  1. code_puppy/__init__.py +7 -1
  2. code_puppy/agents/__init__.py +8 -8
  3. code_puppy/agents/agent_c_reviewer.py +155 -0
  4. code_puppy/agents/agent_code_puppy.py +9 -2
  5. code_puppy/agents/agent_code_reviewer.py +90 -0
  6. code_puppy/agents/agent_cpp_reviewer.py +132 -0
  7. code_puppy/agents/agent_creator_agent.py +48 -9
  8. code_puppy/agents/agent_golang_reviewer.py +151 -0
  9. code_puppy/agents/agent_javascript_reviewer.py +160 -0
  10. code_puppy/agents/agent_manager.py +146 -199
  11. code_puppy/agents/agent_pack_leader.py +383 -0
  12. code_puppy/agents/agent_planning.py +163 -0
  13. code_puppy/agents/agent_python_programmer.py +165 -0
  14. code_puppy/agents/agent_python_reviewer.py +90 -0
  15. code_puppy/agents/agent_qa_expert.py +163 -0
  16. code_puppy/agents/agent_qa_kitten.py +208 -0
  17. code_puppy/agents/agent_security_auditor.py +181 -0
  18. code_puppy/agents/agent_terminal_qa.py +323 -0
  19. code_puppy/agents/agent_typescript_reviewer.py +166 -0
  20. code_puppy/agents/base_agent.py +1713 -1
  21. code_puppy/agents/event_stream_handler.py +350 -0
  22. code_puppy/agents/json_agent.py +12 -1
  23. code_puppy/agents/pack/__init__.py +34 -0
  24. code_puppy/agents/pack/bloodhound.py +304 -0
  25. code_puppy/agents/pack/husky.py +321 -0
  26. code_puppy/agents/pack/retriever.py +393 -0
  27. code_puppy/agents/pack/shepherd.py +348 -0
  28. code_puppy/agents/pack/terrier.py +287 -0
  29. code_puppy/agents/pack/watchdog.py +367 -0
  30. code_puppy/agents/prompt_reviewer.py +145 -0
  31. code_puppy/agents/subagent_stream_handler.py +276 -0
  32. code_puppy/api/__init__.py +13 -0
  33. code_puppy/api/app.py +169 -0
  34. code_puppy/api/main.py +21 -0
  35. code_puppy/api/pty_manager.py +446 -0
  36. code_puppy/api/routers/__init__.py +12 -0
  37. code_puppy/api/routers/agents.py +36 -0
  38. code_puppy/api/routers/commands.py +217 -0
  39. code_puppy/api/routers/config.py +74 -0
  40. code_puppy/api/routers/sessions.py +232 -0
  41. code_puppy/api/templates/terminal.html +361 -0
  42. code_puppy/api/websocket.py +154 -0
  43. code_puppy/callbacks.py +174 -4
  44. code_puppy/chatgpt_codex_client.py +283 -0
  45. code_puppy/claude_cache_client.py +586 -0
  46. code_puppy/cli_runner.py +916 -0
  47. code_puppy/command_line/add_model_menu.py +1079 -0
  48. code_puppy/command_line/agent_menu.py +395 -0
  49. code_puppy/command_line/attachments.py +395 -0
  50. code_puppy/command_line/autosave_menu.py +605 -0
  51. code_puppy/command_line/clipboard.py +527 -0
  52. code_puppy/command_line/colors_menu.py +520 -0
  53. code_puppy/command_line/command_handler.py +233 -627
  54. code_puppy/command_line/command_registry.py +150 -0
  55. code_puppy/command_line/config_commands.py +715 -0
  56. code_puppy/command_line/core_commands.py +792 -0
  57. code_puppy/command_line/diff_menu.py +863 -0
  58. code_puppy/command_line/load_context_completion.py +15 -22
  59. code_puppy/command_line/mcp/base.py +1 -4
  60. code_puppy/command_line/mcp/catalog_server_installer.py +175 -0
  61. code_puppy/command_line/mcp/custom_server_form.py +688 -0
  62. code_puppy/command_line/mcp/custom_server_installer.py +195 -0
  63. code_puppy/command_line/mcp/edit_command.py +148 -0
  64. code_puppy/command_line/mcp/handler.py +9 -4
  65. code_puppy/command_line/mcp/help_command.py +6 -5
  66. code_puppy/command_line/mcp/install_command.py +16 -27
  67. code_puppy/command_line/mcp/install_menu.py +685 -0
  68. code_puppy/command_line/mcp/list_command.py +3 -3
  69. code_puppy/command_line/mcp/logs_command.py +174 -65
  70. code_puppy/command_line/mcp/remove_command.py +2 -2
  71. code_puppy/command_line/mcp/restart_command.py +12 -4
  72. code_puppy/command_line/mcp/search_command.py +17 -11
  73. code_puppy/command_line/mcp/start_all_command.py +22 -13
  74. code_puppy/command_line/mcp/start_command.py +50 -31
  75. code_puppy/command_line/mcp/status_command.py +6 -7
  76. code_puppy/command_line/mcp/stop_all_command.py +11 -8
  77. code_puppy/command_line/mcp/stop_command.py +11 -10
  78. code_puppy/command_line/mcp/test_command.py +2 -2
  79. code_puppy/command_line/mcp/utils.py +1 -1
  80. code_puppy/command_line/mcp/wizard_utils.py +22 -18
  81. code_puppy/command_line/mcp_completion.py +174 -0
  82. code_puppy/command_line/model_picker_completion.py +89 -30
  83. code_puppy/command_line/model_settings_menu.py +884 -0
  84. code_puppy/command_line/motd.py +14 -8
  85. code_puppy/command_line/onboarding_slides.py +179 -0
  86. code_puppy/command_line/onboarding_wizard.py +340 -0
  87. code_puppy/command_line/pin_command_completion.py +329 -0
  88. code_puppy/command_line/prompt_toolkit_completion.py +626 -75
  89. code_puppy/command_line/session_commands.py +296 -0
  90. code_puppy/command_line/utils.py +54 -0
  91. code_puppy/config.py +1181 -51
  92. code_puppy/error_logging.py +118 -0
  93. code_puppy/gemini_code_assist.py +385 -0
  94. code_puppy/gemini_model.py +602 -0
  95. code_puppy/http_utils.py +220 -104
  96. code_puppy/keymap.py +128 -0
  97. code_puppy/main.py +5 -594
  98. code_puppy/{mcp → mcp_}/__init__.py +17 -0
  99. code_puppy/{mcp → mcp_}/async_lifecycle.py +35 -4
  100. code_puppy/{mcp → mcp_}/blocking_startup.py +70 -43
  101. code_puppy/{mcp → mcp_}/captured_stdio_server.py +2 -2
  102. code_puppy/{mcp → mcp_}/config_wizard.py +5 -5
  103. code_puppy/{mcp → mcp_}/dashboard.py +15 -6
  104. code_puppy/{mcp → mcp_}/examples/retry_example.py +4 -1
  105. code_puppy/{mcp → mcp_}/managed_server.py +66 -39
  106. code_puppy/{mcp → mcp_}/manager.py +146 -52
  107. code_puppy/mcp_/mcp_logs.py +224 -0
  108. code_puppy/{mcp → mcp_}/registry.py +6 -6
  109. code_puppy/{mcp → mcp_}/server_registry_catalog.py +25 -8
  110. code_puppy/messaging/__init__.py +199 -2
  111. code_puppy/messaging/bus.py +610 -0
  112. code_puppy/messaging/commands.py +167 -0
  113. code_puppy/messaging/markdown_patches.py +57 -0
  114. code_puppy/messaging/message_queue.py +17 -48
  115. code_puppy/messaging/messages.py +500 -0
  116. code_puppy/messaging/queue_console.py +1 -24
  117. code_puppy/messaging/renderers.py +43 -146
  118. code_puppy/messaging/rich_renderer.py +1027 -0
  119. code_puppy/messaging/spinner/__init__.py +33 -5
  120. code_puppy/messaging/spinner/console_spinner.py +92 -52
  121. code_puppy/messaging/spinner/spinner_base.py +29 -0
  122. code_puppy/messaging/subagent_console.py +461 -0
  123. code_puppy/model_factory.py +686 -80
  124. code_puppy/model_utils.py +167 -0
  125. code_puppy/models.json +86 -104
  126. code_puppy/models_dev_api.json +1 -0
  127. code_puppy/models_dev_parser.py +592 -0
  128. code_puppy/plugins/__init__.py +164 -10
  129. code_puppy/plugins/antigravity_oauth/__init__.py +10 -0
  130. code_puppy/plugins/antigravity_oauth/accounts.py +406 -0
  131. code_puppy/plugins/antigravity_oauth/antigravity_model.py +704 -0
  132. code_puppy/plugins/antigravity_oauth/config.py +42 -0
  133. code_puppy/plugins/antigravity_oauth/constants.py +136 -0
  134. code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
  135. code_puppy/plugins/antigravity_oauth/register_callbacks.py +406 -0
  136. code_puppy/plugins/antigravity_oauth/storage.py +271 -0
  137. code_puppy/plugins/antigravity_oauth/test_plugin.py +319 -0
  138. code_puppy/plugins/antigravity_oauth/token.py +167 -0
  139. code_puppy/plugins/antigravity_oauth/transport.py +767 -0
  140. code_puppy/plugins/antigravity_oauth/utils.py +169 -0
  141. code_puppy/plugins/chatgpt_oauth/__init__.py +8 -0
  142. code_puppy/plugins/chatgpt_oauth/config.py +52 -0
  143. code_puppy/plugins/chatgpt_oauth/oauth_flow.py +328 -0
  144. code_puppy/plugins/chatgpt_oauth/register_callbacks.py +94 -0
  145. code_puppy/plugins/chatgpt_oauth/test_plugin.py +293 -0
  146. code_puppy/plugins/chatgpt_oauth/utils.py +489 -0
  147. code_puppy/plugins/claude_code_oauth/README.md +167 -0
  148. code_puppy/plugins/claude_code_oauth/SETUP.md +93 -0
  149. code_puppy/plugins/claude_code_oauth/__init__.py +6 -0
  150. code_puppy/plugins/claude_code_oauth/config.py +50 -0
  151. code_puppy/plugins/claude_code_oauth/register_callbacks.py +308 -0
  152. code_puppy/plugins/claude_code_oauth/test_plugin.py +283 -0
  153. code_puppy/plugins/claude_code_oauth/utils.py +518 -0
  154. code_puppy/plugins/customizable_commands/__init__.py +0 -0
  155. code_puppy/plugins/customizable_commands/register_callbacks.py +169 -0
  156. code_puppy/plugins/example_custom_command/README.md +280 -0
  157. code_puppy/plugins/example_custom_command/register_callbacks.py +51 -0
  158. code_puppy/plugins/file_permission_handler/__init__.py +4 -0
  159. code_puppy/plugins/file_permission_handler/register_callbacks.py +523 -0
  160. code_puppy/plugins/frontend_emitter/__init__.py +25 -0
  161. code_puppy/plugins/frontend_emitter/emitter.py +121 -0
  162. code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
  163. code_puppy/plugins/oauth_puppy_html.py +228 -0
  164. code_puppy/plugins/shell_safety/__init__.py +6 -0
  165. code_puppy/plugins/shell_safety/agent_shell_safety.py +69 -0
  166. code_puppy/plugins/shell_safety/command_cache.py +156 -0
  167. code_puppy/plugins/shell_safety/register_callbacks.py +202 -0
  168. code_puppy/prompts/antigravity_system_prompt.md +1 -0
  169. code_puppy/prompts/codex_system_prompt.md +310 -0
  170. code_puppy/pydantic_patches.py +131 -0
  171. code_puppy/reopenable_async_client.py +8 -8
  172. code_puppy/round_robin_model.py +10 -15
  173. code_puppy/session_storage.py +294 -0
  174. code_puppy/status_display.py +21 -4
  175. code_puppy/summarization_agent.py +52 -14
  176. code_puppy/terminal_utils.py +418 -0
  177. code_puppy/tools/__init__.py +139 -6
  178. code_puppy/tools/agent_tools.py +548 -49
  179. code_puppy/tools/browser/__init__.py +37 -0
  180. code_puppy/tools/browser/browser_control.py +289 -0
  181. code_puppy/tools/browser/browser_interactions.py +545 -0
  182. code_puppy/tools/browser/browser_locators.py +640 -0
  183. code_puppy/tools/browser/browser_manager.py +316 -0
  184. code_puppy/tools/browser/browser_navigation.py +251 -0
  185. code_puppy/tools/browser/browser_screenshot.py +179 -0
  186. code_puppy/tools/browser/browser_scripts.py +462 -0
  187. code_puppy/tools/browser/browser_workflows.py +221 -0
  188. code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
  189. code_puppy/tools/browser/terminal_command_tools.py +521 -0
  190. code_puppy/tools/browser/terminal_screenshot_tools.py +556 -0
  191. code_puppy/tools/browser/terminal_tools.py +525 -0
  192. code_puppy/tools/command_runner.py +941 -153
  193. code_puppy/tools/common.py +1146 -6
  194. code_puppy/tools/display.py +84 -0
  195. code_puppy/tools/file_modifications.py +288 -89
  196. code_puppy/tools/file_operations.py +352 -266
  197. code_puppy/tools/subagent_context.py +158 -0
  198. code_puppy/uvx_detection.py +242 -0
  199. code_puppy/version_checker.py +30 -11
  200. code_puppy-0.0.366.data/data/code_puppy/models.json +110 -0
  201. code_puppy-0.0.366.data/data/code_puppy/models_dev_api.json +1 -0
  202. {code_puppy-0.0.169.dist-info → code_puppy-0.0.366.dist-info}/METADATA +184 -67
  203. code_puppy-0.0.366.dist-info/RECORD +217 -0
  204. {code_puppy-0.0.169.dist-info → code_puppy-0.0.366.dist-info}/WHEEL +1 -1
  205. {code_puppy-0.0.169.dist-info → code_puppy-0.0.366.dist-info}/entry_points.txt +1 -0
  206. code_puppy/agent.py +0 -231
  207. code_puppy/agents/agent_orchestrator.json +0 -26
  208. code_puppy/agents/runtime_manager.py +0 -272
  209. code_puppy/command_line/mcp/add_command.py +0 -183
  210. code_puppy/command_line/meta_command_handler.py +0 -153
  211. code_puppy/message_history_processor.py +0 -490
  212. code_puppy/messaging/spinner/textual_spinner.py +0 -101
  213. code_puppy/state_management.py +0 -200
  214. code_puppy/tui/__init__.py +0 -10
  215. code_puppy/tui/app.py +0 -986
  216. code_puppy/tui/components/__init__.py +0 -21
  217. code_puppy/tui/components/chat_view.py +0 -550
  218. code_puppy/tui/components/command_history_modal.py +0 -218
  219. code_puppy/tui/components/copy_button.py +0 -139
  220. code_puppy/tui/components/custom_widgets.py +0 -63
  221. code_puppy/tui/components/human_input_modal.py +0 -175
  222. code_puppy/tui/components/input_area.py +0 -167
  223. code_puppy/tui/components/sidebar.py +0 -309
  224. code_puppy/tui/components/status_bar.py +0 -182
  225. code_puppy/tui/messages.py +0 -27
  226. code_puppy/tui/models/__init__.py +0 -8
  227. code_puppy/tui/models/chat_message.py +0 -25
  228. code_puppy/tui/models/command_history.py +0 -89
  229. code_puppy/tui/models/enums.py +0 -24
  230. code_puppy/tui/screens/__init__.py +0 -15
  231. code_puppy/tui/screens/help.py +0 -130
  232. code_puppy/tui/screens/mcp_install_wizard.py +0 -803
  233. code_puppy/tui/screens/settings.py +0 -290
  234. code_puppy/tui/screens/tools.py +0 -74
  235. code_puppy-0.0.169.data/data/code_puppy/models.json +0 -128
  236. code_puppy-0.0.169.dist-info/RECORD +0 -112
  237. /code_puppy/{mcp → mcp_}/circuit_breaker.py +0 -0
  238. /code_puppy/{mcp → mcp_}/error_isolation.py +0 -0
  239. /code_puppy/{mcp → mcp_}/health_monitor.py +0 -0
  240. /code_puppy/{mcp → mcp_}/retry_manager.py +0 -0
  241. /code_puppy/{mcp → mcp_}/status_tracker.py +0 -0
  242. /code_puppy/{mcp → mcp_}/system_tools.py +0 -0
  243. {code_puppy-0.0.169.dist-info → code_puppy-0.0.366.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,500 @@
1
+ """Structured message models for Code Puppy's messaging system.
2
+
3
+ Pydantic models that decouple message content from presentation.
4
+ NO Rich markup or formatting should be embedded in any string fields.
5
+ Renderers decide how to display these structured messages.
6
+ """
7
+
8
+ from datetime import datetime, timezone
9
+ from enum import Enum
10
+ from typing import Dict, List, Literal, Optional, Union
11
+ from uuid import uuid4
12
+
13
+ from pydantic import BaseModel, Field
14
+
15
+ # =============================================================================
16
+ # Enums
17
+ # =============================================================================
18
+
19
+
20
+ class MessageLevel(str, Enum):
21
+ """Severity level for text messages."""
22
+
23
+ DEBUG = "debug"
24
+ INFO = "info"
25
+ WARNING = "warning"
26
+ ERROR = "error"
27
+ SUCCESS = "success"
28
+
29
+
30
+ class MessageCategory(str, Enum):
31
+ """Category of message for routing and rendering decisions."""
32
+
33
+ SYSTEM = "system"
34
+ TOOL_OUTPUT = "tool_output"
35
+ AGENT = "agent"
36
+ USER_INTERACTION = "user_interaction"
37
+ DIVIDER = "divider"
38
+
39
+
40
+ # =============================================================================
41
+ # Base Message
42
+ # =============================================================================
43
+
44
+
45
+ class BaseMessage(BaseModel):
46
+ """Base class for all structured messages with auto-generated id and timestamp."""
47
+
48
+ id: str = Field(
49
+ default_factory=lambda: str(uuid4()),
50
+ description="Unique identifier for this message instance",
51
+ )
52
+ timestamp: datetime = Field(
53
+ default_factory=lambda: datetime.now(timezone.utc),
54
+ description="When this message was created (UTC)",
55
+ )
56
+ category: MessageCategory = Field(
57
+ description="Category for routing and rendering decisions"
58
+ )
59
+ session_id: Optional[str] = Field(
60
+ default=None,
61
+ description="Session ID of the agent that emitted this message (for multi-agent tracking)",
62
+ )
63
+
64
+ model_config = {"frozen": False, "extra": "forbid"}
65
+
66
+
67
+ # =============================================================================
68
+ # Text Messages
69
+ # =============================================================================
70
+
71
+
72
+ class TextMessage(BaseMessage):
73
+ """Simple text message with a severity level. Text must be plain, no markup!"""
74
+
75
+ category: MessageCategory = MessageCategory.SYSTEM
76
+ level: MessageLevel = Field(description="Severity level of this message")
77
+ text: str = Field(description="Plain text content - NO Rich markup allowed")
78
+
79
+
80
+ # =============================================================================
81
+ # File Operation Messages
82
+ # =============================================================================
83
+
84
+
85
+ class FileEntry(BaseModel):
86
+ """A single file or directory entry in a listing."""
87
+
88
+ path: str = Field(description="Path to the file or directory")
89
+ type: Literal["file", "dir"] = Field(
90
+ description="Whether this is a file or directory"
91
+ )
92
+ size: int = Field(ge=0, description="Size in bytes (0 for directories)")
93
+ depth: int = Field(ge=0, description="Nesting depth from listing root")
94
+
95
+ model_config = {"frozen": True, "extra": "forbid"}
96
+
97
+
98
+ class FileListingMessage(BaseMessage):
99
+ """Result of a directory listing operation."""
100
+
101
+ category: MessageCategory = MessageCategory.TOOL_OUTPUT
102
+ directory: str = Field(description="Root directory that was listed")
103
+ files: List[FileEntry] = Field(
104
+ default_factory=list,
105
+ description="List of file and directory entries found",
106
+ )
107
+ recursive: bool = Field(description="Whether the listing was recursive")
108
+ total_size: int = Field(ge=0, description="Total size of all files in bytes")
109
+ dir_count: int = Field(ge=0, description="Number of directories found")
110
+ file_count: int = Field(ge=0, description="Number of files found")
111
+
112
+
113
+ class FileContentMessage(BaseMessage):
114
+ """Content of a file that was read, with metadata for partial reads."""
115
+
116
+ category: MessageCategory = MessageCategory.TOOL_OUTPUT
117
+ path: str = Field(description="Path to the file that was read")
118
+ content: str = Field(description="The file content (plain text)")
119
+ start_line: Optional[int] = Field(
120
+ default=None,
121
+ ge=1,
122
+ description="Starting line number if partial read (1-based)",
123
+ )
124
+ num_lines: Optional[int] = Field(
125
+ default=None,
126
+ ge=1,
127
+ description="Number of lines read if partial read",
128
+ )
129
+ total_lines: int = Field(ge=0, description="Total lines in the file")
130
+ num_tokens: int = Field(ge=0, description="Estimated token count of content")
131
+
132
+
133
+ class GrepMatch(BaseModel):
134
+ """A single match from a grep/search operation."""
135
+
136
+ file_path: str = Field(description="Path to file containing this match")
137
+ line_number: int = Field(ge=1, description="Line number (1-based)")
138
+ line_content: str = Field(description="Full line content containing the match")
139
+
140
+ model_config = {"frozen": True, "extra": "forbid"}
141
+
142
+
143
+ class GrepResultMessage(BaseMessage):
144
+ """Results from a grep/search operation with matches and statistics."""
145
+
146
+ category: MessageCategory = MessageCategory.TOOL_OUTPUT
147
+ search_term: str = Field(description="The search pattern used")
148
+ directory: str = Field(description="Root directory that was searched")
149
+ matches: List[GrepMatch] = Field(
150
+ default_factory=list,
151
+ description="List of matches found",
152
+ )
153
+ total_matches: int = Field(ge=0, description="Total number of matches")
154
+ files_searched: int = Field(ge=0, description="Number of files searched")
155
+ verbose: bool = Field(
156
+ default=False,
157
+ description="Whether to show verbose output with line content",
158
+ )
159
+
160
+
161
+ # =============================================================================
162
+ # Diff/Modification Messages
163
+ # =============================================================================
164
+
165
+
166
+ class DiffLine(BaseModel):
167
+ """A single line in a diff output."""
168
+
169
+ line_number: int = Field(ge=0, description="Line number for this diff line")
170
+ type: Literal["add", "remove", "context"] = Field(description="Type of diff line")
171
+ content: str = Field(description="The line content")
172
+
173
+ model_config = {"frozen": True, "extra": "forbid"}
174
+
175
+
176
+ class DiffMessage(BaseMessage):
177
+ """A file modification with diff information for rendering."""
178
+
179
+ category: MessageCategory = MessageCategory.TOOL_OUTPUT
180
+ path: str = Field(description="Path to the modified file")
181
+ operation: Literal["create", "modify", "delete"] = Field(
182
+ description="Type of file operation"
183
+ )
184
+ old_content: Optional[str] = Field(
185
+ default=None,
186
+ description="Previous file content (None for create)",
187
+ )
188
+ new_content: Optional[str] = Field(
189
+ default=None,
190
+ description="New file content (None for delete)",
191
+ )
192
+ diff_lines: List[DiffLine] = Field(
193
+ default_factory=list,
194
+ description="Individual diff lines for rendering",
195
+ )
196
+
197
+
198
+ # =============================================================================
199
+ # Shell Messages
200
+ # =============================================================================
201
+
202
+
203
+ class ShellStartMessage(BaseMessage):
204
+ """Notification that a shell command has started execution."""
205
+
206
+ category: MessageCategory = MessageCategory.TOOL_OUTPUT
207
+ command: str = Field(description="The shell command being executed")
208
+ cwd: Optional[str] = Field(
209
+ default=None, description="Working directory for the command"
210
+ )
211
+ timeout: int = Field(default=60, description="Timeout in seconds")
212
+ background: bool = Field(
213
+ default=False, description="Whether command runs in background mode"
214
+ )
215
+
216
+
217
+ class ShellLineMessage(BaseMessage):
218
+ """A single line of shell command output with ANSI preservation."""
219
+
220
+ category: MessageCategory = MessageCategory.TOOL_OUTPUT
221
+ line: str = Field(description="The output line (may contain ANSI codes)")
222
+ stream: Literal["stdout", "stderr"] = Field(
223
+ default="stdout", description="Which output stream this line came from"
224
+ )
225
+
226
+
227
+ class ShellOutputMessage(BaseMessage):
228
+ """Output from a shell command execution with stdout, stderr, and timing."""
229
+
230
+ category: MessageCategory = MessageCategory.TOOL_OUTPUT
231
+ command: str = Field(description="The shell command that was executed")
232
+ stdout: str = Field(default="", description="Standard output from the command")
233
+ stderr: str = Field(default="", description="Standard error from the command")
234
+ exit_code: int = Field(description="Process exit code (0 = success)")
235
+ duration_seconds: float = Field(
236
+ ge=0,
237
+ description="How long the command took to execute",
238
+ )
239
+
240
+
241
+ # =============================================================================
242
+ # Agent Messages
243
+ # =============================================================================
244
+
245
+
246
+ class AgentReasoningMessage(BaseMessage):
247
+ """Agent's reasoning and planned next steps. Plain text only!"""
248
+
249
+ category: MessageCategory = MessageCategory.AGENT
250
+ reasoning: str = Field(description="The agent's current reasoning/thought process")
251
+ next_steps: Optional[str] = Field(
252
+ default=None,
253
+ description="Planned next actions (optional)",
254
+ )
255
+
256
+
257
+ class AgentResponseMessage(BaseMessage):
258
+ """A response from the agent. Use is_markdown flag for markdown content."""
259
+
260
+ category: MessageCategory = MessageCategory.AGENT
261
+ content: str = Field(description="The response content")
262
+ is_markdown: bool = Field(
263
+ default=False,
264
+ description="Whether content should be rendered as markdown",
265
+ )
266
+
267
+
268
+ class SubAgentInvocationMessage(BaseMessage):
269
+ """Message for sub-agent invocation header/status. Used by invoke_agent tool."""
270
+
271
+ category: MessageCategory = MessageCategory.AGENT
272
+ agent_name: str = Field(description="Name of the agent being invoked")
273
+ session_id: str = Field(description="Session ID for the invocation")
274
+ prompt: str = Field(description="The prompt being sent to the agent")
275
+ is_new_session: bool = Field(
276
+ description="Whether this is a new session or continuation"
277
+ )
278
+ message_count: int = Field(
279
+ default=0, description="Number of messages in history (for continuation)"
280
+ )
281
+
282
+
283
+ class SubAgentResponseMessage(BaseMessage):
284
+ """Response from a sub-agent invocation. Rendered as markdown."""
285
+
286
+ category: MessageCategory = MessageCategory.AGENT
287
+ agent_name: str = Field(description="Name of the agent that responded")
288
+ session_id: str = Field(description="Session ID for the invocation")
289
+ response: str = Field(description="The agent's response content")
290
+ message_count: int = Field(
291
+ default=0, description="Number of messages now in session history"
292
+ )
293
+
294
+
295
+ class SubAgentStatusMessage(BaseMessage):
296
+ """Real-time status update for a running sub-agent."""
297
+
298
+ category: MessageCategory = MessageCategory.AGENT
299
+ session_id: str = Field(description="Unique session ID of the sub-agent")
300
+ agent_name: str = Field(description="Name of the agent (e.g., 'code-puppy')")
301
+ model_name: str = Field(description="Model being used by this agent")
302
+ status: Literal[
303
+ "starting", "running", "thinking", "tool_calling", "completed", "error"
304
+ ] = Field(description="Current status of the agent")
305
+ tool_call_count: int = Field(
306
+ default=0, ge=0, description="Number of tools called so far"
307
+ )
308
+ token_count: int = Field(default=0, ge=0, description="Estimated tokens in context")
309
+ current_tool: Optional[str] = Field(
310
+ default=None, description="Name of tool currently being called"
311
+ )
312
+ elapsed_seconds: float = Field(
313
+ default=0.0, ge=0, description="Time since agent started"
314
+ )
315
+ error_message: Optional[str] = Field(
316
+ default=None, description="Error message if status is 'error'"
317
+ )
318
+
319
+
320
+ # =============================================================================
321
+ # User Interaction Messages (Agent → User)
322
+ # =============================================================================
323
+
324
+
325
+ class UserInputRequest(BaseMessage):
326
+ """Request for text input from the user."""
327
+
328
+ category: MessageCategory = MessageCategory.USER_INTERACTION
329
+ prompt_id: str = Field(description="Unique ID for matching responses to requests")
330
+ prompt_text: str = Field(description="The prompt to display to the user")
331
+ default_value: Optional[str] = Field(
332
+ default=None,
333
+ description="Default value to use if user provides no input",
334
+ )
335
+ input_type: Literal["text", "password"] = Field(
336
+ default="text",
337
+ description="Type of input field (password hides input)",
338
+ )
339
+
340
+
341
+ class ConfirmationRequest(BaseMessage):
342
+ """Request for user confirmation with options and optional feedback."""
343
+
344
+ category: MessageCategory = MessageCategory.USER_INTERACTION
345
+ prompt_id: str = Field(description="Unique ID for matching responses to requests")
346
+ title: str = Field(description="Title/headline for the confirmation")
347
+ description: str = Field(
348
+ description="Detailed description of what's being confirmed"
349
+ )
350
+ options: List[str] = Field(
351
+ default_factory=lambda: ["Yes", "No"],
352
+ description="Available options to choose from",
353
+ )
354
+ allow_feedback: bool = Field(
355
+ default=False,
356
+ description="Whether to allow free-form feedback in addition to selection",
357
+ )
358
+
359
+
360
+ class SelectionRequest(BaseMessage):
361
+ """Request for user to select from a list of options."""
362
+
363
+ category: MessageCategory = MessageCategory.USER_INTERACTION
364
+ prompt_id: str = Field(description="Unique ID for matching responses to requests")
365
+ prompt_text: str = Field(description="Prompt text to display")
366
+ options: List[str] = Field(description="List of options to choose from")
367
+ allow_cancel: bool = Field(
368
+ default=True,
369
+ description="Whether the user can cancel without selecting",
370
+ )
371
+
372
+
373
+ # =============================================================================
374
+ # Control Messages
375
+ # =============================================================================
376
+
377
+
378
+ class SpinnerControl(BaseMessage):
379
+ """Control message for spinner/progress indicator."""
380
+
381
+ category: MessageCategory = MessageCategory.SYSTEM
382
+ action: Literal["start", "stop", "update", "pause", "resume"] = Field(
383
+ description="What action to take on the spinner"
384
+ )
385
+ spinner_id: str = Field(description="Unique identifier for this spinner")
386
+ text: Optional[str] = Field(
387
+ default=None,
388
+ description="Text to display with the spinner (for start/update)",
389
+ )
390
+
391
+
392
+ class DividerMessage(BaseMessage):
393
+ """Visual divider/separator between sections."""
394
+
395
+ category: MessageCategory = MessageCategory.DIVIDER
396
+ style: Literal["light", "heavy", "double"] = Field(
397
+ default="light",
398
+ description="Visual style hint for the divider",
399
+ )
400
+
401
+
402
+ # =============================================================================
403
+ # Status Messages
404
+ # =============================================================================
405
+
406
+
407
+ class StatusPanelMessage(BaseMessage):
408
+ """A status panel with key-value fields for structured status info."""
409
+
410
+ category: MessageCategory = MessageCategory.SYSTEM
411
+ title: str = Field(description="Title for the status panel")
412
+ fields: Dict[str, str] = Field(
413
+ default_factory=dict,
414
+ description="Key-value pairs to display",
415
+ )
416
+
417
+
418
+ class VersionCheckMessage(BaseMessage):
419
+ """Result of a version check against PyPI or similar."""
420
+
421
+ category: MessageCategory = MessageCategory.SYSTEM
422
+ current_version: str = Field(description="Currently installed version")
423
+ latest_version: str = Field(description="Latest available version")
424
+ update_available: bool = Field(description="Whether an update is available")
425
+
426
+
427
+ # =============================================================================
428
+ # Union Type for Type Checking
429
+ # =============================================================================
430
+
431
+ # All concrete message types (excludes BaseMessage itself)
432
+ AnyMessage = Union[
433
+ TextMessage,
434
+ FileListingMessage,
435
+ FileContentMessage,
436
+ GrepResultMessage,
437
+ DiffMessage,
438
+ ShellStartMessage,
439
+ ShellLineMessage,
440
+ ShellOutputMessage,
441
+ AgentReasoningMessage,
442
+ AgentResponseMessage,
443
+ SubAgentInvocationMessage,
444
+ SubAgentResponseMessage,
445
+ SubAgentStatusMessage,
446
+ UserInputRequest,
447
+ ConfirmationRequest,
448
+ SelectionRequest,
449
+ SpinnerControl,
450
+ DividerMessage,
451
+ StatusPanelMessage,
452
+ VersionCheckMessage,
453
+ ]
454
+ """Union of all message types for type checking."""
455
+
456
+
457
+ # =============================================================================
458
+ # Export all public symbols
459
+ # =============================================================================
460
+
461
+ __all__ = [
462
+ # Enums
463
+ "MessageLevel",
464
+ "MessageCategory",
465
+ # Base
466
+ "BaseMessage",
467
+ # Text
468
+ "TextMessage",
469
+ # File operations
470
+ "FileEntry",
471
+ "FileListingMessage",
472
+ "FileContentMessage",
473
+ "GrepMatch",
474
+ "GrepResultMessage",
475
+ # Diff/Modification
476
+ "DiffLine",
477
+ "DiffMessage",
478
+ # Shell
479
+ "ShellStartMessage",
480
+ "ShellLineMessage",
481
+ "ShellOutputMessage",
482
+ # Agent
483
+ "AgentReasoningMessage",
484
+ "AgentResponseMessage",
485
+ "SubAgentInvocationMessage",
486
+ "SubAgentResponseMessage",
487
+ "SubAgentStatusMessage",
488
+ # User interaction
489
+ "UserInputRequest",
490
+ "ConfirmationRequest",
491
+ "SelectionRequest",
492
+ # Control
493
+ "SpinnerControl",
494
+ "DividerMessage",
495
+ # Status
496
+ "StatusPanelMessage",
497
+ "VersionCheckMessage",
498
+ # Union type
499
+ "AnyMessage",
500
+ ]
@@ -219,18 +219,6 @@ class QueueConsole:
219
219
 
220
220
  set_awaiting_user_input(True)
221
221
 
222
- # Signal TUI to pause spinner and prepare for user input (legacy method)
223
- try:
224
- # Try to get the current TUI app instance and pause spinner
225
- from textual.app import App
226
-
227
- current_app = App.get_running_app()
228
- if hasattr(current_app, "pause_spinner_for_input"):
229
- current_app.pause_spinner_for_input()
230
- except Exception:
231
- # If we can't pause the spinner (not in TUI mode), continue anyway
232
- pass
233
-
234
222
  # Emit the prompt as a system message so it shows in the TUI chat
235
223
  if prompt:
236
224
  self.queue.emit_simple(MessageType.SYSTEM, prompt, requires_user_input=True)
@@ -251,7 +239,7 @@ class QueueConsole:
251
239
  # Show the user's response in the chat as well
252
240
  if user_response:
253
241
  self.queue.emit_simple(
254
- MessageType.USER, f"User response: {user_response}"
242
+ MessageType.INFO, f"User response: {user_response}"
255
243
  )
256
244
 
257
245
  return user_response
@@ -266,17 +254,6 @@ class QueueConsole:
266
254
 
267
255
  set_awaiting_user_input(False)
268
256
 
269
- # Signal TUI to resume spinner if needed (legacy method)
270
- try:
271
- from textual.app import App
272
-
273
- current_app = App.get_running_app()
274
- if hasattr(current_app, "resume_spinner_after_input"):
275
- current_app.resume_spinner_after_input()
276
- except Exception:
277
- # If we can't resume the spinner, continue anyway
278
- pass
279
-
280
257
  # File-like interface for compatibility
281
258
  @property
282
259
  def file(self):