@xmoxmo/bncr 0.6.5 → 0.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,17 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { DatabaseSync } from 'node:sqlite';
3
+ import { normalizeAccountId } from '../core/accounts.ts';
3
4
  import {
4
5
  dumpRegisterDriftSnapshot,
5
6
  normalizeRegisterDriftSnapshot,
6
7
  type RegisterDriftSnapshot,
7
8
  } from '../core/register-trace.ts';
8
9
  import type { BncrRoute, OutboxEntry } from '../core/types.ts';
10
+ import {
11
+ normalizePersistedHistoryKey,
12
+ resolvePersistedHistoryAccountId,
13
+ resolvePersistedHistoryKey,
14
+ } from '../messaging/inbound/conversation-history.ts';
9
15
  import type {
10
16
  BncrGroupReplyMode,
11
17
  BncrPersistedAccountTimestamp,
@@ -776,16 +782,18 @@ function loadControlState(db: DatabaseSync): BncrSqliteControlState {
776
782
  }
777
783
 
778
784
  function deriveHistoryKeyFromRoute(args: {
785
+ accountId?: string | null;
779
786
  platform?: string;
780
787
  groupId?: string;
781
788
  userId?: string;
782
789
  }): string | null {
790
+ const accountId = normalizeAccountId(args.accountId);
783
791
  const platform = String(args.platform || '').trim();
784
792
  const groupId = String(args.groupId || '0').trim() || '0';
785
793
  const userId = String(args.userId || '0').trim() || '0';
786
794
  if (!platform) return null;
787
- if (groupId !== '0') return `${platform}:${groupId}`;
788
- if (userId !== '0') return `${platform}:${userId}`;
795
+ if (groupId !== '0') return `${accountId}:${platform}:${groupId}`;
796
+ if (userId !== '0') return `${accountId}:${platform}:${userId}`;
789
797
  return null;
790
798
  }
791
799
 
@@ -861,7 +869,7 @@ function replaceHistoryMessages(
861
869
  const seenReplayMessageIds = new Map<string, Set<string>>();
862
870
 
863
871
  for (const bucket of historyBuckets) {
864
- const historyKey = String(bucket.key || '').trim();
872
+ const historyKey = normalizePersistedHistoryKey(String(bucket.key || '').trim());
865
873
  if (!historyKey) continue;
866
874
  for (const entry of Array.isArray(bucket.entries) ? bucket.entries : []) {
867
875
  const sender = String(entry?.sender || '').trim();
@@ -909,12 +917,22 @@ function replaceHistoryMessages(
909
917
  }
910
918
 
911
919
  for (const bucket of replayBuckets) {
912
- const bufferKey = String(bucket.key || '').trim();
913
- if (!bufferKey) continue;
920
+ const bucketKey = normalizePersistedHistoryKey(String(bucket.key || '').trim());
921
+ if (!bucketKey) continue;
914
922
  for (const entry of Array.isArray(bucket.entries) ? bucket.entries : []) {
915
923
  const sender = String(entry?.sender || '').trim();
916
924
  const body = String(entry?.body || '').trim();
917
925
  if (!sender || !body) continue;
926
+ const entryAccountId = entry.accountId
927
+ ? normalizeAccountId(entry.accountId)
928
+ : resolvePersistedHistoryAccountId(bucketKey);
929
+ const routeHistoryKey = deriveHistoryKeyFromRoute({
930
+ accountId: entryAccountId,
931
+ platform: entry.route?.platform,
932
+ groupId: entry.route?.groupId,
933
+ userId: entry.route?.userId,
934
+ });
935
+ const bufferKey = routeHistoryKey || resolvePersistedHistoryKey(bucketKey, entryAccountId);
918
936
  if (entry.messageId) {
919
937
  const seen = seenReplayMessageIds.get(bufferKey) ?? new Set<string>();
920
938
  if (seen.has(entry.messageId)) continue;
@@ -930,16 +948,12 @@ function replaceHistoryMessages(
930
948
  if (entry.messageId && isConsumedReplayMessage.get(bufferKey, entry.messageId)) {
931
949
  continue;
932
950
  }
933
- const historyKey = deriveHistoryKeyFromRoute({
934
- platform: entry.route?.platform,
935
- groupId: entry.route?.groupId,
936
- userId: entry.route?.userId,
937
- });
951
+ const historyKey = routeHistoryKey || bufferKey;
938
952
  insert.run(
939
953
  'replay',
940
954
  historyKey ?? '',
941
955
  bufferKey,
942
- entry.accountId ?? null,
956
+ entryAccountId,
943
957
  sender,
944
958
  entry.senderId ?? null,
945
959
  'assistant',
@@ -1807,13 +1821,23 @@ export function createBncrSqliteStateDatabase(
1807
1821
  }
1808
1822
 
1809
1823
  function normalizeHistoryKeySkipList(skipHistoryKeys: readonly string[] | undefined): string[] {
1810
- return Array.from(
1811
- new Set(
1812
- (skipHistoryKeys ?? [])
1813
- .map((key) => String(key || '').trim())
1814
- .filter((key) => Boolean(key)),
1815
- ),
1816
- );
1824
+ const normalized: string[] = [];
1825
+ for (const rawKey of skipHistoryKeys ?? []) {
1826
+ const key = String(rawKey || '').trim();
1827
+ if (!key) continue;
1828
+ normalized.push(key);
1829
+ const legacyKey = toLegacyHistoryKey(key);
1830
+ if (legacyKey && legacyKey !== key) normalized.push(legacyKey);
1831
+ }
1832
+ return Array.from(new Set(normalized));
1833
+ }
1834
+
1835
+ function toLegacyHistoryKey(historyKey: string): string {
1836
+ const firstColon = historyKey.indexOf(':');
1837
+ if (firstColon <= 0) return '';
1838
+ const secondColon = historyKey.indexOf(':', firstColon + 1);
1839
+ if (secondColon <= firstColon + 1) return '';
1840
+ return historyKey.slice(firstColon + 1);
1817
1841
  }
1818
1842
 
1819
1843
  function buildHistoryKeySkipCondition(skipped: readonly string[]): string {
@@ -10,8 +10,12 @@ import {
10
10
  type BncrConversationHistoryMap,
11
11
  buildBncrBotReplyMessageId,
12
12
  buildBncrConversationHistoryKeyFromRoute,
13
+ normalizePersistedHistoryKey,
13
14
  resetConversationHistoryVersions,
14
15
  resolveBncrHistoryLimit,
16
+ resolveBncrSceneKeyFromHistoryKey,
17
+ resolvePersistedHistoryAccountId,
18
+ resolvePersistedHistoryKey,
15
19
  } from '../messaging/inbound/conversation-history.ts';
16
20
  import {
17
21
  getConversationHistorySerialOwner,
@@ -165,7 +169,8 @@ export function createBncrStateStore(runtime: {
165
169
  }
166
170
 
167
171
  function resolvePersistedConversationHistoryLimit(key: string): number {
168
- const scene = runtime.sceneRegistry.get(key);
172
+ const sceneKey = resolveBncrSceneKeyFromHistoryKey(key);
173
+ const scene = runtime.sceneRegistry.get(sceneKey);
169
174
  const sceneLimit = resolveBncrHistoryLimit(scene?.historyLimit);
170
175
  return Math.max(DEFAULT_PERSISTED_CONVERSATION_HISTORY_LIMIT, Math.ceil(sceneLimit * 1.2));
171
176
  }
@@ -176,7 +181,11 @@ export function createBncrStateStore(runtime: {
176
181
  ): string | null {
177
182
  const route = entries.find((entry) => runtime.parseRouteLike(entry?.route))?.route;
178
183
  if (route) {
184
+ const accountId = entries.find((entry) =>
185
+ runtime.asString(entry.accountId || '').trim(),
186
+ )?.accountId;
179
187
  return buildBncrConversationHistoryKeyFromRoute({
188
+ accountId,
180
189
  platform: route.platform,
181
190
  groupId: route.groupId,
182
191
  userId: route.userId,
@@ -184,7 +193,7 @@ export function createBncrStateStore(runtime: {
184
193
  }
185
194
  const separator = bucketKey.indexOf(':');
186
195
  if (separator < 0 || separator >= bucketKey.length - 1) return null;
187
- return bucketKey.slice(separator + 1).trim() || null;
196
+ return normalizePersistedHistoryKey(bucketKey);
188
197
  }
189
198
 
190
199
  function resolveOutboundReplayBucketLimit(
@@ -193,7 +202,8 @@ export function createBncrStateStore(runtime: {
193
202
  ): number {
194
203
  const historyKey = resolveOutboundReplayBucketHistoryKey(bucketKey, entries);
195
204
  if (!historyKey) return DEFAULT_PERSISTED_CONVERSATION_HISTORY_LIMIT;
196
- return resolveBncrHistoryLimit(runtime.sceneRegistry.get(historyKey)?.historyLimit);
205
+ const sceneKey = resolveBncrSceneKeyFromHistoryKey(historyKey);
206
+ return resolveBncrHistoryLimit(runtime.sceneRegistry.get(sceneKey)?.historyLimit);
197
207
  }
198
208
 
199
209
  function normalizeOutboundReplayBucket(
@@ -282,6 +292,7 @@ export function createBncrStateStore(runtime: {
282
292
  for (const bucket of buckets) {
283
293
  const key = runtime.asString(bucket?.key || '').trim();
284
294
  if (!key) continue;
295
+ const normalizedKey = normalizePersistedHistoryKey(key);
285
296
  const entries: BncrPersistedConversationHistoryEntry[] = [];
286
297
  const rawEntries = Array.isArray(bucket?.entries)
287
298
  ? (bucket.entries as PersistedConversationHistoryEntryInput[])
@@ -315,7 +326,7 @@ export function createBncrStateStore(runtime: {
315
326
  }
316
327
 
317
328
  const rawRole = entry?.role;
318
- const assistantMessageIds = options?.assistantMessageIdsByHistoryKey?.get(key);
329
+ const assistantMessageIds = options?.assistantMessageIdsByHistoryKey?.get(normalizedKey);
319
330
  const role =
320
331
  rawRole === 'user' || rawRole === 'assistant' || rawRole === 'system'
321
332
  ? rawRole
@@ -325,7 +336,7 @@ export function createBncrStateStore(runtime: {
325
336
  if (!messageId) {
326
337
  messageId = buildMigratedHistoryMessageId(
327
338
  JSON.stringify({
328
- key,
339
+ key: normalizedKey,
329
340
  entryIndex,
330
341
  sender,
331
342
  senderId: runtime.asString(entry?.senderId || '').trim(),
@@ -365,8 +376,10 @@ export function createBncrStateStore(runtime: {
365
376
  entries.push(normalizedEntry);
366
377
  }
367
378
  if (entries.length > 0) {
368
- const limit = resolveLimit(key);
369
- const merged = options?.merge ? [...(target.get(key) || []), ...entries] : entries;
379
+ const limit = resolveLimit(normalizedKey);
380
+ const merged = options?.merge
381
+ ? [...(target.get(normalizedKey) || []), ...entries]
382
+ : entries;
370
383
  const seenMessageIds = new Set<string>();
371
384
  const deduped: BncrPersistedConversationHistoryEntry[] = [];
372
385
  for (const entry of merged) {
@@ -375,7 +388,7 @@ export function createBncrStateStore(runtime: {
375
388
  if (messageId) seenMessageIds.add(messageId);
376
389
  deduped.push(entry);
377
390
  }
378
- target.set(key, Number.isFinite(limit) ? deduped.slice(-limit) : deduped);
391
+ target.set(normalizedKey, Number.isFinite(limit) ? deduped.slice(-limit) : deduped);
379
392
  }
380
393
  }
381
394
  }
@@ -409,6 +422,7 @@ export function createBncrStateStore(runtime: {
409
422
  sender?: unknown;
410
423
  body?: unknown;
411
424
  messageId?: unknown;
425
+ accountId?: unknown;
412
426
  route?: { platform?: unknown; groupId?: unknown; userId?: unknown };
413
427
  }>) {
414
428
  const sender = runtime.asString(entry?.sender || '').trim();
@@ -417,12 +431,17 @@ export function createBncrStateStore(runtime: {
417
431
  const messageId = runtime.asString(entry?.messageId || '').trim();
418
432
  if (!messageId) continue;
419
433
  const route = entry?.route;
434
+ const entryAccountId = runtime.asString(entry?.accountId || '').trim() || undefined;
420
435
  const historyKey =
421
436
  buildBncrConversationHistoryKeyFromRoute({
437
+ accountId: entryAccountId,
422
438
  platform: typeof route?.platform === 'string' ? route.platform : undefined,
423
439
  groupId: typeof route?.groupId === 'string' ? route.groupId : undefined,
424
440
  userId: typeof route?.userId === 'string' ? route.userId : undefined,
425
- }) ?? (bucketKey.includes(':') ? bucketKey.slice(bucketKey.indexOf(':') + 1) : '');
441
+ }) ??
442
+ (entryAccountId
443
+ ? resolvePersistedHistoryKey(bucketKey, entryAccountId)
444
+ : normalizePersistedHistoryKey(bucketKey));
426
445
  if (!historyKey) continue;
427
446
  const scopedMessageIds = messageIdsByHistoryKey.get(historyKey) ?? new Set<string>();
428
447
  scopedMessageIds.add(messageId);
@@ -440,18 +459,18 @@ export function createBncrStateStore(runtime: {
440
459
  timestamp: number;
441
460
  media: BncrPersistedConversationHistoryMediaEntry[];
442
461
  route?: BncrRoute | null;
462
+ accountId?: string;
443
463
  historyMap?: Map<string, BncrPersistedConversationHistoryEntry[]>;
444
464
  }): string | undefined {
445
465
  const route = args.route;
446
466
  const historyKey = route
447
467
  ? buildBncrConversationHistoryKeyFromRoute({
468
+ accountId: args.accountId,
448
469
  platform: route.platform,
449
470
  groupId: route.groupId,
450
471
  userId: route.userId,
451
472
  })
452
- : args.bucketKey.includes(':')
453
- ? args.bucketKey.slice(args.bucketKey.indexOf(':') + 1).trim()
454
- : '';
473
+ : normalizePersistedHistoryKey(args.bucketKey);
455
474
  if (!historyKey) return undefined;
456
475
 
457
476
  const matchingHistory = args.historyMap
@@ -491,7 +510,7 @@ export function createBncrStateStore(runtime: {
491
510
  for (const bucket of buckets) {
492
511
  const key = runtime.asString(bucket?.key || '').trim();
493
512
  if (!key) continue;
494
- const entries: BncrOutboundReplayEntry[] = [];
513
+ const entriesByHistoryKey = new Map<string, BncrOutboundReplayEntry[]>();
495
514
  const rawEntries = Array.isArray(bucket?.entries)
496
515
  ? (bucket.entries as PersistedOutboundReplayEntryInput[])
497
516
  : [];
@@ -502,6 +521,9 @@ export function createBncrStateStore(runtime: {
502
521
  const timestamp = runtime.finiteNumberOr(entry?.timestamp, 0);
503
522
  const messageId = runtime.asString(entry?.messageId || '').trim();
504
523
  const accountId = runtime.asString(entry?.accountId || '').trim();
524
+ const effectiveAccountId = runtime.normalizeAccountId(
525
+ accountId || resolvePersistedHistoryAccountId(key),
526
+ );
505
527
  const sessionKey = runtime.asString(entry?.sessionKey || '').trim();
506
528
  const type = runtime.asString(entry?.type || '').trim();
507
529
  const mediaUrl = runtime.asString(entry?.mediaUrl || '').trim();
@@ -514,6 +536,14 @@ export function createBncrStateStore(runtime: {
514
536
  groupId: entry?.route?.groupId,
515
537
  userId: entry?.route?.userId,
516
538
  });
539
+ const historyKey = route
540
+ ? buildBncrConversationHistoryKeyFromRoute({
541
+ accountId: effectiveAccountId,
542
+ platform: route.platform,
543
+ groupId: route.groupId,
544
+ userId: route.userId,
545
+ }) || resolvePersistedHistoryKey(key, effectiveAccountId)
546
+ : resolvePersistedHistoryKey(key, effectiveAccountId);
517
547
  const media: BncrPersistedConversationHistoryMediaEntry[] = [];
518
548
  const rawMedia = Array.isArray(entry?.media)
519
549
  ? (entry.media as Partial<BncrPersistedConversationHistoryMediaEntry>[])
@@ -534,13 +564,14 @@ export function createBncrStateStore(runtime: {
534
564
  const resolvedMessageId =
535
565
  messageId ||
536
566
  resolvePersistedOutboundReplayMessageId({
537
- bucketKey: key,
567
+ bucketKey: historyKey,
538
568
  sender,
539
569
  senderId: runtime.asString(entry?.senderId || '').trim(),
540
570
  body,
541
571
  timestamp,
542
572
  media,
543
573
  route,
574
+ accountId: effectiveAccountId,
544
575
  historyMap: options?.historyMap,
545
576
  });
546
577
  const normalizedMedia = media.map((item) => ({
@@ -549,7 +580,7 @@ export function createBncrStateStore(runtime: {
549
580
  ? { messageId: item.messageId || resolvedMessageId }
550
581
  : {}),
551
582
  }));
552
- entries.push({
583
+ const normalizedEntry = {
553
584
  sender,
554
585
  ...(runtime.asString(entry?.senderId || '').trim()
555
586
  ? { senderId: runtime.asString(entry?.senderId || '').trim() }
@@ -558,7 +589,7 @@ export function createBncrStateStore(runtime: {
558
589
  ...(timestamp > 0 ? { timestamp } : {}),
559
590
  ...(resolvedMessageId ? { messageId: resolvedMessageId } : {}),
560
591
  ...(normalizedMedia.length > 0 ? { media: normalizedMedia } : {}),
561
- ...(accountId ? { accountId } : {}),
592
+ accountId: effectiveAccountId,
562
593
  ...(sessionKey ? { sessionKey } : {}),
563
594
  ...(route ? { route } : {}),
564
595
  ...(type ? { type } : {}),
@@ -567,10 +598,13 @@ export function createBncrStateStore(runtime: {
567
598
  ...(status === 'pushed' || status === 'acked'
568
599
  ? { status: status as BncrOutboundReplayStatus }
569
600
  : {}),
570
- });
601
+ } satisfies BncrOutboundReplayEntry;
602
+ const entries = entriesByHistoryKey.get(historyKey) || [];
603
+ entries.push(normalizedEntry);
604
+ entriesByHistoryKey.set(historyKey, entries);
571
605
  }
572
- if (entries.length > 0) {
573
- const combined = options?.merge ? [...(target.get(key) || []), ...entries] : entries;
606
+ for (const [historyKey, entries] of entriesByHistoryKey) {
607
+ const combined = options?.merge ? [...(target.get(historyKey) || []), ...entries] : entries;
574
608
  const seenMessageIds = new Set<string>();
575
609
  const deduped: BncrOutboundReplayEntry[] = [];
576
610
  for (const entry of combined) {
@@ -580,7 +614,7 @@ export function createBncrStateStore(runtime: {
580
614
  deduped.push(entry);
581
615
  }
582
616
  if (deduped.length > 0) {
583
- target.set(key, normalizeOutboundReplayBucket(key, deduped));
617
+ target.set(historyKey, normalizeOutboundReplayBucket(historyKey, deduped));
584
618
  }
585
619
  }
586
620
  }