From 0a9b98ed6732a9cfb42f6e5f29862e4f9ed65598 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 7 Dec 2025 05:10:58 +0100 Subject: [PATCH] feat(cli): add stdin/stdout rpc loop for agent sends --- src/cli/program.ts | 74 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/src/cli/program.ts b/src/cli/program.ts index 399cfc943..1e3bfb814 100644 --- a/src/cli/program.ts +++ b/src/cli/program.ts @@ -216,6 +216,80 @@ Examples: } }); + program + .command("rpc") + .description("Run stdin/stdout JSON RPC loop for agent sends") + .action(async () => { + const { createInterface } = await import("node:readline"); + const rl = createInterface({ input: process.stdin, crlfDelay: Infinity }); + + const respond = (obj: unknown) => { + try { + console.log(JSON.stringify(obj)); + } catch (err) { + console.error(JSON.stringify({ type: "error", error: String(err) })); + } + }; + + rl.on("line", async (line: string) => { + if (!line.trim()) return; + try { + const cmd = JSON.parse(line); + if (cmd.type !== "send" || !cmd.text) { + respond({ type: "error", error: "unsupported command" }); + return; + } + + const logs: string[] = []; + const runtime = { + log: (msg: string) => logs.push(String(msg)), + error: (msg: string) => logs.push(String(msg)), + exit: (_code: number) => {}, + }; + + const opts: { + message: string; + to?: string; + sessionId?: string; + thinking?: string; + json: boolean; + } = { + message: String(cmd.text), + to: cmd.to ? String(cmd.to) : undefined, + sessionId: cmd.session ? String(cmd.session) : undefined, + thinking: cmd.thinking ? String(cmd.thinking) : undefined, + json: true, + }; + + try { + await agentCommand(opts, runtime, createDefaultDeps()); + const payload = extractPayload(logs); + respond({ type: "result", ok: true, payload }); + } catch (err) { + respond({ type: "error", error: String(err) }); + } + } catch (err) { + respond({ type: "error", error: `parse error: ${String(err)}` }); + } + }); + + const extractPayload = (logs: string[]) => { + for (const entry of logs.slice().reverse()) { + try { + const parsed = JSON.parse(entry); + if (parsed && typeof parsed === "object" && "payloads" in parsed) { + return parsed; + } + } catch { + // non-JSON log, ignore + } + } + return null; + }; + + await new Promise(() => {}); + }); + program .command("heartbeat") .description("Trigger a heartbeat or manual send once (web only, no tmux)")