diff --git a/packages/coding-agent/docs/refactor.md b/packages/coding-agent/docs/refactor.md index a1206ef1..8dfd915f 100644 --- a/packages/coding-agent/docs/refactor.md +++ b/packages/coding-agent/docs/refactor.md @@ -1308,8 +1308,8 @@ export async function runRpcMode(session: AgentSession): Promise { 1. `npm run check` passes 2. Manual test: RPC mode still works (if you have a way to test it) -- [ ] Create `src/modes/rpc-mode.ts` -- [ ] Verify with `npm run check` +- [x] Create `src/modes/rpc-mode.ts` +- [x] Verify with `npm run check` --- diff --git a/packages/coding-agent/src/modes/rpc-mode.ts b/packages/coding-agent/src/modes/rpc-mode.ts new file mode 100644 index 00000000..4986b903 --- /dev/null +++ b/packages/coding-agent/src/modes/rpc-mode.ts @@ -0,0 +1,84 @@ +/** + * RPC mode: Headless operation with JSON stdin/stdout protocol. + * + * Used for embedding the agent in other applications. + * Receives commands as JSON on stdin, outputs events as JSON on stdout. + */ + +import * as readline from "readline"; +import type { AgentSession } from "../core/agent-session.js"; + +/** + * Run in RPC mode. + * Listens for JSON commands on stdin, outputs events on stdout. + * + * Commands: + * - { type: "prompt", message: string, attachments?: Attachment[] } + * - { type: "abort" } + * - { type: "compact", customInstructions?: string } + * - { type: "bash", command: string } + * + * Events are output as JSON lines (same format as session manager). + */ +export async function runRpcMode(session: AgentSession): Promise { + // Output all agent events as JSON + session.subscribe((event) => { + console.log(JSON.stringify(event)); + }); + + // Listen for JSON input + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + terminal: false, + }); + + rl.on("line", async (line: string) => { + try { + const input = JSON.parse(line); + + switch (input.type) { + case "prompt": + if (input.message) { + await session.prompt(input.message, { + attachments: input.attachments, + expandSlashCommands: false, // RPC mode doesn't expand slash commands + }); + } + break; + + case "abort": + await session.abort(); + break; + + case "compact": + try { + const result = await session.compact(input.customInstructions); + console.log(JSON.stringify({ type: "compaction", ...result })); + } catch (error: any) { + console.log(JSON.stringify({ type: "error", error: `Compaction failed: ${error.message}` })); + } + break; + + case "bash": + if (input.command) { + try { + const result = await session.executeBash(input.command); + console.log(JSON.stringify({ type: "bash_end", ...result })); + } catch (error: any) { + console.log(JSON.stringify({ type: "error", error: `Bash failed: ${error.message}` })); + } + } + break; + + default: + console.log(JSON.stringify({ type: "error", error: `Unknown command: ${input.type}` })); + } + } catch (error: any) { + console.log(JSON.stringify({ type: "error", error: error.message })); + } + }); + + // Keep process alive forever + return new Promise(() => {}); +}