claude-mpm 4.1.2__py3-none-any.whl → 4.1.4__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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +16 -19
- claude_mpm/agents/MEMORY.md +21 -49
- claude_mpm/agents/templates/OPTIMIZATION_REPORT.md +156 -0
- claude_mpm/agents/templates/api_qa.json +36 -116
- claude_mpm/agents/templates/backup/data_engineer_agent_20250726_234551.json +42 -9
- claude_mpm/agents/templates/backup/documentation_agent_20250726_234551.json +29 -6
- claude_mpm/agents/templates/backup/engineer_agent_20250726_234551.json +34 -6
- claude_mpm/agents/templates/backup/ops_agent_20250726_234551.json +41 -9
- claude_mpm/agents/templates/backup/qa_agent_20250726_234551.json +30 -8
- claude_mpm/agents/templates/backup/research_agent_2025011_234551.json +2 -2
- claude_mpm/agents/templates/backup/research_agent_20250726_234551.json +29 -6
- claude_mpm/agents/templates/backup/research_memory_efficient.json +2 -2
- claude_mpm/agents/templates/backup/security_agent_20250726_234551.json +41 -9
- claude_mpm/agents/templates/backup/version_control_agent_20250726_234551.json +23 -7
- claude_mpm/agents/templates/code_analyzer.json +18 -36
- claude_mpm/agents/templates/data_engineer.json +43 -14
- claude_mpm/agents/templates/documentation.json +55 -74
- claude_mpm/agents/templates/engineer.json +57 -40
- claude_mpm/agents/templates/imagemagick.json +7 -2
- claude_mpm/agents/templates/memory_manager.json +1 -1
- claude_mpm/agents/templates/ops.json +36 -4
- claude_mpm/agents/templates/project_organizer.json +23 -71
- claude_mpm/agents/templates/qa.json +34 -2
- claude_mpm/agents/templates/refactoring_engineer.json +9 -5
- claude_mpm/agents/templates/research.json +36 -4
- claude_mpm/agents/templates/security.json +29 -2
- claude_mpm/agents/templates/ticketing.json +3 -3
- claude_mpm/agents/templates/vercel_ops_agent.json +2 -2
- claude_mpm/agents/templates/version_control.json +28 -2
- claude_mpm/agents/templates/web_qa.json +38 -151
- claude_mpm/agents/templates/web_ui.json +2 -2
- claude_mpm/cli/commands/agent_manager.py +221 -1
- claude_mpm/cli/commands/agents.py +556 -1009
- claude_mpm/cli/commands/memory.py +248 -927
- claude_mpm/cli/commands/run.py +139 -484
- claude_mpm/cli/parsers/agent_manager_parser.py +34 -0
- claude_mpm/cli/startup_logging.py +76 -0
- claude_mpm/core/agent_registry.py +6 -10
- claude_mpm/core/framework_loader.py +205 -595
- claude_mpm/core/log_manager.py +49 -1
- claude_mpm/core/logging_config.py +2 -4
- claude_mpm/hooks/claude_hooks/event_handlers.py +7 -117
- claude_mpm/hooks/claude_hooks/hook_handler.py +91 -755
- claude_mpm/hooks/claude_hooks/hook_handler_original.py +1040 -0
- claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +347 -0
- claude_mpm/hooks/claude_hooks/services/__init__.py +13 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +190 -0
- claude_mpm/hooks/claude_hooks/services/duplicate_detector.py +106 -0
- claude_mpm/hooks/claude_hooks/services/state_manager.py +282 -0
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +374 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +42 -454
- claude_mpm/services/agents/deployment/base_agent_locator.py +132 -0
- claude_mpm/services/agents/deployment/deployment_results_manager.py +185 -0
- claude_mpm/services/agents/deployment/single_agent_deployer.py +315 -0
- claude_mpm/services/agents/memory/agent_memory_manager.py +42 -508
- claude_mpm/services/agents/memory/memory_categorization_service.py +165 -0
- claude_mpm/services/agents/memory/memory_file_service.py +103 -0
- claude_mpm/services/agents/memory/memory_format_service.py +201 -0
- claude_mpm/services/agents/memory/memory_limits_service.py +99 -0
- claude_mpm/services/agents/registry/__init__.py +1 -1
- claude_mpm/services/cli/__init__.py +18 -0
- claude_mpm/services/cli/agent_cleanup_service.py +407 -0
- claude_mpm/services/cli/agent_dependency_service.py +395 -0
- claude_mpm/services/cli/agent_listing_service.py +463 -0
- claude_mpm/services/cli/agent_output_formatter.py +605 -0
- claude_mpm/services/cli/agent_validation_service.py +589 -0
- claude_mpm/services/cli/dashboard_launcher.py +424 -0
- claude_mpm/services/cli/memory_crud_service.py +617 -0
- claude_mpm/services/cli/memory_output_formatter.py +604 -0
- claude_mpm/services/cli/session_manager.py +513 -0
- claude_mpm/services/cli/socketio_manager.py +498 -0
- claude_mpm/services/cli/startup_checker.py +370 -0
- claude_mpm/services/core/cache_manager.py +311 -0
- claude_mpm/services/core/memory_manager.py +637 -0
- claude_mpm/services/core/path_resolver.py +498 -0
- claude_mpm/services/core/service_container.py +520 -0
- claude_mpm/services/core/service_interfaces.py +436 -0
- claude_mpm/services/diagnostics/checks/agent_check.py +65 -19
- claude_mpm/services/memory/router.py +116 -10
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.4.dist-info}/METADATA +1 -1
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.4.dist-info}/RECORD +86 -55
- claude_mpm/cli/commands/run_config_checker.py +0 -159
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.4.dist-info}/WHEEL +0 -0
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.4.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.4.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.4.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent Listing Service for CLI commands
|
|
3
|
+
=======================================
|
|
4
|
+
|
|
5
|
+
WHY: The agent listing logic was previously embedded in the agents CLI command,
|
|
6
|
+
making it difficult to test and reuse. This service extracts that functionality
|
|
7
|
+
into a reusable, testable component that can be used by multiple CLI commands.
|
|
8
|
+
|
|
9
|
+
DESIGN DECISIONS:
|
|
10
|
+
- Interface-based design for dependency injection and testing
|
|
11
|
+
- Caching for performance optimization
|
|
12
|
+
- Integration with AgentRegistryAdapter for unified agent discovery
|
|
13
|
+
- Support for multiple listing modes (system, deployed, by-tier)
|
|
14
|
+
- Comprehensive error handling and logging
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import time
|
|
18
|
+
from abc import ABC, abstractmethod
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
22
|
+
|
|
23
|
+
from ...core.agent_registry import AgentRegistryAdapter
|
|
24
|
+
from ...core.logger import get_logger
|
|
25
|
+
from ...services.agents.deployment import AgentDeploymentService
|
|
26
|
+
from ...services.agents.deployment.deployment_wrapper import DeploymentServiceWrapper
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class AgentInfo:
|
|
31
|
+
"""Information about an agent."""
|
|
32
|
+
|
|
33
|
+
name: str
|
|
34
|
+
type: str
|
|
35
|
+
tier: str
|
|
36
|
+
path: str
|
|
37
|
+
description: Optional[str] = None
|
|
38
|
+
specializations: Optional[List[str]] = None
|
|
39
|
+
version: Optional[str] = None
|
|
40
|
+
deployed: bool = False
|
|
41
|
+
active: bool = True
|
|
42
|
+
overridden_by: Optional[List[str]] = None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class AgentTierInfo:
|
|
47
|
+
"""Information about agents grouped by tier."""
|
|
48
|
+
|
|
49
|
+
project: List[AgentInfo]
|
|
50
|
+
user: List[AgentInfo]
|
|
51
|
+
system: List[AgentInfo]
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def total_count(self) -> int:
|
|
55
|
+
"""Get total number of agents across all tiers."""
|
|
56
|
+
return len(self.project) + len(self.user) + len(self.system)
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def active_count(self) -> int:
|
|
60
|
+
"""Get count of active agents (not overridden)."""
|
|
61
|
+
count = 0
|
|
62
|
+
for agents in [self.project, self.user, self.system]:
|
|
63
|
+
count += sum(1 for a in agents if a.active)
|
|
64
|
+
return count
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class IAgentListingService(ABC):
|
|
68
|
+
"""Interface for agent listing service."""
|
|
69
|
+
|
|
70
|
+
@abstractmethod
|
|
71
|
+
def list_system_agents(self, verbose: bool = False) -> List[AgentInfo]:
|
|
72
|
+
"""
|
|
73
|
+
List available system agent templates.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
verbose: Include detailed metadata
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
List of system agent information
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
@abstractmethod
|
|
83
|
+
def list_deployed_agents(
|
|
84
|
+
self, verbose: bool = False
|
|
85
|
+
) -> Tuple[List[AgentInfo], List[str]]:
|
|
86
|
+
"""
|
|
87
|
+
List currently deployed agents.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
verbose: Include detailed metadata
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Tuple of (agent list, warnings)
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
@abstractmethod
|
|
97
|
+
def list_agents_by_tier(self) -> AgentTierInfo:
|
|
98
|
+
"""
|
|
99
|
+
List agents grouped by tier/precedence.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Agent information grouped by tier
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
@abstractmethod
|
|
106
|
+
def get_agent_details(self, agent_name: str) -> Optional[Dict[str, Any]]:
|
|
107
|
+
"""
|
|
108
|
+
Get detailed information for a specific agent.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
agent_name: Name of the agent
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Agent details or None if not found
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
@abstractmethod
|
|
118
|
+
def compare_versions(self, agent_name: str) -> Dict[str, Any]:
|
|
119
|
+
"""
|
|
120
|
+
Compare versions of an agent across tiers.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
agent_name: Name of the agent
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Version comparison data
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
@abstractmethod
|
|
130
|
+
def find_agent(self, agent_name: str) -> Optional[AgentInfo]:
|
|
131
|
+
"""
|
|
132
|
+
Find an agent by name across all sources.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
agent_name: Name of the agent
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Agent info or None if not found
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
@abstractmethod
|
|
142
|
+
def clear_cache(self) -> None:
|
|
143
|
+
"""Clear the service cache."""
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class AgentListingService(IAgentListingService):
|
|
147
|
+
"""Implementation of agent listing service."""
|
|
148
|
+
|
|
149
|
+
def __init__(self, deployment_service: Optional[AgentDeploymentService] = None):
|
|
150
|
+
"""
|
|
151
|
+
Initialize agent listing service.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
deployment_service: Optional deployment service instance
|
|
155
|
+
"""
|
|
156
|
+
self.logger = get_logger(self.__class__.__name__)
|
|
157
|
+
self._deployment_service = deployment_service
|
|
158
|
+
self._registry_adapter = None
|
|
159
|
+
self._cache = {}
|
|
160
|
+
self._cache_ttl = 60 # Cache for 60 seconds
|
|
161
|
+
self._cache_times = {}
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def deployment_service(self) -> AgentDeploymentService:
|
|
165
|
+
"""Get deployment service instance (lazy loaded)."""
|
|
166
|
+
if self._deployment_service is None:
|
|
167
|
+
try:
|
|
168
|
+
from ...services import AgentDeploymentService
|
|
169
|
+
|
|
170
|
+
base_service = AgentDeploymentService()
|
|
171
|
+
self._deployment_service = DeploymentServiceWrapper(base_service)
|
|
172
|
+
except ImportError:
|
|
173
|
+
raise ImportError("Agent deployment service not available")
|
|
174
|
+
return self._deployment_service
|
|
175
|
+
|
|
176
|
+
@property
|
|
177
|
+
def registry_adapter(self) -> AgentRegistryAdapter:
|
|
178
|
+
"""Get registry adapter instance (lazy loaded)."""
|
|
179
|
+
if self._registry_adapter is None:
|
|
180
|
+
self._registry_adapter = AgentRegistryAdapter()
|
|
181
|
+
return self._registry_adapter
|
|
182
|
+
|
|
183
|
+
def _is_cache_valid(self, key: str) -> bool:
|
|
184
|
+
"""Check if cache entry is still valid."""
|
|
185
|
+
if key not in self._cache_times:
|
|
186
|
+
return False
|
|
187
|
+
return (time.time() - self._cache_times[key]) < self._cache_ttl
|
|
188
|
+
|
|
189
|
+
def _get_from_cache(self, key: str) -> Optional[Any]:
|
|
190
|
+
"""Get value from cache if valid."""
|
|
191
|
+
if self._is_cache_valid(key):
|
|
192
|
+
return self._cache.get(key)
|
|
193
|
+
return None
|
|
194
|
+
|
|
195
|
+
def _set_cache(self, key: str, value: Any) -> None:
|
|
196
|
+
"""Set cache value."""
|
|
197
|
+
self._cache[key] = value
|
|
198
|
+
self._cache_times[key] = time.time()
|
|
199
|
+
|
|
200
|
+
def list_system_agents(self, verbose: bool = False) -> List[AgentInfo]:
|
|
201
|
+
"""List available system agent templates."""
|
|
202
|
+
cache_key = f"system_agents_{verbose}"
|
|
203
|
+
cached = self._get_from_cache(cache_key)
|
|
204
|
+
if cached is not None:
|
|
205
|
+
return cached
|
|
206
|
+
|
|
207
|
+
try:
|
|
208
|
+
agents_data = self.deployment_service.list_available_agents()
|
|
209
|
+
agents = []
|
|
210
|
+
|
|
211
|
+
for agent_data in agents_data:
|
|
212
|
+
agent = AgentInfo(
|
|
213
|
+
name=agent_data.get("name", ""),
|
|
214
|
+
type=agent_data.get("type", "agent"),
|
|
215
|
+
tier="system",
|
|
216
|
+
path=agent_data.get("path", ""),
|
|
217
|
+
description=agent_data.get("description") if verbose else None,
|
|
218
|
+
specializations=(
|
|
219
|
+
agent_data.get("specializations") if verbose else None
|
|
220
|
+
),
|
|
221
|
+
version=agent_data.get("version") if verbose else None,
|
|
222
|
+
)
|
|
223
|
+
agents.append(agent)
|
|
224
|
+
|
|
225
|
+
self._set_cache(cache_key, agents)
|
|
226
|
+
return agents
|
|
227
|
+
|
|
228
|
+
except Exception as e:
|
|
229
|
+
self.logger.error(f"Error listing system agents: {e}", exc_info=True)
|
|
230
|
+
return []
|
|
231
|
+
|
|
232
|
+
def list_deployed_agents(
|
|
233
|
+
self, verbose: bool = False
|
|
234
|
+
) -> Tuple[List[AgentInfo], List[str]]:
|
|
235
|
+
"""List currently deployed agents."""
|
|
236
|
+
cache_key = f"deployed_agents_{verbose}"
|
|
237
|
+
cached = self._get_from_cache(cache_key)
|
|
238
|
+
if cached is not None:
|
|
239
|
+
return cached
|
|
240
|
+
|
|
241
|
+
try:
|
|
242
|
+
verification = self.deployment_service.verify_deployment()
|
|
243
|
+
agents_data = verification.get("agents_found", [])
|
|
244
|
+
warnings = verification.get("warnings", [])
|
|
245
|
+
|
|
246
|
+
agents = []
|
|
247
|
+
for agent_data in agents_data:
|
|
248
|
+
agent = AgentInfo(
|
|
249
|
+
name=agent_data.get("name", ""),
|
|
250
|
+
type=agent_data.get("type", "agent"),
|
|
251
|
+
tier=agent_data.get("tier", "system"),
|
|
252
|
+
path=agent_data.get("path", ""),
|
|
253
|
+
description=agent_data.get("description") if verbose else None,
|
|
254
|
+
specializations=(
|
|
255
|
+
agent_data.get("specializations") if verbose else None
|
|
256
|
+
),
|
|
257
|
+
version=agent_data.get("version") if verbose else None,
|
|
258
|
+
deployed=True,
|
|
259
|
+
)
|
|
260
|
+
agents.append(agent)
|
|
261
|
+
|
|
262
|
+
result = (agents, warnings)
|
|
263
|
+
self._set_cache(cache_key, result)
|
|
264
|
+
return result
|
|
265
|
+
|
|
266
|
+
except Exception as e:
|
|
267
|
+
self.logger.error(f"Error listing deployed agents: {e}", exc_info=True)
|
|
268
|
+
return ([], [f"Error listing deployed agents: {e}"])
|
|
269
|
+
|
|
270
|
+
def list_agents_by_tier(self) -> AgentTierInfo:
|
|
271
|
+
"""List agents grouped by tier/precedence."""
|
|
272
|
+
cache_key = "agents_by_tier"
|
|
273
|
+
cached = self._get_from_cache(cache_key)
|
|
274
|
+
if cached is not None:
|
|
275
|
+
return cached
|
|
276
|
+
|
|
277
|
+
try:
|
|
278
|
+
if not self.registry_adapter.registry:
|
|
279
|
+
self.logger.error("Could not initialize agent registry")
|
|
280
|
+
return AgentTierInfo(project=[], user=[], system=[])
|
|
281
|
+
|
|
282
|
+
# Get all agents and group by tier
|
|
283
|
+
all_agents = self.registry_adapter.registry.list_agents()
|
|
284
|
+
|
|
285
|
+
# Group agents by tier
|
|
286
|
+
tiers = {"project": [], "user": [], "system": []}
|
|
287
|
+
agent_names = set()
|
|
288
|
+
|
|
289
|
+
for agent_id, metadata in all_agents.items():
|
|
290
|
+
tier = metadata.get("tier", "system")
|
|
291
|
+
if tier not in tiers:
|
|
292
|
+
continue
|
|
293
|
+
|
|
294
|
+
agent_info = AgentInfo(
|
|
295
|
+
name=agent_id,
|
|
296
|
+
type=metadata.get("type", "agent"),
|
|
297
|
+
tier=tier,
|
|
298
|
+
path=metadata.get("path", ""),
|
|
299
|
+
description=metadata.get("description"),
|
|
300
|
+
specializations=metadata.get("specializations", []),
|
|
301
|
+
deployed=metadata.get("deployed", False),
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
tiers[tier].append(agent_info)
|
|
305
|
+
agent_names.add(agent_id)
|
|
306
|
+
|
|
307
|
+
# Check for overrides
|
|
308
|
+
for tier_name in ["project", "user", "system"]:
|
|
309
|
+
for agent in tiers[tier_name]:
|
|
310
|
+
overridden_by = []
|
|
311
|
+
|
|
312
|
+
# Check if overridden by higher tiers
|
|
313
|
+
if tier_name == "system":
|
|
314
|
+
# Check if overridden by user or project
|
|
315
|
+
if any(a.name == agent.name for a in tiers["user"]):
|
|
316
|
+
overridden_by.append("USER")
|
|
317
|
+
if any(a.name == agent.name for a in tiers["project"]):
|
|
318
|
+
overridden_by.append("PROJECT")
|
|
319
|
+
elif tier_name == "user":
|
|
320
|
+
# Check if overridden by project
|
|
321
|
+
if any(a.name == agent.name for a in tiers["project"]):
|
|
322
|
+
overridden_by.append("PROJECT")
|
|
323
|
+
|
|
324
|
+
if overridden_by:
|
|
325
|
+
agent.active = False
|
|
326
|
+
agent.overridden_by = overridden_by
|
|
327
|
+
|
|
328
|
+
result = AgentTierInfo(
|
|
329
|
+
project=tiers["project"], user=tiers["user"], system=tiers["system"]
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
self._set_cache(cache_key, result)
|
|
333
|
+
return result
|
|
334
|
+
|
|
335
|
+
except Exception as e:
|
|
336
|
+
self.logger.error(f"Error listing agents by tier: {e}", exc_info=True)
|
|
337
|
+
return AgentTierInfo(project=[], user=[], system=[])
|
|
338
|
+
|
|
339
|
+
def get_agent_details(self, agent_name: str) -> Optional[Dict[str, Any]]:
|
|
340
|
+
"""Get detailed information for a specific agent."""
|
|
341
|
+
cache_key = f"agent_details_{agent_name}"
|
|
342
|
+
cached = self._get_from_cache(cache_key)
|
|
343
|
+
if cached is not None:
|
|
344
|
+
return cached
|
|
345
|
+
|
|
346
|
+
try:
|
|
347
|
+
# Try to get from deployment service first
|
|
348
|
+
details = self.deployment_service.get_agent_details(agent_name)
|
|
349
|
+
|
|
350
|
+
if details:
|
|
351
|
+
self._set_cache(cache_key, details)
|
|
352
|
+
return details
|
|
353
|
+
|
|
354
|
+
# Fall back to registry
|
|
355
|
+
if not self.registry_adapter.registry:
|
|
356
|
+
return None
|
|
357
|
+
|
|
358
|
+
agent = self.registry_adapter.registry.get_agent(agent_name)
|
|
359
|
+
if not agent:
|
|
360
|
+
return None
|
|
361
|
+
|
|
362
|
+
# Read agent file for full content
|
|
363
|
+
agent_path = Path(agent.path)
|
|
364
|
+
if not agent_path.exists():
|
|
365
|
+
return None
|
|
366
|
+
|
|
367
|
+
with open(agent_path) as f:
|
|
368
|
+
content = f.read()
|
|
369
|
+
|
|
370
|
+
details = {
|
|
371
|
+
"name": getattr(agent, "name", agent_name),
|
|
372
|
+
"type": getattr(agent, "type", "agent"),
|
|
373
|
+
"tier": getattr(agent, "tier", "system"),
|
|
374
|
+
"path": str(getattr(agent, "path", agent_path)),
|
|
375
|
+
"description": getattr(agent, "description", None),
|
|
376
|
+
"specializations": getattr(agent, "specializations", []),
|
|
377
|
+
"content": content,
|
|
378
|
+
"size": agent_path.stat().st_size,
|
|
379
|
+
"modified": agent_path.stat().st_mtime,
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
self._set_cache(cache_key, details)
|
|
383
|
+
return details
|
|
384
|
+
|
|
385
|
+
except Exception as e:
|
|
386
|
+
self.logger.error(f"Error getting agent details: {e}", exc_info=True)
|
|
387
|
+
return None
|
|
388
|
+
|
|
389
|
+
def compare_versions(self, agent_name: str) -> Dict[str, Any]:
|
|
390
|
+
"""Compare versions of an agent across tiers."""
|
|
391
|
+
cache_key = f"version_compare_{agent_name}"
|
|
392
|
+
cached = self._get_from_cache(cache_key)
|
|
393
|
+
if cached is not None:
|
|
394
|
+
return cached
|
|
395
|
+
|
|
396
|
+
try:
|
|
397
|
+
tier_info = self.list_agents_by_tier()
|
|
398
|
+
versions = {}
|
|
399
|
+
|
|
400
|
+
# Check each tier for the agent
|
|
401
|
+
for tier_name, agents in [
|
|
402
|
+
("project", tier_info.project),
|
|
403
|
+
("user", tier_info.user),
|
|
404
|
+
("system", tier_info.system),
|
|
405
|
+
]:
|
|
406
|
+
for agent in agents:
|
|
407
|
+
if agent.name == agent_name:
|
|
408
|
+
versions[tier_name] = {
|
|
409
|
+
"path": agent.path,
|
|
410
|
+
"version": agent.version,
|
|
411
|
+
"active": agent.active,
|
|
412
|
+
"overridden_by": agent.overridden_by,
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
result = {
|
|
416
|
+
"agent_name": agent_name,
|
|
417
|
+
"versions": versions,
|
|
418
|
+
"active_tier": next(
|
|
419
|
+
(tier for tier, v in versions.items() if v.get("active")), None
|
|
420
|
+
),
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
self._set_cache(cache_key, result)
|
|
424
|
+
return result
|
|
425
|
+
|
|
426
|
+
except Exception as e:
|
|
427
|
+
self.logger.error(f"Error comparing versions: {e}", exc_info=True)
|
|
428
|
+
return {"agent_name": agent_name, "versions": {}, "error": str(e)}
|
|
429
|
+
|
|
430
|
+
def find_agent(self, agent_name: str) -> Optional[AgentInfo]:
|
|
431
|
+
"""Find an agent by name across all sources."""
|
|
432
|
+
cache_key = f"find_agent_{agent_name}"
|
|
433
|
+
cached = self._get_from_cache(cache_key)
|
|
434
|
+
if cached is not None:
|
|
435
|
+
return cached
|
|
436
|
+
|
|
437
|
+
try:
|
|
438
|
+
# First check deployed agents
|
|
439
|
+
deployed, _ = self.list_deployed_agents()
|
|
440
|
+
for agent in deployed:
|
|
441
|
+
if agent.name == agent_name:
|
|
442
|
+
self._set_cache(cache_key, agent)
|
|
443
|
+
return agent
|
|
444
|
+
|
|
445
|
+
# Check all tiers
|
|
446
|
+
tier_info = self.list_agents_by_tier()
|
|
447
|
+
for agents in [tier_info.project, tier_info.user, tier_info.system]:
|
|
448
|
+
for agent in agents:
|
|
449
|
+
if agent.name == agent_name:
|
|
450
|
+
self._set_cache(cache_key, agent)
|
|
451
|
+
return agent
|
|
452
|
+
|
|
453
|
+
return None
|
|
454
|
+
|
|
455
|
+
except Exception as e:
|
|
456
|
+
self.logger.error(f"Error finding agent: {e}", exc_info=True)
|
|
457
|
+
return None
|
|
458
|
+
|
|
459
|
+
def clear_cache(self) -> None:
|
|
460
|
+
"""Clear the service cache."""
|
|
461
|
+
self._cache.clear()
|
|
462
|
+
self._cache_times.clear()
|
|
463
|
+
self.logger.debug("Agent listing service cache cleared")
|