1 Commits

Author SHA1 Message Date
b1f7467161 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>
2026-05-18 09:18:19 +01:00
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 { 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 CreateChannelInput = {
@@ -130,14 +130,19 @@ export class ChannelsService {
.map((x) => String(x ?? '').trim())
.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(
this.channelRepo.create({
guildId,
name,
xType,
kind: input.kind === 'announcement' ? 'announcement' : 'text',
isPrivate: !input.isPublic,
isPublic: Boolean(input.isPublic),
isPrivate: !isPublic,
isPublic,
lastSeq: 0,
}),
);

View File

@@ -16,9 +16,9 @@ export class Channel {
@Column({
name: 'x_type',
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' })
kind!: 'text' | 'announcement';

View File

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