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
|
@@ -6,11 +6,15 @@ from dataclasses import dataclass
|
|
|
6
6
|
from typing import TYPE_CHECKING, Any, Literal
|
|
7
7
|
|
|
8
8
|
from agentpool.agents.context import AgentContext # noqa: TC001
|
|
9
|
+
from agentpool.agents.events import TextContentItem
|
|
9
10
|
from agentpool.resource_providers import ResourceProvider
|
|
11
|
+
from agentpool.tools.base import ToolResult
|
|
10
12
|
from agentpool.utils.streams import TodoPriority, TodoStatus # noqa: TC001
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
if TYPE_CHECKING:
|
|
16
|
+
from collections.abc import Sequence
|
|
17
|
+
|
|
14
18
|
from agentpool.tools.base import Tool
|
|
15
19
|
from agentpool.utils.streams import TodoTracker
|
|
16
20
|
|
|
@@ -18,7 +22,7 @@ if TYPE_CHECKING:
|
|
|
18
22
|
# Keep PlanEntry for backward compatibility with event emitting
|
|
19
23
|
PlanEntryPriority = Literal["high", "medium", "low"]
|
|
20
24
|
PlanEntryStatus = Literal["pending", "in_progress", "completed"]
|
|
21
|
-
PlanToolMode = Literal["granular", "declarative"
|
|
25
|
+
PlanToolMode = Literal["granular", "declarative"]
|
|
22
26
|
|
|
23
27
|
|
|
24
28
|
@dataclass(kw_only=True)
|
|
@@ -59,12 +63,12 @@ class PlanProvider(ResourceProvider):
|
|
|
59
63
|
|
|
60
64
|
kind = "tools"
|
|
61
65
|
|
|
62
|
-
def __init__(self, mode: PlanToolMode = "
|
|
66
|
+
def __init__(self, mode: PlanToolMode = "declarative") -> None:
|
|
63
67
|
"""Initialize plan provider.
|
|
64
68
|
|
|
65
69
|
Args:
|
|
66
70
|
mode: Tool mode - 'granular' for separate tools, 'declarative' for
|
|
67
|
-
single set_plan tool
|
|
71
|
+
single set_plan tool.
|
|
68
72
|
"""
|
|
69
73
|
super().__init__(name="plan")
|
|
70
74
|
self.mode = mode
|
|
@@ -75,21 +79,13 @@ class PlanProvider(ResourceProvider):
|
|
|
75
79
|
return agent_ctx.pool.todos
|
|
76
80
|
return None
|
|
77
81
|
|
|
78
|
-
async def get_tools(self) ->
|
|
82
|
+
async def get_tools(self) -> Sequence[Tool]:
|
|
79
83
|
"""Get plan management tools based on mode."""
|
|
80
84
|
tools: list[Tool] = [self.create_tool(self.get_plan, category="read")]
|
|
81
85
|
|
|
82
86
|
if self.mode == "declarative":
|
|
83
87
|
# Single bulk tool for capable models
|
|
84
88
|
tools.append(self.create_tool(self.set_plan, category="other"))
|
|
85
|
-
elif self.mode == "hybrid":
|
|
86
|
-
# Both approaches - model chooses
|
|
87
|
-
tools.extend([
|
|
88
|
-
self.create_tool(self.set_plan, category="other"),
|
|
89
|
-
self.create_tool(self.add_plan_entry, category="other"),
|
|
90
|
-
self.create_tool(self.update_plan_entry, category="edit"),
|
|
91
|
-
self.create_tool(self.remove_plan_entry, category="delete"),
|
|
92
|
-
])
|
|
93
89
|
else:
|
|
94
90
|
# granular mode (default) - separate tools for simpler models
|
|
95
91
|
tools.extend([
|
|
@@ -100,7 +96,7 @@ class PlanProvider(ResourceProvider):
|
|
|
100
96
|
|
|
101
97
|
return tools
|
|
102
98
|
|
|
103
|
-
async def get_plan(self, agent_ctx: AgentContext) ->
|
|
99
|
+
async def get_plan(self, agent_ctx: AgentContext) -> ToolResult:
|
|
104
100
|
"""Get the current plan formatted as markdown.
|
|
105
101
|
|
|
106
102
|
Args:
|
|
@@ -111,7 +107,15 @@ class PlanProvider(ResourceProvider):
|
|
|
111
107
|
"""
|
|
112
108
|
tracker = self._get_tracker(agent_ctx)
|
|
113
109
|
if tracker is None or not tracker.entries:
|
|
114
|
-
|
|
110
|
+
# Emit progress for empty plan
|
|
111
|
+
await agent_ctx.events.tool_call_progress(
|
|
112
|
+
title="Fetched plan (empty)",
|
|
113
|
+
items=[TextContentItem(text="*No tasks in plan yet.*")],
|
|
114
|
+
)
|
|
115
|
+
return ToolResult(
|
|
116
|
+
content="## Plan\n\n*No plan entries yet.",
|
|
117
|
+
metadata={"todos": []},
|
|
118
|
+
)
|
|
115
119
|
|
|
116
120
|
lines = ["## Plan", ""]
|
|
117
121
|
status_icons = {
|
|
@@ -129,13 +133,35 @@ class PlanProvider(ResourceProvider):
|
|
|
129
133
|
priority = priority_labels.get(entry.priority, "")
|
|
130
134
|
lines.append(f"{i}. {icon} {priority} {entry.content} *({entry.status})*")
|
|
131
135
|
|
|
132
|
-
|
|
136
|
+
# Count completed entries for summary
|
|
137
|
+
completed = sum(1 for e in tracker.entries if e.status == "completed")
|
|
138
|
+
total = len(tracker.entries)
|
|
139
|
+
|
|
140
|
+
# Build title with summary
|
|
141
|
+
title = "Fetched plan with 1 task" if total == 1 else f"Fetched plan with {total} tasks"
|
|
142
|
+
if completed > 0:
|
|
143
|
+
title += f" ({completed} completed)"
|
|
144
|
+
|
|
145
|
+
# Emit progress with plan preview
|
|
146
|
+
plan_text = "\n".join(lines)
|
|
147
|
+
await agent_ctx.events.tool_call_progress(
|
|
148
|
+
title=title,
|
|
149
|
+
items=[TextContentItem(text=plan_text)],
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# Convert to OpenCode format for metadata
|
|
153
|
+
todos = [{"content": e.content, "status": e.status} for e in tracker.entries]
|
|
154
|
+
|
|
155
|
+
return ToolResult(
|
|
156
|
+
content=plan_text,
|
|
157
|
+
metadata={"todos": todos},
|
|
158
|
+
)
|
|
133
159
|
|
|
134
160
|
async def set_plan(
|
|
135
161
|
self,
|
|
136
162
|
agent_ctx: AgentContext,
|
|
137
163
|
entries: list[dict[str, Any]],
|
|
138
|
-
) ->
|
|
164
|
+
) -> ToolResult:
|
|
139
165
|
"""Replace the entire plan with new entries (declarative/bulk update).
|
|
140
166
|
|
|
141
167
|
This is more efficient than multiple add/update calls when setting
|
|
@@ -154,7 +180,10 @@ class PlanProvider(ResourceProvider):
|
|
|
154
180
|
"""
|
|
155
181
|
tracker = self._get_tracker(agent_ctx)
|
|
156
182
|
if tracker is None:
|
|
157
|
-
return
|
|
183
|
+
return ToolResult(
|
|
184
|
+
content="Error: No pool available for plan tracking",
|
|
185
|
+
metadata={"todos": []},
|
|
186
|
+
)
|
|
158
187
|
|
|
159
188
|
# Clear existing entries
|
|
160
189
|
tracker.clear()
|
|
@@ -170,7 +199,40 @@ class PlanProvider(ResourceProvider):
|
|
|
170
199
|
|
|
171
200
|
await self._emit_plan_update(agent_ctx)
|
|
172
201
|
|
|
173
|
-
|
|
202
|
+
# Build summary for user feedback
|
|
203
|
+
entry_count = len(tracker.entries)
|
|
204
|
+
if entry_count == 0:
|
|
205
|
+
title = "Cleared plan"
|
|
206
|
+
elif entry_count == 1:
|
|
207
|
+
title = "Set plan with 1 task"
|
|
208
|
+
else:
|
|
209
|
+
title = f"Set plan with {entry_count} tasks"
|
|
210
|
+
|
|
211
|
+
# Format entries list for details
|
|
212
|
+
if tracker.entries:
|
|
213
|
+
lines = ["**New Plan:**"]
|
|
214
|
+
for i, e in enumerate(tracker.entries):
|
|
215
|
+
priority_emoji = {"high": "🔴", "medium": "🟡", "low": "🟢"}.get(e.priority, "")
|
|
216
|
+
status_emoji = {"pending": "⬚", "in_progress": "◐", "completed": "✓"}.get(
|
|
217
|
+
e.status, ""
|
|
218
|
+
)
|
|
219
|
+
lines.append(f"{i + 1}. {priority_emoji} {status_emoji} {e.content}")
|
|
220
|
+
details = "\n".join(lines)
|
|
221
|
+
else:
|
|
222
|
+
details = "*Plan is empty*"
|
|
223
|
+
|
|
224
|
+
await agent_ctx.events.tool_call_progress(
|
|
225
|
+
title=title,
|
|
226
|
+
items=[TextContentItem(text=details)],
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
# Convert to OpenCode format for metadata
|
|
230
|
+
todos = [{"content": e.content, "status": e.status} for e in tracker.entries]
|
|
231
|
+
|
|
232
|
+
return ToolResult(
|
|
233
|
+
content=f"Plan updated with {entry_count} entries",
|
|
234
|
+
metadata={"todos": todos},
|
|
235
|
+
)
|
|
174
236
|
|
|
175
237
|
async def add_plan_entry(
|
|
176
238
|
self,
|
|
@@ -199,6 +261,15 @@ class PlanProvider(ResourceProvider):
|
|
|
199
261
|
|
|
200
262
|
await self._emit_plan_update(agent_ctx)
|
|
201
263
|
|
|
264
|
+
# User feedback
|
|
265
|
+
priority_emoji = {"high": "🔴", "medium": "🟡", "low": "🟢"}.get(priority, "")
|
|
266
|
+
title = f"Added task {entry_index + 1} {priority_emoji}"
|
|
267
|
+
details = f"**Task {entry_index + 1}**: {content}"
|
|
268
|
+
await agent_ctx.events.tool_call_progress(
|
|
269
|
+
title=title,
|
|
270
|
+
items=[TextContentItem(text=details)],
|
|
271
|
+
)
|
|
272
|
+
|
|
202
273
|
return f"Added plan entry at index {entry_index}: {content!r} (priority={priority!r})"
|
|
203
274
|
|
|
204
275
|
async def update_plan_entry(
|
|
@@ -242,6 +313,19 @@ class PlanProvider(ResourceProvider):
|
|
|
242
313
|
tracker.update_by_index(index, content=content, status=status, priority=priority)
|
|
243
314
|
|
|
244
315
|
await self._emit_plan_update(agent_ctx)
|
|
316
|
+
|
|
317
|
+
# Build title with key info
|
|
318
|
+
entry = tracker.entries[index]
|
|
319
|
+
status_emoji = {"pending": "⬚", "in_progress": "◐", "completed": "✓"}.get(entry.status, "")
|
|
320
|
+
title = f"Updated task {index + 1} {status_emoji}"
|
|
321
|
+
|
|
322
|
+
# Send detailed content
|
|
323
|
+
details = f"**Task {index + 1}**: {entry.content}\n\nChanges: {', '.join(updates)}"
|
|
324
|
+
await agent_ctx.events.tool_call_progress(
|
|
325
|
+
title=title,
|
|
326
|
+
items=[TextContentItem(text=details)],
|
|
327
|
+
)
|
|
328
|
+
|
|
245
329
|
return f"Updated entry {index}: {', '.join(updates)}"
|
|
246
330
|
|
|
247
331
|
async def remove_plan_entry(self, agent_ctx: AgentContext, index: int) -> str:
|
|
@@ -267,6 +351,15 @@ class PlanProvider(ResourceProvider):
|
|
|
267
351
|
if removed_entry is None:
|
|
268
352
|
return f"Error: Could not remove entry at index {index}"
|
|
269
353
|
|
|
354
|
+
# User feedback
|
|
355
|
+
remaining = len(tracker.entries)
|
|
356
|
+
title = f"Removed task {index + 1}"
|
|
357
|
+
details = f"**Removed**: {removed_entry.content}\n\nRemaining tasks: {remaining}"
|
|
358
|
+
await agent_ctx.events.tool_call_progress(
|
|
359
|
+
title=title,
|
|
360
|
+
items=[TextContentItem(text=details)],
|
|
361
|
+
)
|
|
362
|
+
|
|
270
363
|
if tracker.entries:
|
|
271
364
|
return f"Removed entry {index}: {removed_entry.content!r}, remaining entries reindexed"
|
|
272
365
|
return f"Removed entry {index}: {removed_entry.content!r}, plan is now empty"
|
|
@@ -9,10 +9,12 @@ from agentpool.resource_providers import ResourceProvider
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
if TYPE_CHECKING:
|
|
12
|
+
from collections.abc import Sequence
|
|
13
|
+
|
|
12
14
|
from agentpool import AgentPool
|
|
13
15
|
from agentpool.prompts.prompts import BasePrompt
|
|
14
|
-
from agentpool.
|
|
15
|
-
from
|
|
16
|
+
from agentpool.resource_providers.resource_info import ResourceInfo
|
|
17
|
+
from agentpool.tools import Tool
|
|
16
18
|
|
|
17
19
|
logger = get_logger(__name__)
|
|
18
20
|
|
|
@@ -43,7 +45,7 @@ class PoolResourceProvider(ResourceProvider):
|
|
|
43
45
|
self.zed_mode = zed_mode
|
|
44
46
|
self.include_team_members = include_team_members
|
|
45
47
|
|
|
46
|
-
async def get_tools(self) ->
|
|
48
|
+
async def get_tools(self) -> Sequence[Tool]:
|
|
47
49
|
"""Get tools from all agents in pool."""
|
|
48
50
|
team_tools = [team.to_tool() for team in self.pool.teams.values()]
|
|
49
51
|
agents = list(self.pool.agents.values())
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Resource information model with read capability."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Awaitable, Callable
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Self
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from mcp.types import Resource as MCPResource
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Type alias for the reader function
|
|
15
|
+
ResourceReader = Callable[[str], Awaitable[list[str]]]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class ResourceInfo:
|
|
20
|
+
"""Information about an available resource with read capability.
|
|
21
|
+
|
|
22
|
+
This class provides essential information about a resource and can read
|
|
23
|
+
its content when a reader is available.
|
|
24
|
+
|
|
25
|
+
Example:
|
|
26
|
+
```python
|
|
27
|
+
# List resources from tool manager
|
|
28
|
+
resources = await agent.tools.list_resources()
|
|
29
|
+
|
|
30
|
+
# Read a specific resource
|
|
31
|
+
for resource in resources:
|
|
32
|
+
if resource.name == "config.json":
|
|
33
|
+
content = await resource.read()
|
|
34
|
+
print(content)
|
|
35
|
+
```
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
name: str
|
|
39
|
+
"""Name of the resource"""
|
|
40
|
+
|
|
41
|
+
uri: str
|
|
42
|
+
"""URI identifying the resource location"""
|
|
43
|
+
|
|
44
|
+
description: str | None = None
|
|
45
|
+
"""Optional description of the resource's content or purpose"""
|
|
46
|
+
|
|
47
|
+
mime_type: str | None = None
|
|
48
|
+
"""MIME type of the resource content"""
|
|
49
|
+
|
|
50
|
+
client: str | None = None
|
|
51
|
+
"""Name of the MCP client/server providing this resource"""
|
|
52
|
+
|
|
53
|
+
annotations: dict[str, Any] = field(default_factory=dict)
|
|
54
|
+
"""Additional annotations/metadata for the resource"""
|
|
55
|
+
|
|
56
|
+
_reader: ResourceReader | None = field(default=None, repr=False, compare=False)
|
|
57
|
+
"""Internal reader function for fetching content"""
|
|
58
|
+
|
|
59
|
+
async def read(self) -> list[str]:
|
|
60
|
+
"""Read the resource content.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
List of text contents from the resource
|
|
64
|
+
|
|
65
|
+
Raises:
|
|
66
|
+
RuntimeError: If no reader is available or read fails
|
|
67
|
+
"""
|
|
68
|
+
if self._reader is None:
|
|
69
|
+
msg = f"No reader available for resource: {self.uri}"
|
|
70
|
+
raise RuntimeError(msg)
|
|
71
|
+
return await self._reader(self.uri)
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def can_read(self) -> bool:
|
|
75
|
+
"""Check if this resource can be read."""
|
|
76
|
+
return self._reader is not None
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
async def from_mcp_resource(
|
|
80
|
+
cls,
|
|
81
|
+
resource: MCPResource,
|
|
82
|
+
client_name: str | None = None,
|
|
83
|
+
reader: ResourceReader | None = None,
|
|
84
|
+
) -> Self:
|
|
85
|
+
"""Create ResourceInfo from MCP resource.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
resource: MCP resource object
|
|
89
|
+
client_name: Name of the MCP client providing this resource
|
|
90
|
+
reader: Optional reader function for fetching content
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
ResourceInfo instance
|
|
94
|
+
"""
|
|
95
|
+
annotations: dict[str, Any] = {}
|
|
96
|
+
if resource.annotations:
|
|
97
|
+
# Convert annotations to simple dict
|
|
98
|
+
if hasattr(resource.annotations, "model_dump"):
|
|
99
|
+
annotations = resource.annotations.model_dump(exclude_none=True)
|
|
100
|
+
elif isinstance(resource.annotations, dict):
|
|
101
|
+
annotations = resource.annotations
|
|
102
|
+
|
|
103
|
+
return cls(
|
|
104
|
+
name=resource.name,
|
|
105
|
+
uri=str(resource.uri),
|
|
106
|
+
description=resource.description,
|
|
107
|
+
mime_type=resource.mimeType,
|
|
108
|
+
client=client_name,
|
|
109
|
+
annotations=annotations,
|
|
110
|
+
_reader=reader,
|
|
111
|
+
)
|
|
@@ -14,8 +14,8 @@ if TYPE_CHECKING:
|
|
|
14
14
|
from agentpool import Agent, MessageNode
|
|
15
15
|
from agentpool.common_types import ToolSource, ToolType
|
|
16
16
|
from agentpool.prompts.prompts import BasePrompt
|
|
17
|
+
from agentpool.resource_providers.resource_info import ResourceInfo
|
|
17
18
|
from agentpool.tools.base import Tool
|
|
18
|
-
from agentpool_config.resources import ResourceInfo
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
class StaticResourceProvider(ResourceProvider):
|
|
@@ -48,7 +48,7 @@ class StaticResourceProvider(ResourceProvider):
|
|
|
48
48
|
self._prompts = list(prompts) if prompts else []
|
|
49
49
|
self._resources = list(resources) if resources else []
|
|
50
50
|
|
|
51
|
-
async def get_tools(self) ->
|
|
51
|
+
async def get_tools(self) -> Sequence[Tool]:
|
|
52
52
|
"""Get pre-configured tools."""
|
|
53
53
|
return self._tools
|
|
54
54
|
|
agentpool/sessions/__init__.py
CHANGED
|
@@ -4,11 +4,13 @@ from agentpool.sessions.models import ProjectData, SessionData
|
|
|
4
4
|
from agentpool.sessions.store import SessionStore
|
|
5
5
|
from agentpool.sessions.manager import SessionManager
|
|
6
6
|
from agentpool.sessions.session import ClientSession
|
|
7
|
+
from agentpool.sessions.protocol import SessionInfo
|
|
7
8
|
|
|
8
9
|
__all__ = [
|
|
9
10
|
"ClientSession",
|
|
10
11
|
"ProjectData",
|
|
11
12
|
"SessionData",
|
|
13
|
+
"SessionInfo",
|
|
12
14
|
"SessionManager",
|
|
13
15
|
"SessionStore",
|
|
14
16
|
]
|
agentpool/sessions/manager.py
CHANGED
|
@@ -4,7 +4,6 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
6
|
from typing import TYPE_CHECKING, Any, Self
|
|
7
|
-
from uuid import uuid4
|
|
8
7
|
|
|
9
8
|
from agentpool.log import get_logger
|
|
10
9
|
from agentpool.sessions.models import SessionData
|
|
@@ -171,7 +170,7 @@ class SessionManager:
|
|
|
171
170
|
data = SessionData(
|
|
172
171
|
session_id=session_id,
|
|
173
172
|
agent_name=agent_name,
|
|
174
|
-
conversation_id=conversation_id or
|
|
173
|
+
conversation_id=conversation_id or session_id,
|
|
175
174
|
pool_id=self._pool_id,
|
|
176
175
|
project_id=project_id,
|
|
177
176
|
cwd=cwd,
|
|
@@ -309,7 +308,7 @@ class SessionManager:
|
|
|
309
308
|
if active_only:
|
|
310
309
|
sessions = list(self._active.keys())
|
|
311
310
|
if agent_name:
|
|
312
|
-
sessions = [sid for sid, s in self._active.items() if s.
|
|
311
|
+
sessions = [sid for sid, s in self._active.items() if s.agent.name == agent_name]
|
|
313
312
|
return sessions
|
|
314
313
|
|
|
315
314
|
return await self._store.list_sessions(
|
agentpool/sessions/models.py
CHANGED
|
@@ -68,9 +68,6 @@ class SessionData(Schema):
|
|
|
68
68
|
conversation_id: str
|
|
69
69
|
"""Links to conversation in StorageManager."""
|
|
70
70
|
|
|
71
|
-
title: str | None = None
|
|
72
|
-
"""AI-generated or user-provided title for the conversation."""
|
|
73
|
-
|
|
74
71
|
pool_id: str | None = None
|
|
75
72
|
"""Optional pool/manifest identifier for multi-pool setups."""
|
|
76
73
|
|
|
@@ -116,6 +113,12 @@ class SessionData(Schema):
|
|
|
116
113
|
new_metadata = {**self.metadata, **kwargs}
|
|
117
114
|
return self.model_copy(update={"metadata": new_metadata, "last_active": get_now()})
|
|
118
115
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
116
|
+
@property
|
|
117
|
+
def title(self) -> str | None:
|
|
118
|
+
"""Human-readable title (from metadata, for protocol compatibility)."""
|
|
119
|
+
return self.metadata.get("title")
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def updated_at(self) -> str | None:
|
|
123
|
+
"""ISO timestamp of last activity (for protocol compatibility)."""
|
|
124
|
+
return self.last_active.isoformat() if self.last_active else None
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Session protocol for unified session management across agent types."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Protocol, runtime_checkable
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@runtime_checkable
|
|
9
|
+
class SessionInfo(Protocol):
|
|
10
|
+
"""Protocol for session information.
|
|
11
|
+
|
|
12
|
+
This protocol provides a unified interface for session metadata across
|
|
13
|
+
different agent implementations (ACP, ClaudeCode, native agents).
|
|
14
|
+
|
|
15
|
+
Both ACP's SessionInfo and our SessionData can fulfill this protocol.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
session_id: str
|
|
19
|
+
"""Unique identifier for the session."""
|
|
20
|
+
|
|
21
|
+
cwd: str | None
|
|
22
|
+
"""Working directory for the session (absolute path)."""
|
|
23
|
+
|
|
24
|
+
title: str | None
|
|
25
|
+
"""Human-readable title for the session."""
|
|
26
|
+
|
|
27
|
+
updated_at: str | None
|
|
28
|
+
"""ISO 8601 timestamp of last activity."""
|
agentpool/sessions/session.py
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import asyncio
|
|
6
5
|
from typing import TYPE_CHECKING, Any, Self
|
|
7
6
|
|
|
8
7
|
from agentpool.log import get_logger
|
|
@@ -57,16 +56,9 @@ class ClientSession:
|
|
|
57
56
|
self._manager = manager
|
|
58
57
|
self._agent: Agent[Any, Any] | None = None
|
|
59
58
|
self._closed = False
|
|
60
|
-
self._title_generation_triggered = False
|
|
61
|
-
self._title_task: asyncio.Task[None] | None = None
|
|
62
59
|
# Session owns conversation history - agent is stateless
|
|
63
60
|
self._history = MessageHistory()
|
|
64
|
-
|
|
65
|
-
logger.debug(
|
|
66
|
-
"Created client session",
|
|
67
|
-
session_id=data.session_id,
|
|
68
|
-
agent=data.agent_name,
|
|
69
|
-
)
|
|
61
|
+
logger.debug("Created client session", session_id=data.session_id, agent=data.agent_name)
|
|
70
62
|
|
|
71
63
|
@property
|
|
72
64
|
def session_id(self) -> str:
|
|
@@ -90,16 +82,16 @@ class ClientSession:
|
|
|
90
82
|
self._agent = self._pool.get_agent(self._data.agent_name)
|
|
91
83
|
return self._agent
|
|
92
84
|
|
|
93
|
-
@property
|
|
94
|
-
def agent_name(self) -> str:
|
|
95
|
-
"""Get current agent name."""
|
|
96
|
-
return self._data.agent_name
|
|
97
|
-
|
|
98
85
|
@property
|
|
99
86
|
def conversation_id(self) -> str:
|
|
100
87
|
"""Get conversation ID for message storage."""
|
|
101
88
|
return self._data.conversation_id
|
|
102
89
|
|
|
90
|
+
@property
|
|
91
|
+
def title(self) -> str | None:
|
|
92
|
+
"""Get conversation title (delegated to agent)."""
|
|
93
|
+
return self._agent.conversation_title if self._agent is not None else None
|
|
94
|
+
|
|
103
95
|
@property
|
|
104
96
|
def history(self) -> MessageHistory:
|
|
105
97
|
"""Get the session's conversation history."""
|
|
@@ -130,6 +122,9 @@ class ClientSession:
|
|
|
130
122
|
the agent stateless from the session's perspective. Messages
|
|
131
123
|
are automatically added to the session's history.
|
|
132
124
|
|
|
125
|
+
Title generation is handled automatically by the agent's log_conversation
|
|
126
|
+
call when the conversation is first created.
|
|
127
|
+
|
|
133
128
|
Args:
|
|
134
129
|
prompt: User prompt to send to the agent
|
|
135
130
|
**kwargs: Additional arguments passed to agent.run()
|
|
@@ -137,42 +132,13 @@ class ClientSession:
|
|
|
137
132
|
Returns:
|
|
138
133
|
The agent's response message
|
|
139
134
|
"""
|
|
140
|
-
|
|
135
|
+
return await self.agent.run(
|
|
141
136
|
prompt,
|
|
142
137
|
message_history=self._history,
|
|
143
138
|
conversation_id=self.conversation_id,
|
|
144
139
|
**kwargs,
|
|
145
140
|
)
|
|
146
141
|
|
|
147
|
-
# Trigger title generation after first exchange (fire-and-forget)
|
|
148
|
-
# Note: Message logging is handled by the agent itself via MessageNode.log_message
|
|
149
|
-
if not self._title_generation_triggered and self._pool.storage:
|
|
150
|
-
self._title_generation_triggered = True
|
|
151
|
-
self._title_task = asyncio.create_task(self._generate_title())
|
|
152
|
-
|
|
153
|
-
return result
|
|
154
|
-
|
|
155
|
-
async def _generate_title(self) -> None:
|
|
156
|
-
"""Generate conversation title in the background."""
|
|
157
|
-
if not self._pool.storage:
|
|
158
|
-
return
|
|
159
|
-
try:
|
|
160
|
-
messages = self._history.get_history()
|
|
161
|
-
if messages:
|
|
162
|
-
title = await self._pool.storage.generate_conversation_title(
|
|
163
|
-
self.conversation_id,
|
|
164
|
-
messages,
|
|
165
|
-
)
|
|
166
|
-
# Also update SessionData so title is available when listing sessions
|
|
167
|
-
if title and self._manager:
|
|
168
|
-
self._data = self._data.with_title(title)
|
|
169
|
-
await self._manager.save(self._data)
|
|
170
|
-
except Exception:
|
|
171
|
-
logger.exception(
|
|
172
|
-
"Failed to generate conversation title",
|
|
173
|
-
conversation_id=self.conversation_id,
|
|
174
|
-
)
|
|
175
|
-
|
|
176
142
|
async def switch_agent(self, agent_name: str) -> None:
|
|
177
143
|
"""Switch to a different agent.
|
|
178
144
|
|
|
@@ -190,16 +156,10 @@ class ClientSession:
|
|
|
190
156
|
|
|
191
157
|
self._agent = self._pool.get_agent(agent_name)
|
|
192
158
|
self._data = self._data.with_agent(agent_name)
|
|
193
|
-
|
|
194
159
|
# Persist the change
|
|
195
160
|
if self._manager:
|
|
196
161
|
await self._manager.save(self._data)
|
|
197
|
-
|
|
198
|
-
logger.info(
|
|
199
|
-
"Switched agent",
|
|
200
|
-
session_id=self.session_id,
|
|
201
|
-
agent=agent_name,
|
|
202
|
-
)
|
|
162
|
+
logger.info("Switched agent", session_id=self.session_id, agent=agent_name)
|
|
203
163
|
|
|
204
164
|
async def touch(self) -> None:
|
|
205
165
|
"""Update last_active timestamp and persist."""
|
|
@@ -230,10 +190,6 @@ class ClientSession:
|
|
|
230
190
|
"""
|
|
231
191
|
self._data = self._data.with_metadata(**kwargs)
|
|
232
192
|
|
|
233
|
-
def clear_history(self) -> None:
|
|
234
|
-
"""Clear the session's conversation history."""
|
|
235
|
-
self._history.clear()
|
|
236
|
-
|
|
237
193
|
def get_history_messages(self) -> list[ChatMessage[Any]]:
|
|
238
194
|
"""Get all messages in the session's history."""
|
|
239
195
|
return self._history.get_history()
|