mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 17:44:33 +00:00
refactor(test): share server chat event harness
This commit is contained in:
@@ -7,15 +7,18 @@ import {
|
|||||||
} from "./server-chat.js";
|
} from "./server-chat.js";
|
||||||
|
|
||||||
describe("agent event handler", () => {
|
describe("agent event handler", () => {
|
||||||
it("emits chat delta for assistant text-only events", () => {
|
function createHarness(params?: {
|
||||||
const nowSpy = vi.spyOn(Date, "now").mockReturnValue(1_000);
|
now?: number;
|
||||||
|
resolveSessionKeyForRun?: (runId: string) => string | undefined;
|
||||||
|
}) {
|
||||||
|
const nowSpy =
|
||||||
|
params?.now === undefined ? undefined : vi.spyOn(Date, "now").mockReturnValue(params.now);
|
||||||
const broadcast = vi.fn();
|
const broadcast = vi.fn();
|
||||||
const broadcastToConnIds = vi.fn();
|
const broadcastToConnIds = 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();
|
||||||
const toolEventRecipients = createToolEventRecipientRegistry();
|
const toolEventRecipients = createToolEventRecipientRegistry();
|
||||||
chatRunState.registry.add("run-1", { sessionKey: "session-1", clientRunId: "client-1" });
|
|
||||||
|
|
||||||
const handler = createAgentEventHandler({
|
const handler = createAgentEventHandler({
|
||||||
broadcast,
|
broadcast,
|
||||||
@@ -23,11 +26,29 @@ describe("agent event handler", () => {
|
|||||||
nodeSendToSession,
|
nodeSendToSession,
|
||||||
agentRunSeq,
|
agentRunSeq,
|
||||||
chatRunState,
|
chatRunState,
|
||||||
resolveSessionKeyForRun: () => undefined,
|
resolveSessionKeyForRun: params?.resolveSessionKeyForRun ?? (() => undefined),
|
||||||
clearAgentRunContext: vi.fn(),
|
clearAgentRunContext: vi.fn(),
|
||||||
toolEventRecipients,
|
toolEventRecipients,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
nowSpy,
|
||||||
|
broadcast,
|
||||||
|
broadcastToConnIds,
|
||||||
|
nodeSendToSession,
|
||||||
|
agentRunSeq,
|
||||||
|
chatRunState,
|
||||||
|
toolEventRecipients,
|
||||||
|
handler,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
it("emits chat delta for assistant text-only events", () => {
|
||||||
|
const { broadcast, nodeSendToSession, chatRunState, handler, nowSpy } = createHarness({
|
||||||
|
now: 1_000,
|
||||||
|
});
|
||||||
|
chatRunState.registry.add("run-1", { sessionKey: "session-1", clientRunId: "client-1" });
|
||||||
|
|
||||||
handler({
|
handler({
|
||||||
runId: "run-1",
|
runId: "run-1",
|
||||||
seq: 1,
|
seq: 1,
|
||||||
@@ -46,29 +67,14 @@ describe("agent event handler", () => {
|
|||||||
expect(payload.message?.content?.[0]?.text).toBe("Hello world");
|
expect(payload.message?.content?.[0]?.text).toBe("Hello world");
|
||||||
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);
|
||||||
nowSpy.mockRestore();
|
nowSpy?.mockRestore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not emit chat delta for NO_REPLY streaming text", () => {
|
it("does not emit chat delta for NO_REPLY streaming text", () => {
|
||||||
const nowSpy = vi.spyOn(Date, "now").mockReturnValue(1_000);
|
const { broadcast, nodeSendToSession, chatRunState, handler, nowSpy } = createHarness({
|
||||||
const broadcast = vi.fn();
|
now: 1_000,
|
||||||
const broadcastToConnIds = vi.fn();
|
|
||||||
const nodeSendToSession = vi.fn();
|
|
||||||
const agentRunSeq = new Map<string, number>();
|
|
||||||
const chatRunState = createChatRunState();
|
|
||||||
const toolEventRecipients = createToolEventRecipientRegistry();
|
|
||||||
chatRunState.registry.add("run-1", { sessionKey: "session-1", clientRunId: "client-1" });
|
|
||||||
|
|
||||||
const handler = createAgentEventHandler({
|
|
||||||
broadcast,
|
|
||||||
broadcastToConnIds,
|
|
||||||
nodeSendToSession,
|
|
||||||
agentRunSeq,
|
|
||||||
chatRunState,
|
|
||||||
resolveSessionKeyForRun: () => undefined,
|
|
||||||
clearAgentRunContext: vi.fn(),
|
|
||||||
toolEventRecipients,
|
|
||||||
});
|
});
|
||||||
|
chatRunState.registry.add("run-1", { sessionKey: "session-1", clientRunId: "client-1" });
|
||||||
|
|
||||||
handler({
|
handler({
|
||||||
runId: "run-1",
|
runId: "run-1",
|
||||||
@@ -82,29 +88,14 @@ describe("agent event handler", () => {
|
|||||||
expect(chatCalls).toHaveLength(0);
|
expect(chatCalls).toHaveLength(0);
|
||||||
const sessionChatCalls = nodeSendToSession.mock.calls.filter(([, event]) => event === "chat");
|
const sessionChatCalls = nodeSendToSession.mock.calls.filter(([, event]) => event === "chat");
|
||||||
expect(sessionChatCalls).toHaveLength(0);
|
expect(sessionChatCalls).toHaveLength(0);
|
||||||
nowSpy.mockRestore();
|
nowSpy?.mockRestore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not include NO_REPLY text in chat final message", () => {
|
it("does not include NO_REPLY text in chat final message", () => {
|
||||||
const nowSpy = vi.spyOn(Date, "now").mockReturnValue(2_000);
|
const { broadcast, nodeSendToSession, chatRunState, handler, nowSpy } = createHarness({
|
||||||
const broadcast = vi.fn();
|
now: 2_000,
|
||||||
const broadcastToConnIds = vi.fn();
|
|
||||||
const nodeSendToSession = vi.fn();
|
|
||||||
const agentRunSeq = new Map<string, number>();
|
|
||||||
const chatRunState = createChatRunState();
|
|
||||||
const toolEventRecipients = createToolEventRecipientRegistry();
|
|
||||||
chatRunState.registry.add("run-2", { sessionKey: "session-2", clientRunId: "client-2" });
|
|
||||||
|
|
||||||
const handler = createAgentEventHandler({
|
|
||||||
broadcast,
|
|
||||||
broadcastToConnIds,
|
|
||||||
nodeSendToSession,
|
|
||||||
agentRunSeq,
|
|
||||||
chatRunState,
|
|
||||||
resolveSessionKeyForRun: () => undefined,
|
|
||||||
clearAgentRunContext: vi.fn(),
|
|
||||||
toolEventRecipients,
|
|
||||||
});
|
});
|
||||||
|
chatRunState.registry.add("run-2", { sessionKey: "session-2", clientRunId: "client-2" });
|
||||||
|
|
||||||
handler({
|
handler({
|
||||||
runId: "run-2",
|
runId: "run-2",
|
||||||
@@ -128,33 +119,16 @@ describe("agent event handler", () => {
|
|||||||
expect(payload.message).toBeUndefined();
|
expect(payload.message).toBeUndefined();
|
||||||
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);
|
||||||
nowSpy.mockRestore();
|
nowSpy?.mockRestore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("cleans up agent run sequence tracking when lifecycle completes", () => {
|
it("cleans up agent run sequence tracking when lifecycle completes", () => {
|
||||||
const nowSpy = vi.spyOn(Date, "now").mockReturnValue(2_500);
|
const { agentRunSeq, chatRunState, handler, nowSpy } = createHarness({ now: 2_500 });
|
||||||
const broadcast = vi.fn();
|
|
||||||
const broadcastToConnIds = vi.fn();
|
|
||||||
const nodeSendToSession = vi.fn();
|
|
||||||
const agentRunSeq = new Map<string, number>();
|
|
||||||
const chatRunState = createChatRunState();
|
|
||||||
const toolEventRecipients = createToolEventRecipientRegistry();
|
|
||||||
chatRunState.registry.add("run-cleanup", {
|
chatRunState.registry.add("run-cleanup", {
|
||||||
sessionKey: "session-cleanup",
|
sessionKey: "session-cleanup",
|
||||||
clientRunId: "client-cleanup",
|
clientRunId: "client-cleanup",
|
||||||
});
|
});
|
||||||
|
|
||||||
const handler = createAgentEventHandler({
|
|
||||||
broadcast,
|
|
||||||
broadcastToConnIds,
|
|
||||||
nodeSendToSession,
|
|
||||||
agentRunSeq,
|
|
||||||
chatRunState,
|
|
||||||
resolveSessionKeyForRun: () => undefined,
|
|
||||||
clearAgentRunContext: vi.fn(),
|
|
||||||
toolEventRecipients,
|
|
||||||
});
|
|
||||||
|
|
||||||
handler({
|
handler({
|
||||||
runId: "run-cleanup",
|
runId: "run-cleanup",
|
||||||
seq: 1,
|
seq: 1,
|
||||||
@@ -174,31 +148,17 @@ describe("agent event handler", () => {
|
|||||||
|
|
||||||
expect(agentRunSeq.has("run-cleanup")).toBe(false);
|
expect(agentRunSeq.has("run-cleanup")).toBe(false);
|
||||||
expect(agentRunSeq.has("client-cleanup")).toBe(false);
|
expect(agentRunSeq.has("client-cleanup")).toBe(false);
|
||||||
nowSpy.mockRestore();
|
nowSpy?.mockRestore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("routes tool events only to registered recipients when verbose is enabled", () => {
|
it("routes tool events only to registered recipients when verbose is enabled", () => {
|
||||||
const broadcast = vi.fn();
|
const { broadcast, broadcastToConnIds, toolEventRecipients, handler } = createHarness({
|
||||||
const broadcastToConnIds = vi.fn();
|
resolveSessionKeyForRun: () => "session-1",
|
||||||
const nodeSendToSession = vi.fn();
|
});
|
||||||
const agentRunSeq = new Map<string, number>();
|
|
||||||
const chatRunState = createChatRunState();
|
|
||||||
const toolEventRecipients = createToolEventRecipientRegistry();
|
|
||||||
|
|
||||||
registerAgentRunContext("run-tool", { sessionKey: "session-1", verboseLevel: "on" });
|
registerAgentRunContext("run-tool", { sessionKey: "session-1", verboseLevel: "on" });
|
||||||
toolEventRecipients.add("run-tool", "conn-1");
|
toolEventRecipients.add("run-tool", "conn-1");
|
||||||
|
|
||||||
const handler = createAgentEventHandler({
|
|
||||||
broadcast,
|
|
||||||
broadcastToConnIds,
|
|
||||||
nodeSendToSession,
|
|
||||||
agentRunSeq,
|
|
||||||
chatRunState,
|
|
||||||
resolveSessionKeyForRun: () => "session-1",
|
|
||||||
clearAgentRunContext: vi.fn(),
|
|
||||||
toolEventRecipients,
|
|
||||||
});
|
|
||||||
|
|
||||||
handler({
|
handler({
|
||||||
runId: "run-tool",
|
runId: "run-tool",
|
||||||
seq: 1,
|
seq: 1,
|
||||||
@@ -213,27 +173,13 @@ describe("agent event handler", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("broadcasts tool events to WS recipients even when verbose is off, but skips node send", () => {
|
it("broadcasts tool events to WS recipients even when verbose is off, but skips node send", () => {
|
||||||
const broadcast = vi.fn();
|
const { broadcastToConnIds, nodeSendToSession, toolEventRecipients, handler } = createHarness({
|
||||||
const broadcastToConnIds = vi.fn();
|
resolveSessionKeyForRun: () => "session-1",
|
||||||
const nodeSendToSession = vi.fn();
|
});
|
||||||
const agentRunSeq = new Map<string, number>();
|
|
||||||
const chatRunState = createChatRunState();
|
|
||||||
const toolEventRecipients = createToolEventRecipientRegistry();
|
|
||||||
|
|
||||||
registerAgentRunContext("run-tool-off", { sessionKey: "session-1", verboseLevel: "off" });
|
registerAgentRunContext("run-tool-off", { sessionKey: "session-1", verboseLevel: "off" });
|
||||||
toolEventRecipients.add("run-tool-off", "conn-1");
|
toolEventRecipients.add("run-tool-off", "conn-1");
|
||||||
|
|
||||||
const handler = createAgentEventHandler({
|
|
||||||
broadcast,
|
|
||||||
broadcastToConnIds,
|
|
||||||
nodeSendToSession,
|
|
||||||
agentRunSeq,
|
|
||||||
chatRunState,
|
|
||||||
resolveSessionKeyForRun: () => "session-1",
|
|
||||||
clearAgentRunContext: vi.fn(),
|
|
||||||
toolEventRecipients,
|
|
||||||
});
|
|
||||||
|
|
||||||
handler({
|
handler({
|
||||||
runId: "run-tool-off",
|
runId: "run-tool-off",
|
||||||
seq: 1,
|
seq: 1,
|
||||||
@@ -251,27 +197,13 @@ describe("agent event handler", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("strips tool output when verbose is on", () => {
|
it("strips tool output when verbose is on", () => {
|
||||||
const broadcast = vi.fn();
|
const { broadcastToConnIds, toolEventRecipients, handler } = createHarness({
|
||||||
const broadcastToConnIds = vi.fn();
|
resolveSessionKeyForRun: () => "session-1",
|
||||||
const nodeSendToSession = vi.fn();
|
});
|
||||||
const agentRunSeq = new Map<string, number>();
|
|
||||||
const chatRunState = createChatRunState();
|
|
||||||
const toolEventRecipients = createToolEventRecipientRegistry();
|
|
||||||
|
|
||||||
registerAgentRunContext("run-tool-on", { sessionKey: "session-1", verboseLevel: "on" });
|
registerAgentRunContext("run-tool-on", { sessionKey: "session-1", verboseLevel: "on" });
|
||||||
toolEventRecipients.add("run-tool-on", "conn-1");
|
toolEventRecipients.add("run-tool-on", "conn-1");
|
||||||
|
|
||||||
const handler = createAgentEventHandler({
|
|
||||||
broadcast,
|
|
||||||
broadcastToConnIds,
|
|
||||||
nodeSendToSession,
|
|
||||||
agentRunSeq,
|
|
||||||
chatRunState,
|
|
||||||
resolveSessionKeyForRun: () => "session-1",
|
|
||||||
clearAgentRunContext: vi.fn(),
|
|
||||||
toolEventRecipients,
|
|
||||||
});
|
|
||||||
|
|
||||||
handler({
|
handler({
|
||||||
runId: "run-tool-on",
|
runId: "run-tool-on",
|
||||||
seq: 1,
|
seq: 1,
|
||||||
@@ -294,27 +226,13 @@ describe("agent event handler", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("keeps tool output when verbose is full", () => {
|
it("keeps tool output when verbose is full", () => {
|
||||||
const broadcast = vi.fn();
|
const { broadcastToConnIds, toolEventRecipients, handler } = createHarness({
|
||||||
const broadcastToConnIds = vi.fn();
|
resolveSessionKeyForRun: () => "session-1",
|
||||||
const nodeSendToSession = vi.fn();
|
});
|
||||||
const agentRunSeq = new Map<string, number>();
|
|
||||||
const chatRunState = createChatRunState();
|
|
||||||
const toolEventRecipients = createToolEventRecipientRegistry();
|
|
||||||
|
|
||||||
registerAgentRunContext("run-tool-full", { sessionKey: "session-1", verboseLevel: "full" });
|
registerAgentRunContext("run-tool-full", { sessionKey: "session-1", verboseLevel: "full" });
|
||||||
toolEventRecipients.add("run-tool-full", "conn-1");
|
toolEventRecipients.add("run-tool-full", "conn-1");
|
||||||
|
|
||||||
const handler = createAgentEventHandler({
|
|
||||||
broadcast,
|
|
||||||
broadcastToConnIds,
|
|
||||||
nodeSendToSession,
|
|
||||||
agentRunSeq,
|
|
||||||
chatRunState,
|
|
||||||
resolveSessionKeyForRun: () => "session-1",
|
|
||||||
clearAgentRunContext: vi.fn(),
|
|
||||||
toolEventRecipients,
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = { content: [{ type: "text", text: "secret" }] };
|
const result = { content: [{ type: "text", text: "secret" }] };
|
||||||
handler({
|
handler({
|
||||||
runId: "run-tool-full",
|
runId: "run-tool-full",
|
||||||
|
|||||||
Reference in New Issue
Block a user