mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-07 22:44:16 +00:00
fix(feishu): suppress stale replay typing indicators (#30709) (thanks @arkyu2077)
This commit is contained in:
@@ -117,6 +117,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Telegram/Voice fallback reply chunking: apply reply reference, quote text, and inline buttons only to the first fallback text chunk when voice delivery is blocked, preventing over-quoted multi-chunk replies. Landed from contributor PR #31067 by @xdanger. Thanks @xdanger.
|
||||
- Feishu/System preview prompt leakage: stop enqueuing inbound Feishu message previews as system events so user preview text is not injected into later turns as trusted `System:` context. Landed from contributor PR #31209 by @stakeswky. Thanks @stakeswky.
|
||||
- Feishu/Multi-account + reply reliability: add `channels.feishu.defaultAccount` outbound routing support with schema validation, keep quoted-message extraction text-first (post/interactive/file placeholders instead of raw JSON), route Feishu video sends as `msg_type: "file"`, and avoid websocket event blocking by using non-blocking event handling in monitor dispatch. Landed from contributor PRs #29610, #30432, #30331, and #29501. Thanks @hclsys, @bmendonca3, @patrick-yingxi-pan, and @zwffff.
|
||||
- Feishu/Typing replay suppression: skip typing indicators for stale replayed inbound messages after compaction using message-age checks with second/millisecond timestamp normalization, preventing old-message reaction floods while preserving typing for fresh messages. Landed from contributor PR #30709 by @arkyu2077. Thanks @arkyu2077.
|
||||
|
||||
## Unreleased
|
||||
|
||||
|
||||
@@ -116,6 +116,59 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
|
||||
expect(addTypingIndicatorMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("skips typing indicator for stale replayed messages", async () => {
|
||||
createFeishuReplyDispatcher({
|
||||
cfg: {} as never,
|
||||
agentId: "agent",
|
||||
runtime: {} as never,
|
||||
chatId: "oc_chat",
|
||||
replyToMessageId: "om_parent",
|
||||
messageCreateTimeMs: Date.now() - 3 * 60_000,
|
||||
});
|
||||
|
||||
const options = createReplyDispatcherWithTypingMock.mock.calls[0]?.[0];
|
||||
await options.onReplyStart?.();
|
||||
|
||||
expect(addTypingIndicatorMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("treats second-based timestamps as stale for typing suppression", async () => {
|
||||
createFeishuReplyDispatcher({
|
||||
cfg: {} as never,
|
||||
agentId: "agent",
|
||||
runtime: {} as never,
|
||||
chatId: "oc_chat",
|
||||
replyToMessageId: "om_parent",
|
||||
messageCreateTimeMs: Math.floor((Date.now() - 3 * 60_000) / 1000),
|
||||
});
|
||||
|
||||
const options = createReplyDispatcherWithTypingMock.mock.calls[0]?.[0];
|
||||
await options.onReplyStart?.();
|
||||
|
||||
expect(addTypingIndicatorMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keeps typing indicator for fresh messages", async () => {
|
||||
createFeishuReplyDispatcher({
|
||||
cfg: {} as never,
|
||||
agentId: "agent",
|
||||
runtime: {} as never,
|
||||
chatId: "oc_chat",
|
||||
replyToMessageId: "om_parent",
|
||||
messageCreateTimeMs: Date.now() - 30_000,
|
||||
});
|
||||
|
||||
const options = createReplyDispatcherWithTypingMock.mock.calls[0]?.[0];
|
||||
await options.onReplyStart?.();
|
||||
|
||||
expect(addTypingIndicatorMock).toHaveBeenCalledTimes(1);
|
||||
expect(addTypingIndicatorMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
messageId: "om_parent",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps auto mode plain text on non-streaming send path", async () => {
|
||||
createFeishuReplyDispatcher({
|
||||
cfg: {} as never,
|
||||
|
||||
@@ -25,6 +25,16 @@ function shouldUseCard(text: string): boolean {
|
||||
/** Maximum age (ms) for a message to receive a typing indicator reaction.
|
||||
* Messages older than this are likely replays after context compaction (#30418). */
|
||||
const TYPING_INDICATOR_MAX_AGE_MS = 2 * 60_000;
|
||||
const MS_EPOCH_MIN = 1_000_000_000_000;
|
||||
|
||||
function normalizeEpochMs(timestamp: number | undefined): number | undefined {
|
||||
if (!Number.isFinite(timestamp) || timestamp === undefined || timestamp <= 0) {
|
||||
return undefined;
|
||||
}
|
||||
// Defensive normalization: some payloads use seconds, others milliseconds.
|
||||
// Values below 1e12 are treated as epoch-seconds.
|
||||
return timestamp < MS_EPOCH_MIN ? timestamp * 1000 : timestamp;
|
||||
}
|
||||
|
||||
export type CreateFeishuReplyDispatcherParams = {
|
||||
cfg: ClawdbotConfig;
|
||||
@@ -72,9 +82,10 @@ export function createFeishuReplyDispatcher(params: CreateFeishuReplyDispatcherP
|
||||
}
|
||||
// Skip typing indicator for old messages — likely replays after context
|
||||
// compaction that would flood users with stale notifications (#30418).
|
||||
const messageCreateTimeMs = normalizeEpochMs(params.messageCreateTimeMs);
|
||||
if (
|
||||
params.messageCreateTimeMs &&
|
||||
Date.now() - params.messageCreateTimeMs > TYPING_INDICATOR_MAX_AGE_MS
|
||||
messageCreateTimeMs !== undefined &&
|
||||
Date.now() - messageCreateTimeMs > TYPING_INDICATOR_MAX_AGE_MS
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user