fix(qqbot): re-evaluate routing bindings per inbound message#73567
Conversation
Greptile SummaryThis PR fixes stale routing in the QQBot engine by introducing an Confidence Score: 5/5Safe to merge — targeted, well-tested fix with no new I/O, no auth changes, and clean fallback behavior. The change is minimal and focused: one new helper module, one call-site update, and two test files. The fallback-to-snapshot path is covered by tests, the no-caching invariant is explicitly asserted, and the seam test locks in the cross-cfg routing behavior that was the root cause. No P0 or P1 findings. No files require special attention. Reviews (1): Last reviewed commit: "Merge branch 'main' of https://github.co..." | Re-trigger Greptile |
|
Codex review: needs real behavior proof before merge. Summary Reproducibility: yes. The linked bug gives concrete QQBot C2C steps and before/after routing logs, and current main still passes startup Real behavior proof Next step before merge Security Review detailsBest possible solution: Land the QQBot-side per-inbound active runtime config lookup after redacted live QQBot/log proof is added; the fix should remain localized to the QQBot gateway boundary. Do we have a high-confidence way to reproduce the issue? Yes. The linked bug gives concrete QQBot C2C steps and before/after routing logs, and current main still passes startup Is this the best way to solve the issue? Mostly yes. Resolving active runtime config at the QQBot inbound boundary matches the SDK runtime contract and Telegram's existing pattern; the remaining blocker is real behavior proof, not a different code direction. Acceptance criteria:
What I checked:
Likely related people:
Remaining risk / open question:
Codex review notes: model gpt-5.5, reasoning high; reviewed against 0b79d7cb3a04. |
c0c5380 to
b6455d6
Compare
b6455d6 to
bb53bef
Compare
|
Hi @sliverp Thanks for assigning. Could you let me know what I should do more? |
13fc546 to
cbc1c57
Compare
cbc1c57 to
edbe524
Compare
QQBot's gateway captured `ctx.cfg` once at startup and reused that reference for every inbound, so peer-specific bindings added via the CLI were ignored until the gateway restarted (the routing resolver caches evaluated bindings keyed by the cfg object reference). Add a small `ActiveCfgProvider` that reads `getRuntimeConfig()` from the plugin SDK on every event and falls back to the startup snapshot when the runtime registry is not populated, mirroring Telegram's per-event lookup pattern. Wire it into `handleMessage` so both the inbound pipeline and outbound dispatch run against the live config. Fixes openclaw#69546.
edbe524 to
e6a87cb
Compare
|
Landed via rebase onto main.
Known CI note: the failing Thanks @statxc! |
Summary
cfgsnapshot captured at gateway startup, so peer-specific bindings added through the CLI (bindings=2inopenclaw agents list --bindings) were ignored at runtime (bindings=1) until the gateway was restarted.ActiveCfgProviderthat reads the livegetRuntimeConfig()from the plugin SDK on every inbound and falls back to the startup snapshot when the runtime registry is unavailable.handleMessagenow resolves cfg through the provider, so bothbuildInboundContext(which feedsrunAccessStage→resolveAgentRoute) anddispatchOutboundsee the latest bindings without restart.GatewayConnection(lifecycle, not routing) is intentionally untouched. The bridgeextensions/qqbot/src/bridge/gateway.tskeeps forwarding the caller's cfg — that value now serves as the engine's fallback when the SDK getter is not yet ready (e.g. very early startup, isolated tests).Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
cfg: telegramDeps.getRuntimeConfig()("Fresh config for bindings lookup").Root Cause (if applicable)
extensions/qqbot/src/engine/gateway/gateway.tscapturedctx.cfgonce whenstartGatewaywas called and reused that exact object reference inside thehandleMessageclosure for every inbound. The routing resolver insrc/routing/resolve-route.tskeys its evaluated-bindings cache withWeakMap<OpenClawConfig, …>, so the same cfg reference always returned the cached result — the new bindings on disk never reachedresolveAgentRoute.docs/plugins/sdk-runtime.md).Regression Test Plan (if applicable)
extensions/qqbot/src/engine/gateway/active-cfg.test.ts— unit tests for the helper (fresh on every call, fallback when fetcher returnsundefinedor throws, delegation to SDK).extensions/qqbot/src/engine/gateway/stages/access-stage.test.ts— seam test assertingrunAccessStagere-evaluatesresolveAgentRouteagainst the cfg supplied on each call (account-only → peer binding flip; never reads from a previous cfg reference).runAccessStagecalls with cfg-A (account binding only) then cfg-B (peer binding added) must produce differentroute.agentIds, withresolveAgentRoutereceiving each cfg by reference.User-visible / Behavior Changes
CLI binding edits (peer or account) take effect on the next QQBot inbound message.
openclaw gateway restartis no longer required afteragents bind add/bind remove. No config knobs added; no defaults changed.Security Impact (required)
NoNoNoNoNoThe new helper reads the same runtime config that core already exposes through
openclaw/plugin-sdk/runtime-config-snapshot. No new I/O, no new privileges.Repro + Verification
Environment
modelstudio/qwen3.5-plus(per the issue reporter; the bug is provider-agnostic)qqbot.accountId=study; bindings start with one account-level entry, peer entry added mid-runSteps
openclaw agents bind add ....openclaw agents list --bindingsshows two bindings.Expected
[routing] resolveAgentRoute ... bindings=2andmatchedBy=binding.peer, routed to the peer-bound agent.Actual (before fix)
[routing] resolveAgentRoute ... bindings=1andmatchedBy=binding.account, routed to the account-level agent until restart.Actual (after fix)
Evidence
pnpm test extensions/qqbot/src/engine/gateway/active-cfg.test.ts extensions/qqbot/src/engine/gateway/stages/access-stage.test.ts→ 9 / 9 pass on the merged tree.pnpm test extensions/qqbot): 396 / 396 pass.getRuntimeConfig()per inbound (a registry lookup).Human Verification (required)
undefinedor throws (unit test).runAccessStagere-routes when a new cfg with an additional peer binding is supplied (seam test).extensions/qqbotVitest suite passes locally.pnpm tsgo:extensionsclean;oxfmt --checkclean;oxlintclean on touched files.undefined→ fallback used.resolveAgentRoutewith the same shape the framework injects.Review Conversations
Compatibility / Migration
YesNoNoThe bridge-level
cfgis still accepted and now serves as the fallback for the engine's per-event lookup, so existing callers (including third-party drivers ofcoreStartGateway) keep working without changes.