code-puppy 0.0.361__py3-none-any.whl → 0.0.373__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 (43) hide show
  1. code_puppy/agents/__init__.py +6 -0
  2. code_puppy/agents/agent_manager.py +223 -1
  3. code_puppy/agents/base_agent.py +2 -12
  4. code_puppy/chatgpt_codex_client.py +53 -0
  5. code_puppy/claude_cache_client.py +51 -13
  6. code_puppy/command_line/add_model_menu.py +13 -4
  7. code_puppy/command_line/agent_menu.py +662 -0
  8. code_puppy/command_line/core_commands.py +4 -112
  9. code_puppy/command_line/model_picker_completion.py +3 -20
  10. code_puppy/command_line/model_settings_menu.py +21 -3
  11. code_puppy/config.py +79 -8
  12. code_puppy/gemini_model.py +706 -0
  13. code_puppy/http_utils.py +6 -3
  14. code_puppy/model_factory.py +50 -16
  15. code_puppy/model_switching.py +63 -0
  16. code_puppy/model_utils.py +1 -52
  17. code_puppy/models.json +12 -12
  18. code_puppy/plugins/antigravity_oauth/antigravity_model.py +128 -165
  19. code_puppy/plugins/antigravity_oauth/register_callbacks.py +15 -8
  20. code_puppy/plugins/antigravity_oauth/transport.py +235 -45
  21. code_puppy/plugins/chatgpt_oauth/register_callbacks.py +2 -2
  22. code_puppy/plugins/claude_code_oauth/README.md +1 -1
  23. code_puppy/plugins/claude_code_oauth/SETUP.md +1 -1
  24. code_puppy/plugins/claude_code_oauth/register_callbacks.py +2 -30
  25. code_puppy/plugins/claude_code_oauth/utils.py +44 -10
  26. code_puppy/pydantic_patches.py +52 -0
  27. code_puppy/tools/agent_tools.py +3 -3
  28. code_puppy/tools/browser/__init__.py +1 -1
  29. code_puppy/tools/browser/browser_control.py +1 -1
  30. code_puppy/tools/browser/browser_interactions.py +1 -1
  31. code_puppy/tools/browser/browser_locators.py +1 -1
  32. code_puppy/tools/browser/{camoufox_manager.py → browser_manager.py} +29 -110
  33. code_puppy/tools/browser/browser_navigation.py +1 -1
  34. code_puppy/tools/browser/browser_screenshot.py +1 -1
  35. code_puppy/tools/browser/browser_scripts.py +1 -1
  36. {code_puppy-0.0.361.data → code_puppy-0.0.373.data}/data/code_puppy/models.json +12 -12
  37. {code_puppy-0.0.361.dist-info → code_puppy-0.0.373.dist-info}/METADATA +5 -6
  38. {code_puppy-0.0.361.dist-info → code_puppy-0.0.373.dist-info}/RECORD +42 -40
  39. code_puppy/prompts/codex_system_prompt.md +0 -310
  40. {code_puppy-0.0.361.data → code_puppy-0.0.373.data}/data/code_puppy/models_dev_api.json +0 -0
  41. {code_puppy-0.0.361.dist-info → code_puppy-0.0.373.dist-info}/WHEEL +0 -0
  42. {code_puppy-0.0.361.dist-info → code_puppy-0.0.373.dist-info}/entry_points.txt +0 -0
  43. {code_puppy-0.0.361.dist-info → code_puppy-0.0.373.dist-info}/licenses/LICENSE +0 -0
@@ -6,6 +6,7 @@ discovered by the command registry system.
6
6
 
7
7
  import os
8
8
 
9
+ from code_puppy.command_line.agent_menu import interactive_agent_picker
9
10
  from code_puppy.command_line.command_registry import register_command
10
11
  from code_puppy.command_line.model_picker_completion import update_model_in_input
11
12
  from code_puppy.command_line.motd import print_motd
@@ -168,7 +169,7 @@ def handle_tutorial_command(command: str) -> bool:
168
169
  reset_onboarding,
169
170
  run_onboarding_wizard,
170
171
  )
171
- from code_puppy.config import set_model_name
172
+ from code_puppy.model_switching import set_model_and_reload_agent
172
173
 
173
174
  # Always reset so user can re-run the tutorial anytime
174
175
  reset_onboarding()
@@ -183,7 +184,7 @@ def handle_tutorial_command(command: str) -> bool:
183
184
  from code_puppy.plugins.chatgpt_oauth.oauth_flow import run_oauth_flow
184
185
 
185
186
  run_oauth_flow()
186
- set_model_name("chatgpt-gpt-5.2-codex")
187
+ set_model_and_reload_agent("chatgpt-gpt-5.2-codex")
187
188
  elif result == "claude":
188
189
  emit_info("🔐 Starting Claude Code OAuth flow...")
189
190
  from code_puppy.plugins.claude_code_oauth.register_callbacks import (
@@ -191,7 +192,7 @@ def handle_tutorial_command(command: str) -> bool:
191
192
  )
192
193
 
193
194
  _perform_authentication()
194
- set_model_name("claude-code-claude-opus-4-5-20251101")
195
+ set_model_and_reload_agent("claude-code-claude-opus-4-5-20251101")
195
196
  elif result == "completed":
196
197
  emit_info("🎉 Tutorial complete! Happy coding!")
197
198
  elif result == "skipped":
@@ -398,115 +399,6 @@ def handle_agent_command(command: str) -> bool:
398
399
  return True
399
400
 
400
401
 
401
- async def interactive_agent_picker() -> str | None:
402
- """Show an interactive arrow-key selector to pick an agent (async version).
403
-
404
- Returns:
405
- The selected agent name, or None if cancelled
406
- """
407
- import sys
408
- import time
409
-
410
- from rich.console import Console
411
- from rich.panel import Panel
412
- from rich.text import Text
413
-
414
- from code_puppy.agents import (
415
- get_agent_descriptions,
416
- get_available_agents,
417
- get_current_agent,
418
- )
419
- from code_puppy.tools.command_runner import set_awaiting_user_input
420
- from code_puppy.tools.common import arrow_select_async
421
-
422
- # Load available agents
423
- available_agents = get_available_agents()
424
- descriptions = get_agent_descriptions()
425
- current_agent = get_current_agent()
426
-
427
- # Build choices with current agent indicator and keep track of agent names
428
- choices = []
429
- agent_names = list(available_agents.keys())
430
- for agent_name in agent_names:
431
- display_name = available_agents[agent_name]
432
- if agent_name == current_agent.name:
433
- choices.append(f"✓ {agent_name} - {display_name} (current)")
434
- else:
435
- choices.append(f" {agent_name} - {display_name}")
436
-
437
- # Create preview callback to show agent description
438
- def get_preview(index: int) -> str:
439
- """Get the description for the agent at the given index."""
440
- agent_name = agent_names[index]
441
- description = descriptions.get(agent_name, "No description available")
442
- return description
443
-
444
- # Create panel content
445
- panel_content = Text()
446
- panel_content.append("🐶 Select an agent to use\n", style="bold cyan")
447
- panel_content.append("Current agent: ", style="dim")
448
- panel_content.append(f"{current_agent.name}", style="bold green")
449
- panel_content.append(" - ", style="dim")
450
- panel_content.append(current_agent.display_name, style="bold green")
451
- panel_content.append("\n", style="dim")
452
- panel_content.append(current_agent.description, style="dim italic")
453
-
454
- # Display panel
455
- panel = Panel(
456
- panel_content,
457
- title="[bold white]Agent Selection[/bold white]",
458
- border_style="cyan",
459
- padding=(1, 2),
460
- )
461
-
462
- # Pause spinners BEFORE showing panel
463
- set_awaiting_user_input(True)
464
- time.sleep(0.3) # Let spinners fully stop
465
-
466
- local_console = Console()
467
- emit_info("")
468
- local_console.print(panel)
469
- emit_info("")
470
-
471
- # Flush output before prompt_toolkit takes control
472
- sys.stdout.flush()
473
- sys.stderr.flush()
474
- time.sleep(0.1)
475
-
476
- selected_agent = None
477
-
478
- try:
479
- # Final flush
480
- sys.stdout.flush()
481
-
482
- # Show arrow-key selector with preview (async version)
483
- choice = await arrow_select_async(
484
- "💭 Which agent would you like to use?",
485
- choices,
486
- preview_callback=get_preview,
487
- )
488
-
489
- # Extract agent name from choice (remove prefix and suffix)
490
- if choice:
491
- # Remove the "✓ " or " " prefix and extract agent name (before " - ")
492
- choice_stripped = choice.strip().lstrip("✓").strip()
493
- # Split on " - " and take the first part (agent name)
494
- agent_name = choice_stripped.split(" - ")[0].strip()
495
- # Remove " (current)" suffix if present
496
- if agent_name.endswith(" (current)"):
497
- agent_name = agent_name[:-10].strip()
498
- selected_agent = agent_name
499
-
500
- except (KeyboardInterrupt, EOFError):
501
- emit_error("Cancelled by user")
502
- selected_agent = None
503
-
504
- finally:
505
- set_awaiting_user_input(False)
506
-
507
- return selected_agent
508
-
509
-
510
402
  async def interactive_model_picker() -> str | None:
511
403
  """Show an interactive arrow-key selector to pick a model (async version).
512
404
 
@@ -6,8 +6,9 @@ from prompt_toolkit.completion import Completer, Completion
6
6
  from prompt_toolkit.document import Document
7
7
  from prompt_toolkit.history import FileHistory
8
8
 
9
- from code_puppy.config import get_global_model_name, set_model_name
9
+ from code_puppy.config import get_global_model_name
10
10
  from code_puppy.model_factory import ModelFactory
11
+ from code_puppy.model_switching import set_model_and_reload_agent
11
12
 
12
13
 
13
14
  def load_model_names():
@@ -28,25 +29,7 @@ def set_active_model(model_name: str):
28
29
  """
29
30
  Sets the active model name by updating the config (for persistence).
30
31
  """
31
- from code_puppy.messaging import emit_info, emit_warning
32
-
33
- set_model_name(model_name)
34
- # Reload the currently active agent so the new model takes effect immediately
35
- try:
36
- from code_puppy.agents import get_current_agent
37
-
38
- current_agent = get_current_agent()
39
- # JSON agents may need to refresh their config before reload
40
- if hasattr(current_agent, "refresh_config"):
41
- try:
42
- current_agent.refresh_config()
43
- except Exception:
44
- # Non-fatal, continue to reload
45
- ...
46
- current_agent.reload_code_generation_agent()
47
- emit_info("Active agent reloaded")
48
- except Exception as e:
49
- emit_warning(f"Model changed but agent reload failed: {e}")
32
+ set_model_and_reload_agent(model_name)
50
33
 
51
34
 
52
35
  class ModelNameCompleter(Completer):
@@ -39,10 +39,10 @@ SETTING_DEFINITIONS: Dict[str, Dict] = {
39
39
  "description": "Controls randomness. Lower = more deterministic, higher = more creative.",
40
40
  "type": "numeric",
41
41
  "min": 0.0,
42
- "max": 1.0, # Clamped to 0-1 per user request
43
- "step": 0.1,
42
+ "max": 1.0,
43
+ "step": 0.05,
44
44
  "default": None, # None means use model default
45
- "format": "{:.1f}",
45
+ "format": "{:.2f}",
46
46
  },
47
47
  "seed": {
48
48
  "name": "Seed",
@@ -54,6 +54,16 @@ SETTING_DEFINITIONS: Dict[str, Dict] = {
54
54
  "default": None,
55
55
  "format": "{:.0f}",
56
56
  },
57
+ "top_p": {
58
+ "name": "Top-P (Nucleus Sampling)",
59
+ "description": "Controls token diversity. 0.0 = least random (only most likely tokens), 1.0 = most random (sample from all tokens).",
60
+ "type": "numeric",
61
+ "min": 0.0,
62
+ "max": 1.0,
63
+ "step": 0.05,
64
+ "default": None,
65
+ "format": "{:.2f}",
66
+ },
57
67
  "reasoning_effort": {
58
68
  "name": "Reasoning Effort",
59
69
  "description": "Controls how much effort GPT-5 models spend on reasoning. Higher = more thorough but slower.",
@@ -90,6 +100,12 @@ SETTING_DEFINITIONS: Dict[str, Dict] = {
90
100
  "type": "boolean",
91
101
  "default": False,
92
102
  },
103
+ "clear_thinking": {
104
+ "name": "Clear Thinking",
105
+ "description": "False = Preserved Thinking (keep <think> blocks visible). True = strip thinking from responses.",
106
+ "type": "boolean",
107
+ "default": False,
108
+ },
93
109
  }
94
110
 
95
111
 
@@ -569,6 +585,8 @@ class ModelSettingsMenu:
569
585
  # Default to a sensible starting point for numeric
570
586
  if setting_key == "temperature":
571
587
  self.edit_value = 0.7
588
+ elif setting_key == "top_p":
589
+ self.edit_value = 0.9 # Common default for top_p
572
590
  elif setting_key == "seed":
573
591
  self.edit_value = 42
574
592
  elif setting_key == "budget_tokens":
code_puppy/config.py CHANGED
@@ -88,12 +88,44 @@ def get_subagent_verbose() -> bool:
88
88
  return str(cfg_val).strip().lower() in {"1", "true", "yes", "on"}
89
89
 
90
90
 
91
+ # Pack agents - the specialized sub-agents coordinated by Pack Leader
92
+ PACK_AGENT_NAMES = frozenset(
93
+ [
94
+ "pack-leader",
95
+ "bloodhound",
96
+ "husky",
97
+ "shepherd",
98
+ "terrier",
99
+ "watchdog",
100
+ "retriever",
101
+ ]
102
+ )
103
+
104
+
105
+ def get_pack_agents_enabled() -> bool:
106
+ """Return True if pack agents are enabled (default False).
107
+
108
+ When False (default), pack agents (pack-leader, bloodhound, husky, shepherd,
109
+ terrier, watchdog, retriever) are hidden from `list_agents` tool and `/agents`
110
+ command. They cannot be invoked by other agents or selected by users.
111
+
112
+ When True, pack agents are available for use.
113
+ """
114
+ cfg_val = get_value("enable_pack_agents")
115
+ if cfg_val is None:
116
+ return False
117
+ return str(cfg_val).strip().lower() in {"1", "true", "yes", "on"}
118
+
119
+
91
120
  DEFAULT_SECTION = "puppy"
92
121
  REQUIRED_KEYS = ["puppy_name", "owner_name"]
93
122
 
94
123
  # Runtime-only autosave session ID (per-process)
95
124
  _CURRENT_AUTOSAVE_ID: Optional[str] = None
96
125
 
126
+ # Session-local model name (initialized from file on first access, then cached)
127
+ _SESSION_MODEL: Optional[str] = None
128
+
97
129
  # Cache containers for model validation and defaults
98
130
  _model_validation_cache = {}
99
131
  _default_model_cache = None
@@ -226,6 +258,8 @@ def get_config_keys():
226
258
  ]
227
259
  # Add DBOS control key
228
260
  default_keys.append("enable_dbos")
261
+ # Add pack agents control key
262
+ default_keys.append("enable_pack_agents")
229
263
  # Add cancel agent key configuration
230
264
  default_keys.append("cancel_agent_key")
231
265
  # Add banner color keys
@@ -388,6 +422,16 @@ def clear_model_cache():
388
422
  _default_vision_model_cache = None
389
423
 
390
424
 
425
+ def reset_session_model():
426
+ """Reset the session-local model cache.
427
+
428
+ This is primarily for testing purposes. In normal operation, the session
429
+ model is set once at startup and only changes via set_model_name().
430
+ """
431
+ global _SESSION_MODEL
432
+ _SESSION_MODEL = None
433
+
434
+
391
435
  def model_supports_setting(model_name: str, setting: str) -> bool:
392
436
  """Check if a model supports a particular setting (e.g., 'temperature', 'seed').
393
437
 
@@ -399,6 +443,10 @@ def model_supports_setting(model_name: str, setting: str) -> bool:
399
443
  True if the model supports the setting, False otherwise.
400
444
  Defaults to True for backwards compatibility if model config doesn't specify.
401
445
  """
446
+ # GLM-4.7 models always support clear_thinking setting
447
+ if setting == "clear_thinking" and "glm-4.7" in model_name.lower():
448
+ return True
449
+
402
450
  try:
403
451
  from code_puppy.model_factory import ModelFactory
404
452
 
@@ -424,26 +472,49 @@ def model_supports_setting(model_name: str, setting: str) -> bool:
424
472
  def get_global_model_name():
425
473
  """Return a valid model name for Code Puppy to use.
426
474
 
427
- 1. Look at ``model`` in *puppy.cfg*.
428
- 2. If that value exists **and** is present in *models.json*, use it.
429
- 3. Otherwise return the first model listed in *models.json*.
430
- 4. As a last resort (e.g.
431
- *models.json* unreadable) fall back to ``claude-4-0-sonnet``.
475
+ Uses session-local caching so that model changes in other terminals
476
+ don't affect this running instance. The file is only read once at startup.
477
+
478
+ 1. If _SESSION_MODEL is set, return it (session cache)
479
+ 2. Otherwise, look at ``model`` in *puppy.cfg*
480
+ 3. If that value exists **and** is present in *models.json*, use it
481
+ 4. Otherwise return the first model listed in *models.json*
482
+ 5. As a last resort fall back to ``claude-4-0-sonnet``
483
+
484
+ The result is cached in _SESSION_MODEL for subsequent calls.
432
485
  """
486
+ global _SESSION_MODEL
487
+
488
+ # Return cached session model if already initialized
489
+ if _SESSION_MODEL is not None:
490
+ return _SESSION_MODEL
433
491
 
492
+ # First access - initialize from file
434
493
  stored_model = get_value("model")
435
494
 
436
495
  if stored_model:
437
496
  # Use cached validation to avoid hitting ModelFactory every time
438
497
  if _validate_model_exists(stored_model):
439
- return stored_model
498
+ _SESSION_MODEL = stored_model
499
+ return _SESSION_MODEL
440
500
 
441
501
  # Either no stored model or it's not valid – choose default from models.json
442
- return _default_model_from_models_json()
502
+ _SESSION_MODEL = _default_model_from_models_json()
503
+ return _SESSION_MODEL
443
504
 
444
505
 
445
506
  def set_model_name(model: str):
446
- """Sets the model name in the persistent config file."""
507
+ """Sets the model name in both the session cache and persistent config file.
508
+
509
+ Updates _SESSION_MODEL immediately for this process, and writes to the
510
+ config file so new terminals will pick up this model as their default.
511
+ """
512
+ global _SESSION_MODEL
513
+
514
+ # Update session cache immediately
515
+ _SESSION_MODEL = model
516
+
517
+ # Also persist to file for new terminal sessions
447
518
  config = configparser.ConfigParser()
448
519
  config.read(CONFIG_FILE)
449
520
  if DEFAULT_SECTION not in config: