@xmoxmo/bncr 0.3.5 → 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/README.md +5 -0
  2. package/dist/index.js +28 -5
  3. package/index.ts +55 -721
  4. package/package.json +8 -4
  5. package/scripts/check-pack.mjs +93 -18
  6. package/scripts/check-register-drift.mjs +35 -13
  7. package/scripts/selfcheck.mjs +80 -11
  8. package/src/bootstrap/channel-plugin-runtime.ts +81 -0
  9. package/src/bootstrap/cli.ts +97 -0
  10. package/src/bootstrap/register-runtime-gateway.ts +129 -0
  11. package/src/bootstrap/register-runtime-helpers.ts +140 -0
  12. package/src/bootstrap/register-runtime-singleton.ts +137 -0
  13. package/src/bootstrap/register-runtime.ts +201 -0
  14. package/src/bootstrap/runtime-discovery.ts +187 -0
  15. package/src/bootstrap/runtime-loader.ts +54 -0
  16. package/src/channel.ts +1590 -4967
  17. package/src/core/accounts.ts +23 -4
  18. package/src/core/dead-letter-diagnostics.ts +37 -5
  19. package/src/core/diagnostics.ts +31 -15
  20. package/src/core/downlink-health.ts +3 -11
  21. package/src/core/extended-diagnostics.ts +78 -36
  22. package/src/core/file-transfer-payloads.ts +1 -1
  23. package/src/core/logging.ts +1 -0
  24. package/src/core/outbox-enqueue.ts +13 -2
  25. package/src/core/outbox-entry-builders.ts +2 -0
  26. package/src/core/outbox-summary.ts +75 -3
  27. package/src/core/permissions.ts +15 -2
  28. package/src/core/persisted-outbox-entry.ts +21 -6
  29. package/src/core/policy.ts +45 -4
  30. package/src/core/probe.ts +3 -15
  31. package/src/core/register-trace.ts +3 -3
  32. package/src/core/status.ts +43 -4
  33. package/src/core/targets.ts +216 -205
  34. package/src/core/types.ts +221 -0
  35. package/src/core/value-sanitize.ts +29 -0
  36. package/src/messaging/inbound/commands.ts +147 -172
  37. package/src/messaging/inbound/context-facts.ts +4 -2
  38. package/src/messaging/inbound/contracts.ts +70 -0
  39. package/src/messaging/inbound/dispatch-prep.ts +303 -0
  40. package/src/messaging/inbound/dispatch.ts +49 -462
  41. package/src/messaging/inbound/gate.ts +18 -5
  42. package/src/messaging/inbound/last-route.ts +10 -4
  43. package/src/messaging/inbound/media-url-download.ts +109 -0
  44. package/src/messaging/inbound/native-command-runtime.ts +225 -0
  45. package/src/messaging/inbound/parse.ts +2 -1
  46. package/src/messaging/inbound/remote-media.ts +49 -0
  47. package/src/messaging/inbound/reply-config.ts +16 -4
  48. package/src/messaging/inbound/reply-dispatch.ts +162 -0
  49. package/src/messaging/inbound/runtime-compat.ts +31 -10
  50. package/src/messaging/inbound/session-label.ts +15 -7
  51. package/src/messaging/inbound/turn-context.ts +131 -0
  52. package/src/messaging/outbound/actions.ts +24 -10
  53. package/src/messaging/outbound/diagnostics-debug-builders.ts +365 -0
  54. package/src/messaging/outbound/diagnostics.ts +31 -355
  55. package/src/messaging/outbound/durable-message-adapter.ts +20 -16
  56. package/src/messaging/outbound/durable-queue-adapter.ts +20 -7
  57. package/src/messaging/outbound/media.ts +24 -13
  58. package/src/messaging/outbound/reply-enqueue-media.ts +181 -0
  59. package/src/messaging/outbound/reply-enqueue.ts +57 -134
  60. package/src/messaging/outbound/send-params.ts +3 -0
  61. package/src/messaging/outbound/send.ts +19 -10
  62. package/src/messaging/outbound/session-route.ts +18 -3
  63. package/src/openclaw/channel-runtime-contracts.ts +76 -0
  64. package/src/openclaw/config-runtime.ts +13 -7
  65. package/src/openclaw/inbound-session-runtime.ts +7 -3
  66. package/src/openclaw/ingress-runtime.ts +17 -27
  67. package/src/openclaw/reply-runtime.ts +54 -59
  68. package/src/openclaw/routing-runtime.ts +35 -18
  69. package/src/openclaw/runtime-surface.ts +156 -12
  70. package/src/openclaw/sdk-helpers.ts +8 -1
  71. package/src/openclaw/session-route-runtime.ts +12 -12
  72. package/src/plugin/ack-outbox-runtime-group.ts +264 -0
  73. package/src/plugin/bridge-ack-facade.ts +137 -0
  74. package/src/plugin/bridge-connection-facade.ts +111 -0
  75. package/src/plugin/bridge-diagnostics-facade.ts +23 -0
  76. package/src/plugin/bridge-drain-facade.ts +98 -0
  77. package/src/plugin/bridge-extended-diagnostics-facade.ts +149 -0
  78. package/src/plugin/bridge-file-transfer-push-facade.ts +140 -0
  79. package/src/plugin/bridge-lifecycle.ts +156 -0
  80. package/src/plugin/bridge-media-facade.ts +241 -0
  81. package/src/plugin/bridge-outbox-facade.ts +182 -0
  82. package/src/plugin/bridge-runtime-helpers.ts +266 -0
  83. package/src/plugin/bridge-runtime-snapshots.ts +104 -0
  84. package/src/plugin/bridge-runtime-surface-facade.ts +8 -0
  85. package/src/plugin/bridge-status-facade.ts +76 -0
  86. package/src/plugin/bridge-status-worker-facade.ts +72 -0
  87. package/src/plugin/bridge-support-runtime.ts +137 -0
  88. package/src/plugin/bridge-surface-handlers-group.ts +242 -0
  89. package/src/plugin/bridge-surface-helpers.ts +28 -0
  90. package/src/plugin/capabilities.ts +1 -3
  91. package/src/plugin/channel-components.ts +289 -0
  92. package/src/plugin/channel-inbound-helpers.ts +149 -0
  93. package/src/plugin/channel-plugin-bridge-group.ts +129 -0
  94. package/src/plugin/channel-plugin-surface-group.ts +202 -0
  95. package/src/plugin/channel-runtime-builders-delivery.ts +513 -0
  96. package/src/plugin/channel-runtime-builders-status.ts +331 -0
  97. package/src/plugin/channel-runtime-builders.ts +25 -0
  98. package/src/plugin/channel-runtime-constants.ts +40 -0
  99. package/src/plugin/channel-runtime-types.ts +146 -0
  100. package/src/plugin/channel-send-runtime-group.ts +37 -0
  101. package/src/plugin/channel-send.ts +226 -0
  102. package/src/plugin/channel-utils.ts +102 -0
  103. package/src/plugin/config.ts +24 -3
  104. package/src/plugin/connection-handlers-helpers.ts +254 -0
  105. package/src/plugin/connection-handlers.ts +440 -0
  106. package/src/plugin/connection-state-helpers.ts +159 -0
  107. package/src/plugin/connection-state-runtime-group.ts +51 -0
  108. package/src/plugin/connection-state.ts +527 -0
  109. package/src/plugin/diagnostics-handlers.ts +211 -0
  110. package/src/plugin/error-message.ts +15 -0
  111. package/src/plugin/file-ack-runtime.ts +284 -0
  112. package/src/plugin/file-inbound-abort.ts +112 -0
  113. package/src/plugin/file-inbound-chunk.ts +146 -0
  114. package/src/plugin/file-inbound-complete.ts +153 -0
  115. package/src/plugin/file-inbound-handlers.ts +19 -0
  116. package/src/plugin/file-inbound-init.ts +122 -0
  117. package/src/plugin/file-inbound-runtime.ts +51 -0
  118. package/src/plugin/file-inbound-state.ts +62 -0
  119. package/src/plugin/file-transfer-logs.ts +227 -0
  120. package/src/plugin/file-transfer-orchestrator-chunk.ts +135 -0
  121. package/src/plugin/file-transfer-orchestrator.ts +304 -0
  122. package/src/plugin/file-transfer-runtime-group.ts +102 -0
  123. package/src/plugin/file-transfer-send.ts +89 -0
  124. package/src/plugin/file-transfer-setup.ts +206 -0
  125. package/src/plugin/gateway-event-context.ts +41 -0
  126. package/src/plugin/gateway-runtime.ts +14 -4
  127. package/src/plugin/inbound-acceptance.ts +107 -0
  128. package/src/plugin/inbound-handlers.ts +248 -0
  129. package/src/plugin/inbound-surface-handlers-group.ts +152 -0
  130. package/src/plugin/media-dedupe-runtime.ts +90 -0
  131. package/src/plugin/media-orchestrators-runtime-group.ts +316 -0
  132. package/src/plugin/message-ack-runtime.ts +284 -0
  133. package/src/plugin/message-send.ts +16 -6
  134. package/src/plugin/messaging.ts +98 -36
  135. package/src/plugin/outbound.ts +50 -8
  136. package/src/plugin/outbox-ack-logs.ts +136 -0
  137. package/src/plugin/outbox-ack-outcome.ts +128 -0
  138. package/src/plugin/outbox-drain-ack.ts +145 -0
  139. package/src/plugin/outbox-drain-failure.ts +84 -0
  140. package/src/plugin/outbox-drain-loop.ts +554 -0
  141. package/src/plugin/outbox-drain-post-push.ts +159 -0
  142. package/src/plugin/outbox-drain-runtime.ts +141 -0
  143. package/src/plugin/outbox-drain-schedule.ts +116 -0
  144. package/src/plugin/outbox-file-push-flow.ts +69 -0
  145. package/src/plugin/outbox-push-route-runtime-group.ts +81 -0
  146. package/src/plugin/outbox-push.ts +267 -0
  147. package/src/plugin/outbox-route.ts +181 -0
  148. package/src/plugin/outbox-text-push-flow.ts +90 -0
  149. package/src/plugin/runtime-diagnostics-assembler.ts +183 -0
  150. package/src/plugin/runtime-diagnostics-helpers.ts +302 -0
  151. package/src/plugin/runtime-diagnostics-payload-builders.ts +171 -0
  152. package/src/plugin/runtime-diagnostics-snapshot.ts +31 -0
  153. package/src/plugin/setup.ts +33 -6
  154. package/src/plugin/state-store.ts +249 -0
  155. package/src/plugin/state-transient-runtime-group.ts +105 -0
  156. package/src/plugin/status-runtime.ts +251 -0
  157. package/src/plugin/status.ts +33 -7
  158. package/src/plugin/target-runtime.ts +141 -0
  159. package/src/plugin/target-status-runtime-group.ts +130 -0
  160. package/src/plugin/transient-state-runtime.ts +82 -0
  161. package/src/runtime/outbound-ack-timeout.ts +5 -3
  162. package/src/runtime/outbound-flags.ts +24 -8
  163. package/src/runtime/status-snapshots.ts +36 -7
  164. package/src/runtime/status-worker.ts +34 -4
@@ -0,0 +1,264 @@
1
+ import type { GatewayRequestHandlerOptions } from 'openclaw/plugin-sdk/core';
2
+ import type { OutboxEntry } from '../core/types.ts';
3
+ import { createBncrMessageAckRuntime } from './message-ack-runtime.ts';
4
+ import { createBncrOutboxAckLogs } from './outbox-ack-logs.ts';
5
+ import {
6
+ type BncrOutboxAckOkTelemetryPatch,
7
+ createBncrOutboxAckOutcome,
8
+ } from './outbox-ack-outcome.ts';
9
+ import { createBncrOutboxDrainAck } from './outbox-drain-ack.ts';
10
+ import { createBncrOutboxDrainRuntime } from './outbox-drain-runtime.ts';
11
+ import { createBncrOutboxDrainSchedule } from './outbox-drain-schedule.ts';
12
+
13
+ export function createBncrAckOutboxRuntimeGroup(runtime: {
14
+ bridgeId: string;
15
+ pushEvent: string;
16
+ now: () => number;
17
+ asString: (value: unknown, fallback?: string) => string;
18
+ backoffMs: (retryCount: number) => number;
19
+ isPlainObject: (value: unknown) => value is Record<string, unknown>;
20
+ clampFiniteNumber: (value: unknown, fallback: number, min?: number, max?: number) => number;
21
+ normalizeAccountId: (accountId: string) => string;
22
+ formatDisplayScope: (route: OutboxEntry['route']) => string;
23
+ isFileTransferEntry: (entry: OutboxEntry) => boolean;
24
+ recommendedAckTimeoutMaxMs: number;
25
+ adaptiveAckTimeoutEnabled: boolean;
26
+ defaultAckTimeoutMs: number;
27
+ stopped: () => boolean;
28
+ outbox: Map<string, OutboxEntry>;
29
+ deadLetter: () => OutboxEntry[];
30
+ connectionsValues: () => IterableIterator<{
31
+ accountId: string;
32
+ connId: string;
33
+ clientId?: string;
34
+ connectedAt: number;
35
+ lastSeenAt: number;
36
+ inboundOnly?: boolean;
37
+ outboundReady?: boolean;
38
+ preferredForOutbound?: boolean;
39
+ outboundReadyUntil?: number;
40
+ preferredForOutboundUntil?: number;
41
+ lastAckOkAt?: number;
42
+ lastPushTimeoutAt?: number;
43
+ pushFailureScore?: number;
44
+ }>;
45
+ gatewayContextAvailable: () => boolean;
46
+ messageAckWaiters: Map<
47
+ string,
48
+ {
49
+ promise: Promise<'acked' | 'timeout'>;
50
+ resolve: (result: 'acked' | 'timeout') => void;
51
+ timer: NodeJS.Timeout;
52
+ }
53
+ >;
54
+ fileAckWaiterCount: () => number;
55
+ activeConnectionCount: (accountId: string) => number;
56
+ getAccountPendingOutboxEntries: (accountId: string) => OutboxEntry[];
57
+ pushDrainRunningAccounts: Set<string>;
58
+ pushDrainRunningSinceByAccount: Map<string, number>;
59
+ pushDrainStuckWarnedAtByAccount: Map<string, number>;
60
+ isOnline: (accountId: string) => boolean;
61
+ hasRecentInboundReachability: (accountId: string) => boolean;
62
+ isOutboundAckRequired: (accountId?: string) => boolean;
63
+ resolveMessageAckTimeoutMs: (accountId: string) => number;
64
+ waitForMessageAck: (messageId: string, waitMs: number) => Promise<'acked' | 'timeout'>;
65
+ resolvePushConnIds: (accountId: string) => Iterable<string>;
66
+ sleepMs: (ms: number) => Promise<void>;
67
+ schedulePushDrain: (delayMs: number) => void;
68
+ tryPushEntry: (entry: OutboxEntry) => Promise<boolean>;
69
+ handleFileTransferPushFailure: (args: { entry: OutboxEntry; error: unknown }) => void;
70
+ handleTextPushFailure: (args: { entry: OutboxEntry; error: unknown }) => void;
71
+ isPrePushGuardDeferral: (entry: OutboxEntry) => boolean;
72
+ scheduleSave: () => void;
73
+ logInfo: (scope: string | undefined, message: string, options?: { debugOnly?: boolean }) => void;
74
+ logWarn: (scope: string | undefined, message: string, options?: { debugOnly?: boolean }) => void;
75
+ logError: (scope: string, message: string) => void;
76
+ observeLease: (
77
+ kind:
78
+ | 'connect'
79
+ | 'inbound'
80
+ | 'activity'
81
+ | 'ack'
82
+ | 'file.init'
83
+ | 'file.chunk'
84
+ | 'file.complete'
85
+ | 'file.abort',
86
+ payload: Record<string, unknown>,
87
+ ) => { stale: boolean };
88
+ rememberGatewayContext: (context: GatewayRequestHandlerOptions['context']) => void;
89
+ markSeen: (accountId: string, connId: string, clientId?: string) => void;
90
+ markOutboundCapability: (args: {
91
+ accountId: string;
92
+ connId: string;
93
+ clientId?: string;
94
+ outboundReady: boolean;
95
+ preferredForOutbound: boolean;
96
+ }) => void;
97
+ recordAckOkTelemetry: (args: {
98
+ accountId: string;
99
+ entry: OutboxEntry;
100
+ telemetryPatch: BncrOutboxAckOkTelemetryPatch;
101
+ }) => void;
102
+ deleteOutboxEntry: (messageId: string) => void;
103
+ setOutboxEntry: (messageId: string, entry: OutboxEntry) => void;
104
+ resolveMessageAck: (messageId: string, result: 'acked' | 'timeout') => boolean;
105
+ moveToDeadLetter: (entry: OutboxEntry, reason: string) => void;
106
+ recordAckTimeoutTelemetry: (accountId: string) => void;
107
+ degradeOutboundCapability: (args: {
108
+ accountId: string;
109
+ connId?: string;
110
+ clientId?: string;
111
+ reason: string;
112
+ }) => void;
113
+ flushPushQueueBestEffort: (args?: {
114
+ accountId?: string;
115
+ trigger?: string;
116
+ reason?: string;
117
+ }) => void;
118
+ flushTriggerTimer: string;
119
+ flushReasonScheduledDrain: string;
120
+ outboundFlushTriggerAckOk: string;
121
+ outboundFlushReasonMessageAcked: string;
122
+ pushDrainExceptionRetryLimit: number;
123
+ pushDrainExceptionRetryDelayMs: number;
124
+ pushDrainStuckWarnMs: number;
125
+ pushDrainIntervalMs: number;
126
+ pushDrainAccountTimeBudgetMs: number;
127
+ pushDrainAccountBudget: number;
128
+ pushAckTimeoutMs: number;
129
+ maxRetry: number;
130
+ prePushGuardRetryDelayMs: number;
131
+ }) {
132
+ const outboxAckLogs = createBncrOutboxAckLogs({
133
+ bridgeId: runtime.bridgeId,
134
+ pushEvent: runtime.pushEvent,
135
+ now: runtime.now,
136
+ outboxSize: () => runtime.outbox.size,
137
+ adaptiveAckTimeoutEnabled: runtime.adaptiveAckTimeoutEnabled,
138
+ formatDisplayScope: runtime.formatDisplayScope,
139
+ isFileTransferEntry: runtime.isFileTransferEntry,
140
+ logInfo: runtime.logInfo,
141
+ });
142
+
143
+ const outboxAckOutcome = createBncrOutboxAckOutcome({
144
+ now: runtime.now,
145
+ defaultAckTimeoutMs: runtime.defaultAckTimeoutMs,
146
+ markOutboundCapability: runtime.markOutboundCapability,
147
+ recordAckOkTelemetry: runtime.recordAckOkTelemetry,
148
+ deleteOutboxEntry: runtime.deleteOutboxEntry,
149
+ setOutboxEntry: runtime.setOutboxEntry,
150
+ scheduleSave: runtime.scheduleSave,
151
+ resolveMessageAck: (messageId, result) => {
152
+ runtime.resolveMessageAck(messageId, result);
153
+ },
154
+ moveToDeadLetter: runtime.moveToDeadLetter,
155
+ logOutboxAckSummary: outboxAckLogs.logOutboxAckSummary,
156
+ });
157
+
158
+ const messageAckRuntime = createBncrMessageAckRuntime({
159
+ asString: runtime.asString,
160
+ now: runtime.now,
161
+ clampFiniteNumber: (value, fallback, min, max) =>
162
+ runtime.clampFiniteNumber(value, fallback, min ?? fallback, max ?? fallback),
163
+ normalizeAccountId: runtime.normalizeAccountId,
164
+ recommendedAckTimeoutMaxMs: runtime.recommendedAckTimeoutMaxMs,
165
+ messageAckWaiters: runtime.messageAckWaiters,
166
+ stopped: runtime.stopped,
167
+ outbox: runtime.outbox,
168
+ observeLease: runtime.observeLease,
169
+ rememberGatewayContext: runtime.rememberGatewayContext,
170
+ markSeen: runtime.markSeen,
171
+ logInfo: runtime.logInfo,
172
+ logWarn: runtime.logWarn,
173
+ handleAckOk: outboxAckOutcome.handleAckOk,
174
+ handleAckFatal: outboxAckOutcome.handleAckFatal,
175
+ handleAckRetry: outboxAckOutcome.handleAckRetry,
176
+ flushPushQueueBestEffort: runtime.flushPushQueueBestEffort,
177
+ outboundFlushTriggerAckOk: runtime.outboundFlushTriggerAckOk,
178
+ outboundFlushReasonMessageAcked: runtime.outboundFlushReasonMessageAcked,
179
+ });
180
+
181
+ const outboxDrainAck = createBncrOutboxDrainAck({
182
+ bridgeId: runtime.bridgeId,
183
+ pushEvent: runtime.pushEvent,
184
+ now: runtime.now,
185
+ defaultAckTimeoutMs: runtime.defaultAckTimeoutMs,
186
+ adaptiveAckTimeoutEnabled: runtime.adaptiveAckTimeoutEnabled,
187
+ outboxSize: () => runtime.outbox.size,
188
+ formatDisplayScope: runtime.formatDisplayScope,
189
+ isFileTransferEntry: runtime.isFileTransferEntry,
190
+ setOutboxEntry: runtime.setOutboxEntry,
191
+ scheduleSave: runtime.scheduleSave,
192
+ moveToDeadLetter: runtime.moveToDeadLetter,
193
+ recordAckTimeoutTelemetry: runtime.recordAckTimeoutTelemetry,
194
+ logInfo: runtime.logInfo,
195
+ logOutboxAckReroute: outboxAckLogs.logOutboxAckReroute,
196
+ });
197
+
198
+ const outboxDrainSchedule = createBncrOutboxDrainSchedule({
199
+ bridgeId: runtime.bridgeId,
200
+ logInfo: runtime.logInfo,
201
+ });
202
+
203
+ const outboxDrainRuntime = createBncrOutboxDrainRuntime({
204
+ bridgeId: runtime.bridgeId,
205
+ now: runtime.now,
206
+ asString: runtime.asString,
207
+ backoffMs: runtime.backoffMs,
208
+ isPlainObject: runtime.isPlainObject,
209
+ normalizeAccountId: runtime.normalizeAccountId,
210
+ stopped: runtime.stopped,
211
+ outbox: runtime.outbox,
212
+ deadLetter: runtime.deadLetter,
213
+ connectionsValues: runtime.connectionsValues,
214
+ gatewayContextAvailable: runtime.gatewayContextAvailable,
215
+ messageAckWaiterCount: () => runtime.messageAckWaiters.size,
216
+ fileAckWaiterCount: runtime.fileAckWaiterCount,
217
+ activeConnectionCount: runtime.activeConnectionCount,
218
+ getAccountPendingOutboxEntries: runtime.getAccountPendingOutboxEntries,
219
+ pushDrainRunningAccounts: runtime.pushDrainRunningAccounts,
220
+ pushDrainRunningSinceByAccount: runtime.pushDrainRunningSinceByAccount,
221
+ pushDrainStuckWarnedAtByAccount: runtime.pushDrainStuckWarnedAtByAccount,
222
+ isOnline: runtime.isOnline,
223
+ hasRecentInboundReachability: runtime.hasRecentInboundReachability,
224
+ isOutboundAckRequired: runtime.isOutboundAckRequired,
225
+ resolveMessageAckTimeoutMs: runtime.resolveMessageAckTimeoutMs,
226
+ waitForMessageAck: runtime.waitForMessageAck,
227
+ logOutboxAckWait: outboxAckLogs.logOutboxAckWait,
228
+ degradeOutboundCapability: runtime.degradeOutboundCapability,
229
+ resolvePushConnIds: runtime.resolvePushConnIds,
230
+ sleepMs: runtime.sleepMs,
231
+ schedulePushDrain: runtime.schedulePushDrain,
232
+ outboxDrainSchedule,
233
+ outboxDrainAck,
234
+ tryPushEntry: runtime.tryPushEntry,
235
+ handleFileTransferPushFailure: runtime.handleFileTransferPushFailure,
236
+ handleTextPushFailure: runtime.handleTextPushFailure,
237
+ isPrePushGuardDeferral: runtime.isPrePushGuardDeferral,
238
+ moveToDeadLetter: runtime.moveToDeadLetter,
239
+ scheduleSave: runtime.scheduleSave,
240
+ logInfo: runtime.logInfo,
241
+ logWarn: runtime.logWarn,
242
+ logError: runtime.logError,
243
+ flushTriggerTimer: runtime.flushTriggerTimer,
244
+ flushReasonScheduledDrain: runtime.flushReasonScheduledDrain,
245
+ pushDrainExceptionRetryLimit: runtime.pushDrainExceptionRetryLimit,
246
+ pushDrainExceptionRetryDelayMs: runtime.pushDrainExceptionRetryDelayMs,
247
+ pushDrainStuckWarnMs: runtime.pushDrainStuckWarnMs,
248
+ pushDrainIntervalMs: runtime.pushDrainIntervalMs,
249
+ pushDrainAccountTimeBudgetMs: runtime.pushDrainAccountTimeBudgetMs,
250
+ pushDrainAccountBudget: runtime.pushDrainAccountBudget,
251
+ pushAckTimeoutMs: runtime.pushAckTimeoutMs,
252
+ maxRetry: runtime.maxRetry,
253
+ prePushGuardRetryDelayMs: runtime.prePushGuardRetryDelayMs,
254
+ });
255
+
256
+ return {
257
+ outboxAckLogs,
258
+ outboxAckOutcome,
259
+ messageAckRuntime,
260
+ outboxDrainAck,
261
+ outboxDrainSchedule,
262
+ outboxDrainRuntime,
263
+ };
264
+ }
@@ -0,0 +1,137 @@
1
+ import type { BncrAckObservability, BncrAckStrategy } from '../core/types.ts';
2
+ import {
3
+ buildBncrRuntimeAckStrategy,
4
+ resolveBncrRuntimeAckTimeoutDecision,
5
+ } from '../runtime/outbound-ack-timeout.ts';
6
+ import type { createBncrFileAckRuntime } from './file-ack-runtime.ts';
7
+
8
+ type FileAckRuntime = ReturnType<typeof createBncrFileAckRuntime>;
9
+ type FileAckWaitParams = Parameters<FileAckRuntime['waitForFileAck']>[0];
10
+ type FileAckWaitResult = Awaited<ReturnType<FileAckRuntime['waitForFileAck']>>;
11
+
12
+ export function createBncrBridgeAckFacade(runtime: {
13
+ normalizeAccountId: (accountId: string) => string;
14
+ now: () => number;
15
+ pushAckTimeoutMs: number;
16
+ adaptiveAckTimeoutDefaultEnabled: boolean;
17
+ adaptiveAckTimeoutLogThrottleMs: number;
18
+ adaptiveAckTimeoutObservationTtlMs: number;
19
+ adaptiveAckTimeoutRecoveryOkThreshold: number;
20
+ recommendedAckTimeoutMinMs: number;
21
+ recommendedAckTimeoutMaxMs: number;
22
+ getCounter: (map: Map<string, number>, accountId: string) => number;
23
+ ackTimeoutCountByAccount: Map<string, number>;
24
+ lateAckOkCountByAccount: Map<string, number>;
25
+ lastLateAckPushLatencyMsByAccount: Map<string, number>;
26
+ lastLateAckOkByAccount: Map<string, number>;
27
+ adaptiveAckRecoveryOkCountByAccount: Map<string, number>;
28
+ adaptiveAckTimeoutLogStateByAccount: Map<
29
+ string,
30
+ { at: number; timeoutMs: number; reason: string }
31
+ >;
32
+ logInfo: (scope: string | undefined, message: string, options?: { debugOnly?: boolean }) => void;
33
+ buildRuntimeAckObservability: (accountId: string) => BncrAckObservability;
34
+ buildRuntimeAckStrategy?: (ackObservability: BncrAckObservability) => unknown;
35
+ waitForMessageAck: (messageId: string, waitMs: number) => Promise<'acked' | 'timeout'>;
36
+ resolveMessageAck: (messageId: string, result?: 'acked' | 'timeout') => boolean;
37
+ fileAckKey: (transferId: string, stage: string, chunkIndex?: number) => string;
38
+ waitForFileAck: (params: FileAckWaitParams) => Promise<FileAckWaitResult>;
39
+ resolveFileAck: (params: {
40
+ transferId: string;
41
+ stage: string;
42
+ chunkIndex?: number;
43
+ payload: Record<string, unknown>;
44
+ ok: boolean;
45
+ }) => boolean;
46
+ }) {
47
+ const maybeLogAdaptiveAckTimeout = (args: {
48
+ accountId: string;
49
+ timeoutMs: number;
50
+ reason: string;
51
+ lastLateAckPushLatencyMs: number | null;
52
+ nowMs?: number;
53
+ }) => {
54
+ if (args.timeoutMs <= runtime.pushAckTimeoutMs) return;
55
+ const t = typeof args.nowMs === 'number' ? args.nowMs : runtime.now();
56
+ const previous = runtime.adaptiveAckTimeoutLogStateByAccount.get(args.accountId);
57
+ if (
58
+ previous &&
59
+ previous.timeoutMs === args.timeoutMs &&
60
+ previous.reason === args.reason &&
61
+ t - previous.at < runtime.adaptiveAckTimeoutLogThrottleMs
62
+ ) {
63
+ return;
64
+ }
65
+ runtime.adaptiveAckTimeoutLogStateByAccount.set(args.accountId, {
66
+ at: t,
67
+ timeoutMs: args.timeoutMs,
68
+ reason: args.reason,
69
+ });
70
+ const parts = [
71
+ args.accountId,
72
+ `current=${args.timeoutMs}`,
73
+ `default=${runtime.pushAckTimeoutMs}`,
74
+ `reason=${args.reason}`,
75
+ ];
76
+ if (typeof args.lastLateAckPushLatencyMs === 'number')
77
+ parts.push(`latePushMs=${args.lastLateAckPushLatencyMs}`);
78
+ runtime.logInfo('outbox ack timeout-adaptive', parts.join('|'));
79
+ };
80
+
81
+ const resolveMessageAckTimeoutMs = (accountId?: string) => {
82
+ if (!runtime.adaptiveAckTimeoutDefaultEnabled) return runtime.pushAckTimeoutMs;
83
+ const acc = runtime.normalizeAccountId(accountId || 'default');
84
+ const lateAckOkCount = runtime.getCounter(runtime.lateAckOkCountByAccount, acc);
85
+ const recentAckTimeoutCount = runtime.getCounter(runtime.ackTimeoutCountByAccount, acc);
86
+ const lastLateAckPushLatencyMs = runtime.lastLateAckPushLatencyMsByAccount.get(acc) || null;
87
+ const lastLateAckOkAt = runtime.lastLateAckOkByAccount.get(acc) || null;
88
+ const adaptiveAckRecoveryOkCount = runtime.getCounter(
89
+ runtime.adaptiveAckRecoveryOkCountByAccount,
90
+ acc,
91
+ );
92
+ const nowMs = runtime.now();
93
+ const { timeoutMs, reason } = resolveBncrRuntimeAckTimeoutDecision({
94
+ lateAckOkCount,
95
+ recentAckTimeoutCount,
96
+ lastLateAckPushLatencyMs,
97
+ lastLateAckOkAt,
98
+ adaptiveAckRecoveryOkCount,
99
+ nowMs,
100
+ defaultAckTimeoutMs: runtime.pushAckTimeoutMs,
101
+ minAckTimeoutMs: runtime.recommendedAckTimeoutMinMs,
102
+ maxAckTimeoutMs: runtime.recommendedAckTimeoutMaxMs,
103
+ lateAckObservationTtlMs: runtime.adaptiveAckTimeoutObservationTtlMs,
104
+ recoveryOkThreshold: runtime.adaptiveAckTimeoutRecoveryOkThreshold,
105
+ });
106
+ maybeLogAdaptiveAckTimeout({
107
+ accountId: acc,
108
+ timeoutMs,
109
+ reason,
110
+ lastLateAckPushLatencyMs,
111
+ nowMs,
112
+ });
113
+ return timeoutMs;
114
+ };
115
+
116
+ return {
117
+ maybeLogAdaptiveAckTimeout,
118
+ resolveMessageAckTimeoutMs,
119
+ buildRuntimeAckObservability: (accountId: string) =>
120
+ runtime.buildRuntimeAckObservability(accountId),
121
+ buildRuntimeAckStrategy: (ackObservability: BncrAckObservability): BncrAckStrategy => {
122
+ if (runtime.buildRuntimeAckStrategy) {
123
+ return runtime.buildRuntimeAckStrategy(ackObservability) as BncrAckStrategy;
124
+ }
125
+ return buildBncrRuntimeAckStrategy({
126
+ ackObservability,
127
+ defaultAckTimeoutMs: runtime.pushAckTimeoutMs,
128
+ maxAckTimeoutMs: runtime.recommendedAckTimeoutMaxMs,
129
+ });
130
+ },
131
+ fileAckKey: runtime.fileAckKey,
132
+ waitForFileAck: runtime.waitForFileAck,
133
+ resolveFileAck: runtime.resolveFileAck,
134
+ resolveMessageAck: runtime.resolveMessageAck,
135
+ waitForMessageAck: runtime.waitForMessageAck,
136
+ };
137
+ }
@@ -0,0 +1,111 @@
1
+ import type { GatewayRequestHandlerOptions } from 'openclaw/plugin-sdk/core';
2
+ import type {
3
+ BncrConnection,
4
+ FileRecvTransferState,
5
+ FileSendTransferState,
6
+ OutboxEntry,
7
+ } from '../core/types.ts';
8
+ import { getErrorMessage } from './error-message.ts';
9
+
10
+ export function createBncrBridgeConnectionFacade(runtime: {
11
+ now: () => number;
12
+ asString: (value: unknown, fallback?: string) => string;
13
+ normalizeAccountId: (accountId: string) => string;
14
+ connectionState: {
15
+ hasRecentInboundReachability: (accountId: string) => boolean;
16
+ resolveRecentInboundConnIds: (accountId: string) => Set<string>;
17
+ isRecentlyReachableConn: (accountId: string, connId?: string, clientId?: string) => boolean;
18
+ isRevalidatedAttemptedConn: (entry: OutboxEntry, connId: string) => boolean;
19
+ tryAdoptTransferOwner: (args: {
20
+ accountId: string;
21
+ transfer: FileSendTransferState | FileRecvTransferState | undefined;
22
+ connId: string;
23
+ clientId?: string;
24
+ }) => boolean;
25
+ refreshLiveConnectionState: (args: {
26
+ accountId: string;
27
+ connId: string;
28
+ clientId?: string;
29
+ outboundReady: boolean;
30
+ preferredForOutbound: boolean;
31
+ inboundOnly: boolean;
32
+ context: GatewayRequestHandlerOptions['context'];
33
+ }) => void;
34
+ refreshAcceptedFileTransferLiveState: (args: {
35
+ accountId: string;
36
+ connId: string;
37
+ clientId?: string;
38
+ context: GatewayRequestHandlerOptions['context'];
39
+ }) => void;
40
+ };
41
+ outboxRoute: {
42
+ resolveOutboxPushOwner: (accountId: string) => BncrConnection | null;
43
+ resolvePushConnIds: (accountId: string) => Set<string>;
44
+ };
45
+ rememberGatewayContext: (context: GatewayRequestHandlerOptions['context']) => void;
46
+ markActivity: (accountId: string, at?: number) => void;
47
+ }) {
48
+ const rememberGatewayContext = (context: GatewayRequestHandlerOptions['context']) => {
49
+ if (!context) return;
50
+ runtime.rememberGatewayContext(context);
51
+ };
52
+
53
+ const isRetryableFileTransferError = (error: unknown) => {
54
+ const msg = runtime.asString(getErrorMessage(error, '')).trim().toLowerCase();
55
+ if (!msg) return true;
56
+
57
+ const retryableMarkers = [
58
+ 'gateway context unavailable',
59
+ 'no active bncr client for file chunk transfer',
60
+ 'chunk ack timeout',
61
+ 'complete ack timeout',
62
+ 'transfer state missing',
63
+ 'transfer aborted',
64
+ 'temporarily unavailable',
65
+ 'timeout',
66
+ 'econn',
67
+ 'socket',
68
+ 'network',
69
+ ];
70
+
71
+ return retryableMarkers.some((marker) => msg.includes(marker));
72
+ };
73
+
74
+ return {
75
+ rememberGatewayContext,
76
+ resolveOutboxPushOwner: (accountId: string) =>
77
+ runtime.outboxRoute.resolveOutboxPushOwner(accountId),
78
+ resolvePushConnIds: (accountId: string) => runtime.outboxRoute.resolvePushConnIds(accountId),
79
+ hasRecentInboundReachability: (accountId: string) =>
80
+ runtime.connectionState.hasRecentInboundReachability(accountId),
81
+ resolveRecentInboundConnIds: (accountId: string) =>
82
+ runtime.connectionState.resolveRecentInboundConnIds(accountId),
83
+ isRecentlyReachableConn: (accountId: string, connId?: string, clientId?: string) =>
84
+ runtime.connectionState.isRecentlyReachableConn(accountId, connId, clientId),
85
+ isRevalidatedAttemptedConn: (entry: OutboxEntry, connId: string) =>
86
+ runtime.connectionState.isRevalidatedAttemptedConn(entry, connId),
87
+ tryAdoptTransferOwner: (args: {
88
+ accountId: string;
89
+ transfer: FileSendTransferState | FileRecvTransferState | undefined;
90
+ connId: string;
91
+ clientId?: string;
92
+ }) => runtime.connectionState.tryAdoptTransferOwner(args),
93
+ refreshLiveConnectionState: (args: {
94
+ accountId: string;
95
+ connId: string;
96
+ clientId?: string;
97
+ outboundReady: boolean;
98
+ preferredForOutbound: boolean;
99
+ inboundOnly: boolean;
100
+ context: GatewayRequestHandlerOptions['context'];
101
+ }) => runtime.connectionState.refreshLiveConnectionState(args),
102
+ refreshAcceptedFileTransferLiveState: (args: {
103
+ accountId: string;
104
+ connId: string;
105
+ clientId?: string;
106
+ context: GatewayRequestHandlerOptions['context'];
107
+ }) => runtime.connectionState.refreshAcceptedFileTransferLiveState(args),
108
+ markActivity: (accountId: string, at = runtime.now()) => runtime.markActivity(accountId, at),
109
+ isRetryableFileTransferError,
110
+ };
111
+ }
@@ -0,0 +1,23 @@
1
+ export function createBncrBridgeDiagnosticsFacade<
2
+ TRuntimeFlags,
3
+ TQueueCounters,
4
+ TIntegratedDiagnostics,
5
+ TDownlinkHealth,
6
+ TRuntimeStatusInput = unknown,
7
+ >(runtime: {
8
+ buildRuntimeFlags: (accountId?: string) => TRuntimeFlags;
9
+ buildAccountQueueCounters: (accountId: string) => TQueueCounters;
10
+ buildIntegratedDiagnostics: (
11
+ accountId: string,
12
+ runtimeStatusInput?: TRuntimeStatusInput,
13
+ ) => TIntegratedDiagnostics;
14
+ buildDownlinkHealth: (accountId: string) => TDownlinkHealth;
15
+ }) {
16
+ return {
17
+ buildRuntimeFlags: (accountId?: string) => runtime.buildRuntimeFlags(accountId),
18
+ buildAccountQueueCounters: (accountId: string) => runtime.buildAccountQueueCounters(accountId),
19
+ buildIntegratedDiagnostics: (accountId: string, runtimeStatusInput?: TRuntimeStatusInput) =>
20
+ runtime.buildIntegratedDiagnostics(accountId, runtimeStatusInput),
21
+ buildDownlinkHealth: (accountId: string) => runtime.buildDownlinkHealth(accountId),
22
+ };
23
+ }
@@ -0,0 +1,98 @@
1
+ import { buildOutboxScheduleDebugInfo } from '../messaging/outbound/diagnostics.ts';
2
+ import { clampOutboxDrainDelay } from '../messaging/outbound/queue-selectors.ts';
3
+ import {
4
+ OUTBOUND_FLUSH_REASON,
5
+ OUTBOUND_FLUSH_TRIGGER,
6
+ OUTBOUND_SCHEDULE_SOURCE,
7
+ } from '../messaging/outbound/reasons.ts';
8
+ import { buildFlushBestEffortError } from './bridge-surface-helpers.ts';
9
+
10
+ export function createBncrBridgeDrainFacade(runtime: {
11
+ bridgeId: string;
12
+ asString: (value: unknown, fallback?: string) => string;
13
+ normalizeAccountId: (accountId: string) => string;
14
+ getApi: () => unknown;
15
+ getStopped: () => boolean;
16
+ getPushTimer: () => NodeJS.Timeout | null;
17
+ setPushTimer: (timer: NodeJS.Timeout | null) => void;
18
+ getRetryCount: () => number;
19
+ setRetryCount: (count: number) => void;
20
+ logInfo: (scope: string | undefined, message: string, options?: { debugOnly?: boolean }) => void;
21
+ logError: (scope: string, message: string) => void;
22
+ flushPushQueue: (args?: {
23
+ accountId?: string;
24
+ trigger?: string;
25
+ reason?: string;
26
+ }) => Promise<void>;
27
+ schedulePushDrain: (delayMs?: number) => void;
28
+ resolveOutboundAckRequired: (args: { api: unknown; accountId?: string }) => boolean;
29
+ retryLimit: number;
30
+ retryDelayMs: number;
31
+ }) {
32
+ const schedulePushDrain = (delayMs = 0) => {
33
+ if (runtime.getStopped()) return;
34
+ if (runtime.getPushTimer()) return;
35
+ const delay = clampOutboxDrainDelay(delayMs);
36
+ runtime.logInfo(
37
+ 'outbox',
38
+ `schedule ${JSON.stringify(
39
+ buildOutboxScheduleDebugInfo({
40
+ bridgeId: runtime.bridgeId,
41
+ source: OUTBOUND_SCHEDULE_SOURCE.SCHEDULE_PUSH_DRAIN,
42
+ wait: delay,
43
+ }),
44
+ )}`,
45
+ { debugOnly: true },
46
+ );
47
+ runtime.setPushTimer(
48
+ setTimeout(() => {
49
+ runtime.setPushTimer(null);
50
+ if (runtime.getStopped()) return;
51
+ flushPushQueueBestEffort({
52
+ trigger: OUTBOUND_FLUSH_TRIGGER.TIMER,
53
+ reason: OUTBOUND_FLUSH_REASON.SCHEDULED_DRAIN,
54
+ });
55
+ }, delay),
56
+ );
57
+ };
58
+
59
+ const flushPushQueueBestEffort = (args?: {
60
+ accountId?: string;
61
+ trigger?: string;
62
+ reason?: string;
63
+ }) => {
64
+ void runtime
65
+ .flushPushQueue(args)
66
+ .then(() => {
67
+ runtime.setRetryCount(0);
68
+ })
69
+ .catch((error) => {
70
+ const nextRetryCount = runtime.getRetryCount() + 1;
71
+ const flushError = buildFlushBestEffortError({
72
+ accountId: args?.accountId,
73
+ trigger: args?.trigger,
74
+ reason: args?.reason,
75
+ error,
76
+ asString: runtime.asString,
77
+ normalizeAccountId: runtime.normalizeAccountId,
78
+ nextRetryCount,
79
+ retryLimit: runtime.retryLimit,
80
+ });
81
+ runtime.setRetryCount(nextRetryCount);
82
+ runtime.logError(
83
+ 'outbox drain fail',
84
+ `accountId=${flushError.accountId || '-'}|reason=${flushError.reason}|err=${flushError.err}|retry=${flushError.retryDisplay}|limit=${runtime.retryLimit}`,
85
+ );
86
+ if (flushError.willRetry) runtime.schedulePushDrain(runtime.retryDelayMs);
87
+ });
88
+ };
89
+
90
+ const isOutboundAckRequired = (accountId?: string) =>
91
+ runtime.resolveOutboundAckRequired({ api: runtime.getApi(), accountId });
92
+
93
+ return {
94
+ schedulePushDrain,
95
+ flushPushQueueBestEffort,
96
+ isOutboundAckRequired,
97
+ };
98
+ }