hanzo-mcp 0.8.11__py3-none-any.whl → 0.9.0__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 hanzo-mcp might be problematic. Click here for more details.
- hanzo_mcp/__init__.py +1 -3
- hanzo_mcp/analytics/posthog_analytics.py +3 -9
- hanzo_mcp/bridge.py +9 -25
- hanzo_mcp/cli.py +6 -15
- hanzo_mcp/cli_enhanced.py +5 -14
- hanzo_mcp/cli_plugin.py +3 -9
- hanzo_mcp/config/settings.py +6 -20
- hanzo_mcp/config/tool_config.py +1 -3
- hanzo_mcp/core/base_agent.py +88 -88
- hanzo_mcp/core/model_registry.py +238 -210
- hanzo_mcp/dev_server.py +5 -15
- hanzo_mcp/prompts/__init__.py +2 -6
- hanzo_mcp/prompts/project_todo_reminder.py +3 -9
- hanzo_mcp/prompts/tool_explorer.py +1 -3
- hanzo_mcp/prompts/utils.py +7 -21
- hanzo_mcp/server.py +2 -6
- hanzo_mcp/tools/__init__.py +26 -27
- hanzo_mcp/tools/agent/__init__.py +2 -1
- hanzo_mcp/tools/agent/agent.py +10 -30
- hanzo_mcp/tools/agent/agent_tool.py +22 -15
- hanzo_mcp/tools/agent/claude_desktop_auth.py +3 -9
- hanzo_mcp/tools/agent/cli_agent_base.py +7 -24
- hanzo_mcp/tools/agent/cli_tools.py +75 -74
- hanzo_mcp/tools/agent/code_auth.py +1 -3
- hanzo_mcp/tools/agent/code_auth_tool.py +2 -6
- hanzo_mcp/tools/agent/critic_tool.py +8 -24
- hanzo_mcp/tools/agent/iching_tool.py +12 -36
- hanzo_mcp/tools/agent/network_tool.py +7 -18
- hanzo_mcp/tools/agent/prompt.py +1 -5
- hanzo_mcp/tools/agent/review_tool.py +10 -25
- hanzo_mcp/tools/agent/swarm_alias.py +1 -3
- hanzo_mcp/tools/agent/unified_cli_tools.py +38 -38
- hanzo_mcp/tools/common/batch_tool.py +15 -45
- hanzo_mcp/tools/common/config_tool.py +9 -28
- hanzo_mcp/tools/common/context.py +1 -3
- hanzo_mcp/tools/common/critic_tool.py +1 -3
- hanzo_mcp/tools/common/decorators.py +2 -6
- hanzo_mcp/tools/common/enhanced_base.py +2 -6
- hanzo_mcp/tools/common/fastmcp_pagination.py +4 -12
- hanzo_mcp/tools/common/forgiving_edit.py +9 -28
- hanzo_mcp/tools/common/mode.py +1 -5
- hanzo_mcp/tools/common/paginated_base.py +3 -11
- hanzo_mcp/tools/common/paginated_response.py +10 -30
- hanzo_mcp/tools/common/pagination.py +3 -9
- hanzo_mcp/tools/common/path_utils.py +34 -0
- hanzo_mcp/tools/common/permissions.py +14 -13
- hanzo_mcp/tools/common/personality.py +983 -701
- hanzo_mcp/tools/common/plugin_loader.py +3 -15
- hanzo_mcp/tools/common/stats.py +6 -18
- hanzo_mcp/tools/common/thinking_tool.py +1 -3
- hanzo_mcp/tools/common/tool_disable.py +2 -6
- hanzo_mcp/tools/common/tool_list.py +2 -6
- hanzo_mcp/tools/common/validation.py +1 -3
- hanzo_mcp/tools/compiler/__init__.py +8 -0
- hanzo_mcp/tools/compiler/sandboxed_compiler.py +681 -0
- hanzo_mcp/tools/config/config_tool.py +7 -13
- hanzo_mcp/tools/config/index_config.py +1 -3
- hanzo_mcp/tools/config/mode_tool.py +5 -15
- hanzo_mcp/tools/database/database_manager.py +3 -9
- hanzo_mcp/tools/database/graph.py +1 -3
- hanzo_mcp/tools/database/graph_add.py +3 -9
- hanzo_mcp/tools/database/graph_query.py +11 -34
- hanzo_mcp/tools/database/graph_remove.py +3 -9
- hanzo_mcp/tools/database/graph_search.py +6 -20
- hanzo_mcp/tools/database/graph_stats.py +11 -33
- hanzo_mcp/tools/database/sql.py +4 -12
- hanzo_mcp/tools/database/sql_query.py +6 -10
- hanzo_mcp/tools/database/sql_search.py +2 -6
- hanzo_mcp/tools/database/sql_stats.py +5 -15
- hanzo_mcp/tools/editor/neovim_command.py +1 -3
- hanzo_mcp/tools/editor/neovim_session.py +7 -13
- hanzo_mcp/tools/environment/__init__.py +8 -0
- hanzo_mcp/tools/environment/environment_detector.py +594 -0
- hanzo_mcp/tools/filesystem/__init__.py +28 -26
- hanzo_mcp/tools/filesystem/ast_multi_edit.py +14 -43
- hanzo_mcp/tools/filesystem/ast_tool.py +3 -0
- hanzo_mcp/tools/filesystem/base.py +20 -12
- hanzo_mcp/tools/filesystem/content_replace.py +7 -12
- hanzo_mcp/tools/filesystem/diff.py +2 -10
- hanzo_mcp/tools/filesystem/directory_tree.py +285 -51
- hanzo_mcp/tools/filesystem/edit.py +10 -18
- hanzo_mcp/tools/filesystem/find.py +312 -179
- hanzo_mcp/tools/filesystem/git_search.py +12 -24
- hanzo_mcp/tools/filesystem/multi_edit.py +10 -18
- hanzo_mcp/tools/filesystem/read.py +14 -30
- hanzo_mcp/tools/filesystem/rules_tool.py +9 -17
- hanzo_mcp/tools/filesystem/search.py +1160 -0
- hanzo_mcp/tools/filesystem/watch.py +2 -4
- hanzo_mcp/tools/filesystem/write.py +7 -10
- hanzo_mcp/tools/framework/__init__.py +8 -0
- hanzo_mcp/tools/framework/framework_modes.py +714 -0
- hanzo_mcp/tools/jupyter/base.py +6 -20
- hanzo_mcp/tools/jupyter/jupyter.py +4 -12
- hanzo_mcp/tools/llm/consensus_tool.py +8 -24
- hanzo_mcp/tools/llm/llm_manage.py +2 -6
- hanzo_mcp/tools/llm/llm_tool.py +17 -58
- hanzo_mcp/tools/llm/llm_unified.py +18 -59
- hanzo_mcp/tools/llm/provider_tools.py +1 -3
- hanzo_mcp/tools/lsp/lsp_tool.py +621 -481
- hanzo_mcp/tools/mcp/mcp_add.py +1 -3
- hanzo_mcp/tools/mcp/mcp_stats.py +1 -3
- hanzo_mcp/tools/mcp/mcp_tool.py +9 -23
- hanzo_mcp/tools/memory/__init__.py +10 -27
- hanzo_mcp/tools/memory/conversation_memory.py +636 -0
- hanzo_mcp/tools/memory/knowledge_tools.py +7 -25
- hanzo_mcp/tools/memory/memory_tools.py +6 -18
- hanzo_mcp/tools/search/find_tool.py +12 -34
- hanzo_mcp/tools/search/unified_search.py +24 -78
- hanzo_mcp/tools/shell/__init__.py +16 -4
- hanzo_mcp/tools/shell/auto_background.py +2 -6
- hanzo_mcp/tools/shell/base.py +1 -5
- hanzo_mcp/tools/shell/base_process.py +5 -7
- hanzo_mcp/tools/shell/bash_session.py +7 -24
- hanzo_mcp/tools/shell/bash_session_executor.py +5 -15
- hanzo_mcp/tools/shell/bash_tool.py +3 -7
- hanzo_mcp/tools/shell/command_executor.py +26 -79
- hanzo_mcp/tools/shell/logs.py +4 -16
- hanzo_mcp/tools/shell/npx.py +2 -8
- hanzo_mcp/tools/shell/npx_tool.py +1 -3
- hanzo_mcp/tools/shell/pkill.py +4 -12
- hanzo_mcp/tools/shell/process_tool.py +2 -8
- hanzo_mcp/tools/shell/processes.py +5 -17
- hanzo_mcp/tools/shell/run_background.py +1 -3
- hanzo_mcp/tools/shell/run_command.py +1 -3
- hanzo_mcp/tools/shell/run_command_windows.py +1 -3
- hanzo_mcp/tools/shell/run_tool.py +56 -0
- hanzo_mcp/tools/shell/session_manager.py +2 -6
- hanzo_mcp/tools/shell/session_storage.py +2 -6
- hanzo_mcp/tools/shell/streaming_command.py +7 -23
- hanzo_mcp/tools/shell/uvx.py +4 -14
- hanzo_mcp/tools/shell/uvx_background.py +2 -6
- hanzo_mcp/tools/shell/uvx_tool.py +1 -3
- hanzo_mcp/tools/shell/zsh_tool.py +12 -20
- hanzo_mcp/tools/todo/todo.py +1 -3
- hanzo_mcp/tools/vector/__init__.py +97 -50
- hanzo_mcp/tools/vector/ast_analyzer.py +6 -20
- hanzo_mcp/tools/vector/git_ingester.py +10 -30
- hanzo_mcp/tools/vector/index_tool.py +3 -9
- hanzo_mcp/tools/vector/infinity_store.py +7 -27
- hanzo_mcp/tools/vector/mock_infinity.py +1 -3
- hanzo_mcp/tools/vector/node_tool.py +538 -0
- hanzo_mcp/tools/vector/project_manager.py +4 -12
- hanzo_mcp/tools/vector/unified_vector.py +384 -0
- hanzo_mcp/tools/vector/vector.py +2 -6
- hanzo_mcp/tools/vector/vector_index.py +8 -8
- hanzo_mcp/tools/vector/vector_search.py +7 -21
- {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.9.0.dist-info}/METADATA +2 -2
- hanzo_mcp-0.9.0.dist-info/RECORD +191 -0
- hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +0 -645
- hanzo_mcp/tools/agent/swarm_tool.py +0 -718
- hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +0 -577
- hanzo_mcp/tools/filesystem/batch_search.py +0 -900
- hanzo_mcp/tools/filesystem/directory_tree_paginated.py +0 -350
- hanzo_mcp/tools/filesystem/find_files.py +0 -369
- hanzo_mcp/tools/filesystem/grep.py +0 -467
- hanzo_mcp/tools/filesystem/search_tool.py +0 -767
- hanzo_mcp/tools/filesystem/symbols_tool.py +0 -515
- hanzo_mcp/tools/filesystem/tree.py +0 -270
- hanzo_mcp/tools/jupyter/notebook_edit.py +0 -317
- hanzo_mcp/tools/jupyter/notebook_read.py +0 -147
- hanzo_mcp/tools/todo/todo_read.py +0 -143
- hanzo_mcp/tools/todo/todo_write.py +0 -374
- hanzo_mcp-0.8.11.dist-info/RECORD +0 -193
- {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.9.0.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.9.0.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.9.0.dist-info}/top_level.txt +0 -0
hanzo_mcp/core/base_agent.py
CHANGED
|
@@ -28,11 +28,11 @@ TResult = TypeVar("TResult")
|
|
|
28
28
|
@runtime_checkable
|
|
29
29
|
class AgentContext(Protocol):
|
|
30
30
|
"""Protocol for agent execution context."""
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
async def log(self, message: str, level: str = "info") -> None:
|
|
33
33
|
"""Log a message."""
|
|
34
34
|
pass
|
|
35
|
-
|
|
35
|
+
|
|
36
36
|
async def progress(self, message: str, percentage: Optional[float] = None) -> None:
|
|
37
37
|
"""Report progress."""
|
|
38
38
|
pass
|
|
@@ -41,7 +41,7 @@ class AgentContext(Protocol):
|
|
|
41
41
|
@dataclass
|
|
42
42
|
class AgentConfig:
|
|
43
43
|
"""Configuration for agent execution."""
|
|
44
|
-
|
|
44
|
+
|
|
45
45
|
model: str = "claude-3-5-sonnet-20241022"
|
|
46
46
|
timeout: int = 300
|
|
47
47
|
max_retries: int = 3
|
|
@@ -49,7 +49,7 @@ class AgentConfig:
|
|
|
49
49
|
environment: Dict[str, str] = field(default_factory=dict)
|
|
50
50
|
stream_output: bool = False
|
|
51
51
|
use_worktree: bool = False
|
|
52
|
-
|
|
52
|
+
|
|
53
53
|
def __post_init__(self) -> None:
|
|
54
54
|
"""Resolve model name and validate configuration."""
|
|
55
55
|
self.model = registry.resolve(self.model)
|
|
@@ -60,13 +60,13 @@ class AgentConfig:
|
|
|
60
60
|
@dataclass
|
|
61
61
|
class AgentResult:
|
|
62
62
|
"""Result from agent execution."""
|
|
63
|
-
|
|
63
|
+
|
|
64
64
|
success: bool
|
|
65
65
|
output: Optional[str] = None
|
|
66
66
|
error: Optional[str] = None
|
|
67
67
|
duration: Optional[float] = None
|
|
68
68
|
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
69
|
-
|
|
69
|
+
|
|
70
70
|
@property
|
|
71
71
|
def content(self) -> str:
|
|
72
72
|
"""Get the primary content (output or error)."""
|
|
@@ -75,33 +75,33 @@ class AgentResult:
|
|
|
75
75
|
|
|
76
76
|
class BaseAgent(ABC, Generic[TContext]):
|
|
77
77
|
"""Base class for all AI agents.
|
|
78
|
-
|
|
78
|
+
|
|
79
79
|
This is the single foundation for all agent implementations,
|
|
80
80
|
ensuring consistent behavior and eliminating code duplication.
|
|
81
81
|
"""
|
|
82
|
-
|
|
82
|
+
|
|
83
83
|
def __init__(self, config: Optional[AgentConfig] = None) -> None:
|
|
84
84
|
"""Initialize agent with configuration.
|
|
85
|
-
|
|
85
|
+
|
|
86
86
|
Args:
|
|
87
87
|
config: Agent configuration
|
|
88
88
|
"""
|
|
89
89
|
self.config = config or AgentConfig()
|
|
90
90
|
self._start_time: Optional[datetime] = None
|
|
91
91
|
self._end_time: Optional[datetime] = None
|
|
92
|
-
|
|
92
|
+
|
|
93
93
|
@property
|
|
94
94
|
@abstractmethod
|
|
95
95
|
def name(self) -> str:
|
|
96
96
|
"""Agent name."""
|
|
97
97
|
pass
|
|
98
|
-
|
|
98
|
+
|
|
99
99
|
@property
|
|
100
100
|
@abstractmethod
|
|
101
101
|
def description(self) -> str:
|
|
102
102
|
"""Agent description."""
|
|
103
103
|
pass
|
|
104
|
-
|
|
104
|
+
|
|
105
105
|
async def execute(
|
|
106
106
|
self,
|
|
107
107
|
prompt: str,
|
|
@@ -109,76 +109,76 @@ class BaseAgent(ABC, Generic[TContext]):
|
|
|
109
109
|
**kwargs: Any,
|
|
110
110
|
) -> AgentResult:
|
|
111
111
|
"""Execute agent with prompt.
|
|
112
|
-
|
|
112
|
+
|
|
113
113
|
Args:
|
|
114
114
|
prompt: The prompt or task
|
|
115
115
|
context: Execution context
|
|
116
116
|
**kwargs: Additional parameters
|
|
117
|
-
|
|
117
|
+
|
|
118
118
|
Returns:
|
|
119
119
|
Agent execution result
|
|
120
120
|
"""
|
|
121
121
|
self._start_time = datetime.now()
|
|
122
|
-
|
|
122
|
+
|
|
123
123
|
try:
|
|
124
124
|
# Setup environment
|
|
125
125
|
env = self._prepare_environment()
|
|
126
|
-
|
|
126
|
+
|
|
127
127
|
# Log start
|
|
128
128
|
if context and isinstance(context, AgentContext):
|
|
129
129
|
await context.log(f"Starting {self.name} with model {self.config.model}")
|
|
130
|
-
|
|
130
|
+
|
|
131
131
|
# Execute with retries
|
|
132
132
|
result = await self._execute_with_retries(prompt, context, env, **kwargs)
|
|
133
|
-
|
|
133
|
+
|
|
134
134
|
# Calculate duration
|
|
135
135
|
self._end_time = datetime.now()
|
|
136
136
|
duration = (self._end_time - self._start_time).total_seconds()
|
|
137
|
-
|
|
137
|
+
|
|
138
138
|
return AgentResult(
|
|
139
139
|
success=True,
|
|
140
140
|
output=result,
|
|
141
141
|
duration=duration,
|
|
142
142
|
metadata={"model": self.config.model, "agent": self.name},
|
|
143
143
|
)
|
|
144
|
-
|
|
144
|
+
|
|
145
145
|
except Exception as e:
|
|
146
146
|
self._end_time = datetime.now()
|
|
147
147
|
duration = (self._end_time - self._start_time).total_seconds() if self._start_time else None
|
|
148
|
-
|
|
148
|
+
|
|
149
149
|
logger.error(f"Agent {self.name} failed: {e}")
|
|
150
|
-
|
|
150
|
+
|
|
151
151
|
return AgentResult(
|
|
152
152
|
success=False,
|
|
153
153
|
error=str(e),
|
|
154
154
|
duration=duration,
|
|
155
155
|
metadata={"model": self.config.model, "agent": self.name},
|
|
156
156
|
)
|
|
157
|
-
|
|
157
|
+
|
|
158
158
|
def _prepare_environment(self) -> Dict[str, str]:
|
|
159
159
|
"""Prepare environment variables for execution.
|
|
160
|
-
|
|
160
|
+
|
|
161
161
|
Returns:
|
|
162
162
|
Environment variables dictionary
|
|
163
163
|
"""
|
|
164
164
|
env = os.environ.copy()
|
|
165
|
-
|
|
165
|
+
|
|
166
166
|
# Add model-specific API key
|
|
167
167
|
model_config = registry.get(self.config.model)
|
|
168
168
|
if model_config and model_config.api_key_env:
|
|
169
169
|
key_var = model_config.api_key_env
|
|
170
170
|
if key_var in os.environ:
|
|
171
171
|
env[key_var] = os.environ[key_var]
|
|
172
|
-
|
|
172
|
+
|
|
173
173
|
# Add Hanzo unified auth
|
|
174
174
|
if "HANZO_API_KEY" in os.environ:
|
|
175
175
|
env["HANZO_API_KEY"] = os.environ["HANZO_API_KEY"]
|
|
176
|
-
|
|
176
|
+
|
|
177
177
|
# Add custom environment
|
|
178
178
|
env.update(self.config.environment)
|
|
179
|
-
|
|
179
|
+
|
|
180
180
|
return env
|
|
181
|
-
|
|
181
|
+
|
|
182
182
|
async def _execute_with_retries(
|
|
183
183
|
self,
|
|
184
184
|
prompt: str,
|
|
@@ -187,47 +187,47 @@ class BaseAgent(ABC, Generic[TContext]):
|
|
|
187
187
|
**kwargs: Any,
|
|
188
188
|
) -> str:
|
|
189
189
|
"""Execute with retry logic.
|
|
190
|
-
|
|
190
|
+
|
|
191
191
|
Args:
|
|
192
192
|
prompt: The prompt
|
|
193
193
|
context: Execution context
|
|
194
194
|
env: Environment variables
|
|
195
195
|
**kwargs: Additional parameters
|
|
196
|
-
|
|
196
|
+
|
|
197
197
|
Returns:
|
|
198
198
|
Execution output
|
|
199
|
-
|
|
199
|
+
|
|
200
200
|
Raises:
|
|
201
201
|
Exception: If all retries fail
|
|
202
202
|
"""
|
|
203
203
|
last_error = None
|
|
204
|
-
|
|
204
|
+
|
|
205
205
|
for attempt in range(self.config.max_retries):
|
|
206
206
|
try:
|
|
207
207
|
# Call the implementation
|
|
208
208
|
result = await self._execute_impl(prompt, context, env, **kwargs)
|
|
209
209
|
return result
|
|
210
|
-
|
|
210
|
+
|
|
211
211
|
except asyncio.TimeoutError:
|
|
212
212
|
last_error = f"Timeout after {self.config.timeout} seconds"
|
|
213
213
|
if context and isinstance(context, AgentContext):
|
|
214
214
|
await context.log(f"Attempt {attempt + 1} timed out", "warning")
|
|
215
|
-
|
|
215
|
+
|
|
216
216
|
except Exception as e:
|
|
217
217
|
last_error = str(e)
|
|
218
218
|
if context and isinstance(context, AgentContext):
|
|
219
219
|
await context.log(f"Attempt {attempt + 1} failed: {e}", "warning")
|
|
220
|
-
|
|
220
|
+
|
|
221
221
|
# Don't retry on certain errors
|
|
222
222
|
if "unauthorized" in str(e).lower() or "forbidden" in str(e).lower():
|
|
223
223
|
raise
|
|
224
|
-
|
|
224
|
+
|
|
225
225
|
# Wait before retry (exponential backoff)
|
|
226
226
|
if attempt < self.config.max_retries - 1:
|
|
227
|
-
await asyncio.sleep(2
|
|
228
|
-
|
|
227
|
+
await asyncio.sleep(2**attempt)
|
|
228
|
+
|
|
229
229
|
raise Exception(f"All {self.config.max_retries} attempts failed. Last error: {last_error}")
|
|
230
|
-
|
|
230
|
+
|
|
231
231
|
@abstractmethod
|
|
232
232
|
async def _execute_impl(
|
|
233
233
|
self,
|
|
@@ -237,13 +237,13 @@ class BaseAgent(ABC, Generic[TContext]):
|
|
|
237
237
|
**kwargs: Any,
|
|
238
238
|
) -> str:
|
|
239
239
|
"""Implementation-specific execution.
|
|
240
|
-
|
|
240
|
+
|
|
241
241
|
Args:
|
|
242
242
|
prompt: The prompt
|
|
243
243
|
context: Execution context
|
|
244
244
|
env: Environment variables
|
|
245
245
|
**kwargs: Additional parameters
|
|
246
|
-
|
|
246
|
+
|
|
247
247
|
Returns:
|
|
248
248
|
Execution output
|
|
249
249
|
"""
|
|
@@ -252,35 +252,35 @@ class BaseAgent(ABC, Generic[TContext]):
|
|
|
252
252
|
|
|
253
253
|
class CLIAgent(BaseAgent[TContext]):
|
|
254
254
|
"""Base class for CLI-based agents."""
|
|
255
|
-
|
|
255
|
+
|
|
256
256
|
@property
|
|
257
257
|
@abstractmethod
|
|
258
258
|
def cli_command(self) -> str:
|
|
259
259
|
"""CLI command to execute."""
|
|
260
260
|
pass
|
|
261
|
-
|
|
261
|
+
|
|
262
262
|
def build_command(self, prompt: str, **kwargs: Any) -> List[str]:
|
|
263
263
|
"""Build the CLI command.
|
|
264
|
-
|
|
264
|
+
|
|
265
265
|
Args:
|
|
266
266
|
prompt: The prompt
|
|
267
267
|
**kwargs: Additional parameters
|
|
268
|
-
|
|
268
|
+
|
|
269
269
|
Returns:
|
|
270
270
|
Command arguments list
|
|
271
271
|
"""
|
|
272
272
|
command = [self.cli_command]
|
|
273
|
-
|
|
273
|
+
|
|
274
274
|
# Add model if specified
|
|
275
275
|
model_config = registry.get(self.config.model)
|
|
276
276
|
if model_config:
|
|
277
277
|
command.extend(["--model", model_config.full_name])
|
|
278
|
-
|
|
278
|
+
|
|
279
279
|
# Add prompt
|
|
280
280
|
command.append(prompt)
|
|
281
|
-
|
|
281
|
+
|
|
282
282
|
return command
|
|
283
|
-
|
|
283
|
+
|
|
284
284
|
async def _execute_impl(
|
|
285
285
|
self,
|
|
286
286
|
prompt: str,
|
|
@@ -289,21 +289,21 @@ class CLIAgent(BaseAgent[TContext]):
|
|
|
289
289
|
**kwargs: Any,
|
|
290
290
|
) -> str:
|
|
291
291
|
"""Execute CLI command.
|
|
292
|
-
|
|
292
|
+
|
|
293
293
|
Args:
|
|
294
294
|
prompt: The prompt
|
|
295
295
|
context: Execution context
|
|
296
296
|
env: Environment variables
|
|
297
297
|
**kwargs: Additional parameters
|
|
298
|
-
|
|
298
|
+
|
|
299
299
|
Returns:
|
|
300
300
|
Command output
|
|
301
301
|
"""
|
|
302
302
|
command = self.build_command(prompt, **kwargs)
|
|
303
|
-
|
|
303
|
+
|
|
304
304
|
# Determine if we need stdin
|
|
305
305
|
needs_stdin = self.cli_command in ["claude", "cline"]
|
|
306
|
-
|
|
306
|
+
|
|
307
307
|
# Execute command
|
|
308
308
|
process = await asyncio.create_subprocess_exec(
|
|
309
309
|
*command,
|
|
@@ -313,7 +313,7 @@ class CLIAgent(BaseAgent[TContext]):
|
|
|
313
313
|
cwd=str(self.config.working_dir) if self.config.working_dir else None,
|
|
314
314
|
env=env,
|
|
315
315
|
)
|
|
316
|
-
|
|
316
|
+
|
|
317
317
|
# Handle timeout
|
|
318
318
|
try:
|
|
319
319
|
stdout, stderr = await asyncio.wait_for(
|
|
@@ -323,18 +323,18 @@ class CLIAgent(BaseAgent[TContext]):
|
|
|
323
323
|
except asyncio.TimeoutError:
|
|
324
324
|
process.kill()
|
|
325
325
|
raise asyncio.TimeoutError(f"Command timed out after {self.config.timeout} seconds")
|
|
326
|
-
|
|
326
|
+
|
|
327
327
|
# Check for errors
|
|
328
328
|
if process.returncode != 0:
|
|
329
329
|
error_msg = stderr.decode() if stderr else "Command failed"
|
|
330
330
|
raise Exception(error_msg)
|
|
331
|
-
|
|
331
|
+
|
|
332
332
|
return stdout.decode()
|
|
333
333
|
|
|
334
334
|
|
|
335
335
|
class APIAgent(BaseAgent[TContext]):
|
|
336
336
|
"""Base class for API-based agents."""
|
|
337
|
-
|
|
337
|
+
|
|
338
338
|
async def _execute_impl(
|
|
339
339
|
self,
|
|
340
340
|
prompt: str,
|
|
@@ -343,13 +343,13 @@ class APIAgent(BaseAgent[TContext]):
|
|
|
343
343
|
**kwargs: Any,
|
|
344
344
|
) -> str:
|
|
345
345
|
"""Execute via API.
|
|
346
|
-
|
|
346
|
+
|
|
347
347
|
Args:
|
|
348
348
|
prompt: The prompt
|
|
349
349
|
context: Execution context
|
|
350
350
|
env: Environment variables
|
|
351
351
|
**kwargs: Additional parameters
|
|
352
|
-
|
|
352
|
+
|
|
353
353
|
Returns:
|
|
354
354
|
API response
|
|
355
355
|
"""
|
|
@@ -360,36 +360,36 @@ class APIAgent(BaseAgent[TContext]):
|
|
|
360
360
|
|
|
361
361
|
class AgentOrchestrator:
|
|
362
362
|
"""Orchestrator for managing multiple agents."""
|
|
363
|
-
|
|
363
|
+
|
|
364
364
|
def __init__(self, default_config: Optional[AgentConfig] = None) -> None:
|
|
365
365
|
"""Initialize orchestrator.
|
|
366
|
-
|
|
366
|
+
|
|
367
367
|
Args:
|
|
368
368
|
default_config: Default configuration for agents
|
|
369
369
|
"""
|
|
370
370
|
self.default_config = default_config or AgentConfig()
|
|
371
371
|
self._agents: Dict[str, BaseAgent] = {}
|
|
372
372
|
self._semaphore: Optional[asyncio.Semaphore] = None
|
|
373
|
-
|
|
373
|
+
|
|
374
374
|
def register(self, agent: BaseAgent) -> None:
|
|
375
375
|
"""Register an agent.
|
|
376
|
-
|
|
376
|
+
|
|
377
377
|
Args:
|
|
378
378
|
agent: Agent to register
|
|
379
379
|
"""
|
|
380
380
|
self._agents[agent.name] = agent
|
|
381
|
-
|
|
381
|
+
|
|
382
382
|
def get_agent(self, name: str) -> Optional[BaseAgent]:
|
|
383
383
|
"""Get agent by name.
|
|
384
|
-
|
|
384
|
+
|
|
385
385
|
Args:
|
|
386
386
|
name: Agent name
|
|
387
|
-
|
|
387
|
+
|
|
388
388
|
Returns:
|
|
389
389
|
Agent instance or None
|
|
390
390
|
"""
|
|
391
391
|
return self._agents.get(name)
|
|
392
|
-
|
|
392
|
+
|
|
393
393
|
async def execute_single(
|
|
394
394
|
self,
|
|
395
395
|
agent_name: str,
|
|
@@ -398,13 +398,13 @@ class AgentOrchestrator:
|
|
|
398
398
|
**kwargs: Any,
|
|
399
399
|
) -> AgentResult:
|
|
400
400
|
"""Execute single agent.
|
|
401
|
-
|
|
401
|
+
|
|
402
402
|
Args:
|
|
403
403
|
agent_name: Name of agent to use
|
|
404
404
|
prompt: The prompt
|
|
405
405
|
context: Execution context
|
|
406
406
|
**kwargs: Additional parameters
|
|
407
|
-
|
|
407
|
+
|
|
408
408
|
Returns:
|
|
409
409
|
Execution result
|
|
410
410
|
"""
|
|
@@ -414,25 +414,25 @@ class AgentOrchestrator:
|
|
|
414
414
|
success=False,
|
|
415
415
|
error=f"Agent '{agent_name}' not found",
|
|
416
416
|
)
|
|
417
|
-
|
|
417
|
+
|
|
418
418
|
return await agent.execute(prompt, context, **kwargs)
|
|
419
|
-
|
|
419
|
+
|
|
420
420
|
async def execute_parallel(
|
|
421
421
|
self,
|
|
422
422
|
tasks: List[Dict[str, Any]],
|
|
423
423
|
max_concurrent: int = 5,
|
|
424
424
|
) -> List[AgentResult]:
|
|
425
425
|
"""Execute multiple agents in parallel.
|
|
426
|
-
|
|
426
|
+
|
|
427
427
|
Args:
|
|
428
428
|
tasks: List of task definitions
|
|
429
429
|
max_concurrent: Maximum concurrent executions
|
|
430
|
-
|
|
430
|
+
|
|
431
431
|
Returns:
|
|
432
432
|
List of results
|
|
433
433
|
"""
|
|
434
434
|
self._semaphore = asyncio.Semaphore(max_concurrent)
|
|
435
|
-
|
|
435
|
+
|
|
436
436
|
async def run_with_semaphore(task: Dict[str, Any]) -> AgentResult:
|
|
437
437
|
async with self._semaphore:
|
|
438
438
|
return await self.execute_single(
|
|
@@ -441,12 +441,12 @@ class AgentOrchestrator:
|
|
|
441
441
|
task.get("context"),
|
|
442
442
|
**task.get("kwargs", {}),
|
|
443
443
|
)
|
|
444
|
-
|
|
444
|
+
|
|
445
445
|
return await asyncio.gather(
|
|
446
446
|
*[run_with_semaphore(task) for task in tasks],
|
|
447
447
|
return_exceptions=False,
|
|
448
448
|
)
|
|
449
|
-
|
|
449
|
+
|
|
450
450
|
async def execute_consensus(
|
|
451
451
|
self,
|
|
452
452
|
prompt: str,
|
|
@@ -454,58 +454,58 @@ class AgentOrchestrator:
|
|
|
454
454
|
threshold: float = 0.66,
|
|
455
455
|
) -> Dict[str, Any]:
|
|
456
456
|
"""Execute consensus operation with multiple agents.
|
|
457
|
-
|
|
457
|
+
|
|
458
458
|
Args:
|
|
459
459
|
prompt: The prompt
|
|
460
460
|
agents: List of agent names
|
|
461
461
|
threshold: Agreement threshold
|
|
462
|
-
|
|
462
|
+
|
|
463
463
|
Returns:
|
|
464
464
|
Consensus results
|
|
465
465
|
"""
|
|
466
466
|
# Execute all agents in parallel
|
|
467
467
|
tasks = [{"agent": agent, "prompt": prompt} for agent in agents]
|
|
468
468
|
results = await self.execute_parallel(tasks)
|
|
469
|
-
|
|
469
|
+
|
|
470
470
|
# Analyze consensus
|
|
471
471
|
successful = [r for r in results if r.success]
|
|
472
472
|
agreement = len(successful) / len(results) if results else 0
|
|
473
|
-
|
|
473
|
+
|
|
474
474
|
return {
|
|
475
475
|
"consensus_reached": agreement >= threshold,
|
|
476
476
|
"agreement_score": agreement,
|
|
477
477
|
"individual_results": results,
|
|
478
478
|
"agents_used": agents,
|
|
479
479
|
}
|
|
480
|
-
|
|
480
|
+
|
|
481
481
|
async def execute_chain(
|
|
482
482
|
self,
|
|
483
483
|
initial_prompt: str,
|
|
484
484
|
agents: List[str],
|
|
485
485
|
) -> List[AgentResult]:
|
|
486
486
|
"""Execute agents in a chain, passing output forward.
|
|
487
|
-
|
|
487
|
+
|
|
488
488
|
Args:
|
|
489
489
|
initial_prompt: Initial prompt
|
|
490
490
|
agents: List of agent names
|
|
491
|
-
|
|
491
|
+
|
|
492
492
|
Returns:
|
|
493
493
|
List of results from each step
|
|
494
494
|
"""
|
|
495
495
|
results = []
|
|
496
496
|
current_prompt = initial_prompt
|
|
497
|
-
|
|
497
|
+
|
|
498
498
|
for agent_name in agents:
|
|
499
499
|
result = await self.execute_single(agent_name, current_prompt)
|
|
500
500
|
results.append(result)
|
|
501
|
-
|
|
501
|
+
|
|
502
502
|
if result.success and result.output:
|
|
503
503
|
# Use output as input for next agent
|
|
504
504
|
current_prompt = f"Review and improve:\n{result.output}"
|
|
505
505
|
else:
|
|
506
506
|
# Chain broken
|
|
507
507
|
break
|
|
508
|
-
|
|
508
|
+
|
|
509
509
|
return results
|
|
510
510
|
|
|
511
511
|
|
|
@@ -517,4 +517,4 @@ __all__ = [
|
|
|
517
517
|
"CLIAgent",
|
|
518
518
|
"APIAgent",
|
|
519
519
|
"AgentOrchestrator",
|
|
520
|
-
]
|
|
520
|
+
]
|