@xmoxmo/bncr 0.5.6 → 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 +79 -43
- 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 +34 -18
- 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/parse.ts +1 -31
- package/src/messaging/inbound/scene-admin.ts +8 -8
- 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 +62 -28
- package/src/messaging/outbound/media-dedupe.ts +2 -6
- package/src/messaging/outbound/media.ts +1 -6
- 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.ts +121 -70
- 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 +6 -6
- package/src/plugin/bridge-media-facade.ts +0 -1
- package/src/plugin/channel-components.ts +0 -13
- package/src/plugin/channel-plugin-surface-group.ts +52 -65
- package/src/plugin/channel-runtime-builders-delivery.ts +1 -0
- package/src/plugin/channel-runtime-types.ts +2 -24
- package/src/plugin/channel-send.ts +205 -221
- 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/media-dedupe-runtime.ts +1 -6
- package/src/plugin/message-send.ts +7 -8
- package/src/plugin/outbound.ts +6 -26
- 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 -114
- package/src/messaging/outbound/send.ts +0 -127
- 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';
|
|
@@ -1133,7 +1133,7 @@ class BncrBridgeRuntime {
|
|
|
1133
1133
|
|
|
1134
1134
|
private async pushFileTransferSuccessPath(args: {
|
|
1135
1135
|
entry: OutboxEntry;
|
|
1136
|
-
|
|
1136
|
+
msg: Record<string, unknown>;
|
|
1137
1137
|
owner: ReturnType<BncrBridgeRuntime['resolveOutboxPushOwner']>;
|
|
1138
1138
|
connIds: Iterable<string>;
|
|
1139
1139
|
recentInboundReachable: boolean;
|
|
@@ -1157,11 +1157,11 @@ class BncrBridgeRuntime {
|
|
|
1157
1157
|
|
|
1158
1158
|
private async tryPushFileTransferEntry(
|
|
1159
1159
|
entry: OutboxEntry,
|
|
1160
|
-
|
|
1160
|
+
msg: Record<string, unknown>,
|
|
1161
1161
|
): Promise<boolean> {
|
|
1162
1162
|
return await runBncrFileTransferOutboxPush({
|
|
1163
1163
|
entry,
|
|
1164
|
-
|
|
1164
|
+
msg,
|
|
1165
1165
|
gatewayContext: this.gatewayContext,
|
|
1166
1166
|
owner: this.resolveOutboxPushOwner(entry.accountId),
|
|
1167
1167
|
resolvePushConnIds: (accountId) => this.resolvePushConnIds(accountId),
|
|
@@ -1195,7 +1195,6 @@ class BncrBridgeRuntime {
|
|
|
1195
1195
|
createMessageId: () => randomUUID(),
|
|
1196
1196
|
now,
|
|
1197
1197
|
normalizeAccountId,
|
|
1198
|
-
pushEvent: BNCR_PUSH_EVENT,
|
|
1199
1198
|
accountId: params.accountId,
|
|
1200
1199
|
sessionKey: params.sessionKey,
|
|
1201
1200
|
route: params.route,
|
|
@@ -1209,7 +1208,7 @@ class BncrBridgeRuntime {
|
|
|
1209
1208
|
kind: params.kind,
|
|
1210
1209
|
replyToId: asString(params.replyToId || '').trim() || undefined,
|
|
1211
1210
|
replyTargetPolicy: params.replyTargetPolicy,
|
|
1212
|
-
downloadMedia: params.downloadMedia
|
|
1211
|
+
downloadMedia: params.downloadMedia,
|
|
1213
1212
|
});
|
|
1214
1213
|
}
|
|
1215
1214
|
|
|
@@ -1243,29 +1242,55 @@ class BncrBridgeRuntime {
|
|
|
1243
1242
|
|
|
1244
1243
|
private buildFileTransferOutboundFrame(params: {
|
|
1245
1244
|
entry: OutboxEntry;
|
|
1246
|
-
|
|
1245
|
+
msg: Record<string, unknown>;
|
|
1247
1246
|
media: { fileName?: string; mimeType?: string; path?: string; base64?: string; type?: string };
|
|
1248
1247
|
mediaUrl: string;
|
|
1249
1248
|
}) {
|
|
1250
|
-
const wantsVoice = params.
|
|
1251
|
-
const
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
? 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')
|
|
1256
1254
|
: undefined;
|
|
1257
1255
|
|
|
1258
1256
|
const hasPayload = !!(params.media.base64 || params.media.path);
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
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
|
+
}
|
|
1268
1292
|
}
|
|
1293
|
+
|
|
1269
1294
|
return buildBncrMediaOutboundFrame({
|
|
1270
1295
|
messageId: params.entry.messageId,
|
|
1271
1296
|
sessionKey: params.entry.sessionKey,
|
|
@@ -1278,16 +1303,16 @@ class BncrBridgeRuntime {
|
|
|
1278
1303
|
path: params.media.path,
|
|
1279
1304
|
},
|
|
1280
1305
|
mediaUrl: params.mediaUrl,
|
|
1281
|
-
mediaMsg: asString(params.
|
|
1306
|
+
mediaMsg: asString(params.msg.msg ?? ''),
|
|
1282
1307
|
fileName: resolveOutboundFileName({
|
|
1283
1308
|
mediaUrl: params.mediaUrl,
|
|
1284
1309
|
fileName: params.media.fileName,
|
|
1285
1310
|
mimeType: params.media.mimeType,
|
|
1286
1311
|
}),
|
|
1287
|
-
hintedType: wantsVoice ? 'voice' : asString(params.
|
|
1288
|
-
extra:
|
|
1312
|
+
hintedType: wantsVoice ? 'voice' : asString(params.msg.type || '') || undefined,
|
|
1313
|
+
extra: Object.keys(passthroughExtra).length > 0 ? passthroughExtra : undefined,
|
|
1289
1314
|
kind: messageKind,
|
|
1290
|
-
replyToId: normalizeReplyToId(params.
|
|
1315
|
+
replyToId: normalizeReplyToId(asString(params.msg.replyToId || '')) || undefined,
|
|
1291
1316
|
now: now(),
|
|
1292
1317
|
});
|
|
1293
1318
|
}
|
|
@@ -1305,13 +1330,11 @@ class BncrBridgeRuntime {
|
|
|
1305
1330
|
kind?: 'tool' | 'block' | 'final';
|
|
1306
1331
|
replyToId?: string;
|
|
1307
1332
|
replyTargetPolicy?: OutboundReplyTargetPolicy;
|
|
1308
|
-
downloadMedia?: boolean;
|
|
1309
1333
|
}): OutboxEntry {
|
|
1310
1334
|
return buildTextOutboxEntryFromRuntime({
|
|
1311
1335
|
createMessageId: () => randomUUID(),
|
|
1312
1336
|
now,
|
|
1313
1337
|
normalizeAccountId,
|
|
1314
|
-
normalizeReplyToId,
|
|
1315
1338
|
accountId: params.accountId,
|
|
1316
1339
|
sessionKey: params.sessionKey,
|
|
1317
1340
|
route: params.route,
|
|
@@ -1320,16 +1343,18 @@ class BncrBridgeRuntime {
|
|
|
1320
1343
|
kind: params.kind,
|
|
1321
1344
|
replyToId: params.replyToId,
|
|
1322
1345
|
replyTargetPolicy: params.replyTargetPolicy,
|
|
1323
|
-
downloadMedia: params.downloadMedia === true ? true : undefined,
|
|
1324
1346
|
});
|
|
1325
1347
|
}
|
|
1326
1348
|
|
|
1327
1349
|
private async tryPushEntry(entry: OutboxEntry): Promise<boolean> {
|
|
1328
|
-
const
|
|
1329
|
-
|
|
1330
|
-
|
|
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);
|
|
1331
1357
|
}
|
|
1332
|
-
|
|
1333
1358
|
return this.tryPushTextEntry(entry);
|
|
1334
1359
|
}
|
|
1335
1360
|
|
|
@@ -1753,8 +1778,12 @@ class BncrBridgeRuntime {
|
|
|
1753
1778
|
clampFiniteNumber(value, fallback, min ?? fallback, max ?? fallback),
|
|
1754
1779
|
normalizeAccountId,
|
|
1755
1780
|
formatDisplayScope,
|
|
1756
|
-
isFileTransferEntry: (entry) =>
|
|
1757
|
-
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
|
+
},
|
|
1758
1787
|
recommendedAckTimeoutMaxMs: RECOMMENDED_ACK_TIMEOUT_MAX_MS,
|
|
1759
1788
|
adaptiveAckTimeoutEnabled: ADAPTIVE_ACK_TIMEOUT_DEFAULT_ENABLED,
|
|
1760
1789
|
defaultAckTimeoutMs: PUSH_ACK_TIMEOUT_MS,
|
|
@@ -2937,6 +2966,18 @@ class BncrBridgeRuntime {
|
|
|
2937
2966
|
logInfo: (scope, message, options) => this.logInfo(scope, message, options),
|
|
2938
2967
|
...targetRuntime,
|
|
2939
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
|
+
},
|
|
2940
2981
|
});
|
|
2941
2982
|
}
|
|
2942
2983
|
|
|
@@ -3021,20 +3062,15 @@ class BncrBridgeRuntime {
|
|
|
3021
3062
|
if (ctx.forceDocument === true) extra.forceDocument = true;
|
|
3022
3063
|
if (ctx.gifPlayback === true) extra.gifPlayback = true;
|
|
3023
3064
|
if (ctx.silent === true) extra.silent = true;
|
|
3024
|
-
//
|
|
3065
|
+
// Promote host flags into extra so marker params can override them via mergeMarkerAndHostFields
|
|
3025
3066
|
return { ...ctx, extra, forceDocument: undefined, gifPlayback: undefined, silent: undefined };
|
|
3026
3067
|
}
|
|
3027
3068
|
|
|
3028
3069
|
// Channel send surface assembly ------------------------------------------
|
|
3029
|
-
|
|
3030
|
-
// the final exposed channel* methods for quick top-down scanning.
|
|
3031
|
-
|
|
3032
|
-
private readonly channelSendRuntimeGroup = createBncrChannelSendRuntimeGroup(
|
|
3070
|
+
private readonly channelSendRuntime = createBncrChannelSendRuntime(
|
|
3033
3071
|
this.buildChannelSendRuntime(),
|
|
3034
3072
|
);
|
|
3035
3073
|
|
|
3036
|
-
private readonly channelSendRuntime = this.channelSendRuntimeGroup.channelSendRuntime;
|
|
3037
|
-
|
|
3038
3074
|
channelSendText = async (ctx: BncrChannelSendContext) =>
|
|
3039
3075
|
this.channelSendRuntime.channelSendText(this.mergeHostFields(ctx));
|
|
3040
3076
|
|
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;
|
|
@@ -31,25 +40,34 @@ export function buildFileTransferOutboxEntry(args: {
|
|
|
31
40
|
route: args.route,
|
|
32
41
|
payload: {
|
|
33
42
|
type: 'message.outbound',
|
|
43
|
+
messageId,
|
|
44
|
+
idempotencyKey: messageId,
|
|
34
45
|
sessionKey: args.sessionKey,
|
|
35
|
-
|
|
36
|
-
|
|
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,
|
|
37
61
|
mediaUrl: args.mediaUrl,
|
|
38
62
|
mediaLocalRoots: args.mediaLocalRoots ? Array.from(args.mediaLocalRoots) : undefined,
|
|
39
|
-
text: args.text,
|
|
40
63
|
asVoice: args.asVoice === true,
|
|
41
64
|
audioAsVoice: args.audioAsVoice === true,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
kind: args.kind,
|
|
49
|
-
replyToId: args.replyToId,
|
|
50
|
-
replyTargetPolicy: args.replyTargetPolicy,
|
|
51
|
-
}) || undefined,
|
|
52
|
-
messageKind: args.kind,
|
|
65
|
+
downloadMedia: args.downloadMedia,
|
|
66
|
+
transferMode: 'media',
|
|
67
|
+
path: '',
|
|
68
|
+
base64: '',
|
|
69
|
+
fileName: '',
|
|
70
|
+
...(args.extra ? sanitizeExtraSpread(args.extra) : {}),
|
|
53
71
|
},
|
|
54
72
|
},
|
|
55
73
|
createdAt,
|
|
@@ -62,7 +80,6 @@ export function buildTextOutboxEntry(args: {
|
|
|
62
80
|
createMessageId: () => string;
|
|
63
81
|
now: () => number;
|
|
64
82
|
normalizeAccountId: (accountId?: string | null) => string;
|
|
65
|
-
normalizeReplyToId: (value?: string | null) => string;
|
|
66
83
|
accountId: string;
|
|
67
84
|
extra?: Record<string, unknown>;
|
|
68
85
|
sessionKey: string;
|
|
@@ -71,7 +88,6 @@ export function buildTextOutboxEntry(args: {
|
|
|
71
88
|
kind?: 'tool' | 'block' | 'final';
|
|
72
89
|
replyToId?: string;
|
|
73
90
|
replyTargetPolicy?: OutboundReplyTargetPolicy;
|
|
74
|
-
downloadMedia?: boolean;
|
|
75
91
|
}): OutboxEntry {
|
|
76
92
|
const messageId = args.createMessageId();
|
|
77
93
|
const createdAt = args.now();
|
|
@@ -98,7 +114,7 @@ export function buildTextOutboxEntry(args: {
|
|
|
98
114
|
fileName: '',
|
|
99
115
|
// Extra fields from marker/pipeline spread for adapter-specific data
|
|
100
116
|
// (e.g. type="appmsg", msg="<appmsg>..." from [BncrParam:...])
|
|
101
|
-
...(args.extra ?
|
|
117
|
+
...(args.extra ? sanitizeExtraSpread(args.extra) : {}),
|
|
102
118
|
},
|
|
103
119
|
ts: createdAt,
|
|
104
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 =
|
|
@@ -1,26 +1,29 @@
|
|
|
1
1
|
import type { BncrConnection, OutboxEntry } from './types.ts';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
type PushRouteSelectionArgs<C> = {
|
|
4
4
|
entry: OutboxEntry;
|
|
5
5
|
owner: BncrConnection | null;
|
|
6
6
|
resolvePushConnIds: (accountId: string) => Iterable<string>;
|
|
7
7
|
resolveRecentInboundConnIds: (accountId: string) => Iterable<string>;
|
|
8
8
|
hasRecentInboundReachability: (accountId: string) => boolean;
|
|
9
9
|
isRevalidatedAttemptedConn: (connId: string) => boolean;
|
|
10
|
-
|
|
10
|
+
selectRouteCandidates: (args: {
|
|
11
11
|
routeCandidates: Iterable<string>;
|
|
12
12
|
attemptedConnIds: string[];
|
|
13
13
|
recentInboundConnIds: Iterable<string>;
|
|
14
14
|
ownerConnId?: string;
|
|
15
15
|
recentInboundReachable: boolean;
|
|
16
16
|
isRevalidatedAttemptedConn: (connId: string) => boolean;
|
|
17
|
-
}) =>
|
|
18
|
-
}
|
|
17
|
+
}) => C;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/** Shared route selection prep for both text and file push flows. */
|
|
21
|
+
export function prepareRouteSelection<C>(args: PushRouteSelectionArgs<C>): C {
|
|
19
22
|
const attemptedConnIds = Array.isArray(args.entry.routeAttemptConnIds)
|
|
20
23
|
? args.entry.routeAttemptConnIds.filter((v): v is string => typeof v === 'string' && !!v)
|
|
21
24
|
: [];
|
|
22
25
|
|
|
23
|
-
return args.
|
|
26
|
+
return args.selectRouteCandidates({
|
|
24
27
|
routeCandidates: args.resolvePushConnIds(args.entry.accountId),
|
|
25
28
|
attemptedConnIds,
|
|
26
29
|
recentInboundConnIds: args.resolveRecentInboundConnIds(args.entry.accountId),
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Intentionally separate from outbox-text-push-success.ts.
|
|
3
|
+
* File transfer success uses kind:'file-transfer' tag and different
|
|
4
|
+
* argument shapes (owner vs connId). Separate callers, separate files.
|
|
5
|
+
*/
|
|
1
6
|
import { buildPushBroadcastPayload, buildPushRouteSelectArgs } from './outbox-push-args.ts';
|
|
2
7
|
import type { BncrConnection, OutboxEntry } from './types.ts';
|
|
3
8
|
|
package/src/core/outbox-queue.ts
CHANGED
|
@@ -50,13 +50,7 @@ export function collectDueOutboxEntries(args: {
|
|
|
50
50
|
nextAttemptAt: args.now + args.backoffMs(nextAttempt),
|
|
51
51
|
};
|
|
52
52
|
updatedEntries.push(updatedEntry);
|
|
53
|
-
duePayloads.push(
|
|
54
|
-
...updatedEntry.payload,
|
|
55
|
-
_meta: {
|
|
56
|
-
retryCount: updatedEntry.retryCount,
|
|
57
|
-
nextAttemptAt: updatedEntry.nextAttemptAt,
|
|
58
|
-
},
|
|
59
|
-
});
|
|
53
|
+
duePayloads.push(updatedEntry.payload);
|
|
60
54
|
|
|
61
55
|
if (duePayloads.length >= args.maxBatch) break;
|
|
62
56
|
}
|