agentpool 2.2.3__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 +0 -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/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 +18 -49
- 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/task/supervisor.py +2 -2
- acp/utils.py +2 -2
- agentpool/__init__.py +2 -0
- agentpool/agents/acp_agent/acp_agent.py +278 -263
- agentpool/agents/acp_agent/acp_converters.py +150 -17
- agentpool/agents/acp_agent/client_handler.py +35 -24
- agentpool/agents/acp_agent/session_state.py +14 -6
- agentpool/agents/agent.py +471 -643
- agentpool/agents/agui_agent/agui_agent.py +104 -107
- agentpool/agents/agui_agent/helpers.py +3 -4
- agentpool/agents/base_agent.py +485 -32
- 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 +654 -334
- agentpool/agents/claude_code_agent/converters.py +4 -141
- 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/events/__init__.py +22 -0
- agentpool/agents/events/builtin_handlers.py +65 -0
- agentpool/agents/events/event_emitter.py +3 -0
- agentpool/agents/events/events.py +84 -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 +13 -0
- agentpool/agents/slashed_agent.py +5 -4
- agentpool/agents/tool_wrapping.py +18 -6
- agentpool/common_types.py +35 -21
- 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 +9 -8
- agentpool/config_resources/external_acp_agents.yml +2 -1
- agentpool/delegation/base_team.py +4 -30
- agentpool/delegation/pool.py +104 -265
- agentpool/delegation/team.py +57 -57
- agentpool/delegation/teamrun.py +50 -55
- agentpool/functional/run.py +10 -4
- agentpool/mcp_server/client.py +73 -38
- agentpool/mcp_server/conversions.py +54 -13
- agentpool/mcp_server/manager.py +9 -23
- agentpool/mcp_server/registries/official_registry_client.py +10 -1
- agentpool/mcp_server/tool_bridge.py +114 -79
- 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 +87 -8
- agentpool/messaging/messagenode.py +52 -14
- agentpool/messaging/messages.py +2 -26
- agentpool/messaging/processing.py +10 -22
- agentpool/models/__init__.py +1 -1
- agentpool/models/acp_agents/base.py +6 -2
- agentpool/models/acp_agents/mcp_capable.py +124 -15
- agentpool/models/acp_agents/non_mcp.py +0 -23
- agentpool/models/agents.py +66 -66
- agentpool/models/agui_agents.py +1 -1
- agentpool/models/claude_code_agents.py +111 -17
- agentpool/models/file_parsing.py +0 -1
- agentpool/models/manifest.py +70 -50
- agentpool/prompts/conversion_manager.py +1 -1
- agentpool/prompts/prompts.py +5 -2
- agentpool/resource_providers/__init__.py +2 -0
- agentpool/resource_providers/aggregating.py +4 -2
- agentpool/resource_providers/base.py +13 -3
- 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 +66 -12
- agentpool/resource_providers/plan_provider.py +111 -18
- agentpool/resource_providers/pool.py +5 -3
- agentpool/resource_providers/resource_info.py +111 -0
- agentpool/resource_providers/static.py +2 -2
- agentpool/sessions/__init__.py +2 -0
- agentpool/sessions/manager.py +2 -3
- agentpool/sessions/models.py +9 -6
- agentpool/sessions/protocol.py +28 -0
- agentpool/sessions/session.py +11 -55
- agentpool/storage/manager.py +361 -54
- agentpool/talk/registry.py +4 -4
- agentpool/talk/talk.py +9 -10
- agentpool/testing.py +1 -1
- 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/streams.py +21 -96
- agentpool/vfs_registry.py +7 -2
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/METADATA +16 -22
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/RECORD +242 -195
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/WHEEL +1 -1
- agentpool_cli/__main__.py +20 -0
- agentpool_cli/create.py +1 -1
- agentpool_cli/serve_acp.py +59 -1
- agentpool_cli/serve_opencode.py +1 -1
- agentpool_cli/ui.py +557 -0
- agentpool_commands/__init__.py +12 -5
- agentpool_commands/agents.py +1 -1
- 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/utils.py +31 -32
- agentpool_config/__init__.py +30 -2
- agentpool_config/agentpool_tools.py +498 -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 +1 -5
- agentpool_config/nodes.py +1 -1
- agentpool_config/observability.py +44 -0
- agentpool_config/session.py +0 -3
- agentpool_config/storage.py +38 -39
- agentpool_config/task.py +3 -3
- agentpool_config/tools.py +11 -28
- agentpool_config/toolsets.py +22 -90
- agentpool_server/a2a_server/agent_worker.py +307 -0
- agentpool_server/a2a_server/server.py +23 -18
- agentpool_server/acp_server/acp_agent.py +125 -56
- agentpool_server/acp_server/commands/acp_commands.py +46 -216
- agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +8 -7
- agentpool_server/acp_server/event_converter.py +651 -0
- agentpool_server/acp_server/input_provider.py +53 -10
- agentpool_server/acp_server/server.py +1 -11
- agentpool_server/acp_server/session.py +90 -410
- 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/ENDPOINTS.md +53 -14
- agentpool_server/opencode_server/OPENCODE_UI_TOOLS_COMPLETE.md +202 -0
- agentpool_server/opencode_server/__init__.py +0 -8
- agentpool_server/opencode_server/converters.py +132 -26
- agentpool_server/opencode_server/input_provider.py +160 -8
- agentpool_server/opencode_server/models/__init__.py +42 -20
- agentpool_server/opencode_server/models/app.py +12 -0
- agentpool_server/opencode_server/models/events.py +203 -29
- agentpool_server/opencode_server/models/mcp.py +19 -0
- agentpool_server/opencode_server/models/message.py +18 -1
- agentpool_server/opencode_server/models/parts.py +134 -1
- agentpool_server/opencode_server/models/question.py +56 -0
- agentpool_server/opencode_server/models/session.py +13 -1
- agentpool_server/opencode_server/routes/__init__.py +4 -0
- agentpool_server/opencode_server/routes/agent_routes.py +33 -2
- agentpool_server/opencode_server/routes/app_routes.py +66 -3
- agentpool_server/opencode_server/routes/config_routes.py +66 -5
- agentpool_server/opencode_server/routes/file_routes.py +184 -5
- agentpool_server/opencode_server/routes/global_routes.py +1 -1
- agentpool_server/opencode_server/routes/lsp_routes.py +1 -1
- agentpool_server/opencode_server/routes/message_routes.py +122 -66
- agentpool_server/opencode_server/routes/permission_routes.py +63 -0
- agentpool_server/opencode_server/routes/pty_routes.py +23 -22
- agentpool_server/opencode_server/routes/question_routes.py +128 -0
- agentpool_server/opencode_server/routes/session_routes.py +139 -68
- agentpool_server/opencode_server/routes/tui_routes.py +1 -1
- agentpool_server/opencode_server/server.py +47 -2
- agentpool_server/opencode_server/state.py +30 -0
- agentpool_storage/__init__.py +0 -4
- agentpool_storage/base.py +81 -2
- agentpool_storage/claude_provider/ARCHITECTURE.md +433 -0
- agentpool_storage/claude_provider/__init__.py +42 -0
- agentpool_storage/{claude_provider.py → claude_provider/provider.py} +190 -8
- agentpool_storage/file_provider.py +149 -15
- agentpool_storage/memory_provider.py +132 -12
- 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/session_store.py +20 -6
- agentpool_storage/sql_provider/sql_provider.py +135 -2
- agentpool_storage/sql_provider/utils.py +2 -12
- 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 -4
- agentpool_toolsets/builtin/code.py +4 -4
- agentpool_toolsets/builtin/debug.py +115 -40
- agentpool_toolsets/builtin/execution_environment.py +54 -165
- agentpool_toolsets/builtin/skills.py +0 -77
- 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/grep.py +25 -5
- agentpool_toolsets/fsspec_toolset/helpers.py +3 -2
- agentpool_toolsets/fsspec_toolset/toolset.py +350 -66
- agentpool_toolsets/mcp_discovery/data/mcp_servers.parquet +0 -0
- agentpool_toolsets/mcp_discovery/toolset.py +74 -17
- agentpool_toolsets/mcp_run_toolset.py +8 -11
- agentpool_toolsets/notifications.py +33 -33
- agentpool_toolsets/openapi.py +3 -1
- agentpool_toolsets/search_toolset.py +3 -1
- 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/opencode_provider.py +0 -730
- agentpool_storage/text_log_provider.py +0 -276
- agentpool_toolsets/builtin/chain.py +0 -288
- agentpool_toolsets/builtin/user_interaction.py +0 -52
- agentpool_toolsets/semantic_memory_toolset.py +0 -536
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/entry_points.txt +0 -0
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -7,31 +7,14 @@ between agents and ACP clients through the JSON-RPC protocol.
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
import asyncio
|
|
10
|
-
from collections.abc import AsyncGenerator
|
|
11
10
|
from dataclasses import dataclass, field
|
|
12
11
|
import re
|
|
13
|
-
from typing import TYPE_CHECKING, Any
|
|
12
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
14
13
|
|
|
14
|
+
import anyio
|
|
15
15
|
from exxec.acp_provider import ACPExecutionEnvironment
|
|
16
16
|
import logfire
|
|
17
|
-
from pydantic_ai import
|
|
18
|
-
BuiltinToolCallPart,
|
|
19
|
-
BuiltinToolReturnPart,
|
|
20
|
-
FinalResultEvent,
|
|
21
|
-
FunctionToolCallEvent,
|
|
22
|
-
FunctionToolResultEvent,
|
|
23
|
-
PartDeltaEvent,
|
|
24
|
-
PartStartEvent,
|
|
25
|
-
RetryPromptPart,
|
|
26
|
-
TextPart,
|
|
27
|
-
TextPartDelta,
|
|
28
|
-
ThinkingPart,
|
|
29
|
-
ThinkingPartDelta,
|
|
30
|
-
ToolCallPartDelta,
|
|
31
|
-
ToolReturnPart,
|
|
32
|
-
UsageLimitExceeded,
|
|
33
|
-
UserPromptPart,
|
|
34
|
-
)
|
|
17
|
+
from pydantic_ai import UsageLimitExceeded, UserPromptPart
|
|
35
18
|
from slashed import Command, CommandStore
|
|
36
19
|
from tokonomics.model_discovery.model_info import ModelInfo
|
|
37
20
|
|
|
@@ -39,45 +22,26 @@ from acp import RequestPermissionRequest
|
|
|
39
22
|
from acp.acp_requests import ACPRequests
|
|
40
23
|
from acp.filesystem import ACPFileSystem
|
|
41
24
|
from acp.notifications import ACPNotifications
|
|
42
|
-
from acp.schema import
|
|
43
|
-
AvailableCommand,
|
|
44
|
-
ClientCapabilities,
|
|
45
|
-
ContentToolCallContent,
|
|
46
|
-
PlanEntry,
|
|
47
|
-
TerminalToolCallContent,
|
|
48
|
-
ToolCallLocation,
|
|
49
|
-
)
|
|
50
|
-
from acp.tool_call_state import ToolCallState
|
|
51
|
-
from acp.utils import generate_tool_title, infer_tool_kind, to_acp_content_blocks
|
|
25
|
+
from acp.schema import AvailableCommand, ClientCapabilities, SessionNotification
|
|
52
26
|
from agentpool import Agent, AgentContext # noqa: TC001
|
|
53
27
|
from agentpool.agents import SlashedAgent
|
|
54
28
|
from agentpool.agents.acp_agent import ACPAgent
|
|
55
|
-
from agentpool.agents.events import (
|
|
56
|
-
CompactionEvent,
|
|
57
|
-
PlanUpdateEvent,
|
|
58
|
-
StreamCompleteEvent,
|
|
59
|
-
ToolCallProgressEvent,
|
|
60
|
-
ToolCallStartEvent,
|
|
61
|
-
)
|
|
62
29
|
from agentpool.agents.modes import ModeInfo
|
|
63
30
|
from agentpool.log import get_logger
|
|
64
|
-
from agentpool.utils.pydantic_ai_helpers import safe_args_as_dict
|
|
65
31
|
from agentpool_commands import get_commands
|
|
66
32
|
from agentpool_commands.base import NodeCommand
|
|
67
33
|
from agentpool_server.acp_server.converters import (
|
|
68
34
|
convert_acp_mcp_server_to_config,
|
|
69
35
|
from_acp_content,
|
|
70
36
|
)
|
|
37
|
+
from agentpool_server.acp_server.event_converter import ACPEventConverter
|
|
71
38
|
from agentpool_server.acp_server.input_provider import ACPInputProvider
|
|
72
39
|
|
|
73
40
|
|
|
74
41
|
if TYPE_CHECKING:
|
|
75
42
|
from collections.abc import Callable, Sequence
|
|
76
43
|
|
|
77
|
-
from pydantic_ai import
|
|
78
|
-
SystemPromptPart,
|
|
79
|
-
UserContent,
|
|
80
|
-
)
|
|
44
|
+
from pydantic_ai import SystemPromptPart, UserContent
|
|
81
45
|
from slashed import CommandContext
|
|
82
46
|
|
|
83
47
|
from acp import Client, RequestPermissionResponse
|
|
@@ -92,7 +56,6 @@ if TYPE_CHECKING:
|
|
|
92
56
|
from agentpool import AgentPool
|
|
93
57
|
from agentpool.agents import AGUIAgent
|
|
94
58
|
from agentpool.agents.claude_code_agent import ClaudeCodeAgent
|
|
95
|
-
from agentpool.agents.events import RichAgentStreamEvent
|
|
96
59
|
from agentpool.prompts.manager import PromptManager
|
|
97
60
|
from agentpool.prompts.prompts import MCPClientPrompt
|
|
98
61
|
from agentpool_server.acp_server.acp_agent import AgentPoolACPAgent
|
|
@@ -245,6 +208,12 @@ class ACPSession:
|
|
|
245
208
|
manager: ACPSessionManager | None = None
|
|
246
209
|
"""Session manager for managing sessions. Used for session management commands."""
|
|
247
210
|
|
|
211
|
+
subagent_display_mode: Literal["inline", "tool_box"] = "tool_box"
|
|
212
|
+
"""How to display subagent output:
|
|
213
|
+
- 'inline': Subagent output flows into main message stream
|
|
214
|
+
- 'tool_box': Subagent output contained in the tool call's progress box (default)
|
|
215
|
+
"""
|
|
216
|
+
|
|
248
217
|
def __post_init__(self) -> None:
|
|
249
218
|
"""Initialize session state and set up providers."""
|
|
250
219
|
from agentpool_server.acp_server.commands import get_commands as get_acp_commands
|
|
@@ -253,9 +222,7 @@ class ACPSession:
|
|
|
253
222
|
self.log = logger.bind(session_id=self.session_id)
|
|
254
223
|
self._task_lock = asyncio.Lock()
|
|
255
224
|
self._cancelled = False
|
|
256
|
-
self.
|
|
257
|
-
self._current_tool_inputs: dict[str, dict[str, Any]] = {}
|
|
258
|
-
self._tool_call_states: dict[str, ToolCallState] = {}
|
|
225
|
+
self._current_converter: ACPEventConverter | None = None
|
|
259
226
|
self.fs = ACPFileSystem(self.client, session_id=self.session_id)
|
|
260
227
|
cmds = [
|
|
261
228
|
*get_commands(
|
|
@@ -270,7 +237,6 @@ class ACPSession:
|
|
|
270
237
|
self.command_store._initialize_sync()
|
|
271
238
|
self._update_callbacks: list[Callable[[], None]] = []
|
|
272
239
|
self._remote_commands: list[AvailableCommand] = [] # Commands from nested ACP agents
|
|
273
|
-
|
|
274
240
|
self.staged_content = StagedContent()
|
|
275
241
|
# Inject Zed-specific instructions if client is Zed
|
|
276
242
|
if self.client_info and self.client_info.name and "zed" in self.client_info.name.lower():
|
|
@@ -354,71 +320,44 @@ class ACPSession:
|
|
|
354
320
|
self.log.debug("Forwarding config option update to client")
|
|
355
321
|
|
|
356
322
|
notification = SessionNotification(session_id=self.session_id, update=update)
|
|
357
|
-
await self.client.session_update(notification)
|
|
323
|
+
await self.client.session_update(notification) # pyright: ignore[reportArgumentType]
|
|
358
324
|
|
|
359
325
|
async def initialize(self) -> None:
|
|
360
326
|
"""Initialize async resources. Must be called after construction."""
|
|
361
327
|
await self.acp_env.__aenter__()
|
|
362
328
|
|
|
363
|
-
def _get_or_create_tool_state(
|
|
364
|
-
self,
|
|
365
|
-
tool_call_id: str,
|
|
366
|
-
tool_name: str,
|
|
367
|
-
tool_input: dict[str, Any],
|
|
368
|
-
) -> ToolCallState:
|
|
369
|
-
"""Get existing tool call state or create a new one.
|
|
370
|
-
|
|
371
|
-
Args:
|
|
372
|
-
tool_call_id: Unique identifier for the tool call
|
|
373
|
-
tool_name: Name of the tool being called
|
|
374
|
-
tool_input: Input parameters for the tool
|
|
375
|
-
|
|
376
|
-
Returns:
|
|
377
|
-
ToolCallState instance (existing or newly created)
|
|
378
|
-
"""
|
|
379
|
-
if tool_call_id not in self._tool_call_states:
|
|
380
|
-
state = ToolCallState(
|
|
381
|
-
notifications=self.notifications,
|
|
382
|
-
tool_call_id=tool_call_id,
|
|
383
|
-
tool_name=tool_name,
|
|
384
|
-
title=generate_tool_title(tool_name, tool_input),
|
|
385
|
-
kind=infer_tool_kind(tool_name),
|
|
386
|
-
raw_input=tool_input,
|
|
387
|
-
)
|
|
388
|
-
self._tool_call_states[tool_call_id] = state
|
|
389
|
-
return self._tool_call_states[tool_call_id]
|
|
390
|
-
|
|
391
|
-
def _cleanup_tool_state(self, tool_call_id: str) -> None:
|
|
392
|
-
"""Remove tool call state after completion."""
|
|
393
|
-
self._tool_call_states.pop(tool_call_id, None)
|
|
394
|
-
self._current_tool_inputs.pop(tool_call_id, None)
|
|
395
|
-
|
|
396
329
|
async def initialize_mcp_servers(self) -> None:
|
|
397
330
|
"""Initialize MCP servers if any are configured."""
|
|
398
331
|
if not self.mcp_servers:
|
|
399
332
|
return
|
|
400
333
|
self.log.info("Initializing MCP servers", server_count=len(self.mcp_servers))
|
|
401
334
|
cfgs = [convert_acp_mcp_server_to_config(s) for s in self.mcp_servers]
|
|
402
|
-
#
|
|
403
|
-
|
|
404
|
-
for _cfg in cfgs:
|
|
335
|
+
# Add each MCP server to the current agent's MCP manager dynamically
|
|
336
|
+
for cfg in cfgs:
|
|
405
337
|
try:
|
|
406
|
-
|
|
407
|
-
self.log.info(
|
|
408
|
-
|
|
338
|
+
await self.agent.mcp.setup_server(cfg)
|
|
339
|
+
self.log.info(
|
|
340
|
+
"Added MCP server to agent", server_name=cfg.name, agent=self.current_agent_name
|
|
341
|
+
)
|
|
409
342
|
except Exception:
|
|
410
|
-
self.log.exception("Failed to
|
|
343
|
+
self.log.exception("Failed to setup MCP server", server_name=cfg.name)
|
|
411
344
|
# Don't fail session creation, just log the error
|
|
345
|
+
# Register MCP prompts as commands after all servers are added
|
|
346
|
+
try:
|
|
347
|
+
await self._register_mcp_prompts_as_commands()
|
|
348
|
+
except Exception:
|
|
349
|
+
self.log.exception("Failed to register MCP prompts as commands")
|
|
412
350
|
|
|
413
351
|
async def init_project_context(self) -> None:
|
|
414
|
-
"""Load AGENTS.md file and
|
|
352
|
+
"""Load AGENTS.md/CLAUDE.md file and stage as initial context.
|
|
415
353
|
|
|
416
|
-
|
|
354
|
+
The project context is staged as user message content rather than system prompts,
|
|
355
|
+
which ensures it's available for all agent types and avoids timing issues with
|
|
356
|
+
agent initialization.
|
|
417
357
|
"""
|
|
418
358
|
if info := await self.requests.read_agent_rules(self.cwd):
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
agent.sys_prompts.prompts.append(prompt)
|
|
359
|
+
# Stage as user message to be prepended to first prompt
|
|
360
|
+
self.staged_content.add_text(f"## Project Information\n\n{info}")
|
|
422
361
|
|
|
423
362
|
async def init_client_skills(self) -> None:
|
|
424
363
|
"""Discover and load skills from client-side .claude/skills directory.
|
|
@@ -488,14 +427,14 @@ class ACPSession:
|
|
|
488
427
|
This actively interrupts the running agent by calling its interrupt() method,
|
|
489
428
|
which handles protocol-specific cancellation (e.g., sending CancelNotification
|
|
490
429
|
for ACP agents, calling SDK interrupt for ClaudeCodeAgent, etc.).
|
|
430
|
+
|
|
431
|
+
Note:
|
|
432
|
+
Tool call cleanup is handled in process_prompt() to avoid race conditions
|
|
433
|
+
with the converter state being modified from multiple async contexts.
|
|
491
434
|
"""
|
|
492
435
|
self._cancelled = True
|
|
493
436
|
self.log.info("Session cancelled, interrupting agent")
|
|
494
437
|
|
|
495
|
-
# Clear pending tool call states to avoid stale data on next prompt
|
|
496
|
-
self._tool_call_states.clear()
|
|
497
|
-
self._current_tool_inputs.clear()
|
|
498
|
-
|
|
499
438
|
# Actively interrupt the agent's stream
|
|
500
439
|
try:
|
|
501
440
|
await self.agent.interrupt()
|
|
@@ -541,25 +480,61 @@ class ACPSession:
|
|
|
541
480
|
has_staged=staged is not None,
|
|
542
481
|
)
|
|
543
482
|
event_count = 0
|
|
544
|
-
|
|
483
|
+
# Create a new event converter for this prompt
|
|
484
|
+
converter = ACPEventConverter(subagent_display_mode=self.subagent_display_mode)
|
|
485
|
+
self._current_converter = converter # Track for cancellation
|
|
545
486
|
|
|
546
487
|
try: # Use the session's persistent input provider
|
|
547
488
|
async for event in self.agent.run_stream(
|
|
548
|
-
*all_content,
|
|
489
|
+
*all_content,
|
|
490
|
+
input_provider=self.input_provider,
|
|
491
|
+
deps=self,
|
|
492
|
+
conversation_id=self.session_id, # Tie agent conversation to ACP session
|
|
549
493
|
):
|
|
550
494
|
if self._cancelled:
|
|
551
|
-
self.log.info("Cancelled during event loop")
|
|
495
|
+
self.log.info("Cancelled during event loop, cleaning up tool calls")
|
|
496
|
+
# Send cancellation notifications for any pending tool calls
|
|
497
|
+
# This happens in the same async context as the converter
|
|
498
|
+
async for cancel_update in converter.cancel_pending_tools():
|
|
499
|
+
notification = SessionNotification(
|
|
500
|
+
session_id=self.session_id, update=cancel_update
|
|
501
|
+
)
|
|
502
|
+
await self.client.session_update(notification) # pyright: ignore[reportArgumentType]
|
|
503
|
+
# CRITICAL: Allow time for client to process tool completion notifications
|
|
504
|
+
# before sending PromptResponse. Without this delay, the client may receive
|
|
505
|
+
# and process the PromptResponse before the tool notifications, causing UI
|
|
506
|
+
# state desync where subsequent prompts appear stuck/unresponsive.
|
|
507
|
+
# This is needed because even though send() awaits the write, the client
|
|
508
|
+
# may process messages asynchronously or out of order.
|
|
509
|
+
await anyio.sleep(0.05)
|
|
510
|
+
self._current_converter = None
|
|
552
511
|
return "cancelled"
|
|
553
512
|
|
|
554
513
|
event_count += 1
|
|
555
|
-
|
|
514
|
+
async for update in converter.convert(event):
|
|
515
|
+
notification = SessionNotification(
|
|
516
|
+
session_id=self.session_id, update=update
|
|
517
|
+
)
|
|
518
|
+
await self.client.session_update(notification) # pyright: ignore[reportArgumentType]
|
|
519
|
+
# Yield control to allow notifications to be sent immediately
|
|
520
|
+
await anyio.sleep(0.01)
|
|
556
521
|
self.log.info("Streaming finished", events_processed=event_count)
|
|
557
522
|
|
|
558
523
|
except asyncio.CancelledError:
|
|
559
524
|
# Task was cancelled (e.g., via interrupt()) - return proper stop reason
|
|
560
525
|
# This is critical: CancelledError doesn't inherit from Exception,
|
|
561
526
|
# so we must catch it explicitly to send the PromptResponse
|
|
562
|
-
self.log.info("Stream cancelled via CancelledError")
|
|
527
|
+
self.log.info("Stream cancelled via CancelledError, cleaning up tool calls")
|
|
528
|
+
# Send cancellation notifications for any pending tool calls
|
|
529
|
+
async for cancel_update in converter.cancel_pending_tools():
|
|
530
|
+
notification = SessionNotification(
|
|
531
|
+
session_id=self.session_id, update=cancel_update
|
|
532
|
+
)
|
|
533
|
+
await self.client.session_update(notification) # pyright: ignore[reportArgumentType]
|
|
534
|
+
# CRITICAL: Allow time for client to process tool completion notifications
|
|
535
|
+
# before sending PromptResponse. See comment in cancellation branch above.
|
|
536
|
+
await anyio.sleep(0.05)
|
|
537
|
+
self._current_converter = None
|
|
563
538
|
return "cancelled"
|
|
564
539
|
except UsageLimitExceeded as e:
|
|
565
540
|
self.log.info("Usage limit exceeded", error=str(e))
|
|
@@ -573,6 +548,7 @@ class ACPSession:
|
|
|
573
548
|
return "refusal"
|
|
574
549
|
return "max_tokens" # Default to max_tokens for other usage limits
|
|
575
550
|
except Exception as e:
|
|
551
|
+
self._current_converter = None # Clear converter reference
|
|
576
552
|
self.log.exception("Error during streaming")
|
|
577
553
|
# Send error notification asynchronously to avoid blocking response
|
|
578
554
|
self.acp_agent.tasks.create_task(
|
|
@@ -581,37 +557,10 @@ class ACPSession:
|
|
|
581
557
|
)
|
|
582
558
|
return "end_turn"
|
|
583
559
|
else:
|
|
584
|
-
#
|
|
585
|
-
|
|
586
|
-
self._title_generation_triggered = True
|
|
587
|
-
self.acp_agent.tasks.create_task(
|
|
588
|
-
self._generate_title(),
|
|
589
|
-
name=f"generate_title_{self.session_id}",
|
|
590
|
-
)
|
|
560
|
+
# Title generation is now handled automatically by log_conversation
|
|
561
|
+
self._current_converter = None # Clear converter reference
|
|
591
562
|
return "end_turn"
|
|
592
563
|
|
|
593
|
-
async def _generate_title(self) -> None:
|
|
594
|
-
"""Generate conversation title in the background."""
|
|
595
|
-
try:
|
|
596
|
-
messages = self.agent.conversation.get_history()
|
|
597
|
-
if not messages:
|
|
598
|
-
return
|
|
599
|
-
|
|
600
|
-
title = await self.agent_pool.storage.generate_conversation_title(
|
|
601
|
-
self.session_id,
|
|
602
|
-
messages,
|
|
603
|
-
)
|
|
604
|
-
|
|
605
|
-
# Persist to session store
|
|
606
|
-
if title and self.manager:
|
|
607
|
-
session_data = await self.manager.session_manager.store.load(self.session_id)
|
|
608
|
-
if session_data:
|
|
609
|
-
updated_data = session_data.with_title(title)
|
|
610
|
-
await self.manager.session_manager.store.save(updated_data)
|
|
611
|
-
self.log.info("Generated session title", title=title)
|
|
612
|
-
except Exception:
|
|
613
|
-
self.log.exception("Failed to generate conversation title")
|
|
614
|
-
|
|
615
564
|
async def _send_error_notification(self, message: str) -> None:
|
|
616
565
|
"""Send error notification, with exception handling."""
|
|
617
566
|
if self._cancelled:
|
|
@@ -621,267 +570,8 @@ class ACPSession:
|
|
|
621
570
|
except Exception:
|
|
622
571
|
self.log.exception("Failed to send error notification")
|
|
623
572
|
|
|
624
|
-
async def handle_event(self, event: RichAgentStreamEvent[Any]) -> None: # noqa: PLR0915
|
|
625
|
-
# Don't send notifications after cancellation to avoid stale updates
|
|
626
|
-
if self._cancelled:
|
|
627
|
-
return
|
|
628
|
-
|
|
629
|
-
match event:
|
|
630
|
-
case (
|
|
631
|
-
PartStartEvent(part=TextPart(content=delta))
|
|
632
|
-
| PartDeltaEvent(delta=TextPartDelta(content_delta=delta))
|
|
633
|
-
):
|
|
634
|
-
await self.notifications.send_agent_text(delta)
|
|
635
|
-
|
|
636
|
-
case (
|
|
637
|
-
PartStartEvent(part=ThinkingPart(content=delta))
|
|
638
|
-
| PartDeltaEvent(delta=ThinkingPartDelta(content_delta=delta))
|
|
639
|
-
):
|
|
640
|
-
await self.notifications.send_agent_thought(delta or "\n")
|
|
641
|
-
|
|
642
|
-
# Builtin tool call started (e.g., WebSearchTool, CodeExecutionTool)
|
|
643
|
-
case PartStartEvent(part=BuiltinToolCallPart() as part):
|
|
644
|
-
tool_call_id = part.tool_call_id
|
|
645
|
-
tool_input = safe_args_as_dict(part, default={})
|
|
646
|
-
self._current_tool_inputs[tool_call_id] = tool_input
|
|
647
|
-
state = self._get_or_create_tool_state(
|
|
648
|
-
tool_call_id=tool_call_id,
|
|
649
|
-
tool_name=part.tool_name,
|
|
650
|
-
tool_input=tool_input,
|
|
651
|
-
)
|
|
652
|
-
await state.start()
|
|
653
|
-
|
|
654
|
-
# Builtin tool completed
|
|
655
|
-
case PartStartEvent(part=BuiltinToolReturnPart() as part):
|
|
656
|
-
tool_call_id = part.tool_call_id
|
|
657
|
-
if complete_state := self._tool_call_states.get(tool_call_id):
|
|
658
|
-
final_output = part.content
|
|
659
|
-
if complete_state.has_content:
|
|
660
|
-
await complete_state.complete(raw_output=final_output)
|
|
661
|
-
else:
|
|
662
|
-
converted_blocks = to_acp_content_blocks(final_output)
|
|
663
|
-
content_items = [
|
|
664
|
-
ContentToolCallContent(content=block) for block in converted_blocks
|
|
665
|
-
]
|
|
666
|
-
await complete_state.complete(
|
|
667
|
-
raw_output=final_output,
|
|
668
|
-
content=content_items,
|
|
669
|
-
)
|
|
670
|
-
self._cleanup_tool_state(tool_call_id)
|
|
671
|
-
|
|
672
|
-
case PartStartEvent(part=part):
|
|
673
|
-
self.log.debug("Received unhandled PartStartEvent", part=part)
|
|
674
|
-
|
|
675
|
-
# Tool call streaming delta - create/update state and start notification
|
|
676
|
-
case PartDeltaEvent(delta=ToolCallPartDelta() as delta):
|
|
677
|
-
if delta_part := delta.as_part():
|
|
678
|
-
tool_call_id = delta_part.tool_call_id
|
|
679
|
-
try:
|
|
680
|
-
tool_input = delta_part.args_as_dict()
|
|
681
|
-
except ValueError:
|
|
682
|
-
# Args still streaming, not valid JSON yet - skip this delta
|
|
683
|
-
pass
|
|
684
|
-
else:
|
|
685
|
-
self._current_tool_inputs[tool_call_id] = tool_input
|
|
686
|
-
# Create state and send initial notification
|
|
687
|
-
state = self._get_or_create_tool_state(
|
|
688
|
-
tool_call_id=tool_call_id,
|
|
689
|
-
tool_name=delta_part.tool_name,
|
|
690
|
-
tool_input=tool_input,
|
|
691
|
-
)
|
|
692
|
-
await state.start()
|
|
693
|
-
|
|
694
|
-
# Tool call started - create/update state and start notification
|
|
695
|
-
case FunctionToolCallEvent(part=part):
|
|
696
|
-
tool_call_id = part.tool_call_id
|
|
697
|
-
tool_input = safe_args_as_dict(part, default={})
|
|
698
|
-
self._current_tool_inputs[tool_call_id] = tool_input
|
|
699
|
-
# Create state and send initial notification
|
|
700
|
-
state = self._get_or_create_tool_state(
|
|
701
|
-
tool_call_id=tool_call_id,
|
|
702
|
-
tool_name=part.tool_name,
|
|
703
|
-
tool_input=tool_input,
|
|
704
|
-
)
|
|
705
|
-
await state.start()
|
|
706
|
-
|
|
707
|
-
# Tool completed successfully - update state and finalize
|
|
708
|
-
case FunctionToolResultEvent(
|
|
709
|
-
result=ToolReturnPart(content=content, tool_name=tool_name) as result,
|
|
710
|
-
tool_call_id=tool_call_id,
|
|
711
|
-
):
|
|
712
|
-
if isinstance(content, AsyncGenerator):
|
|
713
|
-
full_content = ""
|
|
714
|
-
async for chunk in content:
|
|
715
|
-
full_content += str(chunk)
|
|
716
|
-
# Stream progress through state
|
|
717
|
-
if tool_state := self._tool_call_states.get(tool_call_id):
|
|
718
|
-
await tool_state.update(status="in_progress", raw_output=chunk)
|
|
719
|
-
|
|
720
|
-
# Replace the AsyncGenerator with the full content to prevent errors
|
|
721
|
-
result.content = full_content
|
|
722
|
-
final_output = full_content
|
|
723
|
-
else:
|
|
724
|
-
final_output = result.content
|
|
725
|
-
|
|
726
|
-
# Complete tool call through state (preserves accumulated content/locations)
|
|
727
|
-
if complete_state := self._tool_call_states.get(tool_call_id):
|
|
728
|
-
# Only add return value as content if no content was emitted during execution
|
|
729
|
-
if complete_state.has_content:
|
|
730
|
-
# Content already provided via progress events - just set raw_output
|
|
731
|
-
await complete_state.complete(raw_output=final_output)
|
|
732
|
-
else:
|
|
733
|
-
# No content yet - convert return value for display
|
|
734
|
-
converted_blocks = to_acp_content_blocks(final_output)
|
|
735
|
-
content_items = [
|
|
736
|
-
ContentToolCallContent(content=block) for block in converted_blocks
|
|
737
|
-
]
|
|
738
|
-
await complete_state.complete(
|
|
739
|
-
raw_output=final_output,
|
|
740
|
-
content=content_items,
|
|
741
|
-
)
|
|
742
|
-
self._cleanup_tool_state(tool_call_id)
|
|
743
|
-
|
|
744
|
-
# Tool failed with retry - update state with error
|
|
745
|
-
case FunctionToolResultEvent(
|
|
746
|
-
result=RetryPromptPart(tool_name=tool_name) as result,
|
|
747
|
-
tool_call_id=tool_call_id,
|
|
748
|
-
):
|
|
749
|
-
error_message = result.model_response()
|
|
750
|
-
if fail_state := self._tool_call_states.get(tool_call_id):
|
|
751
|
-
await fail_state.fail(error=error_message)
|
|
752
|
-
self._cleanup_tool_state(tool_call_id)
|
|
753
|
-
|
|
754
|
-
# Tool emits its own start event - update state with better title/content
|
|
755
|
-
case ToolCallStartEvent(
|
|
756
|
-
tool_call_id=tool_call_id,
|
|
757
|
-
tool_name=tool_name,
|
|
758
|
-
title=title,
|
|
759
|
-
kind=kind,
|
|
760
|
-
locations=loc_items,
|
|
761
|
-
raw_input=raw_input,
|
|
762
|
-
):
|
|
763
|
-
self.log.debug(
|
|
764
|
-
"Tool call start event", tool_name=tool_name, tool_call_id=tool_call_id
|
|
765
|
-
)
|
|
766
|
-
# Get or create state (may already exist from FunctionToolCallEvent)
|
|
767
|
-
state = self._get_or_create_tool_state(
|
|
768
|
-
tool_call_id=tool_call_id,
|
|
769
|
-
tool_name=tool_name,
|
|
770
|
-
tool_input=raw_input or {},
|
|
771
|
-
)
|
|
772
|
-
# Convert LocationContentItem objects to ACP format
|
|
773
|
-
acp_locations = [
|
|
774
|
-
ToolCallLocation(path=loc.path, line=loc.line) for loc in loc_items
|
|
775
|
-
]
|
|
776
|
-
# Update state with tool-provided details (better title, content, locations)
|
|
777
|
-
await state.update(title=title, kind=kind, locations=acp_locations or None)
|
|
778
|
-
|
|
779
|
-
# Tool progress event - update state with title and content
|
|
780
|
-
case ToolCallProgressEvent(
|
|
781
|
-
tool_call_id=tool_call_id,
|
|
782
|
-
title=title,
|
|
783
|
-
status=status,
|
|
784
|
-
items=items,
|
|
785
|
-
) if tool_call_id and tool_call_id in self._tool_call_states:
|
|
786
|
-
progress_state = self._tool_call_states[tool_call_id]
|
|
787
|
-
self.log.debug("Progress event", tool_call_id=tool_call_id, title=title)
|
|
788
|
-
|
|
789
|
-
# Convert items to ACP content
|
|
790
|
-
from agentpool.agents.events import (
|
|
791
|
-
DiffContentItem,
|
|
792
|
-
FileContentItem,
|
|
793
|
-
LocationContentItem,
|
|
794
|
-
TerminalContentItem,
|
|
795
|
-
TextContentItem,
|
|
796
|
-
)
|
|
797
|
-
from agentpool_server.acp_server.syntax_detection import (
|
|
798
|
-
format_zed_code_block,
|
|
799
|
-
)
|
|
800
|
-
|
|
801
|
-
acp_content: list[Any] = []
|
|
802
|
-
location_paths: list[str] = []
|
|
803
|
-
|
|
804
|
-
for item in items:
|
|
805
|
-
match item:
|
|
806
|
-
case TerminalContentItem(terminal_id=tid):
|
|
807
|
-
acp_content.append(TerminalToolCallContent(terminal_id=tid))
|
|
808
|
-
case TextContentItem(text=text):
|
|
809
|
-
acp_content.append(ContentToolCallContent.text(text=text))
|
|
810
|
-
case FileContentItem(
|
|
811
|
-
content=file_content,
|
|
812
|
-
path=file_path,
|
|
813
|
-
start_line=start_line,
|
|
814
|
-
end_line=end_line,
|
|
815
|
-
):
|
|
816
|
-
# Format as Zed-compatible code block with clickable path
|
|
817
|
-
formatted = format_zed_code_block(
|
|
818
|
-
file_content, file_path, start_line, end_line
|
|
819
|
-
)
|
|
820
|
-
acp_content.append(ContentToolCallContent.text(text=formatted))
|
|
821
|
-
# Also add path to locations for "follow along" feature
|
|
822
|
-
location_paths.append(file_path)
|
|
823
|
-
case DiffContentItem(path=diff_path, old_text=old, new_text=new):
|
|
824
|
-
# Send diff via direct notification
|
|
825
|
-
await self.notifications.file_edit_progress(
|
|
826
|
-
tool_call_id=tool_call_id,
|
|
827
|
-
path=diff_path,
|
|
828
|
-
old_text=old or "",
|
|
829
|
-
new_text=new,
|
|
830
|
-
status=status,
|
|
831
|
-
changed_lines=[],
|
|
832
|
-
)
|
|
833
|
-
# Mark content as sent so completion doesn't override
|
|
834
|
-
progress_state._has_content = True
|
|
835
|
-
case LocationContentItem(path=loc_path):
|
|
836
|
-
location_paths.append(loc_path)
|
|
837
|
-
|
|
838
|
-
await progress_state.update(
|
|
839
|
-
title=title,
|
|
840
|
-
status="in_progress",
|
|
841
|
-
content=acp_content if acp_content else None,
|
|
842
|
-
locations=location_paths if location_paths else None,
|
|
843
|
-
)
|
|
844
|
-
|
|
845
|
-
case FinalResultEvent():
|
|
846
|
-
self.log.debug("Final result received")
|
|
847
|
-
|
|
848
|
-
case StreamCompleteEvent():
|
|
849
|
-
pass
|
|
850
|
-
|
|
851
|
-
case PlanUpdateEvent(entries=entries, tool_call_id=tool_call_id):
|
|
852
|
-
acp_entries = [
|
|
853
|
-
PlanEntry(content=e.content, priority=e.priority, status=e.status)
|
|
854
|
-
for e in entries
|
|
855
|
-
]
|
|
856
|
-
await self.notifications.update_plan(acp_entries)
|
|
857
|
-
|
|
858
|
-
case CompactionEvent(trigger=trigger, phase=phase):
|
|
859
|
-
# Convert semantic CompactionEvent to text for ACP display
|
|
860
|
-
if phase == "starting":
|
|
861
|
-
if trigger == "auto":
|
|
862
|
-
text = (
|
|
863
|
-
"\n\n---\n\n"
|
|
864
|
-
"📦 **Context compaction** triggered. "
|
|
865
|
-
"Summarizing conversation..."
|
|
866
|
-
"\n\n---\n\n"
|
|
867
|
-
)
|
|
868
|
-
else:
|
|
869
|
-
text = (
|
|
870
|
-
"\n\n---\n\n"
|
|
871
|
-
"📦 **Manual compaction** requested. "
|
|
872
|
-
"Summarizing conversation..."
|
|
873
|
-
"\n\n---\n\n"
|
|
874
|
-
)
|
|
875
|
-
await self.notifications.send_agent_text(text)
|
|
876
|
-
|
|
877
|
-
case _:
|
|
878
|
-
self.log.debug("Unhandled event", event_type=type(event).__name__)
|
|
879
|
-
|
|
880
573
|
async def close(self) -> None:
|
|
881
574
|
"""Close the session and cleanup resources."""
|
|
882
|
-
self._current_tool_inputs.clear()
|
|
883
|
-
self._tool_call_states.clear()
|
|
884
|
-
|
|
885
575
|
try:
|
|
886
576
|
await self.acp_env.__aexit__(None, None, None)
|
|
887
577
|
except Exception:
|
|
@@ -964,11 +654,10 @@ class ACPSession:
|
|
|
964
654
|
Returns:
|
|
965
655
|
List of ACP AvailableCommand objects compatible with current node
|
|
966
656
|
"""
|
|
967
|
-
all_commands = self.command_store.list_commands()
|
|
968
657
|
current_node = self.agent
|
|
969
658
|
# Filter commands by node compatibility
|
|
970
659
|
compatible_commands = []
|
|
971
|
-
for cmd in
|
|
660
|
+
for cmd in self.command_store.list_commands():
|
|
972
661
|
cmd_cls = cmd if isinstance(cmd, type) else type(cmd)
|
|
973
662
|
# Check if command supports current node type
|
|
974
663
|
if issubclass(cmd_cls, NodeCommand) and not cmd_cls.supports_node(current_node): # type: ignore[union-attr]
|
|
@@ -1022,11 +711,7 @@ class ACPSession:
|
|
|
1022
711
|
)
|
|
1023
712
|
|
|
1024
713
|
def register_update_callback(self, callback: Callable[[], None]) -> None:
|
|
1025
|
-
"""Register callback for command updates.
|
|
1026
|
-
|
|
1027
|
-
Args:
|
|
1028
|
-
callback: Function to call when commands are updated
|
|
1029
|
-
"""
|
|
714
|
+
"""Register callback for command updates."""
|
|
1030
715
|
self._update_callbacks.append(callback)
|
|
1031
716
|
|
|
1032
717
|
def create_mcp_command(self, prompt: MCPClientPrompt) -> Command:
|
|
@@ -1047,16 +732,13 @@ class ACPSession:
|
|
|
1047
732
|
) -> None:
|
|
1048
733
|
"""Execute the MCP prompt with parsed arguments."""
|
|
1049
734
|
# Map parsed args to prompt parameters
|
|
1050
|
-
|
|
1051
|
-
result = {}
|
|
1052
735
|
# Map positional args to prompt parameter names
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
try:
|
|
1059
|
-
# Get prompt components
|
|
736
|
+
result = {
|
|
737
|
+
prompt.arguments[i]["name"]: arg_value
|
|
738
|
+
for i, arg_value in enumerate(args)
|
|
739
|
+
if i < len(prompt.arguments)
|
|
740
|
+
} | kwargs
|
|
741
|
+
try: # Get prompt components
|
|
1060
742
|
components = await prompt.get_components(result or None)
|
|
1061
743
|
self.staged_content.add(components)
|
|
1062
744
|
# Send confirmation
|
|
@@ -1067,15 +749,13 @@ class ACPSession:
|
|
|
1067
749
|
logger.exception("MCP prompt execution failed", prompt=prompt.name)
|
|
1068
750
|
await ctx.print(f"❌ Prompt error: {e}")
|
|
1069
751
|
|
|
1070
|
-
|
|
1071
|
-
" ".join(f"<{arg['name']}>" for arg in prompt.arguments) if prompt.arguments else None
|
|
1072
|
-
)
|
|
752
|
+
usage = " ".join(f"<{i['name']}>" for i in args) if (args := prompt.arguments) else None
|
|
1073
753
|
return Command.from_raw(
|
|
1074
754
|
execute_prompt,
|
|
1075
755
|
name=prompt.name,
|
|
1076
756
|
description=prompt.description or f"MCP prompt: {prompt.name}",
|
|
1077
757
|
category="mcp",
|
|
1078
|
-
usage=
|
|
758
|
+
usage=usage,
|
|
1079
759
|
)
|
|
1080
760
|
|
|
1081
761
|
def create_prompt_hub_command(
|