fix: bridge missing rpc commands

This commit is contained in:
Cristina Poncela Cubeiro
2026-06-18 15:39:14 +02:00
parent f1d9f76298
commit 5cb528428a
7 changed files with 294 additions and 21 deletions
+31 -2
View File
@@ -1,4 +1,4 @@
import type { RpcCommand, RpcResponse } from "@earendil-works/pi-coding-agent";
import type { AgentSessionEvent, RpcCommand, RpcResponse } from "@earendil-works/pi-coding-agent";
import type { InstanceStatus } from "../types.ts";
export interface SpawnRequest {
@@ -29,12 +29,23 @@ export interface RpcRequest {
command: RpcCommand;
}
export interface AttachRequest {
type: "attach";
instanceId: string;
}
export interface AttachRpcRequest {
type: "attach_rpc";
command: RpcCommand;
}
export interface RequestMap {
spawn: SpawnRequest;
list: ListRequest;
stop: StopRequest;
status: StatusRequest;
rpc: RpcRequest;
attach: AttachRequest;
}
export type OrchestratorRequest = RequestMap[keyof RequestMap];
@@ -79,6 +90,21 @@ export interface RpcBridgeResponse extends ResponseBase {
response: RpcResponse;
}
export interface AttachReadyResponse extends ResponseBase {
type: "attach_ready";
instance?: InstanceSummary;
}
export interface AttachEventResponse {
type: "attach_event";
event: AgentSessionEvent;
}
export interface AttachRpcResponse {
type: "attach_rpc_result";
response: RpcResponse;
}
export interface ErrorResponse extends ResponseBase {
type: "error";
ok: false;
@@ -91,9 +117,12 @@ export interface ResponseMap {
stop: StopResponse;
status: StatusResponse;
rpc: RpcBridgeResponse;
attach: AttachReadyResponse;
}
export type OrchestratorResponse = ResponseMap[keyof ResponseMap] | ErrorResponse;
export type AttachClientRequest = AttachRpcRequest;
export type AttachServerResponse = AttachReadyResponse | AttachEventResponse | AttachRpcResponse | ErrorResponse;
export type ResponseFor<T extends OrchestratorRequest> = T extends { type: infer K }
? K extends keyof ResponseMap
@@ -101,7 +130,7 @@ export type ResponseFor<T extends OrchestratorRequest> = T extends { type: infer
: ErrorResponse
: ErrorResponse;
export function encodeMessage(message: OrchestratorRequest | OrchestratorResponse): string {
export function encodeMessage(message: unknown): string {
return `${JSON.stringify(message)}\n`;
}
+72
View File
@@ -2,6 +2,10 @@ import { existsSync, unlinkSync } from "node:fs";
import { createConnection, createServer, type Server } from "node:net";
import { getSocketPath } from "../config.ts";
import {
type AttachClientRequest,
type AttachReadyResponse,
type AttachRequest,
type AttachRpcResponse,
type ErrorResponse,
encodeMessage,
type ListRequest,
@@ -25,7 +29,18 @@ export interface IpcRequestHandler {
(request: StopRequest): Promise<StopResponse | ErrorResponse> | StopResponse | ErrorResponse;
(request: StatusRequest): Promise<StatusResponse | ErrorResponse> | StatusResponse | ErrorResponse;
(request: RpcRequest): Promise<RpcBridgeResponse | ErrorResponse> | RpcBridgeResponse | ErrorResponse;
(request: AttachRequest): Promise<AttachReadyResponse | ErrorResponse> | AttachReadyResponse | ErrorResponse;
(request: OrchestratorRequest): Promise<OrchestratorResponse> | OrchestratorResponse;
attach(
instanceId: string,
onEvent: (response: AttachRpcResponse) => void,
onSessionEvent: (event: import("@earendil-works/pi-coding-agent").AgentSessionEvent) => void,
):
| {
handleRequest(request: AttachClientRequest): Promise<void>;
close(): void;
}
| undefined;
}
export async function startIpcServer(handler: IpcRequestHandler): Promise<Server> {
@@ -50,6 +65,63 @@ export async function startIpcServer(handler: IpcRequestHandler): Promise<Server
try {
const request = parseRequestLine(line);
if (request.type === "attach") {
const response = await handler(request);
if (!response.ok || !response.instance) {
socket.end(encodeMessage(response));
return;
}
const attachment = handler.attach(
request.instanceId,
(response) => {
socket.write(encodeMessage(response));
},
(event) => {
socket.write(encodeMessage({ type: "attach_event", event }));
},
);
if (!attachment) {
socket.end(
encodeMessage({ type: "error", ok: false, error: `Unknown instance: ${request.instanceId}` }),
);
return;
}
socket.write(encodeMessage(response));
socket.removeAllListeners("data");
socket.on("data", (attachChunk: Buffer | string) => {
buffer += attachChunk.toString();
for (;;) {
const attachNewlineIndex = buffer.indexOf("\n");
if (attachNewlineIndex === -1) {
break;
}
const attachLine = buffer.slice(0, attachNewlineIndex).trim();
buffer = buffer.slice(attachNewlineIndex + 1);
if (!attachLine) {
continue;
}
void (async () => {
try {
const attachRequest = JSON.parse(attachLine) as AttachClientRequest;
await attachment.handleRequest(attachRequest);
} catch (attachError) {
socket.write(
encodeMessage({
type: "error",
ok: false,
error: attachError instanceof Error ? attachError.message : String(attachError),
}),
);
}
})();
}
});
socket.once("close", () => attachment.close());
return;
}
const response = await handler(request);
socket.end(encodeMessage(response));
} catch (error) {