@xmoxmo/bncr 0.6.4 → 0.6.6
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.
- package/README.md +146 -307
- package/package.json +7 -7
- package/scripts/check-pack.mjs +7 -1
- package/scripts/selfcheck.mjs +4 -1
- package/src/core/types.ts +1 -1
- package/src/messaging/inbound/command-owner.ts +33 -0
- package/src/messaging/inbound/commands.ts +427 -252
- package/src/messaging/inbound/conversation-history.ts +26 -5
- package/src/messaging/inbound/dispatch-prep.ts +8 -4
- package/src/messaging/inbound/dispatch.ts +3 -1
- package/src/messaging/inbound/native-command-runtime.ts +4 -0
- package/src/messaging/inbound/native-command.ts +92 -22
- package/src/messaging/inbound/outbound-replay-cache.ts +18 -2
- package/src/messaging/inbound/reply-dispatch.ts +25 -36
- package/src/messaging/inbound/scene-admin.ts +31 -3
- package/src/openclaw/inbound-session-runtime.ts +17 -0
- package/src/openclaw/session-reset-runtime.ts +125 -0
- package/src/plugin/channel-runtime-types.ts +1 -1
- package/src/plugin/inbound-acceptance.ts +18 -7
- package/src/plugin/sqlite-state.ts +59 -18
- package/src/plugin/state-store.ts +81 -20
|
@@ -86,7 +86,7 @@ export type BncrPersistedConversationHistoryEntry = {
|
|
|
86
86
|
export type BncrPersistedConversationHistoryMediaEntry = {
|
|
87
87
|
path?: string;
|
|
88
88
|
contentType?: string;
|
|
89
|
-
kind?: 'image' | 'video' | 'audio' | 'document' | 'unknown';
|
|
89
|
+
kind?: 'image' | 'video' | 'audio' | 'document' | 'sticker' | 'unknown';
|
|
90
90
|
messageId?: string;
|
|
91
91
|
};
|
|
92
92
|
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { hasControlCommand } from 'openclaw/plugin-sdk/command-auth';
|
|
2
2
|
import { resolveAccount } from '../core/accounts.ts';
|
|
3
3
|
import { checkBncrMessageGate } from '../messaging/inbound/gate.ts';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
parseBncrNativeCommand,
|
|
6
|
+
resolveBncrNativeCommandParseOptions,
|
|
7
|
+
} from '../messaging/inbound/native-command.ts';
|
|
5
8
|
import type { parseBncrInboundParams } from '../messaging/inbound/parse.ts';
|
|
6
9
|
import type { OpenClawResolvedAgentRoute } from '../openclaw/routing-runtime.ts';
|
|
7
10
|
import type { buildInboundResponsePayload } from './channel-inbound-helpers.ts';
|
|
@@ -21,9 +24,13 @@ function resolveDispatchBy(args: {
|
|
|
21
24
|
if (!admission.allowed) return 'denied';
|
|
22
25
|
|
|
23
26
|
const isBncrNativeCommand =
|
|
24
|
-
parseBncrNativeCommand(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
parseBncrNativeCommand(
|
|
28
|
+
parsed.extracted.text,
|
|
29
|
+
resolveBncrNativeCommandParseOptions({
|
|
30
|
+
isAdmin: parsed.isAdmin,
|
|
31
|
+
peerKind: parsed.peer.kind as 'direct' | 'group',
|
|
32
|
+
}),
|
|
33
|
+
) !== null;
|
|
27
34
|
const isAdminOpenClawNativeCommand =
|
|
28
35
|
parsed.isAdmin === true &&
|
|
29
36
|
!isBncrNativeCommand &&
|
|
@@ -49,9 +56,13 @@ function shouldDispatchForScene(args: {
|
|
|
49
56
|
if (!admission.allowed) return false;
|
|
50
57
|
|
|
51
58
|
const isBncrNativeCommand =
|
|
52
|
-
parseBncrNativeCommand(
|
|
53
|
-
|
|
54
|
-
|
|
59
|
+
parseBncrNativeCommand(
|
|
60
|
+
parsed.extracted.text,
|
|
61
|
+
resolveBncrNativeCommandParseOptions({
|
|
62
|
+
isAdmin: parsed.isAdmin,
|
|
63
|
+
peerKind: parsed.peer.kind as 'direct' | 'group',
|
|
64
|
+
}),
|
|
65
|
+
) !== null;
|
|
55
66
|
const isAdminOpenClawNativeCommand =
|
|
56
67
|
parsed.isAdmin === true &&
|
|
57
68
|
!isBncrNativeCommand &&
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { DatabaseSync } from 'node:sqlite';
|
|
3
|
+
import { BNCR_DEFAULT_ACCOUNT_ID, normalizeAccountId } from '../core/accounts.ts';
|
|
3
4
|
import {
|
|
4
5
|
dumpRegisterDriftSnapshot,
|
|
5
6
|
normalizeRegisterDriftSnapshot,
|
|
@@ -776,19 +777,43 @@ function loadControlState(db: DatabaseSync): BncrSqliteControlState {
|
|
|
776
777
|
}
|
|
777
778
|
|
|
778
779
|
function deriveHistoryKeyFromRoute(args: {
|
|
780
|
+
accountId?: string | null;
|
|
779
781
|
platform?: string;
|
|
780
782
|
groupId?: string;
|
|
781
783
|
userId?: string;
|
|
782
784
|
}): string | null {
|
|
785
|
+
const accountId = normalizeAccountId(args.accountId);
|
|
783
786
|
const platform = String(args.platform || '').trim();
|
|
784
787
|
const groupId = String(args.groupId || '0').trim() || '0';
|
|
785
788
|
const userId = String(args.userId || '0').trim() || '0';
|
|
786
789
|
if (!platform) return null;
|
|
787
|
-
if (groupId !== '0') return `${platform}:${groupId}`;
|
|
788
|
-
if (userId !== '0') return `${platform}:${userId}`;
|
|
790
|
+
if (groupId !== '0') return `${accountId}:${platform}:${groupId}`;
|
|
791
|
+
if (userId !== '0') return `${accountId}:${platform}:${userId}`;
|
|
789
792
|
return null;
|
|
790
793
|
}
|
|
791
794
|
|
|
795
|
+
function normalizePersistedHistoryKey(key: string): string {
|
|
796
|
+
const raw = String(key || '').trim();
|
|
797
|
+
if (!raw) return raw;
|
|
798
|
+
const firstColon = raw.indexOf(':');
|
|
799
|
+
const secondColon = raw.indexOf(':', firstColon + 1);
|
|
800
|
+
if (firstColon <= 0 || secondColon < 0) return `${BNCR_DEFAULT_ACCOUNT_ID}:${raw}`;
|
|
801
|
+
return `${normalizeAccountId(raw.slice(0, firstColon))}:${raw.slice(firstColon + 1)}`;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
function resolvePersistedHistoryAccountId(key: string): string {
|
|
805
|
+
const normalized = normalizePersistedHistoryKey(key);
|
|
806
|
+
const separator = normalized.indexOf(':');
|
|
807
|
+
return normalizeAccountId(separator > 0 ? normalized.slice(0, separator) : undefined);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
function resolvePersistedHistoryKey(key: string, accountId: string): string {
|
|
811
|
+
const normalized = normalizePersistedHistoryKey(key);
|
|
812
|
+
const separator = normalized.indexOf(':');
|
|
813
|
+
const sceneKey = separator > 0 ? normalized.slice(separator + 1) : normalized;
|
|
814
|
+
return `${normalizeAccountId(accountId)}:${sceneKey}`;
|
|
815
|
+
}
|
|
816
|
+
|
|
792
817
|
function parseMediaJson(value: string) {
|
|
793
818
|
try {
|
|
794
819
|
const parsed = JSON.parse(value ?? '[]');
|
|
@@ -861,7 +886,7 @@ function replaceHistoryMessages(
|
|
|
861
886
|
const seenReplayMessageIds = new Map<string, Set<string>>();
|
|
862
887
|
|
|
863
888
|
for (const bucket of historyBuckets) {
|
|
864
|
-
const historyKey = String(bucket.key || '').trim();
|
|
889
|
+
const historyKey = normalizePersistedHistoryKey(String(bucket.key || '').trim());
|
|
865
890
|
if (!historyKey) continue;
|
|
866
891
|
for (const entry of Array.isArray(bucket.entries) ? bucket.entries : []) {
|
|
867
892
|
const sender = String(entry?.sender || '').trim();
|
|
@@ -909,12 +934,22 @@ function replaceHistoryMessages(
|
|
|
909
934
|
}
|
|
910
935
|
|
|
911
936
|
for (const bucket of replayBuckets) {
|
|
912
|
-
const
|
|
913
|
-
if (!
|
|
937
|
+
const bucketKey = normalizePersistedHistoryKey(String(bucket.key || '').trim());
|
|
938
|
+
if (!bucketKey) continue;
|
|
914
939
|
for (const entry of Array.isArray(bucket.entries) ? bucket.entries : []) {
|
|
915
940
|
const sender = String(entry?.sender || '').trim();
|
|
916
941
|
const body = String(entry?.body || '').trim();
|
|
917
942
|
if (!sender || !body) continue;
|
|
943
|
+
const entryAccountId = entry.accountId
|
|
944
|
+
? normalizeAccountId(entry.accountId)
|
|
945
|
+
: resolvePersistedHistoryAccountId(bucketKey);
|
|
946
|
+
const routeHistoryKey = deriveHistoryKeyFromRoute({
|
|
947
|
+
accountId: entryAccountId,
|
|
948
|
+
platform: entry.route?.platform,
|
|
949
|
+
groupId: entry.route?.groupId,
|
|
950
|
+
userId: entry.route?.userId,
|
|
951
|
+
});
|
|
952
|
+
const bufferKey = routeHistoryKey || resolvePersistedHistoryKey(bucketKey, entryAccountId);
|
|
918
953
|
if (entry.messageId) {
|
|
919
954
|
const seen = seenReplayMessageIds.get(bufferKey) ?? new Set<string>();
|
|
920
955
|
if (seen.has(entry.messageId)) continue;
|
|
@@ -930,16 +965,12 @@ function replaceHistoryMessages(
|
|
|
930
965
|
if (entry.messageId && isConsumedReplayMessage.get(bufferKey, entry.messageId)) {
|
|
931
966
|
continue;
|
|
932
967
|
}
|
|
933
|
-
const historyKey =
|
|
934
|
-
platform: entry.route?.platform,
|
|
935
|
-
groupId: entry.route?.groupId,
|
|
936
|
-
userId: entry.route?.userId,
|
|
937
|
-
});
|
|
968
|
+
const historyKey = routeHistoryKey || bufferKey;
|
|
938
969
|
insert.run(
|
|
939
970
|
'replay',
|
|
940
971
|
historyKey ?? '',
|
|
941
972
|
bufferKey,
|
|
942
|
-
|
|
973
|
+
entryAccountId,
|
|
943
974
|
sender,
|
|
944
975
|
entry.senderId ?? null,
|
|
945
976
|
'assistant',
|
|
@@ -1807,13 +1838,23 @@ export function createBncrSqliteStateDatabase(
|
|
|
1807
1838
|
}
|
|
1808
1839
|
|
|
1809
1840
|
function normalizeHistoryKeySkipList(skipHistoryKeys: readonly string[] | undefined): string[] {
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
)
|
|
1816
|
-
|
|
1841
|
+
const normalized: string[] = [];
|
|
1842
|
+
for (const rawKey of skipHistoryKeys ?? []) {
|
|
1843
|
+
const key = String(rawKey || '').trim();
|
|
1844
|
+
if (!key) continue;
|
|
1845
|
+
normalized.push(key);
|
|
1846
|
+
const legacyKey = toLegacyHistoryKey(key);
|
|
1847
|
+
if (legacyKey && legacyKey !== key) normalized.push(legacyKey);
|
|
1848
|
+
}
|
|
1849
|
+
return Array.from(new Set(normalized));
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
function toLegacyHistoryKey(historyKey: string): string {
|
|
1853
|
+
const firstColon = historyKey.indexOf(':');
|
|
1854
|
+
if (firstColon <= 0) return '';
|
|
1855
|
+
const secondColon = historyKey.indexOf(':', firstColon + 1);
|
|
1856
|
+
if (secondColon <= firstColon + 1) return '';
|
|
1857
|
+
return historyKey.slice(firstColon + 1);
|
|
1817
1858
|
}
|
|
1818
1859
|
|
|
1819
1860
|
function buildHistoryKeySkipCondition(skipped: readonly string[]): string {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { readFile, rename } from 'node:fs/promises';
|
|
3
3
|
import { basename, dirname, join } from 'node:path';
|
|
4
|
+
import { BNCR_DEFAULT_ACCOUNT_ID, normalizeAccountId } from '../core/accounts.ts';
|
|
4
5
|
import {
|
|
5
6
|
dumpRegisterDriftSnapshot,
|
|
6
7
|
normalizeRegisterDriftSnapshot,
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
12
13
|
buildBncrConversationHistoryKeyFromRoute,
|
|
13
14
|
resetConversationHistoryVersions,
|
|
14
15
|
resolveBncrHistoryLimit,
|
|
16
|
+
resolveBncrSceneKeyFromHistoryKey,
|
|
15
17
|
} from '../messaging/inbound/conversation-history.ts';
|
|
16
18
|
import {
|
|
17
19
|
getConversationHistorySerialOwner,
|
|
@@ -51,6 +53,35 @@ import type {
|
|
|
51
53
|
const GROUP_REPLY_MODES = new Set<BncrGroupReplyMode>(['admin', 'mention', 'hybrid', 'all']);
|
|
52
54
|
const DEFAULT_PERSISTED_CONVERSATION_HISTORY_LIMIT = 50;
|
|
53
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Migrate legacy scene-only history keys (`tgBot:10001`) to the current
|
|
58
|
+
* account-scoped format (`Primary:tgBot:10001`). Keys that already carry an
|
|
59
|
+
* accountId prefix are returned unchanged.
|
|
60
|
+
*/
|
|
61
|
+
function normalizePersistedHistoryKey(key: string): string {
|
|
62
|
+
const raw = String(key || '').trim();
|
|
63
|
+
if (!raw) return raw;
|
|
64
|
+
const firstColon = raw.indexOf(':');
|
|
65
|
+
const secondColon = raw.indexOf(':', firstColon + 1);
|
|
66
|
+
if (firstColon <= 0 || secondColon < 0) return `${BNCR_DEFAULT_ACCOUNT_ID}:${raw}`;
|
|
67
|
+
return `${normalizeAccountId(raw.slice(0, firstColon))}:${raw.slice(firstColon + 1)}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function resolvePersistedHistoryAccountId(key: string): string {
|
|
71
|
+
const normalized = normalizePersistedHistoryKey(key);
|
|
72
|
+
const separator = normalized.indexOf(':');
|
|
73
|
+
return separator > 0
|
|
74
|
+
? normalized.slice(0, separator).trim() || BNCR_DEFAULT_ACCOUNT_ID
|
|
75
|
+
: BNCR_DEFAULT_ACCOUNT_ID;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function resolvePersistedHistoryKey(key: string, accountId: string): string {
|
|
79
|
+
const normalized = normalizePersistedHistoryKey(key);
|
|
80
|
+
const separator = normalized.indexOf(':');
|
|
81
|
+
const sceneKey = separator > 0 ? normalized.slice(separator + 1) : normalized;
|
|
82
|
+
return `${normalizeAccountId(accountId)}:${sceneKey}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
54
85
|
type BncrPersistedStateStoreInput = {
|
|
55
86
|
outbox?: unknown;
|
|
56
87
|
deadLetter?: unknown;
|
|
@@ -165,7 +196,8 @@ export function createBncrStateStore(runtime: {
|
|
|
165
196
|
}
|
|
166
197
|
|
|
167
198
|
function resolvePersistedConversationHistoryLimit(key: string): number {
|
|
168
|
-
const
|
|
199
|
+
const sceneKey = resolveBncrSceneKeyFromHistoryKey(key);
|
|
200
|
+
const scene = runtime.sceneRegistry.get(sceneKey);
|
|
169
201
|
const sceneLimit = resolveBncrHistoryLimit(scene?.historyLimit);
|
|
170
202
|
return Math.max(DEFAULT_PERSISTED_CONVERSATION_HISTORY_LIMIT, Math.ceil(sceneLimit * 1.2));
|
|
171
203
|
}
|
|
@@ -176,7 +208,11 @@ export function createBncrStateStore(runtime: {
|
|
|
176
208
|
): string | null {
|
|
177
209
|
const route = entries.find((entry) => runtime.parseRouteLike(entry?.route))?.route;
|
|
178
210
|
if (route) {
|
|
211
|
+
const accountId = entries.find((entry) =>
|
|
212
|
+
runtime.asString(entry.accountId || '').trim(),
|
|
213
|
+
)?.accountId;
|
|
179
214
|
return buildBncrConversationHistoryKeyFromRoute({
|
|
215
|
+
accountId,
|
|
180
216
|
platform: route.platform,
|
|
181
217
|
groupId: route.groupId,
|
|
182
218
|
userId: route.userId,
|
|
@@ -184,7 +220,7 @@ export function createBncrStateStore(runtime: {
|
|
|
184
220
|
}
|
|
185
221
|
const separator = bucketKey.indexOf(':');
|
|
186
222
|
if (separator < 0 || separator >= bucketKey.length - 1) return null;
|
|
187
|
-
return bucketKey
|
|
223
|
+
return normalizePersistedHistoryKey(bucketKey);
|
|
188
224
|
}
|
|
189
225
|
|
|
190
226
|
function resolveOutboundReplayBucketLimit(
|
|
@@ -193,7 +229,8 @@ export function createBncrStateStore(runtime: {
|
|
|
193
229
|
): number {
|
|
194
230
|
const historyKey = resolveOutboundReplayBucketHistoryKey(bucketKey, entries);
|
|
195
231
|
if (!historyKey) return DEFAULT_PERSISTED_CONVERSATION_HISTORY_LIMIT;
|
|
196
|
-
|
|
232
|
+
const sceneKey = resolveBncrSceneKeyFromHistoryKey(historyKey);
|
|
233
|
+
return resolveBncrHistoryLimit(runtime.sceneRegistry.get(sceneKey)?.historyLimit);
|
|
197
234
|
}
|
|
198
235
|
|
|
199
236
|
function normalizeOutboundReplayBucket(
|
|
@@ -282,6 +319,7 @@ export function createBncrStateStore(runtime: {
|
|
|
282
319
|
for (const bucket of buckets) {
|
|
283
320
|
const key = runtime.asString(bucket?.key || '').trim();
|
|
284
321
|
if (!key) continue;
|
|
322
|
+
const normalizedKey = normalizePersistedHistoryKey(key);
|
|
285
323
|
const entries: BncrPersistedConversationHistoryEntry[] = [];
|
|
286
324
|
const rawEntries = Array.isArray(bucket?.entries)
|
|
287
325
|
? (bucket.entries as PersistedConversationHistoryEntryInput[])
|
|
@@ -315,7 +353,7 @@ export function createBncrStateStore(runtime: {
|
|
|
315
353
|
}
|
|
316
354
|
|
|
317
355
|
const rawRole = entry?.role;
|
|
318
|
-
const assistantMessageIds = options?.assistantMessageIdsByHistoryKey?.get(
|
|
356
|
+
const assistantMessageIds = options?.assistantMessageIdsByHistoryKey?.get(normalizedKey);
|
|
319
357
|
const role =
|
|
320
358
|
rawRole === 'user' || rawRole === 'assistant' || rawRole === 'system'
|
|
321
359
|
? rawRole
|
|
@@ -325,7 +363,7 @@ export function createBncrStateStore(runtime: {
|
|
|
325
363
|
if (!messageId) {
|
|
326
364
|
messageId = buildMigratedHistoryMessageId(
|
|
327
365
|
JSON.stringify({
|
|
328
|
-
key,
|
|
366
|
+
key: normalizedKey,
|
|
329
367
|
entryIndex,
|
|
330
368
|
sender,
|
|
331
369
|
senderId: runtime.asString(entry?.senderId || '').trim(),
|
|
@@ -365,8 +403,10 @@ export function createBncrStateStore(runtime: {
|
|
|
365
403
|
entries.push(normalizedEntry);
|
|
366
404
|
}
|
|
367
405
|
if (entries.length > 0) {
|
|
368
|
-
const limit = resolveLimit(
|
|
369
|
-
const merged = options?.merge
|
|
406
|
+
const limit = resolveLimit(normalizedKey);
|
|
407
|
+
const merged = options?.merge
|
|
408
|
+
? [...(target.get(normalizedKey) || []), ...entries]
|
|
409
|
+
: entries;
|
|
370
410
|
const seenMessageIds = new Set<string>();
|
|
371
411
|
const deduped: BncrPersistedConversationHistoryEntry[] = [];
|
|
372
412
|
for (const entry of merged) {
|
|
@@ -375,7 +415,7 @@ export function createBncrStateStore(runtime: {
|
|
|
375
415
|
if (messageId) seenMessageIds.add(messageId);
|
|
376
416
|
deduped.push(entry);
|
|
377
417
|
}
|
|
378
|
-
target.set(
|
|
418
|
+
target.set(normalizedKey, Number.isFinite(limit) ? deduped.slice(-limit) : deduped);
|
|
379
419
|
}
|
|
380
420
|
}
|
|
381
421
|
}
|
|
@@ -409,6 +449,7 @@ export function createBncrStateStore(runtime: {
|
|
|
409
449
|
sender?: unknown;
|
|
410
450
|
body?: unknown;
|
|
411
451
|
messageId?: unknown;
|
|
452
|
+
accountId?: unknown;
|
|
412
453
|
route?: { platform?: unknown; groupId?: unknown; userId?: unknown };
|
|
413
454
|
}>) {
|
|
414
455
|
const sender = runtime.asString(entry?.sender || '').trim();
|
|
@@ -417,12 +458,17 @@ export function createBncrStateStore(runtime: {
|
|
|
417
458
|
const messageId = runtime.asString(entry?.messageId || '').trim();
|
|
418
459
|
if (!messageId) continue;
|
|
419
460
|
const route = entry?.route;
|
|
461
|
+
const entryAccountId = runtime.asString(entry?.accountId || '').trim() || undefined;
|
|
420
462
|
const historyKey =
|
|
421
463
|
buildBncrConversationHistoryKeyFromRoute({
|
|
464
|
+
accountId: entryAccountId,
|
|
422
465
|
platform: typeof route?.platform === 'string' ? route.platform : undefined,
|
|
423
466
|
groupId: typeof route?.groupId === 'string' ? route.groupId : undefined,
|
|
424
467
|
userId: typeof route?.userId === 'string' ? route.userId : undefined,
|
|
425
|
-
}) ??
|
|
468
|
+
}) ??
|
|
469
|
+
(entryAccountId
|
|
470
|
+
? resolvePersistedHistoryKey(bucketKey, entryAccountId)
|
|
471
|
+
: normalizePersistedHistoryKey(bucketKey));
|
|
426
472
|
if (!historyKey) continue;
|
|
427
473
|
const scopedMessageIds = messageIdsByHistoryKey.get(historyKey) ?? new Set<string>();
|
|
428
474
|
scopedMessageIds.add(messageId);
|
|
@@ -440,18 +486,18 @@ export function createBncrStateStore(runtime: {
|
|
|
440
486
|
timestamp: number;
|
|
441
487
|
media: BncrPersistedConversationHistoryMediaEntry[];
|
|
442
488
|
route?: BncrRoute | null;
|
|
489
|
+
accountId?: string;
|
|
443
490
|
historyMap?: Map<string, BncrPersistedConversationHistoryEntry[]>;
|
|
444
491
|
}): string | undefined {
|
|
445
492
|
const route = args.route;
|
|
446
493
|
const historyKey = route
|
|
447
494
|
? buildBncrConversationHistoryKeyFromRoute({
|
|
495
|
+
accountId: args.accountId,
|
|
448
496
|
platform: route.platform,
|
|
449
497
|
groupId: route.groupId,
|
|
450
498
|
userId: route.userId,
|
|
451
499
|
})
|
|
452
|
-
: args.bucketKey
|
|
453
|
-
? args.bucketKey.slice(args.bucketKey.indexOf(':') + 1).trim()
|
|
454
|
-
: '';
|
|
500
|
+
: normalizePersistedHistoryKey(args.bucketKey);
|
|
455
501
|
if (!historyKey) return undefined;
|
|
456
502
|
|
|
457
503
|
const matchingHistory = args.historyMap
|
|
@@ -491,7 +537,7 @@ export function createBncrStateStore(runtime: {
|
|
|
491
537
|
for (const bucket of buckets) {
|
|
492
538
|
const key = runtime.asString(bucket?.key || '').trim();
|
|
493
539
|
if (!key) continue;
|
|
494
|
-
const
|
|
540
|
+
const entriesByHistoryKey = new Map<string, BncrOutboundReplayEntry[]>();
|
|
495
541
|
const rawEntries = Array.isArray(bucket?.entries)
|
|
496
542
|
? (bucket.entries as PersistedOutboundReplayEntryInput[])
|
|
497
543
|
: [];
|
|
@@ -502,6 +548,9 @@ export function createBncrStateStore(runtime: {
|
|
|
502
548
|
const timestamp = runtime.finiteNumberOr(entry?.timestamp, 0);
|
|
503
549
|
const messageId = runtime.asString(entry?.messageId || '').trim();
|
|
504
550
|
const accountId = runtime.asString(entry?.accountId || '').trim();
|
|
551
|
+
const effectiveAccountId = runtime.normalizeAccountId(
|
|
552
|
+
accountId || resolvePersistedHistoryAccountId(key),
|
|
553
|
+
);
|
|
505
554
|
const sessionKey = runtime.asString(entry?.sessionKey || '').trim();
|
|
506
555
|
const type = runtime.asString(entry?.type || '').trim();
|
|
507
556
|
const mediaUrl = runtime.asString(entry?.mediaUrl || '').trim();
|
|
@@ -514,6 +563,14 @@ export function createBncrStateStore(runtime: {
|
|
|
514
563
|
groupId: entry?.route?.groupId,
|
|
515
564
|
userId: entry?.route?.userId,
|
|
516
565
|
});
|
|
566
|
+
const historyKey = route
|
|
567
|
+
? buildBncrConversationHistoryKeyFromRoute({
|
|
568
|
+
accountId: effectiveAccountId,
|
|
569
|
+
platform: route.platform,
|
|
570
|
+
groupId: route.groupId,
|
|
571
|
+
userId: route.userId,
|
|
572
|
+
}) || resolvePersistedHistoryKey(key, effectiveAccountId)
|
|
573
|
+
: resolvePersistedHistoryKey(key, effectiveAccountId);
|
|
517
574
|
const media: BncrPersistedConversationHistoryMediaEntry[] = [];
|
|
518
575
|
const rawMedia = Array.isArray(entry?.media)
|
|
519
576
|
? (entry.media as Partial<BncrPersistedConversationHistoryMediaEntry>[])
|
|
@@ -534,13 +591,14 @@ export function createBncrStateStore(runtime: {
|
|
|
534
591
|
const resolvedMessageId =
|
|
535
592
|
messageId ||
|
|
536
593
|
resolvePersistedOutboundReplayMessageId({
|
|
537
|
-
bucketKey:
|
|
594
|
+
bucketKey: historyKey,
|
|
538
595
|
sender,
|
|
539
596
|
senderId: runtime.asString(entry?.senderId || '').trim(),
|
|
540
597
|
body,
|
|
541
598
|
timestamp,
|
|
542
599
|
media,
|
|
543
600
|
route,
|
|
601
|
+
accountId: effectiveAccountId,
|
|
544
602
|
historyMap: options?.historyMap,
|
|
545
603
|
});
|
|
546
604
|
const normalizedMedia = media.map((item) => ({
|
|
@@ -549,7 +607,7 @@ export function createBncrStateStore(runtime: {
|
|
|
549
607
|
? { messageId: item.messageId || resolvedMessageId }
|
|
550
608
|
: {}),
|
|
551
609
|
}));
|
|
552
|
-
|
|
610
|
+
const normalizedEntry = {
|
|
553
611
|
sender,
|
|
554
612
|
...(runtime.asString(entry?.senderId || '').trim()
|
|
555
613
|
? { senderId: runtime.asString(entry?.senderId || '').trim() }
|
|
@@ -558,7 +616,7 @@ export function createBncrStateStore(runtime: {
|
|
|
558
616
|
...(timestamp > 0 ? { timestamp } : {}),
|
|
559
617
|
...(resolvedMessageId ? { messageId: resolvedMessageId } : {}),
|
|
560
618
|
...(normalizedMedia.length > 0 ? { media: normalizedMedia } : {}),
|
|
561
|
-
|
|
619
|
+
accountId: effectiveAccountId,
|
|
562
620
|
...(sessionKey ? { sessionKey } : {}),
|
|
563
621
|
...(route ? { route } : {}),
|
|
564
622
|
...(type ? { type } : {}),
|
|
@@ -567,10 +625,13 @@ export function createBncrStateStore(runtime: {
|
|
|
567
625
|
...(status === 'pushed' || status === 'acked'
|
|
568
626
|
? { status: status as BncrOutboundReplayStatus }
|
|
569
627
|
: {}),
|
|
570
|
-
}
|
|
628
|
+
} satisfies BncrOutboundReplayEntry;
|
|
629
|
+
const entries = entriesByHistoryKey.get(historyKey) || [];
|
|
630
|
+
entries.push(normalizedEntry);
|
|
631
|
+
entriesByHistoryKey.set(historyKey, entries);
|
|
571
632
|
}
|
|
572
|
-
|
|
573
|
-
const combined = options?.merge ? [...(target.get(
|
|
633
|
+
for (const [historyKey, entries] of entriesByHistoryKey) {
|
|
634
|
+
const combined = options?.merge ? [...(target.get(historyKey) || []), ...entries] : entries;
|
|
574
635
|
const seenMessageIds = new Set<string>();
|
|
575
636
|
const deduped: BncrOutboundReplayEntry[] = [];
|
|
576
637
|
for (const entry of combined) {
|
|
@@ -580,7 +641,7 @@ export function createBncrStateStore(runtime: {
|
|
|
580
641
|
deduped.push(entry);
|
|
581
642
|
}
|
|
582
643
|
if (deduped.length > 0) {
|
|
583
|
-
target.set(
|
|
644
|
+
target.set(historyKey, normalizeOutboundReplayBucket(historyKey, deduped));
|
|
584
645
|
}
|
|
585
646
|
}
|
|
586
647
|
}
|