openclaw/src/plugin-sdk
Runkun Miao 7c6f8bfe73
feat(feishu): add broadcast support for multi-agent groups (#29575)
* feat(feishu): add broadcast support for multi-agent group observation

When multiple agents share a Feishu group chat, only the @mentioned
agent receives the message. This prevents observer agents from building
session memory of group activity they weren't directly addressed in.

Adds broadcast support (reusing the same cfg.broadcast schema as
WhatsApp) so all configured agents receive every group message in their
session transcripts. Only the @mentioned agent responds on Feishu;
observer agents process silently via no-op dispatchers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(feishu): guard sequential broadcast dispatch against single-agent failure

Wrap each dispatchForAgent() call in the sequential loop with try/catch
so one agent's dispatch failure doesn't abort delivery to remaining agents.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(feishu): avoid duplicate messages in broadcast observer mode and normalize agent IDs

- Skip recordPendingHistoryEntryIfEnabled for broadcast groups when not
  mentioned, since the message is dispatched directly to all agents.
  Previously the message appeared twice in the agent prompt.
- Normalize agent IDs with toLowerCase() before membership checks so
  config casing mismatches don't silently skip valid agents.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(feishu): set WasMentioned per-agent and normalize broadcast IDs

- buildCtxPayloadForAgent now takes a wasMentioned parameter so active
  agents get WasMentioned=true and observers get false (P1 fix)
- Normalize broadcastAgents to lowercase at resolution time and
  lowercase activeAgentId so all comparisons and session key generation
  use canonical IDs regardless of config casing (P2 fix)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(feishu): canonicalize broadcast agent IDs with normalizeAgentId

* fix(feishu): match ReplyDispatcher sync return types for noop dispatcher

The upstream ReplyDispatcher changed sendToolResult/sendBlockReply/
sendFinalReply to synchronous (returning boolean). Update the broadcast
observer noop dispatcher to match.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(feishu): deduplicate broadcast agent IDs after normalization

Config entries like "Main" and "main" collapse to the same canonical ID
after normalizeAgentId but were dispatched multiple times. Use Set to
deduplicate after normalization.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(feishu): honor requireMention=false when selecting broadcast responder

When requireMention is false, the routed agent should be active (reply
on Feishu) even without an explicit @mention. Previously activeAgentId
was null whenever ctx.mentionedBot was false, so all agents got the
noop dispatcher and no reply was sent — silently breaking groups that
disabled mention gating.

Hoist requireMention out of the if(isGroup) block so it's accessible
in the dispatch code.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(feishu): cross-account broadcast dedup to prevent duplicate dispatches

In multi-account Feishu setups, the same message event is delivered to
every bot account in a group. Without cross-account dedup, each account
independently dispatches broadcast agents, causing 2×N dispatches instead
of N (where N = number of broadcast agents).

Two changes:
1. requireMention=true + bot not mentioned: return early instead of
   falling through to broadcast. The mentioned bot's handler will
   dispatch for all agents. Non-mentioned handlers record to history.
2. Add cross-account broadcast dedup using a shared 'broadcast' namespace
   (tryRecordMessagePersistent). The first handler to reach the broadcast
   block claims the message; subsequent accounts skip. This handles the
   requireMention=false multi-account case.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(feishu): strip CommandAuthorized from broadcast observer contexts

Broadcast observer agents inherited CommandAuthorized from the sender,
causing slash commands (e.g. /reset) to silently execute on every observer
session. Now only the active agent retains CommandAuthorized; observers
have it stripped before dispatch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(feishu): use actual mention state for broadcast WasMentioned

The active broadcast agent's WasMentioned was set to true whenever
requireMention=false, even when the bot was not actually @mentioned.
Now uses ctx.mentionedBot && agentId === activeAgentId, consistent
with the single-agent path which passes ctx.mentionedBot directly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(feishu): skip history buffer for broadcast accounts and log parallel failures

1. In requireMention groups with broadcast, non-mentioned accounts no
   longer buffer pending history — the mentioned handler's broadcast
   dispatch already writes turns into all agent sessions. Buffering
   caused duplicate replay via buildPendingHistoryContextFromMap.

2. Parallel broadcast dispatch now inspects Promise.allSettled results
   and logs rejected entries, matching the sequential path's per-agent
   error logging.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Changelog: note Feishu multi-agent broadcast dispatch

* Changelog: restore author credit for Feishu broadcast entry

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 21:38:46 -06:00
..
account-id.ts fix(channels): add optional defaultAccount routing 2026-03-02 04:03:46 +00:00
account-resolution.ts refactor(plugin-sdk): share auth, routing, and stream/account helpers 2026-03-02 15:21:19 +00:00
agent-media-payload.ts refactor(plugin-sdk): add shared helper utilities 2026-02-15 19:37:40 +00:00
allow-from.test.ts refactor(zalo): split monitor access and webhook logic 2026-02-24 23:40:51 +00:00
allow-from.ts refactor(zalo): split monitor access and webhook logic 2026-02-24 23:40:51 +00:00
boolean-param.ts refactor(plugin-sdk): share boolean action param parsing 2026-03-02 14:36:41 +00:00
channel-config-helpers.ts refactor(extensions): dedupe channel config, onboarding, and monitors 2026-03-02 08:54:20 +00:00
channel-lifecycle.test.ts refactor: harden msteams lifecycle and attachment flows 2026-03-02 21:19:23 +00:00
channel-lifecycle.ts refactor: harden msteams lifecycle and attachment flows 2026-03-02 21:19:23 +00:00
command-auth.test.ts fix: enforce explicit group auth boundaries across channels 2026-02-26 18:49:16 +01:00
command-auth.ts refactor(plugin-sdk): share auth, routing, and stream/account helpers 2026-03-02 15:21:19 +00:00
config-paths.ts refactor(zalo): share outbound chunker 2026-02-15 01:15:43 +00:00
fetch-auth.test.ts refactor: unify typing dispatch lifecycle and policy boundaries 2026-02-26 17:36:16 +01:00
fetch-auth.ts refactor: unify channel/plugin ssrf fetch policy and auth fallback 2026-02-26 16:44:13 +01:00
file-lock.ts refactor: dedupe process-scoped lock maps 2026-02-17 00:45:02 +00:00
group-access.test.ts refactor(zalo): split monitor access and webhook logic 2026-02-24 23:40:51 +00:00
group-access.ts refactor(zalo): split monitor access and webhook logic 2026-02-24 23:40:51 +00:00
inbound-envelope.ts refactor: dedupe channel and gateway surfaces 2026-03-02 19:57:33 +00:00
index.test.ts fix(plugin-sdk): add export verification tests and release guard (#27569) 2026-03-02 21:30:44 +00:00
index.ts feat(feishu): add broadcast support for multi-agent groups (#29575) 2026-03-02 21:38:46 -06:00
json-store.ts fix: harden sandbox writes and centralize atomic file writes 2026-03-02 16:45:12 +00:00
keyed-async-queue.test.ts test: fix tsgo baseline test compatibility 2026-03-03 01:24:20 +00:00
keyed-async-queue.ts refactor: unify queueing and normalize telegram slack flows 2026-03-02 20:55:15 +00:00
oauth-utils.ts refactor(extensions): dedupe channel config, onboarding, and monitors 2026-03-02 08:54:20 +00:00
onboarding.ts style: align formatting with oxfmt 0.33 2026-02-18 01:34:35 +00:00
outbound-media.test.ts refactor: centralize delivery/path/media/version lifecycle 2026-03-02 04:04:36 +00:00
outbound-media.ts refactor: centralize delivery/path/media/version lifecycle 2026-03-02 04:04:36 +00:00
pairing-access.ts refactor(extensions): use scoped pairing helper 2026-02-26 21:57:52 +01:00
persistent-dedupe.test.ts fix(feishu): persist dedup cache across gateway restarts via warmup (openclaw#31605) thanks @Sid-Qin 2026-03-02 17:30:40 -06:00
persistent-dedupe.ts fix(feishu): persist dedup cache across gateway restarts via warmup (openclaw#31605) thanks @Sid-Qin 2026-03-02 17:30:40 -06:00
provider-auth-result.ts refactor(plugin-sdk): add shared helper utilities 2026-02-15 19:37:40 +00:00
reply-payload.ts refactor: de-duplicate channel runtime and payload helpers 2026-02-23 21:25:28 +00:00
resolution-notes.ts refactor(extensions): dedupe channel config, onboarding, and monitors 2026-03-02 08:54:20 +00:00
run-command.ts refactor: de-duplicate channel runtime and payload helpers 2026-02-23 21:25:28 +00:00
runtime.ts refactor: de-duplicate channel runtime and payload helpers 2026-02-23 21:25:28 +00:00
slack-message-actions.test.ts refactor(core): dedupe infra, media, pairing, and plugin helpers 2026-03-02 21:32:11 +00:00
slack-message-actions.ts fix(slack): scope download-file to channel and thread context 2026-03-02 02:23:22 +00:00
ssrf-policy.test.ts refactor: unify channel/plugin ssrf fetch policy and auth fallback 2026-02-26 16:44:13 +01:00
ssrf-policy.ts refactor: unify channel/plugin ssrf fetch policy and auth fallback 2026-02-26 16:44:13 +01:00
status-helpers.test.ts refactor: de-duplicate channel runtime and payload helpers 2026-02-23 21:25:28 +00:00
status-helpers.ts refactor(extensions): dedupe channel config, onboarding, and monitors 2026-03-02 08:54:20 +00:00
temp-path.test.ts fix(security): lock sandbox tmp media paths to openclaw roots 2026-02-24 23:10:19 +00:00
temp-path.ts refactor(tmp): harden temp boundary guardrails 2026-02-24 23:51:10 +00:00
text-chunking.test.ts refactor(shared): reuse outbound text chunking core 2026-02-19 07:01:54 +00:00
text-chunking.ts refactor(shared): reuse outbound text chunking core 2026-02-19 07:01:54 +00:00
tool-send.ts refactor(core): dedupe shared config and runtime helpers 2026-02-16 14:59:30 +00:00
webhook-memory-guards.test.ts refactor(security): unify webhook guardrails across channels 2026-03-02 00:31:42 +00:00
webhook-memory-guards.ts refactor(security): unify webhook guardrails across channels 2026-03-02 00:31:42 +00:00
webhook-path.ts refactor(plugin-sdk): add shared helper utilities 2026-02-15 19:37:40 +00:00
webhook-request-guards.test.ts fix: harden webhook auth-before-body handling 2026-03-02 17:21:09 +00:00
webhook-request-guards.ts refactor: split webhook ingress and policy guards 2026-03-02 18:02:21 +00:00
webhook-targets.test.ts refactor: split webhook ingress and policy guards 2026-03-02 18:02:21 +00:00
webhook-targets.ts refactor: dedupe channel and gateway surfaces 2026-03-02 19:57:33 +00:00
windows-spawn.ts fix(acpx): default strict windows wrapper policy on windows 2026-03-02 01:31:32 +00:00