claude-mpm 4.0.25__py3-none-any.whl → 4.0.29__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/templates/agent-manager.json +24 -0
- claude_mpm/agents/templates/agent-manager.md +304 -0
- claude_mpm/cli/__init__.py +2 -0
- claude_mpm/cli/commands/__init__.py +2 -0
- claude_mpm/cli/commands/agent_manager.py +517 -0
- claude_mpm/cli/commands/memory.py +1 -1
- claude_mpm/cli/parsers/agent_manager_parser.py +247 -0
- claude_mpm/cli/parsers/base_parser.py +7 -0
- claude_mpm/cli/shared/__init__.py +1 -1
- claude_mpm/constants.py +1 -0
- claude_mpm/core/claude_runner.py +3 -2
- claude_mpm/core/constants.py +2 -2
- claude_mpm/core/socketio_pool.py +2 -2
- claude_mpm/dashboard/static/built/components/event-viewer.js +1 -1
- claude_mpm/dashboard/static/built/components/module-viewer.js +1 -1
- claude_mpm/dashboard/static/built/dashboard.js +1 -1
- claude_mpm/dashboard/static/built/socket-client.js +1 -1
- claude_mpm/dashboard/static/css/dashboard.css +170 -0
- claude_mpm/dashboard/static/dist/components/module-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/dashboard.js +1 -1
- claude_mpm/dashboard/static/dist/socket-client.js +1 -1
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +21 -3
- claude_mpm/dashboard/static/js/components/module-viewer.js +129 -1
- claude_mpm/dashboard/static/js/dashboard.js +116 -0
- claude_mpm/dashboard/static/js/socket-client.js +0 -1
- claude_mpm/hooks/claude_hooks/connection_pool.py +1 -1
- claude_mpm/hooks/claude_hooks/hook_handler.py +1 -1
- claude_mpm/services/agents/agent_builder.py +455 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +7 -1
- claude_mpm/services/agents/deployment/agent_template_builder.py +10 -3
- claude_mpm/services/memory/__init__.py +2 -0
- claude_mpm/services/socketio/handlers/connection.py +27 -33
- {claude_mpm-4.0.25.dist-info → claude_mpm-4.0.29.dist-info}/METADATA +1 -1
- {claude_mpm-4.0.25.dist-info → claude_mpm-4.0.29.dist-info}/RECORD +40 -35
- /claude_mpm/cli/shared/{command_base.py → base_command.py} +0 -0
- {claude_mpm-4.0.25.dist-info → claude_mpm-4.0.29.dist-info}/WHEEL +0 -0
- {claude_mpm-4.0.25.dist-info → claude_mpm-4.0.29.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.0.25.dist-info → claude_mpm-4.0.29.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.0.25.dist-info → claude_mpm-4.0.29.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
"""Agent Builder Service for programmatic agent creation and management.
|
|
2
|
+
|
|
3
|
+
This service provides comprehensive agent lifecycle management including:
|
|
4
|
+
- Template-based agent generation
|
|
5
|
+
- Agent variant creation with inheritance
|
|
6
|
+
- Configuration validation and sanitization
|
|
7
|
+
- PM instruction customization
|
|
8
|
+
- Integration with deployment services
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import logging
|
|
13
|
+
import re
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
17
|
+
|
|
18
|
+
from claude_mpm.core.exceptions import AgentDeploymentError
|
|
19
|
+
from claude_mpm.core.logging_config import get_logger
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AgentBuilderService:
|
|
23
|
+
"""Service for building and managing agent configurations."""
|
|
24
|
+
|
|
25
|
+
# Valid agent models
|
|
26
|
+
VALID_MODELS = ["sonnet", "opus", "haiku"]
|
|
27
|
+
|
|
28
|
+
# Valid tool choices
|
|
29
|
+
VALID_TOOL_CHOICES = ["auto", "required", "any", "none"]
|
|
30
|
+
|
|
31
|
+
# Agent categories
|
|
32
|
+
AGENT_CATEGORIES = [
|
|
33
|
+
"engineering", "qa", "documentation", "ops",
|
|
34
|
+
"research", "security", "system", "utility"
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
def __init__(self, templates_dir: Optional[Path] = None):
|
|
38
|
+
"""Initialize the Agent Builder Service.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
templates_dir: Path to agent templates directory
|
|
42
|
+
"""
|
|
43
|
+
self.logger = get_logger(__name__)
|
|
44
|
+
self.templates_dir = templates_dir or Path(__file__).parent.parent.parent / "agents" / "templates"
|
|
45
|
+
self._template_cache = {}
|
|
46
|
+
|
|
47
|
+
def create_agent(
|
|
48
|
+
self,
|
|
49
|
+
agent_id: str,
|
|
50
|
+
name: str,
|
|
51
|
+
description: str,
|
|
52
|
+
model: str = "sonnet",
|
|
53
|
+
tool_choice: str = "auto",
|
|
54
|
+
instructions: Optional[str] = None,
|
|
55
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
56
|
+
base_template: Optional[str] = None
|
|
57
|
+
) -> Dict[str, Any]:
|
|
58
|
+
"""Create a new agent configuration.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
agent_id: Unique identifier for the agent
|
|
62
|
+
name: Display name for the agent
|
|
63
|
+
description: Agent purpose and capabilities
|
|
64
|
+
model: LLM model to use (sonnet/opus/haiku)
|
|
65
|
+
tool_choice: Tool selection strategy
|
|
66
|
+
instructions: Markdown instructions content
|
|
67
|
+
metadata: Additional agent metadata
|
|
68
|
+
base_template: Optional base template to extend
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Complete agent configuration dictionary
|
|
72
|
+
|
|
73
|
+
Raises:
|
|
74
|
+
AgentDeploymentError: If validation fails
|
|
75
|
+
"""
|
|
76
|
+
# Validate inputs
|
|
77
|
+
self._validate_agent_id(agent_id)
|
|
78
|
+
self._validate_model(model)
|
|
79
|
+
self._validate_tool_choice(tool_choice)
|
|
80
|
+
|
|
81
|
+
# Start with base template if provided
|
|
82
|
+
if base_template:
|
|
83
|
+
config = self._load_template(base_template)
|
|
84
|
+
config["id"] = agent_id # Override ID
|
|
85
|
+
else:
|
|
86
|
+
config = {}
|
|
87
|
+
|
|
88
|
+
# Build agent configuration
|
|
89
|
+
config.update({
|
|
90
|
+
"id": agent_id,
|
|
91
|
+
"name": name,
|
|
92
|
+
"prompt": f"{agent_id}.md",
|
|
93
|
+
"model": model,
|
|
94
|
+
"tool_choice": tool_choice
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
# Build metadata
|
|
98
|
+
agent_metadata = {
|
|
99
|
+
"description": description,
|
|
100
|
+
"version": "1.0.0",
|
|
101
|
+
"created": datetime.now().isoformat(),
|
|
102
|
+
"author": "Agent Manager",
|
|
103
|
+
"category": "custom"
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if metadata:
|
|
107
|
+
agent_metadata.update(metadata)
|
|
108
|
+
|
|
109
|
+
config["metadata"] = agent_metadata
|
|
110
|
+
|
|
111
|
+
# Generate instructions if not provided
|
|
112
|
+
if instructions is None:
|
|
113
|
+
instructions = self._generate_default_instructions(agent_id, name, description)
|
|
114
|
+
|
|
115
|
+
return config, instructions
|
|
116
|
+
|
|
117
|
+
def create_variant(
|
|
118
|
+
self,
|
|
119
|
+
base_agent_id: str,
|
|
120
|
+
variant_id: str,
|
|
121
|
+
variant_name: str,
|
|
122
|
+
modifications: Dict[str, Any],
|
|
123
|
+
instructions_append: Optional[str] = None
|
|
124
|
+
) -> Tuple[Dict[str, Any], str]:
|
|
125
|
+
"""Create an agent variant based on an existing agent.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
base_agent_id: ID of the base agent to extend
|
|
129
|
+
variant_id: Unique ID for the variant
|
|
130
|
+
variant_name: Display name for the variant
|
|
131
|
+
modifications: Configuration changes for the variant
|
|
132
|
+
instructions_append: Additional instructions to append
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Tuple of (variant configuration, variant instructions)
|
|
136
|
+
|
|
137
|
+
Raises:
|
|
138
|
+
AgentDeploymentError: If base agent not found or validation fails
|
|
139
|
+
"""
|
|
140
|
+
# Load base agent
|
|
141
|
+
base_config = self._load_template(base_agent_id)
|
|
142
|
+
base_instructions = self._load_instructions(base_agent_id)
|
|
143
|
+
|
|
144
|
+
# Validate variant ID
|
|
145
|
+
self._validate_agent_id(variant_id)
|
|
146
|
+
|
|
147
|
+
# Create variant configuration
|
|
148
|
+
variant_config = base_config.copy()
|
|
149
|
+
variant_config["id"] = variant_id
|
|
150
|
+
variant_config["name"] = variant_name
|
|
151
|
+
variant_config["prompt"] = f"{variant_id}.md"
|
|
152
|
+
|
|
153
|
+
# Apply modifications
|
|
154
|
+
for key, value in modifications.items():
|
|
155
|
+
if key in ["model", "tool_choice"]:
|
|
156
|
+
if key == "model":
|
|
157
|
+
self._validate_model(value)
|
|
158
|
+
elif key == "tool_choice":
|
|
159
|
+
self._validate_tool_choice(value)
|
|
160
|
+
variant_config[key] = value
|
|
161
|
+
|
|
162
|
+
# Update metadata
|
|
163
|
+
if "metadata" not in variant_config:
|
|
164
|
+
variant_config["metadata"] = {}
|
|
165
|
+
|
|
166
|
+
variant_config["metadata"].update({
|
|
167
|
+
"base_agent": base_agent_id,
|
|
168
|
+
"variant": True,
|
|
169
|
+
"variant_created": datetime.now().isoformat()
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
# Build variant instructions
|
|
173
|
+
variant_instructions = f"# {variant_name} (Variant of {base_config.get('name', base_agent_id)})\n\n"
|
|
174
|
+
variant_instructions += base_instructions
|
|
175
|
+
|
|
176
|
+
if instructions_append:
|
|
177
|
+
variant_instructions += f"\n\n## Variant-Specific Instructions\n\n{instructions_append}"
|
|
178
|
+
|
|
179
|
+
return variant_config, variant_instructions
|
|
180
|
+
|
|
181
|
+
def validate_configuration(self, config: Dict[str, Any]) -> List[str]:
|
|
182
|
+
"""Validate an agent configuration.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
config: Agent configuration to validate
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
List of validation errors (empty if valid)
|
|
189
|
+
"""
|
|
190
|
+
errors = []
|
|
191
|
+
|
|
192
|
+
# Required fields
|
|
193
|
+
required_fields = ["id", "name", "prompt", "model"]
|
|
194
|
+
for field in required_fields:
|
|
195
|
+
if field not in config:
|
|
196
|
+
errors.append(f"Missing required field: {field}")
|
|
197
|
+
|
|
198
|
+
# Validate ID
|
|
199
|
+
if "id" in config:
|
|
200
|
+
try:
|
|
201
|
+
self._validate_agent_id(config["id"])
|
|
202
|
+
except AgentDeploymentError as e:
|
|
203
|
+
errors.append(str(e))
|
|
204
|
+
|
|
205
|
+
# Validate model
|
|
206
|
+
if "model" in config:
|
|
207
|
+
try:
|
|
208
|
+
self._validate_model(config["model"])
|
|
209
|
+
except AgentDeploymentError as e:
|
|
210
|
+
errors.append(str(e))
|
|
211
|
+
|
|
212
|
+
# Validate tool_choice
|
|
213
|
+
if "tool_choice" in config:
|
|
214
|
+
try:
|
|
215
|
+
self._validate_tool_choice(config["tool_choice"])
|
|
216
|
+
except AgentDeploymentError as e:
|
|
217
|
+
errors.append(str(e))
|
|
218
|
+
|
|
219
|
+
# Validate metadata
|
|
220
|
+
if "metadata" in config:
|
|
221
|
+
if not isinstance(config["metadata"], dict):
|
|
222
|
+
errors.append("Metadata must be a dictionary")
|
|
223
|
+
|
|
224
|
+
return errors
|
|
225
|
+
|
|
226
|
+
def generate_pm_instructions(
|
|
227
|
+
self,
|
|
228
|
+
delegation_patterns: Optional[List[str]] = None,
|
|
229
|
+
workflow_overrides: Optional[Dict[str, str]] = None,
|
|
230
|
+
custom_rules: Optional[List[str]] = None
|
|
231
|
+
) -> str:
|
|
232
|
+
"""Generate customized PM instructions.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
delegation_patterns: Custom delegation patterns
|
|
236
|
+
workflow_overrides: Workflow sequence modifications
|
|
237
|
+
custom_rules: Additional PM rules
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
Customized PM instructions markdown
|
|
241
|
+
"""
|
|
242
|
+
instructions = "# Custom PM Instructions\n\n"
|
|
243
|
+
|
|
244
|
+
if delegation_patterns:
|
|
245
|
+
instructions += "## Custom Delegation Patterns\n\n"
|
|
246
|
+
for pattern in delegation_patterns:
|
|
247
|
+
instructions += f"- {pattern}\n"
|
|
248
|
+
instructions += "\n"
|
|
249
|
+
|
|
250
|
+
if workflow_overrides:
|
|
251
|
+
instructions += "## Workflow Overrides\n\n"
|
|
252
|
+
for workflow, override in workflow_overrides.items():
|
|
253
|
+
instructions += f"### {workflow}\n{override}\n\n"
|
|
254
|
+
|
|
255
|
+
if custom_rules:
|
|
256
|
+
instructions += "## Additional Rules\n\n"
|
|
257
|
+
for rule in custom_rules:
|
|
258
|
+
instructions += f"- {rule}\n"
|
|
259
|
+
|
|
260
|
+
return instructions
|
|
261
|
+
|
|
262
|
+
def list_available_templates(self) -> List[Dict[str, Any]]:
|
|
263
|
+
"""List all available agent templates.
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
List of template metadata dictionaries
|
|
267
|
+
"""
|
|
268
|
+
templates = []
|
|
269
|
+
|
|
270
|
+
if not self.templates_dir.exists():
|
|
271
|
+
return templates
|
|
272
|
+
|
|
273
|
+
for template_file in self.templates_dir.glob("*.json"):
|
|
274
|
+
try:
|
|
275
|
+
with open(template_file, 'r') as f:
|
|
276
|
+
config = json.load(f)
|
|
277
|
+
|
|
278
|
+
# Use filename stem as ID if not specified in config
|
|
279
|
+
template_id = config.get("id") or template_file.stem
|
|
280
|
+
|
|
281
|
+
templates.append({
|
|
282
|
+
"id": template_id,
|
|
283
|
+
"name": config.get("name", template_id),
|
|
284
|
+
"description": config.get("metadata", {}).get("description"),
|
|
285
|
+
"category": config.get("metadata", {}).get("category"),
|
|
286
|
+
"version": config.get("metadata", {}).get("version"),
|
|
287
|
+
"file": str(template_file)
|
|
288
|
+
})
|
|
289
|
+
except Exception as e:
|
|
290
|
+
self.logger.warning(f"Failed to load template {template_file}: {e}")
|
|
291
|
+
|
|
292
|
+
return templates
|
|
293
|
+
|
|
294
|
+
def _validate_agent_id(self, agent_id: str) -> None:
|
|
295
|
+
"""Validate agent ID format.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
agent_id: Agent ID to validate
|
|
299
|
+
|
|
300
|
+
Raises:
|
|
301
|
+
AgentDeploymentError: If ID is invalid
|
|
302
|
+
"""
|
|
303
|
+
if not agent_id:
|
|
304
|
+
raise AgentDeploymentError("Agent ID cannot be empty")
|
|
305
|
+
|
|
306
|
+
if len(agent_id) > 50:
|
|
307
|
+
raise AgentDeploymentError("Agent ID must be 50 characters or less")
|
|
308
|
+
|
|
309
|
+
if not re.match(r'^[a-z0-9-]+$', agent_id):
|
|
310
|
+
raise AgentDeploymentError(
|
|
311
|
+
"Agent ID must contain only lowercase letters, numbers, and hyphens"
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
def _validate_model(self, model: str) -> None:
|
|
315
|
+
"""Validate model selection.
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
model: Model to validate
|
|
319
|
+
|
|
320
|
+
Raises:
|
|
321
|
+
AgentDeploymentError: If model is invalid
|
|
322
|
+
"""
|
|
323
|
+
if model not in self.VALID_MODELS:
|
|
324
|
+
raise AgentDeploymentError(
|
|
325
|
+
f"Invalid model '{model}'. Must be one of: {', '.join(self.VALID_MODELS)}"
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
def _validate_tool_choice(self, tool_choice: str) -> None:
|
|
329
|
+
"""Validate tool choice setting.
|
|
330
|
+
|
|
331
|
+
Args:
|
|
332
|
+
tool_choice: Tool choice to validate
|
|
333
|
+
|
|
334
|
+
Raises:
|
|
335
|
+
AgentDeploymentError: If tool choice is invalid
|
|
336
|
+
"""
|
|
337
|
+
if tool_choice not in self.VALID_TOOL_CHOICES:
|
|
338
|
+
raise AgentDeploymentError(
|
|
339
|
+
f"Invalid tool_choice '{tool_choice}'. Must be one of: {', '.join(self.VALID_TOOL_CHOICES)}"
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
def _load_template(self, template_id: str) -> Dict[str, Any]:
|
|
343
|
+
"""Load an agent template.
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
template_id: Template ID to load
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
Template configuration dictionary
|
|
350
|
+
|
|
351
|
+
Raises:
|
|
352
|
+
AgentDeploymentError: If template not found
|
|
353
|
+
"""
|
|
354
|
+
if template_id in self._template_cache:
|
|
355
|
+
return self._template_cache[template_id].copy()
|
|
356
|
+
|
|
357
|
+
template_file = self.templates_dir / f"{template_id}.json"
|
|
358
|
+
|
|
359
|
+
if not template_file.exists():
|
|
360
|
+
raise AgentDeploymentError(f"Template '{template_id}' not found")
|
|
361
|
+
|
|
362
|
+
try:
|
|
363
|
+
with open(template_file, 'r') as f:
|
|
364
|
+
config = json.load(f)
|
|
365
|
+
self._template_cache[template_id] = config
|
|
366
|
+
return config.copy()
|
|
367
|
+
except Exception as e:
|
|
368
|
+
raise AgentDeploymentError(f"Failed to load template '{template_id}': {e}")
|
|
369
|
+
|
|
370
|
+
def _load_instructions(self, agent_id: str) -> str:
|
|
371
|
+
"""Load agent instructions.
|
|
372
|
+
|
|
373
|
+
Args:
|
|
374
|
+
agent_id: Agent ID to load instructions for
|
|
375
|
+
|
|
376
|
+
Returns:
|
|
377
|
+
Instructions markdown content
|
|
378
|
+
|
|
379
|
+
Raises:
|
|
380
|
+
AgentDeploymentError: If instructions not found
|
|
381
|
+
"""
|
|
382
|
+
# Try multiple possible locations
|
|
383
|
+
possible_files = [
|
|
384
|
+
self.templates_dir / f"{agent_id}.md",
|
|
385
|
+
self.templates_dir / f"{agent_id}_instructions.md",
|
|
386
|
+
self.templates_dir / f"{agent_id}-instructions.md"
|
|
387
|
+
]
|
|
388
|
+
|
|
389
|
+
for instructions_file in possible_files:
|
|
390
|
+
if instructions_file.exists():
|
|
391
|
+
try:
|
|
392
|
+
with open(instructions_file, 'r') as f:
|
|
393
|
+
return f.read()
|
|
394
|
+
except Exception as e:
|
|
395
|
+
self.logger.warning(f"Failed to read instructions from {instructions_file}: {e}")
|
|
396
|
+
|
|
397
|
+
# If no instructions found, return empty
|
|
398
|
+
return ""
|
|
399
|
+
|
|
400
|
+
def _generate_default_instructions(self, agent_id: str, name: str, description: str) -> str:
|
|
401
|
+
"""Generate default agent instructions.
|
|
402
|
+
|
|
403
|
+
Args:
|
|
404
|
+
agent_id: Agent identifier
|
|
405
|
+
name: Agent display name
|
|
406
|
+
description: Agent description
|
|
407
|
+
|
|
408
|
+
Returns:
|
|
409
|
+
Default instructions markdown
|
|
410
|
+
"""
|
|
411
|
+
return f"""# {name}
|
|
412
|
+
|
|
413
|
+
## Core Identity
|
|
414
|
+
|
|
415
|
+
You are {name}, a specialized agent in the Claude MPM framework.
|
|
416
|
+
|
|
417
|
+
## Purpose
|
|
418
|
+
|
|
419
|
+
{description}
|
|
420
|
+
|
|
421
|
+
## Responsibilities
|
|
422
|
+
|
|
423
|
+
- Primary focus on your specialized domain
|
|
424
|
+
- Collaborate with other agents as needed
|
|
425
|
+
- Follow Claude MPM framework conventions
|
|
426
|
+
- Maintain high quality standards
|
|
427
|
+
|
|
428
|
+
## Operating Principles
|
|
429
|
+
|
|
430
|
+
1. **Expertise**: Apply deep knowledge in your domain
|
|
431
|
+
2. **Efficiency**: Complete tasks effectively and quickly
|
|
432
|
+
3. **Communication**: Provide clear, actionable responses
|
|
433
|
+
4. **Collaboration**: Work well with other agents
|
|
434
|
+
5. **Quality**: Maintain high standards in all outputs
|
|
435
|
+
|
|
436
|
+
## Output Format
|
|
437
|
+
|
|
438
|
+
Provide structured responses with:
|
|
439
|
+
- Clear summaries of actions taken
|
|
440
|
+
- Detailed results when appropriate
|
|
441
|
+
- Any issues or blockers encountered
|
|
442
|
+
- Recommendations for next steps
|
|
443
|
+
|
|
444
|
+
## Integration
|
|
445
|
+
|
|
446
|
+
- Follow framework patterns and conventions
|
|
447
|
+
- Use appropriate tools for the task
|
|
448
|
+
- Coordinate with PM for complex workflows
|
|
449
|
+
- Report completion status clearly
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
*Agent ID: {agent_id}*
|
|
454
|
+
*Generated by Agent Manager*
|
|
455
|
+
"""
|
|
@@ -1123,7 +1123,13 @@ class AgentDeploymentService(ConfigServiceBase, AgentDeploymentInterface):
|
|
|
1123
1123
|
if not force_rebuild:
|
|
1124
1124
|
# Only deploy agents that need updates or are new
|
|
1125
1125
|
agents_needing_update = set(comparison_results.get("needs_update", []))
|
|
1126
|
-
|
|
1126
|
+
|
|
1127
|
+
# Extract agent names from new_agents list (which contains dicts)
|
|
1128
|
+
new_agent_names = [
|
|
1129
|
+
agent["name"] if isinstance(agent, dict) else agent
|
|
1130
|
+
for agent in comparison_results.get("new_agents", [])
|
|
1131
|
+
]
|
|
1132
|
+
agents_needing_update.update(new_agent_names)
|
|
1127
1133
|
|
|
1128
1134
|
# Filter agents_to_deploy to only include those needing updates
|
|
1129
1135
|
filtered_agents = {
|
|
@@ -60,17 +60,23 @@ class AgentTemplateBuilder:
|
|
|
60
60
|
raise
|
|
61
61
|
|
|
62
62
|
# Extract tools from template with fallback
|
|
63
|
+
# Handle both dict and list formats for capabilities (backward compatibility)
|
|
64
|
+
capabilities = template_data.get("capabilities", {})
|
|
65
|
+
capabilities_tools = capabilities.get("tools") if isinstance(capabilities, dict) else None
|
|
66
|
+
|
|
63
67
|
tools = (
|
|
64
68
|
template_data.get("tools")
|
|
65
|
-
or
|
|
69
|
+
or capabilities_tools
|
|
66
70
|
or template_data.get("configuration_fields", {}).get("tools")
|
|
67
71
|
or ["Read", "Write", "Edit", "Grep", "Glob", "LS"] # Default fallback
|
|
68
72
|
)
|
|
69
73
|
|
|
70
74
|
# Extract model from template with fallback
|
|
75
|
+
capabilities_model = capabilities.get("model") if isinstance(capabilities, dict) else None
|
|
76
|
+
|
|
71
77
|
model = (
|
|
72
78
|
template_data.get("model")
|
|
73
|
-
or
|
|
79
|
+
or capabilities_model
|
|
74
80
|
or template_data.get("configuration_fields", {}).get("model")
|
|
75
81
|
or "sonnet" # Default fallback
|
|
76
82
|
)
|
|
@@ -132,7 +138,8 @@ class AgentTemplateBuilder:
|
|
|
132
138
|
# Extract custom metadata fields
|
|
133
139
|
agent_version = template_data.get("agent_version", "1.0.0")
|
|
134
140
|
agent_type = template_data.get("agent_type", "general")
|
|
135
|
-
|
|
141
|
+
# Use the capabilities_model we already extracted earlier
|
|
142
|
+
model_type = capabilities_model or "sonnet"
|
|
136
143
|
|
|
137
144
|
# Map our model types to Claude Code format
|
|
138
145
|
if model_type in ["opus", "sonnet", "haiku"]:
|
|
@@ -9,9 +9,11 @@ This module provides memory management services including:
|
|
|
9
9
|
from .builder import MemoryBuilder
|
|
10
10
|
from .optimizer import MemoryOptimizer
|
|
11
11
|
from .router import MemoryRouter
|
|
12
|
+
from .indexed_memory import IndexedMemoryService
|
|
12
13
|
|
|
13
14
|
__all__ = [
|
|
14
15
|
"MemoryBuilder",
|
|
15
16
|
"MemoryRouter",
|
|
16
17
|
"MemoryOptimizer",
|
|
18
|
+
"IndexedMemoryService",
|
|
17
19
|
]
|
|
@@ -27,59 +27,53 @@ def timeout_handler(timeout_seconds: float = 5.0):
|
|
|
27
27
|
"""
|
|
28
28
|
def decorator(func: Callable) -> Callable:
|
|
29
29
|
@functools.wraps(func)
|
|
30
|
-
async def wrapper(
|
|
30
|
+
async def wrapper(*args, **kwargs):
|
|
31
31
|
handler_name = func.__name__
|
|
32
32
|
start_time = time.time()
|
|
33
33
|
|
|
34
34
|
try:
|
|
35
35
|
# Create a task with timeout
|
|
36
36
|
result = await asyncio.wait_for(
|
|
37
|
-
func(
|
|
37
|
+
func(*args, **kwargs),
|
|
38
38
|
timeout=timeout_seconds
|
|
39
39
|
)
|
|
40
40
|
|
|
41
41
|
elapsed = time.time() - start_time
|
|
42
42
|
if elapsed > timeout_seconds * 0.8: # Warn if close to timeout
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
# Try to get logger from closure scope or fallback to print
|
|
44
|
+
try:
|
|
45
|
+
import logging
|
|
46
|
+
logger = logging.getLogger(__name__)
|
|
47
|
+
logger.warning(
|
|
48
|
+
f"⚠️ Handler {handler_name} took {elapsed:.2f}s "
|
|
49
|
+
f"(close to {timeout_seconds}s timeout)"
|
|
50
|
+
)
|
|
51
|
+
except:
|
|
52
|
+
print(f"⚠️ Handler {handler_name} took {elapsed:.2f}s (close to {timeout_seconds}s timeout)")
|
|
47
53
|
|
|
48
54
|
return result
|
|
49
55
|
|
|
50
56
|
except asyncio.TimeoutError:
|
|
51
57
|
elapsed = time.time() - start_time
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
try:
|
|
60
|
-
# Use a short timeout for error response
|
|
61
|
-
await asyncio.wait_for(
|
|
62
|
-
self.emit_to_client(
|
|
63
|
-
sid,
|
|
64
|
-
"error",
|
|
65
|
-
{
|
|
66
|
-
"message": f"Handler {handler_name} timed out",
|
|
67
|
-
"handler": handler_name,
|
|
68
|
-
"timeout": timeout_seconds
|
|
69
|
-
}
|
|
70
|
-
),
|
|
71
|
-
timeout=1.0
|
|
72
|
-
)
|
|
73
|
-
except:
|
|
74
|
-
pass # Best effort error notification
|
|
58
|
+
# Try to get logger from closure scope or fallback to print
|
|
59
|
+
try:
|
|
60
|
+
import logging
|
|
61
|
+
logger = logging.getLogger(__name__)
|
|
62
|
+
logger.error(f"❌ Handler {handler_name} timed out after {elapsed:.2f}s")
|
|
63
|
+
except:
|
|
64
|
+
print(f"❌ Handler {handler_name} timed out after {elapsed:.2f}s")
|
|
75
65
|
|
|
76
66
|
return None
|
|
77
67
|
|
|
78
68
|
except Exception as e:
|
|
79
69
|
elapsed = time.time() - start_time
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
70
|
+
# Try to get logger from closure scope or fallback to print
|
|
71
|
+
try:
|
|
72
|
+
import logging
|
|
73
|
+
logger = logging.getLogger(__name__)
|
|
74
|
+
logger.error(f"❌ Handler {handler_name} failed after {elapsed:.2f}s: {e}")
|
|
75
|
+
except:
|
|
76
|
+
print(f"❌ Handler {handler_name} failed after {elapsed:.2f}s: {e}")
|
|
83
77
|
raise
|
|
84
78
|
|
|
85
79
|
return wrapper
|
|
@@ -325,7 +319,7 @@ class ConnectionEventHandler(BaseEventHandler):
|
|
|
325
319
|
|
|
326
320
|
@self.sio.event
|
|
327
321
|
@timeout_handler(timeout_seconds=3.0)
|
|
328
|
-
async def disconnect(sid):
|
|
322
|
+
async def disconnect(sid, *args):
|
|
329
323
|
"""Handle client disconnection.
|
|
330
324
|
|
|
331
325
|
WHY: We need to clean up client tracking when they disconnect
|