code-puppy 0.0.302__py3-none-any.whl → 0.0.335__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/agents/base_agent.py +343 -35
- code_puppy/chatgpt_codex_client.py +283 -0
- code_puppy/cli_runner.py +898 -0
- code_puppy/command_line/add_model_menu.py +23 -1
- code_puppy/command_line/autosave_menu.py +271 -35
- code_puppy/command_line/colors_menu.py +520 -0
- code_puppy/command_line/command_handler.py +8 -2
- code_puppy/command_line/config_commands.py +82 -10
- code_puppy/command_line/core_commands.py +70 -7
- code_puppy/command_line/diff_menu.py +5 -0
- code_puppy/command_line/mcp/custom_server_form.py +4 -0
- code_puppy/command_line/mcp/edit_command.py +3 -1
- code_puppy/command_line/mcp/handler.py +7 -2
- code_puppy/command_line/mcp/install_command.py +8 -3
- code_puppy/command_line/mcp/install_menu.py +5 -1
- code_puppy/command_line/mcp/logs_command.py +173 -64
- code_puppy/command_line/mcp/restart_command.py +7 -2
- code_puppy/command_line/mcp/search_command.py +10 -4
- code_puppy/command_line/mcp/start_all_command.py +16 -6
- code_puppy/command_line/mcp/start_command.py +3 -1
- code_puppy/command_line/mcp/status_command.py +2 -1
- code_puppy/command_line/mcp/stop_all_command.py +5 -1
- code_puppy/command_line/mcp/stop_command.py +3 -1
- code_puppy/command_line/mcp/wizard_utils.py +10 -4
- code_puppy/command_line/model_settings_menu.py +58 -7
- code_puppy/command_line/motd.py +13 -7
- code_puppy/command_line/onboarding_slides.py +180 -0
- code_puppy/command_line/onboarding_wizard.py +340 -0
- code_puppy/command_line/prompt_toolkit_completion.py +16 -2
- code_puppy/command_line/session_commands.py +11 -4
- code_puppy/config.py +106 -17
- code_puppy/http_utils.py +155 -196
- code_puppy/keymap.py +8 -0
- code_puppy/main.py +5 -828
- code_puppy/mcp_/__init__.py +17 -0
- code_puppy/mcp_/blocking_startup.py +61 -32
- code_puppy/mcp_/config_wizard.py +5 -1
- code_puppy/mcp_/managed_server.py +23 -3
- code_puppy/mcp_/manager.py +65 -0
- code_puppy/mcp_/mcp_logs.py +224 -0
- code_puppy/messaging/__init__.py +20 -4
- code_puppy/messaging/bus.py +64 -0
- code_puppy/messaging/markdown_patches.py +57 -0
- code_puppy/messaging/messages.py +16 -0
- code_puppy/messaging/renderers.py +21 -9
- code_puppy/messaging/rich_renderer.py +113 -67
- code_puppy/messaging/spinner/console_spinner.py +34 -0
- code_puppy/model_factory.py +271 -45
- code_puppy/model_utils.py +57 -48
- code_puppy/models.json +21 -7
- code_puppy/plugins/__init__.py +12 -0
- 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 +612 -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 +595 -0
- code_puppy/plugins/antigravity_oauth/utils.py +169 -0
- code_puppy/plugins/chatgpt_oauth/config.py +5 -1
- code_puppy/plugins/chatgpt_oauth/oauth_flow.py +5 -6
- code_puppy/plugins/chatgpt_oauth/register_callbacks.py +5 -3
- code_puppy/plugins/chatgpt_oauth/test_plugin.py +26 -11
- code_puppy/plugins/chatgpt_oauth/utils.py +180 -65
- code_puppy/plugins/claude_code_oauth/register_callbacks.py +30 -0
- code_puppy/plugins/claude_code_oauth/utils.py +1 -0
- code_puppy/plugins/shell_safety/agent_shell_safety.py +1 -118
- code_puppy/plugins/shell_safety/register_callbacks.py +44 -3
- 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/terminal_utils.py +291 -0
- code_puppy/tools/agent_tools.py +34 -9
- code_puppy/tools/command_runner.py +344 -27
- code_puppy/tools/file_operations.py +33 -45
- code_puppy/uvx_detection.py +242 -0
- {code_puppy-0.0.302.data → code_puppy-0.0.335.data}/data/code_puppy/models.json +21 -7
- {code_puppy-0.0.302.dist-info → code_puppy-0.0.335.dist-info}/METADATA +30 -1
- {code_puppy-0.0.302.dist-info → code_puppy-0.0.335.dist-info}/RECORD +87 -64
- {code_puppy-0.0.302.data → code_puppy-0.0.335.data}/data/code_puppy/models_dev_api.json +0 -0
- {code_puppy-0.0.302.dist-info → code_puppy-0.0.335.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.302.dist-info → code_puppy-0.0.335.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.302.dist-info → code_puppy-0.0.335.dist-info}/licenses/LICENSE +0 -0
|
@@ -54,13 +54,15 @@ class ReopenableAsyncClient:
|
|
|
54
54
|
if self._stream_context:
|
|
55
55
|
return await self._stream_context.__aexit__(exc_type, exc_val, exc_tb)
|
|
56
56
|
|
|
57
|
-
def __init__(self, **kwargs):
|
|
57
|
+
def __init__(self, client_class=None, **kwargs):
|
|
58
58
|
"""
|
|
59
59
|
Initialize the ReopenableAsyncClient.
|
|
60
60
|
|
|
61
61
|
Args:
|
|
62
|
-
|
|
62
|
+
client_class: Class to use for creating the internal client (defaults to httpx.AsyncClient)
|
|
63
|
+
**kwargs: All arguments that would be passed to the client constructor
|
|
63
64
|
"""
|
|
65
|
+
self._client_class = client_class or httpx.AsyncClient
|
|
64
66
|
self._client_kwargs = kwargs.copy()
|
|
65
67
|
self._client: Optional[httpx.AsyncClient] = None
|
|
66
68
|
self._is_closed = True
|
|
@@ -70,7 +72,7 @@ class ReopenableAsyncClient:
|
|
|
70
72
|
Ensure the underlying client is open and ready to use.
|
|
71
73
|
|
|
72
74
|
Returns:
|
|
73
|
-
The active
|
|
75
|
+
The active client instance
|
|
74
76
|
|
|
75
77
|
Raises:
|
|
76
78
|
RuntimeError: If client cannot be opened
|
|
@@ -80,12 +82,12 @@ class ReopenableAsyncClient:
|
|
|
80
82
|
return self._client
|
|
81
83
|
|
|
82
84
|
async def _create_client(self) -> None:
|
|
83
|
-
"""Create a new
|
|
85
|
+
"""Create a new client with the stored configuration."""
|
|
84
86
|
if self._client is not None and not self._is_closed:
|
|
85
87
|
# Close existing client first
|
|
86
88
|
await self._client.aclose()
|
|
87
89
|
|
|
88
|
-
self._client =
|
|
90
|
+
self._client = self._client_class(**self._client_kwargs)
|
|
89
91
|
self._is_closed = False
|
|
90
92
|
|
|
91
93
|
async def reopen(self) -> None:
|
|
@@ -171,14 +173,12 @@ class ReopenableAsyncClient:
|
|
|
171
173
|
"""
|
|
172
174
|
if self._client is None or self._is_closed:
|
|
173
175
|
# Create a temporary client just for building the request
|
|
174
|
-
temp_client =
|
|
176
|
+
temp_client = self._client_class(**self._client_kwargs)
|
|
175
177
|
try:
|
|
176
178
|
request = temp_client.build_request(method, url, **kwargs)
|
|
177
179
|
return request
|
|
178
180
|
finally:
|
|
179
181
|
# Clean up the temporary client synchronously if possible
|
|
180
|
-
# Note: This might leave a connection open, but it's better than
|
|
181
|
-
# making this method async just for building requests
|
|
182
182
|
pass
|
|
183
183
|
return self._client.build_request(method, url, **kwargs)
|
|
184
184
|
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"""Terminal utilities for cross-platform terminal state management.
|
|
2
|
+
|
|
3
|
+
Handles Windows console mode resets and Unix terminal sanity restoration.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import platform
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
from typing import Callable, Optional
|
|
10
|
+
|
|
11
|
+
# Store the original console ctrl handler so we can restore it if needed
|
|
12
|
+
_original_ctrl_handler: Optional[Callable] = None
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def reset_windows_terminal_ansi() -> None:
|
|
16
|
+
"""Reset ANSI formatting on Windows stdout/stderr.
|
|
17
|
+
|
|
18
|
+
This is a lightweight reset that just clears ANSI escape sequences.
|
|
19
|
+
Use this for quick resets after output operations.
|
|
20
|
+
"""
|
|
21
|
+
if platform.system() != "Windows":
|
|
22
|
+
return
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
sys.stdout.write("\x1b[0m") # Reset ANSI formatting
|
|
26
|
+
sys.stdout.flush()
|
|
27
|
+
sys.stderr.write("\x1b[0m")
|
|
28
|
+
sys.stderr.flush()
|
|
29
|
+
except Exception:
|
|
30
|
+
pass # Silently ignore errors - best effort reset
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def reset_windows_console_mode() -> None:
|
|
34
|
+
"""Full Windows console mode reset using ctypes.
|
|
35
|
+
|
|
36
|
+
This resets both stdout and stdin console modes to restore proper
|
|
37
|
+
terminal behavior after interrupts (Ctrl+C, Ctrl+D). Without this,
|
|
38
|
+
the terminal can become unresponsive (can't type characters).
|
|
39
|
+
"""
|
|
40
|
+
if platform.system() != "Windows":
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
import ctypes
|
|
45
|
+
|
|
46
|
+
kernel32 = ctypes.windll.kernel32
|
|
47
|
+
|
|
48
|
+
# Reset stdout
|
|
49
|
+
STD_OUTPUT_HANDLE = -11
|
|
50
|
+
handle = kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
|
|
51
|
+
|
|
52
|
+
# Enable virtual terminal processing and line input
|
|
53
|
+
mode = ctypes.c_ulong()
|
|
54
|
+
kernel32.GetConsoleMode(handle, ctypes.byref(mode))
|
|
55
|
+
|
|
56
|
+
# Console mode flags for stdout
|
|
57
|
+
ENABLE_PROCESSED_OUTPUT = 0x0001
|
|
58
|
+
ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002
|
|
59
|
+
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
|
|
60
|
+
|
|
61
|
+
new_mode = (
|
|
62
|
+
mode.value
|
|
63
|
+
| ENABLE_PROCESSED_OUTPUT
|
|
64
|
+
| ENABLE_WRAP_AT_EOL_OUTPUT
|
|
65
|
+
| ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
|
66
|
+
)
|
|
67
|
+
kernel32.SetConsoleMode(handle, new_mode)
|
|
68
|
+
|
|
69
|
+
# Reset stdin
|
|
70
|
+
STD_INPUT_HANDLE = -10
|
|
71
|
+
stdin_handle = kernel32.GetStdHandle(STD_INPUT_HANDLE)
|
|
72
|
+
|
|
73
|
+
# Console mode flags for stdin
|
|
74
|
+
ENABLE_LINE_INPUT = 0x0002
|
|
75
|
+
ENABLE_ECHO_INPUT = 0x0004
|
|
76
|
+
ENABLE_PROCESSED_INPUT = 0x0001
|
|
77
|
+
|
|
78
|
+
stdin_mode = ctypes.c_ulong()
|
|
79
|
+
kernel32.GetConsoleMode(stdin_handle, ctypes.byref(stdin_mode))
|
|
80
|
+
|
|
81
|
+
new_stdin_mode = (
|
|
82
|
+
stdin_mode.value
|
|
83
|
+
| ENABLE_LINE_INPUT
|
|
84
|
+
| ENABLE_ECHO_INPUT
|
|
85
|
+
| ENABLE_PROCESSED_INPUT
|
|
86
|
+
)
|
|
87
|
+
kernel32.SetConsoleMode(stdin_handle, new_stdin_mode)
|
|
88
|
+
|
|
89
|
+
except Exception:
|
|
90
|
+
pass # Silently ignore errors - best effort reset
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def flush_windows_keyboard_buffer() -> None:
|
|
94
|
+
"""Flush the Windows keyboard buffer.
|
|
95
|
+
|
|
96
|
+
Clears any pending keyboard input that could interfere with
|
|
97
|
+
subsequent input operations after an interrupt.
|
|
98
|
+
"""
|
|
99
|
+
if platform.system() != "Windows":
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
import msvcrt
|
|
104
|
+
|
|
105
|
+
while msvcrt.kbhit():
|
|
106
|
+
msvcrt.getch()
|
|
107
|
+
except Exception:
|
|
108
|
+
pass # Silently ignore errors - best effort flush
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def reset_windows_terminal_full() -> None:
|
|
112
|
+
"""Perform a full Windows terminal reset (ANSI + console mode + keyboard buffer).
|
|
113
|
+
|
|
114
|
+
Combines ANSI reset, console mode reset, and keyboard buffer flush
|
|
115
|
+
for complete terminal state restoration after interrupts.
|
|
116
|
+
"""
|
|
117
|
+
if platform.system() != "Windows":
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
reset_windows_terminal_ansi()
|
|
121
|
+
reset_windows_console_mode()
|
|
122
|
+
flush_windows_keyboard_buffer()
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def reset_unix_terminal() -> None:
|
|
126
|
+
"""Reset Unix/Linux/macOS terminal to sane state.
|
|
127
|
+
|
|
128
|
+
Uses the `reset` command to restore terminal sanity.
|
|
129
|
+
Silently fails if the command isn't available.
|
|
130
|
+
"""
|
|
131
|
+
if platform.system() == "Windows":
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
subprocess.run(["reset"], check=True, capture_output=True)
|
|
136
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
137
|
+
pass # Silently fail if reset command isn't available
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def reset_terminal() -> None:
|
|
141
|
+
"""Cross-platform terminal reset.
|
|
142
|
+
|
|
143
|
+
Automatically detects the platform and performs the appropriate
|
|
144
|
+
terminal reset operation.
|
|
145
|
+
"""
|
|
146
|
+
if platform.system() == "Windows":
|
|
147
|
+
reset_windows_terminal_full()
|
|
148
|
+
else:
|
|
149
|
+
reset_unix_terminal()
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def disable_windows_ctrl_c() -> bool:
|
|
153
|
+
"""Disable Ctrl+C processing at the Windows console input level.
|
|
154
|
+
|
|
155
|
+
This removes ENABLE_PROCESSED_INPUT from stdin, which prevents
|
|
156
|
+
Ctrl+C from being interpreted as a signal at all. Instead, it
|
|
157
|
+
becomes just a regular character (^C) that gets ignored.
|
|
158
|
+
|
|
159
|
+
This is more reliable than SetConsoleCtrlHandler because it
|
|
160
|
+
prevents Ctrl+C from being processed before it reaches any handler.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
True if successfully disabled, False otherwise.
|
|
164
|
+
"""
|
|
165
|
+
global _original_ctrl_handler
|
|
166
|
+
|
|
167
|
+
if platform.system() != "Windows":
|
|
168
|
+
return False
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
import ctypes
|
|
172
|
+
|
|
173
|
+
kernel32 = ctypes.windll.kernel32
|
|
174
|
+
|
|
175
|
+
# Get stdin handle
|
|
176
|
+
STD_INPUT_HANDLE = -10
|
|
177
|
+
stdin_handle = kernel32.GetStdHandle(STD_INPUT_HANDLE)
|
|
178
|
+
|
|
179
|
+
# Get current console mode
|
|
180
|
+
mode = ctypes.c_ulong()
|
|
181
|
+
if not kernel32.GetConsoleMode(stdin_handle, ctypes.byref(mode)):
|
|
182
|
+
return False
|
|
183
|
+
|
|
184
|
+
# Save original mode for potential restoration
|
|
185
|
+
_original_ctrl_handler = mode.value
|
|
186
|
+
|
|
187
|
+
# Console mode flags
|
|
188
|
+
ENABLE_PROCESSED_INPUT = 0x0001 # This makes Ctrl+C generate signals
|
|
189
|
+
|
|
190
|
+
# Remove ENABLE_PROCESSED_INPUT to disable Ctrl+C signal generation
|
|
191
|
+
new_mode = mode.value & ~ENABLE_PROCESSED_INPUT
|
|
192
|
+
|
|
193
|
+
if kernel32.SetConsoleMode(stdin_handle, new_mode):
|
|
194
|
+
return True
|
|
195
|
+
return False
|
|
196
|
+
|
|
197
|
+
except Exception:
|
|
198
|
+
return False
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def enable_windows_ctrl_c() -> bool:
|
|
202
|
+
"""Re-enable Ctrl+C at the Windows console level.
|
|
203
|
+
|
|
204
|
+
Restores the original console mode saved by disable_windows_ctrl_c().
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
True if successfully re-enabled, False otherwise.
|
|
208
|
+
"""
|
|
209
|
+
global _original_ctrl_handler
|
|
210
|
+
|
|
211
|
+
if platform.system() != "Windows":
|
|
212
|
+
return False
|
|
213
|
+
|
|
214
|
+
if _original_ctrl_handler is None:
|
|
215
|
+
return True # Nothing to restore
|
|
216
|
+
|
|
217
|
+
try:
|
|
218
|
+
import ctypes
|
|
219
|
+
|
|
220
|
+
kernel32 = ctypes.windll.kernel32
|
|
221
|
+
|
|
222
|
+
# Get stdin handle
|
|
223
|
+
STD_INPUT_HANDLE = -10
|
|
224
|
+
stdin_handle = kernel32.GetStdHandle(STD_INPUT_HANDLE)
|
|
225
|
+
|
|
226
|
+
# Restore original mode
|
|
227
|
+
if kernel32.SetConsoleMode(stdin_handle, _original_ctrl_handler):
|
|
228
|
+
_original_ctrl_handler = None
|
|
229
|
+
return True
|
|
230
|
+
return False
|
|
231
|
+
|
|
232
|
+
except Exception:
|
|
233
|
+
return False
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
# Flag to track if we should keep Ctrl+C disabled
|
|
237
|
+
_keep_ctrl_c_disabled: bool = False
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def set_keep_ctrl_c_disabled(value: bool) -> None:
|
|
241
|
+
"""Set whether Ctrl+C should be kept disabled.
|
|
242
|
+
|
|
243
|
+
When True, ensure_ctrl_c_disabled() will re-disable Ctrl+C
|
|
244
|
+
even if something else (like prompt_toolkit) re-enables it.
|
|
245
|
+
"""
|
|
246
|
+
global _keep_ctrl_c_disabled
|
|
247
|
+
_keep_ctrl_c_disabled = value
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def ensure_ctrl_c_disabled() -> bool:
|
|
251
|
+
"""Ensure Ctrl+C is disabled if it should be.
|
|
252
|
+
|
|
253
|
+
Call this after operations that might restore console mode
|
|
254
|
+
(like prompt_toolkit input).
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
True if Ctrl+C is now disabled (or wasn't needed), False on error.
|
|
258
|
+
"""
|
|
259
|
+
if not _keep_ctrl_c_disabled:
|
|
260
|
+
return True
|
|
261
|
+
|
|
262
|
+
if platform.system() != "Windows":
|
|
263
|
+
return True
|
|
264
|
+
|
|
265
|
+
try:
|
|
266
|
+
import ctypes
|
|
267
|
+
|
|
268
|
+
kernel32 = ctypes.windll.kernel32
|
|
269
|
+
|
|
270
|
+
# Get stdin handle
|
|
271
|
+
STD_INPUT_HANDLE = -10
|
|
272
|
+
stdin_handle = kernel32.GetStdHandle(STD_INPUT_HANDLE)
|
|
273
|
+
|
|
274
|
+
# Get current console mode
|
|
275
|
+
mode = ctypes.c_ulong()
|
|
276
|
+
if not kernel32.GetConsoleMode(stdin_handle, ctypes.byref(mode)):
|
|
277
|
+
return False
|
|
278
|
+
|
|
279
|
+
# Console mode flags
|
|
280
|
+
ENABLE_PROCESSED_INPUT = 0x0001
|
|
281
|
+
|
|
282
|
+
# Check if Ctrl+C processing is enabled
|
|
283
|
+
if mode.value & ENABLE_PROCESSED_INPUT:
|
|
284
|
+
# Disable it
|
|
285
|
+
new_mode = mode.value & ~ENABLE_PROCESSED_INPUT
|
|
286
|
+
return bool(kernel32.SetConsoleMode(stdin_handle, new_mode))
|
|
287
|
+
|
|
288
|
+
return True # Already disabled
|
|
289
|
+
|
|
290
|
+
except Exception:
|
|
291
|
+
return False
|
code_puppy/tools/agent_tools.py
CHANGED
|
@@ -27,8 +27,9 @@ from code_puppy.messaging import (
|
|
|
27
27
|
SubAgentResponseMessage,
|
|
28
28
|
emit_error,
|
|
29
29
|
emit_info,
|
|
30
|
-
emit_system_message,
|
|
31
30
|
get_message_bus,
|
|
31
|
+
get_session_context,
|
|
32
|
+
set_session_context,
|
|
32
33
|
)
|
|
33
34
|
from code_puppy.model_factory import ModelFactory, make_model_settings
|
|
34
35
|
from code_puppy.tools.common import generate_group_id
|
|
@@ -207,6 +208,7 @@ class AgentInfo(BaseModel):
|
|
|
207
208
|
|
|
208
209
|
name: str
|
|
209
210
|
display_name: str
|
|
211
|
+
description: str
|
|
210
212
|
|
|
211
213
|
|
|
212
214
|
class ListAgentsOutput(BaseModel):
|
|
@@ -242,29 +244,44 @@ def register_list_agents(agent):
|
|
|
242
244
|
# Generate a group ID for this tool execution
|
|
243
245
|
group_id = generate_group_id("list_agents")
|
|
244
246
|
|
|
247
|
+
from rich.text import Text
|
|
248
|
+
|
|
249
|
+
from code_puppy.config import get_banner_color
|
|
250
|
+
|
|
251
|
+
list_agents_color = get_banner_color("list_agents")
|
|
245
252
|
emit_info(
|
|
246
|
-
|
|
253
|
+
Text.from_markup(
|
|
254
|
+
f"\n[bold white on {list_agents_color}] LIST AGENTS [/bold white on {list_agents_color}]"
|
|
255
|
+
),
|
|
247
256
|
message_group=group_id,
|
|
248
257
|
)
|
|
249
258
|
|
|
250
259
|
try:
|
|
251
|
-
from code_puppy.agents import get_available_agents
|
|
260
|
+
from code_puppy.agents import get_agent_descriptions, get_available_agents
|
|
252
261
|
|
|
253
|
-
# Get available agents from the agent manager
|
|
262
|
+
# Get available agents and their descriptions from the agent manager
|
|
254
263
|
agents_dict = get_available_agents()
|
|
264
|
+
descriptions_dict = get_agent_descriptions()
|
|
255
265
|
|
|
256
266
|
# Convert to list of AgentInfo objects
|
|
257
267
|
agents = [
|
|
258
|
-
AgentInfo(
|
|
268
|
+
AgentInfo(
|
|
269
|
+
name=name,
|
|
270
|
+
display_name=display_name,
|
|
271
|
+
description=descriptions_dict.get(name, "No description available"),
|
|
272
|
+
)
|
|
259
273
|
for name, display_name in agents_dict.items()
|
|
260
274
|
]
|
|
261
275
|
|
|
262
|
-
#
|
|
276
|
+
# Accumulate output into a single string and emit once
|
|
277
|
+
# Use Text.from_markup() to pass a Rich object that won't be escaped
|
|
278
|
+
lines = []
|
|
263
279
|
for agent_item in agents:
|
|
264
|
-
|
|
265
|
-
f"- [bold]{agent_item.name}[/bold]: {agent_item.display_name}"
|
|
266
|
-
|
|
280
|
+
lines.append(
|
|
281
|
+
f"- [bold]{agent_item.name}[/bold]: {agent_item.display_name}\n"
|
|
282
|
+
f" [dim]{agent_item.description}[/dim]"
|
|
267
283
|
)
|
|
284
|
+
emit_info(Text.from_markup("\n".join(lines)), message_group=group_id)
|
|
268
285
|
|
|
269
286
|
return ListAgentsOutput(agents=agents)
|
|
270
287
|
|
|
@@ -407,6 +424,10 @@ def register_invoke_agent(agent):
|
|
|
407
424
|
)
|
|
408
425
|
)
|
|
409
426
|
|
|
427
|
+
# Save current session context and set the new one for this sub-agent
|
|
428
|
+
previous_session_id = get_session_context()
|
|
429
|
+
set_session_context(session_id)
|
|
430
|
+
|
|
410
431
|
try:
|
|
411
432
|
# Load the specified agent config
|
|
412
433
|
agent_config = load_agent(agent_name)
|
|
@@ -543,4 +564,8 @@ def register_invoke_agent(agent):
|
|
|
543
564
|
error=error_msg,
|
|
544
565
|
)
|
|
545
566
|
|
|
567
|
+
finally:
|
|
568
|
+
# Restore the previous session context
|
|
569
|
+
set_session_context(previous_session_id)
|
|
570
|
+
|
|
546
571
|
return invoke_agent
|