@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
@@ -148,6 +148,7 @@ export type BncrStructuredContextFactsFromInboundPartsInput = {
148
148
  rawBody: string;
149
149
  body?: string;
150
150
  mediaPath?: string | null;
151
+ mediaContentType?: string;
151
152
  };
152
153
  senderIdForContext: string;
153
154
  senderDisplayName?: string;
@@ -157,6 +158,7 @@ export function buildBncrStructuredContextFactsFromInboundParts(
157
158
  input: BncrStructuredContextFactsFromInboundPartsInput,
158
159
  ) {
159
160
  const mediaPath = input.prepared.mediaPath || undefined;
161
+ const mediaContentType = input.prepared.mediaContentType || input.parsed.mimeType;
160
162
  return buildBncrStructuredContextFacts({
161
163
  channelId: input.channelId,
162
164
  accountId: input.parsed.accountId,
@@ -190,8 +192,8 @@ export function buildBncrStructuredContextFactsFromInboundParts(
190
192
  ? [
191
193
  {
192
194
  path: mediaPath,
193
- contentType: input.parsed.mimeType,
194
- kind: inferBncrStructuredMediaKind(input.parsed.mimeType),
195
+ contentType: mediaContentType,
196
+ kind: inferBncrStructuredMediaKind(mediaContentType),
195
197
  messageId: input.parsed.msgId,
196
198
  },
197
199
  ]
@@ -0,0 +1,70 @@
1
+ import type { OpenClawPluginApi } from 'openclaw/plugin-sdk/core';
2
+ import type { BncrRoute } from '../../core/types.ts';
3
+ import type {
4
+ OpenClawChannelPeer,
5
+ OpenClawChannelRuntimeContext,
6
+ } from '../../openclaw/channel-runtime-contracts.ts';
7
+ import type {
8
+ BncrChannelConfigRoot,
9
+ BncrChannelConfigSection,
10
+ } from '../../plugin/channel-runtime-types.ts';
11
+ import type { ReplyPayloadInput } from '../outbound/reply-enqueue.ts';
12
+ import type { OutboundReplyTargetPolicy } from '../outbound/reply-target-policy.ts';
13
+
14
+ export type BncrInboundParsedPeer = OpenClawChannelPeer;
15
+
16
+ export type BncrInboundParamsInput = {
17
+ accountId?: unknown;
18
+ platform?: unknown;
19
+ groupId?: unknown;
20
+ userId?: unknown;
21
+ sessionKey?: unknown;
22
+ originatingTo?: unknown;
23
+ providedOriginatingTo?: unknown;
24
+ to?: unknown;
25
+ clientId?: unknown;
26
+ msg?: unknown;
27
+ type?: unknown;
28
+ base64?: unknown;
29
+ path?: unknown;
30
+ mimeType?: unknown;
31
+ fileName?: unknown;
32
+ msgId?: unknown;
33
+ };
34
+
35
+ export type BncrRememberSessionRoute = (
36
+ sessionKey: string,
37
+ accountId: string,
38
+ route: BncrRoute,
39
+ ) => void;
40
+
41
+ export type BncrEnqueueFromReply = (args: {
42
+ accountId: string;
43
+ sessionKey: string;
44
+ route: BncrRoute;
45
+ payload: ReplyPayloadInput;
46
+ mediaLocalRoots?: readonly string[];
47
+ replyTargetPolicy?: OutboundReplyTargetPolicy;
48
+ }) => Promise<void>;
49
+
50
+ export type BncrInboundLogger = { warn?: (msg: string) => void; error?: (msg: string) => void };
51
+
52
+ export type BncrInboundApi = OpenClawPluginApi;
53
+
54
+ export type BncrInboundConfig = BncrChannelConfigRoot & {
55
+ session?: { store?: string; dmScope?: unknown };
56
+ channels?: Record<string, BncrChannelConfigSection | undefined>;
57
+ agents?: {
58
+ defaults?: {
59
+ blockStreamingDefault?: unknown;
60
+ blockStreamingBreak?: unknown;
61
+ blockStreamingChunk?: unknown;
62
+ [key: string]: unknown;
63
+ };
64
+ [key: string]: unknown;
65
+ };
66
+ accessGroups?: unknown;
67
+ [key: string]: unknown;
68
+ };
69
+
70
+ export type BncrInboundContextPayload = OpenClawChannelRuntimeContext;
@@ -0,0 +1,303 @@
1
+ import fs from 'node:fs';
2
+ import {
3
+ formatDisplayScope,
4
+ normalizeInboundSessionKey,
5
+ withTaskSessionKey,
6
+ } from '../../core/targets.ts';
7
+ import {
8
+ readBncrSessionUpdatedAt,
9
+ resolveBncrInboundSessionStorePath,
10
+ } from '../../openclaw/inbound-session-runtime.ts';
11
+ import { saveOpenClawChannelMediaBuffer } from '../../openclaw/media-runtime.ts';
12
+ import {
13
+ formatOpenClawAgentEnvelope,
14
+ resolveOpenClawEnvelopeFormatOptions,
15
+ } from '../../openclaw/reply-runtime.ts';
16
+ import {
17
+ type OpenClawResolvedAgentRoute,
18
+ resolveOpenClawAgentRoute,
19
+ } from '../../openclaw/routing-runtime.ts';
20
+ import type { BncrInboundApi, BncrInboundConfig, BncrRememberSessionRoute } from './contracts.ts';
21
+ import { INBOUND_MEDIA_URL_MAX_BYTES, isHttpMediaUrl } from './media-url-download.ts';
22
+ import { loadInboundRemoteMedia } from './remote-media.ts';
23
+
24
+ export type ParsedInbound = ReturnType<typeof import('./parse.ts')['parseBncrInboundParams']>;
25
+
26
+ export type BncrInboundConversationResolution = {
27
+ accountId: string;
28
+ chatType: 'direct' | 'group';
29
+ route: ParsedInbound['route'];
30
+ resolvedRoute: {
31
+ sessionKey: string;
32
+ agentId: string;
33
+ mainSessionKey?: string;
34
+ };
35
+ canonicalTo: string;
36
+ rawTo: string;
37
+ originatingTo: string;
38
+ baseSessionKey: string;
39
+ taskSessionKey?: string;
40
+ dispatchSessionKey: string;
41
+ };
42
+
43
+ export type BncrInboundReplyRouteFact = {
44
+ accountId: string;
45
+ sessionKey: string;
46
+ route: ParsedInbound['route'];
47
+ canonicalTo: string;
48
+ originatingTo: string;
49
+ chatType: 'direct' | 'group';
50
+ };
51
+
52
+ export type BncrPreparedInboundSessionContext = {
53
+ storePath: string;
54
+ mediaPath?: string;
55
+ mediaContentType?: string;
56
+ rawBody: string;
57
+ body: string;
58
+ };
59
+
60
+ export type BncrInboundPreparation = {
61
+ resolution: BncrInboundConversationResolution;
62
+ prepared: BncrPreparedInboundSessionContext;
63
+ replyRouteFact: BncrInboundReplyRouteFact;
64
+ senderIdForContext: string;
65
+ senderDisplayName: string;
66
+ };
67
+
68
+ const INBOUND_MEDIA_MAX_BYTES = 30 * 1024 * 1024;
69
+
70
+ function assertResolvedAgentRoute(resolvedRoute: OpenClawResolvedAgentRoute): {
71
+ sessionKey: string;
72
+ agentId: string;
73
+ mainSessionKey?: string;
74
+ } {
75
+ const sessionKey =
76
+ typeof resolvedRoute.sessionKey === 'string' ? resolvedRoute.sessionKey.trim() : '';
77
+ const agentId = typeof resolvedRoute.agentId === 'string' ? resolvedRoute.agentId.trim() : '';
78
+ if (!sessionKey) throw new Error('OpenClaw resolveAgentRoute returned empty sessionKey');
79
+ if (!agentId) throw new Error('OpenClaw resolveAgentRoute returned empty agentId');
80
+ return {
81
+ sessionKey,
82
+ agentId,
83
+ ...(typeof resolvedRoute.mainSessionKey === 'string' && resolvedRoute.mainSessionKey.trim()
84
+ ? { mainSessionKey: resolvedRoute.mainSessionKey }
85
+ : {}),
86
+ };
87
+ }
88
+
89
+ function formatRawBncrInboundTarget(route: ParsedInbound['route']): string {
90
+ return `Bncr:${String(route.platform || '').trim()}:${String(route.groupId || '').trim()}:${String(route.userId || '').trim()}`;
91
+ }
92
+
93
+ export function resolveBncrInboundConversation(args: {
94
+ api: BncrInboundApi;
95
+ cfg: BncrInboundConfig;
96
+ channelId: string;
97
+ parsed: ParsedInbound;
98
+ canonicalAgentId: string;
99
+ }) {
100
+ const { api, cfg, channelId, parsed, canonicalAgentId } = args;
101
+ const { accountId, route, peer, sessionKeyfromroute, providedOriginatingTo, extracted } = parsed;
102
+
103
+ const resolvedRoute = assertResolvedAgentRoute(
104
+ resolveOpenClawAgentRoute(api, {
105
+ cfg,
106
+ channel: channelId,
107
+ accountId,
108
+ peer,
109
+ }),
110
+ );
111
+
112
+ const baseSessionKey =
113
+ normalizeInboundSessionKey(sessionKeyfromroute, route, canonicalAgentId) ||
114
+ resolvedRoute.sessionKey;
115
+ const taskSessionKey = withTaskSessionKey(baseSessionKey, extracted.taskKey);
116
+ const dispatchSessionKey = taskSessionKey || baseSessionKey;
117
+ const rawTo = formatRawBncrInboundTarget(route);
118
+ const canonicalTo = formatDisplayScope(route);
119
+ const originatingTo = providedOriginatingTo || rawTo;
120
+
121
+ return {
122
+ accountId,
123
+ chatType: peer.kind,
124
+ route,
125
+ resolvedRoute,
126
+ canonicalTo,
127
+ rawTo,
128
+ originatingTo,
129
+ baseSessionKey,
130
+ ...(taskSessionKey ? { taskSessionKey } : {}),
131
+ dispatchSessionKey,
132
+ } satisfies BncrInboundConversationResolution;
133
+ }
134
+
135
+ export function estimateBase64DecodedBytes(value: string): number {
136
+ const normalized = String(value || '').replace(/\s+/g, '');
137
+ if (!normalized) return 0;
138
+ const padding = normalized.endsWith('==') ? 2 : normalized.endsWith('=') ? 1 : 0;
139
+ return Math.max(0, Math.floor((normalized.length * 3) / 4) - padding);
140
+ }
141
+
142
+ export function assertInboundMediaBase64Size(value: string, maxBytes = INBOUND_MEDIA_MAX_BYTES) {
143
+ const estimatedBytes = estimateBase64DecodedBytes(value);
144
+ if (estimatedBytes > maxBytes) {
145
+ throw new Error(
146
+ `inbound media too large: estimated ${estimatedBytes} bytes exceeds ${maxBytes} bytes`,
147
+ );
148
+ }
149
+ }
150
+
151
+ export function decodeInboundMediaBase64(
152
+ value: string,
153
+ maxBytes = INBOUND_MEDIA_MAX_BYTES,
154
+ ): Buffer {
155
+ assertInboundMediaBase64Size(value, maxBytes);
156
+ const normalized = String(value || '').replace(/\s+/g, '');
157
+ const mediaBuf = Buffer.from(normalized, 'base64');
158
+ if (!mediaBuf.length) {
159
+ throw new Error('inbound media base64 decoded to empty buffer');
160
+ }
161
+ if (mediaBuf.length > maxBytes) {
162
+ throw new Error(
163
+ `inbound media too large: decoded ${mediaBuf.length} bytes exceeds ${maxBytes} bytes`,
164
+ );
165
+ }
166
+ return mediaBuf;
167
+ }
168
+
169
+ export async function prepareBncrInboundSessionContext(args: {
170
+ api: BncrInboundApi;
171
+ cfg: BncrInboundConfig;
172
+ parsed: ParsedInbound;
173
+ resolution: BncrInboundConversationResolution;
174
+ rememberSessionRoute: BncrRememberSessionRoute;
175
+ }) {
176
+ const { api, cfg, parsed, resolution, rememberSessionRoute } = args;
177
+ const {
178
+ msgType,
179
+ mediaBase64,
180
+ mediaPathFromTransfer,
181
+ mimeType,
182
+ fileName,
183
+ extracted,
184
+ platform,
185
+ groupId,
186
+ userId,
187
+ } = parsed;
188
+ const { accountId, route, resolvedRoute, baseSessionKey, taskSessionKey, dispatchSessionKey } =
189
+ resolution;
190
+
191
+ rememberSessionRoute(baseSessionKey, accountId, route);
192
+ if (taskSessionKey && taskSessionKey !== baseSessionKey) {
193
+ rememberSessionRoute(taskSessionKey, accountId, route);
194
+ }
195
+
196
+ const storePath = resolveBncrInboundSessionStorePath({
197
+ storeConfig: cfg?.session?.store,
198
+ agentId: resolvedRoute.agentId,
199
+ });
200
+
201
+ let mediaPath: string | undefined;
202
+ let mediaContentType = mimeType;
203
+ if (mediaBase64) {
204
+ const mediaBuf = decodeInboundMediaBase64(mediaBase64);
205
+ const saved = await saveOpenClawChannelMediaBuffer(
206
+ api,
207
+ mediaBuf,
208
+ mimeType,
209
+ 'inbound',
210
+ 30 * 1024 * 1024,
211
+ fileName,
212
+ );
213
+ mediaPath = saved.path;
214
+ } else if (mediaPathFromTransfer && isHttpMediaUrl(mediaPathFromTransfer)) {
215
+ const downloaded = await loadInboundRemoteMedia(
216
+ api,
217
+ mediaPathFromTransfer,
218
+ INBOUND_MEDIA_URL_MAX_BYTES,
219
+ );
220
+ mediaContentType = downloaded.contentType || mimeType;
221
+ const saved = await saveOpenClawChannelMediaBuffer(
222
+ api,
223
+ downloaded.buffer,
224
+ mediaContentType,
225
+ 'inbound',
226
+ INBOUND_MEDIA_URL_MAX_BYTES,
227
+ fileName,
228
+ );
229
+ mediaPath = saved.path;
230
+ } else if (mediaPathFromTransfer && fs.existsSync(mediaPathFromTransfer)) {
231
+ mediaPath = mediaPathFromTransfer;
232
+ }
233
+
234
+ const rawBody = extracted.text || (msgType === 'text' ? '' : `[${msgType}]`);
235
+ const body = formatOpenClawAgentEnvelope(api, {
236
+ channel: 'Bncr',
237
+ from: `${platform}:${groupId}:${userId}`,
238
+ timestamp: Date.now(),
239
+ previousTimestamp: readBncrSessionUpdatedAt(api, {
240
+ storePath,
241
+ sessionKey: dispatchSessionKey,
242
+ }),
243
+ envelope: resolveOpenClawEnvelopeFormatOptions(api, cfg),
244
+ body: rawBody,
245
+ });
246
+
247
+ return {
248
+ storePath,
249
+ mediaPath,
250
+ mediaContentType,
251
+ rawBody,
252
+ body,
253
+ } satisfies BncrPreparedInboundSessionContext;
254
+ }
255
+
256
+ export function buildBncrInboundReplyRouteFact(
257
+ resolution: BncrInboundConversationResolution,
258
+ ): BncrInboundReplyRouteFact {
259
+ return {
260
+ accountId: resolution.accountId,
261
+ sessionKey: resolution.dispatchSessionKey,
262
+ route: resolution.route,
263
+ canonicalTo: resolution.canonicalTo,
264
+ originatingTo: resolution.originatingTo,
265
+ chatType: resolution.chatType,
266
+ };
267
+ }
268
+
269
+ export async function prepareBncrInboundDispatch(args: {
270
+ api: BncrInboundApi;
271
+ channelId: string;
272
+ cfg: BncrInboundConfig;
273
+ parsed: ParsedInbound;
274
+ canonicalAgentId: string;
275
+ rememberSessionRoute: BncrRememberSessionRoute;
276
+ }) {
277
+ const { api, channelId, cfg, parsed, canonicalAgentId, rememberSessionRoute } = args;
278
+ const resolution = resolveBncrInboundConversation({
279
+ api,
280
+ cfg,
281
+ channelId,
282
+ parsed,
283
+ canonicalAgentId,
284
+ });
285
+ const prepared = await prepareBncrInboundSessionContext({
286
+ api,
287
+ cfg,
288
+ parsed,
289
+ resolution,
290
+ rememberSessionRoute,
291
+ });
292
+ const replyRouteFact = buildBncrInboundReplyRouteFact(resolution);
293
+ const senderIdForContext = parsed.clientId || resolution.canonicalTo;
294
+ const senderDisplayName = parsed.clientId ? 'bncr-client' : resolution.canonicalTo;
295
+
296
+ return {
297
+ resolution,
298
+ prepared,
299
+ replyRouteFact,
300
+ senderIdForContext,
301
+ senderDisplayName,
302
+ } satisfies BncrInboundPreparation;
303
+ }