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.
Files changed (311) hide show
  1. acp/__init__.py +13 -4
  2. acp/acp_requests.py +20 -77
  3. acp/agent/connection.py +8 -0
  4. acp/agent/implementations/debug_server/debug_server.py +6 -2
  5. acp/agent/protocol.py +6 -0
  6. acp/bridge/README.md +15 -2
  7. acp/bridge/__init__.py +3 -2
  8. acp/bridge/__main__.py +60 -19
  9. acp/bridge/ws_server.py +173 -0
  10. acp/bridge/ws_server_cli.py +89 -0
  11. acp/client/connection.py +38 -29
  12. acp/client/implementations/default_client.py +3 -2
  13. acp/client/implementations/headless_client.py +2 -2
  14. acp/connection.py +2 -2
  15. acp/notifications.py +20 -50
  16. acp/schema/__init__.py +2 -0
  17. acp/schema/agent_responses.py +21 -0
  18. acp/schema/client_requests.py +3 -3
  19. acp/schema/session_state.py +63 -29
  20. acp/stdio.py +39 -9
  21. acp/task/supervisor.py +2 -2
  22. acp/transports.py +362 -2
  23. acp/utils.py +17 -4
  24. agentpool/__init__.py +6 -1
  25. agentpool/agents/__init__.py +2 -0
  26. agentpool/agents/acp_agent/acp_agent.py +407 -277
  27. agentpool/agents/acp_agent/acp_converters.py +196 -38
  28. agentpool/agents/acp_agent/client_handler.py +191 -26
  29. agentpool/agents/acp_agent/session_state.py +17 -6
  30. agentpool/agents/agent.py +607 -572
  31. agentpool/agents/agui_agent/__init__.py +0 -2
  32. agentpool/agents/agui_agent/agui_agent.py +176 -110
  33. agentpool/agents/agui_agent/agui_converters.py +0 -131
  34. agentpool/agents/agui_agent/helpers.py +3 -4
  35. agentpool/agents/base_agent.py +632 -17
  36. agentpool/agents/claude_code_agent/FORKING.md +191 -0
  37. agentpool/agents/claude_code_agent/__init__.py +13 -1
  38. agentpool/agents/claude_code_agent/claude_code_agent.py +1058 -291
  39. agentpool/agents/claude_code_agent/converters.py +74 -143
  40. agentpool/agents/claude_code_agent/history.py +474 -0
  41. agentpool/agents/claude_code_agent/models.py +77 -0
  42. agentpool/agents/claude_code_agent/static_info.py +100 -0
  43. agentpool/agents/claude_code_agent/usage.py +242 -0
  44. agentpool/agents/context.py +40 -0
  45. agentpool/agents/events/__init__.py +24 -0
  46. agentpool/agents/events/builtin_handlers.py +67 -1
  47. agentpool/agents/events/event_emitter.py +32 -2
  48. agentpool/agents/events/events.py +104 -3
  49. agentpool/agents/events/infer_info.py +145 -0
  50. agentpool/agents/events/processors.py +254 -0
  51. agentpool/agents/interactions.py +41 -6
  52. agentpool/agents/modes.py +67 -0
  53. agentpool/agents/slashed_agent.py +5 -4
  54. agentpool/agents/tool_call_accumulator.py +213 -0
  55. agentpool/agents/tool_wrapping.py +18 -6
  56. agentpool/common_types.py +56 -21
  57. agentpool/config_resources/__init__.py +38 -1
  58. agentpool/config_resources/acp_assistant.yml +2 -2
  59. agentpool/config_resources/agents.yml +3 -0
  60. agentpool/config_resources/agents_template.yml +1 -0
  61. agentpool/config_resources/claude_code_agent.yml +10 -6
  62. agentpool/config_resources/external_acp_agents.yml +2 -1
  63. agentpool/delegation/base_team.py +4 -30
  64. agentpool/delegation/pool.py +136 -289
  65. agentpool/delegation/team.py +58 -57
  66. agentpool/delegation/teamrun.py +51 -55
  67. agentpool/diagnostics/__init__.py +53 -0
  68. agentpool/diagnostics/lsp_manager.py +1593 -0
  69. agentpool/diagnostics/lsp_proxy.py +41 -0
  70. agentpool/diagnostics/lsp_proxy_script.py +229 -0
  71. agentpool/diagnostics/models.py +398 -0
  72. agentpool/functional/run.py +10 -4
  73. agentpool/mcp_server/__init__.py +0 -2
  74. agentpool/mcp_server/client.py +76 -32
  75. agentpool/mcp_server/conversions.py +54 -13
  76. agentpool/mcp_server/manager.py +34 -54
  77. agentpool/mcp_server/registries/official_registry_client.py +35 -1
  78. agentpool/mcp_server/tool_bridge.py +186 -139
  79. agentpool/messaging/__init__.py +0 -2
  80. agentpool/messaging/compaction.py +72 -197
  81. agentpool/messaging/connection_manager.py +11 -10
  82. agentpool/messaging/event_manager.py +5 -5
  83. agentpool/messaging/message_container.py +6 -30
  84. agentpool/messaging/message_history.py +99 -8
  85. agentpool/messaging/messagenode.py +52 -14
  86. agentpool/messaging/messages.py +54 -35
  87. agentpool/messaging/processing.py +12 -22
  88. agentpool/models/__init__.py +1 -1
  89. agentpool/models/acp_agents/base.py +6 -24
  90. agentpool/models/acp_agents/mcp_capable.py +126 -157
  91. agentpool/models/acp_agents/non_mcp.py +129 -95
  92. agentpool/models/agents.py +98 -76
  93. agentpool/models/agui_agents.py +1 -1
  94. agentpool/models/claude_code_agents.py +144 -19
  95. agentpool/models/file_parsing.py +0 -1
  96. agentpool/models/manifest.py +113 -50
  97. agentpool/prompts/conversion_manager.py +1 -1
  98. agentpool/prompts/prompts.py +5 -2
  99. agentpool/repomap.py +1 -1
  100. agentpool/resource_providers/__init__.py +11 -1
  101. agentpool/resource_providers/aggregating.py +56 -5
  102. agentpool/resource_providers/base.py +70 -4
  103. agentpool/resource_providers/codemode/code_executor.py +72 -5
  104. agentpool/resource_providers/codemode/helpers.py +2 -2
  105. agentpool/resource_providers/codemode/provider.py +64 -12
  106. agentpool/resource_providers/codemode/remote_mcp_execution.py +2 -2
  107. agentpool/resource_providers/codemode/remote_provider.py +9 -12
  108. agentpool/resource_providers/filtering.py +3 -1
  109. agentpool/resource_providers/mcp_provider.py +89 -12
  110. agentpool/resource_providers/plan_provider.py +228 -46
  111. agentpool/resource_providers/pool.py +7 -3
  112. agentpool/resource_providers/resource_info.py +111 -0
  113. agentpool/resource_providers/static.py +4 -2
  114. agentpool/sessions/__init__.py +4 -1
  115. agentpool/sessions/manager.py +33 -5
  116. agentpool/sessions/models.py +59 -6
  117. agentpool/sessions/protocol.py +28 -0
  118. agentpool/sessions/session.py +11 -55
  119. agentpool/skills/registry.py +13 -8
  120. agentpool/storage/manager.py +572 -49
  121. agentpool/talk/registry.py +4 -4
  122. agentpool/talk/talk.py +9 -10
  123. agentpool/testing.py +538 -20
  124. agentpool/tool_impls/__init__.py +6 -0
  125. agentpool/tool_impls/agent_cli/__init__.py +42 -0
  126. agentpool/tool_impls/agent_cli/tool.py +95 -0
  127. agentpool/tool_impls/bash/__init__.py +64 -0
  128. agentpool/tool_impls/bash/helpers.py +35 -0
  129. agentpool/tool_impls/bash/tool.py +171 -0
  130. agentpool/tool_impls/delete_path/__init__.py +70 -0
  131. agentpool/tool_impls/delete_path/tool.py +142 -0
  132. agentpool/tool_impls/download_file/__init__.py +80 -0
  133. agentpool/tool_impls/download_file/tool.py +183 -0
  134. agentpool/tool_impls/execute_code/__init__.py +55 -0
  135. agentpool/tool_impls/execute_code/tool.py +163 -0
  136. agentpool/tool_impls/grep/__init__.py +80 -0
  137. agentpool/tool_impls/grep/tool.py +200 -0
  138. agentpool/tool_impls/list_directory/__init__.py +73 -0
  139. agentpool/tool_impls/list_directory/tool.py +197 -0
  140. agentpool/tool_impls/question/__init__.py +42 -0
  141. agentpool/tool_impls/question/tool.py +127 -0
  142. agentpool/tool_impls/read/__init__.py +104 -0
  143. agentpool/tool_impls/read/tool.py +305 -0
  144. agentpool/tools/__init__.py +2 -1
  145. agentpool/tools/base.py +114 -34
  146. agentpool/tools/manager.py +57 -1
  147. agentpool/ui/base.py +2 -2
  148. agentpool/ui/mock_provider.py +2 -2
  149. agentpool/ui/stdlib_provider.py +2 -2
  150. agentpool/utils/file_watcher.py +269 -0
  151. agentpool/utils/identifiers.py +121 -0
  152. agentpool/utils/pydantic_ai_helpers.py +46 -0
  153. agentpool/utils/streams.py +616 -2
  154. agentpool/utils/subprocess_utils.py +155 -0
  155. agentpool/utils/token_breakdown.py +461 -0
  156. agentpool/vfs_registry.py +7 -2
  157. {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/METADATA +41 -27
  158. agentpool-2.5.0.dist-info/RECORD +579 -0
  159. {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/WHEEL +1 -1
  160. agentpool_cli/__main__.py +24 -0
  161. agentpool_cli/create.py +1 -1
  162. agentpool_cli/serve_acp.py +100 -21
  163. agentpool_cli/serve_agui.py +87 -0
  164. agentpool_cli/serve_opencode.py +119 -0
  165. agentpool_cli/ui.py +557 -0
  166. agentpool_commands/__init__.py +42 -5
  167. agentpool_commands/agents.py +75 -2
  168. agentpool_commands/history.py +62 -0
  169. agentpool_commands/mcp.py +176 -0
  170. agentpool_commands/models.py +56 -3
  171. agentpool_commands/pool.py +260 -0
  172. agentpool_commands/session.py +1 -1
  173. agentpool_commands/text_sharing/__init__.py +119 -0
  174. agentpool_commands/text_sharing/base.py +123 -0
  175. agentpool_commands/text_sharing/github_gist.py +80 -0
  176. agentpool_commands/text_sharing/opencode.py +462 -0
  177. agentpool_commands/text_sharing/paste_rs.py +59 -0
  178. agentpool_commands/text_sharing/pastebin.py +116 -0
  179. agentpool_commands/text_sharing/shittycodingagent.py +112 -0
  180. agentpool_commands/tools.py +57 -0
  181. agentpool_commands/utils.py +80 -30
  182. agentpool_config/__init__.py +30 -2
  183. agentpool_config/agentpool_tools.py +498 -0
  184. agentpool_config/builtin_tools.py +77 -22
  185. agentpool_config/commands.py +24 -1
  186. agentpool_config/compaction.py +258 -0
  187. agentpool_config/converters.py +1 -1
  188. agentpool_config/event_handlers.py +42 -0
  189. agentpool_config/events.py +1 -1
  190. agentpool_config/forward_targets.py +1 -4
  191. agentpool_config/jinja.py +3 -3
  192. agentpool_config/mcp_server.py +132 -6
  193. agentpool_config/nodes.py +1 -1
  194. agentpool_config/observability.py +44 -0
  195. agentpool_config/session.py +0 -3
  196. agentpool_config/storage.py +82 -38
  197. agentpool_config/task.py +3 -3
  198. agentpool_config/tools.py +11 -22
  199. agentpool_config/toolsets.py +109 -233
  200. agentpool_server/a2a_server/agent_worker.py +307 -0
  201. agentpool_server/a2a_server/server.py +23 -18
  202. agentpool_server/acp_server/acp_agent.py +234 -181
  203. agentpool_server/acp_server/commands/acp_commands.py +151 -156
  204. agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +18 -17
  205. agentpool_server/acp_server/event_converter.py +651 -0
  206. agentpool_server/acp_server/input_provider.py +53 -10
  207. agentpool_server/acp_server/server.py +24 -90
  208. agentpool_server/acp_server/session.py +173 -331
  209. agentpool_server/acp_server/session_manager.py +8 -34
  210. agentpool_server/agui_server/server.py +3 -1
  211. agentpool_server/mcp_server/server.py +5 -2
  212. agentpool_server/opencode_server/.rules +95 -0
  213. agentpool_server/opencode_server/ENDPOINTS.md +401 -0
  214. agentpool_server/opencode_server/OPENCODE_UI_TOOLS_COMPLETE.md +202 -0
  215. agentpool_server/opencode_server/__init__.py +19 -0
  216. agentpool_server/opencode_server/command_validation.py +172 -0
  217. agentpool_server/opencode_server/converters.py +975 -0
  218. agentpool_server/opencode_server/dependencies.py +24 -0
  219. agentpool_server/opencode_server/input_provider.py +421 -0
  220. agentpool_server/opencode_server/models/__init__.py +250 -0
  221. agentpool_server/opencode_server/models/agent.py +53 -0
  222. agentpool_server/opencode_server/models/app.py +72 -0
  223. agentpool_server/opencode_server/models/base.py +26 -0
  224. agentpool_server/opencode_server/models/common.py +23 -0
  225. agentpool_server/opencode_server/models/config.py +37 -0
  226. agentpool_server/opencode_server/models/events.py +821 -0
  227. agentpool_server/opencode_server/models/file.py +88 -0
  228. agentpool_server/opencode_server/models/mcp.py +44 -0
  229. agentpool_server/opencode_server/models/message.py +179 -0
  230. agentpool_server/opencode_server/models/parts.py +323 -0
  231. agentpool_server/opencode_server/models/provider.py +81 -0
  232. agentpool_server/opencode_server/models/pty.py +43 -0
  233. agentpool_server/opencode_server/models/question.py +56 -0
  234. agentpool_server/opencode_server/models/session.py +111 -0
  235. agentpool_server/opencode_server/routes/__init__.py +29 -0
  236. agentpool_server/opencode_server/routes/agent_routes.py +473 -0
  237. agentpool_server/opencode_server/routes/app_routes.py +202 -0
  238. agentpool_server/opencode_server/routes/config_routes.py +302 -0
  239. agentpool_server/opencode_server/routes/file_routes.py +571 -0
  240. agentpool_server/opencode_server/routes/global_routes.py +94 -0
  241. agentpool_server/opencode_server/routes/lsp_routes.py +319 -0
  242. agentpool_server/opencode_server/routes/message_routes.py +761 -0
  243. agentpool_server/opencode_server/routes/permission_routes.py +63 -0
  244. agentpool_server/opencode_server/routes/pty_routes.py +300 -0
  245. agentpool_server/opencode_server/routes/question_routes.py +128 -0
  246. agentpool_server/opencode_server/routes/session_routes.py +1276 -0
  247. agentpool_server/opencode_server/routes/tui_routes.py +139 -0
  248. agentpool_server/opencode_server/server.py +475 -0
  249. agentpool_server/opencode_server/state.py +151 -0
  250. agentpool_server/opencode_server/time_utils.py +8 -0
  251. agentpool_storage/__init__.py +12 -0
  252. agentpool_storage/base.py +184 -2
  253. agentpool_storage/claude_provider/ARCHITECTURE.md +433 -0
  254. agentpool_storage/claude_provider/__init__.py +42 -0
  255. agentpool_storage/claude_provider/provider.py +1089 -0
  256. agentpool_storage/file_provider.py +278 -15
  257. agentpool_storage/memory_provider.py +193 -12
  258. agentpool_storage/models.py +3 -0
  259. agentpool_storage/opencode_provider/ARCHITECTURE.md +386 -0
  260. agentpool_storage/opencode_provider/__init__.py +16 -0
  261. agentpool_storage/opencode_provider/helpers.py +414 -0
  262. agentpool_storage/opencode_provider/provider.py +895 -0
  263. agentpool_storage/project_store.py +325 -0
  264. agentpool_storage/session_store.py +26 -6
  265. agentpool_storage/sql_provider/__init__.py +4 -2
  266. agentpool_storage/sql_provider/models.py +48 -0
  267. agentpool_storage/sql_provider/sql_provider.py +269 -3
  268. agentpool_storage/sql_provider/utils.py +12 -13
  269. agentpool_storage/zed_provider/__init__.py +16 -0
  270. agentpool_storage/zed_provider/helpers.py +281 -0
  271. agentpool_storage/zed_provider/models.py +130 -0
  272. agentpool_storage/zed_provider/provider.py +442 -0
  273. agentpool_storage/zed_provider.py +803 -0
  274. agentpool_toolsets/__init__.py +0 -2
  275. agentpool_toolsets/builtin/__init__.py +2 -12
  276. agentpool_toolsets/builtin/code.py +96 -57
  277. agentpool_toolsets/builtin/debug.py +118 -48
  278. agentpool_toolsets/builtin/execution_environment.py +115 -230
  279. agentpool_toolsets/builtin/file_edit/file_edit.py +115 -7
  280. agentpool_toolsets/builtin/skills.py +9 -4
  281. agentpool_toolsets/builtin/subagent_tools.py +64 -51
  282. agentpool_toolsets/builtin/workers.py +4 -2
  283. agentpool_toolsets/composio_toolset.py +2 -2
  284. agentpool_toolsets/entry_points.py +3 -1
  285. agentpool_toolsets/fsspec_toolset/__init__.py +13 -1
  286. agentpool_toolsets/fsspec_toolset/diagnostics.py +860 -73
  287. agentpool_toolsets/fsspec_toolset/grep.py +99 -7
  288. agentpool_toolsets/fsspec_toolset/helpers.py +3 -2
  289. agentpool_toolsets/fsspec_toolset/image_utils.py +161 -0
  290. agentpool_toolsets/fsspec_toolset/toolset.py +500 -95
  291. agentpool_toolsets/mcp_discovery/__init__.py +5 -0
  292. agentpool_toolsets/mcp_discovery/data/mcp_servers.parquet +0 -0
  293. agentpool_toolsets/mcp_discovery/toolset.py +511 -0
  294. agentpool_toolsets/mcp_run_toolset.py +87 -12
  295. agentpool_toolsets/notifications.py +33 -33
  296. agentpool_toolsets/openapi.py +3 -1
  297. agentpool_toolsets/search_toolset.py +3 -1
  298. agentpool-2.1.9.dist-info/RECORD +0 -474
  299. agentpool_config/resources.py +0 -33
  300. agentpool_server/acp_server/acp_tools.py +0 -43
  301. agentpool_server/acp_server/commands/spawn.py +0 -210
  302. agentpool_storage/text_log_provider.py +0 -275
  303. agentpool_toolsets/builtin/agent_management.py +0 -239
  304. agentpool_toolsets/builtin/chain.py +0 -288
  305. agentpool_toolsets/builtin/history.py +0 -36
  306. agentpool_toolsets/builtin/integration.py +0 -85
  307. agentpool_toolsets/builtin/tool_management.py +0 -90
  308. agentpool_toolsets/builtin/user_interaction.py +0 -52
  309. agentpool_toolsets/semantic_memory_toolset.py +0 -536
  310. {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/entry_points.txt +0 -0
  311. {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -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):
@@ -26,6 +26,8 @@ class StaticResourceProvider(ResourceProvider):
26
26
  to the common ResourceProvider interface.
27
27
  """
28
28
 
29
+ kind = "tools"
30
+
29
31
  def __init__(
30
32
  self,
31
33
  name: str = "static",
@@ -46,7 +48,7 @@ class StaticResourceProvider(ResourceProvider):
46
48
  self._prompts = list(prompts) if prompts else []
47
49
  self._resources = list(resources) if resources else []
48
50
 
49
- async def get_tools(self) -> list[Tool]:
51
+ async def get_tools(self) -> Sequence[Tool]:
50
52
  """Get pre-configured tools."""
51
53
  return self._tools
52
54
 
@@ -1,13 +1,16 @@
1
1
  """Session management package."""
2
2
 
3
- from agentpool.sessions.models import SessionData
3
+ 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",
11
+ "ProjectData",
10
12
  "SessionData",
13
+ "SessionInfo",
11
14
  "SessionManager",
12
15
  "SessionStore",
13
16
  ]
@@ -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
@@ -17,6 +16,7 @@ if TYPE_CHECKING:
17
16
 
18
17
  from agentpool.delegation.pool import AgentPool
19
18
  from agentpool.sessions.store import SessionStore
19
+ from agentpool.storage.manager import StorageManager
20
20
 
21
21
  logger = get_logger(__name__)
22
22
 
@@ -37,6 +37,7 @@ class SessionManager:
37
37
  pool: AgentPool[Any],
38
38
  store: SessionStore | None = None,
39
39
  pool_id: str | None = None,
40
+ storage: StorageManager | None = None,
40
41
  ) -> None:
41
42
  """Initialize session manager.
42
43
 
@@ -44,10 +45,12 @@ class SessionManager:
44
45
  pool: Agent pool for agent access
45
46
  store: Session persistence backend (defaults to MemorySessionStore)
46
47
  pool_id: Optional identifier for this pool (for multi-pool setups)
48
+ storage: Optional storage manager for project tracking
47
49
  """
48
50
  self._pool = pool
49
51
  self._store = store or MemorySessionStore()
50
52
  self._pool_id = pool_id
53
+ self._storage = storage
51
54
  self._active: dict[str, ClientSession] = {}
52
55
  self._lock = asyncio.Lock()
53
56
  logger.debug("Initialized session manager", pool_id=pool_id)
@@ -94,8 +97,14 @@ class SessionManager:
94
97
  logger.debug("Session manager closed", session_count=len(sessions))
95
98
 
96
99
  def generate_session_id(self) -> str:
97
- """Generate a unique session ID."""
98
- return f"sess_{uuid4().hex[:12]}"
100
+ """Generate a unique, chronologically sortable session ID.
101
+
102
+ Uses OpenCode-compatible format: ses_{hex_timestamp}{random_base62}
103
+ IDs are lexicographically sortable by creation time.
104
+ """
105
+ from agentpool.utils.identifiers import generate_session_id
106
+
107
+ return generate_session_id()
99
108
 
100
109
  async def create(
101
110
  self,
@@ -139,12 +148,31 @@ class SessionManager:
139
148
  msg = f"Session '{session_id}' already exists"
140
149
  raise ValueError(msg)
141
150
 
151
+ # Get or create project if cwd provided and storage available
152
+ project_id: str | None = None
153
+ if cwd and self._storage:
154
+ try:
155
+ from agentpool_storage.project_store import ProjectStore
156
+
157
+ project_store = ProjectStore(self._storage)
158
+ project = await project_store.get_or_create(cwd)
159
+ project_id = project.project_id
160
+ logger.debug(
161
+ "Associated session with project",
162
+ session_id=session_id,
163
+ project_id=project_id,
164
+ worktree=project.worktree,
165
+ )
166
+ except Exception:
167
+ logger.exception("Failed to create/get project for session")
168
+
142
169
  # Create session data
143
170
  data = SessionData(
144
171
  session_id=session_id,
145
172
  agent_name=agent_name,
146
- conversation_id=conversation_id or f"conv_{uuid4().hex[:12]}",
173
+ conversation_id=conversation_id or session_id,
147
174
  pool_id=self._pool_id,
175
+ project_id=project_id,
148
176
  cwd=cwd,
149
177
  metadata=metadata or {},
150
178
  )
@@ -280,7 +308,7 @@ class SessionManager:
280
308
  if active_only:
281
309
  sessions = list(self._active.keys())
282
310
  if agent_name:
283
- sessions = [sid for sid, s in self._active.items() if s.agent_name == agent_name]
311
+ sessions = [sid for sid, s in self._active.items() if s.agent.name == agent_name]
284
312
  return sessions
285
313
 
286
314
  return await self._store.list_sessions(
@@ -11,6 +11,47 @@ from schemez import Schema
11
11
  from agentpool.utils.now import get_now
12
12
 
13
13
 
14
+ class ProjectData(Schema):
15
+ """Persistable project/worktree state.
16
+
17
+ Represents a codebase/worktree that agentpool operates on.
18
+ Sessions are associated with projects.
19
+ """
20
+
21
+ project_id: str
22
+ """Unique identifier (hash of canonical worktree path)."""
23
+
24
+ worktree: str
25
+ """Absolute path to the project root/worktree."""
26
+
27
+ name: str | None = None
28
+ """Optional friendly name for the project."""
29
+
30
+ vcs: str | None = None
31
+ """Version control system type ('git', 'hg', or None)."""
32
+
33
+ config_path: str | None = None
34
+ """Path to the project's config file, or None for auto-discovery."""
35
+
36
+ created_at: datetime = Field(default_factory=get_now)
37
+ """When the project was first registered."""
38
+
39
+ last_active: datetime = Field(default_factory=get_now)
40
+ """Last activity timestamp."""
41
+
42
+ settings: dict[str, Any] = Field(default_factory=dict)
43
+ """Project-specific settings overrides."""
44
+
45
+ def touch(self) -> ProjectData:
46
+ """Return copy with updated last_active timestamp."""
47
+ return self.model_copy(update={"last_active": get_now()})
48
+
49
+ def with_settings(self, **kwargs: Any) -> ProjectData:
50
+ """Return copy with updated settings."""
51
+ new_settings = {**self.settings, **kwargs}
52
+ return self.model_copy(update={"settings": new_settings, "last_active": get_now()})
53
+
54
+
14
55
  class SessionData(Schema):
15
56
  """Persistable session state.
16
57
 
@@ -27,12 +68,18 @@ class SessionData(Schema):
27
68
  conversation_id: str
28
69
  """Links to conversation in StorageManager."""
29
70
 
30
- title: str | None = None
31
- """AI-generated or user-provided title for the conversation."""
32
-
33
71
  pool_id: str | None = None
34
72
  """Optional pool/manifest identifier for multi-pool setups."""
35
73
 
74
+ project_id: str | None = None
75
+ """Project identifier (e.g., for OpenCode compatibility)."""
76
+
77
+ parent_id: str | None = None
78
+ """Parent session ID for forked sessions."""
79
+
80
+ version: str = "1"
81
+ """Session version string."""
82
+
36
83
  cwd: str | None = None
37
84
  """Working directory for the session."""
38
85
 
@@ -66,6 +113,12 @@ class SessionData(Schema):
66
113
  new_metadata = {**self.metadata, **kwargs}
67
114
  return self.model_copy(update={"metadata": new_metadata, "last_active": get_now()})
68
115
 
69
- def with_title(self, title: str) -> SessionData:
70
- """Return copy with updated title."""
71
- return self.model_copy(update={"title": title, "last_active": get_now()})
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."""
@@ -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
- result = await self.agent.run(
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()
@@ -72,7 +72,9 @@ class SkillsRegistry(BaseRegistry[str, Skill]):
72
72
  if not isinstance(fs, AsyncFileSystem):
73
73
  fs = AsyncFileSystemWrapper(fs)
74
74
  search_path = base_path if base_path is not None else fs.root_marker
75
+ original_skills_dir: UPath | None = None
75
76
  else:
77
+ original_skills_dir = to_upath(skills_dir)
76
78
  fs = upath_to_fs(skills_dir, **storage_options)
77
79
  search_path = fs.root_marker
78
80
 
@@ -93,16 +95,19 @@ class SkillsRegistry(BaseRegistry[str, Skill]):
93
95
  return
94
96
  logger.info("Found skills", skills=skill_dirs, skills_dir=search_path)
95
97
  for skill_entry in skill_dirs:
96
- skill_name = skill_entry["name"].lstrip("./")
97
- # Construct full path for skill directory
98
- if search_path and search_path != fs.root_marker:
99
- skill_dir_path = to_upath(f"{search_path}/{skill_name}")
100
- skill_md_path = f"{search_path}/{skill_name}/SKILL.md"
98
+ # entry["name"] is relative to the filesystem root
99
+ # We need to construct the full path for _parse_skill
100
+ entry_name = skill_entry["name"]
101
+ if original_skills_dir is not None:
102
+ # When we created fs from a path, entry names are relative to that path
103
+ skill_dir_path = original_skills_dir / entry_name
101
104
  else:
102
- skill_dir_path = to_upath(skill_name)
103
- skill_md_path = f"{skill_name}/SKILL.md"
105
+ # When fs was provided directly, entry names should be usable as-is
106
+ skill_dir_path = to_upath(entry_name)
107
+ # For fs._cat_file, use the path relative to the filesystem
108
+ fs_skill_md_path = f"{entry_name}/SKILL.md"
104
109
  try:
105
- await fs._cat_file(skill_md_path)
110
+ await fs._cat_file(fs_skill_md_path)
106
111
  except FileNotFoundError:
107
112
  continue
108
113