Skip to content

fix(qqbot): markdown-first send, replyMsgId TTL, and dead code removal - #6201

Merged
wenshao merged 10 commits into
QwenLM:mainfrom
Eric-GoodBoy-Tech:fix/qqbot-reply-msg-fixes
Jul 3, 2026
Merged

fix(qqbot): markdown-first send, replyMsgId TTL, and dead code removal#6201
wenshao merged 10 commits into
QwenLM:mainfrom
Eric-GoodBoy-Tech:fix/qqbot-reply-msg-fixes

Conversation

@Eric-GoodBoy-Tech

@Eric-GoodBoy-Tech Eric-GoodBoy-Tech commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

What this PR does

Core bug fixes for the QQ Bot channel adapter's message sending. This is PR 2 of 4 splitting the closed PR #5902.

Message sending: Adopts a markdown-first send strategy — always sends as markdown (msg_type: 2) first, falls back to active message retry on non-4xx failure, with a plain-text fallback for active messages without reply context. msgSeqMap tracks sequence with rollback on failure, and errors are re-thrown for .catch() callers.

replyMsgId TTL: Adds a 5-minute TTL with periodic cleanup (60s interval) to prevent stale reply context. replyMsgId storage changes from Map<string, string> to Map<string, {msgId, timestamp}> with cascaded msgSeqMap cleanup and a setReplyMsgId() helper that cleans up old entries. restoreQQState() has backward-compatible migration from the old string format.

Dead code removal: Removes hasMarkdownSyntax(), hasLinkSyntax(), and splitText() exports — no longer needed with the markdown-first strategy.

Test updates: Updated send.test.ts with TTL expiry, markdown fallback chain, and noreply suppression tests. 63 tests pass, typecheck is clean.

Why it's needed

The original code had three problems: (1) the chunking-based send strategy was unreliable and splitText() produced incorrect splits for CJK text, (2) replyMsgId entries accumulated indefinitely with no TTL, causing stale reply context across long-running bot sessions, and (3) errors were silently swallowed with break instead of propagating to error handlers. This PR fixes all three.

Reviewer Test Plan

How to verify

  1. Check that sendMessage() always uses markdown (msg_type: 2) as the first send attempt.
  2. Verify that when markdown is rejected (non-4xx), the code retries as an active message with msg_id and msg_seq.
  3. Confirm replyMsgId entries older than 5 minutes are treated as expired and eagerly evicted.
  4. Verify the cleanup timer persists evictions by calling saveQQState().
  5. Run cd packages/channels/qqbot && npx vitest run — all 63 tests must pass.
  6. Run npx tsc --noEmit — QQChannel.ts must typecheck clean.

Evidence (Before & After)

N/A — behavior change is in the send strategy and data structure, verified by unit tests. No user-visible UI.

Tested on

OS Status
🍏 macOS ✅ tested
🪟 Windows ⚠️ not tested
🐧 Linux ⚠️ not tested

Environment (optional)

Local vitest + tsc validation.

Risk & Scope

  • Main risk or tradeoff: Removing splitText() means messages exceeding ~2000 characters will be sent as a single oversized request and may silently fail on the QQ API side. The system instruction was updated to reflect manual 2000-char limit.
  • Not validated / out of scope: End-to-end integration with a live QQ Bot (requires QQ Bot credentials). Gateway-level race conditions between concurrent sendMessage calls are not addressed.
  • Breaking changes / migration notes: hasMarkdownSyntax, hasLinkSyntax, and splitText exports are removed. restoreQQState() auto-migrates the old string-format replyMsgId on next startup.

Linked Issues

N/A — this is part of a multi-PR split from #5902.

中文说明

本 PR 做了什么

QQ Bot 频道适配器消息发送的核心错误修复。这是从已关闭的 PR #5902 拆分出的 4 个 PR 中的第 2 个。

消息发送:采用 markdown 优先发送策略 — 始终优先以 markdown(msg_type: 2)发送,非 4xx 失败时重试为主动消息,无回复上下文时回退到纯文本。msgSeqMap 跟踪序列并在失败时回滚,错误会被重新抛出让 .catch() 调用方处理。

replyMsgId TTL:添加 5 分钟 TTL 和 60 秒清理定时器,防止回复上下文过期。replyMsgId 存储从 Map<string, string> 改为 Map<string, {msgId, timestamp}>,附带级联 msgSeqMap 清理和 setReplyMsgId() 辅助函数。restoreQQState() 兼容旧字符串格式迁移。

死代码删除:移除 hasMarkdownSyntax()hasLinkSyntax()splitText() 导出 — 在 markdown 优先策略下不再需要。

测试更新:更新 send.test.ts 添加 TTL 过期、markdown 回退链和 noreply 抑制测试。63 个测试通过,类型检查通过。

为什么需要

原代码有三个问题:(1) 基于分块的发送策略不可靠,splitText() 对中文字符串分割不正确;(2) replyMsgId 条目无 TTL 持续累积,导致长期运行的 Bot 会话中出现过期的回复上下文;(3) 错误被 break 静默吞掉而不传播到错误处理器。本 PR 修复了这三个问题。

Reviewer 测试计划

如何验证

  1. 检查 sendMessage() 是否始终先以 markdown(msg_type: 2)发送
  2. 验证 markdown 被拒绝(非 4xx)时,代码重试为带 msg_id 和 msg_seq 的主动消息
  3. 确认超过 5 分钟的 replyMsgId 条目在首次检测时被视为过期并被立即删除
  4. 验证清理定时器通过调用 saveQQState() 持久化删除
  5. 运行 cd packages/channels/qqbot && npx vitest run — 所有 63 个测试必须通过
  6. 运行 npx tsc --noEmit — QQChannel.ts 类型检查必须通过

测试环境

OS 状态
🍏 macOS ✅ 已测试
🪟 Windows ⚠️ 未测试
🐧 Linux ⚠️ 未测试

风险与范围

  • 主要风险:移除 splitText() 后,超过约 2000 字符的消息将作为单次请求发送,可能在 QQ API 端静默失败。系统指令已更新以反映手动 2000 字符限制。
  • 不在此范围内:与真实 QQ Bot 的端到端集成测试(需要 QQ Bot 凭证)。并发 sendMessage 调用的网关级竞态条件未在此 PR 中解决。
  • 破坏性变更/迁移说明:hasMarkdownSyntaxhasLinkSyntaxsplitText 导出已移除。restoreQQState() 在下次启动时自动迁移旧字符串格式的 replyMsgId。

关联 Issue

无 — 这是从 #5902 拆分出的多 PR 工作的一部分。

Loading
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.

4 participants