@xmoxmo/bncr 0.4.6 → 0.4.8
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/package.json +1 -1
- package/src/channel.ts +41 -2
- package/src/core/targets.ts +106 -17
- package/src/messaging/inbound/commands.ts +421 -51
- package/src/messaging/inbound/context-facts.ts +126 -14
- package/src/messaging/inbound/contracts.ts +24 -0
- package/src/messaging/inbound/dispatch-prep.ts +214 -39
- package/src/messaging/inbound/dispatch.ts +71 -5
- package/src/messaging/inbound/gate.ts +56 -86
- package/src/messaging/inbound/group-history.ts +189 -0
- package/src/messaging/inbound/native-command-runtime.ts +77 -61
- package/src/messaging/inbound/native-command.ts +192 -8
- package/src/messaging/inbound/parse.ts +113 -8
- package/src/messaging/inbound/reply-dispatch-serial.ts +62 -0
- package/src/messaging/inbound/reply-dispatch.ts +252 -77
- package/src/messaging/inbound/scene-admin.ts +300 -0
- package/src/messaging/inbound/session-label.ts +122 -13
- package/src/messaging/inbound/session-meta-task.ts +17 -0
- package/src/messaging/inbound/turn-context.ts +184 -71
- package/src/openclaw/channel-runtime-contracts.ts +1 -0
- package/src/plugin/channel-components.ts +35 -1
- package/src/plugin/channel-inbound-helpers.ts +9 -2
- package/src/plugin/channel-runtime-builders-delivery.ts +24 -1
- package/src/plugin/channel-runtime-types.ts +42 -0
- package/src/plugin/file-inbound-init.ts +27 -12
- package/src/plugin/file-inbound-runtime.ts +2 -0
- package/src/plugin/inbound-acceptance.ts +82 -1
- package/src/plugin/inbound-handlers.ts +97 -2
- package/src/plugin/inbound-surface-handlers-group.ts +16 -0
- package/src/plugin/messaging.ts +22 -5
- package/src/plugin/scene-registry.ts +171 -0
- package/src/plugin/state-store.ts +133 -0
- package/src/plugin/state-transient-runtime-group.ts +5 -0
- package/src/plugin/target-runtime.ts +2 -2
|
@@ -19,6 +19,27 @@ export type BncrStructuredContextFactsInput = {
|
|
|
19
19
|
sender: {
|
|
20
20
|
id: string;
|
|
21
21
|
displayName?: string;
|
|
22
|
+
userId?: string;
|
|
23
|
+
userName?: string;
|
|
24
|
+
bridgeId?: string;
|
|
25
|
+
bridgeName?: string;
|
|
26
|
+
isAdmin?: boolean;
|
|
27
|
+
isOwner?: boolean;
|
|
28
|
+
isAuthorizedSender?: boolean;
|
|
29
|
+
role?: 'owner' | 'admin' | 'user';
|
|
30
|
+
};
|
|
31
|
+
platform?: string;
|
|
32
|
+
group?: {
|
|
33
|
+
id?: string;
|
|
34
|
+
name?: string;
|
|
35
|
+
isGroup?: boolean;
|
|
36
|
+
};
|
|
37
|
+
trigger?: {
|
|
38
|
+
shouldRespond?: boolean;
|
|
39
|
+
triggerKind?: string;
|
|
40
|
+
botName?: string;
|
|
41
|
+
isBotMentioned?: boolean;
|
|
42
|
+
isReplyToBot?: boolean;
|
|
22
43
|
};
|
|
23
44
|
message: {
|
|
24
45
|
id?: string | null;
|
|
@@ -60,6 +81,27 @@ export function buildBncrStructuredContextFacts(input: BncrStructuredContextFact
|
|
|
60
81
|
sender: {
|
|
61
82
|
id: input.sender.id,
|
|
62
83
|
displayName: input.sender.displayName || input.sender.id,
|
|
84
|
+
userId: input.sender.userId,
|
|
85
|
+
userName: input.sender.userName,
|
|
86
|
+
bridgeId: input.sender.bridgeId,
|
|
87
|
+
bridgeName: input.sender.bridgeName,
|
|
88
|
+
isAdmin: input.sender.isAdmin,
|
|
89
|
+
...(input.sender.isOwner === true ? { isOwner: true } : {}),
|
|
90
|
+
...(input.sender.isAuthorizedSender === true ? { isAuthorizedSender: true } : {}),
|
|
91
|
+
...(input.sender.role !== undefined ? { role: input.sender.role } : {}),
|
|
92
|
+
},
|
|
93
|
+
platform: input.platform,
|
|
94
|
+
group: {
|
|
95
|
+
id: input.group?.id,
|
|
96
|
+
name: input.group?.name,
|
|
97
|
+
isGroup: input.group?.isGroup,
|
|
98
|
+
},
|
|
99
|
+
trigger: {
|
|
100
|
+
shouldRespond: input.trigger?.shouldRespond,
|
|
101
|
+
triggerKind: input.trigger?.triggerKind,
|
|
102
|
+
botName: input.trigger?.botName,
|
|
103
|
+
isBotMentioned: input.trigger?.isBotMentioned,
|
|
104
|
+
isReplyToBot: input.trigger?.isReplyToBot,
|
|
63
105
|
},
|
|
64
106
|
message: {
|
|
65
107
|
id: input.message.id || undefined,
|
|
@@ -85,6 +127,15 @@ export function buildBncrPromptVisibleContextFacts(
|
|
|
85
127
|
facts: ReturnType<typeof buildBncrStructuredContextFacts>,
|
|
86
128
|
) {
|
|
87
129
|
const result: {
|
|
130
|
+
trigger?: {
|
|
131
|
+
botName?: string;
|
|
132
|
+
};
|
|
133
|
+
sender?: {
|
|
134
|
+
isAdmin?: boolean;
|
|
135
|
+
isOwner?: boolean;
|
|
136
|
+
isAuthorizedSender?: boolean;
|
|
137
|
+
role?: 'owner' | 'admin' | 'user';
|
|
138
|
+
};
|
|
88
139
|
reply?: {
|
|
89
140
|
to: string;
|
|
90
141
|
originatingTo: string;
|
|
@@ -96,6 +147,30 @@ export function buildBncrPromptVisibleContextFacts(
|
|
|
96
147
|
}>;
|
|
97
148
|
} = {};
|
|
98
149
|
|
|
150
|
+
if (
|
|
151
|
+
facts.sender.isAdmin === true ||
|
|
152
|
+
facts.sender.isOwner === true ||
|
|
153
|
+
facts.sender.isAuthorizedSender === true ||
|
|
154
|
+
facts.sender.role === 'owner' ||
|
|
155
|
+
facts.sender.role === 'admin'
|
|
156
|
+
) {
|
|
157
|
+
const sender = {
|
|
158
|
+
...(facts.sender.isAdmin === true ? { isAdmin: true } : {}),
|
|
159
|
+
...(facts.sender.isOwner === true ? { isOwner: true } : {}),
|
|
160
|
+
...(facts.sender.isAuthorizedSender === true ? { isAuthorizedSender: true } : {}),
|
|
161
|
+
...(facts.sender.role === 'owner' || facts.sender.role === 'admin'
|
|
162
|
+
? { role: facts.sender.role }
|
|
163
|
+
: {}),
|
|
164
|
+
};
|
|
165
|
+
if (Object.keys(sender).length > 0) result.sender = sender;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (facts.trigger.botName) {
|
|
169
|
+
result.trigger = {
|
|
170
|
+
botName: facts.trigger.botName,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
99
174
|
if (facts.reply.originatingTo !== facts.reply.to) {
|
|
100
175
|
result.reply = {
|
|
101
176
|
to: facts.reply.to,
|
|
@@ -125,11 +200,25 @@ export type BncrStructuredContextFactsFromInboundPartsInput = {
|
|
|
125
200
|
channelId: string;
|
|
126
201
|
parsed: {
|
|
127
202
|
accountId: string;
|
|
203
|
+
platform?: string;
|
|
128
204
|
peer: {
|
|
129
205
|
kind: string;
|
|
130
206
|
id: string;
|
|
131
207
|
};
|
|
132
208
|
clientId?: string;
|
|
209
|
+
bridgeId?: string;
|
|
210
|
+
bridgeName?: string;
|
|
211
|
+
groupId?: string;
|
|
212
|
+
groupName?: string;
|
|
213
|
+
userId?: string;
|
|
214
|
+
userName?: string;
|
|
215
|
+
isGroup?: boolean;
|
|
216
|
+
isAdmin?: boolean;
|
|
217
|
+
shouldRespond?: boolean;
|
|
218
|
+
triggerKind?: string;
|
|
219
|
+
botName?: string;
|
|
220
|
+
isBotMentioned?: boolean;
|
|
221
|
+
isReplyToBot?: boolean;
|
|
133
222
|
msgId?: string;
|
|
134
223
|
mimeType?: string;
|
|
135
224
|
};
|
|
@@ -147,18 +236,24 @@ export type BncrStructuredContextFactsFromInboundPartsInput = {
|
|
|
147
236
|
prepared: {
|
|
148
237
|
rawBody: string;
|
|
149
238
|
body?: string;
|
|
150
|
-
|
|
151
|
-
|
|
239
|
+
mediaItems?: Array<{
|
|
240
|
+
path: string;
|
|
241
|
+
contentType?: string;
|
|
242
|
+
kind?: string;
|
|
243
|
+
}>;
|
|
152
244
|
};
|
|
153
245
|
senderIdForContext: string;
|
|
154
246
|
senderDisplayName?: string;
|
|
247
|
+
bridgeSenderId?: string;
|
|
248
|
+
bridgeSenderName?: string;
|
|
249
|
+
senderIsOwner?: boolean;
|
|
250
|
+
senderIsAuthorized?: boolean;
|
|
155
251
|
};
|
|
156
252
|
|
|
157
253
|
export function buildBncrStructuredContextFactsFromInboundParts(
|
|
158
254
|
input: BncrStructuredContextFactsFromInboundPartsInput,
|
|
159
255
|
) {
|
|
160
|
-
const
|
|
161
|
-
const mediaContentType = input.prepared.mediaContentType || input.parsed.mimeType;
|
|
256
|
+
const mediaItems = Array.isArray(input.prepared.mediaItems) ? input.prepared.mediaItems : [];
|
|
162
257
|
return buildBncrStructuredContextFacts({
|
|
163
258
|
channelId: input.channelId,
|
|
164
259
|
accountId: input.parsed.accountId,
|
|
@@ -180,6 +275,27 @@ export function buildBncrStructuredContextFactsFromInboundParts(
|
|
|
180
275
|
sender: {
|
|
181
276
|
id: input.senderIdForContext,
|
|
182
277
|
displayName: input.senderDisplayName,
|
|
278
|
+
userId: input.parsed.userId,
|
|
279
|
+
userName: input.parsed.userName,
|
|
280
|
+
bridgeId: input.bridgeSenderId || input.parsed.bridgeId || input.parsed.clientId,
|
|
281
|
+
bridgeName: input.bridgeSenderName || input.parsed.bridgeName,
|
|
282
|
+
isAdmin: input.parsed.isAdmin,
|
|
283
|
+
isOwner: input.senderIsOwner === true ? true : undefined,
|
|
284
|
+
isAuthorizedSender: input.senderIsAuthorized === true ? true : undefined,
|
|
285
|
+
role: input.senderIsOwner ? 'owner' : input.parsed.isAdmin ? 'admin' : undefined,
|
|
286
|
+
},
|
|
287
|
+
platform: input.parsed.platform,
|
|
288
|
+
group: {
|
|
289
|
+
id: input.parsed.groupId,
|
|
290
|
+
name: input.parsed.groupName,
|
|
291
|
+
isGroup: input.parsed.isGroup,
|
|
292
|
+
},
|
|
293
|
+
trigger: {
|
|
294
|
+
shouldRespond: input.parsed.shouldRespond,
|
|
295
|
+
triggerKind: input.parsed.triggerKind,
|
|
296
|
+
botName: input.parsed.botName,
|
|
297
|
+
isBotMentioned: input.parsed.isBotMentioned,
|
|
298
|
+
isReplyToBot: input.parsed.isReplyToBot,
|
|
183
299
|
},
|
|
184
300
|
message: {
|
|
185
301
|
id: input.parsed.msgId,
|
|
@@ -188,15 +304,11 @@ export function buildBncrStructuredContextFactsFromInboundParts(
|
|
|
188
304
|
commandBody: input.prepared.rawBody,
|
|
189
305
|
envelopeBody: input.prepared.body,
|
|
190
306
|
},
|
|
191
|
-
media:
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
messageId: input.parsed.msgId,
|
|
198
|
-
},
|
|
199
|
-
]
|
|
200
|
-
: [],
|
|
307
|
+
media: mediaItems.map((item) => ({
|
|
308
|
+
path: item.path,
|
|
309
|
+
contentType: item.contentType,
|
|
310
|
+
kind: item.kind || inferBncrStructuredMediaKind(item.contentType),
|
|
311
|
+
messageId: input.parsed.msgId,
|
|
312
|
+
})),
|
|
201
313
|
});
|
|
202
314
|
}
|
|
@@ -15,21 +15,45 @@ export type BncrInboundParsedPeer = OpenClawChannelPeer;
|
|
|
15
15
|
|
|
16
16
|
export type BncrInboundParamsInput = {
|
|
17
17
|
accountId?: unknown;
|
|
18
|
+
protocolVersion?: unknown;
|
|
19
|
+
capabilities?: unknown;
|
|
18
20
|
platform?: unknown;
|
|
19
21
|
groupId?: unknown;
|
|
22
|
+
groupName?: unknown;
|
|
20
23
|
userId?: unknown;
|
|
24
|
+
userName?: unknown;
|
|
21
25
|
sessionKey?: unknown;
|
|
22
26
|
originatingTo?: unknown;
|
|
23
27
|
providedOriginatingTo?: unknown;
|
|
24
28
|
to?: unknown;
|
|
25
29
|
clientId?: unknown;
|
|
30
|
+
bridgeId?: unknown;
|
|
31
|
+
bridgeName?: unknown;
|
|
32
|
+
isGroup?: unknown;
|
|
33
|
+
isAdmin?: unknown;
|
|
26
34
|
msg?: unknown;
|
|
27
35
|
type?: unknown;
|
|
28
36
|
base64?: unknown;
|
|
29
37
|
path?: unknown;
|
|
38
|
+
paths?: unknown;
|
|
39
|
+
mediaList?: unknown;
|
|
30
40
|
mimeType?: unknown;
|
|
31
41
|
fileName?: unknown;
|
|
32
42
|
msgId?: unknown;
|
|
43
|
+
shouldRespond?: unknown;
|
|
44
|
+
triggerKind?: unknown;
|
|
45
|
+
botName?: unknown;
|
|
46
|
+
isBotMentioned?: unknown;
|
|
47
|
+
isReplyToBot?: unknown;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type BncrInboundMediaItem = {
|
|
51
|
+
path?: string;
|
|
52
|
+
base64?: string;
|
|
53
|
+
mimeType?: string;
|
|
54
|
+
fileName?: string;
|
|
55
|
+
type?: string;
|
|
56
|
+
transferId?: string;
|
|
33
57
|
};
|
|
34
58
|
|
|
35
59
|
export type BncrRememberSessionRoute = (
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import { hasControlCommand } from 'openclaw/plugin-sdk/command-auth';
|
|
2
3
|
import {
|
|
3
4
|
formatDisplayScope,
|
|
4
5
|
normalizeInboundSessionKey,
|
|
@@ -19,6 +20,7 @@ import {
|
|
|
19
20
|
} from '../../openclaw/routing-runtime.ts';
|
|
20
21
|
import type { BncrInboundApi, BncrInboundConfig, BncrRememberSessionRoute } from './contracts.ts';
|
|
21
22
|
import { INBOUND_MEDIA_URL_MAX_BYTES, isHttpMediaUrl } from './media-url-download.ts';
|
|
23
|
+
import { parseBncrNativeCommand } from './native-command.ts';
|
|
22
24
|
import { loadInboundRemoteMedia } from './remote-media.ts';
|
|
23
25
|
|
|
24
26
|
export type ParsedInbound = ReturnType<typeof import('./parse.ts')['parseBncrInboundParams']>;
|
|
@@ -27,6 +29,7 @@ export type BncrInboundConversationResolution = {
|
|
|
27
29
|
accountId: string;
|
|
28
30
|
chatType: 'direct' | 'group';
|
|
29
31
|
route: ParsedInbound['route'];
|
|
32
|
+
originalResolvedRouteSessionKey: string;
|
|
30
33
|
resolvedRoute: {
|
|
31
34
|
sessionKey: string;
|
|
32
35
|
agentId: string;
|
|
@@ -51,6 +54,13 @@ export type BncrInboundReplyRouteFact = {
|
|
|
51
54
|
|
|
52
55
|
export type BncrPreparedInboundSessionContext = {
|
|
53
56
|
storePath: string;
|
|
57
|
+
mediaItems: Array<{
|
|
58
|
+
path: string;
|
|
59
|
+
contentType?: string;
|
|
60
|
+
fileName?: string;
|
|
61
|
+
kind: 'image' | 'video' | 'audio' | 'document';
|
|
62
|
+
transferId?: string;
|
|
63
|
+
}>;
|
|
54
64
|
mediaPath?: string;
|
|
55
65
|
mediaContentType?: string;
|
|
56
66
|
rawBody: string;
|
|
@@ -63,10 +73,27 @@ export type BncrInboundPreparation = {
|
|
|
63
73
|
replyRouteFact: BncrInboundReplyRouteFact;
|
|
64
74
|
senderIdForContext: string;
|
|
65
75
|
senderDisplayName: string;
|
|
76
|
+
ownerAllowFrom?: string[];
|
|
77
|
+
bridgeSenderId?: string;
|
|
78
|
+
bridgeSenderName?: string;
|
|
66
79
|
};
|
|
67
80
|
|
|
68
81
|
const INBOUND_MEDIA_MAX_BYTES = 30 * 1024 * 1024;
|
|
69
82
|
|
|
83
|
+
const BNCR_GENERIC_MEDIA_TEXTS = new Set([
|
|
84
|
+
'收到媒体文件',
|
|
85
|
+
'收到图片',
|
|
86
|
+
'收到视频',
|
|
87
|
+
'收到语音',
|
|
88
|
+
'收到音频',
|
|
89
|
+
'收到文件',
|
|
90
|
+
'[图片]',
|
|
91
|
+
'[视频]',
|
|
92
|
+
'[语音]',
|
|
93
|
+
'[音频]',
|
|
94
|
+
'[文件]',
|
|
95
|
+
]);
|
|
96
|
+
|
|
70
97
|
function assertResolvedAgentRoute(resolvedRoute: OpenClawResolvedAgentRoute): {
|
|
71
98
|
sessionKey: string;
|
|
72
99
|
agentId: string;
|
|
@@ -90,14 +117,140 @@ function formatRawBncrInboundTarget(route: ParsedInbound['route']): string {
|
|
|
90
117
|
return `Bncr:${String(route.platform || '').trim()}:${String(route.groupId || '').trim()}:${String(route.userId || '').trim()}`;
|
|
91
118
|
}
|
|
92
119
|
|
|
120
|
+
function inferBncrInboundMediaKind(args: {
|
|
121
|
+
msgType?: string;
|
|
122
|
+
mediaContentType?: string;
|
|
123
|
+
}): 'image' | 'video' | 'audio' | 'document' {
|
|
124
|
+
const msgType = String(args.msgType || '')
|
|
125
|
+
.trim()
|
|
126
|
+
.toLowerCase();
|
|
127
|
+
const contentType = String(args.mediaContentType || '')
|
|
128
|
+
.trim()
|
|
129
|
+
.toLowerCase();
|
|
130
|
+
if (msgType === 'image' || contentType.startsWith('image/')) return 'image';
|
|
131
|
+
if (msgType === 'video' || contentType.startsWith('video/')) return 'video';
|
|
132
|
+
if (msgType === 'audio' || msgType === 'voice' || contentType.startsWith('audio/'))
|
|
133
|
+
return 'audio';
|
|
134
|
+
return 'document';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function isBncrGenericMediaText(value: string): boolean {
|
|
138
|
+
const normalized = String(value || '').trim();
|
|
139
|
+
if (!normalized) return true;
|
|
140
|
+
return BNCR_GENERIC_MEDIA_TEXTS.has(normalized);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function resolveBncrInboundRawBody(args: {
|
|
144
|
+
extractedText?: string;
|
|
145
|
+
msgType?: string;
|
|
146
|
+
mediaItems?: Array<{ contentType?: string; kind?: string }>;
|
|
147
|
+
}) {
|
|
148
|
+
const text = String(args.extractedText || '').trim();
|
|
149
|
+
if (
|
|
150
|
+
String(args.msgType || 'text')
|
|
151
|
+
.trim()
|
|
152
|
+
.toLowerCase() === 'text'
|
|
153
|
+
)
|
|
154
|
+
return text;
|
|
155
|
+
if (text && !isBncrGenericMediaText(text)) return text;
|
|
156
|
+
const items = Array.isArray(args.mediaItems) ? args.mediaItems : [];
|
|
157
|
+
if (items.length === 0) {
|
|
158
|
+
const mediaKind = inferBncrInboundMediaKind({
|
|
159
|
+
msgType: args.msgType,
|
|
160
|
+
mediaContentType: undefined,
|
|
161
|
+
});
|
|
162
|
+
return `<media:${mediaKind}>`;
|
|
163
|
+
}
|
|
164
|
+
const kinds = items
|
|
165
|
+
.map((item) =>
|
|
166
|
+
String(item?.kind || '')
|
|
167
|
+
.trim()
|
|
168
|
+
.toLowerCase(),
|
|
169
|
+
)
|
|
170
|
+
.filter(Boolean);
|
|
171
|
+
const contentTypes = items.map((item) => String(item?.contentType || '').trim()).filter(Boolean);
|
|
172
|
+
const firstKind =
|
|
173
|
+
kinds[0] ||
|
|
174
|
+
inferBncrInboundMediaKind({
|
|
175
|
+
msgType: args.msgType,
|
|
176
|
+
mediaContentType: contentTypes[0],
|
|
177
|
+
});
|
|
178
|
+
const uniformKind =
|
|
179
|
+
kinds.length > 0 && kinds.every((candidate) => candidate === firstKind)
|
|
180
|
+
? firstKind
|
|
181
|
+
: 'document';
|
|
182
|
+
const count = Math.max(items.length, 1);
|
|
183
|
+
if (count <= 1) return `<media:${uniformKind}>`;
|
|
184
|
+
if (uniformKind === 'image') return `<media:image> (${count} images)`;
|
|
185
|
+
if (uniformKind === 'video') return `<media:video> (${count} videos)`;
|
|
186
|
+
if (uniformKind === 'audio') return `<media:audio> (${count} audio attachments)`;
|
|
187
|
+
return `<media:document> (${count} attachments)`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function saveBncrInboundMediaItem(args: {
|
|
191
|
+
api: BncrInboundApi;
|
|
192
|
+
item: ParsedInbound['mediaItems'][number];
|
|
193
|
+
}) {
|
|
194
|
+
const pathFromTransfer = String(args.item?.path || '').trim();
|
|
195
|
+
const base64 = String(args.item?.base64 || '').trim();
|
|
196
|
+
const mimeType = String(args.item?.mimeType || '').trim() || undefined;
|
|
197
|
+
const fileName = String(args.item?.fileName || '').trim() || undefined;
|
|
198
|
+
const transferId = String(args.item?.transferId || '').trim() || undefined;
|
|
199
|
+
let mediaPath: string | undefined;
|
|
200
|
+
let mediaContentType = mimeType;
|
|
201
|
+
if (base64) {
|
|
202
|
+
const mediaBuf = decodeInboundMediaBase64(base64);
|
|
203
|
+
const saved = await saveOpenClawChannelMediaBuffer(
|
|
204
|
+
args.api,
|
|
205
|
+
mediaBuf,
|
|
206
|
+
mimeType,
|
|
207
|
+
'inbound',
|
|
208
|
+
30 * 1024 * 1024,
|
|
209
|
+
fileName,
|
|
210
|
+
);
|
|
211
|
+
mediaPath = saved.path;
|
|
212
|
+
} else if (pathFromTransfer && isHttpMediaUrl(pathFromTransfer)) {
|
|
213
|
+
const downloaded = await loadInboundRemoteMedia(
|
|
214
|
+
args.api,
|
|
215
|
+
pathFromTransfer,
|
|
216
|
+
INBOUND_MEDIA_URL_MAX_BYTES,
|
|
217
|
+
);
|
|
218
|
+
mediaContentType = downloaded.contentType || mimeType;
|
|
219
|
+
const saved = await saveOpenClawChannelMediaBuffer(
|
|
220
|
+
args.api,
|
|
221
|
+
downloaded.buffer,
|
|
222
|
+
mediaContentType,
|
|
223
|
+
'inbound',
|
|
224
|
+
INBOUND_MEDIA_URL_MAX_BYTES,
|
|
225
|
+
fileName,
|
|
226
|
+
);
|
|
227
|
+
mediaPath = saved.path;
|
|
228
|
+
} else if (pathFromTransfer && fs.existsSync(pathFromTransfer)) {
|
|
229
|
+
mediaPath = pathFromTransfer;
|
|
230
|
+
}
|
|
231
|
+
if (!mediaPath) return null;
|
|
232
|
+
const kind = inferBncrInboundMediaKind({
|
|
233
|
+
msgType: String(args.item?.type || '').trim(),
|
|
234
|
+
mediaContentType: mediaContentType || mimeType,
|
|
235
|
+
});
|
|
236
|
+
return {
|
|
237
|
+
path: mediaPath,
|
|
238
|
+
...(mediaContentType ? { contentType: mediaContentType } : {}),
|
|
239
|
+
...(fileName ? { fileName } : {}),
|
|
240
|
+
kind,
|
|
241
|
+
...(transferId ? { transferId } : {}),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
93
245
|
export function resolveBncrInboundConversation(args: {
|
|
94
246
|
api: BncrInboundApi;
|
|
95
247
|
cfg: BncrInboundConfig;
|
|
96
248
|
channelId: string;
|
|
97
249
|
parsed: ParsedInbound;
|
|
98
250
|
canonicalAgentId: string;
|
|
251
|
+
resolvedAgentId?: string;
|
|
99
252
|
}) {
|
|
100
|
-
const { api, cfg, channelId, parsed, canonicalAgentId } = args;
|
|
253
|
+
const { api, cfg, channelId, parsed, canonicalAgentId, resolvedAgentId } = args;
|
|
101
254
|
const { accountId, route, peer, sessionKeyfromroute, providedOriginatingTo, extracted } = parsed;
|
|
102
255
|
|
|
103
256
|
const resolvedRoute = assertResolvedAgentRoute(
|
|
@@ -109,8 +262,9 @@ export function resolveBncrInboundConversation(args: {
|
|
|
109
262
|
}),
|
|
110
263
|
);
|
|
111
264
|
|
|
265
|
+
const targetAgentId = (resolvedAgentId || '').trim() || resolvedRoute.agentId || canonicalAgentId;
|
|
112
266
|
const baseSessionKey =
|
|
113
|
-
normalizeInboundSessionKey(sessionKeyfromroute, route,
|
|
267
|
+
normalizeInboundSessionKey(sessionKeyfromroute, route, targetAgentId) ||
|
|
114
268
|
resolvedRoute.sessionKey;
|
|
115
269
|
const taskSessionKey = withTaskSessionKey(baseSessionKey, extracted.taskKey);
|
|
116
270
|
const dispatchSessionKey = taskSessionKey || baseSessionKey;
|
|
@@ -122,7 +276,12 @@ export function resolveBncrInboundConversation(args: {
|
|
|
122
276
|
accountId,
|
|
123
277
|
chatType: peer.kind,
|
|
124
278
|
route,
|
|
125
|
-
resolvedRoute,
|
|
279
|
+
originalResolvedRouteSessionKey: resolvedRoute.sessionKey,
|
|
280
|
+
resolvedRoute: {
|
|
281
|
+
...resolvedRoute,
|
|
282
|
+
agentId: targetAgentId || resolvedRoute.agentId,
|
|
283
|
+
sessionKey: baseSessionKey || resolvedRoute.sessionKey,
|
|
284
|
+
},
|
|
126
285
|
canonicalTo,
|
|
127
286
|
rawTo,
|
|
128
287
|
originatingTo,
|
|
@@ -198,40 +357,38 @@ export async function prepareBncrInboundSessionContext(args: {
|
|
|
198
357
|
agentId: resolvedRoute.agentId,
|
|
199
358
|
});
|
|
200
359
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
fileName,
|
|
228
|
-
);
|
|
229
|
-
mediaPath = saved.path;
|
|
230
|
-
} else if (mediaPathFromTransfer && fs.existsSync(mediaPathFromTransfer)) {
|
|
231
|
-
mediaPath = mediaPathFromTransfer;
|
|
232
|
-
}
|
|
360
|
+
const parsedMediaItems = Array.isArray(parsed.mediaItems) ? parsed.mediaItems : [];
|
|
361
|
+
const fallbackMediaItems =
|
|
362
|
+
parsedMediaItems.length > 0
|
|
363
|
+
? parsedMediaItems
|
|
364
|
+
: [
|
|
365
|
+
{
|
|
366
|
+
...(mediaPathFromTransfer ? { path: mediaPathFromTransfer } : {}),
|
|
367
|
+
...(mediaBase64 ? { base64: mediaBase64 } : {}),
|
|
368
|
+
...(mimeType ? { mimeType } : {}),
|
|
369
|
+
...(fileName ? { fileName } : {}),
|
|
370
|
+
...(msgType ? { type: msgType } : {}),
|
|
371
|
+
},
|
|
372
|
+
];
|
|
373
|
+
const mediaItems = (
|
|
374
|
+
await Promise.all(
|
|
375
|
+
fallbackMediaItems.map((item) =>
|
|
376
|
+
saveBncrInboundMediaItem({
|
|
377
|
+
api,
|
|
378
|
+
item,
|
|
379
|
+
}),
|
|
380
|
+
),
|
|
381
|
+
)
|
|
382
|
+
).filter((item): item is NonNullable<typeof item> => Boolean(item));
|
|
383
|
+
const primaryMedia = mediaItems[0];
|
|
384
|
+
const mediaPath = primaryMedia?.path;
|
|
385
|
+
const mediaContentType = primaryMedia?.contentType || mimeType;
|
|
233
386
|
|
|
234
|
-
const rawBody =
|
|
387
|
+
const rawBody = resolveBncrInboundRawBody({
|
|
388
|
+
extractedText: extracted.text,
|
|
389
|
+
msgType,
|
|
390
|
+
mediaItems,
|
|
391
|
+
});
|
|
235
392
|
const body = formatOpenClawAgentEnvelope(api, {
|
|
236
393
|
channel: 'Bncr',
|
|
237
394
|
from: `${platform}:${groupId}:${userId}`,
|
|
@@ -246,6 +403,7 @@ export async function prepareBncrInboundSessionContext(args: {
|
|
|
246
403
|
|
|
247
404
|
return {
|
|
248
405
|
storePath,
|
|
406
|
+
mediaItems,
|
|
249
407
|
mediaPath,
|
|
250
408
|
mediaContentType,
|
|
251
409
|
rawBody,
|
|
@@ -272,15 +430,18 @@ export async function prepareBncrInboundDispatch(args: {
|
|
|
272
430
|
cfg: BncrInboundConfig;
|
|
273
431
|
parsed: ParsedInbound;
|
|
274
432
|
canonicalAgentId: string;
|
|
433
|
+
resolvedAgentId?: string;
|
|
275
434
|
rememberSessionRoute: BncrRememberSessionRoute;
|
|
276
435
|
}) {
|
|
277
|
-
const { api, channelId, cfg, parsed, canonicalAgentId, rememberSessionRoute } =
|
|
436
|
+
const { api, channelId, cfg, parsed, canonicalAgentId, resolvedAgentId, rememberSessionRoute } =
|
|
437
|
+
args;
|
|
278
438
|
const resolution = resolveBncrInboundConversation({
|
|
279
439
|
api,
|
|
280
440
|
cfg,
|
|
281
441
|
channelId,
|
|
282
442
|
parsed,
|
|
283
443
|
canonicalAgentId,
|
|
444
|
+
resolvedAgentId,
|
|
284
445
|
});
|
|
285
446
|
const prepared = await prepareBncrInboundSessionContext({
|
|
286
447
|
api,
|
|
@@ -290,8 +451,19 @@ export async function prepareBncrInboundDispatch(args: {
|
|
|
290
451
|
rememberSessionRoute,
|
|
291
452
|
});
|
|
292
453
|
const replyRouteFact = buildBncrInboundReplyRouteFact(resolution);
|
|
293
|
-
const senderIdForContext = parsed.clientId || resolution.canonicalTo;
|
|
294
|
-
const senderDisplayName = parsed.
|
|
454
|
+
const senderIdForContext = parsed.userId || parsed.clientId || resolution.canonicalTo;
|
|
455
|
+
const senderDisplayName = parsed.userName || resolution.canonicalTo;
|
|
456
|
+
const isBncrNativeCommand =
|
|
457
|
+
parseBncrNativeCommand(parsed.extracted.text, {
|
|
458
|
+
allowBareWhoami: parsed.isAdmin !== true,
|
|
459
|
+
}) !== null;
|
|
460
|
+
const isOpenClawNativeCommand =
|
|
461
|
+
!isBncrNativeCommand &&
|
|
462
|
+
hasControlCommand(parsed.extracted.text, cfg as Parameters<typeof hasControlCommand>[1]);
|
|
463
|
+
const ownerAllowFrom =
|
|
464
|
+
parsed.isAdmin === true && isOpenClawNativeCommand && senderIdForContext
|
|
465
|
+
? [senderIdForContext]
|
|
466
|
+
: undefined;
|
|
295
467
|
|
|
296
468
|
return {
|
|
297
469
|
resolution,
|
|
@@ -299,5 +471,8 @@ export async function prepareBncrInboundDispatch(args: {
|
|
|
299
471
|
replyRouteFact,
|
|
300
472
|
senderIdForContext,
|
|
301
473
|
senderDisplayName,
|
|
474
|
+
...(ownerAllowFrom ? { ownerAllowFrom } : {}),
|
|
475
|
+
bridgeSenderId: parsed.bridgeId || parsed.clientId,
|
|
476
|
+
bridgeSenderName: parsed.bridgeName || 'Bncr',
|
|
302
477
|
} satisfies BncrInboundPreparation;
|
|
303
478
|
}
|