@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
package/package.json
CHANGED
package/scripts/check-pack.mjs
CHANGED
|
@@ -149,11 +149,9 @@ const requiredSourceFiles = [
|
|
|
149
149
|
'src/plugin/outbox-drain-post-push.ts',
|
|
150
150
|
'src/plugin/outbox-drain-runtime.ts',
|
|
151
151
|
'src/plugin/outbox-drain-schedule.ts',
|
|
152
|
-
'src/plugin/outbox-file-push-flow.ts',
|
|
153
152
|
'src/plugin/outbox-push-route-runtime-group.ts',
|
|
154
153
|
'src/plugin/outbox-push.ts',
|
|
155
154
|
'src/plugin/outbox-route.ts',
|
|
156
|
-
'src/plugin/outbox-text-push-flow.ts',
|
|
157
155
|
'src/plugin/runtime-diagnostics-snapshot.ts',
|
|
158
156
|
'src/plugin/setup.ts',
|
|
159
157
|
'src/plugin/state-transient-runtime-group.ts',
|
package/scripts/selfcheck.mjs
CHANGED
|
@@ -44,7 +44,6 @@ const requiredSourceFiles = [
|
|
|
44
44
|
'src/core/outbox-summary.ts',
|
|
45
45
|
'src/core/outbox-text-push-failure.ts',
|
|
46
46
|
'src/core/outbox-text-push-guards.ts',
|
|
47
|
-
'src/core/outbox-text-push-prep.ts',
|
|
48
47
|
'src/core/outbox-text-push-success.ts',
|
|
49
48
|
'src/core/permissions.ts',
|
|
50
49
|
'src/core/persisted-outbox-entry.ts',
|
|
@@ -145,11 +144,9 @@ const requiredSourceFiles = [
|
|
|
145
144
|
'src/plugin/outbox-drain-post-push.ts',
|
|
146
145
|
'src/plugin/outbox-drain-runtime.ts',
|
|
147
146
|
'src/plugin/outbox-drain-schedule.ts',
|
|
148
|
-
'src/plugin/outbox-file-push-flow.ts',
|
|
149
147
|
'src/plugin/outbox-push-route-runtime-group.ts',
|
|
150
148
|
'src/plugin/outbox-push.ts',
|
|
151
149
|
'src/plugin/outbox-route.ts',
|
|
152
|
-
'src/plugin/outbox-text-push-flow.ts',
|
|
153
150
|
'src/plugin/runtime-diagnostics-snapshot.ts',
|
|
154
151
|
'src/plugin/setup.ts',
|
|
155
152
|
'src/plugin/state-transient-runtime-group.ts',
|
|
@@ -44,7 +44,7 @@ export const pluginRoot = resolveBncrPluginRoot(pluginFile);
|
|
|
44
44
|
const runtimeSourceDir = resolveBncrRuntimeSourceDir(pluginDir);
|
|
45
45
|
let runtime: LoadedRuntime | null = null;
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
const readPluginVersion = (rootDir = pluginRoot) => {
|
|
48
48
|
try {
|
|
49
49
|
const raw = fs.readFileSync(path.join(rootDir, 'package.json'), 'utf8');
|
|
50
50
|
const parsed = JSON.parse(raw);
|
package/src/channel.ts
CHANGED
|
@@ -19,10 +19,7 @@ import {
|
|
|
19
19
|
emitBncrLog,
|
|
20
20
|
summarizeBncrTextPreview,
|
|
21
21
|
} from './core/logging.ts';
|
|
22
|
-
import {
|
|
23
|
-
buildFileTransferOutboxEntry as buildFileTransferOutboxEntryFromRuntime,
|
|
24
|
-
buildTextOutboxEntry as buildTextOutboxEntryFromRuntime,
|
|
25
|
-
} from './core/outbox-entry-builders.ts';
|
|
22
|
+
import { buildOutboxEntry as buildOutboxEntryFromRuntime } from './core/outbox-entry-builders.ts';
|
|
26
23
|
import { summarizeOutboxEntry } from './core/outbox-summary.ts';
|
|
27
24
|
import { normalizePersistedOutboxEntry as normalizePersistedOutboxEntryFromRuntime } from './core/persisted-outbox-entry.ts';
|
|
28
25
|
import {
|
|
@@ -53,7 +50,6 @@ import { OUTBOUND_FLUSH_REASON, OUTBOUND_FLUSH_TRIGGER } from './messaging/outbo
|
|
|
53
50
|
import type {
|
|
54
51
|
NormalizedReplyPayload,
|
|
55
52
|
OutboundReplyTargetPolicy,
|
|
56
|
-
ReplyMediaEntriesParams,
|
|
57
53
|
ReplyPayloadInput,
|
|
58
54
|
} from './messaging/outbound/reply-enqueue.ts';
|
|
59
55
|
import type { OpenClawChannelRuntimeApiHolder } from './openclaw/channel-runtime-contracts.ts';
|
|
@@ -67,7 +63,6 @@ import { createBncrBridgeConnectionFacade } from './plugin/bridge-connection-fac
|
|
|
67
63
|
import { createBncrBridgeDiagnosticsFacade } from './plugin/bridge-diagnostics-facade.ts';
|
|
68
64
|
import { createBncrBridgeDrainFacade } from './plugin/bridge-drain-facade.ts';
|
|
69
65
|
import { createBncrBridgeExtendedDiagnosticsFacade } from './plugin/bridge-extended-diagnostics-facade.ts';
|
|
70
|
-
import { createBncrBridgeFileTransferPushFacade } from './plugin/bridge-file-transfer-push-facade.ts';
|
|
71
66
|
import {
|
|
72
67
|
cleanupBncrBridgeRuntime,
|
|
73
68
|
shutdownBncrBridgeService,
|
|
@@ -175,9 +170,9 @@ import { createBncrInboundSurfaceHandlersGroup } from './plugin/inbound-surface-
|
|
|
175
170
|
import { createBncrMediaOrchestratorsRuntimeGroup } from './plugin/media-orchestrators-runtime-group.ts';
|
|
176
171
|
import { BNCR_CHANNEL_META } from './plugin/meta.ts';
|
|
177
172
|
import type { BncrOutboxAckOkTelemetryPatch } from './plugin/outbox-ack-outcome.ts';
|
|
178
|
-
|
|
173
|
+
|
|
179
174
|
import { createBncrOutboxPushRouteRuntimeGroup } from './plugin/outbox-push-route-runtime-group.ts';
|
|
180
|
-
import {
|
|
175
|
+
import { runBncrOutboxPush } from './plugin/outbox-unified-push-flow.ts';
|
|
181
176
|
import {
|
|
182
177
|
createBncrDeadLetterDiagnosticsHelpers,
|
|
183
178
|
createBncrDiagnosticsSelectionHelpers,
|
|
@@ -1131,83 +1126,53 @@ class BncrBridgeRuntime {
|
|
|
1131
1126
|
// This block prepares media, chooses transfer mode, sends chunk/complete
|
|
1132
1127
|
// frames, and feeds successful media pushes back into the shared outbox path.
|
|
1133
1128
|
|
|
1134
|
-
private async pushFileTransferSuccessPath(args: {
|
|
1135
|
-
entry: OutboxEntry;
|
|
1136
|
-
msg: Record<string, unknown>;
|
|
1137
|
-
owner: ReturnType<BncrBridgeRuntime['resolveOutboxPushOwner']>;
|
|
1138
|
-
connIds: Iterable<string>;
|
|
1139
|
-
recentInboundReachable: boolean;
|
|
1140
|
-
routeReason: string;
|
|
1141
|
-
mediaUrl: string;
|
|
1142
|
-
}): Promise<void> {
|
|
1143
|
-
await this.fileTransferPushFacade.pushFileTransferSuccessPath(args);
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
1129
|
private handleFileTransferPushFailure(args: { entry: OutboxEntry; error: unknown }) {
|
|
1147
|
-
this.
|
|
1130
|
+
this.outboxPush.handleFileTransferPushFailure(args);
|
|
1148
1131
|
}
|
|
1149
1132
|
|
|
1150
1133
|
private handleFileTransferPushGuardFailure(
|
|
1151
|
-
args: Parameters<typeof this.
|
|
1134
|
+
args: Parameters<typeof this.outboxPush.handleFileTransferPushGuardFailure>[0],
|
|
1152
1135
|
) {
|
|
1153
|
-
this.
|
|
1136
|
+
this.outboxPush.handleFileTransferPushGuardFailure(args);
|
|
1154
1137
|
}
|
|
1155
1138
|
|
|
1156
1139
|
// File-transfer outbound entry builders / dedupe -------------------------
|
|
1157
1140
|
|
|
1158
|
-
private
|
|
1159
|
-
entry: OutboxEntry,
|
|
1160
|
-
msg: Record<string, unknown>,
|
|
1161
|
-
): Promise<boolean> {
|
|
1162
|
-
return await runBncrFileTransferOutboxPush({
|
|
1163
|
-
entry,
|
|
1164
|
-
msg,
|
|
1165
|
-
gatewayContext: this.gatewayContext,
|
|
1166
|
-
owner: this.resolveOutboxPushOwner(entry.accountId),
|
|
1167
|
-
resolvePushConnIds: (accountId) => this.resolvePushConnIds(accountId),
|
|
1168
|
-
resolveRecentInboundConnIds: (accountId) => this.resolveRecentInboundConnIds(accountId),
|
|
1169
|
-
hasRecentInboundReachability: (accountId) => this.hasRecentInboundReachability(accountId),
|
|
1170
|
-
isRevalidatedAttemptedConn: (connId) => this.isRevalidatedAttemptedConn(entry, connId),
|
|
1171
|
-
handleFileTransferPushGuardFailure: (flowArgs) =>
|
|
1172
|
-
this.handleFileTransferPushGuardFailure(flowArgs),
|
|
1173
|
-
pushFileTransferSuccessPath: (flowArgs) => this.pushFileTransferSuccessPath(flowArgs),
|
|
1174
|
-
handleFileTransferPushFailure: (flowArgs) => this.handleFileTransferPushFailure(flowArgs),
|
|
1175
|
-
});
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
|
-
private buildFileTransferOutboxEntry(params: {
|
|
1141
|
+
private buildOutboxEntry(params: {
|
|
1179
1142
|
accountId: string;
|
|
1180
1143
|
sessionKey: string;
|
|
1181
1144
|
route: BncrRoute;
|
|
1182
|
-
mediaUrl: string;
|
|
1183
|
-
mediaLocalRoots?: readonly string[];
|
|
1184
|
-
text?: string;
|
|
1185
|
-
asVoice?: boolean;
|
|
1186
|
-
audioAsVoice?: boolean;
|
|
1187
1145
|
type?: string;
|
|
1188
|
-
|
|
1146
|
+
msg: string;
|
|
1189
1147
|
kind?: 'tool' | 'block' | 'final';
|
|
1190
1148
|
replyToId?: string;
|
|
1191
1149
|
replyTargetPolicy?: OutboundReplyTargetPolicy;
|
|
1150
|
+
extra?: Record<string, unknown>;
|
|
1151
|
+
transferMode?: 'text' | 'media';
|
|
1152
|
+
mediaUrl?: string;
|
|
1153
|
+
mediaLocalRoots?: readonly string[];
|
|
1154
|
+
asVoice?: boolean;
|
|
1155
|
+
audioAsVoice?: boolean;
|
|
1192
1156
|
downloadMedia?: boolean;
|
|
1193
1157
|
}): OutboxEntry {
|
|
1194
|
-
return
|
|
1158
|
+
return buildOutboxEntryFromRuntime({
|
|
1195
1159
|
createMessageId: () => randomUUID(),
|
|
1196
1160
|
now,
|
|
1197
1161
|
normalizeAccountId,
|
|
1198
1162
|
accountId: params.accountId,
|
|
1199
1163
|
sessionKey: params.sessionKey,
|
|
1200
1164
|
route: params.route,
|
|
1201
|
-
mediaUrl: params.mediaUrl,
|
|
1202
|
-
mediaLocalRoots: params.mediaLocalRoots,
|
|
1203
|
-
text: asString(params.text || ''),
|
|
1204
|
-
asVoice: params.asVoice,
|
|
1205
|
-
audioAsVoice: params.audioAsVoice,
|
|
1206
1165
|
type: params.type,
|
|
1207
|
-
|
|
1166
|
+
msg: params.msg,
|
|
1208
1167
|
kind: params.kind,
|
|
1209
1168
|
replyToId: asString(params.replyToId || '').trim() || undefined,
|
|
1210
1169
|
replyTargetPolicy: params.replyTargetPolicy,
|
|
1170
|
+
extra: params.extra,
|
|
1171
|
+
transferMode: params.transferMode,
|
|
1172
|
+
mediaUrl: params.mediaUrl,
|
|
1173
|
+
mediaLocalRoots: params.mediaLocalRoots,
|
|
1174
|
+
asVoice: params.asVoice,
|
|
1175
|
+
audioAsVoice: params.audioAsVoice,
|
|
1211
1176
|
downloadMedia: params.downloadMedia,
|
|
1212
1177
|
});
|
|
1213
1178
|
}
|
|
@@ -1321,66 +1286,8 @@ class BncrBridgeRuntime {
|
|
|
1321
1286
|
// This is the core outbound state machine: build entries, enqueue them,
|
|
1322
1287
|
// push them, observe ACK outcomes, and terminate in retry or dead-letter.
|
|
1323
1288
|
|
|
1324
|
-
private buildTextOutboxEntry(params: {
|
|
1325
|
-
accountId: string;
|
|
1326
|
-
sessionKey: string;
|
|
1327
|
-
route: BncrRoute;
|
|
1328
|
-
text: string;
|
|
1329
|
-
extra?: Record<string, unknown>;
|
|
1330
|
-
kind?: 'tool' | 'block' | 'final';
|
|
1331
|
-
replyToId?: string;
|
|
1332
|
-
replyTargetPolicy?: OutboundReplyTargetPolicy;
|
|
1333
|
-
}): OutboxEntry {
|
|
1334
|
-
return buildTextOutboxEntryFromRuntime({
|
|
1335
|
-
createMessageId: () => randomUUID(),
|
|
1336
|
-
now,
|
|
1337
|
-
normalizeAccountId,
|
|
1338
|
-
accountId: params.accountId,
|
|
1339
|
-
sessionKey: params.sessionKey,
|
|
1340
|
-
route: params.route,
|
|
1341
|
-
text: params.text,
|
|
1342
|
-
extra: params.extra && Object.keys(params.extra).length > 0 ? { ...params.extra } : undefined,
|
|
1343
|
-
kind: params.kind,
|
|
1344
|
-
replyToId: params.replyToId,
|
|
1345
|
-
replyTargetPolicy: params.replyTargetPolicy,
|
|
1346
|
-
});
|
|
1347
|
-
}
|
|
1348
|
-
|
|
1349
1289
|
private async tryPushEntry(entry: OutboxEntry): Promise<boolean> {
|
|
1350
|
-
|
|
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);
|
|
1357
|
-
}
|
|
1358
|
-
return this.tryPushTextEntry(entry);
|
|
1359
|
-
}
|
|
1360
|
-
|
|
1361
|
-
private pushTextSuccessPath(args: {
|
|
1362
|
-
entry: OutboxEntry;
|
|
1363
|
-
owner: ReturnType<BncrBridgeRuntime['resolveOutboxPushOwner']>;
|
|
1364
|
-
connIds: Iterable<string>;
|
|
1365
|
-
recentInboundReachable: boolean;
|
|
1366
|
-
routeReason: string;
|
|
1367
|
-
ownerConnId?: string;
|
|
1368
|
-
}) {
|
|
1369
|
-
this.outboxPush.pushTextSuccessPath(args);
|
|
1370
|
-
}
|
|
1371
|
-
|
|
1372
|
-
private handleTextPushFailure(args: { entry: OutboxEntry; error: unknown }) {
|
|
1373
|
-
this.outboxPush.handleTextPushFailure(args);
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
|
-
// Text outbound push runtime bridge --------------------------------------
|
|
1377
|
-
|
|
1378
|
-
// Outbound diagnostics/logging helpers -----------------------------------
|
|
1379
|
-
// Keep the compact logging helpers adjacent to the outbound state machine so
|
|
1380
|
-
// queue transitions and emitted diagnostics remain easy to correlate.
|
|
1381
|
-
|
|
1382
|
-
private async tryPushTextEntry(entry: OutboxEntry): Promise<boolean> {
|
|
1383
|
-
return await runBncrTextOutboxPush({
|
|
1290
|
+
return runBncrOutboxPush({
|
|
1384
1291
|
entry,
|
|
1385
1292
|
gatewayContext: this.gatewayContext,
|
|
1386
1293
|
owner: this.resolveOutboxPushOwner(entry.accountId),
|
|
@@ -1390,11 +1297,34 @@ class BncrBridgeRuntime {
|
|
|
1390
1297
|
isRevalidatedAttemptedConn: (connId) => this.isRevalidatedAttemptedConn(entry, connId),
|
|
1391
1298
|
recordOutboxPrePushFailure: (flowArgs) => this.recordOutboxPrePushFailure(flowArgs),
|
|
1392
1299
|
logOutboxPushSkip: (flowArgs) => this.logOutboxPushSkip(flowArgs),
|
|
1393
|
-
|
|
1300
|
+
handleFileTransferPushGuardFailure: (flowArgs) =>
|
|
1301
|
+
this.handleFileTransferPushGuardFailure(flowArgs),
|
|
1394
1302
|
handleTextPushFailure: (flowArgs) => this.handleTextPushFailure(flowArgs),
|
|
1303
|
+
handleFileTransferPushFailure: (flowArgs) => this.handleFileTransferPushFailure(flowArgs),
|
|
1304
|
+
// Unified push deps
|
|
1305
|
+
pushEvent: BNCR_PUSH_EVENT,
|
|
1306
|
+
gatewayBroadcastToConnIds: (event, payload, connIds) =>
|
|
1307
|
+
this.gatewayContext?.broadcastToConnIds(event, payload, connIds),
|
|
1308
|
+
recordOutboxPushSuccess: (args) => this.recordOutboxPushSuccess(args),
|
|
1309
|
+
logOutboxRouteSelect: (args) => this.logOutboxRouteSelect(args),
|
|
1310
|
+
logOutboxPushOkSummary: (mid) => this.logOutboxPushOkSummary(mid),
|
|
1311
|
+
logOutboundSummary: (entry) => this.logOutboundSummary(entry),
|
|
1312
|
+
logOutboxPushOk: (args) => this.logOutboxPushOk(args),
|
|
1313
|
+
transferMediaToBncrClient: (params) => this.transferMediaToBncrClient(params),
|
|
1314
|
+
buildFileTransferOutboundFrame: (params) => this.buildFileTransferOutboundFrame(params),
|
|
1395
1315
|
});
|
|
1396
1316
|
}
|
|
1397
1317
|
|
|
1318
|
+
private handleTextPushFailure(args: { entry: OutboxEntry; error: unknown }) {
|
|
1319
|
+
this.outboxPush.handleTextPushFailure(args);
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
// Text outbound push runtime bridge --------------------------------------
|
|
1323
|
+
|
|
1324
|
+
// Outbound diagnostics/logging helpers -----------------------------------
|
|
1325
|
+
// Keep the compact logging helpers adjacent to the outbound state machine so
|
|
1326
|
+
// queue transitions and emitted diagnostics remain easy to correlate.
|
|
1327
|
+
|
|
1398
1328
|
private logOutboxPushSkip(args: {
|
|
1399
1329
|
messageId: string;
|
|
1400
1330
|
accountId: string;
|
|
@@ -2734,8 +2664,7 @@ class BncrBridgeRuntime {
|
|
|
2734
2664
|
logInfo: (scope, message, options) => this.logInfo(scope, message, options),
|
|
2735
2665
|
logEnqueueFromReply: (args) => this.logEnqueueFromReply(args),
|
|
2736
2666
|
enqueueOutbound: (entry) => this.enqueueOutbound(entry),
|
|
2737
|
-
|
|
2738
|
-
buildFileTransferOutboxEntry: (args) => this.buildFileTransferOutboxEntry(args),
|
|
2667
|
+
buildOutboxEntry: (args) => this.buildOutboxEntry(args),
|
|
2739
2668
|
rememberRecentMediaSend: (args) => this.rememberRecentMediaSend(args),
|
|
2740
2669
|
tryBuildMediaDedupeFallback: (args) => this.tryBuildMediaDedupeFallback(args),
|
|
2741
2670
|
});
|
|
@@ -2789,7 +2718,7 @@ class BncrBridgeRuntime {
|
|
|
2789
2718
|
mediaLocalRoots?: readonly string[];
|
|
2790
2719
|
replyTargetPolicy?: OutboundReplyTargetPolicy;
|
|
2791
2720
|
}) {
|
|
2792
|
-
this.bridgeMediaFacade.enqueueFromReply(params);
|
|
2721
|
+
return this.bridgeMediaFacade.enqueueFromReply(params);
|
|
2793
2722
|
}
|
|
2794
2723
|
|
|
2795
2724
|
private logEnqueueFromReply(args: {
|
|
@@ -2829,24 +2758,6 @@ class BncrBridgeRuntime {
|
|
|
2829
2758
|
normalizeAccountId,
|
|
2830
2759
|
});
|
|
2831
2760
|
|
|
2832
|
-
private readonly fileTransferPushFacade = createBncrBridgeFileTransferPushFacade({
|
|
2833
|
-
pushEvent: BNCR_PUSH_EVENT,
|
|
2834
|
-
getGatewayContext: () => this.gatewayContext,
|
|
2835
|
-
transferMediaToBncrClient: (params) => this.transferMediaToBncrClient(params),
|
|
2836
|
-
buildFileTransferOutboundFrame: (params) => this.buildFileTransferOutboundFrame(params),
|
|
2837
|
-
logOutboxRouteSelect: (args) => this.logOutboxRouteSelect(args),
|
|
2838
|
-
recordOutboxPushSuccess: (args) => this.recordOutboxPushSuccess(args),
|
|
2839
|
-
logOutboxPushOkSummary: (messageId) => this.logOutboxPushOkSummary(messageId),
|
|
2840
|
-
logOutboxPushOk: (args) => this.logOutboxPushOk(args),
|
|
2841
|
-
handleFileTransferPushFailure: (args) => this.outboxPush.handleFileTransferPushFailure(args),
|
|
2842
|
-
handleFileTransferPushGuardFailure: (args) =>
|
|
2843
|
-
this.outboxPush.handleFileTransferPushGuardFailure(args),
|
|
2844
|
-
});
|
|
2845
|
-
|
|
2846
|
-
private enqueueReplyMediaEntries(params: ReplyMediaEntriesParams) {
|
|
2847
|
-
this.bridgeMediaFacade.enqueueReplyMediaEntries(params);
|
|
2848
|
-
}
|
|
2849
|
-
|
|
2850
2761
|
// Surface runtime assembly -----------------------------------------------
|
|
2851
2762
|
// The bridge remains the ownership root, but these builders keep the final
|
|
2852
2763
|
// gateway/inbound/channel send wiring readable by concern instead of as one
|
package/src/core/logging.ts
CHANGED
|
@@ -117,7 +117,7 @@ function sanitizeDebugValue(key: string, value: unknown): unknown {
|
|
|
117
117
|
return value;
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
|
|
120
|
+
function sanitizeBncrDebugPayload(payload: Record<string, unknown>) {
|
|
121
121
|
return sanitizeDebugPayload(payload);
|
|
122
122
|
}
|
|
123
123
|
|
|
@@ -12,27 +12,67 @@ function sanitizeExtraSpread(extra: Record<string, unknown>): Record<string, unk
|
|
|
12
12
|
return out;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export
|
|
15
|
+
export type BuildOutboxEntryParams = {
|
|
16
16
|
createMessageId: () => string;
|
|
17
17
|
now: () => number;
|
|
18
18
|
normalizeAccountId: (accountId?: string | null) => string;
|
|
19
19
|
accountId: string;
|
|
20
20
|
sessionKey: string;
|
|
21
21
|
route: BncrRoute;
|
|
22
|
-
|
|
23
|
-
mediaLocalRoots?: readonly string[];
|
|
24
|
-
text: string;
|
|
25
|
-
asVoice?: boolean;
|
|
26
|
-
audioAsVoice?: boolean;
|
|
22
|
+
/** For text sends this is 'text'; for media it may be undefined (inferred downstream). */
|
|
27
23
|
type?: string;
|
|
28
|
-
|
|
24
|
+
/** Message body (text content for text sends, caption for media sends). */
|
|
25
|
+
msg: string;
|
|
29
26
|
kind?: 'tool' | 'block' | 'final';
|
|
30
27
|
replyToId?: string;
|
|
31
28
|
replyTargetPolicy?: OutboundReplyTargetPolicy;
|
|
29
|
+
extra?: Record<string, unknown>;
|
|
30
|
+
/** Media-only fields. When transferMode === 'media' these shape the frame. */
|
|
31
|
+
transferMode?: 'text' | 'media';
|
|
32
|
+
mediaUrl?: string;
|
|
33
|
+
mediaLocalRoots?: readonly string[];
|
|
34
|
+
asVoice?: boolean;
|
|
35
|
+
audioAsVoice?: boolean;
|
|
32
36
|
downloadMedia?: boolean;
|
|
33
|
-
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Unified outbox entry builder.
|
|
41
|
+
*
|
|
42
|
+
* Text and media sends share the same outbox lifecycle (queue, drain, retry).
|
|
43
|
+
* The only structural difference is that media entries carry extra media fields
|
|
44
|
+
* and set `transferMode: 'media'` so the push layer knows to process media.
|
|
45
|
+
*
|
|
46
|
+
* - **Text**: `{ type: 'text', transferMode: undefined, no media fields }`
|
|
47
|
+
* - **Media**: `{ type: args.type || undefined, transferMode: 'media', ...media fields }`
|
|
48
|
+
*/
|
|
49
|
+
export function buildOutboxEntry(args: BuildOutboxEntryParams): OutboxEntry {
|
|
34
50
|
const messageId = args.createMessageId();
|
|
35
51
|
const createdAt = args.now();
|
|
52
|
+
const isMedia = args.transferMode === 'media';
|
|
53
|
+
|
|
54
|
+
const message: Record<string, unknown> = {
|
|
55
|
+
platform: args.route.platform,
|
|
56
|
+
groupId: args.route.groupId,
|
|
57
|
+
userId: args.route.userId,
|
|
58
|
+
type: isMedia ? args.type : 'text',
|
|
59
|
+
kind: args.kind,
|
|
60
|
+
msg: args.msg,
|
|
61
|
+
path: '',
|
|
62
|
+
base64: '',
|
|
63
|
+
fileName: '',
|
|
64
|
+
...(args.extra ? sanitizeExtraSpread(args.extra) : {}),
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
if (isMedia) {
|
|
68
|
+
message.mediaUrl = args.mediaUrl || '';
|
|
69
|
+
if (args.mediaLocalRoots) message.mediaLocalRoots = Array.from(args.mediaLocalRoots);
|
|
70
|
+
message.asVoice = args.asVoice === true;
|
|
71
|
+
message.audioAsVoice = args.audioAsVoice === true;
|
|
72
|
+
message.downloadMedia = args.downloadMedia;
|
|
73
|
+
message.transferMode = 'media';
|
|
74
|
+
}
|
|
75
|
+
|
|
36
76
|
return {
|
|
37
77
|
messageId,
|
|
38
78
|
accountId: args.normalizeAccountId(args.accountId),
|
|
@@ -49,84 +89,22 @@ export function buildFileTransferOutboxEntry(args: {
|
|
|
49
89
|
replyToId: args.replyToId,
|
|
50
90
|
replyTargetPolicy: args.replyTargetPolicy,
|
|
51
91
|
}) || undefined,
|
|
52
|
-
message: {
|
|
53
|
-
platform:
|
|
54
|
-
groupId:
|
|
55
|
-
userId:
|
|
56
|
-
type
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
asVoice: args.asVoice === true,
|
|
64
|
-
audioAsVoice: args.audioAsVoice === true,
|
|
65
|
-
downloadMedia: args.downloadMedia,
|
|
66
|
-
transferMode: 'media',
|
|
67
|
-
path: '',
|
|
68
|
-
base64: '',
|
|
69
|
-
fileName: '',
|
|
70
|
-
...(args.extra ? sanitizeExtraSpread(args.extra) : {}),
|
|
92
|
+
message: message as {
|
|
93
|
+
platform: string;
|
|
94
|
+
groupId: string;
|
|
95
|
+
userId: string;
|
|
96
|
+
type?: string;
|
|
97
|
+
kind?: 'tool' | 'block' | 'final';
|
|
98
|
+
msg: string;
|
|
99
|
+
path: string;
|
|
100
|
+
base64: string;
|
|
101
|
+
fileName: string;
|
|
102
|
+
[key: string]: unknown;
|
|
71
103
|
},
|
|
104
|
+
ts: createdAt,
|
|
72
105
|
},
|
|
73
106
|
createdAt,
|
|
74
107
|
retryCount: 0,
|
|
75
108
|
nextAttemptAt: createdAt,
|
|
76
109
|
};
|
|
77
110
|
}
|
|
78
|
-
|
|
79
|
-
export function buildTextOutboxEntry(args: {
|
|
80
|
-
createMessageId: () => string;
|
|
81
|
-
now: () => number;
|
|
82
|
-
normalizeAccountId: (accountId?: string | null) => string;
|
|
83
|
-
accountId: string;
|
|
84
|
-
extra?: Record<string, unknown>;
|
|
85
|
-
sessionKey: string;
|
|
86
|
-
route: BncrRoute;
|
|
87
|
-
text: string;
|
|
88
|
-
kind?: 'tool' | 'block' | 'final';
|
|
89
|
-
replyToId?: string;
|
|
90
|
-
replyTargetPolicy?: OutboundReplyTargetPolicy;
|
|
91
|
-
}): OutboxEntry {
|
|
92
|
-
const messageId = args.createMessageId();
|
|
93
|
-
const createdAt = args.now();
|
|
94
|
-
const frame = {
|
|
95
|
-
type: 'message.outbound',
|
|
96
|
-
messageId,
|
|
97
|
-
idempotencyKey: messageId,
|
|
98
|
-
sessionKey: args.sessionKey,
|
|
99
|
-
replyToId:
|
|
100
|
-
normalizeOutboundReplyToId({
|
|
101
|
-
kind: args.kind,
|
|
102
|
-
replyToId: args.replyToId,
|
|
103
|
-
replyTargetPolicy: args.replyTargetPolicy,
|
|
104
|
-
}) || undefined,
|
|
105
|
-
message: {
|
|
106
|
-
platform: args.route.platform,
|
|
107
|
-
groupId: args.route.groupId,
|
|
108
|
-
userId: args.route.userId,
|
|
109
|
-
type: 'text',
|
|
110
|
-
kind: args.kind,
|
|
111
|
-
msg: args.text,
|
|
112
|
-
path: '',
|
|
113
|
-
base64: '',
|
|
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) : {}),
|
|
118
|
-
},
|
|
119
|
-
ts: createdAt,
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
return {
|
|
123
|
-
messageId,
|
|
124
|
-
accountId: args.normalizeAccountId(args.accountId),
|
|
125
|
-
sessionKey: args.sessionKey,
|
|
126
|
-
route: args.route,
|
|
127
|
-
payload: frame,
|
|
128
|
-
createdAt,
|
|
129
|
-
retryCount: 0,
|
|
130
|
-
nextAttemptAt: createdAt,
|
|
131
|
-
};
|
|
132
|
-
}
|
|
@@ -1,26 +1,6 @@
|
|
|
1
1
|
import type { OutboxEntry } from './types.ts';
|
|
2
2
|
|
|
3
|
-
type OutboxSummaryMessage = {
|
|
4
|
-
type?: unknown;
|
|
5
|
-
msg?: unknown;
|
|
6
|
-
mediaUrl?: unknown;
|
|
7
|
-
audioAsVoice?: boolean;
|
|
8
|
-
asVoice?: boolean;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
type OutboxSummaryPayload = {
|
|
12
|
-
type?: unknown;
|
|
13
|
-
message?: OutboxSummaryMessage;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
function inferMediaTypeFromUrl(raw: string): 'image' | 'video' | 'audio' | 'file' {
|
|
17
|
-
const clean = String(raw || '').split(/[?#]/, 1)[0] || '';
|
|
18
|
-
const ext = clean.includes('.') ? clean.slice(clean.lastIndexOf('.') + 1).toLowerCase() : '';
|
|
19
|
-
if (['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'heic', 'heif'].includes(ext)) return 'image';
|
|
20
|
-
if (['mp4', 'mov', 'mkv', 'webm', 'avi', 'm4v'].includes(ext)) return 'video';
|
|
21
|
-
if (['mp3', 'wav', 'ogg', 'oga', 'opus', 'm4a', 'aac', 'flac'].includes(ext)) return 'audio';
|
|
22
|
-
return 'file';
|
|
23
|
-
}
|
|
3
|
+
type OutboxSummaryMessage = { type?: unknown; msg?: unknown; mediaUrl?: unknown };
|
|
24
4
|
|
|
25
5
|
function filenameFromUrl(raw: string): string {
|
|
26
6
|
const clean = String(raw || '').split(/[?#]/, 1)[0] || '';
|
|
@@ -33,27 +13,13 @@ function filenameFromUrl(raw: string): string {
|
|
|
33
13
|
}
|
|
34
14
|
}
|
|
35
15
|
|
|
36
|
-
function summarizeOutboxType(
|
|
37
|
-
|
|
38
|
-
msg: OutboxSummaryMessage,
|
|
39
|
-
asString: (value: unknown) => string,
|
|
40
|
-
) {
|
|
41
|
-
const directType = asString(msg.type || '');
|
|
42
|
-
if (directType) return directType;
|
|
43
|
-
// Infer type from mediaUrl when no direct type is set
|
|
44
|
-
const mediaUrl = asString(msg.mediaUrl || '');
|
|
45
|
-
if (mediaUrl) {
|
|
46
|
-
if (msg.audioAsVoice === true || msg.asVoice === true) return 'voice';
|
|
47
|
-
const inferred = inferMediaTypeFromUrl(mediaUrl);
|
|
48
|
-
if (inferred) return inferred;
|
|
49
|
-
}
|
|
50
|
-
return asString(payload?.type || 'unknown');
|
|
16
|
+
function summarizeOutboxType(msg: OutboxSummaryMessage, asString: (value: unknown) => string) {
|
|
17
|
+
return asString(msg.type || '') || 'unknown';
|
|
51
18
|
}
|
|
52
19
|
|
|
53
20
|
function summarizeOutboxText(msg: OutboxSummaryMessage, asString: (value: unknown) => string) {
|
|
54
21
|
const text = asString(msg.msg || '').trim();
|
|
55
22
|
if (text) return text;
|
|
56
|
-
// Fall back to filename from mediaUrl when no caption is set
|
|
57
23
|
const mediaUrl = asString(msg.mediaUrl || '');
|
|
58
24
|
if (mediaUrl) return filenameFromUrl(mediaUrl);
|
|
59
25
|
return '';
|
|
@@ -65,9 +31,9 @@ export function summarizeOutboxEntry(args: {
|
|
|
65
31
|
formatDisplayScope: (route: OutboxEntry['route']) => string;
|
|
66
32
|
summarizeTextPreview: (raw: string, limit?: number) => string;
|
|
67
33
|
}) {
|
|
68
|
-
const payload = args.entry.payload as
|
|
34
|
+
const payload = args.entry.payload as { message?: OutboxSummaryMessage } | undefined;
|
|
69
35
|
const msg = payload?.message || {};
|
|
70
|
-
const type = summarizeOutboxType(
|
|
36
|
+
const type = summarizeOutboxType(msg, args.asString);
|
|
71
37
|
const text = summarizeOutboxText(msg, args.asString);
|
|
72
38
|
const preview = args.summarizeTextPreview(text);
|
|
73
39
|
return [type, args.formatDisplayScope(args.entry.route), preview].join('|');
|
package/src/core/permissions.ts
CHANGED
|
@@ -10,7 +10,7 @@ type BncrElevatedConfigRoot = {
|
|
|
10
10
|
};
|
|
11
11
|
};
|
|
12
12
|
};
|
|
13
|
-
|
|
13
|
+
function getBncrElevatedConfig(rootCfg: BncrElevatedConfigRoot | null | undefined) {
|
|
14
14
|
const elevated = rootCfg?.tools?.elevated || {};
|
|
15
15
|
const allowFrom = elevated?.allowFrom || {};
|
|
16
16
|
const bncrRules = Array.isArray(allowFrom?.bncr)
|
|
@@ -43,7 +43,7 @@ export type RegisterDriftSnapshot = {
|
|
|
43
43
|
traceRecent: Array<Record<string, unknown>>;
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
function classifyRegisterTrace(stack: string) {
|
|
47
47
|
if (
|
|
48
48
|
stack.includes('prepareSecretsRuntimeSnapshot') ||
|
|
49
49
|
stack.includes('resolveRuntimeWebTools') ||
|
|
@@ -66,7 +66,7 @@ export function classifyRegisterTrace(stack: string) {
|
|
|
66
66
|
return 'other';
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
function dominantRegisterBucket(sourceBuckets: Record<string, number>) {
|
|
70
70
|
let winner: string | null = null;
|
|
71
71
|
let winnerCount = -1;
|
|
72
72
|
for (const [bucket, count] of Object.entries(sourceBuckets)) {
|
|
@@ -348,18 +348,3 @@ export function buildPushFailureDebugInfo(args: {
|
|
|
348
348
|
(typeof args.lastError === 'string' && args.lastError) || OUTBOUND_TERMINAL_REASON.PUSH_RETRY,
|
|
349
349
|
};
|
|
350
350
|
}
|
|
351
|
-
|
|
352
|
-
export function buildReplyMediaFallbackDebugInfo(args: {
|
|
353
|
-
sessionKey: string;
|
|
354
|
-
mediaUrl: string;
|
|
355
|
-
replyToId: string;
|
|
356
|
-
fallback: { text: string; reason: string };
|
|
357
|
-
}) {
|
|
358
|
-
return {
|
|
359
|
-
sessionKey: args.sessionKey,
|
|
360
|
-
mediaUrl: args.mediaUrl,
|
|
361
|
-
replyToId: args.replyToId || undefined,
|
|
362
|
-
fallbackText: args.fallback.text,
|
|
363
|
-
reason: args.fallback.reason,
|
|
364
|
-
};
|
|
365
|
-
}
|