diff --git a/CHANGELOG.md b/CHANGELOG.md index edad4daaf20..6194a9709b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ Docs: https://docs.openclaw.ai - fix(bluebubbles): replay missed webhook messages after gateway restart via a persistent per-account cursor and `/api/v1/message/query?after=` pass, so messages delivered while the gateway was down no longer disappear. Uses the existing `processMessage` path and is deduped by #66816's inbound GUID cache. (#66857, #66721) Thanks @omarshahine. - Telegram/native commands: keep Telegram command-sync cache process-local so gateway restarts re-register the menu instead of trusting stale on-disk sync state after Telegram cleared commands out-of-band. (#66730) Thanks @nightq. - Audio/self-hosted STT: restore `models.providers.*.request.allowPrivateNetwork` for audio transcription so private or LAN speech-to-text endpoints stop tripping SSRF blocks after the v2026.4.14 regression. (#66692) Thanks @jhsmith409. +- QQBot/cron: guard against undefined `event.content` in `parseFaceTags` and `filterInternalMarkers` so cron-triggered agent turns with no content payload no longer crash with `TypeError: Cannot read properties of undefined (reading 'startsWith')`. (#66302) Thanks @xinmotlanthua. ## 2026.4.14 diff --git a/extensions/qqbot/src/utils/text-parsing.test.ts b/extensions/qqbot/src/utils/text-parsing.test.ts index 451ca2e3111..a48a540c99d 100644 --- a/extensions/qqbot/src/utils/text-parsing.test.ts +++ b/extensions/qqbot/src/utils/text-parsing.test.ts @@ -2,6 +2,18 @@ import { describe, expect, it, vi } from "vitest"; import { parseFaceTags } from "./text-parsing.js"; describe("parseFaceTags", () => { + it("returns empty string when input is undefined", () => { + expect(parseFaceTags(undefined)).toBe(""); + }); + + it("returns empty string when input is null", () => { + expect(parseFaceTags(null)).toBe(""); + }); + + it("returns empty string when input is empty string", () => { + expect(parseFaceTags("")).toBe(""); + }); + it("skips oversized base64 ext payloads before decoding", () => { const oversizedBase64 = "A".repeat(100_000); const tag = ``; diff --git a/extensions/qqbot/src/utils/text-parsing.ts b/extensions/qqbot/src/utils/text-parsing.ts index 7722bfd04c6..5cec13dda9a 100644 --- a/extensions/qqbot/src/utils/text-parsing.ts +++ b/extensions/qqbot/src/utils/text-parsing.ts @@ -5,9 +5,9 @@ import type { RefAttachmentSummary } from "../ref-index-store.js"; const MAX_FACE_EXT_BYTES = 64 * 1024; /** Replace QQ face tags with readable text labels. */ -export function parseFaceTags(text: string): string { +export function parseFaceTags(text: string | undefined | null): string { if (!text) { - return text; + return ""; } return text.replace(//g, (_match, ext: string) => { @@ -26,9 +26,9 @@ export function parseFaceTags(text: string): string { } /** Remove internal framework markers before sending text outward. */ -export function filterInternalMarkers(text: string): string { +export function filterInternalMarkers(text: string | undefined | null): string { if (!text) { - return text; + return ""; } let result = text.replace(/\[\[[a-z_]+:\s*[^\]]*\]\]/gi, "");