Add an API for resuming tasks by ID (#7122)

This commit is contained in:
Matt Rubens 2025-08-15 12:03:58 -04:00 committed by GitHub
parent 44086e4a86
commit 9a734d0cab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 182 additions and 1 deletions

90
packages/ipc/README.md Normal file
View file

@ -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}`

View file

@ -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",

View file

@ -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)
})
})
})

View file

@ -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<typeof taskCommandSchema>

View file

@ -78,6 +78,17 @@ export class API extends EventEmitter<RooCodeEvents> 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
}
})
}