crackerjack 0.30.3__py3-none-any.whl → 0.31.7__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.
Potentially problematic release.
This version of crackerjack might be problematic. Click here for more details.
- crackerjack/CLAUDE.md +1005 -0
- crackerjack/RULES.md +380 -0
- crackerjack/__init__.py +42 -13
- crackerjack/__main__.py +227 -299
- crackerjack/agents/__init__.py +41 -0
- crackerjack/agents/architect_agent.py +281 -0
- crackerjack/agents/base.py +170 -0
- crackerjack/agents/coordinator.py +512 -0
- crackerjack/agents/documentation_agent.py +498 -0
- crackerjack/agents/dry_agent.py +388 -0
- crackerjack/agents/formatting_agent.py +245 -0
- crackerjack/agents/import_optimization_agent.py +281 -0
- crackerjack/agents/performance_agent.py +669 -0
- crackerjack/agents/proactive_agent.py +104 -0
- crackerjack/agents/refactoring_agent.py +788 -0
- crackerjack/agents/security_agent.py +529 -0
- crackerjack/agents/test_creation_agent.py +657 -0
- crackerjack/agents/test_specialist_agent.py +486 -0
- crackerjack/agents/tracker.py +212 -0
- crackerjack/api.py +560 -0
- crackerjack/cli/__init__.py +24 -0
- crackerjack/cli/facade.py +104 -0
- crackerjack/cli/handlers.py +267 -0
- crackerjack/cli/interactive.py +471 -0
- crackerjack/cli/options.py +409 -0
- crackerjack/cli/utils.py +18 -0
- crackerjack/code_cleaner.py +618 -928
- crackerjack/config/__init__.py +19 -0
- crackerjack/config/hooks.py +218 -0
- crackerjack/core/__init__.py +0 -0
- crackerjack/core/async_workflow_orchestrator.py +406 -0
- crackerjack/core/autofix_coordinator.py +200 -0
- crackerjack/core/container.py +104 -0
- crackerjack/core/enhanced_container.py +542 -0
- crackerjack/core/performance.py +243 -0
- crackerjack/core/phase_coordinator.py +585 -0
- crackerjack/core/proactive_workflow.py +316 -0
- crackerjack/core/session_coordinator.py +289 -0
- crackerjack/core/workflow_orchestrator.py +826 -0
- crackerjack/dynamic_config.py +94 -103
- crackerjack/errors.py +263 -41
- crackerjack/executors/__init__.py +11 -0
- crackerjack/executors/async_hook_executor.py +431 -0
- crackerjack/executors/cached_hook_executor.py +242 -0
- crackerjack/executors/hook_executor.py +345 -0
- crackerjack/executors/individual_hook_executor.py +669 -0
- crackerjack/intelligence/__init__.py +44 -0
- crackerjack/intelligence/adaptive_learning.py +751 -0
- crackerjack/intelligence/agent_orchestrator.py +551 -0
- crackerjack/intelligence/agent_registry.py +414 -0
- crackerjack/intelligence/agent_selector.py +502 -0
- crackerjack/intelligence/integration.py +290 -0
- crackerjack/interactive.py +576 -315
- crackerjack/managers/__init__.py +11 -0
- crackerjack/managers/async_hook_manager.py +135 -0
- crackerjack/managers/hook_manager.py +137 -0
- crackerjack/managers/publish_manager.py +433 -0
- crackerjack/managers/test_command_builder.py +151 -0
- crackerjack/managers/test_executor.py +443 -0
- crackerjack/managers/test_manager.py +258 -0
- crackerjack/managers/test_manager_backup.py +1124 -0
- crackerjack/managers/test_progress.py +114 -0
- crackerjack/mcp/__init__.py +0 -0
- crackerjack/mcp/cache.py +336 -0
- crackerjack/mcp/client_runner.py +104 -0
- crackerjack/mcp/context.py +621 -0
- crackerjack/mcp/dashboard.py +636 -0
- crackerjack/mcp/enhanced_progress_monitor.py +479 -0
- crackerjack/mcp/file_monitor.py +336 -0
- crackerjack/mcp/progress_components.py +569 -0
- crackerjack/mcp/progress_monitor.py +949 -0
- crackerjack/mcp/rate_limiter.py +332 -0
- crackerjack/mcp/server.py +22 -0
- crackerjack/mcp/server_core.py +244 -0
- crackerjack/mcp/service_watchdog.py +501 -0
- crackerjack/mcp/state.py +395 -0
- crackerjack/mcp/task_manager.py +257 -0
- crackerjack/mcp/tools/__init__.py +17 -0
- crackerjack/mcp/tools/core_tools.py +249 -0
- crackerjack/mcp/tools/error_analyzer.py +308 -0
- crackerjack/mcp/tools/execution_tools.py +372 -0
- crackerjack/mcp/tools/execution_tools_backup.py +1097 -0
- crackerjack/mcp/tools/intelligence_tool_registry.py +80 -0
- crackerjack/mcp/tools/intelligence_tools.py +314 -0
- crackerjack/mcp/tools/monitoring_tools.py +502 -0
- crackerjack/mcp/tools/proactive_tools.py +384 -0
- crackerjack/mcp/tools/progress_tools.py +217 -0
- crackerjack/mcp/tools/utility_tools.py +341 -0
- crackerjack/mcp/tools/workflow_executor.py +565 -0
- crackerjack/mcp/websocket/__init__.py +14 -0
- crackerjack/mcp/websocket/app.py +39 -0
- crackerjack/mcp/websocket/endpoints.py +559 -0
- crackerjack/mcp/websocket/jobs.py +253 -0
- crackerjack/mcp/websocket/server.py +116 -0
- crackerjack/mcp/websocket/websocket_handler.py +78 -0
- crackerjack/mcp/websocket_server.py +10 -0
- crackerjack/models/__init__.py +31 -0
- crackerjack/models/config.py +93 -0
- crackerjack/models/config_adapter.py +230 -0
- crackerjack/models/protocols.py +118 -0
- crackerjack/models/task.py +154 -0
- crackerjack/monitoring/ai_agent_watchdog.py +450 -0
- crackerjack/monitoring/regression_prevention.py +638 -0
- crackerjack/orchestration/__init__.py +0 -0
- crackerjack/orchestration/advanced_orchestrator.py +970 -0
- crackerjack/orchestration/coverage_improvement.py +223 -0
- crackerjack/orchestration/execution_strategies.py +341 -0
- crackerjack/orchestration/test_progress_streamer.py +636 -0
- crackerjack/plugins/__init__.py +15 -0
- crackerjack/plugins/base.py +200 -0
- crackerjack/plugins/hooks.py +246 -0
- crackerjack/plugins/loader.py +335 -0
- crackerjack/plugins/managers.py +259 -0
- crackerjack/py313.py +8 -3
- crackerjack/services/__init__.py +22 -0
- crackerjack/services/cache.py +314 -0
- crackerjack/services/config.py +358 -0
- crackerjack/services/config_integrity.py +99 -0
- crackerjack/services/contextual_ai_assistant.py +516 -0
- crackerjack/services/coverage_ratchet.py +356 -0
- crackerjack/services/debug.py +736 -0
- crackerjack/services/dependency_monitor.py +617 -0
- crackerjack/services/enhanced_filesystem.py +439 -0
- crackerjack/services/file_hasher.py +151 -0
- crackerjack/services/filesystem.py +421 -0
- crackerjack/services/git.py +176 -0
- crackerjack/services/health_metrics.py +611 -0
- crackerjack/services/initialization.py +873 -0
- crackerjack/services/log_manager.py +286 -0
- crackerjack/services/logging.py +174 -0
- crackerjack/services/metrics.py +578 -0
- crackerjack/services/pattern_cache.py +362 -0
- crackerjack/services/pattern_detector.py +515 -0
- crackerjack/services/performance_benchmarks.py +653 -0
- crackerjack/services/security.py +163 -0
- crackerjack/services/server_manager.py +234 -0
- crackerjack/services/smart_scheduling.py +144 -0
- crackerjack/services/tool_version_service.py +61 -0
- crackerjack/services/unified_config.py +437 -0
- crackerjack/services/version_checker.py +248 -0
- crackerjack/slash_commands/__init__.py +14 -0
- crackerjack/slash_commands/init.md +122 -0
- crackerjack/slash_commands/run.md +163 -0
- crackerjack/slash_commands/status.md +127 -0
- crackerjack-0.31.7.dist-info/METADATA +742 -0
- crackerjack-0.31.7.dist-info/RECORD +149 -0
- crackerjack-0.31.7.dist-info/entry_points.txt +2 -0
- crackerjack/.gitignore +0 -34
- crackerjack/.libcst.codemod.yaml +0 -18
- crackerjack/.pdm.toml +0 -1
- crackerjack/crackerjack.py +0 -3805
- crackerjack/pyproject.toml +0 -286
- crackerjack-0.30.3.dist-info/METADATA +0 -1290
- crackerjack-0.30.3.dist-info/RECORD +0 -16
- {crackerjack-0.30.3.dist-info → crackerjack-0.31.7.dist-info}/WHEEL +0 -0
- {crackerjack-0.30.3.dist-info → crackerjack-0.31.7.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
"""Multi-Agent Execution Orchestrator.
|
|
2
|
+
|
|
3
|
+
Coordinates execution of multiple agents with smart routing, fallback strategies,
|
|
4
|
+
and result aggregation.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import logging
|
|
9
|
+
import typing as t
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from enum import Enum
|
|
12
|
+
|
|
13
|
+
from crackerjack.agents.base import AgentContext, Issue
|
|
14
|
+
|
|
15
|
+
from .agent_registry import (
|
|
16
|
+
AgentRegistry,
|
|
17
|
+
AgentSource,
|
|
18
|
+
RegisteredAgent,
|
|
19
|
+
get_agent_registry,
|
|
20
|
+
)
|
|
21
|
+
from .agent_selector import AgentScore, AgentSelector, TaskDescription
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ExecutionStrategy(Enum):
|
|
25
|
+
"""Strategy for multi-agent execution."""
|
|
26
|
+
|
|
27
|
+
SINGLE_BEST = "single_best" # Use only the highest-scored agent
|
|
28
|
+
PARALLEL = "parallel" # Run multiple agents in parallel
|
|
29
|
+
SEQUENTIAL = "sequential" # Run agents one by one until success
|
|
30
|
+
CONSENSUS = "consensus" # Run multiple agents and compare results
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ExecutionMode(Enum):
|
|
34
|
+
"""Mode of execution."""
|
|
35
|
+
|
|
36
|
+
AUTONOMOUS = "autonomous" # Full automation
|
|
37
|
+
GUIDED = "guided" # With human oversight
|
|
38
|
+
ADVISORY = "advisory" # Recommendations only
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class ExecutionRequest:
|
|
43
|
+
"""Request for agent execution."""
|
|
44
|
+
|
|
45
|
+
task: TaskDescription
|
|
46
|
+
strategy: ExecutionStrategy = ExecutionStrategy.SINGLE_BEST
|
|
47
|
+
mode: ExecutionMode = ExecutionMode.AUTONOMOUS
|
|
48
|
+
max_agents: int = 3
|
|
49
|
+
timeout_seconds: int = 300
|
|
50
|
+
fallback_to_system: bool = True
|
|
51
|
+
context: AgentContext | None = None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class ExecutionResult:
|
|
56
|
+
"""Result of agent execution."""
|
|
57
|
+
|
|
58
|
+
success: bool
|
|
59
|
+
primary_result: t.Any | None # Main result from best agent
|
|
60
|
+
all_results: list[tuple[RegisteredAgent, t.Any]] # All agent results
|
|
61
|
+
execution_time: float
|
|
62
|
+
agents_used: list[str]
|
|
63
|
+
strategy_used: ExecutionStrategy
|
|
64
|
+
error_message: str | None = None
|
|
65
|
+
recommendations: list[str] | None = None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class AgentOrchestrator:
|
|
69
|
+
"""Multi-agent execution orchestrator."""
|
|
70
|
+
|
|
71
|
+
def __init__(
|
|
72
|
+
self,
|
|
73
|
+
registry: AgentRegistry | None = None,
|
|
74
|
+
selector: AgentSelector | None = None,
|
|
75
|
+
) -> None:
|
|
76
|
+
self.logger = logging.getLogger(__name__)
|
|
77
|
+
self.registry = registry
|
|
78
|
+
self.selector = selector or AgentSelector(registry)
|
|
79
|
+
self._execution_stats: dict[str, int] = {}
|
|
80
|
+
|
|
81
|
+
async def execute(self, request: ExecutionRequest) -> ExecutionResult:
|
|
82
|
+
"""Execute a request using the intelligent agent system."""
|
|
83
|
+
start_time = asyncio.get_event_loop().time()
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
self.logger.info(
|
|
87
|
+
f"Executing request: {request.task.description[:50]}... "
|
|
88
|
+
f"(strategy: {request.strategy.value})"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Get agent candidates
|
|
92
|
+
candidates = await self.selector.select_agents(
|
|
93
|
+
request.task, max_candidates=request.max_agents
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
if not candidates:
|
|
97
|
+
return self._create_error_result(
|
|
98
|
+
"No suitable agents found for task",
|
|
99
|
+
start_time,
|
|
100
|
+
request.strategy,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Execute based on strategy
|
|
104
|
+
if request.strategy == ExecutionStrategy.SINGLE_BEST:
|
|
105
|
+
result = await self._execute_single_best(request, candidates)
|
|
106
|
+
elif request.strategy == ExecutionStrategy.PARALLEL:
|
|
107
|
+
result = await self._execute_parallel(request, candidates)
|
|
108
|
+
elif request.strategy == ExecutionStrategy.SEQUENTIAL:
|
|
109
|
+
result = await self._execute_sequential(request, candidates)
|
|
110
|
+
elif request.strategy == ExecutionStrategy.CONSENSUS:
|
|
111
|
+
result = await self._execute_consensus(request, candidates)
|
|
112
|
+
else:
|
|
113
|
+
result = await self._execute_single_best(request, candidates)
|
|
114
|
+
|
|
115
|
+
execution_time = asyncio.get_event_loop().time() - start_time
|
|
116
|
+
result.execution_time = execution_time
|
|
117
|
+
|
|
118
|
+
# Update stats
|
|
119
|
+
for agent_name in result.agents_used:
|
|
120
|
+
self._execution_stats[agent_name] = (
|
|
121
|
+
self._execution_stats.get(agent_name, 0) + 1
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
self.logger.info(
|
|
125
|
+
f"Execution completed in {execution_time:.2f}s: "
|
|
126
|
+
f"{'success' if result.success else 'failure'} "
|
|
127
|
+
f"using {len(result.agents_used)} agents"
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
return result
|
|
131
|
+
|
|
132
|
+
except Exception as e:
|
|
133
|
+
self.logger.exception(f"Execution failed: {e}")
|
|
134
|
+
return self._create_error_result(
|
|
135
|
+
f"Execution error: {e}",
|
|
136
|
+
start_time,
|
|
137
|
+
request.strategy,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
async def _execute_single_best(
|
|
141
|
+
self,
|
|
142
|
+
request: ExecutionRequest,
|
|
143
|
+
candidates: list[AgentScore],
|
|
144
|
+
) -> ExecutionResult:
|
|
145
|
+
"""Execute using the single best agent."""
|
|
146
|
+
best_candidate = candidates[0]
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
result = await self._execute_agent(best_candidate.agent, request)
|
|
150
|
+
|
|
151
|
+
return ExecutionResult(
|
|
152
|
+
success=True,
|
|
153
|
+
primary_result=result,
|
|
154
|
+
all_results=[(best_candidate.agent, result)],
|
|
155
|
+
execution_time=0.0, # Will be set by caller
|
|
156
|
+
agents_used=[best_candidate.agent.metadata.name],
|
|
157
|
+
strategy_used=ExecutionStrategy.SINGLE_BEST,
|
|
158
|
+
recommendations=self._generate_recommendations(best_candidate),
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
except Exception as e:
|
|
162
|
+
# Fallback to next best agent if available
|
|
163
|
+
if len(candidates) > 1 and request.fallback_to_system:
|
|
164
|
+
self.logger.warning(
|
|
165
|
+
f"Primary agent {best_candidate.agent.metadata.name} failed: {e}. "
|
|
166
|
+
f"Trying fallback..."
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
fallback_request = ExecutionRequest(
|
|
170
|
+
task=request.task,
|
|
171
|
+
strategy=ExecutionStrategy.SEQUENTIAL,
|
|
172
|
+
mode=request.mode,
|
|
173
|
+
max_agents=len(candidates) - 1,
|
|
174
|
+
timeout_seconds=request.timeout_seconds,
|
|
175
|
+
fallback_to_system=False, # Prevent infinite recursion
|
|
176
|
+
context=request.context,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
return await self._execute_sequential(fallback_request, candidates[1:])
|
|
180
|
+
|
|
181
|
+
return ExecutionResult(
|
|
182
|
+
success=False,
|
|
183
|
+
primary_result=None,
|
|
184
|
+
all_results=[(best_candidate.agent, e)],
|
|
185
|
+
execution_time=0.0,
|
|
186
|
+
agents_used=[],
|
|
187
|
+
strategy_used=ExecutionStrategy.SINGLE_BEST,
|
|
188
|
+
error_message=str(e),
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
async def _execute_parallel(
|
|
192
|
+
self,
|
|
193
|
+
request: ExecutionRequest,
|
|
194
|
+
candidates: list[AgentScore],
|
|
195
|
+
) -> ExecutionResult:
|
|
196
|
+
"""Execute multiple agents in parallel."""
|
|
197
|
+
tasks = []
|
|
198
|
+
agents_to_execute = candidates[: request.max_agents]
|
|
199
|
+
|
|
200
|
+
for candidate in agents_to_execute:
|
|
201
|
+
task = asyncio.create_task(
|
|
202
|
+
self._execute_agent_safe(candidate.agent, request)
|
|
203
|
+
)
|
|
204
|
+
tasks.append((candidate.agent, task))
|
|
205
|
+
|
|
206
|
+
# Wait for all tasks to complete
|
|
207
|
+
results = []
|
|
208
|
+
successful_results = []
|
|
209
|
+
|
|
210
|
+
for agent, task in tasks:
|
|
211
|
+
try:
|
|
212
|
+
result = await asyncio.wait_for(task, timeout=request.timeout_seconds)
|
|
213
|
+
results.append((agent, result))
|
|
214
|
+
if not isinstance(result, Exception):
|
|
215
|
+
successful_results.append((agent, result))
|
|
216
|
+
except TimeoutError:
|
|
217
|
+
results.append((agent, TimeoutError("Agent execution timed out")))
|
|
218
|
+
except Exception as e:
|
|
219
|
+
results.append((agent, e))
|
|
220
|
+
|
|
221
|
+
# Choose best successful result
|
|
222
|
+
primary_result = None
|
|
223
|
+
agents_used = []
|
|
224
|
+
|
|
225
|
+
if successful_results:
|
|
226
|
+
# Use result from highest-priority agent
|
|
227
|
+
successful_results.sort(key=lambda x: x[0].metadata.priority, reverse=True)
|
|
228
|
+
primary_result = successful_results[0][1]
|
|
229
|
+
agents_used = [agent.metadata.name for agent, _ in successful_results]
|
|
230
|
+
|
|
231
|
+
return ExecutionResult(
|
|
232
|
+
success=len(successful_results) > 0,
|
|
233
|
+
primary_result=primary_result,
|
|
234
|
+
all_results=results,
|
|
235
|
+
execution_time=0.0,
|
|
236
|
+
agents_used=agents_used,
|
|
237
|
+
strategy_used=ExecutionStrategy.PARALLEL,
|
|
238
|
+
error_message=None if successful_results else "All parallel agents failed",
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
async def _execute_sequential(
|
|
242
|
+
self,
|
|
243
|
+
request: ExecutionRequest,
|
|
244
|
+
candidates: list[AgentScore],
|
|
245
|
+
) -> ExecutionResult:
|
|
246
|
+
"""Execute agents sequentially until one succeeds."""
|
|
247
|
+
results = []
|
|
248
|
+
|
|
249
|
+
for candidate in candidates[: request.max_agents]:
|
|
250
|
+
try:
|
|
251
|
+
result = await asyncio.wait_for(
|
|
252
|
+
self._execute_agent(candidate.agent, request),
|
|
253
|
+
timeout=request.timeout_seconds,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
results.append((candidate.agent, result))
|
|
257
|
+
|
|
258
|
+
# Success - return immediately
|
|
259
|
+
return ExecutionResult(
|
|
260
|
+
success=True,
|
|
261
|
+
primary_result=result,
|
|
262
|
+
all_results=results,
|
|
263
|
+
execution_time=0.0,
|
|
264
|
+
agents_used=[candidate.agent.metadata.name],
|
|
265
|
+
strategy_used=ExecutionStrategy.SEQUENTIAL,
|
|
266
|
+
recommendations=self._generate_recommendations(candidate),
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
except Exception as e:
|
|
270
|
+
results.append((candidate.agent, e))
|
|
271
|
+
self.logger.warning(
|
|
272
|
+
f"Sequential agent {candidate.agent.metadata.name} failed: {e}"
|
|
273
|
+
)
|
|
274
|
+
continue
|
|
275
|
+
|
|
276
|
+
# All agents failed
|
|
277
|
+
return ExecutionResult(
|
|
278
|
+
success=False,
|
|
279
|
+
primary_result=None,
|
|
280
|
+
all_results=results,
|
|
281
|
+
execution_time=0.0,
|
|
282
|
+
agents_used=[],
|
|
283
|
+
strategy_used=ExecutionStrategy.SEQUENTIAL,
|
|
284
|
+
error_message="All sequential agents failed",
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
async def _execute_consensus(
|
|
288
|
+
self,
|
|
289
|
+
request: ExecutionRequest,
|
|
290
|
+
candidates: list[AgentScore],
|
|
291
|
+
) -> ExecutionResult:
|
|
292
|
+
"""Execute multiple agents and build consensus from results."""
|
|
293
|
+
# First run parallel execution
|
|
294
|
+
parallel_request = ExecutionRequest(
|
|
295
|
+
task=request.task,
|
|
296
|
+
strategy=ExecutionStrategy.PARALLEL,
|
|
297
|
+
mode=request.mode,
|
|
298
|
+
max_agents=min(request.max_agents, 3), # Limit for consensus
|
|
299
|
+
timeout_seconds=request.timeout_seconds,
|
|
300
|
+
fallback_to_system=False,
|
|
301
|
+
context=request.context,
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
parallel_result = await self._execute_parallel(parallel_request, candidates)
|
|
305
|
+
|
|
306
|
+
if not parallel_result.success:
|
|
307
|
+
return parallel_result
|
|
308
|
+
|
|
309
|
+
# Analyze results for consensus
|
|
310
|
+
successful_results = [
|
|
311
|
+
(agent, result)
|
|
312
|
+
for agent, result in parallel_result.all_results
|
|
313
|
+
if not isinstance(result, Exception)
|
|
314
|
+
]
|
|
315
|
+
|
|
316
|
+
if len(successful_results) < 2:
|
|
317
|
+
# Not enough results for consensus - return best result
|
|
318
|
+
return parallel_result
|
|
319
|
+
|
|
320
|
+
# Build consensus (simplified - could be much more sophisticated)
|
|
321
|
+
consensus_result = self._build_consensus(successful_results)
|
|
322
|
+
|
|
323
|
+
return ExecutionResult(
|
|
324
|
+
success=True,
|
|
325
|
+
primary_result=consensus_result,
|
|
326
|
+
all_results=parallel_result.all_results,
|
|
327
|
+
execution_time=parallel_result.execution_time,
|
|
328
|
+
agents_used=parallel_result.agents_used,
|
|
329
|
+
strategy_used=ExecutionStrategy.CONSENSUS,
|
|
330
|
+
recommendations=["Results validated through multi-agent consensus"],
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
async def _execute_agent(
|
|
334
|
+
self, agent: RegisteredAgent, request: ExecutionRequest
|
|
335
|
+
) -> t.Any:
|
|
336
|
+
"""Execute a specific agent."""
|
|
337
|
+
if agent.agent is not None:
|
|
338
|
+
# Crackerjack agent
|
|
339
|
+
return await self._execute_crackerjack_agent(agent, request)
|
|
340
|
+
elif agent.agent_path is not None:
|
|
341
|
+
# User agent
|
|
342
|
+
return await self._execute_user_agent(agent, request)
|
|
343
|
+
elif agent.subagent_type is not None:
|
|
344
|
+
# System agent
|
|
345
|
+
return await self._execute_system_agent(agent, request)
|
|
346
|
+
else:
|
|
347
|
+
raise ValueError(f"Invalid agent configuration: {agent.metadata.name}")
|
|
348
|
+
|
|
349
|
+
async def _execute_agent_safe(
|
|
350
|
+
self, agent: RegisteredAgent, request: ExecutionRequest
|
|
351
|
+
) -> t.Any:
|
|
352
|
+
"""Execute an agent with exception handling."""
|
|
353
|
+
try:
|
|
354
|
+
return await self._execute_agent(agent, request)
|
|
355
|
+
except Exception as e:
|
|
356
|
+
return e
|
|
357
|
+
|
|
358
|
+
async def _execute_crackerjack_agent(
|
|
359
|
+
self,
|
|
360
|
+
agent: RegisteredAgent,
|
|
361
|
+
request: ExecutionRequest,
|
|
362
|
+
) -> t.Any:
|
|
363
|
+
"""Execute a built-in crackerjack agent."""
|
|
364
|
+
if not agent.agent:
|
|
365
|
+
raise ValueError("No crackerjack agent instance available")
|
|
366
|
+
|
|
367
|
+
# Convert task to Issue for crackerjack agents
|
|
368
|
+
issue = Issue(
|
|
369
|
+
id="orchestrated_task",
|
|
370
|
+
type=self._map_task_to_issue_type(request.task),
|
|
371
|
+
severity=self._map_task_priority_to_severity(request.task),
|
|
372
|
+
message=request.task.description,
|
|
373
|
+
file_path=None, # Could be extracted from task if needed
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
# Execute agent
|
|
377
|
+
result = await agent.agent.analyze_and_fix(issue)
|
|
378
|
+
return result
|
|
379
|
+
|
|
380
|
+
async def _execute_user_agent(
|
|
381
|
+
self,
|
|
382
|
+
agent: RegisteredAgent,
|
|
383
|
+
request: ExecutionRequest,
|
|
384
|
+
) -> t.Any:
|
|
385
|
+
"""Execute a user agent via Task tool."""
|
|
386
|
+
# Import Task tool dynamically to avoid circular imports
|
|
387
|
+
from crackerjack.mcp.tools.core_tools import create_task_with_subagent
|
|
388
|
+
|
|
389
|
+
# Use Task tool to execute user agent
|
|
390
|
+
result = await create_task_with_subagent(
|
|
391
|
+
description=f"Execute task using {agent.metadata.name}",
|
|
392
|
+
prompt=request.task.description,
|
|
393
|
+
subagent_type=agent.metadata.name,
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
return result
|
|
397
|
+
|
|
398
|
+
async def _execute_system_agent(
|
|
399
|
+
self,
|
|
400
|
+
agent: RegisteredAgent,
|
|
401
|
+
request: ExecutionRequest,
|
|
402
|
+
) -> t.Any:
|
|
403
|
+
"""Execute a system agent via Task tool."""
|
|
404
|
+
if not agent.subagent_type:
|
|
405
|
+
raise ValueError("No subagent type specified for system agent")
|
|
406
|
+
|
|
407
|
+
# Import Task tool dynamically
|
|
408
|
+
from crackerjack.mcp.tools.core_tools import create_task_with_subagent
|
|
409
|
+
|
|
410
|
+
# Use Task tool to execute system agent
|
|
411
|
+
result = await create_task_with_subagent(
|
|
412
|
+
description=f"Execute task using {agent.metadata.name}",
|
|
413
|
+
prompt=request.task.description,
|
|
414
|
+
subagent_type=agent.subagent_type,
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
return result
|
|
418
|
+
|
|
419
|
+
def _map_task_to_issue_type(self, task: TaskDescription):
|
|
420
|
+
"""Map task context to Issue type for crackerjack agents."""
|
|
421
|
+
# Import IssueType here to avoid circular imports
|
|
422
|
+
from crackerjack.agents.base import IssueType
|
|
423
|
+
|
|
424
|
+
# Simple mapping - could be more sophisticated
|
|
425
|
+
context_map = {
|
|
426
|
+
"code_quality": IssueType.FORMATTING,
|
|
427
|
+
"refactoring": IssueType.COMPLEXITY,
|
|
428
|
+
"testing": IssueType.TEST_FAILURE,
|
|
429
|
+
"security": IssueType.SECURITY,
|
|
430
|
+
"performance": IssueType.PERFORMANCE,
|
|
431
|
+
"documentation": IssueType.DOCUMENTATION,
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if task.context and task.context.value in context_map:
|
|
435
|
+
return context_map[task.context.value]
|
|
436
|
+
|
|
437
|
+
# Analyze task description for hints
|
|
438
|
+
desc_lower = task.description.lower()
|
|
439
|
+
if "test" in desc_lower:
|
|
440
|
+
return IssueType.TEST_FAILURE
|
|
441
|
+
elif "refurb" in desc_lower or "complexity" in desc_lower:
|
|
442
|
+
return IssueType.COMPLEXITY
|
|
443
|
+
elif "security" in desc_lower:
|
|
444
|
+
return IssueType.SECURITY
|
|
445
|
+
elif "format" in desc_lower:
|
|
446
|
+
return IssueType.FORMATTING
|
|
447
|
+
|
|
448
|
+
return IssueType.FORMATTING # Default
|
|
449
|
+
|
|
450
|
+
def _map_task_priority_to_severity(self, task: TaskDescription):
|
|
451
|
+
"""Map task priority to Issue severity."""
|
|
452
|
+
from crackerjack.agents.base import Priority
|
|
453
|
+
|
|
454
|
+
if task.priority >= 80:
|
|
455
|
+
return Priority.HIGH
|
|
456
|
+
elif task.priority >= 50:
|
|
457
|
+
return Priority.MEDIUM
|
|
458
|
+
|
|
459
|
+
return Priority.LOW
|
|
460
|
+
|
|
461
|
+
def _build_consensus(self, results: list[tuple[RegisteredAgent, t.Any]]) -> t.Any:
|
|
462
|
+
"""Build consensus from multiple agent results."""
|
|
463
|
+
# Simplified consensus - could be much more sophisticated
|
|
464
|
+
# For now, just return the result from the highest-priority agent
|
|
465
|
+
results.sort(key=lambda x: x[0].metadata.priority, reverse=True)
|
|
466
|
+
return results[0][1]
|
|
467
|
+
|
|
468
|
+
def _generate_recommendations(self, candidate: AgentScore) -> list[str]:
|
|
469
|
+
"""Generate recommendations based on agent selection."""
|
|
470
|
+
recommendations = []
|
|
471
|
+
|
|
472
|
+
if candidate.final_score > 0.8:
|
|
473
|
+
recommendations.append("High confidence in agent selection")
|
|
474
|
+
elif candidate.final_score > 0.6:
|
|
475
|
+
recommendations.append("Good agent match for this task")
|
|
476
|
+
else:
|
|
477
|
+
recommendations.append("Consider manual review of results")
|
|
478
|
+
|
|
479
|
+
if candidate.agent.metadata.source == AgentSource.CRACKERJACK:
|
|
480
|
+
recommendations.append("Using specialized built-in agent")
|
|
481
|
+
elif candidate.agent.metadata.source == AgentSource.USER:
|
|
482
|
+
recommendations.append("Using custom user agent")
|
|
483
|
+
else:
|
|
484
|
+
recommendations.append("Using general-purpose system agent")
|
|
485
|
+
|
|
486
|
+
return recommendations
|
|
487
|
+
|
|
488
|
+
def _create_error_result(
|
|
489
|
+
self,
|
|
490
|
+
error_message: str,
|
|
491
|
+
start_time: float,
|
|
492
|
+
strategy: ExecutionStrategy,
|
|
493
|
+
) -> ExecutionResult:
|
|
494
|
+
"""Create an error result."""
|
|
495
|
+
execution_time = asyncio.get_event_loop().time() - start_time
|
|
496
|
+
|
|
497
|
+
return ExecutionResult(
|
|
498
|
+
success=False,
|
|
499
|
+
primary_result=None,
|
|
500
|
+
all_results=[],
|
|
501
|
+
execution_time=execution_time,
|
|
502
|
+
agents_used=[],
|
|
503
|
+
strategy_used=strategy,
|
|
504
|
+
error_message=error_message,
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
def get_execution_stats(self) -> dict[str, t.Any]:
|
|
508
|
+
"""Get execution statistics."""
|
|
509
|
+
from operator import itemgetter
|
|
510
|
+
|
|
511
|
+
return {
|
|
512
|
+
"total_executions": sum(self._execution_stats.values()),
|
|
513
|
+
"agent_usage": self._execution_stats.copy(),
|
|
514
|
+
"most_used_agent": max(
|
|
515
|
+
self._execution_stats.items(), key=itemgetter(1), default=("none", 0)
|
|
516
|
+
)[0]
|
|
517
|
+
if self._execution_stats
|
|
518
|
+
else "none",
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
async def analyze_task_routing(self, task: TaskDescription) -> dict[str, t.Any]:
|
|
522
|
+
"""Analyze how a task would be routed through the system."""
|
|
523
|
+
analysis = await self.selector.analyze_task_complexity(task)
|
|
524
|
+
|
|
525
|
+
# Add orchestration recommendations
|
|
526
|
+
if analysis["complexity_level"] == "high":
|
|
527
|
+
analysis["recommended_strategy"] = ExecutionStrategy.CONSENSUS
|
|
528
|
+
elif analysis["candidate_count"] > 3:
|
|
529
|
+
analysis["recommended_strategy"] = ExecutionStrategy.PARALLEL
|
|
530
|
+
elif analysis["candidate_count"] > 1:
|
|
531
|
+
analysis["recommended_strategy"] = ExecutionStrategy.SEQUENTIAL
|
|
532
|
+
else:
|
|
533
|
+
analysis["recommended_strategy"] = ExecutionStrategy.SINGLE_BEST
|
|
534
|
+
|
|
535
|
+
return analysis
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
# Global orchestrator instance
|
|
539
|
+
_orchestrator_instance: AgentOrchestrator | None = None
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
async def get_agent_orchestrator() -> AgentOrchestrator:
|
|
543
|
+
"""Get or create the global agent orchestrator."""
|
|
544
|
+
global _orchestrator_instance
|
|
545
|
+
|
|
546
|
+
if _orchestrator_instance is None:
|
|
547
|
+
registry = await get_agent_registry()
|
|
548
|
+
selector = AgentSelector(registry)
|
|
549
|
+
_orchestrator_instance = AgentOrchestrator(registry, selector)
|
|
550
|
+
|
|
551
|
+
return _orchestrator_instance
|