@xmoxmo/bncr 0.6.0 → 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/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 +4 -1
- package/src/bootstrap/register-runtime-gateway.ts +1 -0
- package/src/bootstrap/register-runtime.ts +1 -0
- package/src/channel.ts +66 -5
- package/src/core/types.ts +41 -0
- 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 +90 -47
- package/src/messaging/inbound/native-command.ts +1 -1
- package/src/messaging/inbound/outbound-replay-cache.ts +312 -0
- package/src/messaging/inbound/scene-admin.ts +24 -9
- package/src/messaging/inbound/turn-context.ts +105 -33
- 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 +88 -1
- package/src/plugin/channel-components.ts +10 -5
- 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 +4 -2
- package/src/plugin/channel-runtime-types.ts +34 -8
- 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-acceptance.ts +9 -24
- package/src/plugin/inbound-handlers.ts +8 -4
- package/src/plugin/inbound-surface-handlers-group.ts +7 -2
- package/src/plugin/outbox-ack-outcome.ts +2 -0
- package/src/plugin/scene-registry.ts +4 -4
- package/src/plugin/state-store.ts +153 -29
- package/src/plugin/state-transient-runtime-group.ts +10 -3
|
@@ -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,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DEFAULT_GROUP_HISTORY_LIMIT } from 'openclaw/plugin-sdk/reply-history';
|
|
1
|
+
import { DEFAULT_GROUP_HISTORY_LIMIT as DEFAULT_HISTORY_LIMIT } from 'openclaw/plugin-sdk/reply-history';
|
|
2
2
|
import { emitBncrLogLine } from '../../core/logging.ts';
|
|
3
3
|
import type { BncrSceneRecord } from '../../plugin/channel-runtime-types.ts';
|
|
4
4
|
import { buildSceneKey } from '../../plugin/scene-registry.ts';
|
|
@@ -10,6 +10,15 @@ import type {
|
|
|
10
10
|
BncrInboundLogger,
|
|
11
11
|
BncrRememberSessionRoute,
|
|
12
12
|
} from './contracts.ts';
|
|
13
|
+
import {
|
|
14
|
+
type BncrConversationHistoryMap,
|
|
15
|
+
type BncrHistoryEntry,
|
|
16
|
+
buildBncrConversationHistoryKey,
|
|
17
|
+
clearBncrPendingConversationHistory,
|
|
18
|
+
readBncrPendingConversationHistorySnapshot,
|
|
19
|
+
recordBncrPendingConversationMedia,
|
|
20
|
+
recordBncrPendingConversationText,
|
|
21
|
+
} from './conversation-history.ts';
|
|
13
22
|
import {
|
|
14
23
|
assertInboundMediaBase64Size,
|
|
15
24
|
decodeInboundMediaBase64,
|
|
@@ -19,14 +28,10 @@ import {
|
|
|
19
28
|
resolveBncrInboundConversation,
|
|
20
29
|
} from './dispatch-prep.ts';
|
|
21
30
|
import {
|
|
22
|
-
type
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
readBncrPendingGroupHistorySnapshot,
|
|
27
|
-
recordBncrPendingGroupMedia,
|
|
28
|
-
recordBncrPendingGroupText,
|
|
29
|
-
} from './group-history.ts';
|
|
31
|
+
type BncrOutboundReplayCache,
|
|
32
|
+
clearBncrOutboundReplay,
|
|
33
|
+
readBncrOutboundReplaySnapshot,
|
|
34
|
+
} from './outbound-replay-cache.ts';
|
|
30
35
|
import { runBncrInboundReplyDispatch } from './reply-dispatch.ts';
|
|
31
36
|
import { buildBncrInboundTurnContext } from './turn-context.ts';
|
|
32
37
|
|
|
@@ -47,7 +52,8 @@ export async function dispatchBncrInbound(params: {
|
|
|
47
52
|
shouldAccumulate?: boolean;
|
|
48
53
|
resolvedAgentId?: string;
|
|
49
54
|
sceneRegistry: Map<string, BncrSceneRecord>;
|
|
50
|
-
|
|
55
|
+
conversationHistories: BncrConversationHistoryMap;
|
|
56
|
+
outboundReplayCache?: BncrOutboundReplayCache;
|
|
51
57
|
defaultAdminAgentId: string;
|
|
52
58
|
defaultPublicAgentId: string;
|
|
53
59
|
now: () => number;
|
|
@@ -67,7 +73,8 @@ export async function dispatchBncrInbound(params: {
|
|
|
67
73
|
shouldAccumulate = initialShouldDispatch,
|
|
68
74
|
resolvedAgentId,
|
|
69
75
|
sceneRegistry = new Map(),
|
|
70
|
-
|
|
76
|
+
conversationHistories = new Map(),
|
|
77
|
+
outboundReplayCache = new Map(),
|
|
71
78
|
defaultAdminAgentId,
|
|
72
79
|
defaultPublicAgentId,
|
|
73
80
|
now,
|
|
@@ -137,31 +144,29 @@ export async function dispatchBncrInbound(params: {
|
|
|
137
144
|
);
|
|
138
145
|
}
|
|
139
146
|
const sceneHistoryLimit =
|
|
140
|
-
parsed
|
|
141
|
-
? (sceneRegistry.get(buildSceneKey(parsed))?.historyLimit ?? DEFAULT_GROUP_HISTORY_LIMIT)
|
|
142
|
-
: DEFAULT_GROUP_HISTORY_LIMIT;
|
|
147
|
+
sceneRegistry.get(buildSceneKey(parsed))?.historyLimit ?? DEFAULT_HISTORY_LIMIT;
|
|
143
148
|
const resolvedHistoryLimit =
|
|
144
149
|
typeof sceneHistoryLimit === 'number' &&
|
|
145
150
|
Number.isFinite(sceneHistoryLimit) &&
|
|
146
151
|
sceneHistoryLimit >= 0
|
|
147
152
|
? Math.floor(sceneHistoryLimit)
|
|
148
|
-
:
|
|
153
|
+
: DEFAULT_HISTORY_LIMIT;
|
|
149
154
|
|
|
150
155
|
let silentHistoryFlush = false;
|
|
151
156
|
let pendingHistoryEntries: BncrHistoryEntry[] = [];
|
|
152
|
-
if (
|
|
153
|
-
const historyKey =
|
|
157
|
+
if (shouldAccumulate) {
|
|
158
|
+
const historyKey = buildBncrConversationHistoryKey(parsed);
|
|
154
159
|
// Record the message first so overflow context includes it
|
|
155
|
-
|
|
156
|
-
historyMap:
|
|
160
|
+
recordBncrPendingConversationText({
|
|
161
|
+
historyMap: conversationHistories,
|
|
157
162
|
parsed,
|
|
158
163
|
senderDisplayName,
|
|
159
164
|
senderId: senderIdForContext,
|
|
160
165
|
bodyText: rawBody,
|
|
161
166
|
historyLimit: resolvedHistoryLimit,
|
|
162
167
|
});
|
|
163
|
-
await
|
|
164
|
-
historyMap:
|
|
168
|
+
await recordBncrPendingConversationMedia({
|
|
169
|
+
historyMap: conversationHistories,
|
|
165
170
|
parsed,
|
|
166
171
|
senderDisplayName,
|
|
167
172
|
senderId: senderIdForContext,
|
|
@@ -170,42 +175,86 @@ export async function dispatchBncrInbound(params: {
|
|
|
170
175
|
mediaContentType: mediaContentType || mimeType,
|
|
171
176
|
historyLimit: resolvedHistoryLimit,
|
|
172
177
|
});
|
|
173
|
-
if (historyKey
|
|
174
|
-
const entries =
|
|
178
|
+
if (historyKey) {
|
|
179
|
+
const entries = conversationHistories.get(historyKey);
|
|
175
180
|
if (entries && resolvedHistoryLimit > 0 && entries.length >= resolvedHistoryLimit) {
|
|
176
181
|
const sceneForHistory = sceneRegistry.get(buildSceneKey(parsed));
|
|
177
182
|
const historyForce = sceneForHistory?.historyForce !== false;
|
|
178
183
|
if (historyForce) {
|
|
179
|
-
pendingHistoryEntries = readBncrPendingGroupHistorySnapshot({
|
|
180
|
-
historyMap: groupHistories,
|
|
181
|
-
parsed,
|
|
182
|
-
historyLimit: resolvedHistoryLimit,
|
|
183
|
-
});
|
|
184
|
-
clearBncrPendingGroupHistory({
|
|
185
|
-
historyMap: groupHistories,
|
|
186
|
-
parsed,
|
|
187
|
-
historyLimit: resolvedHistoryLimit,
|
|
188
|
-
});
|
|
189
|
-
shouldDispatch = true;
|
|
190
184
|
silentHistoryFlush = true;
|
|
185
|
+
shouldDispatch = true;
|
|
191
186
|
}
|
|
192
187
|
}
|
|
193
188
|
}
|
|
194
189
|
}
|
|
195
190
|
|
|
196
|
-
if (shouldDispatch
|
|
197
|
-
pendingHistoryEntries =
|
|
198
|
-
historyMap:
|
|
191
|
+
if (shouldDispatch) {
|
|
192
|
+
pendingHistoryEntries = readBncrPendingConversationHistorySnapshot({
|
|
193
|
+
historyMap: conversationHistories,
|
|
199
194
|
parsed,
|
|
200
195
|
historyLimit: resolvedHistoryLimit,
|
|
201
196
|
});
|
|
202
197
|
if (pendingHistoryEntries.length > 0) {
|
|
203
|
-
|
|
204
|
-
historyMap:
|
|
198
|
+
clearBncrPendingConversationHistory({
|
|
199
|
+
historyMap: conversationHistories,
|
|
205
200
|
parsed,
|
|
206
201
|
historyLimit: resolvedHistoryLimit,
|
|
207
202
|
});
|
|
208
203
|
}
|
|
204
|
+
const legacyOutboundEntries = readBncrOutboundReplaySnapshot({
|
|
205
|
+
cache: outboundReplayCache,
|
|
206
|
+
conversationHistories,
|
|
207
|
+
parsed,
|
|
208
|
+
accountId,
|
|
209
|
+
excludeMessageId: msgId,
|
|
210
|
+
});
|
|
211
|
+
clearBncrOutboundReplay({
|
|
212
|
+
cache: outboundReplayCache,
|
|
213
|
+
parsed,
|
|
214
|
+
accountId,
|
|
215
|
+
});
|
|
216
|
+
const knownMessageIds = new Set(
|
|
217
|
+
pendingHistoryEntries
|
|
218
|
+
.map((entry) => entry.messageId)
|
|
219
|
+
.filter((messageId): messageId is string => Boolean(messageId)),
|
|
220
|
+
);
|
|
221
|
+
for (const entry of legacyOutboundEntries) {
|
|
222
|
+
if (entry.messageId && knownMessageIds.has(entry.messageId)) continue;
|
|
223
|
+
if (entry.messageId) knownMessageIds.add(entry.messageId);
|
|
224
|
+
pendingHistoryEntries.push({
|
|
225
|
+
sender: entry.sender,
|
|
226
|
+
...(entry.senderId ? { senderId: entry.senderId } : {}),
|
|
227
|
+
body: entry.body,
|
|
228
|
+
...(typeof entry.timestamp === 'number' ? { timestamp: entry.timestamp } : {}),
|
|
229
|
+
...(entry.messageId ? { messageId: entry.messageId } : {}),
|
|
230
|
+
role: 'assistant',
|
|
231
|
+
...(Array.isArray(entry.media)
|
|
232
|
+
? {
|
|
233
|
+
media: entry.media.map((item) => ({
|
|
234
|
+
path: item.path,
|
|
235
|
+
contentType: item.contentType,
|
|
236
|
+
kind: item.kind,
|
|
237
|
+
messageId: item.messageId,
|
|
238
|
+
})),
|
|
239
|
+
}
|
|
240
|
+
: {}),
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
pendingHistoryEntries.sort((a, b) => (a.timestamp ?? 0) - (b.timestamp ?? 0));
|
|
244
|
+
if (resolvedHistoryLimit > 0 && pendingHistoryEntries.length > resolvedHistoryLimit) {
|
|
245
|
+
const currentMessageId = String(msgId || '').trim();
|
|
246
|
+
const currentEntry = currentMessageId
|
|
247
|
+
? pendingHistoryEntries.find((entry) => entry.messageId === currentMessageId)
|
|
248
|
+
: undefined;
|
|
249
|
+
pendingHistoryEntries = pendingHistoryEntries.slice(-resolvedHistoryLimit);
|
|
250
|
+
if (currentEntry && !pendingHistoryEntries.includes(currentEntry)) {
|
|
251
|
+
const withoutCurrent = pendingHistoryEntries.filter((entry) => entry !== currentEntry);
|
|
252
|
+
pendingHistoryEntries = [
|
|
253
|
+
...withoutCurrent.slice(-(resolvedHistoryLimit - 1)),
|
|
254
|
+
currentEntry,
|
|
255
|
+
];
|
|
256
|
+
}
|
|
257
|
+
}
|
|
209
258
|
}
|
|
210
259
|
|
|
211
260
|
const ctxPayload = await buildBncrInboundTurnContext({
|
|
@@ -223,7 +272,7 @@ export async function dispatchBncrInbound(params: {
|
|
|
223
272
|
historyLimit: resolvedHistoryLimit,
|
|
224
273
|
resolution,
|
|
225
274
|
prepared,
|
|
226
|
-
|
|
275
|
+
conversationHistories,
|
|
227
276
|
shouldDispatch,
|
|
228
277
|
silentHistoryFlush,
|
|
229
278
|
pendingHistoryEntries,
|
|
@@ -250,13 +299,7 @@ export async function dispatchBncrInbound(params: {
|
|
|
250
299
|
enqueueFromReply,
|
|
251
300
|
});
|
|
252
301
|
|
|
253
|
-
|
|
254
|
-
clearBncrPendingGroupHistory({
|
|
255
|
-
historyMap: groupHistories,
|
|
256
|
-
parsed,
|
|
257
|
-
historyLimit: resolvedHistoryLimit,
|
|
258
|
-
});
|
|
259
|
-
}
|
|
302
|
+
scheduleSave();
|
|
260
303
|
|
|
261
304
|
return {
|
|
262
305
|
accountId,
|
|
@@ -55,7 +55,7 @@ const BNCR_HELP_TEXT = [
|
|
|
55
55
|
' • /bncr list pending [filters...]',
|
|
56
56
|
' • /bncr list scenes [filters...]',
|
|
57
57
|
'',
|
|
58
|
-
'📋
|
|
58
|
+
'📋 Conversation history',
|
|
59
59
|
' • /bncr history-help',
|
|
60
60
|
' • /bncr history-limit [<number>|clear] [<SceneId>]',
|
|
61
61
|
' • /bncr history-force on|off|clear [<SceneId>]',
|