MCPcopy Create free account
hub / github.com/adcontextprotocol/adcp / handleAppMention

Function handleAppMention

server/src/addie/handler.ts:711–867  ·  view source on GitHub ↗
(event: AppMentionEvent)

Source from the content-addressed store, hash-verified

709 * Handle @mention in a channel
710 */
711export async function handleAppMention(event: AppMentionEvent): Promise<void> {
712 if (!initialized || !claudeClient) {
713 logger.warn('Addie: Not initialized, ignoring mention');
714 return;
715 }
716
717 const startTime = Date.now();
718 const interactionId = generateInteractionId();
719
720 // Check if user is an AAO admin (for admin-only tools access)
721 const isAAOAdmin = await isSlackUserAAOAdmin(event.user);
722 logger.debug({ userId: event.user, isAAOAdmin }, 'Addie: Checked admin status for mention');
723
724 // Strip bot mention
725 const rawText = botUserId ? stripBotMention(event.text, botUserId) : event.text;
726
727 // Resolve user mentions to include names (e.g., <@U123> -> <@U123|John>)
728 const textWithResolvedMentions = await resolveSlackMentions(rawText, lookupSlackUserName);
729
730 // Sanitize input
731 const inputValidation = sanitizeInput(textWithResolvedMentions);
732
733 // Build per-request context for system prompt
734 // Skip goals in channel mentions to prevent membership pitching
735 const { requestContext: baseContext, memberContext, personId } = await buildRequestContext(event.user, { skipGoals: true });
736
737 // Record the user's message in the relationship
738 if (personId) {
739 relationshipDb.recordPersonMessage(personId, 'slack')
740 .then(() => relationshipDb.deriveSentiment(personId))
741 .catch(error => {
742 logger.warn({ error, personId }, 'Addie: Failed to record person message (mention)');
743 });
744 }
745
746 // Add channel guardrails — mentions always happen in channels
747 const channelGuardrails = [
748 '',
749 '## Channel context',
750 '**IMPORTANT: This is a channel message visible to all channel members.**',
751 '- You MUST NOT pitch membership, send join links, or recruit in channels — membership conversations belong in DMs.',
752 '- You MUST NOT share financial data, member counts, invoice information, individual member details, pricing information, or any other sensitive organizational data.',
753 ].join('\n');
754 const requestContext = baseContext + channelGuardrails;
755
756 // Add admin prefix to user message if applicable
757 const userMessage = isAAOAdmin ? `[ADMIN USER] ${inputValidation.sanitized}` : inputValidation.sanitized;
758
759 // Check for sensitive topics before processing (channel mentions are more public)
760 const sensitiveCheck = await checkForSensitiveTopics(
761 inputValidation.sanitized,
762 event.user,
763 event.channel
764 );
765
766 // If we should deflect, return the deflection response instead of processing
767 let response;
768 if (sensitiveCheck.shouldDeflect && sensitiveCheck.deflectResponse) {

Callers 1

handleSlackEventFunction · 0.50

Calls 15

generateInteractionIdFunction · 0.85
isSlackUserAAOAdminFunction · 0.85
stripBotMentionFunction · 0.85
resolveSlackMentionsFunction · 0.85
sanitizeInputFunction · 0.85
checkForSensitiveTopicsFunction · 0.85
buildSlackCostScopeFunction · 0.85
validateOutputFunction · 0.85
sendChannelMessageFunction · 0.85
logInteractionFunction · 0.85
warnMethod · 0.80
nowMethod · 0.80

Tested by

no test coverage detected