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.
Files changed (44) hide show
  1. code_puppy/agents/agent_creator_agent.py +49 -1
  2. code_puppy/agents/agent_helios.py +122 -0
  3. code_puppy/agents/agent_manager.py +60 -4
  4. code_puppy/agents/base_agent.py +61 -4
  5. code_puppy/agents/json_agent.py +30 -7
  6. code_puppy/callbacks.py +125 -0
  7. code_puppy/command_line/colors_menu.py +2 -0
  8. code_puppy/command_line/command_handler.py +1 -0
  9. code_puppy/command_line/config_commands.py +3 -1
  10. code_puppy/command_line/uc_menu.py +890 -0
  11. code_puppy/config.py +29 -0
  12. code_puppy/messaging/messages.py +18 -0
  13. code_puppy/messaging/rich_renderer.py +48 -7
  14. code_puppy/messaging/subagent_console.py +0 -1
  15. code_puppy/model_factory.py +63 -258
  16. code_puppy/model_utils.py +33 -1
  17. code_puppy/plugins/antigravity_oauth/register_callbacks.py +106 -1
  18. code_puppy/plugins/antigravity_oauth/utils.py +2 -3
  19. code_puppy/plugins/chatgpt_oauth/register_callbacks.py +85 -3
  20. code_puppy/plugins/claude_code_oauth/register_callbacks.py +88 -0
  21. code_puppy/plugins/ralph/__init__.py +13 -0
  22. code_puppy/plugins/ralph/agents.py +433 -0
  23. code_puppy/plugins/ralph/commands.py +208 -0
  24. code_puppy/plugins/ralph/loop_controller.py +285 -0
  25. code_puppy/plugins/ralph/models.py +125 -0
  26. code_puppy/plugins/ralph/register_callbacks.py +133 -0
  27. code_puppy/plugins/ralph/state_manager.py +322 -0
  28. code_puppy/plugins/ralph/tools.py +451 -0
  29. code_puppy/plugins/universal_constructor/__init__.py +13 -0
  30. code_puppy/plugins/universal_constructor/models.py +138 -0
  31. code_puppy/plugins/universal_constructor/register_callbacks.py +47 -0
  32. code_puppy/plugins/universal_constructor/registry.py +304 -0
  33. code_puppy/plugins/universal_constructor/sandbox.py +584 -0
  34. code_puppy/tools/__init__.py +169 -1
  35. code_puppy/tools/agent_tools.py +1 -1
  36. code_puppy/tools/command_runner.py +23 -9
  37. code_puppy/tools/universal_constructor.py +889 -0
  38. {code_puppy-0.0.373.dist-info → code_puppy-0.0.375.dist-info}/METADATA +1 -1
  39. {code_puppy-0.0.373.dist-info → code_puppy-0.0.375.dist-info}/RECORD +44 -28
  40. {code_puppy-0.0.373.data → code_puppy-0.0.375.data}/data/code_puppy/models.json +0 -0
  41. {code_puppy-0.0.373.data → code_puppy-0.0.375.data}/data/code_puppy/models_dev_api.json +0 -0
  42. {code_puppy-0.0.373.dist-info → code_puppy-0.0.375.dist-info}/WHEEL +0 -0
  43. {code_puppy-0.0.373.dist-info → code_puppy-0.0.375.dist-info}/entry_points.txt +0 -0
  44. {code_puppy-0.0.373.dist-info → code_puppy-0.0.375.dist-info}/licenses/LICENSE +0 -0
@@ -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())
@@ -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.get_system_prompt()
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\r")
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.splitlines():
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\r")
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\r")
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.splitlines():
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\r")
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=1,
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(