@xmoxmo/bncr 0.5.7 → 0.5.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/package.json +1 -1
- package/scripts/check-pack.mjs +0 -2
- package/scripts/selfcheck.mjs +0 -3
- package/src/bootstrap/runtime-loader.ts +1 -1
- package/src/channel.ts +49 -138
- package/src/core/connection-reachability.ts +1 -1
- package/src/core/logging.ts +1 -1
- package/src/core/outbox-entry-builders.ts +60 -82
- package/src/core/outbox-summary.ts +5 -39
- package/src/core/permissions.ts +1 -1
- package/src/core/register-trace.ts +2 -2
- package/src/messaging/inbound/parse.ts +1 -1
- package/src/messaging/outbound/diagnostics-debug-builders.ts +0 -15
- package/src/messaging/outbound/diagnostics.ts +0 -1
- package/src/messaging/outbound/marker-parser.ts +215 -0
- package/src/messaging/outbound/media-dedupe.ts +1 -4
- package/src/messaging/outbound/normalize-outbound-send.ts +9 -7
- package/src/messaging/outbound/reply-enqueue-media.ts +1 -184
- package/src/messaging/outbound/reply-enqueue.ts +249 -162
- package/src/openclaw/media-runtime.ts +1 -1
- package/src/plugin/bridge-media-facade.ts +0 -3
- package/src/plugin/bridge-outbox-facade.ts +0 -1
- package/src/plugin/channel-runtime-builders-delivery.ts +2 -362
- package/src/plugin/channel-runtime-builders-status.ts +0 -160
- package/src/plugin/channel-runtime-builders.ts +3 -17
- package/src/plugin/channel-runtime-constants.ts +1 -1
- package/src/plugin/channel-send.ts +5 -4
- package/src/plugin/file-transfer-orchestrator.ts +39 -2
- package/src/plugin/media-orchestrators-runtime-group.ts +20 -149
- package/src/plugin/messaging.ts +3 -3
- package/src/plugin/outbox-push.ts +1 -52
- package/src/plugin/outbox-unified-push-flow.ts +307 -0
- package/src/plugin/scene-registry.ts +1 -1
- package/src/runtime/log-dedupe.ts +3 -3
- package/src/runtime/outbound-ack-timeout.ts +2 -4
- package/src/runtime/status-snapshots.ts +2 -2
- package/src/runtime/status-worker.ts +2 -6
- package/src/core/outbox-text-push-prep.ts +0 -1
- package/src/plugin/bridge-file-transfer-push-facade.ts +0 -142
- package/src/plugin/outbox-file-push-flow.ts +0 -69
- package/src/plugin/outbox-text-push-flow.ts +0 -90
|
@@ -1,11 +1,34 @@
|
|
|
1
|
+
import { buildBncrDebugJsonMessage } from '../../core/logging.ts';
|
|
1
2
|
import type { BncrRoute, OutboxEntry } from '../../core/types.ts';
|
|
2
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
buildNormalizedSends,
|
|
5
|
+
type NormalizedOutboundSend,
|
|
6
|
+
normalizeOutboundSend,
|
|
7
|
+
} from './normalize-outbound-send.ts';
|
|
3
8
|
import { hasReplyMediaEntries } from './reply-enqueue-media.ts';
|
|
4
9
|
import type { OutboundReplyTargetPolicy } from './reply-target-policy.ts';
|
|
5
10
|
import { normalizeOutboundReplyToId } from './reply-target-policy.ts';
|
|
6
11
|
|
|
7
12
|
const MEDIA_TEXT_SPLIT_THRESHOLD = 1020;
|
|
8
13
|
|
|
14
|
+
type BuildOutboxEntryFn = (args: {
|
|
15
|
+
accountId: string;
|
|
16
|
+
sessionKey: string;
|
|
17
|
+
route: BncrRoute;
|
|
18
|
+
type?: string;
|
|
19
|
+
msg: string;
|
|
20
|
+
kind?: 'tool' | 'block' | 'final';
|
|
21
|
+
replyToId?: string;
|
|
22
|
+
replyTargetPolicy?: OutboundReplyTargetPolicy;
|
|
23
|
+
extra?: Record<string, unknown>;
|
|
24
|
+
transferMode?: 'text' | 'media';
|
|
25
|
+
mediaUrl?: string;
|
|
26
|
+
mediaLocalRoots?: readonly string[];
|
|
27
|
+
asVoice?: boolean;
|
|
28
|
+
audioAsVoice?: boolean;
|
|
29
|
+
downloadMedia?: boolean;
|
|
30
|
+
}) => OutboxEntry;
|
|
31
|
+
|
|
9
32
|
export type ReplyEnqueuePlan =
|
|
10
33
|
| { kind: 'text-only' }
|
|
11
34
|
| { kind: 'media-only'; clearText: false }
|
|
@@ -43,14 +66,6 @@ export type NormalizedReplyPayload = {
|
|
|
43
66
|
replyTargetPolicy: OutboundReplyTargetPolicy;
|
|
44
67
|
};
|
|
45
68
|
|
|
46
|
-
export type ReplyMediaEntriesParams = {
|
|
47
|
-
accountId: string;
|
|
48
|
-
sessionKey: string;
|
|
49
|
-
route: BncrRoute;
|
|
50
|
-
payload: NormalizedReplyPayload;
|
|
51
|
-
mediaLocalRoots?: readonly string[];
|
|
52
|
-
};
|
|
53
|
-
|
|
54
69
|
export type EnqueueNormalizedReplyPayloadParams = {
|
|
55
70
|
accountId: string;
|
|
56
71
|
sessionKey: string;
|
|
@@ -60,46 +75,6 @@ export type EnqueueNormalizedReplyPayloadParams = {
|
|
|
60
75
|
replyToId?: string;
|
|
61
76
|
};
|
|
62
77
|
|
|
63
|
-
export type ReplyMediaFileTransferParams = {
|
|
64
|
-
accountId: string;
|
|
65
|
-
sessionKey: string;
|
|
66
|
-
route: BncrRoute;
|
|
67
|
-
mediaUrl: string;
|
|
68
|
-
mediaLocalRoots?: readonly string[];
|
|
69
|
-
text: string;
|
|
70
|
-
normalizedText: string;
|
|
71
|
-
asVoice: boolean;
|
|
72
|
-
audioAsVoice: boolean;
|
|
73
|
-
downloadMedia?: boolean;
|
|
74
|
-
type?: string;
|
|
75
|
-
markerHasMsg?: boolean;
|
|
76
|
-
extra?: Record<string, unknown>;
|
|
77
|
-
kind?: 'tool' | 'block' | 'final';
|
|
78
|
-
replyToId: string;
|
|
79
|
-
replyTargetPolicy: OutboundReplyTargetPolicy;
|
|
80
|
-
createdAt: number;
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
export type EnqueueSingleReplyMediaEntryParams = {
|
|
84
|
-
params: ReplyMediaEntriesParams;
|
|
85
|
-
mediaUrl: string;
|
|
86
|
-
normalizedText: string;
|
|
87
|
-
text: string;
|
|
88
|
-
fallback: { text: string; reason: string } | null;
|
|
89
|
-
currentTime: number;
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
export type ReplyMediaFallbackTextEntryParams = {
|
|
93
|
-
accountId: string;
|
|
94
|
-
sessionKey: string;
|
|
95
|
-
route: BncrRoute;
|
|
96
|
-
mediaUrl: string;
|
|
97
|
-
kind?: 'tool' | 'block' | 'final';
|
|
98
|
-
replyToId: string;
|
|
99
|
-
replyTargetPolicy: OutboundReplyTargetPolicy;
|
|
100
|
-
fallback: { text: string; reason: string };
|
|
101
|
-
};
|
|
102
|
-
|
|
103
78
|
function shouldSplitReplyMediaText(payload: NormalizedReplyPayload) {
|
|
104
79
|
if (!payload.text) return false;
|
|
105
80
|
if (payload.mediaList.length > 1) return true;
|
|
@@ -118,51 +93,9 @@ export function buildReplyEnqueuePlan(payload: NormalizedReplyPayload): ReplyEnq
|
|
|
118
93
|
return { kind: 'media-only', clearText: false };
|
|
119
94
|
}
|
|
120
95
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
text: '',
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function buildReplyTextOutboxEntry(
|
|
129
|
-
params: {
|
|
130
|
-
accountId: string;
|
|
131
|
-
sessionKey: string;
|
|
132
|
-
route: BncrRoute;
|
|
133
|
-
text: string;
|
|
134
|
-
kind?: 'tool' | 'block' | 'final';
|
|
135
|
-
replyToId: string;
|
|
136
|
-
replyTargetPolicy: OutboundReplyTargetPolicy;
|
|
137
|
-
markerHasMsg?: boolean;
|
|
138
|
-
extra?: Record<string, unknown>;
|
|
139
|
-
},
|
|
140
|
-
helpers: {
|
|
141
|
-
buildTextOutboxEntry: (args: {
|
|
142
|
-
accountId: string;
|
|
143
|
-
sessionKey: string;
|
|
144
|
-
route: BncrRoute;
|
|
145
|
-
text: string;
|
|
146
|
-
kind?: 'tool' | 'block' | 'final';
|
|
147
|
-
replyToId?: string;
|
|
148
|
-
replyTargetPolicy?: OutboundReplyTargetPolicy;
|
|
149
|
-
markerHasMsg?: boolean;
|
|
150
|
-
extra?: Record<string, unknown>;
|
|
151
|
-
}) => OutboxEntry;
|
|
152
|
-
},
|
|
153
|
-
): OutboxEntry {
|
|
154
|
-
return helpers.buildTextOutboxEntry({
|
|
155
|
-
accountId: params.accountId,
|
|
156
|
-
sessionKey: params.sessionKey,
|
|
157
|
-
route: params.route,
|
|
158
|
-
text: params.text,
|
|
159
|
-
kind: params.kind,
|
|
160
|
-
replyToId: params.replyToId || undefined,
|
|
161
|
-
replyTargetPolicy: params.replyTargetPolicy,
|
|
162
|
-
extra: params.extra,
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
|
|
96
|
+
/**
|
|
97
|
+
* @deprecated Only used by native-reply-delivery.test.mjs. Not called from production code.
|
|
98
|
+
*/
|
|
166
99
|
export function enqueueReplyTextEntry(
|
|
167
100
|
params: {
|
|
168
101
|
accountId: string;
|
|
@@ -172,35 +105,34 @@ export function enqueueReplyTextEntry(
|
|
|
172
105
|
},
|
|
173
106
|
helpers: {
|
|
174
107
|
enqueueOutbound: (entry: OutboxEntry) => void;
|
|
175
|
-
|
|
108
|
+
buildOutboxEntry: (args: {
|
|
176
109
|
accountId: string;
|
|
177
110
|
sessionKey: string;
|
|
178
111
|
route: BncrRoute;
|
|
179
|
-
|
|
112
|
+
type?: string;
|
|
113
|
+
msg: string;
|
|
180
114
|
kind?: 'tool' | 'block' | 'final';
|
|
181
115
|
replyToId?: string;
|
|
182
116
|
replyTargetPolicy?: OutboundReplyTargetPolicy;
|
|
183
|
-
markerHasMsg?: boolean;
|
|
184
117
|
extra?: Record<string, unknown>;
|
|
118
|
+
transferMode?: 'text' | 'media';
|
|
185
119
|
}) => OutboxEntry;
|
|
186
120
|
},
|
|
187
121
|
): void {
|
|
188
122
|
if (!params.payload.text && !params.payload.extra) return;
|
|
189
123
|
|
|
190
124
|
helpers.enqueueOutbound(
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
{ buildTextOutboxEntry: helpers.buildTextOutboxEntry },
|
|
203
|
-
),
|
|
125
|
+
helpers.buildOutboxEntry({
|
|
126
|
+
accountId: params.accountId,
|
|
127
|
+
sessionKey: params.sessionKey,
|
|
128
|
+
route: params.route,
|
|
129
|
+
type: 'text',
|
|
130
|
+
msg: params.payload.text,
|
|
131
|
+
extra: params.payload.extra,
|
|
132
|
+
kind: params.payload.kind,
|
|
133
|
+
replyToId: params.payload.replyToId,
|
|
134
|
+
replyTargetPolicy: params.payload.replyTargetPolicy,
|
|
135
|
+
}),
|
|
204
136
|
);
|
|
205
137
|
}
|
|
206
138
|
|
|
@@ -211,71 +143,209 @@ export function enqueueReplyTextEntry(
|
|
|
211
143
|
* main payload). Every entry goes through `buildReplyEnqueuePlan` → the same
|
|
212
144
|
* text/media dispatch. There is no separate pre-send or main-send branch.
|
|
213
145
|
*/
|
|
214
|
-
function
|
|
146
|
+
function buildSendPayload(
|
|
147
|
+
entry: NormalizedOutboundSend,
|
|
148
|
+
parent: NormalizedReplyPayload,
|
|
149
|
+
): NormalizedReplyPayload {
|
|
150
|
+
return {
|
|
151
|
+
text: entry.text,
|
|
152
|
+
mediaUrl: entry.mediaUrl || '',
|
|
153
|
+
mediaUrls: entry.mediaUrls,
|
|
154
|
+
mediaList: entry.mediaUrl ? [entry.mediaUrl] : entry.mediaUrls?.length ? entry.mediaUrls : [],
|
|
155
|
+
asVoice: entry.asVoice,
|
|
156
|
+
audioAsVoice: entry.audioAsVoice,
|
|
157
|
+
downloadMedia: entry.downloadMedia,
|
|
158
|
+
type: entry.type,
|
|
159
|
+
extra: entry.extra,
|
|
160
|
+
kind: entry.kind,
|
|
161
|
+
replyToId: entry.replyToId ?? '',
|
|
162
|
+
replyTargetPolicy: parent.replyTargetPolicy,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Unified outbound dispatch: single exit point for all sends.
|
|
168
|
+
*
|
|
169
|
+
* Iterates `buildNormalizedSends` entries and dispatches each through
|
|
170
|
+
* the existing text or media helpers internally. External callers only
|
|
171
|
+
* see one unified function — they do not choose the branch.
|
|
172
|
+
*/
|
|
173
|
+
function enqueueUnifiedOutbound(
|
|
215
174
|
params: EnqueueNormalizedReplyPayloadParams,
|
|
216
175
|
helpers: {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
176
|
+
enqueueOutbound: (entry: OutboxEntry) => void;
|
|
177
|
+
buildOutboxEntry: BuildOutboxEntryFn;
|
|
178
|
+
tryBuildMediaDedupeFallback: (args: {
|
|
220
179
|
sessionKey: string;
|
|
221
|
-
|
|
222
|
-
|
|
180
|
+
mediaUrl: string;
|
|
181
|
+
text: string;
|
|
182
|
+
replyToId: string;
|
|
183
|
+
currentTime: number;
|
|
184
|
+
}) => {
|
|
185
|
+
text: string;
|
|
186
|
+
reason: 'same-text-sent-checkmark' | 'text-changed-downgrade';
|
|
187
|
+
} | null;
|
|
188
|
+
rememberRecentMediaSend: (args: {
|
|
189
|
+
sessionKey: string;
|
|
190
|
+
mediaUrl: string;
|
|
191
|
+
text: string;
|
|
192
|
+
replyToId: string;
|
|
193
|
+
createdAt: number;
|
|
223
194
|
}) => void;
|
|
195
|
+
logInfo: (
|
|
196
|
+
scope: string | undefined,
|
|
197
|
+
message: string,
|
|
198
|
+
options?: { debugOnly?: boolean },
|
|
199
|
+
) => void;
|
|
224
200
|
},
|
|
225
201
|
): void {
|
|
226
202
|
for (const entry of buildNormalizedSends(params.payload)) {
|
|
227
|
-
|
|
228
|
-
const sendPayload: NormalizedReplyPayload = {
|
|
229
|
-
text: entry.text,
|
|
230
|
-
mediaUrl: entry.mediaUrl || '',
|
|
231
|
-
mediaUrls: entry.mediaUrls,
|
|
232
|
-
mediaList: entry.mediaUrl ? [entry.mediaUrl] : entry.mediaUrls?.length ? entry.mediaUrls : [],
|
|
233
|
-
asVoice: entry.asVoice,
|
|
234
|
-
audioAsVoice: entry.audioAsVoice,
|
|
235
|
-
downloadMedia: entry.downloadMedia,
|
|
236
|
-
type: entry.type,
|
|
237
|
-
extra: entry.extra,
|
|
238
|
-
kind: entry.kind,
|
|
239
|
-
replyToId: entry.replyToId ?? '',
|
|
240
|
-
replyTargetPolicy: params.payload.replyTargetPolicy,
|
|
241
|
-
};
|
|
242
|
-
|
|
203
|
+
const sendPayload = buildSendPayload(entry, params.payload);
|
|
243
204
|
const plan = buildReplyEnqueuePlan(sendPayload);
|
|
244
205
|
|
|
245
206
|
if (plan.kind === 'text-and-media') {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
207
|
+
// Text first, then media entries with empty text
|
|
208
|
+
helpers.enqueueOutbound(
|
|
209
|
+
helpers.buildOutboxEntry({
|
|
210
|
+
accountId: params.accountId,
|
|
211
|
+
sessionKey: params.sessionKey,
|
|
212
|
+
route: params.route,
|
|
213
|
+
type: 'text',
|
|
214
|
+
msg: sendPayload.text,
|
|
215
|
+
kind: sendPayload.kind,
|
|
216
|
+
replyToId: sendPayload.replyToId || undefined,
|
|
217
|
+
replyTargetPolicy: sendPayload.replyTargetPolicy,
|
|
218
|
+
extra: sendPayload.extra,
|
|
219
|
+
}),
|
|
220
|
+
);
|
|
221
|
+
enqueueUnifiedMediaEntries(params, sendPayload, '', helpers);
|
|
259
222
|
continue;
|
|
260
223
|
}
|
|
261
224
|
|
|
262
225
|
if (plan.kind !== 'text-only') {
|
|
263
|
-
|
|
226
|
+
// Media-only: first entry carries caption text
|
|
227
|
+
enqueueUnifiedMediaEntries(params, sendPayload, sendPayload.text, helpers);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Text-only
|
|
232
|
+
if (!sendPayload.text && !sendPayload.extra) continue;
|
|
233
|
+
helpers.enqueueOutbound(
|
|
234
|
+
helpers.buildOutboxEntry({
|
|
264
235
|
accountId: params.accountId,
|
|
265
236
|
sessionKey: params.sessionKey,
|
|
266
237
|
route: params.route,
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
238
|
+
type: 'text',
|
|
239
|
+
msg: sendPayload.text,
|
|
240
|
+
kind: sendPayload.kind,
|
|
241
|
+
replyToId: sendPayload.replyToId || undefined,
|
|
242
|
+
replyTargetPolicy: sendPayload.replyTargetPolicy,
|
|
243
|
+
extra: sendPayload.extra,
|
|
244
|
+
}),
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** Internal: iterate mediaList and enqueue file-transfer entries (with dedup). */
|
|
250
|
+
function enqueueUnifiedMediaEntries(
|
|
251
|
+
params: EnqueueNormalizedReplyPayloadParams,
|
|
252
|
+
sendPayload: NormalizedReplyPayload,
|
|
253
|
+
firstCaption: string,
|
|
254
|
+
helpers: {
|
|
255
|
+
enqueueOutbound: (entry: OutboxEntry) => void;
|
|
256
|
+
buildOutboxEntry: BuildOutboxEntryFn;
|
|
257
|
+
tryBuildMediaDedupeFallback: (args: {
|
|
258
|
+
sessionKey: string;
|
|
259
|
+
mediaUrl: string;
|
|
260
|
+
text: string;
|
|
261
|
+
replyToId: string;
|
|
262
|
+
currentTime: number;
|
|
263
|
+
}) => {
|
|
264
|
+
text: string;
|
|
265
|
+
reason: 'same-text-sent-checkmark' | 'text-changed-downgrade';
|
|
266
|
+
} | null;
|
|
267
|
+
rememberRecentMediaSend: (args: {
|
|
268
|
+
sessionKey: string;
|
|
269
|
+
mediaUrl: string;
|
|
270
|
+
text: string;
|
|
271
|
+
replyToId: string;
|
|
272
|
+
createdAt: number;
|
|
273
|
+
}) => void;
|
|
274
|
+
logInfo: (
|
|
275
|
+
scope: string | undefined,
|
|
276
|
+
message: string,
|
|
277
|
+
options?: { debugOnly?: boolean },
|
|
278
|
+
) => void;
|
|
279
|
+
},
|
|
280
|
+
): void {
|
|
281
|
+
let first = true;
|
|
282
|
+
for (const mediaUrl of sendPayload.mediaList) {
|
|
283
|
+
const text = first ? firstCaption : '';
|
|
284
|
+
const currentTime = Date.now();
|
|
285
|
+
const normalizedText = text || '';
|
|
286
|
+
const fallback = helpers.tryBuildMediaDedupeFallback({
|
|
287
|
+
sessionKey: params.sessionKey,
|
|
288
|
+
mediaUrl,
|
|
289
|
+
text: normalizedText,
|
|
290
|
+
replyToId: sendPayload.replyToId,
|
|
291
|
+
currentTime,
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
if (fallback) {
|
|
295
|
+
helpers.logInfo(
|
|
296
|
+
'outbound',
|
|
297
|
+
buildBncrDebugJsonMessage('media-dedupe-hit', {
|
|
298
|
+
sessionKey: params.sessionKey,
|
|
299
|
+
mediaUrl,
|
|
300
|
+
text: normalizedText,
|
|
301
|
+
replyToId: sendPayload.replyToId,
|
|
302
|
+
reason: fallback.reason,
|
|
303
|
+
}),
|
|
304
|
+
{ debugOnly: true },
|
|
305
|
+
);
|
|
306
|
+
helpers.enqueueOutbound(
|
|
307
|
+
helpers.buildOutboxEntry({
|
|
308
|
+
accountId: params.accountId,
|
|
309
|
+
sessionKey: params.sessionKey,
|
|
310
|
+
route: params.route,
|
|
311
|
+
type: 'text',
|
|
312
|
+
msg: fallback.text,
|
|
313
|
+
kind: sendPayload.kind,
|
|
314
|
+
replyToId: sendPayload.replyToId || undefined,
|
|
315
|
+
replyTargetPolicy: sendPayload.replyTargetPolicy,
|
|
316
|
+
}),
|
|
317
|
+
);
|
|
318
|
+
first = false;
|
|
270
319
|
continue;
|
|
271
320
|
}
|
|
272
321
|
|
|
273
|
-
helpers.
|
|
274
|
-
|
|
322
|
+
helpers.enqueueOutbound(
|
|
323
|
+
helpers.buildOutboxEntry({
|
|
324
|
+
accountId: params.accountId,
|
|
325
|
+
sessionKey: params.sessionKey,
|
|
326
|
+
route: params.route,
|
|
327
|
+
type: sendPayload.type,
|
|
328
|
+
msg: text,
|
|
329
|
+
kind: sendPayload.kind,
|
|
330
|
+
replyToId: sendPayload.replyToId,
|
|
331
|
+
replyTargetPolicy: sendPayload.replyTargetPolicy,
|
|
332
|
+
extra: sendPayload.extra,
|
|
333
|
+
transferMode: 'media',
|
|
334
|
+
mediaUrl,
|
|
335
|
+
mediaLocalRoots: params.mediaLocalRoots,
|
|
336
|
+
asVoice: sendPayload.asVoice,
|
|
337
|
+
audioAsVoice: sendPayload.audioAsVoice,
|
|
338
|
+
downloadMedia: sendPayload.downloadMedia,
|
|
339
|
+
}),
|
|
340
|
+
);
|
|
341
|
+
helpers.rememberRecentMediaSend({
|
|
275
342
|
sessionKey: params.sessionKey,
|
|
276
|
-
|
|
277
|
-
|
|
343
|
+
mediaUrl,
|
|
344
|
+
text: normalizedText,
|
|
345
|
+
replyToId: sendPayload.replyToId,
|
|
346
|
+
createdAt: currentTime,
|
|
278
347
|
});
|
|
348
|
+
first = false;
|
|
279
349
|
}
|
|
280
350
|
}
|
|
281
351
|
|
|
@@ -288,13 +358,30 @@ export function enqueueNormalizedReplyPayload(
|
|
|
288
358
|
route: BncrRoute;
|
|
289
359
|
payload: NormalizedReplyPayload;
|
|
290
360
|
}) => void;
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
361
|
+
enqueueOutbound: (entry: OutboxEntry) => void;
|
|
362
|
+
buildOutboxEntry: BuildOutboxEntryFn;
|
|
363
|
+
tryBuildMediaDedupeFallback: (args: {
|
|
294
364
|
sessionKey: string;
|
|
295
|
-
|
|
296
|
-
|
|
365
|
+
mediaUrl: string;
|
|
366
|
+
text: string;
|
|
367
|
+
replyToId: string;
|
|
368
|
+
currentTime: number;
|
|
369
|
+
}) => {
|
|
370
|
+
text: string;
|
|
371
|
+
reason: 'same-text-sent-checkmark' | 'text-changed-downgrade';
|
|
372
|
+
} | null;
|
|
373
|
+
rememberRecentMediaSend: (args: {
|
|
374
|
+
sessionKey: string;
|
|
375
|
+
mediaUrl: string;
|
|
376
|
+
text: string;
|
|
377
|
+
replyToId: string;
|
|
378
|
+
createdAt: number;
|
|
297
379
|
}) => void;
|
|
380
|
+
logInfo: (
|
|
381
|
+
scope: string | undefined,
|
|
382
|
+
message: string,
|
|
383
|
+
options?: { debugOnly?: boolean },
|
|
384
|
+
) => void;
|
|
298
385
|
},
|
|
299
386
|
): void {
|
|
300
387
|
// Log only the main (last) send entry for traceability.
|
|
@@ -306,15 +393,15 @@ export function enqueueNormalizedReplyPayload(
|
|
|
306
393
|
});
|
|
307
394
|
|
|
308
395
|
// Dispatch ALL entries (pre-send + main) through the same path.
|
|
309
|
-
|
|
396
|
+
enqueueUnifiedOutbound(params, helpers);
|
|
310
397
|
}
|
|
311
398
|
|
|
312
|
-
export function normalizeReplyPayload(
|
|
399
|
+
export async function normalizeReplyPayload(
|
|
313
400
|
payload: ReplyPayloadInput,
|
|
314
401
|
helpers: { asString: (value: unknown, fallback?: string) => string },
|
|
315
402
|
options?: { replyTargetPolicy?: OutboundReplyTargetPolicy },
|
|
316
|
-
): NormalizedReplyPayload {
|
|
317
|
-
const normalized = normalizeOutboundSend({
|
|
403
|
+
): Promise<NormalizedReplyPayload> {
|
|
404
|
+
const normalized = await normalizeOutboundSend({
|
|
318
405
|
text: helpers.asString(payload?.text || ''),
|
|
319
406
|
mediaUrl: helpers.asString(payload?.mediaUrl || ''),
|
|
320
407
|
mediaUrls: payload?.mediaUrls,
|
|
@@ -48,7 +48,7 @@ export function isOpenClawRemoteHttpMediaUrl(mediaUrl: string): boolean {
|
|
|
48
48
|
* Returns the first absolute path that exists on disk, or the original
|
|
49
49
|
* relative path if nothing is found (the host will then emit its own error).
|
|
50
50
|
*/
|
|
51
|
-
|
|
51
|
+
function resolveRelativeMediaPath(mediaUrl: string, localRoots?: readonly string[]): string {
|
|
52
52
|
if (!mediaUrl || !localRoots?.length) return mediaUrl;
|
|
53
53
|
if (path.isAbsolute(mediaUrl)) return mediaUrl;
|
|
54
54
|
// HTTP / file:// / data: / ~ paths are handled elsewhere
|
|
@@ -2,7 +2,6 @@ import type { BncrRoute, FileSendTransferState } from '../core/types.ts';
|
|
|
2
2
|
import type {
|
|
3
3
|
NormalizedReplyPayload,
|
|
4
4
|
OutboundReplyTargetPolicy,
|
|
5
|
-
ReplyMediaEntriesParams,
|
|
6
5
|
ReplyPayloadInput,
|
|
7
6
|
} from '../messaging/outbound/reply-enqueue.ts';
|
|
8
7
|
import type { OpenClawLoadedMedia } from '../openclaw/media-runtime.ts';
|
|
@@ -61,7 +60,6 @@ export function createBncrBridgeMediaFacade(runtime: {
|
|
|
61
60
|
mediaLocalRoots?: readonly string[];
|
|
62
61
|
replyTargetPolicy?: OutboundReplyTargetPolicy;
|
|
63
62
|
}) => void;
|
|
64
|
-
enqueueReplyMediaEntries: (params: ReplyMediaEntriesParams) => void;
|
|
65
63
|
};
|
|
66
64
|
logInfoJson: (
|
|
67
65
|
scope: string | undefined,
|
|
@@ -210,7 +208,6 @@ export function createBncrBridgeMediaFacade(runtime: {
|
|
|
210
208
|
waitCompleteAck: runtime.fileTransferOrchestrator.waitCompleteAck,
|
|
211
209
|
transferMediaToBncrClient: runtime.fileTransferOrchestrator.transferMediaToBncrClient,
|
|
212
210
|
enqueueFromReply: runtime.replyMediaOrchestrator.enqueueFromReply,
|
|
213
|
-
enqueueReplyMediaEntries: runtime.replyMediaOrchestrator.enqueueReplyMediaEntries,
|
|
214
211
|
logEnqueueFromReply,
|
|
215
212
|
logFileChunkDiag: runtime.fileTransferLogs.logFileChunkDiag,
|
|
216
213
|
logFileTransferStart: runtime.fileTransferLogs.logFileTransferStart,
|
|
@@ -126,7 +126,6 @@ export function createBncrBridgeOutboxFacade(runtime: {
|
|
|
126
126
|
),
|
|
127
127
|
{ debugOnly: true },
|
|
128
128
|
);
|
|
129
|
-
runtime.logOutboundSummary(entry);
|
|
130
129
|
const accountId = runtime.normalizeAccountId(entry.accountId);
|
|
131
130
|
runtime.incrementCounter(runtime.outboundEnqueueCountByAccount, accountId);
|
|
132
131
|
runtime.lastOutboundEnqueueAtByAccount.set(accountId, runtime.now());
|