feat(guild): <@id> mention mechanism

- parse <@user-id> outside backtick spans
- general: message with an at-list wakes only the at'd users (else all)
- report/triage/custom: mentions change nothing
- discuss/work: mention by current speaker pushes a sub-rotation frame
  (atList = mentions - sender, intersected with channel members); single
  linear pass (real/no-reply/force-proceed), then pop back to the saved
  parent pointer (resumes at the pusher); nested frames supported

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
h z
2026-05-15 15:27:35 +01:00
parent 182cfb3c41
commit 02b7c72e70
5 changed files with 193 additions and 45 deletions

View File

@@ -18,18 +18,24 @@ type XType = 'general' | 'work' | 'report' | 'discuss' | 'triage' | 'custom';
// Precedence:
// 1. the author never gets woken by their own message
// 2. triage/custom: only wake users in the channel's wake_mapping
// 3. general: wake everyone
// (mentions change nothing here)
// 3. general: if the message has an at-list, wake only the at'd users;
// otherwise wake everyone
// 4. report (and anything else): wake nobody
export function computeWakeup(args: {
xType: XType;
recipientUserId: string;
authorUserId: string;
wakeUserIds: Set<string>;
mentionUserIds?: Set<string>;
}): boolean {
const { xType, recipientUserId, authorUserId, wakeUserIds } = args;
const { xType, recipientUserId, authorUserId, wakeUserIds, mentionUserIds } = args;
if (recipientUserId === authorUserId) return false;
switch (xType) {
case 'general':
if (mentionUserIds && mentionUserIds.size > 0) {
return mentionUserIds.has(recipientUserId);
}
return true;
case 'triage':
case 'custom':
@@ -166,7 +172,12 @@ export class RealtimeGateway implements OnGatewayConnection, OnGatewayDisconnect
async emitMessageCreated(
channelId: string,
data: Record<string, unknown>,
ctx: { xType: XType; authorUserId: string; wakeUserIds: Set<string> },
ctx: {
xType: XType;
authorUserId: string;
wakeUserIds: Set<string>;
mentionUserIds?: Set<string>;
},
): Promise<void> {
const sockets = await this.server.in(`channel:${channelId}`).fetchSockets();
for (const s of sockets) {
@@ -176,6 +187,7 @@ export class RealtimeGateway implements OnGatewayConnection, OnGatewayDisconnect
recipientUserId,
authorUserId: ctx.authorUserId,
wakeUserIds: ctx.wakeUserIds,
mentionUserIds: ctx.mentionUserIds,
});
s.emit('message.created', { ...data, wakeup });
}