diff --git a/src/gateway/node-registry.test.ts b/src/gateway/node-registry.test.ts index c42e5d82de6..8b945723d31 100644 --- a/src/gateway/node-registry.test.ts +++ b/src/gateway/node-registry.test.ts @@ -493,8 +493,16 @@ describe("gateway/node-registry", () => { const frames: string[] = []; registry.register(makeClient("conn-1", "node-1", frames), {}); const payload = serializeEventPayload({ foo: "bar" }); + const nullPayload = serializeEventPayload(null); + const falsePayload = serializeEventPayload(false); + const zeroPayload = serializeEventPayload(0); + const emptyStringPayload = serializeEventPayload(""); expect(registry.sendEventRaw("node-1", "chat", payload)).toBe(true); + expect(registry.sendEventRaw("node-1", "nullish", nullPayload)).toBe(true); + expect(registry.sendEventRaw("node-1", "flag", falsePayload)).toBe(true); + expect(registry.sendEventRaw("node-1", "count", zeroPayload)).toBe(true); + expect(registry.sendEventRaw("node-1", "empty", emptyStringPayload)).toBe(true); expect(registry.sendEventRaw("missing-node", "chat", payload)).toBe(false); expect(registry.sendEventRaw("node-1", "heartbeat", null)).toBe(true); expect( @@ -514,6 +522,10 @@ describe("gateway/node-registry", () => { expect(frames).toEqual([ '{"type":"event","event":"chat","payload":{"foo":"bar"}}', + '{"type":"event","event":"nullish","payload":null}', + '{"type":"event","event":"flag","payload":false}', + '{"type":"event","event":"count","payload":0}', + '{"type":"event","event":"empty","payload":""}', '{"type":"event","event":"heartbeat"}', ]); }); diff --git a/src/gateway/node-registry.ts b/src/gateway/node-registry.ts index da2118236b5..28971802757 100644 --- a/src/gateway/node-registry.ts +++ b/src/gateway/node-registry.ts @@ -88,7 +88,7 @@ export type SerializedEventPayload = { }; export function serializeEventPayload(payload: unknown): SerializedEventPayload | null { - if (!payload) { + if (payload === undefined) { return null; } const json = JSON.stringify(payload); diff --git a/src/node-host/invoke.sanitize-env.test.ts b/src/node-host/invoke.sanitize-env.test.ts index 8acbde2b90c..e4adce792d6 100644 --- a/src/node-host/invoke.sanitize-env.test.ts +++ b/src/node-host/invoke.sanitize-env.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest"; import { parseWindowsCodePage } from "../infra/windows-encoding.js"; import { withEnv } from "../test-utils/env.js"; import { decodeCapturedOutputBuffer, sanitizeEnv } from "./invoke.js"; -import { buildNodeInvokeResultParams } from "./runner.js"; +import { buildNodeEventParams, buildNodeInvokeResultParams } from "./runner.js"; describe("node-host sanitizeEnv", () => { it("ignores PATH overrides", () => { @@ -121,3 +121,28 @@ describe("buildNodeInvokeResultParams", () => { expect(params.payload).toEqual({ reason: "bad" }); }); }); + +describe("buildNodeEventParams", () => { + it("serializes explicit falsy event payloads", () => { + expect(buildNodeEventParams("node.test", undefined)).toEqual({ + event: "node.test", + payloadJSON: null, + }); + expect(buildNodeEventParams("node.test", null)).toEqual({ + event: "node.test", + payloadJSON: "null", + }); + expect(buildNodeEventParams("node.test", false)).toEqual({ + event: "node.test", + payloadJSON: "false", + }); + expect(buildNodeEventParams("node.test", 0)).toEqual({ + event: "node.test", + payloadJSON: "0", + }); + expect(buildNodeEventParams("node.test", "")).toEqual({ + event: "node.test", + payloadJSON: '""', + }); + }); +}); diff --git a/src/node-host/invoke.ts b/src/node-host/invoke.ts index b99d6ac6596..a3b6d98523c 100644 --- a/src/node-host/invoke.ts +++ b/src/node-host/invoke.ts @@ -618,12 +618,20 @@ export function buildNodeInvokeResultParams( return params; } +export function buildNodeEventParams( + event: string, + payload: unknown, +): { event: string; payloadJSON: string | null } { + const payloadJSON = payload === undefined ? undefined : JSON.stringify(payload); + return { + event, + payloadJSON: typeof payloadJSON === "string" ? payloadJSON : null, + }; +} + async function sendNodeEvent(client: GatewayClient, event: string, payload: unknown) { try { - await client.request("node.event", { - event, - payloadJSON: payload ? JSON.stringify(payload) : null, - }); + await client.request("node.event", buildNodeEventParams(event, payload)); } catch { // ignore: node events are best-effort } diff --git a/src/node-host/runner.ts b/src/node-host/runner.ts index d99edab2f07..6e1de6a05d8 100644 --- a/src/node-host/runner.ts +++ b/src/node-host/runner.ts @@ -28,6 +28,7 @@ import { } from "./plugin-node-host.js"; export { buildNodeInvokeResultParams }; +export { buildNodeEventParams } from "./invoke.js"; type NodeHostRunOptions = { gatewayHost: string;