@xmoxmo/bncr 0.5.9 → 0.6.1
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 +8 -0
- package/dist/index.js +2 -0
- package/index.ts +1 -0
- package/package.json +4 -1
- package/scripts/check-pack.mjs +41 -1
- package/scripts/selfcheck.mjs +3 -0
- package/src/bootstrap/register-runtime-gateway.ts +1 -0
- package/src/bootstrap/register-runtime.ts +1 -0
- package/src/channel.ts +50 -1
- package/src/core/types.ts +41 -0
- package/src/messaging/inbound/dispatch-prep.ts +5 -1
- package/src/messaging/inbound/dispatch.ts +26 -8
- package/src/messaging/inbound/outbound-replay-cache.ts +236 -0
- package/src/messaging/inbound/turn-context.ts +16 -8
- package/src/plugin/ack-outbox-runtime-group.ts +2 -0
- package/src/plugin/bridge-call.ts +168 -0
- package/src/plugin/bridge-outbox-facade.ts +79 -1
- package/src/plugin/channel-components.ts +5 -0
- package/src/plugin/channel-plugin-bridge-group.ts +43 -1
- package/src/plugin/channel-plugin-surface-group.ts +82 -7
- package/src/plugin/channel-runtime-builders-delivery.ts +2 -0
- package/src/plugin/channel-runtime-types.ts +26 -2
- package/src/plugin/channel-send.ts +12 -19
- package/src/plugin/client-rpc-runtime.ts +129 -0
- package/src/plugin/gateway-methods.ts +1 -0
- package/src/plugin/inbound-handlers.ts +4 -0
- package/src/plugin/inbound-surface-handlers-group.ts +3 -0
- package/src/plugin/outbox-ack-outcome.ts +2 -0
- package/src/plugin/state-store.ts +125 -5
- package/src/plugin/state-transient-runtime-group.ts +8 -1
|
@@ -3,6 +3,10 @@ import {
|
|
|
3
3
|
normalizeRegisterDriftSnapshot,
|
|
4
4
|
} from '../core/register-trace.ts';
|
|
5
5
|
import type { BncrRoute, OutboxEntry } from '../core/types.ts';
|
|
6
|
+
import type {
|
|
7
|
+
BncrOutboundReplayEntry,
|
|
8
|
+
BncrOutboundReplayStatus,
|
|
9
|
+
} from '../messaging/inbound/outbound-replay-cache.ts';
|
|
6
10
|
import {
|
|
7
11
|
readOpenClawJsonFileWithFallback,
|
|
8
12
|
writeOpenClawJsonFileAtomically,
|
|
@@ -14,6 +18,8 @@ import type {
|
|
|
14
18
|
BncrPersistedGroupHistoryEntry,
|
|
15
19
|
BncrPersistedGroupHistoryMediaEntry,
|
|
16
20
|
BncrPersistedLastSession,
|
|
21
|
+
BncrPersistedOutboundReplayBucket,
|
|
22
|
+
BncrPersistedOutboundReplayEntry,
|
|
17
23
|
BncrPersistedSessionRoute,
|
|
18
24
|
PersistedState as BncrPersistedState,
|
|
19
25
|
BncrSceneRecord,
|
|
@@ -28,6 +34,9 @@ type BncrPersistedStateStoreInput = {
|
|
|
28
34
|
sessionRoutes?: unknown;
|
|
29
35
|
sceneRegistry?: unknown;
|
|
30
36
|
groupHistories?: unknown;
|
|
37
|
+
outboundReplayCache?: unknown;
|
|
38
|
+
// Legacy persisted key from before the outboundReplayCache rename.
|
|
39
|
+
messageCache?: unknown;
|
|
31
40
|
lastSessionByAccount?: unknown;
|
|
32
41
|
lastActivityByAccount?: unknown;
|
|
33
42
|
lastInboundByAccount?: unknown;
|
|
@@ -41,6 +50,8 @@ type PersistedSessionRouteInput = Partial<BncrPersistedSessionRoute>;
|
|
|
41
50
|
type PersistedSceneRecordInput = Partial<BncrSceneRecord>;
|
|
42
51
|
type PersistedGroupHistoryBucketInput = Partial<BncrPersistedGroupHistoryBucket>;
|
|
43
52
|
type PersistedGroupHistoryEntryInput = Partial<BncrPersistedGroupHistoryEntry>;
|
|
53
|
+
type PersistedOutboundReplayBucketInput = Partial<BncrPersistedOutboundReplayBucket>;
|
|
54
|
+
type PersistedOutboundReplayEntryInput = Partial<BncrOutboundReplayEntry>;
|
|
44
55
|
|
|
45
56
|
export function createBncrStateStore(runtime: {
|
|
46
57
|
getStatePath: () => string | null;
|
|
@@ -65,6 +76,7 @@ export function createBncrStateStore(runtime: {
|
|
|
65
76
|
maxAccountActivityEntries: number;
|
|
66
77
|
sceneRegistry: Map<string, BncrSceneRecord>;
|
|
67
78
|
groupHistories: Map<string, BncrPersistedGroupHistoryEntry[]>;
|
|
79
|
+
outboundReplayCache?: Map<string, BncrPersistedOutboundReplayEntry[]>;
|
|
68
80
|
outbox: Map<string, OutboxEntry>;
|
|
69
81
|
getDeadLetter: () => OutboxEntry[];
|
|
70
82
|
setDeadLetter: (entries: OutboxEntry[]) => void;
|
|
@@ -77,6 +89,9 @@ export function createBncrStateStore(runtime: {
|
|
|
77
89
|
getLastDriftSnapshot: () => BncrPersistedState['lastDriftSnapshot'];
|
|
78
90
|
setLastDriftSnapshot: (value: BncrPersistedState['lastDriftSnapshot']) => void;
|
|
79
91
|
}) {
|
|
92
|
+
const outboundReplayCache =
|
|
93
|
+
runtime.outboundReplayCache ?? new Map<string, BncrPersistedOutboundReplayEntry[]>();
|
|
94
|
+
|
|
80
95
|
function resolvePersistedGroupHistoryLimit(key: string): number {
|
|
81
96
|
const scene = runtime.sceneRegistry.get(key);
|
|
82
97
|
const sceneLimit = scene?.kind === 'group' ? scene.historyLimit : undefined;
|
|
@@ -148,8 +163,12 @@ export function createBncrStateStore(runtime: {
|
|
|
148
163
|
return Array.from(runtime.sceneRegistry.values()).sort((a, b) => a.lastSeenAt - b.lastSeenAt);
|
|
149
164
|
}
|
|
150
165
|
|
|
151
|
-
function
|
|
152
|
-
|
|
166
|
+
function loadPersistedHistoryBuckets(
|
|
167
|
+
persisted: unknown,
|
|
168
|
+
target: Map<string, BncrPersistedGroupHistoryEntry[]>,
|
|
169
|
+
resolveLimit: (key: string) => number,
|
|
170
|
+
): void {
|
|
171
|
+
target.clear();
|
|
153
172
|
const buckets = Array.isArray(persisted)
|
|
154
173
|
? (persisted as PersistedGroupHistoryBucketInput[])
|
|
155
174
|
: [];
|
|
@@ -199,15 +218,112 @@ export function createBncrStateStore(runtime: {
|
|
|
199
218
|
entries.push(normalizedEntry);
|
|
200
219
|
}
|
|
201
220
|
if (entries.length > 0) {
|
|
202
|
-
|
|
221
|
+
const limit = resolveLimit(key);
|
|
222
|
+
target.set(key, Number.isFinite(limit) ? entries.slice(-limit) : entries);
|
|
203
223
|
}
|
|
204
224
|
}
|
|
205
225
|
}
|
|
206
226
|
|
|
227
|
+
function loadPersistedGroupHistories(persisted: unknown): void {
|
|
228
|
+
loadPersistedHistoryBuckets(persisted, runtime.groupHistories, (key) =>
|
|
229
|
+
resolvePersistedGroupHistoryLimit(key),
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function loadPersistedOutboundReplayCache(persisted: unknown): void {
|
|
234
|
+
outboundReplayCache.clear();
|
|
235
|
+
const buckets = Array.isArray(persisted)
|
|
236
|
+
? (persisted as PersistedOutboundReplayBucketInput[])
|
|
237
|
+
: [];
|
|
238
|
+
for (const bucket of buckets) {
|
|
239
|
+
const key = runtime.asString(bucket?.key || '').trim();
|
|
240
|
+
if (!key) continue;
|
|
241
|
+
const entries: BncrOutboundReplayEntry[] = [];
|
|
242
|
+
const rawEntries = Array.isArray(bucket?.entries)
|
|
243
|
+
? (bucket.entries as PersistedOutboundReplayEntryInput[])
|
|
244
|
+
: [];
|
|
245
|
+
for (const entry of rawEntries) {
|
|
246
|
+
const sender = runtime.asString(entry?.sender || '').trim();
|
|
247
|
+
const body = runtime.asString(entry?.body || '').trim();
|
|
248
|
+
if (!sender || !body) continue;
|
|
249
|
+
const timestamp = runtime.finiteNumberOr(entry?.timestamp, 0);
|
|
250
|
+
const messageId = runtime.asString(entry?.messageId || '').trim();
|
|
251
|
+
const accountId = runtime.asString(entry?.accountId || '').trim();
|
|
252
|
+
const sessionKey = runtime.asString(entry?.sessionKey || '').trim();
|
|
253
|
+
const type = runtime.asString(entry?.type || '').trim();
|
|
254
|
+
const mediaUrl = runtime.asString(entry?.mediaUrl || '').trim();
|
|
255
|
+
const createdAt = runtime.finiteNumberOr(entry?.createdAt, 0);
|
|
256
|
+
const status = runtime.asString(entry?.status || '').trim();
|
|
257
|
+
const route =
|
|
258
|
+
runtime.parseRouteLike(entry?.route) ||
|
|
259
|
+
runtime.parseRouteLike({
|
|
260
|
+
platform: entry?.route?.platform,
|
|
261
|
+
groupId: entry?.route?.groupId,
|
|
262
|
+
userId: entry?.route?.userId,
|
|
263
|
+
});
|
|
264
|
+
const media: BncrPersistedGroupHistoryMediaEntry[] = [];
|
|
265
|
+
const rawMedia = Array.isArray(entry?.media)
|
|
266
|
+
? (entry.media as Partial<BncrPersistedGroupHistoryMediaEntry>[])
|
|
267
|
+
: [];
|
|
268
|
+
for (const item of rawMedia) {
|
|
269
|
+
const path = runtime.asString(item?.path || '').trim();
|
|
270
|
+
if (!path) continue;
|
|
271
|
+
const contentType = runtime.asString(item?.contentType || '').trim();
|
|
272
|
+
const kind = runtime.asString(item?.kind || '').trim();
|
|
273
|
+
const mediaMessageId = runtime.asString(item?.messageId || '').trim();
|
|
274
|
+
media.push({
|
|
275
|
+
path,
|
|
276
|
+
...(contentType ? { contentType } : {}),
|
|
277
|
+
...(kind ? { kind: kind as BncrPersistedGroupHistoryMediaEntry['kind'] } : {}),
|
|
278
|
+
...(mediaMessageId ? { messageId: mediaMessageId } : {}),
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
entries.push({
|
|
282
|
+
sender,
|
|
283
|
+
...(runtime.asString(entry?.senderId || '').trim()
|
|
284
|
+
? { senderId: runtime.asString(entry?.senderId || '').trim() }
|
|
285
|
+
: {}),
|
|
286
|
+
body,
|
|
287
|
+
...(timestamp > 0 ? { timestamp } : {}),
|
|
288
|
+
...(messageId ? { messageId } : {}),
|
|
289
|
+
...(media.length > 0 ? { media } : {}),
|
|
290
|
+
...(accountId ? { accountId } : {}),
|
|
291
|
+
...(sessionKey ? { sessionKey } : {}),
|
|
292
|
+
...(route ? { route } : {}),
|
|
293
|
+
...(type ? { type } : {}),
|
|
294
|
+
...(mediaUrl ? { mediaUrl } : {}),
|
|
295
|
+
...(createdAt > 0 ? { createdAt } : {}),
|
|
296
|
+
...(status === 'pushed' || status === 'acked'
|
|
297
|
+
? { status: status as BncrOutboundReplayStatus }
|
|
298
|
+
: {}),
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
if (entries.length > 0) {
|
|
302
|
+
outboundReplayCache.set(key, entries);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function dumpPersistedHistoryBuckets(
|
|
308
|
+
source: Map<string, BncrPersistedGroupHistoryEntry[]>,
|
|
309
|
+
resolveLimit: (key: string) => number,
|
|
310
|
+
) {
|
|
311
|
+
return Array.from(source.entries()).map(([key, entries]) => ({
|
|
312
|
+
key,
|
|
313
|
+
entries: Number.isFinite(resolveLimit(key)) ? entries.slice(-resolveLimit(key)) : entries,
|
|
314
|
+
}));
|
|
315
|
+
}
|
|
316
|
+
|
|
207
317
|
function dumpPersistedGroupHistories() {
|
|
208
|
-
return
|
|
318
|
+
return dumpPersistedHistoryBuckets(runtime.groupHistories, (key) =>
|
|
319
|
+
resolvePersistedGroupHistoryLimit(key),
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function dumpPersistedOutboundReplayCache() {
|
|
324
|
+
return Array.from(outboundReplayCache.entries()).map(([key, entries]) => ({
|
|
209
325
|
key,
|
|
210
|
-
entries
|
|
326
|
+
entries,
|
|
211
327
|
}));
|
|
212
328
|
}
|
|
213
329
|
|
|
@@ -359,6 +475,7 @@ export function createBncrStateStore(runtime: {
|
|
|
359
475
|
loadPersistedSessionRoutes(data.sessionRoutes);
|
|
360
476
|
loadPersistedSceneRegistry(data.sceneRegistry);
|
|
361
477
|
loadPersistedGroupHistories(data.groupHistories);
|
|
478
|
+
loadPersistedOutboundReplayCache(data.outboundReplayCache ?? data.messageCache);
|
|
362
479
|
loadPersistedLastSessionMap(data.lastSessionByAccount);
|
|
363
480
|
loadPersistedAccountTimestampMap(runtime.lastActivityByAccount, data.lastActivityByAccount);
|
|
364
481
|
loadPersistedAccountTimestampMap(runtime.lastInboundByAccount, data.lastInboundByAccount);
|
|
@@ -378,6 +495,7 @@ export function createBncrStateStore(runtime: {
|
|
|
378
495
|
sessionRoutes: dumpPersistedSessionRoutes(),
|
|
379
496
|
sceneRegistry: dumpPersistedSceneRegistry(),
|
|
380
497
|
groupHistories: dumpPersistedGroupHistories(),
|
|
498
|
+
outboundReplayCache: dumpPersistedOutboundReplayCache(),
|
|
381
499
|
lastSessionByAccount: dumpPersistedLastSessionMap(),
|
|
382
500
|
lastActivityByAccount: dumpPersistedAccountTimestampMap(runtime.lastActivityByAccount),
|
|
383
501
|
lastInboundByAccount: dumpPersistedAccountTimestampMap(runtime.lastInboundByAccount),
|
|
@@ -399,6 +517,8 @@ export function createBncrStateStore(runtime: {
|
|
|
399
517
|
dumpPersistedSceneRegistry,
|
|
400
518
|
loadPersistedGroupHistories,
|
|
401
519
|
dumpPersistedGroupHistories,
|
|
520
|
+
loadPersistedOutboundReplayCache,
|
|
521
|
+
dumpPersistedOutboundReplayCache,
|
|
402
522
|
backfillAccountActivityFromSessionRoutes,
|
|
403
523
|
loadState,
|
|
404
524
|
flushState,
|
|
@@ -6,7 +6,11 @@ import type {
|
|
|
6
6
|
FileSendTransferState,
|
|
7
7
|
OutboxEntry,
|
|
8
8
|
} from '../core/types.ts';
|
|
9
|
-
import type {
|
|
9
|
+
import type {
|
|
10
|
+
BncrPersistedGroupHistoryEntry,
|
|
11
|
+
BncrPersistedOutboundReplayEntry,
|
|
12
|
+
BncrSceneRecord,
|
|
13
|
+
} from './channel-runtime-types.ts';
|
|
10
14
|
import { createBncrStateStore } from './state-store.ts';
|
|
11
15
|
import { createBncrTransientStateRuntime } from './transient-state-runtime.ts';
|
|
12
16
|
|
|
@@ -34,6 +38,7 @@ export function createBncrStateTransientRuntimeGroup(runtime: {
|
|
|
34
38
|
maxAccountActivityEntries: number;
|
|
35
39
|
sceneRegistry: Map<string, BncrSceneRecord>;
|
|
36
40
|
groupHistories: Map<string, BncrPersistedGroupHistoryEntry[]>;
|
|
41
|
+
outboundReplayCache?: Map<string, BncrPersistedOutboundReplayEntry[]>;
|
|
37
42
|
outbox: Map<string, OutboxEntry>;
|
|
38
43
|
getDeadLetter: () => OutboxEntry[];
|
|
39
44
|
setDeadLetter: (entries: OutboxEntry[]) => void;
|
|
@@ -74,6 +79,8 @@ export function createBncrStateTransientRuntimeGroup(runtime: {
|
|
|
74
79
|
maxAccountActivityEntries: runtime.maxAccountActivityEntries,
|
|
75
80
|
sceneRegistry: runtime.sceneRegistry,
|
|
76
81
|
groupHistories: runtime.groupHistories,
|
|
82
|
+
outboundReplayCache:
|
|
83
|
+
runtime.outboundReplayCache ?? new Map<string, BncrPersistedOutboundReplayEntry[]>(),
|
|
77
84
|
outbox: runtime.outbox,
|
|
78
85
|
getDeadLetter: runtime.getDeadLetter,
|
|
79
86
|
setDeadLetter: runtime.setDeadLetter,
|