fix(router): trust SDK isMention signal; drop broken hasMention regex
The router's mention / mention-sticky engage check was regex-matching @<agent_group.name> (e.g. @Andy) against message text. Platforms don't work that way — users address bots via the bot's platform username (@nanoclaw_v2_refactr_1_bot on Telegram, user-id mentions on Slack / Discord). The regex matched only coincidentally and never on Telegram, so mention-mode wirings silently never fired there. Two parallel mention detectors existed: the Chat SDK's onNewMention, which correctly resolves the bot's platform identity, and the router's hasMention text regex, which ignored the SDK verdict and invented its own heuristic. The router's detector was wrong in principle — the agent group's display name is a NanoClaw-side nickname, not a platform address. Thread the SDK signal through: InboundMessage gains an optional `isMention` field, the bridge sets it from each handler (onNewMention → true, onDirectMessage → true, onSubscribedMessage → message.isMention, onNewMessage(/./) → false), src/index.ts forwards it into InboundEvent, and evaluateEngage now checks `isMention === true` for mention modes. hasMention deleted entirely — there is only one source of truth for "did the user mention this bot": the platform / SDK. Agent-name-in-text matching for disambiguating multiple agents wired to one chat is a separate feature; users can express it today with engage_mode='pattern' + the agent's name as the regex. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -60,6 +60,21 @@ export interface InboundMessage {
|
||||
kind: 'chat' | 'chat-sdk';
|
||||
content: unknown; // JS object — host will JSON.stringify before writing to session DB
|
||||
timestamp: string;
|
||||
/**
|
||||
* Platform-confirmed signal that this message is a mention of the bot.
|
||||
*
|
||||
* Set by adapters that know the platform's own mention semantics — e.g.
|
||||
* the Chat SDK bridge sets it true from `onNewMention` / `onDirectMessage`
|
||||
* and forwards `message.isMention` from `onSubscribedMessage`. Use this
|
||||
* in the router instead of agent-name regex matching, which breaks on
|
||||
* platforms where the mention text is the bot's platform username (e.g.
|
||||
* Telegram's `@nanoclaw_v2_refactr_1_bot`) rather than the agent_group
|
||||
* display name (e.g. `@Andy`).
|
||||
*
|
||||
* Adapters that don't set it (native / legacy) leave it undefined — the
|
||||
* router falls back to text-match against agent_group_name.
|
||||
*/
|
||||
isMention?: boolean;
|
||||
}
|
||||
|
||||
/** A file attachment to deliver alongside a message. */
|
||||
|
||||
@@ -210,7 +210,7 @@ export function createChatSdkBridge(config: ChatSdkBridgeConfig): ChannelAdapter
|
||||
return shouldEngage(conversations, channelId, source, text);
|
||||
}
|
||||
|
||||
async function messageToInbound(message: ChatMessage): Promise<InboundMessage> {
|
||||
async function messageToInbound(message: ChatMessage, isMention: boolean): Promise<InboundMessage> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const serialized = message.toJSON() as Record<string, any>;
|
||||
|
||||
@@ -266,6 +266,7 @@ export function createChatSdkBridge(config: ChatSdkBridgeConfig): ChannelAdapter
|
||||
kind: 'chat-sdk',
|
||||
content: serialized,
|
||||
timestamp: message.metadata.dateSent.toISOString(),
|
||||
isMention,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -296,7 +297,10 @@ export function createChatSdkBridge(config: ChatSdkBridgeConfig): ChannelAdapter
|
||||
const text = typeof message.text === 'string' ? message.text : '';
|
||||
const decision = engageDecision(channelId, 'subscribed', text);
|
||||
if (!decision.forward) return;
|
||||
await setupConfig.onInbound(channelId, thread.id, await messageToInbound(message));
|
||||
// Subscribed path: the SDK sets message.isMention when the bot was
|
||||
// @-mentioned in an already-subscribed thread (docs at
|
||||
// handling-events.mdx). Forward it verbatim.
|
||||
await setupConfig.onInbound(channelId, thread.id, await messageToInbound(message, message.isMention === true));
|
||||
});
|
||||
|
||||
// @mention in an unsubscribed thread — always engage; subscribe only
|
||||
@@ -306,7 +310,8 @@ export function createChatSdkBridge(config: ChatSdkBridgeConfig): ChannelAdapter
|
||||
const text = typeof message.text === 'string' ? message.text : '';
|
||||
const decision = engageDecision(channelId, 'mention', text);
|
||||
if (!decision.forward) return;
|
||||
await setupConfig.onInbound(channelId, thread.id, await messageToInbound(message));
|
||||
// onNewMention only fires when the SDK confirms the bot was mentioned.
|
||||
await setupConfig.onInbound(channelId, thread.id, await messageToInbound(message, true));
|
||||
if (decision.stickySubscribe) {
|
||||
await thread.subscribe();
|
||||
}
|
||||
@@ -332,7 +337,9 @@ export function createChatSdkBridge(config: ChatSdkBridgeConfig): ChannelAdapter
|
||||
forward: decision.forward,
|
||||
});
|
||||
if (!decision.forward) return;
|
||||
await setupConfig.onInbound(channelId, thread.id, await messageToInbound(message));
|
||||
// A DM is by definition addressed to the bot — treat as a mention
|
||||
// for routing purposes. `mention` / `mention-sticky` wirings fire.
|
||||
await setupConfig.onInbound(channelId, thread.id, await messageToInbound(message, true));
|
||||
if (decision.stickySubscribe) {
|
||||
await thread.subscribe();
|
||||
}
|
||||
@@ -357,7 +364,9 @@ export function createChatSdkBridge(config: ChatSdkBridgeConfig): ChannelAdapter
|
||||
const text = typeof message.text === 'string' ? message.text : '';
|
||||
const decision = engageDecision(channelId, 'new-message', text);
|
||||
if (!decision.forward) return;
|
||||
await setupConfig.onInbound(channelId, thread.id, await messageToInbound(message));
|
||||
// SDK dispatch guarantees this is a non-mention non-DM message in an
|
||||
// unsubscribed thread — isMention is definitively false here.
|
||||
await setupConfig.onInbound(channelId, thread.id, await messageToInbound(message, false));
|
||||
});
|
||||
|
||||
// Handle button clicks (ask_user_question)
|
||||
|
||||
Reference in New Issue
Block a user