@xmoxmo/bncr 0.3.3 → 0.3.5
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 +11 -10
- package/openclaw.plugin.json +21 -0
- package/package.json +4 -4
- package/scripts/check-pack.mjs +112 -22
- package/scripts/check-register-drift.mjs +91 -65
- package/scripts/selfcheck.mjs +79 -3
- package/src/channel.ts +549 -810
- package/src/core/accounts.ts +1 -1
- package/src/core/connection-capability.ts +2 -2
- package/src/core/connection-reachability.ts +112 -1
- 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 +15 -10
- package/src/core/extended-diagnostics.ts +4 -0
- package/src/core/file-transfer-payloads.ts +1 -4
- package/src/core/logging.ts +98 -0
- package/src/core/outbox-entry-builders.ts +15 -2
- package/src/core/outbox-file-transfer-bookkeeping.ts +1 -1
- package/src/core/outbox-file-transfer-failure.ts +2 -5
- package/src/core/outbox-file-transfer-success.ts +1 -4
- package/src/core/outbox-text-push-failure.ts +2 -4
- package/src/core/outbox-text-push-success.ts +1 -1
- 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 +42 -94
- package/src/messaging/inbound/dispatch.ts +25 -54
- 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/inbound/parse.ts +3 -3
- package/src/messaging/inbound/runtime-compat.ts +8 -2
- package/src/messaging/outbound/build-send-action.ts +1 -2
- package/src/messaging/outbound/diagnostics.ts +221 -2
- package/src/messaging/outbound/durable-message-adapter.ts +15 -5
- package/src/messaging/outbound/durable-queue-adapter.ts +3 -1
- package/src/messaging/outbound/media.ts +2 -1
- package/src/messaging/outbound/queue-selectors.ts +19 -6
- package/src/messaging/outbound/reasons.ts +2 -0
- package/src/messaging/outbound/reply-enqueue.ts +29 -2
- package/src/messaging/outbound/reply-target-policy.ts +4 -1
- package/src/messaging/outbound/retry-policy.ts +16 -8
- package/src/messaging/outbound/send-params.ts +56 -0
- package/src/messaging/outbound/session-route.ts +1 -1
- package/src/openclaw/reply-runtime.ts +4 -5
- package/src/openclaw/routing-runtime.ts +0 -1
- package/src/openclaw/runtime-surface.ts +29 -0
- package/src/openclaw/sdk-helpers.ts +4 -1
- package/src/plugin/gateway-methods.ts +2 -0
- package/src/plugin/messaging.ts +2 -9
- package/src/plugin/status.ts +15 -5
- package/src/runtime/outbound-ack-timeout.ts +73 -0
- package/src/runtime/outbound-flags.ts +1 -1
- package/src/runtime/outbox-transitions.ts +4 -4
- package/src/runtime/register-trace-runtime.ts +102 -0
- package/src/runtime/status-snapshots.ts +10 -4
- package/src/runtime/status-worker.ts +78 -13
package/src/channel.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
-
import fs from 'node:fs';
|
|
3
2
|
import path from 'node:path';
|
|
4
3
|
import type {
|
|
5
4
|
GatewayRequestHandlerOptions,
|
|
@@ -24,9 +23,23 @@ import {
|
|
|
24
23
|
getRevalidatedAttemptReason,
|
|
25
24
|
hasAlternativeLiveConnection as hasAlternativeLiveConnectionFromRuntime,
|
|
26
25
|
hasRecentInboundReachability as hasRecentInboundReachabilityFromRuntime,
|
|
26
|
+
isEligibleOutboundPushConnection,
|
|
27
27
|
isRecentlyReachableConn as isRecentlyReachableConnFromRuntime,
|
|
28
28
|
resolveRecentInboundConnIds as resolveRecentInboundConnIdsFromRuntime,
|
|
29
|
+
selectOrderedOutboundPushConnections,
|
|
29
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';
|
|
30
43
|
import { buildDiagnosticsPayload } from './core/diagnostics.ts';
|
|
31
44
|
import { buildDownlinkHealth as buildDownlinkHealthFromRuntime } from './core/downlink-health.ts';
|
|
32
45
|
import { buildExtendedDiagnostics as buildExtendedDiagnosticsFromRuntime } from './core/extended-diagnostics.ts';
|
|
@@ -41,7 +54,12 @@ import {
|
|
|
41
54
|
matchesTransferOwner as matchesTransferOwnerFromRuntime,
|
|
42
55
|
observeLeaseState,
|
|
43
56
|
} from './core/lease-state.ts';
|
|
44
|
-
import {
|
|
57
|
+
import {
|
|
58
|
+
buildBncrDebugJsonMessage,
|
|
59
|
+
emitBncrLog,
|
|
60
|
+
emitBncrLogLine,
|
|
61
|
+
summarizeBncrTextPreview,
|
|
62
|
+
} from './core/logging.ts';
|
|
45
63
|
import { buildOutboxEnqueueDebugInfo } from './core/outbox-enqueue.ts';
|
|
46
64
|
import {
|
|
47
65
|
buildFileTransferOutboxEntry as buildFileTransferOutboxEntryFromRuntime,
|
|
@@ -76,12 +94,11 @@ import {
|
|
|
76
94
|
buildTextPushRouteSelectArgs,
|
|
77
95
|
buildTextPushSuccessArgs,
|
|
78
96
|
} from './core/outbox-text-push-success.ts';
|
|
97
|
+
import { normalizePersistedOutboxEntry as normalizePersistedOutboxEntryFromRuntime } from './core/persisted-outbox-entry.ts';
|
|
79
98
|
import { resolveBncrChannelPolicy, resolveBncrConfigWarnings } from './core/policy.ts';
|
|
80
99
|
import {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
buildRegisterTraceEntry,
|
|
84
|
-
buildRegisterTraceSummary as buildRegisterTraceSummaryFromEntries,
|
|
100
|
+
dumpRegisterDriftSnapshot,
|
|
101
|
+
normalizeRegisterDriftSnapshot,
|
|
85
102
|
} from './core/register-trace.ts';
|
|
86
103
|
import {
|
|
87
104
|
buildAccountRuntimeSnapshot,
|
|
@@ -92,64 +109,57 @@ import {
|
|
|
92
109
|
import {
|
|
93
110
|
buildCanonicalBncrSessionKey,
|
|
94
111
|
formatDisplayScope,
|
|
95
|
-
isLowerHex,
|
|
96
112
|
normalizeInboundSessionKey,
|
|
97
113
|
normalizeStoredSessionKey,
|
|
98
114
|
parseRouteFromDisplayScope,
|
|
99
|
-
parseRouteFromHexScope,
|
|
100
|
-
parseRouteFromScope,
|
|
101
115
|
parseRouteLike,
|
|
102
116
|
parseStrictBncrSessionKey,
|
|
103
117
|
routeKey,
|
|
104
|
-
routeScopeToHex,
|
|
105
118
|
withTaskSessionKey,
|
|
106
119
|
} from './core/targets.ts';
|
|
107
120
|
import type { BncrConnection, BncrRoute, OutboxEntry } from './core/types.ts';
|
|
108
121
|
import { dispatchBncrInbound } from './messaging/inbound/dispatch.ts';
|
|
109
122
|
import { checkBncrMessageGate } from './messaging/inbound/gate.ts';
|
|
110
123
|
import { parseBncrInboundParams } from './messaging/inbound/parse.ts';
|
|
111
|
-
import {
|
|
112
|
-
deleteBncrMessageAction,
|
|
113
|
-
editBncrMessageAction,
|
|
114
|
-
reactBncrMessageAction,
|
|
115
|
-
sendBncrReplyAction,
|
|
116
|
-
} from './messaging/outbound/actions.ts';
|
|
117
124
|
import {
|
|
118
125
|
buildEnqueueFromReplyDebugInfo,
|
|
126
|
+
buildExtendedOutboundDiagnostics,
|
|
119
127
|
buildFlushDebugInfo,
|
|
120
128
|
buildOutboxAckDebugInfo,
|
|
121
129
|
buildOutboxDrainSkipDebugInfo,
|
|
122
130
|
buildOutboxDrainStuckDebugInfo,
|
|
123
131
|
buildOutboxPushOkDebugInfo,
|
|
124
132
|
buildOutboxPushSkipDebugInfo,
|
|
133
|
+
buildOutboxQueueDiagnostics,
|
|
125
134
|
buildOutboxRouteSelectDebugInfo,
|
|
126
135
|
buildOutboxScheduleDebugInfo,
|
|
127
136
|
buildPushFailureDebugInfo,
|
|
128
|
-
buildReplyMediaFallbackDebugInfo,
|
|
129
137
|
buildRetryRerouteDebugInfo,
|
|
130
138
|
} from './messaging/outbound/diagnostics.ts';
|
|
131
|
-
import {
|
|
132
|
-
|
|
133
|
-
resolveBncrOutboundMessageType,
|
|
134
|
-
} from './messaging/outbound/media.ts';
|
|
139
|
+
import { buildBncrMediaOutboundFrame } from './messaging/outbound/media.ts';
|
|
140
|
+
import { normalizeBncrSendParams } from './messaging/outbound/send-params.ts';
|
|
135
141
|
import {
|
|
136
142
|
getOpenClawRuntimeConfig,
|
|
137
143
|
getOpenClawRuntimeConfigOrDefault,
|
|
138
144
|
} from './openclaw/config-runtime.ts';
|
|
139
145
|
import {
|
|
140
|
-
type OpenClawLoadedMedia,
|
|
141
146
|
loadOpenClawWebMedia,
|
|
147
|
+
type OpenClawLoadedMedia,
|
|
142
148
|
saveOpenClawChannelMediaBuffer,
|
|
143
149
|
} from './openclaw/media-runtime.ts';
|
|
144
150
|
import { resolveOpenClawAgentRoute } from './openclaw/routing-runtime.ts';
|
|
151
|
+
import { buildOpenClawChannelRuntimeSurfaceDiagnostics } from './openclaw/runtime-surface.ts';
|
|
145
152
|
import {
|
|
146
153
|
extractOpenClawToolSend,
|
|
147
154
|
openClawJsonResult,
|
|
148
|
-
readOpenClawBooleanParam,
|
|
149
155
|
readOpenClawJsonFileWithFallback,
|
|
150
|
-
readOpenClawStringParam,
|
|
151
156
|
writeOpenClawJsonFileAtomically,
|
|
152
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';
|
|
153
163
|
|
|
154
164
|
function buildInboundAcceptedLifecycleDebugInfo(args: {
|
|
155
165
|
stage: 'accepted';
|
|
@@ -283,10 +293,11 @@ function buildInboundResponsePayload(
|
|
|
283
293
|
};
|
|
284
294
|
}
|
|
285
295
|
}
|
|
296
|
+
|
|
286
297
|
import { buildBncrDurableQueuedResult } from './messaging/outbound/durable-queue-adapter.ts';
|
|
287
298
|
import {
|
|
288
|
-
type MediaDedupeCacheEntry,
|
|
289
299
|
buildMediaTextFallback,
|
|
300
|
+
type MediaDedupeCacheEntry,
|
|
290
301
|
normalizeMessageText,
|
|
291
302
|
normalizeReplyToId,
|
|
292
303
|
} from './messaging/outbound/media-dedupe.ts';
|
|
@@ -310,18 +321,17 @@ import {
|
|
|
310
321
|
OUTBOUND_TERMINAL_REASON,
|
|
311
322
|
} from './messaging/outbound/reasons.ts';
|
|
312
323
|
import {
|
|
313
|
-
type NormalizedReplyPayload,
|
|
314
|
-
type ReplyMediaEntriesParams,
|
|
315
|
-
type ReplyMediaFileTransferParams,
|
|
316
|
-
type ReplyPayloadInput,
|
|
317
|
-
buildReplyTextOutboxEntry,
|
|
318
324
|
enqueueNormalizedReplyPayload,
|
|
319
325
|
enqueueReplyMediaFallbackTextEntry,
|
|
320
326
|
enqueueReplyMediaFileTransferEntry,
|
|
321
327
|
enqueueReplyTextEntry,
|
|
322
328
|
enqueueSingleReplyMediaEntry,
|
|
323
329
|
hasReplyMediaEntries,
|
|
330
|
+
type NormalizedReplyPayload,
|
|
324
331
|
normalizeReplyPayload,
|
|
332
|
+
type OutboundReplyTargetPolicy,
|
|
333
|
+
type ReplyMediaEntriesParams,
|
|
334
|
+
type ReplyPayloadInput,
|
|
325
335
|
} from './messaging/outbound/reply-enqueue.ts';
|
|
326
336
|
import {
|
|
327
337
|
computePushFailureDecision,
|
|
@@ -339,14 +349,11 @@ import { BNCR_CHANNEL_META } from './plugin/meta.ts';
|
|
|
339
349
|
import { createBncrOutboundRuntime } from './plugin/outbound.ts';
|
|
340
350
|
import { BNCR_SETUP_SURFACE } from './plugin/setup.ts';
|
|
341
351
|
import { createBncrStatusSurface } from './plugin/status.ts';
|
|
352
|
+
import { shouldEmitDedupLog as shouldEmitDedupLogFromRuntime } from './runtime/log-dedupe.ts';
|
|
342
353
|
import {
|
|
343
|
-
|
|
344
|
-
shouldEmitDedupLog as shouldEmitDedupLogFromRuntime,
|
|
345
|
-
} from './runtime/log-dedupe.ts';
|
|
346
|
-
import {
|
|
354
|
+
buildBncrRuntimeAckObservability,
|
|
347
355
|
buildBncrRuntimeAckStrategy,
|
|
348
|
-
|
|
349
|
-
computeBncrRecommendedAckTimeoutReason,
|
|
356
|
+
resolveBncrRuntimeAckTimeoutDecision,
|
|
350
357
|
} from './runtime/outbound-ack-timeout.ts';
|
|
351
358
|
import {
|
|
352
359
|
buildBncrRuntimeFlags,
|
|
@@ -365,10 +372,10 @@ import { buildRuntimeStatusSnapshots } from './runtime/status-snapshots.ts';
|
|
|
365
372
|
import {
|
|
366
373
|
type ChannelAccountWorkerHandle,
|
|
367
374
|
clearAllBncrStatusWorkers,
|
|
368
|
-
clearBncrStatusWorker,
|
|
369
375
|
startBncrStatusWorker,
|
|
370
376
|
stopBncrStatusWorker,
|
|
371
377
|
} from './runtime/status-worker.ts';
|
|
378
|
+
|
|
372
379
|
const BRIDGE_VERSION = 2;
|
|
373
380
|
const BNCR_PUSH_EVENT = 'plugin.bncr.push';
|
|
374
381
|
const BNCR_FILE_INIT_EVENT = 'plugin.bncr.file.init';
|
|
@@ -387,6 +394,7 @@ const PUSH_DRAIN_ACCOUNT_TIME_BUDGET_MS = 2_000;
|
|
|
387
394
|
const PUSH_DRAIN_EXCEPTION_RETRY_LIMIT = 3;
|
|
388
395
|
const PUSH_DRAIN_EXCEPTION_RETRY_DELAY_MS = 1_000;
|
|
389
396
|
const PUSH_DRAIN_STUCK_WARN_MS = 30_000;
|
|
397
|
+
const PRE_PUSH_GUARD_RETRY_DELAY_MS = 1_000;
|
|
390
398
|
const PUSH_ACK_TIMEOUT_MS = 30_000;
|
|
391
399
|
const ADAPTIVE_ACK_TIMEOUT_DEFAULT_ENABLED = true;
|
|
392
400
|
const RECOMMENDED_ACK_TIMEOUT_MIN_MS = PUSH_ACK_TIMEOUT_MS;
|
|
@@ -402,7 +410,6 @@ const FILE_CHUNK_SIZE = 256 * 1024; // 256KB
|
|
|
402
410
|
const INBOUND_FILE_TRANSFER_MAX_BYTES = 50 * 1024 * 1024;
|
|
403
411
|
const INBOUND_FILE_TRANSFER_MAX_CHUNKS =
|
|
404
412
|
Math.ceil(INBOUND_FILE_TRANSFER_MAX_BYTES / FILE_CHUNK_SIZE) + 1;
|
|
405
|
-
const FILE_CHUNK_RETRY = 3;
|
|
406
413
|
const FILE_ACK_TIMEOUT_MS = 30_000;
|
|
407
414
|
const FILE_TRANSFER_ACK_TTL_MS = 30_000;
|
|
408
415
|
const MAX_EARLY_FILE_ACKS = 1000;
|
|
@@ -462,8 +469,6 @@ type FileAckPayloadState = {
|
|
|
462
469
|
at: number;
|
|
463
470
|
};
|
|
464
471
|
|
|
465
|
-
type ChatType = 'direct' | 'group' | (string & {});
|
|
466
|
-
|
|
467
472
|
type ChannelMessageActionAdapter = {
|
|
468
473
|
describeMessageTool: (ctx: { cfg: any }) => { actions: string[]; capabilities: unknown[] } | null;
|
|
469
474
|
supportsAction: (ctx: { action: string }) => boolean;
|
|
@@ -517,56 +522,6 @@ type PersistedState = {
|
|
|
517
522
|
} | null;
|
|
518
523
|
};
|
|
519
524
|
|
|
520
|
-
type NormalizedBncrSendParams = {
|
|
521
|
-
to: string;
|
|
522
|
-
accountId: string;
|
|
523
|
-
message: string;
|
|
524
|
-
caption: string;
|
|
525
|
-
mediaUrl?: string;
|
|
526
|
-
asVoice: boolean;
|
|
527
|
-
audioAsVoice: boolean;
|
|
528
|
-
};
|
|
529
|
-
|
|
530
|
-
function normalizeBncrSendParams(input: {
|
|
531
|
-
params: unknown;
|
|
532
|
-
accountId: string;
|
|
533
|
-
}): NormalizedBncrSendParams {
|
|
534
|
-
const paramsObj = isPlainObject(input.params) ? input.params : {};
|
|
535
|
-
const to = readOpenClawStringParam(paramsObj, 'to', { required: true });
|
|
536
|
-
const resolvedAccountId = normalizeAccountId(
|
|
537
|
-
readOpenClawStringParam(paramsObj, 'accountId') ?? input.accountId,
|
|
538
|
-
);
|
|
539
|
-
|
|
540
|
-
const message = readOpenClawStringParam(paramsObj, 'message', { allowEmpty: true }) ?? '';
|
|
541
|
-
const caption = readOpenClawStringParam(paramsObj, 'caption', { allowEmpty: true }) ?? '';
|
|
542
|
-
const mediaUrl =
|
|
543
|
-
readOpenClawStringParam(paramsObj, 'media', { trim: false }) ??
|
|
544
|
-
readOpenClawStringParam(paramsObj, 'path', { trim: false }) ??
|
|
545
|
-
readOpenClawStringParam(paramsObj, 'filePath', { trim: false }) ??
|
|
546
|
-
readOpenClawStringParam(paramsObj, 'mediaUrl', { trim: false });
|
|
547
|
-
const asVoice = readOpenClawBooleanParam(paramsObj, 'asVoice') ?? false;
|
|
548
|
-
const audioAsVoice = readOpenClawBooleanParam(paramsObj, 'audioAsVoice') ?? false;
|
|
549
|
-
|
|
550
|
-
if (asVoice && !mediaUrl) throw new Error('send voice requires media path');
|
|
551
|
-
|
|
552
|
-
const normalizedMessage = mediaUrl ? '' : message || caption || '';
|
|
553
|
-
const normalizedCaption = mediaUrl ? caption || message || '' : '';
|
|
554
|
-
|
|
555
|
-
if (!normalizedMessage.trim() && !normalizedCaption.trim() && !mediaUrl) {
|
|
556
|
-
throw new Error('send requires message or media');
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
return {
|
|
560
|
-
to,
|
|
561
|
-
accountId: resolvedAccountId,
|
|
562
|
-
message: normalizedMessage,
|
|
563
|
-
caption: normalizedCaption,
|
|
564
|
-
mediaUrl: mediaUrl || undefined,
|
|
565
|
-
asVoice,
|
|
566
|
-
audioAsVoice,
|
|
567
|
-
};
|
|
568
|
-
}
|
|
569
|
-
|
|
570
525
|
function now() {
|
|
571
526
|
return Date.now();
|
|
572
527
|
}
|
|
@@ -582,12 +537,6 @@ function finiteNumberOr(value: unknown, fallback: number): number {
|
|
|
582
537
|
return Number.isFinite(n) ? n : fallback;
|
|
583
538
|
}
|
|
584
539
|
|
|
585
|
-
function optionalFiniteNumber(value: unknown): number | undefined {
|
|
586
|
-
if (value == null || value === '') return undefined;
|
|
587
|
-
const n = Number(value);
|
|
588
|
-
return Number.isFinite(n) ? n : undefined;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
540
|
function finiteNonNegativeNumberOrNull(value: unknown): number | null {
|
|
592
541
|
const n = Number(value);
|
|
593
542
|
return Number.isFinite(n) && n >= 0 ? n : null;
|
|
@@ -770,8 +719,6 @@ class BncrBridgeRuntime {
|
|
|
770
719
|
private channelAccountWorkers = new Map<string, ChannelAccountWorkerHandle>();
|
|
771
720
|
private logDedupeState = new Map<string, { at: number; sig: string }>();
|
|
772
721
|
private canonicalAgentId: string | null = null;
|
|
773
|
-
private canonicalAgentSource: 'startup' | 'runtime' | 'fallback-main' | null = null;
|
|
774
|
-
private canonicalAgentResolvedAt: number | null = null;
|
|
775
722
|
|
|
776
723
|
// 内置健康/回归计数(替代独立脚本)
|
|
777
724
|
private startedAt = now();
|
|
@@ -787,6 +734,13 @@ class BncrBridgeRuntime {
|
|
|
787
734
|
private pushDrainRunningSinceByAccount = new Map<string, number>();
|
|
788
735
|
private pushDrainStuckWarnedAtByAccount = new Map<string, number>();
|
|
789
736
|
private pushDrainExceptionRetryCount = 0;
|
|
737
|
+
private lastGatewayContextAt: number | null = null;
|
|
738
|
+
private outboundEnqueueCountByAccount = new Map<string, number>();
|
|
739
|
+
private lastOutboundEnqueueAtByAccount = new Map<string, number>();
|
|
740
|
+
private prePushGuardSkipCountByAccount = new Map<string, number>();
|
|
741
|
+
private lastPrePushGuardSkipAtByAccount = new Map<string, number>();
|
|
742
|
+
private lastPrePushGuardSkipReasonByAccount = new Map<string, string>();
|
|
743
|
+
private deadLetterSinceStartByAccount = new Map<string, number>();
|
|
790
744
|
private messageAckWaiters = new Map<
|
|
791
745
|
// Refactor boundary note (message ACK runtime):
|
|
792
746
|
// These waiters are part of the outbound message-ack lifecycle, not just a utility map.
|
|
@@ -849,39 +803,13 @@ class BncrBridgeRuntime {
|
|
|
849
803
|
emitBncrLog('error', scope, message, options, () => this.isDebugEnabled());
|
|
850
804
|
}
|
|
851
805
|
|
|
852
|
-
private buildDebugJsonMessage(event: string, payload: Record<string, unknown>) {
|
|
853
|
-
return `${event} ${JSON.stringify(payload)}`;
|
|
854
|
-
}
|
|
855
|
-
|
|
856
806
|
private logInfoJson(
|
|
857
807
|
scope: string | undefined,
|
|
858
808
|
event: string,
|
|
859
809
|
payload: Record<string, unknown>,
|
|
860
810
|
options?: { debugOnly?: boolean },
|
|
861
811
|
) {
|
|
862
|
-
this.logInfo(scope,
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
private logWarnJson(
|
|
866
|
-
scope: string | undefined,
|
|
867
|
-
event: string,
|
|
868
|
-
payload: Record<string, unknown>,
|
|
869
|
-
options?: { debugOnly?: boolean },
|
|
870
|
-
) {
|
|
871
|
-
this.logWarn(scope, this.buildDebugJsonMessage(event, payload), options);
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
private logErrorJson(
|
|
875
|
-
scope: string | undefined,
|
|
876
|
-
event: string,
|
|
877
|
-
payload: Record<string, unknown>,
|
|
878
|
-
options?: { debugOnly?: boolean },
|
|
879
|
-
) {
|
|
880
|
-
this.logError(scope, this.buildDebugJsonMessage(event, payload), options);
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
private pruneLogDedupeState(currentTime = now()) {
|
|
884
|
-
pruneLogDedupeStateFromRuntime(this.logDedupeState, currentTime);
|
|
812
|
+
this.logInfo(scope, buildBncrDebugJsonMessage(event, payload), options);
|
|
885
813
|
}
|
|
886
814
|
|
|
887
815
|
private shouldEmitDedupLog(key: string, sig: string, windowMs = 5 * 60 * 1000) {
|
|
@@ -903,24 +831,6 @@ class BncrBridgeRuntime {
|
|
|
903
831
|
this.logInfo(scope, message, { debugOnly: options.debugOnly });
|
|
904
832
|
}
|
|
905
833
|
|
|
906
|
-
private logWarnDedup(
|
|
907
|
-
scope: string | undefined,
|
|
908
|
-
message: string,
|
|
909
|
-
options: { key: string; sig: string; debugOnly?: boolean; windowMs?: number },
|
|
910
|
-
) {
|
|
911
|
-
if (!this.shouldEmitDedupLog(options.key, options.sig, options.windowMs)) return;
|
|
912
|
-
this.logWarn(scope, message, { debugOnly: options.debugOnly });
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
private logErrorDedup(
|
|
916
|
-
scope: string | undefined,
|
|
917
|
-
message: string,
|
|
918
|
-
options: { key: string; sig: string; debugOnly?: boolean; windowMs?: number },
|
|
919
|
-
) {
|
|
920
|
-
if (!this.shouldEmitDedupLog(options.key, options.sig, options.windowMs)) return;
|
|
921
|
-
this.logError(scope, message, { debugOnly: options.debugOnly });
|
|
922
|
-
}
|
|
923
|
-
|
|
924
834
|
private logInfoDedupJson(
|
|
925
835
|
scope: string | undefined,
|
|
926
836
|
event: string,
|
|
@@ -931,33 +841,8 @@ class BncrBridgeRuntime {
|
|
|
931
841
|
this.logInfoJson(scope, event, payload, { debugOnly: options.debugOnly });
|
|
932
842
|
}
|
|
933
843
|
|
|
934
|
-
private logWarnDedupJson(
|
|
935
|
-
scope: string | undefined,
|
|
936
|
-
event: string,
|
|
937
|
-
payload: Record<string, unknown>,
|
|
938
|
-
options: { key: string; sig: string; debugOnly?: boolean; windowMs?: number },
|
|
939
|
-
) {
|
|
940
|
-
if (!this.shouldEmitDedupLog(options.key, options.sig, options.windowMs)) return;
|
|
941
|
-
this.logWarnJson(scope, event, payload, { debugOnly: options.debugOnly });
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
private logErrorDedupJson(
|
|
945
|
-
scope: string | undefined,
|
|
946
|
-
event: string,
|
|
947
|
-
payload: Record<string, unknown>,
|
|
948
|
-
options: { key: string; sig: string; debugOnly?: boolean; windowMs?: number },
|
|
949
|
-
) {
|
|
950
|
-
if (!this.shouldEmitDedupLog(options.key, options.sig, options.windowMs)) return;
|
|
951
|
-
this.logErrorJson(scope, event, payload, { debugOnly: options.debugOnly });
|
|
952
|
-
}
|
|
953
|
-
|
|
954
844
|
private summarizeTextPreview(raw: string, limit = 8) {
|
|
955
|
-
|
|
956
|
-
.replace(/\s+/g, ' ')
|
|
957
|
-
.trim();
|
|
958
|
-
if (!compact) return '-';
|
|
959
|
-
const chars = Array.from(compact);
|
|
960
|
-
return chars.length > limit ? `${chars.slice(0, Math.max(1, limit)).join('')}…` : compact;
|
|
845
|
+
return summarizeBncrTextPreview(raw, limit);
|
|
961
846
|
}
|
|
962
847
|
|
|
963
848
|
private summarizeScope(route: BncrRoute) {
|
|
@@ -1026,33 +911,43 @@ class BncrBridgeRuntime {
|
|
|
1026
911
|
};
|
|
1027
912
|
}
|
|
1028
913
|
|
|
1029
|
-
private clearChannelAccountWorker(accountId: string, reason: string) {
|
|
1030
|
-
return clearBncrStatusWorker(this.buildStatusWorkerRuntime(), accountId, reason);
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
914
|
private clearAllChannelAccountWorkers(reason: string) {
|
|
1034
915
|
clearAllBncrStatusWorkers(this.buildStatusWorkerRuntime(), reason);
|
|
1035
916
|
}
|
|
1036
917
|
|
|
1037
|
-
private
|
|
1038
|
-
|
|
1039
|
-
) {
|
|
1040
|
-
this.lastDriftSnapshot = buildRegisterDriftSnapshot({
|
|
1041
|
-
capturedAt: now(),
|
|
918
|
+
private getRegisterTraceRuntimeState(): RegisterTraceRuntimeState {
|
|
919
|
+
return {
|
|
1042
920
|
registerCount: this.registerCount,
|
|
1043
921
|
apiGeneration: this.apiGeneration,
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
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;
|
|
1050
946
|
}
|
|
1051
947
|
|
|
1052
948
|
private buildRegisterTraceSummary() {
|
|
1053
|
-
return
|
|
1054
|
-
|
|
1055
|
-
firstRegisterAt: this.firstRegisterAt,
|
|
949
|
+
return buildRegisterTraceRuntimeSummary({
|
|
950
|
+
state: this.getRegisterTraceRuntimeState(),
|
|
1056
951
|
warmupWindowMs: REGISTER_WARMUP_WINDOW_MS,
|
|
1057
952
|
});
|
|
1058
953
|
}
|
|
@@ -1065,43 +960,25 @@ class BncrBridgeRuntime {
|
|
|
1065
960
|
registryFingerprint?: string;
|
|
1066
961
|
}) {
|
|
1067
962
|
const ts = now();
|
|
1068
|
-
this.registerCount += 1;
|
|
1069
|
-
if (this.firstRegisterAt == null) this.firstRegisterAt = ts;
|
|
1070
|
-
this.lastRegisterAt = ts;
|
|
1071
|
-
if (meta.apiRebound) {
|
|
1072
|
-
this.apiGeneration += 1;
|
|
1073
|
-
this.lastApiRebindAt = ts;
|
|
1074
|
-
} else if (this.registerCount === 1 && this.apiGeneration === 0) {
|
|
1075
|
-
this.apiGeneration = 1;
|
|
1076
|
-
}
|
|
1077
|
-
if (meta.source) this.pluginSource = meta.source;
|
|
1078
|
-
if (meta.pluginVersion) this.pluginVersion = meta.pluginVersion;
|
|
1079
|
-
if (meta.apiInstanceId) this.lastApiInstanceId = meta.apiInstanceId;
|
|
1080
|
-
if (meta.registryFingerprint) this.lastRegistryFingerprint = meta.registryFingerprint;
|
|
1081
|
-
|
|
1082
963
|
const stack = String(new Error().stack || '')
|
|
1083
964
|
.split('\n')
|
|
1084
965
|
.slice(2, 7)
|
|
1085
966
|
.map((line) => line.trim())
|
|
1086
967
|
.filter(Boolean)
|
|
1087
968
|
.join(' <- ');
|
|
1088
|
-
const
|
|
969
|
+
const state = this.getRegisterTraceRuntimeState();
|
|
970
|
+
const { trace, capturedDriftSnapshot } = noteRegisterTraceRuntime({
|
|
971
|
+
state,
|
|
972
|
+
meta,
|
|
1089
973
|
ts,
|
|
974
|
+
stack,
|
|
1090
975
|
bridgeId: this.bridgeId,
|
|
1091
976
|
gatewayPid: this.gatewayPid,
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
apiRebound: meta.apiRebound === true,
|
|
1095
|
-
apiInstanceId: this.lastApiInstanceId,
|
|
1096
|
-
registryFingerprint: this.lastRegistryFingerprint,
|
|
1097
|
-
source: this.pluginSource,
|
|
1098
|
-
pluginVersion: this.pluginVersion,
|
|
1099
|
-
stack,
|
|
977
|
+
warmupWindowMs: REGISTER_WARMUP_WINDOW_MS,
|
|
978
|
+
maxTraceEntries: 12,
|
|
1100
979
|
});
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
const summary = this.buildRegisterTraceSummary();
|
|
1104
|
-
if (summary.postWarmupRegisterCount > 0) this.captureDriftSnapshot(summary);
|
|
980
|
+
this.applyRegisterTraceRuntimeState(state);
|
|
981
|
+
if (capturedDriftSnapshot) this.scheduleSave();
|
|
1105
982
|
|
|
1106
983
|
this.logInfo('debug', `register-trace ${JSON.stringify(trace)}`, { debugOnly: true });
|
|
1107
984
|
}
|
|
@@ -1202,25 +1079,18 @@ class BncrBridgeRuntime {
|
|
|
1202
1079
|
}
|
|
1203
1080
|
|
|
1204
1081
|
private buildRuntimeSurfaceDiagnostics() {
|
|
1205
|
-
|
|
1206
|
-
const surfaces = {
|
|
1207
|
-
inbound: Boolean(channelRuntime?.inbound),
|
|
1208
|
-
media: Boolean(channelRuntime?.media),
|
|
1209
|
-
reply: Boolean(channelRuntime?.reply),
|
|
1210
|
-
routing: Boolean(channelRuntime?.routing),
|
|
1211
|
-
session: Boolean(channelRuntime?.session),
|
|
1212
|
-
};
|
|
1213
|
-
return {
|
|
1214
|
-
channel: surfaces,
|
|
1215
|
-
missing: Object.entries(surfaces)
|
|
1216
|
-
.filter(([, present]) => !present)
|
|
1217
|
-
.map(([name]) => name),
|
|
1218
|
-
};
|
|
1082
|
+
return buildOpenClawChannelRuntimeSurfaceDiagnostics(this.api);
|
|
1219
1083
|
}
|
|
1220
1084
|
|
|
1221
1085
|
private buildExtendedDiagnostics(accountId: string) {
|
|
1222
1086
|
const acc = normalizeAccountId(accountId);
|
|
1223
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);
|
|
1224
1094
|
return buildExtendedDiagnosticsFromRuntime({
|
|
1225
1095
|
diagnostics,
|
|
1226
1096
|
runtimeSurface: this.buildRuntimeSurfaceDiagnostics(),
|
|
@@ -1250,6 +1120,8 @@ class BncrBridgeRuntime {
|
|
|
1250
1120
|
lastActivityAt: this.lastActivityAtGlobal,
|
|
1251
1121
|
lastInboundAt: this.lastInboundAtGlobal,
|
|
1252
1122
|
lastAckAt: this.lastAckAtGlobal,
|
|
1123
|
+
hasGatewayContext: Boolean(this.gatewayContext),
|
|
1124
|
+
lastGatewayContextAt: this.lastGatewayContextAt,
|
|
1253
1125
|
recent: Array.from(this.recentConnections.entries()).map(([leaseId, entry]) => ({
|
|
1254
1126
|
leaseId,
|
|
1255
1127
|
epoch: entry.epoch,
|
|
@@ -1258,6 +1130,19 @@ class BncrBridgeRuntime {
|
|
|
1258
1130
|
isPrimary: entry.isPrimary,
|
|
1259
1131
|
})),
|
|
1260
1132
|
},
|
|
1133
|
+
outbound: buildExtendedOutboundDiagnostics({
|
|
1134
|
+
outbox: outboxDiagnostics,
|
|
1135
|
+
enqueueCount: this.getCounter(this.outboundEnqueueCountByAccount, acc),
|
|
1136
|
+
lastEnqueueAt: this.lastOutboundEnqueueAtByAccount.get(acc) || null,
|
|
1137
|
+
prePushGuardSkipCount,
|
|
1138
|
+
lastPrePushGuardSkipAt,
|
|
1139
|
+
lastPrePushGuardSkipReason,
|
|
1140
|
+
hasGatewayContext,
|
|
1141
|
+
lastGatewayContextAt: this.lastGatewayContextAt,
|
|
1142
|
+
ackObservability,
|
|
1143
|
+
nowMs: now(),
|
|
1144
|
+
}),
|
|
1145
|
+
deadLetterSummary: this.buildDeadLetterDiagnostics(acc),
|
|
1261
1146
|
protocol: {
|
|
1262
1147
|
bridgeVersion: BRIDGE_VERSION,
|
|
1263
1148
|
protocolVersion: 2,
|
|
@@ -1374,6 +1259,64 @@ class BncrBridgeRuntime {
|
|
|
1374
1259
|
return map.get(normalizeAccountId(accountId)) || 0;
|
|
1375
1260
|
}
|
|
1376
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
|
+
|
|
1377
1320
|
private async refreshDebugFlagFromConfig(options?: { forceLog?: boolean }) {
|
|
1378
1321
|
try {
|
|
1379
1322
|
const cfg = getOpenClawRuntimeConfig(this.api);
|
|
@@ -1423,8 +1366,6 @@ class BncrBridgeRuntime {
|
|
|
1423
1366
|
});
|
|
1424
1367
|
if (!agentId) return;
|
|
1425
1368
|
this.canonicalAgentId = agentId;
|
|
1426
|
-
this.canonicalAgentSource = 'startup';
|
|
1427
|
-
this.canonicalAgentResolvedAt = now();
|
|
1428
1369
|
}
|
|
1429
1370
|
|
|
1430
1371
|
private ensureCanonicalAgentId(args: {
|
|
@@ -1438,14 +1379,10 @@ class BncrBridgeRuntime {
|
|
|
1438
1379
|
const agentId = this.tryResolveBindingAgentId(args);
|
|
1439
1380
|
if (agentId) {
|
|
1440
1381
|
this.canonicalAgentId = agentId;
|
|
1441
|
-
this.canonicalAgentSource = 'runtime';
|
|
1442
|
-
this.canonicalAgentResolvedAt = now();
|
|
1443
1382
|
return agentId;
|
|
1444
1383
|
}
|
|
1445
1384
|
|
|
1446
1385
|
this.canonicalAgentId = 'main';
|
|
1447
|
-
this.canonicalAgentSource = 'fallback-main';
|
|
1448
|
-
this.canonicalAgentResolvedAt = now();
|
|
1449
1386
|
this.logWarn(
|
|
1450
1387
|
'target',
|
|
1451
1388
|
'binding agent unresolved; fallback to main for current process lifetime',
|
|
@@ -1455,45 +1392,23 @@ class BncrBridgeRuntime {
|
|
|
1455
1392
|
}
|
|
1456
1393
|
|
|
1457
1394
|
private countInvalidOutboxSessionKeys(accountId: string): number {
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
if (!parseStrictBncrSessionKey(entry.sessionKey)) count += 1;
|
|
1463
|
-
}
|
|
1464
|
-
return count;
|
|
1395
|
+
return countInvalidOutboxSessionKeysFromRuntime({
|
|
1396
|
+
accountId,
|
|
1397
|
+
outboxEntries: this.outbox.values(),
|
|
1398
|
+
});
|
|
1465
1399
|
}
|
|
1466
1400
|
|
|
1467
1401
|
private countLegacyAccountResidue(accountId: string): number {
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
if (mismatched(entry.accountId)) count += 1;
|
|
1479
|
-
}
|
|
1480
|
-
for (const info of this.sessionRoutes.values()) {
|
|
1481
|
-
if (mismatched(info.accountId)) count += 1;
|
|
1482
|
-
}
|
|
1483
|
-
for (const key of this.lastSessionByAccount.keys()) {
|
|
1484
|
-
if (mismatched(key)) count += 1;
|
|
1485
|
-
}
|
|
1486
|
-
for (const key of this.lastActivityByAccount.keys()) {
|
|
1487
|
-
if (mismatched(key)) count += 1;
|
|
1488
|
-
}
|
|
1489
|
-
for (const key of this.lastInboundByAccount.keys()) {
|
|
1490
|
-
if (mismatched(key)) count += 1;
|
|
1491
|
-
}
|
|
1492
|
-
for (const key of this.lastOutboundByAccount.keys()) {
|
|
1493
|
-
if (mismatched(key)) count += 1;
|
|
1494
|
-
}
|
|
1495
|
-
|
|
1496
|
-
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
|
+
});
|
|
1497
1412
|
}
|
|
1498
1413
|
|
|
1499
1414
|
private buildIntegratedDiagnostics(accountId: string) {
|
|
@@ -1522,86 +1437,71 @@ class BncrBridgeRuntime {
|
|
|
1522
1437
|
});
|
|
1523
1438
|
}
|
|
1524
1439
|
|
|
1525
|
-
private
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
sessionRoutes: [],
|
|
1440
|
+
private normalizePersistedOutboxEntry(entry: any): OutboxEntry | null {
|
|
1441
|
+
return normalizePersistedOutboxEntryFromRuntime({
|
|
1442
|
+
entry,
|
|
1443
|
+
canonicalAgentId: this.canonicalAgentId,
|
|
1444
|
+
now,
|
|
1531
1445
|
});
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
this.outbox.clear();
|
|
1535
|
-
for (const entry of data.outbox || []) {
|
|
1536
|
-
if (!entry?.messageId) continue;
|
|
1537
|
-
const accountId = normalizeAccountId(entry.accountId);
|
|
1538
|
-
const sessionKey = asString(entry.sessionKey || '').trim();
|
|
1539
|
-
const normalized = normalizeStoredSessionKey(sessionKey, this.canonicalAgentId);
|
|
1540
|
-
if (!normalized) continue;
|
|
1541
|
-
|
|
1542
|
-
const route = parseRouteLike(entry.route) || normalized.route;
|
|
1543
|
-
const payload =
|
|
1544
|
-
entry.payload && typeof entry.payload === 'object' ? { ...entry.payload } : {};
|
|
1545
|
-
(payload as any).sessionKey = normalized.sessionKey;
|
|
1546
|
-
(payload as any).platform = route.platform;
|
|
1547
|
-
(payload as any).groupId = route.groupId;
|
|
1548
|
-
(payload as any).userId = route.userId;
|
|
1549
|
-
|
|
1550
|
-
const migratedEntry: OutboxEntry = {
|
|
1551
|
-
...entry,
|
|
1552
|
-
accountId,
|
|
1553
|
-
sessionKey: normalized.sessionKey,
|
|
1554
|
-
route,
|
|
1555
|
-
payload,
|
|
1556
|
-
createdAt: finiteNumberOr(entry.createdAt, now()),
|
|
1557
|
-
retryCount: finiteNumberOr(entry.retryCount, 0),
|
|
1558
|
-
nextAttemptAt: finiteNumberOr(entry.nextAttemptAt, now()),
|
|
1559
|
-
lastAttemptAt: optionalFiniteNumber(entry.lastAttemptAt),
|
|
1560
|
-
lastError: entry.lastError ? asString(entry.lastError) : undefined,
|
|
1561
|
-
};
|
|
1446
|
+
}
|
|
1562
1447
|
|
|
1563
|
-
|
|
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);
|
|
1564
1456
|
}
|
|
1457
|
+
}
|
|
1565
1458
|
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
const normalized = normalizeStoredSessionKey(sessionKey, this.canonicalAgentId);
|
|
1575
|
-
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
|
+
}
|
|
1576
1467
|
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
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;
|
|
1584
1479
|
|
|
1585
|
-
this.
|
|
1586
|
-
...entry,
|
|
1587
|
-
accountId,
|
|
1480
|
+
this.lastSessionByAccount.set(accountId, {
|
|
1588
1481
|
sessionKey: normalized.sessionKey,
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
retryCount: finiteNumberOr(entry.retryCount, 0),
|
|
1593
|
-
nextAttemptAt: finiteNumberOr(entry.nextAttemptAt, now()),
|
|
1594
|
-
lastAttemptAt: optionalFiniteNumber(entry.lastAttemptAt),
|
|
1595
|
-
lastError: entry.lastError ? asString(entry.lastError) : undefined,
|
|
1482
|
+
// 展示统一为 Bncr-platform:group:user
|
|
1483
|
+
scope: formatDisplayScope(normalized.route),
|
|
1484
|
+
updatedAt,
|
|
1596
1485
|
});
|
|
1597
1486
|
}
|
|
1487
|
+
}
|
|
1598
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
|
+
}
|
|
1499
|
+
|
|
1500
|
+
private loadPersistedSessionRoutes(persisted: unknown): void {
|
|
1599
1501
|
this.sessionRoutes.clear();
|
|
1600
1502
|
this.routeAliases.clear();
|
|
1601
|
-
const
|
|
1602
|
-
|
|
1603
|
-
: [];
|
|
1604
|
-
for (const item of persistedSessionRoutes) {
|
|
1503
|
+
const items = Array.isArray(persisted) ? persisted.slice(-MAX_SESSION_ROUTE_ENTRIES) : [];
|
|
1504
|
+
for (const item of items) {
|
|
1605
1505
|
const normalized = normalizeStoredSessionKey(
|
|
1606
1506
|
asString(item?.sessionKey || ''),
|
|
1607
1507
|
this.canonicalAgentId,
|
|
@@ -1611,186 +1511,101 @@ class BncrBridgeRuntime {
|
|
|
1611
1511
|
const route = parseRouteLike(item?.route) || normalized.route;
|
|
1612
1512
|
const accountId = normalizeAccountId(item?.accountId);
|
|
1613
1513
|
const updatedAt = finiteNumberOr(item?.updatedAt, now());
|
|
1614
|
-
|
|
1615
|
-
const info = {
|
|
1616
|
-
accountId,
|
|
1617
|
-
route,
|
|
1618
|
-
updatedAt,
|
|
1619
|
-
};
|
|
1514
|
+
const info = { accountId, route, updatedAt };
|
|
1620
1515
|
|
|
1621
1516
|
this.sessionRoutes.set(normalized.sessionKey, info);
|
|
1622
1517
|
this.routeAliases.set(routeKey(accountId, route), info);
|
|
1623
1518
|
}
|
|
1519
|
+
}
|
|
1624
1520
|
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
const updatedAt = finiteNumberOr(item?.updatedAt, 0);
|
|
1636
|
-
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
|
+
}
|
|
1637
1531
|
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
// 展示统一为 Bncr-platform:group:user
|
|
1641
|
-
scope: formatDisplayScope(normalized.route),
|
|
1642
|
-
updatedAt,
|
|
1643
|
-
});
|
|
1644
|
-
}
|
|
1532
|
+
private backfillAccountActivityFromSessionRoutes(): void {
|
|
1533
|
+
if (this.lastSessionByAccount.size > 0 || this.sessionRoutes.size === 0) return;
|
|
1645
1534
|
|
|
1646
|
-
this.
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
: [];
|
|
1650
|
-
for (const item of persistedLastActivityByAccount) {
|
|
1651
|
-
const accountId = normalizeAccountId(item?.accountId);
|
|
1652
|
-
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);
|
|
1653
1538
|
if (updatedAt <= 0) continue;
|
|
1654
|
-
|
|
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);
|
|
1655
1555
|
}
|
|
1556
|
+
}
|
|
1656
1557
|
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
: []
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
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);
|
|
1666
1572
|
}
|
|
1667
1573
|
|
|
1668
|
-
this.
|
|
1669
|
-
const
|
|
1670
|
-
? data.
|
|
1574
|
+
this.deadLetter = [];
|
|
1575
|
+
const persistedDeadLetter = Array.isArray(data.deadLetter)
|
|
1576
|
+
? data.deadLetter.slice(-MAX_DEAD_LETTER_ENTRIES)
|
|
1671
1577
|
: [];
|
|
1672
|
-
for (const
|
|
1673
|
-
const
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
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);
|
|
1677
1582
|
}
|
|
1678
1583
|
|
|
1679
|
-
this.
|
|
1680
|
-
data.lastDriftSnapshot && typeof data.lastDriftSnapshot === 'object'
|
|
1681
|
-
? {
|
|
1682
|
-
capturedAt: finiteNumberOr((data.lastDriftSnapshot as any).capturedAt, 0),
|
|
1683
|
-
registerCount: Number.isFinite(Number((data.lastDriftSnapshot as any).registerCount))
|
|
1684
|
-
? Number((data.lastDriftSnapshot as any).registerCount)
|
|
1685
|
-
: null,
|
|
1686
|
-
apiGeneration: Number.isFinite(Number((data.lastDriftSnapshot as any).apiGeneration))
|
|
1687
|
-
? Number((data.lastDriftSnapshot as any).apiGeneration)
|
|
1688
|
-
: null,
|
|
1689
|
-
postWarmupRegisterCount: Number.isFinite(
|
|
1690
|
-
Number((data.lastDriftSnapshot as any).postWarmupRegisterCount),
|
|
1691
|
-
)
|
|
1692
|
-
? Number((data.lastDriftSnapshot as any).postWarmupRegisterCount)
|
|
1693
|
-
: null,
|
|
1694
|
-
apiInstanceId:
|
|
1695
|
-
asString((data.lastDriftSnapshot as any).apiInstanceId || '').trim() || null,
|
|
1696
|
-
registryFingerprint:
|
|
1697
|
-
asString((data.lastDriftSnapshot as any).registryFingerprint || '').trim() || null,
|
|
1698
|
-
dominantBucket:
|
|
1699
|
-
asString((data.lastDriftSnapshot as any).dominantBucket || '').trim() || null,
|
|
1700
|
-
sourceBuckets:
|
|
1701
|
-
(data.lastDriftSnapshot as any).sourceBuckets &&
|
|
1702
|
-
typeof (data.lastDriftSnapshot as any).sourceBuckets === 'object'
|
|
1703
|
-
? { ...((data.lastDriftSnapshot as any).sourceBuckets as Record<string, number>) }
|
|
1704
|
-
: {},
|
|
1705
|
-
traceWindowSize: finiteNumberOr((data.lastDriftSnapshot as any).traceWindowSize, 0),
|
|
1706
|
-
traceRecent: Array.isArray((data.lastDriftSnapshot as any).traceRecent)
|
|
1707
|
-
? [...((data.lastDriftSnapshot as any).traceRecent as Array<Record<string, unknown>>)]
|
|
1708
|
-
: [],
|
|
1709
|
-
}
|
|
1710
|
-
: null;
|
|
1584
|
+
this.loadPersistedSessionRoutes(data.sessionRoutes);
|
|
1711
1585
|
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
const updatedAt = finiteNumberOr(info.updatedAt, 0);
|
|
1717
|
-
if (updatedAt <= 0) continue;
|
|
1718
|
-
|
|
1719
|
-
const current = this.lastSessionByAccount.get(acc);
|
|
1720
|
-
if (!current || updatedAt >= current.updatedAt) {
|
|
1721
|
-
this.lastSessionByAccount.set(acc, {
|
|
1722
|
-
sessionKey,
|
|
1723
|
-
// 回填时统一展示为 Bncr-platform:group:user
|
|
1724
|
-
scope: formatDisplayScope(info.route),
|
|
1725
|
-
updatedAt,
|
|
1726
|
-
});
|
|
1727
|
-
}
|
|
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);
|
|
1728
1590
|
|
|
1729
|
-
|
|
1730
|
-
if (updatedAt > lastAct) this.lastActivityByAccount.set(acc, updatedAt);
|
|
1591
|
+
this.lastDriftSnapshot = normalizeRegisterDriftSnapshot(data.lastDriftSnapshot);
|
|
1731
1592
|
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
}
|
|
1735
|
-
}
|
|
1593
|
+
// 兼容旧状态文件:若尚未持久化 lastSession*/lastActivity*,从 sessionRoutes 回填。
|
|
1594
|
+
this.backfillAccountActivityFromSessionRoutes();
|
|
1736
1595
|
}
|
|
1737
1596
|
|
|
1738
1597
|
private async flushState() {
|
|
1739
1598
|
if (!this.statePath) return;
|
|
1740
1599
|
|
|
1741
|
-
const sessionRoutes = Array.from(this.sessionRoutes.entries())
|
|
1742
|
-
.map(([sessionKey, v]) => ({
|
|
1743
|
-
sessionKey,
|
|
1744
|
-
accountId: v.accountId,
|
|
1745
|
-
route: v.route,
|
|
1746
|
-
updatedAt: v.updatedAt,
|
|
1747
|
-
}))
|
|
1748
|
-
.slice(-MAX_SESSION_ROUTE_ENTRIES);
|
|
1749
|
-
|
|
1750
1600
|
const data: PersistedState = {
|
|
1751
1601
|
outbox: Array.from(this.outbox.values()),
|
|
1752
1602
|
deadLetter: this.deadLetter.slice(-MAX_DEAD_LETTER_ENTRIES),
|
|
1753
|
-
sessionRoutes,
|
|
1754
|
-
lastSessionByAccount:
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
updatedAt: v.updatedAt,
|
|
1760
|
-
}))
|
|
1761
|
-
.slice(-MAX_ACCOUNT_ACTIVITY_ENTRIES),
|
|
1762
|
-
lastActivityByAccount: Array.from(this.lastActivityByAccount.entries())
|
|
1763
|
-
.map(([accountId, updatedAt]) => ({
|
|
1764
|
-
accountId,
|
|
1765
|
-
updatedAt,
|
|
1766
|
-
}))
|
|
1767
|
-
.slice(-MAX_ACCOUNT_ACTIVITY_ENTRIES),
|
|
1768
|
-
lastInboundByAccount: Array.from(this.lastInboundByAccount.entries())
|
|
1769
|
-
.map(([accountId, updatedAt]) => ({
|
|
1770
|
-
accountId,
|
|
1771
|
-
updatedAt,
|
|
1772
|
-
}))
|
|
1773
|
-
.slice(-MAX_ACCOUNT_ACTIVITY_ENTRIES),
|
|
1774
|
-
lastOutboundByAccount: Array.from(this.lastOutboundByAccount.entries())
|
|
1775
|
-
.map(([accountId, updatedAt]) => ({
|
|
1776
|
-
accountId,
|
|
1777
|
-
updatedAt,
|
|
1778
|
-
}))
|
|
1779
|
-
.slice(-MAX_ACCOUNT_ACTIVITY_ENTRIES),
|
|
1780
|
-
lastDriftSnapshot: this.lastDriftSnapshot
|
|
1781
|
-
? {
|
|
1782
|
-
capturedAt: this.lastDriftSnapshot.capturedAt,
|
|
1783
|
-
registerCount: this.lastDriftSnapshot.registerCount,
|
|
1784
|
-
apiGeneration: this.lastDriftSnapshot.apiGeneration,
|
|
1785
|
-
postWarmupRegisterCount: this.lastDriftSnapshot.postWarmupRegisterCount,
|
|
1786
|
-
apiInstanceId: this.lastDriftSnapshot.apiInstanceId,
|
|
1787
|
-
registryFingerprint: this.lastDriftSnapshot.registryFingerprint,
|
|
1788
|
-
dominantBucket: this.lastDriftSnapshot.dominantBucket,
|
|
1789
|
-
sourceBuckets: { ...this.lastDriftSnapshot.sourceBuckets },
|
|
1790
|
-
traceWindowSize: this.lastDriftSnapshot.traceWindowSize,
|
|
1791
|
-
traceRecent: this.lastDriftSnapshot.traceRecent.map((trace) => ({ ...trace })),
|
|
1792
|
-
}
|
|
1793
|
-
: 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),
|
|
1794
1609
|
};
|
|
1795
1610
|
|
|
1796
1611
|
await writeOpenClawJsonFileAtomically(this.statePath, data);
|
|
@@ -1808,7 +1623,9 @@ class BncrBridgeRuntime {
|
|
|
1808
1623
|
}
|
|
1809
1624
|
|
|
1810
1625
|
private rememberGatewayContext(context: GatewayRequestHandlerOptions['context']) {
|
|
1811
|
-
if (context)
|
|
1626
|
+
if (!context) return;
|
|
1627
|
+
this.gatewayContext = context;
|
|
1628
|
+
this.lastGatewayContextAt = now();
|
|
1812
1629
|
}
|
|
1813
1630
|
|
|
1814
1631
|
private resolveOutboxPushOwner(accountId: string): BncrConnection | null {
|
|
@@ -1817,64 +1634,30 @@ class BncrBridgeRuntime {
|
|
|
1817
1634
|
const primaryKey = this.activeConnectionByAccount.get(acc);
|
|
1818
1635
|
const primary = primaryKey ? this.connections.get(primaryKey) : null;
|
|
1819
1636
|
|
|
1820
|
-
const isEligible = (
|
|
1821
|
-
conn: BncrConnection | null | undefined,
|
|
1822
|
-
): conn is BncrConnection & {
|
|
1823
|
-
outboundReadyUntil?: number;
|
|
1824
|
-
preferredForOutboundUntil?: number;
|
|
1825
|
-
inboundOnly?: boolean;
|
|
1826
|
-
} => {
|
|
1827
|
-
if (!conn?.connId) return false;
|
|
1828
|
-
if (t - conn.lastSeenAt > CONNECT_TTL_MS) return false;
|
|
1829
|
-
if ((conn as any).inboundOnly === true) return false;
|
|
1830
|
-
return true;
|
|
1831
|
-
};
|
|
1832
|
-
|
|
1833
1637
|
const recentInboundConnIds = this.resolveRecentInboundConnIds(acc);
|
|
1834
|
-
const candidateScore = (conn: BncrConnection) => {
|
|
1835
|
-
const preferredForOutboundUntil = finiteNumberOr((conn as any).preferredForOutboundUntil, 0);
|
|
1836
|
-
const outboundReadyUntil = finiteNumberOr((conn as any).outboundReadyUntil, 0);
|
|
1837
|
-
const lastPushTimeoutAt = finiteNumberOr((conn as any).lastPushTimeoutAt, 0);
|
|
1838
|
-
const lastAckOkAt = finiteNumberOr((conn as any).lastAckOkAt, 0);
|
|
1839
|
-
const pushFailureScore = finiteNumberOr((conn as any).pushFailureScore, 0);
|
|
1840
|
-
const recentTimeoutPenalty = lastPushTimeoutAt > 0 && t - lastPushTimeoutAt <= 30_000 ? 1 : 0;
|
|
1841
|
-
return {
|
|
1842
|
-
preferred: preferredForOutboundUntil > t ? 1 : 0,
|
|
1843
|
-
ready: outboundReadyUntil > t ? 1 : 0,
|
|
1844
|
-
recentInbound: recentInboundConnIds.has(conn.connId) ? 1 : 0,
|
|
1845
|
-
recentTimeoutPenalty,
|
|
1846
|
-
pushFailureScore,
|
|
1847
|
-
lastAckOkAt,
|
|
1848
|
-
lastPushTimeoutAt,
|
|
1849
|
-
lastSeenAt: conn.lastSeenAt,
|
|
1850
|
-
connectedAt: conn.connectedAt,
|
|
1851
|
-
};
|
|
1852
|
-
};
|
|
1853
1638
|
|
|
1854
|
-
if (
|
|
1855
|
-
|
|
1856
|
-
|
|
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;
|
|
1857
1652
|
}
|
|
1858
1653
|
|
|
1859
|
-
const candidates =
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
if (sb.ready !== sa.ready) return sb.ready - sa.ready;
|
|
1867
|
-
if (sa.recentTimeoutPenalty !== sb.recentTimeoutPenalty)
|
|
1868
|
-
return sa.recentTimeoutPenalty - sb.recentTimeoutPenalty;
|
|
1869
|
-
if (sa.pushFailureScore !== sb.pushFailureScore)
|
|
1870
|
-
return sa.pushFailureScore - sb.pushFailureScore;
|
|
1871
|
-
if (sb.lastAckOkAt !== sa.lastAckOkAt) return sb.lastAckOkAt - sa.lastAckOkAt;
|
|
1872
|
-
if (sa.lastPushTimeoutAt !== sb.lastPushTimeoutAt)
|
|
1873
|
-
return sa.lastPushTimeoutAt - sb.lastPushTimeoutAt;
|
|
1874
|
-
if (sb.recentInbound !== sa.recentInbound) return sb.recentInbound - sa.recentInbound;
|
|
1875
|
-
if (sb.lastSeenAt !== sa.lastSeenAt) return sb.lastSeenAt - sa.lastSeenAt;
|
|
1876
|
-
return sb.connectedAt - sa.connectedAt;
|
|
1877
|
-
});
|
|
1654
|
+
const candidates = selectOrderedOutboundPushConnections({
|
|
1655
|
+
accountId: acc,
|
|
1656
|
+
now: t,
|
|
1657
|
+
connectTtlMs: CONNECT_TTL_MS,
|
|
1658
|
+
recentInboundConnIds,
|
|
1659
|
+
connections: this.connections.values(),
|
|
1660
|
+
});
|
|
1878
1661
|
|
|
1879
1662
|
const next = candidates[0] || null;
|
|
1880
1663
|
if (!next) return null;
|
|
@@ -1918,67 +1701,29 @@ class BncrBridgeRuntime {
|
|
|
1918
1701
|
const t = now();
|
|
1919
1702
|
const connIds = new Set<string>();
|
|
1920
1703
|
|
|
1921
|
-
const isEligible = (
|
|
1922
|
-
conn: BncrConnection | null | undefined,
|
|
1923
|
-
): conn is BncrConnection & {
|
|
1924
|
-
outboundReadyUntil?: number;
|
|
1925
|
-
preferredForOutboundUntil?: number;
|
|
1926
|
-
inboundOnly?: boolean;
|
|
1927
|
-
} => {
|
|
1928
|
-
if (!conn?.connId) return false;
|
|
1929
|
-
if (t - conn.lastSeenAt > CONNECT_TTL_MS) return false;
|
|
1930
|
-
if ((conn as any).inboundOnly === true) return false;
|
|
1931
|
-
return true;
|
|
1932
|
-
};
|
|
1933
|
-
|
|
1934
1704
|
const recentInboundConnIds = this.resolveRecentInboundConnIds(acc);
|
|
1935
|
-
const candidateScore = (conn: BncrConnection) => {
|
|
1936
|
-
const preferredForOutboundUntil = finiteNumberOr((conn as any).preferredForOutboundUntil, 0);
|
|
1937
|
-
const outboundReadyUntil = finiteNumberOr((conn as any).outboundReadyUntil, 0);
|
|
1938
|
-
const lastPushTimeoutAt = finiteNumberOr((conn as any).lastPushTimeoutAt, 0);
|
|
1939
|
-
const lastAckOkAt = finiteNumberOr((conn as any).lastAckOkAt, 0);
|
|
1940
|
-
const pushFailureScore = finiteNumberOr((conn as any).pushFailureScore, 0);
|
|
1941
|
-
const recentTimeoutPenalty = lastPushTimeoutAt > 0 && t - lastPushTimeoutAt <= 30_000 ? 1 : 0;
|
|
1942
|
-
return {
|
|
1943
|
-
preferred: preferredForOutboundUntil > t ? 1 : 0,
|
|
1944
|
-
ready: outboundReadyUntil > t ? 1 : 0,
|
|
1945
|
-
recentInbound: recentInboundConnIds.has(conn.connId) ? 1 : 0,
|
|
1946
|
-
recentTimeoutPenalty,
|
|
1947
|
-
pushFailureScore,
|
|
1948
|
-
lastAckOkAt,
|
|
1949
|
-
lastPushTimeoutAt,
|
|
1950
|
-
lastSeenAt: conn.lastSeenAt,
|
|
1951
|
-
connectedAt: conn.connectedAt,
|
|
1952
|
-
};
|
|
1953
|
-
};
|
|
1954
1705
|
|
|
1955
1706
|
const primaryKey = this.activeConnectionByAccount.get(acc);
|
|
1956
1707
|
if (primaryKey) {
|
|
1957
1708
|
const primary = this.connections.get(primaryKey);
|
|
1958
|
-
if (
|
|
1709
|
+
if (
|
|
1710
|
+
isEligibleOutboundPushConnection({
|
|
1711
|
+
connection: primary,
|
|
1712
|
+
now: t,
|
|
1713
|
+
connectTtlMs: CONNECT_TTL_MS,
|
|
1714
|
+
})
|
|
1715
|
+
) {
|
|
1959
1716
|
connIds.add(primary.connId);
|
|
1960
1717
|
}
|
|
1961
1718
|
}
|
|
1962
1719
|
|
|
1963
|
-
const candidates =
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
if (sb.ready !== sa.ready) return sb.ready - sa.ready;
|
|
1971
|
-
if (sa.recentTimeoutPenalty !== sb.recentTimeoutPenalty)
|
|
1972
|
-
return sa.recentTimeoutPenalty - sb.recentTimeoutPenalty;
|
|
1973
|
-
if (sa.pushFailureScore !== sb.pushFailureScore)
|
|
1974
|
-
return sa.pushFailureScore - sb.pushFailureScore;
|
|
1975
|
-
if (sb.lastAckOkAt !== sa.lastAckOkAt) return sb.lastAckOkAt - sa.lastAckOkAt;
|
|
1976
|
-
if (sa.lastPushTimeoutAt !== sb.lastPushTimeoutAt)
|
|
1977
|
-
return sa.lastPushTimeoutAt - sb.lastPushTimeoutAt;
|
|
1978
|
-
if (sb.recentInbound !== sa.recentInbound) return sb.recentInbound - sa.recentInbound;
|
|
1979
|
-
if (sb.lastSeenAt !== sa.lastSeenAt) return sb.lastSeenAt - sa.lastSeenAt;
|
|
1980
|
-
return sb.connectedAt - sa.connectedAt;
|
|
1981
|
-
});
|
|
1720
|
+
const candidates = selectOrderedOutboundPushConnections({
|
|
1721
|
+
accountId: acc,
|
|
1722
|
+
now: t,
|
|
1723
|
+
connectTtlMs: CONNECT_TTL_MS,
|
|
1724
|
+
recentInboundConnIds,
|
|
1725
|
+
connections: this.connections.values(),
|
|
1726
|
+
});
|
|
1982
1727
|
|
|
1983
1728
|
for (const c of candidates) {
|
|
1984
1729
|
connIds.add(c.connId);
|
|
@@ -2156,10 +1901,7 @@ class BncrBridgeRuntime {
|
|
|
2156
1901
|
);
|
|
2157
1902
|
}
|
|
2158
1903
|
|
|
2159
|
-
private handleFileTransferPushFailure(args: {
|
|
2160
|
-
entry: OutboxEntry;
|
|
2161
|
-
error: unknown;
|
|
2162
|
-
}) {
|
|
1904
|
+
private handleFileTransferPushFailure(args: { entry: OutboxEntry; error: unknown }) {
|
|
2163
1905
|
this.recordOutboxPushFailure({
|
|
2164
1906
|
entry: args.entry,
|
|
2165
1907
|
error: args.error,
|
|
@@ -2278,6 +2020,7 @@ class BncrBridgeRuntime {
|
|
|
2278
2020
|
audioAsVoice?: boolean;
|
|
2279
2021
|
kind?: 'tool' | 'block' | 'final';
|
|
2280
2022
|
replyToId?: string;
|
|
2023
|
+
replyTargetPolicy?: OutboundReplyTargetPolicy;
|
|
2281
2024
|
}): OutboxEntry {
|
|
2282
2025
|
return buildFileTransferOutboxEntryFromRuntime({
|
|
2283
2026
|
createMessageId: () => randomUUID(),
|
|
@@ -2294,6 +2037,7 @@ class BncrBridgeRuntime {
|
|
|
2294
2037
|
audioAsVoice: params.audioAsVoice,
|
|
2295
2038
|
kind: params.kind,
|
|
2296
2039
|
replyToId: asString(params.replyToId || '').trim() || undefined,
|
|
2040
|
+
replyTargetPolicy: params.replyTargetPolicy,
|
|
2297
2041
|
});
|
|
2298
2042
|
}
|
|
2299
2043
|
|
|
@@ -2404,6 +2148,7 @@ class BncrBridgeRuntime {
|
|
|
2404
2148
|
text: string;
|
|
2405
2149
|
kind?: 'tool' | 'block' | 'final';
|
|
2406
2150
|
replyToId?: string;
|
|
2151
|
+
replyTargetPolicy?: OutboundReplyTargetPolicy;
|
|
2407
2152
|
}): OutboxEntry {
|
|
2408
2153
|
return buildTextOutboxEntryFromRuntime({
|
|
2409
2154
|
createMessageId: () => randomUUID(),
|
|
@@ -2416,6 +2161,7 @@ class BncrBridgeRuntime {
|
|
|
2416
2161
|
text: params.text,
|
|
2417
2162
|
kind: params.kind,
|
|
2418
2163
|
replyToId: params.replyToId,
|
|
2164
|
+
replyTargetPolicy: params.replyTargetPolicy,
|
|
2419
2165
|
});
|
|
2420
2166
|
}
|
|
2421
2167
|
|
|
@@ -2473,10 +2219,7 @@ class BncrBridgeRuntime {
|
|
|
2473
2219
|
);
|
|
2474
2220
|
}
|
|
2475
2221
|
|
|
2476
|
-
private handleTextPushFailure(args: {
|
|
2477
|
-
entry: OutboxEntry;
|
|
2478
|
-
error: unknown;
|
|
2479
|
-
}) {
|
|
2222
|
+
private handleTextPushFailure(args: { entry: OutboxEntry; error: unknown }) {
|
|
2480
2223
|
this.recordOutboxPushFailure({
|
|
2481
2224
|
entry: args.entry,
|
|
2482
2225
|
error: args.error,
|
|
@@ -2558,6 +2301,7 @@ class BncrBridgeRuntime {
|
|
|
2558
2301
|
ownerConnId?: string;
|
|
2559
2302
|
ownerClientId?: string;
|
|
2560
2303
|
}) {
|
|
2304
|
+
this.recordPrePushGuardSkip({ accountId: args.accountId, reason: args.reason });
|
|
2561
2305
|
this.logInfo(
|
|
2562
2306
|
'outbox push skip',
|
|
2563
2307
|
`mid=${args.messageId}|q=${this.outbox.size}|reason=${args.reason}${args.kind ? `|kind=${args.kind}` : ''}`,
|
|
@@ -3148,6 +2892,25 @@ class BncrBridgeRuntime {
|
|
|
3148
2892
|
if (args.persist) this.scheduleSave();
|
|
3149
2893
|
}
|
|
3150
2894
|
|
|
2895
|
+
private isPrePushGuardReason(reason: string) {
|
|
2896
|
+
return reason === 'no-gateway-context' || reason === 'no-active-connection';
|
|
2897
|
+
}
|
|
2898
|
+
|
|
2899
|
+
private recordPrePushGuardSkip(args: { accountId: string; reason: string }) {
|
|
2900
|
+
if (!this.isPrePushGuardReason(args.reason)) return;
|
|
2901
|
+
const acc = normalizeAccountId(args.accountId);
|
|
2902
|
+
this.incrementCounter(this.prePushGuardSkipCountByAccount, acc);
|
|
2903
|
+
this.lastPrePushGuardSkipAtByAccount.set(acc, now());
|
|
2904
|
+
this.lastPrePushGuardSkipReasonByAccount.set(acc, args.reason);
|
|
2905
|
+
}
|
|
2906
|
+
|
|
2907
|
+
private isPrePushGuardDeferral(entry: OutboxEntry) {
|
|
2908
|
+
return (
|
|
2909
|
+
entry.lastError === 'gateway context unavailable' ||
|
|
2910
|
+
entry.lastError === 'no active bncr client'
|
|
2911
|
+
);
|
|
2912
|
+
}
|
|
2913
|
+
|
|
3151
2914
|
private recordOutboxPushFailure(args: {
|
|
3152
2915
|
entry: OutboxEntry;
|
|
3153
2916
|
error: unknown;
|
|
@@ -3263,11 +3026,42 @@ class BncrBridgeRuntime {
|
|
|
3263
3026
|
return Array.from(this.outbox.values()).filter((entry) => entry.accountId === acc);
|
|
3264
3027
|
}
|
|
3265
3028
|
|
|
3266
|
-
private
|
|
3267
|
-
accountId
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
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
|
+
|
|
3064
|
+
private maybeLogOutboxDrainStuck(args: { accountId: string; trigger: string; reason: string }) {
|
|
3271
3065
|
const acc = normalizeAccountId(args.accountId);
|
|
3272
3066
|
const startedAt = this.pushDrainRunningSinceByAccount.get(acc) || 0;
|
|
3273
3067
|
if (!startedAt) return;
|
|
@@ -3637,6 +3431,26 @@ class BncrBridgeRuntime {
|
|
|
3637
3431
|
continue;
|
|
3638
3432
|
}
|
|
3639
3433
|
|
|
3434
|
+
if (this.isPrePushGuardDeferral(entry)) {
|
|
3435
|
+
const wait = PRE_PUSH_GUARD_RETRY_DELAY_MS;
|
|
3436
|
+
localNextDelay = updateMinOutboxDelay(localNextDelay, wait);
|
|
3437
|
+
this.logInfo(
|
|
3438
|
+
'outbox',
|
|
3439
|
+
`schedule ${JSON.stringify(
|
|
3440
|
+
buildOutboxScheduleDebugInfo({
|
|
3441
|
+
bridgeId: this.bridgeId,
|
|
3442
|
+
accountId: acc,
|
|
3443
|
+
messageId: entry.messageId,
|
|
3444
|
+
source: OUTBOUND_SCHEDULE_SOURCE.PRE_PUSH_GUARD_WAIT,
|
|
3445
|
+
wait,
|
|
3446
|
+
localNextDelay,
|
|
3447
|
+
}),
|
|
3448
|
+
)}`,
|
|
3449
|
+
{ debugOnly: true },
|
|
3450
|
+
);
|
|
3451
|
+
break;
|
|
3452
|
+
}
|
|
3453
|
+
|
|
3640
3454
|
const decision = computePushFailureDecision(
|
|
3641
3455
|
{
|
|
3642
3456
|
nowMs: t,
|
|
@@ -3877,17 +3691,9 @@ class BncrBridgeRuntime {
|
|
|
3877
3691
|
previousActiveConn,
|
|
3878
3692
|
nextActiveKey: key,
|
|
3879
3693
|
nextActiveConn: nextConn,
|
|
3880
|
-
activeConnections:
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
connId: c.connId,
|
|
3884
|
-
clientId: c.clientId,
|
|
3885
|
-
connectedAt: c.connectedAt,
|
|
3886
|
-
lastSeenAt: c.lastSeenAt,
|
|
3887
|
-
outboundReadyUntil: (c as any).outboundReadyUntil || null,
|
|
3888
|
-
preferredForOutboundUntil: (c as any).preferredForOutboundUntil || null,
|
|
3889
|
-
inboundOnly: (c as any).inboundOnly === true,
|
|
3890
|
-
})),
|
|
3694
|
+
activeConnections: this.buildActiveConnectionDebugList(acc, {
|
|
3695
|
+
includeOutboundState: true,
|
|
3696
|
+
}),
|
|
3891
3697
|
})}`,
|
|
3892
3698
|
{ debugOnly: true },
|
|
3893
3699
|
);
|
|
@@ -3907,17 +3713,9 @@ class BncrBridgeRuntime {
|
|
|
3907
3713
|
previousActiveConn,
|
|
3908
3714
|
nextActiveKey: key,
|
|
3909
3715
|
nextActiveConn: nextConn,
|
|
3910
|
-
activeConnections:
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
connId: c.connId,
|
|
3914
|
-
clientId: c.clientId,
|
|
3915
|
-
connectedAt: c.connectedAt,
|
|
3916
|
-
lastSeenAt: c.lastSeenAt,
|
|
3917
|
-
outboundReadyUntil: (c as any).outboundReadyUntil || null,
|
|
3918
|
-
preferredForOutboundUntil: (c as any).preferredForOutboundUntil || null,
|
|
3919
|
-
inboundOnly: (c as any).inboundOnly === true,
|
|
3920
|
-
})),
|
|
3716
|
+
activeConnections: this.buildActiveConnectionDebugList(acc, {
|
|
3717
|
+
includeOutboundState: true,
|
|
3718
|
+
}),
|
|
3921
3719
|
})}`,
|
|
3922
3720
|
{ debugOnly: true },
|
|
3923
3721
|
);
|
|
@@ -4408,76 +4206,6 @@ class BncrBridgeRuntime {
|
|
|
4408
4206
|
return true;
|
|
4409
4207
|
}
|
|
4410
4208
|
|
|
4411
|
-
private pushFileEventToAccount(
|
|
4412
|
-
accountId: string,
|
|
4413
|
-
event: string,
|
|
4414
|
-
payload: Record<string, unknown>,
|
|
4415
|
-
) {
|
|
4416
|
-
const connIds = this.resolvePushConnIds(accountId);
|
|
4417
|
-
if (!connIds.size || !this.gatewayContext) {
|
|
4418
|
-
throw new Error(`no active bncr connection for account=${accountId}`);
|
|
4419
|
-
}
|
|
4420
|
-
const normalizedEvent =
|
|
4421
|
-
event === 'bncr.file.init'
|
|
4422
|
-
? BNCR_FILE_INIT_EVENT
|
|
4423
|
-
: event === 'bncr.file.chunk'
|
|
4424
|
-
? BNCR_FILE_CHUNK_EVENT
|
|
4425
|
-
: event === 'bncr.file.complete'
|
|
4426
|
-
? BNCR_FILE_COMPLETE_EVENT
|
|
4427
|
-
: event === 'bncr.file.abort'
|
|
4428
|
-
? BNCR_FILE_ABORT_EVENT
|
|
4429
|
-
: event;
|
|
4430
|
-
this.gatewayContext.broadcastToConnIds(normalizedEvent, payload, connIds);
|
|
4431
|
-
}
|
|
4432
|
-
|
|
4433
|
-
private resolveInboundFileType(mimeType: string, fileName: string): string {
|
|
4434
|
-
const mt = asString(mimeType).toLowerCase();
|
|
4435
|
-
const fn = asString(fileName).toLowerCase();
|
|
4436
|
-
if (mt.startsWith('image/') || /\.(png|jpe?g|gif|webp|bmp|svg)$/.test(fn)) return 'image';
|
|
4437
|
-
if (mt.startsWith('video/') || /\.(mp4|mov|mkv|avi|webm)$/.test(fn)) return 'video';
|
|
4438
|
-
if (mt.startsWith('audio/') || /\.(mp3|wav|m4a|aac|ogg|flac)$/.test(fn)) return 'audio';
|
|
4439
|
-
return mt || 'file';
|
|
4440
|
-
}
|
|
4441
|
-
|
|
4442
|
-
private computeRecommendedAckTimeoutReason(args: {
|
|
4443
|
-
lateAckOkCount: number;
|
|
4444
|
-
recentAckTimeoutCount: number;
|
|
4445
|
-
lastLateAckPushLatencyMs: number | null;
|
|
4446
|
-
lastLateAckOkAt?: number | null;
|
|
4447
|
-
adaptiveAckRecoveryOkCount?: number;
|
|
4448
|
-
recommendedAckTimeoutMs?: number;
|
|
4449
|
-
nowMs?: number;
|
|
4450
|
-
}) {
|
|
4451
|
-
return computeBncrRecommendedAckTimeoutReason({
|
|
4452
|
-
...args,
|
|
4453
|
-
nowMs: typeof args.nowMs === 'number' ? args.nowMs : now(),
|
|
4454
|
-
defaultAckTimeoutMs: PUSH_ACK_TIMEOUT_MS,
|
|
4455
|
-
minAckTimeoutMs: RECOMMENDED_ACK_TIMEOUT_MIN_MS,
|
|
4456
|
-
maxAckTimeoutMs: RECOMMENDED_ACK_TIMEOUT_MAX_MS,
|
|
4457
|
-
lateAckObservationTtlMs: ADAPTIVE_ACK_TIMEOUT_OBSERVATION_TTL_MS,
|
|
4458
|
-
recoveryOkThreshold: ADAPTIVE_ACK_TIMEOUT_RECOVERY_OK_THRESHOLD,
|
|
4459
|
-
});
|
|
4460
|
-
}
|
|
4461
|
-
|
|
4462
|
-
private computeRecommendedAckTimeoutMs(args: {
|
|
4463
|
-
lateAckOkCount: number;
|
|
4464
|
-
recentAckTimeoutCount: number;
|
|
4465
|
-
lastLateAckPushLatencyMs: number | null;
|
|
4466
|
-
lastLateAckOkAt?: number | null;
|
|
4467
|
-
adaptiveAckRecoveryOkCount?: number;
|
|
4468
|
-
nowMs?: number;
|
|
4469
|
-
}) {
|
|
4470
|
-
return computeBncrRecommendedAckTimeoutMs({
|
|
4471
|
-
...args,
|
|
4472
|
-
nowMs: typeof args.nowMs === 'number' ? args.nowMs : now(),
|
|
4473
|
-
defaultAckTimeoutMs: PUSH_ACK_TIMEOUT_MS,
|
|
4474
|
-
minAckTimeoutMs: RECOMMENDED_ACK_TIMEOUT_MIN_MS,
|
|
4475
|
-
maxAckTimeoutMs: RECOMMENDED_ACK_TIMEOUT_MAX_MS,
|
|
4476
|
-
lateAckObservationTtlMs: ADAPTIVE_ACK_TIMEOUT_OBSERVATION_TTL_MS,
|
|
4477
|
-
recoveryOkThreshold: ADAPTIVE_ACK_TIMEOUT_RECOVERY_OK_THRESHOLD,
|
|
4478
|
-
});
|
|
4479
|
-
}
|
|
4480
|
-
|
|
4481
4209
|
private maybeLogAdaptiveAckTimeout(args: {
|
|
4482
4210
|
accountId: string;
|
|
4483
4211
|
timeoutMs: number;
|
|
@@ -4525,22 +4253,18 @@ class BncrBridgeRuntime {
|
|
|
4525
4253
|
acc,
|
|
4526
4254
|
);
|
|
4527
4255
|
const nowMs = now();
|
|
4528
|
-
const timeoutMs =
|
|
4529
|
-
lateAckOkCount,
|
|
4530
|
-
recentAckTimeoutCount,
|
|
4531
|
-
lastLateAckPushLatencyMs,
|
|
4532
|
-
lastLateAckOkAt,
|
|
4533
|
-
adaptiveAckRecoveryOkCount,
|
|
4534
|
-
nowMs,
|
|
4535
|
-
});
|
|
4536
|
-
const reason = this.computeRecommendedAckTimeoutReason({
|
|
4256
|
+
const { timeoutMs, reason } = resolveBncrRuntimeAckTimeoutDecision({
|
|
4537
4257
|
lateAckOkCount,
|
|
4538
4258
|
recentAckTimeoutCount,
|
|
4539
4259
|
lastLateAckPushLatencyMs,
|
|
4540
4260
|
lastLateAckOkAt,
|
|
4541
4261
|
adaptiveAckRecoveryOkCount,
|
|
4542
|
-
recommendedAckTimeoutMs: timeoutMs,
|
|
4543
4262
|
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,
|
|
4544
4268
|
});
|
|
4545
4269
|
this.maybeLogAdaptiveAckTimeout({
|
|
4546
4270
|
accountId: acc,
|
|
@@ -4559,58 +4283,30 @@ class BncrBridgeRuntime {
|
|
|
4559
4283
|
const lastLateAckPushLatencyMs = this.lastLateAckPushLatencyMsByAccount.get(acc) || null;
|
|
4560
4284
|
const lastLateAckOkAt = this.lastLateAckOkByAccount.get(acc) || null;
|
|
4561
4285
|
const nowMs = now();
|
|
4562
|
-
const lastLateAckAgeMs =
|
|
4563
|
-
typeof lastLateAckOkAt === 'number' && lastLateAckOkAt > 0
|
|
4564
|
-
? Math.max(0, nowMs - lastLateAckOkAt)
|
|
4565
|
-
: null;
|
|
4566
|
-
const lateAckObservationTtlMs = ADAPTIVE_ACK_TIMEOUT_OBSERVATION_TTL_MS;
|
|
4567
|
-
const lateAckObservationExpired =
|
|
4568
|
-
typeof lastLateAckAgeMs === 'number' && lastLateAckAgeMs > lateAckObservationTtlMs;
|
|
4569
4286
|
const adaptiveAckRecoveryOkCount = this.getCounter(
|
|
4570
4287
|
this.adaptiveAckRecoveryOkCountByAccount,
|
|
4571
4288
|
acc,
|
|
4572
4289
|
);
|
|
4573
|
-
|
|
4574
|
-
adaptiveAckRecoveryOkCount >= ADAPTIVE_ACK_TIMEOUT_RECOVERY_OK_THRESHOLD;
|
|
4575
|
-
const recommendedAckTimeoutMs = this.computeRecommendedAckTimeoutMs({
|
|
4576
|
-
lateAckOkCount,
|
|
4577
|
-
recentAckTimeoutCount,
|
|
4578
|
-
lastLateAckPushLatencyMs,
|
|
4579
|
-
lastLateAckOkAt,
|
|
4580
|
-
adaptiveAckRecoveryOkCount,
|
|
4581
|
-
nowMs,
|
|
4582
|
-
});
|
|
4583
|
-
const currentAckTimeoutMs = this.resolveMessageAckTimeoutMs(acc);
|
|
4584
|
-
return {
|
|
4290
|
+
return buildBncrRuntimeAckObservability({
|
|
4585
4291
|
lastAckOkAt: this.lastAckOkByAccount.get(acc) || null,
|
|
4586
4292
|
lastAckTimeoutAt: this.lastAckTimeoutByAccount.get(acc) || null,
|
|
4587
4293
|
recentAckTimeoutCount,
|
|
4588
4294
|
lateAckOkCount,
|
|
4589
4295
|
lastLateAckOkAt,
|
|
4590
|
-
lastLateAckAgeMs,
|
|
4591
|
-
lateAckObservationTtlMs,
|
|
4592
|
-
lateAckObservationExpired,
|
|
4593
4296
|
adaptiveAckRecoveryOkCount,
|
|
4594
|
-
adaptiveAckRecoveryOkThreshold: ADAPTIVE_ACK_TIMEOUT_RECOVERY_OK_THRESHOLD,
|
|
4595
|
-
adaptiveAckRecovered,
|
|
4596
4297
|
lastAckQueueLatencyMs: this.lastAckQueueLatencyMsByAccount.get(acc) || null,
|
|
4597
4298
|
lastAckPushLatencyMs: this.lastAckPushLatencyMsByAccount.get(acc) || null,
|
|
4598
4299
|
lastLateAckQueueLatencyMs: this.lastLateAckQueueLatencyMsByAccount.get(acc) || null,
|
|
4599
4300
|
lastLateAckPushLatencyMs,
|
|
4600
4301
|
adaptiveAckTimeoutEnabled: ADAPTIVE_ACK_TIMEOUT_DEFAULT_ENABLED,
|
|
4601
4302
|
defaultAckTimeoutMs: PUSH_ACK_TIMEOUT_MS,
|
|
4602
|
-
currentAckTimeoutMs,
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
adaptiveAckRecoveryOkCount,
|
|
4610
|
-
recommendedAckTimeoutMs,
|
|
4611
|
-
nowMs,
|
|
4612
|
-
}),
|
|
4613
|
-
};
|
|
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
|
+
});
|
|
4614
4310
|
}
|
|
4615
4311
|
|
|
4616
4312
|
private buildRuntimeAckStrategy(ackObservability: Record<string, any>) {
|
|
@@ -4731,6 +4427,9 @@ class BncrBridgeRuntime {
|
|
|
4731
4427
|
{ debugOnly: true },
|
|
4732
4428
|
);
|
|
4733
4429
|
this.logOutboundSummary(entry);
|
|
4430
|
+
const accountId = normalizeAccountId(entry.accountId);
|
|
4431
|
+
this.incrementCounter(this.outboundEnqueueCountByAccount, accountId);
|
|
4432
|
+
this.lastOutboundEnqueueAtByAccount.set(accountId, now());
|
|
4734
4433
|
this.outbox.set(entry.messageId, entry);
|
|
4735
4434
|
this.scheduleSave();
|
|
4736
4435
|
this.flushPushQueueBestEffort({ accountId: entry.accountId });
|
|
@@ -4752,12 +4451,14 @@ class BncrBridgeRuntime {
|
|
|
4752
4451
|
entry: dead,
|
|
4753
4452
|
maxEntries: MAX_DEAD_LETTER_ENTRIES,
|
|
4754
4453
|
});
|
|
4454
|
+
this.incrementCounter(this.deadLetterSinceStartByAccount, dead.accountId);
|
|
4455
|
+
this.logDeadLetterSummary(dead.accountId, { source: 'move' });
|
|
4755
4456
|
this.outbox.delete(entry.messageId);
|
|
4756
4457
|
this.resolveMessageAck(entry.messageId, 'timeout');
|
|
4757
4458
|
this.scheduleSave();
|
|
4758
4459
|
}
|
|
4759
4460
|
|
|
4760
|
-
|
|
4461
|
+
collectDue(accountId: string, maxBatch: number): Array<Record<string, unknown>> {
|
|
4761
4462
|
const key = normalizeAccountId(accountId);
|
|
4762
4463
|
const result = collectDueOutboxEntries({
|
|
4763
4464
|
outbox: this.outbox.values(),
|
|
@@ -4779,21 +4480,6 @@ class BncrBridgeRuntime {
|
|
|
4779
4480
|
return result.duePayloads;
|
|
4780
4481
|
}
|
|
4781
4482
|
|
|
4782
|
-
private async payloadMediaToBase64(
|
|
4783
|
-
mediaUrl: string,
|
|
4784
|
-
mediaLocalRoots?: readonly string[],
|
|
4785
|
-
): Promise<{ mediaBase64: string; mimeType?: string; fileName?: string }> {
|
|
4786
|
-
const loaded = await loadOpenClawWebMedia(this.api, mediaUrl, {
|
|
4787
|
-
localRoots: mediaLocalRoots,
|
|
4788
|
-
maxBytes: 20 * 1024 * 1024,
|
|
4789
|
-
});
|
|
4790
|
-
return {
|
|
4791
|
-
mediaBase64: loaded.buffer.toString('base64'),
|
|
4792
|
-
mimeType: loaded.contentType,
|
|
4793
|
-
fileName: loaded.fileName,
|
|
4794
|
-
};
|
|
4795
|
-
}
|
|
4796
|
-
|
|
4797
4483
|
private async loadOutboundTransferMedia(params: {
|
|
4798
4484
|
mediaUrl: string;
|
|
4799
4485
|
mediaLocalRoots?: readonly string[];
|
|
@@ -4826,14 +4512,7 @@ class BncrBridgeRuntime {
|
|
|
4826
4512
|
? this.resolveRecentInboundConnIds(args.accountId)
|
|
4827
4513
|
: new Set<string>();
|
|
4828
4514
|
const activeConnectionKey = this.activeConnectionByAccount.get(args.accountId) || null;
|
|
4829
|
-
const accountConnections =
|
|
4830
|
-
.filter((c) => c.accountId === args.accountId)
|
|
4831
|
-
.map((c) => ({
|
|
4832
|
-
connId: c.connId,
|
|
4833
|
-
clientId: c.clientId,
|
|
4834
|
-
connectedAt: c.connectedAt,
|
|
4835
|
-
lastSeenAt: c.lastSeenAt,
|
|
4836
|
-
}));
|
|
4515
|
+
const accountConnections = this.buildActiveConnectionDebugList(args.accountId);
|
|
4837
4516
|
|
|
4838
4517
|
return {
|
|
4839
4518
|
directConnIds,
|
|
@@ -5348,9 +5027,10 @@ class BncrBridgeRuntime {
|
|
|
5348
5027
|
route: BncrRoute;
|
|
5349
5028
|
payload: ReplyPayloadInput;
|
|
5350
5029
|
mediaLocalRoots?: readonly string[];
|
|
5030
|
+
replyTargetPolicy?: OutboundReplyTargetPolicy;
|
|
5351
5031
|
}) {
|
|
5352
|
-
const { accountId, sessionKey, route, payload, mediaLocalRoots } = params;
|
|
5353
|
-
const normalized = normalizeReplyPayload(payload, { asString });
|
|
5032
|
+
const { accountId, sessionKey, route, payload, mediaLocalRoots, replyTargetPolicy } = params;
|
|
5033
|
+
const normalized = normalizeReplyPayload(payload, { asString }, { replyTargetPolicy });
|
|
5354
5034
|
|
|
5355
5035
|
enqueueNormalizedReplyPayload(
|
|
5356
5036
|
{
|
|
@@ -5486,9 +5166,7 @@ class BncrBridgeRuntime {
|
|
|
5486
5166
|
pushEvent: BNCR_PUSH_EVENT,
|
|
5487
5167
|
online: true,
|
|
5488
5168
|
isPrimary: this.isPrimaryConnection(accountId, clientId),
|
|
5489
|
-
|
|
5490
|
-
pending: Array.from(this.outbox.values()).filter((v) => v.accountId === accountId).length,
|
|
5491
|
-
deadLetter: this.deadLetter.filter((v) => v.accountId === accountId).length,
|
|
5169
|
+
...this.buildAccountQueueCounters(accountId),
|
|
5492
5170
|
diagnostics: this.buildExtendedDiagnostics(accountId),
|
|
5493
5171
|
runtimeFlags: this.buildRuntimeFlags(accountId),
|
|
5494
5172
|
waiters: {
|
|
@@ -5582,9 +5260,7 @@ class BncrBridgeRuntime {
|
|
|
5582
5260
|
accountId,
|
|
5583
5261
|
ok: true,
|
|
5584
5262
|
event: 'activity',
|
|
5585
|
-
|
|
5586
|
-
pending: Array.from(this.outbox.values()).filter((v) => v.accountId === accountId).length,
|
|
5587
|
-
deadLetter: this.deadLetter.filter((v) => v.accountId === accountId).length,
|
|
5263
|
+
...this.buildAccountQueueCounters(accountId),
|
|
5588
5264
|
now: now(),
|
|
5589
5265
|
});
|
|
5590
5266
|
this.flushPushQueueBestEffort({
|
|
@@ -5622,6 +5298,78 @@ class BncrBridgeRuntime {
|
|
|
5622
5298
|
);
|
|
5623
5299
|
};
|
|
5624
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
|
+
|
|
5625
5373
|
handleFileInit = async ({ params, respond, client, context }: GatewayRequestHandlerOptions) => {
|
|
5626
5374
|
const accountId = normalizeAccountId(asString(params?.accountId || ''));
|
|
5627
5375
|
const connId = asString(client?.connId || '').trim() || `no-conn-${Date.now()}`;
|
|
@@ -6063,7 +5811,22 @@ class BncrBridgeRuntime {
|
|
|
6063
5811
|
return;
|
|
6064
5812
|
}
|
|
6065
5813
|
|
|
5814
|
+
if (!['init', 'chunk', 'complete', 'abort'].includes(stage)) {
|
|
5815
|
+
respond(false, { error: 'invalid file ack stage' });
|
|
5816
|
+
return;
|
|
5817
|
+
}
|
|
5818
|
+
|
|
6066
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
|
+
|
|
6067
5830
|
const staleKind =
|
|
6068
5831
|
stage === 'init'
|
|
6069
5832
|
? 'file.init'
|
|
@@ -6194,24 +5957,7 @@ class BncrBridgeRuntime {
|
|
|
6194
5957
|
// versus "scheduled retry" versus "ACK-driven continuation".
|
|
6195
5958
|
await this.syncDebugFlag();
|
|
6196
5959
|
const parsed = parseBncrInboundParams(params);
|
|
6197
|
-
const {
|
|
6198
|
-
accountId,
|
|
6199
|
-
platform,
|
|
6200
|
-
groupId,
|
|
6201
|
-
userId,
|
|
6202
|
-
sessionKeyfromroute,
|
|
6203
|
-
route,
|
|
6204
|
-
text,
|
|
6205
|
-
msgType,
|
|
6206
|
-
mediaBase64,
|
|
6207
|
-
mediaPathFromTransfer,
|
|
6208
|
-
mimeType,
|
|
6209
|
-
fileName,
|
|
6210
|
-
msgId,
|
|
6211
|
-
dedupKey,
|
|
6212
|
-
peer,
|
|
6213
|
-
extracted,
|
|
6214
|
-
} = parsed;
|
|
5960
|
+
const { accountId, platform, route, msgType, msgId, peer, extracted } = parsed;
|
|
6215
5961
|
const connId = asString(client?.connId || '').trim() || `no-conn-${Date.now()}`;
|
|
6216
5962
|
const clientId = asString((params as any)?.clientId || '').trim() || undefined;
|
|
6217
5963
|
const outboundReady = (params as any)?.outboundReady === true;
|
|
@@ -6260,14 +6006,7 @@ class BncrBridgeRuntime {
|
|
|
6260
6006
|
onlineAfterSeen: this.isOnline(accountId),
|
|
6261
6007
|
recentInboundReachable: this.hasRecentInboundReachability(accountId),
|
|
6262
6008
|
activeConnectionKey: this.activeConnectionByAccount.get(accountId) || null,
|
|
6263
|
-
activeConnections:
|
|
6264
|
-
.filter((c) => c.accountId === accountId)
|
|
6265
|
-
.map((c) => ({
|
|
6266
|
-
connId: c.connId,
|
|
6267
|
-
clientId: c.clientId,
|
|
6268
|
-
connectedAt: c.connectedAt,
|
|
6269
|
-
lastSeenAt: c.lastSeenAt,
|
|
6270
|
-
})),
|
|
6009
|
+
activeConnections: this.buildActiveConnectionDebugList(accountId),
|
|
6271
6010
|
}),
|
|
6272
6011
|
)}`,
|
|
6273
6012
|
{ debugOnly: true },
|
|
@@ -6375,7 +6114,7 @@ class BncrBridgeRuntime {
|
|
|
6375
6114
|
}) {
|
|
6376
6115
|
this.logInfo(
|
|
6377
6116
|
'outbound',
|
|
6378
|
-
`send-entry:${args.kind}
|
|
6117
|
+
buildBncrDebugJsonMessage(`send-entry:${args.kind}`, {
|
|
6379
6118
|
accountId: args.accountId,
|
|
6380
6119
|
to: args.to,
|
|
6381
6120
|
text: args.payload.text,
|
|
@@ -6391,7 +6130,7 @@ class BncrBridgeRuntime {
|
|
|
6391
6130
|
threadId: args.ctx?.threadId,
|
|
6392
6131
|
replyToId: args.ctx?.replyToId,
|
|
6393
6132
|
},
|
|
6394
|
-
})
|
|
6133
|
+
}),
|
|
6395
6134
|
{ debugOnly: true },
|
|
6396
6135
|
);
|
|
6397
6136
|
}
|