@xmoxmo/bncr 0.3.6 → 0.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/README.md +5 -0
  2. package/dist/index.js +28 -5
  3. package/index.ts +55 -721
  4. package/openclaw.plugin.json +1 -0
  5. package/package.json +8 -4
  6. package/scripts/check-pack.mjs +93 -18
  7. package/scripts/check-register-drift.mjs +35 -13
  8. package/scripts/selfcheck.mjs +80 -11
  9. package/src/bootstrap/channel-plugin-runtime.ts +81 -0
  10. package/src/bootstrap/cli.ts +97 -0
  11. package/src/bootstrap/register-runtime-gateway.ts +129 -0
  12. package/src/bootstrap/register-runtime-helpers.ts +140 -0
  13. package/src/bootstrap/register-runtime-singleton.ts +137 -0
  14. package/src/bootstrap/register-runtime.ts +201 -0
  15. package/src/bootstrap/runtime-discovery.ts +187 -0
  16. package/src/bootstrap/runtime-loader.ts +54 -0
  17. package/src/channel.ts +1590 -4967
  18. package/src/core/accounts.ts +23 -4
  19. package/src/core/dead-letter-diagnostics.ts +37 -5
  20. package/src/core/diagnostics.ts +31 -15
  21. package/src/core/downlink-health.ts +3 -11
  22. package/src/core/extended-diagnostics.ts +78 -36
  23. package/src/core/file-transfer-payloads.ts +1 -1
  24. package/src/core/logging.ts +1 -0
  25. package/src/core/outbox-enqueue.ts +13 -2
  26. package/src/core/outbox-entry-builders.ts +2 -0
  27. package/src/core/outbox-summary.ts +75 -3
  28. package/src/core/permissions.ts +15 -2
  29. package/src/core/persisted-outbox-entry.ts +21 -6
  30. package/src/core/policy.ts +45 -4
  31. package/src/core/probe.ts +3 -15
  32. package/src/core/register-trace.ts +3 -3
  33. package/src/core/status.ts +43 -4
  34. package/src/core/targets.ts +216 -205
  35. package/src/core/types.ts +221 -0
  36. package/src/core/value-sanitize.ts +29 -0
  37. package/src/messaging/inbound/commands.ts +147 -172
  38. package/src/messaging/inbound/context-facts.ts +4 -2
  39. package/src/messaging/inbound/contracts.ts +70 -0
  40. package/src/messaging/inbound/dispatch-prep.ts +303 -0
  41. package/src/messaging/inbound/dispatch.ts +49 -462
  42. package/src/messaging/inbound/gate.ts +18 -5
  43. package/src/messaging/inbound/last-route.ts +10 -4
  44. package/src/messaging/inbound/media-url-download.ts +109 -0
  45. package/src/messaging/inbound/native-command-runtime.ts +225 -0
  46. package/src/messaging/inbound/parse.ts +2 -1
  47. package/src/messaging/inbound/remote-media.ts +49 -0
  48. package/src/messaging/inbound/reply-config.ts +16 -4
  49. package/src/messaging/inbound/reply-dispatch.ts +162 -0
  50. package/src/messaging/inbound/runtime-compat.ts +31 -10
  51. package/src/messaging/inbound/session-label.ts +15 -7
  52. package/src/messaging/inbound/turn-context.ts +131 -0
  53. package/src/messaging/outbound/actions.ts +24 -10
  54. package/src/messaging/outbound/diagnostics-debug-builders.ts +365 -0
  55. package/src/messaging/outbound/diagnostics.ts +31 -355
  56. package/src/messaging/outbound/durable-message-adapter.ts +20 -16
  57. package/src/messaging/outbound/durable-queue-adapter.ts +20 -7
  58. package/src/messaging/outbound/media.ts +24 -13
  59. package/src/messaging/outbound/reply-enqueue-media.ts +181 -0
  60. package/src/messaging/outbound/reply-enqueue.ts +46 -155
  61. package/src/messaging/outbound/send-params.ts +3 -0
  62. package/src/messaging/outbound/send.ts +19 -10
  63. package/src/messaging/outbound/session-route.ts +18 -3
  64. package/src/openclaw/channel-runtime-contracts.ts +76 -0
  65. package/src/openclaw/config-runtime.ts +13 -7
  66. package/src/openclaw/inbound-session-runtime.ts +7 -3
  67. package/src/openclaw/ingress-runtime.ts +17 -27
  68. package/src/openclaw/reply-runtime.ts +54 -59
  69. package/src/openclaw/routing-runtime.ts +35 -18
  70. package/src/openclaw/runtime-surface.ts +156 -12
  71. package/src/openclaw/sdk-helpers.ts +8 -1
  72. package/src/openclaw/session-route-runtime.ts +12 -12
  73. package/src/plugin/ack-outbox-runtime-group.ts +264 -0
  74. package/src/plugin/bridge-ack-facade.ts +137 -0
  75. package/src/plugin/bridge-connection-facade.ts +111 -0
  76. package/src/plugin/bridge-diagnostics-facade.ts +23 -0
  77. package/src/plugin/bridge-drain-facade.ts +98 -0
  78. package/src/plugin/bridge-extended-diagnostics-facade.ts +149 -0
  79. package/src/plugin/bridge-file-transfer-push-facade.ts +140 -0
  80. package/src/plugin/bridge-lifecycle.ts +156 -0
  81. package/src/plugin/bridge-media-facade.ts +241 -0
  82. package/src/plugin/bridge-outbox-facade.ts +182 -0
  83. package/src/plugin/bridge-runtime-helpers.ts +266 -0
  84. package/src/plugin/bridge-runtime-snapshots.ts +104 -0
  85. package/src/plugin/bridge-runtime-surface-facade.ts +8 -0
  86. package/src/plugin/bridge-status-facade.ts +76 -0
  87. package/src/plugin/bridge-status-worker-facade.ts +72 -0
  88. package/src/plugin/bridge-support-runtime.ts +137 -0
  89. package/src/plugin/bridge-surface-handlers-group.ts +242 -0
  90. package/src/plugin/bridge-surface-helpers.ts +28 -0
  91. package/src/plugin/capabilities.ts +1 -3
  92. package/src/plugin/channel-components.ts +289 -0
  93. package/src/plugin/channel-inbound-helpers.ts +149 -0
  94. package/src/plugin/channel-plugin-bridge-group.ts +129 -0
  95. package/src/plugin/channel-plugin-surface-group.ts +202 -0
  96. package/src/plugin/channel-runtime-builders-delivery.ts +513 -0
  97. package/src/plugin/channel-runtime-builders-status.ts +331 -0
  98. package/src/plugin/channel-runtime-builders.ts +25 -0
  99. package/src/plugin/channel-runtime-constants.ts +40 -0
  100. package/src/plugin/channel-runtime-types.ts +146 -0
  101. package/src/plugin/channel-send-runtime-group.ts +37 -0
  102. package/src/plugin/channel-send.ts +226 -0
  103. package/src/plugin/channel-utils.ts +102 -0
  104. package/src/plugin/config.ts +24 -3
  105. package/src/plugin/connection-handlers-helpers.ts +254 -0
  106. package/src/plugin/connection-handlers.ts +440 -0
  107. package/src/plugin/connection-state-helpers.ts +159 -0
  108. package/src/plugin/connection-state-runtime-group.ts +51 -0
  109. package/src/plugin/connection-state.ts +527 -0
  110. package/src/plugin/diagnostics-handlers.ts +211 -0
  111. package/src/plugin/error-message.ts +15 -0
  112. package/src/plugin/file-ack-runtime.ts +284 -0
  113. package/src/plugin/file-inbound-abort.ts +112 -0
  114. package/src/plugin/file-inbound-chunk.ts +146 -0
  115. package/src/plugin/file-inbound-complete.ts +153 -0
  116. package/src/plugin/file-inbound-handlers.ts +19 -0
  117. package/src/plugin/file-inbound-init.ts +122 -0
  118. package/src/plugin/file-inbound-runtime.ts +51 -0
  119. package/src/plugin/file-inbound-state.ts +62 -0
  120. package/src/plugin/file-transfer-logs.ts +227 -0
  121. package/src/plugin/file-transfer-orchestrator-chunk.ts +135 -0
  122. package/src/plugin/file-transfer-orchestrator.ts +304 -0
  123. package/src/plugin/file-transfer-runtime-group.ts +102 -0
  124. package/src/plugin/file-transfer-send.ts +89 -0
  125. package/src/plugin/file-transfer-setup.ts +206 -0
  126. package/src/plugin/gateway-event-context.ts +41 -0
  127. package/src/plugin/gateway-runtime.ts +17 -4
  128. package/src/plugin/inbound-acceptance.ts +107 -0
  129. package/src/plugin/inbound-handlers.ts +248 -0
  130. package/src/plugin/inbound-surface-handlers-group.ts +152 -0
  131. package/src/plugin/media-dedupe-runtime.ts +90 -0
  132. package/src/plugin/media-orchestrators-runtime-group.ts +316 -0
  133. package/src/plugin/message-ack-runtime.ts +284 -0
  134. package/src/plugin/message-send.ts +16 -6
  135. package/src/plugin/messaging.ts +98 -36
  136. package/src/plugin/outbound.ts +50 -8
  137. package/src/plugin/outbox-ack-logs.ts +136 -0
  138. package/src/plugin/outbox-ack-outcome.ts +128 -0
  139. package/src/plugin/outbox-drain-ack.ts +145 -0
  140. package/src/plugin/outbox-drain-failure.ts +84 -0
  141. package/src/plugin/outbox-drain-loop.ts +554 -0
  142. package/src/plugin/outbox-drain-post-push.ts +159 -0
  143. package/src/plugin/outbox-drain-runtime.ts +141 -0
  144. package/src/plugin/outbox-drain-schedule.ts +116 -0
  145. package/src/plugin/outbox-file-push-flow.ts +69 -0
  146. package/src/plugin/outbox-push-route-runtime-group.ts +81 -0
  147. package/src/plugin/outbox-push.ts +267 -0
  148. package/src/plugin/outbox-route.ts +181 -0
  149. package/src/plugin/outbox-text-push-flow.ts +90 -0
  150. package/src/plugin/runtime-diagnostics-assembler.ts +183 -0
  151. package/src/plugin/runtime-diagnostics-helpers.ts +302 -0
  152. package/src/plugin/runtime-diagnostics-payload-builders.ts +171 -0
  153. package/src/plugin/runtime-diagnostics-snapshot.ts +31 -0
  154. package/src/plugin/setup.ts +33 -6
  155. package/src/plugin/state-store.ts +249 -0
  156. package/src/plugin/state-transient-runtime-group.ts +105 -0
  157. package/src/plugin/status-runtime.ts +251 -0
  158. package/src/plugin/status.ts +33 -7
  159. package/src/plugin/target-runtime.ts +141 -0
  160. package/src/plugin/target-status-runtime-group.ts +130 -0
  161. package/src/plugin/transient-state-runtime.ts +82 -0
  162. package/src/runtime/outbound-ack-timeout.ts +5 -3
  163. package/src/runtime/outbound-flags.ts +24 -8
  164. package/src/runtime/status-snapshots.ts +36 -7
  165. package/src/runtime/status-worker.ts +34 -4
@@ -0,0 +1,141 @@
1
+ import type { OutboxEntry } from '../core/types.ts';
2
+ import type { updateMinOutboxDelay } from '../messaging/outbound/queue-selectors.ts';
3
+ import type { RetryRerouteDecision } from '../messaging/outbound/retry-policy.ts';
4
+ import { createBncrOutboxDrainFailure } from './outbox-drain-failure.ts';
5
+ import { createBncrOutboxDrainLoop } from './outbox-drain-loop.ts';
6
+ import { createBncrOutboxDrainPostPush } from './outbox-drain-post-push.ts';
7
+
8
+ type BncrOutboxDrainScheduleRuntime = {
9
+ scheduleAccountWait: (args: {
10
+ accountId: string;
11
+ messageId?: string;
12
+ source: string;
13
+ wait: number;
14
+ localNextDelay: number | null;
15
+ updateMinOutboxDelay: typeof updateMinOutboxDelay;
16
+ }) => number | null;
17
+ scheduleAccountYield: (args: {
18
+ accountId: string;
19
+ source: string;
20
+ localNextDelay: number | null;
21
+ updateMinOutboxDelay: typeof updateMinOutboxDelay;
22
+ }) => number | null;
23
+ mergeAccountNextDelay: (args: {
24
+ accountId: string;
25
+ localNextDelay: number;
26
+ globalNextDelay: number | null;
27
+ updateMinOutboxDelay: typeof updateMinOutboxDelay;
28
+ source: string;
29
+ }) => number | null;
30
+ scheduleFlushNextDrain: (args: { globalNextDelay: number; source: string }) => void;
31
+ };
32
+
33
+ type BncrOutboxDrainAckRuntime = {
34
+ logAckWaitStart: (args: {
35
+ entry: OutboxEntry;
36
+ requireAck: boolean;
37
+ ackTimeoutMs: number | null;
38
+ onlineNow: boolean;
39
+ recentInboundReachable: boolean;
40
+ }) => void;
41
+ handleAckTimeoutReroute: (args: {
42
+ accountId: string;
43
+ entry: OutboxEntry;
44
+ requireAck: boolean;
45
+ currentConnId: string;
46
+ availableConnIds: string[];
47
+ decision: RetryRerouteDecision;
48
+ localNextDelay: number | null;
49
+ ackTimeoutMs: number | null;
50
+ updateMinOutboxDelay: typeof updateMinOutboxDelay;
51
+ }) => { kind: 'dead-letter' } | { kind: 'retry'; localNextDelay: number | null };
52
+ };
53
+
54
+ type BncrOutboxDrainRuntime = {
55
+ bridgeId: string;
56
+ now: () => number;
57
+ asString: (value: unknown, fallback?: string) => string;
58
+ backoffMs: (retryCount: number) => number;
59
+ isPlainObject: (value: unknown) => value is Record<string, unknown>;
60
+ normalizeAccountId: (accountId: string) => string;
61
+ stopped: () => boolean;
62
+ outbox: Map<string, OutboxEntry>;
63
+ deadLetter: () => OutboxEntry[];
64
+ connectionsValues: () => IterableIterator<{
65
+ accountId: string;
66
+ connId: string;
67
+ clientId?: string;
68
+ connectedAt: number;
69
+ lastSeenAt: number;
70
+ inboundOnly?: boolean;
71
+ outboundReady?: boolean;
72
+ preferredForOutbound?: boolean;
73
+ outboundReadyUntil?: number;
74
+ preferredForOutboundUntil?: number;
75
+ lastAckOkAt?: number;
76
+ lastPushTimeoutAt?: number;
77
+ pushFailureScore?: number;
78
+ }>;
79
+ gatewayContextAvailable: () => boolean;
80
+ messageAckWaiterCount: () => number;
81
+ fileAckWaiterCount: () => number;
82
+ activeConnectionCount: (accountId: string) => number;
83
+ getAccountPendingOutboxEntries: (accountId: string) => OutboxEntry[];
84
+ pushDrainRunningAccounts: Set<string>;
85
+ pushDrainRunningSinceByAccount: Map<string, number>;
86
+ pushDrainStuckWarnedAtByAccount: Map<string, number>;
87
+ isOnline: (accountId: string) => boolean;
88
+ hasRecentInboundReachability: (accountId: string) => boolean;
89
+ isOutboundAckRequired: (accountId?: string) => boolean;
90
+ resolveMessageAckTimeoutMs: (accountId: string) => number;
91
+ waitForMessageAck: (messageId: string, waitMs: number) => Promise<'acked' | 'timeout'>;
92
+ logOutboxAckWait: (args: {
93
+ entry: OutboxEntry;
94
+ requireAck: boolean;
95
+ ackResult: 'acked' | 'timeout';
96
+ onlineNow: boolean;
97
+ recentInboundReachable: boolean;
98
+ ackTimeoutMs: number | null;
99
+ }) => void;
100
+ degradeOutboundCapability: (args: {
101
+ accountId: string;
102
+ connId?: string;
103
+ clientId?: string;
104
+ reason: string;
105
+ }) => void;
106
+ resolvePushConnIds: (accountId: string) => Iterable<string>;
107
+ sleepMs: (ms: number) => Promise<void>;
108
+ schedulePushDrain: (delayMs: number) => void;
109
+ outboxDrainSchedule: BncrOutboxDrainScheduleRuntime;
110
+ outboxDrainAck: BncrOutboxDrainAckRuntime;
111
+ tryPushEntry: (entry: OutboxEntry) => Promise<boolean>;
112
+ handleFileTransferPushFailure: (args: { entry: OutboxEntry; error: unknown }) => void;
113
+ handleTextPushFailure: (args: { entry: OutboxEntry; error: unknown }) => void;
114
+ isPrePushGuardDeferral: (entry: OutboxEntry) => boolean;
115
+ moveToDeadLetter: (entry: OutboxEntry, reason: string) => void;
116
+ scheduleSave: () => void;
117
+ logInfo: (scope: string, message: string, options?: { debugOnly?: boolean }) => void;
118
+ logWarn: (scope: string, message: string, options?: { debugOnly?: boolean }) => void;
119
+ logError: (scope: string, message: string) => void;
120
+ flushTriggerTimer: string;
121
+ flushReasonScheduledDrain: 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
+
133
+ export function createBncrOutboxDrainRuntime(runtime: BncrOutboxDrainRuntime) {
134
+ const handlePushedDrainEntry = createBncrOutboxDrainPostPush(runtime);
135
+ const handleFailedDrainEntry = createBncrOutboxDrainFailure(runtime);
136
+
137
+ return createBncrOutboxDrainLoop(runtime, {
138
+ handlePushedDrainEntry,
139
+ handleFailedDrainEntry,
140
+ });
141
+ }
@@ -0,0 +1,116 @@
1
+ import { buildOutboxScheduleDebugInfo } from '../messaging/outbound/diagnostics.ts';
2
+ import type { OutboundScheduleSource } from '../messaging/outbound/reasons.ts';
3
+
4
+ function asScheduleSource(source: string): OutboundScheduleSource {
5
+ return source as OutboundScheduleSource;
6
+ }
7
+
8
+ export type BncrOutboxDrainScheduleRuntime = {
9
+ bridgeId: string;
10
+ logInfo: (scope: string, message: string, options?: { debugOnly?: boolean }) => void;
11
+ };
12
+
13
+ export function createBncrOutboxDrainSchedule(runtime: BncrOutboxDrainScheduleRuntime) {
14
+ const logSchedule = (args: {
15
+ accountId?: string;
16
+ messageId?: string;
17
+ source: string;
18
+ wait?: number | null;
19
+ localNextDelay?: number | null;
20
+ globalNextDelay?: number | null;
21
+ }) => {
22
+ runtime.logInfo(
23
+ 'outbox',
24
+ `schedule ${JSON.stringify(
25
+ buildOutboxScheduleDebugInfo({
26
+ bridgeId: runtime.bridgeId,
27
+ accountId: args.accountId,
28
+ messageId: args.messageId,
29
+ source: asScheduleSource(args.source),
30
+ wait: args.wait,
31
+ localNextDelay: args.localNextDelay,
32
+ globalNextDelay: args.globalNextDelay,
33
+ }),
34
+ )}`,
35
+ { debugOnly: true },
36
+ );
37
+ };
38
+
39
+ const updateDelay = (
40
+ current: number | null,
41
+ wait: number | null,
42
+ updateMinOutboxDelay: (current: number | null, next: number | null) => number | null,
43
+ ) => updateMinOutboxDelay(current, wait);
44
+
45
+ const scheduleAccountYield = (args: {
46
+ accountId: string;
47
+ source: string;
48
+ localNextDelay: number | null;
49
+ updateMinOutboxDelay: (current: number | null, next: number | null) => number | null;
50
+ }) => {
51
+ const localNextDelay = updateDelay(args.localNextDelay, 0, args.updateMinOutboxDelay);
52
+ logSchedule({
53
+ accountId: args.accountId,
54
+ source: args.source,
55
+ wait: 0,
56
+ localNextDelay,
57
+ });
58
+ return localNextDelay;
59
+ };
60
+
61
+ const scheduleAccountWait = (args: {
62
+ accountId: string;
63
+ messageId?: string;
64
+ source: string;
65
+ wait: number;
66
+ localNextDelay: number | null;
67
+ updateMinOutboxDelay: (current: number | null, next: number | null) => number | null;
68
+ }) => {
69
+ const localNextDelay = updateDelay(args.localNextDelay, args.wait, args.updateMinOutboxDelay);
70
+ logSchedule({
71
+ accountId: args.accountId,
72
+ messageId: args.messageId,
73
+ source: args.source,
74
+ wait: args.wait,
75
+ localNextDelay,
76
+ });
77
+ return localNextDelay;
78
+ };
79
+
80
+ const mergeAccountNextDelay = (args: {
81
+ accountId: string;
82
+ localNextDelay: number;
83
+ globalNextDelay: number | null;
84
+ updateMinOutboxDelay: (current: number | null, next: number | null) => number | null;
85
+ source: string;
86
+ }) => {
87
+ const globalNextDelay = updateDelay(
88
+ args.globalNextDelay,
89
+ args.localNextDelay,
90
+ args.updateMinOutboxDelay,
91
+ );
92
+ logSchedule({
93
+ accountId: args.accountId,
94
+ source: args.source,
95
+ localNextDelay: args.localNextDelay,
96
+ globalNextDelay,
97
+ });
98
+ return globalNextDelay;
99
+ };
100
+
101
+ const scheduleFlushNextDrain = (args: { globalNextDelay: number; source: string }) => {
102
+ logSchedule({
103
+ source: args.source,
104
+ globalNextDelay: args.globalNextDelay,
105
+ wait: args.globalNextDelay,
106
+ });
107
+ };
108
+
109
+ return {
110
+ logSchedule,
111
+ scheduleAccountYield,
112
+ scheduleAccountWait,
113
+ mergeAccountNextDelay,
114
+ scheduleFlushNextDrain,
115
+ };
116
+ }
@@ -0,0 +1,69 @@
1
+ import { resolveFileTransferGuard } from '../core/outbox-file-transfer-guards.ts';
2
+ import { prepareFileTransferRouteSelection } from '../core/outbox-file-transfer-prep.ts';
3
+ import type { BncrConnection, OutboxEntry } from '../core/types.ts';
4
+ import { selectOutboxFileTransferRouteCandidates } from '../messaging/outbound/queue-selectors.ts';
5
+
6
+ export async function runBncrFileTransferOutboxPush(args: {
7
+ entry: OutboxEntry;
8
+ meta: Record<string, unknown>;
9
+ gatewayContext: unknown;
10
+ owner: BncrConnection | null;
11
+ resolvePushConnIds: (accountId: string) => Set<string>;
12
+ resolveRecentInboundConnIds: (accountId: string) => Set<string>;
13
+ hasRecentInboundReachability: (accountId: string) => boolean;
14
+ isRevalidatedAttemptedConn: (connId: string) => boolean;
15
+ handleFileTransferPushGuardFailure: (args: {
16
+ entry: OutboxEntry;
17
+ guard: Exclude<ReturnType<typeof resolveFileTransferGuard>, { ok: true }>;
18
+ }) => void;
19
+ pushFileTransferSuccessPath: (args: {
20
+ entry: OutboxEntry;
21
+ meta: Record<string, unknown>;
22
+ owner: BncrConnection | null;
23
+ connIds: Iterable<string>;
24
+ recentInboundReachable: boolean;
25
+ routeReason: string;
26
+ mediaUrl: string;
27
+ }) => Promise<void>;
28
+ handleFileTransferPushFailure: (args: { entry: OutboxEntry; error: unknown }) => void;
29
+ }) {
30
+ const selection = prepareFileTransferRouteSelection({
31
+ entry: args.entry,
32
+ owner: args.owner,
33
+ resolvePushConnIds: args.resolvePushConnIds,
34
+ resolveRecentInboundConnIds: args.resolveRecentInboundConnIds,
35
+ hasRecentInboundReachability: args.hasRecentInboundReachability,
36
+ isRevalidatedAttemptedConn: args.isRevalidatedAttemptedConn,
37
+ selectOutboxFileTransferRouteCandidates,
38
+ });
39
+ const guard = resolveFileTransferGuard({
40
+ gatewayContext: args.gatewayContext,
41
+ entry: args.entry,
42
+ owner: args.owner,
43
+ routeSelection: selection,
44
+ mediaUrl: String(args.meta.mediaUrl || '').trim(),
45
+ });
46
+ if (!guard.ok) {
47
+ args.handleFileTransferPushGuardFailure({
48
+ entry: args.entry,
49
+ guard,
50
+ });
51
+ return false;
52
+ }
53
+
54
+ try {
55
+ await args.pushFileTransferSuccessPath({
56
+ entry: args.entry,
57
+ meta: args.meta,
58
+ owner: args.owner,
59
+ connIds: guard.connIds,
60
+ recentInboundReachable: guard.recentInboundReachable,
61
+ routeReason: guard.routeReason,
62
+ mediaUrl: guard.mediaUrl,
63
+ });
64
+ return true;
65
+ } catch (error) {
66
+ args.handleFileTransferPushFailure({ entry: args.entry, error });
67
+ return false;
68
+ }
69
+ }
@@ -0,0 +1,81 @@
1
+ import type { BncrConnection, OutboxEntry } from '../core/types.ts';
2
+ import { createBncrOutboxPush } from './outbox-push.ts';
3
+ import { createBncrOutboxRoute } from './outbox-route.ts';
4
+
5
+ export function createBncrOutboxPushRouteRuntimeGroup(runtime: {
6
+ bridgeId: string;
7
+ pushEvent: string;
8
+ now: () => number;
9
+ connectTtlMs: number;
10
+ finiteNumberOr: (value: unknown, fallback: number) => number;
11
+ outboxSize: () => number;
12
+ gatewayBroadcastToConnIds: (
13
+ event: string,
14
+ payload: unknown,
15
+ connIds: ReadonlySet<string>,
16
+ ) => void;
17
+ recordOutboxPushSuccess: (args: {
18
+ entry: OutboxEntry;
19
+ connIds: Iterable<string>;
20
+ ownerConnId?: string;
21
+ ownerClientId?: string;
22
+ }) => void;
23
+ recordOutboxPushFailure: (args: {
24
+ entry: OutboxEntry;
25
+ error: unknown;
26
+ fallbackError: string;
27
+ persist?: boolean;
28
+ }) => void;
29
+ recordOutboxPrePushFailure: (args: {
30
+ entry: OutboxEntry;
31
+ lastError: string;
32
+ persist?: boolean;
33
+ }) => void;
34
+ recordPrePushGuardSkip: (args: { accountId: string; reason: string }) => void;
35
+ moveToDeadLetter: (entry: OutboxEntry, reason: string) => void;
36
+ activeConnectionCount: (accountId: string) => number;
37
+ connections: Map<string, BncrConnection>;
38
+ connectionsValues: () => Iterable<BncrConnection>;
39
+ activeConnectionByAccount: Map<string, string>;
40
+ resolveRecentInboundConnIds: (accountId: string) => Set<string>;
41
+ connectionKey: (accountId: string, clientId?: string) => string;
42
+ isRetryableFileTransferError: (value: unknown) => boolean;
43
+ logInfo: (scope: string, message: string, options?: { debugOnly?: boolean }) => void;
44
+ buildActiveConnectionDebugList: (
45
+ accountId: string,
46
+ options?: { includeOutboundState?: boolean },
47
+ ) => unknown;
48
+ }) {
49
+ const outboxPush = createBncrOutboxPush({
50
+ pushEvent: runtime.pushEvent,
51
+ outboxSize: runtime.outboxSize,
52
+ gatewayBroadcastToConnIds: runtime.gatewayBroadcastToConnIds,
53
+ recordOutboxPushSuccess: runtime.recordOutboxPushSuccess,
54
+ recordOutboxPushFailure: runtime.recordOutboxPushFailure,
55
+ recordOutboxPrePushFailure: runtime.recordOutboxPrePushFailure,
56
+ recordPrePushGuardSkip: runtime.recordPrePushGuardSkip,
57
+ moveToDeadLetter: runtime.moveToDeadLetter,
58
+ activeConnectionCount: runtime.activeConnectionCount,
59
+ connectionsValues: runtime.connectionsValues,
60
+ isRetryableFileTransferError: runtime.isRetryableFileTransferError,
61
+ logInfo: runtime.logInfo,
62
+ });
63
+
64
+ const outboxRoute = createBncrOutboxRoute({
65
+ bridgeId: runtime.bridgeId,
66
+ now: runtime.now,
67
+ connectTtlMs: runtime.connectTtlMs,
68
+ finiteNumberOr: runtime.finiteNumberOr,
69
+ connections: runtime.connections,
70
+ activeConnectionByAccount: runtime.activeConnectionByAccount,
71
+ resolveRecentInboundConnIds: runtime.resolveRecentInboundConnIds,
72
+ connectionKey: runtime.connectionKey,
73
+ logInfo: runtime.logInfo,
74
+ buildActiveConnectionDebugList: runtime.buildActiveConnectionDebugList,
75
+ });
76
+
77
+ return {
78
+ outboxPush,
79
+ outboxRoute,
80
+ };
81
+ }
@@ -0,0 +1,267 @@
1
+ import {
2
+ buildFileTransferPushFailureArgs,
3
+ resolveFileTransferFailureState,
4
+ } from '../core/outbox-file-transfer-failure.ts';
5
+ import { buildTextPushFailureArgs } from '../core/outbox-text-push-failure.ts';
6
+ import {
7
+ buildTextPushBroadcastPayload,
8
+ buildTextPushOkArgs,
9
+ buildTextPushRouteSelectArgs,
10
+ buildTextPushSuccessArgs,
11
+ } from '../core/outbox-text-push-success.ts';
12
+ import type { BncrConnection, OutboxEntry } from '../core/types.ts';
13
+ import {
14
+ buildOutboxPushOkDebugInfo,
15
+ buildOutboxPushSkipDebugInfo,
16
+ buildOutboxRouteSelectDebugInfo,
17
+ buildPushFailureDebugInfo,
18
+ } from '../messaging/outbound/diagnostics.ts';
19
+
20
+ export type BncrOutboxPushRuntime = {
21
+ pushEvent: string;
22
+ outboxSize: () => number;
23
+ gatewayBroadcastToConnIds: (
24
+ event: string,
25
+ payload: unknown,
26
+ connIds: ReadonlySet<string>,
27
+ ) => void;
28
+ recordOutboxPushSuccess: (args: {
29
+ entry: OutboxEntry;
30
+ connIds: Iterable<string>;
31
+ ownerConnId?: string;
32
+ ownerClientId?: string;
33
+ }) => void;
34
+ recordOutboxPushFailure: (args: {
35
+ entry: OutboxEntry;
36
+ error: unknown;
37
+ fallbackError: string;
38
+ persist?: boolean;
39
+ }) => void;
40
+ recordOutboxPrePushFailure: (args: {
41
+ entry: OutboxEntry;
42
+ lastError: string;
43
+ persist?: boolean;
44
+ }) => void;
45
+ recordPrePushGuardSkip: (args: { accountId: string; reason: string }) => void;
46
+ moveToDeadLetter: (entry: OutboxEntry, reason: string) => void;
47
+ activeConnectionCount: (accountId: string) => number;
48
+ connectionsValues: () => Iterable<BncrConnection>;
49
+ isRetryableFileTransferError: (value: unknown) => boolean;
50
+ logInfo: (scope: string, message: string, options?: { debugOnly?: boolean }) => void;
51
+ };
52
+
53
+ export function createBncrOutboxPush(runtime: BncrOutboxPushRuntime) {
54
+ const logOutboxPushOk = (args: {
55
+ messageId: string;
56
+ accountId: string;
57
+ kind?: 'file-transfer';
58
+ connIds: Iterable<string>;
59
+ ownerConnId: string;
60
+ ownerClientId: string;
61
+ recentInboundReachable: boolean;
62
+ event: string;
63
+ }) => {
64
+ runtime.logInfo('outbox', `push ${JSON.stringify(buildOutboxPushOkDebugInfo(args))}`, {
65
+ debugOnly: true,
66
+ });
67
+ };
68
+
69
+ const logOutboxPushSkip = (args: {
70
+ messageId: string;
71
+ accountId: string;
72
+ kind?: 'file-transfer';
73
+ reason: string;
74
+ recentInboundReachable?: boolean;
75
+ routeReason?: string;
76
+ connIds?: Iterable<string>;
77
+ ownerConnId?: string;
78
+ ownerClientId?: string;
79
+ }) => {
80
+ runtime.recordPrePushGuardSkip({ accountId: args.accountId, reason: args.reason });
81
+ runtime.logInfo(
82
+ 'outbox push skip',
83
+ `mid=${args.messageId}|q=${runtime.outboxSize()}|reason=${args.reason}${args.kind ? `|kind=${args.kind}` : ''}`,
84
+ );
85
+ runtime.logInfo(
86
+ 'outbox',
87
+ `push-skip ${JSON.stringify(
88
+ buildOutboxPushSkipDebugInfo({
89
+ ...args,
90
+ activeConnectionCount: runtime.activeConnectionCount(args.accountId),
91
+ connections: runtime.connectionsValues(),
92
+ }),
93
+ )}`,
94
+ { debugOnly: true },
95
+ );
96
+ };
97
+
98
+ const logOutboxRouteSelect = (args: {
99
+ messageId: string;
100
+ accountId: string;
101
+ kind?: 'file-transfer';
102
+ routeReason: string;
103
+ connIds: Iterable<string>;
104
+ ownerConnId: string;
105
+ ownerClientId: string;
106
+ recentInboundReachable: boolean;
107
+ event: string;
108
+ }) => {
109
+ runtime.logInfo(
110
+ 'outbox',
111
+ `route-select ${JSON.stringify(buildOutboxRouteSelectDebugInfo(args))}`,
112
+ { debugOnly: true },
113
+ );
114
+ };
115
+
116
+ const logOutboxPushFailure = (args: {
117
+ messageId: string;
118
+ accountId: string;
119
+ retryCount: number;
120
+ kind?: 'file-transfer';
121
+ retryable?: boolean;
122
+ lastError?: string;
123
+ }) => {
124
+ runtime.logInfo('outbox', `push-fail ${JSON.stringify(buildPushFailureDebugInfo(args))}`, {
125
+ debugOnly: true,
126
+ });
127
+ };
128
+
129
+ const logOutboxPushOkSummary = (messageId: string) => {
130
+ runtime.logInfo('outbox push', `mid=${messageId}|q=${runtime.outboxSize()}`);
131
+ };
132
+
133
+ const logOutboxPushFailureSummary = (messageId: string, lastError?: string) => {
134
+ runtime.logInfo(
135
+ 'outbox push fail',
136
+ `mid=${messageId}|q=${runtime.outboxSize()}|err=${lastError}`,
137
+ );
138
+ };
139
+
140
+ const pushTextSuccessPath = (args: {
141
+ entry: OutboxEntry;
142
+ owner: BncrConnection | null;
143
+ connIds: Iterable<string>;
144
+ recentInboundReachable: boolean;
145
+ routeReason: string;
146
+ ownerConnId?: string;
147
+ }) => {
148
+ runtime.gatewayBroadcastToConnIds(
149
+ runtime.pushEvent,
150
+ buildTextPushBroadcastPayload({
151
+ payload: args.entry.payload,
152
+ messageId: args.entry.messageId,
153
+ }),
154
+ new Set(args.connIds),
155
+ );
156
+ logOutboxRouteSelect(
157
+ buildTextPushRouteSelectArgs({
158
+ entry: args.entry,
159
+ connIds: args.connIds,
160
+ routeReason: args.routeReason,
161
+ recentInboundReachable: args.recentInboundReachable,
162
+ owner: args.owner,
163
+ event: runtime.pushEvent,
164
+ }),
165
+ );
166
+ runtime.recordOutboxPushSuccess(
167
+ buildTextPushSuccessArgs({
168
+ entry: args.entry,
169
+ connIds: args.connIds,
170
+ ownerConnId: args.ownerConnId,
171
+ ownerClientId: args.ownerConnId ? args.owner?.clientId : undefined,
172
+ }),
173
+ );
174
+ logOutboxPushOkSummary(args.entry.messageId);
175
+ logOutboxPushOk(
176
+ buildTextPushOkArgs({
177
+ entry: args.entry,
178
+ connIds: args.connIds,
179
+ recentInboundReachable: args.recentInboundReachable,
180
+ event: runtime.pushEvent,
181
+ }),
182
+ );
183
+ };
184
+
185
+ const handleTextPushFailure = (args: { entry: OutboxEntry; error: unknown }) => {
186
+ runtime.recordOutboxPushFailure({
187
+ entry: args.entry,
188
+ error: args.error,
189
+ fallbackError: 'push-error',
190
+ });
191
+ logOutboxPushFailureSummary(args.entry.messageId, args.entry.lastError);
192
+ logOutboxPushFailure(buildTextPushFailureArgs({ entry: args.entry }));
193
+ };
194
+
195
+ const handleFileTransferPushFailure = (args: { entry: OutboxEntry; error: unknown }) => {
196
+ runtime.recordOutboxPushFailure({
197
+ entry: args.entry,
198
+ error: args.error,
199
+ fallbackError: 'file-transfer-error',
200
+ persist: true,
201
+ });
202
+ const failure = resolveFileTransferFailureState({
203
+ entry: args.entry,
204
+ error: args.error,
205
+ isRetryableFileTransferError: (value) => runtime.isRetryableFileTransferError(value),
206
+ });
207
+ logOutboxPushFailureSummary(args.entry.messageId, args.entry.lastError);
208
+ logOutboxPushFailure(
209
+ buildFileTransferPushFailureArgs({
210
+ entry: args.entry,
211
+ retryable: failure.retryable,
212
+ }),
213
+ );
214
+ if (!failure.retryable) {
215
+ runtime.moveToDeadLetter(args.entry, failure.deadLetterReason);
216
+ }
217
+ };
218
+
219
+ const handleFileTransferPushGuardFailure = (args: {
220
+ entry: OutboxEntry;
221
+ guard: {
222
+ reason: 'media-url-missing' | 'no-gateway-context' | 'no-active-connection';
223
+ lastError: string;
224
+ recentInboundReachable?: boolean;
225
+ };
226
+ }) => {
227
+ runtime.recordOutboxPrePushFailure({
228
+ entry: args.entry,
229
+ lastError: args.guard.lastError,
230
+ persist: true,
231
+ });
232
+ if (args.guard.reason === 'media-url-missing') {
233
+ logOutboxPushFailure({
234
+ messageId: args.entry.messageId,
235
+ accountId: args.entry.accountId,
236
+ retryCount: args.entry.retryCount,
237
+ kind: 'file-transfer',
238
+ lastError: args.entry.lastError,
239
+ });
240
+ return;
241
+ }
242
+ logOutboxPushSkip({
243
+ messageId: args.entry.messageId,
244
+ accountId: args.entry.accountId,
245
+ kind: 'file-transfer',
246
+ reason:
247
+ args.guard.reason === 'no-gateway-context' ? 'no-gateway-context' : 'no-active-connection',
248
+ recentInboundReachable:
249
+ args.guard.reason === 'no-active-connection'
250
+ ? args.guard.recentInboundReachable
251
+ : undefined,
252
+ });
253
+ };
254
+
255
+ return {
256
+ pushTextSuccessPath,
257
+ handleTextPushFailure,
258
+ handleFileTransferPushFailure,
259
+ handleFileTransferPushGuardFailure,
260
+ logOutboxPushSkip,
261
+ logOutboxRouteSelect,
262
+ logOutboxPushFailure,
263
+ logOutboxPushOk,
264
+ logOutboxPushOkSummary,
265
+ logOutboxPushFailureSummary,
266
+ };
267
+ }