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