emdash-core 0.1.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.
- emdash_core/__init__.py +3 -0
- emdash_core/agent/__init__.py +37 -0
- emdash_core/agent/agents.py +225 -0
- emdash_core/agent/code_reviewer.py +476 -0
- emdash_core/agent/compaction.py +143 -0
- emdash_core/agent/context_manager.py +140 -0
- emdash_core/agent/events.py +338 -0
- emdash_core/agent/handlers.py +224 -0
- emdash_core/agent/inprocess_subagent.py +377 -0
- emdash_core/agent/mcp/__init__.py +50 -0
- emdash_core/agent/mcp/client.py +346 -0
- emdash_core/agent/mcp/config.py +302 -0
- emdash_core/agent/mcp/manager.py +496 -0
- emdash_core/agent/mcp/tool_factory.py +213 -0
- emdash_core/agent/prompts/__init__.py +38 -0
- emdash_core/agent/prompts/main_agent.py +104 -0
- emdash_core/agent/prompts/subagents.py +131 -0
- emdash_core/agent/prompts/workflow.py +136 -0
- emdash_core/agent/providers/__init__.py +34 -0
- emdash_core/agent/providers/base.py +143 -0
- emdash_core/agent/providers/factory.py +80 -0
- emdash_core/agent/providers/models.py +220 -0
- emdash_core/agent/providers/openai_provider.py +463 -0
- emdash_core/agent/providers/transformers_provider.py +217 -0
- emdash_core/agent/research/__init__.py +81 -0
- emdash_core/agent/research/agent.py +143 -0
- emdash_core/agent/research/controller.py +254 -0
- emdash_core/agent/research/critic.py +428 -0
- emdash_core/agent/research/macros.py +469 -0
- emdash_core/agent/research/planner.py +449 -0
- emdash_core/agent/research/researcher.py +436 -0
- emdash_core/agent/research/state.py +523 -0
- emdash_core/agent/research/synthesizer.py +594 -0
- emdash_core/agent/reviewer_profile.py +475 -0
- emdash_core/agent/rules.py +123 -0
- emdash_core/agent/runner.py +601 -0
- emdash_core/agent/session.py +262 -0
- emdash_core/agent/spec_schema.py +66 -0
- emdash_core/agent/specification.py +479 -0
- emdash_core/agent/subagent.py +397 -0
- emdash_core/agent/subagent_prompts.py +13 -0
- emdash_core/agent/toolkit.py +482 -0
- emdash_core/agent/toolkits/__init__.py +64 -0
- emdash_core/agent/toolkits/base.py +96 -0
- emdash_core/agent/toolkits/explore.py +47 -0
- emdash_core/agent/toolkits/plan.py +55 -0
- emdash_core/agent/tools/__init__.py +141 -0
- emdash_core/agent/tools/analytics.py +436 -0
- emdash_core/agent/tools/base.py +131 -0
- emdash_core/agent/tools/coding.py +484 -0
- emdash_core/agent/tools/github_mcp.py +592 -0
- emdash_core/agent/tools/history.py +13 -0
- emdash_core/agent/tools/modes.py +153 -0
- emdash_core/agent/tools/plan.py +206 -0
- emdash_core/agent/tools/plan_write.py +135 -0
- emdash_core/agent/tools/search.py +412 -0
- emdash_core/agent/tools/spec.py +341 -0
- emdash_core/agent/tools/task.py +262 -0
- emdash_core/agent/tools/task_output.py +204 -0
- emdash_core/agent/tools/tasks.py +454 -0
- emdash_core/agent/tools/traversal.py +588 -0
- emdash_core/agent/tools/web.py +179 -0
- emdash_core/analytics/__init__.py +5 -0
- emdash_core/analytics/engine.py +1286 -0
- emdash_core/api/__init__.py +5 -0
- emdash_core/api/agent.py +308 -0
- emdash_core/api/agents.py +154 -0
- emdash_core/api/analyze.py +264 -0
- emdash_core/api/auth.py +173 -0
- emdash_core/api/context.py +77 -0
- emdash_core/api/db.py +121 -0
- emdash_core/api/embed.py +131 -0
- emdash_core/api/feature.py +143 -0
- emdash_core/api/health.py +93 -0
- emdash_core/api/index.py +162 -0
- emdash_core/api/plan.py +110 -0
- emdash_core/api/projectmd.py +210 -0
- emdash_core/api/query.py +320 -0
- emdash_core/api/research.py +122 -0
- emdash_core/api/review.py +161 -0
- emdash_core/api/router.py +76 -0
- emdash_core/api/rules.py +116 -0
- emdash_core/api/search.py +119 -0
- emdash_core/api/spec.py +99 -0
- emdash_core/api/swarm.py +223 -0
- emdash_core/api/tasks.py +109 -0
- emdash_core/api/team.py +120 -0
- emdash_core/auth/__init__.py +17 -0
- emdash_core/auth/github.py +389 -0
- emdash_core/config.py +74 -0
- emdash_core/context/__init__.py +52 -0
- emdash_core/context/models.py +50 -0
- emdash_core/context/providers/__init__.py +11 -0
- emdash_core/context/providers/base.py +74 -0
- emdash_core/context/providers/explored_areas.py +183 -0
- emdash_core/context/providers/touched_areas.py +360 -0
- emdash_core/context/registry.py +73 -0
- emdash_core/context/reranker.py +199 -0
- emdash_core/context/service.py +260 -0
- emdash_core/context/session.py +352 -0
- emdash_core/core/__init__.py +104 -0
- emdash_core/core/config.py +454 -0
- emdash_core/core/exceptions.py +55 -0
- emdash_core/core/models.py +265 -0
- emdash_core/core/review_config.py +57 -0
- emdash_core/db/__init__.py +67 -0
- emdash_core/db/auth.py +134 -0
- emdash_core/db/models.py +91 -0
- emdash_core/db/provider.py +222 -0
- emdash_core/db/providers/__init__.py +5 -0
- emdash_core/db/providers/supabase.py +452 -0
- emdash_core/embeddings/__init__.py +24 -0
- emdash_core/embeddings/indexer.py +534 -0
- emdash_core/embeddings/models.py +192 -0
- emdash_core/embeddings/providers/__init__.py +7 -0
- emdash_core/embeddings/providers/base.py +112 -0
- emdash_core/embeddings/providers/fireworks.py +141 -0
- emdash_core/embeddings/providers/openai.py +104 -0
- emdash_core/embeddings/registry.py +146 -0
- emdash_core/embeddings/service.py +215 -0
- emdash_core/graph/__init__.py +26 -0
- emdash_core/graph/builder.py +134 -0
- emdash_core/graph/connection.py +692 -0
- emdash_core/graph/schema.py +416 -0
- emdash_core/graph/writer.py +667 -0
- emdash_core/ingestion/__init__.py +7 -0
- emdash_core/ingestion/change_detector.py +150 -0
- emdash_core/ingestion/git/__init__.py +5 -0
- emdash_core/ingestion/git/commit_analyzer.py +196 -0
- emdash_core/ingestion/github/__init__.py +6 -0
- emdash_core/ingestion/github/pr_fetcher.py +296 -0
- emdash_core/ingestion/github/task_extractor.py +100 -0
- emdash_core/ingestion/orchestrator.py +540 -0
- emdash_core/ingestion/parsers/__init__.py +10 -0
- emdash_core/ingestion/parsers/base_parser.py +66 -0
- emdash_core/ingestion/parsers/call_graph_builder.py +121 -0
- emdash_core/ingestion/parsers/class_extractor.py +154 -0
- emdash_core/ingestion/parsers/function_extractor.py +202 -0
- emdash_core/ingestion/parsers/import_analyzer.py +119 -0
- emdash_core/ingestion/parsers/python_parser.py +123 -0
- emdash_core/ingestion/parsers/registry.py +72 -0
- emdash_core/ingestion/parsers/ts_ast_parser.js +313 -0
- emdash_core/ingestion/parsers/typescript_parser.py +278 -0
- emdash_core/ingestion/repository.py +346 -0
- emdash_core/models/__init__.py +38 -0
- emdash_core/models/agent.py +68 -0
- emdash_core/models/index.py +77 -0
- emdash_core/models/query.py +113 -0
- emdash_core/planning/__init__.py +7 -0
- emdash_core/planning/agent_api.py +413 -0
- emdash_core/planning/context_builder.py +265 -0
- emdash_core/planning/feature_context.py +232 -0
- emdash_core/planning/feature_expander.py +646 -0
- emdash_core/planning/llm_explainer.py +198 -0
- emdash_core/planning/similarity.py +509 -0
- emdash_core/planning/team_focus.py +821 -0
- emdash_core/server.py +153 -0
- emdash_core/sse/__init__.py +5 -0
- emdash_core/sse/stream.py +196 -0
- emdash_core/swarm/__init__.py +17 -0
- emdash_core/swarm/merge_agent.py +383 -0
- emdash_core/swarm/session_manager.py +274 -0
- emdash_core/swarm/swarm_runner.py +226 -0
- emdash_core/swarm/task_definition.py +137 -0
- emdash_core/swarm/worker_spawner.py +319 -0
- emdash_core/swarm/worktree_manager.py +278 -0
- emdash_core/templates/__init__.py +10 -0
- emdash_core/templates/defaults/agent-builder.md.template +82 -0
- emdash_core/templates/defaults/focus.md.template +115 -0
- emdash_core/templates/defaults/pr-review-enhanced.md.template +309 -0
- emdash_core/templates/defaults/pr-review.md.template +80 -0
- emdash_core/templates/defaults/project.md.template +85 -0
- emdash_core/templates/defaults/research_critic.md.template +112 -0
- emdash_core/templates/defaults/research_planner.md.template +85 -0
- emdash_core/templates/defaults/research_synthesizer.md.template +128 -0
- emdash_core/templates/defaults/reviewer.md.template +81 -0
- emdash_core/templates/defaults/spec.md.template +41 -0
- emdash_core/templates/defaults/tasks.md.template +78 -0
- emdash_core/templates/loader.py +296 -0
- emdash_core/utils/__init__.py +45 -0
- emdash_core/utils/git.py +84 -0
- emdash_core/utils/image.py +502 -0
- emdash_core/utils/logger.py +51 -0
- emdash_core-0.1.7.dist-info/METADATA +35 -0
- emdash_core-0.1.7.dist-info/RECORD +187 -0
- emdash_core-0.1.7.dist-info/WHEEL +4 -0
- emdash_core-0.1.7.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
"""In-process sub-agent runner.
|
|
2
|
+
|
|
3
|
+
Runs sub-agents in the same process for better UX (real-time events)
|
|
4
|
+
while keeping isolated message histories.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import time
|
|
9
|
+
import uuid
|
|
10
|
+
from dataclasses import dataclass, asdict
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Optional
|
|
13
|
+
from concurrent.futures import ThreadPoolExecutor, Future
|
|
14
|
+
|
|
15
|
+
from .toolkits import get_toolkit
|
|
16
|
+
from .subagent_prompts import get_subagent_prompt
|
|
17
|
+
from .providers import get_provider
|
|
18
|
+
from .providers.factory import DEFAULT_MODEL
|
|
19
|
+
from ..utils.logger import log
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class SubAgentResult:
|
|
24
|
+
"""Result from a sub-agent execution."""
|
|
25
|
+
|
|
26
|
+
success: bool
|
|
27
|
+
agent_type: str
|
|
28
|
+
agent_id: str
|
|
29
|
+
task: str
|
|
30
|
+
summary: str
|
|
31
|
+
files_explored: list[str]
|
|
32
|
+
findings: list[dict]
|
|
33
|
+
iterations: int
|
|
34
|
+
tools_used: list[str]
|
|
35
|
+
execution_time: float
|
|
36
|
+
error: Optional[str] = None
|
|
37
|
+
|
|
38
|
+
def to_dict(self) -> dict:
|
|
39
|
+
return asdict(self)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class InProcessSubAgent:
|
|
43
|
+
"""Sub-agent that runs in the same process.
|
|
44
|
+
|
|
45
|
+
Benefits over subprocess:
|
|
46
|
+
- Real-time event streaming to parent emitter
|
|
47
|
+
- No stdout/stderr parsing
|
|
48
|
+
- Simpler debugging
|
|
49
|
+
- Natural UI integration
|
|
50
|
+
|
|
51
|
+
Each sub-agent has its own:
|
|
52
|
+
- Message history (isolated)
|
|
53
|
+
- Agent ID (for event tagging)
|
|
54
|
+
- Toolkit instance
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(
|
|
58
|
+
self,
|
|
59
|
+
subagent_type: str,
|
|
60
|
+
repo_root: Path,
|
|
61
|
+
emitter=None,
|
|
62
|
+
model: Optional[str] = None,
|
|
63
|
+
max_turns: int = 10,
|
|
64
|
+
agent_id: Optional[str] = None,
|
|
65
|
+
):
|
|
66
|
+
"""Initialize in-process sub-agent.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
subagent_type: Type of agent (Explore, Plan, etc.)
|
|
70
|
+
repo_root: Repository root directory
|
|
71
|
+
emitter: Parent emitter for events (optional)
|
|
72
|
+
model: Model to use (defaults to fast model)
|
|
73
|
+
max_turns: Maximum iterations
|
|
74
|
+
agent_id: Optional agent ID (generated if not provided)
|
|
75
|
+
"""
|
|
76
|
+
self.subagent_type = subagent_type
|
|
77
|
+
self.repo_root = repo_root.resolve()
|
|
78
|
+
self.emitter = emitter
|
|
79
|
+
self.max_turns = max_turns
|
|
80
|
+
self.agent_id = agent_id or str(uuid.uuid4())[:8]
|
|
81
|
+
|
|
82
|
+
# Get toolkit for this agent type
|
|
83
|
+
self.toolkit = get_toolkit(subagent_type, repo_root)
|
|
84
|
+
|
|
85
|
+
# Get model and create provider
|
|
86
|
+
model_name = model or DEFAULT_MODEL
|
|
87
|
+
self.provider = get_provider(model_name)
|
|
88
|
+
|
|
89
|
+
# Get system prompt
|
|
90
|
+
self.system_prompt = get_subagent_prompt(subagent_type)
|
|
91
|
+
|
|
92
|
+
# Tracking
|
|
93
|
+
self.files_explored: set[str] = set()
|
|
94
|
+
self.tools_used: list[str] = []
|
|
95
|
+
|
|
96
|
+
def _emit(self, event_type: str, **data) -> None:
|
|
97
|
+
"""Emit event with agent tagging.
|
|
98
|
+
|
|
99
|
+
Uses the generic emit() method to preserve subagent_id and subagent_type
|
|
100
|
+
in the event data, allowing the UI to display sub-agent events differently.
|
|
101
|
+
"""
|
|
102
|
+
if self.emitter and hasattr(self.emitter, "emit"):
|
|
103
|
+
from .events import EventType
|
|
104
|
+
|
|
105
|
+
# Tag event with agent info
|
|
106
|
+
data["subagent_id"] = self.agent_id
|
|
107
|
+
data["subagent_type"] = self.subagent_type
|
|
108
|
+
|
|
109
|
+
# Map event types
|
|
110
|
+
event_map = {
|
|
111
|
+
"tool_start": EventType.TOOL_START,
|
|
112
|
+
"tool_result": EventType.TOOL_RESULT,
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if event_type in event_map:
|
|
116
|
+
self.emitter.emit(event_map[event_type], data)
|
|
117
|
+
|
|
118
|
+
def run(self, prompt: str) -> SubAgentResult:
|
|
119
|
+
"""Execute the task and return results.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
prompt: The task to perform
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
SubAgentResult with findings
|
|
126
|
+
"""
|
|
127
|
+
start_time = time.time()
|
|
128
|
+
messages = []
|
|
129
|
+
iterations = 0
|
|
130
|
+
last_content = ""
|
|
131
|
+
error = None
|
|
132
|
+
|
|
133
|
+
# Add user message
|
|
134
|
+
messages.append({"role": "user", "content": prompt})
|
|
135
|
+
|
|
136
|
+
log.info(
|
|
137
|
+
"SubAgent {} starting: type={} prompt={}",
|
|
138
|
+
self.agent_id,
|
|
139
|
+
self.subagent_type,
|
|
140
|
+
prompt[:50] + "..." if len(prompt) > 50 else prompt,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
try:
|
|
144
|
+
# Agent loop
|
|
145
|
+
while iterations < self.max_turns:
|
|
146
|
+
iterations += 1
|
|
147
|
+
|
|
148
|
+
log.debug(f"SubAgent {self.agent_id} turn {iterations}/{self.max_turns}")
|
|
149
|
+
|
|
150
|
+
# Call LLM
|
|
151
|
+
response = self.provider.chat(
|
|
152
|
+
messages=messages,
|
|
153
|
+
tools=self.toolkit.get_all_schemas(),
|
|
154
|
+
system=self.system_prompt,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# Add assistant response
|
|
158
|
+
assistant_msg = self.provider.format_assistant_message(response)
|
|
159
|
+
if assistant_msg:
|
|
160
|
+
messages.append(assistant_msg)
|
|
161
|
+
|
|
162
|
+
# Save content
|
|
163
|
+
if response.content:
|
|
164
|
+
last_content = response.content
|
|
165
|
+
|
|
166
|
+
# Check if done
|
|
167
|
+
if not response.tool_calls:
|
|
168
|
+
break
|
|
169
|
+
|
|
170
|
+
# Execute tool calls
|
|
171
|
+
for tool_call in response.tool_calls:
|
|
172
|
+
self.tools_used.append(tool_call.name)
|
|
173
|
+
|
|
174
|
+
# Parse arguments
|
|
175
|
+
try:
|
|
176
|
+
args = json.loads(tool_call.arguments) if tool_call.arguments else {}
|
|
177
|
+
except (json.JSONDecodeError, TypeError):
|
|
178
|
+
args = {}
|
|
179
|
+
|
|
180
|
+
# Emit tool start
|
|
181
|
+
self._emit("tool_start", name=tool_call.name, args=args)
|
|
182
|
+
|
|
183
|
+
# Track files
|
|
184
|
+
if "path" in args:
|
|
185
|
+
self.files_explored.add(args["path"])
|
|
186
|
+
|
|
187
|
+
# Execute tool
|
|
188
|
+
result = self.toolkit.execute(tool_call.name, **args)
|
|
189
|
+
|
|
190
|
+
# Emit tool result
|
|
191
|
+
summary = str(result.data)[:100] if result.data else ""
|
|
192
|
+
self._emit(
|
|
193
|
+
"tool_result",
|
|
194
|
+
name=tool_call.name,
|
|
195
|
+
success=result.success,
|
|
196
|
+
summary=summary,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
# Add tool result to messages
|
|
200
|
+
tool_result_msg = self.provider.format_tool_result(
|
|
201
|
+
tool_call.id,
|
|
202
|
+
json.dumps(result.to_dict(), indent=2),
|
|
203
|
+
)
|
|
204
|
+
if tool_result_msg:
|
|
205
|
+
messages.append(tool_result_msg)
|
|
206
|
+
|
|
207
|
+
except Exception as e:
|
|
208
|
+
log.exception(f"SubAgent {self.agent_id} failed")
|
|
209
|
+
error = str(e)
|
|
210
|
+
|
|
211
|
+
execution_time = time.time() - start_time
|
|
212
|
+
|
|
213
|
+
log.info(
|
|
214
|
+
"SubAgent {} completed: {} turns, {} files, {:.1f}s",
|
|
215
|
+
self.agent_id,
|
|
216
|
+
iterations,
|
|
217
|
+
len(self.files_explored),
|
|
218
|
+
execution_time,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
return SubAgentResult(
|
|
222
|
+
success=error is None,
|
|
223
|
+
agent_type=self.subagent_type,
|
|
224
|
+
agent_id=self.agent_id,
|
|
225
|
+
task=prompt,
|
|
226
|
+
summary=last_content or "No response generated",
|
|
227
|
+
files_explored=list(self.files_explored),
|
|
228
|
+
findings=self._extract_findings(messages),
|
|
229
|
+
iterations=iterations,
|
|
230
|
+
tools_used=list(set(self.tools_used)),
|
|
231
|
+
execution_time=execution_time,
|
|
232
|
+
error=error,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
def _extract_findings(self, messages: list[dict]) -> list[dict]:
|
|
236
|
+
"""Extract key findings from tool results."""
|
|
237
|
+
findings = []
|
|
238
|
+
for msg in messages:
|
|
239
|
+
if msg and msg.get("role") == "tool":
|
|
240
|
+
try:
|
|
241
|
+
content = json.loads(msg.get("content", "{}"))
|
|
242
|
+
if content and content.get("success") and content.get("data"):
|
|
243
|
+
findings.append(content["data"])
|
|
244
|
+
except (json.JSONDecodeError, TypeError):
|
|
245
|
+
pass
|
|
246
|
+
return findings[-10:]
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
# Thread pool for parallel execution
|
|
250
|
+
_executor: Optional[ThreadPoolExecutor] = None
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _get_executor() -> ThreadPoolExecutor:
|
|
254
|
+
"""Get or create thread pool executor."""
|
|
255
|
+
global _executor
|
|
256
|
+
if _executor is None:
|
|
257
|
+
_executor = ThreadPoolExecutor(max_workers=4, thread_name_prefix="subagent")
|
|
258
|
+
return _executor
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def run_subagent(
|
|
262
|
+
subagent_type: str,
|
|
263
|
+
prompt: str,
|
|
264
|
+
repo_root: Path,
|
|
265
|
+
emitter=None,
|
|
266
|
+
model: Optional[str] = None,
|
|
267
|
+
max_turns: int = 10,
|
|
268
|
+
) -> SubAgentResult:
|
|
269
|
+
"""Run a sub-agent synchronously.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
subagent_type: Type of agent (Explore, Plan)
|
|
273
|
+
prompt: Task to perform
|
|
274
|
+
repo_root: Repository root
|
|
275
|
+
emitter: Event emitter
|
|
276
|
+
model: Model to use
|
|
277
|
+
max_turns: Max iterations
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
SubAgentResult
|
|
281
|
+
"""
|
|
282
|
+
agent = InProcessSubAgent(
|
|
283
|
+
subagent_type=subagent_type,
|
|
284
|
+
repo_root=repo_root,
|
|
285
|
+
emitter=emitter,
|
|
286
|
+
model=model,
|
|
287
|
+
max_turns=max_turns,
|
|
288
|
+
)
|
|
289
|
+
return agent.run(prompt)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def run_subagent_async(
|
|
293
|
+
subagent_type: str,
|
|
294
|
+
prompt: str,
|
|
295
|
+
repo_root: Path,
|
|
296
|
+
emitter=None,
|
|
297
|
+
model: Optional[str] = None,
|
|
298
|
+
max_turns: int = 10,
|
|
299
|
+
) -> Future[SubAgentResult]:
|
|
300
|
+
"""Run a sub-agent asynchronously (returns Future).
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
subagent_type: Type of agent (Explore, Plan)
|
|
304
|
+
prompt: Task to perform
|
|
305
|
+
repo_root: Repository root
|
|
306
|
+
emitter: Event emitter
|
|
307
|
+
model: Model to use
|
|
308
|
+
max_turns: Max iterations
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
Future[SubAgentResult] - call .result() to get result
|
|
312
|
+
"""
|
|
313
|
+
executor = _get_executor()
|
|
314
|
+
return executor.submit(
|
|
315
|
+
run_subagent,
|
|
316
|
+
subagent_type=subagent_type,
|
|
317
|
+
prompt=prompt,
|
|
318
|
+
repo_root=repo_root,
|
|
319
|
+
emitter=emitter,
|
|
320
|
+
model=model,
|
|
321
|
+
max_turns=max_turns,
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def run_subagents_parallel(
|
|
326
|
+
tasks: list[dict],
|
|
327
|
+
repo_root: Path,
|
|
328
|
+
emitter=None,
|
|
329
|
+
) -> list[SubAgentResult]:
|
|
330
|
+
"""Run multiple sub-agents in parallel.
|
|
331
|
+
|
|
332
|
+
Args:
|
|
333
|
+
tasks: List of task dicts with keys:
|
|
334
|
+
- subagent_type: str
|
|
335
|
+
- prompt: str
|
|
336
|
+
- model: str (optional)
|
|
337
|
+
- max_turns: int (optional)
|
|
338
|
+
repo_root: Repository root
|
|
339
|
+
emitter: Shared event emitter
|
|
340
|
+
|
|
341
|
+
Returns:
|
|
342
|
+
List of SubAgentResults in same order as tasks
|
|
343
|
+
"""
|
|
344
|
+
futures = []
|
|
345
|
+
for task in tasks:
|
|
346
|
+
future = run_subagent_async(
|
|
347
|
+
subagent_type=task.get("subagent_type", "Explore"),
|
|
348
|
+
prompt=task["prompt"],
|
|
349
|
+
repo_root=repo_root,
|
|
350
|
+
emitter=emitter,
|
|
351
|
+
model=task.get("model"),
|
|
352
|
+
max_turns=task.get("max_turns", 10),
|
|
353
|
+
)
|
|
354
|
+
futures.append(future)
|
|
355
|
+
|
|
356
|
+
# Wait for all to complete and gather results
|
|
357
|
+
results = []
|
|
358
|
+
for future in futures:
|
|
359
|
+
try:
|
|
360
|
+
results.append(future.result())
|
|
361
|
+
except Exception as e:
|
|
362
|
+
log.exception("Sub-agent failed")
|
|
363
|
+
results.append(SubAgentResult(
|
|
364
|
+
success=False,
|
|
365
|
+
agent_type="unknown",
|
|
366
|
+
agent_id="error",
|
|
367
|
+
task="",
|
|
368
|
+
summary="",
|
|
369
|
+
files_explored=[],
|
|
370
|
+
findings=[],
|
|
371
|
+
iterations=0,
|
|
372
|
+
tools_used=[],
|
|
373
|
+
execution_time=0,
|
|
374
|
+
error=str(e),
|
|
375
|
+
))
|
|
376
|
+
|
|
377
|
+
return results
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""MCP (Model Context Protocol) integration for dynamic tool loading.
|
|
2
|
+
|
|
3
|
+
This module provides:
|
|
4
|
+
- MCPServerManager: Manages lifecycle of MCP servers
|
|
5
|
+
- GenericMCPClient: Client for communicating with MCP servers
|
|
6
|
+
- MCPConfigFile: Configuration loading/saving
|
|
7
|
+
- Tool factory functions for creating tools from MCP servers
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from .config import (
|
|
11
|
+
MCPServerConfig,
|
|
12
|
+
MCPConfigFile,
|
|
13
|
+
get_default_mcp_config_path,
|
|
14
|
+
ensure_mcp_config,
|
|
15
|
+
)
|
|
16
|
+
from .client import (
|
|
17
|
+
GenericMCPClient,
|
|
18
|
+
MCPToolInfo,
|
|
19
|
+
MCPResponse,
|
|
20
|
+
MCPError,
|
|
21
|
+
)
|
|
22
|
+
from .manager import (
|
|
23
|
+
MCPServerManager,
|
|
24
|
+
get_mcp_manager,
|
|
25
|
+
reset_mcp_manager,
|
|
26
|
+
)
|
|
27
|
+
from .tool_factory import (
|
|
28
|
+
MCPDynamicTool,
|
|
29
|
+
create_tools_from_mcp,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
# Config
|
|
34
|
+
"MCPServerConfig",
|
|
35
|
+
"MCPConfigFile",
|
|
36
|
+
"get_default_mcp_config_path",
|
|
37
|
+
"ensure_mcp_config",
|
|
38
|
+
# Client
|
|
39
|
+
"GenericMCPClient",
|
|
40
|
+
"MCPToolInfo",
|
|
41
|
+
"MCPResponse",
|
|
42
|
+
"MCPError",
|
|
43
|
+
# Manager
|
|
44
|
+
"MCPServerManager",
|
|
45
|
+
"get_mcp_manager",
|
|
46
|
+
"reset_mcp_manager",
|
|
47
|
+
# Tool factory
|
|
48
|
+
"MCPDynamicTool",
|
|
49
|
+
"create_tools_from_mcp",
|
|
50
|
+
]
|