Skip to content

Feishu: wire inbound message debounce#31548

Merged
Takhoffman merged 9 commits intoopenclaw:mainfrom
bertonhan:fix/feishu-debounce
Mar 2, 2026
Merged

Feishu: wire inbound message debounce#31548
Takhoffman merged 9 commits intoopenclaw:mainfrom
bertonhan:fix/feishu-debounce

Conversation

@bertonhan
Copy link
Contributor

@bertonhan bertonhan commented Mar 2, 2026

Summary

  • Problem: Feishu channel did not wire into messages.inbound debounce, so rapid consecutive text messages were processed as separate turns.
  • Why it matters: This caused extra/duplicated turns under bursty input and made Feishu behavior inconsistent with documented inbound debounce semantics.
  • What changed: In extensions/feishu/src/monitor.account.ts, inbound im.message.receive_v1 now uses resolveInboundDebounceMs({ channel: "feishu" }) + createInboundDebouncer(...); debounce is applied to text-only messages, media/attachments flush immediately, and control commands bypass debounce.
  • What did NOT change (scope boundary): No changes to reaction/card-action flows, no config schema changes, no cross-channel logic changes, and no new dependencies.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

User-visible / Behavior Changes

  • Feishu now honors messages.inbound.debounceMs and messages.inbound.byChannel.feishu.
  • Rapid text messages from the same sender+conversation are coalesced into one agent turn.
  • Media/attachments are still processed immediately.
  • Control commands remain standalone (no debounce).

Security Impact (required)

  • New permissions/capabilities? (No)
  • Secrets/tokens handling changed? (No)
  • New/changed network calls? (No)
  • Command/tool execution surface changed? (No)
  • Data access scope changed? (No)

Repro + Verification

Environment

  • OS: macOS (local dev)
  • Runtime/container: Node.js + Vitest (no container)
  • Model/provider: N/A
  • Integration/channel (if any): Feishu plugin
  • Relevant config (redacted):
{
  messages: {
    inbound: {
      debounceMs: 2000,
      byChannel: { feishu: 10000 }
    }
  }
}

Steps

  1. Configure messages.inbound.byChannel.feishu to a non-zero window (e.g. 10000).
  2. Send rapid consecutive text messages in the same Feishu conversation from the same sender.
  3. Verify they are merged into one turn after quiet period; then send media/attachment and verify immediate processing.

Expected

  • Text bursts are coalesced per sender+conversation(+thread key).
  • Media/attachments bypass debounce.
  • Control commands bypass debounce.

Actual

  • Matched expected behavior in debounce-focused Feishu test coverage.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Trace/log snippet:

./node_modules/.bin/vitest run extensions/feishu/src/monitor.reaction.test.ts
# ✓ 11 passed
image

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios: Feishu inbound text debounce wiring, text coalescing path, non-text immediate path, no regression in reaction handler tests.
  • Edge cases checked: text burst coalescing, media bypass behavior, control-command bypass logic in debounce predicate.
  • What you did not verify: live end-to-end Feishu tenant/manual chat validation in production-like environment.

Compatibility / Migration

  • Backward compatible? (Yes)
  • Config/env changes? (No)
  • Migration needed? (No)

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: set messages.inbound.byChannel.feishu: 0 (or global messages.inbound.debounceMs: 0) to disable debounce behavior.
  • Files/config to restore: revert extensions/feishu/src/monitor.account.ts.
  • Known bad symptoms reviewers should watch for: unexpected delayed Feishu text handling, or incorrect coalescing across unrelated conversations.

Risks and Mitigations

  • Risk: text coalescing could reduce per-message granularity in a burst.
    • Mitigation: coalescing is limited to same sender + conversation (+thread key), and uses latest message metadata for reply threading.
  • Risk: additional parsing on inbound path adds slight CPU overhead.
    • Mitigation: parsing is lightweight and only applied within Feishu inbound event handling.

@openclaw-barnacle openclaw-barnacle bot added channel: feishu Channel integration: feishu size: S labels Mar 2, 2026
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ae34304e47

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

...last.message,
message_type: "text",
content: JSON.stringify({ text: combinedText }),
mentions: undefined,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve mentions in merged Feishu debounce payloads

Dropping message.mentions on merged flushes breaks mention-dependent routing for group chats: parseFeishuMessageEvent computes ctx.mentionedBot from event.message.mentions, and handleFeishuMessage will skip replying when requireMention is enabled (default true for groups). In a common burst like @bot ... followed quickly by another text, the merged synthetic event now has no mentions, so the turn is treated as “not mentioned” and only recorded to history instead of being answered.

Useful? React with 👍 / 👎.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 2, 2026

Greptile Summary

This PR successfully wires Feishu's inbound message handler into the standard debounce mechanism, bringing it in line with other integrations like Discord and Slack.

Key changes:

  • Wraps im.message.receive_v1 handler with createInboundDebouncer from the plugin runtime
  • Text-only messages from the same sender+conversation(+thread) are coalesced after a quiet period
  • Media/attachments and control commands correctly bypass debounce and flush immediately
  • Debounce key includes chat ID, thread ID (via root_id), and sender ID for proper isolation

Implementation quality:

  • Follows established patterns from Discord/Slack integrations consistently
  • Error handling is comprehensive with dedicated onError callbacks
  • Preserves last message metadata for threading/reply context when combining messages
  • Gracefully handles edge cases (missing sender/chat IDs trigger immediate processing)

Confidence Score: 5/5

  • Safe to merge with minimal risk
  • Clean implementation that follows proven patterns from other integrations. The change is focused, backward-compatible (can be disabled via config), has comprehensive error handling, and introduces no breaking changes. The debounce logic correctly handles all documented edge cases (text coalescing, media bypass, control command bypass).
  • No files require special attention

Last reviewed commit: ae34304

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 20ce943089

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +186 to +190
const suppressedIds = new Set(
entries
.slice(0, -1)
.map((entry) => entry.message.message_id?.trim())
.filter((id): id is string => Boolean(id)),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Exclude final message ID from suppressed dedupe recording

If Feishu retries the same text event inside the debounce window, entries can contain duplicate message_id values (for example [m1, m1]). This code records every pre-final ID as suppressed before dispatch, so the first duplicate records m1, and then handleFeishuMessage immediately drops the final m1 as an already-seen duplicate; the net effect is the user message is never processed at all. Filter suppressed IDs so they never include the final entry’s message_id (or otherwise ensure only truly suppressed IDs are pre-recorded).

Useful? React with 👍 / 👎.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 22eceaa6df

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +136 to +140
mention.key?.trim() ||
mention.id.open_id?.trim() ||
mention.id.user_id?.trim() ||
mention.id.union_id?.trim() ||
mention.name?.trim();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Key merged mentions by stable user ID

mergeFeishuDebounceMentions deduplicates on mention.key first, but that value is a per-message placeholder (for example @_user_1) and is not stable across different messages in the same debounce batch. Two different mentions can therefore collide and drop later entries; if the dropped one is the bot mention from a later message, parseFeishuMessageEvent sees mentionedBot=false and group messages can be skipped when requireMention is enabled. Prefer open_id/user_id/union_id as the primary key and only fall back to mention.key last.

Useful? React with 👍 / 👎.

Comment on lines +240 to +243
const combinedText = entries
.map((entry) => resolveDebounceText(entry))
.filter(Boolean)
.join("\n");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Deduplicate retried message IDs before joining text

The flush path builds combinedText from every buffered entry, but retries can place duplicate events for the same message_id into entries during the debounce window (which this file already anticipates in recordSuppressedMessageIds). In a sequence like [m1, m1, m2], the merged turn will include m1 content twice, so the agent receives corrupted/repeated input. Deduplicate by message.message_id before mapping entries into combinedText.

Useful? React with 👍 / 👎.

@bertonhan bertonhan force-pushed the fix/feishu-debounce branch 3 times, most recently from a210ff6 to 61fe440 Compare March 2, 2026 12:11
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 61fe440ba3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

return;
}
await recordSuppressedMessageIds(entries);
const dedupedEntries = dedupeFeishuDebounceEntriesByMessageId(entries);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Exclude previously processed retries from merged text

dedupeFeishuDebounceEntriesByMessageId only deduplicates IDs within the current flush batch, so if Feishu retries an already-processed message and a new message arrives in the same debounce window (for example, m2 retry + new m3), combinedText still includes stale m2 content. In that case the merged dispatch uses m3 as the final message ID, so handleFeishuMessage dedupe does not remove the stale text and the agent receives duplicated/corrupted input for the new turn.

Useful? React with 👍 / 👎.

@bertonhan bertonhan force-pushed the fix/feishu-debounce branch from 61fe440 to dc8a3be Compare March 2, 2026 13:00
@bertonhan bertonhan force-pushed the fix/feishu-debounce branch from dc8a3be to 0fef244 Compare March 2, 2026 13:23
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0fef244bc2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

.map((entry) => resolveDebounceText(entry))
.filter(Boolean)
.join("\n");
const mergedMentions = mergeFeishuDebounceMentions(freshEntries);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid cross-message mention-forward triggers in debounced flushes

mentions on the synthetic debounced event currently come from all freshEntries, so a burst like @alice ... followed by @bot ... in the same debounce window is reinterpreted as a mention-forward request even though no single message requested that. parseFeishuMessageEvent uses the merged mentions with isMentionForwardRequest, so group replies can incorrectly auto-mention unrelated users. This should not combine non-bot mention targets across messages; otherwise debounce changes message intent and can ping the wrong people.

Useful? React with 👍 / 👎.

@bertonhan bertonhan force-pushed the fix/feishu-debounce branch 2 times, most recently from 0b139c5 to 6985ee8 Compare March 2, 2026 15:30
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 43566e7caf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if (settled) {
return;
}
if (isIgnoredExecStdinError(error)) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use the existing stdin-error guard in exec resolver

The new child.stdin listener calls isIgnoredExecStdinError(error), but that identifier is not defined in this module (the existing helper is isIgnorableStdinWriteError). In the exact fast-exit case this code is handling (EPIPE/ERR_STREAM_DESTROYED from stdin), this path will throw a ReferenceError instead of gracefully continuing, so exec-backed secret resolution can fail unexpectedly for providers that exit before consuming stdin.

Useful? React with 👍 / 👎.

@Takhoffman Takhoffman force-pushed the fix/feishu-debounce branch from 6396bdd to 82646bd Compare March 2, 2026 23:10
@Takhoffman Takhoffman merged commit 3b3e47e into openclaw:main Mar 2, 2026
9 checks passed
@Takhoffman
Copy link
Contributor

PR #31548 - Feishu: wire inbound message debounce (#31548)

Merged after verification.

  • Merge commit: 3b3e47e
  • Verified: pnpm install --frozen-lockfile, pnpm build, pnpm check (with unrelated pre-existing lint failures), pnpm test:macmini (passed before rebase)
  • Autoland updates:
    M\tCHANGELOG.md
  • Rationale:
    Scope-safety prep removed unrelated non-Feishu edits from this PR stack and kept a focused changelog line for the inbound debounce behavior change.
  • Changelog: CHANGELOG.md updated=true required=true opt_out=false

@bertonhan bertonhan deleted the fix/feishu-debounce branch March 3, 2026 06:17
dawi369 pushed a commit to dawi369/davis that referenced this pull request Mar 3, 2026


Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check (fails on unrelated pre-existing lint in ui/src/ui/views/agents-utils.ts and src/pairing/pairing-store.ts)
- pnpm test:macmini (previous run passed before rebase)

Co-authored-by: bertonhan <60309291+bertonhan@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
OWALabuy pushed a commit to kcinzgg/openclaw that referenced this pull request Mar 4, 2026


Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check (fails on unrelated pre-existing lint in ui/src/ui/views/agents-utils.ts and src/pairing/pairing-store.ts)
- pnpm test:macmini (previous run passed before rebase)

Co-authored-by: bertonhan <60309291+bertonhan@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
zooqueen pushed a commit to hanzoai/bot that referenced this pull request Mar 6, 2026


Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check (fails on unrelated pre-existing lint in ui/src/ui/views/agents-utils.ts and src/pairing/pairing-store.ts)
- pnpm test:macmini (previous run passed before rebase)

Co-authored-by: bertonhan <60309291+bertonhan@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: feishu Channel integration: feishu size: L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants