superqode 0.1.5__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 (288) hide show
  1. superqode/__init__.py +33 -0
  2. superqode/acp/__init__.py +23 -0
  3. superqode/acp/client.py +913 -0
  4. superqode/acp/permission_screen.py +457 -0
  5. superqode/acp/types.py +480 -0
  6. superqode/acp_discovery.py +856 -0
  7. superqode/agent/__init__.py +22 -0
  8. superqode/agent/edit_strategies.py +334 -0
  9. superqode/agent/loop.py +892 -0
  10. superqode/agent/qe_report_templates.py +39 -0
  11. superqode/agent/system_prompts.py +353 -0
  12. superqode/agent_output.py +721 -0
  13. superqode/agent_stream.py +953 -0
  14. superqode/agents/__init__.py +59 -0
  15. superqode/agents/acp_registry.py +305 -0
  16. superqode/agents/client.py +249 -0
  17. superqode/agents/data/augmentcode.com.toml +51 -0
  18. superqode/agents/data/cagent.dev.toml +51 -0
  19. superqode/agents/data/claude.com.toml +60 -0
  20. superqode/agents/data/codeassistant.dev.toml +51 -0
  21. superqode/agents/data/codex.openai.com.toml +57 -0
  22. superqode/agents/data/fastagent.ai.toml +66 -0
  23. superqode/agents/data/geminicli.com.toml +77 -0
  24. superqode/agents/data/goose.block.xyz.toml +54 -0
  25. superqode/agents/data/junie.jetbrains.com.toml +56 -0
  26. superqode/agents/data/kimi.moonshot.cn.toml +57 -0
  27. superqode/agents/data/llmlingagent.dev.toml +51 -0
  28. superqode/agents/data/molt.bot.toml +49 -0
  29. superqode/agents/data/opencode.ai.toml +60 -0
  30. superqode/agents/data/stakpak.dev.toml +51 -0
  31. superqode/agents/data/vtcode.dev.toml +51 -0
  32. superqode/agents/discovery.py +266 -0
  33. superqode/agents/messaging.py +160 -0
  34. superqode/agents/persona.py +166 -0
  35. superqode/agents/registry.py +421 -0
  36. superqode/agents/schema.py +72 -0
  37. superqode/agents/unified.py +367 -0
  38. superqode/app/__init__.py +111 -0
  39. superqode/app/constants.py +314 -0
  40. superqode/app/css.py +366 -0
  41. superqode/app/models.py +118 -0
  42. superqode/app/suggester.py +125 -0
  43. superqode/app/widgets.py +1591 -0
  44. superqode/app_enhanced.py +399 -0
  45. superqode/app_main.py +17187 -0
  46. superqode/approval.py +312 -0
  47. superqode/atomic.py +296 -0
  48. superqode/commands/__init__.py +1 -0
  49. superqode/commands/acp.py +965 -0
  50. superqode/commands/agents.py +180 -0
  51. superqode/commands/auth.py +278 -0
  52. superqode/commands/config.py +374 -0
  53. superqode/commands/init.py +826 -0
  54. superqode/commands/providers.py +819 -0
  55. superqode/commands/qe.py +1145 -0
  56. superqode/commands/roles.py +380 -0
  57. superqode/commands/serve.py +172 -0
  58. superqode/commands/suggestions.py +127 -0
  59. superqode/commands/superqe.py +460 -0
  60. superqode/config/__init__.py +51 -0
  61. superqode/config/loader.py +812 -0
  62. superqode/config/schema.py +498 -0
  63. superqode/core/__init__.py +111 -0
  64. superqode/core/roles.py +281 -0
  65. superqode/danger.py +386 -0
  66. superqode/data/superqode-template.yaml +1522 -0
  67. superqode/design_system.py +1080 -0
  68. superqode/dialogs/__init__.py +6 -0
  69. superqode/dialogs/base.py +39 -0
  70. superqode/dialogs/model.py +130 -0
  71. superqode/dialogs/provider.py +870 -0
  72. superqode/diff_view.py +919 -0
  73. superqode/enterprise.py +21 -0
  74. superqode/evaluation/__init__.py +25 -0
  75. superqode/evaluation/adapters.py +93 -0
  76. superqode/evaluation/behaviors.py +89 -0
  77. superqode/evaluation/engine.py +209 -0
  78. superqode/evaluation/scenarios.py +96 -0
  79. superqode/execution/__init__.py +36 -0
  80. superqode/execution/linter.py +538 -0
  81. superqode/execution/modes.py +347 -0
  82. superqode/execution/resolver.py +283 -0
  83. superqode/execution/runner.py +642 -0
  84. superqode/file_explorer.py +811 -0
  85. superqode/file_viewer.py +471 -0
  86. superqode/flash.py +183 -0
  87. superqode/guidance/__init__.py +58 -0
  88. superqode/guidance/config.py +203 -0
  89. superqode/guidance/prompts.py +71 -0
  90. superqode/harness/__init__.py +54 -0
  91. superqode/harness/accelerator.py +291 -0
  92. superqode/harness/config.py +319 -0
  93. superqode/harness/validator.py +147 -0
  94. superqode/history.py +279 -0
  95. superqode/integrations/superopt_runner.py +124 -0
  96. superqode/logging/__init__.py +49 -0
  97. superqode/logging/adapters.py +219 -0
  98. superqode/logging/formatter.py +923 -0
  99. superqode/logging/integration.py +341 -0
  100. superqode/logging/sinks.py +170 -0
  101. superqode/logging/unified_log.py +417 -0
  102. superqode/lsp/__init__.py +26 -0
  103. superqode/lsp/client.py +544 -0
  104. superqode/main.py +1069 -0
  105. superqode/mcp/__init__.py +89 -0
  106. superqode/mcp/auth_storage.py +380 -0
  107. superqode/mcp/client.py +1236 -0
  108. superqode/mcp/config.py +319 -0
  109. superqode/mcp/integration.py +337 -0
  110. superqode/mcp/oauth.py +436 -0
  111. superqode/mcp/oauth_callback.py +385 -0
  112. superqode/mcp/types.py +290 -0
  113. superqode/memory/__init__.py +31 -0
  114. superqode/memory/feedback.py +342 -0
  115. superqode/memory/store.py +522 -0
  116. superqode/notifications.py +369 -0
  117. superqode/optimization/__init__.py +5 -0
  118. superqode/optimization/config.py +33 -0
  119. superqode/permissions/__init__.py +25 -0
  120. superqode/permissions/rules.py +488 -0
  121. superqode/plan.py +323 -0
  122. superqode/providers/__init__.py +33 -0
  123. superqode/providers/gateway/__init__.py +165 -0
  124. superqode/providers/gateway/base.py +228 -0
  125. superqode/providers/gateway/litellm_gateway.py +1170 -0
  126. superqode/providers/gateway/openresponses_gateway.py +436 -0
  127. superqode/providers/health.py +297 -0
  128. superqode/providers/huggingface/__init__.py +74 -0
  129. superqode/providers/huggingface/downloader.py +472 -0
  130. superqode/providers/huggingface/endpoints.py +442 -0
  131. superqode/providers/huggingface/hub.py +531 -0
  132. superqode/providers/huggingface/inference.py +394 -0
  133. superqode/providers/huggingface/transformers_runner.py +516 -0
  134. superqode/providers/local/__init__.py +100 -0
  135. superqode/providers/local/base.py +438 -0
  136. superqode/providers/local/discovery.py +418 -0
  137. superqode/providers/local/lmstudio.py +256 -0
  138. superqode/providers/local/mlx.py +457 -0
  139. superqode/providers/local/ollama.py +486 -0
  140. superqode/providers/local/sglang.py +268 -0
  141. superqode/providers/local/tgi.py +260 -0
  142. superqode/providers/local/tool_support.py +477 -0
  143. superqode/providers/local/vllm.py +258 -0
  144. superqode/providers/manager.py +1338 -0
  145. superqode/providers/models.py +1016 -0
  146. superqode/providers/models_dev.py +578 -0
  147. superqode/providers/openresponses/__init__.py +87 -0
  148. superqode/providers/openresponses/converters/__init__.py +17 -0
  149. superqode/providers/openresponses/converters/messages.py +343 -0
  150. superqode/providers/openresponses/converters/tools.py +268 -0
  151. superqode/providers/openresponses/schema/__init__.py +56 -0
  152. superqode/providers/openresponses/schema/models.py +585 -0
  153. superqode/providers/openresponses/streaming/__init__.py +5 -0
  154. superqode/providers/openresponses/streaming/parser.py +338 -0
  155. superqode/providers/openresponses/tools/__init__.py +21 -0
  156. superqode/providers/openresponses/tools/apply_patch.py +352 -0
  157. superqode/providers/openresponses/tools/code_interpreter.py +290 -0
  158. superqode/providers/openresponses/tools/file_search.py +333 -0
  159. superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
  160. superqode/providers/registry.py +716 -0
  161. superqode/providers/usage.py +332 -0
  162. superqode/pure_mode.py +384 -0
  163. superqode/qr/__init__.py +23 -0
  164. superqode/qr/dashboard.py +781 -0
  165. superqode/qr/generator.py +1018 -0
  166. superqode/qr/templates.py +135 -0
  167. superqode/safety/__init__.py +41 -0
  168. superqode/safety/sandbox.py +413 -0
  169. superqode/safety/warnings.py +256 -0
  170. superqode/server/__init__.py +33 -0
  171. superqode/server/lsp_server.py +775 -0
  172. superqode/server/web.py +250 -0
  173. superqode/session/__init__.py +25 -0
  174. superqode/session/persistence.py +580 -0
  175. superqode/session/sharing.py +477 -0
  176. superqode/session.py +475 -0
  177. superqode/sidebar.py +2991 -0
  178. superqode/stream_view.py +648 -0
  179. superqode/styles/__init__.py +3 -0
  180. superqode/superqe/__init__.py +184 -0
  181. superqode/superqe/acp_runner.py +1064 -0
  182. superqode/superqe/constitution/__init__.py +62 -0
  183. superqode/superqe/constitution/evaluator.py +308 -0
  184. superqode/superqe/constitution/loader.py +432 -0
  185. superqode/superqe/constitution/schema.py +250 -0
  186. superqode/superqe/events.py +591 -0
  187. superqode/superqe/frameworks/__init__.py +65 -0
  188. superqode/superqe/frameworks/base.py +234 -0
  189. superqode/superqe/frameworks/e2e.py +263 -0
  190. superqode/superqe/frameworks/executor.py +237 -0
  191. superqode/superqe/frameworks/javascript.py +409 -0
  192. superqode/superqe/frameworks/python.py +373 -0
  193. superqode/superqe/frameworks/registry.py +92 -0
  194. superqode/superqe/mcp_tools/__init__.py +47 -0
  195. superqode/superqe/mcp_tools/core_tools.py +418 -0
  196. superqode/superqe/mcp_tools/registry.py +230 -0
  197. superqode/superqe/mcp_tools/testing_tools.py +167 -0
  198. superqode/superqe/noise.py +89 -0
  199. superqode/superqe/orchestrator.py +778 -0
  200. superqode/superqe/roles.py +609 -0
  201. superqode/superqe/session.py +713 -0
  202. superqode/superqe/skills/__init__.py +57 -0
  203. superqode/superqe/skills/base.py +106 -0
  204. superqode/superqe/skills/core_skills.py +899 -0
  205. superqode/superqe/skills/registry.py +90 -0
  206. superqode/superqe/verifier.py +101 -0
  207. superqode/superqe_cli.py +76 -0
  208. superqode/tool_call.py +358 -0
  209. superqode/tools/__init__.py +93 -0
  210. superqode/tools/agent_tools.py +496 -0
  211. superqode/tools/base.py +324 -0
  212. superqode/tools/batch_tool.py +133 -0
  213. superqode/tools/diagnostics.py +311 -0
  214. superqode/tools/edit_tools.py +653 -0
  215. superqode/tools/enhanced_base.py +515 -0
  216. superqode/tools/file_tools.py +269 -0
  217. superqode/tools/file_tracking.py +45 -0
  218. superqode/tools/lsp_tools.py +610 -0
  219. superqode/tools/network_tools.py +350 -0
  220. superqode/tools/permissions.py +400 -0
  221. superqode/tools/question_tool.py +324 -0
  222. superqode/tools/search_tools.py +598 -0
  223. superqode/tools/shell_tools.py +259 -0
  224. superqode/tools/todo_tools.py +121 -0
  225. superqode/tools/validation.py +80 -0
  226. superqode/tools/web_tools.py +639 -0
  227. superqode/tui.py +1152 -0
  228. superqode/tui_integration.py +875 -0
  229. superqode/tui_widgets/__init__.py +27 -0
  230. superqode/tui_widgets/widgets/__init__.py +18 -0
  231. superqode/tui_widgets/widgets/progress.py +185 -0
  232. superqode/tui_widgets/widgets/tool_display.py +188 -0
  233. superqode/undo_manager.py +574 -0
  234. superqode/utils/__init__.py +5 -0
  235. superqode/utils/error_handling.py +323 -0
  236. superqode/utils/fuzzy.py +257 -0
  237. superqode/widgets/__init__.py +477 -0
  238. superqode/widgets/agent_collab.py +390 -0
  239. superqode/widgets/agent_store.py +936 -0
  240. superqode/widgets/agent_switcher.py +395 -0
  241. superqode/widgets/animation_manager.py +284 -0
  242. superqode/widgets/code_context.py +356 -0
  243. superqode/widgets/command_palette.py +412 -0
  244. superqode/widgets/connection_status.py +537 -0
  245. superqode/widgets/conversation_history.py +470 -0
  246. superqode/widgets/diff_indicator.py +155 -0
  247. superqode/widgets/enhanced_status_bar.py +385 -0
  248. superqode/widgets/enhanced_toast.py +476 -0
  249. superqode/widgets/file_browser.py +809 -0
  250. superqode/widgets/file_reference.py +585 -0
  251. superqode/widgets/issue_timeline.py +340 -0
  252. superqode/widgets/leader_key.py +264 -0
  253. superqode/widgets/mode_switcher.py +445 -0
  254. superqode/widgets/model_picker.py +234 -0
  255. superqode/widgets/permission_preview.py +1205 -0
  256. superqode/widgets/prompt.py +358 -0
  257. superqode/widgets/provider_connect.py +725 -0
  258. superqode/widgets/pty_shell.py +587 -0
  259. superqode/widgets/qe_dashboard.py +321 -0
  260. superqode/widgets/resizable_sidebar.py +377 -0
  261. superqode/widgets/response_changes.py +218 -0
  262. superqode/widgets/response_display.py +528 -0
  263. superqode/widgets/rich_tool_display.py +613 -0
  264. superqode/widgets/sidebar_panels.py +1180 -0
  265. superqode/widgets/slash_complete.py +356 -0
  266. superqode/widgets/split_view.py +612 -0
  267. superqode/widgets/status_bar.py +273 -0
  268. superqode/widgets/superqode_display.py +786 -0
  269. superqode/widgets/thinking_display.py +815 -0
  270. superqode/widgets/throbber.py +87 -0
  271. superqode/widgets/toast.py +206 -0
  272. superqode/widgets/unified_output.py +1073 -0
  273. superqode/workspace/__init__.py +75 -0
  274. superqode/workspace/artifacts.py +472 -0
  275. superqode/workspace/coordinator.py +353 -0
  276. superqode/workspace/diff_tracker.py +429 -0
  277. superqode/workspace/git_guard.py +373 -0
  278. superqode/workspace/git_snapshot.py +526 -0
  279. superqode/workspace/manager.py +750 -0
  280. superqode/workspace/snapshot.py +357 -0
  281. superqode/workspace/watcher.py +535 -0
  282. superqode/workspace/worktree.py +440 -0
  283. superqode-0.1.5.dist-info/METADATA +204 -0
  284. superqode-0.1.5.dist-info/RECORD +288 -0
  285. superqode-0.1.5.dist-info/WHEEL +5 -0
  286. superqode-0.1.5.dist-info/entry_points.txt +3 -0
  287. superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
  288. superqode-0.1.5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,93 @@
1
+ """
2
+ SuperQode Tools - Comprehensive Tool System for AI Coding Agents.
3
+
4
+ Design Philosophy:
5
+ - COMPREHENSIVE: Full-featured tooling for complex tasks
6
+ - TRANSPARENT: No hidden prompts or context injection
7
+ - STANDARD: Use OpenAI-compatible tool format
8
+ - EXTENSIBLE: Easy to add new tools
9
+
10
+ Tool Categories:
11
+ - File Operations: read, write, edit, patch, multi-edit
12
+ - Search: grep, glob, semantic code search
13
+ - Shell: bash with streaming and safety
14
+ - Diagnostics: LSP integration, linter errors
15
+ - Network: fetch URLs, download files, web search
16
+ - Agent: sub-agent spawning for parallel work
17
+ - LSP: Language Server Protocol operations
18
+ - Interactive: ask user questions during execution
19
+ """
20
+
21
+ from .base import Tool, ToolResult, ToolContext, ToolRegistry
22
+ from .file_tools import ReadFileTool, WriteFileTool, ListDirectoryTool
23
+ from .edit_tools import EditFileTool, InsertTextTool, PatchTool, MultiEditTool
24
+ from .todo_tools import TodoWriteTool, TodoReadTool
25
+ from .batch_tool import BatchTool
26
+ from .shell_tools import BashTool
27
+ from .search_tools import GrepTool, GlobTool, CodeSearchTool
28
+ from .diagnostics import DiagnosticsTool
29
+ from .network_tools import FetchTool, DownloadTool
30
+ from .agent_tools import SubAgentTool, TaskCoordinatorTool
31
+ from .lsp_tools import LSPTool
32
+ from .web_tools import WebSearchTool, WebFetchTool
33
+ from .question_tool import QuestionTool, ConfirmTool, set_question_handler, get_question_handler
34
+ from .permissions import (
35
+ Permission,
36
+ PermissionConfig,
37
+ PermissionManager,
38
+ get_permission_manager,
39
+ set_permission_manager,
40
+ load_permission_config,
41
+ )
42
+
43
+ __all__ = [
44
+ # Base
45
+ "Tool",
46
+ "ToolResult",
47
+ "ToolContext",
48
+ "ToolRegistry",
49
+ # File tools
50
+ "ReadFileTool",
51
+ "WriteFileTool",
52
+ "ListDirectoryTool",
53
+ # Edit tools
54
+ "EditFileTool",
55
+ "InsertTextTool",
56
+ "PatchTool",
57
+ "MultiEditTool",
58
+ # TODO tools
59
+ "TodoWriteTool",
60
+ "TodoReadTool",
61
+ "BatchTool",
62
+ # Shell tools
63
+ "BashTool",
64
+ # Search tools
65
+ "GrepTool",
66
+ "GlobTool",
67
+ "CodeSearchTool",
68
+ # Diagnostics
69
+ "DiagnosticsTool",
70
+ # Network tools
71
+ "FetchTool",
72
+ "DownloadTool",
73
+ # Web tools
74
+ "WebSearchTool",
75
+ "WebFetchTool",
76
+ # Agent tools
77
+ "SubAgentTool",
78
+ "TaskCoordinatorTool",
79
+ # LSP tools
80
+ "LSPTool",
81
+ # Interactive tools
82
+ "QuestionTool",
83
+ "ConfirmTool",
84
+ "set_question_handler",
85
+ "get_question_handler",
86
+ # Permissions
87
+ "Permission",
88
+ "PermissionConfig",
89
+ "PermissionManager",
90
+ "get_permission_manager",
91
+ "set_permission_manager",
92
+ "load_permission_config",
93
+ ]
@@ -0,0 +1,496 @@
1
+ """
2
+ Agent Tools - Sub-agent spawning and coordination.
3
+
4
+ Provides tools for:
5
+ - Spawning sub-agents for parallel work
6
+ - Task distribution and coordination
7
+ - Result collection and merging
8
+
9
+ This enables the main agent to delegate independent tasks
10
+ to sub-agents that run in parallel, improving efficiency
11
+ for complex multi-step operations.
12
+ """
13
+
14
+ import asyncio
15
+ import uuid
16
+ from dataclasses import dataclass, field
17
+ from datetime import datetime
18
+ from enum import Enum
19
+ from pathlib import Path
20
+ from typing import Any, Callable, Dict, List, Optional, Tuple
21
+
22
+ from .base import Tool, ToolResult, ToolContext
23
+
24
+
25
+ class SubTaskStatus(Enum):
26
+ """Status of a sub-task."""
27
+
28
+ PENDING = "pending"
29
+ RUNNING = "running"
30
+ COMPLETED = "completed"
31
+ FAILED = "failed"
32
+ CANCELLED = "cancelled"
33
+
34
+
35
+ @dataclass
36
+ class SubTask:
37
+ """A sub-task delegated to a sub-agent."""
38
+
39
+ id: str
40
+ description: str
41
+ status: SubTaskStatus = SubTaskStatus.PENDING
42
+ result: Optional[str] = None
43
+ error: Optional[str] = None
44
+ started_at: Optional[datetime] = None
45
+ completed_at: Optional[datetime] = None
46
+ metadata: Dict[str, Any] = field(default_factory=dict)
47
+
48
+
49
+ @dataclass
50
+ class SubAgentContext:
51
+ """Context for sub-agent execution."""
52
+
53
+ parent_session_id: str
54
+ working_directory: Path
55
+ task: SubTask
56
+ shared_memory: Dict[str, Any] = field(default_factory=dict)
57
+
58
+
59
+ class SubAgentTool(Tool):
60
+ """
61
+ Spawn a sub-agent to handle an independent task.
62
+
63
+ Use this when you need to:
64
+ - Perform independent operations in parallel
65
+ - Delegate a self-contained task
66
+ - Explore multiple approaches simultaneously
67
+
68
+ The sub-agent has access to the same tools as the parent,
69
+ but operates in an isolated context to prevent conflicts.
70
+
71
+ Example uses:
72
+ - "Research how function X is used while I modify function Y"
73
+ - "Run tests in the background while I continue coding"
74
+ - "Search for patterns in multiple directories simultaneously"
75
+ """
76
+
77
+ # Track active sub-tasks
78
+ _active_tasks: Dict[str, SubTask] = {}
79
+ _task_results: Dict[str, SubTask] = {}
80
+
81
+ @property
82
+ def name(self) -> str:
83
+ return "agent"
84
+
85
+ @property
86
+ def description(self) -> str:
87
+ return "Spawn a sub-agent to handle an independent task in parallel. Use for tasks that don't depend on each other."
88
+
89
+ @property
90
+ def parameters(self) -> Dict[str, Any]:
91
+ return {
92
+ "type": "object",
93
+ "properties": {
94
+ "task": {
95
+ "type": "string",
96
+ "description": "Description of the task for the sub-agent to perform",
97
+ },
98
+ "action": {
99
+ "type": "string",
100
+ "enum": ["spawn", "status", "wait", "cancel"],
101
+ "description": "Action: spawn (create sub-agent), status (check task), wait (wait for completion), cancel (stop task)",
102
+ },
103
+ "task_id": {
104
+ "type": "string",
105
+ "description": "Task ID (required for status/wait/cancel actions)",
106
+ },
107
+ "timeout": {
108
+ "type": "integer",
109
+ "description": "Timeout in seconds for wait action (default: 60)",
110
+ },
111
+ "context": {
112
+ "type": "object",
113
+ "description": "Additional context to pass to sub-agent",
114
+ },
115
+ "allowed_tools": {
116
+ "type": "array",
117
+ "items": {"type": "string"},
118
+ "description": "Optional: restrict sub-agent to these tools only (permission filtering)",
119
+ },
120
+ },
121
+ "required": ["action"],
122
+ }
123
+
124
+ async def execute(self, args: Dict[str, Any], ctx: ToolContext) -> ToolResult:
125
+ action = args.get("action", "spawn")
126
+
127
+ if action == "spawn":
128
+ return await self._spawn_subtask(args, ctx)
129
+ elif action == "status":
130
+ return self._get_status(args)
131
+ elif action == "wait":
132
+ return await self._wait_for_task(args)
133
+ elif action == "cancel":
134
+ return self._cancel_task(args)
135
+ else:
136
+ return ToolResult(success=False, output="", error=f"Unknown action: {action}")
137
+
138
+ async def _spawn_subtask(self, args: Dict[str, Any], ctx: ToolContext) -> ToolResult:
139
+ """Spawn a new sub-agent task."""
140
+ task_description = args.get("task", "")
141
+ additional_context = args.get("context", {}) or {}
142
+ allowed_tools = args.get("allowed_tools")
143
+
144
+ # Recursive delegation limit (max depth 3)
145
+ depth = getattr(ctx, "delegation_depth", 0)
146
+ if depth >= 3:
147
+ return ToolResult(
148
+ success=False,
149
+ output="",
150
+ error="Recursive delegation limit reached (max depth 3)",
151
+ )
152
+
153
+ if not task_description:
154
+ return ToolResult(
155
+ success=False, output="", error="Task description is required for spawn action"
156
+ )
157
+
158
+ # Merge metadata: delegation_depth, allowed_tools for child session
159
+ child_depth = depth + 1
160
+ additional_context["delegation_depth"] = child_depth
161
+ if allowed_tools is not None:
162
+ additional_context["allowed_tools"] = list(allowed_tools)
163
+
164
+ # Create task
165
+ task_id = f"subtask-{uuid.uuid4().hex[:8]}"
166
+ task = SubTask(
167
+ id=task_id,
168
+ description=task_description,
169
+ status=SubTaskStatus.PENDING,
170
+ metadata=additional_context,
171
+ )
172
+
173
+ self._active_tasks[task_id] = task
174
+
175
+ # Start the sub-task execution in background
176
+ asyncio.create_task(self._execute_subtask(task, ctx))
177
+
178
+ return ToolResult(
179
+ success=True,
180
+ output=f"Sub-agent spawned with task ID: {task_id}\n\n"
181
+ f"Task: {task_description}\n\n"
182
+ f"Use agent(action='status', task_id='{task_id}') to check progress\n"
183
+ f"Use agent(action='wait', task_id='{task_id}') to wait for completion",
184
+ metadata={
185
+ "task_id": task_id,
186
+ "child_session_id": task_id,
187
+ "parent_session_id": getattr(ctx, "session_id", ""),
188
+ "delegation_depth": child_depth,
189
+ "status": "pending",
190
+ },
191
+ )
192
+
193
+ async def _execute_subtask(self, task: SubTask, parent_ctx: ToolContext) -> None:
194
+ """Execute a sub-task (runs in background)."""
195
+ task.status = SubTaskStatus.RUNNING
196
+ task.started_at = datetime.now()
197
+
198
+ try:
199
+ # Create sub-agent context
200
+ sub_ctx = SubAgentContext(
201
+ parent_session_id=parent_ctx.session_id,
202
+ working_directory=parent_ctx.working_directory,
203
+ task=task,
204
+ shared_memory=task.metadata,
205
+ )
206
+
207
+ # Try to get the agent loop for execution
208
+ result = await self._run_sub_agent(task.description, sub_ctx)
209
+
210
+ task.status = SubTaskStatus.COMPLETED
211
+ task.result = result
212
+ task.completed_at = datetime.now()
213
+
214
+ except asyncio.CancelledError:
215
+ task.status = SubTaskStatus.CANCELLED
216
+ task.error = "Task was cancelled"
217
+ task.completed_at = datetime.now()
218
+
219
+ except Exception as e:
220
+ task.status = SubTaskStatus.FAILED
221
+ task.error = str(e)
222
+ task.completed_at = datetime.now()
223
+
224
+ finally:
225
+ # Move to completed tasks
226
+ self._task_results[task.id] = task
227
+ self._active_tasks.pop(task.id, None)
228
+
229
+ async def _run_sub_agent(self, task_description: str, sub_ctx: SubAgentContext) -> str:
230
+ """
231
+ Run the sub-agent.
232
+
233
+ This is a simplified implementation that simulates sub-agent execution.
234
+ In a full implementation, this would create a new AgentLoop instance
235
+ with its own message history but shared tools.
236
+ """
237
+ # For now, we provide a simplified execution model
238
+ # In a full implementation, this would spawn a real agent loop
239
+
240
+ # Simulate some processing time
241
+ await asyncio.sleep(0.1)
242
+
243
+ # Return a placeholder result
244
+ # A full implementation would actually run the agent
245
+ return (
246
+ f"Sub-agent completed task: {task_description}\n\n"
247
+ f"[Note: Full sub-agent execution requires integration with AgentLoop]"
248
+ )
249
+
250
+ def _get_status(self, args: Dict[str, Any]) -> ToolResult:
251
+ """Get status of a sub-task."""
252
+ task_id = args.get("task_id", "")
253
+
254
+ if not task_id:
255
+ # Return status of all tasks
256
+ active = list(self._active_tasks.values())
257
+ completed = list(self._task_results.values())
258
+
259
+ output_lines = ["=== Active Tasks ==="]
260
+ for task in active:
261
+ output_lines.append(f"[{task.id}] {task.status.value}: {task.description[:50]}...")
262
+
263
+ if not active:
264
+ output_lines.append("(none)")
265
+
266
+ output_lines.append("\n=== Completed Tasks ===")
267
+ for task in completed[-5:]: # Last 5
268
+ status_icon = "✓" if task.status == SubTaskStatus.COMPLETED else "✗"
269
+ output_lines.append(f"[{task.id}] {status_icon} {task.description[:50]}...")
270
+
271
+ if not completed:
272
+ output_lines.append("(none)")
273
+
274
+ return ToolResult(
275
+ success=True,
276
+ output="\n".join(output_lines),
277
+ metadata={"active_count": len(active), "completed_count": len(completed)},
278
+ )
279
+
280
+ # Get specific task
281
+ task = self._active_tasks.get(task_id) or self._task_results.get(task_id)
282
+
283
+ if not task:
284
+ return ToolResult(success=False, output="", error=f"Task not found: {task_id}")
285
+
286
+ output_lines = [
287
+ f"Task ID: {task.id}",
288
+ f"Status: {task.status.value}",
289
+ f"Description: {task.description}",
290
+ ]
291
+
292
+ if task.started_at:
293
+ output_lines.append(f"Started: {task.started_at.isoformat()}")
294
+ if task.completed_at:
295
+ output_lines.append(f"Completed: {task.completed_at.isoformat()}")
296
+ if task.result:
297
+ output_lines.append(f"\nResult:\n{task.result}")
298
+ if task.error:
299
+ output_lines.append(f"\nError: {task.error}")
300
+
301
+ return ToolResult(
302
+ success=True, output="\n".join(output_lines), metadata={"status": task.status.value}
303
+ )
304
+
305
+ async def _wait_for_task(self, args: Dict[str, Any]) -> ToolResult:
306
+ """Wait for a sub-task to complete."""
307
+ task_id = args.get("task_id", "")
308
+ timeout = args.get("timeout", 60)
309
+
310
+ if not task_id:
311
+ return ToolResult(success=False, output="", error="task_id is required for wait action")
312
+
313
+ # Check if already completed
314
+ if task_id in self._task_results:
315
+ task = self._task_results[task_id]
316
+ return self._format_completed_task(task)
317
+
318
+ # Check if active
319
+ if task_id not in self._active_tasks:
320
+ return ToolResult(success=False, output="", error=f"Task not found: {task_id}")
321
+
322
+ # Wait for completion
323
+ start_time = asyncio.get_event_loop().time()
324
+
325
+ while task_id in self._active_tasks:
326
+ if asyncio.get_event_loop().time() - start_time > timeout:
327
+ return ToolResult(
328
+ success=False, output="", error=f"Timeout waiting for task {task_id}"
329
+ )
330
+
331
+ await asyncio.sleep(0.5)
332
+
333
+ # Task completed
334
+ if task_id in self._task_results:
335
+ task = self._task_results[task_id]
336
+ return self._format_completed_task(task)
337
+
338
+ return ToolResult(
339
+ success=False, output="", error=f"Task {task_id} disappeared unexpectedly"
340
+ )
341
+
342
+ def _format_completed_task(self, task: SubTask) -> ToolResult:
343
+ """Format a completed task result."""
344
+ if task.status == SubTaskStatus.COMPLETED:
345
+ return ToolResult(
346
+ success=True,
347
+ output=f"Task {task.id} completed successfully.\n\n{task.result or '(no output)'}",
348
+ metadata={"task_id": task.id, "status": "completed"},
349
+ )
350
+ else:
351
+ return ToolResult(
352
+ success=False,
353
+ output=task.result or "",
354
+ error=f"Task {task.id} failed: {task.error}",
355
+ metadata={"task_id": task.id, "status": task.status.value},
356
+ )
357
+
358
+ def _cancel_task(self, args: Dict[str, Any]) -> ToolResult:
359
+ """Cancel a running sub-task."""
360
+ task_id = args.get("task_id", "")
361
+
362
+ if not task_id:
363
+ return ToolResult(
364
+ success=False, output="", error="task_id is required for cancel action"
365
+ )
366
+
367
+ if task_id not in self._active_tasks:
368
+ if task_id in self._task_results:
369
+ return ToolResult(
370
+ success=False, output="", error=f"Task {task_id} already completed"
371
+ )
372
+ return ToolResult(success=False, output="", error=f"Task not found: {task_id}")
373
+
374
+ # Mark as cancelled
375
+ task = self._active_tasks[task_id]
376
+ task.status = SubTaskStatus.CANCELLED
377
+ task.error = "Cancelled by user"
378
+ task.completed_at = datetime.now()
379
+
380
+ self._task_results[task_id] = task
381
+ self._active_tasks.pop(task_id, None)
382
+
383
+ return ToolResult(
384
+ success=True,
385
+ output=f"Task {task_id} cancelled",
386
+ metadata={"task_id": task_id, "status": "cancelled"},
387
+ )
388
+
389
+
390
+ class TaskCoordinatorTool(Tool):
391
+ """
392
+ Coordinate multiple sub-agents working on related tasks.
393
+
394
+ Higher-level tool for managing multiple sub-agents:
395
+ - Spawn multiple tasks at once
396
+ - Wait for all to complete
397
+ - Collect and merge results
398
+ """
399
+
400
+ @property
401
+ def name(self) -> str:
402
+ return "coordinate"
403
+
404
+ @property
405
+ def description(self) -> str:
406
+ return "Coordinate multiple sub-agents. Spawn tasks in parallel and collect results."
407
+
408
+ @property
409
+ def parameters(self) -> Dict[str, Any]:
410
+ return {
411
+ "type": "object",
412
+ "properties": {
413
+ "tasks": {
414
+ "type": "array",
415
+ "description": "Array of task descriptions to run in parallel",
416
+ "items": {"type": "string"},
417
+ },
418
+ "wait": {
419
+ "type": "boolean",
420
+ "description": "Wait for all tasks to complete (default: true)",
421
+ },
422
+ "timeout": {
423
+ "type": "integer",
424
+ "description": "Timeout in seconds for waiting (default: 120)",
425
+ },
426
+ },
427
+ "required": ["tasks"],
428
+ }
429
+
430
+ async def execute(self, args: Dict[str, Any], ctx: ToolContext) -> ToolResult:
431
+ tasks = args.get("tasks", [])
432
+ wait = args.get("wait", True)
433
+ timeout = args.get("timeout", 120)
434
+
435
+ if not tasks:
436
+ return ToolResult(success=False, output="", error="No tasks provided")
437
+
438
+ # Create sub-agent tool
439
+ sub_agent = SubAgentTool()
440
+
441
+ # Spawn all tasks
442
+ task_ids = []
443
+ for task_desc in tasks:
444
+ result = await sub_agent.execute({"action": "spawn", "task": task_desc}, ctx)
445
+ if result.success and result.metadata:
446
+ task_ids.append(result.metadata.get("task_id"))
447
+
448
+ if not wait:
449
+ return ToolResult(
450
+ success=True,
451
+ output=f"Spawned {len(task_ids)} tasks:\n"
452
+ + "\n".join(f" - {tid}" for tid in task_ids),
453
+ metadata={"task_ids": task_ids},
454
+ )
455
+
456
+ # Wait for all tasks
457
+ results = []
458
+ for task_id in task_ids:
459
+ result = await sub_agent.execute(
460
+ {"action": "wait", "task_id": task_id, "timeout": timeout}, ctx
461
+ )
462
+ results.append(
463
+ {
464
+ "task_id": task_id,
465
+ "success": result.success,
466
+ "output": result.output,
467
+ "error": result.error,
468
+ }
469
+ )
470
+
471
+ # Format combined results
472
+ output_lines = [f"Completed {len(results)} tasks:\n"]
473
+
474
+ for r in results:
475
+ status = "✓" if r["success"] else "✗"
476
+ output_lines.append(f"{status} [{r['task_id']}]")
477
+ if r["output"]:
478
+ # Indent output
479
+ for line in r["output"].split("\n")[:5]:
480
+ output_lines.append(f" {line}")
481
+ if r["error"]:
482
+ output_lines.append(f" Error: {r['error']}")
483
+ output_lines.append("")
484
+
485
+ all_success = all(r["success"] for r in results)
486
+
487
+ return ToolResult(
488
+ success=all_success,
489
+ output="\n".join(output_lines),
490
+ error=None if all_success else "Some tasks failed",
491
+ metadata={
492
+ "task_ids": task_ids,
493
+ "success_count": sum(1 for r in results if r["success"]),
494
+ "failure_count": sum(1 for r in results if not r["success"]),
495
+ },
496
+ )