mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-27 09:52:25 +00:00
136 lines
4.9 KiB
TypeScript
136 lines
4.9 KiB
TypeScript
import { beforeEach, describe, expect, it } from "vitest";
|
|
import { drainFormattedSystemEvents } from "../auto-reply/reply/session-updates.js";
|
|
import type { OpenClawConfig } from "../config/config.js";
|
|
import { resolveMainSessionKey } from "../config/sessions.js";
|
|
import { isCronSystemEvent } from "./heartbeat-runner.js";
|
|
import { enqueueSystemEvent, peekSystemEvents, resetSystemEventsForTest } from "./system-events.js";
|
|
|
|
const cfg = {} as unknown as OpenClawConfig;
|
|
const mainKey = resolveMainSessionKey(cfg);
|
|
|
|
describe("system events (session routing)", () => {
|
|
beforeEach(() => {
|
|
resetSystemEventsForTest();
|
|
});
|
|
|
|
it("does not leak session-scoped events into main", async () => {
|
|
enqueueSystemEvent("Discord reaction added: ✅", {
|
|
sessionKey: "discord:group:123",
|
|
contextKey: "discord:reaction:added:msg:user:✅",
|
|
});
|
|
|
|
expect(peekSystemEvents(mainKey)).toEqual([]);
|
|
expect(peekSystemEvents("discord:group:123")).toEqual(["Discord reaction added: ✅"]);
|
|
|
|
// Main session gets no events — undefined returned
|
|
const main = await drainFormattedSystemEvents({
|
|
cfg,
|
|
sessionKey: mainKey,
|
|
isMainSession: true,
|
|
isNewSession: false,
|
|
});
|
|
expect(main).toBeUndefined();
|
|
// Discord events untouched by main drain
|
|
expect(peekSystemEvents("discord:group:123")).toEqual(["Discord reaction added: ✅"]);
|
|
|
|
// Discord session gets its own events block
|
|
const discord = await drainFormattedSystemEvents({
|
|
cfg,
|
|
sessionKey: "discord:group:123",
|
|
isMainSession: false,
|
|
isNewSession: false,
|
|
});
|
|
expect(discord).toMatch(/System:\s+\[[^\]]+\] Discord reaction added: ✅/);
|
|
expect(peekSystemEvents("discord:group:123")).toEqual([]);
|
|
});
|
|
|
|
it("requires an explicit session key", () => {
|
|
expect(() => enqueueSystemEvent("Node: Mac Studio", { sessionKey: " " })).toThrow("sessionKey");
|
|
});
|
|
|
|
it("returns false for consecutive duplicate events", () => {
|
|
const first = enqueueSystemEvent("Node connected", { sessionKey: "agent:main:main" });
|
|
const second = enqueueSystemEvent("Node connected", { sessionKey: "agent:main:main" });
|
|
|
|
expect(first).toBe(true);
|
|
expect(second).toBe(false);
|
|
});
|
|
|
|
it("filters heartbeat/noise lines, returning undefined", async () => {
|
|
const key = "agent:main:test-heartbeat-filter";
|
|
enqueueSystemEvent("Read HEARTBEAT.md before continuing", { sessionKey: key });
|
|
enqueueSystemEvent("heartbeat poll: pending", { sessionKey: key });
|
|
enqueueSystemEvent("reason periodic: 5m", { sessionKey: key });
|
|
|
|
const result = await drainFormattedSystemEvents({
|
|
cfg,
|
|
sessionKey: key,
|
|
isMainSession: false,
|
|
isNewSession: false,
|
|
});
|
|
expect(result).toBeUndefined();
|
|
expect(peekSystemEvents(key)).toEqual([]);
|
|
});
|
|
|
|
it("prefixes every line of a multi-line event", async () => {
|
|
const key = "agent:main:test-multiline";
|
|
enqueueSystemEvent("Post-compaction context:\nline one\nline two", { sessionKey: key });
|
|
|
|
const result = await drainFormattedSystemEvents({
|
|
cfg,
|
|
sessionKey: key,
|
|
isMainSession: false,
|
|
isNewSession: false,
|
|
});
|
|
expect(result).toBeDefined();
|
|
const lines = result!.split("\n");
|
|
expect(lines.length).toBeGreaterThan(0);
|
|
for (const line of lines) {
|
|
expect(line).toMatch(/^System:/);
|
|
}
|
|
});
|
|
|
|
it("scrubs node last-input suffix", async () => {
|
|
const key = "agent:main:test-node-scrub";
|
|
enqueueSystemEvent("Node: Mac Studio · last input /tmp/secret.txt", { sessionKey: key });
|
|
|
|
const result = await drainFormattedSystemEvents({
|
|
cfg,
|
|
sessionKey: key,
|
|
isMainSession: false,
|
|
isNewSession: false,
|
|
});
|
|
expect(result).toContain("Node: Mac Studio");
|
|
expect(result).not.toContain("last input");
|
|
});
|
|
});
|
|
|
|
describe("isCronSystemEvent", () => {
|
|
it("returns false for empty entries", () => {
|
|
expect(isCronSystemEvent("")).toBe(false);
|
|
expect(isCronSystemEvent(" ")).toBe(false);
|
|
});
|
|
|
|
it("returns false for heartbeat ack markers", () => {
|
|
expect(isCronSystemEvent("HEARTBEAT_OK")).toBe(false);
|
|
expect(isCronSystemEvent("HEARTBEAT_OK 🦞")).toBe(false);
|
|
expect(isCronSystemEvent("heartbeat_ok")).toBe(false);
|
|
expect(isCronSystemEvent("HEARTBEAT_OK:")).toBe(false);
|
|
expect(isCronSystemEvent("HEARTBEAT_OK, continue")).toBe(false);
|
|
});
|
|
|
|
it("returns false for heartbeat poll and wake noise", () => {
|
|
expect(isCronSystemEvent("heartbeat poll: pending")).toBe(false);
|
|
expect(isCronSystemEvent("heartbeat wake complete")).toBe(false);
|
|
});
|
|
|
|
it("returns false for exec completion events", () => {
|
|
expect(isCronSystemEvent("Exec finished (gateway id=abc, code 0)")).toBe(false);
|
|
});
|
|
|
|
it("returns true for real cron reminder content", () => {
|
|
expect(isCronSystemEvent("Reminder: Check Base Scout results")).toBe(true);
|
|
expect(isCronSystemEvent("Send weekly status update to the team")).toBe(true);
|
|
});
|
|
});
|