fix: keep NO_REPLY detection case-insensitive

This commit is contained in:
Ayaan Zaidi
2026-04-04 22:38:59 +05:30
parent cde1e2d3a1
commit 0817bf446f
4 changed files with 33 additions and 1 deletions

View File

@@ -120,6 +120,7 @@ Docs: https://docs.openclaw.ai
- Agents/exec: restore `host=node` routing for node-pinned and `host=auto` sessions, while still blocking sandboxed `auto` sessions from jumping to gateway. (#60788) Thanks @openperf.
- Agents/compaction: keep assistant tool calls and displaced tool results in the same compaction chunk so strict summarization providers stop rejecting orphaned tool pairs. (#58849) Thanks @openperf.
- Cron: suppress exact `NO_REPLY` sentinel direct-delivery payloads, keep silent direct replies from falling back into duplicate main-summary sends, and treat structured `deleteAfterRun` silent replies the same as text silent replies. (#45737) Thanks @openperf.
- Cron: keep exact silent-token detection case-insensitive again so mixed-case `NO_REPLY` outputs still stay silent in text and direct delivery paths. Thanks @obviyus.
## 2026.4.2

View File

@@ -11,6 +11,11 @@ describe("isSilentReplyText", () => {
expect(isSilentReplyText("\nNO_REPLY\n")).toBe(true);
});
it("returns true for mixed-case token", () => {
expect(isSilentReplyText("no_reply")).toBe(true);
expect(isSilentReplyText(" No_RePlY ")).toBe(true);
});
it("returns false for undefined/empty", () => {
expect(isSilentReplyText(undefined)).toBe(false);
expect(isSilentReplyText("")).toBe(false);

View File

@@ -12,7 +12,7 @@ function getSilentExactRegex(token: string): RegExp {
return cached;
}
const escaped = escapeRegExp(token);
const regex = new RegExp(`^\\s*${escaped}\\s*$`);
const regex = new RegExp(`^\\s*${escaped}\\s*$`, "i");
silentExactRegexByToken.set(token, regex);
return regex;
}

View File

@@ -673,6 +673,32 @@ describe("dispatchCronDelivery — double-announce guard", () => {
).toBe(false);
});
it("suppresses mixed-case NO_REPLY in text delivery", async () => {
vi.mocked(countActiveDescendantRuns).mockReturnValue(0);
vi.mocked(isLikelyInterimCronMessage).mockReturnValue(false);
const params = makeBaseParams({ synthesizedText: "No_Reply" });
const state = await dispatchCronDelivery(params);
expect(deliverOutboundPayloads).not.toHaveBeenCalled();
expect(state.result).toEqual(
expect.objectContaining({
status: "ok",
delivered: false,
}),
);
expect(
shouldEnqueueCronMainSummary({
summaryText: "No_Reply",
deliveryRequested: true,
delivered: state.result?.delivered,
deliveryAttempted: state.result?.deliveryAttempted,
suppressMainSummary: false,
isCronSystemEvent: () => true,
}),
).toBe(false);
});
it("cleans up the direct cron session after a structured silent reply when deleteAfterRun is enabled", async () => {
vi.mocked(countActiveDescendantRuns).mockReturnValue(0);
vi.mocked(isLikelyInterimCronMessage).mockReturnValue(false);