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.
Files changed (93) hide show
  1. hanus/__init__.py +5 -0
  2. hanus/__main__.py +10 -0
  3. hanus/action_handlers.py +76 -0
  4. hanus/action_parser.py +82 -0
  5. hanus/agent_runner.py +1445 -0
  6. hanus/analysis/__init__.py +5 -0
  7. hanus/analysis/debt.py +702 -0
  8. hanus/analysis/dependencies.py +475 -0
  9. hanus/cache/__init__.py +5 -0
  10. hanus/cache/response_cache.py +560 -0
  11. hanus/config.py +401 -0
  12. hanus/connectors/__init__.py +19 -0
  13. hanus/connectors/base.py +114 -0
  14. hanus/connectors/claude_connector.py +146 -0
  15. hanus/connectors/gemini_connector.py +141 -0
  16. hanus/connectors/glm_connector.py +160 -0
  17. hanus/connectors/ollama_connector.py +174 -0
  18. hanus/connectors/openai_connector.py +122 -0
  19. hanus/connectors/registry.py +26 -0
  20. hanus/context/__init__.py +7 -0
  21. hanus/context/manager.py +837 -0
  22. hanus/context/selective.py +626 -0
  23. hanus/error_recovery/__init__.py +5 -0
  24. hanus/error_recovery/auto_fix.py +605 -0
  25. hanus/hooks/__init__.py +5 -0
  26. hanus/hooks/manager.py +247 -0
  27. hanus/instincts/__init__.py +44 -0
  28. hanus/instincts/cli.py +372 -0
  29. hanus/instincts/detector.py +281 -0
  30. hanus/instincts/evolver.py +361 -0
  31. hanus/instincts/manager.py +343 -0
  32. hanus/instincts/types.py +253 -0
  33. hanus/logger.py +81 -0
  34. hanus/memory/__init__.py +8 -0
  35. hanus/memory/manager.py +265 -0
  36. hanus/memory/types.py +119 -0
  37. hanus/monitor.py +341 -0
  38. hanus/parallel/__init__.py +5 -0
  39. hanus/parallel/executor.py +300 -0
  40. hanus/permissions.py +182 -0
  41. hanus/plan/__init__.py +8 -0
  42. hanus/plan/mode.py +267 -0
  43. hanus/plan/models.py +152 -0
  44. hanus/plugin_manager.py +754 -0
  45. hanus/plugin_registry.py +391 -0
  46. hanus/plugins/__init__.py +1 -0
  47. hanus/plugins/arena.py +630 -0
  48. hanus/plugins/code_review.py +123 -0
  49. hanus/plugins/cortex.py +1750 -0
  50. hanus/plugins/deps_check.py +27 -0
  51. hanus/plugins/git_ops.py +33 -0
  52. hanus/plugins/metasploit.py +530 -0
  53. hanus/plugins/notes.py +583 -0
  54. hanus/plugins/search_code.py +59 -0
  55. hanus/plugins/searchsploit.py +495 -0
  56. hanus/plugins/strategist.py +175 -0
  57. hanus/plugins/webui.py +5200 -0
  58. hanus/profiles.py +479 -0
  59. hanus/profiles_builtin/__init__.py +0 -0
  60. hanus/profiles_builtin/architect/profile.yaml +12 -0
  61. hanus/profiles_builtin/architect/system_prompt.txt +71 -0
  62. hanus/profiles_builtin/deep/profile.yaml +12 -0
  63. hanus/profiles_builtin/deep/system_prompt.txt +66 -0
  64. hanus/profiles_builtin/developer/__init__.py +0 -0
  65. hanus/profiles_builtin/developer/profile.yaml +9 -0
  66. hanus/profiles_builtin/developer/system_prompt.txt +176 -0
  67. hanus/profiles_builtin/speed/profile.yaml +12 -0
  68. hanus/profiles_builtin/speed/system_prompt.txt +51 -0
  69. hanus/project_tools.py +177 -0
  70. hanus/query_engine.py +1594 -0
  71. hanus/rules/__init__.py +237 -0
  72. hanus/search/__init__.py +5 -0
  73. hanus/search/semantic.py +596 -0
  74. hanus/session_manager.py +547 -0
  75. hanus/skill_manager.py +702 -0
  76. hanus/skills/__init__.py +4 -0
  77. hanus/subagent/__init__.py +8 -0
  78. hanus/subagent/agents/__init__.py +253 -0
  79. hanus/subagent/manager.py +309 -0
  80. hanus/subagent/types.py +266 -0
  81. hanus/suggestions/__init__.py +5 -0
  82. hanus/suggestions/proactive.py +451 -0
  83. hanus/tasks/__init__.py +8 -0
  84. hanus/tasks/manager.py +330 -0
  85. hanus/tasks/models.py +106 -0
  86. hanus/terminal_prompt.py +166 -0
  87. hanus/tools.py +1849 -0
  88. hanus/ui.py +939 -0
  89. hanuscode-1.0.0.dist-info/METADATA +1151 -0
  90. hanuscode-1.0.0.dist-info/RECORD +93 -0
  91. hanuscode-1.0.0.dist-info/WHEEL +5 -0
  92. hanuscode-1.0.0.dist-info/entry_points.txt +2 -0
  93. hanuscode-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,4 @@
1
+ # hanus/skills/__init__.py
2
+ """
3
+ Skills directory - contains skill templates and definitions.
4
+ """
@@ -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]