feat(guild): add 'dm' x-type (private 1:1, always-wake)

channel enum + X_TYPES + realtime XType gain 'dm'. dm channels are
forced private (never public) and non-unique (no dedup; create()
always makes a fresh one). computeWakeup: dm wakes every non-author
participant unconditionally (no rotation / no wake_mapping). The
message.created realtime payload now carries xType so the plugin can
treat dm specially.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
h z
2026-05-18 09:18:19 +01:00
parent 7e944a08f6
commit b1f7467161
3 changed files with 15 additions and 7 deletions

View File

@@ -6,7 +6,7 @@ import { ChannelMember } from '../entities/channel-member.entity.js';
import { WakeMapping } from '../entities/wake-mapping.entity.js'; import { WakeMapping } from '../entities/wake-mapping.entity.js';
import { TurnService } from './turn.service.js'; import { TurnService } from './turn.service.js';
const X_TYPES = ['general', 'work', 'report', 'discuss', 'triage', 'custom'] as const; const X_TYPES = ['general', 'work', 'report', 'discuss', 'triage', 'custom', 'dm'] as const;
type XType = (typeof X_TYPES)[number]; type XType = (typeof X_TYPES)[number];
type CreateChannelInput = { type CreateChannelInput = {
@@ -130,14 +130,19 @@ export class ChannelsService {
.map((x) => String(x ?? '').trim()) .map((x) => String(x ?? '').trim())
.filter(Boolean); .filter(Boolean);
// dm channels are always private (a 1:1 conversation); never public.
// dm is not unique — multiple dm channels between the same users are
// allowed (create() always makes a fresh one, no dedup).
const isPublic = xType === 'dm' ? false : Boolean(input.isPublic);
const channel = await this.channelRepo.save( const channel = await this.channelRepo.save(
this.channelRepo.create({ this.channelRepo.create({
guildId, guildId,
name, name,
xType, xType,
kind: input.kind === 'announcement' ? 'announcement' : 'text', kind: input.kind === 'announcement' ? 'announcement' : 'text',
isPrivate: !input.isPublic, isPrivate: !isPublic,
isPublic: Boolean(input.isPublic), isPublic,
lastSeq: 0, lastSeq: 0,
}), }),
); );

View File

@@ -16,9 +16,9 @@ export class Channel {
@Column({ @Column({
name: 'x_type', name: 'x_type',
type: 'enum', type: 'enum',
enum: ['general', 'work', 'report', 'discuss', 'triage', 'custom'], enum: ['general', 'work', 'report', 'discuss', 'triage', 'custom', 'dm'],
}) })
xType!: 'general' | 'work' | 'report' | 'discuss' | 'triage' | 'custom'; xType!: 'general' | 'work' | 'report' | 'discuss' | 'triage' | 'custom' | 'dm';
@Column({ type: 'varchar', length: 16, default: 'text' }) @Column({ type: 'varchar', length: 16, default: 'text' })
kind!: 'text' | 'announcement'; kind!: 'text' | 'announcement';

View File

@@ -11,7 +11,7 @@ import { Logger } from '@nestjs/common';
import { Server, Socket } from 'socket.io'; import { Server, Socket } from 'socket.io';
import { introspectGuildToken } from '../common/center-auth.js'; import { introspectGuildToken } from '../common/center-auth.js';
type XType = 'general' | 'work' | 'report' | 'discuss' | 'triage' | 'custom'; type XType = 'general' | 'work' | 'report' | 'discuss' | 'triage' | 'custom' | 'dm';
// Wakeup for non-rotating channels only (general/report/triage/custom). // Wakeup for non-rotating channels only (general/report/triage/custom).
// discuss/work go through TurnService + emitMessageTargeted, never here. // discuss/work go through TurnService + emitMessageTargeted, never here.
@@ -40,6 +40,9 @@ export function computeWakeup(args: {
case 'triage': case 'triage':
case 'custom': case 'custom':
return wakeUserIds.has(recipientUserId); return wakeUserIds.has(recipientUserId);
case 'dm':
// 1:1 conversation: every non-author participant is always woken.
return true;
default: default:
return false; return false;
} }
@@ -189,7 +192,7 @@ export class RealtimeGateway implements OnGatewayConnection, OnGatewayDisconnect
wakeUserIds: ctx.wakeUserIds, wakeUserIds: ctx.wakeUserIds,
mentionUserIds: ctx.mentionUserIds, mentionUserIds: ctx.mentionUserIds,
}); });
s.emit('message.created', { ...data, channelId, wakeup }); s.emit('message.created', { ...data, channelId, wakeup, xType: ctx.xType });
} }
} }