@xmoxmo/bncr 0.3.6 → 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 +46 -155
  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
package/src/core/types.ts CHANGED
@@ -10,6 +10,85 @@ export type BncrConnection = {
10
10
  clientId?: string;
11
11
  connectedAt: number;
12
12
  lastSeenAt: number;
13
+ inboundOnly?: boolean;
14
+ outboundReady?: boolean;
15
+ preferredForOutbound?: boolean;
16
+ };
17
+
18
+ export type BncrGatewayCapabilityFlags = {
19
+ outboundReady: boolean;
20
+ preferredForOutbound: boolean;
21
+ inboundOnly: boolean;
22
+ };
23
+
24
+ export type FileRecvTransferStatus = 'init' | 'transferring' | 'completed' | 'aborted';
25
+
26
+ type FileRecvTransferBase = {
27
+ transferId: string;
28
+ accountId: string;
29
+ sessionKey: string;
30
+ route: BncrRoute;
31
+ fileName: string;
32
+ mimeType: string;
33
+ fileSize: number;
34
+ chunkSize: number;
35
+ totalChunks: number;
36
+ fileSha256: string;
37
+ startedAt: number;
38
+ bufferByChunk: Map<number, Buffer>;
39
+ receivedChunks: Set<number>;
40
+ ownerConnId?: string;
41
+ ownerClientId?: string;
42
+ };
43
+
44
+ export type FileRecvTransferInitState = FileRecvTransferBase & {
45
+ status: 'init';
46
+ };
47
+
48
+ export type FileRecvTransferTransferringState = FileRecvTransferBase & {
49
+ status: 'transferring';
50
+ };
51
+
52
+ export type FileRecvTransferCompletedState = FileRecvTransferBase & {
53
+ status: 'completed';
54
+ completedPath: string;
55
+ terminalAt: number;
56
+ };
57
+
58
+ export type FileRecvTransferAbortedState = FileRecvTransferBase & {
59
+ status: 'aborted';
60
+ terminalAt: number;
61
+ error: string;
62
+ };
63
+
64
+ export type FileRecvTransferState =
65
+ | FileRecvTransferInitState
66
+ | FileRecvTransferTransferringState
67
+ | FileRecvTransferCompletedState
68
+ | FileRecvTransferAbortedState;
69
+
70
+ export type FileSendTransferStatus = 'init' | 'transferring' | 'completed' | 'aborted';
71
+
72
+ export type FileSendTransferState = {
73
+ transferId: string;
74
+ accountId: string;
75
+ sessionKey: string;
76
+ route: BncrRoute;
77
+ fileName: string;
78
+ mimeType: string;
79
+ fileSize: number;
80
+ chunkSize: number;
81
+ totalChunks: number;
82
+ fileSha256: string;
83
+ startedAt: number;
84
+ status: FileSendTransferStatus;
85
+ ackedChunks: Set<number>;
86
+ failedChunks: Map<number, string>;
87
+ ownerConnId?: string;
88
+ ownerClientId?: string;
89
+ completedPath?: string;
90
+ terminalAt?: number;
91
+ error?: string;
13
92
  };
14
93
 
15
94
  export type PendingAdmission = {
@@ -78,3 +157,145 @@ export type BncrDiagnosticsSummary = {
78
157
  ok: boolean;
79
158
  };
80
159
  };
160
+
161
+ export type BncrDeadLetterDiagnosticsSummary = {
162
+ total: number;
163
+ allAccountsTotal: number;
164
+ sinceStart: number;
165
+ cappedAt: number;
166
+ oldestAt: number | null;
167
+ newestAt: number | null;
168
+ topReasons: Array<{ reason: string; count: number }>;
169
+ };
170
+
171
+ export type BncrDeadLetterEntrySummary = {
172
+ messageId: string;
173
+ accountId: string;
174
+ sessionKey: string;
175
+ route: string;
176
+ kind: string;
177
+ createdAt: number | null;
178
+ retryCount: number;
179
+ lastError: string | null;
180
+ textPreview: string;
181
+ };
182
+
183
+ export type BncrDownlinkHealthSummary = {
184
+ pendingOutbox: number;
185
+ oldestPendingCreatedAt: number | null;
186
+ oldestPendingAgeMs: number;
187
+ lastAckOkAt: number | null;
188
+ lastAckTimeoutAt: number | null;
189
+ recentAckTimeoutCount: number;
190
+ activeConnectionCount: number;
191
+ recentInboundReachable: boolean;
192
+ onlineByConn: boolean;
193
+ ackStalled: boolean;
194
+ recommendReconnect: boolean;
195
+ recommendReason: string;
196
+ };
197
+
198
+ export type BncrAckObservability = {
199
+ lastAckOkAt: number | null;
200
+ lastAckTimeoutAt: number | null;
201
+ recentAckTimeoutCount: number;
202
+ lateAckOkCount: number;
203
+ lastLateAckOkAt: number | null;
204
+ lastLateAckAgeMs: number | null;
205
+ lateAckObservationTtlMs: number;
206
+ lateAckObservationExpired: boolean;
207
+ adaptiveAckRecoveryOkCount: number;
208
+ adaptiveAckRecoveryOkThreshold: number;
209
+ adaptiveAckRecovered: boolean;
210
+ lastAckQueueLatencyMs: number | null;
211
+ lastAckPushLatencyMs: number | null;
212
+ lastLateAckQueueLatencyMs: number | null;
213
+ lastLateAckPushLatencyMs: number | null;
214
+ adaptiveAckTimeoutEnabled: boolean;
215
+ defaultAckTimeoutMs: number;
216
+ currentAckTimeoutMs: number;
217
+ recommendedAckTimeoutMs: number;
218
+ recommendedAckTimeoutReason: string;
219
+ };
220
+
221
+ export type BncrAckStrategy = {
222
+ mode: 'adaptive' | 'fixed';
223
+ currentMs: number;
224
+ defaultMs: number;
225
+ maxMs: number;
226
+ reason: string;
227
+ active: boolean;
228
+ lastLateAckAgeMs: number | null;
229
+ lateAckObservationTtlMs: number | null;
230
+ recovered: boolean;
231
+ };
232
+
233
+ export type BncrStaleCounterSummary = {
234
+ staleConnect: number;
235
+ staleInbound: number;
236
+ staleActivity: number;
237
+ staleAck: number;
238
+ staleFileInit: number;
239
+ staleFileChunk: number;
240
+ staleFileComplete: number;
241
+ staleFileAbort: number;
242
+ lastStaleAt: number | null;
243
+ };
244
+
245
+ export type BncrRuntimeLastSession = {
246
+ sessionKey: string;
247
+ scope: string;
248
+ updatedAt: number;
249
+ };
250
+
251
+ export type BncrOutboxQueueDiagnostics = {
252
+ pending: number;
253
+ pendingAllAccounts: number;
254
+ oldestPendingAt: number | null;
255
+ newestPendingAt: number | null;
256
+ lastAttemptAt: number | null;
257
+ lastPushAt: number | null;
258
+ lastPushError: string | null;
259
+ activeOutboundConnection: boolean;
260
+ activeOutboundConnectionCount: number;
261
+ };
262
+
263
+ export type BncrOutboxIncidentAckSummary = {
264
+ lastQueueLatencyMs: number | null;
265
+ lastPushLatencyMs: number | null;
266
+ lastLateQueueLatencyMs: number | null;
267
+ lastLatePushLatencyMs: number | null;
268
+ lastLateAckAgeMs: number | null;
269
+ adaptiveTimeoutMs: number | null;
270
+ adaptiveTimeoutReason: string | null;
271
+ };
272
+
273
+ export type BncrOutboxIncidentSummary = {
274
+ active: boolean;
275
+ type: string;
276
+ severity: 'ok' | 'warning' | 'critical';
277
+ recommendedAction: string;
278
+ pending: number;
279
+ oldestPendingAgeMs: number | null;
280
+ lastAttemptAgeMs: number | null;
281
+ lastPushAgeMs: number | null;
282
+ lastPushError: string | null;
283
+ hasGatewayContext: boolean;
284
+ activeOutboundConnection: boolean;
285
+ activeOutboundConnectionCount: number;
286
+ prePushGuardSkipCount: number;
287
+ lastPrePushGuardSkipAgeMs: number | null;
288
+ lastPrePushGuardSkipReason: string | null;
289
+ ack: BncrOutboxIncidentAckSummary;
290
+ };
291
+
292
+ export type BncrExtendedOutboundDiagnostics = BncrOutboxQueueDiagnostics & {
293
+ enqueueCount: number;
294
+ lastEnqueueAt: number | null;
295
+ prePushGuardSkipCount: number;
296
+ lastPrePushGuardSkipAt: number | null;
297
+ lastPrePushGuardSkipReason: string | null;
298
+ hasGatewayContext: boolean;
299
+ lastGatewayContextAt: number | null;
300
+ incident: BncrOutboxIncidentSummary;
301
+ };
@@ -0,0 +1,29 @@
1
+ export function asSanitizedString(value: unknown, fallback = ''): string {
2
+ if (typeof value === 'string') return value;
3
+ if (value == null) return fallback;
4
+ return String(value);
5
+ }
6
+
7
+ export function finiteNumberOr(value: unknown, fallback: number): number {
8
+ const n = Number(value);
9
+ return Number.isFinite(n) ? n : fallback;
10
+ }
11
+
12
+ export function finiteNumberOrNull(value: unknown): number | null {
13
+ const n = Number(value);
14
+ return Number.isFinite(n) ? n : null;
15
+ }
16
+
17
+ export function nonNegativeFiniteNumberOr(value: unknown, fallback: number): number {
18
+ return Math.max(0, finiteNumberOr(value, fallback));
19
+ }
20
+
21
+ export function clampFiniteNumber(
22
+ value: unknown,
23
+ fallback: number,
24
+ min: number,
25
+ max: number,
26
+ ): number {
27
+ const finite = finiteNumberOr(value, fallback);
28
+ return Math.max(min, Math.min(finite, max));
29
+ }
@@ -1,21 +1,37 @@
1
1
  import { emitBncrLogLine } from '../../core/logging.ts';
2
2
  import { resolveBncrChannelPolicy } from '../../core/policy.ts';
3
- import {
4
- formatDisplayScope,
5
- normalizeInboundSessionKey,
6
- withTaskSessionKey,
7
- } from '../../core/targets.ts';
3
+ import { formatDisplayScope } from '../../core/targets.ts';
8
4
  import {
9
5
  recordBncrInboundSession,
10
6
  resolveBncrInboundSessionStorePath,
11
7
  resolveBncrPinnedMainDmOwnerFromAllowlist,
12
8
  } from '../../openclaw/inbound-session-runtime.ts';
13
9
  import { dispatchOpenClawReplyWithBufferedBlockDispatcher } from '../../openclaw/reply-runtime.ts';
14
- import { resolveOpenClawAgentRoute } from '../../openclaw/routing-runtime.ts';
15
- import type { OutboundReplyTargetPolicy } from '../outbound/reply-target-policy.ts';
10
+ import {
11
+ type OpenClawResolvedAgentRoute,
12
+ resolveOpenClawAgentRoute,
13
+ } from '../../openclaw/routing-runtime.ts';
14
+ import type {
15
+ BncrEnqueueFromReply,
16
+ BncrInboundApi,
17
+ BncrInboundConfig,
18
+ BncrInboundLogger,
19
+ BncrRememberSessionRoute,
20
+ } from './contracts.ts';
21
+ import type { ParsedInbound } from './dispatch-prep.ts';
16
22
  import { buildBncrInboundRecordUpdateLastRoute } from './last-route.ts';
17
23
  import { parseBncrNativeCommand, resolveBncrNativeVerboseCommand } from './native-command.ts';
18
- import { buildBncrNativeReplyDeliveryPayload } from './native-reply-delivery.ts';
24
+ import {
25
+ buildBncrNativeCommandSessionState,
26
+ buildBncrNativeCommandSummary,
27
+ buildNativeCommandHandledResult,
28
+ buildNativeCommandRecordErrorLogger,
29
+ createNativeCommandReplyDeliverer,
30
+ createNativeCommandTurnContext,
31
+ logBncrNativeCommandEvent,
32
+ logBncrNativeCommandSummary,
33
+ resolveNativeCommandDebugEnabled,
34
+ } from './native-command-runtime.ts';
19
35
  import { buildBncrReplyConfig } from './reply-config.ts';
20
36
  import { resolveBncrChannelInboundRuntime } from './runtime-compat.ts';
21
37
  import {
@@ -24,54 +40,63 @@ import {
24
40
  wrapBncrInboundRecordSessionLabelCorrection,
25
41
  } from './session-label.ts';
26
42
 
27
- type ParsedInbound = ReturnType<typeof import('./parse.ts')['parseBncrInboundParams']>;
43
+ function assertResolvedAgentRoute(resolvedRoute: OpenClawResolvedAgentRoute): {
44
+ sessionKey: string;
45
+ agentId: string;
46
+ mainSessionKey?: string;
47
+ } {
48
+ const sessionKey =
49
+ typeof resolvedRoute.sessionKey === 'string' ? resolvedRoute.sessionKey.trim() : '';
50
+ const agentId = typeof resolvedRoute.agentId === 'string' ? resolvedRoute.agentId.trim() : '';
51
+ if (!sessionKey) throw new Error('OpenClaw resolveAgentRoute returned empty sessionKey');
52
+ if (!agentId) throw new Error('OpenClaw resolveAgentRoute returned empty agentId');
53
+ return {
54
+ sessionKey,
55
+ agentId,
56
+ ...(typeof resolvedRoute.mainSessionKey === 'string' && resolvedRoute.mainSessionKey.trim()
57
+ ? { mainSessionKey: resolvedRoute.mainSessionKey }
58
+ : {}),
59
+ };
60
+ }
28
61
 
29
- function logBncrNativeCommandEvent(
30
- event: string,
31
- fields: Record<string, unknown>,
32
- options?: { debugOnly?: boolean; debugEnabled?: boolean },
33
- ) {
34
- if (options?.debugOnly && !options?.debugEnabled) return;
35
- emitBncrLogLine('info', `[bncr] native-command ${JSON.stringify({ event, ...fields })}`);
62
+ function buildBncrNativeCommandResolvedRoute(args: {
63
+ api: BncrInboundApi;
64
+ cfg: BncrInboundConfig;
65
+ channelId: string;
66
+ accountId: string;
67
+ peer: ParsedInbound['peer'];
68
+ }) {
69
+ return assertResolvedAgentRoute(
70
+ resolveOpenClawAgentRoute(args.api, {
71
+ cfg: args.cfg,
72
+ channel: args.channelId,
73
+ accountId: args.accountId,
74
+ peer: args.peer,
75
+ }),
76
+ );
36
77
  }
37
78
 
38
79
  export { parseBncrNativeCommand } from './native-command.ts';
39
80
 
40
81
  export async function handleBncrNativeCommand(params: {
41
- api: any;
82
+ api: BncrInboundApi;
42
83
  channelId: string;
43
- cfg: any;
84
+ cfg: BncrInboundConfig;
44
85
  parsed: ParsedInbound;
45
86
  canonicalAgentId: string;
46
- rememberSessionRoute: (sessionKey: string, accountId: string, route: any) => void;
47
- enqueueFromReply: (args: {
48
- accountId: string;
49
- sessionKey: string;
50
- route: any;
51
- payload: { text?: string; mediaUrl?: string; mediaUrls?: string[] };
52
- mediaLocalRoots?: readonly string[];
53
- replyTargetPolicy?: OutboundReplyTargetPolicy;
54
- }) => Promise<void>;
55
- logger?: { warn?: (msg: string) => void; error?: (msg: string) => void };
87
+ rememberSessionRoute: BncrRememberSessionRoute;
88
+ enqueueFromReply: BncrEnqueueFromReply;
89
+ logger?: BncrInboundLogger;
56
90
  }): Promise<
57
91
  | { handled: false }
58
92
  | { handled: true; command: string; sessionKey: string; fallbackToAgent?: boolean }
59
93
  > {
60
94
  const { api, channelId, cfg, parsed, canonicalAgentId, rememberSessionRoute, enqueueFromReply } =
61
95
  params;
62
- const {
63
- accountId,
64
- route,
65
- peer,
66
- sessionKeyfromroute,
67
- providedOriginatingTo,
68
- clientId,
69
- extracted,
70
- msgId,
71
- } = parsed;
96
+ const { accountId, route, peer, clientId, extracted, msgId } = parsed;
72
97
  const command = parseBncrNativeCommand(extracted.text);
73
98
  if (!command) return { handled: false };
74
- const nativeCommandDebugEnabled = cfg?.channels?.[channelId]?.debug?.verbose === true;
99
+ const nativeCommandDebugEnabled = resolveNativeCommandDebugEnabled({ cfg, channelId });
75
100
 
76
101
  logBncrNativeCommandEvent(
77
102
  'detected',
@@ -84,24 +109,23 @@ export async function handleBncrNativeCommand(params: {
84
109
  { debugOnly: true, debugEnabled: nativeCommandDebugEnabled },
85
110
  );
86
111
 
87
- const resolvedRoute = resolveOpenClawAgentRoute(api, {
112
+ const resolvedRoute = buildBncrNativeCommandResolvedRoute({
113
+ api,
88
114
  cfg,
89
- channel: channelId,
115
+ channelId,
90
116
  accountId,
91
117
  peer,
92
118
  });
93
119
 
94
- const baseSessionKey =
95
- normalizeInboundSessionKey(sessionKeyfromroute, route, canonicalAgentId) ||
96
- resolvedRoute.sessionKey;
97
- const taskSessionKey = withTaskSessionKey(baseSessionKey, extracted.taskKey);
98
- const sessionKey = taskSessionKey || baseSessionKey;
120
+ const { baseSessionKey, taskSessionKey, sessionKey, displayTo, originatingTo } =
121
+ buildBncrNativeCommandSessionState({
122
+ parsed,
123
+ canonicalAgentId,
124
+ resolvedRoute,
125
+ });
99
126
  rememberSessionRoute(baseSessionKey, accountId, route);
100
127
  if (taskSessionKey && taskSessionKey !== baseSessionKey)
101
128
  rememberSessionRoute(taskSessionKey, accountId, route);
102
-
103
- const displayTo = formatDisplayScope(route);
104
- const originatingTo = providedOriginatingTo || displayTo;
105
129
  const body = command.body;
106
130
  if (!clientId) {
107
131
  emitBncrLogLine(
@@ -116,72 +140,22 @@ export async function handleBncrNativeCommand(params: {
116
140
  agentId: resolvedRoute.agentId,
117
141
  });
118
142
 
119
- const ctxPayload = resolveBncrChannelInboundRuntime(api).buildContext({
120
- channel: channelId,
121
- provider: channelId,
122
- surface: channelId,
123
- accountId,
124
- messageId: msgId,
125
- timestamp: Date.now(),
126
- from: senderIdForContext,
127
- sender: {
128
- id: senderIdForContext,
129
- name: senderDisplayName,
130
- username: senderDisplayName,
131
- },
132
- conversation: {
133
- kind: peer.kind,
134
- id: peer.id,
135
- label: displayTo,
136
- routePeer: {
137
- kind: peer.kind,
138
- id: peer.id,
139
- },
140
- },
141
- route: {
142
- agentId: resolvedRoute.agentId,
143
+ const ctxPayload = await Promise.resolve(
144
+ createNativeCommandTurnContext({
145
+ api,
146
+ channelId,
143
147
  accountId,
144
- routeSessionKey: resolvedRoute.sessionKey,
145
- dispatchSessionKey: sessionKey,
146
- mainSessionKey: resolvedRoute.mainSessionKey,
147
- },
148
- reply: {
149
- to: displayTo,
148
+ msgId: msgId || undefined,
149
+ peer,
150
+ resolvedRoute,
151
+ sessionKey,
152
+ displayTo,
150
153
  originatingTo,
151
- replyToId: msgId,
152
- },
153
- message: {
154
- inboundEventKind: 'user_request',
155
- body,
156
- rawBody: body,
157
- bodyForAgent: body,
158
- commandBody: body,
159
- envelopeFrom: originatingTo,
160
- senderLabel: senderDisplayName,
161
- },
162
- commandTurn: {
163
- kind: 'native',
164
- source: 'native',
165
- authorized: true,
154
+ senderIdForContext,
155
+ senderDisplayName,
166
156
  body,
167
- },
168
- access: {
169
- mentions: {
170
- canDetectMention: true,
171
- wasMentioned: true,
172
- effectiveWasMentioned: true,
173
- },
174
- commands: {
175
- authorized: true,
176
- allowTextCommands: true,
177
- useAccessGroups: false,
178
- authorizers: [],
179
- },
180
- },
181
- extra: {
182
- OriginatingChannel: channelId,
183
- },
184
- });
157
+ }),
158
+ );
185
159
 
186
160
  const sessionIdentityPatch = buildBncrInboundSessionIdentityPatch({
187
161
  channelId,
@@ -193,14 +167,28 @@ export async function handleBncrNativeCommand(params: {
193
167
 
194
168
  const nativeVerbose = resolveBncrNativeVerboseCommand(command);
195
169
  if (nativeVerbose) {
196
- logBncrNativeCommandEvent('handled-verbose', {
197
- command: command.command,
198
- accountId,
199
- sessionKey,
200
- to: displayTo,
201
- msgId: msgId || null,
202
- fallbackToAgent: false,
203
- });
170
+ logBncrNativeCommandSummary(
171
+ buildBncrNativeCommandSummary({
172
+ kind: 'verbose',
173
+ command: command.command,
174
+ accountId,
175
+ to: displayTo,
176
+ msgId: msgId || null,
177
+ result: 'handled',
178
+ }),
179
+ );
180
+ logBncrNativeCommandEvent(
181
+ 'handled-verbose',
182
+ {
183
+ command: command.command,
184
+ accountId,
185
+ sessionKey,
186
+ to: displayTo,
187
+ msgId: msgId || null,
188
+ fallbackToAgent: false,
189
+ },
190
+ { debugOnly: true, debugEnabled: nativeCommandDebugEnabled },
191
+ );
204
192
  await recordAndPatchBncrInboundSessionEntry({
205
193
  storePath,
206
194
  sessionKey,
@@ -235,7 +223,7 @@ export async function handleBncrNativeCommand(params: {
235
223
  const pinnedMainDmOwner =
236
224
  peer.kind === 'direct'
237
225
  ? resolveBncrPinnedMainDmOwnerFromAllowlist({
238
- dmScope: cfg?.session?.dmScope,
226
+ dmScope: cfg?.session?.dmScope as string | undefined,
239
227
  allowFrom: channelPolicy.allowFrom,
240
228
  normalizeEntry: (entry: string) => String(entry || '').trim(),
241
229
  })
@@ -283,16 +271,15 @@ export async function handleBncrNativeCommand(params: {
283
271
  storePath,
284
272
  ctxPayload,
285
273
  recordInboundSession: wrapBncrInboundRecordSessionLabelCorrection({
286
- recordInboundSession: recordBncrInboundSession,
274
+ recordInboundSession: recordBncrInboundSession as (
275
+ ...args: unknown[]
276
+ ) => Promise<unknown> | unknown,
287
277
  expectedLabel: displayTo,
288
278
  }),
289
279
  record: {
290
280
  updateLastRoute,
291
281
  onRecordError: (err: unknown) => {
292
- emitBncrLogLine(
293
- 'warn',
294
- `[bncr] inbound record native command session failed: ${String(err)}`,
295
- );
282
+ buildNativeCommandRecordErrorLogger(err);
296
283
  },
297
284
  },
298
285
  runDispatch: () =>
@@ -300,47 +287,21 @@ export async function handleBncrNativeCommand(params: {
300
287
  ctx: ctxPayload,
301
288
  cfg: effectiveReply.replyCfg,
302
289
  dispatcherOptions: {
303
- deliver: async (
304
- payload: {
305
- text?: string;
306
- mediaUrl?: string;
307
- mediaUrls?: string[];
308
- audioAsVoice?: boolean;
290
+ deliver: createNativeCommandReplyDeliverer({
291
+ command: command.command,
292
+ accountId,
293
+ sessionKey,
294
+ to: displayTo,
295
+ msgId: msgId || undefined,
296
+ effectiveReply,
297
+ route,
298
+ enqueueFromReply,
299
+ nativeCommandDebugEnabled,
300
+ onResponded: () => responded,
301
+ markResponded: () => {
302
+ responded = true;
309
303
  },
310
- info?: { kind?: 'tool' | 'block' | 'final' },
311
- ) => {
312
- const kind = info?.kind;
313
- const deliveryPayload = buildBncrNativeReplyDeliveryPayload({
314
- payload,
315
- kind,
316
- effectiveReply,
317
- msgId,
318
- });
319
- if (!deliveryPayload) return;
320
- if (!responded) {
321
- logBncrNativeCommandEvent(
322
- 'payload-produced',
323
- {
324
- command: command.command,
325
- accountId,
326
- sessionKey,
327
- to: displayTo,
328
- msgId: msgId || null,
329
- kind: kind || null,
330
- fallbackToAgent: false,
331
- },
332
- { debugOnly: true, debugEnabled: nativeCommandDebugEnabled },
333
- );
334
- }
335
- responded = true;
336
- await enqueueFromReply({
337
- accountId,
338
- sessionKey,
339
- route,
340
- payload: deliveryPayload,
341
- replyTargetPolicy: 'preserve',
342
- });
343
- },
304
+ }),
344
305
  },
345
306
  replyOptions: {
346
307
  disableBlockStreaming: !effectiveReply.blockStreaming,
@@ -352,15 +313,26 @@ export async function handleBncrNativeCommand(params: {
352
313
  });
353
314
 
354
315
  if (!responded) {
355
- logBncrNativeCommandEvent('no-payload-fallback-to-agent', {
316
+ logBncrNativeCommandSummary(
317
+ `fallback command=${command.command}|accountId=${accountId}|to=${displayTo}|msgId=${msgId || '-'}|reason=no-payload`,
318
+ );
319
+ logBncrNativeCommandEvent(
320
+ 'no-payload-fallback-to-agent',
321
+ {
322
+ command: command.command,
323
+ accountId,
324
+ sessionKey,
325
+ to: displayTo,
326
+ msgId: msgId || null,
327
+ fallbackToAgent: true,
328
+ },
329
+ { debugOnly: true, debugEnabled: nativeCommandDebugEnabled },
330
+ );
331
+ return buildNativeCommandHandledResult({
356
332
  command: command.command,
357
- accountId,
358
333
  sessionKey,
359
- to: displayTo,
360
- msgId: msgId || null,
361
334
  fallbackToAgent: true,
362
335
  });
363
- return { handled: true, command: command.command, sessionKey, fallbackToAgent: true };
364
336
  }
365
337
 
366
338
  logBncrNativeCommandEvent(
@@ -375,5 +347,8 @@ export async function handleBncrNativeCommand(params: {
375
347
  },
376
348
  { debugOnly: true, debugEnabled: nativeCommandDebugEnabled },
377
349
  );
378
- return { handled: true, command: command.command, sessionKey };
350
+ return buildNativeCommandHandledResult({
351
+ command: command.command,
352
+ sessionKey,
353
+ });
379
354
  }