@xmoxmo/bncr 0.5.5 → 0.5.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/dist/index.js +1 -9
- package/index.ts +1 -9
- package/package.json +1 -1
- package/scripts/check-pack.mjs +0 -6
- package/scripts/selfcheck.mjs +0 -7
- package/src/channel.ts +114 -37
- package/src/core/accounts.ts +1 -6
- package/src/core/connection-reachability.ts +1 -5
- package/src/core/dead-letter-diagnostics.ts +4 -10
- package/src/core/outbox-entry-builders.ts +38 -15
- package/src/core/outbox-file-transfer-failure.ts +5 -0
- package/src/core/outbox-file-transfer-guards.ts +6 -0
- package/src/core/outbox-file-transfer-prep.ts +8 -5
- package/src/core/outbox-file-transfer-success.ts +5 -0
- package/src/core/outbox-queue.ts +1 -7
- package/src/core/outbox-summary.ts +15 -27
- package/src/core/outbox-text-push-prep.ts +1 -36
- package/src/core/permissions.ts +1 -9
- package/src/core/persisted-outbox-entry.ts +54 -11
- package/src/core/policy.ts +1 -11
- package/src/core/register-trace.ts +2 -5
- package/src/core/status-meta.ts +1 -9
- package/src/core/status.ts +1 -21
- package/src/core/targets.ts +52 -70
- package/src/core/types.ts +1 -17
- package/src/core/value-sanitize.ts +38 -1
- package/src/messaging/inbound/commands.ts +2 -24
- package/src/messaging/inbound/dispatch-prep.ts +1 -1
- package/src/messaging/inbound/gate.ts +2 -31
- package/src/messaging/inbound/group-history.ts +0 -75
- package/src/messaging/inbound/native-command.ts +7 -3
- package/src/messaging/inbound/parse.ts +1 -31
- package/src/messaging/inbound/scene-admin.ts +181 -5
- package/src/messaging/outbound/diagnostics.ts +1 -6
- package/src/messaging/outbound/durable-queue-adapter.ts +17 -11
- package/src/messaging/outbound/marker-parser.ts +188 -0
- package/src/messaging/outbound/media-dedupe.ts +2 -6
- package/src/messaging/outbound/media.ts +6 -20
- package/src/messaging/outbound/normalize-outbound-send.ts +327 -0
- package/src/messaging/outbound/queue-selectors.ts +1 -5
- package/src/messaging/outbound/reply-enqueue-media.ts +4 -0
- package/src/messaging/outbound/reply-enqueue.ts +130 -57
- package/src/messaging/outbound/session-route.ts +6 -11
- package/src/messaging/outbound/target-resolver.ts +1 -6
- package/src/openclaw/routing-runtime.ts +0 -11
- package/src/openclaw/sdk-helpers.ts +0 -2
- package/src/plugin/bridge-file-transfer-push-facade.ts +8 -6
- package/src/plugin/bridge-media-facade.ts +1 -1
- package/src/plugin/channel-components.ts +0 -13
- package/src/plugin/channel-plugin-surface-group.ts +57 -40
- package/src/plugin/channel-runtime-builders-delivery.ts +3 -0
- package/src/plugin/channel-runtime-types.ts +10 -24
- package/src/plugin/channel-send.ts +241 -154
- package/src/plugin/channel-utils.ts +8 -29
- package/src/plugin/connection-handlers-helpers.ts +0 -15
- package/src/plugin/connection-handlers.ts +6 -5
- package/src/plugin/file-inbound-abort.ts +5 -0
- package/src/plugin/file-inbound-chunk.ts +5 -0
- package/src/plugin/file-inbound-complete.ts +5 -0
- package/src/plugin/file-transfer-orchestrator.ts +105 -3
- package/src/plugin/file-transfer-setup.ts +1 -1
- package/src/plugin/media-dedupe-runtime.ts +1 -6
- package/src/plugin/media-orchestrators-runtime-group.ts +2 -0
- package/src/plugin/message-send.ts +7 -8
- package/src/plugin/outbound.ts +6 -22
- package/src/plugin/outbox-drain-loop.ts +2 -2
- package/src/plugin/outbox-file-push-flow.ts +7 -7
- package/src/plugin/outbox-text-push-flow.ts +1 -1
- package/src/plugin/target-runtime.ts +1 -6
- package/src/runtime/outbound-ack-timeout.ts +1 -8
- package/src/runtime/outbox-transitions.ts +1 -4
- package/src/runtime/status-worker.ts +1 -9
- package/src/messaging/outbound/build-send-action.ts +0 -130
- package/src/messaging/outbound/durable-message-adapter.ts +0 -121
- package/src/messaging/outbound/send-params.ts +0 -81
- package/src/messaging/outbound/send.ts +0 -121
- package/src/openclaw/ingress-runtime.ts +0 -25
- package/src/plugin/channel-send-runtime-group.ts +0 -37
package/dist/index.js
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
// index.ts
|
|
2
|
-
import path3 from "node:path";
|
|
3
|
-
|
|
4
1
|
// src/bootstrap/channel-plugin-runtime.ts
|
|
5
2
|
function createDynamicChannelPlugin(args) {
|
|
6
3
|
const { loaded, getCurrentBridge: getCurrentBridge2 } = args;
|
|
@@ -786,12 +783,7 @@ var BncrConfigSchema = {
|
|
|
786
783
|
};
|
|
787
784
|
|
|
788
785
|
// index.ts
|
|
789
|
-
var
|
|
790
|
-
const packageJsonPath = path3.join(rootDir, "package.json");
|
|
791
|
-
void packageJsonPath;
|
|
792
|
-
return pluginVersion;
|
|
793
|
-
};
|
|
794
|
-
var pluginVersion2 = readPluginVersion2();
|
|
786
|
+
var pluginVersion2 = pluginVersion;
|
|
795
787
|
var registerRuntime = createBncrRegisterRuntime();
|
|
796
788
|
var {
|
|
797
789
|
ensureGatewayMethodRegistered,
|
package/index.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
1
|
import { createDynamicChannelPlugin } from './src/bootstrap/channel-plugin-runtime.ts';
|
|
3
2
|
import {
|
|
4
3
|
type BncrRegistrationApi,
|
|
@@ -10,7 +9,6 @@ import {
|
|
|
10
9
|
type ChannelModule,
|
|
11
10
|
type LoadedRuntime,
|
|
12
11
|
loadBncrRuntimeSync,
|
|
13
|
-
pluginRoot,
|
|
14
12
|
pluginVersion as runtimePluginVersion,
|
|
15
13
|
} from './src/bootstrap/runtime-loader.ts';
|
|
16
14
|
import { BncrConfigSchema } from './src/core/config-schema.ts';
|
|
@@ -19,13 +17,7 @@ import { getOpenClawRuntimeConfig } from './src/openclaw/config-runtime.ts';
|
|
|
19
17
|
|
|
20
18
|
type ChannelPlugin = ReturnType<ChannelModule['createBncrChannelPlugin']>;
|
|
21
19
|
|
|
22
|
-
const
|
|
23
|
-
const packageJsonPath = path.join(rootDir, 'package.json');
|
|
24
|
-
void packageJsonPath;
|
|
25
|
-
return runtimePluginVersion;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const pluginVersion = readPluginVersion();
|
|
20
|
+
const pluginVersion = runtimePluginVersion;
|
|
29
21
|
|
|
30
22
|
const registerRuntime = createBncrRegisterRuntime();
|
|
31
23
|
|
package/package.json
CHANGED
package/scripts/check-pack.mjs
CHANGED
|
@@ -76,10 +76,8 @@ const requiredSourceFiles = [
|
|
|
76
76
|
'src/messaging/inbound/session-label.ts',
|
|
77
77
|
'src/messaging/inbound/turn-context.ts',
|
|
78
78
|
'src/messaging/outbound/actions.ts',
|
|
79
|
-
'src/messaging/outbound/build-send-action.ts',
|
|
80
79
|
'src/messaging/outbound/diagnostics.ts',
|
|
81
80
|
'src/messaging/outbound/diagnostics-debug-builders.ts',
|
|
82
|
-
'src/messaging/outbound/durable-message-adapter.ts',
|
|
83
81
|
'src/messaging/outbound/durable-queue-adapter.ts',
|
|
84
82
|
'src/messaging/outbound/media-dedupe.ts',
|
|
85
83
|
'src/messaging/outbound/media.ts',
|
|
@@ -88,13 +86,10 @@ const requiredSourceFiles = [
|
|
|
88
86
|
'src/messaging/outbound/reply-enqueue.ts',
|
|
89
87
|
'src/messaging/outbound/reply-target-policy.ts',
|
|
90
88
|
'src/messaging/outbound/retry-policy.ts',
|
|
91
|
-
'src/messaging/outbound/send-params.ts',
|
|
92
|
-
'src/messaging/outbound/send.ts',
|
|
93
89
|
'src/messaging/outbound/session-route.ts',
|
|
94
90
|
'src/messaging/outbound/target-resolver.ts',
|
|
95
91
|
'src/openclaw/config-runtime.ts',
|
|
96
92
|
'src/openclaw/inbound-session-runtime.ts',
|
|
97
|
-
'src/openclaw/ingress-runtime.ts',
|
|
98
93
|
'src/openclaw/media-runtime.ts',
|
|
99
94
|
'src/openclaw/reply-runtime.ts',
|
|
100
95
|
'src/openclaw/routing-runtime.ts',
|
|
@@ -111,7 +106,6 @@ const requiredSourceFiles = [
|
|
|
111
106
|
'src/plugin/channel-plugin-surface-group.ts',
|
|
112
107
|
'src/plugin/channel-runtime-constants.ts',
|
|
113
108
|
'src/plugin/channel-runtime-builders.ts',
|
|
114
|
-
'src/plugin/channel-send-runtime-group.ts',
|
|
115
109
|
'src/plugin/channel-runtime-types.ts',
|
|
116
110
|
'src/plugin/channel-send.ts',
|
|
117
111
|
'src/plugin/channel-utils.ts',
|
package/scripts/selfcheck.mjs
CHANGED
|
@@ -72,10 +72,8 @@ const requiredSourceFiles = [
|
|
|
72
72
|
'src/messaging/inbound/session-label.ts',
|
|
73
73
|
'src/messaging/inbound/turn-context.ts',
|
|
74
74
|
'src/messaging/outbound/actions.ts',
|
|
75
|
-
'src/messaging/outbound/build-send-action.ts',
|
|
76
75
|
'src/messaging/outbound/diagnostics.ts',
|
|
77
76
|
'src/messaging/outbound/diagnostics-debug-builders.ts',
|
|
78
|
-
'src/messaging/outbound/durable-message-adapter.ts',
|
|
79
77
|
'src/messaging/outbound/durable-queue-adapter.ts',
|
|
80
78
|
'src/messaging/outbound/media-dedupe.ts',
|
|
81
79
|
'src/messaging/outbound/media.ts',
|
|
@@ -84,13 +82,10 @@ const requiredSourceFiles = [
|
|
|
84
82
|
'src/messaging/outbound/reply-enqueue.ts',
|
|
85
83
|
'src/messaging/outbound/reply-target-policy.ts',
|
|
86
84
|
'src/messaging/outbound/retry-policy.ts',
|
|
87
|
-
'src/messaging/outbound/send-params.ts',
|
|
88
|
-
'src/messaging/outbound/send.ts',
|
|
89
85
|
'src/messaging/outbound/session-route.ts',
|
|
90
86
|
'src/messaging/outbound/target-resolver.ts',
|
|
91
87
|
'src/openclaw/config-runtime.ts',
|
|
92
88
|
'src/openclaw/inbound-session-runtime.ts',
|
|
93
|
-
'src/openclaw/ingress-runtime.ts',
|
|
94
89
|
'src/openclaw/media-runtime.ts',
|
|
95
90
|
'src/openclaw/reply-runtime.ts',
|
|
96
91
|
'src/openclaw/routing-runtime.ts',
|
|
@@ -107,7 +102,6 @@ const requiredSourceFiles = [
|
|
|
107
102
|
'src/plugin/channel-plugin-surface-group.ts',
|
|
108
103
|
'src/plugin/channel-runtime-constants.ts',
|
|
109
104
|
'src/plugin/channel-runtime-builders.ts',
|
|
110
|
-
'src/plugin/channel-send-runtime-group.ts',
|
|
111
105
|
'src/plugin/channel-runtime-types.ts',
|
|
112
106
|
'src/plugin/channel-send.ts',
|
|
113
107
|
'src/plugin/channel-utils.ts',
|
|
@@ -198,7 +192,6 @@ const readNpmLatestVersion = (packageName) => {
|
|
|
198
192
|
|
|
199
193
|
const requiredOpenClawSdkSubpaths = [
|
|
200
194
|
'openclaw/plugin-sdk',
|
|
201
|
-
'openclaw/plugin-sdk/boolean-param',
|
|
202
195
|
'openclaw/plugin-sdk/channel-outbound',
|
|
203
196
|
'openclaw/plugin-sdk/channel-ingress-runtime',
|
|
204
197
|
'openclaw/plugin-sdk/channel-message',
|
package/src/channel.ts
CHANGED
|
@@ -152,10 +152,9 @@ import type {
|
|
|
152
152
|
BncrChannelConfigRoot,
|
|
153
153
|
BncrChannelSendContext,
|
|
154
154
|
BncrSceneRecord,
|
|
155
|
-
FileAckPayloadState,
|
|
156
155
|
PersistedState,
|
|
157
156
|
} from './plugin/channel-runtime-types.ts';
|
|
158
|
-
import {
|
|
157
|
+
import { createBncrChannelSendRuntime } from './plugin/channel-send.ts';
|
|
159
158
|
import {
|
|
160
159
|
asString,
|
|
161
160
|
backoffMs,
|
|
@@ -168,6 +167,7 @@ import {
|
|
|
168
167
|
} from './plugin/channel-utils.ts';
|
|
169
168
|
import { BNCR_CONFIG_SURFACE } from './plugin/config.ts';
|
|
170
169
|
import { createBncrConnectionStateRuntimeGroup } from './plugin/connection-state-runtime-group.ts';
|
|
170
|
+
import type { FileAckPayloadState } from './plugin/file-ack-runtime.ts';
|
|
171
171
|
import { createBncrFileTransferRuntimeGroup } from './plugin/file-transfer-runtime-group.ts';
|
|
172
172
|
import { BNCR_GATEWAY_METHODS } from './plugin/gateway-methods.ts';
|
|
173
173
|
import { prepareBncrInboundAcceptance } from './plugin/inbound-acceptance.ts';
|
|
@@ -288,6 +288,9 @@ class BncrBridgeRuntime {
|
|
|
288
288
|
{ accountId: string; route: BncrRoute; updatedAt: number }
|
|
289
289
|
>();
|
|
290
290
|
private sceneRegistry = new Map<string, BncrSceneRecord>();
|
|
291
|
+
getSceneRegistry(): Map<string, BncrSceneRecord> {
|
|
292
|
+
return this.sceneRegistry;
|
|
293
|
+
}
|
|
291
294
|
private groupHistories: BncrGroupHistoryMap = new Map();
|
|
292
295
|
private routeAliases = new Map<
|
|
293
296
|
string,
|
|
@@ -1130,7 +1133,7 @@ class BncrBridgeRuntime {
|
|
|
1130
1133
|
|
|
1131
1134
|
private async pushFileTransferSuccessPath(args: {
|
|
1132
1135
|
entry: OutboxEntry;
|
|
1133
|
-
|
|
1136
|
+
msg: Record<string, unknown>;
|
|
1134
1137
|
owner: ReturnType<BncrBridgeRuntime['resolveOutboxPushOwner']>;
|
|
1135
1138
|
connIds: Iterable<string>;
|
|
1136
1139
|
recentInboundReachable: boolean;
|
|
@@ -1154,11 +1157,11 @@ class BncrBridgeRuntime {
|
|
|
1154
1157
|
|
|
1155
1158
|
private async tryPushFileTransferEntry(
|
|
1156
1159
|
entry: OutboxEntry,
|
|
1157
|
-
|
|
1160
|
+
msg: Record<string, unknown>,
|
|
1158
1161
|
): Promise<boolean> {
|
|
1159
1162
|
return await runBncrFileTransferOutboxPush({
|
|
1160
1163
|
entry,
|
|
1161
|
-
|
|
1164
|
+
msg,
|
|
1162
1165
|
gatewayContext: this.gatewayContext,
|
|
1163
1166
|
owner: this.resolveOutboxPushOwner(entry.accountId),
|
|
1164
1167
|
resolvePushConnIds: (accountId) => this.resolvePushConnIds(accountId),
|
|
@@ -1186,12 +1189,12 @@ class BncrBridgeRuntime {
|
|
|
1186
1189
|
kind?: 'tool' | 'block' | 'final';
|
|
1187
1190
|
replyToId?: string;
|
|
1188
1191
|
replyTargetPolicy?: OutboundReplyTargetPolicy;
|
|
1192
|
+
downloadMedia?: boolean;
|
|
1189
1193
|
}): OutboxEntry {
|
|
1190
1194
|
return buildFileTransferOutboxEntryFromRuntime({
|
|
1191
1195
|
createMessageId: () => randomUUID(),
|
|
1192
1196
|
now,
|
|
1193
1197
|
normalizeAccountId,
|
|
1194
|
-
pushEvent: BNCR_PUSH_EVENT,
|
|
1195
1198
|
accountId: params.accountId,
|
|
1196
1199
|
sessionKey: params.sessionKey,
|
|
1197
1200
|
route: params.route,
|
|
@@ -1205,6 +1208,7 @@ class BncrBridgeRuntime {
|
|
|
1205
1208
|
kind: params.kind,
|
|
1206
1209
|
replyToId: asString(params.replyToId || '').trim() || undefined,
|
|
1207
1210
|
replyTargetPolicy: params.replyTargetPolicy,
|
|
1211
|
+
downloadMedia: params.downloadMedia,
|
|
1208
1212
|
});
|
|
1209
1213
|
}
|
|
1210
1214
|
|
|
@@ -1222,6 +1226,8 @@ class BncrBridgeRuntime {
|
|
|
1222
1226
|
this.mediaDedupeRuntime.rememberRecentMediaSend(params);
|
|
1223
1227
|
}
|
|
1224
1228
|
|
|
1229
|
+
private HTTP_URL_RE = /^https?:\/\//i;
|
|
1230
|
+
|
|
1225
1231
|
private tryBuildMediaDedupeFallback(params: {
|
|
1226
1232
|
sessionKey: string;
|
|
1227
1233
|
mediaUrl: string;
|
|
@@ -1229,23 +1235,62 @@ class BncrBridgeRuntime {
|
|
|
1229
1235
|
replyToId: string;
|
|
1230
1236
|
currentTime?: number;
|
|
1231
1237
|
}): { text: string; reason: 'same-text-sent-checkmark' | 'text-changed-downgrade' } | null {
|
|
1238
|
+
// Only deduplicate local file paths - HTTP URLs bypass dedup
|
|
1239
|
+
if (this.HTTP_URL_RE.test(params.mediaUrl)) return null;
|
|
1232
1240
|
return this.mediaDedupeRuntime.tryBuildMediaDedupeFallback(params);
|
|
1233
1241
|
}
|
|
1234
1242
|
|
|
1235
1243
|
private buildFileTransferOutboundFrame(params: {
|
|
1236
1244
|
entry: OutboxEntry;
|
|
1237
|
-
|
|
1245
|
+
msg: Record<string, unknown>;
|
|
1238
1246
|
media: { fileName?: string; mimeType?: string; path?: string; base64?: string; type?: string };
|
|
1239
1247
|
mediaUrl: string;
|
|
1240
1248
|
}) {
|
|
1241
|
-
const wantsVoice = params.
|
|
1242
|
-
const
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
? params.meta.messageKind
|
|
1249
|
+
const wantsVoice = params.msg.asVoice === true || params.msg.audioAsVoice === true;
|
|
1250
|
+
const rawKind = params.msg.kind ?? (params.msg as Record<string, unknown>).messageKind;
|
|
1251
|
+
const messageKind: 'tool' | 'block' | 'final' | undefined =
|
|
1252
|
+
rawKind === 'tool' || rawKind === 'block' || rawKind === 'final'
|
|
1253
|
+
? (rawKind as 'tool' | 'block' | 'final')
|
|
1247
1254
|
: undefined;
|
|
1248
1255
|
|
|
1256
|
+
const hasPayload = !!(params.media.base64 || params.media.path);
|
|
1257
|
+
this.logInfoJson(
|
|
1258
|
+
'outbound',
|
|
1259
|
+
'media-frame',
|
|
1260
|
+
{
|
|
1261
|
+
mid: params.entry.messageId,
|
|
1262
|
+
mode: hasPayload ? (params.media.path ? 'chunk' : 'base64') : 'pass-through',
|
|
1263
|
+
type: wantsVoice ? 'voice' : asString(params.msg.type || '') || undefined,
|
|
1264
|
+
url: params.mediaUrl.split('?')[0],
|
|
1265
|
+
...(params.media.path ? { path: params.media.path } : {}),
|
|
1266
|
+
...(params.media.base64 ? { base64: true } : {}),
|
|
1267
|
+
},
|
|
1268
|
+
{ debugOnly: true },
|
|
1269
|
+
);
|
|
1270
|
+
// Collect non-control fields from msg for passthrough into message.*
|
|
1271
|
+
const MSG_PASSTHROUGH_KEYS = new Set([
|
|
1272
|
+
'platform',
|
|
1273
|
+
'groupId',
|
|
1274
|
+
'userId',
|
|
1275
|
+
'mediaUrl',
|
|
1276
|
+
'mediaLocalRoots',
|
|
1277
|
+
'asVoice',
|
|
1278
|
+
'audioAsVoice',
|
|
1279
|
+
'downloadMedia',
|
|
1280
|
+
'transferMode',
|
|
1281
|
+
'path',
|
|
1282
|
+
'base64',
|
|
1283
|
+
'fileName',
|
|
1284
|
+
'kind',
|
|
1285
|
+
'replyToId',
|
|
1286
|
+
]);
|
|
1287
|
+
const passthroughExtra: Record<string, unknown> = {};
|
|
1288
|
+
for (const [key, value] of Object.entries(params.msg)) {
|
|
1289
|
+
if (!MSG_PASSTHROUGH_KEYS.has(key) && value !== undefined) {
|
|
1290
|
+
passthroughExtra[key] = value;
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1249
1294
|
return buildBncrMediaOutboundFrame({
|
|
1250
1295
|
messageId: params.entry.messageId,
|
|
1251
1296
|
sessionKey: params.entry.sessionKey,
|
|
@@ -1254,20 +1299,20 @@ class BncrBridgeRuntime {
|
|
|
1254
1299
|
mode: params.media.path ? 'chunk' : 'base64',
|
|
1255
1300
|
mimeType: params.media.mimeType,
|
|
1256
1301
|
fileName: params.media.fileName,
|
|
1257
|
-
|
|
1302
|
+
base64: params.media.base64,
|
|
1258
1303
|
path: params.media.path,
|
|
1259
1304
|
},
|
|
1260
1305
|
mediaUrl: params.mediaUrl,
|
|
1261
|
-
mediaMsg: asString(params.
|
|
1306
|
+
mediaMsg: asString(params.msg.msg ?? ''),
|
|
1262
1307
|
fileName: resolveOutboundFileName({
|
|
1263
1308
|
mediaUrl: params.mediaUrl,
|
|
1264
1309
|
fileName: params.media.fileName,
|
|
1265
1310
|
mimeType: params.media.mimeType,
|
|
1266
1311
|
}),
|
|
1267
|
-
hintedType: wantsVoice ? 'voice' : asString(params.
|
|
1268
|
-
extra:
|
|
1312
|
+
hintedType: wantsVoice ? 'voice' : asString(params.msg.type || '') || undefined,
|
|
1313
|
+
extra: Object.keys(passthroughExtra).length > 0 ? passthroughExtra : undefined,
|
|
1269
1314
|
kind: messageKind,
|
|
1270
|
-
replyToId: normalizeReplyToId(params.
|
|
1315
|
+
replyToId: normalizeReplyToId(asString(params.msg.replyToId || '')) || undefined,
|
|
1271
1316
|
now: now(),
|
|
1272
1317
|
});
|
|
1273
1318
|
}
|
|
@@ -1281,6 +1326,7 @@ class BncrBridgeRuntime {
|
|
|
1281
1326
|
sessionKey: string;
|
|
1282
1327
|
route: BncrRoute;
|
|
1283
1328
|
text: string;
|
|
1329
|
+
extra?: Record<string, unknown>;
|
|
1284
1330
|
kind?: 'tool' | 'block' | 'final';
|
|
1285
1331
|
replyToId?: string;
|
|
1286
1332
|
replyTargetPolicy?: OutboundReplyTargetPolicy;
|
|
@@ -1289,11 +1335,11 @@ class BncrBridgeRuntime {
|
|
|
1289
1335
|
createMessageId: () => randomUUID(),
|
|
1290
1336
|
now,
|
|
1291
1337
|
normalizeAccountId,
|
|
1292
|
-
normalizeReplyToId,
|
|
1293
1338
|
accountId: params.accountId,
|
|
1294
1339
|
sessionKey: params.sessionKey,
|
|
1295
1340
|
route: params.route,
|
|
1296
1341
|
text: params.text,
|
|
1342
|
+
extra: params.extra && Object.keys(params.extra).length > 0 ? { ...params.extra } : undefined,
|
|
1297
1343
|
kind: params.kind,
|
|
1298
1344
|
replyToId: params.replyToId,
|
|
1299
1345
|
replyTargetPolicy: params.replyTargetPolicy,
|
|
@@ -1301,11 +1347,14 @@ class BncrBridgeRuntime {
|
|
|
1301
1347
|
}
|
|
1302
1348
|
|
|
1303
1349
|
private async tryPushEntry(entry: OutboxEntry): Promise<boolean> {
|
|
1304
|
-
const
|
|
1305
|
-
|
|
1306
|
-
|
|
1350
|
+
const msg =
|
|
1351
|
+
isPlainObject(entry.payload) &&
|
|
1352
|
+
isPlainObject((entry.payload as Record<string, unknown>).message)
|
|
1353
|
+
? ((entry.payload as Record<string, unknown>).message as Record<string, unknown>)
|
|
1354
|
+
: null;
|
|
1355
|
+
if (msg?.transferMode === 'media') {
|
|
1356
|
+
return this.tryPushFileTransferEntry(entry, msg);
|
|
1307
1357
|
}
|
|
1308
|
-
|
|
1309
1358
|
return this.tryPushTextEntry(entry);
|
|
1310
1359
|
}
|
|
1311
1360
|
|
|
@@ -1729,8 +1778,12 @@ class BncrBridgeRuntime {
|
|
|
1729
1778
|
clampFiniteNumber(value, fallback, min ?? fallback, max ?? fallback),
|
|
1730
1779
|
normalizeAccountId,
|
|
1731
1780
|
formatDisplayScope,
|
|
1732
|
-
isFileTransferEntry: (entry) =>
|
|
1733
|
-
isPlainObject(entry.payload
|
|
1781
|
+
isFileTransferEntry: (entry) => {
|
|
1782
|
+
const msg = isPlainObject(entry.payload)
|
|
1783
|
+
? (entry.payload as Record<string, unknown>).message
|
|
1784
|
+
: null;
|
|
1785
|
+
return isPlainObject(msg) && (msg as Record<string, unknown>).transferMode === 'media';
|
|
1786
|
+
},
|
|
1734
1787
|
recommendedAckTimeoutMaxMs: RECOMMENDED_ACK_TIMEOUT_MAX_MS,
|
|
1735
1788
|
adaptiveAckTimeoutEnabled: ADAPTIVE_ACK_TIMEOUT_DEFAULT_ENABLED,
|
|
1736
1789
|
defaultAckTimeoutMs: PUSH_ACK_TIMEOUT_MS,
|
|
@@ -2722,7 +2775,7 @@ class BncrBridgeRuntime {
|
|
|
2722
2775
|
mode: 'base64' | 'chunk';
|
|
2723
2776
|
mimeType?: string;
|
|
2724
2777
|
fileName?: string;
|
|
2725
|
-
|
|
2778
|
+
base64?: string;
|
|
2726
2779
|
path?: string;
|
|
2727
2780
|
}> {
|
|
2728
2781
|
return this.bridgeMediaFacade.transferMediaToBncrClient(params);
|
|
@@ -2913,6 +2966,18 @@ class BncrBridgeRuntime {
|
|
|
2913
2966
|
logInfo: (scope, message, options) => this.logInfo(scope, message, options),
|
|
2914
2967
|
...targetRuntime,
|
|
2915
2968
|
listOutboxEntries: () => Array.from(this.outbox.values()),
|
|
2969
|
+
resolveSceneDownloadMedia: (to: string) => {
|
|
2970
|
+
const parts = to.split(':');
|
|
2971
|
+
if (parts.length >= 3) {
|
|
2972
|
+
const key = `${parts[1]}:${parts[2]}`;
|
|
2973
|
+
const sc = this.sceneRegistry.get(key);
|
|
2974
|
+
// Scene boolean wins (including explicit false); else fall to global.
|
|
2975
|
+
if (typeof sc?.downloadMedia === 'boolean') return sc.downloadMedia;
|
|
2976
|
+
const g = this.sceneRegistry.get('__global__');
|
|
2977
|
+
if (typeof g?.downloadMedia === 'boolean') return g.downloadMedia;
|
|
2978
|
+
}
|
|
2979
|
+
return undefined;
|
|
2980
|
+
},
|
|
2916
2981
|
});
|
|
2917
2982
|
}
|
|
2918
2983
|
|
|
@@ -2985,30 +3050,41 @@ class BncrBridgeRuntime {
|
|
|
2985
3050
|
await stopBncrStatusWorker(this.buildStatusWorkerRuntime(), ctx);
|
|
2986
3051
|
};
|
|
2987
3052
|
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
3053
|
+
/**
|
|
3054
|
+
* Merge host-level outbound fields (forceDocument, gifPlayback, silent)
|
|
3055
|
+
* into extra before dispatching to channel-send runtime.
|
|
3056
|
+
*/
|
|
3057
|
+
private mergeHostFields(ctx: BncrChannelSendContext): BncrChannelSendContext {
|
|
3058
|
+
if (!ctx.forceDocument && !ctx.gifPlayback && !ctx.silent) {
|
|
3059
|
+
return ctx;
|
|
3060
|
+
}
|
|
3061
|
+
const extra = { ...(typeof ctx.extra === 'object' && ctx.extra !== null ? ctx.extra : {}) };
|
|
3062
|
+
if (ctx.forceDocument === true) extra.forceDocument = true;
|
|
3063
|
+
if (ctx.gifPlayback === true) extra.gifPlayback = true;
|
|
3064
|
+
if (ctx.silent === true) extra.silent = true;
|
|
3065
|
+
// Promote host flags into extra so marker params can override them via mergeMarkerAndHostFields
|
|
3066
|
+
return { ...ctx, extra, forceDocument: undefined, gifPlayback: undefined, silent: undefined };
|
|
3067
|
+
}
|
|
2991
3068
|
|
|
2992
|
-
|
|
3069
|
+
// Channel send surface assembly ------------------------------------------
|
|
3070
|
+
private readonly channelSendRuntime = createBncrChannelSendRuntime(
|
|
2993
3071
|
this.buildChannelSendRuntime(),
|
|
2994
3072
|
);
|
|
2995
3073
|
|
|
2996
|
-
private readonly channelSendRuntime = this.channelSendRuntimeGroup.channelSendRuntime;
|
|
2997
|
-
|
|
2998
3074
|
channelSendText = async (ctx: BncrChannelSendContext) =>
|
|
2999
|
-
this.channelSendRuntime.channelSendText(ctx);
|
|
3075
|
+
this.channelSendRuntime.channelSendText(this.mergeHostFields(ctx));
|
|
3000
3076
|
|
|
3001
3077
|
channelSendMedia = async (ctx: BncrChannelSendContext) =>
|
|
3002
|
-
this.channelSendRuntime.channelSendMedia(ctx);
|
|
3078
|
+
this.channelSendRuntime.channelSendMedia(this.mergeHostFields(ctx));
|
|
3003
3079
|
|
|
3004
3080
|
channelMessageSendText = async (ctx: BncrChannelSendContext) =>
|
|
3005
|
-
this.channelSendRuntime.channelMessageSendText(ctx);
|
|
3081
|
+
this.channelSendRuntime.channelMessageSendText(this.mergeHostFields(ctx));
|
|
3006
3082
|
|
|
3007
3083
|
channelMessageSendMedia = async (ctx: BncrChannelSendContext) =>
|
|
3008
|
-
this.channelSendRuntime.channelMessageSendMedia(ctx);
|
|
3084
|
+
this.channelSendRuntime.channelMessageSendMedia(this.mergeHostFields(ctx));
|
|
3009
3085
|
|
|
3010
3086
|
channelMessageSendPayload = async (ctx: BncrChannelSendContext) =>
|
|
3011
|
-
this.channelSendRuntime.channelMessageSendPayload(ctx);
|
|
3087
|
+
this.channelSendRuntime.channelMessageSendPayload(this.mergeHostFields(ctx));
|
|
3012
3088
|
}
|
|
3013
3089
|
|
|
3014
3090
|
// Plugin surface export -----------------------------------------------------
|
|
@@ -3029,6 +3105,7 @@ export function createBncrChannelPlugin(getBridge: () => BncrBridgeRuntime) {
|
|
|
3029
3105
|
|
|
3030
3106
|
return createBncrChannelPluginSurfaceGroup({
|
|
3031
3107
|
channelId: CHANNEL_ID,
|
|
3108
|
+
sceneRegistry: getBridge().getSceneRegistry(),
|
|
3032
3109
|
getMessageSendBridge: bridgeGroup.getMessageSendBridge,
|
|
3033
3110
|
getOutboundBridge: bridgeGroup.getOutboundBridge,
|
|
3034
3111
|
getMessagingBridge: bridgeGroup.getMessagingBridge,
|
package/src/core/accounts.ts
CHANGED
|
@@ -3,16 +3,11 @@ import type {
|
|
|
3
3
|
BncrChannelConfigRoot,
|
|
4
4
|
BncrChannelConfigSection,
|
|
5
5
|
} from '../plugin/channel-runtime-types.ts';
|
|
6
|
+
import { asString } from './value-sanitize.ts';
|
|
6
7
|
|
|
7
8
|
const CHANNEL_ID = 'bncr';
|
|
8
9
|
const BNCR_DEFAULT_ACCOUNT_ID = 'Primary';
|
|
9
10
|
|
|
10
|
-
function asString(v: unknown, fallback = ''): string {
|
|
11
|
-
if (typeof v === 'string') return v;
|
|
12
|
-
if (v == null) return fallback;
|
|
13
|
-
return String(v);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
11
|
export function normalizeAccountId(accountId?: string | null): string {
|
|
17
12
|
const v = asString(accountId || '').trim();
|
|
18
13
|
if (!v) return BNCR_DEFAULT_ACCOUNT_ID;
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import type { BncrConnection, OutboxEntry } from './types.ts';
|
|
2
|
-
|
|
3
|
-
function finiteNumberOr(value: unknown, fallback: number): number {
|
|
4
|
-
const n = Number(value);
|
|
5
|
-
return Number.isFinite(n) ? n : fallback;
|
|
6
|
-
}
|
|
2
|
+
import { finiteNumberOr } from './value-sanitize.ts';
|
|
7
3
|
|
|
8
4
|
export function hasRecentInboundReachability(args: {
|
|
9
5
|
now: number;
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
BncrDeadLetterEntrySummary,
|
|
6
6
|
OutboxEntry,
|
|
7
7
|
} from './types.ts';
|
|
8
|
+
import { asString } from './value-sanitize.ts';
|
|
8
9
|
|
|
9
10
|
export type DeadLetterTopReason = { reason: string; count: number };
|
|
10
11
|
|
|
@@ -15,12 +16,6 @@ export type BuildDeadLetterDiagnosticsOptions = {
|
|
|
15
16
|
cappedAt: number;
|
|
16
17
|
};
|
|
17
18
|
|
|
18
|
-
function asString(value: unknown, fallback = ''): string {
|
|
19
|
-
if (typeof value === 'string') return value;
|
|
20
|
-
if (value == null) return fallback;
|
|
21
|
-
return String(value);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
19
|
function asPayloadMessage(payload: OutboxEntry['payload']): {
|
|
25
20
|
msg?: string;
|
|
26
21
|
type?: string;
|
|
@@ -87,15 +82,14 @@ export function parseDeadLetterOlderThan(raw: unknown): number | null {
|
|
|
87
82
|
}
|
|
88
83
|
|
|
89
84
|
export function summarizeDeadLetterEntry(entry: OutboxEntry) {
|
|
90
|
-
const
|
|
91
|
-
const
|
|
92
|
-
const text = asString(meta.text || msg.msg || '');
|
|
85
|
+
const msg = asPayloadMessage(entry.payload) || {};
|
|
86
|
+
const text = asString(msg.msg || '');
|
|
93
87
|
return {
|
|
94
88
|
messageId: entry.messageId,
|
|
95
89
|
accountId: entry.accountId,
|
|
96
90
|
sessionKey: entry.sessionKey,
|
|
97
91
|
route: formatDisplayScope(entry.route),
|
|
98
|
-
kind: asString(
|
|
92
|
+
kind: asString(msg.type || 'message'),
|
|
99
93
|
createdAt: Number.isFinite(Number(entry.createdAt)) ? Number(entry.createdAt) : null,
|
|
100
94
|
retryCount: Number.isFinite(Number(entry.retryCount)) ? Number(entry.retryCount) : 0,
|
|
101
95
|
lastError: entry.lastError || null,
|
|
@@ -2,11 +2,20 @@ import type { OutboundReplyTargetPolicy } from '../messaging/outbound/reply-targ
|
|
|
2
2
|
import { normalizeOutboundReplyToId } from '../messaging/outbound/reply-target-policy.ts';
|
|
3
3
|
import type { BncrRoute, OutboxEntry } from './types.ts';
|
|
4
4
|
|
|
5
|
+
/** Strip empty/falsy values from extra so they don't override existing message defaults. */
|
|
6
|
+
function sanitizeExtraSpread(extra: Record<string, unknown>): Record<string, unknown> {
|
|
7
|
+
const out: Record<string, unknown> = {};
|
|
8
|
+
for (const [key, value] of Object.entries(extra)) {
|
|
9
|
+
if (value === '' || value === null || value === undefined) continue;
|
|
10
|
+
out[key] = value;
|
|
11
|
+
}
|
|
12
|
+
return out;
|
|
13
|
+
}
|
|
14
|
+
|
|
5
15
|
export function buildFileTransferOutboxEntry(args: {
|
|
6
16
|
createMessageId: () => string;
|
|
7
17
|
now: () => number;
|
|
8
18
|
normalizeAccountId: (accountId?: string | null) => string;
|
|
9
|
-
pushEvent: string;
|
|
10
19
|
accountId: string;
|
|
11
20
|
sessionKey: string;
|
|
12
21
|
route: BncrRoute;
|
|
@@ -20,6 +29,7 @@ export function buildFileTransferOutboxEntry(args: {
|
|
|
20
29
|
kind?: 'tool' | 'block' | 'final';
|
|
21
30
|
replyToId?: string;
|
|
22
31
|
replyTargetPolicy?: OutboundReplyTargetPolicy;
|
|
32
|
+
downloadMedia?: boolean;
|
|
23
33
|
}): OutboxEntry {
|
|
24
34
|
const messageId = args.createMessageId();
|
|
25
35
|
const createdAt = args.now();
|
|
@@ -30,24 +40,34 @@ export function buildFileTransferOutboxEntry(args: {
|
|
|
30
40
|
route: args.route,
|
|
31
41
|
payload: {
|
|
32
42
|
type: 'message.outbound',
|
|
43
|
+
messageId,
|
|
44
|
+
idempotencyKey: messageId,
|
|
33
45
|
sessionKey: args.sessionKey,
|
|
34
|
-
|
|
35
|
-
|
|
46
|
+
replyToId:
|
|
47
|
+
normalizeOutboundReplyToId({
|
|
48
|
+
kind: args.kind,
|
|
49
|
+
replyToId: args.replyToId,
|
|
50
|
+
replyTargetPolicy: args.replyTargetPolicy,
|
|
51
|
+
}) || undefined,
|
|
52
|
+
message: {
|
|
53
|
+
platform: args.route.platform,
|
|
54
|
+
groupId: args.route.groupId,
|
|
55
|
+
userId: args.route.userId,
|
|
56
|
+
type: args.type,
|
|
57
|
+
// No fallback: when type is not provided the downstream adapter
|
|
58
|
+
// (tgBot / GewePlus) infers from mediaUrl extension.
|
|
59
|
+
kind: args.kind,
|
|
60
|
+
msg: args.text,
|
|
36
61
|
mediaUrl: args.mediaUrl,
|
|
37
62
|
mediaLocalRoots: args.mediaLocalRoots ? Array.from(args.mediaLocalRoots) : undefined,
|
|
38
|
-
text: args.text,
|
|
39
63
|
asVoice: args.asVoice === true,
|
|
40
64
|
audioAsVoice: args.audioAsVoice === true,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
replyToId: args.replyToId,
|
|
48
|
-
replyTargetPolicy: args.replyTargetPolicy,
|
|
49
|
-
}) || undefined,
|
|
50
|
-
messageKind: args.kind,
|
|
65
|
+
downloadMedia: args.downloadMedia,
|
|
66
|
+
transferMode: 'media',
|
|
67
|
+
path: '',
|
|
68
|
+
base64: '',
|
|
69
|
+
fileName: '',
|
|
70
|
+
...(args.extra ? sanitizeExtraSpread(args.extra) : {}),
|
|
51
71
|
},
|
|
52
72
|
},
|
|
53
73
|
createdAt,
|
|
@@ -60,8 +80,8 @@ export function buildTextOutboxEntry(args: {
|
|
|
60
80
|
createMessageId: () => string;
|
|
61
81
|
now: () => number;
|
|
62
82
|
normalizeAccountId: (accountId?: string | null) => string;
|
|
63
|
-
normalizeReplyToId: (value?: string | null) => string;
|
|
64
83
|
accountId: string;
|
|
84
|
+
extra?: Record<string, unknown>;
|
|
65
85
|
sessionKey: string;
|
|
66
86
|
route: BncrRoute;
|
|
67
87
|
text: string;
|
|
@@ -92,6 +112,9 @@ export function buildTextOutboxEntry(args: {
|
|
|
92
112
|
path: '',
|
|
93
113
|
base64: '',
|
|
94
114
|
fileName: '',
|
|
115
|
+
// Extra fields from marker/pipeline spread for adapter-specific data
|
|
116
|
+
// (e.g. type="appmsg", msg="<appmsg>..." from [BncrParam:...])
|
|
117
|
+
...(args.extra ? sanitizeExtraSpread(args.extra) : {}),
|
|
95
118
|
},
|
|
96
119
|
ts: createdAt,
|
|
97
120
|
};
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Intentionally separate from outbox-text-push-failure.ts.
|
|
3
|
+
* File transfer failure adds retryable-state resolution logic
|
|
4
|
+
* that text push doesn't need (no chunk-level retry semantics).
|
|
5
|
+
*/
|
|
1
6
|
import { buildPushFailureArgs } from './outbox-push-args.ts';
|
|
2
7
|
import type { OutboxEntry } from './types.ts';
|
|
3
8
|
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Intentionally separate from outbox-text-push-guards.ts.
|
|
3
|
+
* File transfer guard has distinct output type (includes owner/mediaUrl)
|
|
4
|
+
* and extra mediaUrl validation that text push doesn't need.
|
|
5
|
+
* Merging would add unnecessary conditional branches.
|
|
6
|
+
*/
|
|
1
7
|
import type { BncrConnection, OutboxEntry } from './types.ts';
|
|
2
8
|
|
|
3
9
|
export type FileTransferGuardResult =
|