Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 20 additions & 20 deletions packages/ipc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,27 +48,27 @@ Resumes a task from history.

## Usage Example

The package exports only the server half. A client connects to the socket
`IpcServer` listens on and speaks the same `node-ipc` framing: a js-message
envelope (`{"type":"message","data":…}`) terminated by a form feed. The
extension exposes the server through `src/extension/api.ts` when
`ROO_CODE_IPC_SOCKET_PATH` is set; `scripts/agent-evals/ipc-client.ts` is the
in-repo reference client.

```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,
},
})
import { IpcMessageType, IpcOrigin, TaskCommandName } from "@roo-code/types"

socket.write(
JSON.stringify({
type: "message",
data: {
type: IpcMessageType.TaskCommand,
origin: IpcOrigin.Client,
clientId,
data: { commandName: TaskCommandName.ResumeTask, data: "task-123" },
},
}) + "\f",
)
```

## Events
Expand Down
2 changes: 1 addition & 1 deletion packages/ipc/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@roo-code/ipc",
"description": "IPC server and client for remote Roo Code access.",
"description": "IPC server for remote Roo Code access.",
"version": "0.0.1",
"type": "module",
"exports": "./src/index.ts",
Expand Down
1 change: 0 additions & 1 deletion packages/ipc/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export * from "./ipc-client.js"
export * from "./ipc-server.js"
147 changes: 0 additions & 147 deletions packages/ipc/src/ipc-client.ts

This file was deleted.

12 changes: 0 additions & 12 deletions packages/types/src/ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,18 +128,6 @@ export const ipcMessageSchema = z.discriminatedUnion("type", [

export type IpcMessage = z.infer<typeof ipcMessageSchema>

/**
* IpcClientEvents
*/

export type IpcClientEvents = {
[IpcMessageType.Connect]: []
[IpcMessageType.Disconnect]: []
[IpcMessageType.Ack]: [data: Ack]
[IpcMessageType.TaskCommand]: [data: TaskCommand]
[IpcMessageType.TaskEvent]: [data: TaskEvent]
}

/**
* IpcServerEvents
*/
Expand Down
75 changes: 75 additions & 0 deletions scripts/agent-evals/ipc-client.interop.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import assert from "node:assert/strict"
import fs from "node:fs/promises"
import { createRequire } from "node:module"
import os from "node:os"
import path from "node:path"
import { describe, it } from "node:test"
import { fileURLToPath, pathToFileURL } from "node:url"

import { IpcMessageType, IpcOrigin, RooCodeEventName, TaskCommandName, type TaskEvent } from "@roo-code/types"
import { IpcServer } from "@roo-code/ipc"
import { EvalIpcClient } from "./ipc-client.ts"

const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..")

// IpcServer keeps node-ipc's listening handle open and exposes no stop(), so the
// suite has to close it or the runner never exits. scripts/ cannot resolve
// node-ipc itself, and the ESM entry is the singleton IpcServer actually uses,
// so resolve it through packages/ipc and import that exact module URL.
const stopListening = async (): Promise<void> => {
const requireFromIpc = createRequire(path.join(repoRoot, "packages/ipc/package.json"))
const esmEntry = requireFromIpc.resolve("node-ipc").replace(/node-ipc\.cjs$/, "node-ipc.js")
const ipc = (await import(pathToFileURL(esmEntry).href)).default as { server: { stop?: () => void } | false }
ipc.server && ipc.server.stop?.()
}

const socketDir = async (): Promise<string> => fs.mkdtemp(path.join(os.tmpdir(), "agent-evals-interop-"))

const socketPathIn = (dir: string, name: string): string =>
process.platform === "win32" ? `agent-evals-interop-${process.pid}-${name}` : path.join(dir, `${name}.sock`)

const waitFor = async (check: () => boolean, timeoutMs = 5_000): Promise<void> => {
const deadline = Date.now() + timeoutMs
while (!check() && Date.now() < deadline) await new Promise((resolve) => setTimeout(resolve, 20))
}

// The extension runs the real IpcServer, so the launcher client has to speak its
// exact framing. This suite is the interop gate between the two halves.
describe("eval ipc client against the real extension server", () => {
it("handshakes with IpcServer and round-trips a command and a task event", async () => {
const dir = await socketDir()
const socketPath = socketPathIn(dir, "interop")
const server = new IpcServer(socketPath, () => {})
const commands: string[] = []
const events: TaskEvent[] = []

server.on(IpcMessageType.TaskCommand, (clientId, command) => {
commands.push(command.commandName)
server.send(clientId, {
type: IpcMessageType.TaskEvent,
origin: IpcOrigin.Server,
data: { eventName: RooCodeEventName.TaskStarted, payload: ["task-1"], taskId: 1 } satisfies TaskEvent,
})
})
server.listen()

const client = new EvalIpcClient(socketPath, () => {})
client.on(IpcMessageType.TaskEvent, (event) => events.push(event as TaskEvent))

try {
await waitFor(() => client.isReady)
assert.equal(client.isReady, true, "client must receive the server ack")

client.sendCommand({ commandName: TaskCommandName.CloseTask })
await waitFor(() => events.length > 0)

assert.deepEqual(commands, [TaskCommandName.CloseTask])
assert.equal(events.length, 1)
assert.equal(events[0]?.eventName, RooCodeEventName.TaskStarted)
} finally {
client.disconnect()
await stopListening()
await fs.rm(dir, { recursive: true, force: true })
}
})
})
Loading
Loading