hanuscode 1.0.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.
- hanus/__init__.py +5 -0
- hanus/__main__.py +10 -0
- hanus/action_handlers.py +76 -0
- hanus/action_parser.py +82 -0
- hanus/agent_runner.py +1445 -0
- hanus/analysis/__init__.py +5 -0
- hanus/analysis/debt.py +702 -0
- hanus/analysis/dependencies.py +475 -0
- hanus/cache/__init__.py +5 -0
- hanus/cache/response_cache.py +560 -0
- hanus/config.py +401 -0
- hanus/connectors/__init__.py +19 -0
- hanus/connectors/base.py +114 -0
- hanus/connectors/claude_connector.py +146 -0
- hanus/connectors/gemini_connector.py +141 -0
- hanus/connectors/glm_connector.py +160 -0
- hanus/connectors/ollama_connector.py +174 -0
- hanus/connectors/openai_connector.py +122 -0
- hanus/connectors/registry.py +26 -0
- hanus/context/__init__.py +7 -0
- hanus/context/manager.py +837 -0
- hanus/context/selective.py +626 -0
- hanus/error_recovery/__init__.py +5 -0
- hanus/error_recovery/auto_fix.py +605 -0
- hanus/hooks/__init__.py +5 -0
- hanus/hooks/manager.py +247 -0
- hanus/instincts/__init__.py +44 -0
- hanus/instincts/cli.py +372 -0
- hanus/instincts/detector.py +281 -0
- hanus/instincts/evolver.py +361 -0
- hanus/instincts/manager.py +343 -0
- hanus/instincts/types.py +253 -0
- hanus/logger.py +81 -0
- hanus/memory/__init__.py +8 -0
- hanus/memory/manager.py +265 -0
- hanus/memory/types.py +119 -0
- hanus/monitor.py +341 -0
- hanus/parallel/__init__.py +5 -0
- hanus/parallel/executor.py +300 -0
- hanus/permissions.py +182 -0
- hanus/plan/__init__.py +8 -0
- hanus/plan/mode.py +267 -0
- hanus/plan/models.py +152 -0
- hanus/plugin_manager.py +754 -0
- hanus/plugin_registry.py +391 -0
- hanus/plugins/__init__.py +1 -0
- hanus/plugins/arena.py +630 -0
- hanus/plugins/code_review.py +123 -0
- hanus/plugins/cortex.py +1750 -0
- hanus/plugins/deps_check.py +27 -0
- hanus/plugins/git_ops.py +33 -0
- hanus/plugins/metasploit.py +530 -0
- hanus/plugins/notes.py +583 -0
- hanus/plugins/search_code.py +59 -0
- hanus/plugins/searchsploit.py +495 -0
- hanus/plugins/strategist.py +175 -0
- hanus/plugins/webui.py +5200 -0
- hanus/profiles.py +479 -0
- hanus/profiles_builtin/__init__.py +0 -0
- hanus/profiles_builtin/architect/profile.yaml +12 -0
- hanus/profiles_builtin/architect/system_prompt.txt +71 -0
- hanus/profiles_builtin/deep/profile.yaml +12 -0
- hanus/profiles_builtin/deep/system_prompt.txt +66 -0
- hanus/profiles_builtin/developer/__init__.py +0 -0
- hanus/profiles_builtin/developer/profile.yaml +9 -0
- hanus/profiles_builtin/developer/system_prompt.txt +176 -0
- hanus/profiles_builtin/speed/profile.yaml +12 -0
- hanus/profiles_builtin/speed/system_prompt.txt +51 -0
- hanus/project_tools.py +177 -0
- hanus/query_engine.py +1594 -0
- hanus/rules/__init__.py +237 -0
- hanus/search/__init__.py +5 -0
- hanus/search/semantic.py +596 -0
- hanus/session_manager.py +547 -0
- hanus/skill_manager.py +702 -0
- hanus/skills/__init__.py +4 -0
- hanus/subagent/__init__.py +8 -0
- hanus/subagent/agents/__init__.py +253 -0
- hanus/subagent/manager.py +309 -0
- hanus/subagent/types.py +266 -0
- hanus/suggestions/__init__.py +5 -0
- hanus/suggestions/proactive.py +451 -0
- hanus/tasks/__init__.py +8 -0
- hanus/tasks/manager.py +330 -0
- hanus/tasks/models.py +106 -0
- hanus/terminal_prompt.py +166 -0
- hanus/tools.py +1849 -0
- hanus/ui.py +939 -0
- hanuscode-1.0.0.dist-info/METADATA +1151 -0
- hanuscode-1.0.0.dist-info/RECORD +93 -0
- hanuscode-1.0.0.dist-info/WHEEL +5 -0
- hanuscode-1.0.0.dist-info/entry_points.txt +2 -0
- hanuscode-1.0.0.dist-info/top_level.txt +1 -0
hanus/skills/__init__.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# hanus/subagent/__init__.py
|
|
2
|
+
"""
|
|
3
|
+
Sistema de subagentes para delegar tareas complejas.
|
|
4
|
+
"""
|
|
5
|
+
from hanus.subagent.types import SubagentType, SubagentConfig, SubagentResult
|
|
6
|
+
from hanus.subagent.manager import SubagentManager
|
|
7
|
+
|
|
8
|
+
__all__ = ["SubagentType", "SubagentConfig", "SubagentResult", "SubagentManager"]
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# hanus/subagent/agents/__init__.py
|
|
2
|
+
"""
|
|
3
|
+
Agent definitions loader and registry.
|
|
4
|
+
|
|
5
|
+
Carga definiciones de agentes desde archivos .md y las registra
|
|
6
|
+
para uso del SubagentManager.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
import re
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from typing import Dict, List, Optional, Any
|
|
13
|
+
from enum import Enum
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AgentCategory(Enum):
|
|
17
|
+
"""Categorías de agentes."""
|
|
18
|
+
ARCHITECTURE = "architecture"
|
|
19
|
+
REVIEWERS = "reviewers"
|
|
20
|
+
BUILD_RESOLVERS = "build_resolvers"
|
|
21
|
+
SPECIALIZED = "specialized"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class AgentConfig:
|
|
26
|
+
"""Configuración de un agente."""
|
|
27
|
+
name: str
|
|
28
|
+
description: str
|
|
29
|
+
model: str = "sonnet"
|
|
30
|
+
tools: List[str] = field(default_factory=list)
|
|
31
|
+
prompt_defense: str = ""
|
|
32
|
+
role: str = ""
|
|
33
|
+
responsibilities: List[str] = field(default_factory=list)
|
|
34
|
+
checklist: Dict[str, List[str]] = field(default_factory=dict)
|
|
35
|
+
patterns: Dict[str, Any] = field(default_factory=dict)
|
|
36
|
+
best_practices: List[str] = field(default_factory=list)
|
|
37
|
+
tools_list: List[str] = field(default_factory=list)
|
|
38
|
+
category: AgentCategory = AgentCategory.SPECIALIZED
|
|
39
|
+
raw_content: str = ""
|
|
40
|
+
|
|
41
|
+
def get_system_prompt(self) -> str:
|
|
42
|
+
"""Genera el system prompt para este agente."""
|
|
43
|
+
parts = []
|
|
44
|
+
|
|
45
|
+
# Prompt defense
|
|
46
|
+
if self.prompt_defense:
|
|
47
|
+
parts.append("## Security Guidelines\n")
|
|
48
|
+
parts.append(self.prompt_defense)
|
|
49
|
+
parts.append("\n")
|
|
50
|
+
|
|
51
|
+
# Role
|
|
52
|
+
if self.role:
|
|
53
|
+
parts.append("## Role\n")
|
|
54
|
+
parts.append(self.role)
|
|
55
|
+
parts.append("\n")
|
|
56
|
+
|
|
57
|
+
# Responsibilities
|
|
58
|
+
if self.responsibilities:
|
|
59
|
+
parts.append("## Responsibilities\n")
|
|
60
|
+
for resp in self.responsibilities:
|
|
61
|
+
parts.append(f"- {resp}")
|
|
62
|
+
parts.append("\n")
|
|
63
|
+
|
|
64
|
+
# Checklist
|
|
65
|
+
if self.checklist:
|
|
66
|
+
parts.append("## Review Checklist\n")
|
|
67
|
+
for severity, items in self.checklist.items():
|
|
68
|
+
parts.append(f"\n### {severity.upper()}\n")
|
|
69
|
+
for item in items:
|
|
70
|
+
parts.append(f"- {item}")
|
|
71
|
+
parts.append("\n")
|
|
72
|
+
|
|
73
|
+
# Best practices
|
|
74
|
+
if self.best_practices:
|
|
75
|
+
parts.append("## Best Practices\n")
|
|
76
|
+
for bp in self.best_practices:
|
|
77
|
+
parts.append(f"- {bp}")
|
|
78
|
+
parts.append("\n")
|
|
79
|
+
|
|
80
|
+
return "\n".join(parts)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class AgentLoader:
|
|
84
|
+
"""Cargador de definiciones de agentes."""
|
|
85
|
+
|
|
86
|
+
def __init__(self, agents_dir: Path = None):
|
|
87
|
+
self.agents_dir = agents_dir or Path(__file__).parent
|
|
88
|
+
self._agents: Dict[str, AgentConfig] = {}
|
|
89
|
+
self._loaded = False
|
|
90
|
+
|
|
91
|
+
def load_all(self, force: bool = False) -> Dict[str, AgentConfig]:
|
|
92
|
+
"""Carga todos los agentes desde los archivos .md."""
|
|
93
|
+
if self._loaded and not force:
|
|
94
|
+
return self._agents
|
|
95
|
+
|
|
96
|
+
self._agents.clear()
|
|
97
|
+
|
|
98
|
+
# Cargar desde cada subdirectorio
|
|
99
|
+
for category in AgentCategory:
|
|
100
|
+
category_dir = self.agents_dir / category.value
|
|
101
|
+
if category_dir.exists():
|
|
102
|
+
for agent_file in category_dir.glob("*.md"):
|
|
103
|
+
agent = self._load_agent(agent_file, category)
|
|
104
|
+
if agent:
|
|
105
|
+
self._agents[agent.name] = agent
|
|
106
|
+
|
|
107
|
+
self._loaded = True
|
|
108
|
+
return self._agents
|
|
109
|
+
|
|
110
|
+
def _load_agent(self, file_path: Path, category: AgentCategory) -> Optional[AgentConfig]:
|
|
111
|
+
"""Carga un agente desde un archivo .md."""
|
|
112
|
+
try:
|
|
113
|
+
content = file_path.read_text(encoding="utf-8")
|
|
114
|
+
|
|
115
|
+
# Parsear frontmatter y contenido
|
|
116
|
+
config = self._parse_agent_file(content, category)
|
|
117
|
+
if config:
|
|
118
|
+
config.raw_content = content
|
|
119
|
+
|
|
120
|
+
return config
|
|
121
|
+
except Exception as e:
|
|
122
|
+
print(f"[AgentLoader] Error loading {file_path}: {e}")
|
|
123
|
+
return None
|
|
124
|
+
|
|
125
|
+
def _parse_agent_file(self, content: str, category: AgentCategory) -> Optional[AgentConfig]:
|
|
126
|
+
"""Parsea el contenido de un archivo de agente."""
|
|
127
|
+
lines = content.split("\n")
|
|
128
|
+
|
|
129
|
+
# Extraer campos básicos
|
|
130
|
+
data = {
|
|
131
|
+
"name": "",
|
|
132
|
+
"description": "",
|
|
133
|
+
"model": "sonnet",
|
|
134
|
+
"tools": [],
|
|
135
|
+
"prompt_defense": "",
|
|
136
|
+
"role": "",
|
|
137
|
+
"category": category,
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
current_section = None
|
|
141
|
+
section_content = []
|
|
142
|
+
|
|
143
|
+
for line in lines:
|
|
144
|
+
# Detectar campos simples
|
|
145
|
+
if line.startswith("name:"):
|
|
146
|
+
data["name"] = line.split(":", 1)[1].strip()
|
|
147
|
+
elif line.startswith("description:"):
|
|
148
|
+
data["description"] = line.split(":", 1)[1].strip()
|
|
149
|
+
elif line.startswith("model:"):
|
|
150
|
+
data["model"] = line.split(":", 1)[1].strip()
|
|
151
|
+
elif line.startswith("tools:"):
|
|
152
|
+
# Lista de herramientas
|
|
153
|
+
pass # Se procesa después
|
|
154
|
+
elif line.startswith("prompt_defense:"):
|
|
155
|
+
current_section = "prompt_defense"
|
|
156
|
+
section_content = []
|
|
157
|
+
elif line.startswith("role:"):
|
|
158
|
+
current_section = "role"
|
|
159
|
+
section_content = []
|
|
160
|
+
elif line.startswith("responsibilities:"):
|
|
161
|
+
current_section = "responsibilities"
|
|
162
|
+
section_content = []
|
|
163
|
+
elif line.startswith("checklist:"):
|
|
164
|
+
current_section = "checklist"
|
|
165
|
+
section_content = []
|
|
166
|
+
elif line.startswith("tools:"):
|
|
167
|
+
# Ignorar, se parsea después
|
|
168
|
+
pass
|
|
169
|
+
elif line.startswith(" - "):
|
|
170
|
+
# Item de lista
|
|
171
|
+
if current_section:
|
|
172
|
+
section_content.append(line.strip(" - "))
|
|
173
|
+
elif line.strip() and current_section:
|
|
174
|
+
section_content.append(line)
|
|
175
|
+
elif not line.strip():
|
|
176
|
+
# Línea vacía, guardar sección
|
|
177
|
+
if current_section and section_content:
|
|
178
|
+
data[current_section] = "\n".join(section_content)
|
|
179
|
+
section_content = []
|
|
180
|
+
|
|
181
|
+
# Guardar última sección
|
|
182
|
+
if current_section and section_content:
|
|
183
|
+
data[current_section] = "\n".join(section_content)
|
|
184
|
+
|
|
185
|
+
# Parsear tools desde el contenido
|
|
186
|
+
tools_match = re.search(r"tools:\s*\n((?: - .+\n)+)", content)
|
|
187
|
+
if tools_match:
|
|
188
|
+
tools_text = tools_match.group(1)
|
|
189
|
+
data["tools"] = [
|
|
190
|
+
t.strip("- ") for t in tools_text.strip().split("\n")
|
|
191
|
+
if t.strip().startswith("-")
|
|
192
|
+
]
|
|
193
|
+
|
|
194
|
+
if not data["name"]:
|
|
195
|
+
return None
|
|
196
|
+
|
|
197
|
+
return AgentConfig(
|
|
198
|
+
name=data["name"],
|
|
199
|
+
description=data.get("description", ""),
|
|
200
|
+
model=data.get("model", "sonnet"),
|
|
201
|
+
tools=data.get("tools", []),
|
|
202
|
+
prompt_defense=data.get("prompt_defense", ""),
|
|
203
|
+
role=data.get("role", ""),
|
|
204
|
+
category=data.get("category", AgentCategory.SPECIALIZED),
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
def get_agent(self, name: str) -> Optional[AgentConfig]:
|
|
208
|
+
"""Obtiene un agente por nombre."""
|
|
209
|
+
if not self._loaded:
|
|
210
|
+
self.load_all()
|
|
211
|
+
return self._agents.get(name)
|
|
212
|
+
|
|
213
|
+
def list_agents(self, category: AgentCategory = None) -> List[AgentConfig]:
|
|
214
|
+
"""Lista todos los agentes, opcionalmente filtrados por categoría."""
|
|
215
|
+
if not self._loaded:
|
|
216
|
+
self.load_all()
|
|
217
|
+
|
|
218
|
+
agents = list(self._agents.values())
|
|
219
|
+
if category:
|
|
220
|
+
agents = [a for a in agents if a.category == category]
|
|
221
|
+
return agents
|
|
222
|
+
|
|
223
|
+
def get_agents_by_tool(self, tool: str) -> List[AgentConfig]:
|
|
224
|
+
"""Obtiene agentes que tienen acceso a una herramienta específica."""
|
|
225
|
+
if not self._loaded:
|
|
226
|
+
self.load_all()
|
|
227
|
+
|
|
228
|
+
return [
|
|
229
|
+
agent for agent in self._agents.values()
|
|
230
|
+
if tool in agent.tools or not agent.tools # Sin restricción de tools
|
|
231
|
+
]
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
# Instancia global
|
|
235
|
+
_loader: Optional[AgentLoader] = None
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def get_agent_loader(agents_dir: Path = None) -> AgentLoader:
|
|
239
|
+
"""Obtiene el cargador de agentes."""
|
|
240
|
+
global _loader
|
|
241
|
+
if _loader is None:
|
|
242
|
+
_loader = AgentLoader(agents_dir)
|
|
243
|
+
return _loader
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def get_agent(name: str) -> Optional[AgentConfig]:
|
|
247
|
+
"""Obtiene un agente por nombre."""
|
|
248
|
+
return get_agent_loader().get_agent(name)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def list_all_agents() -> List[AgentConfig]:
|
|
252
|
+
"""Lista todos los agentes disponibles."""
|
|
253
|
+
return get_agent_loader().list_agents()
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# hanus/subagent/manager.py
|
|
2
|
+
"""
|
|
3
|
+
Gestor de subagentes para delegar tareas complejas.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
import uuid
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Dict, List, Optional, Any, Callable
|
|
9
|
+
|
|
10
|
+
from hanus.subagent.types import (
|
|
11
|
+
SubagentType, SubagentConfig, SubagentResult,
|
|
12
|
+
SUBAGENT_TOOLS, SUBAGENT_SYSTEM_PROMPTS,
|
|
13
|
+
get_subagent_type, get_tools_for_type, get_prompt_for_type
|
|
14
|
+
)
|
|
15
|
+
from hanus.subagent.agents import get_agent_loader, AgentConfig
|
|
16
|
+
|
|
17
|
+
import hanus.logger as log
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SubagentManager:
|
|
21
|
+
"""
|
|
22
|
+
Gestiona el ciclo de vida de subagentes.
|
|
23
|
+
|
|
24
|
+
Los subagentes son instancias especializadas del agente que pueden
|
|
25
|
+
ejecutarse en paralelo o en segundo plano para tareas complejas.
|
|
26
|
+
|
|
27
|
+
Uso:
|
|
28
|
+
manager = SubagentManager(connector, executor, session_mgr, perms, root_dir)
|
|
29
|
+
|
|
30
|
+
# Ejecutar subagente de exploración
|
|
31
|
+
result = manager.execute(SubagentConfig(
|
|
32
|
+
agent_type=SubagentType.EXPLORE,
|
|
33
|
+
task_description="Encuentra todos los endpoints de API",
|
|
34
|
+
context="El proyecto es una app Flask en src/",
|
|
35
|
+
))
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
connector,
|
|
41
|
+
tool_executor,
|
|
42
|
+
session_manager,
|
|
43
|
+
permission_manager,
|
|
44
|
+
root_dir: Path,
|
|
45
|
+
progress_callback: Optional[Callable[[str, str, Dict], None]] = None,
|
|
46
|
+
tool_callback: Optional[Callable[[str, Dict, str, bool], None]] = None,
|
|
47
|
+
):
|
|
48
|
+
self.connector = connector
|
|
49
|
+
self.executor = tool_executor
|
|
50
|
+
self.session = session_manager
|
|
51
|
+
self.perms = permission_manager
|
|
52
|
+
self.root_dir = root_dir
|
|
53
|
+
self.progress_callback = progress_callback # (agent_id, event, data)
|
|
54
|
+
self.tool_callback = tool_callback # (tool_name, args, result, success)
|
|
55
|
+
self._active_agents: Dict[str, Dict] = {}
|
|
56
|
+
|
|
57
|
+
def _emit_progress(self, agent_id: str, event: str, data: Dict = None):
|
|
58
|
+
"""Emite un evento de progreso."""
|
|
59
|
+
log.info(f"[Subagent {agent_id}] {event}: {data or {}}")
|
|
60
|
+
if self.progress_callback:
|
|
61
|
+
self.progress_callback(agent_id, event, data or {})
|
|
62
|
+
|
|
63
|
+
def spawn(self, config: SubagentConfig) -> str:
|
|
64
|
+
"""
|
|
65
|
+
Crea un nuevo subagente.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
config: Configuración del subagente
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
ID del subagente creado
|
|
72
|
+
"""
|
|
73
|
+
agent_id = str(uuid.uuid4())[:8]
|
|
74
|
+
self._active_agents[agent_id] = {
|
|
75
|
+
"config": config,
|
|
76
|
+
"status": "pending",
|
|
77
|
+
"created_at": __import__("time").time(),
|
|
78
|
+
}
|
|
79
|
+
return agent_id
|
|
80
|
+
|
|
81
|
+
def execute(self, config: SubagentConfig) -> SubagentResult:
|
|
82
|
+
"""
|
|
83
|
+
Ejecuta un subagente inmediatamente.
|
|
84
|
+
|
|
85
|
+
Este es un método de conveniencia que combina spawn + run.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
config: Configuración del subagente
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Resultado de la ejecución
|
|
92
|
+
"""
|
|
93
|
+
agent_id = self.spawn(config)
|
|
94
|
+
return self.run(agent_id)
|
|
95
|
+
|
|
96
|
+
def run(self, agent_id: str) -> SubagentResult:
|
|
97
|
+
"""
|
|
98
|
+
Ejecuta un subagente existente.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
agent_id: ID del subagente a ejecutar
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
Resultado de la ejecución
|
|
105
|
+
"""
|
|
106
|
+
if agent_id not in self._active_agents:
|
|
107
|
+
return SubagentResult(
|
|
108
|
+
success=False,
|
|
109
|
+
error=f"Subagente {agent_id} no encontrado",
|
|
110
|
+
agent_id=agent_id,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
agent_info = self._active_agents[agent_id]
|
|
114
|
+
config: SubagentConfig = agent_info["config"]
|
|
115
|
+
agent_info["status"] = "running"
|
|
116
|
+
|
|
117
|
+
# Emitir inicio
|
|
118
|
+
self._emit_progress(agent_id, "started", {
|
|
119
|
+
"type": config.agent_type.value,
|
|
120
|
+
"task": config.task_description[:100],
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
# Crear motor de consultas para el subagente
|
|
125
|
+
from hanus.query_engine import QueryEngine
|
|
126
|
+
|
|
127
|
+
# Filtrar herramientas según tipo
|
|
128
|
+
allowed_tools = SUBAGENT_TOOLS.get(config.agent_type)
|
|
129
|
+
|
|
130
|
+
# Crear un executor con herramientas filtradas y callback
|
|
131
|
+
filtered_executor = self._create_filtered_executor(allowed_tools, agent_id)
|
|
132
|
+
|
|
133
|
+
# Crear callback de streaming para mostrar progreso
|
|
134
|
+
def stream_callback(text: str):
|
|
135
|
+
self._emit_progress(agent_id, "stream", {"text": text[:200]})
|
|
136
|
+
|
|
137
|
+
# Crear callback de herramientas
|
|
138
|
+
def tool_start_callback(tool_name: str, args: Dict):
|
|
139
|
+
self._emit_progress(agent_id, "tool_start", {
|
|
140
|
+
"tool": tool_name,
|
|
141
|
+
"args": {k: str(v)[:50] for k, v in args.items()},
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
def tool_end_callback(tool_name: str, result: str, success: bool):
|
|
145
|
+
self._emit_progress(agent_id, "tool_end", {
|
|
146
|
+
"tool": tool_name,
|
|
147
|
+
"success": success,
|
|
148
|
+
"result_preview": result[:200] if result else "",
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
# Crear motor con callbacks
|
|
152
|
+
engine = QueryEngine(
|
|
153
|
+
connector=self.connector,
|
|
154
|
+
tool_executor=filtered_executor,
|
|
155
|
+
session_manager=self.session,
|
|
156
|
+
permission_manager=self.perms,
|
|
157
|
+
plugin_manager=None, # Sin plugins para subagentes
|
|
158
|
+
stream_callback=stream_callback,
|
|
159
|
+
tool_start_callback=tool_start_callback,
|
|
160
|
+
tool_end_callback=tool_end_callback,
|
|
161
|
+
budget_usd=0,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# Establecer system prompt específico
|
|
165
|
+
system_prompt = SUBAGENT_SYSTEM_PROMPTS.get(
|
|
166
|
+
config.agent_type,
|
|
167
|
+
SUBAGENT_SYSTEM_PROMPTS[SubagentType.GENERAL]
|
|
168
|
+
)
|
|
169
|
+
engine.set_system_prompt(system_prompt)
|
|
170
|
+
|
|
171
|
+
# Construir mensaje de tarea
|
|
172
|
+
task_message = self._build_task_message(config)
|
|
173
|
+
|
|
174
|
+
self._emit_progress(agent_id, "executing", {"message": "Iniciando ejecución..."})
|
|
175
|
+
|
|
176
|
+
# Ejecutar
|
|
177
|
+
response = engine.send(task_message)
|
|
178
|
+
|
|
179
|
+
# Construir resultado
|
|
180
|
+
result = SubagentResult(
|
|
181
|
+
success=response.stop_reason not in ("error", "interrupted"),
|
|
182
|
+
output=response.text or "",
|
|
183
|
+
tokens_used=response.input_tokens + response.output_tokens,
|
|
184
|
+
cost_usd=response.cost_usd,
|
|
185
|
+
agent_id=agent_id,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
agent_info["status"] = "completed"
|
|
189
|
+
agent_info["result"] = result
|
|
190
|
+
|
|
191
|
+
# Emitir finalización
|
|
192
|
+
self._emit_progress(agent_id, "completed", {
|
|
193
|
+
"success": result.success,
|
|
194
|
+
"tokens": result.tokens_used,
|
|
195
|
+
"output_preview": result.output[:500] if result.output else "",
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
return result
|
|
199
|
+
|
|
200
|
+
except Exception as e:
|
|
201
|
+
agent_info["status"] = "failed"
|
|
202
|
+
self._emit_progress(agent_id, "error", {"error": str(e)})
|
|
203
|
+
return SubagentResult(
|
|
204
|
+
success=False,
|
|
205
|
+
error=f"Error ejecutando subagente: {type(e).__name__}: {e}",
|
|
206
|
+
agent_id=agent_id,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
def _create_filtered_executor(self, allowed_tools: Optional[List[str]], agent_id: str = None):
|
|
210
|
+
"""
|
|
211
|
+
Crea un executor con herramientas filtradas y callbacks de progreso.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
allowed_tools: Lista de herramientas permitidas, o None para todas
|
|
215
|
+
agent_id: ID del subagente para callbacks
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
Executor filtrado
|
|
219
|
+
"""
|
|
220
|
+
parent_manager = self
|
|
221
|
+
|
|
222
|
+
class FilteredExecutor:
|
|
223
|
+
def __init__(self, parent, allowed, aid):
|
|
224
|
+
self._parent = parent
|
|
225
|
+
self._allowed = set(allowed) if allowed else None # None = todas
|
|
226
|
+
self._agent_id = aid
|
|
227
|
+
self.perms = parent.perms
|
|
228
|
+
self.tasks = parent.tasks if hasattr(parent, 'tasks') else None
|
|
229
|
+
self.memory = parent.memory if hasattr(parent, 'memory') else None
|
|
230
|
+
self.root = parent.root
|
|
231
|
+
|
|
232
|
+
def execute(self, tool_name: str, args: Dict) -> Any:
|
|
233
|
+
# Verificar si la herramienta está permitida
|
|
234
|
+
if self._allowed is not None and tool_name not in self._allowed:
|
|
235
|
+
from hanus.tools import ToolResult
|
|
236
|
+
return ToolResult.err(
|
|
237
|
+
f"Herramienta '{tool_name}' no disponible para este subagente. "
|
|
238
|
+
f"Permitidas: {list(self._allowed)}"
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
# Emitir progreso de herramienta
|
|
242
|
+
if parent_manager and self._agent_id:
|
|
243
|
+
parent_manager._emit_progress(self._agent_id, "tool_exec", {
|
|
244
|
+
"tool": tool_name,
|
|
245
|
+
"args_preview": str(args)[:100],
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
return self._parent.execute(tool_name, args)
|
|
249
|
+
|
|
250
|
+
return FilteredExecutor(self.executor, allowed_tools, agent_id)
|
|
251
|
+
|
|
252
|
+
def _build_task_message(self, config: SubagentConfig) -> str:
|
|
253
|
+
"""Construye el mensaje de tarea para el subagente."""
|
|
254
|
+
parts = [f"## Tarea\n\n{config.task_description}"]
|
|
255
|
+
|
|
256
|
+
if config.context:
|
|
257
|
+
parts.append(f"\n## Contexto\n\n{config.context}")
|
|
258
|
+
|
|
259
|
+
# Añadir instrucciones específicas según tipo
|
|
260
|
+
if config.agent_type == SubagentType.EXPLORE:
|
|
261
|
+
parts.append("\n## Instrucciones")
|
|
262
|
+
parts.append("- Explora el código de forma eficiente")
|
|
263
|
+
parts.append("- Proporciona rutas de archivo y números de línea")
|
|
264
|
+
parts.append("- Resume los hallazgos de forma clara")
|
|
265
|
+
|
|
266
|
+
elif config.agent_type == SubagentType.REVIEW:
|
|
267
|
+
parts.append("\n## Revisa")
|
|
268
|
+
parts.append("- Bugs y errores potenciales")
|
|
269
|
+
parts.append("- Problemas de seguridad")
|
|
270
|
+
parts.append("- Calidad del código")
|
|
271
|
+
parts.append("- Best practices")
|
|
272
|
+
|
|
273
|
+
elif config.agent_type == SubagentType.TEST:
|
|
274
|
+
parts.append("\n## Instrucciones")
|
|
275
|
+
parts.append("- Escribe tests comprehensivos")
|
|
276
|
+
parts.append("- Ejecuta los tests para verificar que pasan")
|
|
277
|
+
parts.append("- Reporta cualquier problema encontrado")
|
|
278
|
+
|
|
279
|
+
return "\n".join(parts)
|
|
280
|
+
|
|
281
|
+
def get_status(self, agent_id: str) -> Optional[Dict]:
|
|
282
|
+
"""Obtiene el estado de un subagente."""
|
|
283
|
+
return self._active_agents.get(agent_id)
|
|
284
|
+
|
|
285
|
+
def list_active(self) -> List[Dict]:
|
|
286
|
+
"""Lista todos los subagentes activos."""
|
|
287
|
+
return [
|
|
288
|
+
{"id": aid, **info}
|
|
289
|
+
for aid, info in self._active_agents.items()
|
|
290
|
+
]
|
|
291
|
+
|
|
292
|
+
def cancel(self, agent_id: str) -> bool:
|
|
293
|
+
"""Cancela un subagente en ejecución."""
|
|
294
|
+
if agent_id not in self._active_agents:
|
|
295
|
+
return False
|
|
296
|
+
|
|
297
|
+
agent_info = self._active_agents[agent_id]
|
|
298
|
+
if agent_info["status"] == "running":
|
|
299
|
+
agent_info["status"] = "cancelled"
|
|
300
|
+
return True
|
|
301
|
+
|
|
302
|
+
def cleanup(self):
|
|
303
|
+
"""Limpia subagentes completados."""
|
|
304
|
+
to_remove = [
|
|
305
|
+
aid for aid, info in self._active_agents.items()
|
|
306
|
+
if info["status"] in ("completed", "failed", "cancelled")
|
|
307
|
+
]
|
|
308
|
+
for aid in to_remove:
|
|
309
|
+
del self._active_agents[aid]
|