fix(routing): make bindings dynamic by calling loadConfig() per-message (#11372)

This commit is contained in:
juanpablodlc
2026-02-08 22:34:55 -08:00
committed by GitHub
parent 0cf93b8fa7
commit 8d96955e19
6 changed files with 22 additions and 5 deletions

View File

@@ -103,6 +103,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Heartbeat: allow explicit accountId routing for multi-account channels. (#8702) Thanks @lsh411.
- Routing: refresh bindings per message by loading config at route resolution so binding changes apply without restart. (#11372) Thanks @juanpablodlc.
- TUI/Gateway: handle non-streaming finals, refresh history for non-local chat runs, and avoid event gap warnings for targeted tool streams. (#8432) Thanks @gumadeiras.
- Shell completion: auto-detect and migrate slow dynamic patterns to cached files for faster terminal startup; add completion health checks to doctor/update/onboard.
- Telegram: honor session model overrides in inline model selection. (#8193) Thanks @gildo.

View File

@@ -10,6 +10,7 @@ const updateLastRouteMock = vi.fn();
const dispatchMock = vi.fn();
const readAllowFromStoreMock = vi.fn();
const upsertPairingRequestMock = vi.fn();
const loadConfigMock = vi.fn();
vi.mock("./send.js", () => ({
sendMessageDiscord: (...args: unknown[]) => sendMock(...args),
@@ -30,6 +31,13 @@ vi.mock("../pairing/pairing-store.js", () => ({
readChannelAllowFromStore: (...args: unknown[]) => readAllowFromStoreMock(...args),
upsertChannelPairingRequest: (...args: unknown[]) => upsertPairingRequestMock(...args),
}));
vi.mock("../config/config.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../config/config.js")>();
return {
...actual,
loadConfig: (...args: unknown[]) => loadConfigMock(...args),
};
});
vi.mock("../config/sessions.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../config/sessions.js")>();
return {
@@ -50,6 +58,7 @@ beforeEach(() => {
});
readAllowFromStoreMock.mockReset().mockResolvedValue([]);
upsertPairingRequestMock.mockReset().mockResolvedValue({ code: "PAIRCODE", created: true });
loadConfigMock.mockReset().mockReturnValue({});
__resetDiscordChannelInfoCacheForTest();
});
@@ -685,6 +694,7 @@ describe("discord tool result dispatch", () => {
},
bindings: [{ agentId: "support", match: { channel: "discord", guildId: "g1" } }],
} as ReturnType<typeof import("../config/config.js").loadConfig>;
loadConfigMock.mockReturnValue(cfg);
const handler = createDiscordMessageHandler({
cfg,

View File

@@ -17,6 +17,7 @@ import { formatAllowlistMatchMeta } from "../../channels/allowlist-match.js";
import { resolveControlCommandGate } from "../../channels/command-gating.js";
import { logInboundDrop } from "../../channels/logging.js";
import { resolveMentionGatingWithBypass } from "../../channels/mention-gating.js";
import { loadConfig } from "../../config/config.js";
import { logVerbose, shouldLogVerbose } from "../../globals.js";
import { recordChannelActivity } from "../../infra/channel-activity.js";
import { enqueueSystemEvent } from "../../infra/system-events.js";
@@ -218,8 +219,9 @@ export async function preflightDiscordMessage(
earlyThreadParentType = parentInfo.type;
}
// Fresh config for bindings lookup; other routing inputs are payload-derived.
const route = resolveAgentRoute({
cfg: params.cfg,
cfg: loadConfig(),
channel: "discord",
accountId: params.accountId,
guildId: params.data.guild_id ?? undefined,

View File

@@ -26,6 +26,7 @@ import { logInboundDrop } from "../channels/logging.js";
import { resolveMentionGatingWithBypass } from "../channels/mention-gating.js";
import { recordInboundSession } from "../channels/session.js";
import { formatCliCommand } from "../cli/command-format.js";
import { loadConfig } from "../config/config.js";
import { readSessionUpdatedAt, resolveStorePath } from "../config/sessions.js";
import { logVerbose, shouldLogVerbose } from "../globals.js";
import { recordChannelActivity } from "../infra/channel-activity.js";
@@ -163,8 +164,9 @@ export const buildTelegramMessageContext = async ({
const { groupConfig, topicConfig } = resolveTelegramGroupConfig(chatId, resolvedThreadId);
const peerId = isGroup ? buildTelegramGroupPeerId(chatId, resolvedThreadId) : String(chatId);
const parentPeer = buildTelegramParentPeer({ isGroup, resolvedThreadId, chatId });
// Fresh config for bindings lookup; other routing inputs are payload-derived.
const route = resolveAgentRoute({
cfg,
cfg: loadConfig(),
channel: "telegram",
accountId: account.accountId,
peer: {

View File

@@ -449,8 +449,9 @@ export function createTelegramBot(opts: TelegramBotOptions) {
: undefined;
const peerId = isGroup ? buildTelegramGroupPeerId(chatId, resolvedThreadId) : String(chatId);
const parentPeer = buildTelegramParentPeer({ isGroup, resolvedThreadId, chatId });
// Fresh config for bindings lookup; other routing inputs are payload-derived.
const route = resolveAgentRoute({
cfg,
cfg: loadConfig(),
channel: "telegram",
accountId: account.accountId,
peer: { kind: isGroup ? "group" : "direct", id: peerId },

View File

@@ -1,10 +1,10 @@
import type { getReplyFromConfig } from "../../../auto-reply/reply.js";
import type { MsgContext } from "../../../auto-reply/templating.js";
import type { loadConfig } from "../../../config/config.js";
import type { MentionConfig } from "../mentions.js";
import type { WebInboundMsg } from "../types.js";
import type { EchoTracker } from "./echo.js";
import type { GroupHistoryEntry } from "./group-gating.js";
import { loadConfig } from "../../../config/config.js";
import { logVerbose } from "../../../globals.js";
import { resolveAgentRoute } from "../../../routing/resolve-route.js";
import { buildGroupHistoryKey } from "../../../routing/session-key.js";
@@ -63,8 +63,9 @@ export function createWebOnMessageHandler(params: {
return async (msg: WebInboundMsg) => {
const conversationId = msg.conversationId ?? msg.from;
const peerId = resolvePeerId(msg);
// Fresh config for bindings lookup; other routing inputs are payload-derived.
const route = resolveAgentRoute({
cfg: params.cfg,
cfg: loadConfig(),
channel: "whatsapp",
accountId: msg.accountId,
peer: {