code-puppy 0.0.325__py3-none-any.whl → 0.0.341__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 +110 -124
- code_puppy/claude_cache_client.py +208 -2
- code_puppy/cli_runner.py +152 -32
- code_puppy/command_line/add_model_menu.py +4 -0
- code_puppy/command_line/autosave_menu.py +23 -24
- code_puppy/command_line/clipboard.py +527 -0
- code_puppy/command_line/colors_menu.py +5 -0
- code_puppy/command_line/config_commands.py +24 -1
- code_puppy/command_line/core_commands.py +85 -0
- 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/install_menu.py +5 -1
- code_puppy/command_line/model_settings_menu.py +5 -0
- 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 +118 -0
- code_puppy/config.py +3 -2
- code_puppy/http_utils.py +201 -279
- code_puppy/keymap.py +10 -8
- code_puppy/mcp_/managed_server.py +7 -11
- code_puppy/messaging/messages.py +3 -0
- code_puppy/messaging/rich_renderer.py +114 -22
- code_puppy/model_factory.py +102 -15
- code_puppy/models.json +2 -2
- 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 +668 -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 +664 -0
- code_puppy/plugins/antigravity_oauth/utils.py +169 -0
- code_puppy/plugins/chatgpt_oauth/register_callbacks.py +2 -0
- code_puppy/plugins/claude_code_oauth/register_callbacks.py +2 -0
- code_puppy/plugins/claude_code_oauth/utils.py +126 -7
- code_puppy/reopenable_async_client.py +8 -8
- code_puppy/terminal_utils.py +295 -3
- code_puppy/tools/command_runner.py +43 -54
- code_puppy/tools/common.py +3 -9
- code_puppy/uvx_detection.py +242 -0
- {code_puppy-0.0.325.data → code_puppy-0.0.341.data}/data/code_puppy/models.json +2 -2
- {code_puppy-0.0.325.dist-info → code_puppy-0.0.341.dist-info}/METADATA +26 -49
- {code_puppy-0.0.325.dist-info → code_puppy-0.0.341.dist-info}/RECORD +52 -36
- {code_puppy-0.0.325.data → code_puppy-0.0.341.data}/data/code_puppy/models_dev_api.json +0 -0
- {code_puppy-0.0.325.dist-info → code_puppy-0.0.341.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.325.dist-info → code_puppy-0.0.341.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.325.dist-info → code_puppy-0.0.341.dist-info}/licenses/LICENSE +0 -0
code_puppy/terminal_utils.py
CHANGED
|
@@ -3,9 +3,17 @@
|
|
|
3
3
|
Handles Windows console mode resets and Unix terminal sanity restoration.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
+
import os
|
|
6
7
|
import platform
|
|
7
8
|
import subprocess
|
|
8
9
|
import sys
|
|
10
|
+
from typing import TYPE_CHECKING, Callable, Optional
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from rich.console import Console
|
|
14
|
+
|
|
15
|
+
# Store the original console ctrl handler so we can restore it if needed
|
|
16
|
+
_original_ctrl_handler: Optional[Callable] = None
|
|
9
17
|
|
|
10
18
|
|
|
11
19
|
def reset_windows_terminal_ansi() -> None:
|
|
@@ -86,17 +94,36 @@ def reset_windows_console_mode() -> None:
|
|
|
86
94
|
pass # Silently ignore errors - best effort reset
|
|
87
95
|
|
|
88
96
|
|
|
97
|
+
def flush_windows_keyboard_buffer() -> None:
|
|
98
|
+
"""Flush the Windows keyboard buffer.
|
|
99
|
+
|
|
100
|
+
Clears any pending keyboard input that could interfere with
|
|
101
|
+
subsequent input operations after an interrupt.
|
|
102
|
+
"""
|
|
103
|
+
if platform.system() != "Windows":
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
import msvcrt
|
|
108
|
+
|
|
109
|
+
while msvcrt.kbhit():
|
|
110
|
+
msvcrt.getch()
|
|
111
|
+
except Exception:
|
|
112
|
+
pass # Silently ignore errors - best effort flush
|
|
113
|
+
|
|
114
|
+
|
|
89
115
|
def reset_windows_terminal_full() -> None:
|
|
90
|
-
"""Perform a full Windows terminal reset (ANSI + console mode).
|
|
116
|
+
"""Perform a full Windows terminal reset (ANSI + console mode + keyboard buffer).
|
|
91
117
|
|
|
92
|
-
Combines
|
|
93
|
-
terminal state restoration after interrupts.
|
|
118
|
+
Combines ANSI reset, console mode reset, and keyboard buffer flush
|
|
119
|
+
for complete terminal state restoration after interrupts.
|
|
94
120
|
"""
|
|
95
121
|
if platform.system() != "Windows":
|
|
96
122
|
return
|
|
97
123
|
|
|
98
124
|
reset_windows_terminal_ansi()
|
|
99
125
|
reset_windows_console_mode()
|
|
126
|
+
flush_windows_keyboard_buffer()
|
|
100
127
|
|
|
101
128
|
|
|
102
129
|
def reset_unix_terminal() -> None:
|
|
@@ -124,3 +151,268 @@ def reset_terminal() -> None:
|
|
|
124
151
|
reset_windows_terminal_full()
|
|
125
152
|
else:
|
|
126
153
|
reset_unix_terminal()
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def disable_windows_ctrl_c() -> bool:
|
|
157
|
+
"""Disable Ctrl+C processing at the Windows console input level.
|
|
158
|
+
|
|
159
|
+
This removes ENABLE_PROCESSED_INPUT from stdin, which prevents
|
|
160
|
+
Ctrl+C from being interpreted as a signal at all. Instead, it
|
|
161
|
+
becomes just a regular character (^C) that gets ignored.
|
|
162
|
+
|
|
163
|
+
This is more reliable than SetConsoleCtrlHandler because it
|
|
164
|
+
prevents Ctrl+C from being processed before it reaches any handler.
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
True if successfully disabled, False otherwise.
|
|
168
|
+
"""
|
|
169
|
+
global _original_ctrl_handler
|
|
170
|
+
|
|
171
|
+
if platform.system() != "Windows":
|
|
172
|
+
return False
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
import ctypes
|
|
176
|
+
|
|
177
|
+
kernel32 = ctypes.windll.kernel32
|
|
178
|
+
|
|
179
|
+
# Get stdin handle
|
|
180
|
+
STD_INPUT_HANDLE = -10
|
|
181
|
+
stdin_handle = kernel32.GetStdHandle(STD_INPUT_HANDLE)
|
|
182
|
+
|
|
183
|
+
# Get current console mode
|
|
184
|
+
mode = ctypes.c_ulong()
|
|
185
|
+
if not kernel32.GetConsoleMode(stdin_handle, ctypes.byref(mode)):
|
|
186
|
+
return False
|
|
187
|
+
|
|
188
|
+
# Save original mode for potential restoration
|
|
189
|
+
_original_ctrl_handler = mode.value
|
|
190
|
+
|
|
191
|
+
# Console mode flags
|
|
192
|
+
ENABLE_PROCESSED_INPUT = 0x0001 # This makes Ctrl+C generate signals
|
|
193
|
+
|
|
194
|
+
# Remove ENABLE_PROCESSED_INPUT to disable Ctrl+C signal generation
|
|
195
|
+
new_mode = mode.value & ~ENABLE_PROCESSED_INPUT
|
|
196
|
+
|
|
197
|
+
if kernel32.SetConsoleMode(stdin_handle, new_mode):
|
|
198
|
+
return True
|
|
199
|
+
return False
|
|
200
|
+
|
|
201
|
+
except Exception:
|
|
202
|
+
return False
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def enable_windows_ctrl_c() -> bool:
|
|
206
|
+
"""Re-enable Ctrl+C at the Windows console level.
|
|
207
|
+
|
|
208
|
+
Restores the original console mode saved by disable_windows_ctrl_c().
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
True if successfully re-enabled, False otherwise.
|
|
212
|
+
"""
|
|
213
|
+
global _original_ctrl_handler
|
|
214
|
+
|
|
215
|
+
if platform.system() != "Windows":
|
|
216
|
+
return False
|
|
217
|
+
|
|
218
|
+
if _original_ctrl_handler is None:
|
|
219
|
+
return True # Nothing to restore
|
|
220
|
+
|
|
221
|
+
try:
|
|
222
|
+
import ctypes
|
|
223
|
+
|
|
224
|
+
kernel32 = ctypes.windll.kernel32
|
|
225
|
+
|
|
226
|
+
# Get stdin handle
|
|
227
|
+
STD_INPUT_HANDLE = -10
|
|
228
|
+
stdin_handle = kernel32.GetStdHandle(STD_INPUT_HANDLE)
|
|
229
|
+
|
|
230
|
+
# Restore original mode
|
|
231
|
+
if kernel32.SetConsoleMode(stdin_handle, _original_ctrl_handler):
|
|
232
|
+
_original_ctrl_handler = None
|
|
233
|
+
return True
|
|
234
|
+
return False
|
|
235
|
+
|
|
236
|
+
except Exception:
|
|
237
|
+
return False
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
# Flag to track if we should keep Ctrl+C disabled
|
|
241
|
+
_keep_ctrl_c_disabled: bool = False
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def set_keep_ctrl_c_disabled(value: bool) -> None:
|
|
245
|
+
"""Set whether Ctrl+C should be kept disabled.
|
|
246
|
+
|
|
247
|
+
When True, ensure_ctrl_c_disabled() will re-disable Ctrl+C
|
|
248
|
+
even if something else (like prompt_toolkit) re-enables it.
|
|
249
|
+
"""
|
|
250
|
+
global _keep_ctrl_c_disabled
|
|
251
|
+
_keep_ctrl_c_disabled = value
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def ensure_ctrl_c_disabled() -> bool:
|
|
255
|
+
"""Ensure Ctrl+C is disabled if it should be.
|
|
256
|
+
|
|
257
|
+
Call this after operations that might restore console mode
|
|
258
|
+
(like prompt_toolkit input).
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
True if Ctrl+C is now disabled (or wasn't needed), False on error.
|
|
262
|
+
"""
|
|
263
|
+
if not _keep_ctrl_c_disabled:
|
|
264
|
+
return True
|
|
265
|
+
|
|
266
|
+
if platform.system() != "Windows":
|
|
267
|
+
return True
|
|
268
|
+
|
|
269
|
+
try:
|
|
270
|
+
import ctypes
|
|
271
|
+
|
|
272
|
+
kernel32 = ctypes.windll.kernel32
|
|
273
|
+
|
|
274
|
+
# Get stdin handle
|
|
275
|
+
STD_INPUT_HANDLE = -10
|
|
276
|
+
stdin_handle = kernel32.GetStdHandle(STD_INPUT_HANDLE)
|
|
277
|
+
|
|
278
|
+
# Get current console mode
|
|
279
|
+
mode = ctypes.c_ulong()
|
|
280
|
+
if not kernel32.GetConsoleMode(stdin_handle, ctypes.byref(mode)):
|
|
281
|
+
return False
|
|
282
|
+
|
|
283
|
+
# Console mode flags
|
|
284
|
+
ENABLE_PROCESSED_INPUT = 0x0001
|
|
285
|
+
|
|
286
|
+
# Check if Ctrl+C processing is enabled
|
|
287
|
+
if mode.value & ENABLE_PROCESSED_INPUT:
|
|
288
|
+
# Disable it
|
|
289
|
+
new_mode = mode.value & ~ENABLE_PROCESSED_INPUT
|
|
290
|
+
return bool(kernel32.SetConsoleMode(stdin_handle, new_mode))
|
|
291
|
+
|
|
292
|
+
return True # Already disabled
|
|
293
|
+
|
|
294
|
+
except Exception:
|
|
295
|
+
return False
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def detect_truecolor_support() -> bool:
|
|
299
|
+
"""Detect if the terminal supports truecolor (24-bit color).
|
|
300
|
+
|
|
301
|
+
Checks multiple indicators:
|
|
302
|
+
1. COLORTERM environment variable (most reliable)
|
|
303
|
+
2. TERM environment variable patterns
|
|
304
|
+
3. Rich's Console color_system detection as fallback
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
True if truecolor is supported, False otherwise.
|
|
308
|
+
"""
|
|
309
|
+
# Check COLORTERM - this is the most reliable indicator
|
|
310
|
+
colorterm = os.environ.get("COLORTERM", "").lower()
|
|
311
|
+
if colorterm in ("truecolor", "24bit"):
|
|
312
|
+
return True
|
|
313
|
+
|
|
314
|
+
# Check TERM for known truecolor-capable terminals
|
|
315
|
+
term = os.environ.get("TERM", "").lower()
|
|
316
|
+
truecolor_terms = (
|
|
317
|
+
"xterm-direct",
|
|
318
|
+
"xterm-truecolor",
|
|
319
|
+
"iterm2",
|
|
320
|
+
"vte-256color", # Many modern terminals set this
|
|
321
|
+
)
|
|
322
|
+
if any(t in term for t in truecolor_terms):
|
|
323
|
+
return True
|
|
324
|
+
|
|
325
|
+
# Some terminals like iTerm2, Kitty, Alacritty set specific env vars
|
|
326
|
+
if os.environ.get("ITERM_SESSION_ID"):
|
|
327
|
+
return True
|
|
328
|
+
if os.environ.get("KITTY_WINDOW_ID"):
|
|
329
|
+
return True
|
|
330
|
+
if os.environ.get("ALACRITTY_SOCKET"):
|
|
331
|
+
return True
|
|
332
|
+
if os.environ.get("WT_SESSION"): # Windows Terminal
|
|
333
|
+
return True
|
|
334
|
+
|
|
335
|
+
# Use Rich's detection as a fallback
|
|
336
|
+
try:
|
|
337
|
+
from rich.console import Console
|
|
338
|
+
|
|
339
|
+
console = Console(force_terminal=True)
|
|
340
|
+
color_system = console.color_system
|
|
341
|
+
return color_system == "truecolor"
|
|
342
|
+
except Exception:
|
|
343
|
+
pass
|
|
344
|
+
|
|
345
|
+
return False
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def print_truecolor_warning(console: Optional["Console"] = None) -> None:
|
|
349
|
+
"""Print a big fat red warning if truecolor is not supported.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
console: Optional Rich Console instance. If None, creates a new one.
|
|
353
|
+
"""
|
|
354
|
+
if detect_truecolor_support():
|
|
355
|
+
return # All good, no warning needed
|
|
356
|
+
|
|
357
|
+
if console is None:
|
|
358
|
+
try:
|
|
359
|
+
from rich.console import Console
|
|
360
|
+
|
|
361
|
+
console = Console()
|
|
362
|
+
except ImportError:
|
|
363
|
+
# Rich not available, fall back to plain print
|
|
364
|
+
print("\n" + "=" * 70)
|
|
365
|
+
print("⚠️ WARNING: TERMINAL DOES NOT SUPPORT TRUECOLOR (24-BIT COLOR)")
|
|
366
|
+
print("=" * 70)
|
|
367
|
+
print("Code Puppy looks best with truecolor support.")
|
|
368
|
+
print("Consider using a modern terminal like:")
|
|
369
|
+
print(" • iTerm2 (macOS)")
|
|
370
|
+
print(" • Windows Terminal (Windows)")
|
|
371
|
+
print(" • Kitty, Alacritty, or any modern terminal emulator")
|
|
372
|
+
print("")
|
|
373
|
+
print("You can also try setting: export COLORTERM=truecolor")
|
|
374
|
+
print("")
|
|
375
|
+
print("Note: The built-in macOS Terminal.app does not support truecolor")
|
|
376
|
+
print("(Sequoia and earlier). You'll need a different terminal app.")
|
|
377
|
+
print("=" * 70 + "\n")
|
|
378
|
+
return
|
|
379
|
+
|
|
380
|
+
# Get detected color system for diagnostic info
|
|
381
|
+
color_system = console.color_system or "unknown"
|
|
382
|
+
|
|
383
|
+
# Build the warning box
|
|
384
|
+
warning_lines = [
|
|
385
|
+
"",
|
|
386
|
+
"[bold bright_red on red]" + "━" * 72 + "[/]",
|
|
387
|
+
"[bold bright_red on red]┃[/][bold bright_white on red]"
|
|
388
|
+
+ " " * 70
|
|
389
|
+
+ "[/][bold bright_red on red]┃[/]",
|
|
390
|
+
"[bold bright_red on red]┃[/][bold bright_white on red] ⚠️ WARNING: TERMINAL DOES NOT SUPPORT TRUECOLOR (24-BIT COLOR) ⚠️ [/][bold bright_red on red]┃[/]",
|
|
391
|
+
"[bold bright_red on red]┃[/][bold bright_white on red]"
|
|
392
|
+
+ " " * 70
|
|
393
|
+
+ "[/][bold bright_red on red]┃[/]",
|
|
394
|
+
"[bold bright_red on red]" + "━" * 72 + "[/]",
|
|
395
|
+
"",
|
|
396
|
+
f"[yellow]Detected color system:[/] [bold]{color_system}[/]",
|
|
397
|
+
"",
|
|
398
|
+
"[bold white]Code Puppy uses rich colors and will look degraded without truecolor.[/]",
|
|
399
|
+
"",
|
|
400
|
+
"[cyan]Consider using a modern terminal emulator:[/]",
|
|
401
|
+
" [green]•[/] [bold]iTerm2[/] (macOS) - https://iterm2.com",
|
|
402
|
+
" [green]•[/] [bold]Windows Terminal[/] (Windows) - Built into Windows 11",
|
|
403
|
+
" [green]•[/] [bold]Kitty[/] - https://sw.kovidgoyal.net/kitty",
|
|
404
|
+
" [green]•[/] [bold]Alacritty[/] - https://alacritty.org",
|
|
405
|
+
" [green]•[/] [bold]Warp[/] (macOS) - https://warp.dev",
|
|
406
|
+
"",
|
|
407
|
+
"[cyan]Or try setting the COLORTERM environment variable:[/]",
|
|
408
|
+
" [dim]export COLORTERM=truecolor[/]",
|
|
409
|
+
"",
|
|
410
|
+
"[dim italic]Note: The built-in macOS Terminal.app does not support truecolor (Sequoia and earlier).[/]",
|
|
411
|
+
"[dim italic]Setting COLORTERM=truecolor won't help - you'll need a different terminal app.[/]",
|
|
412
|
+
"",
|
|
413
|
+
"[bold bright_red]" + "─" * 72 + "[/]",
|
|
414
|
+
"",
|
|
415
|
+
]
|
|
416
|
+
|
|
417
|
+
for line in warning_lines:
|
|
418
|
+
console.print(line)
|
|
@@ -9,7 +9,7 @@ import threading
|
|
|
9
9
|
import time
|
|
10
10
|
import traceback
|
|
11
11
|
from contextlib import contextmanager
|
|
12
|
-
from typing import Callable, Literal, Optional, Set
|
|
12
|
+
from typing import Callable, List, Literal, Optional, Set
|
|
13
13
|
|
|
14
14
|
from pydantic import BaseModel
|
|
15
15
|
from pydantic_ai import RunContext
|
|
@@ -192,11 +192,6 @@ def kill_all_running_shell_processes() -> int:
|
|
|
192
192
|
"""Kill all currently tracked running shell processes and stop reader threads.
|
|
193
193
|
|
|
194
194
|
Returns the number of processes signaled.
|
|
195
|
-
|
|
196
|
-
Implementation notes:
|
|
197
|
-
- Atomically snapshot and clear the registry to prevent race conditions
|
|
198
|
-
- Deduplicate by PID to ensure each process is killed at most once
|
|
199
|
-
- Let exceptions from _kill_process_group propagate (tests expect this)
|
|
200
195
|
"""
|
|
201
196
|
global _READER_STOP_EVENT
|
|
202
197
|
|
|
@@ -204,52 +199,30 @@ def kill_all_running_shell_processes() -> int:
|
|
|
204
199
|
if _READER_STOP_EVENT:
|
|
205
200
|
_READER_STOP_EVENT.set()
|
|
206
201
|
|
|
207
|
-
|
|
208
|
-
# This prevents other threads from seeing/processing the same processes
|
|
202
|
+
procs: list[subprocess.Popen]
|
|
209
203
|
with _RUNNING_PROCESSES_LOCK:
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
# Deduplicate by pid to ensure at-most-one kill per process
|
|
214
|
-
seen_pids: set = set()
|
|
215
|
-
killed_count = 0
|
|
216
|
-
|
|
217
|
-
for proc in procs_snapshot:
|
|
218
|
-
if proc is None:
|
|
219
|
-
continue
|
|
220
|
-
|
|
221
|
-
pid = getattr(proc, "pid", None)
|
|
222
|
-
key = pid if pid is not None else id(proc)
|
|
223
|
-
|
|
224
|
-
if key in seen_pids:
|
|
225
|
-
continue
|
|
226
|
-
seen_pids.add(key)
|
|
227
|
-
|
|
228
|
-
# Close pipes first to unblock readline()
|
|
204
|
+
procs = list(_RUNNING_PROCESSES)
|
|
205
|
+
count = 0
|
|
206
|
+
for p in procs:
|
|
229
207
|
try:
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
if proc.poll() is None:
|
|
241
|
-
# Let exceptions bubble up (tests expect this behavior)
|
|
242
|
-
_kill_process_group(proc)
|
|
243
|
-
killed_count += 1
|
|
244
|
-
|
|
245
|
-
# Track user-killed PIDs
|
|
246
|
-
if pid is not None:
|
|
247
|
-
try:
|
|
248
|
-
_USER_KILLED_PROCESSES.add(pid)
|
|
249
|
-
except Exception:
|
|
250
|
-
pass # Non-fatal bookkeeping
|
|
208
|
+
# Close pipes first to unblock readline()
|
|
209
|
+
try:
|
|
210
|
+
if p.stdout and not p.stdout.closed:
|
|
211
|
+
p.stdout.close()
|
|
212
|
+
if p.stderr and not p.stderr.closed:
|
|
213
|
+
p.stderr.close()
|
|
214
|
+
if p.stdin and not p.stdin.closed:
|
|
215
|
+
p.stdin.close()
|
|
216
|
+
except (OSError, ValueError):
|
|
217
|
+
pass
|
|
251
218
|
|
|
252
|
-
|
|
219
|
+
if p.poll() is None:
|
|
220
|
+
_kill_process_group(p)
|
|
221
|
+
count += 1
|
|
222
|
+
_USER_KILLED_PROCESSES.add(p.pid)
|
|
223
|
+
finally:
|
|
224
|
+
_unregister_process(p)
|
|
225
|
+
return count
|
|
253
226
|
|
|
254
227
|
|
|
255
228
|
def get_running_shell_process_count() -> int:
|
|
@@ -907,6 +880,7 @@ async def run_shell_command(
|
|
|
907
880
|
command=command,
|
|
908
881
|
cwd=cwd,
|
|
909
882
|
timeout=0, # No timeout for background processes
|
|
883
|
+
background=True,
|
|
910
884
|
)
|
|
911
885
|
)
|
|
912
886
|
|
|
@@ -1104,12 +1078,21 @@ class ReasoningOutput(BaseModel):
|
|
|
1104
1078
|
|
|
1105
1079
|
|
|
1106
1080
|
def share_your_reasoning(
|
|
1107
|
-
context: RunContext, reasoning: str, next_steps: str | None = None
|
|
1081
|
+
context: RunContext, reasoning: str, next_steps: str | List[str] | None = None
|
|
1108
1082
|
) -> ReasoningOutput:
|
|
1083
|
+
# Handle list of next steps by formatting them
|
|
1084
|
+
formatted_next_steps = next_steps
|
|
1085
|
+
if isinstance(next_steps, list):
|
|
1086
|
+
formatted_next_steps = "\n".join(
|
|
1087
|
+
[f"{i + 1}. {step}" for i, step in enumerate(next_steps)]
|
|
1088
|
+
)
|
|
1089
|
+
|
|
1109
1090
|
# Emit structured AgentReasoningMessage for the UI
|
|
1110
1091
|
reasoning_msg = AgentReasoningMessage(
|
|
1111
1092
|
reasoning=reasoning,
|
|
1112
|
-
next_steps=
|
|
1093
|
+
next_steps=formatted_next_steps
|
|
1094
|
+
if formatted_next_steps and formatted_next_steps.strip()
|
|
1095
|
+
else None,
|
|
1113
1096
|
)
|
|
1114
1097
|
get_message_bus().emit(reasoning_msg)
|
|
1115
1098
|
|
|
@@ -1197,7 +1180,9 @@ def register_agent_share_your_reasoning(agent):
|
|
|
1197
1180
|
|
|
1198
1181
|
@agent.tool
|
|
1199
1182
|
def agent_share_your_reasoning(
|
|
1200
|
-
context: RunContext,
|
|
1183
|
+
context: RunContext,
|
|
1184
|
+
reasoning: str = "",
|
|
1185
|
+
next_steps: str | List[str] | None = None,
|
|
1201
1186
|
) -> ReasoningOutput:
|
|
1202
1187
|
"""Share the agent's current reasoning and planned next steps with the user.
|
|
1203
1188
|
|
|
@@ -1211,8 +1196,8 @@ def register_agent_share_your_reasoning(agent):
|
|
|
1211
1196
|
reasoning for the current situation. This should be clear,
|
|
1212
1197
|
comprehensive, and explain the 'why' behind decisions.
|
|
1213
1198
|
next_steps: Planned upcoming actions or steps
|
|
1214
|
-
the agent intends to take. Can be
|
|
1215
|
-
are determined. Defaults to None.
|
|
1199
|
+
the agent intends to take. Can be a string or a list of strings.
|
|
1200
|
+
Can be None if no specific next steps are determined. Defaults to None.
|
|
1216
1201
|
|
|
1217
1202
|
Returns:
|
|
1218
1203
|
ReasoningOutput: A simple response object containing:
|
|
@@ -1223,6 +1208,10 @@ def register_agent_share_your_reasoning(agent):
|
|
|
1223
1208
|
>>> next_steps = "First, I'll list the directory contents, then read key files"
|
|
1224
1209
|
>>> result = agent_share_your_reasoning(ctx, reasoning, next_steps)
|
|
1225
1210
|
|
|
1211
|
+
>>> # Using a list for next steps
|
|
1212
|
+
>>> next_steps_list = ["List files", "Read README.md", "Run tests"]
|
|
1213
|
+
>>> result = agent_share_your_reasoning(ctx, reasoning, next_steps_list)
|
|
1214
|
+
|
|
1226
1215
|
Best Practice:
|
|
1227
1216
|
Use this tool frequently to maintain transparency. Call it:
|
|
1228
1217
|
- Before starting complex operations
|
code_puppy/tools/common.py
CHANGED
|
@@ -727,15 +727,9 @@ def _format_diff_with_syntax_highlighting(
|
|
|
727
727
|
result.append("\n")
|
|
728
728
|
continue
|
|
729
729
|
|
|
730
|
-
#
|
|
731
|
-
if line.startswith("---"):
|
|
732
|
-
|
|
733
|
-
elif line.startswith("+++"):
|
|
734
|
-
result.append(line, style="yellow")
|
|
735
|
-
elif line.startswith("@@"):
|
|
736
|
-
result.append(line, style="cyan")
|
|
737
|
-
elif line.startswith(("diff ", "index ")):
|
|
738
|
-
result.append(line, style="dim")
|
|
730
|
+
# Skip diff headers - they're redundant noise since we show the filename in the banner
|
|
731
|
+
if line.startswith(("---", "+++", "@@", "diff ", "index ")):
|
|
732
|
+
continue
|
|
739
733
|
else:
|
|
740
734
|
# Determine line type and extract code content
|
|
741
735
|
if line.startswith("-"):
|