Phase 666: DM embed queue, gossip, retry, and IME prefetch gate.

Wire DM link-embed ingest, conversation-scoped cache lookups, retryLinkEmbedForMessage,
and document slowmode-safe read-only embed resolve path.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Raven Scott
2026-06-02 20:16:30 -04:00
co-authored by Cursor
parent 09e254432a
commit 743f6bfb37
2 changed files with 49 additions and 8 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
Application facade: one `PearcordPlatform` class that wires identity, database, guild mesh, DMs, invites, voice, discovery, bots, and dozens of feature modules for the Pearcord desktop sidecar and companion.
**Phase 666 (v0.8.641):** Embed parity — `exportEmbedDiagnostics`, `_healStaleEmbedCache`, `_embedPrefetchCoalesce`, `embed.prefetch`/`embed.heal` spans, DM+guild `_queueLinkEmbed`, multi-embed view slice, `embed.clear` on edit. Bundle: `npm run test:ci-phase666`.
**Phase 666 (v0.8.641):** Embed parity — `exportEmbedDiagnostics`, `_healStaleEmbedCache`, `_embedPrefetchCoalesce`, `embed.prefetch`/`embed.heal` spans, DM+guild `_queueLinkEmbed`, DM `gossipLinkEmbed`, `retryLinkEmbedForMessage`, `setComposerImeComposing`, conversation-scoped cache, multi-embed view slice, `embed.clear` on edit. Bundle: `npm run test:ci-phase666`.
**Phase 659 (v0.8.638):** DM polls slice — `sendMessage` parses `/poll`, `/pollm`, `/polla`, `/pollma` in DM mode; `createDmPoll` supports expiry metadata, `voteDmPoll` enforces close/expiry guardrails with idempotent desired-state apply, `editDmPoll` allows edits only before first vote, `closeDmPoll` force-closes active polls, and `deleteDmPoll` enforces DM/group DM poll delete permissions. See [DM_POLLS.md](../../docs/DM_POLLS.md).
+48 -7
View File
@@ -580,6 +580,8 @@ class PearcordPlatform extends EventEmitter {
this._guildOpenFallbackThrottledUntil = 0
this._embedPrefetchCoalesce = new Map()
this._embedResolveLatencyByMessage = new Map()
/** Suppress composer-driven embed prefetch during IME composition (Phase 666). */
this._composerImeComposing = false
}
_getGuildOpenSlowThresholdMs () {
@@ -3129,6 +3131,10 @@ class PearcordPlatform extends EventEmitter {
this.attachments?.ingestGossip(p).catch(() => {})
this.emit('attachment', p)
})
dmInstance.on('link-embed', (p) => {
this.embeds?.ingestGossip(p).catch(() => {})
this.emit('link-embed', p)
})
dmInstance.on('user', (u) => {
void this._ingestUserProfile(u).catch(() => {})
})
@@ -3154,6 +3160,7 @@ class PearcordPlatform extends EventEmitter {
members: [],
usersById: {}
})
this._queueLinkEmbed(row).catch(() => {})
this.emit('message', row)
return row
})
@@ -12990,6 +12997,29 @@ class PearcordPlatform extends EventEmitter {
return this.exportEmbedDiagnostics(channelId)
}
setComposerImeComposing (active) {
this._composerImeComposing = !!active
return { active: this._composerImeComposing }
}
/** Re-resolve embed for an existing message (read-only fetch; ignores slowmode send gates). */
async retryLinkEmbedForMessage (messageId) {
if (!this.embeds || !messageId) throw new Error('embed retry requires messageId')
const row = await this.db.get(COLLECTIONS.MESSAGES, {
channelId: this.activeChannelId,
id: messageId
})
if (!row) {
const fallback = await this.db.find(COLLECTIONS.MESSAGES, { id: messageId })
const hit = (fallback || []).find((m) => m.id === messageId)
if (!hit) throw new Error('message not found')
await this._queueLinkEmbed(hit, { force: true })
return { messageId, retried: true }
}
await this._queueLinkEmbed(row, { force: true })
return { messageId, retried: true }
}
async updateStagedAttachmentMeta (attachmentId, patch = {}) {
if (!this.attachments) throw new Error('attachments not ready')
const row = await this.attachments.updateMeta(attachmentId, patch)
@@ -17392,10 +17422,12 @@ class PearcordPlatform extends EventEmitter {
}
}
async _queueLinkEmbed (msg) {
async _queueLinkEmbed (msg, opts = {}) {
if (!this.embeds || !msg) return
// Embed resolve is read-only fetch — never gated by slowmode send limits (P666-22).
const prefs = this.userSettings ? await this.userSettings.getPrefs() : null
if (prefs && (prefs.showLinkPreviews === false || prefs.linkPreviewsEnabled === false)) return
if (opts.composerPrefetch && this._composerImeComposing) return
if (this._readOnly) return
const plain = this._messagePlaintext(msg)
const url = extractFirstUrl(plain)
@@ -17403,7 +17435,7 @@ class PearcordPlatform extends EventEmitter {
if (this._embedPrefetchCoalesce.has(url)) {
return this._embedPrefetchCoalesce.get(url)
}
const job = this._resolveLinkEmbedForMessage(msg, url, plain)
const job = this._resolveLinkEmbedForMessage(msg, url, plain, opts)
this._embedPrefetchCoalesce.set(url, job)
try {
await job
@@ -17412,7 +17444,7 @@ class PearcordPlatform extends EventEmitter {
}
}
async _resolveLinkEmbedForMessage (msg, url, plain) {
async _resolveLinkEmbedForMessage (msg, url, plain, opts = {}) {
const guildId =
msg.guildId ||
(this.mode === 'dm' ? DM_GUILD_ID : this.guild?.guild?.id) ||
@@ -17434,8 +17466,13 @@ class PearcordPlatform extends EventEmitter {
channelId
})
try {
const fromCache = !!(await this.embeds.get(url))
const row = await this.embeds.resolveForContent(plain)
const conversationId =
this.mode === 'dm' ? msg.channelId || this.activeChannelId || null : null
const fromCache = !!(await this.embeds.get(url, { conversationId }))
const row = await this.embeds.resolveForContent(plain, {
force: !!opts.force,
conversationId
})
const latencyMs = Date.now() - started
if (msg.id) this._embedResolveLatencyByMessage.set(msg.id, latencyMs)
prefetchSpan.end({
@@ -17489,10 +17526,12 @@ class PearcordPlatform extends EventEmitter {
})
gossipSent = true
} else if (row && this.mode === 'dm' && this.dm?.gossipLinkEmbed) {
const conversationId = msg.channelId || this.activeChannelId || null
this.dm.gossipLinkEmbed({
...row,
messageId: msg.id,
channelId: msg.channelId
channelId: conversationId,
conversationId
})
gossipSent = true
}
@@ -20409,6 +20448,7 @@ class PearcordPlatform extends EventEmitter {
await dm.touchLastMessage(msg.channelId, { ...msg, content: previewPlain })
dm.gossipMessage(gossipMsg)
}
this._queueLinkEmbed(msg).catch(() => {})
} else if (this.guild) {
void this._guildGossipOrQueue('message', () => this.guild.gossipMessage(gossipMsg))
this._queueLinkEmbed(msg).catch(() => {})
@@ -23113,7 +23153,8 @@ class PearcordPlatform extends EventEmitter {
if (!urls.length || !this.embeds) continue
const rows = []
for (const url of urls) {
const row = await this.embeds.get(url)
const conversationId = this.mode === 'dm' ? m.channelId || this.activeChannelId : null
const row = await this.embeds.get(url, { conversationId })
if (row) rows.push(row)
}
if (rows.length === 1) linkEmbedsByMessage[m.id] = rows[0]