apiVersion: v1 kind: ConfigMap metadata: name: agent-run-script namespace: agent-pod data: agent-run.js: | #!/usr/bin/env node // Spawns `pi -p --mode json` and relays its session-protocol events to // agent-hub (localhost:9090, same pod) line by line as they're emitted, so // agent-console sees the turn live instead of after the process exits. // // Usage: node agent-run.js [-- ...] const { spawn } = require("node:child_process"); const readline = require("node:readline"); const crypto = require("node:crypto"); const HUB = process.env.HUB_ADDR || "http://localhost:9090"; async function post(path, body) { await fetch(`${HUB}${path}`, { method: "POST", headers: { "Content-Type": "application/json" }, body, }).catch((err) => console.error(`hub post ${path} failed:`, err.message)); } // `rawLine` is already a full JSON object (one per pi --mode json stdout // line) -- embed it as-is rather than JSON.stringify-ing it into a nested // string, or the hub ends up storing an escaped string instead of an event. function postEvent(id, rawLine) { return post("/agent/event", `{"id":${JSON.stringify(id)},"event":${rawLine}}`); } async function main() { const args = process.argv.slice(2); const agent = args.shift(); if (!agent || args.length === 0) { console.error("usage: agent-run.js [pi args...] "); process.exit(1); } const id = crypto.randomUUID(); await post("/agent/start", JSON.stringify({ id, agent })); const child = spawn("pi", ["-p", "--mode", "json", ...args], { stdio: ["ignore", "pipe", "pipe"], }); // Track in-flight event posts so /agent/end can't race ahead of them -- // fetch() isn't awaited per-line (would serialize on network latency), but // 'close' must wait for all of them before reporting done. const pending = []; const rl = readline.createInterface({ input: child.stdout }); rl.on("line", (line) => { if (!line.trim()) return; try { JSON.parse(line); // pi emits one JSON object per line; validate before relay pending.push(postEvent(id, line)); } catch { // non-JSON stdout noise, ignore } }); child.stderr.on("data", (chunk) => process.stderr.write(chunk)); child.on("close", async (code) => { await Promise.all(pending); await post("/agent/end", JSON.stringify({ id, status: code === 0 ? "done" : "error" })); process.exit(code ?? 1); }); } main();