@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.
- package/README.md +146 -338
- package/package.json +7 -7
- package/scripts/check-pack.mjs +5 -1
- package/scripts/selfcheck.mjs +2 -1
- package/src/core/types.ts +1 -1
- package/src/messaging/inbound/commands.ts +118 -67
- package/src/messaging/inbound/conversation-history.ts +53 -5
- package/src/messaging/inbound/native-command.ts +7 -11
- package/src/messaging/inbound/outbound-replay-cache.ts +18 -2
- package/src/messaging/inbound/reply-dispatch.ts +8 -0
- package/src/openclaw/session-reset-runtime.ts +80 -32
- package/src/plugin/channel-runtime-types.ts +1 -1
- package/src/plugin/config.ts +18 -0
- package/src/plugin/inbound-handlers.ts +11 -5
- package/src/plugin/sqlite-state.ts +42 -18
- package/src/plugin/state-store.ts +54 -20
|
@@ -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
|
|
913
|
-
if (!
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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(
|
|
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(
|
|
369
|
-
const merged = options?.merge
|
|
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(
|
|
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
|
-
}) ??
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
573
|
-
const combined = options?.merge ? [...(target.get(
|
|
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(
|
|
617
|
+
target.set(historyKey, normalizeOutboundReplayBucket(historyKey, deduped));
|
|
584
618
|
}
|
|
585
619
|
}
|
|
586
620
|
}
|