Skip to content

feat(acp): add ACP (Agent Client Protocol) external agent support#1544

Open
YingchaoX wants to merge 4 commits intoagentscope-ai:mainfrom
YingchaoX:feat/acp-external-agent
Open

feat(acp): add ACP (Agent Client Protocol) external agent support#1544
YingchaoX wants to merge 4 commits intoagentscope-ai:mainfrom
YingchaoX:feat/acp-external-agent

Conversation

@YingchaoX
Copy link
Contributor

Add ACP feature to enable CoPaw invoking external agents via ACP protocol (e.g., OpenCode, Qwen-code, Gemini CLI).

Backend changes:

  • Add ACP module with config, runtime, transport, service, and permissions
  • Add sessions_spawn tool for ACP compatibility
  • Add approvals router for permission handling
  • Update runner to support external agent execution
  • Update agent react_agent to register ACP tool
  • Update command_handler to support /acp command
  • Update prompt builder with ACP guidance
  • Add ACP constants for drain loop configuration

Frontend changes:

  • Add ACP configuration page
  • Add ACP API modules and types
  • Add approval API for permission handling
  • Update Chat page with external agent selector
  • Update session API for external agent metadata
  • Add ACP navigation and i18n support

Follows Conventional Commits specification.

Description

This PR implements ACP (Agent Client Protocol) support, enabling CoPaw to invoke external coding agents (OpenCode, Qwen-code, Gemini CLI) via t
he ACP protocol. Users can now leverage specialized coding agents directly from CoPaw chat interface.

Key Features:

  1. Multi-harness support: Pre-configured support for OpenCode, Qwen-code, and Gemini CLI
  2. Session management: Persistent ACP sessions with keep_session option
  3. Permission control: Integration with existing ToolGuard approval system
  4. Natural language triggers: Support for /acp opencode ..., /opencode ..., and natural language like "用 opencode 分析代码"
  5. UI Integration: External Agent selector in chat page + dedicated ACP configuration page

Why:
CoPaw users often need specialized coding capabilities that external agents provide. Instead of switching between tools, users can now invoke them
seamlessly within CoPaw conversations while maintaining context.

Related Issue: Fixes #1059

Security Considerations:

  • All ACP harnesses are disabled by default - users must explicitly enable them
  • Support for require_approval configuration - ACP tasks can require user confirmation
  • Environment variables are isolated per harness configuration
  • Commands run in sandboxed subprocess with stdio transport
  • Integration with existing ToolGuard approval system for permission requests

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation
  • Refactoring

Component(s) Affected

  • Core / Backend (app, agents, config, providers, utils, local_models)
  • Console (frontend web UI)
  • Channels (DingTalk, Feishu, QQ, Discord, iMessage, etc.)
  • Skills
  • CLI
  • Documentation (website)
  • Tests
  • CI/CD
  • Scripts / Deploy

Checklist

  • I ran pre-commit run --all-files locally and it passes
  • If pre-commit auto-fixed files, I committed those changes and reran checks
  • I ran tests locally (pytest or as relevant) and they pass
  • Documentation updated (if needed)
  • Ready for review

Testing

Backend:

pytest tests/unit/acp/ -v

Frontend:

cd console && npm run build

Manual Testing:

1. Enable ACP in Settings → ACP Configuration
2. Enable at least one harness (e.g., OpenCode)
3. In chat, select "External Agent""OpenCode" or type /acp opencode --cwd . 分析代码 
4. Verify session persistence with "Keep Session" toggle
5. Test approval flow by enabling "Require Approval"

Local Verification Evidence

pre-commit run --all-files
# All checks passed

pytest tests/unit/acp/
# 7 passed in 2.34s

Additional Notes

• ACP harness commands require npx and Node.js installed
• Default harnesses use latest npm packages: opencode-ai@latest, @qwen-code/qwen-code@latest
• Session states are saved to ~/.copaw/acp_sessions by default
• i18n support added for en, ja, ru, zh

Add ACP feature to enable CoPaw invoking external agents via ACP protocol
(e.g., OpenCode, Qwen-code, Gemini CLI).

Backend changes:
- Add ACP module with config, runtime, transport, service, and permissions
- Add sessions_spawn tool for ACP compatibility
- Add approvals router for permission handling
- Update runner to support external agent execution
- Update agent react_agent to register ACP tool
- Update command_handler to support /acp command
- Update prompt builder with ACP guidance
- Add ACP constants for drain loop configuration

Frontend changes:
- Add ACP configuration page
- Add ACP API modules and types
- Add approval API for permission handling
- Update Chat page with external agent selector
- Update session API for external agent metadata
- Add ACP navigation and i18n support

Follows Conventional Commits specification.
@YingchaoX YingchaoX requested a deployment to maintainer-approved March 15, 2026 23:43 — with GitHub Actions Waiting
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces the Agent Client Protocol (ACP) feature, significantly expanding CoPaw's capabilities by allowing it to seamlessly interact with external coding agents. This integration enables users to leverage specialized tools like OpenCode and Qwen-code directly within their CoPaw conversations, enhancing productivity and providing a more versatile development environment. The changes span both backend and frontend, ensuring robust functionality, configurable settings, and an intuitive user experience for managing and utilizing these external agents.

Highlights

  • External Agent Support (ACP): Introduced Agent Client Protocol (ACP) to enable CoPaw to integrate with and invoke external coding agents like OpenCode, Qwen-code, and Gemini CLI directly from the chat interface.
  • Backend ACP Module: Added a comprehensive backend ACP module encompassing configuration, runtime management, transport layer, service logic, and permission handling, along with a dedicated session store.
  • Frontend ACP Configuration & Chat Integration: Developed a new ACP configuration page in the console UI, added ACP-specific API modules and types, and integrated an external agent selector and pending approval UI into the chat page for seamless user interaction.
  • Enhanced Command Handling & Tooling: Updated the command handler to support /acp commands and natural language triggers for external agents. A sessions_spawn tool was added as a compatibility shim for ACP.
  • Approval System Integration: Integrated ACP tasks with the existing ToolGuard approval system, allowing for user confirmation before executing potentially sensitive external agent operations.
  • Internationalization (i18n) Support: Added new localization strings for ACP features across English, Japanese, Russian, and Chinese languages.
  • Documentation: Provided comprehensive documentation for ACP in both English and Chinese, covering configuration, supported harnesses, usage examples, and FAQs.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • console/src/api/index.ts
    • Imported and exposed new ACP and approval API modules.
  • console/src/api/modules/acp.ts
    • Added API functions for fetching and updating ACP configuration.
  • console/src/api/modules/approval.ts
    • Added API functions for retrieving pending approvals and for approving or denying requests.
  • console/src/api/types/acp.ts
    • Defined TypeScript interfaces for ACP configuration and harness details.
  • console/src/api/types/chat.ts
    • Added an optional 'name' field to the ChatSpec interface.
  • console/src/api/types/index.ts
    • Exported the newly defined ACP types.
  • console/src/layouts/MainLayout/index.tsx
    • Imported the new ACP configuration page and added a route for it.
  • console/src/layouts/Sidebar.tsx
    • Added a new icon and navigation item for the ACP configuration page in the sidebar.
  • console/src/locales/en.json
    • Added English localization strings for ACP features.
  • console/src/locales/ja.json
    • Added Japanese localization strings for ACP features.
  • console/src/locales/ru.json
    • Added Russian localization strings for ACP features.
  • console/src/locales/zh.json
    • Added Chinese localization strings for ACP features.
  • console/src/pages/Agent/ACP/components/ACPGlobalSettings.tsx
    • Added a React component for managing global ACP settings.
  • console/src/pages/Agent/ACP/components/ACPHarnessCard.tsx
    • Added a React component to display and interact with individual ACP harnesses.
  • console/src/pages/Agent/ACP/components/HarnessEditDrawer.tsx
    • Added a React component for creating and editing ACP harness configurations.
  • console/src/pages/Agent/ACP/components/index.ts
    • Exported all ACP-related React components.
  • console/src/pages/Agent/ACP/index.module.less
    • Added Less CSS styles for the ACP configuration page and its components.
  • console/src/pages/Agent/ACP/index.tsx
    • Added the main ACP configuration page component, integrating global settings and harness management.
  • console/src/pages/Agent/ACP/useACP.ts
    • Added a React hook to manage ACP configuration state and API calls.
  • console/src/pages/Chat/index.tsx
    • Implemented logic to parse external agent commands from user input, added UI elements for external agent selection and session management, and integrated a display for pending approval requests.
  • console/src/pages/Chat/sessionApi/index.ts
    • Updated message interfaces to support ACP metadata and refined session message merging logic.
  • opencode.json
    • Added a sample configuration file for OpenCode, defining its schema and model.
  • src/copaw/acp/init.py
    • Initialized the ACP Python module, exposing its core components.
  • src/copaw/acp/config.py
    • Defined Pydantic models for ACP global and harness configurations, including default settings and merge logic.
  • src/copaw/acp/errors.py
    • Defined custom exception classes specific to ACP operations.
  • src/copaw/acp/permissions.py
    • Implemented an adapter to translate ACP permission requests into CoPaw's existing approval system.
  • src/copaw/acp/projector.py
    • Developed a projector to convert raw ACP events into structured CoPaw messages for display.
  • src/copaw/acp/runtime.py
    • Created a runtime manager for ACP harnesses, handling process lifecycle and JSON-RPC communication.
  • src/copaw/acp/service.py
    • Provided a high-level service for managing ACP turns and persistent sessions across chats.
  • src/copaw/acp/session_store.py
    • Implemented an in-memory store for active ACP conversation sessions.
  • src/copaw/acp/transport.py
    • Developed a transport layer for bidirectional JSON-RPC communication over standard I/O with ACP harnesses.
  • src/copaw/acp/types.py
    • Defined various data types and utility functions for ACP, including external agent configuration parsing.
  • src/copaw/agents/command_handler.py
    • Updated command recognition logic to handle /acp commands and provided informational responses for them.
  • src/copaw/agents/prompt.py
    • Modified the system prompt builder to conditionally include guidance for ACP intent recognition.
  • src/copaw/agents/react_agent.py
    • Added a mechanism to register the sessions_spawn tool for ACP and updated reply handling for multi-message responses.
  • src/copaw/agents/tools/sessions_spawn.py
    • Introduced a sessions_spawn tool to provide compatibility and guidance for ACP usage.
  • src/copaw/app/routers/init.py
    • Included the new approvals router in the main application router.
  • src/copaw/app/routers/approvals.py
    • Added FastAPI endpoints for managing pending approval requests.
  • src/copaw/app/routers/config.py
    • Added FastAPI endpoints for retrieving and updating ACP configuration.
  • src/copaw/app/runner/api.py
    • Modified the chat history retrieval to include messages from external agent memory.
  • src/copaw/app/runner/command_dispatch.py
    • Adjusted command dispatch logic to correctly route /acp commands and handle multi-message command outputs.
  • src/copaw/app/runner/runner.py
    • Integrated the ACP service into the main agent runner, enabling external agent execution, message streaming, and session state persistence.
  • src/copaw/config/config.py
    • Added the acp configuration section to the main application configuration model.
  • src/copaw/constant.py
    • Added a new constant ACP_DRAIN_MAX_ATTEMPTS for ACP message draining logic.
  • tests/unit/acp/test_chat_history_api.py
    • Added unit tests for chat history API's handling of external agent memory.
  • tests/unit/acp/test_command_dispatch.py
    • Added unit tests for command dispatch logic related to ACP commands and multi-message persistence.
  • tests/unit/acp/test_config.py
    • Added unit tests for ACP configuration, including default harnesses and override merging.
  • tests/unit/acp/test_projector.py
    • Added unit tests for the ACP event projector, verifying message streaming and event mapping.
  • tests/unit/acp/test_runner.py
    • Added unit tests for the ACP runner, covering CWD handling, session reuse, and history persistence.
  • tests/unit/acp/test_runtime.py
    • Added unit tests for the ACP runtime, focusing on error handling, transport behavior, and payload normalization.
  • tests/unit/acp/test_types.py
    • Added unit tests for ACP type parsing and configuration extraction.
  • website/public/docs/acp.en.md
    • Added new English documentation for the ACP feature.
  • website/public/docs/acp.zh.md
    • Added new Chinese documentation for the ACP feature.
  • website/src/pages/Docs.tsx
    • Updated the documentation navigation to include the new ACP section and its icon.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Generative AI Prohibited Use Policy, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This is a substantial and well-executed feature addition that significantly expands CoPaw's capabilities by integrating external agents via ACP. The implementation is comprehensive, covering backend services, frontend UI, configuration, permissions, and documentation.

Key strengths of this PR include:

  • Robust Architecture: The separation of concerns into transport, runtime, service, and projector layers on the backend is clean and maintainable.
  • User Experience: The frontend work is thoughtful, with a dedicated configuration page, a new selector in the chat UI, and support for triggering agents via both commands and natural language.
  • Security and Permissions: The integration with the existing approval system for dangerous operations is a critical security feature that has been implemented correctly.
  • Backward Compatibility: The sessions_spawn tool shim is a great touch for guiding users from the old way to the new ACP flow.

I have a couple of suggestions for improvement related to maintainability and internationalization, which are detailed in the review comments. Overall, this is an excellent contribution.

Comment on lines +148 to +277
def parse_external_agent_text(raw: str | None) -> ExternalAgentConfig | None:
"""Parse ACP intent from command-style or natural-language text."""
text = (raw or "").strip()
if not text:
return None

harness: str | None = None
working = text

if re.match(r"^/acp\b", text, re.IGNORECASE):
working = re.sub(r"^/acp\b", "", text, count=1, flags=re.IGNORECASE).strip()
harness, working = _pop_leading_harness(working)
if harness is None:
harness, working = _pop_option_value(
working,
("--harness", "--agent"),
)
harness = normalize_harness_name(harness)
if not harness:
return None
else:
slash_match = re.match(
r"^/(opencode|open(?:\s|-)?code|qwen(?:\s*code|-code)?|qwencode)\b\s*(.*)$",
text,
re.IGNORECASE,
)
cli_match = re.match(
r"^(?:--harness)(?:=|\s+)(opencode|open(?:\s|-)?code|qwen(?:\s*code|-code)?|qwencode)\b\s*(.*)$",
text,
re.IGNORECASE,
)
zh_match = re.match(
r"^(?:用|使用|让|通过|调用)\s+(opencode|open(?:\s|-)?code|qwen(?:\s*code|-code)?|qwencode)\b(?:\s*(?:来|去|帮忙|帮助))?\s*(.*)$",
text,
re.IGNORECASE,
)
en_match = re.match(
r"^(?:use|with|via|call)\s+(opencode|open(?:\s|-)?code|qwen(?:\s*code|-code)?|qwencode)\b(?:\s+to)?\s*(.*)$",
text,
re.IGNORECASE,
)
match = slash_match or cli_match or zh_match or en_match
if match is None:
return None
harness = normalize_harness_name(match.group(1).replace(" ", " "))
working = match.group(2).strip()

keep_session = False
keep_session_specified = False

keep_flag, working = _pop_flag(working, ("--keep-session", "--session"))
if keep_flag:
keep_session = True
keep_session_specified = True

session_id, working = _pop_option_value(
working,
("--session-id", "--resume-session", "--load-session"),
)

if session_id is None:
natural_session = re.search(
r"(?:继续|复用|加载)\s*(?:session|会话)\s+(\"[^\"]+\"|'[^']+'|\S+)",
working,
re.IGNORECASE,
)
if natural_session is not None:
session_id = _strip_quotes(natural_session.group(1))
working = (
working[:natural_session.start()] + " " + working[natural_session.end():]
).strip()

cwd, working = _pop_option_value(
working,
("--cwd", "--workdir", "--working-dir", "--work-path"),
)

if cwd is None:
explicit_cwd = re.search(
r"(?:工作路径|工作目录|workdir|cwd)\s*(?:是|为|=|:|:)?\s*(\"[^\"]+\"|'[^']+'|\S+)",
working,
re.IGNORECASE,
)
if explicit_cwd is not None:
cwd = _strip_quotes(explicit_cwd.group(1))
working = (working[:explicit_cwd.start()] + " " + working[explicit_cwd.end():]).strip()

if cwd is None:
natural_cwd = re.search(
r"在\s+(\"[^\"]+\"|'[^']+'|\S+)\s+(?:下|目录下|工作目录下)",
working,
re.IGNORECASE,
)
candidate = _strip_quotes(natural_cwd.group(1)) if natural_cwd is not None else None
if natural_cwd is not None and _looks_like_path(candidate):
cwd = candidate
working = (working[:natural_cwd.start()] + " " + working[natural_cwd.end():]).strip()

if re.search(r"(?:保持会话|keep session)", working, re.IGNORECASE):
keep_session = True
keep_session_specified = True
working = re.sub(r"(?:保持会话|keep session)", " ", working, flags=re.IGNORECASE)

if re.search(
r"(?:之前的|上一个|上次的|刚才的|当前的?|现在的?)\s*(?:acp\s*)?(?:session|会话)|(?:previous|last|current)\s+(?:acp\s+)?session",
working,
re.IGNORECASE,
):
keep_session = True
keep_session_specified = True
working = re.sub(
r"(?:请)?\s*(?:使用|复用|继续用|沿用|在)?\s*(?:之前的|上一个|上次的|刚才的|当前的?|现在的?)\s*(?:acp\s*)?(?:session|会话)(?:\s*用)?|(?:use|reuse|continue with)\s+(?:the\s+)?(?:previous|last|current)\s+(?:acp\s+)?session",
" ",
working,
flags=re.IGNORECASE,
)

if session_id:
keep_session = True
keep_session_specified = True

return ExternalAgentConfig(
enabled=True,
harness=harness,
keep_session=keep_session,
cwd=cwd,
existing_session_id=session_id,
prompt=_normalize_prompt(working),
keep_session_specified=keep_session_specified,
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic for parsing external agent commands from text in parse_external_agent_text is quite complex and appears to be duplicated in the frontend in console/src/pages/Chat/index.tsx. This creates a maintainability issue, as any changes to the parsing rules (e.g., adding new flags or natural language triggers) must be manually synchronized between the Python and TypeScript implementations.

Consider unifying this logic. One approach could be to have the frontend send the raw text and let the backend always handle the parsing. This would remove the need for the complex parseExternalAgentFromText function in the frontend, making the system easier to maintain and evolve.

Comment on lines +166 to +201
def _build_summary(
self,
*,
tool_call: dict[str, Any],
tool_name: str,
tool_kind: str,
) -> str:
target = (
tool_call.get("path")
or tool_call.get("target")
or tool_call.get("command")
or tool_call.get("description")
or tool_call.get("input")
or ""
)
target_text = str(target).strip()
if len(target_text) > 240:
target_text = target_text[:240] + "..."

lines = [
f"等待外部 Agent 权限确认 / Waiting for external agent approval",
"",
f"- Harness: `{tool_call.get('harness') or 'external-agent'}`",
f"- Tool: `{tool_name}`",
f"- Kind: `{tool_kind or 'unknown'}`",
]
if target_text:
lines.append(f"- Target: `{target_text}`")
lines.extend(
[
"",
"可以在聊天里输入 `/approve` 批准,或发送任意消息拒绝。",
"You can type `/approve` to allow it, or send any other message to deny it.",
],
)
return "\n".join(lines)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The _build_summary function constructs a user-facing message with hardcoded English and Chinese text. This couples the backend with UI presentation concerns and makes it difficult to add support for new languages.

A better approach would be for the backend to return structured data or message keys (e.g., approval_request_summary with placeholders for harness, tool_name, etc.) and let the frontend be responsible for rendering and translating the text using its i18n framework. This would improve separation of concerns and simplify future internationalization efforts.

许琤芳 added 3 commits March 16, 2026 22:28
- Add /config/acp/parse-text API endpoint to unify external agent text parsing
  between frontend and backend (Gemini comment agentscope-ai#1)
- Remove duplicated parseExternalAgentFromText() from Chat/index.tsx
- Make _build_summary() return structured data for frontend i18n rendering
  instead of hardcoded text (Gemini comment agentscope-ai#2)
- Add ACPApprovalSummary dataclass for type-safe approval data
- Add i18n translation keys for approval UI (en/zh/ja/ru)
- Add renderApprovalSummary() helper with backward compatibility
- Fix code formatting with black and prettier
- Fix flake8 E501 line too long error
- Add /config/acp/parse-text API endpoint to unify external agent text parsing
  between frontend and backend (Gemini comment agentscope-ai#1)
- Remove duplicated parseExternalAgentFromText() from Chat/index.tsx
- Make _build_summary() return structured data for frontend i18n rendering
  instead of hardcoded text (Gemini comment agentscope-ai#2)
- Add ACPApprovalSummary dataclass for type-safe approval data
- Add i18n translation keys for approval UI (en/zh/ja/ru)
- Add renderApprovalSummary() helper with backward compatibility
- Fix code formatting with black and prettier
- Fix flake8 E501 line too long error
…into feat/acp-external-agent

# Conflicts:
#	src/copaw/acp/permissions.py
#	src/copaw/app/routers/config.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: ACP (Agent Communication Protocol) Support

1 participant