@xmoxmo/bncr 0.2.5 → 0.2.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 +9 -3
- package/index.ts +30 -15
- package/package.json +4 -3
- package/scripts/check-pack.mjs +61 -0
- package/scripts/selfcheck.mjs +10 -0
- package/src/channel.ts +892 -255
- package/src/core/connection-reachability.ts +41 -14
- package/src/core/diagnostics.ts +7 -2
- package/src/core/downlink-health.ts +7 -2
- package/src/core/outbox-entry-builders.ts +3 -2
- package/src/core/policy.ts +9 -0
- package/src/core/register-trace.ts +6 -1
- package/src/core/status.ts +7 -2
- package/src/core/targets.ts +10 -1
- package/src/core/types.ts +1 -0
- package/src/messaging/inbound/commands.ts +330 -77
- package/src/messaging/inbound/context-facts.ts +200 -0
- package/src/messaging/inbound/dispatch.ts +429 -119
- package/src/messaging/inbound/gate.ts +66 -26
- package/src/messaging/inbound/parse.ts +8 -0
- package/src/messaging/inbound/runtime-compat.ts +39 -0
- package/src/messaging/inbound/session-label.ts +115 -0
- package/src/messaging/outbound/diagnostics.ts +16 -0
- package/src/messaging/outbound/durable-message-adapter.ts +107 -0
- package/src/messaging/outbound/durable-queue-adapter.ts +157 -0
- package/src/messaging/outbound/media.ts +3 -1
- package/src/messaging/outbound/queue-selectors.ts +7 -2
- package/src/messaging/outbound/reasons.ts +4 -0
- package/src/messaging/outbound/reply-enqueue.ts +2 -2
- package/src/messaging/outbound/reply-target-policy.ts +13 -0
- package/src/messaging/outbound/retry-policy.ts +12 -3
- package/src/messaging/outbound/send.ts +6 -0
- package/src/messaging/outbound/session-route.ts +2 -2
- package/src/openclaw/config-runtime.ts +52 -0
- package/src/openclaw/inbound-session-runtime.ts +94 -0
- package/src/openclaw/ingress-runtime.ts +35 -0
- package/src/openclaw/media-runtime.ts +73 -0
- package/src/openclaw/reply-runtime.ts +104 -0
- package/src/openclaw/routing-runtime.ts +48 -0
- package/src/openclaw/sdk-helpers.ts +20 -0
- package/src/openclaw/session-route-runtime.ts +15 -0
|
@@ -1,29 +1,156 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
1
2
|
import { emitBncrLogLine } from '../../core/logging.ts';
|
|
3
|
+
import { resolveBncrChannelPolicy } from '../../core/policy.ts';
|
|
2
4
|
import {
|
|
3
5
|
formatDisplayScope,
|
|
4
6
|
normalizeInboundSessionKey,
|
|
5
7
|
withTaskSessionKey,
|
|
6
8
|
} from '../../core/targets.ts';
|
|
7
9
|
import { handleBncrNativeCommand } from './commands.ts';
|
|
10
|
+
import {
|
|
11
|
+
buildBncrPromptVisibleContextFacts,
|
|
12
|
+
buildBncrStructuredContextFactsFromInboundParts,
|
|
13
|
+
} from './context-facts.ts';
|
|
8
14
|
import { buildBncrReplyConfig } from './reply-config.ts';
|
|
15
|
+
import { resolveBncrChannelInboundRuntime } from './runtime-compat.ts';
|
|
16
|
+
import { wrapBncrInboundRecordSessionLabelCorrection } from './session-label.ts';
|
|
17
|
+
import { saveOpenClawChannelMediaBuffer } from '../../openclaw/media-runtime.ts';
|
|
18
|
+
import {
|
|
19
|
+
dispatchOpenClawReplyWithBufferedBlockDispatcher,
|
|
20
|
+
formatOpenClawAgentEnvelope,
|
|
21
|
+
resolveOpenClawEnvelopeFormatOptions,
|
|
22
|
+
} from '../../openclaw/reply-runtime.ts';
|
|
23
|
+
import {
|
|
24
|
+
resolveOpenClawAgentRoute,
|
|
25
|
+
resolveOpenClawInboundLastRouteSessionKey,
|
|
26
|
+
} from '../../openclaw/routing-runtime.ts';
|
|
27
|
+
import {
|
|
28
|
+
readBncrSessionUpdatedAt,
|
|
29
|
+
recordBncrInboundSession,
|
|
30
|
+
resolveBncrInboundSessionStorePath,
|
|
31
|
+
resolveBncrPinnedMainDmOwnerFromAllowlist,
|
|
32
|
+
} from '../../openclaw/inbound-session-runtime.ts';
|
|
9
33
|
|
|
10
34
|
type ParsedInbound = ReturnType<typeof import('./parse.ts')['parseBncrInboundParams']>;
|
|
11
35
|
|
|
12
|
-
|
|
36
|
+
type BncrInboundConversationResolution = {
|
|
37
|
+
accountId: string;
|
|
38
|
+
chatType: 'direct' | 'group';
|
|
39
|
+
route: ParsedInbound['route'];
|
|
40
|
+
resolvedRoute: {
|
|
41
|
+
sessionKey: string;
|
|
42
|
+
agentId: string;
|
|
43
|
+
mainSessionKey?: string;
|
|
44
|
+
};
|
|
45
|
+
canonicalTo: string;
|
|
46
|
+
rawTo: string;
|
|
47
|
+
originatingTo: string;
|
|
48
|
+
baseSessionKey: string;
|
|
49
|
+
taskSessionKey?: string;
|
|
50
|
+
dispatchSessionKey: string;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
type BncrInboundReplyRouteFact = {
|
|
54
|
+
accountId: string;
|
|
55
|
+
sessionKey: string;
|
|
56
|
+
route: ParsedInbound['route'];
|
|
57
|
+
canonicalTo: string;
|
|
58
|
+
originatingTo: string;
|
|
59
|
+
chatType: 'direct' | 'group';
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const INBOUND_MEDIA_MAX_BYTES = 30 * 1024 * 1024;
|
|
63
|
+
|
|
64
|
+
export function estimateBase64DecodedBytes(value: string): number {
|
|
65
|
+
const normalized = String(value || '').replace(/\s+/g, '');
|
|
66
|
+
if (!normalized) return 0;
|
|
67
|
+
const padding = normalized.endsWith('==') ? 2 : normalized.endsWith('=') ? 1 : 0;
|
|
68
|
+
return Math.max(0, Math.floor((normalized.length * 3) / 4) - padding);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function assertInboundMediaBase64Size(
|
|
72
|
+
value: string,
|
|
73
|
+
maxBytes = INBOUND_MEDIA_MAX_BYTES,
|
|
74
|
+
) {
|
|
75
|
+
const estimatedBytes = estimateBase64DecodedBytes(value);
|
|
76
|
+
if (estimatedBytes > maxBytes) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`inbound media too large: estimated ${estimatedBytes} bytes exceeds ${maxBytes} bytes`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function decodeInboundMediaBase64(
|
|
84
|
+
value: string,
|
|
85
|
+
maxBytes = INBOUND_MEDIA_MAX_BYTES,
|
|
86
|
+
): Buffer {
|
|
87
|
+
assertInboundMediaBase64Size(value, maxBytes);
|
|
88
|
+
const normalized = String(value || '').replace(/\s+/g, '');
|
|
89
|
+
const mediaBuf = Buffer.from(normalized, 'base64');
|
|
90
|
+
if (!mediaBuf.length) {
|
|
91
|
+
throw new Error('inbound media base64 decoded to empty buffer');
|
|
92
|
+
}
|
|
93
|
+
if (mediaBuf.length > maxBytes) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
`inbound media too large: decoded ${mediaBuf.length} bytes exceeds ${maxBytes} bytes`,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
return mediaBuf;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function formatRawBncrInboundTarget(route: ParsedInbound['route']): string {
|
|
102
|
+
return `Bncr:${String(route.platform || '').trim()}:${String(route.groupId || '').trim()}:${String(route.userId || '').trim()}`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function resolveBncrInboundConversation(args: {
|
|
13
106
|
api: any;
|
|
14
107
|
cfg: any;
|
|
15
108
|
channelId: string;
|
|
16
109
|
parsed: ParsedInbound;
|
|
17
110
|
canonicalAgentId: string;
|
|
18
|
-
rememberSessionRoute: (sessionKey: string, accountId: string, route: any) => void;
|
|
19
111
|
}) {
|
|
20
|
-
const { api, cfg, channelId, parsed, canonicalAgentId
|
|
21
|
-
const {
|
|
112
|
+
const { api, cfg, channelId, parsed, canonicalAgentId } = args;
|
|
113
|
+
const { accountId, route, peer, sessionKeyfromroute, providedOriginatingTo, extracted } = parsed;
|
|
114
|
+
|
|
115
|
+
const resolvedRoute = resolveOpenClawAgentRoute(api, {
|
|
116
|
+
cfg,
|
|
117
|
+
channel: channelId,
|
|
22
118
|
accountId,
|
|
23
|
-
route,
|
|
24
119
|
peer,
|
|
25
|
-
|
|
26
|
-
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const baseSessionKey =
|
|
123
|
+
normalizeInboundSessionKey(sessionKeyfromroute, route, canonicalAgentId) ||
|
|
124
|
+
resolvedRoute.sessionKey;
|
|
125
|
+
const taskSessionKey = withTaskSessionKey(baseSessionKey, extracted.taskKey);
|
|
126
|
+
const dispatchSessionKey = taskSessionKey || baseSessionKey;
|
|
127
|
+
const rawTo = formatRawBncrInboundTarget(route);
|
|
128
|
+
const canonicalTo = formatDisplayScope(route);
|
|
129
|
+
const originatingTo = providedOriginatingTo || rawTo;
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
accountId,
|
|
133
|
+
chatType: peer.kind,
|
|
134
|
+
route,
|
|
135
|
+
resolvedRoute,
|
|
136
|
+
canonicalTo,
|
|
137
|
+
rawTo,
|
|
138
|
+
originatingTo,
|
|
139
|
+
baseSessionKey,
|
|
140
|
+
...(taskSessionKey ? { taskSessionKey } : {}),
|
|
141
|
+
dispatchSessionKey,
|
|
142
|
+
} satisfies BncrInboundConversationResolution;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function prepareBncrInboundSessionContext(args: {
|
|
146
|
+
api: any;
|
|
147
|
+
cfg: any;
|
|
148
|
+
parsed: ParsedInbound;
|
|
149
|
+
resolution: BncrInboundConversationResolution;
|
|
150
|
+
rememberSessionRoute: (sessionKey: string, accountId: string, route: any) => void;
|
|
151
|
+
}) {
|
|
152
|
+
const { api, cfg, parsed, resolution, rememberSessionRoute } = args;
|
|
153
|
+
const {
|
|
27
154
|
msgType,
|
|
28
155
|
mediaBase64,
|
|
29
156
|
mediaPathFromTransfer,
|
|
@@ -34,34 +161,23 @@ async function prepareBncrInboundSessionContext(args: {
|
|
|
34
161
|
groupId,
|
|
35
162
|
userId,
|
|
36
163
|
} = parsed;
|
|
37
|
-
|
|
38
|
-
const resolvedRoute = api.runtime.channel.routing.resolveAgentRoute({
|
|
39
|
-
cfg,
|
|
40
|
-
channel: channelId,
|
|
41
|
-
accountId,
|
|
42
|
-
peer,
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
const baseSessionKey =
|
|
46
|
-
normalizeInboundSessionKey(sessionKeyfromroute, route, canonicalAgentId) ||
|
|
47
|
-
resolvedRoute.sessionKey;
|
|
48
|
-
const agentText = extracted.text;
|
|
49
|
-
const taskSessionKey = withTaskSessionKey(baseSessionKey, extracted.taskKey);
|
|
50
|
-
const sessionKey = taskSessionKey || baseSessionKey;
|
|
164
|
+
const { accountId, route, resolvedRoute, baseSessionKey, taskSessionKey, dispatchSessionKey } = resolution;
|
|
51
165
|
|
|
52
166
|
rememberSessionRoute(baseSessionKey, accountId, route);
|
|
53
167
|
if (taskSessionKey && taskSessionKey !== baseSessionKey) {
|
|
54
168
|
rememberSessionRoute(taskSessionKey, accountId, route);
|
|
55
169
|
}
|
|
56
170
|
|
|
57
|
-
const storePath =
|
|
171
|
+
const storePath = resolveBncrInboundSessionStorePath({
|
|
172
|
+
storeConfig: cfg?.session?.store,
|
|
58
173
|
agentId: resolvedRoute.agentId,
|
|
59
174
|
});
|
|
60
175
|
|
|
61
176
|
let mediaPath: string | undefined;
|
|
62
177
|
if (mediaBase64) {
|
|
63
|
-
const mediaBuf =
|
|
64
|
-
const saved = await
|
|
178
|
+
const mediaBuf = decodeInboundMediaBase64(mediaBase64);
|
|
179
|
+
const saved = await saveOpenClawChannelMediaBuffer(
|
|
180
|
+
api,
|
|
65
181
|
mediaBuf,
|
|
66
182
|
mimeType,
|
|
67
183
|
'inbound',
|
|
@@ -73,30 +189,189 @@ async function prepareBncrInboundSessionContext(args: {
|
|
|
73
189
|
mediaPath = mediaPathFromTransfer;
|
|
74
190
|
}
|
|
75
191
|
|
|
76
|
-
const rawBody =
|
|
77
|
-
const body = api
|
|
192
|
+
const rawBody = extracted.text || (msgType === 'text' ? '' : `[${msgType}]`);
|
|
193
|
+
const body = formatOpenClawAgentEnvelope(api, {
|
|
78
194
|
channel: 'Bncr',
|
|
79
195
|
from: `${platform}:${groupId}:${userId}`,
|
|
80
196
|
timestamp: Date.now(),
|
|
81
|
-
previousTimestamp: api
|
|
197
|
+
previousTimestamp: readBncrSessionUpdatedAt(api, {
|
|
82
198
|
storePath,
|
|
83
|
-
sessionKey,
|
|
199
|
+
sessionKey: dispatchSessionKey,
|
|
84
200
|
}),
|
|
85
|
-
envelope: api
|
|
201
|
+
envelope: resolveOpenClawEnvelopeFormatOptions(api, cfg),
|
|
86
202
|
body: rawBody,
|
|
87
203
|
});
|
|
88
204
|
|
|
89
|
-
const displayTo = formatDisplayScope(route);
|
|
90
205
|
return {
|
|
91
|
-
resolvedRoute,
|
|
92
|
-
baseSessionKey,
|
|
93
|
-
taskSessionKey,
|
|
94
|
-
sessionKey,
|
|
95
206
|
storePath,
|
|
96
207
|
mediaPath,
|
|
97
208
|
rawBody,
|
|
98
209
|
body,
|
|
99
|
-
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function buildBncrInboundTurnContext(args: {
|
|
214
|
+
api: any;
|
|
215
|
+
channelId: string;
|
|
216
|
+
parsed: ParsedInbound;
|
|
217
|
+
msgId?: string | null;
|
|
218
|
+
mimeType?: string;
|
|
219
|
+
mediaPath?: string;
|
|
220
|
+
peer: ParsedInbound['peer'];
|
|
221
|
+
senderIdForContext: string;
|
|
222
|
+
senderDisplayName: string;
|
|
223
|
+
resolution: BncrInboundConversationResolution;
|
|
224
|
+
prepared: {
|
|
225
|
+
rawBody: string;
|
|
226
|
+
body: string;
|
|
227
|
+
};
|
|
228
|
+
}) {
|
|
229
|
+
const {
|
|
230
|
+
api,
|
|
231
|
+
channelId,
|
|
232
|
+
parsed,
|
|
233
|
+
msgId,
|
|
234
|
+
mimeType,
|
|
235
|
+
mediaPath,
|
|
236
|
+
peer,
|
|
237
|
+
senderIdForContext,
|
|
238
|
+
senderDisplayName,
|
|
239
|
+
resolution,
|
|
240
|
+
prepared,
|
|
241
|
+
} = args;
|
|
242
|
+
const structuredContextFacts = buildBncrStructuredContextFactsFromInboundParts({
|
|
243
|
+
channelId,
|
|
244
|
+
parsed,
|
|
245
|
+
resolution,
|
|
246
|
+
prepared: {
|
|
247
|
+
rawBody: prepared.rawBody,
|
|
248
|
+
body: prepared.body,
|
|
249
|
+
mediaPath,
|
|
250
|
+
},
|
|
251
|
+
senderIdForContext,
|
|
252
|
+
senderDisplayName,
|
|
253
|
+
});
|
|
254
|
+
const promptVisibleContextFacts = buildBncrPromptVisibleContextFacts(structuredContextFacts);
|
|
255
|
+
const supplementalUntrustedContext = Object.keys(promptVisibleContextFacts).length
|
|
256
|
+
? [
|
|
257
|
+
{
|
|
258
|
+
label: 'Bncr inbound context',
|
|
259
|
+
source: channelId,
|
|
260
|
+
type: 'bncr.inbound_context',
|
|
261
|
+
payload: promptVisibleContextFacts,
|
|
262
|
+
},
|
|
263
|
+
]
|
|
264
|
+
: [];
|
|
265
|
+
|
|
266
|
+
return resolveBncrChannelInboundRuntime(api).buildContext({
|
|
267
|
+
channel: channelId,
|
|
268
|
+
provider: channelId,
|
|
269
|
+
surface: channelId,
|
|
270
|
+
accountId: resolution.accountId,
|
|
271
|
+
messageId: msgId,
|
|
272
|
+
timestamp: Date.now(),
|
|
273
|
+
from: senderIdForContext,
|
|
274
|
+
sender: {
|
|
275
|
+
id: senderIdForContext,
|
|
276
|
+
name: senderDisplayName,
|
|
277
|
+
username: senderDisplayName,
|
|
278
|
+
},
|
|
279
|
+
conversation: {
|
|
280
|
+
kind: resolution.chatType,
|
|
281
|
+
id: peer.id,
|
|
282
|
+
label: resolution.canonicalTo,
|
|
283
|
+
routePeer: {
|
|
284
|
+
kind: peer.kind,
|
|
285
|
+
id: peer.id,
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
route: {
|
|
289
|
+
agentId: resolution.resolvedRoute.agentId,
|
|
290
|
+
accountId: resolution.accountId,
|
|
291
|
+
routeSessionKey: resolution.resolvedRoute.sessionKey,
|
|
292
|
+
dispatchSessionKey: resolution.dispatchSessionKey,
|
|
293
|
+
mainSessionKey: resolution.resolvedRoute.mainSessionKey,
|
|
294
|
+
},
|
|
295
|
+
reply: {
|
|
296
|
+
to: resolution.canonicalTo,
|
|
297
|
+
originatingTo: resolution.originatingTo,
|
|
298
|
+
},
|
|
299
|
+
message: {
|
|
300
|
+
inboundEventKind: 'user_request',
|
|
301
|
+
body: prepared.body,
|
|
302
|
+
rawBody: prepared.rawBody,
|
|
303
|
+
bodyForAgent: prepared.rawBody,
|
|
304
|
+
commandBody: prepared.rawBody,
|
|
305
|
+
envelopeFrom: resolution.originatingTo,
|
|
306
|
+
senderLabel: senderDisplayName,
|
|
307
|
+
},
|
|
308
|
+
media: mediaPath
|
|
309
|
+
? [
|
|
310
|
+
{
|
|
311
|
+
path: mediaPath,
|
|
312
|
+
contentType: mimeType,
|
|
313
|
+
kind: mimeType?.startsWith('image/')
|
|
314
|
+
? 'image'
|
|
315
|
+
: mimeType?.startsWith('video/')
|
|
316
|
+
? 'video'
|
|
317
|
+
: mimeType?.startsWith('audio/')
|
|
318
|
+
? 'audio'
|
|
319
|
+
: 'document',
|
|
320
|
+
messageId: msgId ?? undefined,
|
|
321
|
+
},
|
|
322
|
+
]
|
|
323
|
+
: [],
|
|
324
|
+
supplemental: {
|
|
325
|
+
untrustedContext: supplementalUntrustedContext,
|
|
326
|
+
},
|
|
327
|
+
extra: {
|
|
328
|
+
OriginatingChannel: channelId,
|
|
329
|
+
BncrStructuredContextFacts: structuredContextFacts,
|
|
330
|
+
StructuredContextFacts: structuredContextFacts,
|
|
331
|
+
},
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function buildBncrInboundRecordUpdateLastRoute(args: {
|
|
336
|
+
channelId: string;
|
|
337
|
+
peer: ParsedInbound['peer'];
|
|
338
|
+
senderIdForContext: string;
|
|
339
|
+
resolution: BncrInboundConversationResolution;
|
|
340
|
+
pinnedMainDmOwner: string | null;
|
|
341
|
+
}) {
|
|
342
|
+
const { channelId, peer, senderIdForContext, resolution, pinnedMainDmOwner } = args;
|
|
343
|
+
if (peer.kind !== 'direct') return undefined;
|
|
344
|
+
|
|
345
|
+
const sessionKey = resolveOpenClawInboundLastRouteSessionKey({
|
|
346
|
+
route: resolution.resolvedRoute,
|
|
347
|
+
sessionKey: resolution.dispatchSessionKey,
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
return {
|
|
351
|
+
sessionKey,
|
|
352
|
+
channel: channelId,
|
|
353
|
+
to: resolution.canonicalTo,
|
|
354
|
+
accountId: resolution.accountId,
|
|
355
|
+
mainDmOwnerPin:
|
|
356
|
+
sessionKey === resolution.resolvedRoute.mainSessionKey && pinnedMainDmOwner
|
|
357
|
+
? {
|
|
358
|
+
ownerRecipient: pinnedMainDmOwner,
|
|
359
|
+
senderRecipient: senderIdForContext,
|
|
360
|
+
}
|
|
361
|
+
: undefined,
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function buildBncrInboundReplyRouteFact(
|
|
366
|
+
resolution: BncrInboundConversationResolution,
|
|
367
|
+
): BncrInboundReplyRouteFact {
|
|
368
|
+
return {
|
|
369
|
+
accountId: resolution.accountId,
|
|
370
|
+
sessionKey: resolution.dispatchSessionKey,
|
|
371
|
+
route: resolution.route,
|
|
372
|
+
canonicalTo: resolution.canonicalTo,
|
|
373
|
+
originatingTo: resolution.originatingTo,
|
|
374
|
+
chatType: resolution.chatType,
|
|
100
375
|
};
|
|
101
376
|
}
|
|
102
377
|
|
|
@@ -130,7 +405,7 @@ export async function dispatchBncrInbound(params: {
|
|
|
130
405
|
scheduleSave,
|
|
131
406
|
logger,
|
|
132
407
|
} = params;
|
|
133
|
-
const { accountId,
|
|
408
|
+
const { accountId, clientId, msgId, extracted, mimeType, peer } = parsed;
|
|
134
409
|
|
|
135
410
|
const nativeCommand = await handleBncrNativeCommand({
|
|
136
411
|
api,
|
|
@@ -142,7 +417,7 @@ export async function dispatchBncrInbound(params: {
|
|
|
142
417
|
enqueueFromReply,
|
|
143
418
|
logger,
|
|
144
419
|
});
|
|
145
|
-
if (nativeCommand.handled) {
|
|
420
|
+
if (nativeCommand.handled && !nativeCommand.fallbackToAgent) {
|
|
146
421
|
const inboundAt = Date.now();
|
|
147
422
|
setInboundActivity(accountId, inboundAt);
|
|
148
423
|
scheduleSave();
|
|
@@ -154,104 +429,139 @@ export async function dispatchBncrInbound(params: {
|
|
|
154
429
|
};
|
|
155
430
|
}
|
|
156
431
|
|
|
157
|
-
const {
|
|
158
|
-
resolvedRoute,
|
|
159
|
-
sessionKey,
|
|
160
|
-
storePath,
|
|
161
|
-
mediaPath,
|
|
162
|
-
rawBody,
|
|
163
|
-
body,
|
|
164
|
-
displayTo,
|
|
165
|
-
} = await prepareBncrInboundSessionContext({
|
|
432
|
+
const resolution = resolveBncrInboundConversation({
|
|
166
433
|
api,
|
|
167
434
|
cfg,
|
|
168
435
|
channelId,
|
|
169
436
|
parsed,
|
|
170
437
|
canonicalAgentId,
|
|
438
|
+
});
|
|
439
|
+
const { resolvedRoute, canonicalTo, dispatchSessionKey: sessionKey } = resolution;
|
|
440
|
+
const prepared = await prepareBncrInboundSessionContext({
|
|
441
|
+
api,
|
|
442
|
+
cfg,
|
|
443
|
+
parsed,
|
|
444
|
+
resolution,
|
|
171
445
|
rememberSessionRoute,
|
|
172
446
|
});
|
|
447
|
+
const { storePath, mediaPath, rawBody, body } = prepared;
|
|
448
|
+
const replyRouteFact = buildBncrInboundReplyRouteFact(resolution);
|
|
173
449
|
if (!clientId) {
|
|
174
|
-
emitBncrLogLine(
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
taskKey: extracted.taskKey ?? null,
|
|
179
|
-
msgId: msgId ?? null,
|
|
180
|
-
};
|
|
450
|
+
emitBncrLogLine(
|
|
451
|
+
'warn',
|
|
452
|
+
'[bncr] inbound missing clientId for chat identity; using route identity fallback',
|
|
453
|
+
);
|
|
181
454
|
}
|
|
182
|
-
const senderIdForContext = clientId;
|
|
183
|
-
const senderDisplayName = 'bncr-client';
|
|
184
|
-
const ctxPayload =
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
ConversationLabel: displayTo,
|
|
197
|
-
SenderId: senderIdForContext,
|
|
198
|
-
SenderName: senderDisplayName,
|
|
199
|
-
SenderUsername: senderDisplayName,
|
|
200
|
-
Provider: channelId,
|
|
201
|
-
Surface: channelId,
|
|
202
|
-
MessageSid: msgId,
|
|
203
|
-
Timestamp: Date.now(),
|
|
204
|
-
OriginatingChannel: channelId,
|
|
205
|
-
OriginatingTo: displayTo,
|
|
455
|
+
const senderIdForContext = clientId || canonicalTo;
|
|
456
|
+
const senderDisplayName = clientId ? 'bncr-client' : canonicalTo;
|
|
457
|
+
const ctxPayload = buildBncrInboundTurnContext({
|
|
458
|
+
api,
|
|
459
|
+
channelId,
|
|
460
|
+
parsed,
|
|
461
|
+
msgId,
|
|
462
|
+
mimeType,
|
|
463
|
+
mediaPath,
|
|
464
|
+
peer,
|
|
465
|
+
senderIdForContext,
|
|
466
|
+
senderDisplayName,
|
|
467
|
+
resolution,
|
|
468
|
+
prepared,
|
|
206
469
|
});
|
|
207
470
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
471
|
+
const effectiveReply = buildBncrReplyConfig(cfg);
|
|
472
|
+
const channelPolicy = resolveBncrChannelPolicy(cfg?.channels?.bncr || {});
|
|
473
|
+
const pinnedMainDmOwner =
|
|
474
|
+
peer.kind === 'direct'
|
|
475
|
+
? resolveBncrPinnedMainDmOwnerFromAllowlist({
|
|
476
|
+
dmScope: cfg?.session?.dmScope,
|
|
477
|
+
allowFrom: channelPolicy.allowFrom,
|
|
478
|
+
normalizeEntry: (entry: string) => String(entry || '').trim(),
|
|
479
|
+
})
|
|
480
|
+
: null;
|
|
481
|
+
const updateLastRoute = buildBncrInboundRecordUpdateLastRoute({
|
|
482
|
+
channelId,
|
|
483
|
+
peer,
|
|
484
|
+
senderIdForContext,
|
|
485
|
+
resolution,
|
|
486
|
+
pinnedMainDmOwner,
|
|
215
487
|
});
|
|
216
488
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
489
|
+
await resolveBncrChannelInboundRuntime(api).run({
|
|
490
|
+
channel: channelId,
|
|
491
|
+
accountId,
|
|
492
|
+
raw: parsed,
|
|
493
|
+
adapter: {
|
|
494
|
+
ingest: () => ({
|
|
495
|
+
id: msgId ?? `${canonicalTo}:${Date.now()}`,
|
|
496
|
+
timestamp: Date.now(),
|
|
497
|
+
rawText: rawBody,
|
|
498
|
+
textForAgent: ctxPayload.BodyForAgent,
|
|
499
|
+
textForCommands: ctxPayload.CommandBody,
|
|
500
|
+
raw: parsed,
|
|
501
|
+
}),
|
|
502
|
+
resolveTurn: () => ({
|
|
503
|
+
channel: channelId,
|
|
504
|
+
accountId,
|
|
505
|
+
routeSessionKey: resolvedRoute.sessionKey,
|
|
506
|
+
storePath,
|
|
507
|
+
ctxPayload,
|
|
508
|
+
recordInboundSession: wrapBncrInboundRecordSessionLabelCorrection({
|
|
509
|
+
recordInboundSession: recordBncrInboundSession,
|
|
510
|
+
expectedLabel: canonicalTo,
|
|
511
|
+
}),
|
|
512
|
+
record: {
|
|
513
|
+
updateLastRoute,
|
|
514
|
+
onRecordError: (err: unknown) => {
|
|
515
|
+
emitBncrLogLine('warn', `[bncr] inbound record session failed: ${String(err)}`);
|
|
516
|
+
},
|
|
517
|
+
},
|
|
518
|
+
runDispatch: () =>
|
|
519
|
+
dispatchOpenClawReplyWithBufferedBlockDispatcher(api, {
|
|
520
|
+
ctx: ctxPayload,
|
|
521
|
+
cfg: effectiveReply.replyCfg,
|
|
522
|
+
dispatcherOptions: {
|
|
523
|
+
deliver: async (
|
|
524
|
+
payload: {
|
|
525
|
+
text?: string;
|
|
526
|
+
mediaUrl?: string;
|
|
527
|
+
mediaUrls?: string[];
|
|
528
|
+
audioAsVoice?: boolean;
|
|
529
|
+
},
|
|
530
|
+
info?: { kind?: 'tool' | 'block' | 'final' },
|
|
531
|
+
) => {
|
|
532
|
+
const kind = info?.kind;
|
|
533
|
+
const shouldForwardTool = effectiveReply.blockStreaming && effectiveReply.allowTool;
|
|
220
534
|
|
|
221
|
-
|
|
535
|
+
if (kind === 'tool' && !shouldForwardTool) {
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
222
538
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
539
|
+
await enqueueFromReply({
|
|
540
|
+
accountId: replyRouteFact.accountId,
|
|
541
|
+
sessionKey: replyRouteFact.sessionKey,
|
|
542
|
+
route: replyRouteFact.route,
|
|
543
|
+
payload: {
|
|
544
|
+
...payload,
|
|
545
|
+
kind: kind as 'tool' | 'block' | 'final' | undefined,
|
|
546
|
+
replyToId: msgId || undefined,
|
|
547
|
+
},
|
|
548
|
+
});
|
|
549
|
+
},
|
|
550
|
+
onError: (err: unknown) => {
|
|
551
|
+
emitBncrLogLine('error', `[bncr] outbound reply failed: ${String(err)}`);
|
|
552
|
+
},
|
|
553
|
+
},
|
|
554
|
+
replyOptions: {
|
|
555
|
+
disableBlockStreaming: !effectiveReply.blockStreaming,
|
|
556
|
+
shouldEmitToolResult: effectiveReply.allowTool ? () => true : undefined,
|
|
557
|
+
},
|
|
558
|
+
}),
|
|
559
|
+
}),
|
|
560
|
+
onFinalize: () => {
|
|
561
|
+
const inboundAt = Date.now();
|
|
562
|
+
setInboundActivity(accountId, inboundAt);
|
|
563
|
+
scheduleSave();
|
|
247
564
|
},
|
|
248
|
-
onError: (err: unknown) => {
|
|
249
|
-
emitBncrLogLine('error', `[bncr] outbound reply failed: ${String(err)}`);
|
|
250
|
-
},
|
|
251
|
-
},
|
|
252
|
-
replyOptions: {
|
|
253
|
-
disableBlockStreaming: !effectiveReply.blockStreaming,
|
|
254
|
-
shouldEmitToolResult: effectiveReply.allowTool ? () => true : undefined,
|
|
255
565
|
},
|
|
256
566
|
});
|
|
257
567
|
|