code-puppy 0.0.135__py3-none-any.whl → 0.0.136__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/agent.py +15 -17
- code_puppy/agents/agent_manager.py +320 -9
- code_puppy/agents/base_agent.py +58 -2
- code_puppy/agents/runtime_manager.py +68 -42
- code_puppy/command_line/command_handler.py +82 -33
- code_puppy/command_line/mcp/__init__.py +10 -0
- code_puppy/command_line/mcp/add_command.py +183 -0
- code_puppy/command_line/mcp/base.py +35 -0
- code_puppy/command_line/mcp/handler.py +133 -0
- code_puppy/command_line/mcp/help_command.py +146 -0
- code_puppy/command_line/mcp/install_command.py +176 -0
- code_puppy/command_line/mcp/list_command.py +94 -0
- code_puppy/command_line/mcp/logs_command.py +126 -0
- code_puppy/command_line/mcp/remove_command.py +82 -0
- code_puppy/command_line/mcp/restart_command.py +92 -0
- code_puppy/command_line/mcp/search_command.py +117 -0
- code_puppy/command_line/mcp/start_all_command.py +126 -0
- code_puppy/command_line/mcp/start_command.py +98 -0
- code_puppy/command_line/mcp/status_command.py +185 -0
- code_puppy/command_line/mcp/stop_all_command.py +109 -0
- code_puppy/command_line/mcp/stop_command.py +79 -0
- code_puppy/command_line/mcp/test_command.py +107 -0
- code_puppy/command_line/mcp/utils.py +129 -0
- code_puppy/command_line/mcp/wizard_utils.py +259 -0
- code_puppy/command_line/model_picker_completion.py +21 -4
- code_puppy/command_line/prompt_toolkit_completion.py +9 -0
- code_puppy/main.py +23 -17
- code_puppy/mcp/__init__.py +42 -16
- code_puppy/mcp/async_lifecycle.py +51 -49
- code_puppy/mcp/blocking_startup.py +125 -113
- code_puppy/mcp/captured_stdio_server.py +63 -70
- code_puppy/mcp/circuit_breaker.py +63 -47
- code_puppy/mcp/config_wizard.py +169 -136
- code_puppy/mcp/dashboard.py +79 -71
- code_puppy/mcp/error_isolation.py +147 -100
- code_puppy/mcp/examples/retry_example.py +55 -42
- code_puppy/mcp/health_monitor.py +152 -141
- code_puppy/mcp/managed_server.py +100 -93
- code_puppy/mcp/manager.py +168 -156
- code_puppy/mcp/registry.py +148 -110
- code_puppy/mcp/retry_manager.py +63 -61
- code_puppy/mcp/server_registry_catalog.py +271 -225
- code_puppy/mcp/status_tracker.py +80 -80
- code_puppy/mcp/system_tools.py +47 -52
- code_puppy/messaging/message_queue.py +20 -13
- code_puppy/messaging/renderers.py +30 -15
- code_puppy/state_management.py +103 -0
- code_puppy/tui/app.py +64 -7
- code_puppy/tui/components/chat_view.py +3 -3
- code_puppy/tui/components/human_input_modal.py +12 -8
- code_puppy/tui/screens/__init__.py +2 -2
- code_puppy/tui/screens/mcp_install_wizard.py +208 -179
- code_puppy/tui/tests/test_agent_command.py +3 -3
- {code_puppy-0.0.135.dist-info → code_puppy-0.0.136.dist-info}/METADATA +1 -1
- {code_puppy-0.0.135.dist-info → code_puppy-0.0.136.dist-info}/RECORD +59 -41
- code_puppy/command_line/mcp_commands.py +0 -1789
- {code_puppy-0.0.135.data → code_puppy-0.0.136.data}/data/code_puppy/models.json +0 -0
- {code_puppy-0.0.135.dist-info → code_puppy-0.0.136.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.135.dist-info → code_puppy-0.0.136.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.135.dist-info → code_puppy-0.0.136.dist-info}/licenses/LICENSE +0 -0
code_puppy/agent.py
CHANGED
|
@@ -3,15 +3,10 @@ from pathlib import Path
|
|
|
3
3
|
from typing import Dict, Optional
|
|
4
4
|
|
|
5
5
|
from pydantic_ai import Agent
|
|
6
|
-
from pydantic_ai.mcp import MCPServerSSE, MCPServerStdio, MCPServerStreamableHTTP
|
|
7
6
|
from pydantic_ai.settings import ModelSettings
|
|
8
7
|
from pydantic_ai.usage import UsageLimits
|
|
9
8
|
|
|
10
9
|
from code_puppy.agents import get_current_agent_config
|
|
11
|
-
from code_puppy.http_utils import (
|
|
12
|
-
create_reopenable_async_client,
|
|
13
|
-
resolve_env_var_in_header,
|
|
14
|
-
)
|
|
15
10
|
from code_puppy.message_history_processor import (
|
|
16
11
|
get_model_context_length,
|
|
17
12
|
message_history_accumulator,
|
|
@@ -45,7 +40,7 @@ _code_generation_agent = None
|
|
|
45
40
|
def _load_mcp_servers(extra_headers: Optional[Dict[str, str]] = None):
|
|
46
41
|
"""Load MCP servers using the new manager while maintaining backward compatibility."""
|
|
47
42
|
from code_puppy.config import get_value, load_mcp_server_configs
|
|
48
|
-
from code_puppy.mcp import
|
|
43
|
+
from code_puppy.mcp import ServerConfig, get_mcp_manager
|
|
49
44
|
|
|
50
45
|
# Check if MCP servers are disabled
|
|
51
46
|
mcp_disabled = get_value("disable_mcp_servers")
|
|
@@ -55,7 +50,7 @@ def _load_mcp_servers(extra_headers: Optional[Dict[str, str]] = None):
|
|
|
55
50
|
|
|
56
51
|
# Get the MCP manager singleton
|
|
57
52
|
manager = get_mcp_manager()
|
|
58
|
-
|
|
53
|
+
|
|
59
54
|
# Load configurations from legacy file for backward compatibility
|
|
60
55
|
configs = load_mcp_server_configs()
|
|
61
56
|
if not configs:
|
|
@@ -74,9 +69,9 @@ def _load_mcp_servers(extra_headers: Optional[Dict[str, str]] = None):
|
|
|
74
69
|
name=name,
|
|
75
70
|
type=conf.get("type", "sse"),
|
|
76
71
|
enabled=conf.get("enabled", True),
|
|
77
|
-
config=conf
|
|
72
|
+
config=conf,
|
|
78
73
|
)
|
|
79
|
-
|
|
74
|
+
|
|
80
75
|
# Check if server already registered
|
|
81
76
|
existing = manager.get_server_by_name(name)
|
|
82
77
|
if not existing:
|
|
@@ -88,14 +83,14 @@ def _load_mcp_servers(extra_headers: Optional[Dict[str, str]] = None):
|
|
|
88
83
|
if existing.config != server_config.config:
|
|
89
84
|
manager.update_server(existing.id, server_config)
|
|
90
85
|
emit_system_message(f"[dim]Updated MCP server: {name}[/dim]")
|
|
91
|
-
|
|
86
|
+
|
|
92
87
|
except Exception as e:
|
|
93
88
|
emit_error(f"Failed to register MCP server '{name}': {str(e)}")
|
|
94
89
|
continue
|
|
95
|
-
|
|
90
|
+
|
|
96
91
|
# Get pydantic-ai compatible servers from manager
|
|
97
92
|
servers = manager.get_servers_for_agent()
|
|
98
|
-
|
|
93
|
+
|
|
99
94
|
if servers:
|
|
100
95
|
emit_system_message(
|
|
101
96
|
f"[green]Successfully loaded {len(servers)} MCP server(s)[/green]"
|
|
@@ -104,14 +99,14 @@ def _load_mcp_servers(extra_headers: Optional[Dict[str, str]] = None):
|
|
|
104
99
|
emit_system_message(
|
|
105
100
|
"[yellow]No MCP servers available (check if servers are enabled)[/yellow]"
|
|
106
101
|
)
|
|
107
|
-
|
|
102
|
+
|
|
108
103
|
return servers
|
|
109
104
|
|
|
110
105
|
|
|
111
106
|
def reload_mcp_servers():
|
|
112
107
|
"""Reload MCP servers without restarting the agent."""
|
|
113
108
|
from code_puppy.mcp import get_mcp_manager
|
|
114
|
-
|
|
109
|
+
|
|
115
110
|
manager = get_mcp_manager()
|
|
116
111
|
# Reload configurations
|
|
117
112
|
_load_mcp_servers()
|
|
@@ -124,15 +119,18 @@ def reload_code_generation_agent(message_group: str | None):
|
|
|
124
119
|
if message_group is None:
|
|
125
120
|
message_group = str(uuid.uuid4())
|
|
126
121
|
global _code_generation_agent, _LAST_MODEL_NAME
|
|
127
|
-
from code_puppy.config import clear_model_cache, get_model_name
|
|
128
122
|
from code_puppy.agents import clear_agent_cache
|
|
123
|
+
from code_puppy.config import clear_model_cache, get_model_name
|
|
129
124
|
|
|
130
125
|
# Clear both ModelFactory cache and config cache when force reloading
|
|
131
126
|
clear_model_cache()
|
|
132
127
|
clear_agent_cache()
|
|
133
128
|
|
|
134
129
|
model_name = get_model_name()
|
|
135
|
-
emit_info(
|
|
130
|
+
emit_info(
|
|
131
|
+
f"[bold cyan]Loading Model: {model_name}[/bold cyan]",
|
|
132
|
+
message_group=message_group,
|
|
133
|
+
)
|
|
136
134
|
models_config = ModelFactory.load_config()
|
|
137
135
|
model = ModelFactory.get_model(model_name, models_config)
|
|
138
136
|
|
|
@@ -140,7 +138,7 @@ def reload_code_generation_agent(message_group: str | None):
|
|
|
140
138
|
agent_config = get_current_agent_config()
|
|
141
139
|
emit_info(
|
|
142
140
|
f"[bold magenta]Loading Agent: {agent_config.display_name}[/bold magenta]",
|
|
143
|
-
message_group=message_group
|
|
141
|
+
message_group=message_group,
|
|
144
142
|
)
|
|
145
143
|
|
|
146
144
|
instructions = agent_config.get_system_prompt()
|
|
@@ -1,20 +1,185 @@
|
|
|
1
1
|
"""Agent manager for handling different agent configurations."""
|
|
2
2
|
|
|
3
3
|
import importlib
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
4
6
|
import pkgutil
|
|
5
7
|
import uuid
|
|
8
|
+
from pathlib import Path
|
|
6
9
|
from typing import Dict, Optional, Type, Union
|
|
7
10
|
|
|
8
|
-
from code_puppy.config import get_value, set_config_value
|
|
9
|
-
from .base_agent import BaseAgent
|
|
10
|
-
from .json_agent import JSONAgent, discover_json_agents
|
|
11
11
|
from ..callbacks import on_agent_reload
|
|
12
12
|
from ..messaging import emit_warning
|
|
13
|
+
from .base_agent import BaseAgent
|
|
14
|
+
from .json_agent import JSONAgent, discover_json_agents
|
|
13
15
|
|
|
14
16
|
# Registry of available agents (Python classes and JSON file paths)
|
|
15
17
|
_AGENT_REGISTRY: Dict[str, Union[Type[BaseAgent], str]] = {}
|
|
16
18
|
_CURRENT_AGENT_CONFIG: Optional[BaseAgent] = None
|
|
17
19
|
|
|
20
|
+
# Terminal session-based agent selection
|
|
21
|
+
_SESSION_AGENTS_CACHE: dict[str, str] = {}
|
|
22
|
+
_SESSION_FILE_LOADED: bool = False
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Session persistence file path
|
|
26
|
+
def _get_session_file_path() -> Path:
|
|
27
|
+
"""Get the path to the terminal sessions file."""
|
|
28
|
+
from ..config import CONFIG_DIR
|
|
29
|
+
|
|
30
|
+
return Path(CONFIG_DIR) / "terminal_sessions.json"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_terminal_session_id() -> str:
|
|
34
|
+
"""Get a unique identifier for the current terminal session.
|
|
35
|
+
|
|
36
|
+
Uses parent process ID (PPID) as the session identifier.
|
|
37
|
+
This works across all platforms and provides session isolation.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
str: Unique session identifier (e.g., "session_12345")
|
|
41
|
+
"""
|
|
42
|
+
try:
|
|
43
|
+
ppid = os.getppid()
|
|
44
|
+
return f"session_{ppid}"
|
|
45
|
+
except (OSError, AttributeError):
|
|
46
|
+
# Fallback to current process ID if PPID unavailable
|
|
47
|
+
return f"fallback_{os.getpid()}"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _is_process_alive(pid: int) -> bool:
|
|
51
|
+
"""Check if a process with the given PID is still alive.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
pid: Process ID to check
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
bool: True if process exists, False otherwise
|
|
58
|
+
"""
|
|
59
|
+
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
|
+
return True
|
|
64
|
+
except (OSError, ProcessLookupError):
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _cleanup_dead_sessions(sessions: dict[str, str]) -> dict[str, str]:
|
|
69
|
+
"""Remove sessions for processes that no longer exist.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
sessions: Dictionary of session_id -> agent_name
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
dict: Cleaned sessions dictionary
|
|
76
|
+
"""
|
|
77
|
+
cleaned = {}
|
|
78
|
+
for session_id, agent_name in sessions.items():
|
|
79
|
+
if session_id.startswith("session_"):
|
|
80
|
+
try:
|
|
81
|
+
pid_str = session_id.replace("session_", "")
|
|
82
|
+
pid = int(pid_str)
|
|
83
|
+
if _is_process_alive(pid):
|
|
84
|
+
cleaned[session_id] = agent_name
|
|
85
|
+
# else: skip dead session
|
|
86
|
+
except (ValueError, TypeError):
|
|
87
|
+
# Invalid session ID format, keep it anyway
|
|
88
|
+
cleaned[session_id] = agent_name
|
|
89
|
+
else:
|
|
90
|
+
# Non-standard session ID (like "fallback_"), keep it
|
|
91
|
+
cleaned[session_id] = agent_name
|
|
92
|
+
return cleaned
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _load_session_data() -> dict[str, str]:
|
|
96
|
+
"""Load terminal session data from the JSON file.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
dict: Session ID to agent name mapping
|
|
100
|
+
"""
|
|
101
|
+
session_file = _get_session_file_path()
|
|
102
|
+
try:
|
|
103
|
+
if session_file.exists():
|
|
104
|
+
with open(session_file, "r", encoding="utf-8") as f:
|
|
105
|
+
data = json.load(f)
|
|
106
|
+
# Clean up dead sessions while loading
|
|
107
|
+
return _cleanup_dead_sessions(data)
|
|
108
|
+
return {}
|
|
109
|
+
except (json.JSONDecodeError, IOError, OSError):
|
|
110
|
+
# File corrupted or permission issues, start fresh
|
|
111
|
+
return {}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _save_session_data(sessions: dict[str, str]) -> None:
|
|
115
|
+
"""Save terminal session data to the JSON file.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
sessions: Session ID to agent name mapping
|
|
119
|
+
"""
|
|
120
|
+
session_file = _get_session_file_path()
|
|
121
|
+
try:
|
|
122
|
+
# Ensure the config directory exists
|
|
123
|
+
session_file.parent.mkdir(parents=True, exist_ok=True)
|
|
124
|
+
|
|
125
|
+
# Clean up dead sessions before saving
|
|
126
|
+
cleaned_sessions = _cleanup_dead_sessions(sessions)
|
|
127
|
+
|
|
128
|
+
# Write to file atomically (write to temp file, then rename)
|
|
129
|
+
temp_file = session_file.with_suffix(".tmp")
|
|
130
|
+
with open(temp_file, "w", encoding="utf-8") as f:
|
|
131
|
+
json.dump(cleaned_sessions, f, indent=2)
|
|
132
|
+
|
|
133
|
+
# Atomic rename (works on all platforms)
|
|
134
|
+
temp_file.replace(session_file)
|
|
135
|
+
|
|
136
|
+
except (IOError, OSError):
|
|
137
|
+
# File permission issues, etc. - just continue without persistence
|
|
138
|
+
pass
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _ensure_session_cache_loaded() -> None:
|
|
142
|
+
"""Ensure the session cache is loaded from disk."""
|
|
143
|
+
global _SESSION_AGENTS_CACHE, _SESSION_FILE_LOADED
|
|
144
|
+
if not _SESSION_FILE_LOADED:
|
|
145
|
+
_SESSION_AGENTS_CACHE.update(_load_session_data())
|
|
146
|
+
_SESSION_FILE_LOADED = True
|
|
147
|
+
|
|
148
|
+
|
|
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
|
+
|
|
18
183
|
|
|
19
184
|
def _discover_agents(message_group_id: Optional[str] = None):
|
|
20
185
|
"""Dynamically discover all agent classes and JSON agents."""
|
|
@@ -97,12 +262,14 @@ def get_available_agents() -> Dict[str, str]:
|
|
|
97
262
|
|
|
98
263
|
|
|
99
264
|
def get_current_agent_name() -> str:
|
|
100
|
-
"""Get the name of the currently active agent.
|
|
265
|
+
"""Get the name of the currently active agent for this terminal session.
|
|
101
266
|
|
|
102
267
|
Returns:
|
|
103
|
-
The name of the current agent, defaults to 'code-puppy'.
|
|
268
|
+
The name of the current agent for this session, defaults to 'code-puppy'.
|
|
104
269
|
"""
|
|
105
|
-
|
|
270
|
+
_ensure_session_cache_loaded()
|
|
271
|
+
session_id = get_terminal_session_id()
|
|
272
|
+
return _SESSION_AGENTS_CACHE.get(session_id, "code-puppy")
|
|
106
273
|
|
|
107
274
|
|
|
108
275
|
def set_current_agent(agent_name: str) -> bool:
|
|
@@ -117,12 +284,26 @@ def set_current_agent(agent_name: str) -> bool:
|
|
|
117
284
|
# Generate a message group ID for agent switching
|
|
118
285
|
message_group_id = str(uuid.uuid4())
|
|
119
286
|
_discover_agents(message_group_id=message_group_id)
|
|
287
|
+
|
|
288
|
+
# 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
|
+
|
|
120
293
|
# Clear the cached config when switching agents
|
|
121
|
-
global _CURRENT_AGENT_CONFIG
|
|
122
294
|
_CURRENT_AGENT_CONFIG = None
|
|
123
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)
|
|
299
|
+
|
|
300
|
+
# Update session-based agent selection and persist to disk
|
|
301
|
+
_ensure_session_cache_loaded()
|
|
302
|
+
session_id = get_terminal_session_id()
|
|
303
|
+
_SESSION_AGENTS_CACHE[session_id] = agent_name
|
|
304
|
+
_save_session_data(_SESSION_AGENTS_CACHE)
|
|
305
|
+
|
|
124
306
|
on_agent_reload(agent_obj.id, agent_name)
|
|
125
|
-
set_config_value("current_agent", agent_name)
|
|
126
307
|
return True
|
|
127
308
|
|
|
128
309
|
|
|
@@ -134,7 +315,11 @@ def get_current_agent_config() -> BaseAgent:
|
|
|
134
315
|
"""
|
|
135
316
|
global _CURRENT_AGENT_CONFIG
|
|
136
317
|
|
|
137
|
-
_CURRENT_AGENT_CONFIG
|
|
318
|
+
if _CURRENT_AGENT_CONFIG is None:
|
|
319
|
+
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)
|
|
138
323
|
|
|
139
324
|
return _CURRENT_AGENT_CONFIG
|
|
140
325
|
|
|
@@ -201,6 +386,20 @@ def clear_agent_cache():
|
|
|
201
386
|
_CURRENT_AGENT_CONFIG = None
|
|
202
387
|
|
|
203
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
|
+
|
|
204
403
|
def refresh_agents():
|
|
205
404
|
"""Refresh the agent discovery to pick up newly created agents.
|
|
206
405
|
|
|
@@ -209,3 +408,115 @@ def refresh_agents():
|
|
|
209
408
|
# Generate a message group ID for agent refreshing
|
|
210
409
|
message_group_id = str(uuid.uuid4())
|
|
211
410
|
_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)
|
code_puppy/agents/base_agent.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"""Base agent configuration class for defining agent properties."""
|
|
2
2
|
|
|
3
|
-
from abc import ABC, abstractmethod
|
|
4
|
-
from typing import Any, Dict, List, Optional
|
|
5
3
|
import uuid
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from typing import Any, Dict, List, Optional, Set
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class BaseAgent(ABC):
|
|
@@ -10,6 +10,8 @@ class BaseAgent(ABC):
|
|
|
10
10
|
|
|
11
11
|
def __init__(self):
|
|
12
12
|
self.id = str(uuid.uuid4())
|
|
13
|
+
self._message_history: List[Any] = []
|
|
14
|
+
self._compacted_message_hashes: Set[str] = set()
|
|
13
15
|
|
|
14
16
|
@property
|
|
15
17
|
@abstractmethod
|
|
@@ -58,3 +60,57 @@ class BaseAgent(ABC):
|
|
|
58
60
|
Custom prompt string, or None to use default.
|
|
59
61
|
"""
|
|
60
62
|
return None
|
|
63
|
+
|
|
64
|
+
# Message history management methods
|
|
65
|
+
def get_message_history(self) -> List[Any]:
|
|
66
|
+
"""Get the message history for this agent.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
List of messages in this agent's conversation history.
|
|
70
|
+
"""
|
|
71
|
+
return self._message_history
|
|
72
|
+
|
|
73
|
+
def set_message_history(self, history: List[Any]) -> None:
|
|
74
|
+
"""Set the message history for this agent.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
history: List of messages to set as the conversation history.
|
|
78
|
+
"""
|
|
79
|
+
self._message_history = history
|
|
80
|
+
|
|
81
|
+
def clear_message_history(self) -> None:
|
|
82
|
+
"""Clear the message history for this agent."""
|
|
83
|
+
self._message_history = []
|
|
84
|
+
self._compacted_message_hashes.clear()
|
|
85
|
+
|
|
86
|
+
def append_to_message_history(self, message: Any) -> None:
|
|
87
|
+
"""Append a message to this agent's history.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
message: Message to append to the conversation history.
|
|
91
|
+
"""
|
|
92
|
+
self._message_history.append(message)
|
|
93
|
+
|
|
94
|
+
def extend_message_history(self, history: List[Any]) -> None:
|
|
95
|
+
"""Extend this agent's message history with multiple messages.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
history: List of messages to append to the conversation history.
|
|
99
|
+
"""
|
|
100
|
+
self._message_history.extend(history)
|
|
101
|
+
|
|
102
|
+
def get_compacted_message_hashes(self) -> Set[str]:
|
|
103
|
+
"""Get the set of compacted message hashes for this agent.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Set of hashes for messages that have been compacted/summarized.
|
|
107
|
+
"""
|
|
108
|
+
return self._compacted_message_hashes
|
|
109
|
+
|
|
110
|
+
def add_compacted_message_hash(self, message_hash: str) -> None:
|
|
111
|
+
"""Add a message hash to the set of compacted message hashes.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
message_hash: Hash of a message that has been compacted/summarized.
|
|
115
|
+
"""
|
|
116
|
+
self._compacted_message_hashes.add(message_hash)
|