agentpool 2.1.9__py3-none-any.whl → 2.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (311) hide show
  1. acp/__init__.py +13 -4
  2. acp/acp_requests.py +20 -77
  3. acp/agent/connection.py +8 -0
  4. acp/agent/implementations/debug_server/debug_server.py +6 -2
  5. acp/agent/protocol.py +6 -0
  6. acp/bridge/README.md +15 -2
  7. acp/bridge/__init__.py +3 -2
  8. acp/bridge/__main__.py +60 -19
  9. acp/bridge/ws_server.py +173 -0
  10. acp/bridge/ws_server_cli.py +89 -0
  11. acp/client/connection.py +38 -29
  12. acp/client/implementations/default_client.py +3 -2
  13. acp/client/implementations/headless_client.py +2 -2
  14. acp/connection.py +2 -2
  15. acp/notifications.py +20 -50
  16. acp/schema/__init__.py +2 -0
  17. acp/schema/agent_responses.py +21 -0
  18. acp/schema/client_requests.py +3 -3
  19. acp/schema/session_state.py +63 -29
  20. acp/stdio.py +39 -9
  21. acp/task/supervisor.py +2 -2
  22. acp/transports.py +362 -2
  23. acp/utils.py +17 -4
  24. agentpool/__init__.py +6 -1
  25. agentpool/agents/__init__.py +2 -0
  26. agentpool/agents/acp_agent/acp_agent.py +407 -277
  27. agentpool/agents/acp_agent/acp_converters.py +196 -38
  28. agentpool/agents/acp_agent/client_handler.py +191 -26
  29. agentpool/agents/acp_agent/session_state.py +17 -6
  30. agentpool/agents/agent.py +607 -572
  31. agentpool/agents/agui_agent/__init__.py +0 -2
  32. agentpool/agents/agui_agent/agui_agent.py +176 -110
  33. agentpool/agents/agui_agent/agui_converters.py +0 -131
  34. agentpool/agents/agui_agent/helpers.py +3 -4
  35. agentpool/agents/base_agent.py +632 -17
  36. agentpool/agents/claude_code_agent/FORKING.md +191 -0
  37. agentpool/agents/claude_code_agent/__init__.py +13 -1
  38. agentpool/agents/claude_code_agent/claude_code_agent.py +1058 -291
  39. agentpool/agents/claude_code_agent/converters.py +74 -143
  40. agentpool/agents/claude_code_agent/history.py +474 -0
  41. agentpool/agents/claude_code_agent/models.py +77 -0
  42. agentpool/agents/claude_code_agent/static_info.py +100 -0
  43. agentpool/agents/claude_code_agent/usage.py +242 -0
  44. agentpool/agents/context.py +40 -0
  45. agentpool/agents/events/__init__.py +24 -0
  46. agentpool/agents/events/builtin_handlers.py +67 -1
  47. agentpool/agents/events/event_emitter.py +32 -2
  48. agentpool/agents/events/events.py +104 -3
  49. agentpool/agents/events/infer_info.py +145 -0
  50. agentpool/agents/events/processors.py +254 -0
  51. agentpool/agents/interactions.py +41 -6
  52. agentpool/agents/modes.py +67 -0
  53. agentpool/agents/slashed_agent.py +5 -4
  54. agentpool/agents/tool_call_accumulator.py +213 -0
  55. agentpool/agents/tool_wrapping.py +18 -6
  56. agentpool/common_types.py +56 -21
  57. agentpool/config_resources/__init__.py +38 -1
  58. agentpool/config_resources/acp_assistant.yml +2 -2
  59. agentpool/config_resources/agents.yml +3 -0
  60. agentpool/config_resources/agents_template.yml +1 -0
  61. agentpool/config_resources/claude_code_agent.yml +10 -6
  62. agentpool/config_resources/external_acp_agents.yml +2 -1
  63. agentpool/delegation/base_team.py +4 -30
  64. agentpool/delegation/pool.py +136 -289
  65. agentpool/delegation/team.py +58 -57
  66. agentpool/delegation/teamrun.py +51 -55
  67. agentpool/diagnostics/__init__.py +53 -0
  68. agentpool/diagnostics/lsp_manager.py +1593 -0
  69. agentpool/diagnostics/lsp_proxy.py +41 -0
  70. agentpool/diagnostics/lsp_proxy_script.py +229 -0
  71. agentpool/diagnostics/models.py +398 -0
  72. agentpool/functional/run.py +10 -4
  73. agentpool/mcp_server/__init__.py +0 -2
  74. agentpool/mcp_server/client.py +76 -32
  75. agentpool/mcp_server/conversions.py +54 -13
  76. agentpool/mcp_server/manager.py +34 -54
  77. agentpool/mcp_server/registries/official_registry_client.py +35 -1
  78. agentpool/mcp_server/tool_bridge.py +186 -139
  79. agentpool/messaging/__init__.py +0 -2
  80. agentpool/messaging/compaction.py +72 -197
  81. agentpool/messaging/connection_manager.py +11 -10
  82. agentpool/messaging/event_manager.py +5 -5
  83. agentpool/messaging/message_container.py +6 -30
  84. agentpool/messaging/message_history.py +99 -8
  85. agentpool/messaging/messagenode.py +52 -14
  86. agentpool/messaging/messages.py +54 -35
  87. agentpool/messaging/processing.py +12 -22
  88. agentpool/models/__init__.py +1 -1
  89. agentpool/models/acp_agents/base.py +6 -24
  90. agentpool/models/acp_agents/mcp_capable.py +126 -157
  91. agentpool/models/acp_agents/non_mcp.py +129 -95
  92. agentpool/models/agents.py +98 -76
  93. agentpool/models/agui_agents.py +1 -1
  94. agentpool/models/claude_code_agents.py +144 -19
  95. agentpool/models/file_parsing.py +0 -1
  96. agentpool/models/manifest.py +113 -50
  97. agentpool/prompts/conversion_manager.py +1 -1
  98. agentpool/prompts/prompts.py +5 -2
  99. agentpool/repomap.py +1 -1
  100. agentpool/resource_providers/__init__.py +11 -1
  101. agentpool/resource_providers/aggregating.py +56 -5
  102. agentpool/resource_providers/base.py +70 -4
  103. agentpool/resource_providers/codemode/code_executor.py +72 -5
  104. agentpool/resource_providers/codemode/helpers.py +2 -2
  105. agentpool/resource_providers/codemode/provider.py +64 -12
  106. agentpool/resource_providers/codemode/remote_mcp_execution.py +2 -2
  107. agentpool/resource_providers/codemode/remote_provider.py +9 -12
  108. agentpool/resource_providers/filtering.py +3 -1
  109. agentpool/resource_providers/mcp_provider.py +89 -12
  110. agentpool/resource_providers/plan_provider.py +228 -46
  111. agentpool/resource_providers/pool.py +7 -3
  112. agentpool/resource_providers/resource_info.py +111 -0
  113. agentpool/resource_providers/static.py +4 -2
  114. agentpool/sessions/__init__.py +4 -1
  115. agentpool/sessions/manager.py +33 -5
  116. agentpool/sessions/models.py +59 -6
  117. agentpool/sessions/protocol.py +28 -0
  118. agentpool/sessions/session.py +11 -55
  119. agentpool/skills/registry.py +13 -8
  120. agentpool/storage/manager.py +572 -49
  121. agentpool/talk/registry.py +4 -4
  122. agentpool/talk/talk.py +9 -10
  123. agentpool/testing.py +538 -20
  124. agentpool/tool_impls/__init__.py +6 -0
  125. agentpool/tool_impls/agent_cli/__init__.py +42 -0
  126. agentpool/tool_impls/agent_cli/tool.py +95 -0
  127. agentpool/tool_impls/bash/__init__.py +64 -0
  128. agentpool/tool_impls/bash/helpers.py +35 -0
  129. agentpool/tool_impls/bash/tool.py +171 -0
  130. agentpool/tool_impls/delete_path/__init__.py +70 -0
  131. agentpool/tool_impls/delete_path/tool.py +142 -0
  132. agentpool/tool_impls/download_file/__init__.py +80 -0
  133. agentpool/tool_impls/download_file/tool.py +183 -0
  134. agentpool/tool_impls/execute_code/__init__.py +55 -0
  135. agentpool/tool_impls/execute_code/tool.py +163 -0
  136. agentpool/tool_impls/grep/__init__.py +80 -0
  137. agentpool/tool_impls/grep/tool.py +200 -0
  138. agentpool/tool_impls/list_directory/__init__.py +73 -0
  139. agentpool/tool_impls/list_directory/tool.py +197 -0
  140. agentpool/tool_impls/question/__init__.py +42 -0
  141. agentpool/tool_impls/question/tool.py +127 -0
  142. agentpool/tool_impls/read/__init__.py +104 -0
  143. agentpool/tool_impls/read/tool.py +305 -0
  144. agentpool/tools/__init__.py +2 -1
  145. agentpool/tools/base.py +114 -34
  146. agentpool/tools/manager.py +57 -1
  147. agentpool/ui/base.py +2 -2
  148. agentpool/ui/mock_provider.py +2 -2
  149. agentpool/ui/stdlib_provider.py +2 -2
  150. agentpool/utils/file_watcher.py +269 -0
  151. agentpool/utils/identifiers.py +121 -0
  152. agentpool/utils/pydantic_ai_helpers.py +46 -0
  153. agentpool/utils/streams.py +616 -2
  154. agentpool/utils/subprocess_utils.py +155 -0
  155. agentpool/utils/token_breakdown.py +461 -0
  156. agentpool/vfs_registry.py +7 -2
  157. {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/METADATA +41 -27
  158. agentpool-2.5.0.dist-info/RECORD +579 -0
  159. {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/WHEEL +1 -1
  160. agentpool_cli/__main__.py +24 -0
  161. agentpool_cli/create.py +1 -1
  162. agentpool_cli/serve_acp.py +100 -21
  163. agentpool_cli/serve_agui.py +87 -0
  164. agentpool_cli/serve_opencode.py +119 -0
  165. agentpool_cli/ui.py +557 -0
  166. agentpool_commands/__init__.py +42 -5
  167. agentpool_commands/agents.py +75 -2
  168. agentpool_commands/history.py +62 -0
  169. agentpool_commands/mcp.py +176 -0
  170. agentpool_commands/models.py +56 -3
  171. agentpool_commands/pool.py +260 -0
  172. agentpool_commands/session.py +1 -1
  173. agentpool_commands/text_sharing/__init__.py +119 -0
  174. agentpool_commands/text_sharing/base.py +123 -0
  175. agentpool_commands/text_sharing/github_gist.py +80 -0
  176. agentpool_commands/text_sharing/opencode.py +462 -0
  177. agentpool_commands/text_sharing/paste_rs.py +59 -0
  178. agentpool_commands/text_sharing/pastebin.py +116 -0
  179. agentpool_commands/text_sharing/shittycodingagent.py +112 -0
  180. agentpool_commands/tools.py +57 -0
  181. agentpool_commands/utils.py +80 -30
  182. agentpool_config/__init__.py +30 -2
  183. agentpool_config/agentpool_tools.py +498 -0
  184. agentpool_config/builtin_tools.py +77 -22
  185. agentpool_config/commands.py +24 -1
  186. agentpool_config/compaction.py +258 -0
  187. agentpool_config/converters.py +1 -1
  188. agentpool_config/event_handlers.py +42 -0
  189. agentpool_config/events.py +1 -1
  190. agentpool_config/forward_targets.py +1 -4
  191. agentpool_config/jinja.py +3 -3
  192. agentpool_config/mcp_server.py +132 -6
  193. agentpool_config/nodes.py +1 -1
  194. agentpool_config/observability.py +44 -0
  195. agentpool_config/session.py +0 -3
  196. agentpool_config/storage.py +82 -38
  197. agentpool_config/task.py +3 -3
  198. agentpool_config/tools.py +11 -22
  199. agentpool_config/toolsets.py +109 -233
  200. agentpool_server/a2a_server/agent_worker.py +307 -0
  201. agentpool_server/a2a_server/server.py +23 -18
  202. agentpool_server/acp_server/acp_agent.py +234 -181
  203. agentpool_server/acp_server/commands/acp_commands.py +151 -156
  204. agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +18 -17
  205. agentpool_server/acp_server/event_converter.py +651 -0
  206. agentpool_server/acp_server/input_provider.py +53 -10
  207. agentpool_server/acp_server/server.py +24 -90
  208. agentpool_server/acp_server/session.py +173 -331
  209. agentpool_server/acp_server/session_manager.py +8 -34
  210. agentpool_server/agui_server/server.py +3 -1
  211. agentpool_server/mcp_server/server.py +5 -2
  212. agentpool_server/opencode_server/.rules +95 -0
  213. agentpool_server/opencode_server/ENDPOINTS.md +401 -0
  214. agentpool_server/opencode_server/OPENCODE_UI_TOOLS_COMPLETE.md +202 -0
  215. agentpool_server/opencode_server/__init__.py +19 -0
  216. agentpool_server/opencode_server/command_validation.py +172 -0
  217. agentpool_server/opencode_server/converters.py +975 -0
  218. agentpool_server/opencode_server/dependencies.py +24 -0
  219. agentpool_server/opencode_server/input_provider.py +421 -0
  220. agentpool_server/opencode_server/models/__init__.py +250 -0
  221. agentpool_server/opencode_server/models/agent.py +53 -0
  222. agentpool_server/opencode_server/models/app.py +72 -0
  223. agentpool_server/opencode_server/models/base.py +26 -0
  224. agentpool_server/opencode_server/models/common.py +23 -0
  225. agentpool_server/opencode_server/models/config.py +37 -0
  226. agentpool_server/opencode_server/models/events.py +821 -0
  227. agentpool_server/opencode_server/models/file.py +88 -0
  228. agentpool_server/opencode_server/models/mcp.py +44 -0
  229. agentpool_server/opencode_server/models/message.py +179 -0
  230. agentpool_server/opencode_server/models/parts.py +323 -0
  231. agentpool_server/opencode_server/models/provider.py +81 -0
  232. agentpool_server/opencode_server/models/pty.py +43 -0
  233. agentpool_server/opencode_server/models/question.py +56 -0
  234. agentpool_server/opencode_server/models/session.py +111 -0
  235. agentpool_server/opencode_server/routes/__init__.py +29 -0
  236. agentpool_server/opencode_server/routes/agent_routes.py +473 -0
  237. agentpool_server/opencode_server/routes/app_routes.py +202 -0
  238. agentpool_server/opencode_server/routes/config_routes.py +302 -0
  239. agentpool_server/opencode_server/routes/file_routes.py +571 -0
  240. agentpool_server/opencode_server/routes/global_routes.py +94 -0
  241. agentpool_server/opencode_server/routes/lsp_routes.py +319 -0
  242. agentpool_server/opencode_server/routes/message_routes.py +761 -0
  243. agentpool_server/opencode_server/routes/permission_routes.py +63 -0
  244. agentpool_server/opencode_server/routes/pty_routes.py +300 -0
  245. agentpool_server/opencode_server/routes/question_routes.py +128 -0
  246. agentpool_server/opencode_server/routes/session_routes.py +1276 -0
  247. agentpool_server/opencode_server/routes/tui_routes.py +139 -0
  248. agentpool_server/opencode_server/server.py +475 -0
  249. agentpool_server/opencode_server/state.py +151 -0
  250. agentpool_server/opencode_server/time_utils.py +8 -0
  251. agentpool_storage/__init__.py +12 -0
  252. agentpool_storage/base.py +184 -2
  253. agentpool_storage/claude_provider/ARCHITECTURE.md +433 -0
  254. agentpool_storage/claude_provider/__init__.py +42 -0
  255. agentpool_storage/claude_provider/provider.py +1089 -0
  256. agentpool_storage/file_provider.py +278 -15
  257. agentpool_storage/memory_provider.py +193 -12
  258. agentpool_storage/models.py +3 -0
  259. agentpool_storage/opencode_provider/ARCHITECTURE.md +386 -0
  260. agentpool_storage/opencode_provider/__init__.py +16 -0
  261. agentpool_storage/opencode_provider/helpers.py +414 -0
  262. agentpool_storage/opencode_provider/provider.py +895 -0
  263. agentpool_storage/project_store.py +325 -0
  264. agentpool_storage/session_store.py +26 -6
  265. agentpool_storage/sql_provider/__init__.py +4 -2
  266. agentpool_storage/sql_provider/models.py +48 -0
  267. agentpool_storage/sql_provider/sql_provider.py +269 -3
  268. agentpool_storage/sql_provider/utils.py +12 -13
  269. agentpool_storage/zed_provider/__init__.py +16 -0
  270. agentpool_storage/zed_provider/helpers.py +281 -0
  271. agentpool_storage/zed_provider/models.py +130 -0
  272. agentpool_storage/zed_provider/provider.py +442 -0
  273. agentpool_storage/zed_provider.py +803 -0
  274. agentpool_toolsets/__init__.py +0 -2
  275. agentpool_toolsets/builtin/__init__.py +2 -12
  276. agentpool_toolsets/builtin/code.py +96 -57
  277. agentpool_toolsets/builtin/debug.py +118 -48
  278. agentpool_toolsets/builtin/execution_environment.py +115 -230
  279. agentpool_toolsets/builtin/file_edit/file_edit.py +115 -7
  280. agentpool_toolsets/builtin/skills.py +9 -4
  281. agentpool_toolsets/builtin/subagent_tools.py +64 -51
  282. agentpool_toolsets/builtin/workers.py +4 -2
  283. agentpool_toolsets/composio_toolset.py +2 -2
  284. agentpool_toolsets/entry_points.py +3 -1
  285. agentpool_toolsets/fsspec_toolset/__init__.py +13 -1
  286. agentpool_toolsets/fsspec_toolset/diagnostics.py +860 -73
  287. agentpool_toolsets/fsspec_toolset/grep.py +99 -7
  288. agentpool_toolsets/fsspec_toolset/helpers.py +3 -2
  289. agentpool_toolsets/fsspec_toolset/image_utils.py +161 -0
  290. agentpool_toolsets/fsspec_toolset/toolset.py +500 -95
  291. agentpool_toolsets/mcp_discovery/__init__.py +5 -0
  292. agentpool_toolsets/mcp_discovery/data/mcp_servers.parquet +0 -0
  293. agentpool_toolsets/mcp_discovery/toolset.py +511 -0
  294. agentpool_toolsets/mcp_run_toolset.py +87 -12
  295. agentpool_toolsets/notifications.py +33 -33
  296. agentpool_toolsets/openapi.py +3 -1
  297. agentpool_toolsets/search_toolset.py +3 -1
  298. agentpool-2.1.9.dist-info/RECORD +0 -474
  299. agentpool_config/resources.py +0 -33
  300. agentpool_server/acp_server/acp_tools.py +0 -43
  301. agentpool_server/acp_server/commands/spawn.py +0 -210
  302. agentpool_storage/text_log_provider.py +0 -275
  303. agentpool_toolsets/builtin/agent_management.py +0 -239
  304. agentpool_toolsets/builtin/chain.py +0 -288
  305. agentpool_toolsets/builtin/history.py +0 -36
  306. agentpool_toolsets/builtin/integration.py +0 -85
  307. agentpool_toolsets/builtin/tool_management.py +0 -90
  308. agentpool_toolsets/builtin/user_interaction.py +0 -52
  309. agentpool_toolsets/semantic_memory_toolset.py +0 -536
  310. {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/entry_points.txt +0 -0
  311. {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/licenses/LICENSE +0 -0
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)
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  from agentpool_commands.base import NodeCommand
6
6
  from agentpool_commands.agents import (
7
7
  CreateAgentCommand,
8
+ CreateTeamCommand,
8
9
  ListAgentsCommand,
9
10
  ShowAgentCommand,
10
11
  # SwitchAgentCommand,
@@ -15,6 +16,12 @@ from agentpool_commands.connections import (
15
16
  ListConnectionsCommand,
16
17
  DisconnectAllCommand,
17
18
  )
19
+ from agentpool_commands.history import SearchHistoryCommand
20
+ from agentpool_commands.mcp import (
21
+ AddMCPServerCommand,
22
+ AddRemoteMCPServerCommand,
23
+ ListMCPServersCommand,
24
+ )
18
25
  from agentpool_commands.models import SetModelCommand
19
26
  from agentpool_commands.prompts import ListPromptsCommand, ShowPromptCommand
20
27
  from agentpool_commands.resources import (
@@ -28,19 +35,22 @@ from agentpool_commands.tools import (
28
35
  DisableToolCommand,
29
36
  EnableToolCommand,
30
37
  ListToolsCommand,
38
+ RegisterCodeToolCommand,
31
39
  RegisterToolCommand,
32
40
  ShowToolCommand,
33
41
  )
34
- from agentpool_commands.workers import (
35
- AddWorkerCommand,
36
- RemoveWorkerCommand,
37
- ListWorkersCommand,
38
- )
42
+ from agentpool_commands.workers import AddWorkerCommand, RemoveWorkerCommand, ListWorkersCommand
39
43
  from agentpool_commands.utils import (
40
44
  CopyClipboardCommand,
41
45
  EditAgentFileCommand,
46
+ GetLogsCommand,
42
47
  ShareHistoryCommand,
43
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
44
54
  from typing import TYPE_CHECKING, Any
45
55
 
46
56
  if TYPE_CHECKING:
@@ -64,6 +74,7 @@ def get_agent_commands(**kwargs: Any) -> Sequence[BaseCommand | type[SlashedComm
64
74
  "enable_enable_tool": EnableToolCommand,
65
75
  "enable_disable_tool": DisableToolCommand,
66
76
  "enable_register_tool": RegisterToolCommand,
77
+ "enable_register_code_tool": RegisterCodeToolCommand,
67
78
  "enable_list_resources": ListResourcesCommand,
68
79
  "enable_show_resource": ShowResourceCommand,
69
80
  "enable_add_resource": AddResourceCommand,
@@ -77,6 +88,11 @@ def get_agent_commands(**kwargs: Any) -> Sequence[BaseCommand | type[SlashedComm
77
88
  "enable_list_connections": ListConnectionsCommand,
78
89
  "enable_disconnect_all": DisconnectAllCommand,
79
90
  "enable_read": ReadCommand,
91
+ "enable_add_mcp_server": AddMCPServerCommand,
92
+ "enable_add_remote_mcp_server": AddRemoteMCPServerCommand,
93
+ "enable_list_mcp_servers": ListMCPServersCommand,
94
+ "enable_search_history": SearchHistoryCommand,
95
+ "enable_get_logs": GetLogsCommand,
80
96
  }
81
97
  return [command for flag, command in command_map.items() if kwargs.get(flag, True)]
82
98
 
@@ -85,9 +101,12 @@ def get_pool_commands(**kwargs: Any) -> Sequence[BaseCommand | type[SlashedComma
85
101
  """Get commands that operate on multiple agents or the pool itself."""
86
102
  command_map = {
87
103
  "enable_create_agent": CreateAgentCommand,
104
+ "enable_create_team": CreateTeamCommand,
88
105
  "enable_list_agents": ListAgentsCommand,
89
106
  "enable_show_agent": ShowAgentCommand,
90
107
  "enable_edit_agent_file": EditAgentFileCommand,
108
+ "enable_list_pools": ListPoolsCommand,
109
+ "enable_spawn": SpawnCommand,
91
110
  }
92
111
  return [command for flag, command in command_map.items() if kwargs.get(flag, True)]
93
112
 
@@ -123,6 +142,7 @@ def get_commands(
123
142
  enable_enable_tool: bool = True,
124
143
  enable_disable_tool: bool = True,
125
144
  enable_register_tool: bool = True,
145
+ enable_register_code_tool: bool = True,
126
146
  enable_list_resources: bool = True,
127
147
  enable_show_resource: bool = True,
128
148
  enable_add_resource: bool = True,
@@ -136,10 +156,18 @@ def get_commands(
136
156
  enable_list_connections: bool = True,
137
157
  enable_disconnect_all: bool = True,
138
158
  enable_read: bool = True,
159
+ enable_add_mcp_server: bool = True,
160
+ enable_add_remote_mcp_server: bool = True,
161
+ enable_list_mcp_servers: bool = True,
162
+ enable_search_history: bool = True,
163
+ enable_get_logs: bool = True,
139
164
  enable_create_agent: bool = True,
165
+ enable_create_team: bool = True,
140
166
  enable_list_agents: bool = True,
141
167
  enable_show_agent: bool = True,
142
168
  enable_edit_agent_file: bool = True,
169
+ enable_list_pools: bool = True,
170
+ enable_spawn: bool = True,
143
171
  ) -> list[BaseCommand | type[SlashedCommand]]:
144
172
  """Get all built-in commands."""
145
173
  agent_kwargs = {
@@ -153,6 +181,7 @@ def get_commands(
153
181
  "enable_enable_tool": enable_enable_tool,
154
182
  "enable_disable_tool": enable_disable_tool,
155
183
  "enable_register_tool": enable_register_tool,
184
+ "enable_register_code_tool": enable_register_code_tool,
156
185
  "enable_list_resources": enable_list_resources,
157
186
  "enable_show_resource": enable_show_resource,
158
187
  "enable_add_resource": enable_add_resource,
@@ -166,12 +195,20 @@ def get_commands(
166
195
  "enable_list_connections": enable_list_connections,
167
196
  "enable_disconnect_all": enable_disconnect_all,
168
197
  "enable_read": enable_read,
198
+ "enable_add_mcp_server": enable_add_mcp_server,
199
+ "enable_add_remote_mcp_server": enable_add_remote_mcp_server,
200
+ "enable_list_mcp_servers": enable_list_mcp_servers,
201
+ "enable_search_history": enable_search_history,
202
+ "enable_get_logs": enable_get_logs,
169
203
  }
170
204
  pool_kwargs = {
171
205
  "enable_create_agent": enable_create_agent,
206
+ "enable_create_team": enable_create_team,
172
207
  "enable_list_agents": enable_list_agents,
173
208
  "enable_show_agent": enable_show_agent,
174
209
  "enable_edit_agent_file": enable_edit_agent_file,
210
+ "enable_list_pools": enable_list_pools,
211
+ "enable_spawn": enable_spawn,
175
212
  }
176
213
 
177
214
  return [