fix(heartbeat): explain target-none skips (#119689)

Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
This commit is contained in:
metaforismo
2026-08-07 03:51:29 +02:00
committed by GitHub
parent 804ae7f121
commit 89173ea9c2
3 changed files with 45 additions and 1 deletions
+31
View File
@@ -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) => {
+11 -1
View File
@@ -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}`
);
},