diff --git a/README.md b/README.md index baae34f8..f12ddfa2 100644 --- a/README.md +++ b/README.md @@ -288,6 +288,7 @@ Built-ins: | `gemini` | native (`gemini --acp`) | [Gemini CLI](https://github.com/google/gemini-cli) | | `cursor` | native (`cursor-agent acp`) | [Cursor CLI](https://cursor.com/docs/cli/acp) | | `copilot` | native (`copilot --acp --stdio`) | [GitHub Copilot CLI](https://docs.github.com/copilot/how-tos/copilot-chat/use-copilot-chat-in-the-command-line) | +| `droid` | native (`droid exec --output-format acp`) | [Factory Droid](https://www.factory.ai) | | `kimi` | native (`kimi acp`) | [Kimi CLI](https://github.com/MoonshotAI/kimi-cli) | | `opencode` | `npx -y opencode-ai acp` | [OpenCode](https://opencode.ai) | | `kiro` | native (`kiro-cli acp`) | [Kiro CLI](https://kiro.dev) | diff --git a/agents/Droid.md b/agents/Droid.md new file mode 100644 index 00000000..644f19fe --- /dev/null +++ b/agents/Droid.md @@ -0,0 +1,5 @@ +# Droid + +- Built-in name: `droid` +- Default command: `droid exec --output-format acp` +- Upstream: https://www.factory.ai diff --git a/agents/README.md b/agents/README.md index a0f18955..e6d4f1c9 100644 --- a/agents/README.md +++ b/agents/README.md @@ -9,6 +9,7 @@ Built-in agents: - `gemini -> gemini --acp` - `cursor -> cursor-agent acp` - `copilot -> copilot --acp --stdio` +- `droid -> droid exec --output-format acp` - `kimi -> kimi acp` - `opencode -> npx -y opencode-ai acp` - `kiro -> kiro-cli acp` @@ -20,6 +21,7 @@ Harness-specific docs in this directory: - [Gemini](Gemini.md): built-in `gemini -> gemini --acp` - [Cursor](Cursor.md): built-in `cursor -> cursor-agent acp` - [Copilot](Copilot.md): built-in `copilot -> copilot --acp --stdio` +- [Droid](Droid.md): built-in `droid -> droid exec --output-format acp` - [Kimi](Kimi.md): built-in `kimi -> kimi acp` - [OpenCode](OpenCode.md): built-in `opencode -> npx -y opencode-ai acp` - [Kiro](Kiro.md): built-in `kiro -> kiro-cli acp` diff --git a/skills/acpx/SKILL.md b/skills/acpx/SKILL.md index 5fc0a36c..99a30ca5 100644 --- a/skills/acpx/SKILL.md +++ b/skills/acpx/SKILL.md @@ -71,15 +71,17 @@ If prompt text is omitted and stdin is piped, `acpx` reads prompt text from stdi Friendly agent names resolve to commands: +- `pi` -> `npx pi-acp` +- `openclaw` -> `openclaw acp` - `codex` -> `npx @zed-industries/codex-acp` - `claude` -> `npx -y @zed-industries/claude-agent-acp` -- `copilot` -> `copilot --acp --stdio` - `gemini` -> `gemini --acp` -- `openclaw` -> `openclaw acp` +- `cursor` -> `cursor-agent acp` +- `copilot` -> `copilot --acp --stdio` +- `droid` -> `droid exec --output-format acp` - `kimi` -> `kimi acp` - `opencode` -> `npx -y opencode-ai acp` - `kiro` -> `kiro-cli acp` -- `pi` -> `npx pi-acp` - `kilocode` -> `npx -y @kilocode/cli acp` - `qwen` -> `qwen --acp` diff --git a/src/agent-registry.ts b/src/agent-registry.ts index 4fbd6259..6dd9b990 100644 --- a/src/agent-registry.ts +++ b/src/agent-registry.ts @@ -12,6 +12,7 @@ export const AGENT_REGISTRY: Record = { gemini: "gemini --acp", cursor: "cursor-agent acp", copilot: "copilot --acp --stdio", + droid: "droid exec --output-format acp", kimi: "kimi acp", opencode: "npx -y opencode-ai acp", kiro: "kiro-cli acp", diff --git a/test/agent-registry.test.ts b/test/agent-registry.test.ts index 4a30dc79..f5b16d41 100644 --- a/test/agent-registry.test.ts +++ b/test/agent-registry.test.ts @@ -20,7 +20,7 @@ test("resolveAgentCommand returns raw value for unknown agents", () => { test("listBuiltInAgents preserves the required built-in example order", () => { const agents = listBuiltInAgents(); assert.deepEqual(agents, Object.keys(AGENT_REGISTRY)); - assert.deepEqual(agents.slice(0, 7), [ + assert.deepEqual(agents, [ "pi", "openclaw", "codex", @@ -28,6 +28,12 @@ test("listBuiltInAgents preserves the required built-in example order", () => { "gemini", "cursor", "copilot", + "droid", + "kimi", + "opencode", + "kiro", + "kilocode", + "qwen", ]); }); diff --git a/test/integration.test.ts b/test/integration.test.ts index 31b89e61..3e18083d 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -72,6 +72,33 @@ test("integration: built-in cursor agent resolves to cursor-agent acp", async () }); }); +test("integration: built-in droid agent resolves to droid exec --output-format acp", async () => { + await withTempHome(async (homeDir) => { + const cwd = await fs.mkdtemp(path.join(os.tmpdir(), "acpx-integration-cwd-")); + const fakeBinDir = await fs.mkdtemp(path.join(os.tmpdir(), "acpx-fake-droid-")); + + try { + await writeFakeDroidAgent(fakeBinDir); + + const result = await runCli( + ["--approve-all", "--cwd", cwd, "--format", "quiet", "droid", "exec", "echo hello"], + homeDir, + { + env: { + PATH: `${fakeBinDir}${path.delimiter}${process.env.PATH ?? ""}`, + }, + }, + ); + + assert.equal(result.code, 0, result.stderr); + assert.match(result.stdout, /hello/); + } finally { + await fs.rm(fakeBinDir, { recursive: true, force: true }); + await fs.rm(cwd, { recursive: true, force: true }); + } + }); +}); + test("integration: exec forwards model, allowed-tools, and max-turns in session/new _meta", async () => { await withTempHome(async (homeDir) => { const cwd = await fs.mkdtemp(path.join(os.tmpdir(), "acpx-integration-cwd-")); @@ -1529,6 +1556,44 @@ async function writeFakeCursorAgent(binDir: string): Promise { ); } +async function writeFakeDroidAgent(binDir: string): Promise { + if (process.platform === "win32") { + await fs.writeFile( + path.join(binDir, "droid.cmd"), + [ + "@echo off", + "setlocal", + 'if /I "%~1"=="exec" shift', + 'if /I "%~1"=="--output-format" shift', + 'if /I "%~1"=="acp" shift', + `"${process.execPath}" "${MOCK_AGENT_PATH}" %*`, + "", + ].join("\r\n"), + { encoding: "utf8" }, + ); + return; + } + + await fs.writeFile( + path.join(binDir, "droid"), + [ + "#!/bin/sh", + 'if [ "$1" = "exec" ]; then', + " shift", + "fi", + 'if [ "$1" = "--output-format" ]; then', + " shift", + "fi", + 'if [ "$1" = "acp" ]; then', + " shift", + "fi", + `exec "${process.execPath}" "${MOCK_AGENT_PATH}" "$@"`, + "", + ].join("\n"), + { encoding: "utf8", mode: 0o755 }, + ); +} + async function withTempHome(run: (homeDir: string) => Promise): Promise { const tempHome = await fs.mkdtemp(path.join(os.tmpdir(), "acpx-integration-home-")); try {