Roo-Code/packages/ipc/src/ipc-client.ts
roomote[bot] d575295883
feat: add DeleteQueuedMessage IPC command (#11464)
* feat: add DeleteQueuedMessage IPC command for queue removal

* Delete .changeset/delete-queued-message-ipc.md

* fix: add try/catch to DeleteQueuedMessage IPC handler and early return in deleteQueuedMessage

---------

Co-authored-by: Roo Code <roomote@roocode.com>
Co-authored-by: Hannes Rudolph <hrudolph@gmail.com>
2026-02-18 10:42:48 -07:00

147 lines
3.1 KiB
TypeScript

import EventEmitter from "node:events"
import * as crypto from "node:crypto"
import ipc from "node-ipc"
import {
type TaskCommand,
type IpcClientEvents,
type IpcMessage,
IpcOrigin,
IpcMessageType,
TaskCommandName,
ipcMessageSchema,
} from "@roo-code/types"
export class IpcClient extends EventEmitter<IpcClientEvents> {
private readonly _socketPath: string
private readonly _id: string
private readonly _log: (...args: unknown[]) => void
private _isConnected = false
private _clientId?: string
constructor(socketPath: string, log = console.log) {
super()
this._socketPath = socketPath
this._id = `roo-code-evals-${crypto.randomBytes(6).toString("hex")}`
this._log = log
ipc.config.silent = true
ipc.connectTo(this._id, this.socketPath, () => {
ipc.of[this._id]?.on("connect", () => this.onConnect())
ipc.of[this._id]?.on("disconnect", () => this.onDisconnect())
ipc.of[this._id]?.on("message", (data) => this.onMessage(data))
})
}
private onConnect() {
if (this._isConnected) {
return
}
this.log("[client#onConnect]")
this._isConnected = true
this.emit(IpcMessageType.Connect)
}
private onDisconnect() {
if (!this._isConnected) {
return
}
this.log("[client#onDisconnect]")
this._isConnected = false
this.emit(IpcMessageType.Disconnect)
}
private onMessage(data: unknown) {
if (typeof data !== "object") {
this.log(`[client#onMessage] invalid data -> ${JSON.stringify(data)}`)
return
}
const result = ipcMessageSchema.safeParse(data)
if (!result.success) {
this.log(
`[client#onMessage] invalid payload -> ${JSON.stringify(result.error.issues)} -> ${JSON.stringify(data)}`,
)
return
}
const payload = result.data
if (payload.origin === IpcOrigin.Server) {
switch (payload.type) {
case IpcMessageType.Ack:
this._clientId = payload.data.clientId
this.emit(IpcMessageType.Ack, payload.data)
break
case IpcMessageType.TaskEvent:
this.emit(IpcMessageType.TaskEvent, payload.data)
break
}
}
}
private log(...args: unknown[]) {
this._log(...args)
}
public sendCommand(command: TaskCommand) {
const message: IpcMessage = {
type: IpcMessageType.TaskCommand,
origin: IpcOrigin.Client,
clientId: this._clientId!,
data: command,
}
this.sendMessage(message)
}
public sendTaskMessage(text?: string, images?: string[]) {
this.sendCommand({
commandName: TaskCommandName.SendMessage,
data: { text, images },
})
}
public deleteQueuedMessage(messageId: string) {
this.sendCommand({
commandName: TaskCommandName.DeleteQueuedMessage,
data: messageId,
})
}
public sendMessage(message: IpcMessage) {
ipc.of[this._id]?.emit("message", message)
}
public disconnect() {
try {
ipc.disconnect(this._id)
} catch (error) {
this.log(
`[client#disconnect] error disconnecting -> ${error instanceof Error ? error.message : String(error)}`,
)
}
}
public get socketPath() {
return this._socketPath
}
public get clientId() {
return this._clientId
}
public get isConnected() {
return this._isConnected
}
public get isReady() {
return this._isConnected && this._clientId !== undefined
}
}