* Append a message to the user's transcript. * No-ops silently when no `userKey` is resolved (from `opts.userKey` or `msg.userKey`).
(
thread: { platform: string; conversationKey: string },
msg: { role?: "user" | "assistant"; text: string; userKey?: string },
opts?: { userKey?: string },
)
| 40 | * No-ops silently when no `userKey` is resolved (from `opts.userKey` or `msg.userKey`). |
| 41 | */ |
| 42 | async append( |
| 43 | thread: { platform: string; conversationKey: string }, |
| 44 | msg: { role?: "user" | "assistant"; text: string; userKey?: string }, |
| 45 | opts?: { userKey?: string }, |
| 46 | ): Promise<void> { |
| 47 | const userKey = opts?.userKey ?? msg.userKey; |
| 48 | if (!userKey) return; // identity unresolved → no-op |
| 49 | const entry: TranscriptEntry = { |
| 50 | role: msg.role ?? "user", |
| 51 | text: msg.text, |
| 52 | platform: thread.platform, |
| 53 | threadId: thread.conversationKey, |
| 54 | userKey, |
| 55 | ts: Date.now(), |
| 56 | }; |
| 57 | await this.state.list.append(keyFor(userKey), entry, { |
| 58 | maxLen: this.cfg.maxPerUser, |
| 59 | ttlMs: this.retentionMs, |
| 60 | }); |
| 61 | if (this.retentionMs !== undefined) { |
| 62 | const cutoff = Date.now() - this.retentionMs; |
| 63 | const all = await this.state.list.range<TranscriptEntry>(keyFor(userKey)); |
| 64 | const expired = all.filter((e) => e.ts < cutoff).length; |
| 65 | if (expired > 0) { |
| 66 | const survivors = all.length - expired; |
| 67 | if (survivors <= 0) await this.state.list.delete(keyFor(userKey)); |
| 68 | else await this.state.list.trim(keyFor(userKey), survivors); |
| 69 | } |
| 70 | } |
| 71 | } |
| 72 | |
| 73 | async list(q: { |
| 74 | userKey: string; |