mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 21:54:32 +00:00
Fix #12767: Heartbeat strip responsePrefix before HEARTBEAT_OK suppression
This commit is contained in:
committed by
Peter Steinberger
parent
feed570984
commit
f476c8b48b
@@ -5,7 +5,7 @@ import { describe, expect, it, vi } from "vitest";
|
|||||||
import type { OpenClawConfig } from "../config/config.js";
|
import type { OpenClawConfig } from "../config/config.js";
|
||||||
import * as replyModule from "../auto-reply/reply.js";
|
import * as replyModule from "../auto-reply/reply.js";
|
||||||
import { resolveMainSessionKey } from "../config/sessions.js";
|
import { resolveMainSessionKey } from "../config/sessions.js";
|
||||||
import { runHeartbeatOnce } from "./heartbeat-runner.js";
|
import { runHeartbeatOnce, type HeartbeatDeps } from "./heartbeat-runner.js";
|
||||||
import { installHeartbeatRunnerTestRuntime } from "./heartbeat-runner.test-harness.js";
|
import { installHeartbeatRunnerTestRuntime } from "./heartbeat-runner.test-harness.js";
|
||||||
|
|
||||||
// Avoid pulling optional runtime deps during isolated runs.
|
// Avoid pulling optional runtime deps during isolated runs.
|
||||||
@@ -19,6 +19,7 @@ describe("resolveHeartbeatIntervalMs", () => {
|
|||||||
storePath: string;
|
storePath: string;
|
||||||
heartbeat: Record<string, unknown>;
|
heartbeat: Record<string, unknown>;
|
||||||
channels: Record<string, unknown>;
|
channels: Record<string, unknown>;
|
||||||
|
messages?: Record<string, unknown>;
|
||||||
}): OpenClawConfig {
|
}): OpenClawConfig {
|
||||||
return {
|
return {
|
||||||
agents: {
|
agents: {
|
||||||
@@ -28,6 +29,7 @@ describe("resolveHeartbeatIntervalMs", () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
channels: params.channels as never,
|
channels: params.channels as never,
|
||||||
|
...(params.messages ? { messages: params.messages as never } : {}),
|
||||||
session: { store: params.storePath },
|
session: { store: params.storePath },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -58,12 +60,14 @@ describe("resolveHeartbeatIntervalMs", () => {
|
|||||||
} = {},
|
} = {},
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
...(params.sendWhatsApp ? { sendWhatsApp: params.sendWhatsApp } : {}),
|
...(params.sendWhatsApp
|
||||||
|
? { sendWhatsApp: params.sendWhatsApp as unknown as HeartbeatDeps["sendWhatsApp"] }
|
||||||
|
: {}),
|
||||||
getQueueSize: params.getQueueSize ?? (() => 0),
|
getQueueSize: params.getQueueSize ?? (() => 0),
|
||||||
nowMs: params.nowMs ?? (() => 0),
|
nowMs: params.nowMs ?? (() => 0),
|
||||||
webAuthExists: params.webAuthExists ?? (async () => true),
|
webAuthExists: params.webAuthExists ?? (async () => true),
|
||||||
hasActiveWebListener: params.hasActiveWebListener ?? (() => true),
|
hasActiveWebListener: params.hasActiveWebListener ?? (() => true),
|
||||||
};
|
} satisfies HeartbeatDeps;
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeTelegramDeps(
|
function makeTelegramDeps(
|
||||||
@@ -74,10 +78,12 @@ describe("resolveHeartbeatIntervalMs", () => {
|
|||||||
} = {},
|
} = {},
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
...(params.sendTelegram ? { sendTelegram: params.sendTelegram } : {}),
|
...(params.sendTelegram
|
||||||
|
? { sendTelegram: params.sendTelegram as unknown as HeartbeatDeps["sendTelegram"] }
|
||||||
|
: {}),
|
||||||
getQueueSize: params.getQueueSize ?? (() => 0),
|
getQueueSize: params.getQueueSize ?? (() => 0),
|
||||||
nowMs: params.nowMs ?? (() => 0),
|
nowMs: params.nowMs ?? (() => 0),
|
||||||
};
|
} satisfies HeartbeatDeps;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function seedSessionStore(
|
async function seedSessionStore(
|
||||||
@@ -252,6 +258,46 @@ describe("resolveHeartbeatIntervalMs", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("strips responsePrefix before detecting HEARTBEAT_OK and skips telegram delivery", async () => {
|
||||||
|
await withTempTelegramHeartbeatSandbox(async ({ tmpDir, storePath, replySpy }) => {
|
||||||
|
const cfg = createHeartbeatConfig({
|
||||||
|
tmpDir,
|
||||||
|
storePath,
|
||||||
|
heartbeat: {
|
||||||
|
every: "5m",
|
||||||
|
target: "telegram",
|
||||||
|
},
|
||||||
|
channels: {
|
||||||
|
telegram: {
|
||||||
|
token: "test-token",
|
||||||
|
allowFrom: ["*"],
|
||||||
|
heartbeat: { showOk: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
messages: { responsePrefix: "[openclaw]" },
|
||||||
|
});
|
||||||
|
|
||||||
|
await seedMainSession(storePath, cfg, {
|
||||||
|
lastChannel: "telegram",
|
||||||
|
lastProvider: "telegram",
|
||||||
|
lastTo: "12345",
|
||||||
|
});
|
||||||
|
|
||||||
|
replySpy.mockResolvedValue({ text: "[openclaw] HEARTBEAT_OK" });
|
||||||
|
const sendTelegram = vi.fn().mockResolvedValue({
|
||||||
|
messageId: "m1",
|
||||||
|
toJid: "jid",
|
||||||
|
});
|
||||||
|
|
||||||
|
await runHeartbeatOnce({
|
||||||
|
cfg,
|
||||||
|
deps: makeTelegramDeps({ sendTelegram }),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(sendTelegram).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("skips heartbeat LLM calls when visibility disables all output", async () => {
|
it("skips heartbeat LLM calls when visibility disables all output", async () => {
|
||||||
await withTempHeartbeatSandbox(async ({ tmpDir, storePath, replySpy }) => {
|
await withTempHeartbeatSandbox(async ({ tmpDir, storePath, replySpy }) => {
|
||||||
const cfg = createHeartbeatConfig({
|
const cfg = createHeartbeatConfig({
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import { getQueueSize } from "../process/command-queue.js";
|
|||||||
import { CommandLane } from "../process/lanes.js";
|
import { CommandLane } from "../process/lanes.js";
|
||||||
import { normalizeAgentId, toAgentStoreSessionKey } from "../routing/session-key.js";
|
import { normalizeAgentId, toAgentStoreSessionKey } from "../routing/session-key.js";
|
||||||
import { defaultRuntime, type RuntimeEnv } from "../runtime.js";
|
import { defaultRuntime, type RuntimeEnv } from "../runtime.js";
|
||||||
|
import { escapeRegExp } from "../utils.js";
|
||||||
import { formatErrorMessage } from "./errors.js";
|
import { formatErrorMessage } from "./errors.js";
|
||||||
import { isWithinActiveHours } from "./heartbeat-active-hours.js";
|
import { isWithinActiveHours } from "./heartbeat-active-hours.js";
|
||||||
import {
|
import {
|
||||||
@@ -62,7 +63,7 @@ import {
|
|||||||
} from "./outbound/targets.js";
|
} from "./outbound/targets.js";
|
||||||
import { peekSystemEventEntries } from "./system-events.js";
|
import { peekSystemEventEntries } from "./system-events.js";
|
||||||
|
|
||||||
type HeartbeatDeps = OutboundSendDeps &
|
export type HeartbeatDeps = OutboundSendDeps &
|
||||||
ChannelHeartbeatDeps & {
|
ChannelHeartbeatDeps & {
|
||||||
runtime?: RuntimeEnv;
|
runtime?: RuntimeEnv;
|
||||||
getQueueSize?: (lane?: string) => number;
|
getQueueSize?: (lane?: string) => number;
|
||||||
@@ -355,7 +356,13 @@ function normalizeHeartbeatReply(
|
|||||||
responsePrefix: string | undefined,
|
responsePrefix: string | undefined,
|
||||||
ackMaxChars: number,
|
ackMaxChars: number,
|
||||||
) {
|
) {
|
||||||
const stripped = stripHeartbeatToken(payload.text, {
|
const rawText = typeof payload.text === "string" ? payload.text : "";
|
||||||
|
// Normalize away responsePrefix so a prefixed HEARTBEAT_OK still strips.
|
||||||
|
const prefixPattern = responsePrefix?.trim()
|
||||||
|
? new RegExp(`^${escapeRegExp(responsePrefix.trim())}\\s*`, "i")
|
||||||
|
: null;
|
||||||
|
const textForStrip = prefixPattern ? rawText.replace(prefixPattern, "") : rawText;
|
||||||
|
const stripped = stripHeartbeatToken(textForStrip, {
|
||||||
mode: "heartbeat",
|
mode: "heartbeat",
|
||||||
maxAckChars: ackMaxChars,
|
maxAckChars: ackMaxChars,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user