agentpool 2.2.3__py3-none-any.whl → 2.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- acp/__init__.py +0 -4
- acp/acp_requests.py +20 -77
- acp/agent/connection.py +8 -0
- acp/agent/implementations/debug_server/debug_server.py +6 -2
- acp/agent/protocol.py +6 -0
- acp/client/connection.py +38 -29
- acp/client/implementations/default_client.py +3 -2
- acp/client/implementations/headless_client.py +2 -2
- acp/connection.py +2 -2
- acp/notifications.py +18 -49
- acp/schema/__init__.py +2 -0
- acp/schema/agent_responses.py +21 -0
- acp/schema/client_requests.py +3 -3
- acp/schema/session_state.py +63 -29
- acp/task/supervisor.py +2 -2
- acp/utils.py +2 -2
- agentpool/__init__.py +2 -0
- agentpool/agents/acp_agent/acp_agent.py +278 -263
- agentpool/agents/acp_agent/acp_converters.py +150 -17
- agentpool/agents/acp_agent/client_handler.py +35 -24
- agentpool/agents/acp_agent/session_state.py +14 -6
- agentpool/agents/agent.py +471 -643
- agentpool/agents/agui_agent/agui_agent.py +104 -107
- agentpool/agents/agui_agent/helpers.py +3 -4
- agentpool/agents/base_agent.py +485 -32
- agentpool/agents/claude_code_agent/FORKING.md +191 -0
- agentpool/agents/claude_code_agent/__init__.py +13 -1
- agentpool/agents/claude_code_agent/claude_code_agent.py +654 -334
- agentpool/agents/claude_code_agent/converters.py +4 -141
- agentpool/agents/claude_code_agent/models.py +77 -0
- agentpool/agents/claude_code_agent/static_info.py +100 -0
- agentpool/agents/claude_code_agent/usage.py +242 -0
- agentpool/agents/events/__init__.py +22 -0
- agentpool/agents/events/builtin_handlers.py +65 -0
- agentpool/agents/events/event_emitter.py +3 -0
- agentpool/agents/events/events.py +84 -3
- agentpool/agents/events/infer_info.py +145 -0
- agentpool/agents/events/processors.py +254 -0
- agentpool/agents/interactions.py +41 -6
- agentpool/agents/modes.py +13 -0
- agentpool/agents/slashed_agent.py +5 -4
- agentpool/agents/tool_wrapping.py +18 -6
- agentpool/common_types.py +35 -21
- agentpool/config_resources/acp_assistant.yml +2 -2
- agentpool/config_resources/agents.yml +3 -0
- agentpool/config_resources/agents_template.yml +1 -0
- agentpool/config_resources/claude_code_agent.yml +9 -8
- agentpool/config_resources/external_acp_agents.yml +2 -1
- agentpool/delegation/base_team.py +4 -30
- agentpool/delegation/pool.py +104 -265
- agentpool/delegation/team.py +57 -57
- agentpool/delegation/teamrun.py +50 -55
- agentpool/functional/run.py +10 -4
- agentpool/mcp_server/client.py +73 -38
- agentpool/mcp_server/conversions.py +54 -13
- agentpool/mcp_server/manager.py +9 -23
- agentpool/mcp_server/registries/official_registry_client.py +10 -1
- agentpool/mcp_server/tool_bridge.py +114 -79
- agentpool/messaging/connection_manager.py +11 -10
- agentpool/messaging/event_manager.py +5 -5
- agentpool/messaging/message_container.py +6 -30
- agentpool/messaging/message_history.py +87 -8
- agentpool/messaging/messagenode.py +52 -14
- agentpool/messaging/messages.py +2 -26
- agentpool/messaging/processing.py +10 -22
- agentpool/models/__init__.py +1 -1
- agentpool/models/acp_agents/base.py +6 -2
- agentpool/models/acp_agents/mcp_capable.py +124 -15
- agentpool/models/acp_agents/non_mcp.py +0 -23
- agentpool/models/agents.py +66 -66
- agentpool/models/agui_agents.py +1 -1
- agentpool/models/claude_code_agents.py +111 -17
- agentpool/models/file_parsing.py +0 -1
- agentpool/models/manifest.py +70 -50
- agentpool/prompts/conversion_manager.py +1 -1
- agentpool/prompts/prompts.py +5 -2
- agentpool/resource_providers/__init__.py +2 -0
- agentpool/resource_providers/aggregating.py +4 -2
- agentpool/resource_providers/base.py +13 -3
- agentpool/resource_providers/codemode/code_executor.py +72 -5
- agentpool/resource_providers/codemode/helpers.py +2 -2
- agentpool/resource_providers/codemode/provider.py +64 -12
- agentpool/resource_providers/codemode/remote_mcp_execution.py +2 -2
- agentpool/resource_providers/codemode/remote_provider.py +9 -12
- agentpool/resource_providers/filtering.py +3 -1
- agentpool/resource_providers/mcp_provider.py +66 -12
- agentpool/resource_providers/plan_provider.py +111 -18
- agentpool/resource_providers/pool.py +5 -3
- agentpool/resource_providers/resource_info.py +111 -0
- agentpool/resource_providers/static.py +2 -2
- agentpool/sessions/__init__.py +2 -0
- agentpool/sessions/manager.py +2 -3
- agentpool/sessions/models.py +9 -6
- agentpool/sessions/protocol.py +28 -0
- agentpool/sessions/session.py +11 -55
- agentpool/storage/manager.py +361 -54
- agentpool/talk/registry.py +4 -4
- agentpool/talk/talk.py +9 -10
- agentpool/testing.py +1 -1
- agentpool/tool_impls/__init__.py +6 -0
- agentpool/tool_impls/agent_cli/__init__.py +42 -0
- agentpool/tool_impls/agent_cli/tool.py +95 -0
- agentpool/tool_impls/bash/__init__.py +64 -0
- agentpool/tool_impls/bash/helpers.py +35 -0
- agentpool/tool_impls/bash/tool.py +171 -0
- agentpool/tool_impls/delete_path/__init__.py +70 -0
- agentpool/tool_impls/delete_path/tool.py +142 -0
- agentpool/tool_impls/download_file/__init__.py +80 -0
- agentpool/tool_impls/download_file/tool.py +183 -0
- agentpool/tool_impls/execute_code/__init__.py +55 -0
- agentpool/tool_impls/execute_code/tool.py +163 -0
- agentpool/tool_impls/grep/__init__.py +80 -0
- agentpool/tool_impls/grep/tool.py +200 -0
- agentpool/tool_impls/list_directory/__init__.py +73 -0
- agentpool/tool_impls/list_directory/tool.py +197 -0
- agentpool/tool_impls/question/__init__.py +42 -0
- agentpool/tool_impls/question/tool.py +127 -0
- agentpool/tool_impls/read/__init__.py +104 -0
- agentpool/tool_impls/read/tool.py +305 -0
- agentpool/tools/__init__.py +2 -1
- agentpool/tools/base.py +114 -34
- agentpool/tools/manager.py +57 -1
- agentpool/ui/base.py +2 -2
- agentpool/ui/mock_provider.py +2 -2
- agentpool/ui/stdlib_provider.py +2 -2
- agentpool/utils/streams.py +21 -96
- agentpool/vfs_registry.py +7 -2
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/METADATA +16 -22
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/RECORD +242 -195
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/WHEEL +1 -1
- agentpool_cli/__main__.py +20 -0
- agentpool_cli/create.py +1 -1
- agentpool_cli/serve_acp.py +59 -1
- agentpool_cli/serve_opencode.py +1 -1
- agentpool_cli/ui.py +557 -0
- agentpool_commands/__init__.py +12 -5
- agentpool_commands/agents.py +1 -1
- agentpool_commands/pool.py +260 -0
- agentpool_commands/session.py +1 -1
- agentpool_commands/text_sharing/__init__.py +119 -0
- agentpool_commands/text_sharing/base.py +123 -0
- agentpool_commands/text_sharing/github_gist.py +80 -0
- agentpool_commands/text_sharing/opencode.py +462 -0
- agentpool_commands/text_sharing/paste_rs.py +59 -0
- agentpool_commands/text_sharing/pastebin.py +116 -0
- agentpool_commands/text_sharing/shittycodingagent.py +112 -0
- agentpool_commands/utils.py +31 -32
- agentpool_config/__init__.py +30 -2
- agentpool_config/agentpool_tools.py +498 -0
- agentpool_config/converters.py +1 -1
- agentpool_config/event_handlers.py +42 -0
- agentpool_config/events.py +1 -1
- agentpool_config/forward_targets.py +1 -4
- agentpool_config/jinja.py +3 -3
- agentpool_config/mcp_server.py +1 -5
- agentpool_config/nodes.py +1 -1
- agentpool_config/observability.py +44 -0
- agentpool_config/session.py +0 -3
- agentpool_config/storage.py +38 -39
- agentpool_config/task.py +3 -3
- agentpool_config/tools.py +11 -28
- agentpool_config/toolsets.py +22 -90
- agentpool_server/a2a_server/agent_worker.py +307 -0
- agentpool_server/a2a_server/server.py +23 -18
- agentpool_server/acp_server/acp_agent.py +125 -56
- agentpool_server/acp_server/commands/acp_commands.py +46 -216
- agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +8 -7
- agentpool_server/acp_server/event_converter.py +651 -0
- agentpool_server/acp_server/input_provider.py +53 -10
- agentpool_server/acp_server/server.py +1 -11
- agentpool_server/acp_server/session.py +90 -410
- agentpool_server/acp_server/session_manager.py +8 -34
- agentpool_server/agui_server/server.py +3 -1
- agentpool_server/mcp_server/server.py +5 -2
- agentpool_server/opencode_server/ENDPOINTS.md +53 -14
- agentpool_server/opencode_server/OPENCODE_UI_TOOLS_COMPLETE.md +202 -0
- agentpool_server/opencode_server/__init__.py +0 -8
- agentpool_server/opencode_server/converters.py +132 -26
- agentpool_server/opencode_server/input_provider.py +160 -8
- agentpool_server/opencode_server/models/__init__.py +42 -20
- agentpool_server/opencode_server/models/app.py +12 -0
- agentpool_server/opencode_server/models/events.py +203 -29
- agentpool_server/opencode_server/models/mcp.py +19 -0
- agentpool_server/opencode_server/models/message.py +18 -1
- agentpool_server/opencode_server/models/parts.py +134 -1
- agentpool_server/opencode_server/models/question.py +56 -0
- agentpool_server/opencode_server/models/session.py +13 -1
- agentpool_server/opencode_server/routes/__init__.py +4 -0
- agentpool_server/opencode_server/routes/agent_routes.py +33 -2
- agentpool_server/opencode_server/routes/app_routes.py +66 -3
- agentpool_server/opencode_server/routes/config_routes.py +66 -5
- agentpool_server/opencode_server/routes/file_routes.py +184 -5
- agentpool_server/opencode_server/routes/global_routes.py +1 -1
- agentpool_server/opencode_server/routes/lsp_routes.py +1 -1
- agentpool_server/opencode_server/routes/message_routes.py +122 -66
- agentpool_server/opencode_server/routes/permission_routes.py +63 -0
- agentpool_server/opencode_server/routes/pty_routes.py +23 -22
- agentpool_server/opencode_server/routes/question_routes.py +128 -0
- agentpool_server/opencode_server/routes/session_routes.py +139 -68
- agentpool_server/opencode_server/routes/tui_routes.py +1 -1
- agentpool_server/opencode_server/server.py +47 -2
- agentpool_server/opencode_server/state.py +30 -0
- agentpool_storage/__init__.py +0 -4
- agentpool_storage/base.py +81 -2
- agentpool_storage/claude_provider/ARCHITECTURE.md +433 -0
- agentpool_storage/claude_provider/__init__.py +42 -0
- agentpool_storage/{claude_provider.py → claude_provider/provider.py} +190 -8
- agentpool_storage/file_provider.py +149 -15
- agentpool_storage/memory_provider.py +132 -12
- agentpool_storage/opencode_provider/ARCHITECTURE.md +386 -0
- agentpool_storage/opencode_provider/__init__.py +16 -0
- agentpool_storage/opencode_provider/helpers.py +414 -0
- agentpool_storage/opencode_provider/provider.py +895 -0
- agentpool_storage/session_store.py +20 -6
- agentpool_storage/sql_provider/sql_provider.py +135 -2
- agentpool_storage/sql_provider/utils.py +2 -12
- agentpool_storage/zed_provider/__init__.py +16 -0
- agentpool_storage/zed_provider/helpers.py +281 -0
- agentpool_storage/zed_provider/models.py +130 -0
- agentpool_storage/zed_provider/provider.py +442 -0
- agentpool_storage/zed_provider.py +803 -0
- agentpool_toolsets/__init__.py +0 -2
- agentpool_toolsets/builtin/__init__.py +2 -4
- agentpool_toolsets/builtin/code.py +4 -4
- agentpool_toolsets/builtin/debug.py +115 -40
- agentpool_toolsets/builtin/execution_environment.py +54 -165
- agentpool_toolsets/builtin/skills.py +0 -77
- agentpool_toolsets/builtin/subagent_tools.py +64 -51
- agentpool_toolsets/builtin/workers.py +4 -2
- agentpool_toolsets/composio_toolset.py +2 -2
- agentpool_toolsets/entry_points.py +3 -1
- agentpool_toolsets/fsspec_toolset/grep.py +25 -5
- agentpool_toolsets/fsspec_toolset/helpers.py +3 -2
- agentpool_toolsets/fsspec_toolset/toolset.py +350 -66
- agentpool_toolsets/mcp_discovery/data/mcp_servers.parquet +0 -0
- agentpool_toolsets/mcp_discovery/toolset.py +74 -17
- agentpool_toolsets/mcp_run_toolset.py +8 -11
- agentpool_toolsets/notifications.py +33 -33
- agentpool_toolsets/openapi.py +3 -1
- agentpool_toolsets/search_toolset.py +3 -1
- agentpool_config/resources.py +0 -33
- agentpool_server/acp_server/acp_tools.py +0 -43
- agentpool_server/acp_server/commands/spawn.py +0 -210
- agentpool_storage/opencode_provider.py +0 -730
- agentpool_storage/text_log_provider.py +0 -276
- agentpool_toolsets/builtin/chain.py +0 -288
- agentpool_toolsets/builtin/user_interaction.py +0 -52
- agentpool_toolsets/semantic_memory_toolset.py +0 -536
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/entry_points.txt +0 -0
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"""Pool-level commands for managing agent pools and configurations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
from agentpool.agents.base_agent import BaseAgent
|
|
8
|
+
from agentpool_commands.base import NodeCommand
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from slashed import CommandContext
|
|
13
|
+
|
|
14
|
+
from agentpool.messaging.context import NodeContext
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ListPoolsCommand(NodeCommand):
|
|
18
|
+
"""List available agent pool configurations.
|
|
19
|
+
|
|
20
|
+
Examples:
|
|
21
|
+
/list-pools
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
name = "list-pools"
|
|
25
|
+
category = "pool"
|
|
26
|
+
|
|
27
|
+
async def execute_command(self, ctx: CommandContext[NodeContext[Any]]) -> None:
|
|
28
|
+
"""List available pool configurations.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
ctx: Command context with node context
|
|
32
|
+
"""
|
|
33
|
+
from agentpool_cli import agent_store
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
output_lines = ["## 🏊 Agent Pool Configurations\n"]
|
|
37
|
+
|
|
38
|
+
# Show current pool info
|
|
39
|
+
output_lines.append("### 📍 Current Pool")
|
|
40
|
+
|
|
41
|
+
# Get config path from context config (works for all agent types)
|
|
42
|
+
current_config = ctx.context.config.config_file_path if ctx.context.config else None
|
|
43
|
+
if current_config:
|
|
44
|
+
output_lines.append(f"**Config:** `{current_config}`")
|
|
45
|
+
else:
|
|
46
|
+
output_lines.append("**Config:** *(default/built-in)*")
|
|
47
|
+
|
|
48
|
+
# Show agents in current pool
|
|
49
|
+
pool = ctx.context.pool
|
|
50
|
+
if pool:
|
|
51
|
+
agent_names = list(pool.all_agents.keys())
|
|
52
|
+
output_lines.append(f"**Agents:** {', '.join(f'`{n}`' for n in agent_names)}")
|
|
53
|
+
output_lines.append(f"**Active agent:** `{ctx.context.node.name}`")
|
|
54
|
+
else:
|
|
55
|
+
output_lines.append("**No pool available**")
|
|
56
|
+
output_lines.append("")
|
|
57
|
+
|
|
58
|
+
# Show stored configurations
|
|
59
|
+
output_lines.append("### 💾 Stored Configurations")
|
|
60
|
+
stored_configs = agent_store.list_configs()
|
|
61
|
+
active_config = agent_store.get_active()
|
|
62
|
+
|
|
63
|
+
if not stored_configs:
|
|
64
|
+
output_lines.append("*No stored configurations*")
|
|
65
|
+
output_lines.append("")
|
|
66
|
+
output_lines.append("Use `agentpool add <name> <path>` to add configurations.")
|
|
67
|
+
else:
|
|
68
|
+
# Build markdown table
|
|
69
|
+
output_lines.append("| Name | Path |")
|
|
70
|
+
output_lines.append("|------|------|")
|
|
71
|
+
for name, path in stored_configs:
|
|
72
|
+
is_active = active_config and active_config.name == name
|
|
73
|
+
is_current = current_config and path == current_config
|
|
74
|
+
markers = []
|
|
75
|
+
if is_active:
|
|
76
|
+
markers.append("default")
|
|
77
|
+
if is_current:
|
|
78
|
+
markers.append("current")
|
|
79
|
+
name_col = f"{name} ({', '.join(markers)})" if markers else name
|
|
80
|
+
output_lines.append(f"| {name_col} | `{path}` |")
|
|
81
|
+
|
|
82
|
+
output_lines.append("")
|
|
83
|
+
output_lines.append("*Use `/set-pool <name>` or `/set-pool <path>` to switch pools.*")
|
|
84
|
+
|
|
85
|
+
await ctx.output.print("\n".join(output_lines))
|
|
86
|
+
|
|
87
|
+
except Exception as e: # noqa: BLE001
|
|
88
|
+
await ctx.output.print(f"❌ **Error listing pools:** {e}")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class CompactCommand(NodeCommand):
|
|
92
|
+
"""Compact the conversation history to reduce context size.
|
|
93
|
+
|
|
94
|
+
Uses the configured compaction pipeline from the agent pool manifest,
|
|
95
|
+
or falls back to a default summarizing pipeline.
|
|
96
|
+
|
|
97
|
+
Options:
|
|
98
|
+
--preset <name> Use a specific preset (minimal, balanced, summarizing)
|
|
99
|
+
|
|
100
|
+
Examples:
|
|
101
|
+
/compact
|
|
102
|
+
/compact --preset=minimal
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
name = "compact"
|
|
106
|
+
category = "pool"
|
|
107
|
+
|
|
108
|
+
async def execute_command(
|
|
109
|
+
self,
|
|
110
|
+
ctx: CommandContext[NodeContext[Any]],
|
|
111
|
+
*,
|
|
112
|
+
preset: str | None = None,
|
|
113
|
+
) -> None:
|
|
114
|
+
"""Compact the conversation history.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
ctx: Command context with node context
|
|
118
|
+
preset: Optional preset name (minimal, balanced, summarizing)
|
|
119
|
+
"""
|
|
120
|
+
from agentpool.agents.base_agent import BaseAgent
|
|
121
|
+
|
|
122
|
+
# Get agent from context
|
|
123
|
+
agent = ctx.context.node
|
|
124
|
+
if not isinstance(agent, BaseAgent):
|
|
125
|
+
await ctx.output.print(
|
|
126
|
+
"❌ **This command requires an agent with conversation history**"
|
|
127
|
+
)
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
# Check if there's any history to compact
|
|
131
|
+
if not agent.conversation.get_history():
|
|
132
|
+
await ctx.output.print("📭 **No message history to compact**")
|
|
133
|
+
return
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
# Get compaction pipeline
|
|
137
|
+
from agentpool.messaging.compaction import (
|
|
138
|
+
balanced_context,
|
|
139
|
+
minimal_context,
|
|
140
|
+
summarizing_context,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
pipeline = None
|
|
144
|
+
|
|
145
|
+
# Check for preset override
|
|
146
|
+
if preset:
|
|
147
|
+
match preset.lower():
|
|
148
|
+
case "minimal":
|
|
149
|
+
pipeline = minimal_context()
|
|
150
|
+
case "balanced":
|
|
151
|
+
pipeline = balanced_context()
|
|
152
|
+
case "summarizing":
|
|
153
|
+
pipeline = summarizing_context()
|
|
154
|
+
case _:
|
|
155
|
+
await ctx.output.print(
|
|
156
|
+
f"⚠️ **Unknown preset:** `{preset}`\n"
|
|
157
|
+
"Available: minimal, balanced, summarizing"
|
|
158
|
+
)
|
|
159
|
+
return
|
|
160
|
+
|
|
161
|
+
# Fall back to pool's configured pipeline
|
|
162
|
+
if pipeline is None and ctx.context.pool:
|
|
163
|
+
pipeline = ctx.context.pool.compaction_pipeline
|
|
164
|
+
|
|
165
|
+
# Fall back to default summarizing pipeline
|
|
166
|
+
if pipeline is None:
|
|
167
|
+
pipeline = summarizing_context()
|
|
168
|
+
|
|
169
|
+
await ctx.output.print("🔄 **Compacting conversation history...**")
|
|
170
|
+
|
|
171
|
+
# Apply the pipeline using shared helper
|
|
172
|
+
from agentpool.messaging.compaction import compact_conversation
|
|
173
|
+
|
|
174
|
+
original_count, compacted_count = await compact_conversation(
|
|
175
|
+
pipeline, agent.conversation
|
|
176
|
+
)
|
|
177
|
+
reduction = original_count - compacted_count
|
|
178
|
+
|
|
179
|
+
await ctx.output.print(
|
|
180
|
+
f"✅ **Compaction complete**\n"
|
|
181
|
+
f"- Messages: {original_count} → {compacted_count} ({reduction} removed)\n"
|
|
182
|
+
f"- Reduction: {reduction / original_count * 100:.1f}%"
|
|
183
|
+
if original_count > 0
|
|
184
|
+
else "✅ **Compaction complete** (no messages)"
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
except Exception as e: # noqa: BLE001
|
|
188
|
+
await ctx.output.print(f"❌ **Error compacting history:** {e}")
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class SpawnCommand(NodeCommand):
|
|
192
|
+
"""Spawn a subagent to execute a specific task.
|
|
193
|
+
|
|
194
|
+
The subagent runs and its progress is streamed back through the event system.
|
|
195
|
+
How the progress is displayed depends on the protocol (tool box in ACP, inline, etc.).
|
|
196
|
+
|
|
197
|
+
Examples:
|
|
198
|
+
/spawn agent-name "task description"
|
|
199
|
+
/spawn code-reviewer "Review main.py for bugs"
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
name = "spawn"
|
|
203
|
+
category = "pool"
|
|
204
|
+
|
|
205
|
+
@classmethod
|
|
206
|
+
def supports_node(cls, node: Any) -> bool:
|
|
207
|
+
"""Only available when running from an agent (needs events)."""
|
|
208
|
+
return isinstance(node, BaseAgent)
|
|
209
|
+
|
|
210
|
+
async def execute_command(
|
|
211
|
+
self,
|
|
212
|
+
ctx: CommandContext[NodeContext[Any]],
|
|
213
|
+
agent_name: str,
|
|
214
|
+
task_prompt: str,
|
|
215
|
+
) -> None:
|
|
216
|
+
"""Spawn a subagent to execute a task.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
ctx: Command context with node context
|
|
220
|
+
agent_name: Name of the agent to spawn
|
|
221
|
+
task_prompt: Task prompt for the subagent
|
|
222
|
+
"""
|
|
223
|
+
from agentpool.agents.events import SubAgentEvent
|
|
224
|
+
|
|
225
|
+
pool = ctx.context.pool
|
|
226
|
+
if not pool:
|
|
227
|
+
await ctx.output.print("❌ **No agent pool available**")
|
|
228
|
+
return
|
|
229
|
+
|
|
230
|
+
if agent_name not in pool.nodes:
|
|
231
|
+
available = list(pool.nodes.keys())
|
|
232
|
+
await ctx.output.print(
|
|
233
|
+
f"❌ **Agent** `{agent_name}` **not found**\n\n"
|
|
234
|
+
f"Available agents: {', '.join(available)}"
|
|
235
|
+
)
|
|
236
|
+
return
|
|
237
|
+
|
|
238
|
+
from agentpool.common_types import SupportsRunStream
|
|
239
|
+
|
|
240
|
+
agent = pool.nodes[agent_name]
|
|
241
|
+
|
|
242
|
+
# Check if node supports streaming
|
|
243
|
+
if not isinstance(agent, SupportsRunStream):
|
|
244
|
+
await ctx.output.print(f"❌ **Agent** `{agent_name}` **does not support streaming**")
|
|
245
|
+
return
|
|
246
|
+
|
|
247
|
+
# Stream subagent execution by wrapping events in SubAgentEvent
|
|
248
|
+
# The event handler system (ACP, OpenCode, CLI, etc.) handles rendering
|
|
249
|
+
# Get parent agent's context to access event emitter
|
|
250
|
+
parent_ctx = ctx.context.agent.get_context()
|
|
251
|
+
|
|
252
|
+
async for event in agent.run_stream(task_prompt):
|
|
253
|
+
wrapped = SubAgentEvent(
|
|
254
|
+
source_name=agent_name,
|
|
255
|
+
source_type="agent",
|
|
256
|
+
event=event,
|
|
257
|
+
depth=1,
|
|
258
|
+
)
|
|
259
|
+
# Emit to parent agent's event stream
|
|
260
|
+
await parent_ctx.events.emit_event(wrapped)
|
agentpool_commands/session.py
CHANGED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""Text sharing services for sharing content via paste services."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Literal, assert_never, overload
|
|
6
|
+
|
|
7
|
+
from agentpool_commands.text_sharing.base import ShareResult, TextSharer, Visibility
|
|
8
|
+
from agentpool_commands.text_sharing.github_gist import GistSharer
|
|
9
|
+
from agentpool_commands.text_sharing.opencode import OpenCodeSharer
|
|
10
|
+
from agentpool_commands.text_sharing.paste_rs import PasteRsSharer
|
|
11
|
+
from agentpool_commands.text_sharing.pastebin import PastebinSharer
|
|
12
|
+
from agentpool_commands.text_sharing.shittycodingagent import ShittyCodingAgentSharer
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
TextSharerStr = Literal["gist", "pastebin", "paste_rs", "opencode", "shittycodingagent"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@overload
|
|
19
|
+
def get_sharer(
|
|
20
|
+
provider: Literal["gist"],
|
|
21
|
+
*,
|
|
22
|
+
token: str | None = None,
|
|
23
|
+
) -> GistSharer: ...
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@overload
|
|
27
|
+
def get_sharer(
|
|
28
|
+
provider: Literal["pastebin"],
|
|
29
|
+
*,
|
|
30
|
+
api_key: str | None = None,
|
|
31
|
+
) -> PastebinSharer: ...
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@overload
|
|
35
|
+
def get_sharer(
|
|
36
|
+
provider: Literal["paste_rs"],
|
|
37
|
+
) -> PasteRsSharer: ...
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@overload
|
|
41
|
+
def get_sharer(
|
|
42
|
+
provider: Literal["opencode"],
|
|
43
|
+
*,
|
|
44
|
+
api_url: str | None = None,
|
|
45
|
+
) -> OpenCodeSharer: ...
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@overload
|
|
49
|
+
def get_sharer(
|
|
50
|
+
provider: Literal["shittycodingagent"],
|
|
51
|
+
*,
|
|
52
|
+
token: str | None = None,
|
|
53
|
+
) -> ShittyCodingAgentSharer: ...
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def get_sharer(
|
|
57
|
+
provider: TextSharerStr,
|
|
58
|
+
**kwargs: str | None,
|
|
59
|
+
) -> TextSharer:
|
|
60
|
+
"""Get a text sharer based on provider name.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
provider: The text sharing provider to use
|
|
64
|
+
**kwargs: Keyword arguments to pass to the provider constructor
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
An instance of the specified text sharer
|
|
68
|
+
|
|
69
|
+
Example:
|
|
70
|
+
```python
|
|
71
|
+
# GitHub Gist (reads GITHUB_TOKEN/GH_TOKEN from env)
|
|
72
|
+
sharer = get_sharer("gist")
|
|
73
|
+
|
|
74
|
+
# GitHub Gist with explicit token
|
|
75
|
+
sharer = get_sharer("gist", token="ghp_...")
|
|
76
|
+
|
|
77
|
+
# Pastebin (reads PASTEBIN_API_KEY from env)
|
|
78
|
+
sharer = get_sharer("pastebin")
|
|
79
|
+
|
|
80
|
+
# Pastebin with explicit key
|
|
81
|
+
sharer = get_sharer("pastebin", api_key="...")
|
|
82
|
+
|
|
83
|
+
# paste.rs (no auth needed)
|
|
84
|
+
sharer = get_sharer("paste_rs")
|
|
85
|
+
|
|
86
|
+
# OpenCode (no auth needed)
|
|
87
|
+
sharer = get_sharer("opencode")
|
|
88
|
+
|
|
89
|
+
# OpenCode with custom API URL
|
|
90
|
+
sharer = get_sharer("opencode", api_url="https://api.dev.opencode.ai")
|
|
91
|
+
```
|
|
92
|
+
"""
|
|
93
|
+
match provider:
|
|
94
|
+
case "gist":
|
|
95
|
+
return GistSharer(**kwargs)
|
|
96
|
+
case "pastebin":
|
|
97
|
+
return PastebinSharer(**kwargs)
|
|
98
|
+
case "paste_rs":
|
|
99
|
+
return PasteRsSharer()
|
|
100
|
+
case "opencode":
|
|
101
|
+
return OpenCodeSharer(**kwargs) # type: ignore[arg-type]
|
|
102
|
+
case "shittycodingagent":
|
|
103
|
+
return ShittyCodingAgentSharer(**kwargs)
|
|
104
|
+
case _ as unreachable:
|
|
105
|
+
assert_never(unreachable)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
__all__ = [
|
|
109
|
+
"GistSharer",
|
|
110
|
+
"OpenCodeSharer",
|
|
111
|
+
"PasteRsSharer",
|
|
112
|
+
"PastebinSharer",
|
|
113
|
+
"ShareResult",
|
|
114
|
+
"ShittyCodingAgentSharer",
|
|
115
|
+
"TextSharer",
|
|
116
|
+
"TextSharerStr",
|
|
117
|
+
"Visibility",
|
|
118
|
+
"get_sharer",
|
|
119
|
+
]
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Base class for text sharing services."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import abc
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import TYPE_CHECKING, Literal
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from agentpool.messaging.message_history import MessageHistory
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
Visibility = Literal["public", "unlisted", "private"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class ShareResult:
|
|
19
|
+
"""Result of a text sharing operation."""
|
|
20
|
+
|
|
21
|
+
url: str
|
|
22
|
+
"""URL to access the shared content."""
|
|
23
|
+
|
|
24
|
+
raw_url: str | None = None
|
|
25
|
+
"""Direct URL to raw content, if available."""
|
|
26
|
+
|
|
27
|
+
delete_url: str | None = None
|
|
28
|
+
"""URL to delete the content, if available."""
|
|
29
|
+
|
|
30
|
+
id: str | None = None
|
|
31
|
+
"""Provider-specific ID of the shared content."""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TextSharer(abc.ABC):
|
|
35
|
+
"""Base class for text sharing services."""
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
@abc.abstractmethod
|
|
39
|
+
def name(self) -> str:
|
|
40
|
+
"""Name of the sharing service."""
|
|
41
|
+
|
|
42
|
+
@abc.abstractmethod
|
|
43
|
+
async def share(
|
|
44
|
+
self,
|
|
45
|
+
content: str,
|
|
46
|
+
*,
|
|
47
|
+
title: str | None = None,
|
|
48
|
+
syntax: str | None = None,
|
|
49
|
+
visibility: Visibility = "unlisted",
|
|
50
|
+
expires_in: int | None = None,
|
|
51
|
+
) -> ShareResult:
|
|
52
|
+
"""Share text content.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
content: The text content to share
|
|
56
|
+
title: Optional title/filename for the content
|
|
57
|
+
syntax: Syntax highlighting hint (e.g. "python", "markdown")
|
|
58
|
+
visibility: Visibility level (not all providers support all levels)
|
|
59
|
+
expires_in: Expiration time in seconds (not supported by all providers)
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
ShareResult with URL and metadata
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
async def share_conversation(
|
|
66
|
+
self,
|
|
67
|
+
conversation: MessageHistory,
|
|
68
|
+
*,
|
|
69
|
+
title: str | None = None,
|
|
70
|
+
visibility: Visibility = "unlisted",
|
|
71
|
+
expires_in: int | None = None,
|
|
72
|
+
num_messages: int | None = None,
|
|
73
|
+
) -> ShareResult:
|
|
74
|
+
"""Share a conversation in structured format.
|
|
75
|
+
|
|
76
|
+
Default implementation formats conversation as text and calls share().
|
|
77
|
+
Providers that support structured conversations (e.g., OpenCode)
|
|
78
|
+
should override this method.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
conversation: MessageHistory object to share
|
|
82
|
+
title: Optional title for the conversation
|
|
83
|
+
visibility: Visibility level
|
|
84
|
+
expires_in: Expiration time in seconds
|
|
85
|
+
num_messages: Number of messages to include (None = all)
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
ShareResult with URL and metadata
|
|
89
|
+
"""
|
|
90
|
+
# Default: format as plain text
|
|
91
|
+
content = await conversation.format_history(
|
|
92
|
+
num_messages=num_messages,
|
|
93
|
+
)
|
|
94
|
+
return await self.share(
|
|
95
|
+
content,
|
|
96
|
+
title=title,
|
|
97
|
+
syntax="markdown",
|
|
98
|
+
visibility=visibility,
|
|
99
|
+
expires_in=expires_in,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
def share_sync(
|
|
103
|
+
self,
|
|
104
|
+
content: str,
|
|
105
|
+
*,
|
|
106
|
+
title: str | None = None,
|
|
107
|
+
syntax: str | None = None,
|
|
108
|
+
visibility: Visibility = "unlisted",
|
|
109
|
+
expires_in: int | None = None,
|
|
110
|
+
) -> ShareResult:
|
|
111
|
+
"""Synchronous version of share."""
|
|
112
|
+
from anyenv import run_sync
|
|
113
|
+
|
|
114
|
+
async def wrapper() -> ShareResult:
|
|
115
|
+
return await self.share(
|
|
116
|
+
content,
|
|
117
|
+
title=title,
|
|
118
|
+
syntax=syntax,
|
|
119
|
+
visibility=visibility,
|
|
120
|
+
expires_in=expires_in,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
return run_sync(wrapper())
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""GitHub Gist text sharing provider."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import TYPE_CHECKING, Any
|
|
7
|
+
|
|
8
|
+
from agentpool_commands.text_sharing.base import ShareResult, TextSharer
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from agentpool_commands.text_sharing.base import Visibility
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class GistSharer(TextSharer):
|
|
16
|
+
"""Share text via GitHub Gists."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, token: str | None = None) -> None:
|
|
19
|
+
"""Initialize the GitHub Gist sharer.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
token: GitHub personal access token. If not provided,
|
|
23
|
+
reads from GITHUB_TOKEN environment variable.
|
|
24
|
+
"""
|
|
25
|
+
self._token = token or os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
|
|
26
|
+
if not self._token:
|
|
27
|
+
msg = "GitHub token required. Set GITHUB_TOKEN/GH_TOKEN or pass token parameter."
|
|
28
|
+
raise ValueError(msg)
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def name(self) -> str:
|
|
32
|
+
"""Name of the sharing service."""
|
|
33
|
+
return "GitHub Gist"
|
|
34
|
+
|
|
35
|
+
async def share(
|
|
36
|
+
self,
|
|
37
|
+
content: str,
|
|
38
|
+
*,
|
|
39
|
+
title: str | None = None,
|
|
40
|
+
syntax: str | None = None,
|
|
41
|
+
visibility: Visibility = "unlisted",
|
|
42
|
+
expires_in: int | None = None,
|
|
43
|
+
) -> ShareResult:
|
|
44
|
+
"""Share content as a GitHub Gist.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
content: The text content to share
|
|
48
|
+
title: Filename for the gist (defaults to "shared.txt" or "shared.{syntax}")
|
|
49
|
+
syntax: File extension hint (e.g. "py", "md")
|
|
50
|
+
visibility: "public" or "unlisted" (private not supported)
|
|
51
|
+
expires_in: Ignored (gists don't expire)
|
|
52
|
+
"""
|
|
53
|
+
import anyenv
|
|
54
|
+
|
|
55
|
+
filename = title or f"shared.{syntax or 'txt'}"
|
|
56
|
+
public = visibility == "public"
|
|
57
|
+
payload: dict[str, Any] = {"files": {filename: {"content": content}}, "public": public}
|
|
58
|
+
headers = {
|
|
59
|
+
"Authorization": f"Bearer {self._token}",
|
|
60
|
+
"Accept": "application/vnd.github+json",
|
|
61
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
62
|
+
}
|
|
63
|
+
url = "https://api.github.com/gists"
|
|
64
|
+
response: dict[str, Any] = await anyenv.post_json(url, payload, headers=headers)
|
|
65
|
+
raw_url = response["files"][filename]["raw_url"]
|
|
66
|
+
return ShareResult(url=response["html_url"], raw_url=raw_url, id=response["id"])
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
if __name__ == "__main__":
|
|
70
|
+
import asyncio
|
|
71
|
+
|
|
72
|
+
async def main() -> None:
|
|
73
|
+
"""Example usage of the GistSharer class."""
|
|
74
|
+
sharer = GistSharer()
|
|
75
|
+
result = await sharer.share("# Test Gist!", title="test.md", syntax="md")
|
|
76
|
+
print(f"URL: {result.url}")
|
|
77
|
+
print(f"Raw: {result.raw_url}")
|
|
78
|
+
print(f"ID: {result.id}")
|
|
79
|
+
|
|
80
|
+
asyncio.run(main())
|