Skip to content

fix(qqofficial): allocate fresh msg_seq per inbound msg_id (#2290)#2294

Open
dadachann wants to merge 62 commits into
dev/4.11.xfrom
fix/qqofficial-msg-seq-dedup
Open

fix(qqofficial): allocate fresh msg_seq per inbound msg_id (#2290)#2294
dadachann wants to merge 62 commits into
dev/4.11.xfrom
fix/qqofficial-msg-seq-dedup

Conversation

@dadachann

Copy link
Copy Markdown
Contributor

Summary

Fixes #2290. QQ Official v2 API deduplicates passive messages by (msg_id, msg_seq). The adapter reused msg_seq for multiple sends tied to the same inbound msg_id, so QQ rejected later sends with:

40054005 消息被去重,请检查请求msgseq

Three pre-existing strategies collided:

  • Text (send_private_text_msg / send_group_text_msg): no msg_seq at all → QQ defaulted it to 1, so any 2nd text reply under the same msg_id was a duplicate.
  • Rich media (_send_media_msg): used a global _msg_seq_counter — unique, but could still collide with the implicit msg_seq=1 of a text send under the same msg_id.
  • Streaming (reply_message_chunk): msg_seq pinned at 1 forever, only index advanced → every chunk reused msg_seq=1 (the most frequent trigger).

Changes

  • Add QQOfficialClient.next_reply_msg_seq(msg_id) — a per-inbound-msg_id allocator backed by a bounded OrderedDict (max 1024 keys, LRU eviction) + an asyncio.Lock for concurrency safety. No msg_id (proactive sends) falls back to the existing global counter.
  • Wire it into C2C/group text sends and rich media.
  • Streaming now advances ctx['msg_seq'] per chunk.
  • Guild channel sends (send_channle_*) are intentionally left unchanged — they hit the legacy guild API, which does not use this dedup mechanism.

Tests

Added 3 regression tests to test_qqofficial_eba_adapter.py:

  • allocator increments per msg_id, is independent across ids, falls back for missing id, and is gap-free under 50 concurrent allocations;
  • multiple text replies under one inbound msg_id carry msg_seq 1, 2, 3;
  • stream chunks emit strictly increasing, unique msg_seq.

All 12 tests in the file pass; ruff check and ruff format --check clean.

RockChinQ and others added 30 commits May 7, 2026 15:18
- TelegramAdapter inherits AbstractPlatformAdapter with all capabilities
- TelegramEventConverter handles all Update types: message, edited_message,
  chat_member, my_chat_member, callback_query, message_reaction
- TelegramAPIMixin implements: edit_message, delete_message, forward_message,
  get_group_info, get_group_member_list/info, get_user_info, get_file_url,
  mute/unmute/kick_member, leave_group
- PLATFORM_API_MAP for call_platform_api: pin/unpin message, set chat title/desc,
  get admins, send chat action, create invite link, answer callback query
- Full backward compat: legacy FriendMessage/GroupMessage listeners still work
- Preserves all existing functionality: stream output, markdown card, forum topics
- Old sources/telegram.py untouched for gradual migration
Expose skill tools (activate/register_skill/native exec) like native tools
instead of gating them behind the skill_authoring capability:
- toolmgr.get_all_tools drops include_skill_authoring; SkillToolLoader
  self-gates on sandbox + skill_mgr
- preproc drops the include_skill_authoring branch; pipeline-bound skills
  and the skills resource gate on skill_mgr presence

Persist activated skills into host.activated_skills conversation state so
they survive across runs (host writes at activate; last-write-wins); drop
the dead restore_activated_skills helper.

Prefill ToolResource.parameters host-side (tool_mgr.get_tool_schema) so
runners build LLM tools without per-tool get_tool_detail round-trips.

Align agent-runner-pluginization design docs to the all-tool model.
- references/skill-all-tool-acceptance.md: acceptance matrix for the skill
  all-tool model (runner x lifecycle x backend), case status, exit criteria,
  and the #2271 known issue (pre-existing box nested-mount, not this branch)
- cases/skill-discovery-via-mcp-gateway.yaml: schema-valid case proving an
  external harness discovers skills via langbot_list_assets (the new 'skills'
  asset class); marked blocked-env until remote claude-code is responsive
huanghuoguoguo and others added 25 commits June 22, 2026 23:26
# Conflicts:
#	pyproject.toml
#	uv.lock
- Replace legacy pipeline binding card + RoutingRulesEditor with unified
  EventBindingsEditor; remove use_pipeline_uuid/pipeline_routing_rules
  from bot form schema and API update handler
- Add _augment_event_data() to botmgr for filter virtual fields
  (message_text, message_element_types, chat_type)
- Add alembic migration 0009: migrate use_pipeline_uuid and
  pipeline_routing_rules into event_bindings on first run
- Fix command.tsx: data-[disabled] -> data-[disabled=true] so cmdk 1.x
  items (data-disabled=false) are not pointer-events:none
- EventBindingsEditor: onSelect on CommandItems, filter conditions panel,
  disabled bindings section, dnd reorder
- i18n: add filter/condition keys for zh-Hans and en-US
- Update tests to match new bot service behavior

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
groupByCategory pushed multi-category adapters (lark, wecom, discord,
slack) into every matching bucket, so the adapter Select rendered
duplicate SelectItem values — triggering React duplicate-key warnings
and corrupting Radix item tracking. Assign each item to its highest
-priority matching category only. Also de-dupes the wizard card grid.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Unorchestrated events are discarded by default, so an explicit discard
target is redundant. Drop the discard CommandGroup (and the now-unused
CommandSeparator import); the currentLabel() discard branch is kept so
any pre-existing discard bindings still render correctly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The 12 old adapters that now have an EBA replacement are tagged
`spec.legacy: true` in their source manifests. Principle: don't delete,
de-emphasize.

- sources/*.yaml (aiocqhttp, dingtalk, discord, kook, lark,
  officialaccount, qqofficial, slack, telegram, wecom, wecombot,
  wecomcs): add spec.legacy: true
- Adapter / IChooseAdapterEntity types: add optional legacy flag
- BotForm adapter Select: split legacy adapters into a collapsed,
  grayscale group at the bottom with an explanatory hint; auto-expand
  when the bot already uses a legacy adapter
- Wizard platform picker: same collapsed legacy section
- i18n: legacyAdapters / legacyAdaptersHint (zh-Hans, en-US)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Telegram (EBA) manifest was missing spec.categories, so it fell
into the uncategorized/protocol bucket. Restore popular + global to
match the legacy telegram adapter.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Move conditions toggle between event select and arrow; drop "IF" label
- Remove "all events" (*) option from event select
- Add i18n labels for concrete event names (zh/en/ja) via bots.eventNames
- Narrow fallback event set to message.received for adapters without
  declared supported_events (legacy adapters only emit message.received)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Only surface `ns.*` wildcard when the namespace has 2+ concrete events,
  so single-event adapters (legacy) show just the one event
- Show raw event code as a muted subtitle under each option's label

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a trailing badge (icon + label) to each entry in the sidebar Agent
section so users can tell Agent orchestration apart from legacy Pipeline.
Thread the agent `kind` field through SidebarEntityItem.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the text label; keep the icon with a title tooltip so names have
more room in the narrow sidebar.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a toggle (left of the "+" button) that groups the Agent section by
kind, showing "Agent" and "Pipeline" sub-headers. State persists in
localStorage, mirroring the extensions group-by-type pattern.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add group-by-type button to extensions category header (mirrors the
  agent group-by-kind pattern) — syncs with the extensions page Switch
  via shared SidebarDataContext state
- Relocate both group and refresh controls to sit left-aligned
  immediately after the title for both Agent and Extensions sections
- Add plugins.groupByTypeShort i18n key to all 8 locales

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…0054005 dedup

QQ Official v2 API deduplicates passive messages by (msg_id, msg_seq).
The adapter reused msg_seq across multiple sends under the same inbound
msg_id, so multi-part text replies, rich media, and especially streaming
chunks (msg_seq pinned at 1) were rejected with:

  40054005 消息被去重,请检查请求msgseq

Add a per-inbound-msg_id sequence allocator (next_reply_msg_seq) on
QQOfficialClient, backed by a bounded OrderedDict + asyncio lock, and use
it for C2C/group text sends and rich media. Streaming now advances
ctx['msg_seq'] per chunk. Proactive sends (no msg_id) fall back to the
existing global counter.

Closes #2290
@dosubot dosubot Bot added size:M This PR changes 30-99 lines, ignoring generated files. eh: Improve enhance: 现有功能的改进 / improve current features IM: qqofficial QQ 官方 API Webhook 适配器相关 / QQ API (Webhook) adapter related labels Jun 29, 2026
@dadachann

Copy link
Copy Markdown
Contributor Author

CI 说明:当前红勾来自 base 分支 (dev/4.11.x) 的既有问题,与本 PR 无关

逐项核对:

Check 失败原因 是否本 PR 引入
Ruff Lint & Format dev/4.11.x 上已有 48 个文件未通过 ruff format --check(涉及 slack / telegram / wecombot / agent-runner / alembic 等,本 PR 均未触碰)
Unit Tests 3.11/3.12/3.13 51 个 collection error,全部为 ModuleNotFoundError: No module named 'langbot_plugin.api.entities.builtin.agent_runner',即 CI 解析到的 langbot_plugin 版本尚无该模块
E2E / Fast Integration 同上,import 阶段即失败
Frontend Lint 前端检查,与后端改动无关
Box Integration / CLA ✅ pass

验证:

  • 直接 checkout 未带本改动的干净 origin/dev/4.11.x,执行 uv run ruff format src --check 同样报 48 files would be reformatted —— 基线本身即红。
  • 本 PR 改动的 3 个文件单独跑 CI 同版本 ruff(0.14.14):All checks passed + 3 files already formatted
  • 51 个 test collection error 中无任何 qqofficial 相关项;本 PR 新增的 3 个回归测试本地全部通过(12 passed)。

结论:本 PR 代码与测试本身可通过全部相关检查。待 dev/4.11.x 的格式化债与 langbot_plugin 版本问题在 base 修复后 rerun 即可转绿。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

eh: Improve enhance: 现有功能的改进 / improve current features IM: qqofficial QQ 官方 API Webhook 适配器相关 / QQ API (Webhook) adapter related size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants