test(webchat): cover heartbeat visibility suppression
This commit is contained in:
parent
1284c3d868
commit
1a172ba873
@ -1,38 +1,42 @@
|
|||||||
import { describe, expect, it, vi } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
vi.mock("../config/config.js", () => ({
|
describe("agent event handler (webchat heartbeat visibility)", () => {
|
||||||
loadConfig: vi.fn(() => ({
|
it("suppresses HEARTBEAT_OK-only broadcasts to webchat when showOk is false (context on clientRunId)", async () => {
|
||||||
channels: {
|
vi.resetModules();
|
||||||
defaults: {
|
vi.doMock("../config/config.js", () => {
|
||||||
heartbeat: {
|
return {
|
||||||
showOk: false,
|
loadConfig: vi.fn(() => ({
|
||||||
},
|
agents: {
|
||||||
},
|
defaults: {
|
||||||
},
|
heartbeat: {
|
||||||
})),
|
ackMaxChars: 30,
|
||||||
}));
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
channels: {
|
||||||
|
defaults: {
|
||||||
|
heartbeat: {
|
||||||
|
showOk: false,
|
||||||
|
showAlerts: true,
|
||||||
|
useIndicator: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
vi.mock("../infra/heartbeat-visibility.js", () => ({
|
|
||||||
resolveHeartbeatVisibility: vi.fn(() => ({
|
|
||||||
showOk: false,
|
|
||||||
showAlerts: true,
|
|
||||||
useIndicator: true,
|
|
||||||
})),
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe("server-chat heartbeat visibility", () => {
|
|
||||||
it("suppresses webchat chat broadcast for heartbeat runs (even when clientRunId differs)", async () => {
|
|
||||||
const { createAgentEventHandler, createChatRunState } = await import("./server-chat.js");
|
|
||||||
const { registerAgentRunContext } = await import("../infra/agent-events.js");
|
const { registerAgentRunContext } = await import("../infra/agent-events.js");
|
||||||
|
const { createAgentEventHandler, createChatRunState } = await import("./server-chat.js");
|
||||||
|
|
||||||
const broadcast = vi.fn();
|
const broadcast = vi.fn();
|
||||||
const nodeSendToSession = vi.fn();
|
const nodeSendToSession = vi.fn();
|
||||||
const agentRunSeq = new Map<string, number>();
|
const agentRunSeq = new Map<string, number>();
|
||||||
const chatRunState = createChatRunState();
|
const chatRunState = createChatRunState();
|
||||||
|
|
||||||
// runId is the agent run id; clientRunId is what webchat uses in payloads.
|
|
||||||
chatRunState.registry.add("run-1", { sessionKey: "session-1", clientRunId: "client-1" });
|
chatRunState.registry.add("run-1", { sessionKey: "session-1", clientRunId: "client-1" });
|
||||||
registerAgentRunContext("run-1", { isHeartbeat: true });
|
|
||||||
|
// server-chat uses clientRunId for the broadcast payload.
|
||||||
|
registerAgentRunContext("client-1", { isHeartbeat: true });
|
||||||
|
|
||||||
const handler = createAgentEventHandler({
|
const handler = createAgentEventHandler({
|
||||||
broadcast,
|
broadcast,
|
||||||
@ -57,4 +61,118 @@ describe("server-chat heartbeat visibility", () => {
|
|||||||
const sessionChatCalls = nodeSendToSession.mock.calls.filter(([, event]) => event === "chat");
|
const sessionChatCalls = nodeSendToSession.mock.calls.filter(([, event]) => event === "chat");
|
||||||
expect(sessionChatCalls).toHaveLength(1);
|
expect(sessionChatCalls).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("suppresses when heartbeat context is only registered under agentRunId (clientRunId differs)", async () => {
|
||||||
|
vi.resetModules();
|
||||||
|
vi.doMock("../config/config.js", () => {
|
||||||
|
return {
|
||||||
|
loadConfig: vi.fn(() => ({
|
||||||
|
agents: {
|
||||||
|
defaults: {
|
||||||
|
heartbeat: {
|
||||||
|
ackMaxChars: 30,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
channels: {
|
||||||
|
defaults: {
|
||||||
|
heartbeat: {
|
||||||
|
showOk: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const { registerAgentRunContext } = await import("../infra/agent-events.js");
|
||||||
|
const { createAgentEventHandler, createChatRunState } = await import("./server-chat.js");
|
||||||
|
|
||||||
|
const broadcast = vi.fn();
|
||||||
|
const nodeSendToSession = vi.fn();
|
||||||
|
const agentRunSeq = new Map<string, number>();
|
||||||
|
const chatRunState = createChatRunState();
|
||||||
|
|
||||||
|
chatRunState.registry.add("run-1", { sessionKey: "session-1", clientRunId: "client-1" });
|
||||||
|
|
||||||
|
// This matches how agent-runner registers context (runId), while webchat may use a separate clientRunId.
|
||||||
|
registerAgentRunContext("run-1", { isHeartbeat: true });
|
||||||
|
|
||||||
|
const handler = createAgentEventHandler({
|
||||||
|
broadcast,
|
||||||
|
nodeSendToSession,
|
||||||
|
agentRunSeq,
|
||||||
|
chatRunState,
|
||||||
|
resolveSessionKeyForRun: () => undefined,
|
||||||
|
clearAgentRunContext: vi.fn(),
|
||||||
|
});
|
||||||
|
|
||||||
|
handler({
|
||||||
|
runId: "run-1",
|
||||||
|
seq: 1,
|
||||||
|
stream: "assistant",
|
||||||
|
ts: Date.now(),
|
||||||
|
data: { text: "HEARTBEAT_OK" },
|
||||||
|
});
|
||||||
|
|
||||||
|
const chatCalls = broadcast.mock.calls.filter(([event]) => event === "chat");
|
||||||
|
expect(chatCalls).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("still broadcasts non-HEARTBEAT_OK heartbeat alerts to webchat when showOk is false", async () => {
|
||||||
|
vi.resetModules();
|
||||||
|
vi.doMock("../config/config.js", () => {
|
||||||
|
return {
|
||||||
|
loadConfig: vi.fn(() => ({
|
||||||
|
agents: {
|
||||||
|
defaults: {
|
||||||
|
heartbeat: {
|
||||||
|
ackMaxChars: 30,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
channels: {
|
||||||
|
defaults: {
|
||||||
|
heartbeat: {
|
||||||
|
showOk: false,
|
||||||
|
showAlerts: true,
|
||||||
|
useIndicator: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const { registerAgentRunContext } = await import("../infra/agent-events.js");
|
||||||
|
const { createAgentEventHandler, createChatRunState } = await import("./server-chat.js");
|
||||||
|
|
||||||
|
const broadcast = vi.fn();
|
||||||
|
const nodeSendToSession = vi.fn();
|
||||||
|
const agentRunSeq = new Map<string, number>();
|
||||||
|
const chatRunState = createChatRunState();
|
||||||
|
chatRunState.registry.add("run-1", { sessionKey: "session-1", clientRunId: "client-1" });
|
||||||
|
|
||||||
|
registerAgentRunContext("client-1", { isHeartbeat: true });
|
||||||
|
|
||||||
|
const handler = createAgentEventHandler({
|
||||||
|
broadcast,
|
||||||
|
nodeSendToSession,
|
||||||
|
agentRunSeq,
|
||||||
|
chatRunState,
|
||||||
|
resolveSessionKeyForRun: () => undefined,
|
||||||
|
clearAgentRunContext: vi.fn(),
|
||||||
|
});
|
||||||
|
|
||||||
|
handler({
|
||||||
|
runId: "run-1",
|
||||||
|
seq: 1,
|
||||||
|
stream: "assistant",
|
||||||
|
ts: Date.now(),
|
||||||
|
data: { text: "ALERT: something happened" },
|
||||||
|
});
|
||||||
|
|
||||||
|
const chatCalls = broadcast.mock.calls.filter(([event]) => event === "chat");
|
||||||
|
expect(chatCalls).toHaveLength(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -39,9 +39,19 @@ function resolveWebchatHeartbeatPolicy(): WebchatHeartbeatPolicy {
|
|||||||
return policy;
|
return policy;
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldSuppressHeartbeatBroadcast(runId: string, text: string | undefined): boolean {
|
function resolveHeartbeatContextIsHeartbeat(runId: string, clientRunId: string): boolean {
|
||||||
|
const clientContext = getAgentRunContext(clientRunId);
|
||||||
|
if (clientContext?.isHeartbeat !== undefined) return clientContext.isHeartbeat;
|
||||||
const runContext = getAgentRunContext(runId);
|
const runContext = getAgentRunContext(runId);
|
||||||
if (!runContext?.isHeartbeat) return false;
|
return Boolean(runContext?.isHeartbeat);
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldSuppressHeartbeatBroadcast(
|
||||||
|
runId: string,
|
||||||
|
clientRunId: string,
|
||||||
|
text: string | undefined,
|
||||||
|
): boolean {
|
||||||
|
if (!resolveHeartbeatContextIsHeartbeat(runId, clientRunId)) return false;
|
||||||
|
|
||||||
const policy = resolveWebchatHeartbeatPolicy();
|
const policy = resolveWebchatHeartbeatPolicy();
|
||||||
if (policy.showOk) return false;
|
if (policy.showOk) return false;
|
||||||
@ -164,14 +174,6 @@ export function createAgentEventHandler({
|
|||||||
resolveSessionKeyForRun,
|
resolveSessionKeyForRun,
|
||||||
clearAgentRunContext,
|
clearAgentRunContext,
|
||||||
}: AgentEventHandlerOptions) {
|
}: AgentEventHandlerOptions) {
|
||||||
const webchatHeartbeatShowOk = resolveWebchatHeartbeatShowOk();
|
|
||||||
|
|
||||||
const shouldSuppressHeartbeatBroadcast = (agentRunId: string): boolean => {
|
|
||||||
const runContext = getAgentRunContext(agentRunId);
|
|
||||||
if (!runContext?.isHeartbeat) return false;
|
|
||||||
return !webchatHeartbeatShowOk;
|
|
||||||
};
|
|
||||||
|
|
||||||
const emitChatDelta = (
|
const emitChatDelta = (
|
||||||
sessionKey: string,
|
sessionKey: string,
|
||||||
clientRunId: string,
|
clientRunId: string,
|
||||||
@ -195,12 +197,8 @@ export function createAgentEventHandler({
|
|||||||
timestamp: now,
|
timestamp: now,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
<<<<<<< HEAD
|
|
||||||
// Suppress webchat broadcast for heartbeat runs when showOk is false
|
if (!shouldSuppressHeartbeatBroadcast(agentRunId, clientRunId, text)) {
|
||||||
if (!shouldSuppressHeartbeatBroadcast(agentRunId)) {
|
|
||||||
=======
|
|
||||||
if (!shouldSuppressHeartbeatBroadcast(clientRunId, text)) {
|
|
||||||
>>>>>>> 3f72ad7bd (fix(webchat): suppress heartbeat ok broadcasts; stabilize audit/status tests)
|
|
||||||
broadcast("chat", payload, { dropIfSlow: true });
|
broadcast("chat", payload, { dropIfSlow: true });
|
||||||
}
|
}
|
||||||
nodeSendToSession(sessionKey, "chat", payload);
|
nodeSendToSession(sessionKey, "chat", payload);
|
||||||
@ -231,12 +229,8 @@ export function createAgentEventHandler({
|
|||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
};
|
};
|
||||||
<<<<<<< HEAD
|
|
||||||
// Suppress webchat broadcast for heartbeat runs when showOk is false
|
if (!shouldSuppressHeartbeatBroadcast(agentRunId, clientRunId, text)) {
|
||||||
if (!shouldSuppressHeartbeatBroadcast(agentRunId)) {
|
|
||||||
=======
|
|
||||||
if (!shouldSuppressHeartbeatBroadcast(clientRunId, text)) {
|
|
||||||
>>>>>>> 3f72ad7bd (fix(webchat): suppress heartbeat ok broadcasts; stabilize audit/status tests)
|
|
||||||
broadcast("chat", payload);
|
broadcast("chat", payload);
|
||||||
}
|
}
|
||||||
nodeSendToSession(sessionKey, "chat", payload);
|
nodeSendToSession(sessionKey, "chat", payload);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user