fix: drop active heartbeat followups from queue (#25610, thanks @mcaxtr)
Co-authored-by: Marcus Castro <mcaxtr@gmail.com>
This commit is contained in:
@@ -49,6 +49,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Messaging tool dedupe: treat originating channel metadata as authoritative for same-target `message.send` suppression in proactive runs (heartbeat/cron/exec-event), including synthetic-provider contexts, so `delivery-mirror` transcript entries no longer cause duplicate Telegram sends. (#25835) Thanks @jadeathena84-arch.
|
||||
- Cron/Heartbeat delivery: stop inheriting cached session `lastThreadId` for heartbeat-mode target resolution unless a thread/topic is explicitly requested, so announce-mode cron and heartbeat deliveries stay on top-level destinations instead of leaking into active conversation threads. (#25730) Thanks @markshields-tl.
|
||||
- Heartbeat defaults/prompts: switch the implicit heartbeat delivery target from `last` to `none` (opt-in for external delivery), and use internal-only cron/exec heartbeat prompt wording when delivery is disabled so background checks do not nudge user-facing relay behavior. (#25871, #24638, #25851)
|
||||
- Auto-reply/Heartbeat queueing: drop heartbeat runs when a session already has an active run instead of enqueueing a stale followup, preventing duplicate heartbeat response branches after queue drain. (#25610, #25606) Thanks @mcaxtr.
|
||||
- Security/Sandbox media: restrict sandbox media tmp-path allowances to OpenClaw-managed tmp roots instead of broad host `os.tmpdir()` trust, and add outbound/channel guardrails (tmp-path lint + media-root smoke tests) to prevent regressions in local media attachment reads. Thanks @tdjackey for reporting.
|
||||
- Security/Sandbox media: reject hard-linked OpenClaw tmp media aliases (including symlink-to-hardlink chains) during sandbox media path resolution to prevent out-of-sandbox inode alias reads. (#25820) Thanks @bmendonca3.
|
||||
- Config/Plugins: treat stale removed `google-antigravity-auth` plugin references as compatibility warnings (not hard validation errors) across `plugins.entries`, `plugins.allow`, `plugins.deny`, and `plugins.slots.memory`, so startup no longer fails after antigravity removal. (#25538, #25862) Thanks @chilu18.
|
||||
|
||||
@@ -8,7 +8,7 @@ import type { TypingMode } from "../../config/types.js";
|
||||
import { withStateDirEnv } from "../../test-helpers/state-dir-env.js";
|
||||
import type { TemplateContext } from "../templating.js";
|
||||
import type { GetReplyOptions } from "../types.js";
|
||||
import type { FollowupRun, QueueSettings } from "./queue.js";
|
||||
import { enqueueFollowupRun, type FollowupRun, type QueueSettings } from "./queue.js";
|
||||
import { createMockTypingController } from "./test-helpers.js";
|
||||
|
||||
type AgentRunParams = {
|
||||
@@ -86,6 +86,7 @@ beforeAll(async () => {
|
||||
beforeEach(() => {
|
||||
state.runEmbeddedPiAgentMock.mockClear();
|
||||
state.runCliAgentMock.mockClear();
|
||||
vi.mocked(enqueueFollowupRun).mockClear();
|
||||
vi.stubEnv("OPENCLAW_TEST_FAST", "1");
|
||||
});
|
||||
|
||||
@@ -98,6 +99,9 @@ function createMinimalRun(params?: {
|
||||
storePath?: string;
|
||||
typingMode?: TypingMode;
|
||||
blockStreamingEnabled?: boolean;
|
||||
isActive?: boolean;
|
||||
shouldFollowup?: boolean;
|
||||
resolvedQueueMode?: string;
|
||||
runOverrides?: Partial<FollowupRun["run"]>;
|
||||
}) {
|
||||
const typing = createMockTypingController();
|
||||
@@ -106,7 +110,9 @@ function createMinimalRun(params?: {
|
||||
Provider: "whatsapp",
|
||||
MessageSid: "msg",
|
||||
} as unknown as TemplateContext;
|
||||
const resolvedQueue = { mode: "interrupt" } as unknown as QueueSettings;
|
||||
const resolvedQueue = {
|
||||
mode: params?.resolvedQueueMode ?? "interrupt",
|
||||
} as unknown as QueueSettings;
|
||||
const sessionKey = params?.sessionKey ?? "main";
|
||||
const followupRun = {
|
||||
prompt: "hello",
|
||||
@@ -147,8 +153,8 @@ function createMinimalRun(params?: {
|
||||
queueKey: "main",
|
||||
resolvedQueue,
|
||||
shouldSteer: false,
|
||||
shouldFollowup: false,
|
||||
isActive: false,
|
||||
shouldFollowup: params?.shouldFollowup ?? false,
|
||||
isActive: params?.isActive ?? false,
|
||||
isStreaming: false,
|
||||
opts,
|
||||
typing,
|
||||
@@ -274,6 +280,39 @@ async function runReplyAgentWithBase(params: {
|
||||
});
|
||||
}
|
||||
|
||||
describe("runReplyAgent heartbeat followup guard", () => {
|
||||
it("drops heartbeat runs when another run is active", async () => {
|
||||
const { run, typing } = createMinimalRun({
|
||||
opts: { isHeartbeat: true },
|
||||
isActive: true,
|
||||
shouldFollowup: true,
|
||||
resolvedQueueMode: "collect",
|
||||
});
|
||||
|
||||
const result = await run();
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
expect(vi.mocked(enqueueFollowupRun)).not.toHaveBeenCalled();
|
||||
expect(state.runEmbeddedPiAgentMock).not.toHaveBeenCalled();
|
||||
expect(typing.cleanup).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("still enqueues non-heartbeat runs when another run is active", async () => {
|
||||
const { run } = createMinimalRun({
|
||||
opts: { isHeartbeat: false },
|
||||
isActive: true,
|
||||
shouldFollowup: true,
|
||||
resolvedQueueMode: "collect",
|
||||
});
|
||||
|
||||
const result = await run();
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
expect(vi.mocked(enqueueFollowupRun)).toHaveBeenCalledTimes(1);
|
||||
expect(state.runEmbeddedPiAgentMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("runReplyAgent typing (heartbeat)", () => {
|
||||
async function withTempStateDir<T>(fn: (stateDir: string) => Promise<T>): Promise<T> {
|
||||
return await withStateDirEnv(
|
||||
|
||||
@@ -235,6 +235,11 @@ export async function runReplyAgent(params: {
|
||||
}
|
||||
}
|
||||
|
||||
if (isHeartbeat && isActive) {
|
||||
typing.cleanup();
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (isActive && (shouldFollowup || resolvedQueue.mode === "steer")) {
|
||||
enqueueFollowupRun(queueKey, followupRun, resolvedQueue);
|
||||
await touchActiveSessionEntry();
|
||||
|
||||
Reference in New Issue
Block a user