@xmoxmo/bncr 0.3.4 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +7 -3
- package/index.ts +6 -0
- package/openclaw.plugin.json +21 -0
- package/package.json +1 -1
- package/scripts/check-pack.mjs +97 -17
- package/scripts/check-register-drift.mjs +91 -65
- package/scripts/selfcheck.mjs +79 -3
- package/src/channel.ts +477 -635
- package/src/core/connection-capability.ts +2 -2
- package/src/core/connection-reachability.ts +106 -0
- package/src/core/dead-letter-diagnostics.ts +91 -0
- package/src/core/diagnostic-counters.ts +61 -0
- package/src/core/diagnostics.ts +9 -5
- package/src/core/downlink-health.ts +12 -7
- package/src/core/extended-diagnostics.ts +2 -0
- package/src/core/logging.ts +98 -0
- package/src/core/outbox-entry-builders.ts +13 -2
- package/src/core/persisted-outbox-entry.ts +53 -0
- package/src/core/probe.ts +33 -13
- package/src/core/register-trace.ts +48 -0
- package/src/core/status-meta.ts +77 -0
- package/src/core/status.ts +50 -57
- package/src/messaging/inbound/commands.ts +25 -86
- package/src/messaging/inbound/dispatch.ts +9 -36
- package/src/messaging/inbound/last-route.ts +46 -0
- package/src/messaging/inbound/native-command.ts +49 -0
- package/src/messaging/inbound/native-reply-delivery.ts +43 -0
- package/src/messaging/outbound/diagnostics.ts +221 -2
- package/src/messaging/outbound/reply-enqueue.ts +56 -1
- package/src/messaging/outbound/reply-target-policy.ts +4 -1
- package/src/messaging/outbound/send-params.ts +56 -0
- package/src/openclaw/runtime-surface.ts +29 -0
- package/src/plugin/gateway-methods.ts +2 -0
- package/src/plugin/status.ts +10 -4
- package/src/runtime/outbound-ack-timeout.ts +73 -0
- package/src/runtime/register-trace-runtime.ts +102 -0
- package/src/runtime/status-snapshots.ts +7 -3
- package/src/runtime/status-worker.ts +70 -11
package/src/channel.ts
CHANGED
|
@@ -23,9 +23,23 @@ import {
|
|
|
23
23
|
getRevalidatedAttemptReason,
|
|
24
24
|
hasAlternativeLiveConnection as hasAlternativeLiveConnectionFromRuntime,
|
|
25
25
|
hasRecentInboundReachability as hasRecentInboundReachabilityFromRuntime,
|
|
26
|
+
isEligibleOutboundPushConnection,
|
|
26
27
|
isRecentlyReachableConn as isRecentlyReachableConnFromRuntime,
|
|
27
28
|
resolveRecentInboundConnIds as resolveRecentInboundConnIdsFromRuntime,
|
|
29
|
+
selectOrderedOutboundPushConnections,
|
|
28
30
|
} from './core/connection-reachability.ts';
|
|
31
|
+
import {
|
|
32
|
+
buildDeadLetterDiagnostics as buildDeadLetterDiagnosticsFromRuntime,
|
|
33
|
+
formatDeadLetterTopReasons,
|
|
34
|
+
parseDeadLetterLimit,
|
|
35
|
+
parseDeadLetterOffset,
|
|
36
|
+
parseDeadLetterOlderThan,
|
|
37
|
+
summarizeDeadLetterEntry,
|
|
38
|
+
} from './core/dead-letter-diagnostics.ts';
|
|
39
|
+
import {
|
|
40
|
+
countInvalidOutboxSessionKeys as countInvalidOutboxSessionKeysFromRuntime,
|
|
41
|
+
countLegacyAccountResidue as countLegacyAccountResidueFromRuntime,
|
|
42
|
+
} from './core/diagnostic-counters.ts';
|
|
29
43
|
import { buildDiagnosticsPayload } from './core/diagnostics.ts';
|
|
30
44
|
import { buildDownlinkHealth as buildDownlinkHealthFromRuntime } from './core/downlink-health.ts';
|
|
31
45
|
import { buildExtendedDiagnostics as buildExtendedDiagnosticsFromRuntime } from './core/extended-diagnostics.ts';
|
|
@@ -40,7 +54,12 @@ import {
|
|
|
40
54
|
matchesTransferOwner as matchesTransferOwnerFromRuntime,
|
|
41
55
|
observeLeaseState,
|
|
42
56
|
} from './core/lease-state.ts';
|
|
43
|
-
import {
|
|
57
|
+
import {
|
|
58
|
+
buildBncrDebugJsonMessage,
|
|
59
|
+
emitBncrLog,
|
|
60
|
+
emitBncrLogLine,
|
|
61
|
+
summarizeBncrTextPreview,
|
|
62
|
+
} from './core/logging.ts';
|
|
44
63
|
import { buildOutboxEnqueueDebugInfo } from './core/outbox-enqueue.ts';
|
|
45
64
|
import {
|
|
46
65
|
buildFileTransferOutboxEntry as buildFileTransferOutboxEntryFromRuntime,
|
|
@@ -75,12 +94,11 @@ import {
|
|
|
75
94
|
buildTextPushRouteSelectArgs,
|
|
76
95
|
buildTextPushSuccessArgs,
|
|
77
96
|
} from './core/outbox-text-push-success.ts';
|
|
97
|
+
import { normalizePersistedOutboxEntry as normalizePersistedOutboxEntryFromRuntime } from './core/persisted-outbox-entry.ts';
|
|
78
98
|
import { resolveBncrChannelPolicy, resolveBncrConfigWarnings } from './core/policy.ts';
|
|
79
99
|
import {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
buildRegisterTraceEntry,
|
|
83
|
-
buildRegisterTraceSummary as buildRegisterTraceSummaryFromEntries,
|
|
100
|
+
dumpRegisterDriftSnapshot,
|
|
101
|
+
normalizeRegisterDriftSnapshot,
|
|
84
102
|
} from './core/register-trace.ts';
|
|
85
103
|
import {
|
|
86
104
|
buildAccountRuntimeSnapshot,
|
|
@@ -105,18 +123,21 @@ import { checkBncrMessageGate } from './messaging/inbound/gate.ts';
|
|
|
105
123
|
import { parseBncrInboundParams } from './messaging/inbound/parse.ts';
|
|
106
124
|
import {
|
|
107
125
|
buildEnqueueFromReplyDebugInfo,
|
|
126
|
+
buildExtendedOutboundDiagnostics,
|
|
108
127
|
buildFlushDebugInfo,
|
|
109
128
|
buildOutboxAckDebugInfo,
|
|
110
129
|
buildOutboxDrainSkipDebugInfo,
|
|
111
130
|
buildOutboxDrainStuckDebugInfo,
|
|
112
131
|
buildOutboxPushOkDebugInfo,
|
|
113
132
|
buildOutboxPushSkipDebugInfo,
|
|
133
|
+
buildOutboxQueueDiagnostics,
|
|
114
134
|
buildOutboxRouteSelectDebugInfo,
|
|
115
135
|
buildOutboxScheduleDebugInfo,
|
|
116
136
|
buildPushFailureDebugInfo,
|
|
117
137
|
buildRetryRerouteDebugInfo,
|
|
118
138
|
} from './messaging/outbound/diagnostics.ts';
|
|
119
139
|
import { buildBncrMediaOutboundFrame } from './messaging/outbound/media.ts';
|
|
140
|
+
import { normalizeBncrSendParams } from './messaging/outbound/send-params.ts';
|
|
120
141
|
import {
|
|
121
142
|
getOpenClawRuntimeConfig,
|
|
122
143
|
getOpenClawRuntimeConfigOrDefault,
|
|
@@ -127,14 +148,18 @@ import {
|
|
|
127
148
|
saveOpenClawChannelMediaBuffer,
|
|
128
149
|
} from './openclaw/media-runtime.ts';
|
|
129
150
|
import { resolveOpenClawAgentRoute } from './openclaw/routing-runtime.ts';
|
|
151
|
+
import { buildOpenClawChannelRuntimeSurfaceDiagnostics } from './openclaw/runtime-surface.ts';
|
|
130
152
|
import {
|
|
131
153
|
extractOpenClawToolSend,
|
|
132
154
|
openClawJsonResult,
|
|
133
|
-
readOpenClawBooleanParam,
|
|
134
155
|
readOpenClawJsonFileWithFallback,
|
|
135
|
-
readOpenClawStringParam,
|
|
136
156
|
writeOpenClawJsonFileAtomically,
|
|
137
157
|
} from './openclaw/sdk-helpers.ts';
|
|
158
|
+
import type { RegisterTraceRuntimeState } from './runtime/register-trace-runtime.ts';
|
|
159
|
+
import {
|
|
160
|
+
buildRegisterTraceRuntimeSummary,
|
|
161
|
+
noteRegisterTraceRuntime,
|
|
162
|
+
} from './runtime/register-trace-runtime.ts';
|
|
138
163
|
|
|
139
164
|
function buildInboundAcceptedLifecycleDebugInfo(args: {
|
|
140
165
|
stage: 'accepted';
|
|
@@ -304,6 +329,7 @@ import {
|
|
|
304
329
|
hasReplyMediaEntries,
|
|
305
330
|
type NormalizedReplyPayload,
|
|
306
331
|
normalizeReplyPayload,
|
|
332
|
+
type OutboundReplyTargetPolicy,
|
|
307
333
|
type ReplyMediaEntriesParams,
|
|
308
334
|
type ReplyPayloadInput,
|
|
309
335
|
} from './messaging/outbound/reply-enqueue.ts';
|
|
@@ -325,9 +351,9 @@ import { BNCR_SETUP_SURFACE } from './plugin/setup.ts';
|
|
|
325
351
|
import { createBncrStatusSurface } from './plugin/status.ts';
|
|
326
352
|
import { shouldEmitDedupLog as shouldEmitDedupLogFromRuntime } from './runtime/log-dedupe.ts';
|
|
327
353
|
import {
|
|
354
|
+
buildBncrRuntimeAckObservability,
|
|
328
355
|
buildBncrRuntimeAckStrategy,
|
|
329
|
-
|
|
330
|
-
computeBncrRecommendedAckTimeoutReason,
|
|
356
|
+
resolveBncrRuntimeAckTimeoutDecision,
|
|
331
357
|
} from './runtime/outbound-ack-timeout.ts';
|
|
332
358
|
import {
|
|
333
359
|
buildBncrRuntimeFlags,
|
|
@@ -496,56 +522,6 @@ type PersistedState = {
|
|
|
496
522
|
} | null;
|
|
497
523
|
};
|
|
498
524
|
|
|
499
|
-
type NormalizedBncrSendParams = {
|
|
500
|
-
to: string;
|
|
501
|
-
accountId: string;
|
|
502
|
-
message: string;
|
|
503
|
-
caption: string;
|
|
504
|
-
mediaUrl?: string;
|
|
505
|
-
asVoice: boolean;
|
|
506
|
-
audioAsVoice: boolean;
|
|
507
|
-
};
|
|
508
|
-
|
|
509
|
-
function normalizeBncrSendParams(input: {
|
|
510
|
-
params: unknown;
|
|
511
|
-
accountId: string;
|
|
512
|
-
}): NormalizedBncrSendParams {
|
|
513
|
-
const paramsObj = isPlainObject(input.params) ? input.params : {};
|
|
514
|
-
const to = readOpenClawStringParam(paramsObj, 'to', { required: true });
|
|
515
|
-
const resolvedAccountId = normalizeAccountId(
|
|
516
|
-
readOpenClawStringParam(paramsObj, 'accountId') ?? input.accountId,
|
|
517
|
-
);
|
|
518
|
-
|
|
519
|
-
const message = readOpenClawStringParam(paramsObj, 'message', { allowEmpty: true }) ?? '';
|
|
520
|
-
const caption = readOpenClawStringParam(paramsObj, 'caption', { allowEmpty: true }) ?? '';
|
|
521
|
-
const mediaUrl =
|
|
522
|
-
readOpenClawStringParam(paramsObj, 'media', { trim: false }) ??
|
|
523
|
-
readOpenClawStringParam(paramsObj, 'path', { trim: false }) ??
|
|
524
|
-
readOpenClawStringParam(paramsObj, 'filePath', { trim: false }) ??
|
|
525
|
-
readOpenClawStringParam(paramsObj, 'mediaUrl', { trim: false });
|
|
526
|
-
const asVoice = readOpenClawBooleanParam(paramsObj, 'asVoice') ?? false;
|
|
527
|
-
const audioAsVoice = readOpenClawBooleanParam(paramsObj, 'audioAsVoice') ?? false;
|
|
528
|
-
|
|
529
|
-
if (asVoice && !mediaUrl) throw new Error('send voice requires media path');
|
|
530
|
-
|
|
531
|
-
const normalizedMessage = mediaUrl ? '' : message || caption || '';
|
|
532
|
-
const normalizedCaption = mediaUrl ? caption || message || '' : '';
|
|
533
|
-
|
|
534
|
-
if (!normalizedMessage.trim() && !normalizedCaption.trim() && !mediaUrl) {
|
|
535
|
-
throw new Error('send requires message or media');
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
return {
|
|
539
|
-
to,
|
|
540
|
-
accountId: resolvedAccountId,
|
|
541
|
-
message: normalizedMessage,
|
|
542
|
-
caption: normalizedCaption,
|
|
543
|
-
mediaUrl: mediaUrl || undefined,
|
|
544
|
-
asVoice,
|
|
545
|
-
audioAsVoice,
|
|
546
|
-
};
|
|
547
|
-
}
|
|
548
|
-
|
|
549
525
|
function now() {
|
|
550
526
|
return Date.now();
|
|
551
527
|
}
|
|
@@ -561,12 +537,6 @@ function finiteNumberOr(value: unknown, fallback: number): number {
|
|
|
561
537
|
return Number.isFinite(n) ? n : fallback;
|
|
562
538
|
}
|
|
563
539
|
|
|
564
|
-
function optionalFiniteNumber(value: unknown): number | undefined {
|
|
565
|
-
if (value == null || value === '') return undefined;
|
|
566
|
-
const n = Number(value);
|
|
567
|
-
return Number.isFinite(n) ? n : undefined;
|
|
568
|
-
}
|
|
569
|
-
|
|
570
540
|
function finiteNonNegativeNumberOrNull(value: unknown): number | null {
|
|
571
541
|
const n = Number(value);
|
|
572
542
|
return Number.isFinite(n) && n >= 0 ? n : null;
|
|
@@ -770,6 +740,7 @@ class BncrBridgeRuntime {
|
|
|
770
740
|
private prePushGuardSkipCountByAccount = new Map<string, number>();
|
|
771
741
|
private lastPrePushGuardSkipAtByAccount = new Map<string, number>();
|
|
772
742
|
private lastPrePushGuardSkipReasonByAccount = new Map<string, string>();
|
|
743
|
+
private deadLetterSinceStartByAccount = new Map<string, number>();
|
|
773
744
|
private messageAckWaiters = new Map<
|
|
774
745
|
// Refactor boundary note (message ACK runtime):
|
|
775
746
|
// These waiters are part of the outbound message-ack lifecycle, not just a utility map.
|
|
@@ -832,17 +803,13 @@ class BncrBridgeRuntime {
|
|
|
832
803
|
emitBncrLog('error', scope, message, options, () => this.isDebugEnabled());
|
|
833
804
|
}
|
|
834
805
|
|
|
835
|
-
private buildDebugJsonMessage(event: string, payload: Record<string, unknown>) {
|
|
836
|
-
return `${event} ${JSON.stringify(payload)}`;
|
|
837
|
-
}
|
|
838
|
-
|
|
839
806
|
private logInfoJson(
|
|
840
807
|
scope: string | undefined,
|
|
841
808
|
event: string,
|
|
842
809
|
payload: Record<string, unknown>,
|
|
843
810
|
options?: { debugOnly?: boolean },
|
|
844
811
|
) {
|
|
845
|
-
this.logInfo(scope,
|
|
812
|
+
this.logInfo(scope, buildBncrDebugJsonMessage(event, payload), options);
|
|
846
813
|
}
|
|
847
814
|
|
|
848
815
|
private shouldEmitDedupLog(key: string, sig: string, windowMs = 5 * 60 * 1000) {
|
|
@@ -875,12 +842,7 @@ class BncrBridgeRuntime {
|
|
|
875
842
|
}
|
|
876
843
|
|
|
877
844
|
private summarizeTextPreview(raw: string, limit = 8) {
|
|
878
|
-
|
|
879
|
-
.replace(/\s+/g, ' ')
|
|
880
|
-
.trim();
|
|
881
|
-
if (!compact) return '-';
|
|
882
|
-
const chars = Array.from(compact);
|
|
883
|
-
return chars.length > limit ? `${chars.slice(0, Math.max(1, limit)).join('')}…` : compact;
|
|
845
|
+
return summarizeBncrTextPreview(raw, limit);
|
|
884
846
|
}
|
|
885
847
|
|
|
886
848
|
private summarizeScope(route: BncrRoute) {
|
|
@@ -953,25 +915,39 @@ class BncrBridgeRuntime {
|
|
|
953
915
|
clearAllBncrStatusWorkers(this.buildStatusWorkerRuntime(), reason);
|
|
954
916
|
}
|
|
955
917
|
|
|
956
|
-
private
|
|
957
|
-
|
|
958
|
-
) {
|
|
959
|
-
this.lastDriftSnapshot = buildRegisterDriftSnapshot({
|
|
960
|
-
capturedAt: now(),
|
|
918
|
+
private getRegisterTraceRuntimeState(): RegisterTraceRuntimeState {
|
|
919
|
+
return {
|
|
961
920
|
registerCount: this.registerCount,
|
|
962
921
|
apiGeneration: this.apiGeneration,
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
922
|
+
firstRegisterAt: this.firstRegisterAt,
|
|
923
|
+
lastRegisterAt: this.lastRegisterAt,
|
|
924
|
+
lastApiRebindAt: this.lastApiRebindAt,
|
|
925
|
+
pluginSource: this.pluginSource,
|
|
926
|
+
pluginVersion: this.pluginVersion,
|
|
927
|
+
lastApiInstanceId: this.lastApiInstanceId,
|
|
928
|
+
lastRegistryFingerprint: this.lastRegistryFingerprint,
|
|
929
|
+
lastDriftSnapshot: this.lastDriftSnapshot,
|
|
930
|
+
registerTraceRecent: this.registerTraceRecent,
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
private applyRegisterTraceRuntimeState(state: RegisterTraceRuntimeState) {
|
|
935
|
+
this.registerCount = state.registerCount;
|
|
936
|
+
this.apiGeneration = state.apiGeneration;
|
|
937
|
+
this.firstRegisterAt = state.firstRegisterAt;
|
|
938
|
+
this.lastRegisterAt = state.lastRegisterAt;
|
|
939
|
+
this.lastApiRebindAt = state.lastApiRebindAt;
|
|
940
|
+
this.pluginSource = state.pluginSource;
|
|
941
|
+
this.pluginVersion = state.pluginVersion;
|
|
942
|
+
this.lastApiInstanceId = state.lastApiInstanceId;
|
|
943
|
+
this.lastRegistryFingerprint = state.lastRegistryFingerprint;
|
|
944
|
+
this.lastDriftSnapshot = state.lastDriftSnapshot;
|
|
945
|
+
this.registerTraceRecent = state.registerTraceRecent;
|
|
969
946
|
}
|
|
970
947
|
|
|
971
948
|
private buildRegisterTraceSummary() {
|
|
972
|
-
return
|
|
973
|
-
|
|
974
|
-
firstRegisterAt: this.firstRegisterAt,
|
|
949
|
+
return buildRegisterTraceRuntimeSummary({
|
|
950
|
+
state: this.getRegisterTraceRuntimeState(),
|
|
975
951
|
warmupWindowMs: REGISTER_WARMUP_WINDOW_MS,
|
|
976
952
|
});
|
|
977
953
|
}
|
|
@@ -984,43 +960,25 @@ class BncrBridgeRuntime {
|
|
|
984
960
|
registryFingerprint?: string;
|
|
985
961
|
}) {
|
|
986
962
|
const ts = now();
|
|
987
|
-
this.registerCount += 1;
|
|
988
|
-
if (this.firstRegisterAt == null) this.firstRegisterAt = ts;
|
|
989
|
-
this.lastRegisterAt = ts;
|
|
990
|
-
if (meta.apiRebound) {
|
|
991
|
-
this.apiGeneration += 1;
|
|
992
|
-
this.lastApiRebindAt = ts;
|
|
993
|
-
} else if (this.registerCount === 1 && this.apiGeneration === 0) {
|
|
994
|
-
this.apiGeneration = 1;
|
|
995
|
-
}
|
|
996
|
-
if (meta.source) this.pluginSource = meta.source;
|
|
997
|
-
if (meta.pluginVersion) this.pluginVersion = meta.pluginVersion;
|
|
998
|
-
if (meta.apiInstanceId) this.lastApiInstanceId = meta.apiInstanceId;
|
|
999
|
-
if (meta.registryFingerprint) this.lastRegistryFingerprint = meta.registryFingerprint;
|
|
1000
|
-
|
|
1001
963
|
const stack = String(new Error().stack || '')
|
|
1002
964
|
.split('\n')
|
|
1003
965
|
.slice(2, 7)
|
|
1004
966
|
.map((line) => line.trim())
|
|
1005
967
|
.filter(Boolean)
|
|
1006
968
|
.join(' <- ');
|
|
1007
|
-
const
|
|
969
|
+
const state = this.getRegisterTraceRuntimeState();
|
|
970
|
+
const { trace, capturedDriftSnapshot } = noteRegisterTraceRuntime({
|
|
971
|
+
state,
|
|
972
|
+
meta,
|
|
1008
973
|
ts,
|
|
974
|
+
stack,
|
|
1009
975
|
bridgeId: this.bridgeId,
|
|
1010
976
|
gatewayPid: this.gatewayPid,
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
apiRebound: meta.apiRebound === true,
|
|
1014
|
-
apiInstanceId: this.lastApiInstanceId,
|
|
1015
|
-
registryFingerprint: this.lastRegistryFingerprint,
|
|
1016
|
-
source: this.pluginSource,
|
|
1017
|
-
pluginVersion: this.pluginVersion,
|
|
1018
|
-
stack,
|
|
977
|
+
warmupWindowMs: REGISTER_WARMUP_WINDOW_MS,
|
|
978
|
+
maxTraceEntries: 12,
|
|
1019
979
|
});
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
const summary = this.buildRegisterTraceSummary();
|
|
1023
|
-
if (summary.postWarmupRegisterCount > 0) this.captureDriftSnapshot(summary);
|
|
980
|
+
this.applyRegisterTraceRuntimeState(state);
|
|
981
|
+
if (capturedDriftSnapshot) this.scheduleSave();
|
|
1024
982
|
|
|
1025
983
|
this.logInfo('debug', `register-trace ${JSON.stringify(trace)}`, { debugOnly: true });
|
|
1026
984
|
}
|
|
@@ -1121,25 +1079,18 @@ class BncrBridgeRuntime {
|
|
|
1121
1079
|
}
|
|
1122
1080
|
|
|
1123
1081
|
private buildRuntimeSurfaceDiagnostics() {
|
|
1124
|
-
|
|
1125
|
-
const surfaces = {
|
|
1126
|
-
inbound: Boolean(channelRuntime?.inbound),
|
|
1127
|
-
media: Boolean(channelRuntime?.media),
|
|
1128
|
-
reply: Boolean(channelRuntime?.reply),
|
|
1129
|
-
routing: Boolean(channelRuntime?.routing),
|
|
1130
|
-
session: Boolean(channelRuntime?.session),
|
|
1131
|
-
};
|
|
1132
|
-
return {
|
|
1133
|
-
channel: surfaces,
|
|
1134
|
-
missing: Object.entries(surfaces)
|
|
1135
|
-
.filter(([, present]) => !present)
|
|
1136
|
-
.map(([name]) => name),
|
|
1137
|
-
};
|
|
1082
|
+
return buildOpenClawChannelRuntimeSurfaceDiagnostics(this.api);
|
|
1138
1083
|
}
|
|
1139
1084
|
|
|
1140
1085
|
private buildExtendedDiagnostics(accountId: string) {
|
|
1141
1086
|
const acc = normalizeAccountId(accountId);
|
|
1142
1087
|
const diagnostics = this.buildIntegratedDiagnostics(acc) as Record<string, any>;
|
|
1088
|
+
const outboxDiagnostics = this.buildOutboxDiagnostics(acc);
|
|
1089
|
+
const ackObservability = this.buildRuntimeAckObservability(acc);
|
|
1090
|
+
const prePushGuardSkipCount = this.getCounter(this.prePushGuardSkipCountByAccount, acc);
|
|
1091
|
+
const lastPrePushGuardSkipAt = this.lastPrePushGuardSkipAtByAccount.get(acc) || null;
|
|
1092
|
+
const lastPrePushGuardSkipReason = this.lastPrePushGuardSkipReasonByAccount.get(acc) || null;
|
|
1093
|
+
const hasGatewayContext = Boolean(this.gatewayContext);
|
|
1143
1094
|
return buildExtendedDiagnosticsFromRuntime({
|
|
1144
1095
|
diagnostics,
|
|
1145
1096
|
runtimeSurface: this.buildRuntimeSurfaceDiagnostics(),
|
|
@@ -1179,16 +1130,19 @@ class BncrBridgeRuntime {
|
|
|
1179
1130
|
isPrimary: entry.isPrimary,
|
|
1180
1131
|
})),
|
|
1181
1132
|
},
|
|
1182
|
-
outbound: {
|
|
1183
|
-
|
|
1133
|
+
outbound: buildExtendedOutboundDiagnostics({
|
|
1134
|
+
outbox: outboxDiagnostics,
|
|
1184
1135
|
enqueueCount: this.getCounter(this.outboundEnqueueCountByAccount, acc),
|
|
1185
1136
|
lastEnqueueAt: this.lastOutboundEnqueueAtByAccount.get(acc) || null,
|
|
1186
|
-
prePushGuardSkipCount
|
|
1187
|
-
lastPrePushGuardSkipAt
|
|
1188
|
-
lastPrePushGuardSkipReason
|
|
1189
|
-
hasGatewayContext
|
|
1137
|
+
prePushGuardSkipCount,
|
|
1138
|
+
lastPrePushGuardSkipAt,
|
|
1139
|
+
lastPrePushGuardSkipReason,
|
|
1140
|
+
hasGatewayContext,
|
|
1190
1141
|
lastGatewayContextAt: this.lastGatewayContextAt,
|
|
1191
|
-
|
|
1142
|
+
ackObservability,
|
|
1143
|
+
nowMs: now(),
|
|
1144
|
+
}),
|
|
1145
|
+
deadLetterSummary: this.buildDeadLetterDiagnostics(acc),
|
|
1192
1146
|
protocol: {
|
|
1193
1147
|
bridgeVersion: BRIDGE_VERSION,
|
|
1194
1148
|
protocolVersion: 2,
|
|
@@ -1305,6 +1259,64 @@ class BncrBridgeRuntime {
|
|
|
1305
1259
|
return map.get(normalizeAccountId(accountId)) || 0;
|
|
1306
1260
|
}
|
|
1307
1261
|
|
|
1262
|
+
private buildDeadLetterDiagnostics(accountId: string) {
|
|
1263
|
+
const acc = normalizeAccountId(accountId);
|
|
1264
|
+
return buildDeadLetterDiagnosticsFromRuntime({
|
|
1265
|
+
entries: this.getAccountDeadLetterEntries(acc),
|
|
1266
|
+
allAccountsTotal: this.deadLetter.length,
|
|
1267
|
+
sinceStart: this.getCounter(this.deadLetterSinceStartByAccount, acc),
|
|
1268
|
+
cappedAt: MAX_DEAD_LETTER_ENTRIES,
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
private logDeadLetterSummary(accountId: string, options?: { force?: boolean; source?: string }) {
|
|
1273
|
+
const acc = normalizeAccountId(accountId);
|
|
1274
|
+
const summary = this.buildDeadLetterDiagnostics(acc);
|
|
1275
|
+
const message = [
|
|
1276
|
+
`${acc}|total=${summary.total}`,
|
|
1277
|
+
`all=${summary.allAccountsTotal}`,
|
|
1278
|
+
`sinceStart=${summary.sinceStart}`,
|
|
1279
|
+
`top=${formatDeadLetterTopReasons(summary.topReasons)}`,
|
|
1280
|
+
`source=${options?.source || 'update'}`,
|
|
1281
|
+
].join('|');
|
|
1282
|
+
if (options?.force) {
|
|
1283
|
+
this.logInfo('deadLetter summary', message);
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
this.logInfoDedup('deadLetter summary', message, {
|
|
1287
|
+
key: `dead-letter-summary:${acc}:update`,
|
|
1288
|
+
sig: 'dead-letter-summary',
|
|
1289
|
+
windowMs: 5 * 60 * 1000,
|
|
1290
|
+
});
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
private buildOutboxDiagnostics(accountId: string) {
|
|
1294
|
+
const acc = normalizeAccountId(accountId);
|
|
1295
|
+
return buildOutboxQueueDiagnostics({
|
|
1296
|
+
accountId: acc,
|
|
1297
|
+
outboxEntries: this.outbox.values(),
|
|
1298
|
+
pendingAllAccounts: this.outbox.size,
|
|
1299
|
+
pushConnIds: this.resolvePushConnIds(acc),
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
private filterDeadLetterEntries(params: {
|
|
1304
|
+
accountId: string;
|
|
1305
|
+
reason?: string | null;
|
|
1306
|
+
olderThan?: number | null;
|
|
1307
|
+
}) {
|
|
1308
|
+
const acc = normalizeAccountId(params.accountId);
|
|
1309
|
+
const reason = asString(params.reason || '').trim();
|
|
1310
|
+
return this.getAccountDeadLetterEntries(acc).filter((entry) => {
|
|
1311
|
+
if (reason && entry.lastError !== reason) return false;
|
|
1312
|
+
if (typeof params.olderThan === 'number') {
|
|
1313
|
+
const createdAt = Number(entry.createdAt);
|
|
1314
|
+
if (!Number.isFinite(createdAt) || createdAt >= params.olderThan) return false;
|
|
1315
|
+
}
|
|
1316
|
+
return true;
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1308
1320
|
private async refreshDebugFlagFromConfig(options?: { forceLog?: boolean }) {
|
|
1309
1321
|
try {
|
|
1310
1322
|
const cfg = getOpenClawRuntimeConfig(this.api);
|
|
@@ -1380,45 +1392,23 @@ class BncrBridgeRuntime {
|
|
|
1380
1392
|
}
|
|
1381
1393
|
|
|
1382
1394
|
private countInvalidOutboxSessionKeys(accountId: string): number {
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
if (!parseStrictBncrSessionKey(entry.sessionKey)) count += 1;
|
|
1388
|
-
}
|
|
1389
|
-
return count;
|
|
1395
|
+
return countInvalidOutboxSessionKeysFromRuntime({
|
|
1396
|
+
accountId,
|
|
1397
|
+
outboxEntries: this.outbox.values(),
|
|
1398
|
+
});
|
|
1390
1399
|
}
|
|
1391
1400
|
|
|
1392
1401
|
private countLegacyAccountResidue(accountId: string): number {
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
if (mismatched(entry.accountId)) count += 1;
|
|
1404
|
-
}
|
|
1405
|
-
for (const info of this.sessionRoutes.values()) {
|
|
1406
|
-
if (mismatched(info.accountId)) count += 1;
|
|
1407
|
-
}
|
|
1408
|
-
for (const key of this.lastSessionByAccount.keys()) {
|
|
1409
|
-
if (mismatched(key)) count += 1;
|
|
1410
|
-
}
|
|
1411
|
-
for (const key of this.lastActivityByAccount.keys()) {
|
|
1412
|
-
if (mismatched(key)) count += 1;
|
|
1413
|
-
}
|
|
1414
|
-
for (const key of this.lastInboundByAccount.keys()) {
|
|
1415
|
-
if (mismatched(key)) count += 1;
|
|
1416
|
-
}
|
|
1417
|
-
for (const key of this.lastOutboundByAccount.keys()) {
|
|
1418
|
-
if (mismatched(key)) count += 1;
|
|
1419
|
-
}
|
|
1420
|
-
|
|
1421
|
-
return count;
|
|
1402
|
+
return countLegacyAccountResidueFromRuntime({
|
|
1403
|
+
accountId,
|
|
1404
|
+
outboxEntries: this.outbox.values(),
|
|
1405
|
+
deadLetterEntries: this.deadLetter,
|
|
1406
|
+
sessionRoutes: this.sessionRoutes.values(),
|
|
1407
|
+
lastSessionAccountIds: this.lastSessionByAccount.keys(),
|
|
1408
|
+
lastActivityAccountIds: this.lastActivityByAccount.keys(),
|
|
1409
|
+
lastInboundAccountIds: this.lastInboundByAccount.keys(),
|
|
1410
|
+
lastOutboundAccountIds: this.lastOutboundByAccount.keys(),
|
|
1411
|
+
});
|
|
1422
1412
|
}
|
|
1423
1413
|
|
|
1424
1414
|
private buildIntegratedDiagnostics(accountId: string) {
|
|
@@ -1447,86 +1437,71 @@ class BncrBridgeRuntime {
|
|
|
1447
1437
|
});
|
|
1448
1438
|
}
|
|
1449
1439
|
|
|
1450
|
-
private
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
sessionRoutes: [],
|
|
1440
|
+
private normalizePersistedOutboxEntry(entry: any): OutboxEntry | null {
|
|
1441
|
+
return normalizePersistedOutboxEntryFromRuntime({
|
|
1442
|
+
entry,
|
|
1443
|
+
canonicalAgentId: this.canonicalAgentId,
|
|
1444
|
+
now,
|
|
1456
1445
|
});
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
this.outbox.clear();
|
|
1460
|
-
for (const entry of data.outbox || []) {
|
|
1461
|
-
if (!entry?.messageId) continue;
|
|
1462
|
-
const accountId = normalizeAccountId(entry.accountId);
|
|
1463
|
-
const sessionKey = asString(entry.sessionKey || '').trim();
|
|
1464
|
-
const normalized = normalizeStoredSessionKey(sessionKey, this.canonicalAgentId);
|
|
1465
|
-
if (!normalized) continue;
|
|
1466
|
-
|
|
1467
|
-
const route = parseRouteLike(entry.route) || normalized.route;
|
|
1468
|
-
const payload =
|
|
1469
|
-
entry.payload && typeof entry.payload === 'object' ? { ...entry.payload } : {};
|
|
1470
|
-
(payload as any).sessionKey = normalized.sessionKey;
|
|
1471
|
-
(payload as any).platform = route.platform;
|
|
1472
|
-
(payload as any).groupId = route.groupId;
|
|
1473
|
-
(payload as any).userId = route.userId;
|
|
1474
|
-
|
|
1475
|
-
const migratedEntry: OutboxEntry = {
|
|
1476
|
-
...entry,
|
|
1477
|
-
accountId,
|
|
1478
|
-
sessionKey: normalized.sessionKey,
|
|
1479
|
-
route,
|
|
1480
|
-
payload,
|
|
1481
|
-
createdAt: finiteNumberOr(entry.createdAt, now()),
|
|
1482
|
-
retryCount: finiteNumberOr(entry.retryCount, 0),
|
|
1483
|
-
nextAttemptAt: finiteNumberOr(entry.nextAttemptAt, now()),
|
|
1484
|
-
lastAttemptAt: optionalFiniteNumber(entry.lastAttemptAt),
|
|
1485
|
-
lastError: entry.lastError ? asString(entry.lastError) : undefined,
|
|
1486
|
-
};
|
|
1446
|
+
}
|
|
1487
1447
|
|
|
1488
|
-
|
|
1448
|
+
private loadPersistedAccountTimestampMap(target: Map<string, number>, persisted: unknown): void {
|
|
1449
|
+
target.clear();
|
|
1450
|
+
const items = Array.isArray(persisted) ? persisted.slice(-MAX_ACCOUNT_ACTIVITY_ENTRIES) : [];
|
|
1451
|
+
for (const item of items) {
|
|
1452
|
+
const accountId = normalizeAccountId(item?.accountId);
|
|
1453
|
+
const updatedAt = finiteNumberOr(item?.updatedAt, 0);
|
|
1454
|
+
if (updatedAt <= 0) continue;
|
|
1455
|
+
target.set(accountId, updatedAt);
|
|
1489
1456
|
}
|
|
1457
|
+
}
|
|
1490
1458
|
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
const normalized = normalizeStoredSessionKey(sessionKey, this.canonicalAgentId);
|
|
1500
|
-
if (!normalized) continue;
|
|
1459
|
+
private dumpPersistedAccountTimestampMap(source: Map<string, number>) {
|
|
1460
|
+
return Array.from(source.entries())
|
|
1461
|
+
.map(([accountId, updatedAt]) => ({
|
|
1462
|
+
accountId,
|
|
1463
|
+
updatedAt,
|
|
1464
|
+
}))
|
|
1465
|
+
.slice(-MAX_ACCOUNT_ACTIVITY_ENTRIES);
|
|
1466
|
+
}
|
|
1501
1467
|
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1468
|
+
private loadPersistedLastSessionMap(persisted: unknown): void {
|
|
1469
|
+
this.lastSessionByAccount.clear();
|
|
1470
|
+
const items = Array.isArray(persisted) ? persisted.slice(-MAX_ACCOUNT_ACTIVITY_ENTRIES) : [];
|
|
1471
|
+
for (const item of items) {
|
|
1472
|
+
const accountId = normalizeAccountId(item?.accountId);
|
|
1473
|
+
const normalized = normalizeStoredSessionKey(
|
|
1474
|
+
asString(item?.sessionKey || ''),
|
|
1475
|
+
this.canonicalAgentId,
|
|
1476
|
+
);
|
|
1477
|
+
const updatedAt = finiteNumberOr(item?.updatedAt, 0);
|
|
1478
|
+
if (!normalized || updatedAt <= 0) continue;
|
|
1509
1479
|
|
|
1510
|
-
this.
|
|
1511
|
-
...entry,
|
|
1512
|
-
accountId,
|
|
1480
|
+
this.lastSessionByAccount.set(accountId, {
|
|
1513
1481
|
sessionKey: normalized.sessionKey,
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
retryCount: finiteNumberOr(entry.retryCount, 0),
|
|
1518
|
-
nextAttemptAt: finiteNumberOr(entry.nextAttemptAt, now()),
|
|
1519
|
-
lastAttemptAt: optionalFiniteNumber(entry.lastAttemptAt),
|
|
1520
|
-
lastError: entry.lastError ? asString(entry.lastError) : undefined,
|
|
1482
|
+
// 展示统一为 Bncr-platform:group:user
|
|
1483
|
+
scope: formatDisplayScope(normalized.route),
|
|
1484
|
+
updatedAt,
|
|
1521
1485
|
});
|
|
1522
1486
|
}
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
private dumpPersistedLastSessionMap() {
|
|
1490
|
+
return Array.from(this.lastSessionByAccount.entries())
|
|
1491
|
+
.map(([accountId, v]) => ({
|
|
1492
|
+
accountId,
|
|
1493
|
+
sessionKey: v.sessionKey,
|
|
1494
|
+
scope: v.scope,
|
|
1495
|
+
updatedAt: v.updatedAt,
|
|
1496
|
+
}))
|
|
1497
|
+
.slice(-MAX_ACCOUNT_ACTIVITY_ENTRIES);
|
|
1498
|
+
}
|
|
1523
1499
|
|
|
1500
|
+
private loadPersistedSessionRoutes(persisted: unknown): void {
|
|
1524
1501
|
this.sessionRoutes.clear();
|
|
1525
1502
|
this.routeAliases.clear();
|
|
1526
|
-
const
|
|
1527
|
-
|
|
1528
|
-
: [];
|
|
1529
|
-
for (const item of persistedSessionRoutes) {
|
|
1503
|
+
const items = Array.isArray(persisted) ? persisted.slice(-MAX_SESSION_ROUTE_ENTRIES) : [];
|
|
1504
|
+
for (const item of items) {
|
|
1530
1505
|
const normalized = normalizeStoredSessionKey(
|
|
1531
1506
|
asString(item?.sessionKey || ''),
|
|
1532
1507
|
this.canonicalAgentId,
|
|
@@ -1536,186 +1511,101 @@ class BncrBridgeRuntime {
|
|
|
1536
1511
|
const route = parseRouteLike(item?.route) || normalized.route;
|
|
1537
1512
|
const accountId = normalizeAccountId(item?.accountId);
|
|
1538
1513
|
const updatedAt = finiteNumberOr(item?.updatedAt, now());
|
|
1539
|
-
|
|
1540
|
-
const info = {
|
|
1541
|
-
accountId,
|
|
1542
|
-
route,
|
|
1543
|
-
updatedAt,
|
|
1544
|
-
};
|
|
1514
|
+
const info = { accountId, route, updatedAt };
|
|
1545
1515
|
|
|
1546
1516
|
this.sessionRoutes.set(normalized.sessionKey, info);
|
|
1547
1517
|
this.routeAliases.set(routeKey(accountId, route), info);
|
|
1548
1518
|
}
|
|
1519
|
+
}
|
|
1549
1520
|
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
const updatedAt = finiteNumberOr(item?.updatedAt, 0);
|
|
1561
|
-
if (!normalized || updatedAt <= 0) continue;
|
|
1521
|
+
private dumpPersistedSessionRoutes() {
|
|
1522
|
+
return Array.from(this.sessionRoutes.entries())
|
|
1523
|
+
.map(([sessionKey, v]) => ({
|
|
1524
|
+
sessionKey,
|
|
1525
|
+
accountId: v.accountId,
|
|
1526
|
+
route: v.route,
|
|
1527
|
+
updatedAt: v.updatedAt,
|
|
1528
|
+
}))
|
|
1529
|
+
.slice(-MAX_SESSION_ROUTE_ENTRIES);
|
|
1530
|
+
}
|
|
1562
1531
|
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
// 展示统一为 Bncr-platform:group:user
|
|
1566
|
-
scope: formatDisplayScope(normalized.route),
|
|
1567
|
-
updatedAt,
|
|
1568
|
-
});
|
|
1569
|
-
}
|
|
1532
|
+
private backfillAccountActivityFromSessionRoutes(): void {
|
|
1533
|
+
if (this.lastSessionByAccount.size > 0 || this.sessionRoutes.size === 0) return;
|
|
1570
1534
|
|
|
1571
|
-
this.
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
: [];
|
|
1575
|
-
for (const item of persistedLastActivityByAccount) {
|
|
1576
|
-
const accountId = normalizeAccountId(item?.accountId);
|
|
1577
|
-
const updatedAt = finiteNumberOr(item?.updatedAt, 0);
|
|
1535
|
+
for (const [sessionKey, info] of this.sessionRoutes.entries()) {
|
|
1536
|
+
const acc = normalizeAccountId(info.accountId);
|
|
1537
|
+
const updatedAt = finiteNumberOr(info.updatedAt, 0);
|
|
1578
1538
|
if (updatedAt <= 0) continue;
|
|
1579
|
-
|
|
1539
|
+
|
|
1540
|
+
const current = this.lastSessionByAccount.get(acc);
|
|
1541
|
+
if (!current || updatedAt >= current.updatedAt) {
|
|
1542
|
+
this.lastSessionByAccount.set(acc, {
|
|
1543
|
+
sessionKey,
|
|
1544
|
+
// 回填时统一展示为 Bncr-platform:group:user
|
|
1545
|
+
scope: formatDisplayScope(info.route),
|
|
1546
|
+
updatedAt,
|
|
1547
|
+
});
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
const lastAct = this.lastActivityByAccount.get(acc) || 0;
|
|
1551
|
+
if (updatedAt > lastAct) this.lastActivityByAccount.set(acc, updatedAt);
|
|
1552
|
+
|
|
1553
|
+
const lastIn = this.lastInboundByAccount.get(acc) || 0;
|
|
1554
|
+
if (updatedAt > lastIn) this.lastInboundByAccount.set(acc, updatedAt);
|
|
1580
1555
|
}
|
|
1556
|
+
}
|
|
1581
1557
|
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
: []
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1558
|
+
private async loadState() {
|
|
1559
|
+
if (!this.statePath) return;
|
|
1560
|
+
const loaded = await readOpenClawJsonFileWithFallback(this.statePath, {
|
|
1561
|
+
outbox: [],
|
|
1562
|
+
deadLetter: [],
|
|
1563
|
+
sessionRoutes: [],
|
|
1564
|
+
});
|
|
1565
|
+
const data = loaded.value as PersistedState;
|
|
1566
|
+
|
|
1567
|
+
this.outbox.clear();
|
|
1568
|
+
for (const entry of data.outbox || []) {
|
|
1569
|
+
const migratedEntry = this.normalizePersistedOutboxEntry(entry);
|
|
1570
|
+
if (!migratedEntry) continue;
|
|
1571
|
+
this.outbox.set(migratedEntry.messageId, migratedEntry);
|
|
1591
1572
|
}
|
|
1592
1573
|
|
|
1593
|
-
this.
|
|
1594
|
-
const
|
|
1595
|
-
? data.
|
|
1574
|
+
this.deadLetter = [];
|
|
1575
|
+
const persistedDeadLetter = Array.isArray(data.deadLetter)
|
|
1576
|
+
? data.deadLetter.slice(-MAX_DEAD_LETTER_ENTRIES)
|
|
1596
1577
|
: [];
|
|
1597
|
-
for (const
|
|
1598
|
-
const
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
this.lastOutboundByAccount.set(accountId, updatedAt);
|
|
1578
|
+
for (const entry of persistedDeadLetter) {
|
|
1579
|
+
const migratedEntry = this.normalizePersistedOutboxEntry(entry);
|
|
1580
|
+
if (!migratedEntry) continue;
|
|
1581
|
+
this.deadLetter.push(migratedEntry);
|
|
1602
1582
|
}
|
|
1603
1583
|
|
|
1604
|
-
this.
|
|
1605
|
-
data.lastDriftSnapshot && typeof data.lastDriftSnapshot === 'object'
|
|
1606
|
-
? {
|
|
1607
|
-
capturedAt: finiteNumberOr((data.lastDriftSnapshot as any).capturedAt, 0),
|
|
1608
|
-
registerCount: Number.isFinite(Number((data.lastDriftSnapshot as any).registerCount))
|
|
1609
|
-
? Number((data.lastDriftSnapshot as any).registerCount)
|
|
1610
|
-
: null,
|
|
1611
|
-
apiGeneration: Number.isFinite(Number((data.lastDriftSnapshot as any).apiGeneration))
|
|
1612
|
-
? Number((data.lastDriftSnapshot as any).apiGeneration)
|
|
1613
|
-
: null,
|
|
1614
|
-
postWarmupRegisterCount: Number.isFinite(
|
|
1615
|
-
Number((data.lastDriftSnapshot as any).postWarmupRegisterCount),
|
|
1616
|
-
)
|
|
1617
|
-
? Number((data.lastDriftSnapshot as any).postWarmupRegisterCount)
|
|
1618
|
-
: null,
|
|
1619
|
-
apiInstanceId:
|
|
1620
|
-
asString((data.lastDriftSnapshot as any).apiInstanceId || '').trim() || null,
|
|
1621
|
-
registryFingerprint:
|
|
1622
|
-
asString((data.lastDriftSnapshot as any).registryFingerprint || '').trim() || null,
|
|
1623
|
-
dominantBucket:
|
|
1624
|
-
asString((data.lastDriftSnapshot as any).dominantBucket || '').trim() || null,
|
|
1625
|
-
sourceBuckets:
|
|
1626
|
-
(data.lastDriftSnapshot as any).sourceBuckets &&
|
|
1627
|
-
typeof (data.lastDriftSnapshot as any).sourceBuckets === 'object'
|
|
1628
|
-
? { ...((data.lastDriftSnapshot as any).sourceBuckets as Record<string, number>) }
|
|
1629
|
-
: {},
|
|
1630
|
-
traceWindowSize: finiteNumberOr((data.lastDriftSnapshot as any).traceWindowSize, 0),
|
|
1631
|
-
traceRecent: Array.isArray((data.lastDriftSnapshot as any).traceRecent)
|
|
1632
|
-
? [...((data.lastDriftSnapshot as any).traceRecent as Array<Record<string, unknown>>)]
|
|
1633
|
-
: [],
|
|
1634
|
-
}
|
|
1635
|
-
: null;
|
|
1584
|
+
this.loadPersistedSessionRoutes(data.sessionRoutes);
|
|
1636
1585
|
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
const updatedAt = finiteNumberOr(info.updatedAt, 0);
|
|
1642
|
-
if (updatedAt <= 0) continue;
|
|
1643
|
-
|
|
1644
|
-
const current = this.lastSessionByAccount.get(acc);
|
|
1645
|
-
if (!current || updatedAt >= current.updatedAt) {
|
|
1646
|
-
this.lastSessionByAccount.set(acc, {
|
|
1647
|
-
sessionKey,
|
|
1648
|
-
// 回填时统一展示为 Bncr-platform:group:user
|
|
1649
|
-
scope: formatDisplayScope(info.route),
|
|
1650
|
-
updatedAt,
|
|
1651
|
-
});
|
|
1652
|
-
}
|
|
1586
|
+
this.loadPersistedLastSessionMap(data.lastSessionByAccount);
|
|
1587
|
+
this.loadPersistedAccountTimestampMap(this.lastActivityByAccount, data.lastActivityByAccount);
|
|
1588
|
+
this.loadPersistedAccountTimestampMap(this.lastInboundByAccount, data.lastInboundByAccount);
|
|
1589
|
+
this.loadPersistedAccountTimestampMap(this.lastOutboundByAccount, data.lastOutboundByAccount);
|
|
1653
1590
|
|
|
1654
|
-
|
|
1655
|
-
if (updatedAt > lastAct) this.lastActivityByAccount.set(acc, updatedAt);
|
|
1591
|
+
this.lastDriftSnapshot = normalizeRegisterDriftSnapshot(data.lastDriftSnapshot);
|
|
1656
1592
|
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
}
|
|
1660
|
-
}
|
|
1593
|
+
// 兼容旧状态文件:若尚未持久化 lastSession*/lastActivity*,从 sessionRoutes 回填。
|
|
1594
|
+
this.backfillAccountActivityFromSessionRoutes();
|
|
1661
1595
|
}
|
|
1662
1596
|
|
|
1663
1597
|
private async flushState() {
|
|
1664
1598
|
if (!this.statePath) return;
|
|
1665
1599
|
|
|
1666
|
-
const sessionRoutes = Array.from(this.sessionRoutes.entries())
|
|
1667
|
-
.map(([sessionKey, v]) => ({
|
|
1668
|
-
sessionKey,
|
|
1669
|
-
accountId: v.accountId,
|
|
1670
|
-
route: v.route,
|
|
1671
|
-
updatedAt: v.updatedAt,
|
|
1672
|
-
}))
|
|
1673
|
-
.slice(-MAX_SESSION_ROUTE_ENTRIES);
|
|
1674
|
-
|
|
1675
1600
|
const data: PersistedState = {
|
|
1676
1601
|
outbox: Array.from(this.outbox.values()),
|
|
1677
1602
|
deadLetter: this.deadLetter.slice(-MAX_DEAD_LETTER_ENTRIES),
|
|
1678
|
-
sessionRoutes,
|
|
1679
|
-
lastSessionByAccount:
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
updatedAt: v.updatedAt,
|
|
1685
|
-
}))
|
|
1686
|
-
.slice(-MAX_ACCOUNT_ACTIVITY_ENTRIES),
|
|
1687
|
-
lastActivityByAccount: Array.from(this.lastActivityByAccount.entries())
|
|
1688
|
-
.map(([accountId, updatedAt]) => ({
|
|
1689
|
-
accountId,
|
|
1690
|
-
updatedAt,
|
|
1691
|
-
}))
|
|
1692
|
-
.slice(-MAX_ACCOUNT_ACTIVITY_ENTRIES),
|
|
1693
|
-
lastInboundByAccount: Array.from(this.lastInboundByAccount.entries())
|
|
1694
|
-
.map(([accountId, updatedAt]) => ({
|
|
1695
|
-
accountId,
|
|
1696
|
-
updatedAt,
|
|
1697
|
-
}))
|
|
1698
|
-
.slice(-MAX_ACCOUNT_ACTIVITY_ENTRIES),
|
|
1699
|
-
lastOutboundByAccount: Array.from(this.lastOutboundByAccount.entries())
|
|
1700
|
-
.map(([accountId, updatedAt]) => ({
|
|
1701
|
-
accountId,
|
|
1702
|
-
updatedAt,
|
|
1703
|
-
}))
|
|
1704
|
-
.slice(-MAX_ACCOUNT_ACTIVITY_ENTRIES),
|
|
1705
|
-
lastDriftSnapshot: this.lastDriftSnapshot
|
|
1706
|
-
? {
|
|
1707
|
-
capturedAt: this.lastDriftSnapshot.capturedAt,
|
|
1708
|
-
registerCount: this.lastDriftSnapshot.registerCount,
|
|
1709
|
-
apiGeneration: this.lastDriftSnapshot.apiGeneration,
|
|
1710
|
-
postWarmupRegisterCount: this.lastDriftSnapshot.postWarmupRegisterCount,
|
|
1711
|
-
apiInstanceId: this.lastDriftSnapshot.apiInstanceId,
|
|
1712
|
-
registryFingerprint: this.lastDriftSnapshot.registryFingerprint,
|
|
1713
|
-
dominantBucket: this.lastDriftSnapshot.dominantBucket,
|
|
1714
|
-
sourceBuckets: { ...this.lastDriftSnapshot.sourceBuckets },
|
|
1715
|
-
traceWindowSize: this.lastDriftSnapshot.traceWindowSize,
|
|
1716
|
-
traceRecent: this.lastDriftSnapshot.traceRecent.map((trace) => ({ ...trace })),
|
|
1717
|
-
}
|
|
1718
|
-
: null,
|
|
1603
|
+
sessionRoutes: this.dumpPersistedSessionRoutes(),
|
|
1604
|
+
lastSessionByAccount: this.dumpPersistedLastSessionMap(),
|
|
1605
|
+
lastActivityByAccount: this.dumpPersistedAccountTimestampMap(this.lastActivityByAccount),
|
|
1606
|
+
lastInboundByAccount: this.dumpPersistedAccountTimestampMap(this.lastInboundByAccount),
|
|
1607
|
+
lastOutboundByAccount: this.dumpPersistedAccountTimestampMap(this.lastOutboundByAccount),
|
|
1608
|
+
lastDriftSnapshot: dumpRegisterDriftSnapshot(this.lastDriftSnapshot),
|
|
1719
1609
|
};
|
|
1720
1610
|
|
|
1721
1611
|
await writeOpenClawJsonFileAtomically(this.statePath, data);
|
|
@@ -1744,64 +1634,30 @@ class BncrBridgeRuntime {
|
|
|
1744
1634
|
const primaryKey = this.activeConnectionByAccount.get(acc);
|
|
1745
1635
|
const primary = primaryKey ? this.connections.get(primaryKey) : null;
|
|
1746
1636
|
|
|
1747
|
-
const isEligible = (
|
|
1748
|
-
conn: BncrConnection | null | undefined,
|
|
1749
|
-
): conn is BncrConnection & {
|
|
1750
|
-
outboundReadyUntil?: number;
|
|
1751
|
-
preferredForOutboundUntil?: number;
|
|
1752
|
-
inboundOnly?: boolean;
|
|
1753
|
-
} => {
|
|
1754
|
-
if (!conn?.connId) return false;
|
|
1755
|
-
if (t - conn.lastSeenAt > CONNECT_TTL_MS) return false;
|
|
1756
|
-
if ((conn as any).inboundOnly === true) return false;
|
|
1757
|
-
return true;
|
|
1758
|
-
};
|
|
1759
|
-
|
|
1760
1637
|
const recentInboundConnIds = this.resolveRecentInboundConnIds(acc);
|
|
1761
|
-
const candidateScore = (conn: BncrConnection) => {
|
|
1762
|
-
const preferredForOutboundUntil = finiteNumberOr((conn as any).preferredForOutboundUntil, 0);
|
|
1763
|
-
const outboundReadyUntil = finiteNumberOr((conn as any).outboundReadyUntil, 0);
|
|
1764
|
-
const lastPushTimeoutAt = finiteNumberOr((conn as any).lastPushTimeoutAt, 0);
|
|
1765
|
-
const lastAckOkAt = finiteNumberOr((conn as any).lastAckOkAt, 0);
|
|
1766
|
-
const pushFailureScore = finiteNumberOr((conn as any).pushFailureScore, 0);
|
|
1767
|
-
const recentTimeoutPenalty = lastPushTimeoutAt > 0 && t - lastPushTimeoutAt <= 30_000 ? 1 : 0;
|
|
1768
|
-
return {
|
|
1769
|
-
preferred: preferredForOutboundUntil > t ? 1 : 0,
|
|
1770
|
-
ready: outboundReadyUntil > t ? 1 : 0,
|
|
1771
|
-
recentInbound: recentInboundConnIds.has(conn.connId) ? 1 : 0,
|
|
1772
|
-
recentTimeoutPenalty,
|
|
1773
|
-
pushFailureScore,
|
|
1774
|
-
lastAckOkAt,
|
|
1775
|
-
lastPushTimeoutAt,
|
|
1776
|
-
lastSeenAt: conn.lastSeenAt,
|
|
1777
|
-
connectedAt: conn.connectedAt,
|
|
1778
|
-
};
|
|
1779
|
-
};
|
|
1780
1638
|
|
|
1781
|
-
if (
|
|
1782
|
-
|
|
1783
|
-
|
|
1639
|
+
if (
|
|
1640
|
+
isEligibleOutboundPushConnection({
|
|
1641
|
+
connection: primary,
|
|
1642
|
+
now: t,
|
|
1643
|
+
connectTtlMs: CONNECT_TTL_MS,
|
|
1644
|
+
})
|
|
1645
|
+
) {
|
|
1646
|
+
const preferredForOutboundUntil = finiteNumberOr(
|
|
1647
|
+
(primary as any).preferredForOutboundUntil,
|
|
1648
|
+
0,
|
|
1649
|
+
);
|
|
1650
|
+
const outboundReadyUntil = finiteNumberOr((primary as any).outboundReadyUntil, 0);
|
|
1651
|
+
if (preferredForOutboundUntil > t || outboundReadyUntil > t) return primary;
|
|
1784
1652
|
}
|
|
1785
1653
|
|
|
1786
|
-
const candidates =
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
if (sb.ready !== sa.ready) return sb.ready - sa.ready;
|
|
1794
|
-
if (sa.recentTimeoutPenalty !== sb.recentTimeoutPenalty)
|
|
1795
|
-
return sa.recentTimeoutPenalty - sb.recentTimeoutPenalty;
|
|
1796
|
-
if (sa.pushFailureScore !== sb.pushFailureScore)
|
|
1797
|
-
return sa.pushFailureScore - sb.pushFailureScore;
|
|
1798
|
-
if (sb.lastAckOkAt !== sa.lastAckOkAt) return sb.lastAckOkAt - sa.lastAckOkAt;
|
|
1799
|
-
if (sa.lastPushTimeoutAt !== sb.lastPushTimeoutAt)
|
|
1800
|
-
return sa.lastPushTimeoutAt - sb.lastPushTimeoutAt;
|
|
1801
|
-
if (sb.recentInbound !== sa.recentInbound) return sb.recentInbound - sa.recentInbound;
|
|
1802
|
-
if (sb.lastSeenAt !== sa.lastSeenAt) return sb.lastSeenAt - sa.lastSeenAt;
|
|
1803
|
-
return sb.connectedAt - sa.connectedAt;
|
|
1804
|
-
});
|
|
1654
|
+
const candidates = selectOrderedOutboundPushConnections({
|
|
1655
|
+
accountId: acc,
|
|
1656
|
+
now: t,
|
|
1657
|
+
connectTtlMs: CONNECT_TTL_MS,
|
|
1658
|
+
recentInboundConnIds,
|
|
1659
|
+
connections: this.connections.values(),
|
|
1660
|
+
});
|
|
1805
1661
|
|
|
1806
1662
|
const next = candidates[0] || null;
|
|
1807
1663
|
if (!next) return null;
|
|
@@ -1845,67 +1701,29 @@ class BncrBridgeRuntime {
|
|
|
1845
1701
|
const t = now();
|
|
1846
1702
|
const connIds = new Set<string>();
|
|
1847
1703
|
|
|
1848
|
-
const isEligible = (
|
|
1849
|
-
conn: BncrConnection | null | undefined,
|
|
1850
|
-
): conn is BncrConnection & {
|
|
1851
|
-
outboundReadyUntil?: number;
|
|
1852
|
-
preferredForOutboundUntil?: number;
|
|
1853
|
-
inboundOnly?: boolean;
|
|
1854
|
-
} => {
|
|
1855
|
-
if (!conn?.connId) return false;
|
|
1856
|
-
if (t - conn.lastSeenAt > CONNECT_TTL_MS) return false;
|
|
1857
|
-
if ((conn as any).inboundOnly === true) return false;
|
|
1858
|
-
return true;
|
|
1859
|
-
};
|
|
1860
|
-
|
|
1861
1704
|
const recentInboundConnIds = this.resolveRecentInboundConnIds(acc);
|
|
1862
|
-
const candidateScore = (conn: BncrConnection) => {
|
|
1863
|
-
const preferredForOutboundUntil = finiteNumberOr((conn as any).preferredForOutboundUntil, 0);
|
|
1864
|
-
const outboundReadyUntil = finiteNumberOr((conn as any).outboundReadyUntil, 0);
|
|
1865
|
-
const lastPushTimeoutAt = finiteNumberOr((conn as any).lastPushTimeoutAt, 0);
|
|
1866
|
-
const lastAckOkAt = finiteNumberOr((conn as any).lastAckOkAt, 0);
|
|
1867
|
-
const pushFailureScore = finiteNumberOr((conn as any).pushFailureScore, 0);
|
|
1868
|
-
const recentTimeoutPenalty = lastPushTimeoutAt > 0 && t - lastPushTimeoutAt <= 30_000 ? 1 : 0;
|
|
1869
|
-
return {
|
|
1870
|
-
preferred: preferredForOutboundUntil > t ? 1 : 0,
|
|
1871
|
-
ready: outboundReadyUntil > t ? 1 : 0,
|
|
1872
|
-
recentInbound: recentInboundConnIds.has(conn.connId) ? 1 : 0,
|
|
1873
|
-
recentTimeoutPenalty,
|
|
1874
|
-
pushFailureScore,
|
|
1875
|
-
lastAckOkAt,
|
|
1876
|
-
lastPushTimeoutAt,
|
|
1877
|
-
lastSeenAt: conn.lastSeenAt,
|
|
1878
|
-
connectedAt: conn.connectedAt,
|
|
1879
|
-
};
|
|
1880
|
-
};
|
|
1881
1705
|
|
|
1882
1706
|
const primaryKey = this.activeConnectionByAccount.get(acc);
|
|
1883
1707
|
if (primaryKey) {
|
|
1884
1708
|
const primary = this.connections.get(primaryKey);
|
|
1885
|
-
if (
|
|
1709
|
+
if (
|
|
1710
|
+
isEligibleOutboundPushConnection({
|
|
1711
|
+
connection: primary,
|
|
1712
|
+
now: t,
|
|
1713
|
+
connectTtlMs: CONNECT_TTL_MS,
|
|
1714
|
+
})
|
|
1715
|
+
) {
|
|
1886
1716
|
connIds.add(primary.connId);
|
|
1887
1717
|
}
|
|
1888
1718
|
}
|
|
1889
1719
|
|
|
1890
|
-
const candidates =
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
if (sb.ready !== sa.ready) return sb.ready - sa.ready;
|
|
1898
|
-
if (sa.recentTimeoutPenalty !== sb.recentTimeoutPenalty)
|
|
1899
|
-
return sa.recentTimeoutPenalty - sb.recentTimeoutPenalty;
|
|
1900
|
-
if (sa.pushFailureScore !== sb.pushFailureScore)
|
|
1901
|
-
return sa.pushFailureScore - sb.pushFailureScore;
|
|
1902
|
-
if (sb.lastAckOkAt !== sa.lastAckOkAt) return sb.lastAckOkAt - sa.lastAckOkAt;
|
|
1903
|
-
if (sa.lastPushTimeoutAt !== sb.lastPushTimeoutAt)
|
|
1904
|
-
return sa.lastPushTimeoutAt - sb.lastPushTimeoutAt;
|
|
1905
|
-
if (sb.recentInbound !== sa.recentInbound) return sb.recentInbound - sa.recentInbound;
|
|
1906
|
-
if (sb.lastSeenAt !== sa.lastSeenAt) return sb.lastSeenAt - sa.lastSeenAt;
|
|
1907
|
-
return sb.connectedAt - sa.connectedAt;
|
|
1908
|
-
});
|
|
1720
|
+
const candidates = selectOrderedOutboundPushConnections({
|
|
1721
|
+
accountId: acc,
|
|
1722
|
+
now: t,
|
|
1723
|
+
connectTtlMs: CONNECT_TTL_MS,
|
|
1724
|
+
recentInboundConnIds,
|
|
1725
|
+
connections: this.connections.values(),
|
|
1726
|
+
});
|
|
1909
1727
|
|
|
1910
1728
|
for (const c of candidates) {
|
|
1911
1729
|
connIds.add(c.connId);
|
|
@@ -2202,6 +2020,7 @@ class BncrBridgeRuntime {
|
|
|
2202
2020
|
audioAsVoice?: boolean;
|
|
2203
2021
|
kind?: 'tool' | 'block' | 'final';
|
|
2204
2022
|
replyToId?: string;
|
|
2023
|
+
replyTargetPolicy?: OutboundReplyTargetPolicy;
|
|
2205
2024
|
}): OutboxEntry {
|
|
2206
2025
|
return buildFileTransferOutboxEntryFromRuntime({
|
|
2207
2026
|
createMessageId: () => randomUUID(),
|
|
@@ -2218,6 +2037,7 @@ class BncrBridgeRuntime {
|
|
|
2218
2037
|
audioAsVoice: params.audioAsVoice,
|
|
2219
2038
|
kind: params.kind,
|
|
2220
2039
|
replyToId: asString(params.replyToId || '').trim() || undefined,
|
|
2040
|
+
replyTargetPolicy: params.replyTargetPolicy,
|
|
2221
2041
|
});
|
|
2222
2042
|
}
|
|
2223
2043
|
|
|
@@ -2328,6 +2148,7 @@ class BncrBridgeRuntime {
|
|
|
2328
2148
|
text: string;
|
|
2329
2149
|
kind?: 'tool' | 'block' | 'final';
|
|
2330
2150
|
replyToId?: string;
|
|
2151
|
+
replyTargetPolicy?: OutboundReplyTargetPolicy;
|
|
2331
2152
|
}): OutboxEntry {
|
|
2332
2153
|
return buildTextOutboxEntryFromRuntime({
|
|
2333
2154
|
createMessageId: () => randomUUID(),
|
|
@@ -2340,6 +2161,7 @@ class BncrBridgeRuntime {
|
|
|
2340
2161
|
text: params.text,
|
|
2341
2162
|
kind: params.kind,
|
|
2342
2163
|
replyToId: params.replyToId,
|
|
2164
|
+
replyTargetPolicy: params.replyTargetPolicy,
|
|
2343
2165
|
});
|
|
2344
2166
|
}
|
|
2345
2167
|
|
|
@@ -3204,6 +3026,41 @@ class BncrBridgeRuntime {
|
|
|
3204
3026
|
return Array.from(this.outbox.values()).filter((entry) => entry.accountId === acc);
|
|
3205
3027
|
}
|
|
3206
3028
|
|
|
3029
|
+
private getAccountDeadLetterEntries(accountId: string) {
|
|
3030
|
+
const acc = normalizeAccountId(accountId);
|
|
3031
|
+
return this.deadLetter.filter((entry) => entry.accountId === acc);
|
|
3032
|
+
}
|
|
3033
|
+
|
|
3034
|
+
private buildAccountQueueCounters(accountId: string) {
|
|
3035
|
+
return {
|
|
3036
|
+
activeConnections: this.activeConnectionCount(accountId),
|
|
3037
|
+
pending: this.getAccountPendingOutboxEntries(accountId).length,
|
|
3038
|
+
deadLetter: this.getAccountDeadLetterEntries(accountId).length,
|
|
3039
|
+
};
|
|
3040
|
+
}
|
|
3041
|
+
|
|
3042
|
+
private buildActiveConnectionDebugList(
|
|
3043
|
+
accountId: string,
|
|
3044
|
+
options?: { includeOutboundState?: boolean },
|
|
3045
|
+
) {
|
|
3046
|
+
const acc = normalizeAccountId(accountId);
|
|
3047
|
+
return Array.from(this.connections.values())
|
|
3048
|
+
.filter((conn) => conn.accountId === acc)
|
|
3049
|
+
.map((conn) => ({
|
|
3050
|
+
connId: conn.connId,
|
|
3051
|
+
clientId: conn.clientId,
|
|
3052
|
+
connectedAt: conn.connectedAt,
|
|
3053
|
+
lastSeenAt: conn.lastSeenAt,
|
|
3054
|
+
...(options?.includeOutboundState
|
|
3055
|
+
? {
|
|
3056
|
+
outboundReadyUntil: (conn as any).outboundReadyUntil || null,
|
|
3057
|
+
preferredForOutboundUntil: (conn as any).preferredForOutboundUntil || null,
|
|
3058
|
+
inboundOnly: (conn as any).inboundOnly === true,
|
|
3059
|
+
}
|
|
3060
|
+
: {}),
|
|
3061
|
+
}));
|
|
3062
|
+
}
|
|
3063
|
+
|
|
3207
3064
|
private maybeLogOutboxDrainStuck(args: { accountId: string; trigger: string; reason: string }) {
|
|
3208
3065
|
const acc = normalizeAccountId(args.accountId);
|
|
3209
3066
|
const startedAt = this.pushDrainRunningSinceByAccount.get(acc) || 0;
|
|
@@ -3834,17 +3691,9 @@ class BncrBridgeRuntime {
|
|
|
3834
3691
|
previousActiveConn,
|
|
3835
3692
|
nextActiveKey: key,
|
|
3836
3693
|
nextActiveConn: nextConn,
|
|
3837
|
-
activeConnections:
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
connId: c.connId,
|
|
3841
|
-
clientId: c.clientId,
|
|
3842
|
-
connectedAt: c.connectedAt,
|
|
3843
|
-
lastSeenAt: c.lastSeenAt,
|
|
3844
|
-
outboundReadyUntil: (c as any).outboundReadyUntil || null,
|
|
3845
|
-
preferredForOutboundUntil: (c as any).preferredForOutboundUntil || null,
|
|
3846
|
-
inboundOnly: (c as any).inboundOnly === true,
|
|
3847
|
-
})),
|
|
3694
|
+
activeConnections: this.buildActiveConnectionDebugList(acc, {
|
|
3695
|
+
includeOutboundState: true,
|
|
3696
|
+
}),
|
|
3848
3697
|
})}`,
|
|
3849
3698
|
{ debugOnly: true },
|
|
3850
3699
|
);
|
|
@@ -3864,17 +3713,9 @@ class BncrBridgeRuntime {
|
|
|
3864
3713
|
previousActiveConn,
|
|
3865
3714
|
nextActiveKey: key,
|
|
3866
3715
|
nextActiveConn: nextConn,
|
|
3867
|
-
activeConnections:
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
connId: c.connId,
|
|
3871
|
-
clientId: c.clientId,
|
|
3872
|
-
connectedAt: c.connectedAt,
|
|
3873
|
-
lastSeenAt: c.lastSeenAt,
|
|
3874
|
-
outboundReadyUntil: (c as any).outboundReadyUntil || null,
|
|
3875
|
-
preferredForOutboundUntil: (c as any).preferredForOutboundUntil || null,
|
|
3876
|
-
inboundOnly: (c as any).inboundOnly === true,
|
|
3877
|
-
})),
|
|
3716
|
+
activeConnections: this.buildActiveConnectionDebugList(acc, {
|
|
3717
|
+
includeOutboundState: true,
|
|
3718
|
+
}),
|
|
3878
3719
|
})}`,
|
|
3879
3720
|
{ debugOnly: true },
|
|
3880
3721
|
);
|
|
@@ -4365,45 +4206,6 @@ class BncrBridgeRuntime {
|
|
|
4365
4206
|
return true;
|
|
4366
4207
|
}
|
|
4367
4208
|
|
|
4368
|
-
private computeRecommendedAckTimeoutReason(args: {
|
|
4369
|
-
lateAckOkCount: number;
|
|
4370
|
-
recentAckTimeoutCount: number;
|
|
4371
|
-
lastLateAckPushLatencyMs: number | null;
|
|
4372
|
-
lastLateAckOkAt?: number | null;
|
|
4373
|
-
adaptiveAckRecoveryOkCount?: number;
|
|
4374
|
-
recommendedAckTimeoutMs?: number;
|
|
4375
|
-
nowMs?: number;
|
|
4376
|
-
}) {
|
|
4377
|
-
return computeBncrRecommendedAckTimeoutReason({
|
|
4378
|
-
...args,
|
|
4379
|
-
nowMs: typeof args.nowMs === 'number' ? args.nowMs : now(),
|
|
4380
|
-
defaultAckTimeoutMs: PUSH_ACK_TIMEOUT_MS,
|
|
4381
|
-
minAckTimeoutMs: RECOMMENDED_ACK_TIMEOUT_MIN_MS,
|
|
4382
|
-
maxAckTimeoutMs: RECOMMENDED_ACK_TIMEOUT_MAX_MS,
|
|
4383
|
-
lateAckObservationTtlMs: ADAPTIVE_ACK_TIMEOUT_OBSERVATION_TTL_MS,
|
|
4384
|
-
recoveryOkThreshold: ADAPTIVE_ACK_TIMEOUT_RECOVERY_OK_THRESHOLD,
|
|
4385
|
-
});
|
|
4386
|
-
}
|
|
4387
|
-
|
|
4388
|
-
private computeRecommendedAckTimeoutMs(args: {
|
|
4389
|
-
lateAckOkCount: number;
|
|
4390
|
-
recentAckTimeoutCount: number;
|
|
4391
|
-
lastLateAckPushLatencyMs: number | null;
|
|
4392
|
-
lastLateAckOkAt?: number | null;
|
|
4393
|
-
adaptiveAckRecoveryOkCount?: number;
|
|
4394
|
-
nowMs?: number;
|
|
4395
|
-
}) {
|
|
4396
|
-
return computeBncrRecommendedAckTimeoutMs({
|
|
4397
|
-
...args,
|
|
4398
|
-
nowMs: typeof args.nowMs === 'number' ? args.nowMs : now(),
|
|
4399
|
-
defaultAckTimeoutMs: PUSH_ACK_TIMEOUT_MS,
|
|
4400
|
-
minAckTimeoutMs: RECOMMENDED_ACK_TIMEOUT_MIN_MS,
|
|
4401
|
-
maxAckTimeoutMs: RECOMMENDED_ACK_TIMEOUT_MAX_MS,
|
|
4402
|
-
lateAckObservationTtlMs: ADAPTIVE_ACK_TIMEOUT_OBSERVATION_TTL_MS,
|
|
4403
|
-
recoveryOkThreshold: ADAPTIVE_ACK_TIMEOUT_RECOVERY_OK_THRESHOLD,
|
|
4404
|
-
});
|
|
4405
|
-
}
|
|
4406
|
-
|
|
4407
4209
|
private maybeLogAdaptiveAckTimeout(args: {
|
|
4408
4210
|
accountId: string;
|
|
4409
4211
|
timeoutMs: number;
|
|
@@ -4451,22 +4253,18 @@ class BncrBridgeRuntime {
|
|
|
4451
4253
|
acc,
|
|
4452
4254
|
);
|
|
4453
4255
|
const nowMs = now();
|
|
4454
|
-
const timeoutMs =
|
|
4256
|
+
const { timeoutMs, reason } = resolveBncrRuntimeAckTimeoutDecision({
|
|
4455
4257
|
lateAckOkCount,
|
|
4456
4258
|
recentAckTimeoutCount,
|
|
4457
4259
|
lastLateAckPushLatencyMs,
|
|
4458
4260
|
lastLateAckOkAt,
|
|
4459
4261
|
adaptiveAckRecoveryOkCount,
|
|
4460
4262
|
nowMs,
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
lastLateAckOkAt,
|
|
4467
|
-
adaptiveAckRecoveryOkCount,
|
|
4468
|
-
recommendedAckTimeoutMs: timeoutMs,
|
|
4469
|
-
nowMs,
|
|
4263
|
+
defaultAckTimeoutMs: PUSH_ACK_TIMEOUT_MS,
|
|
4264
|
+
minAckTimeoutMs: RECOMMENDED_ACK_TIMEOUT_MIN_MS,
|
|
4265
|
+
maxAckTimeoutMs: RECOMMENDED_ACK_TIMEOUT_MAX_MS,
|
|
4266
|
+
lateAckObservationTtlMs: ADAPTIVE_ACK_TIMEOUT_OBSERVATION_TTL_MS,
|
|
4267
|
+
recoveryOkThreshold: ADAPTIVE_ACK_TIMEOUT_RECOVERY_OK_THRESHOLD,
|
|
4470
4268
|
});
|
|
4471
4269
|
this.maybeLogAdaptiveAckTimeout({
|
|
4472
4270
|
accountId: acc,
|
|
@@ -4485,58 +4283,30 @@ class BncrBridgeRuntime {
|
|
|
4485
4283
|
const lastLateAckPushLatencyMs = this.lastLateAckPushLatencyMsByAccount.get(acc) || null;
|
|
4486
4284
|
const lastLateAckOkAt = this.lastLateAckOkByAccount.get(acc) || null;
|
|
4487
4285
|
const nowMs = now();
|
|
4488
|
-
const lastLateAckAgeMs =
|
|
4489
|
-
typeof lastLateAckOkAt === 'number' && lastLateAckOkAt > 0
|
|
4490
|
-
? Math.max(0, nowMs - lastLateAckOkAt)
|
|
4491
|
-
: null;
|
|
4492
|
-
const lateAckObservationTtlMs = ADAPTIVE_ACK_TIMEOUT_OBSERVATION_TTL_MS;
|
|
4493
|
-
const lateAckObservationExpired =
|
|
4494
|
-
typeof lastLateAckAgeMs === 'number' && lastLateAckAgeMs > lateAckObservationTtlMs;
|
|
4495
4286
|
const adaptiveAckRecoveryOkCount = this.getCounter(
|
|
4496
4287
|
this.adaptiveAckRecoveryOkCountByAccount,
|
|
4497
4288
|
acc,
|
|
4498
4289
|
);
|
|
4499
|
-
|
|
4500
|
-
adaptiveAckRecoveryOkCount >= ADAPTIVE_ACK_TIMEOUT_RECOVERY_OK_THRESHOLD;
|
|
4501
|
-
const recommendedAckTimeoutMs = this.computeRecommendedAckTimeoutMs({
|
|
4502
|
-
lateAckOkCount,
|
|
4503
|
-
recentAckTimeoutCount,
|
|
4504
|
-
lastLateAckPushLatencyMs,
|
|
4505
|
-
lastLateAckOkAt,
|
|
4506
|
-
adaptiveAckRecoveryOkCount,
|
|
4507
|
-
nowMs,
|
|
4508
|
-
});
|
|
4509
|
-
const currentAckTimeoutMs = this.resolveMessageAckTimeoutMs(acc);
|
|
4510
|
-
return {
|
|
4290
|
+
return buildBncrRuntimeAckObservability({
|
|
4511
4291
|
lastAckOkAt: this.lastAckOkByAccount.get(acc) || null,
|
|
4512
4292
|
lastAckTimeoutAt: this.lastAckTimeoutByAccount.get(acc) || null,
|
|
4513
4293
|
recentAckTimeoutCount,
|
|
4514
4294
|
lateAckOkCount,
|
|
4515
4295
|
lastLateAckOkAt,
|
|
4516
|
-
lastLateAckAgeMs,
|
|
4517
|
-
lateAckObservationTtlMs,
|
|
4518
|
-
lateAckObservationExpired,
|
|
4519
4296
|
adaptiveAckRecoveryOkCount,
|
|
4520
|
-
adaptiveAckRecoveryOkThreshold: ADAPTIVE_ACK_TIMEOUT_RECOVERY_OK_THRESHOLD,
|
|
4521
|
-
adaptiveAckRecovered,
|
|
4522
4297
|
lastAckQueueLatencyMs: this.lastAckQueueLatencyMsByAccount.get(acc) || null,
|
|
4523
4298
|
lastAckPushLatencyMs: this.lastAckPushLatencyMsByAccount.get(acc) || null,
|
|
4524
4299
|
lastLateAckQueueLatencyMs: this.lastLateAckQueueLatencyMsByAccount.get(acc) || null,
|
|
4525
4300
|
lastLateAckPushLatencyMs,
|
|
4526
4301
|
adaptiveAckTimeoutEnabled: ADAPTIVE_ACK_TIMEOUT_DEFAULT_ENABLED,
|
|
4527
4302
|
defaultAckTimeoutMs: PUSH_ACK_TIMEOUT_MS,
|
|
4528
|
-
currentAckTimeoutMs,
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
adaptiveAckRecoveryOkCount,
|
|
4536
|
-
recommendedAckTimeoutMs,
|
|
4537
|
-
nowMs,
|
|
4538
|
-
}),
|
|
4539
|
-
};
|
|
4303
|
+
currentAckTimeoutMs: this.resolveMessageAckTimeoutMs(acc),
|
|
4304
|
+
minAckTimeoutMs: RECOMMENDED_ACK_TIMEOUT_MIN_MS,
|
|
4305
|
+
maxAckTimeoutMs: RECOMMENDED_ACK_TIMEOUT_MAX_MS,
|
|
4306
|
+
lateAckObservationTtlMs: ADAPTIVE_ACK_TIMEOUT_OBSERVATION_TTL_MS,
|
|
4307
|
+
recoveryOkThreshold: ADAPTIVE_ACK_TIMEOUT_RECOVERY_OK_THRESHOLD,
|
|
4308
|
+
nowMs,
|
|
4309
|
+
});
|
|
4540
4310
|
}
|
|
4541
4311
|
|
|
4542
4312
|
private buildRuntimeAckStrategy(ackObservability: Record<string, any>) {
|
|
@@ -4681,6 +4451,8 @@ class BncrBridgeRuntime {
|
|
|
4681
4451
|
entry: dead,
|
|
4682
4452
|
maxEntries: MAX_DEAD_LETTER_ENTRIES,
|
|
4683
4453
|
});
|
|
4454
|
+
this.incrementCounter(this.deadLetterSinceStartByAccount, dead.accountId);
|
|
4455
|
+
this.logDeadLetterSummary(dead.accountId, { source: 'move' });
|
|
4684
4456
|
this.outbox.delete(entry.messageId);
|
|
4685
4457
|
this.resolveMessageAck(entry.messageId, 'timeout');
|
|
4686
4458
|
this.scheduleSave();
|
|
@@ -4740,14 +4512,7 @@ class BncrBridgeRuntime {
|
|
|
4740
4512
|
? this.resolveRecentInboundConnIds(args.accountId)
|
|
4741
4513
|
: new Set<string>();
|
|
4742
4514
|
const activeConnectionKey = this.activeConnectionByAccount.get(args.accountId) || null;
|
|
4743
|
-
const accountConnections =
|
|
4744
|
-
.filter((c) => c.accountId === args.accountId)
|
|
4745
|
-
.map((c) => ({
|
|
4746
|
-
connId: c.connId,
|
|
4747
|
-
clientId: c.clientId,
|
|
4748
|
-
connectedAt: c.connectedAt,
|
|
4749
|
-
lastSeenAt: c.lastSeenAt,
|
|
4750
|
-
}));
|
|
4515
|
+
const accountConnections = this.buildActiveConnectionDebugList(args.accountId);
|
|
4751
4516
|
|
|
4752
4517
|
return {
|
|
4753
4518
|
directConnIds,
|
|
@@ -5262,9 +5027,10 @@ class BncrBridgeRuntime {
|
|
|
5262
5027
|
route: BncrRoute;
|
|
5263
5028
|
payload: ReplyPayloadInput;
|
|
5264
5029
|
mediaLocalRoots?: readonly string[];
|
|
5030
|
+
replyTargetPolicy?: OutboundReplyTargetPolicy;
|
|
5265
5031
|
}) {
|
|
5266
|
-
const { accountId, sessionKey, route, payload, mediaLocalRoots } = params;
|
|
5267
|
-
const normalized = normalizeReplyPayload(payload, { asString });
|
|
5032
|
+
const { accountId, sessionKey, route, payload, mediaLocalRoots, replyTargetPolicy } = params;
|
|
5033
|
+
const normalized = normalizeReplyPayload(payload, { asString }, { replyTargetPolicy });
|
|
5268
5034
|
|
|
5269
5035
|
enqueueNormalizedReplyPayload(
|
|
5270
5036
|
{
|
|
@@ -5400,9 +5166,7 @@ class BncrBridgeRuntime {
|
|
|
5400
5166
|
pushEvent: BNCR_PUSH_EVENT,
|
|
5401
5167
|
online: true,
|
|
5402
5168
|
isPrimary: this.isPrimaryConnection(accountId, clientId),
|
|
5403
|
-
|
|
5404
|
-
pending: Array.from(this.outbox.values()).filter((v) => v.accountId === accountId).length,
|
|
5405
|
-
deadLetter: this.deadLetter.filter((v) => v.accountId === accountId).length,
|
|
5169
|
+
...this.buildAccountQueueCounters(accountId),
|
|
5406
5170
|
diagnostics: this.buildExtendedDiagnostics(accountId),
|
|
5407
5171
|
runtimeFlags: this.buildRuntimeFlags(accountId),
|
|
5408
5172
|
waiters: {
|
|
@@ -5496,9 +5260,7 @@ class BncrBridgeRuntime {
|
|
|
5496
5260
|
accountId,
|
|
5497
5261
|
ok: true,
|
|
5498
5262
|
event: 'activity',
|
|
5499
|
-
|
|
5500
|
-
pending: Array.from(this.outbox.values()).filter((v) => v.accountId === accountId).length,
|
|
5501
|
-
deadLetter: this.deadLetter.filter((v) => v.accountId === accountId).length,
|
|
5263
|
+
...this.buildAccountQueueCounters(accountId),
|
|
5502
5264
|
now: now(),
|
|
5503
5265
|
});
|
|
5504
5266
|
this.flushPushQueueBestEffort({
|
|
@@ -5536,6 +5298,78 @@ class BncrBridgeRuntime {
|
|
|
5536
5298
|
);
|
|
5537
5299
|
};
|
|
5538
5300
|
|
|
5301
|
+
handleDeadLetterInspect = async ({ params, respond }: GatewayRequestHandlerOptions) => {
|
|
5302
|
+
const accountId = normalizeAccountId(asString(params?.accountId || BNCR_DEFAULT_ACCOUNT_ID));
|
|
5303
|
+
const reason = asString(params?.reason || '').trim() || null;
|
|
5304
|
+
const olderThan = parseDeadLetterOlderThan(params?.olderThan);
|
|
5305
|
+
const limit = parseDeadLetterLimit(params?.limit, 20);
|
|
5306
|
+
const offset = parseDeadLetterOffset(params?.offset, 0);
|
|
5307
|
+
const matches = this.filterDeadLetterEntries({ accountId, reason, olderThan })
|
|
5308
|
+
.slice()
|
|
5309
|
+
.sort((a, b) => Number(b.createdAt || 0) - Number(a.createdAt || 0));
|
|
5310
|
+
|
|
5311
|
+
respond(true, {
|
|
5312
|
+
ok: true,
|
|
5313
|
+
accountId,
|
|
5314
|
+
filters: { reason, olderThan },
|
|
5315
|
+
total: matches.length,
|
|
5316
|
+
offset,
|
|
5317
|
+
limit,
|
|
5318
|
+
entries: matches
|
|
5319
|
+
.slice(offset, offset + limit)
|
|
5320
|
+
.map((entry) => summarizeDeadLetterEntry(entry)),
|
|
5321
|
+
summary: this.buildDeadLetterDiagnostics(accountId),
|
|
5322
|
+
now: now(),
|
|
5323
|
+
});
|
|
5324
|
+
};
|
|
5325
|
+
|
|
5326
|
+
handleDeadLetterPrune = async ({ params, respond }: GatewayRequestHandlerOptions) => {
|
|
5327
|
+
const accountId = normalizeAccountId(asString(params?.accountId || BNCR_DEFAULT_ACCOUNT_ID));
|
|
5328
|
+
const reason = asString(params?.reason || '').trim() || null;
|
|
5329
|
+
const olderThan = parseDeadLetterOlderThan(params?.olderThan);
|
|
5330
|
+
const limit = parseDeadLetterLimit(params?.limit, 100);
|
|
5331
|
+
const dryRun = params?.dryRun !== false;
|
|
5332
|
+
const hasDestructiveFilter = Boolean(reason || olderThan !== null);
|
|
5333
|
+
if (!dryRun && !hasDestructiveFilter) {
|
|
5334
|
+
respond(false, {
|
|
5335
|
+
ok: false,
|
|
5336
|
+
error: 'deadLetter-prune-requires-filter',
|
|
5337
|
+
message: 'dryRun=false requires at least one destructive filter: reason or olderThan',
|
|
5338
|
+
dryRun,
|
|
5339
|
+
accountId,
|
|
5340
|
+
filters: { reason, olderThan },
|
|
5341
|
+
summary: this.buildDeadLetterDiagnostics(accountId),
|
|
5342
|
+
now: now(),
|
|
5343
|
+
});
|
|
5344
|
+
return;
|
|
5345
|
+
}
|
|
5346
|
+
const matches = this.filterDeadLetterEntries({ accountId, reason, olderThan })
|
|
5347
|
+
.slice()
|
|
5348
|
+
.sort((a, b) => Number(a.createdAt || 0) - Number(b.createdAt || 0));
|
|
5349
|
+
const selected = matches.slice(0, limit);
|
|
5350
|
+
const selectedEntries = new Set(selected);
|
|
5351
|
+
|
|
5352
|
+
if (!dryRun && selectedEntries.size > 0) {
|
|
5353
|
+
this.deadLetter = this.deadLetter.filter((entry) => !selectedEntries.has(entry));
|
|
5354
|
+
this.scheduleSave();
|
|
5355
|
+
this.logDeadLetterSummary(accountId, { force: true, source: 'prune' });
|
|
5356
|
+
}
|
|
5357
|
+
|
|
5358
|
+
respond(true, {
|
|
5359
|
+
ok: true,
|
|
5360
|
+
dryRun,
|
|
5361
|
+
accountId,
|
|
5362
|
+
filters: { reason, olderThan },
|
|
5363
|
+
matched: matches.length,
|
|
5364
|
+
pruned: dryRun ? 0 : selected.length,
|
|
5365
|
+
wouldPrune: selected.length,
|
|
5366
|
+
limit,
|
|
5367
|
+
entries: selected.map((entry) => summarizeDeadLetterEntry(entry)),
|
|
5368
|
+
summary: this.buildDeadLetterDiagnostics(accountId),
|
|
5369
|
+
now: now(),
|
|
5370
|
+
});
|
|
5371
|
+
};
|
|
5372
|
+
|
|
5539
5373
|
handleFileInit = async ({ params, respond, client, context }: GatewayRequestHandlerOptions) => {
|
|
5540
5374
|
const accountId = normalizeAccountId(asString(params?.accountId || ''));
|
|
5541
5375
|
const connId = asString(client?.connId || '').trim() || `no-conn-${Date.now()}`;
|
|
@@ -5977,7 +5811,22 @@ class BncrBridgeRuntime {
|
|
|
5977
5811
|
return;
|
|
5978
5812
|
}
|
|
5979
5813
|
|
|
5814
|
+
if (!['init', 'chunk', 'complete', 'abort'].includes(stage)) {
|
|
5815
|
+
respond(false, { error: 'invalid file ack stage' });
|
|
5816
|
+
return;
|
|
5817
|
+
}
|
|
5818
|
+
|
|
5980
5819
|
const st = this.fileSendTransfers.get(transferId);
|
|
5820
|
+
const fileAckWaiterKey = this.fileAckKey(
|
|
5821
|
+
transferId,
|
|
5822
|
+
stage,
|
|
5823
|
+
chunkIndex != null ? chunkIndex : undefined,
|
|
5824
|
+
);
|
|
5825
|
+
if (!st && !this.fileAckWaiters.has(fileAckWaiterKey)) {
|
|
5826
|
+
respond(false, { error: 'unknown transferId' });
|
|
5827
|
+
return;
|
|
5828
|
+
}
|
|
5829
|
+
|
|
5981
5830
|
const staleKind =
|
|
5982
5831
|
stage === 'init'
|
|
5983
5832
|
? 'file.init'
|
|
@@ -6157,14 +6006,7 @@ class BncrBridgeRuntime {
|
|
|
6157
6006
|
onlineAfterSeen: this.isOnline(accountId),
|
|
6158
6007
|
recentInboundReachable: this.hasRecentInboundReachability(accountId),
|
|
6159
6008
|
activeConnectionKey: this.activeConnectionByAccount.get(accountId) || null,
|
|
6160
|
-
activeConnections:
|
|
6161
|
-
.filter((c) => c.accountId === accountId)
|
|
6162
|
-
.map((c) => ({
|
|
6163
|
-
connId: c.connId,
|
|
6164
|
-
clientId: c.clientId,
|
|
6165
|
-
connectedAt: c.connectedAt,
|
|
6166
|
-
lastSeenAt: c.lastSeenAt,
|
|
6167
|
-
})),
|
|
6009
|
+
activeConnections: this.buildActiveConnectionDebugList(accountId),
|
|
6168
6010
|
}),
|
|
6169
6011
|
)}`,
|
|
6170
6012
|
{ debugOnly: true },
|
|
@@ -6272,7 +6114,7 @@ class BncrBridgeRuntime {
|
|
|
6272
6114
|
}) {
|
|
6273
6115
|
this.logInfo(
|
|
6274
6116
|
'outbound',
|
|
6275
|
-
`send-entry:${args.kind}
|
|
6117
|
+
buildBncrDebugJsonMessage(`send-entry:${args.kind}`, {
|
|
6276
6118
|
accountId: args.accountId,
|
|
6277
6119
|
to: args.to,
|
|
6278
6120
|
text: args.payload.text,
|
|
@@ -6288,7 +6130,7 @@ class BncrBridgeRuntime {
|
|
|
6288
6130
|
threadId: args.ctx?.threadId,
|
|
6289
6131
|
replyToId: args.ctx?.replyToId,
|
|
6290
6132
|
},
|
|
6291
|
-
})
|
|
6133
|
+
}),
|
|
6292
6134
|
{ debugOnly: true },
|
|
6293
6135
|
);
|
|
6294
6136
|
}
|