Backend half of the plugin push-based channel sync (companion to nav/Fabric.OpenclawPlugin#1 follow-up). Before this, the OpenClaw fabric inbound had to poll `/api/channels?guildId=...` every 60s to discover newly-joined channels (any DM another user just dragged the agent into). Now the server tells the agent's socket directly so sub/unsub is realtime. Changes: - realtime.gateway.ts: * handleConnection joins the socket into a `user:<userId>` room. All of a user's connected sockets now share that room. * New `emitToUser(userId, event, data)` helper that emits into that room. No-op for offline users (next connect resyncs via the plugin's initial channel-list fetch). - channels.service.ts: * Inject RealtimeGateway (RealtimeModule is @Global, no module plumbing needed). * Private `notifyMembership(kind, channelId, userIds, extra)` helper that emits `channel.<kind>` (joined|left) with payload {channelId, userId, xType, occurredAt}. * create(): emit channel.joined to every seeded member (creator + explicit memberUserIds + triage on-duty). * joinChannel(): emit channel.joined to userId (only if the row was actually inserted, idempotent on existing membership). * leaveChannel(): emit channel.left to userId iff a row was actually deleted. Event shape: { channelId: string, userId: string, xType?: string, occurredAt: ISO string, } Client-side contract (fabric plugin): socket.on('channel.joined', m => socket.emit('join_channel', {channelId: m.channelId})) socket.on('channel.left', m => socket.emit('leave_channel', {channelId: m.channelId})) The plugin keeps its 60s polling resync as a safety net for missed events (transient socket drops between emit and reconnect, partial failures, etc). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7.5 KiB
7.5 KiB