From 60bf56517f9351fef63565c8f3a0674718258785 Mon Sep 17 00:00:00 2001 From: Tak Hoffman <781889+Takhoffman@users.noreply.github.com> Date: Fri, 27 Feb 2026 23:22:38 -0600 Subject: [PATCH] fix(feishu): honor wildcard group config for reply policy (#29456) ## Summary - honor Feishu wildcard group policy fallback via `channels.feishu.groups["*"]` when no explicit group entry matches - keep exact and case-insensitive explicit group matches higher precedence than wildcard fallback - add changelog credit and TypeScript-safe test assertions ## Verification - pnpm install --frozen-lockfile - pnpm build - pnpm check - pnpm test:macmini Co-authored-by: Wayne Pika <262095977+WaynePika@users.noreply.github.com> Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com> --- CHANGELOG.md | 1 + extensions/feishu/src/policy.test.ts | 57 +++++++++++++++++++++++++++- extensions/feishu/src/policy.ts | 6 ++- 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8ed4db834d..31aa606df51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ Docs: https://docs.openclaw.ai - Feishu/Inbound rich-text parsing: preserve `share_chat` payload summaries when available and add explicit parsing for rich-text `code`/`code_block`/`pre` tags so forwarded and code-heavy messages keep useful context in agent input. (#28591) Thanks @kevinWangSheng. - Feishu/Local media sends: propagate `mediaLocalRoots` through Feishu outbound media sending into `loadWebMedia` so local path attachments work with post-CVE local-root enforcement. (#27884) Thanks @joelnishanth. - Feishu/Group sender allowlist fallback: add global `channels.feishu.groupSenderAllowFrom` sender authorization for group chats, with per-group `groups..allowFrom` precedence and regression coverage for allow/block/precedence behavior. (#29174) Thanks @1MoreBuild. +- Feishu/Group wildcard policy fallback: honor `channels.feishu.groups["*"]` when no explicit group match exists so unmatched groups inherit wildcard reply-policy settings instead of falling back to global defaults. (#29456) Thanks @WaynePika. - Feishu/Docx append/write ordering: insert converted Docx blocks sequentially (single-block creates) so Feishu append/write preserves markdown block order instead of returning shuffled sections in asynchronous batch inserts. (#26172, #26022) Thanks @echoVic. - Feishu/Docx convert fallback chunking: recursively split oversized markdown chunks (including long no-heading sections) when `document.convert` hits content limits, while keeping fenced-code-aware split boundaries whenever possible. (#14402) Thanks @lml2468. - Feishu/Inbound media regression coverage: add explicit tests for message resource type mapping (`image` stays `image`, non-image maps to `file`) to prevent reintroducing unsupported Feishu `type=audio` fetches. (#16311, #8746) Thanks @Yaxuan42. diff --git a/extensions/feishu/src/policy.test.ts b/extensions/feishu/src/policy.test.ts index 8e7d24ba67b..3a159023546 100644 --- a/extensions/feishu/src/policy.test.ts +++ b/extensions/feishu/src/policy.test.ts @@ -1,7 +1,62 @@ import { describe, expect, it } from "vitest"; -import { isFeishuGroupAllowed, resolveFeishuAllowlistMatch } from "./policy.js"; +import { + isFeishuGroupAllowed, + resolveFeishuAllowlistMatch, + resolveFeishuGroupConfig, +} from "./policy.js"; +import type { FeishuConfig } from "./types.js"; describe("feishu policy", () => { + describe("resolveFeishuGroupConfig", () => { + it("falls back to wildcard group config when direct match is missing", () => { + const cfg = { + groups: { + "*": { requireMention: false }, + "oc-explicit": { requireMention: true }, + }, + } as unknown as FeishuConfig; + + const resolved = resolveFeishuGroupConfig({ + cfg, + groupId: "oc-missing", + }); + + expect(resolved).toEqual({ requireMention: false }); + }); + + it("prefers exact group config over wildcard", () => { + const cfg = { + groups: { + "*": { requireMention: false }, + "oc-explicit": { requireMention: true }, + }, + } as unknown as FeishuConfig; + + const resolved = resolveFeishuGroupConfig({ + cfg, + groupId: "oc-explicit", + }); + + expect(resolved).toEqual({ requireMention: true }); + }); + + it("keeps case-insensitive matching for explicit group ids", () => { + const cfg = { + groups: { + "*": { requireMention: false }, + OC_UPPER: { requireMention: true }, + }, + } as unknown as FeishuConfig; + + const resolved = resolveFeishuGroupConfig({ + cfg, + groupId: "oc_upper", + }); + + expect(resolved).toEqual({ requireMention: true }); + }); + }); + describe("resolveFeishuAllowlistMatch", () => { it("allows wildcard", () => { expect( diff --git a/extensions/feishu/src/policy.ts b/extensions/feishu/src/policy.ts index 6ddac42d0e6..430fa7005ec 100644 --- a/extensions/feishu/src/policy.ts +++ b/extensions/feishu/src/policy.ts @@ -56,6 +56,7 @@ export function resolveFeishuGroupConfig(params: { groupId?: string | null; }): FeishuGroupConfig | undefined { const groups = params.cfg?.groups ?? {}; + const wildcard = groups["*"]; const groupId = params.groupId?.trim(); if (!groupId) { return undefined; @@ -68,7 +69,10 @@ export function resolveFeishuGroupConfig(params: { const lowered = groupId.toLowerCase(); const matchKey = Object.keys(groups).find((key) => key.toLowerCase() === lowered); - return matchKey ? groups[matchKey] : undefined; + if (matchKey) { + return groups[matchKey]; + } + return wildcard; } export function resolveFeishuGroupToolPolicy(