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
agentpool_cli/ui.py
ADDED
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
"""UI commands for launching interactive interfaces."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import signal
|
|
6
|
+
import socket
|
|
7
|
+
import subprocess
|
|
8
|
+
import time
|
|
9
|
+
from typing import Annotated
|
|
10
|
+
|
|
11
|
+
import typer as t
|
|
12
|
+
|
|
13
|
+
from agentpool_cli import log
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
logger = log.get_logger(__name__)
|
|
17
|
+
|
|
18
|
+
# Create UI subcommand group
|
|
19
|
+
ui_app = t.Typer(help="Launch interactive user interfaces")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@ui_app.command("opencode")
|
|
23
|
+
def opencode_ui_command( # noqa: PLR0915
|
|
24
|
+
config: Annotated[
|
|
25
|
+
str | None,
|
|
26
|
+
t.Argument(help="Path to agent configuration (optional, not used with --attach)"),
|
|
27
|
+
] = None,
|
|
28
|
+
host: Annotated[
|
|
29
|
+
str,
|
|
30
|
+
t.Option("--host", "-h", help="Host to bind/connect to"),
|
|
31
|
+
] = "127.0.0.1",
|
|
32
|
+
port: Annotated[
|
|
33
|
+
int,
|
|
34
|
+
t.Option("--port", "-p", help="Port for server to listen on / connect to"),
|
|
35
|
+
] = 4096,
|
|
36
|
+
agent: Annotated[
|
|
37
|
+
str | None,
|
|
38
|
+
t.Option(
|
|
39
|
+
"--agent",
|
|
40
|
+
help="Name of specific agent to use (not used with --attach)",
|
|
41
|
+
),
|
|
42
|
+
] = None,
|
|
43
|
+
attach: Annotated[
|
|
44
|
+
bool,
|
|
45
|
+
t.Option("--attach", help="Only attach TUI to existing server (don't start server)"),
|
|
46
|
+
] = False,
|
|
47
|
+
) -> None:
|
|
48
|
+
"""Launch OpenCode TUI with integrated server or attach to existing one.
|
|
49
|
+
|
|
50
|
+
By default, starts an OpenCode-compatible server in the background and
|
|
51
|
+
automatically attaches the OpenCode TUI to it. When you exit the TUI,
|
|
52
|
+
the server is automatically shut down.
|
|
53
|
+
|
|
54
|
+
With --attach, only launches the TUI and connects to an existing server
|
|
55
|
+
(useful when running the server separately or connecting from multiple clients).
|
|
56
|
+
|
|
57
|
+
Examples:
|
|
58
|
+
# Start server + TUI
|
|
59
|
+
agentpool ui opencode
|
|
60
|
+
|
|
61
|
+
# Use specific config and agent
|
|
62
|
+
agentpool ui opencode agents.yml --agent myagent
|
|
63
|
+
|
|
64
|
+
# Custom port
|
|
65
|
+
agentpool ui opencode --port 8080
|
|
66
|
+
|
|
67
|
+
# Attach to existing server (no server startup)
|
|
68
|
+
agentpool ui opencode --attach
|
|
69
|
+
agentpool ui opencode --attach --port 8080
|
|
70
|
+
"""
|
|
71
|
+
url = f"http://{host}:{port}"
|
|
72
|
+
|
|
73
|
+
# Attach-only mode: just launch TUI
|
|
74
|
+
if attach:
|
|
75
|
+
logger.info("Attaching to existing OpenCode server", url=url)
|
|
76
|
+
|
|
77
|
+
# Clear screen for clean TUI
|
|
78
|
+
import os
|
|
79
|
+
|
|
80
|
+
os.system("clear" if os.name != "nt" else "cls")
|
|
81
|
+
|
|
82
|
+
result = subprocess.run(["opencode", "attach", url], check=False)
|
|
83
|
+
if result.returncode not in {0, 130}: # 130 = Ctrl+C
|
|
84
|
+
logger.warning("OpenCode TUI exited with non-zero status", code=result.returncode)
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
# Build server command
|
|
88
|
+
server_cmd = [
|
|
89
|
+
"agentpool",
|
|
90
|
+
"serve-opencode",
|
|
91
|
+
"--host",
|
|
92
|
+
host,
|
|
93
|
+
"--port",
|
|
94
|
+
str(port),
|
|
95
|
+
]
|
|
96
|
+
if config:
|
|
97
|
+
server_cmd.append(config)
|
|
98
|
+
if agent:
|
|
99
|
+
server_cmd.extend(["--agent", agent])
|
|
100
|
+
|
|
101
|
+
logger.info("Starting OpenCode server", url=url)
|
|
102
|
+
|
|
103
|
+
# Start server in background with suppressed output
|
|
104
|
+
server = subprocess.Popen(
|
|
105
|
+
server_cmd,
|
|
106
|
+
stdout=subprocess.DEVNULL,
|
|
107
|
+
stderr=subprocess.DEVNULL,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
# Wait for server to be ready with retry
|
|
112
|
+
max_retries = 30
|
|
113
|
+
for i in range(max_retries):
|
|
114
|
+
try:
|
|
115
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
116
|
+
sock.settimeout(0.5)
|
|
117
|
+
sock.connect((host, port))
|
|
118
|
+
sock.close()
|
|
119
|
+
logger.info("Server is ready", url=url)
|
|
120
|
+
break
|
|
121
|
+
except (TimeoutError, ConnectionRefusedError, OSError):
|
|
122
|
+
if i == max_retries - 1:
|
|
123
|
+
msg = f"Server failed to start after {max_retries} attempts"
|
|
124
|
+
raise RuntimeError(msg) # noqa: B904
|
|
125
|
+
time.sleep(0.5)
|
|
126
|
+
|
|
127
|
+
# Give HTTP layer a moment to be fully ready
|
|
128
|
+
time.sleep(0.5)
|
|
129
|
+
|
|
130
|
+
# Clear screen before launching TUI
|
|
131
|
+
import os
|
|
132
|
+
|
|
133
|
+
os.system("clear" if os.name != "nt" else "cls")
|
|
134
|
+
|
|
135
|
+
# Attach TUI
|
|
136
|
+
result = subprocess.run(["opencode", "attach", url], check=False)
|
|
137
|
+
if result.returncode != 0:
|
|
138
|
+
logger.warning("OpenCode TUI exited with non-zero status", code=result.returncode)
|
|
139
|
+
|
|
140
|
+
except KeyboardInterrupt:
|
|
141
|
+
logger.info("UI interrupted by user")
|
|
142
|
+
except Exception as e:
|
|
143
|
+
logger.exception("Error running OpenCode UI")
|
|
144
|
+
raise t.Exit(1) from e
|
|
145
|
+
finally:
|
|
146
|
+
# Clean up server
|
|
147
|
+
logger.info("Shutting down server")
|
|
148
|
+
server.send_signal(signal.SIGTERM)
|
|
149
|
+
try:
|
|
150
|
+
server.wait(timeout=5)
|
|
151
|
+
except subprocess.TimeoutExpired:
|
|
152
|
+
logger.warning("Server did not shut down gracefully, killing")
|
|
153
|
+
server.kill()
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@ui_app.command("toad")
|
|
157
|
+
def toad_ui_command(
|
|
158
|
+
config: Annotated[
|
|
159
|
+
str | None,
|
|
160
|
+
t.Argument(help="Path to agent configuration (optional)"),
|
|
161
|
+
] = None,
|
|
162
|
+
websocket: Annotated[
|
|
163
|
+
bool,
|
|
164
|
+
t.Option("--websocket", "-w", help="Use WebSocket transport (otherwise stdio)"),
|
|
165
|
+
] = False,
|
|
166
|
+
port: Annotated[
|
|
167
|
+
int,
|
|
168
|
+
t.Option("--port", "-p", help="Port for WebSocket server (only with --websocket)"),
|
|
169
|
+
] = 8765,
|
|
170
|
+
) -> None:
|
|
171
|
+
"""Launch Toad TUI for ACP agents.
|
|
172
|
+
|
|
173
|
+
By default uses stdio transport where Toad spawns the agentpool server.
|
|
174
|
+
With --websocket, starts a WebSocket ACP server in the background first.
|
|
175
|
+
|
|
176
|
+
Examples:
|
|
177
|
+
# Direct stdio (Toad spawns server)
|
|
178
|
+
agentpool ui toad
|
|
179
|
+
|
|
180
|
+
# Use specific config
|
|
181
|
+
agentpool ui toad agents.yml
|
|
182
|
+
|
|
183
|
+
# WebSocket transport
|
|
184
|
+
agentpool ui toad --websocket
|
|
185
|
+
|
|
186
|
+
# WebSocket with custom port
|
|
187
|
+
agentpool ui toad --websocket --port 9000
|
|
188
|
+
"""
|
|
189
|
+
if websocket:
|
|
190
|
+
_run_toad_websocket(config, port)
|
|
191
|
+
else:
|
|
192
|
+
_run_toad_stdio(config)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _run_toad_stdio(config: str | None) -> None:
|
|
196
|
+
"""Run Toad with stdio transport (Toad spawns server)."""
|
|
197
|
+
# Build agentpool command that Toad will spawn
|
|
198
|
+
agentpool_cmd = "agentpool serve-acp"
|
|
199
|
+
if config:
|
|
200
|
+
agentpool_cmd += f" {config}"
|
|
201
|
+
|
|
202
|
+
# Clear screen for clean TUI
|
|
203
|
+
import os
|
|
204
|
+
|
|
205
|
+
os.system("clear" if os.name != "nt" else "cls")
|
|
206
|
+
|
|
207
|
+
# Run toad with agentpool as subprocess
|
|
208
|
+
result = subprocess.run(
|
|
209
|
+
["uvx", "--from", "batrachian-toad@latest", "toad", "acp", agentpool_cmd],
|
|
210
|
+
check=False,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
if result.returncode not in {0, 130}: # 130 = Ctrl+C
|
|
214
|
+
logger.warning("Toad TUI exited with non-zero status", code=result.returncode)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _run_toad_websocket(config: str | None, port: int) -> None:
|
|
218
|
+
"""Run Toad with WebSocket transport."""
|
|
219
|
+
url = f"ws://localhost:{port}"
|
|
220
|
+
|
|
221
|
+
# Build server command
|
|
222
|
+
server_cmd = [
|
|
223
|
+
"agentpool",
|
|
224
|
+
"serve-acp",
|
|
225
|
+
"--transport",
|
|
226
|
+
"websocket",
|
|
227
|
+
"--ws-port",
|
|
228
|
+
str(port),
|
|
229
|
+
]
|
|
230
|
+
if config:
|
|
231
|
+
server_cmd.append(config)
|
|
232
|
+
|
|
233
|
+
logger.info("Starting ACP WebSocket server", url=url)
|
|
234
|
+
|
|
235
|
+
# Start server in background
|
|
236
|
+
server = subprocess.Popen(
|
|
237
|
+
server_cmd,
|
|
238
|
+
stdout=subprocess.DEVNULL,
|
|
239
|
+
stderr=subprocess.DEVNULL,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
try:
|
|
243
|
+
# Wait for server startup
|
|
244
|
+
time.sleep(1.5)
|
|
245
|
+
|
|
246
|
+
# Clear screen for clean TUI
|
|
247
|
+
import os
|
|
248
|
+
|
|
249
|
+
os.system("clear" if os.name != "nt" else "cls")
|
|
250
|
+
|
|
251
|
+
# Run toad with mcp-ws client
|
|
252
|
+
result = subprocess.run(
|
|
253
|
+
["uvx", "--from", "batrachian-toad@latest", "toad", "acp", f"uvx mcp-ws {url}"],
|
|
254
|
+
check=False,
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
if result.returncode not in {0, 130}: # 130 = Ctrl+C
|
|
258
|
+
logger.warning("Toad TUI exited with non-zero status", code=result.returncode)
|
|
259
|
+
|
|
260
|
+
except KeyboardInterrupt:
|
|
261
|
+
logger.info("UI interrupted by user")
|
|
262
|
+
except Exception as e:
|
|
263
|
+
logger.exception("Error running Toad UI")
|
|
264
|
+
raise t.Exit(1) from e
|
|
265
|
+
finally:
|
|
266
|
+
# Clean up server
|
|
267
|
+
logger.info("Shutting down server")
|
|
268
|
+
server.send_signal(signal.SIGTERM)
|
|
269
|
+
try:
|
|
270
|
+
server.wait(timeout=5)
|
|
271
|
+
except subprocess.TimeoutExpired:
|
|
272
|
+
logger.warning("Server did not shut down gracefully, killing")
|
|
273
|
+
server.kill()
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
@ui_app.command("desktop")
|
|
277
|
+
def opencode_desktop_command( # noqa: PLR0915
|
|
278
|
+
config: Annotated[
|
|
279
|
+
str | None,
|
|
280
|
+
t.Argument(help="Path to agent configuration (optional, not used with --attach)"),
|
|
281
|
+
] = None,
|
|
282
|
+
host: Annotated[
|
|
283
|
+
str,
|
|
284
|
+
t.Option("--host", "-h", help="Host to bind/connect to"),
|
|
285
|
+
] = "127.0.0.1",
|
|
286
|
+
port: Annotated[
|
|
287
|
+
int,
|
|
288
|
+
t.Option("--port", "-p", help="Port for server to listen on / connect to"),
|
|
289
|
+
] = 4096,
|
|
290
|
+
agent: Annotated[
|
|
291
|
+
str | None,
|
|
292
|
+
t.Option(
|
|
293
|
+
"--agent",
|
|
294
|
+
help="Name of specific agent to use (not used with --attach)",
|
|
295
|
+
),
|
|
296
|
+
] = None,
|
|
297
|
+
attach: Annotated[
|
|
298
|
+
bool,
|
|
299
|
+
t.Option("--attach", help="Connect desktop app to existing server (don't start server)"),
|
|
300
|
+
] = False,
|
|
301
|
+
) -> None:
|
|
302
|
+
"""Launch OpenCode desktop app with integrated server or attach to existing one.
|
|
303
|
+
|
|
304
|
+
By default, starts an OpenCode-compatible server in the background and
|
|
305
|
+
configures the desktop app to connect to it. The desktop app will run
|
|
306
|
+
independently and you can close the terminal.
|
|
307
|
+
|
|
308
|
+
With --attach, configures the desktop app to connect to an existing server
|
|
309
|
+
without starting a new one.
|
|
310
|
+
|
|
311
|
+
Note: This command requires the OpenCode desktop app to be installed.
|
|
312
|
+
The app will be configured to use the specified server URL via its config.
|
|
313
|
+
|
|
314
|
+
Examples:
|
|
315
|
+
# Start server, configure desktop app, and launch it
|
|
316
|
+
agentpool ui desktop
|
|
317
|
+
|
|
318
|
+
# Use specific config and agent
|
|
319
|
+
agentpool ui desktop agents.yml --agent myagent
|
|
320
|
+
|
|
321
|
+
# Custom port
|
|
322
|
+
agentpool ui desktop --port 8080
|
|
323
|
+
|
|
324
|
+
# Configure desktop to attach to existing server
|
|
325
|
+
agentpool ui desktop --attach
|
|
326
|
+
agentpool ui desktop --attach --port 8080
|
|
327
|
+
|
|
328
|
+
# After using --attach, reset to default (spawn local server)
|
|
329
|
+
agentpool ui desktop --attach --port 0 # port 0 clears the setting
|
|
330
|
+
"""
|
|
331
|
+
import json
|
|
332
|
+
from pathlib import Path
|
|
333
|
+
|
|
334
|
+
url = f"http://{host}:{port}"
|
|
335
|
+
|
|
336
|
+
# Determine config path based on platform
|
|
337
|
+
config_dir = Path.home() / ".config" / "opencode"
|
|
338
|
+
config_file = config_dir / "config.json"
|
|
339
|
+
|
|
340
|
+
# Handle attach mode - configure desktop app to use external server
|
|
341
|
+
if attach:
|
|
342
|
+
if port == 0:
|
|
343
|
+
# Special case: port 0 means clear the setting
|
|
344
|
+
logger.info("Clearing desktop app server configuration")
|
|
345
|
+
if config_file.exists():
|
|
346
|
+
try:
|
|
347
|
+
with config_file.open() as f:
|
|
348
|
+
existing_config = json.load(f)
|
|
349
|
+
# Remove server config
|
|
350
|
+
if "server" in existing_config:
|
|
351
|
+
del existing_config["server"]
|
|
352
|
+
with config_file.open("w") as f:
|
|
353
|
+
json.dump(existing_config, f, indent=2)
|
|
354
|
+
logger.info("Cleared server configuration from config file")
|
|
355
|
+
except Exception as e: # noqa: BLE001
|
|
356
|
+
logger.warning("Failed to clear config", error=str(e))
|
|
357
|
+
else:
|
|
358
|
+
# Configure desktop app to use specified server
|
|
359
|
+
logger.info("Configuring desktop app to attach to server", url=url)
|
|
360
|
+
|
|
361
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
|
362
|
+
|
|
363
|
+
# Read existing config or create new
|
|
364
|
+
existing_config = {}
|
|
365
|
+
if config_file.exists():
|
|
366
|
+
try:
|
|
367
|
+
with config_file.open() as f:
|
|
368
|
+
existing_config = json.load(f)
|
|
369
|
+
except Exception as e: # noqa: BLE001
|
|
370
|
+
logger.warning("Failed to read existing config", error=str(e))
|
|
371
|
+
|
|
372
|
+
# Update server configuration
|
|
373
|
+
existing_config["server"] = {
|
|
374
|
+
"hostname": host,
|
|
375
|
+
"port": port,
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
try:
|
|
379
|
+
with config_file.open("w") as f:
|
|
380
|
+
json.dump(existing_config, f, indent=2)
|
|
381
|
+
logger.info("Updated desktop app configuration", config=str(config_file))
|
|
382
|
+
except Exception as e:
|
|
383
|
+
logger.exception("Failed to write config", error=str(e))
|
|
384
|
+
raise t.Exit(1) from e
|
|
385
|
+
|
|
386
|
+
# Launch desktop app
|
|
387
|
+
logger.info("Launching OpenCode desktop app")
|
|
388
|
+
try:
|
|
389
|
+
# Try common desktop app launch commands
|
|
390
|
+
# On macOS: open -a OpenCode
|
|
391
|
+
# On Linux: OpenCode (capital O) is the desktop app
|
|
392
|
+
# On Windows: start opencode
|
|
393
|
+
import platform
|
|
394
|
+
|
|
395
|
+
system = platform.system()
|
|
396
|
+
if system == "Darwin":
|
|
397
|
+
subprocess.Popen(["open", "-a", "OpenCode"])
|
|
398
|
+
elif system == "Windows":
|
|
399
|
+
subprocess.Popen(["start", "opencode"], shell=True)
|
|
400
|
+
else: # Linux and others
|
|
401
|
+
# Try different possible command names - OpenCode (capital O) is the desktop app
|
|
402
|
+
for cmd in ["OpenCode", "opencode-desktop"]:
|
|
403
|
+
try:
|
|
404
|
+
subprocess.Popen([cmd])
|
|
405
|
+
break
|
|
406
|
+
except FileNotFoundError:
|
|
407
|
+
continue
|
|
408
|
+
else:
|
|
409
|
+
msg = (
|
|
410
|
+
"Could not find OpenCode desktop app. Please install it or launch manually."
|
|
411
|
+
)
|
|
412
|
+
raise FileNotFoundError(msg) # noqa: TRY301
|
|
413
|
+
|
|
414
|
+
if port != 0:
|
|
415
|
+
logger.info(
|
|
416
|
+
"Desktop app launched and configured to use server",
|
|
417
|
+
url=url,
|
|
418
|
+
note="The app will connect to the server. You can close this terminal.",
|
|
419
|
+
)
|
|
420
|
+
else:
|
|
421
|
+
logger.info(
|
|
422
|
+
"Desktop app launched with default configuration",
|
|
423
|
+
note="The app will spawn its own local server. You can close this terminal.",
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
except Exception as e:
|
|
427
|
+
logger.exception("Failed to launch desktop app", error=str(e))
|
|
428
|
+
logger.info(
|
|
429
|
+
"Configuration has been updated. Please launch the OpenCode desktop app manually.",
|
|
430
|
+
config=str(config_file),
|
|
431
|
+
)
|
|
432
|
+
raise t.Exit(1) from e
|
|
433
|
+
|
|
434
|
+
return
|
|
435
|
+
|
|
436
|
+
# Default mode: Start server + launch desktop app
|
|
437
|
+
# Build server command
|
|
438
|
+
server_cmd = [
|
|
439
|
+
"agentpool",
|
|
440
|
+
"serve-opencode",
|
|
441
|
+
"--host",
|
|
442
|
+
host,
|
|
443
|
+
"--port",
|
|
444
|
+
str(port),
|
|
445
|
+
]
|
|
446
|
+
if config:
|
|
447
|
+
server_cmd.append(config)
|
|
448
|
+
if agent:
|
|
449
|
+
server_cmd.extend(["--agent", agent])
|
|
450
|
+
|
|
451
|
+
logger.info("Starting OpenCode server for desktop app", url=url)
|
|
452
|
+
|
|
453
|
+
# Start server in background
|
|
454
|
+
server = subprocess.Popen(
|
|
455
|
+
server_cmd,
|
|
456
|
+
stdout=subprocess.DEVNULL,
|
|
457
|
+
stderr=subprocess.DEVNULL,
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
try:
|
|
461
|
+
# Wait for server to be ready
|
|
462
|
+
max_retries = 30
|
|
463
|
+
for i in range(max_retries):
|
|
464
|
+
try:
|
|
465
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
466
|
+
sock.settimeout(0.5)
|
|
467
|
+
sock.connect((host, port))
|
|
468
|
+
sock.close()
|
|
469
|
+
logger.info("Server is ready", url=url)
|
|
470
|
+
break
|
|
471
|
+
except (TimeoutError, ConnectionRefusedError, OSError):
|
|
472
|
+
if i == max_retries - 1:
|
|
473
|
+
msg = f"Server failed to start after {max_retries} attempts"
|
|
474
|
+
raise RuntimeError(msg) # noqa: B904
|
|
475
|
+
time.sleep(0.5)
|
|
476
|
+
|
|
477
|
+
# Give HTTP layer a moment to be fully ready
|
|
478
|
+
time.sleep(0.5)
|
|
479
|
+
|
|
480
|
+
# Configure desktop app to use this server
|
|
481
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
|
482
|
+
|
|
483
|
+
existing_config = {}
|
|
484
|
+
if config_file.exists():
|
|
485
|
+
try:
|
|
486
|
+
with config_file.open() as f:
|
|
487
|
+
existing_config = json.load(f)
|
|
488
|
+
except Exception as e: # noqa: BLE001
|
|
489
|
+
logger.warning("Failed to read existing config", error=str(e))
|
|
490
|
+
|
|
491
|
+
existing_config["server"] = {
|
|
492
|
+
"hostname": host,
|
|
493
|
+
"port": port,
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
try:
|
|
497
|
+
with config_file.open("w") as f:
|
|
498
|
+
json.dump(existing_config, f, indent=2)
|
|
499
|
+
logger.info("Configured desktop app", config=str(config_file))
|
|
500
|
+
except Exception as e: # noqa: BLE001
|
|
501
|
+
logger.warning("Failed to write config", error=str(e))
|
|
502
|
+
|
|
503
|
+
# Launch desktop app
|
|
504
|
+
logger.info("Launching OpenCode desktop app")
|
|
505
|
+
import platform
|
|
506
|
+
|
|
507
|
+
system = platform.system()
|
|
508
|
+
try:
|
|
509
|
+
if system == "Darwin":
|
|
510
|
+
subprocess.Popen(["open", "-a", "OpenCode"])
|
|
511
|
+
elif system == "Windows":
|
|
512
|
+
subprocess.Popen(["start", "opencode"], shell=True)
|
|
513
|
+
else: # Linux
|
|
514
|
+
# OpenCode (capital O) is the desktop app
|
|
515
|
+
for cmd in ["OpenCode", "opencode-desktop"]:
|
|
516
|
+
try:
|
|
517
|
+
subprocess.Popen([cmd])
|
|
518
|
+
break
|
|
519
|
+
except FileNotFoundError:
|
|
520
|
+
continue
|
|
521
|
+
else:
|
|
522
|
+
msg = "Could not find OpenCode desktop app"
|
|
523
|
+
raise FileNotFoundError(msg) # noqa: TRY301
|
|
524
|
+
|
|
525
|
+
logger.info(
|
|
526
|
+
"Desktop app launched",
|
|
527
|
+
note="Server is running in background. Press Ctrl+C to stop the server.",
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
# Keep server running until interrupted
|
|
531
|
+
logger.info("Server running. Press Ctrl+C to stop.")
|
|
532
|
+
server.wait()
|
|
533
|
+
|
|
534
|
+
except FileNotFoundError as e:
|
|
535
|
+
logger.exception(
|
|
536
|
+
"Desktop app not found. Please install OpenCode desktop app or launch it manually.",
|
|
537
|
+
config=str(config_file),
|
|
538
|
+
)
|
|
539
|
+
raise t.Exit(1) from e
|
|
540
|
+
|
|
541
|
+
except KeyboardInterrupt:
|
|
542
|
+
logger.info("Shutting down server")
|
|
543
|
+
except Exception as e:
|
|
544
|
+
logger.exception("Error running desktop app")
|
|
545
|
+
raise t.Exit(1) from e
|
|
546
|
+
finally:
|
|
547
|
+
# Clean up server
|
|
548
|
+
server.send_signal(signal.SIGTERM)
|
|
549
|
+
try:
|
|
550
|
+
server.wait(timeout=5)
|
|
551
|
+
except subprocess.TimeoutExpired:
|
|
552
|
+
logger.warning("Server did not shut down gracefully, killing")
|
|
553
|
+
server.kill()
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
if __name__ == "__main__":
|
|
557
|
+
t.run(ui_app)
|
agentpool_commands/__init__.py
CHANGED
|
@@ -39,17 +39,18 @@ from agentpool_commands.tools import (
|
|
|
39
39
|
RegisterToolCommand,
|
|
40
40
|
ShowToolCommand,
|
|
41
41
|
)
|
|
42
|
-
from agentpool_commands.workers import
|
|
43
|
-
AddWorkerCommand,
|
|
44
|
-
RemoveWorkerCommand,
|
|
45
|
-
ListWorkersCommand,
|
|
46
|
-
)
|
|
42
|
+
from agentpool_commands.workers import AddWorkerCommand, RemoveWorkerCommand, ListWorkersCommand
|
|
47
43
|
from agentpool_commands.utils import (
|
|
48
44
|
CopyClipboardCommand,
|
|
49
45
|
EditAgentFileCommand,
|
|
50
46
|
GetLogsCommand,
|
|
51
47
|
ShareHistoryCommand,
|
|
52
48
|
)
|
|
49
|
+
from agentpool_commands.pool import ListPoolsCommand, SpawnCommand
|
|
50
|
+
|
|
51
|
+
# CompactCommand is only for Native Agent (has its own history)
|
|
52
|
+
# Other agents (ClaudeCode, ACP, AGUI) don't control their own history
|
|
53
|
+
from agentpool_commands.pool import CompactCommand # noqa: F401
|
|
53
54
|
from typing import TYPE_CHECKING, Any
|
|
54
55
|
|
|
55
56
|
if TYPE_CHECKING:
|
|
@@ -104,6 +105,8 @@ def get_pool_commands(**kwargs: Any) -> Sequence[BaseCommand | type[SlashedComma
|
|
|
104
105
|
"enable_list_agents": ListAgentsCommand,
|
|
105
106
|
"enable_show_agent": ShowAgentCommand,
|
|
106
107
|
"enable_edit_agent_file": EditAgentFileCommand,
|
|
108
|
+
"enable_list_pools": ListPoolsCommand,
|
|
109
|
+
"enable_spawn": SpawnCommand,
|
|
107
110
|
}
|
|
108
111
|
return [command for flag, command in command_map.items() if kwargs.get(flag, True)]
|
|
109
112
|
|
|
@@ -163,6 +166,8 @@ def get_commands(
|
|
|
163
166
|
enable_list_agents: bool = True,
|
|
164
167
|
enable_show_agent: bool = True,
|
|
165
168
|
enable_edit_agent_file: bool = True,
|
|
169
|
+
enable_list_pools: bool = True,
|
|
170
|
+
enable_spawn: bool = True,
|
|
166
171
|
) -> list[BaseCommand | type[SlashedCommand]]:
|
|
167
172
|
"""Get all built-in commands."""
|
|
168
173
|
agent_kwargs = {
|
|
@@ -202,6 +207,8 @@ def get_commands(
|
|
|
202
207
|
"enable_list_agents": enable_list_agents,
|
|
203
208
|
"enable_show_agent": enable_show_agent,
|
|
204
209
|
"enable_edit_agent_file": enable_edit_agent_file,
|
|
210
|
+
"enable_list_pools": enable_list_pools,
|
|
211
|
+
"enable_spawn": enable_spawn,
|
|
205
212
|
}
|
|
206
213
|
|
|
207
214
|
return [
|
agentpool_commands/agents.py
CHANGED
|
@@ -77,7 +77,7 @@ class CreateAgentCommand(NodeCommand):
|
|
|
77
77
|
# Create and register the new agent
|
|
78
78
|
await ctx.context.pool.add_agent(
|
|
79
79
|
name=agent_name,
|
|
80
|
-
model=model or current_agent.model_name,
|
|
80
|
+
model=model or current_agent.model_name or "openai:gpt-4o-mini",
|
|
81
81
|
system_prompt=system_prompt or (),
|
|
82
82
|
description=description,
|
|
83
83
|
tools=tool_list,
|