Skip to content

fix: trust structured mention uids without cache validation#218

Merged
Jerry-Xin merged 3 commits intodmwork-org:developfrom
Jerry-Xin:fix/bot-mention-validation
Apr 28, 2026
Merged

fix: trust structured mention uids without cache validation#218
Jerry-Xin merged 3 commits intodmwork-org:developfrom
Jerry-Xin:fix/bot-mention-validation

Conversation

@Jerry-Xin
Copy link
Copy Markdown
Collaborator

Summary

  • remove cache-based validUids filtering from structured mention conversion
  • add defensive inbound text fallback for missing mention payloads
  • add / update tests for structured mentions and fallback boundaries

Problem

Bot-to-bot @[uid:Name] mentions in groups could fail on the first attempt when member cache was not warmed. The message text was converted to @name, but payload.mention was omitted, so the receiving bot was not triggered.

Closes #217

Testing

  • npx vitest run
  • 620/620 tests passing

@Jerry-Xin
Copy link
Copy Markdown
Collaborator Author

Good. I have everything I need. Let me compile the review.


🔥 Code Review: PR #218fix: trust structured mention uids without cache validation

一句话总结: 核心改动干净利落——删掉 validUids 过滤是正确决策,但新增的 inbound text fallback 正则有两个实际问题需要处理。


💀 严重问题

问题 1: inbound text fallback 是条死路 —— botUid 几乎不会出现在 uidToNameMap

位置: inbound.ts:1200

追踪:

channel.ts:881 → handleInboundMessage(botUid: credentials.robot_id)
  → inbound.ts:1200 → uidToNameMap.get(botUid)

uidToNameMap 的填充路径我全部追踪了:

  1. channel.ts:738-744 — 群成员预加载,只填 getGroupMembersFromCache() 返回的成员,bot 通常不在成员列表里
  2. inbound.ts:896-900refreshGroupMemberCache 同理,只填 API 返回的成员
  3. inbound.ts:1168 — reply sender 的 uid→name,只在有引用消息时填
  4. inbound.ts:1437 — DM 用户查询,只填发送方的 uid

没有任何代码路径会把 credentials.robot_idbotName 写入 uidToNameMap registerBot 的返回值 (api-fetch.ts:290-314) 包含 robot_idim_tokenws_urlowner_uid,但不包含 bot 的显示名称

结果:uidToNameMap.get(botUid) 在绝大多数情况下返回 undefined,整个 fallback 块被跳过。你写了 15 行防御性 fallback,但它基本上是个 no-op。

评分: 85 — 这不是会炸的 bug,但它是个虚假的安全感。代码注释说"covers old senders that don't populate the mention payload",实际上在正常运行条件下它什么都 cover 不了。

修复方案: 在 registerBot 返回后(或首次群成员加载时),确保 uidToNameMap.set(credentials.robot_id, botDisplayName)。如果 API 不返回 bot name,则需要另行获取(例如 fetchUserInfo(botUid) 或从群成员列表中筛选 bot 记录)。或者在注释中明确说明此 fallback 只在 bot uid 恰好出现在群成员列表中时才生效。


😤 一般问题

问题 2: 正则 lookbehind 和 MENTION_PATTERN 不一致,CJK 前置字符会导致误匹配

位置: inbound.ts:1203

数据流分析:

rawBody = payload.content (原始网络数据, inbound.ts:1100)
  ↓ 构造正则: (?<=^|[^\w])@${botName}(...)
  ↓ re.test(rawBody) → isMentioned = true

inbound.ts:1203 的 lookbehind 用了 [^\w],但 JavaScript 的 \w 只覆盖 [a-zA-Z0-9_]CJK 字符不是 \w,所以 "你好@BotName" 会通过 lookbehind 检查。

mention-utils.ts:27MENTION_PATTERN 用的是 [^a-zA-Z0-9](不含下划线),两个 lookbehind 在 _@Name 这种输入上行为也不一致。

具体来说:

  • "你好@BotName" → fallback 正则匹配( 不是 \w),但这不应该是一个 mention
  • "foo_@BotName" → fallback 正则不匹配_\w),但 MENTION_PATTERN 匹配

评分: 80 — 在中文群聊中,CJK 字符紧跟 @ 的情况虽然不常见但完全可能(例如用户打字不加空格)。这会导致 bot 被虚假触发。

修复方案: 复用 MENTION_PATTERN 的 lookbehind 策略,或者至少在 [^\w] 中加入 CJK 范围排除:

const re = new RegExp(
  `(?<=^|[^\\w\\u4e00-\\u9fff\\u3040-\\u30FF\\uAC00-\\uD7AF\\u00C0-\\u024F])@${escaped}(?![\\w\\u4e00-\\u9fff\\u3040-\\u30FF\\uAC00-\\uD7AF\\u00C0-\\u024F.\\-])`
);

📊 总结

代码质量评分: 7/10

  • 追踪了 4 条完整调用链(outbound send、outbound sendText、inbound mention gating、inbound reply processing)
  • 检查了 7 个变更文件
  • 发现 1 个严重问题(fallback dead code),1 个一般问题(正则 CJK 误匹配)

核心改动(删除 validUids 过滤)完全正确。 追踪了 convertStructuredMentionsactions.ts:192channel.ts:427inbound.ts:1528 三个调用点,改动一致且干净。注释 (mention-utils.ts:129-133) 准确解释了 trade-off:server-side 拒绝未知 uid 比 client-side cache-miss 导致 mention 丢失要好得多。测试覆盖了 unknown uid 场景(actions.test.ts:310mention-utils.test.ts:194),逻辑自洽。

新增的 fallback 正则想法是对的——确实需要兜底没有 mention payload 的场景。但实现有两个问题:一是 bot 自己的名字几乎不在 uidToNameMap 里所以 fallback 不会生效;二是正则的 CJK 边界处理有 false positive 风险。

最终点评: 主菜做得很好,配菜需要再调一下味。validUids 的删除是 well-reasoned 的正确决策。但如果 inbound text fallback 现在不能正常工作,就不应该在这个 PR 里引入——要么补全前置条件(确保 bot name 在 map 里),要么拆成单独的 PR 处理。

Copy link
Copy Markdown
Collaborator

@lml2468 lml2468 left a comment

Choose a reason for hiding this comment

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

Reviewed the diff. Core change is clean and well-reasoned.

Core change: remove validUids filtering

Traced all three call sites (actions.ts:192, channel.ts:427, inbound.ts:1528) — the simplification is consistent. The rationale in mention-utils.ts:129-133 is correct: server-side rejection of unknown uids is a better failure mode than client-side cache-miss dropping mentions entirely. Tests updated accordingly and the new test case (actions.test.ts:333) correctly validates the intent.

Inbound text fallback — agreeing with @Jerry-Xin's analysis

Both issues identified are valid:

  1. Dead code path (problem 1): confirmed via code trace. uidToNameMap is populated by getGroupMembersFromCache() + refreshGroupMemberCache() + a few narrow paths (reply sender, DM user). There's no code path that writes credentials.robot_id → botName into the map. Unless the bot happens to appear in the group member list response, uidToNameMap.get(botUid) returns undefined and the entire fallback block is skipped. Suggest either: (a) seed the map on startup with the bot's own name from a fetchUserInfo call, or (b) document explicitly that this fallback only fires when bot is in the member list.

  2. Regex CJK boundary (problem 2): the new tests in mention-utils.test.ts show @BotName你好 correctly doesn't match (lookahead handles it), but the lookbehind [^\\w] doesn't exclude CJK — 你好@BotName would match. Jerry-Xin's suggested fix (add CJK ranges to lookbehind) is the right approach. Worth applying even if the scenario is rare in practice.

Suggestion: consider splitting the fallback into a follow-up PR once the botUid → botName seeding issue is resolved. The core validUids removal is ready to merge; the fallback adds complexity that isn't functional yet.

Copy link
Copy Markdown
Collaborator

@lml2468 lml2468 left a comment

Choose a reason for hiding this comment

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

Code Review — dmwork-adapters#218

Step 1 — Code Quality: PASS

+113/−39 across 7 files. Clean simplification of mention handling.

Core change (validUids removal): Excellent

  • Consistent removal across all 3 call sites (actions.ts:192, channel.ts:427, inbound.ts:1528).
  • The rationale is correct: server-side rejection of unknown uids is a strictly better failure mode than client-side cache-miss silently dropping mentions. This fixes a real production issue (bot-to-bot mentions failing on cold cache).
  • Tests updated to verify all structured mentions now generate entities.

Inbound text fallback: Functional but has known issues
Agreeing with @lml2468's analysis:

  1. The uidToNameMap.get(botUid) dead-code path — bot uid is rarely present in the group member cache map, so this fallback rarely activates in practice.
  2. CJK lookbehind gap — [^\\w] doesn't exclude CJK, so 你好@BotName would be a false positive. The lookahead correctly handles CJK-after (@BotName你好 → no match), but the lookbehind needs CJK ranges added for symmetry.

@lml2468's suggestion to split the fallback into a follow-up is sound — the core validUids removal is the high-value fix and is independently correct. The fallback can be hardened (seed botUid→botName on init, fix lookbehind) in a focused PR.

Approving based on the core change quality. Fallback issues are non-blocking for merge but should be tracked.

Step 2 — Language Compliance: PASS
PR title, description, commit messages all in English.

@Jerry-Xin
Copy link
Copy Markdown
Collaborator Author

Addressed review feedback:

Issue 1 (fallback dead code) → Tracked separately as #220. The core fix (removing validUids filter) is correct and ships in this PR. The fallback at best is a no-op; fixing it requires populating uidToNameMap with the bot's own uid→name during registration, which is a follow-up.

Issue 2 (CJK false positive regex) → Fixed in commit 76b1005. The lookbehind now excludes CJK and extended Latin ranges (\u4e00-\u9fff, \u3040-\u30FF, \uAC00-\uD7AF, \u00C0-\u024F) in addition to \w, so 你好@BotName no longer triggers a false positive. Aligned with MENTION_PATTERN boundary logic. Added 7 unit tests covering CJK boundary, underscore prefix, and suffix word char cases (100/100 passing).

@Jerry-Xin
Copy link
Copy Markdown
Collaborator Author

Now I have full context. Here is my review.


Code Review: PR #218 — trust structured mention uids without cache validation

总体评价: ⚠️ 需要修改

核心设计决策合理——移除 validUids 缓存验证以避免 cache-miss 导致 bot-to-bot mention 失败是正确的方向。但存在一个需要修复的测试缺陷和几个值得讨论的问题。


关键发现

🔴 Critical — 测试用了与生产代码不同的正则

文件: mention-utils.test.ts:411-413 ("inbound text fallback regex" 测试块)

测试中的 makeFallbackRegex lookbehind 是 [^\w],但 inbound.ts:1205 生产代码的 lookbehind 是 [^\w\u4e00-\u9fff\u3040-\u30FF\uAC00-\uD7AF\u00C0-\u024F]

// 测试 (line 413) — 缺少 CJK 排除
(?<=^|[^\w])@${escaped}

// 生产代码 (inbound.ts:1205) — 包含 CJK 排除
(?<=^|[^\w\u4e00-\u9fff\u3040-\u30FF\uAC00-\uD7AF\u00C0-\u024F])@${escaped}

Test Block 1 碰巧全部通过,因为它的用例都没有测试 "CJK 字符紧接在 @ 前面" 的场景(如 你好@BotName)。如果加上这个用例,Test Block 1 会断言失败(旧正则会匹配,而预期应为不匹配)。

Test Block 2(line 755-792)正确复制了生产代码的正则,且覆盖了 CJK lookbehind 场景。

建议: 将 Test Block 1 的 makeFallbackRegex 修正为与生产代码一致,或者删除 Test Block 1(因为 Test Block 2 已经完整覆盖且是正确的)。保留一个使用错误正则的测试块会造成维护混乱。


🟡 Warning — 注释称 "consistent with MENTION_PATTERN" 但实际不一致

文件: inbound.ts:1203

// Lookbehind: exclude ASCII word chars AND Unicode letters/CJK — consistent with MENTION_PATTERN.

MENTION_PATTERNmention-utils.ts:27)的 lookbehind 是:

(?:^|(?<=\s|[^a-zA-Z0-9]))

它只排除 a-zA-Z0-9不排除 CJK。也就是说 MENTION_PATTERN 允许 你好@BotName 匹配,而 inbound fallback 不允许。两者行为不同。

注释应改为更准确的描述,例如:

// Lookbehind: exclude ASCII word chars AND CJK — more conservative than MENTION_PATTERN
// to avoid false-positive bot activations from adjacent CJK text.

🟡 Warning — CJK 前缀排除可能导致合法 mention 漏检

文件: inbound.ts:1204-1205

中文文本不使用空格分词,你好@Jeff 在中文语境中是合法的 mention 写法。当前正则将其排除,意味着如果一个旧版 bot 发送 你好@Jeff 且没有填充 payload.mention,接收方不会识别为 mention。

这可能是有意的保守策略(避免 false positive 比避免 false negative 更重要),但应在注释中明确说明这个 trade-off,而不是仅仅称 CJK 前缀为 "false positive"。


🟡 Warning — 空 botName 防御缺失

文件: inbound.ts:1200-1201

const botName = uidToNameMap.get(botUid);
if (botName) {

if (botName) 对空字符串 "" 是 falsy,所以空名场景被排除了。但如果 botName 是只含空格的字符串(如 " "),escape 后会生成 \ ,产生一个匹配任意位置的弱正则。虽然实际中 bot name 不太可能是纯空格,但建议改为:

if (botName?.trim()) {

🔵 Suggestion — 重复测试用例

文件: mention-utils.test.ts:207-217 ("should generate entities for known uid")

这个测试与 line 168-179 的 "应正确转换单个 mention" 完全相同(输入 "Hi @[uid_bob:Bob]!",验证相同的输出),没有新增任何验证逻辑。建议删除以减少维护负担。


🔵 Suggestion — 每条消息动态编译正则

文件: inbound.ts:1202-1205

每次走到 fallback 分支时都会 new RegExp(...)。对于高流量群组消息(isMentioned === false 时),这是可优化的。可以按 botUid 缓存编译后的 RegExp。不过考虑到这只在 payload.mention 缺失时才触发,实际影响应该很小,优先级低。


🔵 Suggestion — 核心设计决策的安全兜底

移除 validUids 的核心逻辑是合理的,注释也清楚地说明了 rationale:

While the LLM could theoretically hallucinate a uid, the server will reject unknown uids, and filtering here causes cache-miss false negatives that are worse than the low risk of hallucinated uids.

一个值得确认的点:如果 LLM 碰巧 hallucinate 了一个真实存在但非目标的 UID,该用户会收到意外的 mention 通知。依赖 "server will reject unknown uids" 只能防止不存在的 UID,无法防止错误但存在的 UID。这个风险确实很低(UID 格式是随机 hash),但值得在注释中补充说明。


代码风格与架构

  • 变更符合项目现有的 v2/v1 dual-path mention 架构
  • convertStructuredMentions 签名简化后更清晰,调用方也更简洁
  • 所有 3 个调用点(actions.tschannel.tsinbound.ts)都正确移除了 validUids 参数
  • 测试命名从中文改为英文(如 "应处理无效 uid" → "should generate entities for all structured mentions"),风格不完全一致但可接受

总结

级别 数量 关键项
🔴 Critical 1 Test Block 1 正则与生产代码不一致
🟡 Warning 3 注释不准确、CJK 漏检 trade-off 未文档化、空名防御
🔵 Suggestion 3 重复测试、正则缓存、hallucinated UID 风险说明

建议: 修复 Critical 项后可以合并。Warning 项可以在后续 PR 中处理。

@Jerry-Xin
Copy link
Copy Markdown
Collaborator Author

✅ Code Review: PR #218fix: trust structured mention uids without cache validation

总体评价: ✅ LGTM


审查摘要

这个 PR 做了两件事:

  1. 移除 convertStructuredMentions 中的 validUids 缓存过滤 — 结构化 mention (@[uid:name]) 的 uid 不再通过 uidToNameMap 缓存验证,直接信任并生成 entity。
  2. 新增 inbound 文本回退检测 — 当 payload.mention 缺失但消息文本包含 @botName 时,通过正则匹配作为防御性回退。

两者构成纵深防御:修复 1 确保 outbound 始终生成 mention payload;修复 2 确保即使 payload 丢失,inbound 仍能识别 mention。


追踪过程

调用链 1: convertStructuredMentions 的 3 个调用点

actions.ts:192   → handleSend()      → sendMessage (api-fetch.ts:237)
channel.ts:427   → sendText()        → sendMessage (api-fetch.ts:237)
inbound.ts:1533  → resolveAndSendText() → sendMessage (api-fetch.ts:237)

所有调用点已统一移除 validUids 参数。sendMessageapi-fetch.ts:237 将 entities 原样传给服务端。postJsonapi-fetch.ts:36 对非 2xx 响应抛异常,上层均有 try-catch 处理。若服务端拒绝未知 uid,异常会被正确捕获——但实际上服务端大概率忽略未知 uid(mention 是通知元数据,非必须字段)。

调用链 2: inbound 文本回退

inbound.ts:1199  → 检查 !isMentioned && rawBody && type === Text
inbound.ts:1200  → botName = uidToNameMap.get(botUid)  ← 来自服务端成员列表,非用户输入
inbound.ts:1202  → escaped = botName.replace(特殊字符)  ← 正确转义
inbound.ts:1208  → new RegExp(lookbehind + @escaped + lookahead)
inbound.ts:1209  → re.test(rawBody)  ← rawBody 来自 resolveContent (line 1100)

rawBodyresolveContent(message.payload, ...) 的返回值。对 Text 类型消息就是原始文本内容。botName 来自服务端 API 的成员列表缓存(uidToNameMap),不受用户输入控制。

ReDoS 风险: 无。正则使用 lookbehind/lookahead 字符类,无嵌套量词,escaped 已正确转义所有元字符。复杂度 O(n)。

安全性: botName 源自服务端 API 而非用户输入,即使包含特殊字符也已被 escaped 处理。无注入风险。


设计决策验证

"信任结构化 mention uid" 的合理性:

追踪了 uid 的来源链路:system prompt 中注入成员列表 → LLM 生成 @[uid:name]parseStructuredMentions 解析 → convertStructuredMentions 生成 entity。uid 源自成员列表 API,LLM 理论上可能幻觉一个 uid,但 dmwork uid 是随机 32 位 hex hash(见 mention-utils.ts:136 注释),碰撞概率可忽略。旧行为(缓存未命中 → 静默丢弃 mention → bot-to-bot 首次 @mention 失败)的影响明显更严重。

文本回退正则的保守策略:

lookbehind 排除了 CJK/扩展拉丁字符(比 MENTION_PATTERNmention-utils.ts:27[^a-zA-Z0-9] 更严格)。你好@BotName 不会匹配——这是有意的(false positive 比 false negative 更糟,注释在 inbound.ts:1205-1206 明确说明了这个权衡)。


历史背景

ee814ff  feat: add mention entities support with structured @[uid:name] format (#134)
4c46601  fix: resolve bot mention issues — accountId, @all mentionAll, v2 structured mention (#190)
2485bce  fix: trust structured mention uids without cache validation  ← 本 PR

validUids 过滤在 #134 引入,当时是合理的保守策略。#190 保留了该过滤。本 PR 基于实际生产问题(bot-to-bot @mention 首次失败 #217)移除了过滤,属于合理的策略演进。未发现撤销了之前有意的设计——相反,validUids 的存在正是 #217 的根本原因。


测试覆盖

  • mention-utils.test.ts: 15 个新用例覆盖回退正则的正向/反向边界(CJK 前缀、email 格式、域名后缀、日韩文字符等)
  • 已有测试均已更新移除 validUids 参数,期望值更新为"所有结构化 mention 都生成 entity"
  • 回退正则测试使用本地 buildFallbackRegex() 函数镜像 inbound.ts 中的实现,注释 (mention-utils.test.ts:687) 明确标注了同步要求

📊 总结

代码质量评分: 8/10

  • 追踪了 5 个调用链(3 个 convertStructuredMentions 出口 + inbound 回退路径 + sendMessagepostJson 错误处理)
  • 检查了 7 个变更文件 + 1 个依赖文件 (api-fetch.ts)
  • 发现 0 个严重问题,0 个一般问题
  • 2 个 nitpick(均低于 80 分阈值,不值得标记):回退正则在 inbound.ts 和测试文件中存在代码重复(可提取为共享函数);inbound 回退路径缺少集成测试(仅有正则单元测试)

最终点评: 改动干净利落——移除了一个在缓存冷启动时制造 false negative 的防御层,同时用两道纵深防御(outbound 信任 uid + inbound 文本回退)替代。注释清楚地解释了权衡。测试覆盖了关键边界。没什么可说的。

- Remove validUids filtering from convertStructuredMentions()
- Add left-boundary lookbehind to inbound text fallback regex
- Add 10 regex boundary unit tests
- Clean up stale validUids from all call sites and tests
- 610/610 tests passing
- Lookbehind now excludes CJK and extended Latin ranges in addition to \w,
  preventing false positive matches like '你好@botName'
- Aligns boundary logic with MENTION_PATTERN in mention-utils.ts
- Add 7 unit tests covering CJK boundary, underscore, suffix word chars

Refs dmwork-org#220
- Remove old test block with stale regex (lookbehind lacked CJK exclusion)
- Consolidate into single test block with 13 cases matching production regex
- Fix comment: 'more conservative than MENTION_PATTERN' (not 'consistent with')
- Add trade-off comment: CJK-adjacent false negative is intentional
- Guard botName with .trim() to prevent weak regex from whitespace-only names
- Remove duplicate test case 'should generate entities for known uid'
- Add hallucinated UID risk note in mention-utils.ts comment

Refs dmwork-org#220
@Jerry-Xin Jerry-Xin force-pushed the fix/bot-mention-validation branch from d11b606 to d9fe732 Compare April 28, 2026 10:03
Copy link
Copy Markdown
Collaborator Author

@Jerry-Xin Jerry-Xin left a comment

Choose a reason for hiding this comment

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

Well-scoped fix for a real cache-miss race condition in bot-to-bot mentions, with solid test coverage.


✅ Highlights

  • Root cause is correct: The validUids filter using uidToNameMap.keys() was a cache-dependent gate. On first message before refreshGroupMemberCache populates the map, structured mentions with valid UIDs would be silently dropped. Removing this filter is the right fix — structured mentions are already LLM-generated from the system prompt's member list, so the uid is almost certainly valid.

  • Defense-in-depth for inbound: The text fallback at inbound.ts:1225-1241 is a good safety net for bot-to-bot messages where payload.mention may be absent. The conservative regex (excluding CJK lookbehind) correctly favors false negatives over false positives for bot activation.

  • Test coverage is thorough: The fallback regex tests (mention-utils.test.ts:686-751) cover both positive and negative boundary cases well — email-like patterns, CJK adjacency, domain-like suffixes, and word-char lookahead.

  • Consistent across all three call sites: actions.ts:192, channel.ts:427, and inbound.ts:1565 all have the validUids parameter removed consistently.


💬 Non-blocking

  1. 🔵 Suggestion — Duplicated regex between inbound.ts and test (inbound.ts:1234, mention-utils.test.ts:690-694)

    The fallback regex is constructed identically in both inbound.ts and buildFallbackRegex() in the test file. If either copy drifts, the tests become meaningless. Consider extracting a buildMentionFallbackRegex(botName: string): RegExp function into mention-utils.ts and importing it from both places. The test comment "Must mirror the regex in inbound.ts" acknowledges the risk but doesn't prevent it.

  2. 🔵 Suggestion — new RegExp per inbound message (inbound.ts:1234)

    The fallback regex is compiled on every inbound group message where !isMentioned. For high-traffic groups this is a per-message allocation. It's not a performance problem today (the regex is simple, the string is short), but if bot names are stable per session, a cached regex keyed on botUid would be trivial. Low priority — just noting it.

  3. 🔵 Suggestion — Comment length in convertStructuredMentions (mention-utils.ts:129-136)

    The 7-line comment explaining why validation was removed is useful context for this PR, but is long relative to the surrounding code. The first sentence ("Always generate entity for structured mentions") plus the key insight ("filtering here causes cache-miss false negatives") would suffice. The rest reads more like PR description than code comment.

  4. 🟡 Warning — botName?.trim() empty-string check (inbound.ts:1227)

    uidToNameMap.get(botUid) could return a whitespace-only string if the member cache is populated with bad data, which .trim() catches. Good. However, if botName contains regex-special characters (e.g., a display name like C++Bot or (Test)), the escapeRegExp call at line 1228 handles it correctly. No issue — just confirming I checked.


Summary

The change is clean, well-reasoned, and correctly addresses the cache-miss race condition described in #217. The tradeoff (trusting LLM-generated UIDs instead of validating against cache) is explicitly documented and the risk is genuinely low given the 32-char hex hash uid space. The inbound text fallback is a sensible addition with appropriate conservatism. Ship it.

@Jerry-Xin Jerry-Xin merged commit 96c5850 into dmwork-org:develop Apr 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: bot-to-bot structured mentions fail before member cache warms

2 participants