From a75295f35986dfeef9fbbc1911c5b229aca166e7 Mon Sep 17 00:00:00 2001 From: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Date: Wed, 22 Jul 2026 00:25:35 -0300 Subject: [PATCH] fix(a2a): conductor bridge parses the hub's real SSE wire format (id/type in frame, data={ts,payload}) --- src/lib/conductor/bridge.ts | 18 +++++++++--------- tests/unit/conductor-bridge-loop.test.ts | 4 +++- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/lib/conductor/bridge.ts b/src/lib/conductor/bridge.ts index 5839f9cb31..ed3396b472 100644 --- a/src/lib/conductor/bridge.ts +++ b/src/lib/conductor/bridge.ts @@ -145,10 +145,9 @@ export class SseParser { // ============ Connection loop ============ -/** Wire shape of one hub event (the SSE `data:` payload) — untrusted input, Zod-validated. */ -const hubEventSchema = z.object({ - id: z.string(), - type: z.string(), +/** Wire shape of the hub's SSE `data:` field — {ts, payload}; id/type live in the + * SSE frame fields (verified against the live hub, 2026-07-22). Untrusted input → Zod. */ +const hubDataSchema = z.object({ payload: z.record(z.string(), z.unknown()), }); @@ -194,15 +193,16 @@ export function createConductorBridge(opts: ConductorBridgeOptions): ConductorBr const { value, done } = await reader.read(); if (done) return; for (const frame of parser.push(decoder.decode(value, { stream: true }))) { - let parsed: z.infer; + if (!frame.id || !frame.event) continue; // keepalive/partial frame — nada a espelhar + let payload: Record; try { - parsed = hubEventSchema.parse(JSON.parse(frame.data)); + payload = hubDataSchema.parse(JSON.parse(frame.data)).payload; } catch { - log(`evento SSE malformado ignorado (id=${frame.id ?? "?"})`); + log(`evento SSE malformado ignorado (id=${frame.id})`); continue; } - applyConductorEvent(opts.tm, index, parsed); - opts.cursor.set(parsed.id); // persisted per event — replay covers any gap on reconnect + applyConductorEvent(opts.tm, index, { id: frame.id, type: frame.event, payload }); + opts.cursor.set(frame.id); // persisted per event — replay covers any gap on reconnect } } } diff --git a/tests/unit/conductor-bridge-loop.test.ts b/tests/unit/conductor-bridge-loop.test.ts index 37fb4d6abf..c03dafc493 100644 --- a/tests/unit/conductor-bridge-loop.test.ts +++ b/tests/unit/conductor-bridge-loop.test.ts @@ -24,8 +24,10 @@ test.afterEach(async () => { } }); +// Formato REAL do SSE do hub (verificado ao vivo 2026-07-22): id/type nos campos do +// frame; o `data:` carrega só {ts, payload}. const sse = (id: number, type: string, payload: Record) => - `id: ${id}\nevent: ${type}\ndata: ${JSON.stringify({ id: String(id), type, ts: "2026-07-22T00:00:00Z", payload })}\n\n`; + `id: ${id}\nevent: ${type}\ndata: ${JSON.stringify({ ts: "2026-07-22T00:00:00Z", payload })}\n\n`; interface FakeHub { url: string;