code-puppy 0.0.214__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.
- code_puppy/__init__.py +7 -1
- code_puppy/agents/__init__.py +2 -0
- code_puppy/agents/agent_c_reviewer.py +59 -6
- code_puppy/agents/agent_code_puppy.py +7 -1
- code_puppy/agents/agent_code_reviewer.py +12 -2
- code_puppy/agents/agent_cpp_reviewer.py +73 -6
- code_puppy/agents/agent_creator_agent.py +45 -4
- code_puppy/agents/agent_golang_reviewer.py +92 -3
- code_puppy/agents/agent_javascript_reviewer.py +101 -8
- code_puppy/agents/agent_manager.py +81 -4
- code_puppy/agents/agent_pack_leader.py +383 -0
- code_puppy/agents/agent_planning.py +163 -0
- code_puppy/agents/agent_python_programmer.py +165 -0
- code_puppy/agents/agent_python_reviewer.py +28 -6
- code_puppy/agents/agent_qa_expert.py +98 -6
- code_puppy/agents/agent_qa_kitten.py +12 -7
- code_puppy/agents/agent_security_auditor.py +113 -3
- code_puppy/agents/agent_terminal_qa.py +323 -0
- code_puppy/agents/agent_typescript_reviewer.py +106 -7
- code_puppy/agents/base_agent.py +802 -176
- code_puppy/agents/event_stream_handler.py +350 -0
- code_puppy/agents/pack/__init__.py +34 -0
- code_puppy/agents/pack/bloodhound.py +304 -0
- code_puppy/agents/pack/husky.py +321 -0
- code_puppy/agents/pack/retriever.py +393 -0
- code_puppy/agents/pack/shepherd.py +348 -0
- code_puppy/agents/pack/terrier.py +287 -0
- code_puppy/agents/pack/watchdog.py +367 -0
- code_puppy/agents/prompt_reviewer.py +145 -0
- code_puppy/agents/subagent_stream_handler.py +276 -0
- code_puppy/api/__init__.py +13 -0
- code_puppy/api/app.py +169 -0
- code_puppy/api/main.py +21 -0
- code_puppy/api/pty_manager.py +446 -0
- code_puppy/api/routers/__init__.py +12 -0
- code_puppy/api/routers/agents.py +36 -0
- code_puppy/api/routers/commands.py +217 -0
- code_puppy/api/routers/config.py +74 -0
- code_puppy/api/routers/sessions.py +232 -0
- code_puppy/api/templates/terminal.html +361 -0
- code_puppy/api/websocket.py +154 -0
- code_puppy/callbacks.py +142 -4
- code_puppy/chatgpt_codex_client.py +283 -0
- code_puppy/claude_cache_client.py +586 -0
- code_puppy/cli_runner.py +916 -0
- code_puppy/command_line/add_model_menu.py +1079 -0
- code_puppy/command_line/agent_menu.py +395 -0
- code_puppy/command_line/attachments.py +10 -5
- code_puppy/command_line/autosave_menu.py +605 -0
- code_puppy/command_line/clipboard.py +527 -0
- code_puppy/command_line/colors_menu.py +520 -0
- code_puppy/command_line/command_handler.py +176 -738
- code_puppy/command_line/command_registry.py +150 -0
- code_puppy/command_line/config_commands.py +715 -0
- code_puppy/command_line/core_commands.py +792 -0
- code_puppy/command_line/diff_menu.py +863 -0
- code_puppy/command_line/load_context_completion.py +15 -22
- code_puppy/command_line/mcp/base.py +0 -3
- code_puppy/command_line/mcp/catalog_server_installer.py +175 -0
- code_puppy/command_line/mcp/custom_server_form.py +688 -0
- code_puppy/command_line/mcp/custom_server_installer.py +195 -0
- code_puppy/command_line/mcp/edit_command.py +148 -0
- code_puppy/command_line/mcp/handler.py +9 -4
- code_puppy/command_line/mcp/help_command.py +6 -5
- code_puppy/command_line/mcp/install_command.py +15 -26
- code_puppy/command_line/mcp/install_menu.py +685 -0
- code_puppy/command_line/mcp/list_command.py +2 -2
- code_puppy/command_line/mcp/logs_command.py +174 -65
- code_puppy/command_line/mcp/remove_command.py +2 -2
- code_puppy/command_line/mcp/restart_command.py +12 -4
- code_puppy/command_line/mcp/search_command.py +16 -10
- code_puppy/command_line/mcp/start_all_command.py +18 -6
- code_puppy/command_line/mcp/start_command.py +47 -25
- code_puppy/command_line/mcp/status_command.py +4 -5
- code_puppy/command_line/mcp/stop_all_command.py +7 -1
- code_puppy/command_line/mcp/stop_command.py +8 -4
- code_puppy/command_line/mcp/test_command.py +2 -2
- code_puppy/command_line/mcp/wizard_utils.py +20 -16
- code_puppy/command_line/mcp_completion.py +174 -0
- code_puppy/command_line/model_picker_completion.py +75 -25
- code_puppy/command_line/model_settings_menu.py +884 -0
- code_puppy/command_line/motd.py +14 -8
- code_puppy/command_line/onboarding_slides.py +179 -0
- code_puppy/command_line/onboarding_wizard.py +340 -0
- code_puppy/command_line/pin_command_completion.py +329 -0
- code_puppy/command_line/prompt_toolkit_completion.py +463 -63
- code_puppy/command_line/session_commands.py +296 -0
- code_puppy/command_line/utils.py +54 -0
- code_puppy/config.py +898 -112
- code_puppy/error_logging.py +118 -0
- code_puppy/gemini_code_assist.py +385 -0
- code_puppy/gemini_model.py +602 -0
- code_puppy/http_utils.py +210 -148
- code_puppy/keymap.py +128 -0
- code_puppy/main.py +5 -698
- code_puppy/mcp_/__init__.py +17 -0
- code_puppy/mcp_/async_lifecycle.py +35 -4
- code_puppy/mcp_/blocking_startup.py +70 -43
- code_puppy/mcp_/captured_stdio_server.py +2 -2
- code_puppy/mcp_/config_wizard.py +4 -4
- code_puppy/mcp_/dashboard.py +15 -6
- code_puppy/mcp_/managed_server.py +65 -38
- code_puppy/mcp_/manager.py +146 -52
- code_puppy/mcp_/mcp_logs.py +224 -0
- code_puppy/mcp_/registry.py +6 -6
- code_puppy/mcp_/server_registry_catalog.py +24 -5
- code_puppy/messaging/__init__.py +199 -2
- code_puppy/messaging/bus.py +610 -0
- code_puppy/messaging/commands.py +167 -0
- code_puppy/messaging/markdown_patches.py +57 -0
- code_puppy/messaging/message_queue.py +17 -48
- code_puppy/messaging/messages.py +500 -0
- code_puppy/messaging/queue_console.py +1 -24
- code_puppy/messaging/renderers.py +43 -146
- code_puppy/messaging/rich_renderer.py +1027 -0
- code_puppy/messaging/spinner/__init__.py +21 -5
- code_puppy/messaging/spinner/console_spinner.py +86 -51
- code_puppy/messaging/subagent_console.py +461 -0
- code_puppy/model_factory.py +634 -83
- code_puppy/model_utils.py +167 -0
- code_puppy/models.json +66 -68
- code_puppy/models_dev_api.json +1 -0
- code_puppy/models_dev_parser.py +592 -0
- code_puppy/plugins/__init__.py +164 -10
- code_puppy/plugins/antigravity_oauth/__init__.py +10 -0
- code_puppy/plugins/antigravity_oauth/accounts.py +406 -0
- code_puppy/plugins/antigravity_oauth/antigravity_model.py +704 -0
- code_puppy/plugins/antigravity_oauth/config.py +42 -0
- code_puppy/plugins/antigravity_oauth/constants.py +136 -0
- code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
- code_puppy/plugins/antigravity_oauth/register_callbacks.py +406 -0
- code_puppy/plugins/antigravity_oauth/storage.py +271 -0
- code_puppy/plugins/antigravity_oauth/test_plugin.py +319 -0
- code_puppy/plugins/antigravity_oauth/token.py +167 -0
- code_puppy/plugins/antigravity_oauth/transport.py +767 -0
- code_puppy/plugins/antigravity_oauth/utils.py +169 -0
- code_puppy/plugins/chatgpt_oauth/__init__.py +8 -0
- code_puppy/plugins/chatgpt_oauth/config.py +52 -0
- code_puppy/plugins/chatgpt_oauth/oauth_flow.py +328 -0
- code_puppy/plugins/chatgpt_oauth/register_callbacks.py +94 -0
- code_puppy/plugins/chatgpt_oauth/test_plugin.py +293 -0
- code_puppy/plugins/chatgpt_oauth/utils.py +489 -0
- code_puppy/plugins/claude_code_oauth/README.md +167 -0
- code_puppy/plugins/claude_code_oauth/SETUP.md +93 -0
- code_puppy/plugins/claude_code_oauth/__init__.py +6 -0
- code_puppy/plugins/claude_code_oauth/config.py +50 -0
- code_puppy/plugins/claude_code_oauth/register_callbacks.py +308 -0
- code_puppy/plugins/claude_code_oauth/test_plugin.py +283 -0
- code_puppy/plugins/claude_code_oauth/utils.py +518 -0
- code_puppy/plugins/customizable_commands/__init__.py +0 -0
- code_puppy/plugins/customizable_commands/register_callbacks.py +169 -0
- code_puppy/plugins/example_custom_command/README.md +280 -0
- code_puppy/plugins/example_custom_command/register_callbacks.py +2 -2
- code_puppy/plugins/file_permission_handler/__init__.py +4 -0
- code_puppy/plugins/file_permission_handler/register_callbacks.py +523 -0
- code_puppy/plugins/frontend_emitter/__init__.py +25 -0
- code_puppy/plugins/frontend_emitter/emitter.py +121 -0
- code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
- code_puppy/plugins/oauth_puppy_html.py +228 -0
- code_puppy/plugins/shell_safety/__init__.py +6 -0
- code_puppy/plugins/shell_safety/agent_shell_safety.py +69 -0
- code_puppy/plugins/shell_safety/command_cache.py +156 -0
- code_puppy/plugins/shell_safety/register_callbacks.py +202 -0
- code_puppy/prompts/antigravity_system_prompt.md +1 -0
- code_puppy/prompts/codex_system_prompt.md +310 -0
- code_puppy/pydantic_patches.py +131 -0
- code_puppy/reopenable_async_client.py +8 -8
- code_puppy/round_robin_model.py +9 -12
- code_puppy/session_storage.py +2 -1
- code_puppy/status_display.py +21 -4
- code_puppy/summarization_agent.py +41 -13
- code_puppy/terminal_utils.py +418 -0
- code_puppy/tools/__init__.py +37 -1
- code_puppy/tools/agent_tools.py +536 -52
- code_puppy/tools/browser/__init__.py +37 -0
- code_puppy/tools/browser/browser_control.py +19 -23
- code_puppy/tools/browser/browser_interactions.py +41 -48
- code_puppy/tools/browser/browser_locators.py +36 -38
- code_puppy/tools/browser/browser_manager.py +316 -0
- code_puppy/tools/browser/browser_navigation.py +16 -16
- code_puppy/tools/browser/browser_screenshot.py +79 -143
- code_puppy/tools/browser/browser_scripts.py +32 -42
- code_puppy/tools/browser/browser_workflows.py +44 -27
- code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
- code_puppy/tools/browser/terminal_command_tools.py +521 -0
- code_puppy/tools/browser/terminal_screenshot_tools.py +556 -0
- code_puppy/tools/browser/terminal_tools.py +525 -0
- code_puppy/tools/command_runner.py +930 -147
- code_puppy/tools/common.py +1113 -5
- code_puppy/tools/display.py +84 -0
- code_puppy/tools/file_modifications.py +288 -89
- code_puppy/tools/file_operations.py +226 -154
- code_puppy/tools/subagent_context.py +158 -0
- code_puppy/uvx_detection.py +242 -0
- code_puppy/version_checker.py +30 -11
- code_puppy-0.0.366.data/data/code_puppy/models.json +110 -0
- code_puppy-0.0.366.data/data/code_puppy/models_dev_api.json +1 -0
- {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/METADATA +149 -75
- code_puppy-0.0.366.dist-info/RECORD +217 -0
- {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/WHEEL +1 -1
- code_puppy/command_line/mcp/add_command.py +0 -183
- code_puppy/messaging/spinner/textual_spinner.py +0 -106
- code_puppy/tools/browser/camoufox_manager.py +0 -216
- code_puppy/tools/browser/vqa_agent.py +0 -70
- code_puppy/tui/__init__.py +0 -10
- code_puppy/tui/app.py +0 -1105
- code_puppy/tui/components/__init__.py +0 -21
- code_puppy/tui/components/chat_view.py +0 -551
- code_puppy/tui/components/command_history_modal.py +0 -218
- code_puppy/tui/components/copy_button.py +0 -139
- code_puppy/tui/components/custom_widgets.py +0 -63
- code_puppy/tui/components/human_input_modal.py +0 -175
- code_puppy/tui/components/input_area.py +0 -167
- code_puppy/tui/components/sidebar.py +0 -309
- code_puppy/tui/components/status_bar.py +0 -185
- code_puppy/tui/messages.py +0 -27
- code_puppy/tui/models/__init__.py +0 -8
- code_puppy/tui/models/chat_message.py +0 -25
- code_puppy/tui/models/command_history.py +0 -89
- code_puppy/tui/models/enums.py +0 -24
- code_puppy/tui/screens/__init__.py +0 -17
- code_puppy/tui/screens/autosave_picker.py +0 -175
- code_puppy/tui/screens/help.py +0 -130
- code_puppy/tui/screens/mcp_install_wizard.py +0 -803
- code_puppy/tui/screens/settings.py +0 -306
- code_puppy/tui/screens/tools.py +0 -74
- code_puppy/tui_state.py +0 -55
- code_puppy-0.0.214.data/data/code_puppy/models.json +0 -112
- code_puppy-0.0.214.dist-info/RECORD +0 -131
- {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.214.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.
|
|
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):
|