mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 07:57:39 +00:00
Gateway/TUI: filter heartbeat ACK noise in chat events
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { DEFAULT_HEARTBEAT_ACK_MAX_CHARS, stripHeartbeatToken } from "../auto-reply/heartbeat.js";
|
||||
import { normalizeVerboseLevel } from "../auto-reply/thinking.js";
|
||||
import { isSilentReplyText, SILENT_REPLY_TOKEN } from "../auto-reply/tokens.js";
|
||||
import { loadConfig } from "../config/config.js";
|
||||
@@ -6,12 +7,37 @@ import { resolveHeartbeatVisibility } from "../infra/heartbeat-visibility.js";
|
||||
import { loadSessionEntry } from "./session-utils.js";
|
||||
import { formatForLog } from "./ws-log.js";
|
||||
|
||||
function resolveHeartbeatAckMaxChars(): number {
|
||||
try {
|
||||
const cfg = loadConfig();
|
||||
return Math.max(
|
||||
0,
|
||||
cfg.agents?.defaults?.heartbeat?.ackMaxChars ?? DEFAULT_HEARTBEAT_ACK_MAX_CHARS,
|
||||
);
|
||||
} catch {
|
||||
return DEFAULT_HEARTBEAT_ACK_MAX_CHARS;
|
||||
}
|
||||
}
|
||||
|
||||
function resolveHeartbeatContext(runId: string, sourceRunId?: string) {
|
||||
const primary = getAgentRunContext(runId);
|
||||
if (primary?.isHeartbeat) {
|
||||
return primary;
|
||||
}
|
||||
if (sourceRunId && sourceRunId !== runId) {
|
||||
const source = getAgentRunContext(sourceRunId);
|
||||
if (source?.isHeartbeat) {
|
||||
return source;
|
||||
}
|
||||
}
|
||||
return primary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if webchat broadcasts should be suppressed for heartbeat runs.
|
||||
* Returns true if the run is a heartbeat and showOk is false.
|
||||
* Check if heartbeat ACK/noise should be hidden from interactive chat surfaces.
|
||||
*/
|
||||
function shouldSuppressHeartbeatBroadcast(runId: string): boolean {
|
||||
const runContext = getAgentRunContext(runId);
|
||||
function shouldHideHeartbeatChatOutput(runId: string, sourceRunId?: string): boolean {
|
||||
const runContext = resolveHeartbeatContext(runId, sourceRunId);
|
||||
if (!runContext?.isHeartbeat) {
|
||||
return false;
|
||||
}
|
||||
@@ -26,6 +52,28 @@ function shouldSuppressHeartbeatBroadcast(runId: string): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeHeartbeatChatFinalText(params: {
|
||||
runId: string;
|
||||
sourceRunId?: string;
|
||||
text: string;
|
||||
}): { suppress: boolean; text: string } {
|
||||
if (!shouldHideHeartbeatChatOutput(params.runId, params.sourceRunId)) {
|
||||
return { suppress: false, text: params.text };
|
||||
}
|
||||
|
||||
const stripped = stripHeartbeatToken(params.text, {
|
||||
mode: "heartbeat",
|
||||
maxAckChars: resolveHeartbeatAckMaxChars(),
|
||||
});
|
||||
if (!stripped.didStrip) {
|
||||
return { suppress: false, text: params.text };
|
||||
}
|
||||
if (stripped.shouldSkip) {
|
||||
return { suppress: true, text: "" };
|
||||
}
|
||||
return { suppress: false, text: stripped.text };
|
||||
}
|
||||
|
||||
export type ChatRunEntry = {
|
||||
sessionKey: string;
|
||||
clientRunId: string;
|
||||
@@ -228,11 +276,20 @@ export function createAgentEventHandler({
|
||||
clearAgentRunContext,
|
||||
toolEventRecipients,
|
||||
}: AgentEventHandlerOptions) {
|
||||
const emitChatDelta = (sessionKey: string, clientRunId: string, seq: number, text: string) => {
|
||||
const emitChatDelta = (
|
||||
sessionKey: string,
|
||||
clientRunId: string,
|
||||
sourceRunId: string,
|
||||
seq: number,
|
||||
text: string,
|
||||
) => {
|
||||
if (isSilentReplyText(text, SILENT_REPLY_TOKEN)) {
|
||||
return;
|
||||
}
|
||||
chatRunState.buffers.set(clientRunId, text);
|
||||
if (shouldHideHeartbeatChatOutput(clientRunId, sourceRunId)) {
|
||||
return;
|
||||
}
|
||||
const now = Date.now();
|
||||
const last = chatRunState.deltaSentAt.get(clientRunId) ?? 0;
|
||||
if (now - last < 150) {
|
||||
@@ -250,22 +307,27 @@ export function createAgentEventHandler({
|
||||
timestamp: now,
|
||||
},
|
||||
};
|
||||
// Suppress webchat broadcast for heartbeat runs when showOk is false
|
||||
if (!shouldSuppressHeartbeatBroadcast(clientRunId)) {
|
||||
broadcast("chat", payload, { dropIfSlow: true });
|
||||
}
|
||||
broadcast("chat", payload, { dropIfSlow: true });
|
||||
nodeSendToSession(sessionKey, "chat", payload);
|
||||
};
|
||||
|
||||
const emitChatFinal = (
|
||||
sessionKey: string,
|
||||
clientRunId: string,
|
||||
sourceRunId: string,
|
||||
seq: number,
|
||||
jobState: "done" | "error",
|
||||
error?: unknown,
|
||||
) => {
|
||||
const text = chatRunState.buffers.get(clientRunId)?.trim() ?? "";
|
||||
const shouldSuppressSilent = isSilentReplyText(text, SILENT_REPLY_TOKEN);
|
||||
const bufferedText = chatRunState.buffers.get(clientRunId)?.trim() ?? "";
|
||||
const normalizedHeartbeatText = normalizeHeartbeatChatFinalText({
|
||||
runId: clientRunId,
|
||||
sourceRunId,
|
||||
text: bufferedText,
|
||||
});
|
||||
const text = normalizedHeartbeatText.text.trim();
|
||||
const shouldSuppressSilent =
|
||||
normalizedHeartbeatText.suppress || isSilentReplyText(text, SILENT_REPLY_TOKEN);
|
||||
chatRunState.buffers.delete(clientRunId);
|
||||
chatRunState.deltaSentAt.delete(clientRunId);
|
||||
if (jobState === "done") {
|
||||
@@ -283,10 +345,7 @@ export function createAgentEventHandler({
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
// Suppress webchat broadcast for heartbeat runs when showOk is false
|
||||
if (!shouldSuppressHeartbeatBroadcast(clientRunId)) {
|
||||
broadcast("chat", payload);
|
||||
}
|
||||
broadcast("chat", payload);
|
||||
nodeSendToSession(sessionKey, "chat", payload);
|
||||
return;
|
||||
}
|
||||
@@ -388,7 +447,7 @@ export function createAgentEventHandler({
|
||||
nodeSendToSession(sessionKey, "agent", isToolEvent ? toolPayload : agentPayload);
|
||||
}
|
||||
if (!isAborted && evt.stream === "assistant" && typeof evt.data?.text === "string") {
|
||||
emitChatDelta(sessionKey, clientRunId, evt.seq, evt.data.text);
|
||||
emitChatDelta(sessionKey, clientRunId, evt.runId, evt.seq, evt.data.text);
|
||||
} else if (!isAborted && (lifecyclePhase === "end" || lifecyclePhase === "error")) {
|
||||
if (chatLink) {
|
||||
const finished = chatRunState.registry.shift(evt.runId);
|
||||
@@ -399,6 +458,7 @@ export function createAgentEventHandler({
|
||||
emitChatFinal(
|
||||
finished.sessionKey,
|
||||
finished.clientRunId,
|
||||
evt.runId,
|
||||
evt.seq,
|
||||
lifecyclePhase === "error" ? "error" : "done",
|
||||
evt.data?.error,
|
||||
@@ -407,6 +467,7 @@ export function createAgentEventHandler({
|
||||
emitChatFinal(
|
||||
sessionKey,
|
||||
eventRunId,
|
||||
evt.runId,
|
||||
evt.seq,
|
||||
lifecyclePhase === "error" ? "error" : "done",
|
||||
evt.data?.error,
|
||||
|
||||
Reference in New Issue
Block a user