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.
Files changed (250) hide show
  1. acp/__init__.py +0 -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/client/connection.py +38 -29
  7. acp/client/implementations/default_client.py +3 -2
  8. acp/client/implementations/headless_client.py +2 -2
  9. acp/connection.py +2 -2
  10. acp/notifications.py +18 -49
  11. acp/schema/__init__.py +2 -0
  12. acp/schema/agent_responses.py +21 -0
  13. acp/schema/client_requests.py +3 -3
  14. acp/schema/session_state.py +63 -29
  15. acp/task/supervisor.py +2 -2
  16. acp/utils.py +2 -2
  17. agentpool/__init__.py +2 -0
  18. agentpool/agents/acp_agent/acp_agent.py +278 -263
  19. agentpool/agents/acp_agent/acp_converters.py +150 -17
  20. agentpool/agents/acp_agent/client_handler.py +35 -24
  21. agentpool/agents/acp_agent/session_state.py +14 -6
  22. agentpool/agents/agent.py +471 -643
  23. agentpool/agents/agui_agent/agui_agent.py +104 -107
  24. agentpool/agents/agui_agent/helpers.py +3 -4
  25. agentpool/agents/base_agent.py +485 -32
  26. agentpool/agents/claude_code_agent/FORKING.md +191 -0
  27. agentpool/agents/claude_code_agent/__init__.py +13 -1
  28. agentpool/agents/claude_code_agent/claude_code_agent.py +654 -334
  29. agentpool/agents/claude_code_agent/converters.py +4 -141
  30. agentpool/agents/claude_code_agent/models.py +77 -0
  31. agentpool/agents/claude_code_agent/static_info.py +100 -0
  32. agentpool/agents/claude_code_agent/usage.py +242 -0
  33. agentpool/agents/events/__init__.py +22 -0
  34. agentpool/agents/events/builtin_handlers.py +65 -0
  35. agentpool/agents/events/event_emitter.py +3 -0
  36. agentpool/agents/events/events.py +84 -3
  37. agentpool/agents/events/infer_info.py +145 -0
  38. agentpool/agents/events/processors.py +254 -0
  39. agentpool/agents/interactions.py +41 -6
  40. agentpool/agents/modes.py +13 -0
  41. agentpool/agents/slashed_agent.py +5 -4
  42. agentpool/agents/tool_wrapping.py +18 -6
  43. agentpool/common_types.py +35 -21
  44. agentpool/config_resources/acp_assistant.yml +2 -2
  45. agentpool/config_resources/agents.yml +3 -0
  46. agentpool/config_resources/agents_template.yml +1 -0
  47. agentpool/config_resources/claude_code_agent.yml +9 -8
  48. agentpool/config_resources/external_acp_agents.yml +2 -1
  49. agentpool/delegation/base_team.py +4 -30
  50. agentpool/delegation/pool.py +104 -265
  51. agentpool/delegation/team.py +57 -57
  52. agentpool/delegation/teamrun.py +50 -55
  53. agentpool/functional/run.py +10 -4
  54. agentpool/mcp_server/client.py +73 -38
  55. agentpool/mcp_server/conversions.py +54 -13
  56. agentpool/mcp_server/manager.py +9 -23
  57. agentpool/mcp_server/registries/official_registry_client.py +10 -1
  58. agentpool/mcp_server/tool_bridge.py +114 -79
  59. agentpool/messaging/connection_manager.py +11 -10
  60. agentpool/messaging/event_manager.py +5 -5
  61. agentpool/messaging/message_container.py +6 -30
  62. agentpool/messaging/message_history.py +87 -8
  63. agentpool/messaging/messagenode.py +52 -14
  64. agentpool/messaging/messages.py +2 -26
  65. agentpool/messaging/processing.py +10 -22
  66. agentpool/models/__init__.py +1 -1
  67. agentpool/models/acp_agents/base.py +6 -2
  68. agentpool/models/acp_agents/mcp_capable.py +124 -15
  69. agentpool/models/acp_agents/non_mcp.py +0 -23
  70. agentpool/models/agents.py +66 -66
  71. agentpool/models/agui_agents.py +1 -1
  72. agentpool/models/claude_code_agents.py +111 -17
  73. agentpool/models/file_parsing.py +0 -1
  74. agentpool/models/manifest.py +70 -50
  75. agentpool/prompts/conversion_manager.py +1 -1
  76. agentpool/prompts/prompts.py +5 -2
  77. agentpool/resource_providers/__init__.py +2 -0
  78. agentpool/resource_providers/aggregating.py +4 -2
  79. agentpool/resource_providers/base.py +13 -3
  80. agentpool/resource_providers/codemode/code_executor.py +72 -5
  81. agentpool/resource_providers/codemode/helpers.py +2 -2
  82. agentpool/resource_providers/codemode/provider.py +64 -12
  83. agentpool/resource_providers/codemode/remote_mcp_execution.py +2 -2
  84. agentpool/resource_providers/codemode/remote_provider.py +9 -12
  85. agentpool/resource_providers/filtering.py +3 -1
  86. agentpool/resource_providers/mcp_provider.py +66 -12
  87. agentpool/resource_providers/plan_provider.py +111 -18
  88. agentpool/resource_providers/pool.py +5 -3
  89. agentpool/resource_providers/resource_info.py +111 -0
  90. agentpool/resource_providers/static.py +2 -2
  91. agentpool/sessions/__init__.py +2 -0
  92. agentpool/sessions/manager.py +2 -3
  93. agentpool/sessions/models.py +9 -6
  94. agentpool/sessions/protocol.py +28 -0
  95. agentpool/sessions/session.py +11 -55
  96. agentpool/storage/manager.py +361 -54
  97. agentpool/talk/registry.py +4 -4
  98. agentpool/talk/talk.py +9 -10
  99. agentpool/testing.py +1 -1
  100. agentpool/tool_impls/__init__.py +6 -0
  101. agentpool/tool_impls/agent_cli/__init__.py +42 -0
  102. agentpool/tool_impls/agent_cli/tool.py +95 -0
  103. agentpool/tool_impls/bash/__init__.py +64 -0
  104. agentpool/tool_impls/bash/helpers.py +35 -0
  105. agentpool/tool_impls/bash/tool.py +171 -0
  106. agentpool/tool_impls/delete_path/__init__.py +70 -0
  107. agentpool/tool_impls/delete_path/tool.py +142 -0
  108. agentpool/tool_impls/download_file/__init__.py +80 -0
  109. agentpool/tool_impls/download_file/tool.py +183 -0
  110. agentpool/tool_impls/execute_code/__init__.py +55 -0
  111. agentpool/tool_impls/execute_code/tool.py +163 -0
  112. agentpool/tool_impls/grep/__init__.py +80 -0
  113. agentpool/tool_impls/grep/tool.py +200 -0
  114. agentpool/tool_impls/list_directory/__init__.py +73 -0
  115. agentpool/tool_impls/list_directory/tool.py +197 -0
  116. agentpool/tool_impls/question/__init__.py +42 -0
  117. agentpool/tool_impls/question/tool.py +127 -0
  118. agentpool/tool_impls/read/__init__.py +104 -0
  119. agentpool/tool_impls/read/tool.py +305 -0
  120. agentpool/tools/__init__.py +2 -1
  121. agentpool/tools/base.py +114 -34
  122. agentpool/tools/manager.py +57 -1
  123. agentpool/ui/base.py +2 -2
  124. agentpool/ui/mock_provider.py +2 -2
  125. agentpool/ui/stdlib_provider.py +2 -2
  126. agentpool/utils/streams.py +21 -96
  127. agentpool/vfs_registry.py +7 -2
  128. {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/METADATA +16 -22
  129. {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/RECORD +242 -195
  130. {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/WHEEL +1 -1
  131. agentpool_cli/__main__.py +20 -0
  132. agentpool_cli/create.py +1 -1
  133. agentpool_cli/serve_acp.py +59 -1
  134. agentpool_cli/serve_opencode.py +1 -1
  135. agentpool_cli/ui.py +557 -0
  136. agentpool_commands/__init__.py +12 -5
  137. agentpool_commands/agents.py +1 -1
  138. agentpool_commands/pool.py +260 -0
  139. agentpool_commands/session.py +1 -1
  140. agentpool_commands/text_sharing/__init__.py +119 -0
  141. agentpool_commands/text_sharing/base.py +123 -0
  142. agentpool_commands/text_sharing/github_gist.py +80 -0
  143. agentpool_commands/text_sharing/opencode.py +462 -0
  144. agentpool_commands/text_sharing/paste_rs.py +59 -0
  145. agentpool_commands/text_sharing/pastebin.py +116 -0
  146. agentpool_commands/text_sharing/shittycodingagent.py +112 -0
  147. agentpool_commands/utils.py +31 -32
  148. agentpool_config/__init__.py +30 -2
  149. agentpool_config/agentpool_tools.py +498 -0
  150. agentpool_config/converters.py +1 -1
  151. agentpool_config/event_handlers.py +42 -0
  152. agentpool_config/events.py +1 -1
  153. agentpool_config/forward_targets.py +1 -4
  154. agentpool_config/jinja.py +3 -3
  155. agentpool_config/mcp_server.py +1 -5
  156. agentpool_config/nodes.py +1 -1
  157. agentpool_config/observability.py +44 -0
  158. agentpool_config/session.py +0 -3
  159. agentpool_config/storage.py +38 -39
  160. agentpool_config/task.py +3 -3
  161. agentpool_config/tools.py +11 -28
  162. agentpool_config/toolsets.py +22 -90
  163. agentpool_server/a2a_server/agent_worker.py +307 -0
  164. agentpool_server/a2a_server/server.py +23 -18
  165. agentpool_server/acp_server/acp_agent.py +125 -56
  166. agentpool_server/acp_server/commands/acp_commands.py +46 -216
  167. agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +8 -7
  168. agentpool_server/acp_server/event_converter.py +651 -0
  169. agentpool_server/acp_server/input_provider.py +53 -10
  170. agentpool_server/acp_server/server.py +1 -11
  171. agentpool_server/acp_server/session.py +90 -410
  172. agentpool_server/acp_server/session_manager.py +8 -34
  173. agentpool_server/agui_server/server.py +3 -1
  174. agentpool_server/mcp_server/server.py +5 -2
  175. agentpool_server/opencode_server/ENDPOINTS.md +53 -14
  176. agentpool_server/opencode_server/OPENCODE_UI_TOOLS_COMPLETE.md +202 -0
  177. agentpool_server/opencode_server/__init__.py +0 -8
  178. agentpool_server/opencode_server/converters.py +132 -26
  179. agentpool_server/opencode_server/input_provider.py +160 -8
  180. agentpool_server/opencode_server/models/__init__.py +42 -20
  181. agentpool_server/opencode_server/models/app.py +12 -0
  182. agentpool_server/opencode_server/models/events.py +203 -29
  183. agentpool_server/opencode_server/models/mcp.py +19 -0
  184. agentpool_server/opencode_server/models/message.py +18 -1
  185. agentpool_server/opencode_server/models/parts.py +134 -1
  186. agentpool_server/opencode_server/models/question.py +56 -0
  187. agentpool_server/opencode_server/models/session.py +13 -1
  188. agentpool_server/opencode_server/routes/__init__.py +4 -0
  189. agentpool_server/opencode_server/routes/agent_routes.py +33 -2
  190. agentpool_server/opencode_server/routes/app_routes.py +66 -3
  191. agentpool_server/opencode_server/routes/config_routes.py +66 -5
  192. agentpool_server/opencode_server/routes/file_routes.py +184 -5
  193. agentpool_server/opencode_server/routes/global_routes.py +1 -1
  194. agentpool_server/opencode_server/routes/lsp_routes.py +1 -1
  195. agentpool_server/opencode_server/routes/message_routes.py +122 -66
  196. agentpool_server/opencode_server/routes/permission_routes.py +63 -0
  197. agentpool_server/opencode_server/routes/pty_routes.py +23 -22
  198. agentpool_server/opencode_server/routes/question_routes.py +128 -0
  199. agentpool_server/opencode_server/routes/session_routes.py +139 -68
  200. agentpool_server/opencode_server/routes/tui_routes.py +1 -1
  201. agentpool_server/opencode_server/server.py +47 -2
  202. agentpool_server/opencode_server/state.py +30 -0
  203. agentpool_storage/__init__.py +0 -4
  204. agentpool_storage/base.py +81 -2
  205. agentpool_storage/claude_provider/ARCHITECTURE.md +433 -0
  206. agentpool_storage/claude_provider/__init__.py +42 -0
  207. agentpool_storage/{claude_provider.py → claude_provider/provider.py} +190 -8
  208. agentpool_storage/file_provider.py +149 -15
  209. agentpool_storage/memory_provider.py +132 -12
  210. agentpool_storage/opencode_provider/ARCHITECTURE.md +386 -0
  211. agentpool_storage/opencode_provider/__init__.py +16 -0
  212. agentpool_storage/opencode_provider/helpers.py +414 -0
  213. agentpool_storage/opencode_provider/provider.py +895 -0
  214. agentpool_storage/session_store.py +20 -6
  215. agentpool_storage/sql_provider/sql_provider.py +135 -2
  216. agentpool_storage/sql_provider/utils.py +2 -12
  217. agentpool_storage/zed_provider/__init__.py +16 -0
  218. agentpool_storage/zed_provider/helpers.py +281 -0
  219. agentpool_storage/zed_provider/models.py +130 -0
  220. agentpool_storage/zed_provider/provider.py +442 -0
  221. agentpool_storage/zed_provider.py +803 -0
  222. agentpool_toolsets/__init__.py +0 -2
  223. agentpool_toolsets/builtin/__init__.py +2 -4
  224. agentpool_toolsets/builtin/code.py +4 -4
  225. agentpool_toolsets/builtin/debug.py +115 -40
  226. agentpool_toolsets/builtin/execution_environment.py +54 -165
  227. agentpool_toolsets/builtin/skills.py +0 -77
  228. agentpool_toolsets/builtin/subagent_tools.py +64 -51
  229. agentpool_toolsets/builtin/workers.py +4 -2
  230. agentpool_toolsets/composio_toolset.py +2 -2
  231. agentpool_toolsets/entry_points.py +3 -1
  232. agentpool_toolsets/fsspec_toolset/grep.py +25 -5
  233. agentpool_toolsets/fsspec_toolset/helpers.py +3 -2
  234. agentpool_toolsets/fsspec_toolset/toolset.py +350 -66
  235. agentpool_toolsets/mcp_discovery/data/mcp_servers.parquet +0 -0
  236. agentpool_toolsets/mcp_discovery/toolset.py +74 -17
  237. agentpool_toolsets/mcp_run_toolset.py +8 -11
  238. agentpool_toolsets/notifications.py +33 -33
  239. agentpool_toolsets/openapi.py +3 -1
  240. agentpool_toolsets/search_toolset.py +3 -1
  241. agentpool_config/resources.py +0 -33
  242. agentpool_server/acp_server/acp_tools.py +0 -43
  243. agentpool_server/acp_server/commands/spawn.py +0 -210
  244. agentpool_storage/opencode_provider.py +0 -730
  245. agentpool_storage/text_log_provider.py +0 -276
  246. agentpool_toolsets/builtin/chain.py +0 -288
  247. agentpool_toolsets/builtin/user_interaction.py +0 -52
  248. agentpool_toolsets/semantic_memory_toolset.py +0 -536
  249. {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/entry_points.txt +0 -0
  250. {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -3,13 +3,11 @@
3
3
  from agentpool_toolsets.config_creation import ConfigCreationTools
4
4
  from agentpool_toolsets.fsspec_toolset import FSSpecTools
5
5
  from agentpool_toolsets.notifications import NotificationsTools
6
- from agentpool_toolsets.semantic_memory_toolset import SemanticMemoryTools
7
6
  from agentpool_toolsets.vfs_toolset import VFSTools
8
7
 
9
8
  __all__ = [
10
9
  "ConfigCreationTools",
11
10
  "FSSpecTools",
12
11
  "NotificationsTools",
13
- "SemanticMemoryTools",
14
12
  "VFSTools",
15
13
  ]
@@ -6,10 +6,9 @@ from __future__ import annotations
6
6
  # Import provider classes
7
7
  from agentpool_toolsets.builtin.code import CodeTools
8
8
  from agentpool_toolsets.builtin.debug import DebugTools
9
- from agentpool_toolsets.builtin.execution_environment import ExecutionEnvironmentTools
9
+ from agentpool_toolsets.builtin.execution_environment import ProcessManagementTools
10
10
  from agentpool_toolsets.builtin.skills import SkillsTools
11
11
  from agentpool_toolsets.builtin.subagent_tools import SubagentTools
12
- from agentpool_toolsets.builtin.user_interaction import UserInteractionTools
13
12
  from agentpool_toolsets.builtin.workers import WorkersTools
14
13
 
15
14
 
@@ -17,9 +16,8 @@ __all__ = [
17
16
  # Provider classes
18
17
  "CodeTools",
19
18
  "DebugTools",
20
- "ExecutionEnvironmentTools",
19
+ "ProcessManagementTools",
21
20
  "SkillsTools",
22
21
  "SubagentTools",
23
- "UserInteractionTools",
24
22
  "WorkersTools",
25
23
  ]
@@ -21,13 +21,13 @@ from agentpool_toolsets.fsspec_toolset.diagnostics import (
21
21
 
22
22
 
23
23
  if TYPE_CHECKING:
24
+ from collections.abc import Sequence
25
+
24
26
  from exxec.base import ExecutionEnvironment
25
27
  from fsspec.asyn import AsyncFileSystem
26
28
 
27
29
  from agentpool.tools.base import Tool
28
- from agentpool_toolsets.fsspec_toolset.diagnostics import (
29
- DiagnosticsConfig,
30
- )
30
+ from agentpool_toolsets.fsspec_toolset.diagnostics import DiagnosticsConfig
31
31
 
32
32
  logger = get_logger(__name__)
33
33
 
@@ -149,7 +149,7 @@ class CodeTools(ResourceProvider):
149
149
  return str(Path(cwd) / path)
150
150
  return path
151
151
 
152
- async def get_tools(self) -> list[Tool]:
152
+ async def get_tools(self) -> Sequence[Tool]:
153
153
  """Get code analysis tools."""
154
154
  if self._tools is not None:
155
155
  return self._tools
@@ -4,15 +4,23 @@ from __future__ import annotations
4
4
 
5
5
  from collections import deque
6
6
  from dataclasses import dataclass, field
7
+ from datetime import UTC, datetime
8
+ import json
7
9
  import logging
8
- from typing import Any, Literal
10
+ from typing import TYPE_CHECKING, Any, Literal
9
11
 
12
+ from fsspec.implementations.asyn_wrapper import AsyncFileSystemWrapper
13
+ from fsspec.implementations.memory import MemoryFileSystem
10
14
  from pydantic_ai import RunContext # noqa: TC002
11
15
 
12
16
  from agentpool.agents.context import AgentContext # noqa: TC001
13
17
  from agentpool.resource_providers import StaticResourceProvider
14
18
 
15
19
 
20
+ if TYPE_CHECKING:
21
+ from fsspec.asyn import AsyncFileSystem
22
+
23
+
16
24
  LogLevel = Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
17
25
 
18
26
 
@@ -131,6 +139,8 @@ Available in namespace:
131
139
  - ctx: AgentContext (ctx.agent, ctx.pool, ctx.config, ctx.definition, etc.)
132
140
  - run_ctx: pydantic-ai RunContext (current run state)
133
141
  - me: Shortcut for ctx.agent (your Agent instance)
142
+ - save(key, value): Save an object to persist between calls
143
+ - state: Dict of saved objects from previous calls
134
144
 
135
145
  You can inspect yourself, the pool, other agents, your tools, and more.
136
146
  Write an async main() function that returns the result.
@@ -159,43 +169,6 @@ async def main():
159
169
  """
160
170
 
161
171
 
162
- async def execute_introspection(ctx: AgentContext, run_ctx: RunContext[Any], code: str) -> str: # noqa: D417
163
- """Execute Python code with access to your own runtime context.
164
-
165
- This is a debugging/development tool that gives you full access to
166
- inspect and interact with your runtime environment.
167
-
168
- Args:
169
- code: Python code with async main() function to execute
170
-
171
- Returns:
172
- Result of execution or error message
173
- """
174
- # Emit progress with the code being executed
175
- await ctx.events.tool_call_progress(
176
- title="Executing introspection code",
177
- status="in_progress",
178
- items=[f"```python\n{code}\n```"],
179
- )
180
-
181
- # Build namespace with runtime context
182
- namespace: dict[str, Any] = {"ctx": ctx, "run_ctx": run_ctx, "me": ctx.agent}
183
- try:
184
- exec(code, namespace)
185
- if "main" not in namespace:
186
- return "Error: Code must define an async main() function"
187
- result = await namespace["main"]()
188
- await ctx.events.tool_call_progress(
189
- title="Executed introspection code successfully",
190
- status="in_progress",
191
- items=[f"```python\n{code}\n```\n\n```terminal\n{result}\n```"],
192
- )
193
-
194
- return str(result) if result is not None else "Code executed successfully (no return value)"
195
- except Exception as e: # noqa: BLE001
196
- return f"Error executing code: {type(e).__name__}: {e}"
197
-
198
-
199
172
  # =============================================================================
200
173
  # Log Tools
201
174
  # =============================================================================
@@ -269,6 +242,7 @@ class DebugTools(StaticResourceProvider):
269
242
  - Self-introspection via code execution with runtime context access
270
243
  - Log inspection and management
271
244
  - Platform path discovery
245
+ - Stateful namespace for persisting objects between introspection calls
272
246
  """
273
247
 
274
248
  def __init__(self, name: str = "debug") -> None:
@@ -278,9 +252,110 @@ class DebugTools(StaticResourceProvider):
278
252
  name: Toolset name/namespace
279
253
  """
280
254
  super().__init__(name=name)
255
+ self._namespace_storage: dict[str, Any] = {} # Stateful storage for introspection
256
+ # Wrap MemoryFileSystem for async support
257
+ self._memory_fs = MemoryFileSystem()
258
+ self._fs = AsyncFileSystemWrapper(self._memory_fs)
281
259
 
282
- desc = (execute_introspection.__doc__ or "") + "\n\n" + INTROSPECTION_USAGE
260
+ desc = (self.execute_introspection.__doc__ or "") + "\n\n" + INTROSPECTION_USAGE
283
261
  self._tools = [
284
- self.create_tool(execute_introspection, category="other", description_override=desc),
262
+ self.create_tool(
263
+ self.execute_introspection, category="other", description_override=desc
264
+ ),
285
265
  self.create_tool(get_platform_paths, category="other", read_only=True, idempotent=True),
286
266
  ]
267
+
268
+ def get_fs(self) -> AsyncFileSystem:
269
+ """Get filesystem view of script history and state.
270
+
271
+ Returns:
272
+ AsyncFileSystem containing:
273
+ - scripts/{timestamp}_{title}.py - Executed scripts
274
+ - scripts/{timestamp}_{title}.json - Execution metadata
275
+ """
276
+ return self._fs
277
+
278
+ async def execute_introspection( # noqa: D417
279
+ self, ctx: AgentContext, run_ctx: RunContext[Any], code: str, title: str
280
+ ) -> str:
281
+ """Execute Python code with access to your own runtime context.
282
+
283
+ This is a debugging/development tool that gives you full access to
284
+ inspect and interact with your runtime environment.
285
+
286
+ Args:
287
+ code: Python code with async main() function to execute
288
+ title: Short descriptive title for this script (3-4 words)
289
+
290
+ Returns:
291
+ Result of execution or error message
292
+ """
293
+ # Emit progress with the code being executed
294
+ await ctx.events.tool_call_progress(
295
+ title="Executing introspection code",
296
+ status="in_progress",
297
+ items=[f"```python\n{code}\n```"],
298
+ )
299
+
300
+ # Build namespace with runtime context and stateful storage
301
+ def save(key: str, value: Any) -> None:
302
+ """Save a value to persist between introspection calls."""
303
+ self._namespace_storage[key] = value
304
+
305
+ state = self._namespace_storage.copy()
306
+
307
+ namespace: dict[str, Any] = {
308
+ "ctx": ctx,
309
+ "run_ctx": run_ctx,
310
+ "me": ctx.agent,
311
+ "save": save,
312
+ "state": state,
313
+ }
314
+ start_time = datetime.now(UTC)
315
+ exit_code = 0
316
+ error_msg = None
317
+ result_str = None
318
+
319
+ try:
320
+ exec(code, namespace)
321
+ if "main" not in namespace:
322
+ return "Error: Code must define an async main() function"
323
+ result = await namespace["main"]()
324
+ result_str = (
325
+ str(result)
326
+ if result is not None
327
+ else "Code executed successfully (no return value)"
328
+ )
329
+
330
+ await ctx.events.tool_call_progress(
331
+ title="Executed introspection code successfully",
332
+ status="in_progress",
333
+ items=[f"```python\n{code}\n```\n\n```terminal\n{result_str}\n```"],
334
+ )
335
+ except Exception as e: # noqa: BLE001
336
+ exit_code = 1
337
+ error_msg = f"{type(e).__name__}: {e}"
338
+ result_str = error_msg
339
+ finally:
340
+ # Save to filesystem
341
+ end_time = datetime.now(UTC)
342
+ duration = (end_time - start_time).total_seconds()
343
+ timestamp = start_time.strftime("%Y%m%d_%H%M%S")
344
+
345
+ # Write script file
346
+ script_path = f"scripts/{timestamp}_{title}.py"
347
+ self._memory_fs.pipe(script_path, code.encode("utf-8"))
348
+
349
+ # Write metadata file
350
+ metadata = {
351
+ "title": title,
352
+ "timestamp": start_time.isoformat(),
353
+ "exit_code": exit_code,
354
+ "duration": duration,
355
+ "result": result_str,
356
+ "error": error_msg,
357
+ }
358
+ metadata_path = f"scripts/{timestamp}_{title}.json"
359
+ self._memory_fs.pipe(metadata_path, json.dumps(metadata, indent=2).encode("utf-8"))
360
+
361
+ return result_str
@@ -1,11 +1,9 @@
1
- """Provider for execution environment tools with event emission."""
1
+ """Provider for process management tools with event emission."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import re
5
6
  from typing import TYPE_CHECKING
6
- import uuid
7
-
8
- from exxec.events import OutputEvent, ProcessCompletedEvent, ProcessErrorEvent, ProcessStartedEvent
9
7
 
10
8
  from agentpool import log
11
9
  from agentpool.agents.context import AgentContext # noqa: TC001
@@ -16,26 +14,29 @@ logger = log.get_logger(__name__)
16
14
 
17
15
 
18
16
  if TYPE_CHECKING:
17
+ from collections.abc import Sequence
18
+
19
19
  from exxec import ExecutionEnvironment
20
20
 
21
21
  from agentpool.tools.base import Tool
22
22
 
23
23
 
24
- class ExecutionEnvironmentTools(ResourceProvider):
25
- """Provider for execution environment tools.
24
+ class ProcessManagementTools(ResourceProvider):
25
+ """Provider for background process management tools.
26
26
 
27
- Combines code execution and process management capabilities
28
- using any ExecutionEnvironment backend. Emits events via AgentContext.
27
+ Provides tools for starting, monitoring, and controlling background processes.
28
+ Uses any ExecutionEnvironment backend and emits events via AgentContext.
29
29
 
30
- NOTE: The ACP execution environment used handles the Terminal events of the protocol,
31
- the toolset should deal with the ToolCall events for UI display purposes.
30
+ For code execution and bash commands, use the standalone tools:
31
+ - agentpool.tool_impls.bash.BashTool
32
+ - agentpool.tool_impls.execute_code.ExecuteCodeTool
32
33
  """
33
34
 
34
- def __init__(self, env: ExecutionEnvironment | None = None, name: str = "execution") -> None:
35
- """Initialize execution environment toolset.
35
+ def __init__(self, env: ExecutionEnvironment | None = None, name: str = "process") -> None:
36
+ """Initialize process management toolset.
36
37
 
37
38
  Args:
38
- env: Execution environment to use (defaults to LocalExecutionEnvironment)
39
+ env: Execution environment to use (defaults to agent.env)
39
40
  name: The name of the toolset
40
41
  """
41
42
  super().__init__(name=name)
@@ -51,12 +52,8 @@ class ExecutionEnvironmentTools(ResourceProvider):
51
52
  return self._env
52
53
  return agent_ctx.agent.env
53
54
 
54
- async def get_tools(self) -> list[Tool]:
55
+ async def get_tools(self) -> Sequence[Tool]:
55
56
  return [
56
- # Code execution tools
57
- self.create_tool(self.execute_code, category="execute"),
58
- self.create_tool(self.bash, category="execute", open_world=True),
59
- # Process management tools
60
57
  self.create_tool(self.start_process, category="execute", open_world=True),
61
58
  self.create_tool(
62
59
  self.get_process_output, category="execute", read_only=True, idempotent=True
@@ -71,151 +68,6 @@ class ExecutionEnvironmentTools(ResourceProvider):
71
68
  ),
72
69
  ]
73
70
 
74
- async def execute_code(self, agent_ctx: AgentContext, code: str) -> str: # noqa: D417
75
- """Execute Python code and return the result.
76
-
77
- Args:
78
- code: Python code to execute
79
- """
80
- process_id: str | None = None
81
- output_parts: list[str] = []
82
- exit_code: int | None = None
83
- error_msg: str | None = None
84
- try:
85
- async for event in self.get_env(agent_ctx).stream_code(code):
86
- match event:
87
- case ProcessStartedEvent(process_id=pid, command=cmd):
88
- process_id = pid # save for later on.
89
- await agent_ctx.events.process_started(pid, cmd, success=True)
90
- case OutputEvent(data=data):
91
- output_parts.append(data)
92
- if process_id:
93
- await agent_ctx.events.process_output(process_id, data)
94
- case ProcessCompletedEvent(exit_code=code_):
95
- exit_code = code_
96
- out = "".join(output_parts)
97
- if process_id:
98
- await agent_ctx.events.process_exit(
99
- process_id, exit_code, final_output=out
100
- )
101
- case ProcessErrorEvent(error=err, exit_code=code_):
102
- error_msg = err
103
- exit_code = code_
104
- if process_id:
105
- await agent_ctx.events.process_exit(
106
- process_id, exit_code or 1, final_output=err
107
- )
108
-
109
- combined_output = "".join(output_parts)
110
-
111
- # Format as plain text for LLM
112
- if error_msg:
113
- return f"{combined_output}\n\nError: {error_msg}\nExit code: {exit_code}"
114
-
115
- except Exception as e: # noqa: BLE001
116
- error_id = process_id or f"code_{uuid.uuid4().hex[:8]}"
117
- await agent_ctx.events.process_started(
118
- error_id, "execute_code", success=False, error=str(e)
119
- )
120
- return f"Error executing code: {e}"
121
- else:
122
- # Return just output if success, add exit code only if non-zero
123
- if exit_code and exit_code != 0:
124
- return f"{combined_output}\n\nExit code: {exit_code}"
125
- return combined_output
126
-
127
- async def bash( # noqa: PLR0915, D417
128
- self,
129
- agent_ctx: AgentContext,
130
- command: str,
131
- output_limit: int | None = None,
132
- ) -> str:
133
- """Execute a shell command and return the output.
134
-
135
- Args:
136
- command: Shell command to execute
137
- output_limit: Maximum bytes of output to return
138
- """
139
- # process_id comes from exxec events (is terminal_id when using ACP)
140
- process_id: str | None = None
141
- stdout_parts: list[str] = []
142
- stderr_parts: list[str] = []
143
- exit_code: int | None = None
144
- error_msg: str | None = None
145
- try:
146
- async for event in self.get_env(agent_ctx).stream_command(command):
147
- match event:
148
- case ProcessStartedEvent(process_id=pid, command=cmd):
149
- process_id = pid
150
- if pid:
151
- await agent_ctx.events.process_started(pid, cmd, success=True)
152
- else:
153
- logger.warning("ProcessStartedEvent missing process_id", command=cmd)
154
- case OutputEvent(process_id=pid, data=data, stream=stream):
155
- if stream == "stderr":
156
- stderr_parts.append(data)
157
- else:
158
- stdout_parts.append(data)
159
- if pid:
160
- await agent_ctx.events.process_output(pid, data)
161
- else:
162
- logger.warning("OutputEvent missing process_id", stream=stream)
163
- case ProcessCompletedEvent(process_id=pid, exit_code=code_):
164
- exit_code = code_
165
- combined = "".join(stdout_parts) + "".join(stderr_parts)
166
- if pid:
167
- await agent_ctx.events.process_exit(
168
- pid, exit_code, final_output=combined
169
- )
170
- else:
171
- msg = "ProcessCompletedEvent missing process_id,"
172
- logger.warning(msg, exit_code=code_)
173
- case ProcessErrorEvent(process_id=pid, error=err, exit_code=code_):
174
- error_msg = err
175
- exit_code = code_
176
-
177
- stdout = "".join(stdout_parts)
178
- stderr = "".join(stderr_parts)
179
-
180
- # Apply output limit if specified
181
- truncated = False
182
- if output_limit:
183
- if len(stdout.encode()) > output_limit:
184
- out = stdout.encode()[-output_limit:].decode(errors="ignore")
185
- stdout = "...[truncated]\n" + out
186
- truncated = True
187
- if len(stderr.encode()) > output_limit:
188
- out = stderr.encode()[-output_limit:].decode(errors="ignore")
189
- stderr = "...[truncated]\n" + out
190
- truncated = True
191
-
192
- # Format as plain text for LLM
193
- if error_msg:
194
- output = stdout + stderr if stdout or stderr else ""
195
- return f"{output}\n\nError: {error_msg}\nExit code: {exit_code}"
196
-
197
- except Exception as e: # noqa: BLE001
198
- # Use process_id from events if available, otherwise generate fallback
199
- error_id = process_id or f"cmd_{uuid.uuid4().hex[:8]}"
200
- await agent_ctx.events.process_started(error_id, command, success=False, error=str(e))
201
- return f"Error executing command: {e}"
202
- else:
203
- # Combine stdout and stderr for output
204
- output = stdout
205
- if stderr:
206
- output = f"{stdout}\n\nSTDERR:\n{stderr}" if stdout else f"STDERR:\n{stderr}"
207
-
208
- # Add metadata only when relevant
209
- suffix_parts = []
210
- if truncated:
211
- suffix_parts.append("[output truncated]")
212
- if exit_code and exit_code != 0:
213
- suffix_parts.append(f"Exit code: {exit_code}")
214
-
215
- if suffix_parts:
216
- return f"{output}\n\n{' | '.join(suffix_parts)}"
217
- return output
218
-
219
71
  async def start_process( # noqa: D417
220
72
  self,
221
73
  agent_ctx: AgentContext,
@@ -252,11 +104,18 @@ class ExecutionEnvironmentTools(ResourceProvider):
252
104
  full_cmd = f"{command} {' '.join(args)}" if args else command
253
105
  return f"Started background process {process_id}\nCommand: {full_cmd}"
254
106
 
255
- async def get_process_output(self, agent_ctx: AgentContext, process_id: str) -> str: # noqa: D417
107
+ async def get_process_output( # noqa: D417
108
+ self,
109
+ agent_ctx: AgentContext,
110
+ process_id: str,
111
+ filter_lines: str | None = None,
112
+ ) -> str:
256
113
  """Get current output from a background process.
257
114
 
258
115
  Args:
259
116
  process_id: Process identifier from start_process
117
+ filter_lines: Optional regex pattern to filter output lines
118
+ (only matching lines returned)
260
119
  """
261
120
  manager = self.get_env(agent_ctx).process_manager
262
121
  try:
@@ -264,6 +123,18 @@ class ExecutionEnvironmentTools(ResourceProvider):
264
123
  await agent_ctx.events.process_output(process_id, output.combined or "")
265
124
 
266
125
  combined = output.combined or ""
126
+
127
+ # Apply regex filter if specified
128
+ if filter_lines and combined:
129
+ try:
130
+ pattern = re.compile(filter_lines)
131
+ filtered_lines = [
132
+ line for line in combined.splitlines(keepends=True) if pattern.search(line)
133
+ ]
134
+ combined = "".join(filtered_lines)
135
+ except re.error as regex_err:
136
+ return f"Invalid filter regex: {regex_err}"
137
+
267
138
  status = "completed" if output.exit_code is not None else "running"
268
139
 
269
140
  # Format as plain text
@@ -283,11 +154,18 @@ class ExecutionEnvironmentTools(ResourceProvider):
283
154
  except Exception as e: # noqa: BLE001
284
155
  return f"Error getting process output: {e}"
285
156
 
286
- async def wait_for_process(self, agent_ctx: AgentContext, process_id: str) -> str: # noqa: D417
157
+ async def wait_for_process( # noqa: D417
158
+ self,
159
+ agent_ctx: AgentContext,
160
+ process_id: str,
161
+ filter_lines: str | None = None,
162
+ ) -> str:
287
163
  """Wait for background process to complete and return final output.
288
164
 
289
165
  Args:
290
166
  process_id: Process identifier from start_process
167
+ filter_lines: Optional regex pattern to filter output lines
168
+ (only matching lines returned)
291
169
  """
292
170
  manager = self.get_env(agent_ctx).process_manager
293
171
  try:
@@ -301,6 +179,17 @@ class ExecutionEnvironmentTools(ResourceProvider):
301
179
  else:
302
180
  combined = output.combined or ""
303
181
 
182
+ # Apply regex filter if specified
183
+ if filter_lines and combined:
184
+ try:
185
+ pattern = re.compile(filter_lines)
186
+ filtered_lines = [
187
+ line for line in combined.splitlines(keepends=True) if pattern.search(line)
188
+ ]
189
+ combined = "".join(filtered_lines)
190
+ except re.error as regex_err:
191
+ return f"Invalid filter regex: {regex_err}"
192
+
304
193
  # Format as plain text
305
194
  suffix_parts = []
306
195
  if output.truncated:
@@ -59,77 +59,6 @@ async def list_skills(ctx: AgentContext) -> str:
59
59
  return "\n".join(lines)
60
60
 
61
61
 
62
- class _StringOutputWriter:
63
- """Output writer that captures output to a string buffer."""
64
-
65
- def __init__(self) -> None:
66
- from io import StringIO
67
-
68
- self._buffer = StringIO()
69
-
70
- async def print(self, message: str) -> None:
71
- """Write a message to the buffer."""
72
- self._buffer.write(message)
73
- self._buffer.write("\n")
74
-
75
- def getvalue(self) -> str:
76
- """Get the captured output."""
77
- return self._buffer.getvalue()
78
-
79
-
80
- async def run_command(ctx: AgentContext, command: str) -> str: # noqa: D417
81
- """Execute an internal command.
82
-
83
- This provides access to the agent's internal CLI for management operations.
84
-
85
- IMPORTANT: Before using any command for the first time, call "help <command>" to learn
86
- the correct syntax and available options. Commands have specific argument orders and
87
- flags that must be followed exactly.
88
-
89
- Discovery commands:
90
- - "help" - list all available commands
91
- - "help <command>" - get detailed usage for a specific command (ALWAYS do this first!)
92
-
93
- Command categories:
94
- - Agent/team management: create-agent, create-team, list-agents
95
- - Tool management: list-tools, register-tool, enable-tool, disable-tool
96
- - MCP servers: add-mcp-server, add-remote-mcp-server, list-mcp-servers
97
- - Connections: connect, disconnect, connections
98
- - Workers: add-worker, remove-worker, list-workers
99
-
100
- Args:
101
- command: The command to execute. Leading slash is optional.
102
-
103
- Returns:
104
- Command output or error message
105
- """
106
- from slashed import CommandContext
107
-
108
- if not ctx.agent.command_store:
109
- return "No command store available"
110
-
111
- # Remove leading slash if present (slashed expects command name without /)
112
- cmd = command.lstrip("/")
113
-
114
- # Create output capture
115
- output = _StringOutputWriter()
116
-
117
- # Create CommandContext with output capture and AgentContext as data
118
- cmd_ctx = CommandContext(
119
- output=output,
120
- data=ctx,
121
- command_store=ctx.agent.command_store,
122
- )
123
-
124
- try:
125
- await ctx.agent.command_store.execute_command(cmd, cmd_ctx)
126
- result = output.getvalue()
127
- except Exception as e: # noqa: BLE001
128
- return f"Command failed: {e}"
129
- else:
130
- return result if result else "Command executed successfully."
131
-
132
-
133
62
  class SkillsTools(StaticResourceProvider):
134
63
  """Provider for skills and commands tools.
135
64
 
@@ -150,10 +79,4 @@ class SkillsTools(StaticResourceProvider):
150
79
  self._tools = [
151
80
  self.create_tool(load_skill, category="read", read_only=True, idempotent=True),
152
81
  self.create_tool(list_skills, category="read", read_only=True, idempotent=True),
153
- self.create_tool(
154
- run_command,
155
- category="other",
156
- read_only=False,
157
- idempotent=False,
158
- ),
159
82
  ]