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
|
@@ -1,577 +0,0 @@
|
|
|
1
|
-
"""Swarm tool implementation for parallel and hierarchical agent execution.
|
|
2
|
-
|
|
3
|
-
This module implements the SwarmTool that enables both parallel execution of multiple
|
|
4
|
-
agent instances and hierarchical workflows with specialized roles.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import os
|
|
8
|
-
import asyncio
|
|
9
|
-
from typing import (
|
|
10
|
-
Any,
|
|
11
|
-
Dict,
|
|
12
|
-
List,
|
|
13
|
-
Unpack,
|
|
14
|
-
Optional,
|
|
15
|
-
TypedDict,
|
|
16
|
-
final,
|
|
17
|
-
override,
|
|
18
|
-
)
|
|
19
|
-
|
|
20
|
-
from mcp.server import FastMCP
|
|
21
|
-
from mcp.server.fastmcp import Context as MCPContext
|
|
22
|
-
|
|
23
|
-
from hanzo_mcp.tools.common.base import BaseTool
|
|
24
|
-
from hanzo_mcp.tools.common.context import create_tool_context
|
|
25
|
-
from hanzo_mcp.tools.agent.agent_tool import AgentTool
|
|
26
|
-
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
class AgentNode(TypedDict):
|
|
30
|
-
"""Node in the agent network.
|
|
31
|
-
|
|
32
|
-
Attributes:
|
|
33
|
-
id: Unique identifier for this agent
|
|
34
|
-
query: The specific query/task for this agent
|
|
35
|
-
model: Optional model override (e.g., 'claude-3-5-sonnet', 'gpt-4o')
|
|
36
|
-
role: Optional role description (e.g., 'architect', 'frontend', 'reviewer')
|
|
37
|
-
connections: List of agent IDs this agent connects to (sends results to)
|
|
38
|
-
receives_from: Optional list of agent IDs this agent receives input from
|
|
39
|
-
file_path: Optional specific file for the agent to work on
|
|
40
|
-
"""
|
|
41
|
-
|
|
42
|
-
id: str
|
|
43
|
-
query: str
|
|
44
|
-
model: Optional[str]
|
|
45
|
-
role: Optional[str]
|
|
46
|
-
connections: Optional[List[str]]
|
|
47
|
-
receives_from: Optional[List[str]]
|
|
48
|
-
file_path: Optional[str]
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
class SwarmConfig(TypedDict):
|
|
52
|
-
"""Configuration for an agent network.
|
|
53
|
-
|
|
54
|
-
Attributes:
|
|
55
|
-
agents: Dictionary of agent configurations keyed by ID
|
|
56
|
-
entry_point: ID of the first agent to execute (optional, defaults to finding roots)
|
|
57
|
-
topology: Optional topology type (tree, dag, pipeline, star, mesh)
|
|
58
|
-
"""
|
|
59
|
-
|
|
60
|
-
agents: Dict[str, AgentNode]
|
|
61
|
-
entry_point: Optional[str]
|
|
62
|
-
topology: Optional[str]
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
class SwarmToolParams(TypedDict):
|
|
66
|
-
"""Parameters for the SwarmTool.
|
|
67
|
-
|
|
68
|
-
Attributes:
|
|
69
|
-
config: Agent network configuration
|
|
70
|
-
query: Initial query to send to entry point agent(s)
|
|
71
|
-
context: Optional context shared by all agents
|
|
72
|
-
max_concurrent: Maximum number of concurrent agents (default: 10)
|
|
73
|
-
"""
|
|
74
|
-
|
|
75
|
-
config: SwarmConfig
|
|
76
|
-
query: str
|
|
77
|
-
context: Optional[str]
|
|
78
|
-
max_concurrent: Optional[int]
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
@final
|
|
82
|
-
class SwarmTool(BaseTool):
|
|
83
|
-
"""Tool for executing multiple agent tasks in parallel.
|
|
84
|
-
|
|
85
|
-
The SwarmTool enables efficient parallel processing of multiple files or tasks
|
|
86
|
-
by spawning independent agent instances for each task.
|
|
87
|
-
"""
|
|
88
|
-
|
|
89
|
-
@property
|
|
90
|
-
@override
|
|
91
|
-
def name(self) -> str:
|
|
92
|
-
"""Get the tool name."""
|
|
93
|
-
return "swarm"
|
|
94
|
-
|
|
95
|
-
@property
|
|
96
|
-
@override
|
|
97
|
-
def description(self) -> str:
|
|
98
|
-
"""Get the tool description."""
|
|
99
|
-
return """Execute a network of AI agents with flexible connection topologies.
|
|
100
|
-
|
|
101
|
-
This tool enables sophisticated agent orchestration where agents can be connected
|
|
102
|
-
in various network patterns. Each agent can pass results to connected agents,
|
|
103
|
-
enabling complex workflows.
|
|
104
|
-
|
|
105
|
-
Features:
|
|
106
|
-
- Flexible agent networks (tree, DAG, pipeline, star, mesh)
|
|
107
|
-
- Each agent can use different models (Claude, GPT-4, Gemini, etc.)
|
|
108
|
-
- Agents automatically pass results to connected agents
|
|
109
|
-
- Parallel execution with dependency management
|
|
110
|
-
- Full editing capabilities for each agent
|
|
111
|
-
|
|
112
|
-
Common Topologies:
|
|
113
|
-
|
|
114
|
-
1. Tree (Architect pattern):
|
|
115
|
-
architect → [frontend, backend, database] → reviewer
|
|
116
|
-
|
|
117
|
-
2. Pipeline (Sequential processing):
|
|
118
|
-
analyzer → planner → implementer → tester → reviewer
|
|
119
|
-
|
|
120
|
-
3. Star (Central coordinator):
|
|
121
|
-
coordinator ← → [agent1, agent2, agent3, agent4]
|
|
122
|
-
|
|
123
|
-
4. DAG (Complex dependencies):
|
|
124
|
-
Multiple agents with custom connections
|
|
125
|
-
|
|
126
|
-
Usage Example:
|
|
127
|
-
|
|
128
|
-
swarm(
|
|
129
|
-
config={
|
|
130
|
-
"agents": {
|
|
131
|
-
"architect": {
|
|
132
|
-
"id": "architect",
|
|
133
|
-
"query": "Analyze codebase and create refactoring plan",
|
|
134
|
-
"model": "claude-3-5-sonnet",
|
|
135
|
-
"connections": ["frontend", "backend", "database"]
|
|
136
|
-
},
|
|
137
|
-
"frontend": {
|
|
138
|
-
"id": "frontend",
|
|
139
|
-
"query": "Refactor UI components based on architect's plan",
|
|
140
|
-
"role": "Frontend Developer",
|
|
141
|
-
"connections": ["reviewer"]
|
|
142
|
-
},
|
|
143
|
-
"backend": {
|
|
144
|
-
"id": "backend",
|
|
145
|
-
"query": "Refactor API endpoints based on architect's plan",
|
|
146
|
-
"role": "Backend Developer",
|
|
147
|
-
"connections": ["reviewer"]
|
|
148
|
-
},
|
|
149
|
-
"database": {
|
|
150
|
-
"id": "database",
|
|
151
|
-
"query": "Optimize database schema based on architect's plan",
|
|
152
|
-
"role": "Database Expert",
|
|
153
|
-
"connections": ["reviewer"]
|
|
154
|
-
},
|
|
155
|
-
"reviewer": {
|
|
156
|
-
"id": "reviewer",
|
|
157
|
-
"query": "Review all changes and ensure consistency",
|
|
158
|
-
"model": "gpt-4o",
|
|
159
|
-
"receives_from": ["frontend", "backend", "database"]
|
|
160
|
-
}
|
|
161
|
-
},
|
|
162
|
-
"entry_point": "architect"
|
|
163
|
-
},
|
|
164
|
-
query="Refactor the authentication system for better security and performance"
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
Models can be specified as:
|
|
168
|
-
- Full: 'anthropic/claude-3-5-sonnet-20241022'
|
|
169
|
-
- Short: 'claude-3-5-sonnet', 'gpt-4o', 'gemini-1.5-pro'
|
|
170
|
-
- CLI tools: 'claude_cli', 'codex_cli', 'gemini_cli', 'grok_cli'
|
|
171
|
-
"""
|
|
172
|
-
|
|
173
|
-
def __init__(
|
|
174
|
-
self,
|
|
175
|
-
permission_manager: PermissionManager,
|
|
176
|
-
model: str | None = None,
|
|
177
|
-
api_key: str | None = None,
|
|
178
|
-
base_url: str | None = None,
|
|
179
|
-
max_tokens: int | None = None,
|
|
180
|
-
agent_max_iterations: int = 10,
|
|
181
|
-
agent_max_tool_uses: int = 30,
|
|
182
|
-
):
|
|
183
|
-
"""Initialize the swarm tool.
|
|
184
|
-
|
|
185
|
-
Args:
|
|
186
|
-
permission_manager: Permission manager for access control
|
|
187
|
-
model: Optional model name override (defaults to Claude Sonnet)
|
|
188
|
-
api_key: Optional API key for the model provider
|
|
189
|
-
base_url: Optional base URL for the model provider
|
|
190
|
-
max_tokens: Optional maximum tokens for model responses
|
|
191
|
-
agent_max_iterations: Max iterations per agent (default: 10)
|
|
192
|
-
agent_max_tool_uses: Max tool uses per agent (default: 30)
|
|
193
|
-
"""
|
|
194
|
-
self.permission_manager = permission_manager
|
|
195
|
-
# Default to latest Claude Sonnet if no model specified
|
|
196
|
-
from hanzo_mcp.tools.agent.code_auth import get_latest_claude_model
|
|
197
|
-
|
|
198
|
-
self.model = model or f"anthropic/{get_latest_claude_model()}"
|
|
199
|
-
self.api_key = (
|
|
200
|
-
api_key
|
|
201
|
-
or os.environ.get("ANTHROPIC_API_KEY")
|
|
202
|
-
or os.environ.get("CLAUDE_API_KEY")
|
|
203
|
-
)
|
|
204
|
-
self.base_url = base_url
|
|
205
|
-
self.max_tokens = max_tokens
|
|
206
|
-
self.agent_max_iterations = agent_max_iterations
|
|
207
|
-
self.agent_max_tool_uses = agent_max_tool_uses
|
|
208
|
-
|
|
209
|
-
@override
|
|
210
|
-
async def call(
|
|
211
|
-
self,
|
|
212
|
-
ctx: MCPContext,
|
|
213
|
-
**params: Unpack[SwarmToolParams],
|
|
214
|
-
) -> str:
|
|
215
|
-
"""Execute the swarm tool.
|
|
216
|
-
|
|
217
|
-
Args:
|
|
218
|
-
ctx: MCP context
|
|
219
|
-
**params: Tool parameters
|
|
220
|
-
|
|
221
|
-
Returns:
|
|
222
|
-
Combined results from all agents
|
|
223
|
-
"""
|
|
224
|
-
tool_ctx = create_tool_context(ctx)
|
|
225
|
-
await tool_ctx.set_tool_info(self.name)
|
|
226
|
-
|
|
227
|
-
# Extract parameters
|
|
228
|
-
agents = params.get("agents", [])
|
|
229
|
-
manager_query = params.get("manager_query")
|
|
230
|
-
reviewer_query = params.get("reviewer_query")
|
|
231
|
-
common_context = params.get("common_context", "")
|
|
232
|
-
max_concurrent = params.get("max_concurrent", 10)
|
|
233
|
-
|
|
234
|
-
if not agents:
|
|
235
|
-
await tool_ctx.error("No agents provided")
|
|
236
|
-
return "Error: At least one agent must be provided."
|
|
237
|
-
|
|
238
|
-
# Extract parameters
|
|
239
|
-
config = params.get("config", {})
|
|
240
|
-
initial_query = params.get("query", "")
|
|
241
|
-
context = params.get("context", "")
|
|
242
|
-
|
|
243
|
-
agents_config = config.get("agents", {})
|
|
244
|
-
entry_point = config.get("entry_point")
|
|
245
|
-
|
|
246
|
-
await tool_ctx.info(
|
|
247
|
-
f"Starting swarm execution with {len(agents_config)} agents"
|
|
248
|
-
)
|
|
249
|
-
|
|
250
|
-
# Build agent network
|
|
251
|
-
agent_instances = {}
|
|
252
|
-
agent_results = {}
|
|
253
|
-
execution_queue = asyncio.Queue()
|
|
254
|
-
completed_agents = set()
|
|
255
|
-
|
|
256
|
-
# Create agent instances
|
|
257
|
-
for agent_id, agent_config in agents_config.items():
|
|
258
|
-
model = agent_config.get("model", self.model)
|
|
259
|
-
|
|
260
|
-
# Support CLI tools
|
|
261
|
-
cli_tools = {
|
|
262
|
-
"claude_cli": self._get_cli_tool("claude_cli"),
|
|
263
|
-
"codex_cli": self._get_cli_tool("codex_cli"),
|
|
264
|
-
"gemini_cli": self._get_cli_tool("gemini_cli"),
|
|
265
|
-
"grok_cli": self._get_cli_tool("grok_cli"),
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
if model in cli_tools:
|
|
269
|
-
agent = cli_tools[model]
|
|
270
|
-
else:
|
|
271
|
-
# Regular agent with model
|
|
272
|
-
agent = AgentTool(
|
|
273
|
-
permission_manager=self.permission_manager,
|
|
274
|
-
model=self._normalize_model(model),
|
|
275
|
-
api_key=self.api_key,
|
|
276
|
-
base_url=self.base_url,
|
|
277
|
-
max_tokens=self.max_tokens,
|
|
278
|
-
max_iterations=self.agent_max_iterations,
|
|
279
|
-
max_tool_uses=self.agent_max_tool_uses,
|
|
280
|
-
)
|
|
281
|
-
|
|
282
|
-
agent_instances[agent_id] = agent
|
|
283
|
-
|
|
284
|
-
# Find entry points (agents with no incoming connections)
|
|
285
|
-
if entry_point:
|
|
286
|
-
await execution_queue.put((entry_point, initial_query, {}))
|
|
287
|
-
else:
|
|
288
|
-
# Find root agents (no receives_from)
|
|
289
|
-
roots = []
|
|
290
|
-
for agent_id, agent_config in agents_config.items():
|
|
291
|
-
if not agent_config.get("receives_from"):
|
|
292
|
-
# Check if any other agent connects to this one
|
|
293
|
-
has_incoming = False
|
|
294
|
-
for other_config in agents_config.values():
|
|
295
|
-
if (
|
|
296
|
-
other_config.get("connections")
|
|
297
|
-
and agent_id in other_config["connections"]
|
|
298
|
-
):
|
|
299
|
-
has_incoming = True
|
|
300
|
-
break
|
|
301
|
-
if not has_incoming:
|
|
302
|
-
roots.append(agent_id)
|
|
303
|
-
|
|
304
|
-
if not roots:
|
|
305
|
-
await tool_ctx.error("No entry point found in agent network")
|
|
306
|
-
return "Error: Could not determine entry point for agent network"
|
|
307
|
-
|
|
308
|
-
for root in roots:
|
|
309
|
-
await execution_queue.put((root, initial_query, {}))
|
|
310
|
-
|
|
311
|
-
# Execute agents in network order
|
|
312
|
-
async def execute_agent(
|
|
313
|
-
agent_id: str, query: str, inputs: Dict[str, str]
|
|
314
|
-
) -> str:
|
|
315
|
-
"""Execute a single agent in the network."""
|
|
316
|
-
async with semaphore:
|
|
317
|
-
try:
|
|
318
|
-
agent_config = agents_config[agent_id]
|
|
319
|
-
agent = agent_instances[agent_id]
|
|
320
|
-
|
|
321
|
-
await tool_ctx.info(
|
|
322
|
-
f"Executing agent: {agent_id} ({agent_config.get('role', 'Agent')})"
|
|
323
|
-
)
|
|
324
|
-
|
|
325
|
-
# Build prompt with context and inputs
|
|
326
|
-
prompt_parts = []
|
|
327
|
-
|
|
328
|
-
# Add role context
|
|
329
|
-
if agent_config.get("role"):
|
|
330
|
-
prompt_parts.append(f"Your role: {agent_config['role']}")
|
|
331
|
-
|
|
332
|
-
# Add shared context
|
|
333
|
-
if context:
|
|
334
|
-
prompt_parts.append(f"Context:\n{context}")
|
|
335
|
-
|
|
336
|
-
# Add inputs from connected agents
|
|
337
|
-
if inputs:
|
|
338
|
-
prompt_parts.append("Input from previous agents:")
|
|
339
|
-
for input_agent, input_result in inputs.items():
|
|
340
|
-
prompt_parts.append(
|
|
341
|
-
f"\n--- From {input_agent} ---\n{input_result}"
|
|
342
|
-
)
|
|
343
|
-
|
|
344
|
-
# Add file context if specified
|
|
345
|
-
if agent_config.get("file_path"):
|
|
346
|
-
prompt_parts.append(
|
|
347
|
-
f"\nFile to work on: {agent_config['file_path']}"
|
|
348
|
-
)
|
|
349
|
-
|
|
350
|
-
# Add the main query
|
|
351
|
-
prompt_parts.append(f"\nTask: {agent_config['query']}")
|
|
352
|
-
|
|
353
|
-
# Combine query with initial query if this is entry point
|
|
354
|
-
if query and query != agent_config["query"]:
|
|
355
|
-
prompt_parts.append(f"\nMain objective: {query}")
|
|
356
|
-
|
|
357
|
-
full_prompt = "\n\n".join(prompt_parts)
|
|
358
|
-
|
|
359
|
-
# Execute the agent
|
|
360
|
-
result = await agent.call(ctx, prompts=full_prompt)
|
|
361
|
-
|
|
362
|
-
await tool_ctx.info(f"Agent {agent_id} completed")
|
|
363
|
-
return result
|
|
364
|
-
|
|
365
|
-
except Exception as e:
|
|
366
|
-
error_msg = f"Agent {agent_id} failed: {str(e)}"
|
|
367
|
-
await tool_ctx.error(error_msg)
|
|
368
|
-
return f"Error: {error_msg}"
|
|
369
|
-
|
|
370
|
-
# Process agent network
|
|
371
|
-
running_tasks = set()
|
|
372
|
-
|
|
373
|
-
while not execution_queue.empty() or running_tasks:
|
|
374
|
-
# Start new tasks up to concurrency limit
|
|
375
|
-
while not execution_queue.empty() and len(running_tasks) < max_concurrent:
|
|
376
|
-
agent_id, query, inputs = await execution_queue.get()
|
|
377
|
-
|
|
378
|
-
if agent_id not in completed_agents:
|
|
379
|
-
# Check if all dependencies are met
|
|
380
|
-
agent_config = agents_config[agent_id]
|
|
381
|
-
receives_from = agent_config.get("receives_from", [])
|
|
382
|
-
|
|
383
|
-
# Collect inputs from dependencies
|
|
384
|
-
ready = True
|
|
385
|
-
for dep in receives_from:
|
|
386
|
-
if dep not in agent_results:
|
|
387
|
-
ready = False
|
|
388
|
-
# Re-queue for later
|
|
389
|
-
await execution_queue.put((agent_id, query, inputs))
|
|
390
|
-
break
|
|
391
|
-
else:
|
|
392
|
-
inputs[dep] = agent_results[dep]
|
|
393
|
-
|
|
394
|
-
if ready:
|
|
395
|
-
# Execute agent
|
|
396
|
-
task = asyncio.create_task(
|
|
397
|
-
execute_agent(agent_id, query, inputs)
|
|
398
|
-
)
|
|
399
|
-
running_tasks.add(task)
|
|
400
|
-
|
|
401
|
-
async def handle_completion(task, agent_id=agent_id):
|
|
402
|
-
result = await task
|
|
403
|
-
agent_results[agent_id] = result
|
|
404
|
-
completed_agents.add(agent_id)
|
|
405
|
-
running_tasks.discard(task)
|
|
406
|
-
|
|
407
|
-
# Queue connected agents
|
|
408
|
-
agent_config = agents_config[agent_id]
|
|
409
|
-
connections = agent_config.get("connections", [])
|
|
410
|
-
for next_agent in connections:
|
|
411
|
-
if next_agent in agents_config:
|
|
412
|
-
await execution_queue.put(
|
|
413
|
-
(next_agent, "", {agent_id: result})
|
|
414
|
-
)
|
|
415
|
-
|
|
416
|
-
asyncio.create_task(handle_completion(task))
|
|
417
|
-
|
|
418
|
-
# Wait a bit if we're at capacity
|
|
419
|
-
if running_tasks:
|
|
420
|
-
await asyncio.sleep(0.1)
|
|
421
|
-
|
|
422
|
-
# Wait for all tasks to complete
|
|
423
|
-
if running_tasks:
|
|
424
|
-
await asyncio.gather(*running_tasks, return_exceptions=True)
|
|
425
|
-
|
|
426
|
-
# Format results
|
|
427
|
-
return self._format_network_results(agents_config, agent_results, entry_point)
|
|
428
|
-
|
|
429
|
-
def _normalize_model(self, model: str) -> str:
|
|
430
|
-
"""Normalize model names to full format."""
|
|
431
|
-
model_map = {
|
|
432
|
-
"claude-3-5-sonnet": "anthropic/claude-3-5-sonnet-20241022",
|
|
433
|
-
"claude-3-opus": "anthropic/claude-3-opus-20240229",
|
|
434
|
-
"gpt-4o": "openai/gpt-4o",
|
|
435
|
-
"gpt-4": "openai/gpt-4",
|
|
436
|
-
"gemini-1.5-pro": "google/gemini-1.5-pro",
|
|
437
|
-
"gemini-1.5-flash": "google/gemini-1.5-flash",
|
|
438
|
-
}
|
|
439
|
-
return model_map.get(model, model)
|
|
440
|
-
|
|
441
|
-
def _get_cli_tool(self, tool_name: str):
|
|
442
|
-
"""Get CLI tool instance."""
|
|
443
|
-
# Import here to avoid circular imports
|
|
444
|
-
if tool_name == "claude_cli":
|
|
445
|
-
from hanzo_mcp.tools.agent.claude_cli_tool import ClaudeCLITool
|
|
446
|
-
|
|
447
|
-
return ClaudeCLITool(self.permission_manager)
|
|
448
|
-
elif tool_name == "codex_cli":
|
|
449
|
-
from hanzo_mcp.tools.agent.codex_cli_tool import CodexCLITool
|
|
450
|
-
|
|
451
|
-
return CodexCLITool(self.permission_manager)
|
|
452
|
-
elif tool_name == "gemini_cli":
|
|
453
|
-
from hanzo_mcp.tools.agent.gemini_cli_tool import GeminiCLITool
|
|
454
|
-
|
|
455
|
-
return GeminiCLITool(self.permission_manager)
|
|
456
|
-
elif tool_name == "grok_cli":
|
|
457
|
-
from hanzo_mcp.tools.agent.grok_cli_tool import GrokCLITool
|
|
458
|
-
|
|
459
|
-
return GrokCLITool(self.permission_manager)
|
|
460
|
-
return None
|
|
461
|
-
|
|
462
|
-
def _format_network_results(
|
|
463
|
-
self,
|
|
464
|
-
agents_config: Dict[str, Any],
|
|
465
|
-
results: Dict[str, str],
|
|
466
|
-
entry_point: Optional[str],
|
|
467
|
-
) -> str:
|
|
468
|
-
"""Format results from agent network execution."""
|
|
469
|
-
output = ["Agent Network Execution Results"]
|
|
470
|
-
output.append("=" * 80)
|
|
471
|
-
output.append(f"Total agents: {len(agents_config)}")
|
|
472
|
-
output.append(f"Completed: {len(results)}")
|
|
473
|
-
output.append(
|
|
474
|
-
f"Failed: {len([r for r in results.values() if r.startswith('Error:')])}"
|
|
475
|
-
)
|
|
476
|
-
|
|
477
|
-
if entry_point:
|
|
478
|
-
output.append(f"Entry point: {entry_point}")
|
|
479
|
-
|
|
480
|
-
output.append("\nExecution Flow:")
|
|
481
|
-
output.append("-" * 40)
|
|
482
|
-
|
|
483
|
-
# Show results in execution order
|
|
484
|
-
def format_agent_tree(agent_id: str, level: int = 0) -> List[str]:
|
|
485
|
-
lines = []
|
|
486
|
-
indent = " " * level
|
|
487
|
-
|
|
488
|
-
if agent_id in agents_config:
|
|
489
|
-
config = agents_config[agent_id]
|
|
490
|
-
role = config.get("role", "Agent")
|
|
491
|
-
model = config.get("model", "default")
|
|
492
|
-
|
|
493
|
-
status = (
|
|
494
|
-
"✅"
|
|
495
|
-
if agent_id in results
|
|
496
|
-
and not results[agent_id].startswith("Error:")
|
|
497
|
-
else "❌"
|
|
498
|
-
)
|
|
499
|
-
lines.append(f"{indent}{status} {agent_id} ({role}) [{model}]")
|
|
500
|
-
|
|
501
|
-
# Show connections
|
|
502
|
-
connections = config.get("connections", [])
|
|
503
|
-
for conn in connections:
|
|
504
|
-
if conn in agents_config:
|
|
505
|
-
lines.extend(format_agent_tree(conn, level + 1))
|
|
506
|
-
|
|
507
|
-
return lines
|
|
508
|
-
|
|
509
|
-
# Start from entry point or roots
|
|
510
|
-
if entry_point:
|
|
511
|
-
output.extend(format_agent_tree(entry_point))
|
|
512
|
-
else:
|
|
513
|
-
# Find roots
|
|
514
|
-
roots = []
|
|
515
|
-
for agent_id in agents_config:
|
|
516
|
-
has_incoming = False
|
|
517
|
-
for config in agents_config.values():
|
|
518
|
-
if config.get("connections") and agent_id in config["connections"]:
|
|
519
|
-
has_incoming = True
|
|
520
|
-
break
|
|
521
|
-
if not has_incoming:
|
|
522
|
-
roots.append(agent_id)
|
|
523
|
-
|
|
524
|
-
for root in roots:
|
|
525
|
-
output.extend(format_agent_tree(root))
|
|
526
|
-
|
|
527
|
-
# Detailed results
|
|
528
|
-
output.append("\n\nDetailed Results:")
|
|
529
|
-
output.append("=" * 80)
|
|
530
|
-
|
|
531
|
-
for agent_id, result in results.items():
|
|
532
|
-
config = agents_config.get(agent_id, {})
|
|
533
|
-
role = config.get("role", "Agent")
|
|
534
|
-
|
|
535
|
-
output.append(f"\n### {agent_id} ({role})")
|
|
536
|
-
output.append("-" * 40)
|
|
537
|
-
|
|
538
|
-
if result.startswith("Error:"):
|
|
539
|
-
output.append(result)
|
|
540
|
-
else:
|
|
541
|
-
# Show first part of result
|
|
542
|
-
lines = result.split("\n")
|
|
543
|
-
preview_lines = lines[:10]
|
|
544
|
-
output.extend(preview_lines)
|
|
545
|
-
|
|
546
|
-
if len(lines) > 10:
|
|
547
|
-
output.append(f"... ({len(lines) - 10} more lines)")
|
|
548
|
-
|
|
549
|
-
return "\n".join(output)
|
|
550
|
-
|
|
551
|
-
@override
|
|
552
|
-
def register(self, mcp_server: FastMCP) -> None:
|
|
553
|
-
"""Register this swarm tool with the MCP server."""
|
|
554
|
-
tool_self = self
|
|
555
|
-
|
|
556
|
-
@mcp_server.tool(name=self.name, description=self.description)
|
|
557
|
-
async def swarm(
|
|
558
|
-
ctx: MCPContext,
|
|
559
|
-
config: dict[str, Any],
|
|
560
|
-
query: str,
|
|
561
|
-
context: Optional[str] = None,
|
|
562
|
-
max_concurrent: int = 10,
|
|
563
|
-
) -> str:
|
|
564
|
-
# Convert to typed format
|
|
565
|
-
typed_config = SwarmConfig(
|
|
566
|
-
agents=config.get("agents", {}),
|
|
567
|
-
entry_point=config.get("entry_point"),
|
|
568
|
-
topology=config.get("topology"),
|
|
569
|
-
)
|
|
570
|
-
|
|
571
|
-
return await tool_self.call(
|
|
572
|
-
ctx,
|
|
573
|
-
config=typed_config,
|
|
574
|
-
query=query,
|
|
575
|
-
context=context,
|
|
576
|
-
max_concurrent=max_concurrent,
|
|
577
|
-
)
|