@xmoxmo/bncr 0.6.1 → 0.6.2
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/package.json +1 -1
- package/scripts/check-pack.mjs +1 -1
- package/scripts/selfcheck.mjs +1 -1
- package/src/channel.ts +16 -4
- package/src/messaging/inbound/context-facts.ts +88 -58
- package/src/messaging/inbound/{group-history.ts → conversation-history.ts} +92 -25
- package/src/messaging/inbound/dispatch.ts +73 -48
- package/src/messaging/inbound/native-command.ts +1 -1
- package/src/messaging/inbound/outbound-replay-cache.ts +82 -6
- package/src/messaging/inbound/scene-admin.ts +24 -9
- package/src/messaging/inbound/turn-context.ts +101 -37
- package/src/plugin/bridge-outbox-facade.ts +9 -0
- package/src/plugin/channel-components.ts +5 -5
- package/src/plugin/channel-runtime-builders-delivery.ts +2 -2
- package/src/plugin/channel-runtime-types.ts +9 -7
- package/src/plugin/inbound-acceptance.ts +9 -24
- package/src/plugin/inbound-handlers.ts +4 -4
- package/src/plugin/inbound-surface-handlers-group.ts +4 -2
- package/src/plugin/scene-registry.ts +4 -4
- package/src/plugin/state-store.ts +38 -34
- package/src/plugin/state-transient-runtime-group.ts +3 -3
package/package.json
CHANGED
package/scripts/check-pack.mjs
CHANGED
|
@@ -174,7 +174,7 @@ const requiredSourceFiles = [
|
|
|
174
174
|
'src/bootstrap/register-runtime-singleton.ts',
|
|
175
175
|
'src/core/value-sanitize.ts',
|
|
176
176
|
'src/messaging/inbound/contracts.ts',
|
|
177
|
-
'src/messaging/inbound/
|
|
177
|
+
'src/messaging/inbound/conversation-history.ts',
|
|
178
178
|
'src/messaging/inbound/native-command-runtime.ts',
|
|
179
179
|
'src/messaging/inbound/reply-dispatch-serial.ts',
|
|
180
180
|
'src/messaging/inbound/scene-admin.ts',
|
package/scripts/selfcheck.mjs
CHANGED
|
@@ -63,7 +63,7 @@ const requiredSourceFiles = [
|
|
|
63
63
|
'src/messaging/inbound/dispatch-prep.ts',
|
|
64
64
|
'src/messaging/inbound/dispatch.ts',
|
|
65
65
|
'src/messaging/inbound/gate.ts',
|
|
66
|
-
'src/messaging/inbound/
|
|
66
|
+
'src/messaging/inbound/conversation-history.ts',
|
|
67
67
|
'src/messaging/inbound/last-route.ts',
|
|
68
68
|
'src/messaging/inbound/media-url-download.ts',
|
|
69
69
|
'src/messaging/inbound/native-command-runtime.ts',
|
package/src/channel.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
OpenClawPluginApi,
|
|
5
5
|
OpenClawPluginServiceContext,
|
|
6
6
|
} from 'openclaw/plugin-sdk/core';
|
|
7
|
+
import { DEFAULT_GROUP_HISTORY_LIMIT as DEFAULT_HISTORY_LIMIT } from 'openclaw/plugin-sdk/reply-history';
|
|
7
8
|
import {
|
|
8
9
|
BNCR_DEFAULT_ACCOUNT_ID,
|
|
9
10
|
CHANNEL_ID,
|
|
@@ -43,7 +44,7 @@ import type {
|
|
|
43
44
|
FileSendTransferState,
|
|
44
45
|
OutboxEntry,
|
|
45
46
|
} from './core/types.ts';
|
|
46
|
-
import type {
|
|
47
|
+
import type { BncrConversationHistoryMap } from './messaging/inbound/conversation-history.ts';
|
|
47
48
|
import type { BncrOutboundReplayCache } from './messaging/inbound/outbound-replay-cache.ts';
|
|
48
49
|
import type { parseBncrInboundParams } from './messaging/inbound/parse.ts';
|
|
49
50
|
import { buildEnqueueFromReplyDebugInfo } from './messaging/outbound/diagnostics.ts';
|
|
@@ -293,7 +294,7 @@ class BncrBridgeRuntime {
|
|
|
293
294
|
getSceneRegistry(): Map<string, BncrSceneRecord> {
|
|
294
295
|
return this.sceneRegistry;
|
|
295
296
|
}
|
|
296
|
-
private
|
|
297
|
+
private conversationHistories: BncrConversationHistoryMap = new Map();
|
|
297
298
|
private outboundReplayCache: BncrOutboundReplayCache = new Map();
|
|
298
299
|
private routeAliases = new Map<
|
|
299
300
|
string,
|
|
@@ -1901,7 +1902,7 @@ class BncrBridgeRuntime {
|
|
|
1901
1902
|
maxSessionRouteEntries: MAX_SESSION_ROUTE_ENTRIES,
|
|
1902
1903
|
maxAccountActivityEntries: MAX_ACCOUNT_ACTIVITY_ENTRIES,
|
|
1903
1904
|
sceneRegistry: this.sceneRegistry,
|
|
1904
|
-
|
|
1905
|
+
conversationHistories: this.conversationHistories,
|
|
1905
1906
|
outboundReplayCache: this.outboundReplayCache,
|
|
1906
1907
|
outbox: this.outbox,
|
|
1907
1908
|
getDeadLetter: () => this.deadLetter,
|
|
@@ -2168,6 +2169,17 @@ class BncrBridgeRuntime {
|
|
|
2168
2169
|
deadLetterSinceStartByAccount: this.deadLetterSinceStartByAccount,
|
|
2169
2170
|
lastOutboundByAccount: this.lastOutboundByAccount,
|
|
2170
2171
|
outboundReplayCache: this.outboundReplayCache,
|
|
2172
|
+
conversationHistories: this.conversationHistories,
|
|
2173
|
+
resolveOutboundHistoryLimit: (entry) => {
|
|
2174
|
+
const platform = entry.route?.platform || '';
|
|
2175
|
+
const groupId = entry.route?.groupId || '0';
|
|
2176
|
+
const userId = entry.route?.userId || '0';
|
|
2177
|
+
const sceneKey = groupId !== '0' ? `${platform}:${groupId}` : `${platform}:${userId}`;
|
|
2178
|
+
const sceneLimit = this.sceneRegistry.get(sceneKey)?.historyLimit;
|
|
2179
|
+
return typeof sceneLimit === 'number' && Number.isFinite(sceneLimit) && sceneLimit >= 0
|
|
2180
|
+
? Math.floor(sceneLimit)
|
|
2181
|
+
: DEFAULT_HISTORY_LIMIT;
|
|
2182
|
+
},
|
|
2171
2183
|
resolveOutboundSender: (entry) => {
|
|
2172
2184
|
try {
|
|
2173
2185
|
const account = resolveAccount(getOpenClawRuntimeConfig(this.api), entry.accountId);
|
|
@@ -2893,7 +2905,7 @@ class BncrBridgeRuntime {
|
|
|
2893
2905
|
defaultAdminAgentId: (args) => this.defaultAdminAgentId(args),
|
|
2894
2906
|
defaultPublicAgentId: () => this.defaultPublicAgentId(),
|
|
2895
2907
|
sceneRegistry: this.sceneRegistry,
|
|
2896
|
-
|
|
2908
|
+
conversationHistories: this.conversationHistories,
|
|
2897
2909
|
outboundReplayCache: this.outboundReplayCache,
|
|
2898
2910
|
prepareInboundAcceptance: (args) => this.prepareInboundAcceptance(args),
|
|
2899
2911
|
logInboundSummary: (args) => this.logInboundSummary(args),
|
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
|
|
3
|
+
type BncrConversationContextMedia = {
|
|
4
|
+
type?: string;
|
|
5
|
+
path?: string;
|
|
6
|
+
contentType?: string;
|
|
7
|
+
kind?: string;
|
|
8
|
+
messageId?: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
type BncrConversationContextEntry = {
|
|
12
|
+
messageId?: string;
|
|
13
|
+
timestamp?: number;
|
|
14
|
+
role?: 'user' | 'assistant' | 'system';
|
|
15
|
+
sender?: string;
|
|
16
|
+
senderId?: string;
|
|
17
|
+
content?: string;
|
|
18
|
+
media?: BncrConversationContextMedia[];
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type BncrParticipantRecord = Record<
|
|
22
|
+
string,
|
|
23
|
+
{
|
|
24
|
+
name: string;
|
|
25
|
+
username?: string;
|
|
26
|
+
isBot?: boolean;
|
|
27
|
+
isAdmin?: boolean;
|
|
28
|
+
isOwner?: boolean;
|
|
29
|
+
isAuthorizedSender?: boolean;
|
|
30
|
+
role?: 'owner' | 'admin' | 'user';
|
|
31
|
+
displayName?: string;
|
|
32
|
+
}
|
|
33
|
+
>;
|
|
34
|
+
|
|
3
35
|
export type BncrStructuredContextFactsInput = {
|
|
4
36
|
channelId: string;
|
|
5
37
|
accountId: string;
|
|
@@ -57,18 +89,10 @@ export type BncrStructuredContextFactsInput = {
|
|
|
57
89
|
kind?: string;
|
|
58
90
|
messageId?: string;
|
|
59
91
|
}>;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
body?: string;
|
|
65
|
-
media?: Array<{
|
|
66
|
-
path?: string;
|
|
67
|
-
contentType?: string;
|
|
68
|
-
kind?: string;
|
|
69
|
-
messageId?: string;
|
|
70
|
-
}>;
|
|
71
|
-
}>;
|
|
92
|
+
conversationContext?: BncrConversationContextEntry[];
|
|
93
|
+
participants?: BncrParticipantRecord | null;
|
|
94
|
+
isGroupChat?: boolean;
|
|
95
|
+
groupSubject?: string;
|
|
72
96
|
};
|
|
73
97
|
|
|
74
98
|
function toBncrPromptMediaPath(id: string): string | undefined {
|
|
@@ -166,18 +190,24 @@ export function buildBncrStructuredContextFacts(input: BncrStructuredContextFact
|
|
|
166
190
|
kind: item.kind,
|
|
167
191
|
messageId: item.messageId,
|
|
168
192
|
})),
|
|
169
|
-
|
|
193
|
+
conversationContext: (input.conversationContext || []).map((entry) => ({
|
|
170
194
|
messageId: entry.messageId,
|
|
195
|
+
timestamp: entry.timestamp,
|
|
196
|
+
role: entry.role,
|
|
171
197
|
sender: entry.sender,
|
|
172
198
|
senderId: entry.senderId,
|
|
173
|
-
|
|
199
|
+
content: entry.content,
|
|
174
200
|
media: (entry.media || []).map((item) => ({
|
|
201
|
+
type: item.type || item.kind || 'document',
|
|
175
202
|
path: item.path,
|
|
176
203
|
contentType: item.contentType,
|
|
177
|
-
kind: item.kind,
|
|
178
204
|
messageId: item.messageId,
|
|
179
205
|
})),
|
|
180
206
|
})),
|
|
207
|
+
participants: input.participants ?? null,
|
|
208
|
+
isGroupChat: input.isGroupChat,
|
|
209
|
+
groupSubject: input.groupSubject,
|
|
210
|
+
accountId: input.accountId,
|
|
181
211
|
};
|
|
182
212
|
}
|
|
183
213
|
|
|
@@ -210,18 +240,11 @@ export function buildBncrPromptVisibleContextFacts(
|
|
|
210
240
|
kind?: string;
|
|
211
241
|
messageId?: string;
|
|
212
242
|
}>;
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
media?: Array<{
|
|
219
|
-
path?: string;
|
|
220
|
-
contentType?: string;
|
|
221
|
-
kind?: string;
|
|
222
|
-
messageId?: string;
|
|
223
|
-
}>;
|
|
224
|
-
}>;
|
|
243
|
+
conversation_context?: Array<Record<string, unknown>>;
|
|
244
|
+
participants?: BncrParticipantRecord;
|
|
245
|
+
is_group_chat?: boolean;
|
|
246
|
+
group_subject?: string;
|
|
247
|
+
account_id?: string;
|
|
225
248
|
} = {};
|
|
226
249
|
|
|
227
250
|
if (facts.platform) {
|
|
@@ -272,27 +295,39 @@ export function buildBncrPromptVisibleContextFacts(
|
|
|
272
295
|
return Object.values(payload).some((field) => field !== undefined) ? [payload] : [];
|
|
273
296
|
});
|
|
274
297
|
}
|
|
275
|
-
if (facts.
|
|
276
|
-
result.
|
|
277
|
-
const
|
|
298
|
+
if (facts.conversationContext?.length) {
|
|
299
|
+
result.conversation_context = facts.conversationContext.flatMap((entry) => {
|
|
300
|
+
const items: Array<Record<string, unknown>> = [];
|
|
301
|
+
if (entry.content) {
|
|
302
|
+
items.push({
|
|
303
|
+
messageId: entry.messageId,
|
|
304
|
+
timestamp: entry.timestamp,
|
|
305
|
+
role: entry.role,
|
|
306
|
+
sender: entry.sender,
|
|
307
|
+
senderId: entry.senderId,
|
|
308
|
+
content: entry.content,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
for (const item of entry.media || []) {
|
|
278
312
|
const promptMediaPath = resolveBncrPromptMediaPath(item.path);
|
|
279
313
|
const payload = {
|
|
314
|
+
messageId: item.messageId ?? entry.messageId,
|
|
315
|
+
timestamp: entry.timestamp,
|
|
316
|
+
role: entry.role,
|
|
317
|
+
sender: entry.sender,
|
|
318
|
+
senderId: entry.senderId,
|
|
319
|
+
media_type: item.type || 'document',
|
|
280
320
|
...(promptMediaPath ? { path: promptMediaPath } : {}),
|
|
281
|
-
contentType: item.contentType,
|
|
282
|
-
kind: item.kind,
|
|
283
|
-
messageId: item.messageId,
|
|
321
|
+
...(item.contentType ? { contentType: item.contentType } : {}),
|
|
284
322
|
};
|
|
285
|
-
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
messageId: entry.messageId,
|
|
289
|
-
sender: entry.sender,
|
|
290
|
-
senderId: entry.senderId,
|
|
291
|
-
body: entry.body,
|
|
292
|
-
...(media.length > 0 ? { media } : {}),
|
|
293
|
-
};
|
|
294
|
-
return Object.values(payload).some((field) => field !== undefined) ? [payload] : [];
|
|
323
|
+
if (Object.values(payload).some((field) => field !== undefined)) items.push(payload);
|
|
324
|
+
}
|
|
325
|
+
return items;
|
|
295
326
|
});
|
|
327
|
+
if (facts.participants) result.participants = facts.participants;
|
|
328
|
+
if (facts.isGroupChat !== undefined) result.is_group_chat = facts.isGroupChat;
|
|
329
|
+
if (facts.groupSubject) result.group_subject = facts.groupSubject;
|
|
330
|
+
if (facts.accountId) result.account_id = facts.accountId;
|
|
296
331
|
}
|
|
297
332
|
|
|
298
333
|
return result;
|
|
@@ -358,25 +393,17 @@ export type BncrStructuredContextFactsFromInboundPartsInput = {
|
|
|
358
393
|
bridgeSenderName?: string;
|
|
359
394
|
senderIsOwner?: boolean;
|
|
360
395
|
senderIsAuthorized?: boolean;
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
body?: string;
|
|
366
|
-
media?: Array<{
|
|
367
|
-
path?: string;
|
|
368
|
-
contentType?: string;
|
|
369
|
-
kind?: string;
|
|
370
|
-
messageId?: string;
|
|
371
|
-
}>;
|
|
372
|
-
}>;
|
|
396
|
+
conversationHistoryContext?: BncrConversationContextEntry[];
|
|
397
|
+
participants?: BncrParticipantRecord | null;
|
|
398
|
+
isGroupChat?: boolean;
|
|
399
|
+
groupSubject?: string;
|
|
373
400
|
};
|
|
374
401
|
export function buildBncrStructuredContextFactsFromInboundParts(
|
|
375
402
|
input: BncrStructuredContextFactsFromInboundPartsInput,
|
|
376
403
|
) {
|
|
377
404
|
const mediaItems = Array.isArray(input.prepared.mediaItems) ? input.prepared.mediaItems : [];
|
|
378
|
-
const
|
|
379
|
-
? input.
|
|
405
|
+
const conversationHistoryContext = Array.isArray(input.conversationHistoryContext)
|
|
406
|
+
? input.conversationHistoryContext
|
|
380
407
|
: [];
|
|
381
408
|
return buildBncrStructuredContextFacts({
|
|
382
409
|
channelId: input.channelId,
|
|
@@ -435,6 +462,9 @@ export function buildBncrStructuredContextFactsFromInboundParts(
|
|
|
435
462
|
kind: item.kind || inferBncrStructuredMediaKind(item.contentType),
|
|
436
463
|
messageId: input.parsed.msgId,
|
|
437
464
|
})),
|
|
438
|
-
|
|
465
|
+
conversationContext: conversationHistoryContext,
|
|
466
|
+
participants: input.participants ?? null,
|
|
467
|
+
isGroupChat: input.isGroupChat,
|
|
468
|
+
groupSubject: input.groupSubject,
|
|
439
469
|
});
|
|
440
470
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createChannelHistoryWindow,
|
|
3
|
-
DEFAULT_GROUP_HISTORY_LIMIT,
|
|
3
|
+
DEFAULT_GROUP_HISTORY_LIMIT as DEFAULT_HISTORY_LIMIT,
|
|
4
4
|
type HistoryEntry,
|
|
5
5
|
type HistoryMediaEntry,
|
|
6
6
|
} from 'openclaw/plugin-sdk/reply-history';
|
|
@@ -10,9 +10,10 @@ import type { ParsedInbound } from './dispatch-prep.ts';
|
|
|
10
10
|
|
|
11
11
|
export type BncrHistoryEntry = HistoryEntry & {
|
|
12
12
|
senderId?: string;
|
|
13
|
+
role?: 'user' | 'assistant' | 'system';
|
|
13
14
|
};
|
|
14
15
|
|
|
15
|
-
export type
|
|
16
|
+
export type BncrConversationHistoryMap = Map<string, BncrHistoryEntry[]>;
|
|
16
17
|
|
|
17
18
|
type BncrHistoryMediaKind = NonNullable<HistoryMediaEntry['kind']>;
|
|
18
19
|
|
|
@@ -22,16 +23,35 @@ function normalizeTextBody(value: string): string {
|
|
|
22
23
|
.trim();
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
export function
|
|
26
|
-
if (parsed.peer.kind !== 'group') return null;
|
|
26
|
+
export function buildBncrConversationHistoryKey(parsed: ParsedInbound): string | null {
|
|
27
27
|
const platform = String(parsed.platform || '').trim();
|
|
28
|
-
|
|
29
|
-
if (
|
|
30
|
-
|
|
28
|
+
if (!platform) return null;
|
|
29
|
+
if (parsed.peer.kind === 'group') {
|
|
30
|
+
const groupId = String(parsed.groupId || '').trim();
|
|
31
|
+
if (!groupId || groupId === '0') return null;
|
|
32
|
+
return `${platform}:${groupId}`;
|
|
33
|
+
}
|
|
34
|
+
const userId = String(parsed.userId || '').trim();
|
|
35
|
+
if (!userId || userId === '0') return null;
|
|
36
|
+
return `${platform}:${userId}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function buildBncrConversationHistoryKeyFromRoute(args: {
|
|
40
|
+
platform?: string;
|
|
41
|
+
groupId?: string;
|
|
42
|
+
userId?: string;
|
|
43
|
+
}): string | null {
|
|
44
|
+
const platform = String(args.platform || '').trim();
|
|
45
|
+
if (!platform) return null;
|
|
46
|
+
const groupId = String(args.groupId || '0').trim() || '0';
|
|
47
|
+
const userId = String(args.userId || '0').trim() || '0';
|
|
48
|
+
if (groupId !== '0') return `${platform}:${groupId}`;
|
|
49
|
+
if (userId !== '0') return `${platform}:${userId}`;
|
|
50
|
+
return null;
|
|
31
51
|
}
|
|
32
52
|
|
|
33
|
-
export function
|
|
34
|
-
historyMap:
|
|
53
|
+
export function recordBncrPendingConversationText(args: {
|
|
54
|
+
historyMap: BncrConversationHistoryMap;
|
|
35
55
|
parsed: ParsedInbound;
|
|
36
56
|
senderDisplayName: string;
|
|
37
57
|
senderId: string;
|
|
@@ -43,8 +63,8 @@ export function recordBncrPendingGroupText(args: {
|
|
|
43
63
|
Number.isFinite(args.historyLimit) &&
|
|
44
64
|
args.historyLimit >= 0
|
|
45
65
|
? Math.floor(args.historyLimit)
|
|
46
|
-
:
|
|
47
|
-
const historyKey =
|
|
66
|
+
: DEFAULT_HISTORY_LIMIT;
|
|
67
|
+
const historyKey = buildBncrConversationHistoryKey(args.parsed);
|
|
48
68
|
const body = normalizeTextBody(args.bodyText);
|
|
49
69
|
if (!historyKey || !body || args.parsed.msgType !== 'text') return;
|
|
50
70
|
const msgId = String(args.parsed.msgId || '').trim();
|
|
@@ -61,6 +81,7 @@ export function recordBncrPendingGroupText(args: {
|
|
|
61
81
|
body,
|
|
62
82
|
timestamp: Date.now(),
|
|
63
83
|
messageId: args.parsed.msgId,
|
|
84
|
+
role: 'user',
|
|
64
85
|
},
|
|
65
86
|
});
|
|
66
87
|
}
|
|
@@ -88,8 +109,8 @@ function buildBncrHistoryMediaBody(kind: BncrHistoryMediaKind): string {
|
|
|
88
109
|
return `<media:${kind}>`;
|
|
89
110
|
}
|
|
90
111
|
|
|
91
|
-
export async function
|
|
92
|
-
historyMap:
|
|
112
|
+
export async function recordBncrPendingConversationMedia(args: {
|
|
113
|
+
historyMap: BncrConversationHistoryMap;
|
|
93
114
|
parsed: ParsedInbound;
|
|
94
115
|
senderDisplayName: string;
|
|
95
116
|
senderId: string;
|
|
@@ -107,8 +128,8 @@ export async function recordBncrPendingGroupMedia(args: {
|
|
|
107
128
|
Number.isFinite(args.historyLimit) &&
|
|
108
129
|
args.historyLimit >= 0
|
|
109
130
|
? Math.floor(args.historyLimit)
|
|
110
|
-
:
|
|
111
|
-
const historyKey =
|
|
131
|
+
: DEFAULT_HISTORY_LIMIT;
|
|
132
|
+
const historyKey = buildBncrConversationHistoryKey(args.parsed);
|
|
112
133
|
if (!historyKey || args.parsed.msgType === 'text') return;
|
|
113
134
|
const normalizedMediaItems = Array.isArray(args.mediaItems) ? args.mediaItems : [];
|
|
114
135
|
const itemKinds = normalizedMediaItems
|
|
@@ -142,6 +163,7 @@ export async function recordBncrPendingGroupMedia(args: {
|
|
|
142
163
|
body,
|
|
143
164
|
timestamp: Date.now(),
|
|
144
165
|
messageId: args.parsed.msgId,
|
|
166
|
+
role: 'user',
|
|
145
167
|
},
|
|
146
168
|
});
|
|
147
169
|
return;
|
|
@@ -160,6 +182,7 @@ export async function recordBncrPendingGroupMedia(args: {
|
|
|
160
182
|
body,
|
|
161
183
|
timestamp: Date.now(),
|
|
162
184
|
messageId: args.parsed.msgId,
|
|
185
|
+
role: 'user',
|
|
163
186
|
media: normalizedMediaItems.map(
|
|
164
187
|
(item) =>
|
|
165
188
|
({
|
|
@@ -173,12 +196,52 @@ export async function recordBncrPendingGroupMedia(args: {
|
|
|
173
196
|
});
|
|
174
197
|
}
|
|
175
198
|
|
|
199
|
+
export function recordBncrBotReply(args: {
|
|
200
|
+
historyMap: BncrConversationHistoryMap;
|
|
201
|
+
historyKey: string;
|
|
202
|
+
sender: string;
|
|
203
|
+
senderId?: string;
|
|
204
|
+
body: string;
|
|
205
|
+
timestamp?: number;
|
|
206
|
+
messageId?: string;
|
|
207
|
+
media?: HistoryMediaEntry[];
|
|
208
|
+
historyLimit?: number;
|
|
209
|
+
}) {
|
|
210
|
+
const limit =
|
|
211
|
+
typeof args.historyLimit === 'number' &&
|
|
212
|
+
Number.isFinite(args.historyLimit) &&
|
|
213
|
+
args.historyLimit >= 0
|
|
214
|
+
? Math.floor(args.historyLimit)
|
|
215
|
+
: DEFAULT_HISTORY_LIMIT;
|
|
216
|
+
const body = normalizeTextBody(args.body);
|
|
217
|
+
if (!body) return;
|
|
218
|
+
const msgId = String(args.messageId || '').trim();
|
|
219
|
+
if (msgId) {
|
|
220
|
+
const existing = args.historyMap.get(args.historyKey) || [];
|
|
221
|
+
if (existing.some((e) => e.messageId === msgId)) return;
|
|
222
|
+
}
|
|
223
|
+
createChannelHistoryWindow({ historyMap: args.historyMap }).record({
|
|
224
|
+
historyKey: args.historyKey,
|
|
225
|
+
limit,
|
|
226
|
+
entry: {
|
|
227
|
+
sender: args.sender,
|
|
228
|
+
...(args.senderId ? { senderId: args.senderId } : {}),
|
|
229
|
+
body,
|
|
230
|
+
timestamp: args.timestamp ?? Date.now(),
|
|
231
|
+
...(args.messageId ? { messageId: args.messageId } : {}),
|
|
232
|
+
role: 'assistant',
|
|
233
|
+
...(Array.isArray(args.media) && args.media.length > 0 ? { media: args.media } : {}),
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
176
238
|
function cloneBncrHistoryEntry(entry: BncrHistoryEntry): BncrHistoryEntry {
|
|
177
239
|
return {
|
|
178
240
|
sender: entry.sender,
|
|
179
241
|
...(entry.messageId ? { messageId: entry.messageId } : {}),
|
|
180
242
|
...(typeof entry.timestamp === 'number' ? { timestamp: entry.timestamp } : {}),
|
|
181
243
|
...(entry.senderId ? { senderId: entry.senderId } : {}),
|
|
244
|
+
...(entry.role ? { role: entry.role } : {}),
|
|
182
245
|
body: entry.body,
|
|
183
246
|
...(Array.isArray(entry.media)
|
|
184
247
|
? {
|
|
@@ -193,8 +256,8 @@ function cloneBncrHistoryEntry(entry: BncrHistoryEntry): BncrHistoryEntry {
|
|
|
193
256
|
};
|
|
194
257
|
}
|
|
195
258
|
|
|
196
|
-
export function
|
|
197
|
-
historyMap:
|
|
259
|
+
export function readBncrPendingConversationHistorySnapshot(args: {
|
|
260
|
+
historyMap: BncrConversationHistoryMap;
|
|
198
261
|
parsed: ParsedInbound;
|
|
199
262
|
historyLimit?: number;
|
|
200
263
|
}): BncrHistoryEntry[] {
|
|
@@ -203,15 +266,19 @@ export function readBncrPendingGroupHistorySnapshot(args: {
|
|
|
203
266
|
Number.isFinite(args.historyLimit) &&
|
|
204
267
|
args.historyLimit >= 0
|
|
205
268
|
? Math.floor(args.historyLimit)
|
|
206
|
-
:
|
|
207
|
-
const historyKey =
|
|
269
|
+
: DEFAULT_HISTORY_LIMIT;
|
|
270
|
+
const historyKey = buildBncrConversationHistoryKey(args.parsed);
|
|
208
271
|
if (!historyKey) return [];
|
|
209
272
|
const entries = args.historyMap.get(historyKey) || [];
|
|
210
273
|
if (entries.length === 0) return [];
|
|
211
|
-
return entries
|
|
274
|
+
return entries
|
|
275
|
+
.slice()
|
|
276
|
+
.sort((a, b) => (a.timestamp ?? 0) - (b.timestamp ?? 0))
|
|
277
|
+
.slice(-limit)
|
|
278
|
+
.map(cloneBncrHistoryEntry);
|
|
212
279
|
}
|
|
213
280
|
|
|
214
|
-
export function
|
|
281
|
+
export function buildBncrConversationContextFromEntries(args: {
|
|
215
282
|
api: BncrInboundApi;
|
|
216
283
|
entries: readonly BncrHistoryEntry[];
|
|
217
284
|
channelLabel: string;
|
|
@@ -244,8 +311,8 @@ export function collectBncrHistoryMediaFromEntries(args: {
|
|
|
244
311
|
}): HistoryMediaEntry[] {
|
|
245
312
|
return args.entries.flatMap((entry) => (Array.isArray(entry.media) ? entry.media : []));
|
|
246
313
|
}
|
|
247
|
-
export function
|
|
248
|
-
historyMap:
|
|
314
|
+
export function clearBncrPendingConversationHistory(args: {
|
|
315
|
+
historyMap: BncrConversationHistoryMap;
|
|
249
316
|
parsed: ParsedInbound;
|
|
250
317
|
historyLimit?: number;
|
|
251
318
|
}) {
|
|
@@ -254,8 +321,8 @@ export function clearBncrPendingGroupHistory(args: {
|
|
|
254
321
|
Number.isFinite(args.historyLimit) &&
|
|
255
322
|
args.historyLimit >= 0
|
|
256
323
|
? Math.floor(args.historyLimit)
|
|
257
|
-
:
|
|
258
|
-
const historyKey =
|
|
324
|
+
: DEFAULT_HISTORY_LIMIT;
|
|
325
|
+
const historyKey = buildBncrConversationHistoryKey(args.parsed);
|
|
259
326
|
if (!historyKey) return;
|
|
260
327
|
createChannelHistoryWindow({ historyMap: args.historyMap }).clear({
|
|
261
328
|
historyKey,
|