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:
@@ -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).
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user