code-puppy 0.0.169__py3-none-any.whl → 0.0.366__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 (243) hide show
  1. code_puppy/__init__.py +7 -1
  2. code_puppy/agents/__init__.py +8 -8
  3. code_puppy/agents/agent_c_reviewer.py +155 -0
  4. code_puppy/agents/agent_code_puppy.py +9 -2
  5. code_puppy/agents/agent_code_reviewer.py +90 -0
  6. code_puppy/agents/agent_cpp_reviewer.py +132 -0
  7. code_puppy/agents/agent_creator_agent.py +48 -9
  8. code_puppy/agents/agent_golang_reviewer.py +151 -0
  9. code_puppy/agents/agent_javascript_reviewer.py +160 -0
  10. code_puppy/agents/agent_manager.py +146 -199
  11. code_puppy/agents/agent_pack_leader.py +383 -0
  12. code_puppy/agents/agent_planning.py +163 -0
  13. code_puppy/agents/agent_python_programmer.py +165 -0
  14. code_puppy/agents/agent_python_reviewer.py +90 -0
  15. code_puppy/agents/agent_qa_expert.py +163 -0
  16. code_puppy/agents/agent_qa_kitten.py +208 -0
  17. code_puppy/agents/agent_security_auditor.py +181 -0
  18. code_puppy/agents/agent_terminal_qa.py +323 -0
  19. code_puppy/agents/agent_typescript_reviewer.py +166 -0
  20. code_puppy/agents/base_agent.py +1713 -1
  21. code_puppy/agents/event_stream_handler.py +350 -0
  22. code_puppy/agents/json_agent.py +12 -1
  23. code_puppy/agents/pack/__init__.py +34 -0
  24. code_puppy/agents/pack/bloodhound.py +304 -0
  25. code_puppy/agents/pack/husky.py +321 -0
  26. code_puppy/agents/pack/retriever.py +393 -0
  27. code_puppy/agents/pack/shepherd.py +348 -0
  28. code_puppy/agents/pack/terrier.py +287 -0
  29. code_puppy/agents/pack/watchdog.py +367 -0
  30. code_puppy/agents/prompt_reviewer.py +145 -0
  31. code_puppy/agents/subagent_stream_handler.py +276 -0
  32. code_puppy/api/__init__.py +13 -0
  33. code_puppy/api/app.py +169 -0
  34. code_puppy/api/main.py +21 -0
  35. code_puppy/api/pty_manager.py +446 -0
  36. code_puppy/api/routers/__init__.py +12 -0
  37. code_puppy/api/routers/agents.py +36 -0
  38. code_puppy/api/routers/commands.py +217 -0
  39. code_puppy/api/routers/config.py +74 -0
  40. code_puppy/api/routers/sessions.py +232 -0
  41. code_puppy/api/templates/terminal.html +361 -0
  42. code_puppy/api/websocket.py +154 -0
  43. code_puppy/callbacks.py +174 -4
  44. code_puppy/chatgpt_codex_client.py +283 -0
  45. code_puppy/claude_cache_client.py +586 -0
  46. code_puppy/cli_runner.py +916 -0
  47. code_puppy/command_line/add_model_menu.py +1079 -0
  48. code_puppy/command_line/agent_menu.py +395 -0
  49. code_puppy/command_line/attachments.py +395 -0
  50. code_puppy/command_line/autosave_menu.py +605 -0
  51. code_puppy/command_line/clipboard.py +527 -0
  52. code_puppy/command_line/colors_menu.py +520 -0
  53. code_puppy/command_line/command_handler.py +233 -627
  54. code_puppy/command_line/command_registry.py +150 -0
  55. code_puppy/command_line/config_commands.py +715 -0
  56. code_puppy/command_line/core_commands.py +792 -0
  57. code_puppy/command_line/diff_menu.py +863 -0
  58. code_puppy/command_line/load_context_completion.py +15 -22
  59. code_puppy/command_line/mcp/base.py +1 -4
  60. code_puppy/command_line/mcp/catalog_server_installer.py +175 -0
  61. code_puppy/command_line/mcp/custom_server_form.py +688 -0
  62. code_puppy/command_line/mcp/custom_server_installer.py +195 -0
  63. code_puppy/command_line/mcp/edit_command.py +148 -0
  64. code_puppy/command_line/mcp/handler.py +9 -4
  65. code_puppy/command_line/mcp/help_command.py +6 -5
  66. code_puppy/command_line/mcp/install_command.py +16 -27
  67. code_puppy/command_line/mcp/install_menu.py +685 -0
  68. code_puppy/command_line/mcp/list_command.py +3 -3
  69. code_puppy/command_line/mcp/logs_command.py +174 -65
  70. code_puppy/command_line/mcp/remove_command.py +2 -2
  71. code_puppy/command_line/mcp/restart_command.py +12 -4
  72. code_puppy/command_line/mcp/search_command.py +17 -11
  73. code_puppy/command_line/mcp/start_all_command.py +22 -13
  74. code_puppy/command_line/mcp/start_command.py +50 -31
  75. code_puppy/command_line/mcp/status_command.py +6 -7
  76. code_puppy/command_line/mcp/stop_all_command.py +11 -8
  77. code_puppy/command_line/mcp/stop_command.py +11 -10
  78. code_puppy/command_line/mcp/test_command.py +2 -2
  79. code_puppy/command_line/mcp/utils.py +1 -1
  80. code_puppy/command_line/mcp/wizard_utils.py +22 -18
  81. code_puppy/command_line/mcp_completion.py +174 -0
  82. code_puppy/command_line/model_picker_completion.py +89 -30
  83. code_puppy/command_line/model_settings_menu.py +884 -0
  84. code_puppy/command_line/motd.py +14 -8
  85. code_puppy/command_line/onboarding_slides.py +179 -0
  86. code_puppy/command_line/onboarding_wizard.py +340 -0
  87. code_puppy/command_line/pin_command_completion.py +329 -0
  88. code_puppy/command_line/prompt_toolkit_completion.py +626 -75
  89. code_puppy/command_line/session_commands.py +296 -0
  90. code_puppy/command_line/utils.py +54 -0
  91. code_puppy/config.py +1181 -51
  92. code_puppy/error_logging.py +118 -0
  93. code_puppy/gemini_code_assist.py +385 -0
  94. code_puppy/gemini_model.py +602 -0
  95. code_puppy/http_utils.py +220 -104
  96. code_puppy/keymap.py +128 -0
  97. code_puppy/main.py +5 -594
  98. code_puppy/{mcp → mcp_}/__init__.py +17 -0
  99. code_puppy/{mcp → mcp_}/async_lifecycle.py +35 -4
  100. code_puppy/{mcp → mcp_}/blocking_startup.py +70 -43
  101. code_puppy/{mcp → mcp_}/captured_stdio_server.py +2 -2
  102. code_puppy/{mcp → mcp_}/config_wizard.py +5 -5
  103. code_puppy/{mcp → mcp_}/dashboard.py +15 -6
  104. code_puppy/{mcp → mcp_}/examples/retry_example.py +4 -1
  105. code_puppy/{mcp → mcp_}/managed_server.py +66 -39
  106. code_puppy/{mcp → mcp_}/manager.py +146 -52
  107. code_puppy/mcp_/mcp_logs.py +224 -0
  108. code_puppy/{mcp → mcp_}/registry.py +6 -6
  109. code_puppy/{mcp → mcp_}/server_registry_catalog.py +25 -8
  110. code_puppy/messaging/__init__.py +199 -2
  111. code_puppy/messaging/bus.py +610 -0
  112. code_puppy/messaging/commands.py +167 -0
  113. code_puppy/messaging/markdown_patches.py +57 -0
  114. code_puppy/messaging/message_queue.py +17 -48
  115. code_puppy/messaging/messages.py +500 -0
  116. code_puppy/messaging/queue_console.py +1 -24
  117. code_puppy/messaging/renderers.py +43 -146
  118. code_puppy/messaging/rich_renderer.py +1027 -0
  119. code_puppy/messaging/spinner/__init__.py +33 -5
  120. code_puppy/messaging/spinner/console_spinner.py +92 -52
  121. code_puppy/messaging/spinner/spinner_base.py +29 -0
  122. code_puppy/messaging/subagent_console.py +461 -0
  123. code_puppy/model_factory.py +686 -80
  124. code_puppy/model_utils.py +167 -0
  125. code_puppy/models.json +86 -104
  126. code_puppy/models_dev_api.json +1 -0
  127. code_puppy/models_dev_parser.py +592 -0
  128. code_puppy/plugins/__init__.py +164 -10
  129. code_puppy/plugins/antigravity_oauth/__init__.py +10 -0
  130. code_puppy/plugins/antigravity_oauth/accounts.py +406 -0
  131. code_puppy/plugins/antigravity_oauth/antigravity_model.py +704 -0
  132. code_puppy/plugins/antigravity_oauth/config.py +42 -0
  133. code_puppy/plugins/antigravity_oauth/constants.py +136 -0
  134. code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
  135. code_puppy/plugins/antigravity_oauth/register_callbacks.py +406 -0
  136. code_puppy/plugins/antigravity_oauth/storage.py +271 -0
  137. code_puppy/plugins/antigravity_oauth/test_plugin.py +319 -0
  138. code_puppy/plugins/antigravity_oauth/token.py +167 -0
  139. code_puppy/plugins/antigravity_oauth/transport.py +767 -0
  140. code_puppy/plugins/antigravity_oauth/utils.py +169 -0
  141. code_puppy/plugins/chatgpt_oauth/__init__.py +8 -0
  142. code_puppy/plugins/chatgpt_oauth/config.py +52 -0
  143. code_puppy/plugins/chatgpt_oauth/oauth_flow.py +328 -0
  144. code_puppy/plugins/chatgpt_oauth/register_callbacks.py +94 -0
  145. code_puppy/plugins/chatgpt_oauth/test_plugin.py +293 -0
  146. code_puppy/plugins/chatgpt_oauth/utils.py +489 -0
  147. code_puppy/plugins/claude_code_oauth/README.md +167 -0
  148. code_puppy/plugins/claude_code_oauth/SETUP.md +93 -0
  149. code_puppy/plugins/claude_code_oauth/__init__.py +6 -0
  150. code_puppy/plugins/claude_code_oauth/config.py +50 -0
  151. code_puppy/plugins/claude_code_oauth/register_callbacks.py +308 -0
  152. code_puppy/plugins/claude_code_oauth/test_plugin.py +283 -0
  153. code_puppy/plugins/claude_code_oauth/utils.py +518 -0
  154. code_puppy/plugins/customizable_commands/__init__.py +0 -0
  155. code_puppy/plugins/customizable_commands/register_callbacks.py +169 -0
  156. code_puppy/plugins/example_custom_command/README.md +280 -0
  157. code_puppy/plugins/example_custom_command/register_callbacks.py +51 -0
  158. code_puppy/plugins/file_permission_handler/__init__.py +4 -0
  159. code_puppy/plugins/file_permission_handler/register_callbacks.py +523 -0
  160. code_puppy/plugins/frontend_emitter/__init__.py +25 -0
  161. code_puppy/plugins/frontend_emitter/emitter.py +121 -0
  162. code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
  163. code_puppy/plugins/oauth_puppy_html.py +228 -0
  164. code_puppy/plugins/shell_safety/__init__.py +6 -0
  165. code_puppy/plugins/shell_safety/agent_shell_safety.py +69 -0
  166. code_puppy/plugins/shell_safety/command_cache.py +156 -0
  167. code_puppy/plugins/shell_safety/register_callbacks.py +202 -0
  168. code_puppy/prompts/antigravity_system_prompt.md +1 -0
  169. code_puppy/prompts/codex_system_prompt.md +310 -0
  170. code_puppy/pydantic_patches.py +131 -0
  171. code_puppy/reopenable_async_client.py +8 -8
  172. code_puppy/round_robin_model.py +10 -15
  173. code_puppy/session_storage.py +294 -0
  174. code_puppy/status_display.py +21 -4
  175. code_puppy/summarization_agent.py +52 -14
  176. code_puppy/terminal_utils.py +418 -0
  177. code_puppy/tools/__init__.py +139 -6
  178. code_puppy/tools/agent_tools.py +548 -49
  179. code_puppy/tools/browser/__init__.py +37 -0
  180. code_puppy/tools/browser/browser_control.py +289 -0
  181. code_puppy/tools/browser/browser_interactions.py +545 -0
  182. code_puppy/tools/browser/browser_locators.py +640 -0
  183. code_puppy/tools/browser/browser_manager.py +316 -0
  184. code_puppy/tools/browser/browser_navigation.py +251 -0
  185. code_puppy/tools/browser/browser_screenshot.py +179 -0
  186. code_puppy/tools/browser/browser_scripts.py +462 -0
  187. code_puppy/tools/browser/browser_workflows.py +221 -0
  188. code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
  189. code_puppy/tools/browser/terminal_command_tools.py +521 -0
  190. code_puppy/tools/browser/terminal_screenshot_tools.py +556 -0
  191. code_puppy/tools/browser/terminal_tools.py +525 -0
  192. code_puppy/tools/command_runner.py +941 -153
  193. code_puppy/tools/common.py +1146 -6
  194. code_puppy/tools/display.py +84 -0
  195. code_puppy/tools/file_modifications.py +288 -89
  196. code_puppy/tools/file_operations.py +352 -266
  197. code_puppy/tools/subagent_context.py +158 -0
  198. code_puppy/uvx_detection.py +242 -0
  199. code_puppy/version_checker.py +30 -11
  200. code_puppy-0.0.366.data/data/code_puppy/models.json +110 -0
  201. code_puppy-0.0.366.data/data/code_puppy/models_dev_api.json +1 -0
  202. {code_puppy-0.0.169.dist-info → code_puppy-0.0.366.dist-info}/METADATA +184 -67
  203. code_puppy-0.0.366.dist-info/RECORD +217 -0
  204. {code_puppy-0.0.169.dist-info → code_puppy-0.0.366.dist-info}/WHEEL +1 -1
  205. {code_puppy-0.0.169.dist-info → code_puppy-0.0.366.dist-info}/entry_points.txt +1 -0
  206. code_puppy/agent.py +0 -231
  207. code_puppy/agents/agent_orchestrator.json +0 -26
  208. code_puppy/agents/runtime_manager.py +0 -272
  209. code_puppy/command_line/mcp/add_command.py +0 -183
  210. code_puppy/command_line/meta_command_handler.py +0 -153
  211. code_puppy/message_history_processor.py +0 -490
  212. code_puppy/messaging/spinner/textual_spinner.py +0 -101
  213. code_puppy/state_management.py +0 -200
  214. code_puppy/tui/__init__.py +0 -10
  215. code_puppy/tui/app.py +0 -986
  216. code_puppy/tui/components/__init__.py +0 -21
  217. code_puppy/tui/components/chat_view.py +0 -550
  218. code_puppy/tui/components/command_history_modal.py +0 -218
  219. code_puppy/tui/components/copy_button.py +0 -139
  220. code_puppy/tui/components/custom_widgets.py +0 -63
  221. code_puppy/tui/components/human_input_modal.py +0 -175
  222. code_puppy/tui/components/input_area.py +0 -167
  223. code_puppy/tui/components/sidebar.py +0 -309
  224. code_puppy/tui/components/status_bar.py +0 -182
  225. code_puppy/tui/messages.py +0 -27
  226. code_puppy/tui/models/__init__.py +0 -8
  227. code_puppy/tui/models/chat_message.py +0 -25
  228. code_puppy/tui/models/command_history.py +0 -89
  229. code_puppy/tui/models/enums.py +0 -24
  230. code_puppy/tui/screens/__init__.py +0 -15
  231. code_puppy/tui/screens/help.py +0 -130
  232. code_puppy/tui/screens/mcp_install_wizard.py +0 -803
  233. code_puppy/tui/screens/settings.py +0 -290
  234. code_puppy/tui/screens/tools.py +0 -74
  235. code_puppy-0.0.169.data/data/code_puppy/models.json +0 -128
  236. code_puppy-0.0.169.dist-info/RECORD +0 -112
  237. /code_puppy/{mcp → mcp_}/circuit_breaker.py +0 -0
  238. /code_puppy/{mcp → mcp_}/error_isolation.py +0 -0
  239. /code_puppy/{mcp → mcp_}/health_monitor.py +0 -0
  240. /code_puppy/{mcp → mcp_}/retry_manager.py +0 -0
  241. /code_puppy/{mcp → mcp_}/status_tracker.py +0 -0
  242. /code_puppy/{mcp → mcp_}/system_tools.py +0 -0
  243. {code_puppy-0.0.169.dist-info → code_puppy-0.0.366.dist-info}/licenses/LICENSE +0 -0
@@ -6,16 +6,19 @@ import os
6
6
  import pkgutil
7
7
  import uuid
8
8
  from pathlib import Path
9
- from typing import Dict, Optional, Type, Union
9
+ from typing import Dict, List, Optional, Type, Union
10
10
 
11
- from ..callbacks import on_agent_reload
12
- from ..messaging import emit_warning
13
- from .base_agent import BaseAgent
14
- from .json_agent import JSONAgent, discover_json_agents
11
+ from pydantic_ai.messages import ModelMessage
12
+
13
+ from code_puppy.agents.base_agent import BaseAgent
14
+ from code_puppy.agents.json_agent import JSONAgent, discover_json_agents
15
+ from code_puppy.callbacks import on_agent_reload
16
+ from code_puppy.messaging import emit_warning
15
17
 
16
18
  # Registry of available agents (Python classes and JSON file paths)
17
19
  _AGENT_REGISTRY: Dict[str, Union[Type[BaseAgent], str]] = {}
18
- _CURRENT_AGENT_CONFIG: Optional[BaseAgent] = None
20
+ _AGENT_HISTORIES: Dict[str, List[ModelMessage]] = {}
21
+ _CURRENT_AGENT: Optional[BaseAgent] = None
19
22
 
20
23
  # Terminal session-based agent selection
21
24
  _SESSION_AGENTS_CACHE: dict[str, str] = {}
@@ -25,9 +28,9 @@ _SESSION_FILE_LOADED: bool = False
25
28
  # Session persistence file path
26
29
  def _get_session_file_path() -> Path:
27
30
  """Get the path to the terminal sessions file."""
28
- from ..config import CONFIG_DIR
31
+ from ..config import STATE_DIR
29
32
 
30
- return Path(CONFIG_DIR) / "terminal_sessions.json"
33
+ return Path(STATE_DIR) / "terminal_sessions.json"
31
34
 
32
35
 
33
36
  def get_terminal_session_id() -> str:
@@ -48,21 +51,56 @@ def get_terminal_session_id() -> str:
48
51
 
49
52
 
50
53
  def _is_process_alive(pid: int) -> bool:
51
- """Check if a process with the given PID is still alive.
54
+ """Check if a process with the given PID is still alive, cross-platform.
52
55
 
53
56
  Args:
54
57
  pid: Process ID to check
55
58
 
56
59
  Returns:
57
- bool: True if process exists, False otherwise
60
+ bool: True if process likely exists, False otherwise
58
61
  """
59
62
  try:
60
- # On Unix: os.kill(pid, 0) raises OSError if process doesn't exist
61
- # On Windows: This also works with signal 0
62
- os.kill(pid, 0)
63
+ if os.name == "nt":
64
+ # Windows: use OpenProcess to probe liveness safely
65
+ import ctypes
66
+ from ctypes import wintypes
67
+
68
+ PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
69
+ kernel32 = ctypes.windll.kernel32 # type: ignore[attr-defined]
70
+ kernel32.OpenProcess.argtypes = [
71
+ wintypes.DWORD,
72
+ wintypes.BOOL,
73
+ wintypes.DWORD,
74
+ ]
75
+ kernel32.OpenProcess.restype = wintypes.HANDLE
76
+ handle = kernel32.OpenProcess(
77
+ PROCESS_QUERY_LIMITED_INFORMATION, False, int(pid)
78
+ )
79
+ if handle:
80
+ kernel32.CloseHandle(handle)
81
+ return True
82
+ # If access denied, process likely exists but we can't query it
83
+ last_error = kernel32.GetLastError()
84
+ # ERROR_ACCESS_DENIED = 5
85
+ if last_error == 5:
86
+ return True
87
+ return False
88
+ else:
89
+ # Unix-like: signal 0 does not deliver a signal but checks existence
90
+ os.kill(int(pid), 0)
91
+ return True
92
+ except PermissionError:
93
+ # No permission to signal -> process exists
63
94
  return True
64
95
  except (OSError, ProcessLookupError):
96
+ # Process does not exist
65
97
  return False
98
+ except ValueError:
99
+ # Invalid signal or pid format
100
+ return False
101
+ except Exception:
102
+ # Be conservative – don't crash session cleanup due to platform quirks
103
+ return True
66
104
 
67
105
 
68
106
  def _cleanup_dead_sessions(sessions: dict[str, str]) -> dict[str, str]:
@@ -146,41 +184,6 @@ def _ensure_session_cache_loaded() -> None:
146
184
  _SESSION_FILE_LOADED = True
147
185
 
148
186
 
149
- # Persistent storage for agent message histories
150
- _AGENT_HISTORIES: Dict[str, Dict[str, any]] = {}
151
- # Structure: {agent_name: {"message_history": [...], "compacted_hashes": set(...)}}
152
-
153
-
154
- def _save_agent_history(agent_name: str, agent: BaseAgent) -> None:
155
- """Save an agent's message history to persistent storage.
156
-
157
- Args:
158
- agent_name: The name of the agent
159
- agent: The agent instance to save history from
160
- """
161
- global _AGENT_HISTORIES
162
- _AGENT_HISTORIES[agent_name] = {
163
- "message_history": agent.get_message_history().copy(),
164
- "compacted_hashes": agent.get_compacted_message_hashes().copy(),
165
- }
166
-
167
-
168
- def _restore_agent_history(agent_name: str, agent: BaseAgent) -> None:
169
- """Restore an agent's message history from persistent storage.
170
-
171
- Args:
172
- agent_name: The name of the agent
173
- agent: The agent instance to restore history to
174
- """
175
- global _AGENT_HISTORIES
176
- if agent_name in _AGENT_HISTORIES:
177
- stored_data = _AGENT_HISTORIES[agent_name]
178
- agent.set_message_history(stored_data["message_history"])
179
- # Restore compacted hashes
180
- for hash_val in stored_data["compacted_hashes"]:
181
- agent.add_compacted_message_hash(hash_val)
182
-
183
-
184
187
  def _discover_agents(message_group_id: Optional[str] = None):
185
188
  """Dynamically discover all agent classes and JSON agents."""
186
189
  # Always clear the registry to force refresh
@@ -222,6 +225,55 @@ def _discover_agents(message_group_id: Optional[str] = None):
222
225
  )
223
226
  continue
224
227
 
228
+ # 1b. Discover agents in sub-packages (like 'pack')
229
+ for _, subpkg_name, ispkg in pkgutil.iter_modules(agents_package.__path__):
230
+ if not ispkg or subpkg_name.startswith("_"):
231
+ continue
232
+
233
+ try:
234
+ # Import the sub-package
235
+ subpkg = importlib.import_module(f"code_puppy.agents.{subpkg_name}")
236
+
237
+ # Iterate through modules in the sub-package
238
+ if not hasattr(subpkg, "__path__"):
239
+ continue
240
+
241
+ for _, modname, _ in pkgutil.iter_modules(subpkg.__path__):
242
+ if modname.startswith("_"):
243
+ continue
244
+
245
+ try:
246
+ # Import the submodule
247
+ module = importlib.import_module(
248
+ f"code_puppy.agents.{subpkg_name}.{modname}"
249
+ )
250
+
251
+ # Look for BaseAgent subclasses
252
+ for attr_name in dir(module):
253
+ attr = getattr(module, attr_name)
254
+ if (
255
+ isinstance(attr, type)
256
+ and issubclass(attr, BaseAgent)
257
+ and attr not in [BaseAgent, JSONAgent]
258
+ ):
259
+ # Create an instance to get the name
260
+ agent_instance = attr()
261
+ _AGENT_REGISTRY[agent_instance.name] = attr
262
+
263
+ except Exception as e:
264
+ emit_warning(
265
+ f"Warning: Could not load agent {subpkg_name}.{modname}: {e}",
266
+ message_group=message_group_id,
267
+ )
268
+ continue
269
+
270
+ except Exception as e:
271
+ emit_warning(
272
+ f"Warning: Could not load agent sub-package {subpkg_name}: {e}",
273
+ message_group=message_group_id,
274
+ )
275
+ continue
276
+
225
277
  # 2. Discover JSON agents in user directory
226
278
  try:
227
279
  json_agents = discover_json_agents()
@@ -243,12 +295,21 @@ def get_available_agents() -> Dict[str, str]:
243
295
  Returns:
244
296
  Dict mapping agent names to display names.
245
297
  """
298
+ from ..config import PACK_AGENT_NAMES, get_pack_agents_enabled
299
+
246
300
  # Generate a message group ID for this operation
247
301
  message_group_id = str(uuid.uuid4())
248
302
  _discover_agents(message_group_id=message_group_id)
249
303
 
304
+ # Check if pack agents are enabled
305
+ pack_agents_enabled = get_pack_agents_enabled()
306
+
250
307
  agents = {}
251
308
  for name, agent_ref in _AGENT_REGISTRY.items():
309
+ # Filter out pack agents if disabled
310
+ if not pack_agents_enabled and name in PACK_AGENT_NAMES:
311
+ continue
312
+
252
313
  try:
253
314
  if isinstance(agent_ref, str): # JSON agent (file path)
254
315
  agent_instance = JSONAgent(agent_ref)
@@ -265,11 +326,21 @@ def get_current_agent_name() -> str:
265
326
  """Get the name of the currently active agent for this terminal session.
266
327
 
267
328
  Returns:
268
- The name of the current agent for this session, defaults to 'code-puppy'.
329
+ The name of the current agent for this session.
330
+ Priority: session agent > config default > 'code-puppy'.
269
331
  """
270
332
  _ensure_session_cache_loaded()
271
333
  session_id = get_terminal_session_id()
272
- return _SESSION_AGENTS_CACHE.get(session_id, "code-puppy")
334
+
335
+ # First check for session-specific agent
336
+ session_agent = _SESSION_AGENTS_CACHE.get(session_id)
337
+ if session_agent:
338
+ return session_agent
339
+
340
+ # Fall back to config default
341
+ from ..config import get_default_agent
342
+
343
+ return get_default_agent()
273
344
 
274
345
 
275
346
  def set_current_agent(agent_name: str) -> bool:
@@ -281,50 +352,49 @@ def set_current_agent(agent_name: str) -> bool:
281
352
  Returns:
282
353
  True if the agent was set successfully, False if agent not found.
283
354
  """
355
+ global _CURRENT_AGENT
356
+ curr_agent = get_current_agent()
357
+ if curr_agent is not None:
358
+ # Store a shallow copy so future mutations don't affect saved history
359
+ _AGENT_HISTORIES[curr_agent.name] = list(curr_agent.get_message_history())
284
360
  # Generate a message group ID for agent switching
285
361
  message_group_id = str(uuid.uuid4())
286
362
  _discover_agents(message_group_id=message_group_id)
287
363
 
288
364
  # Save current agent's history before switching
289
- global _CURRENT_AGENT_CONFIG, _CURRENT_AGENT_NAME
290
- if _CURRENT_AGENT_CONFIG is not None:
291
- _save_agent_history(_CURRENT_AGENT_CONFIG.name, _CURRENT_AGENT_CONFIG)
292
365
 
293
366
  # Clear the cached config when switching agents
294
- _CURRENT_AGENT_CONFIG = None
295
- agent_obj = load_agent_config(agent_name)
296
-
297
- # Restore the agent's history if it exists
298
- _restore_agent_history(agent_name, agent_obj)
367
+ agent_obj = load_agent(agent_name)
368
+ _CURRENT_AGENT = agent_obj
299
369
 
300
370
  # Update session-based agent selection and persist to disk
301
371
  _ensure_session_cache_loaded()
302
372
  session_id = get_terminal_session_id()
303
373
  _SESSION_AGENTS_CACHE[session_id] = agent_name
304
374
  _save_session_data(_SESSION_AGENTS_CACHE)
305
-
375
+ if agent_obj.name in _AGENT_HISTORIES:
376
+ # Restore a copy to avoid sharing the same list instance
377
+ agent_obj.set_message_history(list(_AGENT_HISTORIES[agent_obj.name]))
306
378
  on_agent_reload(agent_obj.id, agent_name)
307
379
  return True
308
380
 
309
381
 
310
- def get_current_agent_config() -> BaseAgent:
382
+ def get_current_agent() -> BaseAgent:
311
383
  """Get the current agent configuration.
312
384
 
313
385
  Returns:
314
386
  The current agent configuration instance.
315
387
  """
316
- global _CURRENT_AGENT_CONFIG
388
+ global _CURRENT_AGENT
317
389
 
318
- if _CURRENT_AGENT_CONFIG is None:
390
+ if _CURRENT_AGENT is None:
319
391
  agent_name = get_current_agent_name()
320
- _CURRENT_AGENT_CONFIG = load_agent_config(agent_name)
321
- # Restore the agent's history if it exists
322
- _restore_agent_history(agent_name, _CURRENT_AGENT_CONFIG)
392
+ _CURRENT_AGENT = load_agent(agent_name)
323
393
 
324
- return _CURRENT_AGENT_CONFIG
394
+ return _CURRENT_AGENT
325
395
 
326
396
 
327
- def load_agent_config(agent_name: str) -> BaseAgent:
397
+ def load_agent(agent_name: str) -> BaseAgent:
328
398
  """Load an agent configuration by name.
329
399
 
330
400
  Args:
@@ -362,12 +432,21 @@ def get_agent_descriptions() -> Dict[str, str]:
362
432
  Returns:
363
433
  Dict mapping agent names to their descriptions.
364
434
  """
435
+ from ..config import PACK_AGENT_NAMES, get_pack_agents_enabled
436
+
365
437
  # Generate a message group ID for this operation
366
438
  message_group_id = str(uuid.uuid4())
367
439
  _discover_agents(message_group_id=message_group_id)
368
440
 
441
+ # Check if pack agents are enabled
442
+ pack_agents_enabled = get_pack_agents_enabled()
443
+
369
444
  descriptions = {}
370
445
  for name, agent_ref in _AGENT_REGISTRY.items():
446
+ # Filter out pack agents if disabled
447
+ if not pack_agents_enabled and name in PACK_AGENT_NAMES:
448
+ continue
449
+
371
450
  try:
372
451
  if isinstance(agent_ref, str): # JSON agent (file path)
373
452
  agent_instance = JSONAgent(agent_ref)
@@ -380,26 +459,6 @@ def get_agent_descriptions() -> Dict[str, str]:
380
459
  return descriptions
381
460
 
382
461
 
383
- def clear_agent_cache():
384
- """Clear the cached agent configuration to force reload."""
385
- global _CURRENT_AGENT_CONFIG
386
- _CURRENT_AGENT_CONFIG = None
387
-
388
-
389
- def reset_to_default_agent():
390
- """Reset the current agent to the default (code-puppy) for this terminal session.
391
-
392
- This is useful for testing or when you want to start fresh.
393
- """
394
- global _CURRENT_AGENT_CONFIG
395
- _ensure_session_cache_loaded()
396
- session_id = get_terminal_session_id()
397
- if session_id in _SESSION_AGENTS_CACHE:
398
- del _SESSION_AGENTS_CACHE[session_id]
399
- _save_session_data(_SESSION_AGENTS_CACHE)
400
- _CURRENT_AGENT_CONFIG = None
401
-
402
-
403
462
  def refresh_agents():
404
463
  """Refresh the agent discovery to pick up newly created agents.
405
464
 
@@ -408,115 +467,3 @@ def refresh_agents():
408
467
  # Generate a message group ID for agent refreshing
409
468
  message_group_id = str(uuid.uuid4())
410
469
  _discover_agents(message_group_id=message_group_id)
411
-
412
-
413
- def clear_all_agent_histories():
414
- """Clear all agent message histories from persistent storage.
415
-
416
- This is useful for debugging or when you want a fresh start.
417
- """
418
- global _AGENT_HISTORIES
419
- _AGENT_HISTORIES.clear()
420
- # Also clear the current agent's history
421
- if _CURRENT_AGENT_CONFIG is not None:
422
- _CURRENT_AGENT_CONFIG.messages = []
423
-
424
-
425
- def cleanup_dead_terminal_sessions() -> int:
426
- """Clean up terminal sessions for processes that no longer exist.
427
-
428
- Returns:
429
- int: Number of dead sessions removed
430
- """
431
- _ensure_session_cache_loaded()
432
- original_count = len(_SESSION_AGENTS_CACHE)
433
- cleaned_cache = _cleanup_dead_sessions(_SESSION_AGENTS_CACHE)
434
-
435
- if len(cleaned_cache) != original_count:
436
- _SESSION_AGENTS_CACHE.clear()
437
- _SESSION_AGENTS_CACHE.update(cleaned_cache)
438
- _save_session_data(_SESSION_AGENTS_CACHE)
439
-
440
- return original_count - len(cleaned_cache)
441
-
442
-
443
- # Agent-aware message history functions
444
- def get_current_agent_message_history():
445
- """Get the message history for the currently active agent.
446
-
447
- Returns:
448
- List of messages from the current agent's conversation history.
449
- """
450
- current_agent = get_current_agent_config()
451
- return current_agent.get_message_history()
452
-
453
-
454
- def set_current_agent_message_history(history):
455
- """Set the message history for the currently active agent.
456
-
457
- Args:
458
- history: List of messages to set as the current agent's conversation history.
459
- """
460
- current_agent = get_current_agent_config()
461
- current_agent.set_message_history(history)
462
- # Also update persistent storage
463
- _save_agent_history(current_agent.name, current_agent)
464
-
465
-
466
- def clear_current_agent_message_history():
467
- """Clear the message history for the currently active agent."""
468
- current_agent = get_current_agent_config()
469
- current_agent.clear_message_history()
470
- # Also clear from persistent storage
471
- global _AGENT_HISTORIES
472
- if current_agent.name in _AGENT_HISTORIES:
473
- _AGENT_HISTORIES[current_agent.name] = {
474
- "message_history": [],
475
- "compacted_hashes": set(),
476
- }
477
-
478
-
479
- def append_to_current_agent_message_history(message):
480
- """Append a message to the currently active agent's history.
481
-
482
- Args:
483
- message: Message to append to the current agent's conversation history.
484
- """
485
- current_agent = get_current_agent_config()
486
- current_agent.append_to_message_history(message)
487
- # Also update persistent storage
488
- _save_agent_history(current_agent.name, current_agent)
489
-
490
-
491
- def extend_current_agent_message_history(history):
492
- """Extend the currently active agent's message history with multiple messages.
493
-
494
- Args:
495
- history: List of messages to append to the current agent's conversation history.
496
- """
497
- current_agent = get_current_agent_config()
498
- current_agent.extend_message_history(history)
499
- # Also update persistent storage
500
- _save_agent_history(current_agent.name, current_agent)
501
-
502
-
503
- def get_current_agent_compacted_message_hashes():
504
- """Get the set of compacted message hashes for the currently active agent.
505
-
506
- Returns:
507
- Set of hashes for messages that have been compacted/summarized.
508
- """
509
- current_agent = get_current_agent_config()
510
- return current_agent.get_compacted_message_hashes()
511
-
512
-
513
- def add_current_agent_compacted_message_hash(message_hash: str):
514
- """Add a message hash to the current agent's set of compacted message hashes.
515
-
516
- Args:
517
- message_hash: Hash of a message that has been compacted/summarized.
518
- """
519
- current_agent = get_current_agent_config()
520
- current_agent.add_compacted_message_hash(message_hash)
521
- # Also update persistent storage
522
- _save_agent_history(current_agent.name, current_agent)