From 942aa663dc57f8badc42b75a3ccf15a7d36b8327 Mon Sep 17 00:00:00 2001 From: "qiang.zeng" Date: Thu, 28 May 2026 16:37:52 +0800 Subject: [PATCH 1/2] feat(acp): register global commands from manifest and improve permission callback formatting --- src/agentpool_server/acp_server/session.py | 69 ++++++++++++++++++++-- 1 file changed, 65 insertions(+), 4 deletions(-) diff --git a/src/agentpool_server/acp_server/session.py b/src/agentpool_server/acp_server/session.py index 433760a41..af9edb1a2 100644 --- a/src/agentpool_server/acp_server/session.py +++ b/src/agentpool_server/acp_server/session.py @@ -27,6 +27,7 @@ from agentpool.agents.acp_agent import ACPAgent from agentpool.agents.modes import ConfigOptionChanged, ModeInfo from agentpool.log import get_logger +from agentpool_config.commands import CommandConfig from agentpool.resource_providers.mcp_provider import MCPResourceProvider from agentpool_commands.base import NodeCommand from agentpool_server.acp_server.converters import ( @@ -227,10 +228,14 @@ def __post_init__(self) -> None: if isinstance(self.agent, Agent): self.agent.sys_prompts.prompts.append(self.get_cwd_context) # pyright: ignore[reportArgumentType] # ty: ignore[invalid-argument-type] if isinstance(self.agent, ACPAgent): - async def permission_callback(params: RequestPermissionRequest) -> RequestPermissionResponse: + + async def permission_callback( + params: RequestPermissionRequest, + ) -> RequestPermissionResponse: forwarded = params.model_copy(update={"session_id": self.session_id}) response = await self.requests.client.request_permission(forwarded) return response + self.agent.acp_permission_callback = permission_callback # Subscribe to state changes for THIS agent only @@ -238,8 +243,10 @@ async def permission_callback(params: RequestPermissionRequest) -> RequestPermis with suppress(Exception): self.agent.state_updated.disconnect(self._on_state_updated) self.agent.state_updated.connect(self._on_state_updated) - # Register skill commands from pool's skill_commands registry + # Register skill commands from pool's SkillCommandRegistry self._register_skill_commands() + # Register global commands from manifest.commands (e.g., static commands like start_eval) + self._register_manifest_commands() self.log.info("Created ACP session", current_agent=self.agent.name) @@ -304,6 +311,56 @@ def _on_skill_command_changed(self, name: str, command: Any | None) -> None: except Exception: self.log.exception("Failed to schedule command update") + def _register_manifest_commands(self) -> None: + """Register global commands from manifest to command_store. + + Loads commands defined in manifest.commands (like static commands) + and registers them as slashed commands in the session's command_store + so they are included in available_commands_update notifications to ACP clients. + """ + from agentpool_config.commands import CommandConfig + + pool = self.agent_pool + if not pool or not pool.manifest.commands: + self.log.debug("No manifest commands to register") + return + + cmd_count = 0 + for cmd_name, cmd_config in pool.manifest.commands.items(): + # Skip string shorthand (should be CommandConfig object) + if isinstance(cmd_config, str): + self.log.debug( + "Skipping shorthand command", + name=cmd_name, + type=type(cmd_config).__name__, + ) + continue + + try: + # Convert CommandConfig to slashed Command + slashed_cmd = cmd_config.get_slashed_command(category="manifest") + # Register in session's command_store + self.command_store.register_command(slashed_cmd) + cmd_count += 1 + self.log.debug( + "Registered manifest command", + name=cmd_name, + type=cmd_config.type, + ) + except Exception: + self.log.exception( + "Failed to register manifest command", + name=cmd_name, + config_type=type(cmd_config).__name__ + if hasattr(cmd_config, "type") + else "unknown", + ) + + if cmd_count > 0: + # Schedule update to notify client of new commands + self._notify_command_update() + self.log.info("Registered manifest commands", count=cmd_count) + async def _on_state_updated( self, state: ModeInfo | ModelInfo | AvailableCommandsUpdate | ConfigOptionChanged ) -> None: @@ -632,7 +689,9 @@ async def close(self) -> None: try: await provider.__aexit__(None, None, None) except Exception: - self.log.exception("Error cleaning up session MCP provider", provider=provider.name) + self.log.exception( + "Error cleaning up session MCP provider", provider=provider.name + ) self.session_mcp_providers.clear() # NEW: Disconnect state_updated signal to prevent stale callbacks @@ -647,7 +706,9 @@ async def close(self) -> None: # Unregister skill command callback to prevent memory leak if hasattr(self, "_skill_command_callback"): skill_registry = getattr(self.agent_pool, "skill_commands", None) - if skill_registry is not None and hasattr(skill_registry, "_command_change_handlers"): + if skill_registry is not None and hasattr( + skill_registry, "_command_change_handlers" + ): try: skill_registry._command_change_handlers.remove(self._skill_command_callback) except ValueError: From 816a7918a0a047d6844ee45fb951d25dd81bb14f Mon Sep 17 00:00:00 2001 From: A-Qiang <285093074@qq.com> Date: Fri, 29 May 2026 11:33:40 +0800 Subject: [PATCH 2/2] fix(acp): use get_command_configs() to properly resolve shorthand commands and use explicit None checks - Use pool.manifest.get_command_configs() instead of direct access to automatically convert string shorthands to StaticCommandConfig objects - Ensure command names are populated from dictionary keys when missing in config - Use explicit 'is None' check instead of implicit boolean checks for commands mapping to prevent logic errors with empty collections --- src/agentpool_server/acp_server/session.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/agentpool_server/acp_server/session.py b/src/agentpool_server/acp_server/session.py index af9edb1a2..a8bbe6ec0 100644 --- a/src/agentpool_server/acp_server/session.py +++ b/src/agentpool_server/acp_server/session.py @@ -318,23 +318,14 @@ def _register_manifest_commands(self) -> None: and registers them as slashed commands in the session's command_store so they are included in available_commands_update notifications to ACP clients. """ - from agentpool_config.commands import CommandConfig - pool = self.agent_pool - if not pool or not pool.manifest.commands: + commands = pool.manifest.get_command_configs() + if commands is None: self.log.debug("No manifest commands to register") return cmd_count = 0 - for cmd_name, cmd_config in pool.manifest.commands.items(): - # Skip string shorthand (should be CommandConfig object) - if isinstance(cmd_config, str): - self.log.debug( - "Skipping shorthand command", - name=cmd_name, - type=type(cmd_config).__name__, - ) - continue + for cmd_name, cmd_config in commands.items(): try: # Convert CommandConfig to slashed Command