@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 {
2
+ buildCanonicalBncrSessionKey,
3
+ formatDisplayScope,
4
+ parseRouteFromDisplayScope,
5
+ parseStrictBncrSessionKey,
6
+ routeKey,
7
+ } from '../core/targets.ts';
8
+ import type { BncrRoute } from '../core/types.ts';
9
+ import { getOpenClawRuntimeConfigOrDefault } from '../openclaw/config-runtime.ts';
10
+ import type { BncrChannelConfigRoot } from './channel-runtime-types.ts';
11
+
12
+ function asString(v: unknown, fallback = ''): string {
13
+ if (typeof v === 'string') return v;
14
+ if (v == null) return fallback;
15
+ return String(v);
16
+ }
17
+
18
+ export function createBncrTargetRuntime(runtime: {
19
+ api: Parameters<typeof getOpenClawRuntimeConfigOrDefault>[0];
20
+ channelId: string;
21
+ canonicalAgentId: string | null;
22
+ now: () => number;
23
+ normalizeAccountId: (accountId: string) => string;
24
+ sessionRoutes: Map<string, { accountId: string; route: BncrRoute; updatedAt: number }>;
25
+ routeAliases: Map<string, { accountId: string; route: BncrRoute; updatedAt: number }>;
26
+ lastSessionByAccount: Map<string, { sessionKey: string; scope: string; updatedAt: number }>;
27
+ markActivity: (accountId: string, at?: number) => void;
28
+ scheduleSave: () => void;
29
+ logInfo: (scope: string | undefined, message: string, options?: { debugOnly?: boolean }) => void;
30
+ logWarn: (scope: string | undefined, message: string, options?: { debugOnly?: boolean }) => void;
31
+ ensureCanonicalAgentId: (args: {
32
+ cfg: BncrChannelConfigRoot;
33
+ accountId: string;
34
+ channelId: string;
35
+ peer: { kind: 'direct'; id: string };
36
+ }) => string;
37
+ }) {
38
+ function rememberSessionRoute(sessionKey: string, accountId: string, route: BncrRoute) {
39
+ const key = asString(sessionKey).trim();
40
+ if (!key) return;
41
+
42
+ const acc = runtime.normalizeAccountId(accountId);
43
+ const t = runtime.now();
44
+ const info = { accountId: acc, route, updatedAt: t };
45
+
46
+ runtime.sessionRoutes.set(key, info);
47
+ runtime.routeAliases.set(routeKey(acc, route), info);
48
+ runtime.lastSessionByAccount.set(acc, {
49
+ sessionKey: key,
50
+ scope: formatDisplayScope(route),
51
+ updatedAt: t,
52
+ });
53
+ runtime.markActivity(acc, t);
54
+ runtime.scheduleSave();
55
+ }
56
+
57
+ function resolveRouteBySession(sessionKey: string, accountId: string): BncrRoute | null {
58
+ const key = asString(sessionKey).trim();
59
+ const hit = runtime.sessionRoutes.get(key);
60
+ if (
61
+ hit &&
62
+ runtime.normalizeAccountId(accountId) === runtime.normalizeAccountId(hit.accountId)
63
+ ) {
64
+ return hit.route;
65
+ }
66
+
67
+ const parsed = parseStrictBncrSessionKey(key);
68
+ if (!parsed) return null;
69
+
70
+ const alias = runtime.routeAliases.get(
71
+ routeKey(runtime.normalizeAccountId(accountId), parsed.route),
72
+ );
73
+ return alias?.route || parsed.route;
74
+ }
75
+
76
+ function resolveVerifiedTarget(
77
+ rawTarget: string,
78
+ accountId: string,
79
+ ): { sessionKey: string; route: BncrRoute; displayScope: string } {
80
+ const acc = runtime.normalizeAccountId(accountId);
81
+ const raw = asString(rawTarget).trim();
82
+ if (!raw) throw new Error('bncr invalid target(empty)');
83
+
84
+ runtime.logInfo('target', `incoming raw=${raw} accountId=${acc}`, { debugOnly: true });
85
+
86
+ let route: BncrRoute | null = null;
87
+
88
+ const strict = parseStrictBncrSessionKey(raw);
89
+ if (strict) {
90
+ route = strict.route;
91
+ } else {
92
+ route = parseRouteFromDisplayScope(raw) || resolveRouteBySession(raw, acc);
93
+ }
94
+
95
+ if (!route) {
96
+ runtime.logWarn(
97
+ 'target',
98
+ `invalid raw=${raw} accountId=${acc} reason=unparseable-or-unknown standardTo=Bncr:<platform>:<groupId>:<userId>|Bncr:<platform>:<userId> standardSessionKey=agent:<agentId>:bncr:direct:<hex(scope)>`,
99
+ { debugOnly: true },
100
+ );
101
+ throw new Error(
102
+ `bncr invalid target(standard: Bncr:<platform>:<groupId>:<userId> | Bncr:<platform>:<userId>): ${raw}`,
103
+ );
104
+ }
105
+
106
+ const canonicalAgentId =
107
+ runtime.canonicalAgentId ||
108
+ runtime.ensureCanonicalAgentId({
109
+ cfg: getOpenClawRuntimeConfigOrDefault<BncrChannelConfigRoot>(runtime.api, {}),
110
+ accountId: acc,
111
+ channelId: runtime.channelId,
112
+ peer: { kind: 'direct', id: route.groupId === '0' ? route.userId : route.groupId },
113
+ });
114
+ const verified = {
115
+ sessionKey: buildCanonicalBncrSessionKey(route, canonicalAgentId),
116
+ route,
117
+ displayScope: formatDisplayScope(route),
118
+ };
119
+
120
+ runtime.logInfo(
121
+ 'target',
122
+ `canonical raw=${raw} accountId=${acc} verified=${JSON.stringify(verified)}`,
123
+ { debugOnly: true },
124
+ );
125
+
126
+ runtime.lastSessionByAccount.set(acc, {
127
+ sessionKey: verified.sessionKey,
128
+ scope: verified.displayScope,
129
+ updatedAt: runtime.now(),
130
+ });
131
+ runtime.scheduleSave();
132
+
133
+ return verified;
134
+ }
135
+
136
+ return {
137
+ rememberSessionRoute,
138
+ resolveRouteBySession,
139
+ resolveVerifiedTarget,
140
+ };
141
+ }
@@ -0,0 +1,130 @@
1
+ import type {
2
+ BncrAckObservability,
3
+ BncrAckStrategy,
4
+ BncrRoute,
5
+ BncrRuntimeLastSession,
6
+ OutboxEntry,
7
+ } from '../core/types.ts';
8
+ import type { MediaDedupeCacheEntry } from '../messaging/outbound/media-dedupe.ts';
9
+ import type { getOpenClawRuntimeConfigOrDefault } from '../openclaw/config-runtime.ts';
10
+ import type { BncrChannelConfigRoot } from './channel-runtime-types.ts';
11
+ import { createBncrMediaDedupeRuntime } from './media-dedupe-runtime.ts';
12
+ import { createBncrStatusRuntime } from './status-runtime.ts';
13
+ import { createBncrTargetRuntime } from './target-runtime.ts';
14
+
15
+ export function createBncrTargetStatusRuntimeGroup(runtime: {
16
+ api: Parameters<typeof getOpenClawRuntimeConfigOrDefault>[0];
17
+ channelId: string;
18
+ canonicalAgentId: string | null;
19
+ getPluginRoot: () => string | null;
20
+ startedAt: number;
21
+ debugVerbose: boolean;
22
+ adaptiveAckTimeoutEnabled: boolean;
23
+ defaultMessageAckTimeoutMs: number;
24
+ fileAckTimeoutMs: number;
25
+ maxAckTimeoutMs: number;
26
+ now: () => number;
27
+ normalizeAccountId: (accountId: string) => string;
28
+ sessionRoutes: Map<string, { accountId: string; route: BncrRoute; updatedAt: number }>;
29
+ routeAliases: Map<string, { accountId: string; route: BncrRoute; updatedAt: number }>;
30
+ lastSessionByAccount: Map<string, BncrRuntimeLastSession>;
31
+ markActivity: (accountId: string, at?: number) => void;
32
+ scheduleSave: () => void;
33
+ logInfo: (scope: string | undefined, message: string, options?: { debugOnly?: boolean }) => void;
34
+ logWarn: (scope: string | undefined, message: string, options?: { debugOnly?: boolean }) => void;
35
+ ensureCanonicalAgentId: (args: {
36
+ cfg: BncrChannelConfigRoot;
37
+ accountId: string;
38
+ channelId: string;
39
+ peer: { kind: 'direct'; id: string };
40
+ }) => string;
41
+ recentMediaDedupeBySession: Map<string, Map<string, MediaDedupeCacheEntry>>;
42
+ resolveMessageAckTimeoutMs: (accountId?: string) => number;
43
+ isOnline: (accountId: string) => boolean;
44
+ outboxValues: () => Iterable<OutboxEntry>;
45
+ deadLetterEntries: () => OutboxEntry[];
46
+ sessionRouteValues: () => Iterable<{ accountId: string }>;
47
+ countInvalidOutboxSessionKeys: (accountId: string) => number;
48
+ countLegacyAccountResidue: (accountId: string) => number;
49
+ connectEventsByAccount: Map<string, number>;
50
+ inboundEventsByAccount: Map<string, number>;
51
+ activityEventsByAccount: Map<string, number>;
52
+ ackEventsByAccount: Map<string, number>;
53
+ activeConnectionCount: (accountId: string) => number;
54
+ lastActivityByAccount: Map<string, number>;
55
+ lastInboundByAccount: Map<string, number>;
56
+ lastOutboundByAccount: Map<string, number>;
57
+ buildRuntimeAckObservability: (accountId: string) => BncrAckObservability;
58
+ buildRuntimeAckStrategy: (ackObservability: BncrAckObservability) => BncrAckStrategy;
59
+ lastAckOkByAccount: Map<string, number>;
60
+ lastAckTimeoutByAccount: Map<string, number>;
61
+ getAckTimeoutCount: (accountId: string) => number;
62
+ getAccountPendingOutboxEntries: (accountId: string) => OutboxEntry[];
63
+ getAccountDeadLetterEntries: (accountId: string) => OutboxEntry[];
64
+ connectionsValues: () => Iterable<{ lastSeenAt: number }>;
65
+ connectTtlMs: number;
66
+ }) {
67
+ const targetRuntime = createBncrTargetRuntime({
68
+ api: runtime.api,
69
+ channelId: runtime.channelId,
70
+ canonicalAgentId: runtime.canonicalAgentId,
71
+ now: runtime.now,
72
+ normalizeAccountId: runtime.normalizeAccountId,
73
+ sessionRoutes: runtime.sessionRoutes,
74
+ routeAliases: runtime.routeAliases,
75
+ lastSessionByAccount: runtime.lastSessionByAccount,
76
+ markActivity: runtime.markActivity,
77
+ scheduleSave: runtime.scheduleSave,
78
+ logInfo: runtime.logInfo,
79
+ logWarn: runtime.logWarn,
80
+ ensureCanonicalAgentId: runtime.ensureCanonicalAgentId,
81
+ });
82
+
83
+ const mediaDedupeRuntime = createBncrMediaDedupeRuntime({
84
+ now: runtime.now,
85
+ recentMediaDedupeBySession: runtime.recentMediaDedupeBySession,
86
+ });
87
+
88
+ const statusRuntime = createBncrStatusRuntime({
89
+ api: runtime.api,
90
+ getPluginRoot: runtime.getPluginRoot,
91
+ startedAt: runtime.startedAt,
92
+ debugVerbose: runtime.debugVerbose,
93
+ adaptiveAckTimeoutEnabled: runtime.adaptiveAckTimeoutEnabled,
94
+ defaultMessageAckTimeoutMs: runtime.defaultMessageAckTimeoutMs,
95
+ fileAckTimeoutMs: runtime.fileAckTimeoutMs,
96
+ maxAckTimeoutMs: runtime.maxAckTimeoutMs,
97
+ now: runtime.now,
98
+ resolveMessageAckTimeoutMs: runtime.resolveMessageAckTimeoutMs,
99
+ isOnline: runtime.isOnline,
100
+ outboxValues: runtime.outboxValues,
101
+ deadLetterEntries: runtime.deadLetterEntries,
102
+ sessionRouteValues: runtime.sessionRouteValues,
103
+ countInvalidOutboxSessionKeys: runtime.countInvalidOutboxSessionKeys,
104
+ countLegacyAccountResidue: runtime.countLegacyAccountResidue,
105
+ connectEventsByAccount: runtime.connectEventsByAccount,
106
+ inboundEventsByAccount: runtime.inboundEventsByAccount,
107
+ activityEventsByAccount: runtime.activityEventsByAccount,
108
+ ackEventsByAccount: runtime.ackEventsByAccount,
109
+ activeConnectionCount: runtime.activeConnectionCount,
110
+ lastSessionByAccount: runtime.lastSessionByAccount,
111
+ lastActivityByAccount: runtime.lastActivityByAccount,
112
+ lastInboundByAccount: runtime.lastInboundByAccount,
113
+ lastOutboundByAccount: runtime.lastOutboundByAccount,
114
+ buildRuntimeAckObservability: runtime.buildRuntimeAckObservability,
115
+ buildRuntimeAckStrategy: runtime.buildRuntimeAckStrategy,
116
+ lastAckOkByAccount: runtime.lastAckOkByAccount,
117
+ lastAckTimeoutByAccount: runtime.lastAckTimeoutByAccount,
118
+ getAckTimeoutCount: runtime.getAckTimeoutCount,
119
+ getAccountPendingOutboxEntries: runtime.getAccountPendingOutboxEntries,
120
+ getAccountDeadLetterEntries: runtime.getAccountDeadLetterEntries,
121
+ connectionsValues: runtime.connectionsValues,
122
+ connectTtlMs: runtime.connectTtlMs,
123
+ });
124
+
125
+ return {
126
+ targetRuntime,
127
+ mediaDedupeRuntime,
128
+ statusRuntime,
129
+ };
130
+ }
@@ -0,0 +1,82 @@
1
+ import type { BncrConnection } from '../core/types.ts';
2
+
3
+ export function createBncrTransientStateRuntime(runtime: {
4
+ now: () => number;
5
+ connectTtlMs: number;
6
+ fileTransferKeepMs: number;
7
+ fileTransferTerminalKeepMs: number;
8
+ fileTransferAckTtlMs: number;
9
+ connections: Map<string, BncrConnection>;
10
+ activeConnectionByAccount: Map<string, string>;
11
+ recentInbound: Map<string, number>;
12
+ fileSendTransfers: Map<string, { status: string; startedAt: number; terminalAt?: number }>;
13
+ fileRecvTransfers: Map<string, { status: string; startedAt: number; terminalAt?: number }>;
14
+ earlyFileAcks: Map<string, { at: number }>;
15
+ logInfo: (scope: string | undefined, message: string, options?: { debugOnly?: boolean }) => void;
16
+ bridgeId: string;
17
+ }) {
18
+ function cleanupFileTransfers() {
19
+ const t = runtime.now();
20
+ const keepMsForTransfer = (st: { status: string; startedAt: number; terminalAt?: number }) => {
21
+ const startedAt = Number.isFinite(st.startedAt) ? st.startedAt : t;
22
+ if (st.status === 'completed' || st.status === 'aborted') {
23
+ return {
24
+ since: Number.isFinite(st.terminalAt) ? (st.terminalAt as number) : startedAt,
25
+ keepMs: runtime.fileTransferTerminalKeepMs,
26
+ };
27
+ }
28
+ return { since: startedAt, keepMs: runtime.fileTransferKeepMs };
29
+ };
30
+
31
+ for (const [id, st] of runtime.fileSendTransfers.entries()) {
32
+ const keep = keepMsForTransfer(st);
33
+ if (t - keep.since > keep.keepMs) runtime.fileSendTransfers.delete(id);
34
+ }
35
+ for (const [id, st] of runtime.fileRecvTransfers.entries()) {
36
+ const keep = keepMsForTransfer(st);
37
+ if (t - keep.since > keep.keepMs) runtime.fileRecvTransfers.delete(id);
38
+ }
39
+ for (const [key, ack] of runtime.earlyFileAcks.entries()) {
40
+ if (t - ack.at > runtime.fileTransferAckTtlMs) runtime.earlyFileAcks.delete(key);
41
+ }
42
+ }
43
+
44
+ function gcTransientState() {
45
+ const t = runtime.now();
46
+ const staleBefore = t - runtime.connectTtlMs * 2;
47
+
48
+ for (const [key, c] of runtime.connections.entries()) {
49
+ if (c.lastSeenAt < staleBefore) {
50
+ runtime.logInfo(
51
+ 'connection',
52
+ `gc ${JSON.stringify({
53
+ bridge: runtime.bridgeId,
54
+ key,
55
+ accountId: c.accountId,
56
+ connId: c.connId,
57
+ clientId: c.clientId,
58
+ lastSeenAt: c.lastSeenAt,
59
+ staleBefore,
60
+ })}`,
61
+ { debugOnly: true },
62
+ );
63
+ runtime.connections.delete(key);
64
+ if (runtime.activeConnectionByAccount.get(c.accountId) === key) {
65
+ runtime.activeConnectionByAccount.delete(c.accountId);
66
+ }
67
+ }
68
+ }
69
+
70
+ const dedupWindowMs = 90_000;
71
+ for (const [key, ts] of runtime.recentInbound.entries()) {
72
+ if (t - ts > dedupWindowMs) runtime.recentInbound.delete(key);
73
+ }
74
+
75
+ cleanupFileTransfers();
76
+ }
77
+
78
+ return {
79
+ gcTransientState,
80
+ cleanupFileTransfers,
81
+ };
82
+ }
@@ -1,3 +1,5 @@
1
+ import type { BncrAckObservability, BncrAckStrategy } from '../core/types.ts';
2
+
1
3
  type ComputeBncrRecommendedAckTimeoutArgs = {
2
4
  lateAckOkCount: number;
3
5
  recentAckTimeoutCount: number;
@@ -83,7 +85,7 @@ function asString(value: unknown, fallback = '') {
83
85
  }
84
86
 
85
87
  export function buildBncrRuntimeAckStrategy(args: {
86
- ackObservability: Record<string, any>;
88
+ ackObservability: BncrAckObservability;
87
89
  defaultAckTimeoutMs: number;
88
90
  maxAckTimeoutMs: number;
89
91
  }) {
@@ -101,7 +103,7 @@ export function buildBncrRuntimeAckStrategy(args: {
101
103
  lastLateAckAgeMs: ackObservability.lastLateAckAgeMs ?? null,
102
104
  lateAckObservationTtlMs: ackObservability.lateAckObservationTtlMs ?? null,
103
105
  recovered: ackObservability.adaptiveAckRecovered === true,
104
- };
106
+ } satisfies BncrAckStrategy;
105
107
  }
106
108
 
107
109
  export function buildBncrRuntimeAckObservability(args: {
@@ -165,5 +167,5 @@ export function buildBncrRuntimeAckObservability(args: {
165
167
  currentAckTimeoutMs: args.currentAckTimeoutMs,
166
168
  recommendedAckTimeoutMs: ackTimeoutDecision.timeoutMs,
167
169
  recommendedAckTimeoutReason: ackTimeoutDecision.reason,
168
- };
170
+ } satisfies BncrAckObservability;
169
171
  }
@@ -1,7 +1,23 @@
1
1
  import { BNCR_DEFAULT_ACCOUNT_ID, CHANNEL_ID, normalizeAccountId } from '../core/accounts.ts';
2
2
  import { getOpenClawRuntimeConfig } from '../openclaw/config-runtime.ts';
3
+ import type {
4
+ BncrChannelConfigRoot,
5
+ BncrChannelConfigSection,
6
+ BncrStatusRuntimeSnapshot,
7
+ } from '../plugin/channel-runtime-types.ts';
3
8
 
4
9
  type RuntimeApiHolder = { api: unknown };
10
+ type BncrAckConfigAccount = { outboundRequireAck?: boolean };
11
+ type BncrAckConfigChannel = BncrChannelConfigSection & {
12
+ accounts?: Record<string, BncrAckConfigAccount | undefined>;
13
+ };
14
+
15
+ type OpenClawConfigApi = Parameters<typeof getOpenClawRuntimeConfig>[0];
16
+
17
+ function resolveBncrAckConfigChannel(api: unknown): BncrAckConfigChannel | undefined {
18
+ const cfg = getOpenClawRuntimeConfig(api as OpenClawConfigApi) as BncrChannelConfigRoot;
19
+ return cfg?.channels?.[CHANNEL_ID] as BncrAckConfigChannel | undefined;
20
+ }
5
21
 
6
22
  type ResolveOutboundAckRequiredArgs = RuntimeApiHolder & {
7
23
  accountId?: string;
@@ -9,11 +25,10 @@ type ResolveOutboundAckRequiredArgs = RuntimeApiHolder & {
9
25
 
10
26
  export function resolveBncrOutboundAckRequired(args: ResolveOutboundAckRequiredArgs) {
11
27
  try {
12
- const cfg = getOpenClawRuntimeConfig(args.api as any);
13
- const channelCfg = (cfg as any)?.channels?.[CHANNEL_ID];
28
+ const channelCfg = resolveBncrAckConfigChannel(args.api);
14
29
  const accountCfg =
15
30
  args.accountId && channelCfg?.accounts && typeof channelCfg.accounts === 'object'
16
- ? (channelCfg.accounts as Record<string, any>)[normalizeAccountId(args.accountId)]
31
+ ? channelCfg.accounts[normalizeAccountId(args.accountId)]
17
32
  : null;
18
33
  const scoped = accountCfg?.outboundRequireAck;
19
34
  const global = channelCfg?.outboundRequireAck;
@@ -37,9 +52,9 @@ type BuildBncrRuntimeFlagsArgs = RuntimeApiHolder & {
37
52
  export function buildBncrRuntimeStatusInput(args: {
38
53
  accountId: string;
39
54
  connected: boolean;
40
- queueSnapshot: Record<string, any>;
41
- eventCounters: Record<string, any>;
42
- activitySnapshot: Record<string, any>;
55
+ queueSnapshot: BncrStatusRuntimeSnapshot;
56
+ eventCounters: BncrStatusRuntimeSnapshot;
57
+ activitySnapshot: BncrStatusRuntimeSnapshot;
43
58
  startedAt: number | null;
44
59
  running?: boolean;
45
60
  channelRoot: string;
@@ -59,8 +74,7 @@ export function buildBncrRuntimeStatusInput(args: {
59
74
  export function buildBncrRuntimeFlags(args: BuildBncrRuntimeFlagsArgs) {
60
75
  let ackPolicySource: 'channel' | 'default' = 'default';
61
76
  try {
62
- const cfg = getOpenClawRuntimeConfig(args.api as any);
63
- const global = (cfg as any)?.channels?.[CHANNEL_ID]?.outboundRequireAck;
77
+ const global = resolveBncrAckConfigChannel(args.api)?.outboundRequireAck;
64
78
  if (typeof global === 'boolean') ackPolicySource = 'channel';
65
79
  } catch {
66
80
  // keep default source
@@ -79,3 +93,5 @@ export function buildBncrRuntimeFlags(args: BuildBncrRuntimeFlagsArgs) {
79
93
  debugVerbose: args.debugVerbose,
80
94
  };
81
95
  }
96
+
97
+ export type BncrRuntimeFlags = ReturnType<typeof buildBncrRuntimeFlags>;
@@ -1,5 +1,34 @@
1
1
  import { normalizeAccountId } from '../core/accounts.ts';
2
- import type { OutboxEntry } from '../core/types.ts';
2
+ import type { BncrRuntimeLastSession, OutboxEntry } from '../core/types.ts';
3
+
4
+ export type RuntimeQueueSnapshot = {
5
+ pending: number;
6
+ deadLetter: number;
7
+ sessionRoutesCount: number;
8
+ invalidOutboxSessionKeys: number;
9
+ legacyAccountResidue: number;
10
+ };
11
+
12
+ export type RuntimeEventCounters = {
13
+ connectEvents: number;
14
+ inboundEvents: number;
15
+ activityEvents: number;
16
+ ackEvents: number;
17
+ };
18
+
19
+ export type RuntimeActivitySnapshot = {
20
+ activeConnections: number;
21
+ lastSession: BncrRuntimeLastSession | null;
22
+ lastActivityAt: number | null;
23
+ lastInboundAt: number | null;
24
+ lastOutboundAt: number | null;
25
+ };
26
+
27
+ export type RuntimeStatusSnapshots = {
28
+ queueSnapshot: RuntimeQueueSnapshot;
29
+ eventCounters: RuntimeEventCounters;
30
+ activitySnapshot: RuntimeActivitySnapshot;
31
+ };
3
32
 
4
33
  function getCounter(map: Map<string, number>, accountId: string): number {
5
34
  return map.get(normalizeAccountId(accountId)) || 0;
@@ -12,7 +41,7 @@ export function buildRuntimeQueueSnapshot(args: {
12
41
  sessionRouteEntries: Iterable<{ accountId: string }>;
13
42
  countInvalidOutboxSessionKeys: (accountId: string) => number;
14
43
  countLegacyAccountResidue: (accountId: string) => number;
15
- }) {
44
+ }): RuntimeQueueSnapshot {
16
45
  const accountId = normalizeAccountId(args.accountId);
17
46
  const pending = Array.from(args.outboxEntries).filter((v) => v.accountId === accountId).length;
18
47
  const deadLetter = Array.from(args.deadLetterEntries).filter(
@@ -36,7 +65,7 @@ export function buildRuntimeEventCounters(args: {
36
65
  inboundEventsByAccount: Map<string, number>;
37
66
  activityEventsByAccount: Map<string, number>;
38
67
  ackEventsByAccount: Map<string, number>;
39
- }) {
68
+ }): RuntimeEventCounters {
40
69
  const accountId = normalizeAccountId(args.accountId);
41
70
  return {
42
71
  connectEvents: getCounter(args.connectEventsByAccount, accountId),
@@ -53,11 +82,11 @@ function nullableMapNumber(map: Map<string, number>, key: string): number | null
53
82
  export function buildRuntimeActivitySnapshot(args: {
54
83
  accountId: string;
55
84
  activeConnectionCount: (accountId: string) => number;
56
- lastSessionByAccount: Map<string, { sessionKey: string; scope: string; updatedAt: number }>;
85
+ lastSessionByAccount: Map<string, BncrRuntimeLastSession>;
57
86
  lastActivityByAccount: Map<string, number>;
58
87
  lastInboundByAccount: Map<string, number>;
59
88
  lastOutboundByAccount: Map<string, number>;
60
- }) {
89
+ }): RuntimeActivitySnapshot {
61
90
  const accountId = normalizeAccountId(args.accountId);
62
91
  return {
63
92
  activeConnections: args.activeConnectionCount(accountId),
@@ -80,11 +109,11 @@ export function buildRuntimeStatusSnapshots(args: {
80
109
  activityEventsByAccount: Map<string, number>;
81
110
  ackEventsByAccount: Map<string, number>;
82
111
  activeConnectionCount: (accountId: string) => number;
83
- lastSessionByAccount: Map<string, { sessionKey: string; scope: string; updatedAt: number }>;
112
+ lastSessionByAccount: Map<string, BncrRuntimeLastSession>;
84
113
  lastActivityByAccount: Map<string, number>;
85
114
  lastInboundByAccount: Map<string, number>;
86
115
  lastOutboundByAccount: Map<string, number>;
87
- }) {
116
+ }): RuntimeStatusSnapshots {
88
117
  const accountId = normalizeAccountId(args.accountId);
89
118
  return {
90
119
  queueSnapshot: buildRuntimeQueueSnapshot({
@@ -1,9 +1,39 @@
1
1
  import { normalizeAccountId } from '../core/accounts.ts';
2
+ import type { BncrDiagnosticsSummary } from '../core/types.ts';
3
+
4
+ type StatusRuntimeMeta = {
5
+ pending?: number;
6
+ pendingAdmissionsCount?: number;
7
+ pendingAdmissions?: unknown[];
8
+ deadLetter?: number;
9
+ lastSessionScope?: string | null;
10
+ lastSessionAt?: number | null;
11
+ lastSessionAgo?: string | null;
12
+ lastActivityAt?: number | null;
13
+ lastActivityAgo?: string | null;
14
+ lastInboundAt?: number | null;
15
+ lastInboundAgo?: string | null;
16
+ lastOutboundAt?: number | null;
17
+ lastOutboundAgo?: string | null;
18
+ diagnostics?: BncrDiagnosticsSummary | Record<string, unknown>;
19
+ };
20
+
21
+ type StatusRuntimeState = {
22
+ accountId?: string;
23
+ running?: boolean;
24
+ connected?: boolean;
25
+ restartPending?: boolean;
26
+ lastEventAt?: number | null;
27
+ lastStopAt?: number | null;
28
+ mode?: 'linked' | 'configured' | string;
29
+ lastError?: string | null;
30
+ meta?: StatusRuntimeMeta;
31
+ };
2
32
 
3
33
  type StatusWorkerContext = {
4
34
  accountId: string;
5
- getStatus?: () => Record<string, any>;
6
- setStatus?: (status: Record<string, any>) => void;
35
+ getStatus?: () => StatusRuntimeState;
36
+ setStatus?: (status: StatusRuntimeState) => void;
7
37
  abortSignal?: {
8
38
  aborted?: boolean;
9
39
  addEventListener?: (event: 'abort', listener: () => void, options?: { once?: boolean }) => void;
@@ -70,10 +100,10 @@ export function updateHealthStatusLogState(args: {
70
100
  type StatusWorkerHooks = {
71
101
  isOnline: (accountId: string) => boolean;
72
102
  hasRecentInboundReachability: (accountId: string) => boolean;
73
- getLastActivityAt: (accountId: string, previous: Record<string, any>) => number | null;
103
+ getLastActivityAt: (accountId: string, previous: StatusRuntimeState) => number | null;
74
104
  getActiveConnectionKey: (accountId: string) => string | null;
75
105
  getActiveConnections: (accountId: string) => Array<Record<string, unknown>>;
76
- buildStatusMeta: (accountId: string) => Record<string, any>;
106
+ buildStatusMeta: (accountId: string) => StatusRuntimeMeta;
77
107
  logInfo: (scope: string | undefined, message: string, options?: { debugOnly?: boolean }) => void;
78
108
  logInfoDedup: (
79
109
  scope: string | undefined,