* fix(feishu): guard against false-positive @mentions in multi-app groups
When multiple Feishu bot apps share a group chat, Feishu's WebSocket
event delivery remaps the open_id in mentions[] per-app. This causes
checkBotMentioned() to return true for ALL bots when only one was
actually @mentioned, making requireMention ineffective.
Add a botName guard: if the mention's open_id matches this bot but the
mention's display name differs from this bot's configured botName, treat
it as a false positive and skip.
botName is already available via account.config.botName (set during
onboarding).
Closes#24249
* fix(feishu): support @all mention in multi-bot groups
When a user sends @all (@_all in Feishu message content), treat it as
mentioning every bot so all agents respond when requireMention is true.
Feishu's @all does not populate the mentions[] array, so this needs
explicit content-level detection.
* fix(feishu): auto-fetch bot display name from API for reliable mention matching
Instead of relying on the manually configured botName (which may differ
from the actual Feishu bot display name), fetch the bot's display name
from the Feishu API at startup via probeFeishu().
This ensures checkBotMentioned() always compares against the correct
display name, even when the config botName doesn't match (e.g. config
says 'Wanda' but Feishu shows '绯红女巫').
Changes:
- monitor.ts: fetchBotOpenId → fetchBotInfo (returns both openId and name)
- monitor.ts: store botNames map, pass botName to handleFeishuMessage
- bot.ts: accept botName from params, prefer it over config fallback
* Changelog: note Feishu multi-app mention false-positive guard
---------
Co-authored-by: Teague Xiao <teaguexiao@TeaguedeMac-mini.local>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
* fix: add session-memory hook support for Feishu provider
Issue #31275: Session-memory hook not triggered when using /new command in Feishu
- Added command handler to Feishu provider
- Integrated with OpenClaw's before_reset hook system
- Ensures session memory is saved when /new or /reset commands are used
* Changelog: note Feishu session-memory hook parity
---------
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
* fix(feishu): non-blocking ws ack and preserve streaming card full content
* fix(feishu): preserve fragmented streaming text without newline artifacts
---------
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
* feishu: pass per-group systemPrompt to inbound context
The Feishu extension schema supports systemPrompt in per-group config
(channels.feishu.accounts.<id>.groups.<groupId>.systemPrompt) but the
value was never forwarded to the inbound context as GroupSystemPrompt.
This means per-group system prompts configured for Feishu had no effect,
unlike IRC, Discord, Slack, Telegram, Matrix, and other channels that
already pass this field correctly.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* line: pass per-group systemPrompt to inbound context
Same issue as feishu: the Line config schema defines systemPrompt in
per-group config but the value was never forwarded as GroupSystemPrompt
in the inbound context payload.
Added resolveLineGroupSystemPrompt helper that mirrors the existing
resolveLineGroupConfig lookup logic (groupId > roomId > wildcard).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Changelog: note Feishu and LINE group systemPrompt propagation
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
* fix(feishu): correct invalid scope name in permission grant URL
The Feishu API returns error code 99991672 with an authorization URL
containing the non-existent scope `contact:contact.base:readonly`
when the `contact.user.get` endpoint is called without the correct
permission. The valid scope is `contact:user.base:readonly`.
Add a scope correction map that replaces known incorrect scope names
in the extracted grant URL before presenting it to the user/agent,
so the authorization link actually works.
Closes#31761
* chore(changelog): note feishu scope correction
---------
Co-authored-by: SidQin-cyber <sidqin0410@gmail.com>
* 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>
* fix(feishu): preserve block streaming text when final payload is missing
When Feishu card streaming receives block payloads without matching final/partial
callbacks, keep block text in stream state so onIdle close still publishes the
reply instead of an empty message. Add a regression test for block-only streaming.
Closes#30628
* Feishu: preserve streaming block fallback when final text is missing
---------
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
* CI: add windows scope output for changed-scope
* Test: cover windows scope gating in changed-scope
* CI: gate checks-windows by windows scope
* Docs: update CI windows scope and runner label
* CI: move checks-windows to 32 vCPU runner
* Docs: align CI windows runner with workflow