mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-29 18:12:52 +00:00
feat(heartbeat): add configurable visibility for heartbeat responses
Add per-channel and per-account heartbeat visibility settings: - showOk: hide/show HEARTBEAT_OK messages (default: false) - showAlerts: hide/show alert messages (default: true) - useIndicator: emit typing indicator events (default: true) Config precedence: per-account > per-channel > channel-defaults > global This allows silencing routine heartbeat acks while still surfacing alerts when something needs attention.
This commit is contained in:
committed by
Peter Steinberger
parent
9b12275fe1
commit
f9cf508cff
@@ -3,6 +3,7 @@ import {
|
||||
resolveHeartbeatPrompt,
|
||||
stripHeartbeatToken,
|
||||
} from "../../auto-reply/heartbeat.js";
|
||||
import { HEARTBEAT_TOKEN } from "../../auto-reply/tokens.js";
|
||||
import { getReplyFromConfig } from "../../auto-reply/reply.js";
|
||||
import type { ReplyPayload } from "../../auto-reply/types.js";
|
||||
import { resolveWhatsAppHeartbeatRecipients } from "../../channels/plugins/whatsapp-heartbeat.js";
|
||||
@@ -13,7 +14,8 @@ import {
|
||||
resolveStorePath,
|
||||
updateSessionStore,
|
||||
} from "../../config/sessions.js";
|
||||
import { emitHeartbeatEvent } from "../../infra/heartbeat-events.js";
|
||||
import { emitHeartbeatEvent, resolveIndicatorType } from "../../infra/heartbeat-events.js";
|
||||
import { resolveHeartbeatVisibility } from "../../infra/heartbeat-visibility.js";
|
||||
import { getChildLogger } from "../../logging.js";
|
||||
import { normalizeMainKey } from "../../routing/session-key.js";
|
||||
import { sendMessageWhatsApp } from "../outbound.js";
|
||||
@@ -59,6 +61,11 @@ export async function runWebHeartbeatOnce(opts: {
|
||||
});
|
||||
|
||||
const cfg = cfgOverride ?? loadConfig();
|
||||
|
||||
// Resolve heartbeat visibility settings for WhatsApp
|
||||
const visibility = resolveHeartbeatVisibility({ cfg, channel: "whatsapp" });
|
||||
const heartbeatOkText = HEARTBEAT_TOKEN;
|
||||
|
||||
const sessionCfg = cfg.session;
|
||||
const sessionScope = sessionCfg?.scope ?? "per-sender";
|
||||
const mainKey = normalizeMainKey(sessionCfg?.mainKey);
|
||||
@@ -117,6 +124,8 @@ export async function runWebHeartbeatOnce(opts: {
|
||||
to,
|
||||
preview: overrideBody.slice(0, 160),
|
||||
hasMedia: false,
|
||||
channel: "whatsapp",
|
||||
indicatorType: visibility.useIndicator ? resolveIndicatorType("sent") : undefined,
|
||||
});
|
||||
heartbeatLogger.info(
|
||||
{
|
||||
@@ -131,6 +140,17 @@ export async function runWebHeartbeatOnce(opts: {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!visibility.showAlerts && !visibility.showOk && !visibility.useIndicator) {
|
||||
heartbeatLogger.info({ to, reason: "alerts-disabled" }, "heartbeat skipped");
|
||||
emitHeartbeatEvent({
|
||||
status: "skipped",
|
||||
to,
|
||||
reason: "alerts-disabled",
|
||||
channel: "whatsapp",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const replyResult = await replyResolver(
|
||||
{
|
||||
Body: resolveHeartbeatPrompt(cfg.agents?.defaults?.heartbeat?.prompt),
|
||||
@@ -155,7 +175,32 @@ export async function runWebHeartbeatOnce(opts: {
|
||||
},
|
||||
"heartbeat skipped",
|
||||
);
|
||||
emitHeartbeatEvent({ status: "ok-empty", to });
|
||||
let okSent = false;
|
||||
if (visibility.showOk) {
|
||||
if (dryRun) {
|
||||
whatsappHeartbeatLog.info(`[dry-run] heartbeat ok -> ${to}`);
|
||||
} else {
|
||||
const sendResult = await sender(to, heartbeatOkText, { verbose });
|
||||
okSent = true;
|
||||
heartbeatLogger.info(
|
||||
{
|
||||
to,
|
||||
messageId: sendResult.messageId,
|
||||
chars: heartbeatOkText.length,
|
||||
reason: "heartbeat-ok",
|
||||
},
|
||||
"heartbeat ok sent",
|
||||
);
|
||||
whatsappHeartbeatLog.info(`heartbeat ok sent to ${to} (id ${sendResult.messageId})`);
|
||||
}
|
||||
}
|
||||
emitHeartbeatEvent({
|
||||
status: "ok-empty",
|
||||
to,
|
||||
channel: "whatsapp",
|
||||
silent: !okSent,
|
||||
indicatorType: visibility.useIndicator ? resolveIndicatorType("ok-empty") : undefined,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -188,7 +233,32 @@ export async function runWebHeartbeatOnce(opts: {
|
||||
{ to, reason: "heartbeat-token", rawLength: replyPayload.text?.length },
|
||||
"heartbeat skipped",
|
||||
);
|
||||
emitHeartbeatEvent({ status: "ok-token", to });
|
||||
let okSent = false;
|
||||
if (visibility.showOk) {
|
||||
if (dryRun) {
|
||||
whatsappHeartbeatLog.info(`[dry-run] heartbeat ok -> ${to}`);
|
||||
} else {
|
||||
const sendResult = await sender(to, heartbeatOkText, { verbose });
|
||||
okSent = true;
|
||||
heartbeatLogger.info(
|
||||
{
|
||||
to,
|
||||
messageId: sendResult.messageId,
|
||||
chars: heartbeatOkText.length,
|
||||
reason: "heartbeat-ok",
|
||||
},
|
||||
"heartbeat ok sent",
|
||||
);
|
||||
whatsappHeartbeatLog.info(`heartbeat ok sent to ${to} (id ${sendResult.messageId})`);
|
||||
}
|
||||
}
|
||||
emitHeartbeatEvent({
|
||||
status: "ok-token",
|
||||
to,
|
||||
channel: "whatsapp",
|
||||
silent: !okSent,
|
||||
indicatorType: visibility.useIndicator ? resolveIndicatorType("ok-token") : undefined,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -197,6 +267,22 @@ export async function runWebHeartbeatOnce(opts: {
|
||||
}
|
||||
|
||||
const finalText = stripped.text || replyPayload.text || "";
|
||||
|
||||
// Check if alerts are disabled for WhatsApp
|
||||
if (!visibility.showAlerts) {
|
||||
heartbeatLogger.info({ to, reason: "alerts-disabled" }, "heartbeat skipped");
|
||||
emitHeartbeatEvent({
|
||||
status: "skipped",
|
||||
to,
|
||||
reason: "alerts-disabled",
|
||||
preview: finalText.slice(0, 200),
|
||||
channel: "whatsapp",
|
||||
hasMedia,
|
||||
indicatorType: visibility.useIndicator ? resolveIndicatorType("sent") : undefined,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (dryRun) {
|
||||
heartbeatLogger.info({ to, reason: "dry-run", chars: finalText.length }, "heartbeat dry-run");
|
||||
whatsappHeartbeatLog.info(`[dry-run] heartbeat -> ${to}: ${elide(finalText, 200)}`);
|
||||
@@ -209,6 +295,8 @@ export async function runWebHeartbeatOnce(opts: {
|
||||
to,
|
||||
preview: finalText.slice(0, 160),
|
||||
hasMedia,
|
||||
channel: "whatsapp",
|
||||
indicatorType: visibility.useIndicator ? resolveIndicatorType("sent") : undefined,
|
||||
});
|
||||
heartbeatLogger.info(
|
||||
{
|
||||
@@ -224,7 +312,13 @@ export async function runWebHeartbeatOnce(opts: {
|
||||
const reason = formatError(err);
|
||||
heartbeatLogger.warn({ to, error: reason }, "heartbeat failed");
|
||||
whatsappHeartbeatLog.warn(`heartbeat failed (${reason})`);
|
||||
emitHeartbeatEvent({ status: "failed", to, reason });
|
||||
emitHeartbeatEvent({
|
||||
status: "failed",
|
||||
to,
|
||||
reason,
|
||||
channel: "whatsapp",
|
||||
indicatorType: visibility.useIndicator ? resolveIndicatorType("failed") : undefined,
|
||||
});
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user