@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,182 @@
1
+ import { buildBncrDebugJsonMessage } from '../core/logging.ts';
2
+ import { buildOutboxEnqueueDebugInfo } from '../core/outbox-enqueue.ts';
3
+ import {
4
+ appendDeadLetter,
5
+ buildDeadLetterEntry,
6
+ collectDueOutboxEntries,
7
+ } from '../core/outbox-queue.ts';
8
+ import { formatDisplayScope } from '../core/targets.ts';
9
+ import type { OutboxEntry } from '../core/types.ts';
10
+ import {
11
+ buildBncrOutboxFailureEntryPatch,
12
+ buildBncrOutboxPushSuccessEntryPatch,
13
+ } from '../runtime/outbox-transitions.ts';
14
+ import { getErrorMessage } from './error-message.ts';
15
+
16
+ function isPrePushGuardReason(reason: string) {
17
+ return reason === 'no-gateway-context' || reason === 'no-active-connection';
18
+ }
19
+
20
+ export function createBncrBridgeOutboxFacade(runtime: {
21
+ bridgeId: string;
22
+ normalizeAccountId: (accountId: string) => string;
23
+ asString: (value: unknown, fallback?: string) => string;
24
+ now: () => number;
25
+ backoffMs: (retryCount: number) => number;
26
+ maxRetry: number;
27
+ maxDeadLetterEntries: number;
28
+ outbox: Map<string, OutboxEntry>;
29
+ getDeadLetter: () => OutboxEntry[];
30
+ setDeadLetter: (entries: OutboxEntry[]) => void;
31
+ incrementCounter: (map: Map<string, number>, accountId: string) => void;
32
+ outboundEnqueueCountByAccount: Map<string, number>;
33
+ lastOutboundEnqueueAtByAccount: Map<string, number>;
34
+ prePushGuardSkipCountByAccount: Map<string, number>;
35
+ lastPrePushGuardSkipAtByAccount: Map<string, number>;
36
+ lastPrePushGuardSkipReasonByAccount: Map<string, string>;
37
+ deadLetterSinceStartByAccount: Map<string, number>;
38
+ lastOutboundByAccount: Map<string, number>;
39
+ scheduleSave: () => void;
40
+ flushPushQueueBestEffort: (args?: {
41
+ accountId?: string;
42
+ trigger?: string;
43
+ reason?: string;
44
+ }) => void;
45
+ logInfo: (scope: string | undefined, message: string, options?: { debugOnly?: boolean }) => void;
46
+ logOutboundSummary: (entry: OutboxEntry) => void;
47
+ logDeadLetterSummary: (accountId: string, options?: { force?: boolean; source?: string }) => void;
48
+ resolveMessageAck: (messageId: string, result?: 'acked' | 'timeout') => boolean;
49
+ markActivity: (accountId: string, at?: number) => void;
50
+ }) {
51
+ const recordOutboxPrePushFailure = (args: {
52
+ entry: OutboxEntry;
53
+ lastError: string;
54
+ persist?: boolean;
55
+ }) => {
56
+ const nextEntry = buildBncrOutboxFailureEntryPatch({
57
+ entry: args.entry,
58
+ lastError: args.lastError,
59
+ });
60
+ Object.assign(args.entry, nextEntry);
61
+ runtime.outbox.set(nextEntry.messageId, args.entry);
62
+ if (args.persist) runtime.scheduleSave();
63
+ };
64
+
65
+ const recordPrePushGuardSkip = (args: { accountId: string; reason: string }) => {
66
+ if (!isPrePushGuardReason(args.reason)) return;
67
+ const acc = runtime.normalizeAccountId(args.accountId);
68
+ runtime.incrementCounter(runtime.prePushGuardSkipCountByAccount, acc);
69
+ runtime.lastPrePushGuardSkipAtByAccount.set(acc, runtime.now());
70
+ runtime.lastPrePushGuardSkipReasonByAccount.set(acc, args.reason);
71
+ };
72
+
73
+ const isPrePushGuardDeferral = (entry: OutboxEntry) =>
74
+ entry.lastError === 'gateway context unavailable' ||
75
+ entry.lastError === 'no active bncr client';
76
+
77
+ const recordOutboxPushFailure = (args: {
78
+ entry: OutboxEntry;
79
+ error: unknown;
80
+ fallbackError: string;
81
+ persist?: boolean;
82
+ }) => {
83
+ const nextEntry = buildBncrOutboxFailureEntryPatch({
84
+ entry: args.entry,
85
+ lastError: runtime.asString(getErrorMessage(args.error, args.fallbackError)),
86
+ });
87
+ Object.assign(args.entry, nextEntry);
88
+ runtime.outbox.set(nextEntry.messageId, args.entry);
89
+ if (args.persist) runtime.scheduleSave();
90
+ };
91
+
92
+ const recordOutboxPushSuccess = (args: {
93
+ entry: OutboxEntry;
94
+ connIds: Iterable<string>;
95
+ ownerConnId?: string;
96
+ ownerClientId?: string;
97
+ clearLastError?: boolean;
98
+ }) => {
99
+ const pushedAt = runtime.now();
100
+ const nextEntry = buildBncrOutboxPushSuccessEntryPatch({
101
+ entry: args.entry,
102
+ connIds: args.connIds,
103
+ pushedAt,
104
+ ownerConnId: args.ownerConnId,
105
+ ownerClientId: args.ownerClientId,
106
+ clearLastError: args.clearLastError,
107
+ });
108
+ Object.assign(args.entry, nextEntry);
109
+ runtime.outbox.set(nextEntry.messageId, args.entry);
110
+ runtime.lastOutboundByAccount.set(nextEntry.accountId, pushedAt);
111
+ runtime.markActivity(nextEntry.accountId, pushedAt);
112
+ runtime.scheduleSave();
113
+ };
114
+
115
+ const enqueueOutbound = (entry: OutboxEntry) => {
116
+ runtime.logInfo(
117
+ 'outbound',
118
+ buildBncrDebugJsonMessage(
119
+ 'enqueue',
120
+ buildOutboxEnqueueDebugInfo({
121
+ bridgeId: runtime.bridgeId,
122
+ entry,
123
+ asString: runtime.asString,
124
+ formatDisplayScope,
125
+ }),
126
+ ),
127
+ { debugOnly: true },
128
+ );
129
+ runtime.logOutboundSummary(entry);
130
+ const accountId = runtime.normalizeAccountId(entry.accountId);
131
+ runtime.incrementCounter(runtime.outboundEnqueueCountByAccount, accountId);
132
+ runtime.lastOutboundEnqueueAtByAccount.set(accountId, runtime.now());
133
+ runtime.outbox.set(entry.messageId, entry);
134
+ runtime.scheduleSave();
135
+ runtime.flushPushQueueBestEffort({ accountId: entry.accountId });
136
+ };
137
+
138
+ const moveToDeadLetter = (entry: OutboxEntry, reason: string) => {
139
+ const dead = buildDeadLetterEntry(entry, reason);
140
+ runtime.setDeadLetter(
141
+ appendDeadLetter({
142
+ deadLetter: runtime.getDeadLetter(),
143
+ entry: dead,
144
+ maxEntries: runtime.maxDeadLetterEntries,
145
+ }),
146
+ );
147
+ runtime.incrementCounter(runtime.deadLetterSinceStartByAccount, dead.accountId);
148
+ runtime.logDeadLetterSummary(dead.accountId, { source: 'move' });
149
+ runtime.outbox.delete(entry.messageId);
150
+ runtime.resolveMessageAck(entry.messageId, 'timeout');
151
+ runtime.scheduleSave();
152
+ };
153
+
154
+ const collectDue = (args: { accountId: string; maxBatch: number }) => {
155
+ const key = runtime.normalizeAccountId(args.accountId);
156
+ const result = collectDueOutboxEntries({
157
+ outbox: runtime.outbox.values(),
158
+ accountId: key,
159
+ now: runtime.now(),
160
+ maxBatch: args.maxBatch,
161
+ maxRetry: runtime.maxRetry,
162
+ backoffMs: runtime.backoffMs,
163
+ });
164
+
165
+ for (const entry of result.updatedEntries) runtime.outbox.set(entry.messageId, entry);
166
+ for (const entry of result.deadLetterEntries)
167
+ moveToDeadLetter(entry, entry.lastError || 'retry-limit');
168
+ if (result.duePayloads.length) runtime.scheduleSave();
169
+ return result.duePayloads;
170
+ };
171
+
172
+ return {
173
+ recordOutboxPrePushFailure,
174
+ recordPrePushGuardSkip,
175
+ isPrePushGuardDeferral,
176
+ recordOutboxPushFailure,
177
+ recordOutboxPushSuccess,
178
+ enqueueOutbound,
179
+ moveToDeadLetter,
180
+ collectDue,
181
+ };
182
+ }
@@ -0,0 +1,266 @@
1
+ import type { GatewayRequestHandlerOptions } from 'openclaw/plugin-sdk/core';
2
+ import type { BncrExtendedDiagnostics } from '../core/extended-diagnostics.ts';
3
+ import type {
4
+ BncrAccountRuntimeSnapshot,
5
+ buildIntegratedDiagnostics as buildIntegratedDiagnosticsFromRuntime,
6
+ } from '../core/status.ts';
7
+ import type { BncrDownlinkHealthSummary, BncrRoute } from '../core/types.ts';
8
+ import { OUTBOUND_FLUSH_REASON, OUTBOUND_FLUSH_TRIGGER } from '../messaging/outbound/reasons.ts';
9
+ import type { ReplyPayloadInput } from '../messaging/outbound/reply-enqueue.ts';
10
+ import type { BncrRuntimeFlags } from '../runtime/outbound-flags.ts';
11
+ import type { BncrVerifiedTarget } from './channel-runtime-types.ts';
12
+ import type { ConnectionQueueCounters, LeaseEventPayload } from './connection-handlers.ts';
13
+ import type { BncrActiveConnectionDebugEntry } from './connection-state.ts';
14
+
15
+ type DiagnosticsRuntimeStatusInput = Parameters<typeof buildIntegratedDiagnosticsFromRuntime>[0] & {
16
+ running: boolean | undefined;
17
+ channelRoot: string;
18
+ };
19
+
20
+ type DiagnosticsRuntimeStatusOverrides = {
21
+ running: boolean;
22
+ invalidOutboxSessionKeys?: number;
23
+ legacyAccountResidue?: number;
24
+ };
25
+
26
+ type ExtendedDiagnosticsOptions = {
27
+ runtimeStatusInput?: DiagnosticsRuntimeStatusInput;
28
+ integratedDiagnostics?: ReturnType<typeof buildIntegratedDiagnosticsFromRuntime>;
29
+ };
30
+
31
+ export function buildBridgeRuntimeStatusInput<T extends object>(args: {
32
+ runtimeStatusInput: T;
33
+ running: boolean | undefined;
34
+ channelRoot: string;
35
+ }): T & { running: boolean | undefined; channelRoot: string } {
36
+ return {
37
+ ...args.runtimeStatusInput,
38
+ running: args.running,
39
+ channelRoot: args.channelRoot,
40
+ };
41
+ }
42
+
43
+ export function buildFlushOnConnectArgs(accountId: string) {
44
+ return {
45
+ accountId,
46
+ trigger: OUTBOUND_FLUSH_TRIGGER.CONNECT,
47
+ reason: OUTBOUND_FLUSH_REASON.WS_ONLINE,
48
+ };
49
+ }
50
+
51
+ export function buildFlushOnActivityArgs(accountId: string) {
52
+ return {
53
+ accountId,
54
+ trigger: OUTBOUND_FLUSH_TRIGGER.ACTIVITY,
55
+ reason: OUTBOUND_FLUSH_REASON.ACTIVITY_HEARTBEAT,
56
+ };
57
+ }
58
+
59
+ export function buildBridgeStatusProjectionRuntime(args: {
60
+ buildAccountQueueCounters: (accountId: string) => ConnectionQueueCounters;
61
+ buildExtendedDiagnostics: (
62
+ accountId: string,
63
+ options?: ExtendedDiagnosticsOptions,
64
+ ) => BncrExtendedDiagnostics;
65
+ buildRuntimeFlags: (accountId?: string) => BncrRuntimeFlags;
66
+ buildRuntimeStatusInput: (
67
+ accountId: string,
68
+ overrides: DiagnosticsRuntimeStatusOverrides,
69
+ ) => Parameters<typeof buildIntegratedDiagnosticsFromRuntime>[0];
70
+ getAccountRuntimeSnapshot: (
71
+ accountId: string,
72
+ runtimeStatusInput: DiagnosticsRuntimeStatusInput,
73
+ ) => BncrAccountRuntimeSnapshot;
74
+ buildIntegratedDiagnostics: (
75
+ accountId: string,
76
+ runtimeStatusInput: DiagnosticsRuntimeStatusInput,
77
+ ) => ReturnType<typeof buildIntegratedDiagnosticsFromRuntime>;
78
+ buildDownlinkHealth: (accountId: string) => BncrDownlinkHealthSummary;
79
+ resolveChannelRoot: () => string;
80
+ }) {
81
+ return {
82
+ buildAccountQueueCounters: (accountId: string) => args.buildAccountQueueCounters(accountId),
83
+ buildExtendedDiagnostics: (accountId: string, options?: ExtendedDiagnosticsOptions) =>
84
+ args.buildExtendedDiagnostics(accountId, options),
85
+ buildRuntimeFlags: (accountId?: string) => args.buildRuntimeFlags(accountId),
86
+ buildRuntimeStatusInput: (accountId: string, overrides: DiagnosticsRuntimeStatusOverrides) =>
87
+ buildBridgeRuntimeStatusInput({
88
+ runtimeStatusInput: args.buildRuntimeStatusInput(accountId, overrides),
89
+ running: overrides.running,
90
+ channelRoot: args.resolveChannelRoot(),
91
+ }),
92
+ getAccountRuntimeSnapshot: (
93
+ accountId: string,
94
+ runtimeStatusInput: DiagnosticsRuntimeStatusInput,
95
+ ) => args.getAccountRuntimeSnapshot(accountId, runtimeStatusInput),
96
+ buildIntegratedDiagnostics: (
97
+ accountId: string,
98
+ runtimeStatusInput: DiagnosticsRuntimeStatusInput,
99
+ ) => args.buildIntegratedDiagnostics(accountId, runtimeStatusInput),
100
+ buildDownlinkHealth: (accountId: string) => args.buildDownlinkHealth(accountId),
101
+ };
102
+ }
103
+
104
+ export function buildBridgeDrainTriggers(args: {
105
+ flushPushQueueBestEffort: (args: { accountId: string; trigger: string; reason: string }) => void;
106
+ }) {
107
+ return {
108
+ flushOnConnect: (accountId: string) =>
109
+ args.flushPushQueueBestEffort(buildFlushOnConnectArgs(accountId)),
110
+ flushOnActivity: (accountId: string) =>
111
+ args.flushPushQueueBestEffort(buildFlushOnActivityArgs(accountId)),
112
+ };
113
+ }
114
+
115
+ export function buildBridgeLifecycleMarkers(args: {
116
+ markLastActivityAt: () => void;
117
+ markLastAckAt: () => void;
118
+ }) {
119
+ return {
120
+ markLastActivityAt: () => args.markLastActivityAt(),
121
+ markLastAckAt: () => args.markLastAckAt(),
122
+ };
123
+ }
124
+
125
+ export function buildInboundSurfaceActivityRuntime(args: {
126
+ markInboundGlobalActivity: () => void;
127
+ incrementInboundEvents: (accountId: string) => void;
128
+ setLastInboundByAccount: (accountId: string, at: number) => void;
129
+ markActivity: (accountId: string, at: number) => void;
130
+ }) {
131
+ return {
132
+ markLastInboundAt: (accountId: string) => {
133
+ args.markInboundGlobalActivity();
134
+ args.incrementInboundEvents(accountId);
135
+ },
136
+ setInboundActivity: (accountId: string, at: number) => {
137
+ args.setLastInboundByAccount(accountId, at);
138
+ args.markActivity(accountId, at);
139
+ },
140
+ };
141
+ }
142
+
143
+ export function buildInboundSurfaceConnectionRuntime(args: {
144
+ shouldIgnoreStaleEvent: (args: {
145
+ kind:
146
+ | 'inbound'
147
+ | 'activity'
148
+ | 'ack'
149
+ | 'file.init'
150
+ | 'file.chunk'
151
+ | 'file.complete'
152
+ | 'file.abort';
153
+ payload: LeaseEventPayload;
154
+ accountId: string;
155
+ connId: string;
156
+ clientId?: string;
157
+ }) => boolean;
158
+ observeLease: (
159
+ kind:
160
+ | 'connect'
161
+ | 'inbound'
162
+ | 'activity'
163
+ | 'ack'
164
+ | 'file.init'
165
+ | 'file.chunk'
166
+ | 'file.complete'
167
+ | 'file.abort',
168
+ payload: LeaseEventPayload,
169
+ ) => { stale: boolean };
170
+ matchesTransferOwner: (args: {
171
+ ownerConnId?: string;
172
+ ownerClientId?: string;
173
+ connId: string;
174
+ clientId?: string;
175
+ }) => boolean;
176
+ refreshAcceptedFileTransferLiveState: (args: {
177
+ accountId: string;
178
+ connId: string;
179
+ clientId?: string;
180
+ context: GatewayRequestHandlerOptions['context'];
181
+ }) => void;
182
+ refreshLiveConnectionState: (args: {
183
+ accountId: string;
184
+ connId: string;
185
+ clientId?: string;
186
+ outboundReady: boolean;
187
+ preferredForOutbound: boolean;
188
+ inboundOnly: boolean;
189
+ context: GatewayRequestHandlerOptions['context'];
190
+ }) => void;
191
+ isOnline: (accountId: string) => boolean;
192
+ hasRecentInboundReachability: (accountId: string) => boolean;
193
+ getActiveConnectionKey: (accountId: string) => string | null;
194
+ buildActiveConnectionDebugList: (accountId: string) => BncrActiveConnectionDebugEntry[];
195
+ }) {
196
+ return {
197
+ shouldIgnoreStaleEvent: (runtimeArgs: Parameters<typeof args.shouldIgnoreStaleEvent>[0]) =>
198
+ args.shouldIgnoreStaleEvent(runtimeArgs),
199
+ observeLease: (
200
+ kind: Parameters<typeof args.observeLease>[0],
201
+ payload: Parameters<typeof args.observeLease>[1],
202
+ ) => args.observeLease(kind, payload),
203
+ matchesTransferOwner: (runtimeArgs: Parameters<typeof args.matchesTransferOwner>[0]) =>
204
+ args.matchesTransferOwner(runtimeArgs),
205
+ refreshAcceptedFileTransferLiveState: (
206
+ runtimeArgs: Parameters<typeof args.refreshAcceptedFileTransferLiveState>[0],
207
+ ) => args.refreshAcceptedFileTransferLiveState(runtimeArgs),
208
+ refreshLiveConnectionState: (
209
+ runtimeArgs: Parameters<typeof args.refreshLiveConnectionState>[0],
210
+ ) => args.refreshLiveConnectionState(runtimeArgs),
211
+ isOnline: (accountId: string) => args.isOnline(accountId),
212
+ hasRecentInboundReachability: (accountId: string) =>
213
+ args.hasRecentInboundReachability(accountId),
214
+ getActiveConnectionKey: (accountId: string) => args.getActiveConnectionKey(accountId),
215
+ buildActiveConnectionDebugList: (accountId: string) =>
216
+ args.buildActiveConnectionDebugList(accountId),
217
+ };
218
+ }
219
+
220
+ export function buildChannelSendTargetRuntime(args: {
221
+ resolveVerifiedTarget: (to: string, accountId: string) => BncrVerifiedTarget;
222
+ rememberSessionRoute: (sessionKey: string, accountId: string, route: BncrRoute) => void;
223
+ enqueueFromReply: (args: {
224
+ accountId: string;
225
+ sessionKey: string;
226
+ route: BncrRoute;
227
+ payload: ReplyPayloadInput;
228
+ mediaLocalRoots?: readonly string[];
229
+ }) => Promise<void>;
230
+ }) {
231
+ return {
232
+ resolveVerifiedTarget: (to: string, accountId: string) =>
233
+ args.resolveVerifiedTarget(to, accountId),
234
+ rememberSessionRoute: (sessionKey: string, accountId: string, route: BncrRoute) =>
235
+ args.rememberSessionRoute(sessionKey, accountId, route),
236
+ enqueueFromReply: (runtimeArgs: Parameters<typeof args.enqueueFromReply>[0]) =>
237
+ args.enqueueFromReply(runtimeArgs),
238
+ };
239
+ }
240
+
241
+ export function buildFlushBestEffortError(args: {
242
+ accountId?: string;
243
+ trigger?: string;
244
+ reason?: string;
245
+ error: unknown;
246
+ asString: (value: unknown, fallback?: string) => string;
247
+ normalizeAccountId: (accountId: string) => string;
248
+ nextRetryCount: number;
249
+ retryLimit: number;
250
+ }) {
251
+ const accountId = args.accountId ? args.normalizeAccountId(args.accountId) : '';
252
+ const reason = args.asString(args.reason || args.trigger || 'flush-error');
253
+ const err =
254
+ args.error && typeof args.error === 'object' && 'message' in args.error
255
+ ? args.asString((args.error as { message?: unknown }).message || 'flush-error')
256
+ : args.asString(args.error || 'flush-error');
257
+ const willRetry = args.nextRetryCount <= args.retryLimit;
258
+
259
+ return {
260
+ accountId,
261
+ reason,
262
+ err,
263
+ willRetry,
264
+ retryDisplay: willRetry ? args.nextRetryCount : 'false',
265
+ };
266
+ }
@@ -0,0 +1,104 @@
1
+ import type { RegisterDriftSnapshot, RegisterTraceEntry } from '../core/register-trace.ts';
2
+ import type { BncrConnection } from '../core/types.ts';
3
+
4
+ export function buildRegisterRuntimeSnapshot(args: {
5
+ bridgeId: string;
6
+ gatewayPid: number;
7
+ pluginVersion: string | null;
8
+ pluginSource: string | null;
9
+ lastApiInstanceId: string | null;
10
+ lastRegistryFingerprint: string | null;
11
+ registerCount: number;
12
+ firstRegisterAt: number | null;
13
+ lastRegisterAt: number | null;
14
+ lastApiRebindAt: number | null;
15
+ apiGeneration: number;
16
+ registerTraceRecent: RegisterTraceEntry[];
17
+ lastDriftSnapshot: RegisterDriftSnapshot | null;
18
+ }) {
19
+ return {
20
+ bridgeId: args.bridgeId,
21
+ gatewayPid: args.gatewayPid,
22
+ pluginVersion: args.pluginVersion,
23
+ pluginSource: args.pluginSource,
24
+ lastApiInstanceId: args.lastApiInstanceId,
25
+ lastRegistryFingerprint: args.lastRegistryFingerprint,
26
+ registerCount: args.registerCount,
27
+ firstRegisterAt: args.firstRegisterAt,
28
+ lastRegisterAt: args.lastRegisterAt,
29
+ lastApiRebindAt: args.lastApiRebindAt,
30
+ apiGeneration: args.apiGeneration,
31
+ registerTraceRecent: args.registerTraceRecent,
32
+ lastDriftSnapshot: args.lastDriftSnapshot ?? null,
33
+ };
34
+ }
35
+
36
+ export function buildConnectionRuntimeSnapshot(args: {
37
+ lastGatewayContextAt: number | null;
38
+ primaryLeaseId: string | null;
39
+ connectionEpoch: number;
40
+ acceptedConnections: number;
41
+ lastConnectAt: number | null;
42
+ lastDisconnectAt: number | null;
43
+ lastActivityAtGlobal: number | null;
44
+ lastInboundAtGlobal: number | null;
45
+ lastAckAtGlobal: number | null;
46
+ recentConnections: Map<
47
+ string,
48
+ { epoch: number; connectedAt: number; lastActivityAt: number | null; isPrimary: boolean }
49
+ >;
50
+ }) {
51
+ return {
52
+ lastGatewayContextAt: args.lastGatewayContextAt,
53
+ primaryLeaseId: args.primaryLeaseId,
54
+ connectionEpoch: args.connectionEpoch,
55
+ acceptedConnections: args.acceptedConnections,
56
+ lastConnectAt: args.lastConnectAt,
57
+ lastDisconnectAt: args.lastDisconnectAt,
58
+ lastActivityAtGlobal: args.lastActivityAtGlobal,
59
+ lastInboundAtGlobal: args.lastInboundAtGlobal,
60
+ lastAckAtGlobal: args.lastAckAtGlobal,
61
+ recentConnections: args.recentConnections,
62
+ };
63
+ }
64
+
65
+ export function buildOutboundRuntimeSnapshot(args: {
66
+ outboundEnqueueCountByAccount: Map<string, number>;
67
+ lastOutboundEnqueueAtByAccount: Map<string, number>;
68
+ }) {
69
+ return {
70
+ outboundEnqueueCountByAccount: args.outboundEnqueueCountByAccount,
71
+ lastOutboundEnqueueAtByAccount: args.lastOutboundEnqueueAtByAccount,
72
+ };
73
+ }
74
+
75
+ export function buildStatusWorkerLastEventAt(args: {
76
+ accountId: string;
77
+ previous: { lastEventAt?: number | null };
78
+ lastActivityByAccount: ReadonlyMap<string, number>;
79
+ lastInboundByAccount: ReadonlyMap<string, number>;
80
+ lastOutboundByAccount: ReadonlyMap<string, number>;
81
+ }) {
82
+ return (
83
+ args.lastActivityByAccount.get(args.accountId) ||
84
+ args.lastInboundByAccount.get(args.accountId) ||
85
+ args.lastOutboundByAccount.get(args.accountId) ||
86
+ args.previous?.lastEventAt ||
87
+ null
88
+ );
89
+ }
90
+
91
+ export function buildStatusWorkerActiveConnections(args: {
92
+ accountId: string;
93
+ connections: Iterable<BncrConnection>;
94
+ }) {
95
+ return Array.from(args.connections)
96
+ .filter((connection) => connection.accountId === args.accountId)
97
+ .map((connection) => ({
98
+ connId: connection.connId,
99
+ clientId: connection.clientId,
100
+ inboundOnly: connection.inboundOnly === true,
101
+ outboundReady: connection.outboundReady === true,
102
+ preferredForOutbound: connection.preferredForOutbound === true,
103
+ }));
104
+ }
@@ -0,0 +1,8 @@
1
+ import { buildOpenClawChannelRuntimeSurfaceDiagnostics } from '../openclaw/runtime-surface.ts';
2
+
3
+ export function createBncrBridgeRuntimeSurfaceFacade(runtime: { getApi: () => unknown }) {
4
+ return {
5
+ buildRuntimeSurfaceDiagnostics: () =>
6
+ buildOpenClawChannelRuntimeSurfaceDiagnostics(runtime.getApi()),
7
+ };
8
+ }
@@ -0,0 +1,76 @@
1
+ import type {
2
+ BncrAccountRuntimeSnapshot,
3
+ buildIntegratedDiagnostics as buildIntegratedDiagnosticsFromRuntime,
4
+ } from '../core/status.ts';
5
+ import type { BncrAckObservability } from '../core/types.ts';
6
+
7
+ type RuntimeStatusInput = Parameters<typeof buildIntegratedDiagnosticsFromRuntime>[0];
8
+ type RuntimeStatusMeta = BncrAccountRuntimeSnapshot['meta'];
9
+
10
+ export function buildBncrStatusProjectionRuntime(runtime: {
11
+ buildRuntimeStatusInput: (
12
+ accountId: string,
13
+ overrides?: Partial<RuntimeStatusInput>,
14
+ ) => RuntimeStatusInput;
15
+ buildStatusMeta: (accountId: string) => RuntimeStatusMeta;
16
+ getAccountRuntimeSnapshot: (
17
+ accountId: string,
18
+ runtimeStatusInput: RuntimeStatusInput,
19
+ ) => BncrAccountRuntimeSnapshot;
20
+ buildStatusHeadline: (accountId: string) => string;
21
+ getStatusHeadline: (accountId: string) => string;
22
+ getChannelSummary: (defaultAccountId: string) => Record<string, unknown>;
23
+ }) {
24
+ return { ...runtime };
25
+ }
26
+
27
+ export function buildBncrAckDiagnosticsRuntime(runtime: {
28
+ buildRuntimeAckObservability: (accountId: string) => BncrAckObservability;
29
+ buildRuntimeAckStrategy: (ackObservability: BncrAckObservability) => unknown;
30
+ }) {
31
+ return { ...runtime };
32
+ }
33
+
34
+ export function createBncrBridgeStatusFacade(runtime: {
35
+ statusProjection: ReturnType<typeof buildBncrStatusProjectionRuntime>;
36
+ ackDiagnostics: ReturnType<typeof buildBncrAckDiagnosticsRuntime>;
37
+ }) {
38
+ const buildRuntimeStatusInput = (
39
+ accountId: string,
40
+ overrides: Partial<RuntimeStatusInput> = {},
41
+ ) => runtime.statusProjection.buildRuntimeStatusInput(accountId, overrides);
42
+
43
+ const buildStatusMeta = (accountId: string) =>
44
+ runtime.statusProjection.buildStatusMeta(accountId);
45
+
46
+ const getAccountRuntimeSnapshot = (
47
+ accountId: string,
48
+ runtimeStatusInput = buildRuntimeStatusInput(accountId, { running: true }),
49
+ ) => runtime.statusProjection.getAccountRuntimeSnapshot(accountId, runtimeStatusInput);
50
+
51
+ const buildStatusHeadline = (accountId: string) =>
52
+ runtime.statusProjection.buildStatusHeadline(accountId);
53
+
54
+ const getStatusHeadline = (accountId: string) =>
55
+ runtime.statusProjection.getStatusHeadline(accountId);
56
+
57
+ const getChannelSummary = (defaultAccountId: string) =>
58
+ runtime.statusProjection.getChannelSummary(defaultAccountId);
59
+
60
+ const buildRuntimeAckObservability = (accountId: string) =>
61
+ runtime.ackDiagnostics.buildRuntimeAckObservability(accountId);
62
+
63
+ const buildRuntimeAckStrategy = (ackObservability: BncrAckObservability) =>
64
+ runtime.ackDiagnostics.buildRuntimeAckStrategy(ackObservability);
65
+
66
+ return {
67
+ buildRuntimeStatusInput,
68
+ buildStatusMeta,
69
+ getAccountRuntimeSnapshot,
70
+ buildStatusHeadline,
71
+ getStatusHeadline,
72
+ getChannelSummary,
73
+ buildRuntimeAckObservability,
74
+ buildRuntimeAckStrategy,
75
+ };
76
+ }