claude-mpm 4.3.6__py3-none-any.whl → 4.3.12__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_PM.md +41 -8
- claude_mpm/agents/PM_INSTRUCTIONS.md +85 -43
- claude_mpm/agents/templates/clerk-ops.json +223 -0
- claude_mpm/agents/templates/data_engineer.json +41 -5
- claude_mpm/agents/templates/php-engineer.json +185 -0
- claude_mpm/agents/templates/research.json +20 -8
- claude_mpm/agents/templates/web_qa.json +25 -10
- claude_mpm/cli/__init__.py +41 -2
- claude_mpm/cli/commands/agents.py +2 -2
- claude_mpm/cli/commands/analyze.py +4 -4
- claude_mpm/cli/commands/cleanup.py +7 -7
- claude_mpm/cli/commands/configure_tui.py +2 -2
- claude_mpm/cli/commands/debug.py +2 -2
- claude_mpm/cli/commands/info.py +3 -4
- claude_mpm/cli/commands/mcp.py +8 -6
- claude_mpm/cli/commands/mcp_command_router.py +11 -0
- claude_mpm/cli/commands/mcp_config.py +157 -0
- claude_mpm/cli/commands/mcp_external_commands.py +241 -0
- claude_mpm/cli/commands/mcp_install_commands.py +73 -32
- claude_mpm/cli/commands/mcp_setup_external.py +829 -0
- claude_mpm/cli/commands/run.py +73 -3
- claude_mpm/cli/commands/search.py +285 -0
- claude_mpm/cli/parsers/base_parser.py +13 -0
- claude_mpm/cli/parsers/mcp_parser.py +17 -0
- claude_mpm/cli/parsers/run_parser.py +5 -0
- claude_mpm/cli/parsers/search_parser.py +239 -0
- claude_mpm/cli/startup_logging.py +20 -7
- claude_mpm/constants.py +1 -0
- claude_mpm/core/unified_agent_registry.py +7 -0
- claude_mpm/hooks/instruction_reinforcement.py +295 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +28 -13
- claude_mpm/services/agents/deployment/agent_discovery_service.py +16 -6
- claude_mpm/services/agents/deployment/deployment_wrapper.py +59 -0
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +6 -4
- claude_mpm/services/cli/agent_cleanup_service.py +5 -0
- claude_mpm/services/mcp_config_manager.py +294 -0
- claude_mpm/services/mcp_gateway/config/configuration.py +17 -0
- claude_mpm/services/mcp_gateway/main.py +38 -0
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +390 -0
- claude_mpm/utils/log_cleanup.py +17 -17
- claude_mpm/utils/subprocess_utils.py +6 -6
- {claude_mpm-4.3.6.dist-info → claude_mpm-4.3.12.dist-info}/METADATA +24 -1
- {claude_mpm-4.3.6.dist-info → claude_mpm-4.3.12.dist-info}/RECORD +48 -39
- claude_mpm/agents/templates/agent-manager.md +0 -619
- {claude_mpm-4.3.6.dist-info → claude_mpm-4.3.12.dist-info}/WHEEL +0 -0
- {claude_mpm-4.3.6.dist-info → claude_mpm-4.3.12.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.3.6.dist-info → claude_mpm-4.3.12.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.3.6.dist-info → claude_mpm-4.3.12.dist-info}/top_level.txt +0 -0
|
@@ -18,7 +18,7 @@ import logging
|
|
|
18
18
|
import shutil
|
|
19
19
|
import subprocess
|
|
20
20
|
import sys
|
|
21
|
-
from datetime import datetime
|
|
21
|
+
from datetime import datetime, timezone
|
|
22
22
|
from pathlib import Path
|
|
23
23
|
from typing import Any, Dict, Optional
|
|
24
24
|
|
|
@@ -339,14 +339,27 @@ class StartupStatusLogger:
|
|
|
339
339
|
|
|
340
340
|
def _check_socketio_dependencies(self) -> Dict[str, Any]:
|
|
341
341
|
"""Check if Socket.IO dependencies are available."""
|
|
342
|
+
import importlib.util
|
|
343
|
+
|
|
342
344
|
result = {"available": False, "error": None}
|
|
343
345
|
|
|
344
346
|
try:
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
347
|
+
# Check for socketio dependencies without importing them
|
|
348
|
+
aiohttp_spec = importlib.util.find_spec("aiohttp")
|
|
349
|
+
engineio_spec = importlib.util.find_spec("engineio")
|
|
350
|
+
socketio_spec = importlib.util.find_spec("socketio")
|
|
348
351
|
|
|
349
|
-
|
|
352
|
+
if aiohttp_spec and engineio_spec and socketio_spec:
|
|
353
|
+
result["available"] = True
|
|
354
|
+
else:
|
|
355
|
+
missing = []
|
|
356
|
+
if not aiohttp_spec:
|
|
357
|
+
missing.append("aiohttp")
|
|
358
|
+
if not engineio_spec:
|
|
359
|
+
missing.append("engineio")
|
|
360
|
+
if not socketio_spec:
|
|
361
|
+
missing.append("socketio")
|
|
362
|
+
result["error"] = f"Missing dependencies: {', '.join(missing)}"
|
|
350
363
|
except ImportError as e:
|
|
351
364
|
result["error"] = f"Missing dependencies: {e}"
|
|
352
365
|
except Exception as e:
|
|
@@ -493,7 +506,7 @@ def setup_startup_logging(project_root: Optional[Path] = None) -> Path:
|
|
|
493
506
|
log_dir.mkdir(parents=True, exist_ok=True)
|
|
494
507
|
|
|
495
508
|
# Generate timestamp for log file
|
|
496
|
-
timestamp = datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
|
|
509
|
+
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H-%M-%S")
|
|
497
510
|
log_file = log_dir / f"startup-{timestamp}.log"
|
|
498
511
|
|
|
499
512
|
# Create file handler with detailed formatting
|
|
@@ -516,7 +529,7 @@ def setup_startup_logging(project_root: Optional[Path] = None) -> Path:
|
|
|
516
529
|
# Log startup header
|
|
517
530
|
logger = get_logger("startup")
|
|
518
531
|
logger.info("=" * 60)
|
|
519
|
-
logger.info(f"Claude MPM Startup - {datetime.now().isoformat()}")
|
|
532
|
+
logger.info(f"Claude MPM Startup - {datetime.now(timezone.utc).isoformat()}")
|
|
520
533
|
logger.info(f"Log file: {log_file}")
|
|
521
534
|
logger.info("=" * 60)
|
|
522
535
|
|
claude_mpm/constants.py
CHANGED
|
@@ -460,6 +460,13 @@ class UnifiedAgentRegistry:
|
|
|
460
460
|
if agent_format == AgentFormat.JSON:
|
|
461
461
|
data = json.loads(content)
|
|
462
462
|
|
|
463
|
+
# Ensure data is a dictionary, not a list
|
|
464
|
+
if not isinstance(data, dict):
|
|
465
|
+
logger.warning(
|
|
466
|
+
f"Invalid JSON structure in {file_path}: expected object, got {type(data).__name__}"
|
|
467
|
+
)
|
|
468
|
+
return "", []
|
|
469
|
+
|
|
463
470
|
# Handle local agent JSON templates with metadata structure
|
|
464
471
|
if "metadata" in data:
|
|
465
472
|
metadata = data["metadata"]
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Instruction Reinforcement Hook for PM Delegation Compliance
|
|
3
|
+
|
|
4
|
+
This hook monitors PM behavior for delegation violations and provides
|
|
5
|
+
escalating warnings when the PM attempts to implement instead of delegate.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import re
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from typing import Dict, List, Optional, Tuple
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ViolationType(Enum):
|
|
18
|
+
"""Types of PM delegation violations"""
|
|
19
|
+
|
|
20
|
+
EDIT_ATTEMPT = "Edit/Write/MultiEdit"
|
|
21
|
+
BASH_IMPLEMENTATION = "Bash for implementation"
|
|
22
|
+
FILE_CREATION = "Direct file creation"
|
|
23
|
+
TEST_EXECUTION = "Test execution"
|
|
24
|
+
DEPLOYMENT = "Deployment operation"
|
|
25
|
+
CODE_WRITING = "Code writing"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class Violation:
|
|
30
|
+
"""Record of a delegation violation"""
|
|
31
|
+
|
|
32
|
+
violation_type: ViolationType
|
|
33
|
+
context: str
|
|
34
|
+
timestamp: float
|
|
35
|
+
severity_level: int
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class InstructionReinforcementHook:
|
|
39
|
+
"""
|
|
40
|
+
Monitors PM messages for delegation violations and provides corrective feedback.
|
|
41
|
+
|
|
42
|
+
Circuit breaker pattern implementation to prevent PM from implementing
|
|
43
|
+
instead of delegating work to appropriate agents.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(self):
|
|
47
|
+
self.violation_count = 0
|
|
48
|
+
self.violations: List[Violation] = []
|
|
49
|
+
|
|
50
|
+
# Patterns that indicate PM is attempting forbidden actions
|
|
51
|
+
self.forbidden_patterns = [
|
|
52
|
+
# Direct implementation language - EXPANDED
|
|
53
|
+
(
|
|
54
|
+
r"I'll\s+(fix|create|write|implement|code|build|update|generate|modify|set\s+up|configure|optimize|rewrite|run|test|deploy|push|analyze|review|setup)",
|
|
55
|
+
ViolationType.CODE_WRITING,
|
|
56
|
+
),
|
|
57
|
+
(
|
|
58
|
+
r"I'm\s+(fix|create|write|implement|code|build|update|generate|modify)",
|
|
59
|
+
ViolationType.CODE_WRITING,
|
|
60
|
+
),
|
|
61
|
+
(
|
|
62
|
+
r"Let\s+me\s+(edit|write|modify|create|update|fix|run|execute|commit|refactor|configure|set\s+up|review)",
|
|
63
|
+
ViolationType.EDIT_ATTEMPT,
|
|
64
|
+
),
|
|
65
|
+
(
|
|
66
|
+
r"I\s+will\s+(implement|code|build|create|write|update|fix)",
|
|
67
|
+
ViolationType.CODE_WRITING,
|
|
68
|
+
),
|
|
69
|
+
(
|
|
70
|
+
r"I'm\s+(going\s+to|about\s+to)\s+(fix|create|write|implement|update|modify)",
|
|
71
|
+
ViolationType.CODE_WRITING,
|
|
72
|
+
),
|
|
73
|
+
# Common honeypot phrases
|
|
74
|
+
(
|
|
75
|
+
r"Here's\s+(the|my|an?)\s+(implementation|code|SQL|query|solution|analysis|fix)",
|
|
76
|
+
ViolationType.CODE_WRITING,
|
|
77
|
+
),
|
|
78
|
+
(
|
|
79
|
+
r"The\s+(query|code|implementation|solution)\s+(would\s+be|is)",
|
|
80
|
+
ViolationType.CODE_WRITING,
|
|
81
|
+
),
|
|
82
|
+
(r"I\s+(found|identified)\s+(these\s+)?issues", ViolationType.CODE_WRITING),
|
|
83
|
+
# Deployment and setup patterns
|
|
84
|
+
(
|
|
85
|
+
r"Setting\s+up\s+(the\s+)?(authentication|containers|environment|docker)",
|
|
86
|
+
ViolationType.DEPLOYMENT,
|
|
87
|
+
),
|
|
88
|
+
(r"Deploying\s+(to|the)", ViolationType.DEPLOYMENT),
|
|
89
|
+
(r"I'll\s+(deploy|push|host|launch)", ViolationType.DEPLOYMENT),
|
|
90
|
+
# Tool usage patterns - EXPANDED
|
|
91
|
+
(r"Using\s+(Edit|Write|MultiEdit)\s+tool", ViolationType.EDIT_ATTEMPT),
|
|
92
|
+
(r"<invoke\s+name=\"(Edit|Write|MultiEdit)\"", ViolationType.EDIT_ATTEMPT),
|
|
93
|
+
(
|
|
94
|
+
r"Running\s+(bash\s+command|git\s+commit|npm|yarn|python|node|go|tests|pytest)",
|
|
95
|
+
ViolationType.BASH_IMPLEMENTATION,
|
|
96
|
+
),
|
|
97
|
+
(r"Executing\s+(tests|test\s+suite|pytest)", ViolationType.TEST_EXECUTION),
|
|
98
|
+
# Testing patterns - EXPANDED
|
|
99
|
+
(
|
|
100
|
+
r"(Testing|I'll\s+test|Let\s+me\s+test)\s+(the\s+)?(payment|API|endpoint)",
|
|
101
|
+
ViolationType.TEST_EXECUTION,
|
|
102
|
+
),
|
|
103
|
+
(
|
|
104
|
+
r"I'll\s+(run|execute|verify)\s+(the\s+)?(tests|test\s+suite|endpoint)",
|
|
105
|
+
ViolationType.TEST_EXECUTION,
|
|
106
|
+
),
|
|
107
|
+
(r"pytest|npm\s+test|yarn\s+test|go\s+test", ViolationType.TEST_EXECUTION),
|
|
108
|
+
# File operation patterns - EXPANDED
|
|
109
|
+
(
|
|
110
|
+
r"Creating\s+(new\s+|a\s+|the\s+)?(file|YAML|README|workflow)",
|
|
111
|
+
ViolationType.FILE_CREATION,
|
|
112
|
+
),
|
|
113
|
+
(r"Writing\s+to\s+file", ViolationType.FILE_CREATION),
|
|
114
|
+
(
|
|
115
|
+
r"Updating\s+(the\s+)?(code|component|queries)",
|
|
116
|
+
ViolationType.CODE_WRITING,
|
|
117
|
+
),
|
|
118
|
+
(
|
|
119
|
+
r"I'll\s+(update|modify)\s+(the\s+)?(component|code|React)",
|
|
120
|
+
ViolationType.CODE_WRITING,
|
|
121
|
+
),
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
# Patterns for correct delegation behavior - EXPANDED
|
|
125
|
+
self.delegation_patterns = [
|
|
126
|
+
r"delegat(e|ing)\s+(to|this)",
|
|
127
|
+
r"Task\s+tool",
|
|
128
|
+
r"(asking|request|have|use)\s+\w+\s+agent",
|
|
129
|
+
r"requesting\s+\w+\s+to",
|
|
130
|
+
r"will\s+(have|ask|request)\s+\w+\s+agent",
|
|
131
|
+
r"I'll\s+(have|ask|request|delegate)",
|
|
132
|
+
r"the\s+\w+\s+agent\s+(will|can|should)",
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
def detect_violation_intent(
|
|
136
|
+
self, message: str
|
|
137
|
+
) -> Optional[Tuple[ViolationType, str]]:
|
|
138
|
+
"""
|
|
139
|
+
Check message for patterns indicating PM violation intent.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
message: The PM's message to analyze
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Tuple of (ViolationType, matched_text) if violation detected, None otherwise
|
|
146
|
+
"""
|
|
147
|
+
message_lower = message.lower()
|
|
148
|
+
|
|
149
|
+
# Check for forbidden patterns
|
|
150
|
+
for pattern, violation_type in self.forbidden_patterns:
|
|
151
|
+
match = re.search(pattern, message_lower, re.IGNORECASE)
|
|
152
|
+
if match:
|
|
153
|
+
# Check if this is actually a delegation (false positive check)
|
|
154
|
+
is_delegation = any(
|
|
155
|
+
re.search(
|
|
156
|
+
del_pattern,
|
|
157
|
+
message_lower[max(0, match.start() - 50) : match.end() + 50],
|
|
158
|
+
)
|
|
159
|
+
for del_pattern in self.delegation_patterns
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
if not is_delegation:
|
|
163
|
+
return (violation_type, match.group(0))
|
|
164
|
+
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
def escalate_warning(self) -> str:
|
|
168
|
+
"""
|
|
169
|
+
Generate escalating warning message based on violation count.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
Warning message with appropriate severity
|
|
173
|
+
"""
|
|
174
|
+
if self.violation_count == 1:
|
|
175
|
+
return (
|
|
176
|
+
"⚠️ DELEGATION REMINDER: PM must delegate ALL implementation work.\n"
|
|
177
|
+
"Use the Task tool to delegate to the appropriate agent."
|
|
178
|
+
)
|
|
179
|
+
if self.violation_count == 2:
|
|
180
|
+
return (
|
|
181
|
+
"🚨 DELEGATION WARNING: Critical PM violation detected!\n"
|
|
182
|
+
"You MUST delegate implementation work. Do NOT use Edit/Write/Bash for implementation.\n"
|
|
183
|
+
"Next violation will result in session failure."
|
|
184
|
+
)
|
|
185
|
+
if self.violation_count == 3:
|
|
186
|
+
return (
|
|
187
|
+
"❌ CRITICAL DELEGATION FAILURE: Multiple PM violations detected.\n"
|
|
188
|
+
"PM has repeatedly attempted to implement instead of delegate.\n"
|
|
189
|
+
"Session integrity compromised. All work must be delegated to agents."
|
|
190
|
+
)
|
|
191
|
+
return (
|
|
192
|
+
f"❌❌❌ SEVERE VIOLATION (Count: {self.violation_count}): PM continues to violate delegation rules.\n"
|
|
193
|
+
"MANDATORY: Use Task tool to delegate ALL implementation to appropriate agents.\n"
|
|
194
|
+
"Current session may need to be terminated and restarted."
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
def check_message(self, message: str) -> Optional[Dict[str, any]]:
|
|
198
|
+
"""
|
|
199
|
+
Check a PM message for violations and return feedback if needed.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
message: The PM's message to check
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
Dictionary with violation details and correction, or None if compliant
|
|
206
|
+
"""
|
|
207
|
+
violation_result = self.detect_violation_intent(message)
|
|
208
|
+
|
|
209
|
+
if violation_result:
|
|
210
|
+
violation_type, context = violation_result
|
|
211
|
+
self.violation_count += 1
|
|
212
|
+
|
|
213
|
+
# Record the violation
|
|
214
|
+
import time
|
|
215
|
+
|
|
216
|
+
violation = Violation(
|
|
217
|
+
violation_type=violation_type,
|
|
218
|
+
context=context,
|
|
219
|
+
timestamp=time.time(),
|
|
220
|
+
severity_level=min(self.violation_count, 4),
|
|
221
|
+
)
|
|
222
|
+
self.violations.append(violation)
|
|
223
|
+
|
|
224
|
+
# Generate corrective feedback
|
|
225
|
+
warning = self.escalate_warning()
|
|
226
|
+
|
|
227
|
+
# Determine which agent should handle this
|
|
228
|
+
agent_mapping = {
|
|
229
|
+
ViolationType.EDIT_ATTEMPT: "Engineer",
|
|
230
|
+
ViolationType.CODE_WRITING: "Engineer",
|
|
231
|
+
ViolationType.BASH_IMPLEMENTATION: "Engineer or Ops",
|
|
232
|
+
ViolationType.FILE_CREATION: "Engineer or Documentation",
|
|
233
|
+
ViolationType.TEST_EXECUTION: "QA",
|
|
234
|
+
ViolationType.DEPLOYMENT: "Ops",
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
suggested_agent = agent_mapping.get(violation_type, "appropriate agent")
|
|
238
|
+
|
|
239
|
+
# Clean up context for task suggestion
|
|
240
|
+
clean_context = (
|
|
241
|
+
context.replace("I will ", "")
|
|
242
|
+
.replace("I'll ", "")
|
|
243
|
+
.replace("Let me ", "")
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
"violation_detected": True,
|
|
248
|
+
"violation_count": self.violation_count,
|
|
249
|
+
"violation_type": violation_type.value,
|
|
250
|
+
"context": context,
|
|
251
|
+
"warning": warning,
|
|
252
|
+
"correction": f"MUST delegate to {suggested_agent} using Task tool",
|
|
253
|
+
"suggested_task": f"Task: Please {clean_context}",
|
|
254
|
+
"severity": min(self.violation_count, 4),
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return None
|
|
258
|
+
|
|
259
|
+
def get_violation_summary(self) -> Dict[str, any]:
|
|
260
|
+
"""
|
|
261
|
+
Get a summary of all violations in the session.
|
|
262
|
+
|
|
263
|
+
Returns:
|
|
264
|
+
Dictionary with violation statistics and details
|
|
265
|
+
"""
|
|
266
|
+
if not self.violations:
|
|
267
|
+
return {
|
|
268
|
+
"total_violations": 0,
|
|
269
|
+
"status": "COMPLIANT",
|
|
270
|
+
"message": "No PM delegation violations detected",
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
violation_types = {}
|
|
274
|
+
for v in self.violations:
|
|
275
|
+
vtype = v.violation_type.value
|
|
276
|
+
violation_types[vtype] = violation_types.get(vtype, 0) + 1
|
|
277
|
+
|
|
278
|
+
status = "WARNING" if self.violation_count < 3 else "CRITICAL"
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
"total_violations": self.violation_count,
|
|
282
|
+
"status": status,
|
|
283
|
+
"violation_types": violation_types,
|
|
284
|
+
"most_recent": self.violations[-1].context if self.violations else None,
|
|
285
|
+
"recommendation": (
|
|
286
|
+
"Review PM delegation training"
|
|
287
|
+
if self.violation_count > 2
|
|
288
|
+
else "Continue monitoring"
|
|
289
|
+
),
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
def reset(self):
|
|
293
|
+
"""Reset violation tracking for a new session"""
|
|
294
|
+
self.violation_count = 0
|
|
295
|
+
self.violations = []
|
|
@@ -776,16 +776,6 @@ class AgentDeploymentService(ConfigServiceBase, AgentDeploymentInterface):
|
|
|
776
776
|
agent_sources=agent_sources,
|
|
777
777
|
)
|
|
778
778
|
|
|
779
|
-
# Log version upgrades and source changes
|
|
780
|
-
if comparison_results.get("version_upgrades"):
|
|
781
|
-
self.logger.info(
|
|
782
|
-
f"Version upgrades available for {len(comparison_results['version_upgrades'])} agents"
|
|
783
|
-
)
|
|
784
|
-
if comparison_results.get("source_changes"):
|
|
785
|
-
self.logger.info(
|
|
786
|
-
f"Source changes for {len(comparison_results['source_changes'])} agents"
|
|
787
|
-
)
|
|
788
|
-
|
|
789
779
|
# Filter agents based on comparison results (unless force_rebuild is set)
|
|
790
780
|
if not force_rebuild:
|
|
791
781
|
# Only deploy agents that need updates or are new
|
|
@@ -804,11 +794,36 @@ class AgentDeploymentService(ConfigServiceBase, AgentDeploymentInterface):
|
|
|
804
794
|
for name, path in agents_to_deploy.items()
|
|
805
795
|
if name in agents_needing_update
|
|
806
796
|
}
|
|
797
|
+
|
|
798
|
+
# Only log upgrade messages if we're actually going to deploy them
|
|
799
|
+
if filtered_agents and comparison_results.get("version_upgrades"):
|
|
800
|
+
# Filter upgrades to only those actually being deployed
|
|
801
|
+
deployed_upgrades = [
|
|
802
|
+
upgrade for upgrade in comparison_results["version_upgrades"]
|
|
803
|
+
if upgrade["name"] in filtered_agents
|
|
804
|
+
]
|
|
805
|
+
|
|
806
|
+
if deployed_upgrades:
|
|
807
|
+
self.logger.info(
|
|
808
|
+
f"Deploying {len(deployed_upgrades)} agent upgrade(s):"
|
|
809
|
+
)
|
|
810
|
+
for upgrade in deployed_upgrades:
|
|
811
|
+
self.logger.info(
|
|
812
|
+
f" Upgrading: {upgrade['name']} "
|
|
813
|
+
f"{upgrade['deployed_version']} -> {upgrade['new_version']} "
|
|
814
|
+
f"(from {upgrade['source']})"
|
|
815
|
+
)
|
|
816
|
+
|
|
807
817
|
agents_to_deploy = filtered_agents
|
|
808
818
|
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
819
|
+
if agents_to_deploy:
|
|
820
|
+
self.logger.info(
|
|
821
|
+
f"Deploying {len(agents_to_deploy)} agents that need updates"
|
|
822
|
+
)
|
|
823
|
+
else:
|
|
824
|
+
self.logger.debug(
|
|
825
|
+
f"All {len(comparison_results.get('up_to_date', []))} agents are up to date"
|
|
826
|
+
)
|
|
812
827
|
|
|
813
828
|
# Convert to list of Path objects
|
|
814
829
|
template_files = list(agents_to_deploy.values())
|
|
@@ -216,25 +216,35 @@ class AgentDiscoveryService:
|
|
|
216
216
|
metadata = template_data.get("metadata", {})
|
|
217
217
|
capabilities = template_data.get("capabilities", {})
|
|
218
218
|
|
|
219
|
+
# Handle capabilities as either dict or list
|
|
220
|
+
if isinstance(capabilities, list):
|
|
221
|
+
# If capabilities is a list (like in php-engineer.json), treat it as capabilities list
|
|
222
|
+
tools_list = template_data.get("tools", []) # Look for tools at root level
|
|
223
|
+
model_value = template_data.get("model", "sonnet")
|
|
224
|
+
else:
|
|
225
|
+
# If capabilities is a dict, extract tools and model from it
|
|
226
|
+
tools_list = capabilities.get("tools", [])
|
|
227
|
+
model_value = capabilities.get("model", "sonnet")
|
|
228
|
+
|
|
219
229
|
agent_info = {
|
|
220
230
|
"name": metadata.get("name", template_file.stem),
|
|
221
|
-
"description": metadata.get("description", "No description available"),
|
|
231
|
+
"description": metadata.get("description", template_data.get("description", "No description available")),
|
|
222
232
|
"type": template_data.get(
|
|
223
|
-
"agent_type", metadata.get("category", "agent")
|
|
233
|
+
"agent_type", metadata.get("category", template_data.get("category", "agent"))
|
|
224
234
|
), # Extract agent type
|
|
225
235
|
"version": template_data.get(
|
|
226
236
|
"agent_version",
|
|
227
237
|
template_data.get("version", metadata.get("version", "1.0.0")),
|
|
228
238
|
),
|
|
229
|
-
"tools":
|
|
239
|
+
"tools": tools_list,
|
|
230
240
|
"specializations": metadata.get(
|
|
231
|
-
"tags", []
|
|
232
|
-
), # Use tags as specializations
|
|
241
|
+
"tags", template_data.get("tags", [])
|
|
242
|
+
), # Use tags as specializations, fallback to root-level tags
|
|
233
243
|
"file": template_file.name,
|
|
234
244
|
"path": str(template_file),
|
|
235
245
|
"file_path": str(template_file), # Keep for backward compatibility
|
|
236
246
|
"size": template_file.stat().st_size,
|
|
237
|
-
"model":
|
|
247
|
+
"model": model_value,
|
|
238
248
|
"author": metadata.get("author", "unknown"),
|
|
239
249
|
}
|
|
240
250
|
|
|
@@ -69,3 +69,62 @@ class DeploymentServiceWrapper:
|
|
|
69
69
|
"errors": [],
|
|
70
70
|
"target_dir": str(project_dir),
|
|
71
71
|
}
|
|
72
|
+
|
|
73
|
+
def get_agent_details(self, agent_name: str) -> Dict[str, Any]:
|
|
74
|
+
"""Get detailed information for a specific agent.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
agent_name: Name of the agent
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Agent details dictionary or empty dict if not found
|
|
81
|
+
"""
|
|
82
|
+
try:
|
|
83
|
+
# Try to get from list of available agents
|
|
84
|
+
available_agents = self.service.list_available_agents()
|
|
85
|
+
for agent in available_agents:
|
|
86
|
+
if agent.get("name") == agent_name:
|
|
87
|
+
# Get template path for the agent
|
|
88
|
+
templates_dir = self.service.templates_dir
|
|
89
|
+
agent_path = templates_dir / f"{agent_name}.md"
|
|
90
|
+
|
|
91
|
+
# Read agent content if file exists
|
|
92
|
+
if agent_path.exists():
|
|
93
|
+
with open(agent_path) as f:
|
|
94
|
+
content = f.read()
|
|
95
|
+
|
|
96
|
+
# Parse metadata from content
|
|
97
|
+
import yaml
|
|
98
|
+
|
|
99
|
+
metadata = {}
|
|
100
|
+
if content.startswith("---"):
|
|
101
|
+
# Extract frontmatter
|
|
102
|
+
parts = content.split("---", 2)
|
|
103
|
+
if len(parts) >= 2:
|
|
104
|
+
try:
|
|
105
|
+
metadata = yaml.safe_load(parts[1])
|
|
106
|
+
except yaml.YAMLError:
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
"name": agent_name,
|
|
111
|
+
"path": str(agent_path),
|
|
112
|
+
"type": agent.get("type", "agent"),
|
|
113
|
+
"version": metadata.get("version", "1.0.0"),
|
|
114
|
+
"description": metadata.get("description", ""),
|
|
115
|
+
"specializations": metadata.get("specializations", []),
|
|
116
|
+
"metadata": metadata,
|
|
117
|
+
"content": content,
|
|
118
|
+
"exists": True,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
# Agent not found in available agents
|
|
122
|
+
return {
|
|
123
|
+
"name": agent_name,
|
|
124
|
+
"exists": False,
|
|
125
|
+
"error": f"Agent '{agent_name}' not found",
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
except Exception as e:
|
|
129
|
+
# Return error information
|
|
130
|
+
return {"name": agent_name, "exists": False, "error": str(e)}
|
|
@@ -742,18 +742,20 @@ class MultiSourceAgentDeploymentService:
|
|
|
742
742
|
|
|
743
743
|
self.logger.info(f"Version comparison complete: {', '.join(summary_parts)}")
|
|
744
744
|
|
|
745
|
+
# Don't log upgrades here - let the caller decide when to log
|
|
746
|
+
# This prevents repeated upgrade messages on every startup
|
|
745
747
|
if comparison_results["version_upgrades"]:
|
|
746
748
|
for upgrade in comparison_results["version_upgrades"]:
|
|
747
|
-
self.logger.
|
|
748
|
-
f" Upgrade: {upgrade['name']} "
|
|
749
|
+
self.logger.debug(
|
|
750
|
+
f" Upgrade available: {upgrade['name']} "
|
|
749
751
|
f"{upgrade['deployed_version']} -> {upgrade['new_version']} "
|
|
750
752
|
f"(from {upgrade['source']})"
|
|
751
753
|
)
|
|
752
754
|
|
|
753
755
|
if comparison_results["source_changes"]:
|
|
754
756
|
for change in comparison_results["source_changes"]:
|
|
755
|
-
self.logger.
|
|
756
|
-
f" Source change: {change['name']} "
|
|
757
|
+
self.logger.debug(
|
|
758
|
+
f" Source change available: {change['name']} "
|
|
757
759
|
f"from {change['from_source']} to {change['to_source']}"
|
|
758
760
|
)
|
|
759
761
|
|
|
@@ -138,6 +138,11 @@ class AgentCleanupService(IAgentCleanupService):
|
|
|
138
138
|
if not isinstance(result, dict):
|
|
139
139
|
result = {"success": bool(result)}
|
|
140
140
|
|
|
141
|
+
# Add success flag based on whether there were errors
|
|
142
|
+
if "success" not in result:
|
|
143
|
+
# Consider it successful if no errors occurred
|
|
144
|
+
result["success"] = not bool(result.get("errors"))
|
|
145
|
+
|
|
141
146
|
# Add cleaned_count for backward compatibility
|
|
142
147
|
if "cleaned_count" not in result:
|
|
143
148
|
removed_count = len(result.get("removed", []))
|