code-puppy 0.0.373__py3-none-any.whl → 0.0.375__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/agent_creator_agent.py +49 -1
- code_puppy/agents/agent_helios.py +122 -0
- code_puppy/agents/agent_manager.py +60 -4
- code_puppy/agents/base_agent.py +61 -4
- code_puppy/agents/json_agent.py +30 -7
- code_puppy/callbacks.py +125 -0
- code_puppy/command_line/colors_menu.py +2 -0
- code_puppy/command_line/command_handler.py +1 -0
- code_puppy/command_line/config_commands.py +3 -1
- code_puppy/command_line/uc_menu.py +890 -0
- code_puppy/config.py +29 -0
- code_puppy/messaging/messages.py +18 -0
- code_puppy/messaging/rich_renderer.py +48 -7
- code_puppy/messaging/subagent_console.py +0 -1
- code_puppy/model_factory.py +63 -258
- code_puppy/model_utils.py +33 -1
- code_puppy/plugins/antigravity_oauth/register_callbacks.py +106 -1
- code_puppy/plugins/antigravity_oauth/utils.py +2 -3
- code_puppy/plugins/chatgpt_oauth/register_callbacks.py +85 -3
- code_puppy/plugins/claude_code_oauth/register_callbacks.py +88 -0
- code_puppy/plugins/ralph/__init__.py +13 -0
- code_puppy/plugins/ralph/agents.py +433 -0
- code_puppy/plugins/ralph/commands.py +208 -0
- code_puppy/plugins/ralph/loop_controller.py +285 -0
- code_puppy/plugins/ralph/models.py +125 -0
- code_puppy/plugins/ralph/register_callbacks.py +133 -0
- code_puppy/plugins/ralph/state_manager.py +322 -0
- code_puppy/plugins/ralph/tools.py +451 -0
- code_puppy/plugins/universal_constructor/__init__.py +13 -0
- code_puppy/plugins/universal_constructor/models.py +138 -0
- code_puppy/plugins/universal_constructor/register_callbacks.py +47 -0
- code_puppy/plugins/universal_constructor/registry.py +304 -0
- code_puppy/plugins/universal_constructor/sandbox.py +584 -0
- code_puppy/tools/__init__.py +169 -1
- code_puppy/tools/agent_tools.py +1 -1
- code_puppy/tools/command_runner.py +23 -9
- code_puppy/tools/universal_constructor.py +889 -0
- {code_puppy-0.0.373.dist-info → code_puppy-0.0.375.dist-info}/METADATA +1 -1
- {code_puppy-0.0.373.dist-info → code_puppy-0.0.375.dist-info}/RECORD +44 -28
- {code_puppy-0.0.373.data → code_puppy-0.0.375.data}/data/code_puppy/models.json +0 -0
- {code_puppy-0.0.373.data → code_puppy-0.0.375.data}/data/code_puppy/models_dev_api.json +0 -0
- {code_puppy-0.0.373.dist-info → code_puppy-0.0.375.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.373.dist-info → code_puppy-0.0.375.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.373.dist-info → code_puppy-0.0.375.dist-info}/licenses/LICENSE +0 -0
code_puppy/tools/__init__.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from code_puppy.callbacks import on_register_tools
|
|
1
2
|
from code_puppy.messaging import emit_warning
|
|
2
3
|
from code_puppy.tools.agent_tools import register_invoke_agent, register_list_agents
|
|
3
4
|
|
|
@@ -87,6 +88,7 @@ from code_puppy.tools.file_operations import (
|
|
|
87
88
|
register_list_files,
|
|
88
89
|
register_read_file,
|
|
89
90
|
)
|
|
91
|
+
from code_puppy.tools.universal_constructor import register_universal_constructor
|
|
90
92
|
|
|
91
93
|
# Map of tool names to their individual registration functions
|
|
92
94
|
TOOL_REGISTRY = {
|
|
@@ -163,27 +165,192 @@ TOOL_REGISTRY = {
|
|
|
163
165
|
"terminal_read_output": register_terminal_read_output,
|
|
164
166
|
"terminal_compare_mockup": register_terminal_compare_mockup,
|
|
165
167
|
"load_image_for_analysis": register_load_image,
|
|
168
|
+
# Universal Constructor
|
|
169
|
+
"universal_constructor": register_universal_constructor,
|
|
166
170
|
}
|
|
167
171
|
|
|
168
172
|
|
|
173
|
+
def _load_plugin_tools() -> None:
|
|
174
|
+
"""Load tools registered by plugins via the register_tools callback.
|
|
175
|
+
|
|
176
|
+
This merges plugin-provided tools into the TOOL_REGISTRY.
|
|
177
|
+
Called lazily when tools are first accessed.
|
|
178
|
+
"""
|
|
179
|
+
try:
|
|
180
|
+
results = on_register_tools()
|
|
181
|
+
for result in results:
|
|
182
|
+
if result is None:
|
|
183
|
+
continue
|
|
184
|
+
# Each result should be a list of tool definitions
|
|
185
|
+
tools_list = result if isinstance(result, list) else [result]
|
|
186
|
+
for tool_def in tools_list:
|
|
187
|
+
if (
|
|
188
|
+
isinstance(tool_def, dict)
|
|
189
|
+
and "name" in tool_def
|
|
190
|
+
and "register_func" in tool_def
|
|
191
|
+
):
|
|
192
|
+
tool_name = tool_def["name"]
|
|
193
|
+
register_func = tool_def["register_func"]
|
|
194
|
+
if callable(register_func):
|
|
195
|
+
TOOL_REGISTRY[tool_name] = register_func
|
|
196
|
+
except Exception:
|
|
197
|
+
# Don't let plugin failures break core functionality
|
|
198
|
+
pass
|
|
199
|
+
|
|
200
|
+
|
|
169
201
|
def register_tools_for_agent(agent, tool_names: list[str]):
|
|
170
202
|
"""Register specific tools for an agent based on tool names.
|
|
171
203
|
|
|
172
204
|
Args:
|
|
173
205
|
agent: The agent to register tools to.
|
|
174
|
-
tool_names: List of tool names to register.
|
|
206
|
+
tool_names: List of tool names to register. UC tools are prefixed with "uc:".
|
|
175
207
|
"""
|
|
208
|
+
from code_puppy.config import get_universal_constructor_enabled
|
|
209
|
+
|
|
210
|
+
_load_plugin_tools()
|
|
176
211
|
for tool_name in tool_names:
|
|
212
|
+
# Handle UC tools (prefixed with "uc:")
|
|
213
|
+
if tool_name.startswith("uc:"):
|
|
214
|
+
# Skip UC tools if UC is disabled
|
|
215
|
+
if not get_universal_constructor_enabled():
|
|
216
|
+
continue
|
|
217
|
+
uc_tool_name = tool_name[3:] # Remove "uc:" prefix
|
|
218
|
+
_register_uc_tool_wrapper(agent, uc_tool_name)
|
|
219
|
+
continue
|
|
220
|
+
|
|
177
221
|
if tool_name not in TOOL_REGISTRY:
|
|
178
222
|
# Skip unknown tools with a warning instead of failing
|
|
179
223
|
emit_warning(f"Warning: Unknown tool '{tool_name}' requested, skipping...")
|
|
180
224
|
continue
|
|
181
225
|
|
|
226
|
+
# Check if Universal Constructor is disabled
|
|
227
|
+
if (
|
|
228
|
+
tool_name == "universal_constructor"
|
|
229
|
+
and not get_universal_constructor_enabled()
|
|
230
|
+
):
|
|
231
|
+
continue # Skip UC if disabled in config
|
|
232
|
+
|
|
182
233
|
# Register the individual tool
|
|
183
234
|
register_func = TOOL_REGISTRY[tool_name]
|
|
184
235
|
register_func(agent)
|
|
185
236
|
|
|
186
237
|
|
|
238
|
+
def _register_uc_tool_wrapper(agent, uc_tool_name: str):
|
|
239
|
+
"""Register a wrapper for a UC tool that calls it via the UC registry.
|
|
240
|
+
|
|
241
|
+
This creates a dynamic tool that wraps the UC tool, preserving its
|
|
242
|
+
parameter signature so pydantic-ai can generate proper JSON schema.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
agent: The agent to register the tool wrapper to.
|
|
246
|
+
uc_tool_name: The full name of the UC tool (e.g., "api.weather").
|
|
247
|
+
"""
|
|
248
|
+
import inspect
|
|
249
|
+
from typing import Any
|
|
250
|
+
|
|
251
|
+
from pydantic_ai import RunContext
|
|
252
|
+
|
|
253
|
+
# Get tool info and function from registry
|
|
254
|
+
try:
|
|
255
|
+
from code_puppy.plugins.universal_constructor.registry import get_registry
|
|
256
|
+
|
|
257
|
+
registry = get_registry()
|
|
258
|
+
tool_info = registry.get_tool(uc_tool_name)
|
|
259
|
+
if not tool_info:
|
|
260
|
+
emit_warning(f"Warning: UC tool '{uc_tool_name}' not found, skipping...")
|
|
261
|
+
return
|
|
262
|
+
|
|
263
|
+
func = registry.get_tool_function(uc_tool_name)
|
|
264
|
+
if not func:
|
|
265
|
+
emit_warning(
|
|
266
|
+
f"Warning: UC tool '{uc_tool_name}' function not found, skipping..."
|
|
267
|
+
)
|
|
268
|
+
return
|
|
269
|
+
|
|
270
|
+
description = tool_info.meta.description
|
|
271
|
+
docstring = tool_info.docstring or description
|
|
272
|
+
except Exception as e:
|
|
273
|
+
emit_warning(f"Warning: Failed to get UC tool '{uc_tool_name}' info: {e}")
|
|
274
|
+
return
|
|
275
|
+
|
|
276
|
+
# Get the original function's signature
|
|
277
|
+
try:
|
|
278
|
+
sig = inspect.signature(func)
|
|
279
|
+
# Get annotations from the original function
|
|
280
|
+
annotations = getattr(func, "__annotations__", {}).copy()
|
|
281
|
+
except (ValueError, TypeError):
|
|
282
|
+
sig = None
|
|
283
|
+
annotations = {}
|
|
284
|
+
|
|
285
|
+
# Create wrapper that preserves the signature
|
|
286
|
+
def make_uc_wrapper(
|
|
287
|
+
tool_name: str, original_func, original_sig, original_annotations
|
|
288
|
+
):
|
|
289
|
+
# Build the wrapper function
|
|
290
|
+
async def uc_tool_wrapper(context: RunContext, **kwargs: Any) -> Any:
|
|
291
|
+
"""Dynamically generated wrapper for a UC tool."""
|
|
292
|
+
try:
|
|
293
|
+
result = original_func(**kwargs)
|
|
294
|
+
# Await async tool implementations
|
|
295
|
+
if inspect.isawaitable(result):
|
|
296
|
+
result = await result
|
|
297
|
+
return result
|
|
298
|
+
except Exception as e:
|
|
299
|
+
return {"error": f"UC tool '{tool_name}' failed: {e}"}
|
|
300
|
+
|
|
301
|
+
# Copy signature info from original function
|
|
302
|
+
uc_tool_wrapper.__name__ = tool_name.replace(".", "_")
|
|
303
|
+
uc_tool_wrapper.__doc__ = (
|
|
304
|
+
f"{docstring}\n\nThis is a Universal Constructor tool."
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
# Preserve annotations for pydantic-ai schema generation
|
|
308
|
+
if original_annotations:
|
|
309
|
+
# Add 'context' param and copy original params (excluding 'return')
|
|
310
|
+
new_annotations = {"context": RunContext}
|
|
311
|
+
for param_name, param_type in original_annotations.items():
|
|
312
|
+
if param_name != "return":
|
|
313
|
+
new_annotations[param_name] = param_type
|
|
314
|
+
if "return" in original_annotations:
|
|
315
|
+
new_annotations["return"] = original_annotations["return"]
|
|
316
|
+
else:
|
|
317
|
+
new_annotations["return"] = Any
|
|
318
|
+
uc_tool_wrapper.__annotations__ = new_annotations
|
|
319
|
+
|
|
320
|
+
# Try to set __signature__ for better introspection
|
|
321
|
+
if original_sig:
|
|
322
|
+
try:
|
|
323
|
+
# Build new parameters list: context first, then original params
|
|
324
|
+
new_params = [
|
|
325
|
+
inspect.Parameter(
|
|
326
|
+
"context",
|
|
327
|
+
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
|
328
|
+
annotation=RunContext,
|
|
329
|
+
)
|
|
330
|
+
]
|
|
331
|
+
for param in original_sig.parameters.values():
|
|
332
|
+
new_params.append(param)
|
|
333
|
+
|
|
334
|
+
# Create new signature with return annotation
|
|
335
|
+
return_annotation = original_annotations.get("return", Any)
|
|
336
|
+
new_sig = original_sig.replace(
|
|
337
|
+
parameters=new_params, return_annotation=return_annotation
|
|
338
|
+
)
|
|
339
|
+
uc_tool_wrapper.__signature__ = new_sig
|
|
340
|
+
except (ValueError, TypeError):
|
|
341
|
+
pass # Signature manipulation failed, continue without it
|
|
342
|
+
|
|
343
|
+
return uc_tool_wrapper
|
|
344
|
+
|
|
345
|
+
wrapper = make_uc_wrapper(uc_tool_name, func, sig, annotations)
|
|
346
|
+
|
|
347
|
+
# Register the wrapper as a tool
|
|
348
|
+
try:
|
|
349
|
+
agent.tool(wrapper)
|
|
350
|
+
except Exception as e:
|
|
351
|
+
emit_warning(f"Warning: Failed to register UC tool '{uc_tool_name}': {e}")
|
|
352
|
+
|
|
353
|
+
|
|
187
354
|
def register_all_tools(agent):
|
|
188
355
|
"""Register all available tools to the provided agent.
|
|
189
356
|
|
|
@@ -200,4 +367,5 @@ def get_available_tool_names() -> list[str]:
|
|
|
200
367
|
Returns:
|
|
201
368
|
List of all tool names that can be registered.
|
|
202
369
|
"""
|
|
370
|
+
_load_plugin_tools()
|
|
203
371
|
return list(TOOL_REGISTRY.keys())
|
code_puppy/tools/agent_tools.py
CHANGED
|
@@ -469,7 +469,7 @@ def register_invoke_agent(agent):
|
|
|
469
469
|
model = ModelFactory.get_model(model_name, models_config)
|
|
470
470
|
|
|
471
471
|
# Create a temporary agent instance to avoid interfering with current agent state
|
|
472
|
-
instructions = agent_config.
|
|
472
|
+
instructions = agent_config.get_full_system_prompt()
|
|
473
473
|
|
|
474
474
|
# Add AGENTS.md content to subagents
|
|
475
475
|
puppy_rules = agent_config.load_puppy_rules()
|
|
@@ -647,7 +647,7 @@ def run_shell_command_streaming(
|
|
|
647
647
|
line = process.stdout.readline()
|
|
648
648
|
if not line: # EOF
|
|
649
649
|
break
|
|
650
|
-
line = line.rstrip("\n
|
|
650
|
+
line = line.rstrip("\n")
|
|
651
651
|
line = _truncate_line(line)
|
|
652
652
|
stdout_lines.append(line)
|
|
653
653
|
if not silent:
|
|
@@ -660,7 +660,7 @@ def run_shell_command_streaming(
|
|
|
660
660
|
try:
|
|
661
661
|
remaining = process.stdout.read()
|
|
662
662
|
if remaining:
|
|
663
|
-
for line in remaining.
|
|
663
|
+
for line in remaining.split("\n"):
|
|
664
664
|
line = _truncate_line(line)
|
|
665
665
|
stdout_lines.append(line)
|
|
666
666
|
if not silent:
|
|
@@ -683,7 +683,7 @@ def run_shell_command_streaming(
|
|
|
683
683
|
line = process.stdout.readline()
|
|
684
684
|
if not line: # EOF
|
|
685
685
|
break
|
|
686
|
-
line = line.rstrip("\n
|
|
686
|
+
line = line.rstrip("\n")
|
|
687
687
|
line = _truncate_line(line)
|
|
688
688
|
stdout_lines.append(line)
|
|
689
689
|
if not silent:
|
|
@@ -716,7 +716,7 @@ def run_shell_command_streaming(
|
|
|
716
716
|
line = process.stderr.readline()
|
|
717
717
|
if not line: # EOF
|
|
718
718
|
break
|
|
719
|
-
line = line.rstrip("\n
|
|
719
|
+
line = line.rstrip("\n")
|
|
720
720
|
line = _truncate_line(line)
|
|
721
721
|
stderr_lines.append(line)
|
|
722
722
|
if not silent:
|
|
@@ -729,7 +729,7 @@ def run_shell_command_streaming(
|
|
|
729
729
|
try:
|
|
730
730
|
remaining = process.stderr.read()
|
|
731
731
|
if remaining:
|
|
732
|
-
for line in remaining.
|
|
732
|
+
for line in remaining.split("\n"):
|
|
733
733
|
line = _truncate_line(line)
|
|
734
734
|
stderr_lines.append(line)
|
|
735
735
|
if not silent:
|
|
@@ -751,7 +751,7 @@ def run_shell_command_streaming(
|
|
|
751
751
|
line = process.stderr.readline()
|
|
752
752
|
if not line: # EOF
|
|
753
753
|
break
|
|
754
|
-
line = line.rstrip("\n
|
|
754
|
+
line = line.rstrip("\n")
|
|
755
755
|
line = _truncate_line(line)
|
|
756
756
|
stderr_lines.append(line)
|
|
757
757
|
if not silent:
|
|
@@ -1165,6 +1165,11 @@ async def _execute_shell_command(
|
|
|
1165
1165
|
)
|
|
1166
1166
|
)
|
|
1167
1167
|
|
|
1168
|
+
# Pause spinner during shell command so \r output can work properly
|
|
1169
|
+
from code_puppy.messaging.spinner import pause_all_spinners, resume_all_spinners
|
|
1170
|
+
|
|
1171
|
+
pause_all_spinners()
|
|
1172
|
+
|
|
1168
1173
|
# Acquire shared keyboard context - Ctrl-X/Ctrl-C will kill ALL running commands
|
|
1169
1174
|
# This is reference-counted: listener starts on first command, stops on last
|
|
1170
1175
|
_acquire_keyboard_context()
|
|
@@ -1172,6 +1177,7 @@ async def _execute_shell_command(
|
|
|
1172
1177
|
return await _run_command_inner(command, cwd, timeout, group_id, silent=silent)
|
|
1173
1178
|
finally:
|
|
1174
1179
|
_release_keyboard_context()
|
|
1180
|
+
resume_all_spinners()
|
|
1175
1181
|
|
|
1176
1182
|
|
|
1177
1183
|
def _run_command_sync(
|
|
@@ -1192,18 +1198,26 @@ def _run_command_sync(
|
|
|
1192
1198
|
else:
|
|
1193
1199
|
preexec_fn = os.setsid if hasattr(os, "setsid") else None
|
|
1194
1200
|
|
|
1201
|
+
import io
|
|
1202
|
+
|
|
1195
1203
|
process = subprocess.Popen(
|
|
1196
1204
|
command,
|
|
1197
1205
|
shell=True,
|
|
1198
1206
|
stdout=subprocess.PIPE,
|
|
1199
1207
|
stderr=subprocess.PIPE,
|
|
1200
|
-
text=True,
|
|
1201
1208
|
cwd=cwd,
|
|
1202
|
-
bufsize=
|
|
1203
|
-
universal_newlines=True,
|
|
1209
|
+
bufsize=0, # Unbuffered for real-time output
|
|
1204
1210
|
preexec_fn=preexec_fn,
|
|
1205
1211
|
creationflags=creationflags,
|
|
1206
1212
|
)
|
|
1213
|
+
|
|
1214
|
+
# Wrap pipes with TextIOWrapper that preserves \r (newline='' disables translation)
|
|
1215
|
+
process.stdout = io.TextIOWrapper(
|
|
1216
|
+
process.stdout, newline="", encoding="utf-8", errors="replace"
|
|
1217
|
+
)
|
|
1218
|
+
process.stderr = io.TextIOWrapper(
|
|
1219
|
+
process.stderr, newline="", encoding="utf-8", errors="replace"
|
|
1220
|
+
)
|
|
1207
1221
|
_register_process(process)
|
|
1208
1222
|
try:
|
|
1209
1223
|
return run_shell_command_streaming(
|