mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 20:05:46 -06:00
fix(heartbeat): explain target-none skips (#119689)
Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
This commit is contained in:
@@ -48,6 +48,37 @@ describe("heartbeat events", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("adds a delivery-disabled message to target-none events without changing the reason", () => {
|
||||
const listener = vi.fn();
|
||||
const unsubscribe = onHeartbeatEvent(listener);
|
||||
|
||||
emitHeartbeatEvent({ status: "skipped", reason: "target-none" });
|
||||
|
||||
const expected = {
|
||||
ts: 1767960000000,
|
||||
status: "skipped",
|
||||
reason: "target-none",
|
||||
message: "Heartbeat delivery is disabled by configuration (target: none).",
|
||||
};
|
||||
expect(getLastHeartbeatEvent()).toEqual(expected);
|
||||
expect(listener).toHaveBeenCalledWith(expected);
|
||||
|
||||
unsubscribe();
|
||||
});
|
||||
|
||||
it("preserves an explicit message for target-none events", () => {
|
||||
emitHeartbeatEvent({
|
||||
status: "skipped",
|
||||
reason: "target-none",
|
||||
message: "custom diagnostic",
|
||||
});
|
||||
|
||||
expect(getLastHeartbeatEvent()).toMatchObject({
|
||||
reason: "target-none",
|
||||
message: "custom diagnostic",
|
||||
});
|
||||
});
|
||||
|
||||
it("delivers events to listeners, isolates listener failures, and supports unsubscribe", () => {
|
||||
const seen: string[] = [];
|
||||
const unsubscribeFirst = onHeartbeatEvent((evt) => {
|
||||
|
||||
@@ -4,6 +4,8 @@ import { notifyListeners, registerListener } from "../shared/listeners.js";
|
||||
|
||||
export type HeartbeatIndicatorType = "ok" | "alert" | "error";
|
||||
|
||||
const TARGET_NONE_MESSAGE = "Heartbeat delivery is disabled by configuration (target: none).";
|
||||
|
||||
export type HeartbeatEventPayload = {
|
||||
ts: number;
|
||||
status: "sent" | "ok-empty" | "ok-token" | "skipped" | "failed";
|
||||
@@ -13,6 +15,8 @@ export type HeartbeatEventPayload = {
|
||||
durationMs?: number;
|
||||
hasMedia?: boolean;
|
||||
reason?: string;
|
||||
/** Operator-facing companion to the machine-stable reason code. */
|
||||
message?: string;
|
||||
/** The channel this heartbeat was sent to. */
|
||||
channel?: string;
|
||||
/** Whether the message was silently suppressed (showOk: false). */
|
||||
@@ -51,7 +55,13 @@ const state = resolveGlobalSingleton<HeartbeatEventState>(HEARTBEAT_EVENT_STATE_
|
||||
}));
|
||||
|
||||
export function emitHeartbeatEvent(evt: Omit<HeartbeatEventPayload, "ts">) {
|
||||
const enriched: HeartbeatEventPayload = { ts: Date.now(), ...evt };
|
||||
const enriched: HeartbeatEventPayload = {
|
||||
ts: Date.now(),
|
||||
...evt,
|
||||
...(evt.reason === "target-none" && evt.message === undefined
|
||||
? { message: TARGET_NONE_MESSAGE }
|
||||
: {}),
|
||||
};
|
||||
state.lastHeartbeat = enriched;
|
||||
notifyListeners(state.listeners, enriched);
|
||||
}
|
||||
|
||||
@@ -406,12 +406,15 @@ describe("Gateway task and automation RPCs", () => {
|
||||
ts: number;
|
||||
status: string;
|
||||
reason?: string;
|
||||
message?: string;
|
||||
preview?: string;
|
||||
}>("last-heartbeat", {});
|
||||
return (
|
||||
lastHeartbeat.ts >= wakeRequestedAt &&
|
||||
lastHeartbeat.status === "skipped" &&
|
||||
lastHeartbeat.reason === "target-none" &&
|
||||
lastHeartbeat.message ===
|
||||
"Heartbeat delivery is disabled by configuration (target: none)." &&
|
||||
lastHeartbeat.preview === `Heartbeat handled: ${wakeText}`
|
||||
);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user