Phase 664: voice note diagnostics, gate in view, and gossip idempotency.

Adds exportVoiceNoteDiagnostics, voiceNoteGate in view, voiceNote.commit span, max duration guard on stage, and _ingestVoiceNoteGossip for duplicate VOICE_NOTE_CREATE payloads.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Raven Scott
2026-06-02 19:15:28 -04:00
co-authored by Cursor
parent aac8cb4da3
commit ec14ac1dc0
2 changed files with 59 additions and 5 deletions
+57 -5
View File
@@ -9896,6 +9896,9 @@ class PearcordPlatform extends EventEmitter {
}
})
})
guildInstance.on('voice-note', (payload) => {
void this._ingestVoiceNoteGossip(payload).catch(() => {})
})
guildInstance.on('channel', (ch) => {
this._fanoutBotEvent(BOT_EVENTS.CHANNEL_CREATE, { channel: ch })
this.emit('channel', ch)
@@ -12908,6 +12911,13 @@ class PearcordPlatform extends EventEmitter {
async stageVoiceNoteDraft ({ data, durationMs, mimeType, waveformPeaks }) {
const guildId = this.mode === 'dm' ? DM_GUILD_ID : this.guild?.guild?.id || null
const channelId = this.activeChannelId
const maxMs = Math.max(
1000,
Number(process.env.PEARCORD_VOICE_NOTE_MAX_MS) || 15 * 60 * 1000
)
if (Number(durationMs) > maxMs) {
throw new Error(`Voice message exceeds maximum length (${Math.round(maxMs / 60000)} minutes)`)
}
const span = this.log.time('voiceNote.stage', {
spanKind: 'voiceNote.stage',
channelId,
@@ -12978,14 +12988,36 @@ class PearcordPlatform extends EventEmitter {
return row
}
exportVoiceNoteDiagnostics (guildId = null) {
const gid = guildId || this.guild?.guild?.id || (this.mode === 'dm' ? DM_GUILD_ID : null)
const diag = this.voiceNotes?.getDiagnostics?.() || {}
return {
guildId: gid,
channelId: this.activeChannelId || null,
mode: this.mode,
...diag,
draft: this.voiceNotes?.getActiveDraftId?.() || null
}
}
async commitVoiceNoteDraft ({ messageId } = {}) {
if (!this.voiceNotes) throw new Error('voice notes not ready')
const out = await this.voiceNotes.commitDraft({ messageId })
if (this.mode === 'guild' && this.guild?.gossipVoiceNoteCreate) {
void this.guild.gossipVoiceNoteCreate(out.gossip).catch(() => {})
const commitSpan = this.log.time('voiceNote.commit', {
spanKind: 'voiceNote.commit',
draftId: this.voiceNotes.getActiveDraftId?.() || null
})
try {
const out = await this.voiceNotes.commitDraft({ messageId })
if (this.mode === 'guild' && this.guild?.gossipVoiceNoteCreate) {
void this.guild.gossipVoiceNoteCreate(out.gossip).catch(() => {})
}
commitSpan.end({ voiceNoteId: out.record?.id, size: out.record?.size })
this.emit('voice-note-draft')
return out
} catch (err) {
commitSpan.fail(err)
throw err
}
this.emit('voice-note-draft')
return out
}
async sendVoiceNote ({ replyToId } = {}) {
@@ -13031,6 +13063,19 @@ class PearcordPlatform extends EventEmitter {
}
}
async _ingestVoiceNoteGossip (payload) {
if (!this.voiceNotes?.upsertFromGossip) return null
const out = await this.voiceNotes.upsertFromGossip(payload)
if (out?.applied) {
this.log.info('voiceNote.ingest', {
spanKind: 'voiceNote.ingest',
voiceNoteId: payload?.id,
duplicate: false
})
}
return out
}
async setMemberRole (userId, role) {
const user = this.identity.user
if (!user || !this.guild?.guild) throw new Error('no guild')
@@ -23681,6 +23726,7 @@ class PearcordPlatform extends EventEmitter {
channelConsistencyAudit.skippedAfterChannelRecovery = true
}
let voiceNoteDraft = null
let voiceNoteGate = { ok: true, reason: null, message: '' }
if (this.voiceNotes) {
const vn = await this.voiceNotes.getDraft().catch(() => null)
if (vn) {
@@ -23694,6 +23740,11 @@ class PearcordPlatform extends EventEmitter {
transcriptPlaceholder: vn.transcriptPlaceholder || null
}
}
const activeCh =
this.mode === 'guild' && this.guild
? activeChannel
: this.dm?.channel
voiceNoteGate = this._voiceNoteGate(activeCh || null)
}
let stagedAttachments = []
if (this.attachments && this.activeChannelId) {
@@ -23839,6 +23890,7 @@ class PearcordPlatform extends EventEmitter {
stagedAttachmentUi:
userPrefs?.stagedAttachmentUi?.[this.activeChannelId] || null,
voiceNoteDraft,
voiceNoteGate,
voiceStatesByChannel,
stageRolesByChannel,
myStageRole,