@xmoxmo/bncr 0.5.5 → 0.5.6
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/src/channel.ts +48 -7
- package/src/core/outbox-entry-builders.ts +7 -0
- package/src/messaging/inbound/native-command.ts +7 -3
- package/src/messaging/inbound/scene-admin.ts +181 -5
- package/src/messaging/outbound/marker-parser.ts +154 -0
- package/src/messaging/outbound/media.ts +5 -14
- package/src/messaging/outbound/reply-enqueue-media.ts +4 -0
- package/src/messaging/outbound/reply-enqueue.ts +25 -3
- package/src/messaging/outbound/send-params.ts +43 -10
- package/src/messaging/outbound/send.ts +6 -0
- package/src/plugin/bridge-file-transfer-push-facade.ts +3 -1
- package/src/plugin/bridge-media-facade.ts +2 -1
- package/src/plugin/channel-plugin-surface-group.ts +30 -0
- package/src/plugin/channel-runtime-builders-delivery.ts +2 -0
- package/src/plugin/channel-runtime-types.ts +8 -0
- package/src/plugin/channel-send.ts +197 -94
- package/src/plugin/file-transfer-orchestrator.ts +105 -3
- package/src/plugin/file-transfer-setup.ts +1 -1
- package/src/plugin/media-orchestrators-runtime-group.ts +2 -0
- package/src/plugin/outbound.ts +4 -0
|
@@ -8,11 +8,6 @@ function asString(v: unknown, fallback = ''): string {
|
|
|
8
8
|
return String(v);
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
function isAudioMimeType(mimeType?: string): boolean {
|
|
12
|
-
const mt = asString(mimeType || '').toLowerCase();
|
|
13
|
-
return mt.startsWith('audio/');
|
|
14
|
-
}
|
|
15
|
-
|
|
16
11
|
function isBncrOutboundMessageType(value: string): value is BncrOutboundMessageType {
|
|
17
12
|
return (
|
|
18
13
|
value === 'text' ||
|
|
@@ -43,11 +38,7 @@ export function resolveBncrOutboundMessageType(params: {
|
|
|
43
38
|
const isStandard = isBncrOutboundMessageType(hinted);
|
|
44
39
|
|
|
45
40
|
if (hasPayload && major === 'text' && (hinted === 'text' || !isStandard)) return 'file';
|
|
46
|
-
if (hinted === 'voice')
|
|
47
|
-
if (isAudioMimeType(mt)) return 'voice';
|
|
48
|
-
if (isMimeMajorOutboundMessageType(major)) return major;
|
|
49
|
-
return 'file';
|
|
50
|
-
}
|
|
41
|
+
if (hinted === 'voice') return 'voice';
|
|
51
42
|
if (isStandard) return hinted;
|
|
52
43
|
if (isMimeMajorOutboundMessageType(major)) return major;
|
|
53
44
|
return 'file';
|
|
@@ -61,7 +52,7 @@ export function buildBncrMediaOutboundFrame(params: {
|
|
|
61
52
|
mode: 'base64' | 'chunk';
|
|
62
53
|
mimeType?: string;
|
|
63
54
|
fileName?: string;
|
|
64
|
-
|
|
55
|
+
base64?: string;
|
|
65
56
|
path?: string;
|
|
66
57
|
};
|
|
67
58
|
mediaUrl: string;
|
|
@@ -87,17 +78,17 @@ export function buildBncrMediaOutboundFrame(params: {
|
|
|
87
78
|
type: resolveBncrOutboundMessageType({
|
|
88
79
|
mimeType: params.media.mimeType,
|
|
89
80
|
fileName: params.media.fileName,
|
|
90
|
-
hasPayload: !!(params.media.path || params.media.
|
|
81
|
+
hasPayload: !!(params.media.path || params.media.base64),
|
|
91
82
|
hintedType: params.hintedType,
|
|
92
83
|
}),
|
|
93
84
|
kind: params.kind,
|
|
94
85
|
mimeType: params.media.mimeType || '',
|
|
95
86
|
msg: params.mediaMsg,
|
|
96
87
|
path: params.media.path || params.mediaUrl,
|
|
97
|
-
base64: params.media.
|
|
88
|
+
base64: params.media.base64 || '',
|
|
98
89
|
fileName: params.fileName,
|
|
99
90
|
transferMode: params.media.mode,
|
|
100
|
-
...(params.extra ? {
|
|
91
|
+
...(params.extra ? { ...params.extra } : {}),
|
|
101
92
|
},
|
|
102
93
|
ts: params.now,
|
|
103
94
|
};
|
|
@@ -24,6 +24,7 @@ export function enqueueReplyMediaFallbackTextEntry(
|
|
|
24
24
|
sessionKey: string;
|
|
25
25
|
route: BncrRoute;
|
|
26
26
|
text: string;
|
|
27
|
+
extra?: Record<string, unknown>;
|
|
27
28
|
kind?: 'tool' | 'block' | 'final';
|
|
28
29
|
replyToId?: string;
|
|
29
30
|
replyTargetPolicy?: OutboundReplyTargetPolicy;
|
|
@@ -61,6 +62,7 @@ export function enqueueReplyMediaFileTransferEntry(
|
|
|
61
62
|
text: string;
|
|
62
63
|
asVoice: boolean;
|
|
63
64
|
audioAsVoice: boolean;
|
|
65
|
+
downloadMedia?: boolean;
|
|
64
66
|
type?: string;
|
|
65
67
|
extra?: Record<string, unknown>;
|
|
66
68
|
kind?: 'tool' | 'block' | 'final';
|
|
@@ -86,6 +88,7 @@ export function enqueueReplyMediaFileTransferEntry(
|
|
|
86
88
|
text: params.text,
|
|
87
89
|
asVoice: params.asVoice,
|
|
88
90
|
audioAsVoice: params.audioAsVoice,
|
|
91
|
+
downloadMedia: params.downloadMedia,
|
|
89
92
|
type: params.type,
|
|
90
93
|
extra: params.extra,
|
|
91
94
|
kind: params.kind,
|
|
@@ -133,6 +136,7 @@ export function enqueueSingleReplyMediaEntry(
|
|
|
133
136
|
normalizedText: params.normalizedText,
|
|
134
137
|
asVoice: params.params.payload.asVoice,
|
|
135
138
|
audioAsVoice: params.params.payload.audioAsVoice,
|
|
139
|
+
downloadMedia: params.params.payload.downloadMedia,
|
|
136
140
|
type: params.params.payload.type,
|
|
137
141
|
extra: params.params.payload.extra,
|
|
138
142
|
kind: params.params.payload.kind,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { BncrRoute, OutboxEntry } from '../../core/types.ts';
|
|
2
|
+
import { extractConsumptionFields, parseBncrMarker } from './marker-parser.ts';
|
|
2
3
|
import { hasReplyMediaEntries } from './reply-enqueue-media.ts';
|
|
3
4
|
import type { OutboundReplyTargetPolicy } from './reply-target-policy.ts';
|
|
4
5
|
import { normalizeOutboundReplyToId } from './reply-target-policy.ts';
|
|
@@ -18,6 +19,7 @@ export type ReplyPayloadInput = {
|
|
|
18
19
|
mediaUrls?: string[];
|
|
19
20
|
asVoice?: boolean;
|
|
20
21
|
audioAsVoice?: boolean;
|
|
22
|
+
downloadMedia?: boolean;
|
|
21
23
|
type?: string;
|
|
22
24
|
extra?: Record<string, unknown>;
|
|
23
25
|
kind?: 'tool' | 'block' | 'final';
|
|
@@ -31,6 +33,7 @@ export type NormalizedReplyPayload = {
|
|
|
31
33
|
mediaList: string[];
|
|
32
34
|
asVoice: boolean;
|
|
33
35
|
audioAsVoice: boolean;
|
|
36
|
+
downloadMedia?: boolean;
|
|
34
37
|
type?: string;
|
|
35
38
|
extra?: Record<string, unknown>;
|
|
36
39
|
kind?: 'tool' | 'block' | 'final';
|
|
@@ -65,6 +68,7 @@ export type ReplyMediaFileTransferParams = {
|
|
|
65
68
|
normalizedText: string;
|
|
66
69
|
asVoice: boolean;
|
|
67
70
|
audioAsVoice: boolean;
|
|
71
|
+
downloadMedia?: boolean;
|
|
68
72
|
type?: string;
|
|
69
73
|
extra?: Record<string, unknown>;
|
|
70
74
|
kind?: 'tool' | 'block' | 'final';
|
|
@@ -127,6 +131,7 @@ export function buildReplyTextOutboxEntry(
|
|
|
127
131
|
kind?: 'tool' | 'block' | 'final';
|
|
128
132
|
replyToId: string;
|
|
129
133
|
replyTargetPolicy: OutboundReplyTargetPolicy;
|
|
134
|
+
extra?: Record<string, unknown>;
|
|
130
135
|
},
|
|
131
136
|
helpers: {
|
|
132
137
|
buildTextOutboxEntry: (args: {
|
|
@@ -137,6 +142,7 @@ export function buildReplyTextOutboxEntry(
|
|
|
137
142
|
kind?: 'tool' | 'block' | 'final';
|
|
138
143
|
replyToId?: string;
|
|
139
144
|
replyTargetPolicy?: OutboundReplyTargetPolicy;
|
|
145
|
+
extra?: Record<string, unknown>;
|
|
140
146
|
}) => OutboxEntry;
|
|
141
147
|
},
|
|
142
148
|
): OutboxEntry {
|
|
@@ -148,6 +154,7 @@ export function buildReplyTextOutboxEntry(
|
|
|
148
154
|
kind: params.kind,
|
|
149
155
|
replyToId: params.replyToId || undefined,
|
|
150
156
|
replyTargetPolicy: params.replyTargetPolicy,
|
|
157
|
+
extra: params.extra,
|
|
151
158
|
});
|
|
152
159
|
}
|
|
153
160
|
|
|
@@ -168,10 +175,11 @@ export function enqueueReplyTextEntry(
|
|
|
168
175
|
kind?: 'tool' | 'block' | 'final';
|
|
169
176
|
replyToId?: string;
|
|
170
177
|
replyTargetPolicy?: OutboundReplyTargetPolicy;
|
|
178
|
+
extra?: Record<string, unknown>;
|
|
171
179
|
}) => OutboxEntry;
|
|
172
180
|
},
|
|
173
181
|
): void {
|
|
174
|
-
if (!params.payload.text) return;
|
|
182
|
+
if (!params.payload.text && !params.payload.extra) return;
|
|
175
183
|
|
|
176
184
|
helpers.enqueueOutbound(
|
|
177
185
|
buildReplyTextOutboxEntry(
|
|
@@ -180,6 +188,7 @@ export function enqueueReplyTextEntry(
|
|
|
180
188
|
sessionKey: params.sessionKey,
|
|
181
189
|
route: params.route,
|
|
182
190
|
text: params.payload.text,
|
|
191
|
+
extra: params.payload.extra,
|
|
183
192
|
kind: params.payload.kind,
|
|
184
193
|
replyToId: params.payload.replyToId,
|
|
185
194
|
replyTargetPolicy: params.payload.replyTargetPolicy,
|
|
@@ -257,12 +266,24 @@ export function normalizeReplyPayload(
|
|
|
257
266
|
helpers: { asString: (value: unknown, fallback?: string) => string },
|
|
258
267
|
options?: { replyTargetPolicy?: OutboundReplyTargetPolicy },
|
|
259
268
|
): NormalizedReplyPayload {
|
|
260
|
-
const
|
|
269
|
+
const rawText = helpers.asString(payload?.text || '').trim();
|
|
270
|
+
// Parse [BncrParam:...] marker from reply text
|
|
271
|
+
const { cleanText, params: markerParams } = parseBncrMarker(rawText);
|
|
272
|
+
const text = cleanText;
|
|
261
273
|
const mediaUrl = helpers.asString(payload?.mediaUrl || '').trim();
|
|
262
274
|
const mediaUrls = Array.isArray(payload?.mediaUrls)
|
|
263
275
|
? payload.mediaUrls.map((v) => helpers.asString(v || '').trim()).filter(Boolean)
|
|
264
276
|
: undefined;
|
|
265
277
|
const type = helpers.asString(payload?.type || '').trim();
|
|
278
|
+
// Merge marker params with payload extra (marker wins), strip consumption fields
|
|
279
|
+
const mergedExtra: Record<string, unknown> = {
|
|
280
|
+
...(payload?.extra ? { ...payload.extra } : {}),
|
|
281
|
+
...markerParams,
|
|
282
|
+
};
|
|
283
|
+
const { remaining } = extractConsumptionFields(
|
|
284
|
+
Object.keys(mergedExtra).length > 0 ? mergedExtra : undefined,
|
|
285
|
+
);
|
|
286
|
+
const finalExtra = Object.keys(remaining).length > 0 ? remaining : undefined;
|
|
266
287
|
return {
|
|
267
288
|
text,
|
|
268
289
|
mediaUrl,
|
|
@@ -270,8 +291,9 @@ export function normalizeReplyPayload(
|
|
|
270
291
|
mediaList: mediaUrls?.length ? mediaUrls : mediaUrl ? [mediaUrl] : [],
|
|
271
292
|
asVoice: payload?.asVoice === true,
|
|
272
293
|
audioAsVoice: payload?.audioAsVoice === true,
|
|
294
|
+
downloadMedia: payload?.downloadMedia === true ? true : undefined,
|
|
273
295
|
...(type ? { type } : {}),
|
|
274
|
-
...(
|
|
296
|
+
...(finalExtra ? { extra: finalExtra } : {}),
|
|
275
297
|
kind: payload?.kind,
|
|
276
298
|
replyTargetPolicy: options?.replyTargetPolicy ?? 'agent-default',
|
|
277
299
|
replyToId: normalizeOutboundReplyToId({
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { normalizeAccountId } from '../../core/accounts.ts';
|
|
2
2
|
import { readOpenClawBooleanParam, readOpenClawStringParam } from '../../openclaw/sdk-helpers.ts';
|
|
3
|
+
import { extractConsumptionFields, parseBncrMarker } from './marker-parser.ts';
|
|
3
4
|
|
|
4
5
|
export type NormalizedBncrSendParams = {
|
|
5
6
|
to: string;
|
|
@@ -11,6 +12,7 @@ export type NormalizedBncrSendParams = {
|
|
|
11
12
|
asVoice: boolean;
|
|
12
13
|
audioAsVoice: boolean;
|
|
13
14
|
type?: string;
|
|
15
|
+
downloadMedia?: boolean;
|
|
14
16
|
extra?: Record<string, unknown>;
|
|
15
17
|
};
|
|
16
18
|
|
|
@@ -28,8 +30,13 @@ export function normalizeBncrSendParams(input: {
|
|
|
28
30
|
readOpenClawStringParam(paramsObj, 'accountId') ?? input.accountId,
|
|
29
31
|
);
|
|
30
32
|
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
+
const rawMessage = readOpenClawStringParam(paramsObj, 'message', { allowEmpty: true }) ?? '';
|
|
34
|
+
const rawCaption = readOpenClawStringParam(paramsObj, 'caption', { allowEmpty: true }) ?? '';
|
|
35
|
+
// Parse [BncrParam:...] markers from message and caption text
|
|
36
|
+
const { cleanText: parsedMessage, params: msgMarker } = parseBncrMarker(rawMessage);
|
|
37
|
+
const { cleanText: parsedCaption, params: capMarker } = parseBncrMarker(rawCaption);
|
|
38
|
+
const message = parsedMessage;
|
|
39
|
+
const caption = parsedCaption;
|
|
33
40
|
const mediaUrl =
|
|
34
41
|
readOpenClawStringParam(paramsObj, 'media', { trim: false }) ??
|
|
35
42
|
readOpenClawStringParam(paramsObj, 'path', { trim: false }) ??
|
|
@@ -53,18 +60,43 @@ export function normalizeBncrSendParams(input: {
|
|
|
53
60
|
const audioAsVoice = readOpenClawBooleanParam(paramsObj, 'audioAsVoice') ?? false;
|
|
54
61
|
const type = readOpenClawStringParam(paramsObj, 'type') || undefined;
|
|
55
62
|
const rawExtra = paramsObj.extra;
|
|
56
|
-
const
|
|
63
|
+
const paramsExtra = isPlainObject(rawExtra) ? { ...rawExtra } : {};
|
|
64
|
+
// Merge marker params into extra (marker takes priority over direct extra)
|
|
65
|
+
const mergedExtra = { ...paramsExtra, ...msgMarker, ...capMarker };
|
|
66
|
+
const extra = Object.keys(mergedExtra).length > 0 ? mergedExtra : undefined;
|
|
67
|
+
|
|
68
|
+
// Extract consumption fields from merged extra - marker-specified values override params
|
|
69
|
+
const { consumed, remaining } = extractConsumptionFields(
|
|
70
|
+
Object.keys(mergedExtra).length > 0 ? mergedExtra : undefined,
|
|
71
|
+
);
|
|
72
|
+
const effectiveAsVoice = consumed.asVoice ?? asVoice;
|
|
73
|
+
const effectiveAudioAsVoice = consumed.audioAsVoice ?? audioAsVoice;
|
|
74
|
+
const effectiveDownloadMedia = consumed.downloadMedia === true;
|
|
75
|
+
const effectiveType = consumed.type ?? type;
|
|
76
|
+
const cleanedExtra = Object.keys(remaining).length > 0 ? remaining : undefined;
|
|
57
77
|
|
|
58
78
|
const hasMedia = Boolean(mediaUrl || dedupedMediaUrls?.length);
|
|
59
79
|
|
|
60
|
-
if (
|
|
80
|
+
if (effectiveAsVoice && !hasMedia) throw new Error('send voice requires media path');
|
|
61
81
|
|
|
62
82
|
const normalizedMessage = hasMedia ? '' : message || caption || '';
|
|
63
83
|
const normalizedCaption = hasMedia ? caption || message || '' : '';
|
|
64
84
|
|
|
65
|
-
if (!normalizedMessage.trim() && !normalizedCaption.trim() && !hasMedia) {
|
|
66
|
-
throw new Error('send requires message or
|
|
85
|
+
if (!normalizedMessage.trim() && !normalizedCaption.trim() && !hasMedia && !extra) {
|
|
86
|
+
throw new Error('send requires message, media, or extra params');
|
|
67
87
|
}
|
|
88
|
+
console.log(
|
|
89
|
+
'[bncr] normalizeBncrSendParams rawMessage=' +
|
|
90
|
+
JSON.stringify(rawMessage) +
|
|
91
|
+
'|msgMarker=' +
|
|
92
|
+
JSON.stringify(msgMarker) +
|
|
93
|
+
'|consumed=' +
|
|
94
|
+
JSON.stringify(consumed) +
|
|
95
|
+
'|effectiveDownloadMedia=' +
|
|
96
|
+
effectiveDownloadMedia +
|
|
97
|
+
'|mediaUrl=' +
|
|
98
|
+
JSON.stringify(mediaUrl),
|
|
99
|
+
);
|
|
68
100
|
|
|
69
101
|
return {
|
|
70
102
|
to,
|
|
@@ -73,9 +105,10 @@ export function normalizeBncrSendParams(input: {
|
|
|
73
105
|
caption: normalizedCaption,
|
|
74
106
|
mediaUrl: dedupedMediaUrls?.length ? undefined : mediaUrl || undefined,
|
|
75
107
|
mediaUrls: dedupedMediaUrls,
|
|
76
|
-
asVoice,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
...(
|
|
108
|
+
asVoice: effectiveAsVoice,
|
|
109
|
+
...(effectiveDownloadMedia ? { downloadMedia: true } : {}),
|
|
110
|
+
audioAsVoice: effectiveAudioAsVoice,
|
|
111
|
+
...(effectiveType ? { type: effectiveType } : {}),
|
|
112
|
+
...(cleanedExtra ? { extra: cleanedExtra } : {}),
|
|
80
113
|
};
|
|
81
114
|
}
|
|
@@ -6,6 +6,7 @@ function normalizeReplyKind(value: unknown): 'tool' | 'block' | 'final' | undefi
|
|
|
6
6
|
|
|
7
7
|
export async function sendBncrText(params: {
|
|
8
8
|
channelId: string;
|
|
9
|
+
extra?: Record<string, unknown>;
|
|
9
10
|
accountId: string;
|
|
10
11
|
to: string;
|
|
11
12
|
text: string;
|
|
@@ -26,6 +27,7 @@ export async function sendBncrText(params: {
|
|
|
26
27
|
mediaUrl?: string;
|
|
27
28
|
mediaUrls?: string[];
|
|
28
29
|
kind?: 'tool' | 'block' | 'final';
|
|
30
|
+
extra?: Record<string, unknown>;
|
|
29
31
|
replyToId?: string;
|
|
30
32
|
};
|
|
31
33
|
mediaLocalRoots?: readonly string[];
|
|
@@ -43,6 +45,7 @@ export async function sendBncrText(params: {
|
|
|
43
45
|
text: params.text,
|
|
44
46
|
kind: normalizeReplyKind(params.kind),
|
|
45
47
|
replyToId: params.replyToId,
|
|
48
|
+
extra: params.extra,
|
|
46
49
|
},
|
|
47
50
|
mediaLocalRoots: params.mediaLocalRoots,
|
|
48
51
|
});
|
|
@@ -64,6 +67,7 @@ export async function sendBncrMedia(params: {
|
|
|
64
67
|
asVoice?: boolean;
|
|
65
68
|
audioAsVoice?: boolean;
|
|
66
69
|
type?: string;
|
|
70
|
+
downloadMedia?: boolean;
|
|
67
71
|
extra?: Record<string, unknown>;
|
|
68
72
|
kind?: string;
|
|
69
73
|
replyToId?: string;
|
|
@@ -84,6 +88,7 @@ export async function sendBncrMedia(params: {
|
|
|
84
88
|
asVoice?: boolean;
|
|
85
89
|
audioAsVoice?: boolean;
|
|
86
90
|
type?: string;
|
|
91
|
+
downloadMedia?: boolean;
|
|
87
92
|
extra?: Record<string, unknown>;
|
|
88
93
|
kind?: 'tool' | 'block' | 'final';
|
|
89
94
|
replyToId?: string;
|
|
@@ -105,6 +110,7 @@ export async function sendBncrMedia(params: {
|
|
|
105
110
|
mediaUrls: params.mediaUrls?.length ? params.mediaUrls : undefined,
|
|
106
111
|
asVoice: params.asVoice === true ? true : undefined,
|
|
107
112
|
audioAsVoice: params.audioAsVoice === true ? true : undefined,
|
|
113
|
+
downloadMedia: params.downloadMedia === true ? true : undefined,
|
|
108
114
|
type: params.type,
|
|
109
115
|
extra: params.extra,
|
|
110
116
|
kind: normalizeReplyKind(params.kind),
|
|
@@ -19,11 +19,12 @@ export function createBncrBridgeFileTransferPushFacade(runtime: {
|
|
|
19
19
|
route: BncrRoute;
|
|
20
20
|
mediaUrl: string;
|
|
21
21
|
mediaLocalRoots?: readonly string[];
|
|
22
|
+
downloadMedia?: boolean;
|
|
22
23
|
}) => Promise<{
|
|
23
24
|
mode: 'base64' | 'chunk';
|
|
24
25
|
mimeType?: string;
|
|
25
26
|
fileName?: string;
|
|
26
|
-
|
|
27
|
+
base64?: string;
|
|
27
28
|
path?: string;
|
|
28
29
|
}>;
|
|
29
30
|
buildFileTransferOutboundFrame: (params: {
|
|
@@ -88,6 +89,7 @@ export function createBncrBridgeFileTransferPushFacade(runtime: {
|
|
|
88
89
|
mediaLocalRoots: Array.isArray(args.meta.mediaLocalRoots)
|
|
89
90
|
? args.meta.mediaLocalRoots.filter((v): v is string => typeof v === 'string')
|
|
90
91
|
: undefined,
|
|
92
|
+
downloadMedia: args.meta.downloadMedia === true,
|
|
91
93
|
});
|
|
92
94
|
const frame = runtime.buildFileTransferOutboundFrame({
|
|
93
95
|
entry: args.entry,
|
|
@@ -48,7 +48,7 @@ export function createBncrBridgeMediaFacade(runtime: {
|
|
|
48
48
|
mode: 'base64' | 'chunk';
|
|
49
49
|
mimeType?: string;
|
|
50
50
|
fileName?: string;
|
|
51
|
-
|
|
51
|
+
base64?: string;
|
|
52
52
|
path?: string;
|
|
53
53
|
}>;
|
|
54
54
|
};
|
|
@@ -161,6 +161,7 @@ export function createBncrBridgeMediaFacade(runtime: {
|
|
|
161
161
|
const loadOutboundTransferMedia = async (params: {
|
|
162
162
|
mediaUrl: string;
|
|
163
163
|
mediaLocalRoots?: readonly string[];
|
|
164
|
+
downloadMedia?: boolean;
|
|
164
165
|
}): Promise<{
|
|
165
166
|
loaded: OpenClawLoadedMedia;
|
|
166
167
|
size: number;
|
|
@@ -10,6 +10,7 @@ import { normalizeBncrSendParams } from '../messaging/outbound/send-params.ts';
|
|
|
10
10
|
import type { OpenClawChannelToolSend, openClawJsonResult } from '../openclaw/sdk-helpers.ts';
|
|
11
11
|
import type {
|
|
12
12
|
BncrChannelConfigRoot,
|
|
13
|
+
BncrSceneRecord,
|
|
13
14
|
BncrStatusRuntimeSnapshot,
|
|
14
15
|
BncrVerifiedTarget,
|
|
15
16
|
ChannelMessageActionAdapter,
|
|
@@ -37,6 +38,7 @@ type PluginSurfaceResult = ReturnType<typeof openClawJsonResult>;
|
|
|
37
38
|
|
|
38
39
|
export function createBncrChannelPluginSurfaceGroup(runtime: {
|
|
39
40
|
channelId: string;
|
|
41
|
+
sceneRegistry: Map<string, BncrSceneRecord>;
|
|
40
42
|
getMessageSendBridge: () => {
|
|
41
43
|
channelMessageSendText: (
|
|
42
44
|
ctx: Record<string, unknown>,
|
|
@@ -138,6 +140,32 @@ export function createBncrChannelPluginSurfaceGroup(runtime: {
|
|
|
138
140
|
throw new Error(`Action ${action} is not supported for provider ${runtime.channelId}.`);
|
|
139
141
|
}
|
|
140
142
|
const normalized = normalizeBncrSendParams({ params, accountId: accountId || '' });
|
|
143
|
+
console.log(
|
|
144
|
+
'[bncr] handleAction normalized.downloadMedia=' +
|
|
145
|
+
JSON.stringify(normalized.downloadMedia) +
|
|
146
|
+
'|mediaUrl=' +
|
|
147
|
+
JSON.stringify(normalized.mediaUrl) +
|
|
148
|
+
'|hasMedia=' +
|
|
149
|
+
Boolean(normalized.mediaUrl || normalized.mediaUrls?.length),
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
// Priority: marker → scene config → global default → false
|
|
153
|
+
if (normalized.downloadMedia === undefined) {
|
|
154
|
+
const parts = normalized.to.split(':');
|
|
155
|
+
if (parts.length >= 3) {
|
|
156
|
+
const sceneKey = `${parts[1]}:${parts[2]}`;
|
|
157
|
+
const scene = runtime.sceneRegistry.get(sceneKey);
|
|
158
|
+
if (scene?.downloadMedia === true) {
|
|
159
|
+
normalized.downloadMedia = true;
|
|
160
|
+
}
|
|
161
|
+
if (normalized.downloadMedia === undefined) {
|
|
162
|
+
const globalScene = runtime.sceneRegistry.get('__global__');
|
|
163
|
+
if (globalScene?.downloadMedia === true) {
|
|
164
|
+
normalized.downloadMedia = true;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
141
169
|
|
|
142
170
|
const toolActionBridge = runtime.getToolActionBridge();
|
|
143
171
|
const result =
|
|
@@ -152,6 +180,7 @@ export function createBncrChannelPluginSurfaceGroup(runtime: {
|
|
|
152
180
|
asVoice: normalized.asVoice,
|
|
153
181
|
audioAsVoice: normalized.audioAsVoice,
|
|
154
182
|
type: normalized.type,
|
|
183
|
+
downloadMedia: normalized.downloadMedia,
|
|
155
184
|
extra: normalized.extra,
|
|
156
185
|
mediaLocalRoots,
|
|
157
186
|
resolveVerifiedTarget: (to, accountId) =>
|
|
@@ -166,6 +195,7 @@ export function createBncrChannelPluginSurfaceGroup(runtime: {
|
|
|
166
195
|
accountId: normalized.accountId,
|
|
167
196
|
to: normalized.to,
|
|
168
197
|
text: normalized.message,
|
|
198
|
+
extra: normalized.extra,
|
|
169
199
|
mediaLocalRoots,
|
|
170
200
|
resolveVerifiedTarget: (to, accountId) =>
|
|
171
201
|
toolActionBridge.resolveVerifiedTarget(to, accountId),
|
|
@@ -333,6 +333,7 @@ export function buildBncrMediaOrchestratorsRuntime(deps: {
|
|
|
333
333
|
sessionKey: string;
|
|
334
334
|
route: BncrRoute;
|
|
335
335
|
text: string;
|
|
336
|
+
extra?: Record<string, unknown>;
|
|
336
337
|
kind?: 'tool' | 'block' | 'final';
|
|
337
338
|
replyToId?: string;
|
|
338
339
|
replyTargetPolicy?: OutboundReplyTargetPolicy;
|
|
@@ -351,6 +352,7 @@ export function buildBncrMediaOrchestratorsRuntime(deps: {
|
|
|
351
352
|
kind?: 'tool' | 'block' | 'final';
|
|
352
353
|
replyToId?: string;
|
|
353
354
|
replyTargetPolicy?: OutboundReplyTargetPolicy;
|
|
355
|
+
downloadMedia?: boolean;
|
|
354
356
|
}) => OutboxEntry;
|
|
355
357
|
rememberRecentMediaSend: (args: {
|
|
356
358
|
sessionKey: string;
|
|
@@ -63,6 +63,7 @@ export type BncrSceneRecord = {
|
|
|
63
63
|
groupReplyMode?: BncrGroupReplyMode;
|
|
64
64
|
historyLimit?: number;
|
|
65
65
|
historyForce?: boolean;
|
|
66
|
+
downloadMedia?: boolean;
|
|
66
67
|
lastSeenAt: number;
|
|
67
68
|
};
|
|
68
69
|
|
|
@@ -133,6 +134,13 @@ export type BncrChannelSendContext = {
|
|
|
133
134
|
sessionKey?: string;
|
|
134
135
|
mirror?: { sessionKey?: string };
|
|
135
136
|
threadId?: string;
|
|
137
|
+
/** Pass-through fields from host - bncr plugin does NOT consume these. */
|
|
138
|
+
forceDocument?: boolean;
|
|
139
|
+
gifPlayback?: boolean;
|
|
140
|
+
silent?: boolean;
|
|
141
|
+
downloadMedia?: boolean;
|
|
142
|
+
/** Extra key-value pairs from marker parsing or direct pass-through. */
|
|
143
|
+
extra?: Record<string, unknown>;
|
|
136
144
|
};
|
|
137
145
|
|
|
138
146
|
export type BncrRegisterRuntimeSnapshot = {
|