mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 09:41:11 +00:00
* fix(imessage): prevent self-chat dedupe false positives (#47830) Move echo cache remember() to post-send only, add early return when inbound message ID doesn't match cached IDs (prevents text-based false positives in self-chat), and reduce text TTL from 5s to 3s. Three targeted changes to fix silent user message loss in self-chat: 1. deliver.ts: Remove pre-send remember() call — cache only reflects successfully-delivered content, not pre-send full text. 2. echo-cache.ts: Skip text fallback when inbound has a valid message ID that doesn't match any cached outbound ID. In self-chat, sender == target so scopes collide; a user message with a fresh ID but matching text was incorrectly dropped as an echo. 3. echo-cache.ts: Reduce text TTL from 5000ms to 3000ms — agent echoes arrive within 1-2s, 5s was too wide. Adds self-chat-dedupe.test.ts (7 tests) + updates deliver.test.ts. BlueBubbles uses a different cache pattern — no changes needed there. Closes #47830 * review(imessage): strip debug logs, bump echo TTL to 4s (#47830) Bruce Phase 4 review changes: - Remove all [IMSG-DEBUG] console.error calls from inbound-processing.ts and monitor-provider.ts (23 lines, left over from Phase 2 debug deploy) - Bump SENT_MESSAGE_TEXT_TTL_MS from 3s to 4s in echo-cache.ts to give ~2s margin above the observed 2.2s echo arrival time under load - Update TTL tests to reflect 4s TTL (expired at 5s, live at 3s) * fix(imessage): add dedupe comments and canary/compat/TTL tests * fix(imessage): address review feedback on echo cache, shadowing, and test IDs * refactor(imessage): hoist inboundMessageId to eliminate duplicate computation (#47830) * fix(imessage): unify self-chat echo matching * fix: use inbound guid for self-chat echo matching (#55359) (thanks @rmarr) --------- Co-authored-by: Rohan Marr <rmarr@users.noreply.github.com> Co-authored-by: Ayaan Zaidi <hi@obviy.us>
85 lines
2.6 KiB
TypeScript
85 lines
2.6 KiB
TypeScript
import type { IMessagePayload } from "./types.js";
|
|
|
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
}
|
|
|
|
function isOptionalString(value: unknown): value is string | null | undefined {
|
|
return value === undefined || value === null || typeof value === "string";
|
|
}
|
|
|
|
function isOptionalStringOrNumber(value: unknown): value is string | number | null | undefined {
|
|
return (
|
|
value === undefined || value === null || typeof value === "string" || typeof value === "number"
|
|
);
|
|
}
|
|
|
|
function isOptionalNumber(value: unknown): value is number | null | undefined {
|
|
return value === undefined || value === null || typeof value === "number";
|
|
}
|
|
|
|
function isOptionalBoolean(value: unknown): value is boolean | null | undefined {
|
|
return value === undefined || value === null || typeof value === "boolean";
|
|
}
|
|
|
|
function isOptionalStringArray(value: unknown): value is string[] | null | undefined {
|
|
return (
|
|
value === undefined ||
|
|
value === null ||
|
|
(Array.isArray(value) && value.every((entry) => typeof entry === "string"))
|
|
);
|
|
}
|
|
|
|
function isOptionalAttachments(value: unknown): value is IMessagePayload["attachments"] {
|
|
if (value === undefined || value === null) {
|
|
return true;
|
|
}
|
|
if (!Array.isArray(value)) {
|
|
return false;
|
|
}
|
|
return value.every((attachment) => {
|
|
if (!isRecord(attachment)) {
|
|
return false;
|
|
}
|
|
return (
|
|
isOptionalString(attachment.original_path) &&
|
|
isOptionalString(attachment.mime_type) &&
|
|
isOptionalBoolean(attachment.missing)
|
|
);
|
|
});
|
|
}
|
|
|
|
export function parseIMessageNotification(raw: unknown): IMessagePayload | null {
|
|
if (!isRecord(raw)) {
|
|
return null;
|
|
}
|
|
const maybeMessage = raw.message;
|
|
if (!isRecord(maybeMessage)) {
|
|
return null;
|
|
}
|
|
|
|
const message: IMessagePayload = maybeMessage;
|
|
if (
|
|
!isOptionalNumber(message.id) ||
|
|
!isOptionalString(message.guid) ||
|
|
!isOptionalNumber(message.chat_id) ||
|
|
!isOptionalString(message.sender) ||
|
|
!isOptionalBoolean(message.is_from_me) ||
|
|
!isOptionalString(message.text) ||
|
|
!isOptionalStringOrNumber(message.reply_to_id) ||
|
|
!isOptionalString(message.reply_to_text) ||
|
|
!isOptionalString(message.reply_to_sender) ||
|
|
!isOptionalString(message.created_at) ||
|
|
!isOptionalAttachments(message.attachments) ||
|
|
!isOptionalString(message.chat_identifier) ||
|
|
!isOptionalString(message.chat_guid) ||
|
|
!isOptionalString(message.chat_name) ||
|
|
!isOptionalStringArray(message.participants) ||
|
|
!isOptionalBoolean(message.is_group)
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
return message;
|
|
}
|