mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-07 11:21:23 +00:00
* feat(heartbeat): add accountId config option for multi-agent routing Add optional accountId field to heartbeat configuration, allowing multi-agent setups to explicitly specify which Telegram account should be used for heartbeat delivery. Previously, heartbeat delivery would use the accountId from the session's deliveryContext. When a session had no prior conversation history, heartbeats would default to the first/primary account instead of the agent's intended bot. Changes: - Add accountId to HeartbeatSchema (zod-schema.agent-runtime.ts) - Use heartbeat.accountId with fallback to session accountId (targets.ts) Backward compatible: if accountId is not specified, behavior is unchanged. Closes #8695 * fix: improve heartbeat accountId routing (#8702) (thanks @lsh411) * fix: harden heartbeat accountId routing (#8702) (thanks @lsh411) * fix: expose heartbeat accountId in status (#8702) (thanks @lsh411) * chore: format status + heartbeat tests (#8702) (thanks @lsh411) --------- Co-authored-by: m1 16 512 <m116512@m1ui-MacBookAir-2.local> Co-authored-by: Gustavo Madeira Santana <gumadeiras@gmail.com>
59 lines
1.5 KiB
TypeScript
59 lines
1.5 KiB
TypeScript
export type HeartbeatIndicatorType = "ok" | "alert" | "error";
|
|
|
|
export type HeartbeatEventPayload = {
|
|
ts: number;
|
|
status: "sent" | "ok-empty" | "ok-token" | "skipped" | "failed";
|
|
to?: string;
|
|
accountId?: string;
|
|
preview?: string;
|
|
durationMs?: number;
|
|
hasMedia?: boolean;
|
|
reason?: string;
|
|
/** The channel this heartbeat was sent to. */
|
|
channel?: string;
|
|
/** Whether the message was silently suppressed (showOk: false). */
|
|
silent?: boolean;
|
|
/** Indicator type for UI status display. */
|
|
indicatorType?: HeartbeatIndicatorType;
|
|
};
|
|
|
|
export function resolveIndicatorType(
|
|
status: HeartbeatEventPayload["status"],
|
|
): HeartbeatIndicatorType | undefined {
|
|
switch (status) {
|
|
case "ok-empty":
|
|
case "ok-token":
|
|
return "ok";
|
|
case "sent":
|
|
return "alert";
|
|
case "failed":
|
|
return "error";
|
|
case "skipped":
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
let lastHeartbeat: HeartbeatEventPayload | null = null;
|
|
const listeners = new Set<(evt: HeartbeatEventPayload) => void>();
|
|
|
|
export function emitHeartbeatEvent(evt: Omit<HeartbeatEventPayload, "ts">) {
|
|
const enriched: HeartbeatEventPayload = { ts: Date.now(), ...evt };
|
|
lastHeartbeat = enriched;
|
|
for (const listener of listeners) {
|
|
try {
|
|
listener(enriched);
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
}
|
|
}
|
|
|
|
export function onHeartbeatEvent(listener: (evt: HeartbeatEventPayload) => void): () => void {
|
|
listeners.add(listener);
|
|
return () => listeners.delete(listener);
|
|
}
|
|
|
|
export function getLastHeartbeatEvent(): HeartbeatEventPayload | null {
|
|
return lastHeartbeat;
|
|
}
|