diff --git a/apps/cli/src/acp/__tests__/agent.test.ts b/apps/cli/src/acp/__tests__/agent.test.ts index 0c61f0e474..3fb2dc4736 100644 --- a/apps/cli/src/acp/__tests__/agent.test.ts +++ b/apps/cli/src/acp/__tests__/agent.test.ts @@ -1,20 +1,13 @@ -/** - * Tests for RooCodeAgent - */ - -import { describe, it, expect, vi, beforeEach, afterEach } from "vitest" import type * as acp from "@agentclientprotocol/sdk" import { RooCodeAgent, type RooCodeAgentOptions } from "../agent.js" -// Mock the auth module vi.mock("@/commands/auth/index.js", () => ({ login: vi.fn().mockResolvedValue({ success: true }), logout: vi.fn().mockResolvedValue({ success: true }), status: vi.fn().mockResolvedValue({ authenticated: false }), })) -// Mock AcpSession vi.mock("../session.js", () => ({ AcpSession: { create: vi.fn().mockResolvedValue({ @@ -40,7 +33,6 @@ describe("RooCodeAgent", () => { } beforeEach(() => { - // Create a mock connection mockConnection = { sessionUpdate: vi.fn().mockResolvedValue(undefined), requestPermission: vi.fn().mockResolvedValue({ diff --git a/apps/cli/src/acp/__tests__/delta-tracker.test.ts b/apps/cli/src/acp/__tests__/delta-tracker.test.ts index 3874a22a45..1157c32c69 100644 --- a/apps/cli/src/acp/__tests__/delta-tracker.test.ts +++ b/apps/cli/src/acp/__tests__/delta-tracker.test.ts @@ -1,4 +1,3 @@ -import { describe, it, expect, beforeEach } from "vitest" import { DeltaTracker } from "../delta-tracker.js" describe("DeltaTracker", () => { diff --git a/apps/cli/src/acp/__tests__/session.test.ts b/apps/cli/src/acp/__tests__/session.test.ts index 2c172e1d3e..895872f9ac 100644 --- a/apps/cli/src/acp/__tests__/session.test.ts +++ b/apps/cli/src/acp/__tests__/session.test.ts @@ -1,11 +1,5 @@ -/** - * Tests for AcpSession - */ - -import { describe, it, expect, vi, beforeEach, afterEach } from "vitest" import type * as acp from "@agentclientprotocol/sdk" -// Mock the ExtensionHost before importing AcpSession vi.mock("@/agent/extension-host.js", () => { const mockClient = { on: vi.fn().mockReturnThis(), @@ -25,7 +19,6 @@ vi.mock("@/agent/extension-host.js", () => { } }) -// Import after mocking import { AcpSession, type AcpSessionOptions } from "../session.js" import { ExtensionHost } from "@/agent/extension-host.js" @@ -41,7 +34,6 @@ describe("AcpSession", () => { } beforeEach(() => { - // Create a mock connection mockConnection = { sessionUpdate: vi.fn().mockResolvedValue(undefined), requestPermission: vi.fn().mockResolvedValue({ diff --git a/apps/cli/src/acp/__tests__/translator.test.ts b/apps/cli/src/acp/__tests__/translator.test.ts index 5e3b80c338..26795e72d6 100644 --- a/apps/cli/src/acp/__tests__/translator.test.ts +++ b/apps/cli/src/acp/__tests__/translator.test.ts @@ -1,8 +1,3 @@ -/** - * Tests for ACP Message Translator - */ - -import { describe, it, expect } from "vitest" import type { ClineMessage } from "@roo-code/types" import { diff --git a/apps/cli/src/acp/__tests__/update-buffer.test.ts b/apps/cli/src/acp/__tests__/update-buffer.test.ts index a1307d1450..60e3c7a934 100644 --- a/apps/cli/src/acp/__tests__/update-buffer.test.ts +++ b/apps/cli/src/acp/__tests__/update-buffer.test.ts @@ -1,10 +1,3 @@ -/** - * Tests for UpdateBuffer - * - * Verifies that the buffer correctly batches text chunk updates - * while passing through other updates immediately. - */ - import type * as acp from "@agentclientprotocol/sdk" import { UpdateBuffer } from "../update-buffer.js" diff --git a/apps/cli/src/acp/index.ts b/apps/cli/src/acp/index.ts index 96b896bc2e..12a6e23947 100644 --- a/apps/cli/src/acp/index.ts +++ b/apps/cli/src/acp/index.ts @@ -1,22 +1,2 @@ -/** - * ACP (Agent Client Protocol) Integration Module - * - * This module provides ACP support for the Roo Code CLI, allowing ACP-compatible - * clients like Zed to use Roo Code as their AI coding assistant. - * - * Main components: - * - RooCodeAgent: Implements the acp.Agent interface - * - AcpSession: Wraps ExtensionHost for individual sessions - * - Translator: Converts between internal and ACP message formats - * - UpdateBuffer: Batches session updates to reduce message frequency - * - acpLog: File-based logger for debugging (writes to ~/.roo/acp.log) - * - * Note: Commands are executed internally by the extension (like the reference - * implementations gemini-cli and opencode), not through ACP terminals. - */ - -export { RooCodeAgent, type RooCodeAgentOptions } from "./agent.js" -export { AcpSession, type AcpSessionOptions } from "./session.js" -export { UpdateBuffer, type UpdateBufferOptions } from "./update-buffer.js" +export { type RooCodeAgentOptions, RooCodeAgent } from "./agent.js" export { acpLog } from "./logger.js" -export * from "./translator.js" diff --git a/apps/cli/src/commands/acp/index.ts b/apps/cli/src/commands/acp/index.ts index bc0b10954b..9be7b8b529 100644 --- a/apps/cli/src/commands/acp/index.ts +++ b/apps/cli/src/commands/acp/index.ts @@ -1,70 +1,30 @@ -/** - * ACP Command - * - * Starts the Roo Code CLI in ACP server mode, allowing ACP-compatible clients - * like Zed to use Roo Code as their AI coding assistant. - * - * Usage: - * roo acp [options] - * - * The ACP server communicates over stdin/stdout using the ACP protocol - * (JSON-RPC over newline-delimited JSON). - */ - import { Readable, Writable } from "node:stream" import path from "node:path" import { fileURLToPath } from "node:url" import * as acpSdk from "@agentclientprotocol/sdk" -import { type RooCodeAgentOptions, RooCodeAgent, acpLog } from "@/acp/index.js" -import { DEFAULT_FLAGS } from "@/types/constants.js" +import { type SupportedProvider, DEFAULT_FLAGS } from "@/types/index.js" import { getDefaultExtensionPath } from "@/lib/utils/extension.js" - -// ============================================================================= -// Types -// ============================================================================= +import { type RooCodeAgentOptions, RooCodeAgent, acpLog } from "@/acp/index.js" export interface AcpCommandOptions { - /** Path to the extension bundle directory */ extension?: string - /** API provider (anthropic, openai, openrouter, etc.) */ - provider?: string - /** Model to use */ + provider?: SupportedProvider model?: string - /** Initial mode (code, architect, ask, debug) */ mode?: string - /** API key */ apiKey?: string } -// ============================================================================= -// ACP Server -// ============================================================================= - -/** - * Run the ACP server. - * - * This sets up the ACP connection using stdin/stdout and creates a RooCodeAgent - * to handle incoming requests. - */ export async function runAcpServer(options: AcpCommandOptions): Promise { - acpLog.info("Command", "Starting ACP server") - acpLog.debug("Command", "Options", options) - - // Resolve extension path const __dirname = path.dirname(fileURLToPath(import.meta.url)) const extensionPath = options.extension || getDefaultExtensionPath(__dirname) if (!extensionPath) { - acpLog.error("Command", "Extension path not found") console.error("Error: Extension path not found. Use --extension to specify the path.") process.exit(1) } - acpLog.info("Command", `Extension path: ${extensionPath}`) - - // Create agent options const agentOptions: RooCodeAgentOptions = { extensionPath, provider: options.provider || DEFAULT_FLAGS.provider, @@ -73,24 +33,14 @@ export async function runAcpServer(options: AcpCommandOptions): Promise { apiKey: options.apiKey || process.env.OPENROUTER_API_KEY, } - acpLog.debug("Command", "Agent options", { - extensionPath: agentOptions.extensionPath, - provider: agentOptions.provider, - model: agentOptions.model, - mode: agentOptions.mode, - hasApiKey: !!agentOptions.apiKey, - }) - - // Set up stdio streams for ACP communication - // Note: We write to stdout (agent -> client) and read from stdin (client -> agent) + // Set up stdio streams for ACP communication. + // Note: We write to stdout (agent -> client) and read from stdin (client -> agent). const stdout = Writable.toWeb(process.stdout) as WritableStream const stdin = Readable.toWeb(process.stdin) as ReadableStream - // Create the ACP stream const stream = acpSdk.ndJsonStream(stdout, stdin) acpLog.info("Command", "ACP stream created, waiting for connection") - // Create the agent connection let agent: RooCodeAgent | null = null const connection = new acpSdk.AgentSideConnection((conn: acpSdk.AgentSideConnection) => { @@ -99,12 +49,13 @@ export async function runAcpServer(options: AcpCommandOptions): Promise { return agent }, stream) - // Handle graceful shutdown const cleanup = async () => { acpLog.info("Command", "Received shutdown signal, cleaning up") + if (agent) { await agent.dispose() } + acpLog.info("Command", "Cleanup complete, exiting") process.exit(0) } @@ -112,26 +63,17 @@ export async function runAcpServer(options: AcpCommandOptions): Promise { process.on("SIGINT", cleanup) process.on("SIGTERM", cleanup) - // Wait for the connection to close acpLog.info("Command", "Waiting for connection to close") await connection.closed acpLog.info("Command", "Connection closed") } -// ============================================================================= -// Command Action -// ============================================================================= - -/** - * Action handler for the `roo acp` command. - */ export async function acp(options: AcpCommandOptions): Promise { try { await runAcpServer(options) } catch (error) { - // Log errors to file and stderr so they don't interfere with ACP protocol acpLog.error("Command", "Fatal error", error) - console.error("[ACP] Fatal error:", error) + console.error(error) process.exit(1) } } diff --git a/apps/cli/src/commands/index.ts b/apps/cli/src/commands/index.ts index 717a7040ef..566cf1a330 100644 --- a/apps/cli/src/commands/index.ts +++ b/apps/cli/src/commands/index.ts @@ -1,2 +1,3 @@ export * from "./auth/index.js" export * from "./cli/index.js" +export * from "./acp/index.js" diff --git a/apps/cli/src/index.ts b/apps/cli/src/index.ts index 4a81710908..4139325c73 100644 --- a/apps/cli/src/index.ts +++ b/apps/cli/src/index.ts @@ -2,8 +2,7 @@ import { Command } from "commander" import { DEFAULT_FLAGS } from "@/types/constants.js" import { VERSION } from "@/lib/utils/version.js" -import { run, login, logout, status } from "@/commands/index.js" -import { acp } from "@/commands/acp/index.js" +import { run, login, logout, status, acp } from "@/commands/index.js" const program = new Command()