( event: AssistantMessageEvent, channelId: string )
| 521 | * Handle message in Assistant thread |
| 522 | */ |
| 523 | export async function handleAssistantMessage( |
| 524 | event: AssistantMessageEvent, |
| 525 | channelId: string |
| 526 | ): Promise<void> { |
| 527 | if (!initialized || !claudeClient) { |
| 528 | logger.warn('Addie: Not initialized, ignoring message'); |
| 529 | return; |
| 530 | } |
| 531 | |
| 532 | // Skip Slackbot system messages (e.g., "added you to #channel") |
| 533 | if (event.user === SLACKBOT_USER_ID) { |
| 534 | logger.debug({ messageText: event.text?.substring(0, 50) }, 'Addie: Ignoring Slackbot system message'); |
| 535 | return; |
| 536 | } |
| 537 | |
| 538 | const startTime = Date.now(); |
| 539 | const interactionId = generateInteractionId(); |
| 540 | |
| 541 | // Check if user is an AAO admin (for admin-only tools access) |
| 542 | const isAAOAdmin = await isSlackUserAAOAdmin(event.user); |
| 543 | logger.debug({ userId: event.user, isAAOAdmin }, 'Addie: Checked admin status'); |
| 544 | |
| 545 | // Resolve user mentions to include names (e.g., <@U123> -> <@U123|John>) |
| 546 | const textWithResolvedMentions = await resolveSlackMentions(event.text, lookupSlackUserName); |
| 547 | |
| 548 | // Sanitize input |
| 549 | const inputValidation = sanitizeInput(textWithResolvedMentions); |
| 550 | |
| 551 | // Set status to thinking |
| 552 | try { |
| 553 | await setAssistantStatus(channelId, STATUS_MESSAGES.thinking); |
| 554 | } catch { |
| 555 | // Status update failed, continue anyway |
| 556 | } |
| 557 | |
| 558 | // Build per-request context for system prompt |
| 559 | const { requestContext, memberContext, personId } = await buildRequestContext(event.user); |
| 560 | |
| 561 | // Heuristic click telemetry: if the incoming message text matches a |
| 562 | // known suggested-prompt verbatim, record a click against that rule. |
| 563 | const matchedRuleId = matchRuleIdFromMessage(event.text); |
| 564 | const messageWorkosUserId = memberContext?.workos_user?.workos_user_id; |
| 565 | if (matchedRuleId && messageWorkosUserId) { |
| 566 | void recordPromptClicked(messageWorkosUserId, matchedRuleId); |
| 567 | } |
| 568 | |
| 569 | // Record the user's message in the relationship and event log |
| 570 | if (personId) { |
| 571 | relationshipDb.recordPersonMessage(personId, 'slack') |
| 572 | .then(() => relationshipDb.deriveSentiment(personId)) |
| 573 | .catch(error => { |
| 574 | logger.warn({ error, personId }, 'Addie: Failed to record person message'); |
| 575 | }); |
| 576 | personEvents.recordEvent(personId, 'message_received', { |
| 577 | channel: 'slack', |
| 578 | data: { source: 'dm', text_length: textWithResolvedMentions.length }, |
| 579 | }).catch(err => logger.warn({ err, personId }, 'Addie: Failed to record message_received event')); |
| 580 | } |
no test coverage detected