mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-18 18:04:45 +00:00
fix(gateway): defer heartbeats during active replies
* fix(gateway): defer heartbeats during active replies * fix(gateway): bind heartbeat reply run fallback
This commit is contained in:
committed by
GitHub
parent
77ca3dc99c
commit
bea4f0d2f4
@@ -37,6 +37,7 @@ Docs: https://docs.openclaw.ai
|
||||
- CLI/setup: order the model/auth provider picker as OpenAI, Anthropic, xAI, Google, then the remaining providers alphabetically.
|
||||
- Gateway/diagnostics: add opt-in critical memory pressure stability snapshots with gateway logs, V8 heap, cgroup, active-resource, and redacted large session-file evidence. Fixes #82518.
|
||||
- Doctor/Gateway: avoid treating unrelated macOS LaunchAgents as legacy gateways just because their environment values mention old checkout paths.
|
||||
- Gateway/heartbeat: defer heartbeat runs while the target reply operation is queued or active, preventing heartbeat prompts from interleaving with WebChat responses before the streaming lane starts. Fixes #82722. Thanks @Andy-Xie-1145.
|
||||
- CLI/setup: collapse raw gateway config keys in existing-config summaries into friendly `Model` and `Gateway` rows.
|
||||
- CLI/config: show concise human config-write output with an indented backup path instead of printing checksum-heavy overwrite audit details by default.
|
||||
- CLI/docs: call the canonical lowercase docs MCP search tool and surface MCP errors instead of returning empty search results. Fixes #82702. (#82704) Thanks @hclsys.
|
||||
|
||||
@@ -228,6 +228,28 @@ describe("heartbeat runner skips when target session lane is busy", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("returns requests-in-flight when the target session has an active reply run", async () => {
|
||||
await withTempHeartbeatSandbox(async ({ storePath, replySpy }) => {
|
||||
const cfg = createHeartbeatTelegramConfig();
|
||||
const sessionKey = await seedHeartbeatTelegramSession(storePath, cfg);
|
||||
const isReplyRunActive = vi.fn((key: string) => key === sessionKey);
|
||||
|
||||
const result = await runHeartbeatOnce({
|
||||
cfg,
|
||||
deps: {
|
||||
getQueueSize: vi.fn((_lane?: string) => 0),
|
||||
isReplyRunActive,
|
||||
nowMs: () => Date.now(),
|
||||
getReplyFromConfig: replySpy,
|
||||
} as HeartbeatDeps,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ status: "skipped", reason: HEARTBEAT_SKIP_REQUESTS_IN_FLIGHT });
|
||||
expect(isReplyRunActive).toHaveBeenCalledWith(sessionKey);
|
||||
expect(replySpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("does not defer on a recent heartbeat ack pending final delivery", async () => {
|
||||
await withTempHeartbeatSandbox(async ({ storePath, replySpy }) => {
|
||||
const cfg = createHeartbeatTelegramConfig();
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
type HeartbeatTask,
|
||||
} from "../auto-reply/heartbeat.js";
|
||||
import { resolveDefaultModel } from "../auto-reply/reply/directive-handling.defaults.js";
|
||||
import { replyRunRegistry } from "../auto-reply/reply/reply-run-registry.js";
|
||||
import { resolveResponsePrefixTemplate } from "../auto-reply/reply/response-prefix-template.js";
|
||||
import { HEARTBEAT_TOKEN } from "../auto-reply/tokens.js";
|
||||
import type { ReplyPayload } from "../auto-reply/types.js";
|
||||
@@ -142,6 +143,7 @@ export type HeartbeatDeps = OutboundSendDeps &
|
||||
runtime?: RuntimeEnv;
|
||||
getQueueSize?: (lane?: string) => number;
|
||||
getCommandLaneSnapshots?: () => readonly CommandLaneSnapshot[];
|
||||
isReplyRunActive?: (sessionKey: string) => boolean;
|
||||
nowMs?: () => number;
|
||||
};
|
||||
|
||||
@@ -1350,6 +1352,16 @@ export async function runHeartbeatOnce(opts: {
|
||||
return { status: "skipped", reason: preflight.skipReason };
|
||||
}
|
||||
const { entry, sessionKey, storePath, suppressOriginatingContext } = preflight.session;
|
||||
const isReplyRunActive =
|
||||
opts.deps?.isReplyRunActive ?? ((key: string) => replyRunRegistry.isActive(key));
|
||||
if (isReplyRunActive(sessionKey)) {
|
||||
emitHeartbeatEvent({
|
||||
status: "skipped",
|
||||
reason: HEARTBEAT_SKIP_REQUESTS_IN_FLIGHT,
|
||||
durationMs: Date.now() - startedAt,
|
||||
});
|
||||
return { status: "skipped", reason: HEARTBEAT_SKIP_REQUESTS_IN_FLIGHT };
|
||||
}
|
||||
|
||||
// Check the resolved session lane — if it is busy, skip to avoid interrupting
|
||||
// an active streaming turn. The wake-layer retry (heartbeat-wake.ts) will
|
||||
|
||||
Reference in New Issue
Block a user