From 83efe8ef9eccb23fa16d383372cb9708c0ec7f8f Mon Sep 17 00:00:00 2001 From: Story Crater Bot <19826264+Riotpiaole@users.noreply.github.com> Date: Sun, 16 Aug 2026 09:06:46 -0700 Subject: [PATCH] fix(agent-pod): stop double-encoding events in agent-run.js JSON.stringify({id, event: line}) treated the already-JSON `line` as a plain string, so every event landed in the hub double-encoded (a JSON string containing escaped JSON, not an object) -- agent-console's json.Unmarshal into a struct silently failed on every single event. Now the raw JSON line is spliced directly into the request body. Also await all in-flight event posts before posting /agent/end, since those POSTs were fire-and-forget and could reorder past it on the wire. --- k8s/apps/agent-pod/agent-run-configmap.yaml | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/k8s/apps/agent-pod/agent-run-configmap.yaml b/k8s/apps/agent-pod/agent-run-configmap.yaml index e1319c2..83277b8 100644 --- a/k8s/apps/agent-pod/agent-run-configmap.yaml +++ b/k8s/apps/agent-pod/agent-run-configmap.yaml @@ -21,10 +21,17 @@ data: await fetch(`${HUB}${path}`, { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify(body), + 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(); @@ -34,18 +41,22 @@ data: } const id = crypto.randomUUID(); - await post("/agent/start", { id, agent }); + 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 - post("/agent/event", { id, event: line }); + pending.push(postEvent(id, line)); } catch { // non-JSON stdout noise, ignore } @@ -54,7 +65,8 @@ data: child.stderr.on("data", (chunk) => process.stderr.write(chunk)); child.on("close", async (code) => { - await post("/agent/end", { id, status: code === 0 ? "done" : "error" }); + await Promise.all(pending); + await post("/agent/end", JSON.stringify({ id, status: code === 0 ? "done" : "error" })); process.exit(code ?? 1); }); }