diff --git a/packages/ipc/README.md b/packages/ipc/README.md new file mode 100644 index 0000000000..7642af4958 --- /dev/null +++ b/packages/ipc/README.md @@ -0,0 +1,90 @@ +# IPC (Inter-Process Communication) + +This package provides IPC functionality for Roo Code, allowing external applications to communicate with the extension through a socket-based interface. + +## Available Commands + +The IPC interface supports the following task commands: + +### StartNewTask + +Starts a new task with optional configuration and initial message. + +**Parameters:** + +- `configuration`: RooCode settings object +- `text`: Initial task message (string) +- `images`: Array of image data URIs (optional) +- `newTab`: Whether to open in a new tab (boolean, optional) + +### CancelTask + +Cancels a running task. + +**Parameters:** + +- `data`: Task ID to cancel (string) + +### CloseTask + +Closes a task and performs cleanup. + +**Parameters:** + +- `data`: Task ID to close (string) + +### ResumeTask + +Resumes a task from history. + +**Parameters:** + +- `data`: Task ID to resume (string) + +**Error Handling:** + +- If the task ID is not found in history, the command will fail gracefully without crashing the IPC server +- Errors are logged for debugging purposes but do not propagate to the client + +## Usage Example + +```typescript +import { IpcClient } from "@roo-code/ipc" + +const client = new IpcClient("/path/to/socket") + +// Resume a task +client.sendCommand({ + commandName: "ResumeTask", + data: "task-123", +}) + +// Start a new task +client.sendCommand({ + commandName: "StartNewTask", + data: { + configuration: { + /* RooCode settings */ + }, + text: "Hello, world!", + images: [], + newTab: false, + }, +}) +``` + +## Events + +The IPC interface also emits task events that clients can listen to: + +- `TaskStarted`: When a task begins +- `TaskCompleted`: When a task finishes +- `TaskAborted`: When a task is cancelled +- `Message`: When a task sends a message + +## Socket Path + +The socket path is typically located in the system's temporary directory and follows the pattern: + +- Unix/Linux/macOS: `/tmp/roo-code-{id}.sock` +- Windows: `\\.\pipe\roo-code-{id}` diff --git a/packages/types/npm/package.metadata.json b/packages/types/npm/package.metadata.json index e33d5530af..3de8670447 100644 --- a/packages/types/npm/package.metadata.json +++ b/packages/types/npm/package.metadata.json @@ -1,6 +1,6 @@ { "name": "@roo-code/types", - "version": "1.49.0", + "version": "1.50.0", "description": "TypeScript type definitions for Roo Code.", "publishConfig": { "access": "public", diff --git a/packages/types/src/__tests__/ipc.test.ts b/packages/types/src/__tests__/ipc.test.ts new file mode 100644 index 0000000000..a2b4429356 --- /dev/null +++ b/packages/types/src/__tests__/ipc.test.ts @@ -0,0 +1,75 @@ +import { describe, it, expect } from "vitest" +import { TaskCommandName, taskCommandSchema } from "../ipc.js" + +describe("IPC Types", () => { + describe("TaskCommandName", () => { + it("should include ResumeTask command", () => { + expect(TaskCommandName.ResumeTask).toBe("ResumeTask") + }) + + it("should have all expected task commands", () => { + const expectedCommands = ["StartNewTask", "CancelTask", "CloseTask", "ResumeTask"] + const actualCommands = Object.values(TaskCommandName) + + expectedCommands.forEach((command) => { + expect(actualCommands).toContain(command) + }) + }) + + describe("Error Handling", () => { + it("should handle ResumeTask command gracefully when task not found", () => { + // This test verifies the schema validation - the actual error handling + // for invalid task IDs is tested at the API level, not the schema level + const resumeTaskCommand = { + commandName: TaskCommandName.ResumeTask, + data: "non-existent-task-id", + } + + const result = taskCommandSchema.safeParse(resumeTaskCommand) + expect(result.success).toBe(true) + + if (result.success) { + expect(result.data.commandName).toBe("ResumeTask") + expect(result.data.data).toBe("non-existent-task-id") + } + }) + }) + }) + + describe("taskCommandSchema", () => { + it("should validate ResumeTask command with taskId", () => { + const resumeTaskCommand = { + commandName: TaskCommandName.ResumeTask, + data: "task-123", + } + + const result = taskCommandSchema.safeParse(resumeTaskCommand) + expect(result.success).toBe(true) + + if (result.success) { + expect(result.data.commandName).toBe("ResumeTask") + expect(result.data.data).toBe("task-123") + } + }) + + it("should reject ResumeTask command with invalid data", () => { + const invalidCommand = { + commandName: TaskCommandName.ResumeTask, + data: 123, // Should be string + } + + const result = taskCommandSchema.safeParse(invalidCommand) + expect(result.success).toBe(false) + }) + + it("should reject ResumeTask command without data", () => { + const invalidCommand = { + commandName: TaskCommandName.ResumeTask, + // Missing data field + } + + const result = taskCommandSchema.safeParse(invalidCommand) + expect(result.success).toBe(false) + }) + }) +}) diff --git a/packages/types/src/ipc.ts b/packages/types/src/ipc.ts index 22cba1dea8..ace39c3f2b 100644 --- a/packages/types/src/ipc.ts +++ b/packages/types/src/ipc.ts @@ -44,6 +44,7 @@ export enum TaskCommandName { StartNewTask = "StartNewTask", CancelTask = "CancelTask", CloseTask = "CloseTask", + ResumeTask = "ResumeTask", } /** @@ -68,6 +69,10 @@ export const taskCommandSchema = z.discriminatedUnion("commandName", [ commandName: z.literal(TaskCommandName.CloseTask), data: z.string(), }), + z.object({ + commandName: z.literal(TaskCommandName.ResumeTask), + data: z.string(), + }), ]) export type TaskCommand = z.infer diff --git a/src/extension/api.ts b/src/extension/api.ts index f419613b8e..2fc51e7afb 100644 --- a/src/extension/api.ts +++ b/src/extension/api.ts @@ -78,6 +78,17 @@ export class API extends EventEmitter implements RooCodeAPI { await vscode.commands.executeCommand("workbench.action.files.saveFiles") await vscode.commands.executeCommand("workbench.action.closeWindow") break + case TaskCommandName.ResumeTask: + this.log(`[API] ResumeTask -> ${data}`) + try { + await this.resumeTask(data) + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error) + this.log(`[API] ResumeTask failed for taskId ${data}: ${errorMessage}`) + // Don't rethrow - we want to prevent IPC server crashes + // The error is logged for debugging purposes + } + break } }) }