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.
- superqode/__init__.py +33 -0
- superqode/acp/__init__.py +23 -0
- superqode/acp/client.py +913 -0
- superqode/acp/permission_screen.py +457 -0
- superqode/acp/types.py +480 -0
- superqode/acp_discovery.py +856 -0
- superqode/agent/__init__.py +22 -0
- superqode/agent/edit_strategies.py +334 -0
- superqode/agent/loop.py +892 -0
- superqode/agent/qe_report_templates.py +39 -0
- superqode/agent/system_prompts.py +353 -0
- superqode/agent_output.py +721 -0
- superqode/agent_stream.py +953 -0
- superqode/agents/__init__.py +59 -0
- superqode/agents/acp_registry.py +305 -0
- superqode/agents/client.py +249 -0
- superqode/agents/data/augmentcode.com.toml +51 -0
- superqode/agents/data/cagent.dev.toml +51 -0
- superqode/agents/data/claude.com.toml +60 -0
- superqode/agents/data/codeassistant.dev.toml +51 -0
- superqode/agents/data/codex.openai.com.toml +57 -0
- superqode/agents/data/fastagent.ai.toml +66 -0
- superqode/agents/data/geminicli.com.toml +77 -0
- superqode/agents/data/goose.block.xyz.toml +54 -0
- superqode/agents/data/junie.jetbrains.com.toml +56 -0
- superqode/agents/data/kimi.moonshot.cn.toml +57 -0
- superqode/agents/data/llmlingagent.dev.toml +51 -0
- superqode/agents/data/molt.bot.toml +49 -0
- superqode/agents/data/opencode.ai.toml +60 -0
- superqode/agents/data/stakpak.dev.toml +51 -0
- superqode/agents/data/vtcode.dev.toml +51 -0
- superqode/agents/discovery.py +266 -0
- superqode/agents/messaging.py +160 -0
- superqode/agents/persona.py +166 -0
- superqode/agents/registry.py +421 -0
- superqode/agents/schema.py +72 -0
- superqode/agents/unified.py +367 -0
- superqode/app/__init__.py +111 -0
- superqode/app/constants.py +314 -0
- superqode/app/css.py +366 -0
- superqode/app/models.py +118 -0
- superqode/app/suggester.py +125 -0
- superqode/app/widgets.py +1591 -0
- superqode/app_enhanced.py +399 -0
- superqode/app_main.py +17187 -0
- superqode/approval.py +312 -0
- superqode/atomic.py +296 -0
- superqode/commands/__init__.py +1 -0
- superqode/commands/acp.py +965 -0
- superqode/commands/agents.py +180 -0
- superqode/commands/auth.py +278 -0
- superqode/commands/config.py +374 -0
- superqode/commands/init.py +826 -0
- superqode/commands/providers.py +819 -0
- superqode/commands/qe.py +1145 -0
- superqode/commands/roles.py +380 -0
- superqode/commands/serve.py +172 -0
- superqode/commands/suggestions.py +127 -0
- superqode/commands/superqe.py +460 -0
- superqode/config/__init__.py +51 -0
- superqode/config/loader.py +812 -0
- superqode/config/schema.py +498 -0
- superqode/core/__init__.py +111 -0
- superqode/core/roles.py +281 -0
- superqode/danger.py +386 -0
- superqode/data/superqode-template.yaml +1522 -0
- superqode/design_system.py +1080 -0
- superqode/dialogs/__init__.py +6 -0
- superqode/dialogs/base.py +39 -0
- superqode/dialogs/model.py +130 -0
- superqode/dialogs/provider.py +870 -0
- superqode/diff_view.py +919 -0
- superqode/enterprise.py +21 -0
- superqode/evaluation/__init__.py +25 -0
- superqode/evaluation/adapters.py +93 -0
- superqode/evaluation/behaviors.py +89 -0
- superqode/evaluation/engine.py +209 -0
- superqode/evaluation/scenarios.py +96 -0
- superqode/execution/__init__.py +36 -0
- superqode/execution/linter.py +538 -0
- superqode/execution/modes.py +347 -0
- superqode/execution/resolver.py +283 -0
- superqode/execution/runner.py +642 -0
- superqode/file_explorer.py +811 -0
- superqode/file_viewer.py +471 -0
- superqode/flash.py +183 -0
- superqode/guidance/__init__.py +58 -0
- superqode/guidance/config.py +203 -0
- superqode/guidance/prompts.py +71 -0
- superqode/harness/__init__.py +54 -0
- superqode/harness/accelerator.py +291 -0
- superqode/harness/config.py +319 -0
- superqode/harness/validator.py +147 -0
- superqode/history.py +279 -0
- superqode/integrations/superopt_runner.py +124 -0
- superqode/logging/__init__.py +49 -0
- superqode/logging/adapters.py +219 -0
- superqode/logging/formatter.py +923 -0
- superqode/logging/integration.py +341 -0
- superqode/logging/sinks.py +170 -0
- superqode/logging/unified_log.py +417 -0
- superqode/lsp/__init__.py +26 -0
- superqode/lsp/client.py +544 -0
- superqode/main.py +1069 -0
- superqode/mcp/__init__.py +89 -0
- superqode/mcp/auth_storage.py +380 -0
- superqode/mcp/client.py +1236 -0
- superqode/mcp/config.py +319 -0
- superqode/mcp/integration.py +337 -0
- superqode/mcp/oauth.py +436 -0
- superqode/mcp/oauth_callback.py +385 -0
- superqode/mcp/types.py +290 -0
- superqode/memory/__init__.py +31 -0
- superqode/memory/feedback.py +342 -0
- superqode/memory/store.py +522 -0
- superqode/notifications.py +369 -0
- superqode/optimization/__init__.py +5 -0
- superqode/optimization/config.py +33 -0
- superqode/permissions/__init__.py +25 -0
- superqode/permissions/rules.py +488 -0
- superqode/plan.py +323 -0
- superqode/providers/__init__.py +33 -0
- superqode/providers/gateway/__init__.py +165 -0
- superqode/providers/gateway/base.py +228 -0
- superqode/providers/gateway/litellm_gateway.py +1170 -0
- superqode/providers/gateway/openresponses_gateway.py +436 -0
- superqode/providers/health.py +297 -0
- superqode/providers/huggingface/__init__.py +74 -0
- superqode/providers/huggingface/downloader.py +472 -0
- superqode/providers/huggingface/endpoints.py +442 -0
- superqode/providers/huggingface/hub.py +531 -0
- superqode/providers/huggingface/inference.py +394 -0
- superqode/providers/huggingface/transformers_runner.py +516 -0
- superqode/providers/local/__init__.py +100 -0
- superqode/providers/local/base.py +438 -0
- superqode/providers/local/discovery.py +418 -0
- superqode/providers/local/lmstudio.py +256 -0
- superqode/providers/local/mlx.py +457 -0
- superqode/providers/local/ollama.py +486 -0
- superqode/providers/local/sglang.py +268 -0
- superqode/providers/local/tgi.py +260 -0
- superqode/providers/local/tool_support.py +477 -0
- superqode/providers/local/vllm.py +258 -0
- superqode/providers/manager.py +1338 -0
- superqode/providers/models.py +1016 -0
- superqode/providers/models_dev.py +578 -0
- superqode/providers/openresponses/__init__.py +87 -0
- superqode/providers/openresponses/converters/__init__.py +17 -0
- superqode/providers/openresponses/converters/messages.py +343 -0
- superqode/providers/openresponses/converters/tools.py +268 -0
- superqode/providers/openresponses/schema/__init__.py +56 -0
- superqode/providers/openresponses/schema/models.py +585 -0
- superqode/providers/openresponses/streaming/__init__.py +5 -0
- superqode/providers/openresponses/streaming/parser.py +338 -0
- superqode/providers/openresponses/tools/__init__.py +21 -0
- superqode/providers/openresponses/tools/apply_patch.py +352 -0
- superqode/providers/openresponses/tools/code_interpreter.py +290 -0
- superqode/providers/openresponses/tools/file_search.py +333 -0
- superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
- superqode/providers/registry.py +716 -0
- superqode/providers/usage.py +332 -0
- superqode/pure_mode.py +384 -0
- superqode/qr/__init__.py +23 -0
- superqode/qr/dashboard.py +781 -0
- superqode/qr/generator.py +1018 -0
- superqode/qr/templates.py +135 -0
- superqode/safety/__init__.py +41 -0
- superqode/safety/sandbox.py +413 -0
- superqode/safety/warnings.py +256 -0
- superqode/server/__init__.py +33 -0
- superqode/server/lsp_server.py +775 -0
- superqode/server/web.py +250 -0
- superqode/session/__init__.py +25 -0
- superqode/session/persistence.py +580 -0
- superqode/session/sharing.py +477 -0
- superqode/session.py +475 -0
- superqode/sidebar.py +2991 -0
- superqode/stream_view.py +648 -0
- superqode/styles/__init__.py +3 -0
- superqode/superqe/__init__.py +184 -0
- superqode/superqe/acp_runner.py +1064 -0
- superqode/superqe/constitution/__init__.py +62 -0
- superqode/superqe/constitution/evaluator.py +308 -0
- superqode/superqe/constitution/loader.py +432 -0
- superqode/superqe/constitution/schema.py +250 -0
- superqode/superqe/events.py +591 -0
- superqode/superqe/frameworks/__init__.py +65 -0
- superqode/superqe/frameworks/base.py +234 -0
- superqode/superqe/frameworks/e2e.py +263 -0
- superqode/superqe/frameworks/executor.py +237 -0
- superqode/superqe/frameworks/javascript.py +409 -0
- superqode/superqe/frameworks/python.py +373 -0
- superqode/superqe/frameworks/registry.py +92 -0
- superqode/superqe/mcp_tools/__init__.py +47 -0
- superqode/superqe/mcp_tools/core_tools.py +418 -0
- superqode/superqe/mcp_tools/registry.py +230 -0
- superqode/superqe/mcp_tools/testing_tools.py +167 -0
- superqode/superqe/noise.py +89 -0
- superqode/superqe/orchestrator.py +778 -0
- superqode/superqe/roles.py +609 -0
- superqode/superqe/session.py +713 -0
- superqode/superqe/skills/__init__.py +57 -0
- superqode/superqe/skills/base.py +106 -0
- superqode/superqe/skills/core_skills.py +899 -0
- superqode/superqe/skills/registry.py +90 -0
- superqode/superqe/verifier.py +101 -0
- superqode/superqe_cli.py +76 -0
- superqode/tool_call.py +358 -0
- superqode/tools/__init__.py +93 -0
- superqode/tools/agent_tools.py +496 -0
- superqode/tools/base.py +324 -0
- superqode/tools/batch_tool.py +133 -0
- superqode/tools/diagnostics.py +311 -0
- superqode/tools/edit_tools.py +653 -0
- superqode/tools/enhanced_base.py +515 -0
- superqode/tools/file_tools.py +269 -0
- superqode/tools/file_tracking.py +45 -0
- superqode/tools/lsp_tools.py +610 -0
- superqode/tools/network_tools.py +350 -0
- superqode/tools/permissions.py +400 -0
- superqode/tools/question_tool.py +324 -0
- superqode/tools/search_tools.py +598 -0
- superqode/tools/shell_tools.py +259 -0
- superqode/tools/todo_tools.py +121 -0
- superqode/tools/validation.py +80 -0
- superqode/tools/web_tools.py +639 -0
- superqode/tui.py +1152 -0
- superqode/tui_integration.py +875 -0
- superqode/tui_widgets/__init__.py +27 -0
- superqode/tui_widgets/widgets/__init__.py +18 -0
- superqode/tui_widgets/widgets/progress.py +185 -0
- superqode/tui_widgets/widgets/tool_display.py +188 -0
- superqode/undo_manager.py +574 -0
- superqode/utils/__init__.py +5 -0
- superqode/utils/error_handling.py +323 -0
- superqode/utils/fuzzy.py +257 -0
- superqode/widgets/__init__.py +477 -0
- superqode/widgets/agent_collab.py +390 -0
- superqode/widgets/agent_store.py +936 -0
- superqode/widgets/agent_switcher.py +395 -0
- superqode/widgets/animation_manager.py +284 -0
- superqode/widgets/code_context.py +356 -0
- superqode/widgets/command_palette.py +412 -0
- superqode/widgets/connection_status.py +537 -0
- superqode/widgets/conversation_history.py +470 -0
- superqode/widgets/diff_indicator.py +155 -0
- superqode/widgets/enhanced_status_bar.py +385 -0
- superqode/widgets/enhanced_toast.py +476 -0
- superqode/widgets/file_browser.py +809 -0
- superqode/widgets/file_reference.py +585 -0
- superqode/widgets/issue_timeline.py +340 -0
- superqode/widgets/leader_key.py +264 -0
- superqode/widgets/mode_switcher.py +445 -0
- superqode/widgets/model_picker.py +234 -0
- superqode/widgets/permission_preview.py +1205 -0
- superqode/widgets/prompt.py +358 -0
- superqode/widgets/provider_connect.py +725 -0
- superqode/widgets/pty_shell.py +587 -0
- superqode/widgets/qe_dashboard.py +321 -0
- superqode/widgets/resizable_sidebar.py +377 -0
- superqode/widgets/response_changes.py +218 -0
- superqode/widgets/response_display.py +528 -0
- superqode/widgets/rich_tool_display.py +613 -0
- superqode/widgets/sidebar_panels.py +1180 -0
- superqode/widgets/slash_complete.py +356 -0
- superqode/widgets/split_view.py +612 -0
- superqode/widgets/status_bar.py +273 -0
- superqode/widgets/superqode_display.py +786 -0
- superqode/widgets/thinking_display.py +815 -0
- superqode/widgets/throbber.py +87 -0
- superqode/widgets/toast.py +206 -0
- superqode/widgets/unified_output.py +1073 -0
- superqode/workspace/__init__.py +75 -0
- superqode/workspace/artifacts.py +472 -0
- superqode/workspace/coordinator.py +353 -0
- superqode/workspace/diff_tracker.py +429 -0
- superqode/workspace/git_guard.py +373 -0
- superqode/workspace/git_snapshot.py +526 -0
- superqode/workspace/manager.py +750 -0
- superqode/workspace/snapshot.py +357 -0
- superqode/workspace/watcher.py +535 -0
- superqode/workspace/worktree.py +440 -0
- superqode-0.1.5.dist-info/METADATA +204 -0
- superqode-0.1.5.dist-info/RECORD +288 -0
- superqode-0.1.5.dist-info/WHEEL +5 -0
- superqode-0.1.5.dist-info/entry_points.txt +3 -0
- superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
- 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
|
+
)
|