agentpool 2.1.9__py3-none-any.whl → 2.5.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.
- acp/__init__.py +13 -4
- acp/acp_requests.py +20 -77
- acp/agent/connection.py +8 -0
- acp/agent/implementations/debug_server/debug_server.py +6 -2
- acp/agent/protocol.py +6 -0
- acp/bridge/README.md +15 -2
- acp/bridge/__init__.py +3 -2
- acp/bridge/__main__.py +60 -19
- acp/bridge/ws_server.py +173 -0
- acp/bridge/ws_server_cli.py +89 -0
- acp/client/connection.py +38 -29
- acp/client/implementations/default_client.py +3 -2
- acp/client/implementations/headless_client.py +2 -2
- acp/connection.py +2 -2
- acp/notifications.py +20 -50
- acp/schema/__init__.py +2 -0
- acp/schema/agent_responses.py +21 -0
- acp/schema/client_requests.py +3 -3
- acp/schema/session_state.py +63 -29
- acp/stdio.py +39 -9
- acp/task/supervisor.py +2 -2
- acp/transports.py +362 -2
- acp/utils.py +17 -4
- agentpool/__init__.py +6 -1
- agentpool/agents/__init__.py +2 -0
- agentpool/agents/acp_agent/acp_agent.py +407 -277
- agentpool/agents/acp_agent/acp_converters.py +196 -38
- agentpool/agents/acp_agent/client_handler.py +191 -26
- agentpool/agents/acp_agent/session_state.py +17 -6
- agentpool/agents/agent.py +607 -572
- agentpool/agents/agui_agent/__init__.py +0 -2
- agentpool/agents/agui_agent/agui_agent.py +176 -110
- agentpool/agents/agui_agent/agui_converters.py +0 -131
- agentpool/agents/agui_agent/helpers.py +3 -4
- agentpool/agents/base_agent.py +632 -17
- agentpool/agents/claude_code_agent/FORKING.md +191 -0
- agentpool/agents/claude_code_agent/__init__.py +13 -1
- agentpool/agents/claude_code_agent/claude_code_agent.py +1058 -291
- agentpool/agents/claude_code_agent/converters.py +74 -143
- agentpool/agents/claude_code_agent/history.py +474 -0
- agentpool/agents/claude_code_agent/models.py +77 -0
- agentpool/agents/claude_code_agent/static_info.py +100 -0
- agentpool/agents/claude_code_agent/usage.py +242 -0
- agentpool/agents/context.py +40 -0
- agentpool/agents/events/__init__.py +24 -0
- agentpool/agents/events/builtin_handlers.py +67 -1
- agentpool/agents/events/event_emitter.py +32 -2
- agentpool/agents/events/events.py +104 -3
- agentpool/agents/events/infer_info.py +145 -0
- agentpool/agents/events/processors.py +254 -0
- agentpool/agents/interactions.py +41 -6
- agentpool/agents/modes.py +67 -0
- agentpool/agents/slashed_agent.py +5 -4
- agentpool/agents/tool_call_accumulator.py +213 -0
- agentpool/agents/tool_wrapping.py +18 -6
- agentpool/common_types.py +56 -21
- agentpool/config_resources/__init__.py +38 -1
- agentpool/config_resources/acp_assistant.yml +2 -2
- agentpool/config_resources/agents.yml +3 -0
- agentpool/config_resources/agents_template.yml +1 -0
- agentpool/config_resources/claude_code_agent.yml +10 -6
- agentpool/config_resources/external_acp_agents.yml +2 -1
- agentpool/delegation/base_team.py +4 -30
- agentpool/delegation/pool.py +136 -289
- agentpool/delegation/team.py +58 -57
- agentpool/delegation/teamrun.py +51 -55
- agentpool/diagnostics/__init__.py +53 -0
- agentpool/diagnostics/lsp_manager.py +1593 -0
- agentpool/diagnostics/lsp_proxy.py +41 -0
- agentpool/diagnostics/lsp_proxy_script.py +229 -0
- agentpool/diagnostics/models.py +398 -0
- agentpool/functional/run.py +10 -4
- agentpool/mcp_server/__init__.py +0 -2
- agentpool/mcp_server/client.py +76 -32
- agentpool/mcp_server/conversions.py +54 -13
- agentpool/mcp_server/manager.py +34 -54
- agentpool/mcp_server/registries/official_registry_client.py +35 -1
- agentpool/mcp_server/tool_bridge.py +186 -139
- agentpool/messaging/__init__.py +0 -2
- agentpool/messaging/compaction.py +72 -197
- agentpool/messaging/connection_manager.py +11 -10
- agentpool/messaging/event_manager.py +5 -5
- agentpool/messaging/message_container.py +6 -30
- agentpool/messaging/message_history.py +99 -8
- agentpool/messaging/messagenode.py +52 -14
- agentpool/messaging/messages.py +54 -35
- agentpool/messaging/processing.py +12 -22
- agentpool/models/__init__.py +1 -1
- agentpool/models/acp_agents/base.py +6 -24
- agentpool/models/acp_agents/mcp_capable.py +126 -157
- agentpool/models/acp_agents/non_mcp.py +129 -95
- agentpool/models/agents.py +98 -76
- agentpool/models/agui_agents.py +1 -1
- agentpool/models/claude_code_agents.py +144 -19
- agentpool/models/file_parsing.py +0 -1
- agentpool/models/manifest.py +113 -50
- agentpool/prompts/conversion_manager.py +1 -1
- agentpool/prompts/prompts.py +5 -2
- agentpool/repomap.py +1 -1
- agentpool/resource_providers/__init__.py +11 -1
- agentpool/resource_providers/aggregating.py +56 -5
- agentpool/resource_providers/base.py +70 -4
- agentpool/resource_providers/codemode/code_executor.py +72 -5
- agentpool/resource_providers/codemode/helpers.py +2 -2
- agentpool/resource_providers/codemode/provider.py +64 -12
- agentpool/resource_providers/codemode/remote_mcp_execution.py +2 -2
- agentpool/resource_providers/codemode/remote_provider.py +9 -12
- agentpool/resource_providers/filtering.py +3 -1
- agentpool/resource_providers/mcp_provider.py +89 -12
- agentpool/resource_providers/plan_provider.py +228 -46
- agentpool/resource_providers/pool.py +7 -3
- agentpool/resource_providers/resource_info.py +111 -0
- agentpool/resource_providers/static.py +4 -2
- agentpool/sessions/__init__.py +4 -1
- agentpool/sessions/manager.py +33 -5
- agentpool/sessions/models.py +59 -6
- agentpool/sessions/protocol.py +28 -0
- agentpool/sessions/session.py +11 -55
- agentpool/skills/registry.py +13 -8
- agentpool/storage/manager.py +572 -49
- agentpool/talk/registry.py +4 -4
- agentpool/talk/talk.py +9 -10
- agentpool/testing.py +538 -20
- agentpool/tool_impls/__init__.py +6 -0
- agentpool/tool_impls/agent_cli/__init__.py +42 -0
- agentpool/tool_impls/agent_cli/tool.py +95 -0
- agentpool/tool_impls/bash/__init__.py +64 -0
- agentpool/tool_impls/bash/helpers.py +35 -0
- agentpool/tool_impls/bash/tool.py +171 -0
- agentpool/tool_impls/delete_path/__init__.py +70 -0
- agentpool/tool_impls/delete_path/tool.py +142 -0
- agentpool/tool_impls/download_file/__init__.py +80 -0
- agentpool/tool_impls/download_file/tool.py +183 -0
- agentpool/tool_impls/execute_code/__init__.py +55 -0
- agentpool/tool_impls/execute_code/tool.py +163 -0
- agentpool/tool_impls/grep/__init__.py +80 -0
- agentpool/tool_impls/grep/tool.py +200 -0
- agentpool/tool_impls/list_directory/__init__.py +73 -0
- agentpool/tool_impls/list_directory/tool.py +197 -0
- agentpool/tool_impls/question/__init__.py +42 -0
- agentpool/tool_impls/question/tool.py +127 -0
- agentpool/tool_impls/read/__init__.py +104 -0
- agentpool/tool_impls/read/tool.py +305 -0
- agentpool/tools/__init__.py +2 -1
- agentpool/tools/base.py +114 -34
- agentpool/tools/manager.py +57 -1
- agentpool/ui/base.py +2 -2
- agentpool/ui/mock_provider.py +2 -2
- agentpool/ui/stdlib_provider.py +2 -2
- agentpool/utils/file_watcher.py +269 -0
- agentpool/utils/identifiers.py +121 -0
- agentpool/utils/pydantic_ai_helpers.py +46 -0
- agentpool/utils/streams.py +616 -2
- agentpool/utils/subprocess_utils.py +155 -0
- agentpool/utils/token_breakdown.py +461 -0
- agentpool/vfs_registry.py +7 -2
- {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/METADATA +41 -27
- agentpool-2.5.0.dist-info/RECORD +579 -0
- {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/WHEEL +1 -1
- agentpool_cli/__main__.py +24 -0
- agentpool_cli/create.py +1 -1
- agentpool_cli/serve_acp.py +100 -21
- agentpool_cli/serve_agui.py +87 -0
- agentpool_cli/serve_opencode.py +119 -0
- agentpool_cli/ui.py +557 -0
- agentpool_commands/__init__.py +42 -5
- agentpool_commands/agents.py +75 -2
- agentpool_commands/history.py +62 -0
- agentpool_commands/mcp.py +176 -0
- agentpool_commands/models.py +56 -3
- agentpool_commands/pool.py +260 -0
- agentpool_commands/session.py +1 -1
- agentpool_commands/text_sharing/__init__.py +119 -0
- agentpool_commands/text_sharing/base.py +123 -0
- agentpool_commands/text_sharing/github_gist.py +80 -0
- agentpool_commands/text_sharing/opencode.py +462 -0
- agentpool_commands/text_sharing/paste_rs.py +59 -0
- agentpool_commands/text_sharing/pastebin.py +116 -0
- agentpool_commands/text_sharing/shittycodingagent.py +112 -0
- agentpool_commands/tools.py +57 -0
- agentpool_commands/utils.py +80 -30
- agentpool_config/__init__.py +30 -2
- agentpool_config/agentpool_tools.py +498 -0
- agentpool_config/builtin_tools.py +77 -22
- agentpool_config/commands.py +24 -1
- agentpool_config/compaction.py +258 -0
- agentpool_config/converters.py +1 -1
- agentpool_config/event_handlers.py +42 -0
- agentpool_config/events.py +1 -1
- agentpool_config/forward_targets.py +1 -4
- agentpool_config/jinja.py +3 -3
- agentpool_config/mcp_server.py +132 -6
- agentpool_config/nodes.py +1 -1
- agentpool_config/observability.py +44 -0
- agentpool_config/session.py +0 -3
- agentpool_config/storage.py +82 -38
- agentpool_config/task.py +3 -3
- agentpool_config/tools.py +11 -22
- agentpool_config/toolsets.py +109 -233
- agentpool_server/a2a_server/agent_worker.py +307 -0
- agentpool_server/a2a_server/server.py +23 -18
- agentpool_server/acp_server/acp_agent.py +234 -181
- agentpool_server/acp_server/commands/acp_commands.py +151 -156
- agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +18 -17
- agentpool_server/acp_server/event_converter.py +651 -0
- agentpool_server/acp_server/input_provider.py +53 -10
- agentpool_server/acp_server/server.py +24 -90
- agentpool_server/acp_server/session.py +173 -331
- agentpool_server/acp_server/session_manager.py +8 -34
- agentpool_server/agui_server/server.py +3 -1
- agentpool_server/mcp_server/server.py +5 -2
- agentpool_server/opencode_server/.rules +95 -0
- agentpool_server/opencode_server/ENDPOINTS.md +401 -0
- agentpool_server/opencode_server/OPENCODE_UI_TOOLS_COMPLETE.md +202 -0
- agentpool_server/opencode_server/__init__.py +19 -0
- agentpool_server/opencode_server/command_validation.py +172 -0
- agentpool_server/opencode_server/converters.py +975 -0
- agentpool_server/opencode_server/dependencies.py +24 -0
- agentpool_server/opencode_server/input_provider.py +421 -0
- agentpool_server/opencode_server/models/__init__.py +250 -0
- agentpool_server/opencode_server/models/agent.py +53 -0
- agentpool_server/opencode_server/models/app.py +72 -0
- agentpool_server/opencode_server/models/base.py +26 -0
- agentpool_server/opencode_server/models/common.py +23 -0
- agentpool_server/opencode_server/models/config.py +37 -0
- agentpool_server/opencode_server/models/events.py +821 -0
- agentpool_server/opencode_server/models/file.py +88 -0
- agentpool_server/opencode_server/models/mcp.py +44 -0
- agentpool_server/opencode_server/models/message.py +179 -0
- agentpool_server/opencode_server/models/parts.py +323 -0
- agentpool_server/opencode_server/models/provider.py +81 -0
- agentpool_server/opencode_server/models/pty.py +43 -0
- agentpool_server/opencode_server/models/question.py +56 -0
- agentpool_server/opencode_server/models/session.py +111 -0
- agentpool_server/opencode_server/routes/__init__.py +29 -0
- agentpool_server/opencode_server/routes/agent_routes.py +473 -0
- agentpool_server/opencode_server/routes/app_routes.py +202 -0
- agentpool_server/opencode_server/routes/config_routes.py +302 -0
- agentpool_server/opencode_server/routes/file_routes.py +571 -0
- agentpool_server/opencode_server/routes/global_routes.py +94 -0
- agentpool_server/opencode_server/routes/lsp_routes.py +319 -0
- agentpool_server/opencode_server/routes/message_routes.py +761 -0
- agentpool_server/opencode_server/routes/permission_routes.py +63 -0
- agentpool_server/opencode_server/routes/pty_routes.py +300 -0
- agentpool_server/opencode_server/routes/question_routes.py +128 -0
- agentpool_server/opencode_server/routes/session_routes.py +1276 -0
- agentpool_server/opencode_server/routes/tui_routes.py +139 -0
- agentpool_server/opencode_server/server.py +475 -0
- agentpool_server/opencode_server/state.py +151 -0
- agentpool_server/opencode_server/time_utils.py +8 -0
- agentpool_storage/__init__.py +12 -0
- agentpool_storage/base.py +184 -2
- agentpool_storage/claude_provider/ARCHITECTURE.md +433 -0
- agentpool_storage/claude_provider/__init__.py +42 -0
- agentpool_storage/claude_provider/provider.py +1089 -0
- agentpool_storage/file_provider.py +278 -15
- agentpool_storage/memory_provider.py +193 -12
- agentpool_storage/models.py +3 -0
- agentpool_storage/opencode_provider/ARCHITECTURE.md +386 -0
- agentpool_storage/opencode_provider/__init__.py +16 -0
- agentpool_storage/opencode_provider/helpers.py +414 -0
- agentpool_storage/opencode_provider/provider.py +895 -0
- agentpool_storage/project_store.py +325 -0
- agentpool_storage/session_store.py +26 -6
- agentpool_storage/sql_provider/__init__.py +4 -2
- agentpool_storage/sql_provider/models.py +48 -0
- agentpool_storage/sql_provider/sql_provider.py +269 -3
- agentpool_storage/sql_provider/utils.py +12 -13
- agentpool_storage/zed_provider/__init__.py +16 -0
- agentpool_storage/zed_provider/helpers.py +281 -0
- agentpool_storage/zed_provider/models.py +130 -0
- agentpool_storage/zed_provider/provider.py +442 -0
- agentpool_storage/zed_provider.py +803 -0
- agentpool_toolsets/__init__.py +0 -2
- agentpool_toolsets/builtin/__init__.py +2 -12
- agentpool_toolsets/builtin/code.py +96 -57
- agentpool_toolsets/builtin/debug.py +118 -48
- agentpool_toolsets/builtin/execution_environment.py +115 -230
- agentpool_toolsets/builtin/file_edit/file_edit.py +115 -7
- agentpool_toolsets/builtin/skills.py +9 -4
- agentpool_toolsets/builtin/subagent_tools.py +64 -51
- agentpool_toolsets/builtin/workers.py +4 -2
- agentpool_toolsets/composio_toolset.py +2 -2
- agentpool_toolsets/entry_points.py +3 -1
- agentpool_toolsets/fsspec_toolset/__init__.py +13 -1
- agentpool_toolsets/fsspec_toolset/diagnostics.py +860 -73
- agentpool_toolsets/fsspec_toolset/grep.py +99 -7
- agentpool_toolsets/fsspec_toolset/helpers.py +3 -2
- agentpool_toolsets/fsspec_toolset/image_utils.py +161 -0
- agentpool_toolsets/fsspec_toolset/toolset.py +500 -95
- agentpool_toolsets/mcp_discovery/__init__.py +5 -0
- agentpool_toolsets/mcp_discovery/data/mcp_servers.parquet +0 -0
- agentpool_toolsets/mcp_discovery/toolset.py +511 -0
- agentpool_toolsets/mcp_run_toolset.py +87 -12
- agentpool_toolsets/notifications.py +33 -33
- agentpool_toolsets/openapi.py +3 -1
- agentpool_toolsets/search_toolset.py +3 -1
- agentpool-2.1.9.dist-info/RECORD +0 -474
- agentpool_config/resources.py +0 -33
- agentpool_server/acp_server/acp_tools.py +0 -43
- agentpool_server/acp_server/commands/spawn.py +0 -210
- agentpool_storage/text_log_provider.py +0 -275
- agentpool_toolsets/builtin/agent_management.py +0 -239
- agentpool_toolsets/builtin/chain.py +0 -288
- agentpool_toolsets/builtin/history.py +0 -36
- agentpool_toolsets/builtin/integration.py +0 -85
- agentpool_toolsets/builtin/tool_management.py +0 -90
- agentpool_toolsets/builtin/user_interaction.py +0 -52
- agentpool_toolsets/semantic_memory_toolset.py +0 -536
- {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/entry_points.txt +0 -0
- {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
"""Provider for
|
|
1
|
+
"""Provider for process management tools with event emission."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
from exxec.events import OutputEvent, ProcessCompletedEvent, ProcessErrorEvent, ProcessStartedEvent
|
|
5
|
+
import re
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
9
7
|
|
|
10
8
|
from agentpool import log
|
|
11
9
|
from agentpool.agents.context import AgentContext # noqa: TC001
|
|
@@ -16,26 +14,29 @@ logger = log.get_logger(__name__)
|
|
|
16
14
|
|
|
17
15
|
|
|
18
16
|
if TYPE_CHECKING:
|
|
17
|
+
from collections.abc import Sequence
|
|
18
|
+
|
|
19
19
|
from exxec import ExecutionEnvironment
|
|
20
20
|
|
|
21
21
|
from agentpool.tools.base import Tool
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
class
|
|
25
|
-
"""Provider for
|
|
24
|
+
class ProcessManagementTools(ResourceProvider):
|
|
25
|
+
"""Provider for background process management tools.
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
Provides tools for starting, monitoring, and controlling background processes.
|
|
28
|
+
Uses any ExecutionEnvironment backend and emits events via AgentContext.
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
For code execution and bash commands, use the standalone tools:
|
|
31
|
+
- agentpool.tool_impls.bash.BashTool
|
|
32
|
+
- agentpool.tool_impls.execute_code.ExecuteCodeTool
|
|
32
33
|
"""
|
|
33
34
|
|
|
34
|
-
def __init__(self, env: ExecutionEnvironment | None = None, name: str = "
|
|
35
|
-
"""Initialize
|
|
35
|
+
def __init__(self, env: ExecutionEnvironment | None = None, name: str = "process") -> None:
|
|
36
|
+
"""Initialize process management toolset.
|
|
36
37
|
|
|
37
38
|
Args:
|
|
38
|
-
env: Execution environment to use (defaults to
|
|
39
|
+
env: Execution environment to use (defaults to agent.env)
|
|
39
40
|
name: The name of the toolset
|
|
40
41
|
"""
|
|
41
42
|
super().__init__(name=name)
|
|
@@ -51,12 +52,8 @@ class ExecutionEnvironmentTools(ResourceProvider):
|
|
|
51
52
|
return self._env
|
|
52
53
|
return agent_ctx.agent.env
|
|
53
54
|
|
|
54
|
-
async def get_tools(self) ->
|
|
55
|
+
async def get_tools(self) -> Sequence[Tool]:
|
|
55
56
|
return [
|
|
56
|
-
# Code execution tools
|
|
57
|
-
self.create_tool(self.execute_code, category="execute"),
|
|
58
|
-
self.create_tool(self.execute_command, category="execute", open_world=True),
|
|
59
|
-
# Process management tools
|
|
60
57
|
self.create_tool(self.start_process, category="execute", open_world=True),
|
|
61
58
|
self.create_tool(
|
|
62
59
|
self.get_process_output, category="execute", read_only=True, idempotent=True
|
|
@@ -71,142 +68,6 @@ class ExecutionEnvironmentTools(ResourceProvider):
|
|
|
71
68
|
),
|
|
72
69
|
]
|
|
73
70
|
|
|
74
|
-
async def execute_code(self, agent_ctx: AgentContext, code: str) -> dict[str, Any]: # noqa: D417
|
|
75
|
-
"""Execute Python code and return the result.
|
|
76
|
-
|
|
77
|
-
Args:
|
|
78
|
-
code: Python code to execute
|
|
79
|
-
"""
|
|
80
|
-
process_id: str | None = None
|
|
81
|
-
output_parts: list[str] = []
|
|
82
|
-
exit_code: int | None = None
|
|
83
|
-
error_msg: str | None = None
|
|
84
|
-
duration: float | None = None
|
|
85
|
-
try:
|
|
86
|
-
async for event in self.get_env(agent_ctx).stream_code(code):
|
|
87
|
-
match event:
|
|
88
|
-
case ProcessStartedEvent(process_id=pid, command=cmd):
|
|
89
|
-
process_id = pid # save for later on.
|
|
90
|
-
await agent_ctx.events.process_started(pid, cmd, success=True)
|
|
91
|
-
case OutputEvent(data=data):
|
|
92
|
-
output_parts.append(data)
|
|
93
|
-
if process_id:
|
|
94
|
-
await agent_ctx.events.process_output(process_id, data)
|
|
95
|
-
case ProcessCompletedEvent(exit_code=code_, duration=dur):
|
|
96
|
-
exit_code = code_
|
|
97
|
-
duration = dur
|
|
98
|
-
out = "".join(output_parts)
|
|
99
|
-
if process_id:
|
|
100
|
-
await agent_ctx.events.process_exit(
|
|
101
|
-
process_id, exit_code, final_output=out
|
|
102
|
-
)
|
|
103
|
-
case ProcessErrorEvent(error=err, exit_code=code_):
|
|
104
|
-
error_msg = err
|
|
105
|
-
exit_code = code_
|
|
106
|
-
if process_id:
|
|
107
|
-
await agent_ctx.events.process_exit(
|
|
108
|
-
process_id, exit_code or 1, final_output=err
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
combined_output = "".join(output_parts)
|
|
112
|
-
if error_msg:
|
|
113
|
-
return {"error": error_msg, "output": combined_output, "exit_code": exit_code}
|
|
114
|
-
|
|
115
|
-
except Exception as e: # noqa: BLE001
|
|
116
|
-
error_id = process_id or f"code_{uuid.uuid4().hex[:8]}"
|
|
117
|
-
await agent_ctx.events.process_started(
|
|
118
|
-
error_id, "execute_code", success=False, error=str(e)
|
|
119
|
-
)
|
|
120
|
-
return {"error": f"Error executing code: {e}"}
|
|
121
|
-
else:
|
|
122
|
-
return {"output": combined_output, "exit_code": exit_code, "duration": duration}
|
|
123
|
-
|
|
124
|
-
async def execute_command( # noqa: PLR0915, D417
|
|
125
|
-
self,
|
|
126
|
-
agent_ctx: AgentContext,
|
|
127
|
-
command: str,
|
|
128
|
-
output_limit: int | None = None,
|
|
129
|
-
) -> dict[str, Any]:
|
|
130
|
-
"""Execute a shell command and return the output.
|
|
131
|
-
|
|
132
|
-
Args:
|
|
133
|
-
command: Shell command to execute
|
|
134
|
-
output_limit: Maximum bytes of output to return
|
|
135
|
-
"""
|
|
136
|
-
# process_id comes from exxec events (is terminal_id when using ACP)
|
|
137
|
-
process_id: str | None = None
|
|
138
|
-
stdout_parts: list[str] = []
|
|
139
|
-
stderr_parts: list[str] = []
|
|
140
|
-
exit_code: int | None = None
|
|
141
|
-
error_msg: str | None = None
|
|
142
|
-
duration: float | None = None
|
|
143
|
-
try:
|
|
144
|
-
async for event in self.get_env(agent_ctx).stream_command(command):
|
|
145
|
-
match event:
|
|
146
|
-
case ProcessStartedEvent(process_id=pid, command=cmd):
|
|
147
|
-
process_id = pid
|
|
148
|
-
if pid:
|
|
149
|
-
await agent_ctx.events.process_started(pid, cmd, success=True)
|
|
150
|
-
else:
|
|
151
|
-
logger.warning("ProcessStartedEvent missing process_id", command=cmd)
|
|
152
|
-
case OutputEvent(process_id=pid, data=data, stream=stream):
|
|
153
|
-
if stream == "stderr":
|
|
154
|
-
stderr_parts.append(data)
|
|
155
|
-
else:
|
|
156
|
-
stdout_parts.append(data)
|
|
157
|
-
if pid:
|
|
158
|
-
await agent_ctx.events.process_output(pid, data)
|
|
159
|
-
else:
|
|
160
|
-
logger.warning("OutputEvent missing process_id", stream=stream)
|
|
161
|
-
case ProcessCompletedEvent(process_id=pid, exit_code=code_, duration=dur):
|
|
162
|
-
exit_code = code_
|
|
163
|
-
duration = dur
|
|
164
|
-
combined = "".join(stdout_parts) + "".join(stderr_parts)
|
|
165
|
-
if pid:
|
|
166
|
-
await agent_ctx.events.process_exit(
|
|
167
|
-
pid, exit_code, final_output=combined
|
|
168
|
-
)
|
|
169
|
-
else:
|
|
170
|
-
msg = "ProcessCompletedEvent missing process_id,"
|
|
171
|
-
logger.warning(msg, exit_code=code_)
|
|
172
|
-
case ProcessErrorEvent(process_id=pid, error=err, exit_code=code_):
|
|
173
|
-
error_msg = err
|
|
174
|
-
exit_code = code_
|
|
175
|
-
|
|
176
|
-
stdout = "".join(stdout_parts)
|
|
177
|
-
stderr = "".join(stderr_parts)
|
|
178
|
-
# Apply output limit if specified
|
|
179
|
-
truncated = False
|
|
180
|
-
if output_limit:
|
|
181
|
-
if len(stdout.encode()) > output_limit:
|
|
182
|
-
out = stdout.encode()[-output_limit:].decode(errors="ignore")
|
|
183
|
-
stdout = "...[truncated]\n" + out
|
|
184
|
-
truncated = True
|
|
185
|
-
if len(stderr.encode()) > output_limit:
|
|
186
|
-
out = stderr.encode()[-output_limit:].decode(errors="ignore")
|
|
187
|
-
stderr = "...[truncated]\n" + out
|
|
188
|
-
truncated = True
|
|
189
|
-
if error_msg:
|
|
190
|
-
return {
|
|
191
|
-
"error": error_msg,
|
|
192
|
-
"stdout": stdout,
|
|
193
|
-
"stderr": stderr,
|
|
194
|
-
"exit_code": exit_code,
|
|
195
|
-
}
|
|
196
|
-
except Exception as e: # noqa: BLE001
|
|
197
|
-
# Use process_id from events if available, otherwise generate fallback
|
|
198
|
-
error_id = process_id or f"cmd_{uuid.uuid4().hex[:8]}"
|
|
199
|
-
await agent_ctx.events.process_started(error_id, command, success=False, error=str(e))
|
|
200
|
-
return {"success": False, "error": f"Error executing command: {e}"}
|
|
201
|
-
else:
|
|
202
|
-
return {
|
|
203
|
-
"stdout": stdout,
|
|
204
|
-
"stderr": stderr,
|
|
205
|
-
"exit_code": exit_code,
|
|
206
|
-
"duration": duration,
|
|
207
|
-
"truncated": truncated,
|
|
208
|
-
}
|
|
209
|
-
|
|
210
71
|
async def start_process( # noqa: D417
|
|
211
72
|
self,
|
|
212
73
|
agent_ctx: AgentContext,
|
|
@@ -215,7 +76,7 @@ class ExecutionEnvironmentTools(ResourceProvider):
|
|
|
215
76
|
cwd: str | None = None,
|
|
216
77
|
env: dict[str, str] | None = None,
|
|
217
78
|
output_limit: int | None = None,
|
|
218
|
-
) ->
|
|
79
|
+
) -> str:
|
|
219
80
|
"""Start a command in the background and return process ID.
|
|
220
81
|
|
|
221
82
|
Args:
|
|
@@ -238,73 +99,109 @@ class ExecutionEnvironmentTools(ResourceProvider):
|
|
|
238
99
|
|
|
239
100
|
except Exception as e: # noqa: BLE001
|
|
240
101
|
await agent_ctx.events.process_started("", command, success=False, error=str(e))
|
|
241
|
-
return
|
|
102
|
+
return f"Failed to start process: {e}"
|
|
242
103
|
else:
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
104
|
+
full_cmd = f"{command} {' '.join(args)}" if args else command
|
|
105
|
+
return f"Started background process {process_id}\nCommand: {full_cmd}"
|
|
106
|
+
|
|
107
|
+
async def get_process_output( # noqa: D417
|
|
108
|
+
self,
|
|
109
|
+
agent_ctx: AgentContext,
|
|
110
|
+
process_id: str,
|
|
111
|
+
filter_lines: str | None = None,
|
|
112
|
+
) -> str:
|
|
252
113
|
"""Get current output from a background process.
|
|
253
114
|
|
|
254
115
|
Args:
|
|
255
116
|
process_id: Process identifier from start_process
|
|
117
|
+
filter_lines: Optional regex pattern to filter output lines
|
|
118
|
+
(only matching lines returned)
|
|
256
119
|
"""
|
|
257
120
|
manager = self.get_env(agent_ctx).process_manager
|
|
258
121
|
try:
|
|
259
122
|
output = await manager.get_output(process_id)
|
|
260
123
|
await agent_ctx.events.process_output(process_id, output.combined or "")
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
124
|
+
|
|
125
|
+
combined = output.combined or ""
|
|
126
|
+
|
|
127
|
+
# Apply regex filter if specified
|
|
128
|
+
if filter_lines and combined:
|
|
129
|
+
try:
|
|
130
|
+
pattern = re.compile(filter_lines)
|
|
131
|
+
filtered_lines = [
|
|
132
|
+
line for line in combined.splitlines(keepends=True) if pattern.search(line)
|
|
133
|
+
]
|
|
134
|
+
combined = "".join(filtered_lines)
|
|
135
|
+
except re.error as regex_err:
|
|
136
|
+
return f"Invalid filter regex: {regex_err}"
|
|
137
|
+
|
|
138
|
+
status = "completed" if output.exit_code is not None else "running"
|
|
139
|
+
|
|
140
|
+
# Format as plain text
|
|
141
|
+
suffix_parts = [f"Status: {status}"]
|
|
268
142
|
if output.exit_code is not None:
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
143
|
+
suffix_parts.append(f"Exit code: {output.exit_code}")
|
|
144
|
+
if output.truncated:
|
|
145
|
+
suffix_parts.append("[output truncated]")
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
f"{combined}\n\n{' | '.join(suffix_parts)}"
|
|
149
|
+
if combined
|
|
150
|
+
else " | ".join(suffix_parts)
|
|
151
|
+
)
|
|
273
152
|
except ValueError as e:
|
|
274
|
-
return
|
|
153
|
+
return f"Error: {e}"
|
|
275
154
|
except Exception as e: # noqa: BLE001
|
|
276
|
-
return
|
|
277
|
-
else:
|
|
278
|
-
return result
|
|
155
|
+
return f"Error getting process output: {e}"
|
|
279
156
|
|
|
280
|
-
async def wait_for_process(
|
|
157
|
+
async def wait_for_process( # noqa: D417
|
|
158
|
+
self,
|
|
159
|
+
agent_ctx: AgentContext,
|
|
160
|
+
process_id: str,
|
|
161
|
+
filter_lines: str | None = None,
|
|
162
|
+
) -> str:
|
|
281
163
|
"""Wait for background process to complete and return final output.
|
|
282
164
|
|
|
283
165
|
Args:
|
|
284
166
|
process_id: Process identifier from start_process
|
|
167
|
+
filter_lines: Optional regex pattern to filter output lines
|
|
168
|
+
(only matching lines returned)
|
|
285
169
|
"""
|
|
286
170
|
manager = self.get_env(agent_ctx).process_manager
|
|
287
171
|
try:
|
|
288
172
|
exit_code = await manager.wait_for_exit(process_id)
|
|
289
173
|
output = await manager.get_output(process_id)
|
|
290
174
|
await agent_ctx.events.process_exit(process_id, exit_code, final_output=output.combined)
|
|
291
|
-
|
|
292
175
|
except ValueError as e:
|
|
293
|
-
return
|
|
176
|
+
return f"Error: {e}"
|
|
294
177
|
except Exception as e: # noqa: BLE001
|
|
295
|
-
return
|
|
178
|
+
return f"Error waiting for process: {e}"
|
|
296
179
|
else:
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
180
|
+
combined = output.combined or ""
|
|
181
|
+
|
|
182
|
+
# Apply regex filter if specified
|
|
183
|
+
if filter_lines and combined:
|
|
184
|
+
try:
|
|
185
|
+
pattern = re.compile(filter_lines)
|
|
186
|
+
filtered_lines = [
|
|
187
|
+
line for line in combined.splitlines(keepends=True) if pattern.search(line)
|
|
188
|
+
]
|
|
189
|
+
combined = "".join(filtered_lines)
|
|
190
|
+
except re.error as regex_err:
|
|
191
|
+
return f"Invalid filter regex: {regex_err}"
|
|
192
|
+
|
|
193
|
+
# Format as plain text
|
|
194
|
+
suffix_parts = []
|
|
195
|
+
if output.truncated:
|
|
196
|
+
suffix_parts.append("[output truncated]")
|
|
197
|
+
if exit_code != 0:
|
|
198
|
+
suffix_parts.append(f"Exit code: {exit_code}")
|
|
199
|
+
|
|
200
|
+
if suffix_parts:
|
|
201
|
+
return f"{combined}\n\n{' | '.join(suffix_parts)}"
|
|
202
|
+
return combined
|
|
203
|
+
|
|
204
|
+
async def kill_process(self, agent_ctx: AgentContext, process_id: str) -> str: # noqa: D417
|
|
308
205
|
"""Terminate a background process.
|
|
309
206
|
|
|
310
207
|
Args:
|
|
@@ -315,18 +212,14 @@ class ExecutionEnvironmentTools(ResourceProvider):
|
|
|
315
212
|
await agent_ctx.events.process_killed(process_id=process_id, success=True)
|
|
316
213
|
except ValueError as e:
|
|
317
214
|
await agent_ctx.events.process_killed(process_id, success=False, error=str(e))
|
|
318
|
-
return
|
|
215
|
+
return f"Error: {e}"
|
|
319
216
|
except Exception as e: # noqa: BLE001
|
|
320
217
|
await agent_ctx.events.process_killed(process_id, success=False, error=str(e))
|
|
321
|
-
return
|
|
218
|
+
return f"Error killing process: {e}"
|
|
322
219
|
else:
|
|
323
|
-
return {
|
|
324
|
-
"process_id": process_id,
|
|
325
|
-
"status": "killed",
|
|
326
|
-
"message": f"Process {process_id} has been terminated",
|
|
327
|
-
}
|
|
220
|
+
return f"Process {process_id} has been terminated"
|
|
328
221
|
|
|
329
|
-
async def release_process(self, agent_ctx: AgentContext, process_id: str) ->
|
|
222
|
+
async def release_process(self, agent_ctx: AgentContext, process_id: str) -> str: # noqa: D417
|
|
330
223
|
"""Release resources for a background process.
|
|
331
224
|
|
|
332
225
|
Args:
|
|
@@ -335,47 +228,39 @@ class ExecutionEnvironmentTools(ResourceProvider):
|
|
|
335
228
|
try:
|
|
336
229
|
await self.get_env(agent_ctx).process_manager.release_process(process_id)
|
|
337
230
|
await agent_ctx.events.process_released(process_id=process_id, success=True)
|
|
338
|
-
|
|
339
231
|
except ValueError as e:
|
|
340
232
|
await agent_ctx.events.process_released(process_id, success=False, error=str(e))
|
|
341
|
-
return
|
|
233
|
+
return f"Error: {e}"
|
|
342
234
|
except Exception as e: # noqa: BLE001
|
|
343
235
|
await agent_ctx.events.process_released(process_id, success=False, error=str(e))
|
|
344
|
-
return
|
|
236
|
+
return f"Error releasing process: {e}"
|
|
345
237
|
else:
|
|
346
|
-
return {
|
|
347
|
-
"process_id": process_id,
|
|
348
|
-
"status": "released",
|
|
349
|
-
"message": f"Process {process_id} resources have been released",
|
|
350
|
-
}
|
|
238
|
+
return f"Process {process_id} resources have been released"
|
|
351
239
|
|
|
352
|
-
async def list_processes(self, agent_ctx: AgentContext) ->
|
|
240
|
+
async def list_processes(self, agent_ctx: AgentContext) -> str:
|
|
353
241
|
"""List all active background processes."""
|
|
354
242
|
env = self.get_env(agent_ctx)
|
|
355
243
|
try:
|
|
356
244
|
process_ids = await env.process_manager.list_processes()
|
|
357
245
|
if not process_ids:
|
|
358
|
-
return
|
|
246
|
+
return "No active background processes"
|
|
359
247
|
|
|
360
|
-
|
|
248
|
+
lines = [f"Active processes ({len(process_ids)}):"]
|
|
361
249
|
for process_id in process_ids:
|
|
362
250
|
try:
|
|
363
251
|
info = await env.process_manager.get_process_info(process_id)
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
"exit_code"
|
|
371
|
-
|
|
372
|
-
})
|
|
252
|
+
command = info["command"]
|
|
253
|
+
args = info.get("args", [])
|
|
254
|
+
full_cmd = f"{command} {' '.join(args)}" if args else command
|
|
255
|
+
status = "running" if info.get("is_running", False) else "stopped"
|
|
256
|
+
exit_code = info.get("exit_code")
|
|
257
|
+
status_str = (
|
|
258
|
+
f"{status}" if exit_code is None else f"{status} (exit {exit_code})"
|
|
259
|
+
)
|
|
260
|
+
lines.append(f" - {process_id}: {full_cmd} [{status_str}]")
|
|
373
261
|
except Exception as e: # noqa: BLE001
|
|
374
|
-
|
|
375
|
-
"process_id": process_id,
|
|
376
|
-
"error": f"Error getting info: {e}",
|
|
377
|
-
})
|
|
262
|
+
lines.append(f" - {process_id}: [error getting info: {e}]")
|
|
378
263
|
|
|
379
|
-
return
|
|
264
|
+
return "\n".join(lines)
|
|
380
265
|
except Exception as e: # noqa: BLE001
|
|
381
|
-
return
|
|
266
|
+
return f"Error listing processes: {e}"
|
|
@@ -479,9 +479,22 @@ def _trim_diff(diff_text: str) -> str:
|
|
|
479
479
|
|
|
480
480
|
|
|
481
481
|
def replace_content(
|
|
482
|
-
content: str,
|
|
482
|
+
content: str,
|
|
483
|
+
old_string: str,
|
|
484
|
+
new_string: str,
|
|
485
|
+
replace_all: bool = False,
|
|
486
|
+
line_hint: int | None = None,
|
|
483
487
|
) -> str:
|
|
484
|
-
"""Replace content using multiple fallback strategies with detailed error messages.
|
|
488
|
+
"""Replace content using multiple fallback strategies with detailed error messages.
|
|
489
|
+
|
|
490
|
+
Args:
|
|
491
|
+
content: The file content to edit
|
|
492
|
+
old_string: Text to find and replace
|
|
493
|
+
new_string: Replacement text
|
|
494
|
+
replace_all: If True, replace all occurrences
|
|
495
|
+
line_hint: If provided and multiple matches exist, use the match closest to this line.
|
|
496
|
+
Useful for disambiguation after getting a "multiple matches" error.
|
|
497
|
+
"""
|
|
485
498
|
if old_string == new_string:
|
|
486
499
|
msg = "old_string and new_string must be different"
|
|
487
500
|
raise ValueError(msg)
|
|
@@ -518,6 +531,16 @@ def replace_content(
|
|
|
518
531
|
# Check if there are multiple occurrences
|
|
519
532
|
last_index = content.rfind(search_text)
|
|
520
533
|
if index != last_index:
|
|
534
|
+
# Multiple occurrences found
|
|
535
|
+
if line_hint is not None:
|
|
536
|
+
# Use line_hint to pick the closest match
|
|
537
|
+
best_index = _find_closest_match(content, search_text, line_hint)
|
|
538
|
+
if best_index is not None:
|
|
539
|
+
return (
|
|
540
|
+
content[:best_index]
|
|
541
|
+
+ new_string
|
|
542
|
+
+ content[best_index + len(search_text) :]
|
|
543
|
+
)
|
|
521
544
|
continue # Multiple occurrences, need more context
|
|
522
545
|
|
|
523
546
|
# Single occurrence - replace it
|
|
@@ -528,11 +551,9 @@ def replace_content(
|
|
|
528
551
|
error_msg = _build_not_found_error(content, old_string)
|
|
529
552
|
raise ValueError(error_msg)
|
|
530
553
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
)
|
|
535
|
-
raise ValueError(msg)
|
|
554
|
+
# Multiple matches found - provide helpful error with locations
|
|
555
|
+
error_msg = _build_multiple_matches_error(content, old_string)
|
|
556
|
+
raise ValueError(error_msg)
|
|
536
557
|
|
|
537
558
|
|
|
538
559
|
def _find_best_fuzzy_match(
|
|
@@ -614,6 +635,93 @@ def _create_unified_diff(text1: str, text2: str) -> str:
|
|
|
614
635
|
return result.rstrip()
|
|
615
636
|
|
|
616
637
|
|
|
638
|
+
def _find_all_match_locations(content: str, search_text: str) -> list[int]:
|
|
639
|
+
"""Find all line numbers where search_text starts.
|
|
640
|
+
|
|
641
|
+
Returns 1-based line numbers for each occurrence.
|
|
642
|
+
"""
|
|
643
|
+
lines = content.split("\n")
|
|
644
|
+
locations: list[int] = []
|
|
645
|
+
|
|
646
|
+
# For single-line search, find direct matches
|
|
647
|
+
search_lines = search_text.split("\n")
|
|
648
|
+
first_search_line = search_lines[0] if search_lines else search_text
|
|
649
|
+
|
|
650
|
+
for i, line in enumerate(lines):
|
|
651
|
+
if first_search_line in line:
|
|
652
|
+
# Verify full match if multi-line
|
|
653
|
+
if len(search_lines) > 1:
|
|
654
|
+
window = "\n".join(lines[i : i + len(search_lines)])
|
|
655
|
+
if search_text in window:
|
|
656
|
+
locations.append(i + 1) # 1-based
|
|
657
|
+
else:
|
|
658
|
+
locations.append(i + 1) # 1-based
|
|
659
|
+
|
|
660
|
+
return locations
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
def _find_closest_match(content: str, search_text: str, line_hint: int) -> int | None:
|
|
664
|
+
"""Find the occurrence of search_text closest to line_hint.
|
|
665
|
+
|
|
666
|
+
Args:
|
|
667
|
+
content: The file content
|
|
668
|
+
search_text: Text to search for
|
|
669
|
+
line_hint: Target line number (1-based)
|
|
670
|
+
|
|
671
|
+
Returns:
|
|
672
|
+
The character index of the closest match, or None if no matches found.
|
|
673
|
+
"""
|
|
674
|
+
matches: list[tuple[int, int]] = [] # (line_number, char_index)
|
|
675
|
+
|
|
676
|
+
# Find all occurrences with their positions
|
|
677
|
+
start = 0
|
|
678
|
+
while True:
|
|
679
|
+
index = content.find(search_text, start)
|
|
680
|
+
if index == -1:
|
|
681
|
+
break
|
|
682
|
+
# Calculate line number for this index
|
|
683
|
+
line_num = content[:index].count("\n") + 1
|
|
684
|
+
matches.append((line_num, index))
|
|
685
|
+
start = index + 1
|
|
686
|
+
|
|
687
|
+
if not matches:
|
|
688
|
+
return None
|
|
689
|
+
|
|
690
|
+
# Find the match closest to line_hint
|
|
691
|
+
closest = min(matches, key=lambda m: abs(m[0] - line_hint))
|
|
692
|
+
return closest[1]
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
def _build_multiple_matches_error(content: str, old_string: str) -> str:
|
|
696
|
+
"""Build a helpful error message when old_string matches multiple locations."""
|
|
697
|
+
locations = _find_all_match_locations(content, old_string)
|
|
698
|
+
|
|
699
|
+
if not locations:
|
|
700
|
+
# Fallback - shouldn't happen but be safe
|
|
701
|
+
return (
|
|
702
|
+
"old_string found multiple times and requires more code context "
|
|
703
|
+
"to uniquely identify the intended match"
|
|
704
|
+
)
|
|
705
|
+
|
|
706
|
+
# Show first few lines of the search text for context
|
|
707
|
+
search_preview = old_string.split("\n")[0][:60]
|
|
708
|
+
if len(old_string.split("\n")[0]) > 60: # noqa: PLR2004
|
|
709
|
+
search_preview += "..."
|
|
710
|
+
|
|
711
|
+
location_str = ", ".join(str(loc) for loc in locations[:5])
|
|
712
|
+
if len(locations) > 5: # noqa: PLR2004
|
|
713
|
+
location_str += f", ... ({len(locations)} total)"
|
|
714
|
+
|
|
715
|
+
error_parts = [
|
|
716
|
+
f"Pattern found at multiple locations (lines: {location_str}).",
|
|
717
|
+
f"\nSearch text starts with: {search_preview!r}",
|
|
718
|
+
"\n\nTo fix, include more surrounding context in old_string to uniquely identify "
|
|
719
|
+
"the target location, or use replace_all=True to replace all occurrences.",
|
|
720
|
+
]
|
|
721
|
+
|
|
722
|
+
return "".join(error_parts)
|
|
723
|
+
|
|
724
|
+
|
|
617
725
|
def _build_not_found_error(content: str, old_string: str) -> str:
|
|
618
726
|
"""Build a helpful error message when old_string is not found."""
|
|
619
727
|
lines = content.split("\n")
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Provider for skills tools."""
|
|
1
|
+
"""Provider for skills and commands tools."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -60,13 +60,18 @@ async def list_skills(ctx: AgentContext) -> str:
|
|
|
60
60
|
|
|
61
61
|
|
|
62
62
|
class SkillsTools(StaticResourceProvider):
|
|
63
|
-
"""Provider for
|
|
63
|
+
"""Provider for skills and commands tools.
|
|
64
|
+
|
|
65
|
+
Provides tools to:
|
|
66
|
+
- Discover and load skills from the pool's skills registry
|
|
67
|
+
- Execute internal commands via the agent's command system
|
|
64
68
|
|
|
65
|
-
Provides tools to discover and load skills from the pool's skills registry.
|
|
66
69
|
Skills are discovered from configured directories (e.g., ~/.claude/skills/,
|
|
67
70
|
.claude/skills/).
|
|
68
71
|
|
|
69
|
-
|
|
72
|
+
Commands provide access to management operations like creating agents,
|
|
73
|
+
managing tools, connecting nodes, etc. Use run_command("/help") to discover
|
|
74
|
+
available commands.
|
|
70
75
|
"""
|
|
71
76
|
|
|
72
77
|
def __init__(self, name: str = "skills") -> None:
|