mirror of https://github.com/openclaw/openclaw.git
Align ACPX built-in agent registry with latest acpx (#55476)
* Add Cursor CLI to ACP allowedAgents - acpx: add cursor to ACPX_BUILTIN_AGENT_COMMANDS (agent acp) - docs: add cursor to acp-agents harness list and allowedAgents example Fixes #28321 Made-with: Cursor * ACP Cursor: add to acp-router skill, system-prompt, and schema help - acp-router SKILL: add Cursor to description, intent, agentId mapping, harness aliases, and built-in adapter commands (agent acp) - system-prompt: add cursor to ACP harness example - schema.help: add cursor to runtime.acp.agent example Fixes #28321 Made-with: Cursor * fix(acpx): align built-in agent registry with latest acpx --------- Co-authored-by: Rob MacDonald <rob@robmacdonald.com>
This commit is contained in:
parent
e9f54ca815
commit
fa2a318f40
|
|
@ -27,6 +27,7 @@ Docs: https://docs.openclaw.ai
|
|||
|
||||
### Fixes
|
||||
|
||||
- ACP/ACPX agent registry: align OpenClaw's ACPX built-in agent mirror with the latest `openclaw/acpx` command defaults and built-in aliases, pin versioned `npx` built-ins to exact versions, and stop unknown ACP agent ids from falling through to raw `--agent` command execution on the MCP-proxy path. (#28321) Thanks @m0nkmaster and @vincentkoc.
|
||||
- Control UI/config: keep sensitive raw config hidden by default, replace the blank blocked editor with an explicit reveal-to-edit state, and restore raw JSON editing without auto-exposing secrets. Fixes #55322.
|
||||
- WhatsApp: fix infinite echo loop in self-chat DM mode where the bot's own outbound replies were re-processed as new inbound user messages. (#54570) Thanks @joelnishanth
|
||||
- OpenAI Codex/image tools: register Codex for media understanding and route image prompts through Codex instructions so image analysis no longer fails on missing provider registration or missing `instructions`. (#54829) Thanks @neeravmakwana.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
summary: "Use ACP runtime sessions for Pi, Claude Code, Codex, OpenCode, Gemini CLI, and other harness agents"
|
||||
summary: "Use ACP runtime sessions for Codex, Claude Code, Cursor, Gemini CLI, OpenClaw ACP, and other harness agents"
|
||||
read_when:
|
||||
- Running coding harnesses through ACP
|
||||
- Setting up thread-bound ACP sessions on thread-capable channels
|
||||
|
|
@ -11,7 +11,7 @@ title: "ACP Agents"
|
|||
|
||||
# ACP agents
|
||||
|
||||
[Agent Client Protocol (ACP)](https://agentclientprotocol.com/) sessions let OpenClaw run external coding harnesses (for example Pi, Claude Code, Codex, OpenCode, and Gemini CLI) through an ACP backend plugin.
|
||||
[Agent Client Protocol (ACP)](https://agentclientprotocol.com/) sessions let OpenClaw run external coding harnesses (for example Pi, Claude Code, Codex, Cursor, Copilot, OpenClaw ACP, OpenCode, Gemini CLI, and other supported ACPX harnesses) through an ACP backend plugin.
|
||||
|
||||
If you ask OpenClaw in plain language to "run this in Codex" or "start Claude Code in a thread", OpenClaw should route that request to the ACP runtime (not the native sub-agent runtime).
|
||||
|
||||
|
|
@ -441,14 +441,23 @@ Equivalent operations:
|
|||
|
||||
Current acpx built-in harness aliases:
|
||||
|
||||
- `pi`
|
||||
- `claude`
|
||||
- `codex`
|
||||
- `opencode`
|
||||
- `copilot`
|
||||
- `cursor` (Cursor CLI: `cursor-agent acp`)
|
||||
- `droid`
|
||||
- `gemini`
|
||||
- `iflow`
|
||||
- `kilocode`
|
||||
- `kimi`
|
||||
- `kiro`
|
||||
- `openclaw`
|
||||
- `opencode`
|
||||
- `pi`
|
||||
- `qwen`
|
||||
|
||||
When OpenClaw uses the acpx backend, prefer these values for `agentId` unless your acpx config defines custom agent aliases.
|
||||
If your local Cursor install still exposes ACP as `agent acp`, override the `cursor` agent command in your acpx config instead of changing the built-in default.
|
||||
|
||||
Direct acpx CLI usage can also target arbitrary adapters via `--agent <command>`, but that raw escape hatch is an acpx CLI feature (not the normal OpenClaw `agentId` path).
|
||||
|
||||
|
|
@ -464,7 +473,22 @@ Core ACP baseline:
|
|||
dispatch: { enabled: true },
|
||||
backend: "acpx",
|
||||
defaultAgent: "codex",
|
||||
allowedAgents: ["pi", "claude", "codex", "opencode", "gemini", "kimi"],
|
||||
allowedAgents: [
|
||||
"claude",
|
||||
"codex",
|
||||
"copilot",
|
||||
"cursor",
|
||||
"droid",
|
||||
"gemini",
|
||||
"iflow",
|
||||
"kilocode",
|
||||
"kimi",
|
||||
"kiro",
|
||||
"openclaw",
|
||||
"opencode",
|
||||
"pi",
|
||||
"qwen",
|
||||
],
|
||||
maxConcurrentSessions: 8,
|
||||
stream: {
|
||||
coalesceIdleMs: 300,
|
||||
|
|
|
|||
|
|
@ -1,25 +1,25 @@
|
|||
---
|
||||
name: acp-router
|
||||
description: Route plain-language requests for Pi, Claude Code, Codex, OpenCode, Gemini CLI, or ACP harness work into either OpenClaw ACP runtime sessions or direct acpx-driven sessions ("telephone game" flow). For coding-agent thread requests, read this skill first, then use only `sessions_spawn` for thread creation.
|
||||
description: Route plain-language requests for Pi, Claude Code, Codex, Cursor, Copilot, OpenClaw ACP, OpenCode, Gemini CLI, Qwen, Kiro, Kimi, iFlow, Factory Droid, Kilocode, or ACP harness work into either OpenClaw ACP runtime sessions or direct acpx-driven sessions ("telephone game" flow). For coding-agent thread requests, read this skill first, then use only `sessions_spawn` for thread creation.
|
||||
user-invocable: false
|
||||
---
|
||||
|
||||
# ACP Harness Router
|
||||
|
||||
When user intent is "run this in Pi/Claude Code/Codex/OpenCode/Gemini/Kimi (ACP harness)", do not use subagent runtime or PTY scraping. Route through ACP-aware flows.
|
||||
When user intent is "run this in Pi/Claude Code/Codex/Cursor/Copilot/OpenClaw/OpenCode/Gemini/Qwen/Kiro/Kimi/iFlow/Droid/Kilocode (ACP harness)", do not use subagent runtime or PTY scraping. Route through ACP-aware flows.
|
||||
|
||||
## Intent detection
|
||||
|
||||
Trigger this skill when the user asks OpenClaw to:
|
||||
|
||||
- run something in Pi / Claude Code / Codex / OpenCode / Gemini
|
||||
- run something in Pi / Claude Code / Codex / Cursor / Copilot / OpenClaw / OpenCode / Gemini / Qwen / Kiro / Kimi / iFlow / Droid / Kilocode
|
||||
- continue existing harness work
|
||||
- relay instructions to an external coding harness
|
||||
- keep an external harness conversation in a thread-like conversation
|
||||
|
||||
Mandatory preflight for coding-agent thread requests:
|
||||
|
||||
- Before creating any thread for Pi/Claude/Codex/OpenCode/Gemini work, read this skill first in the same turn.
|
||||
- Before creating any thread for ACP harness work, read this skill first in the same turn.
|
||||
- After reading, follow `OpenClaw ACP runtime path` below; do not use `message(action="thread-create")` for ACP harness thread spawn.
|
||||
|
||||
## Mode selection
|
||||
|
|
@ -39,18 +39,26 @@ Do not use:
|
|||
|
||||
- `subagents` runtime for harness control
|
||||
- `/acp` command delegation as a requirement for the user
|
||||
- PTY scraping of pi/claude/codex/opencode/gemini/kimi CLIs when `acpx` is available
|
||||
- PTY scraping of supported ACP harness CLIs when `acpx` is available
|
||||
|
||||
## AgentId mapping
|
||||
|
||||
Use these defaults when user names a harness directly:
|
||||
|
||||
- "pi" -> `agentId: "pi"`
|
||||
- "openclaw" -> `agentId: "openclaw"`
|
||||
- "claude" or "claude code" -> `agentId: "claude"`
|
||||
- "codex" -> `agentId: "codex"`
|
||||
- "copilot" or "github copilot" -> `agentId: "copilot"`
|
||||
- "cursor" or "cursor cli" -> `agentId: "cursor"`
|
||||
- "droid" or "factory droid" -> `agentId: "droid"`
|
||||
- "opencode" -> `agentId: "opencode"`
|
||||
- "gemini" or "gemini cli" -> `agentId: "gemini"`
|
||||
- "iflow" -> `agentId: "iflow"`
|
||||
- "kilocode" -> `agentId: "kilocode"`
|
||||
- "kimi" or "kimi cli" -> `agentId: "kimi"`
|
||||
- "kiro" or "kiro cli" -> `agentId: "kiro"`
|
||||
- "qwen" or "qwen code" -> `agentId: "qwen"`
|
||||
|
||||
These defaults match current acpx built-in aliases.
|
||||
|
||||
|
|
@ -88,7 +96,7 @@ Call:
|
|||
|
||||
## Thread spawn recovery policy
|
||||
|
||||
When the user asks to start a coding harness in a thread (for example "start a codex/claude/pi/kimi thread"), treat that as an ACP runtime request and try to satisfy it end-to-end.
|
||||
When the user asks to start a coding harness in a thread, treat that as an ACP runtime request and try to satisfy it end-to-end.
|
||||
|
||||
Required behavior when ACP backend is unavailable:
|
||||
|
||||
|
|
@ -179,25 +187,42 @@ ${ACPX_CMD} codex sessions close oc-codex-<conversationId>
|
|||
|
||||
### Harness aliases in acpx
|
||||
|
||||
- `pi`
|
||||
- `claude`
|
||||
- `codex`
|
||||
- `opencode`
|
||||
- `copilot`
|
||||
- `cursor`
|
||||
- `droid`
|
||||
- `gemini`
|
||||
- `iflow`
|
||||
- `kilocode`
|
||||
- `kimi`
|
||||
- `kiro`
|
||||
- `openclaw`
|
||||
- `opencode`
|
||||
- `pi`
|
||||
- `qwen`
|
||||
|
||||
### Built-in adapter commands in acpx
|
||||
|
||||
Defaults are:
|
||||
|
||||
- `pi -> npx pi-acp`
|
||||
- `claude -> npx -y @zed-industries/claude-agent-acp`
|
||||
- `codex -> npx @zed-industries/codex-acp`
|
||||
- `opencode -> npx -y opencode-ai acp`
|
||||
- `gemini -> gemini`
|
||||
- `openclaw -> openclaw acp`
|
||||
- `claude -> npx -y @zed-industries/claude-agent-acp@0.21.0`
|
||||
- `codex -> npx @zed-industries/codex-acp@^0.9.5`
|
||||
- `copilot -> copilot --acp --stdio`
|
||||
- `cursor -> cursor-agent acp`
|
||||
- `droid -> droid exec --output-format acp`
|
||||
- `gemini -> gemini --acp`
|
||||
- `iflow -> iflow --experimental-acp`
|
||||
- `kilocode -> npx -y @kilocode/cli acp`
|
||||
- `kimi -> kimi acp`
|
||||
- `kiro -> kiro-cli acp`
|
||||
- `opencode -> npx -y opencode-ai acp`
|
||||
- `pi -> npx pi-acp@^0.0.22`
|
||||
- `qwen -> qwen --acp`
|
||||
|
||||
If `~/.acpx/config.json` overrides `agents`, those overrides replace defaults.
|
||||
If your local Cursor install still exposes ACP as `agent acp`, set that as the `cursor` agent override explicitly.
|
||||
|
||||
### Failure handling
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,48 @@ vi.mock("./process.js", () => ({
|
|||
import { __testing, resolveAcpxAgentCommand } from "./mcp-agent-command.js";
|
||||
|
||||
describe("resolveAcpxAgentCommand", () => {
|
||||
it.each([
|
||||
["cursor", "cursor-agent acp"],
|
||||
["gemini", "gemini --acp"],
|
||||
["openclaw", "openclaw acp"],
|
||||
["copilot", "copilot --acp --stdio"],
|
||||
["pi", "npx -y pi-acp@0.0.22"],
|
||||
["codex", "npx -y @zed-industries/codex-acp@0.9.5"],
|
||||
["claude", "npx -y @zed-industries/claude-agent-acp@0.21.0"],
|
||||
])("uses the current acpx built-in for %s by default", async (agent, expected) => {
|
||||
spawnAndCollectMock.mockResolvedValueOnce({
|
||||
stdout: JSON.stringify({ agents: {} }),
|
||||
stderr: "",
|
||||
code: 0,
|
||||
error: null,
|
||||
});
|
||||
|
||||
const command = await resolveAcpxAgentCommand({
|
||||
acpxCommand: "/plugin/node_modules/.bin/acpx",
|
||||
cwd: "/plugin",
|
||||
agent,
|
||||
});
|
||||
|
||||
expect(command).toBe(expected);
|
||||
});
|
||||
|
||||
it("returns null for unknown agent ids instead of falling back to raw commands", async () => {
|
||||
spawnAndCollectMock.mockResolvedValueOnce({
|
||||
stdout: JSON.stringify({ agents: {} }),
|
||||
stderr: "",
|
||||
code: 0,
|
||||
error: null,
|
||||
});
|
||||
|
||||
const command = await resolveAcpxAgentCommand({
|
||||
acpxCommand: "/plugin/node_modules/.bin/acpx",
|
||||
cwd: "/plugin",
|
||||
agent: "sh -c whoami",
|
||||
});
|
||||
|
||||
expect(command).toBeNull();
|
||||
});
|
||||
|
||||
it("threads stripProviderAuthEnvVars through the config show probe", async () => {
|
||||
spawnAndCollectMock.mockResolvedValueOnce({
|
||||
stdout: JSON.stringify({
|
||||
|
|
|
|||
|
|
@ -2,12 +2,22 @@ import path from "node:path";
|
|||
import { fileURLToPath } from "node:url";
|
||||
import { spawnAndCollect, type SpawnCommandOptions } from "./process.js";
|
||||
|
||||
// Keep this mirror aligned with openclaw/acpx src/agent-registry.ts built-ins.
|
||||
const ACPX_BUILTIN_AGENT_COMMANDS: Record<string, string> = {
|
||||
codex: "npx @zed-industries/codex-acp",
|
||||
claude: "npx -y @zed-industries/claude-agent-acp",
|
||||
gemini: "gemini",
|
||||
pi: "npx -y pi-acp@0.0.22",
|
||||
openclaw: "openclaw acp",
|
||||
codex: "npx -y @zed-industries/codex-acp@0.9.5",
|
||||
claude: "npx -y @zed-industries/claude-agent-acp@0.21.0",
|
||||
gemini: "gemini --acp",
|
||||
cursor: "cursor-agent acp",
|
||||
copilot: "copilot --acp --stdio",
|
||||
droid: "droid exec --output-format acp",
|
||||
iflow: "iflow --experimental-acp",
|
||||
kilocode: "npx -y @kilocode/cli acp",
|
||||
kimi: "kimi acp",
|
||||
kiro: "kiro-cli acp",
|
||||
opencode: "npx -y opencode-ai acp",
|
||||
pi: "npx pi-acp",
|
||||
qwen: "qwen --acp",
|
||||
};
|
||||
|
||||
const MCP_PROXY_PATH = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "mcp-proxy.mjs");
|
||||
|
|
@ -95,7 +105,7 @@ export async function resolveAcpxAgentCommand(params: {
|
|||
agent: string;
|
||||
stripProviderAuthEnvVars?: boolean;
|
||||
spawnOptions?: SpawnCommandOptions;
|
||||
}): Promise<string> {
|
||||
}): Promise<string | null> {
|
||||
const normalizedAgent = normalizeAgentName(params.agent);
|
||||
const overrides = await loadAgentOverrides({
|
||||
acpxCommand: params.acpxCommand,
|
||||
|
|
@ -103,7 +113,7 @@ export async function resolveAcpxAgentCommand(params: {
|
|||
stripProviderAuthEnvVars: params.stripProviderAuthEnvVars,
|
||||
spawnOptions: params.spawnOptions,
|
||||
});
|
||||
return overrides[normalizedAgent] ?? ACPX_BUILTIN_AGENT_COMMANDS[normalizedAgent] ?? params.agent;
|
||||
return overrides[normalizedAgent] ?? ACPX_BUILTIN_AGENT_COMMANDS[normalizedAgent] ?? null;
|
||||
}
|
||||
|
||||
export function buildMcpProxyAgentCommand(params: {
|
||||
|
|
|
|||
|
|
@ -551,6 +551,31 @@ describe("AcpxRuntime", () => {
|
|||
}
|
||||
});
|
||||
|
||||
it("does not pass unknown agent ids through acpx --agent when MCP servers are configured", async () => {
|
||||
const { runtime, logPath } = await createMockRuntimeFixture({
|
||||
mcpServers: {
|
||||
canva: {
|
||||
command: "npx",
|
||||
args: ["-y", "mcp-remote@latest", "https://mcp.canva.com/mcp"],
|
||||
env: {
|
||||
CANVA_TOKEN: "secret", // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await runtime.ensureSession({
|
||||
sessionKey: "agent:sh:acp:mcp",
|
||||
agent: "sh -c whoami",
|
||||
mode: "persistent",
|
||||
});
|
||||
|
||||
const logs = await readMockRuntimeLogEntries(logPath);
|
||||
const ensureArgs = (logs.find((entry) => entry.kind === "ensure")?.args as string[]) ?? [];
|
||||
expect(ensureArgs).not.toContain("--agent");
|
||||
expect(ensureArgs).toContain("sh -c whoami");
|
||||
});
|
||||
|
||||
it("skips prompt execution when runTurn starts with an already-aborted signal", async () => {
|
||||
const { runtime, logPath } = await createMockRuntimeFixture();
|
||||
const handle = await runtime.ensureSession({
|
||||
|
|
|
|||
|
|
@ -435,7 +435,7 @@ export function buildAgentSystemPrompt(params: {
|
|||
"If a task is more complex or takes longer, spawn a sub-agent. Completion is push-based: it will auto-announce when done.",
|
||||
...(acpHarnessSpawnAllowed
|
||||
? [
|
||||
'For requests like "do this in codex/claude code/gemini", treat it as ACP harness intent and call `sessions_spawn` with `runtime: "acp"`.',
|
||||
'For requests like "do this in codex/claude code/cursor/gemini" or similar ACP harnesses, treat it as ACP harness intent and call `sessions_spawn` with `runtime: "acp"`.',
|
||||
'On Discord, default ACP harness requests to thread-bound persistent sessions (`thread: true`, `mode: "session"`) unless the user asks otherwise.',
|
||||
"Set `agentId` explicitly unless `acp.defaultAgent` is configured, and do not route ACP harness requests through `subagents`/`agents_list` or local PTY exec flows.",
|
||||
'For ACP harness thread spawns, do not call `message` with `action=thread-create`; use `sessions_spawn` (`runtime: "acp"`, `thread: true`) as the single thread creation path.',
|
||||
|
|
|
|||
|
|
@ -222,7 +222,7 @@ export const FIELD_HELP: Record<string, string> = {
|
|||
"agents.list[].runtime.acp":
|
||||
"ACP runtime defaults for this agent when runtime.type=acp. Binding-level ACP overrides still take precedence per conversation.",
|
||||
"agents.list[].runtime.acp.agent":
|
||||
"Optional ACP harness agent id to use for this OpenClaw agent (for example codex, claude).",
|
||||
"Optional ACP harness agent id to use for this OpenClaw agent (for example codex, claude, cursor, gemini, openclaw).",
|
||||
"agents.list[].runtime.acp.backend":
|
||||
"Optional ACP backend override for this agent's ACP sessions (falls back to global acp.backend).",
|
||||
"agents.list[].runtime.acp.mode":
|
||||
|
|
|
|||
Loading…
Reference in New Issue