32 KiB
Minimal Cross-Platform Runtime Plan
Goal
Make MaiBot runtime identity handling platform-aware with the smallest safe change set.
Success means:
- One canonical
is_bot_self(platform, user_id)with no argument-order traps. - Platform-correct outbound bot sender IDs for configured platforms.
- Platform-aware bot filtering in stored message queries.
- No false assumption that QQ is the universal bot identity for unknown platforms.
- Existing QQ configuration and WebUI behavior preserved without regressions.
This pass does not try to clean every QQ-oriented string, thread platform through PFC internals, or touch user-visible formats.
Out of Scope for This Plan
The following are explicitly deferred because they are not required for runtime correctness in this pass:
- PFC platform propagation.
ChatObserver._message_to_dictcurrently omits platform from the serializeduser_infodict;observation_info.dict_to_session_messagereadsuser_info_dict.get("platform", "")which is always"". Fixing this cascades intochat_observer.py,chat_states.py,observation_info.py, and possiblyconversation.py. Deferred to a follow-up plan. - PFC-internal identity checks. Sites like
reply_checker.py:61,action_planner.py:157, andchat_observer.py:337(insideprocess_chat_history, which has no in-repo call sites) operate on PFC serialized dicts where platform is always"". They cannot be migrated tois_bot_self(platform, user_id)until PFC serialization is fixed. Deferred. - PFC prompt platformization. Replacing
QQ私聊/QQ聊天inaction_planner.py,reply_generator.py,pfc.py, andemoji_pluginrequires threading platform through PFC prompt construction context. Deferred. - MCP permission ID format changes (
qq:...->{platform}:...). - Capability API default changes (
args.get("platform", "qq")->"all_platforms"). - Dashboard/UI/config label cleanup.
- Config field renames (
enable_qq_tools,qq_api_base_url, etc.). - A new local WebUI bot identity constant (
WEBUI_BOT_USER_ID). - Historical database migration.
Verified Current State
This plan is based on the checked-in code, not on assumptions from previous drafts.
Bot-Self Logic
| Location | Signature | Status |
|---|---|---|
src/common/utils/system_utils.py:2 |
is_bot_self(user_id, platform) |
Stub; only matches literal "bot_self" + "test_platform" — never true in production |
src/chat/utils/utils.py:69 |
is_bot_self(platform, user_id) |
Real implementation; has dangerous QQ fallback at lines 112-113 |
src/person_info/person_info.py:247 |
_is_bot_self(self, platform, user_id) |
Duplicate logic with same QQ fallback |
Wrong-order call sites (8 total):
src/bw_learner/expression_learner.pyx3 (lines 158, 241, 301)src/common/utils/utils_message.pyx4 (lines 370, 440, 476, 515)src/webui/routers/chat/support.pyx1 (line 65)
Correct-order callers (verify only, do not modify):
src/chat/replyer/private_generator.py(lines 680, 816)src/chat/replyer/group_generator.py(line 831)src/chat/planner_actions/planner.py(line 245)src/memory_system/chat_history_summarizer.py(line 368)
PFC Serialization Gap (Known, Not Fixed Here)
ChatObserver._message_to_dict() (lines 30-45) serializes messages without platform in user_info:
"user_info": {
"user_id": message.user_id,
"user_nickname": message.user_nickname,
# NOTE: no "platform" key
},
Meanwhile, observation_info.dict_to_session_message() (line 30) reads:
platform = user_info_dict.get("platform", "") # always "" due to above gap
Consequence: PFC-internal identity checks that operate on these serialized dicts cannot use is_bot_self(platform, user_id) until this gap is closed. Those sites are excluded from this plan entirely.
WebUI Compatibility Reality
- Local WebUI bot messages are stored with
platform="webui"anduser_id=str(global_config.bot.qq_account). is_bot_selfalready treatsplatform == "webui"as comparing againstqq_account.support.pycontains additional heuristic fallbacks built around this sender shape.
This plan preserves this behavior. Introducing a new WebUI bot ID would require transition logic in both Python and SQL and is deferred.
Sender Construction Sites
Outbound bot sender IDs are hard-coded to global_config.bot.qq_account in:
src/services/send_service.py:91(hastarget_stream.platform)src/chat/replyer/group_generator.py:1125(hasself.chat_stream.platform)src/chat/replyer/private_generator.py:965(hasself.chat_stream.platform)src/chat/brain_chat/PFC/message_sender.py:44(has chat stream context)
All have platform available at runtime.
Design Rules
- Prefer compatibility over cleanliness.
- Fix runtime identity and sender correctness only at sites where
platformis already available. - Do not thread platform through PFC serialization or prompt construction in this pass.
- Do not introduce new user-visible formats unless required for runtime correctness.
- Do not key implementation on
ChatObserver.process_chat_history(no in-repo call sites). - Preserve
bot.qq_accountconfig — no schema migration.
Identity Matrix (This Plan)
| Runtime context | Canonical bot user ID |
|---|---|
platform == "qq" and qq_account configured |
str(global_config.bot.qq_account) |
platform == "telegram" |
platforms["tg"] or platforms["telegram"] |
platform == "webui" |
str(global_config.bot.qq_account) (current storage reality) |
| Other configured adapter platforms | platforms[platform] |
| Unknown / unconfigured platform | no account; is_bot_self returns False + warning |
qq_account in {None, "", 0, "0"} means QQ bot identity is not configured. get_bot_account("qq") returns "" and is_bot_self("qq", any_id) returns False in that case.
Phase 0: Unify is_bot_self
Objective
Make src/chat/utils/utils.py::is_bot_self(platform, user_id) the only real implementation, and remove the argument-order trap.
Allowed Files
src/common/utils/system_utils.pysrc/chat/utils/utils.pysrc/person_info/person_info.pysrc/bw_learner/expression_learner.pysrc/common/utils/utils_message.pysrc/webui/routers/chat/support.py- tests
Required Changes
ATOMICITY CONSTRAINT: Steps 1 and 2 MUST be committed together. Do NOT commit step 2 without all step 1 fixes. A partial commit creates a silent semantic inversion: callers passing
(platform, user_id)to a function that still interprets them as(user_id, platform). The stub always returnsFalsein production so the bug would be silent — no crash, just wrong results.
-
Convert
src/common/utils/system_utils.py::is_bot_selfinto a thin wrapper with signature(platform, user_id)that delegates tosrc.chat.utils.utils.is_bot_self. Use a method-local import to avoid circular dependency (system_utils -> chat.utils.utils -> message -> mai_message_data_model -> utils_message -> system_utils). -
Fix all 8 wrong-order call sites — swap arguments to
(platform, user_id):File Line Current Call Required Fix expression_learner.py158 is_bot_self(msg...user_id, msg.platform)swap to (msg.platform, msg...user_id)expression_learner.py241 is_bot_self(target_msg...user_id, target_msg.platform)swap expression_learner.py301 is_bot_self(current_msg...user_id, current_msg.platform)swap utils_message.py370 is_bot_self(user_id, platform)swap utils_message.py440 is_bot_self(user_id, platform)swap utils_message.py476 is_bot_self(user_id, platform)swap utils_message.py515 is_bot_self(user_id, platform)swap support.py65 is_bot_self(user_id, msg.platform)swap + change import source to src.chat.utils.utils -
Replace
Person._is_bot_selfbody with delegation to the canonical function. Use a method-local import insideperson_info.pyto avoid a new import cycle. -
Do not change identity semantics yet (QQ fallback deletion is Phase 1).
-
Update existing test mocks:
pytests/utils_test/message_utils_test.py:203definesdummy_is_bot_self(user_id, platform)with the old argument order. After swapping call sites, this mock must be updated todummy_is_bot_self(platform, user_id)and the injection at line 235 must match.
Acceptance Criteria
- Only one real implementation remains:
src/chat/utils/utils.py::is_bot_self. - No runtime caller passes
(user_id, platform). system_utils.is_bot_selfis a compatibility wrapper only, using a local import (not top-level).Person._is_bot_selfdelegates instead of duplicating logic.- Tests cover QQ, Telegram, WebUI, and unknown platform cases.
- Existing test mocks updated to match new argument order.
Phase 1: Identity Resolution + Sender Construction + Trivial Prompt Cleanup
REVIEW NOTE (Claude + Codex joint review, 2026-03-15): Original plan had Phase 1 (identity resolution) and Phase 2 (sender construction) as separate phases. Joint review identified a critical regression window: Phase 1 deletes the QQ fallback (lines 112-113), but sender construction still hard-codes
qq_accountuntil Phase 2. Between the two phases,is_bot_self("telegram", qq_account)returnsFalsefor existing stored bot messages, breakingfilter_bot, stats, and identity checks on non-QQ platforms. Resolution: merge into a single atomic phase.
Objective
Create one canonical place to resolve bot accounts for any runtime platform, delete the dangerous unknown-platform QQ fallback, make filter_bot platform-aware, fix sender construction, and clean up trivial prompt wording. Only touch sites where platform is already available at runtime — do not touch PFC internals.
ATOMICITY CONSTRAINT: The QQ fallback deletion (lines 112-113) and sender construction fixes (4 sites) MUST be in the same commit. Deleting the fallback without fixing senders creates a regression for any non-QQ platform where bot messages are stored with
qq_account.
Allowed Files
src/chat/utils/utils.pysrc/chat/planner_actions/planner.pysrc/chat/utils/statistic.pysrc/common/message_repository.pysrc/webui/routers/chat/support.pysrc/services/send_service.pysrc/chat/replyer/group_generator.pysrc/chat/replyer/private_generator.pysrc/chat/brain_chat/PFC/message_sender.pysrc/person_info/person_info.py- tests
Required Helper Additions (in src/chat/utils/utils.py)
def get_bot_account(platform: str) -> str
def get_all_bot_accounts() -> dict[str, str]
get_bot_accountreplacesget_current_platform_account. After Phase 1,get_current_platform_accountmust not exist. Update all its callers (includingis_mentioned_bot_in_messageat line 127).get_all_bot_accounts()returns only configured, non-empty runtime identities. Whenqq_accountis configured and non-zero, include:"qq": str(qq_account)"webui": str(qq_account)(current storage reality)- plus any configured external platform accounts from
bot.platforms
parse_platform_accountsremains as internal helper.- Normalize platform strings: apply
.lower().strip()inis_bot_self,get_bot_account, andparse_platform_accounts(on parsed keys). This ensures config values like"TG:123"are stored as key"tg"and matched correctly.
No classes, registries, managers, or generalized identity frameworks.
Required Identity Semantics Update
Update is_bot_self to follow the Identity Matrix. Key change:
Delete lines 112-113 (the return user_id_str == qq_account fallback for unknown platforms). Replace with return False + logger.warning(...). This is the single most important line deletion in the entire plan.
Direct Comparison Replacements
| File | Line | Current Code | Action |
|---|---|---|---|
utils.py |
88 | inside is_bot_self |
KEEP — internal to canonical function |
utils.py |
124 | inside is_mentioned_bot_in_message |
UPDATE — use get_bot_account(platform) |
planner.py |
125 | platform = message.platform or "qq" |
UPDATE — see below |
planner.py |
138 | if user_id == global_config.bot.qq_account: |
REPLACE with is_bot_self(platform, str(user_id)) |
statistic.py |
2111 | str(global_config.bot.qq_account) |
REPLACE — see below |
message_repository.py |
166 | Messages.user_id != global_config.bot.qq_account |
UPDATE — see below |
support.py |
68 | user_id == str(global_config.bot.qq_account) |
REPLACE with is_bot_self(msg.platform, user_id) |
Excluded from This Phase (PFC-Internal, Platform Unavailable)
| File | Line | Current Code | Why excluded |
|---|---|---|---|
chat_observer.py |
337 | user_info.user_id == global_config.bot.qq_account |
process_chat_history has no call sites; PFC dict lacks platform |
reply_checker.py |
61 | str(user_info.user_id) == str(global_config.bot.qq_account) |
Operates on PFC dicts without platform |
action_planner.py |
157 | bot_id = str(global_config.bot.qq_account) |
PFC-internal; platform not propagated |
Sender Construction Sites (Now Part of This Phase)
These were originally deferred to a separate Phase 2. After joint review, they are merged into Phase 1 to prevent a regression window. See Sender Construction Changes section below for the required fixes.
| File | Line | Current Code | Action |
|---|---|---|---|
send_service.py |
91 | user_id=str(global_config.bot.qq_account) |
REPLACE with get_bot_account(target_stream.platform) |
group_generator.py |
1125 | user_id=str(global_config.bot.qq_account) |
REPLACE with get_bot_account(self.chat_stream.platform) |
private_generator.py |
965 | user_id=str(global_config.bot.qq_account) |
REPLACE with get_bot_account(self.chat_stream.platform) |
message_sender.py |
44 | user_id=global_config.bot.qq_account |
REPLACE with get_bot_account(platform) from chat stream context |
Detailed Instructions
planner.py:125 — Empty Platform Handling
# BEFORE:
platform = message.platform or "qq"
# AFTER:
platform = message.platform or ""
if not platform:
logger.warning("planner: message has no platform set, bot-self detection will be skipped")
Instead of falsely assuming QQ, an empty platform means is_bot_self("", user_id) returns False. This is safer than falsely matching a QQ account that may belong to a real user on another platform.
statistic.py:2109-2151
# BEFORE (lines 2109-2114):
bot_qq_account = (
str(global_config.bot.qq_account)
if hasattr(global_config, "bot") and hasattr(global_config.bot, "qq_account")
else ""
)
# AFTER:
from src.chat.utils.utils import is_bot_self
# BEFORE (line 2151):
if bot_qq_account and message.user_id == bot_qq_account:
total_replies[interval_index] += 1
# AFTER:
if is_bot_self(message.platform or "", message.user_id or ""):
total_replies[interval_index] += 1
Place the import locally to avoid circular import risk. Messages records have a .platform field so this handles all platforms.
support.py:62-70 — WebUI Bot Detection
# CURRENT (after Phase 0 arg-order fix):
is_bot = is_bot_self(msg.platform, user_id)
if not is_bot and group_id and group_id.startswith(VIRTUAL_GROUP_ID_PREFIX):
is_bot = user_id == str(global_config.bot.qq_account) # QQ-only fallback
elif not is_bot:
is_bot = not user_id.startswith(WEBUI_USER_ID_PREFIX) # reverse heuristic
After Phase 1, is_bot_self correctly handles all current platform cases:
- WebUI local chat:
is_bot_self("webui", str(qq_account))returnsTrue. - Virtual-group mode:
msg.platformcarries the simulated platform (e.g.,"qq"), sois_bot_selfuses the correct platform account.
The entire if/elif block becomes dead code.
Target code:
is_bot = is_bot_self(msg.platform, user_id)
Delete the if/elif fallback block. After Phase 1, is_bot_self handles all current platform cases correctly, making this block dead code. If a smoke test disproves this assumption, stop and report instead of keeping the fallback.
filter_bot in message_repository.py:166
Current code has a type mismatch: Messages.user_id is string, global_config.bot.qq_account is int.
NOTE: message_repository.py does not currently import or_, and_, or not_ from SQLAlchemy (only func at line 7). These imports must be added.
# AFTER:
if filter_bot:
from src.chat.utils.utils import get_all_bot_accounts
bot_accounts = get_all_bot_accounts()
if bot_accounts:
from sqlalchemy import or_, and_, not_
bot_identity_predicate = or_(
*[
and_(Messages.platform == plat, Messages.user_id == acct)
for plat, acct in bot_accounts.items()
]
)
conditions.append(not_(bot_identity_predicate))
# If no bot accounts configured, skip bot filtering (no-op).
EDGE CASE:
or_()with zero arguments raisesTypeErrorin SQLAlchemy. Theif bot_accountsguard is mandatory.
WebUI compatibility: get_all_bot_accounts() includes {"webui": str(qq_account), ...}. Since WebUI bot messages are stored with platform="webui" and user_id=str(qq_account), the pair correctly matches them. No regression.
Acceptance Criteria
is_bot_selfno longer falls back to QQ for unknown platforms (lines 112-113 deleted).get_bot_account(platform)exists and resolves configured platform accounts.get_current_platform_accountis deleted — no callers remain.get_bot_account("qq") == ""whenqq_accountis unconfigured.is_bot_self("webui", str(qq_account))returnsTrue(preserves current behavior).filter_bot=Trueexcludes configured bot identities by(platform, user_id)pairs.statistic.pyusesis_bot_selffor reply counting.support.pyusesis_bot_selfwithout reverse heuristic fallbacks.- PFC-internal sites are not changed.
Sender Construction Changes (formerly Phase 2)
Replace hard-coded global_config.bot.qq_account sender IDs with get_bot_account(platform):
| File | Line | Platform Source |
|---|---|---|
send_service.py |
91 | target_stream.platform |
group_generator.py |
1125 | self.chat_stream.platform |
private_generator.py |
965 | self.chat_stream.platform |
message_sender.py |
44 | available from chat stream context |
For this pass:
- Local WebUI resolves to
str(qq_account)viaget_bot_account("webui")— same value as today, no regression. - WebUI virtual-group sessions carry the simulated platform, so
get_bot_account(self.chat_stream.platform)produces the correct platform-specific account. - Do not introduce
WEBUI_BOT_USER_ID.
Trivial Prompt Cleanup
These sites use QQ-specific wording but need only text replacement, no runtime platform info:
| File | Line(s) | Current Text | Change |
|---|---|---|---|
person_info.py |
734 | 用户的qq昵称是 |
用户的昵称是 |
person_info.py |
735 | 用户的qq群昵称名是 |
用户的群昵称名是 |
person_info.py |
737 | 用户的qq头像是 |
用户的头像是 |
person_info.py |
742 | qq昵称或群昵称 (multiple) |
Remove qq prefix, keep 昵称 and 群昵称 |
Prompt Sites Deferred (Require Platform Threading)
| File | Line(s) | Text | Why deferred |
|---|---|---|---|
action_planner.py |
22, 55, 89 | QQ私聊 / QQ 私聊 |
Needs platform in PFC prompt context |
reply_generator.py |
18, 43, 68 | QQ私聊 |
Same |
pfc.py |
125, 254 | QQ聊天 |
Same |
emoji_plugin/plugin.py |
66 | 你正在进行QQ聊天 |
Needs plugin message context |
Known Behavior Changes
| Site | Current Behavior | New Behavior | Risk |
|---|---|---|---|
planner.py:138 |
user_id == global_config.bot.qq_account compares string to int — always False (existing bug) |
is_bot_self(platform, str(user_id)) — correctly identifies bot |
Fixes existing bug, but changes behavior. May cause bot name to render as {nickname}(你) where it previously showed the raw user reference. |
Acceptance Criteria
is_bot_selfno longer falls back to QQ for unknown platforms (lines 112-113 deleted).get_bot_account(platform)exists and resolves configured platform accounts.get_current_platform_accountis deleted — no callers remain.get_bot_account("qq") == ""whenqq_accountis unconfigured.is_bot_self("webui", str(qq_account))returnsTrue(preserves current behavior).filter_bot=Trueexcludes configured bot identities by(platform, user_id)pairs.statistic.pyusesis_bot_selffor reply counting.support.pyusesis_bot_selfwithout reverse heuristic fallbacks.- PFC-internal sites are not changed.
- Outbound QQ sender IDs still use
qq_account. - Outbound Telegram and other configured platform sender IDs use that platform's configured account.
- Outbound local WebUI sender IDs remain
str(qq_account)— compatible with current stored history. - No sender construction path hard-codes
global_config.bot.qq_account. person_info.pynickname/avatar prompts use neutral wording (昵称, notqq昵称).- No PFC-internal prompt files are modified.
Implementation Order
- Phase 0 — unify
is_bot_self(signature + argument order only, no semantic changes) - Phase 1 — identity resolution, QQ fallback deletion,
filter_bot, direct comparison cleanup, sender construction, trivial prompt cleanup
Do not start Phase 1 until Phase 0 is committed and verified.
Hard Execution Contract
This section is written for code-generation agents. If any rule here conflicts with a generic agent preference, this section wins.
Required Workflow
- Read every file that will be modified in full before editing. Read at least 30 lines above and below any target line.
- Check
git status --shortbefore the first edit of a phase. If a file in the current phase allowlist has unrelated user changes, stop and report. Unrelated changes in files outside the allowlist (e.g.,docs/) do not block the phase. - One phase per commit or one clearly isolated change batch.
- Run the search checklist after each phase.
- Produce a short phase report before moving on (files changed, searches before/after, tests run, residual hits).
Allowed to Do
- Read any file needed to confirm current behavior.
- Edit only files in the current phase allowlist, plus narrowly scoped tests.
- Add local imports or compatibility wrappers to avoid circular imports.
- Fix additional call sites of the same semantic bug pattern if within the phase allowlist.
- Stop after completing the current phase.
Forbidden to Do
- Do not edit files outside the current phase allowlist.
- Do not bundle Phase 0 and Phase 1 into one commit.
- Do not touch PFC serialization files (
chat_observer.py._message_to_dict,chat_states.py,observation_info.py,conversation.py). - Do not introduce
WEBUI_BOT_USER_IDor any new WebUI bot identity constant. - Do not change capability query API defaults (
args.get("platform", "qq")). - Do not change MCP permission context ID format.
- Do not change data models, adapter protocol, or config schema.
- Do not perform repo-wide formatting, lint churn, comment churn, or opportunistic refactors.
- Do not use destructive git commands (
git checkout -- .,git reset --hard, etc.). - Do not guess platform values — if
platformis empty, treat as unknown, never substitute"qq". - Do not treat
qq_account == 0as a real QQ bot identity. - Do not create
get_bot_accountalongsideget_current_platform_account— the old function must be deleted. - Do not implement
filter_botasMessages.user_id NOT IN (...)— use(platform, user_id)pair matching.
Must Stop and Report
Stop immediately and report instead of improvising if:
- a required fix needs a file outside the current phase allowlist
- a file in the current phase allowlist already contains unrelated user changes
- the checked-in code no longer matches the plan's assumed signatures or control flow
- a circular import cannot be avoided with a local import or wrapper
- a search reveals a new runtime platform-coupling category not covered by the current phase
- tests fail twice and the root cause is still unclear
When stopping, name: the exact file(s), the blocking mismatch, why it is outside scope, and the smallest safe next step.
Per-Phase File Allowlist
| Phase | Allowed files |
|---|---|
| Phase 0 | src/common/utils/system_utils.py, src/chat/utils/utils.py, src/person_info/person_info.py, src/bw_learner/expression_learner.py, src/common/utils/utils_message.py, src/webui/routers/chat/support.py, tests (including pytests/utils_test/message_utils_test.py) |
| Phase 1 | src/chat/utils/utils.py, src/chat/planner_actions/planner.py, src/chat/utils/statistic.py, src/common/message_repository.py, src/webui/routers/chat/support.py, src/services/send_service.py, src/chat/replyer/group_generator.py, src/chat/replyer/private_generator.py, src/chat/brain_chat/PFC/message_sender.py, src/person_info/person_info.py, tests |
INVALID OUTPUT EXAMPLES
Any of the following means the implementation has drifted and must be rejected:
- Editing PFC serialization files (
chat_observer.py._message_to_dict,observation_info.py,chat_states.py) - Introducing
WEBUI_BOT_USER_IDconstant - Changing WebUI sender storage to anything other than
str(qq_account) get_current_platform_accountstill existing after Phase 1is_bot_selfreturningTruefor unknown platforms (lines 112-113 still present)- Implementing
filter_botasMessages.user_id.notin_(...) - Changing
args.get("platform", "qq")in capability query APIs - Modifying PFC prompt strings (
QQ私聊,QQ聊天) in this plan - Editing Phase 1 files while Phase 0 is incomplete
- Introducing
PlatformRegistry,BotIdentityManager, or similar abstractions - Deleting the QQ fallback (lines 112-113) without simultaneously fixing all 4 sender construction sites
Search Checklist
Run before and after each phase:
rg -n "def is_bot_self|_is_bot_self|is_bot_self\(" src
rg -n "global_config\.bot\.qq_account" src
rg -n "get_current_platform_account|get_bot_account|get_all_bot_accounts" src
rg -n 'filter_bot|filter_mai' src
rg -n 'qq昵称|qq群昵称|qq头像' src
Expected Residual Hits After All Phases
| Pattern | File | Why it remains |
|---|---|---|
global_config.bot.qq_account |
src/chat/utils/utils.py |
Internal to is_bot_self and get_bot_account |
global_config.bot.qq_account |
src/chat/brain_chat/PFC/chat_observer.py |
PFC-internal, process_chat_history has no call sites — deferred |
global_config.bot.qq_account |
src/chat/brain_chat/PFC/reply_checker.py |
PFC-internal, platform not available — deferred |
global_config.bot.qq_account |
src/chat/brain_chat/PFC/action_planner.py |
PFC-internal, platform not available — deferred |
QQ私聊 / QQ聊天 |
PFC prompt files | Requires platform threading into PFC context — deferred |
进行QQ聊天 |
emoji_plugin/plugin.py |
Requires plugin context investigation — deferred |
platform or "qq" |
capability query APIs | Default semantics change — deferred |
Known Issues Identified During Review
Added during Claude + Codex joint review (2026-03-15). These are pre-existing issues or edge cases discovered during plan review that do not block this plan but must be tracked.
1. WebUI Virtual-Group Session ID Mismatch (Pre-existing Bug)
ChatHistoryManager._resolve_session_id() at support.py:84 always hashes with WEBUI_CHAT_PLATFORM ("webui"), but virtual-identity messages are created with the simulated platform (e.g., "telegram") at support.py:341 and stored under SessionUtils.calculate_session_id(message.platform, ...) at bot.py:316-317. These produce different session IDs. This means history retrieval for virtual-group sessions may not find the stored messages. This is a pre-existing bug, not introduced by this plan. The plan's support.py heuristic deletion should include a smoke test of virtual-group mode to confirm behavior.
2. filter_bot Legacy Data Contingency
The new (platform, user_id) pair matching in filter_bot will not catch historical rows where platform is empty or inconsistent. Current write paths reject empty platform at chat_manager.py:128-131 before storage, so this is unlikely for recent data. However, if the database contains legacy rows from earlier versions with empty platform, those bot messages will no longer be filtered. Recommendation: Before deploying, run SELECT DISTINCT platform FROM mai_messages WHERE user_id = '{qq_account}' to verify data distribution. If empty-platform rows exist, consider a one-time data migration or add ("", str(qq_account)) to get_all_bot_accounts().
3. parse_platform_accounts Key Normalization (Fixed by This Plan)
parse_platform_accounts() at utils.py:31 historically called .strip() on keys but not .lower(). This plan adds .lower().strip() normalization in parse_platform_accounts, is_bot_self, and get_bot_account (see Phase 1 Required Helper Additions), resolving this gap.
Phase Gates
Every phase must pass before the next starts:
- Read every file that will be modified in full.
- Record the search checklist output before editing.
- Make only the changes required for the current phase.
- Record the search checklist output after editing.
- Run tests or smoke checks.
- Verify no unrelated file was modified.
- Phase 1 only: Before committing, run
SELECT DISTINCT platform FROM mai_messages WHERE user_id = '{qq_account}'(or equivalent) to verify stored platform distribution. If empty-platform bot rows exist, add("", str(qq_account))toget_all_bot_accounts()as a legacy compatibility entry and document this in the phase report. - Produce a short phase report.
Definition of Done
This plan is complete when:
is_bot_self(platform, user_id)has one real implementation with no argument-order traps- The unknown-platform QQ fallback (lines 112-113) is deleted
get_bot_account(platform)exists;get_current_platform_accountis deleted- Sender construction uses
get_bot_account(platform)at all 4 sites filter_bot=Trueuses platform-aware(platform, user_id)pair matchingperson_info.pyprompts use neutral wording- No regression in WebUI bot message storage or filtering
- User-visible format changes and PFC platform propagation remain deferred
Follow-Up Plan Topics
These are explicitly deferred and should be addressed in subsequent plans:
- PFC platform propagation — Add
platformtochat_observer._message_to_dict()serialization, update consumers inchat_states.py,observation_info.py,conversation.py. Prerequisite for migrating PFC-internal identity checks. - PFC-internal identity migration — After propagation is fixed, migrate
reply_checker.py:61,action_planner.py:157, and other PFC dict-based bot checks tois_bot_self. - PFC prompt platformization — Replace
QQ私聊/QQ聊天with platform-aware wording after platform is available in PFC prompt context. - WebUI identity separation — Introduce
WEBUI_BOT_USER_ID, update sender construction, add dual-acceptance transition period. - Capability query API defaults — Change
args.get("platform", "qq")toargs.get("platform", "all_platforms"). - MCP permission format — Change context IDs from
qq:{id}:...to{platform}:{id}:.... - UI/config cleanup — Dashboard labels, setup flow, adapter naming, config field renames.
- WebUI virtual-group session ID fix —
ChatHistoryManager._resolve_session_id()always uses"webui"platform, but virtual messages are stored with simulated platform. Session IDs mismatch — needs investigation and fix.