@xmoxmo/bncr 0.3.2 → 0.3.4

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 (39) hide show
  1. package/README.md +15 -1
  2. package/index.ts +5 -10
  3. package/package.json +4 -4
  4. package/scripts/check-pack.mjs +15 -5
  5. package/scripts/selfcheck.mjs +30 -2
  6. package/src/channel.ts +79 -182
  7. package/src/core/accounts.ts +1 -1
  8. package/src/core/connection-reachability.ts +6 -1
  9. package/src/core/downlink-health.ts +3 -3
  10. package/src/core/extended-diagnostics.ts +2 -0
  11. package/src/core/file-transfer-payloads.ts +1 -4
  12. package/src/core/outbox-entry-builders.ts +4 -2
  13. package/src/core/outbox-file-transfer-bookkeeping.ts +1 -1
  14. package/src/core/outbox-file-transfer-failure.ts +2 -5
  15. package/src/core/outbox-file-transfer-success.ts +1 -4
  16. package/src/core/outbox-text-push-failure.ts +2 -4
  17. package/src/core/outbox-text-push-success.ts +1 -1
  18. package/src/messaging/inbound/commands.ts +34 -25
  19. package/src/messaging/inbound/dispatch.ts +16 -18
  20. package/src/messaging/inbound/parse.ts +3 -3
  21. package/src/messaging/inbound/runtime-compat.ts +8 -2
  22. package/src/messaging/outbound/build-send-action.ts +1 -2
  23. package/src/messaging/outbound/durable-message-adapter.ts +16 -6
  24. package/src/messaging/outbound/durable-queue-adapter.ts +3 -1
  25. package/src/messaging/outbound/media.ts +2 -1
  26. package/src/messaging/outbound/queue-selectors.ts +19 -6
  27. package/src/messaging/outbound/reasons.ts +2 -0
  28. package/src/messaging/outbound/reply-enqueue.ts +5 -1
  29. package/src/messaging/outbound/retry-policy.ts +16 -8
  30. package/src/messaging/outbound/session-route.ts +1 -1
  31. package/src/openclaw/reply-runtime.ts +4 -5
  32. package/src/openclaw/routing-runtime.ts +0 -1
  33. package/src/openclaw/sdk-helpers.ts +4 -1
  34. package/src/plugin/messaging.ts +2 -9
  35. package/src/plugin/status.ts +5 -1
  36. package/src/runtime/outbound-flags.ts +1 -1
  37. package/src/runtime/outbox-transitions.ts +4 -4
  38. package/src/runtime/status-snapshots.ts +3 -1
  39. package/src/runtime/status-worker.ts +8 -2
@@ -5,23 +5,23 @@ import {
5
5
  normalizeInboundSessionKey,
6
6
  withTaskSessionKey,
7
7
  } from '../../core/targets.ts';
8
- import { buildBncrReplyConfig } from './reply-config.ts';
9
- import { resolveBncrChannelInboundRuntime } from './runtime-compat.ts';
10
8
  import {
11
- buildBncrInboundSessionIdentityPatch,
12
- recordAndPatchBncrInboundSessionEntry,
13
- wrapBncrInboundRecordSessionLabelCorrection,
14
- } from './session-label.ts';
9
+ recordBncrInboundSession,
10
+ resolveBncrInboundSessionStorePath,
11
+ resolveBncrPinnedMainDmOwnerFromAllowlist,
12
+ } from '../../openclaw/inbound-session-runtime.ts';
15
13
  import { dispatchOpenClawReplyWithBufferedBlockDispatcher } from '../../openclaw/reply-runtime.ts';
16
14
  import {
17
15
  resolveOpenClawAgentRoute,
18
16
  resolveOpenClawInboundLastRouteSessionKey,
19
17
  } from '../../openclaw/routing-runtime.ts';
18
+ import { buildBncrReplyConfig } from './reply-config.ts';
19
+ import { resolveBncrChannelInboundRuntime } from './runtime-compat.ts';
20
20
  import {
21
- recordBncrInboundSession,
22
- resolveBncrInboundSessionStorePath,
23
- resolveBncrPinnedMainDmOwnerFromAllowlist,
24
- } from '../../openclaw/inbound-session-runtime.ts';
21
+ buildBncrInboundSessionIdentityPatch,
22
+ recordAndPatchBncrInboundSessionEntry,
23
+ wrapBncrInboundRecordSessionLabelCorrection,
24
+ } from './session-label.ts';
25
25
 
26
26
  type ParsedInbound = ReturnType<typeof import('./parse.ts')['parseBncrInboundParams']>;
27
27
 
@@ -39,14 +39,22 @@ type NativeVerboseCommand = {
39
39
 
40
40
  function resolveBncrNativeVerboseCommand(command: NativeCommand): NativeVerboseCommand | null {
41
41
  if (command.command !== 'verbose') return null;
42
- const rawLevel = String(command.raw.slice('/verbose'.length) || '').trim().toLowerCase();
42
+ const rawLevel = String(command.raw.slice('/verbose'.length) || '')
43
+ .trim()
44
+ .toLowerCase();
43
45
  if (!rawLevel || rawLevel === 'status') {
44
46
  return { handled: true, text: 'Current verbose level is unchanged.' };
45
47
  }
46
- if (rawLevel === 'on') return { handled: true, verboseLevel: 'on', text: 'Verbose logging enabled.' };
47
- if (rawLevel === 'off') return { handled: true, verboseLevel: 'off', text: 'Verbose logging disabled.' };
48
- if (rawLevel === 'full') return { handled: true, verboseLevel: 'full', text: 'Verbose logging set to full.' };
49
- return { handled: true, text: `Unrecognized verbose level "${rawLevel}". Valid levels: off, on, full.` };
48
+ if (rawLevel === 'on')
49
+ return { handled: true, verboseLevel: 'on', text: 'Verbose logging enabled.' };
50
+ if (rawLevel === 'off')
51
+ return { handled: true, verboseLevel: 'off', text: 'Verbose logging disabled.' };
52
+ if (rawLevel === 'full')
53
+ return { handled: true, verboseLevel: 'full', text: 'Verbose logging set to full.' };
54
+ return {
55
+ handled: true,
56
+ text: `Unrecognized verbose level "${rawLevel}". Valid levels: off, on, full.`,
57
+ };
50
58
  }
51
59
 
52
60
  function logBncrNativeCommandEvent(
@@ -93,17 +101,18 @@ export async function handleBncrNativeCommand(params: {
93
101
  | { handled: false }
94
102
  | { handled: true; command: string; sessionKey: string; fallbackToAgent?: boolean }
95
103
  > {
104
+ const { api, channelId, cfg, parsed, canonicalAgentId, rememberSessionRoute, enqueueFromReply } =
105
+ params;
96
106
  const {
97
- api,
98
- channelId,
99
- cfg,
100
- parsed,
101
- canonicalAgentId,
102
- rememberSessionRoute,
103
- enqueueFromReply,
104
- logger,
105
- } = params;
106
- const { accountId, route, peer, sessionKeyfromroute, providedOriginatingTo, clientId, extracted, msgId } = parsed;
107
+ accountId,
108
+ route,
109
+ peer,
110
+ sessionKeyfromroute,
111
+ providedOriginatingTo,
112
+ clientId,
113
+ extracted,
114
+ msgId,
115
+ } = parsed;
107
116
  const command = parseBncrNativeCommand(extracted.text);
108
117
  if (!command) return { handled: false };
109
118
  const nativeCommandDebugEnabled = cfg?.channels?.[channelId]?.debug?.verbose === true;
@@ -6,14 +6,12 @@ import {
6
6
  normalizeInboundSessionKey,
7
7
  withTaskSessionKey,
8
8
  } from '../../core/targets.ts';
9
- import { handleBncrNativeCommand } from './commands.ts';
10
9
  import {
11
- buildBncrPromptVisibleContextFacts,
12
- buildBncrStructuredContextFactsFromInboundParts,
13
- } from './context-facts.ts';
14
- import { buildBncrReplyConfig } from './reply-config.ts';
15
- import { resolveBncrChannelInboundRuntime } from './runtime-compat.ts';
16
- import { wrapBncrInboundRecordSessionLabelCorrection } from './session-label.ts';
10
+ readBncrSessionUpdatedAt,
11
+ recordBncrInboundSession,
12
+ resolveBncrInboundSessionStorePath,
13
+ resolveBncrPinnedMainDmOwnerFromAllowlist,
14
+ } from '../../openclaw/inbound-session-runtime.ts';
17
15
  import { saveOpenClawChannelMediaBuffer } from '../../openclaw/media-runtime.ts';
18
16
  import {
19
17
  dispatchOpenClawReplyWithBufferedBlockDispatcher,
@@ -24,12 +22,14 @@ import {
24
22
  resolveOpenClawAgentRoute,
25
23
  resolveOpenClawInboundLastRouteSessionKey,
26
24
  } from '../../openclaw/routing-runtime.ts';
25
+ import { handleBncrNativeCommand } from './commands.ts';
27
26
  import {
28
- readBncrSessionUpdatedAt,
29
- recordBncrInboundSession,
30
- resolveBncrInboundSessionStorePath,
31
- resolveBncrPinnedMainDmOwnerFromAllowlist,
32
- } from '../../openclaw/inbound-session-runtime.ts';
27
+ buildBncrPromptVisibleContextFacts,
28
+ buildBncrStructuredContextFactsFromInboundParts,
29
+ } from './context-facts.ts';
30
+ import { buildBncrReplyConfig } from './reply-config.ts';
31
+ import { resolveBncrChannelInboundRuntime } from './runtime-compat.ts';
32
+ import { wrapBncrInboundRecordSessionLabelCorrection } from './session-label.ts';
33
33
 
34
34
  type ParsedInbound = ReturnType<typeof import('./parse.ts')['parseBncrInboundParams']>;
35
35
 
@@ -68,10 +68,7 @@ export function estimateBase64DecodedBytes(value: string): number {
68
68
  return Math.max(0, Math.floor((normalized.length * 3) / 4) - padding);
69
69
  }
70
70
 
71
- export function assertInboundMediaBase64Size(
72
- value: string,
73
- maxBytes = INBOUND_MEDIA_MAX_BYTES,
74
- ) {
71
+ export function assertInboundMediaBase64Size(value: string, maxBytes = INBOUND_MEDIA_MAX_BYTES) {
75
72
  const estimatedBytes = estimateBase64DecodedBytes(value);
76
73
  if (estimatedBytes > maxBytes) {
77
74
  throw new Error(
@@ -161,7 +158,8 @@ async function prepareBncrInboundSessionContext(args: {
161
158
  groupId,
162
159
  userId,
163
160
  } = parsed;
164
- const { accountId, route, resolvedRoute, baseSessionKey, taskSessionKey, dispatchSessionKey } = resolution;
161
+ const { accountId, route, resolvedRoute, baseSessionKey, taskSessionKey, dispatchSessionKey } =
162
+ resolution;
165
163
 
166
164
  rememberSessionRoute(baseSessionKey, accountId, route);
167
165
  if (taskSessionKey && taskSessionKey !== baseSessionKey) {
@@ -444,7 +442,7 @@ export async function dispatchBncrInbound(params: {
444
442
  resolution,
445
443
  rememberSessionRoute,
446
444
  });
447
- const { storePath, mediaPath, rawBody, body } = prepared;
445
+ const { storePath, mediaPath, rawBody } = prepared;
448
446
  const replyRouteFact = buildBncrInboundReplyRouteFact(resolution);
449
447
  if (!clientId) {
450
448
  emitBncrLogLine(
@@ -49,9 +49,9 @@ export function parseBncrInboundParams(params: any) {
49
49
  const groupId = asString(params?.groupId || '0').trim() || '0';
50
50
  const userId = asString(params?.userId || '').trim();
51
51
  const sessionKeyfromroute = asString(params?.sessionKey || '').trim();
52
- const providedOriginatingTo = asString(
53
- params?.originatingTo || params?.providedOriginatingTo || params?.to || '',
54
- ).trim() || undefined;
52
+ const providedOriginatingTo =
53
+ asString(params?.originatingTo || params?.providedOriginatingTo || params?.to || '').trim() ||
54
+ undefined;
55
55
  const clientId = asString(params?.clientId || '').trim() || undefined;
56
56
 
57
57
  const route: BncrRoute = {
@@ -20,8 +20,14 @@ export function resolveBncrChannelInboundRuntime(api: any): ChannelRuntimeCompat
20
20
  if (legacyTurnRuntime?.buildContext && legacyTurnRuntime?.run) {
21
21
  if (!warnedLegacyTurnRuntime) {
22
22
  warnedLegacyTurnRuntime = true;
23
- const channelRuntimeKeys = Object.keys(channelRuntime ?? {}).sort().join(',') || 'none';
24
- const inboundRuntimeKeys = Object.keys(inboundRuntime ?? {}).sort().join(',') || 'none';
23
+ const channelRuntimeKeys =
24
+ Object.keys(channelRuntime ?? {})
25
+ .sort()
26
+ .join(',') || 'none';
27
+ const inboundRuntimeKeys =
28
+ Object.keys(inboundRuntime ?? {})
29
+ .sort()
30
+ .join(',') || 'none';
25
31
  emitBncrLogLine(
26
32
  'warn',
27
33
  `[bncr] inbound runtime fallback=turn|preferred=inbound|channelKeys=${channelRuntimeKeys}|inboundKeys=${inboundRuntimeKeys}`,
@@ -56,8 +56,7 @@ export function buildBncrMessageAction(input: MinimalBncrSendInput): BuiltBncrMe
56
56
 
57
57
  const channel = asString(input.channel || 'bncr').trim() || 'bncr';
58
58
  const action = asString(input.action || 'send').trim() || 'send';
59
- const idempotencyKey =
60
- asString(input.idempotencyKey || '').trim() || `bncr-${randomUUID()}`;
59
+ const idempotencyKey = asString(input.idempotencyKey || '').trim() || `bncr-${randomUUID()}`;
61
60
  const accountId =
62
61
  asString(pickFirstString(paramsObj.accountId, input.accountId) || '').trim() || undefined;
63
62
 
@@ -1,20 +1,27 @@
1
- import { defineChannelMessageAdapter } from 'openclaw/plugin-sdk/channel-outbound';
2
1
  import type {
3
2
  ChannelMessageAdapterShape,
4
3
  ChannelMessageSendMediaContext,
5
4
  ChannelMessageSendPayloadContext,
6
5
  ChannelMessageSendResult,
7
6
  ChannelMessageSendTextContext,
8
- } from 'openclaw/plugin-sdk/channel-outbound';
7
+ } from 'openclaw/plugin-sdk/channel-message';
8
+ import { defineChannelMessageAdapter } from 'openclaw/plugin-sdk/channel-message';
9
9
 
10
- import { buildFileTransferOutboxEntry, buildTextOutboxEntry } from '../../core/outbox-entry-builders.ts';
10
+ import {
11
+ buildFileTransferOutboxEntry,
12
+ buildTextOutboxEntry,
13
+ } from '../../core/outbox-entry-builders.ts';
11
14
  import type { BncrRoute, OutboxEntry } from '../../core/types.ts';
12
15
  import { buildBncrDurableQueuedResult } from './durable-queue-adapter.ts';
13
16
 
14
17
  export type BncrDurableMessageQueuedAdapterDeps<TConfig = unknown> = {
15
18
  enqueueText: (ctx: ChannelMessageSendTextContext<TConfig>) => Promise<OutboxEntry> | OutboxEntry;
16
- enqueueMedia?: (ctx: ChannelMessageSendMediaContext<TConfig>) => Promise<OutboxEntry> | OutboxEntry;
17
- enqueuePayload?: (ctx: ChannelMessageSendPayloadContext<TConfig>) => Promise<OutboxEntry> | OutboxEntry;
19
+ enqueueMedia?: (
20
+ ctx: ChannelMessageSendMediaContext<TConfig>,
21
+ ) => Promise<OutboxEntry> | OutboxEntry;
22
+ enqueuePayload?: (
23
+ ctx: ChannelMessageSendPayloadContext<TConfig>,
24
+ ) => Promise<OutboxEntry> | OutboxEntry;
18
25
  now?: () => number;
19
26
  };
20
27
 
@@ -97,7 +104,10 @@ export function createBncrDurableMessageQueuedAdapterFromBuilders<TConfig = unkn
97
104
  });
98
105
  }
99
106
 
100
- function toChannelMessageSendResult(entry: OutboxEntry | undefined, now?: () => number): ChannelMessageSendResult {
107
+ function toChannelMessageSendResult(
108
+ entry: OutboxEntry | undefined,
109
+ now?: () => number,
110
+ ): ChannelMessageSendResult {
101
111
  if (!entry) throw new Error('bncr durable message adapter did not receive an outbox entry');
102
112
  const queued = buildBncrDurableQueuedResult({ entry, sentAt: now?.() });
103
113
  return {
@@ -74,7 +74,9 @@ export function buildBncrDurableQueuedResult(args: {
74
74
  }): BncrDurableQueuedResult {
75
75
  const sentAt = Number.isFinite(args.sentAt) ? Number(args.sentAt) : args.entry.createdAt;
76
76
  const platformMessageId = args.entry.messageId;
77
- const replyToId = normalizeOutboundReplyToId({ replyToId: args.replyToId ?? extractReplyToId(args.entry) }) || undefined;
77
+ const replyToId =
78
+ normalizeOutboundReplyToId({ replyToId: args.replyToId ?? extractReplyToId(args.entry) }) ||
79
+ undefined;
78
80
  const chatId = formatQueuedReceiptChatId(args.entry.route);
79
81
  const meta: BncrDurableQueuedReceiptMeta = {
80
82
  status: 'accepted',
@@ -66,7 +66,8 @@ export function buildBncrMediaOutboundFrame(params: {
66
66
  messageId: params.messageId,
67
67
  idempotencyKey: params.messageId,
68
68
  sessionKey: params.sessionKey,
69
- replyToId: normalizeOutboundReplyToId({ kind: params.kind, replyToId: params.replyToId }) || undefined,
69
+ replyToId:
70
+ normalizeOutboundReplyToId({ kind: params.kind, replyToId: params.replyToId }) || undefined,
70
71
  message: {
71
72
  platform: params.route.platform,
72
73
  groupId: params.route.groupId,
@@ -34,7 +34,10 @@ export function computeOutboxRetryWait(nextAttemptAt: number, nowMs: number): nu
34
34
  return Math.max(0, finiteNumberOr(nextAttemptAt, 0) - finiteNumberOr(nowMs, 0));
35
35
  }
36
36
 
37
- export function updateMinOutboxDelay(currentDelay: number | null, candidateDelay: number | null): number | null {
37
+ export function updateMinOutboxDelay(
38
+ currentDelay: number | null,
39
+ candidateDelay: number | null,
40
+ ): number | null {
38
41
  if (candidateDelay == null) return currentDelay;
39
42
  return currentDelay == null ? candidateDelay : Math.min(currentDelay, candidateDelay);
40
43
  }
@@ -51,7 +54,9 @@ export function selectOutboxTargetAccounts(args: {
51
54
  const filterAcc = args.accountId ? args.normalizeAccountId(args.accountId) : null;
52
55
  if (filterAcc) return [filterAcc];
53
56
  return Array.from(
54
- new Set(Array.from(args.outboxEntries).map((entry) => args.normalizeAccountId(entry.accountId))),
57
+ new Set(
58
+ Array.from(args.outboxEntries).map((entry) => args.normalizeAccountId(entry.accountId)),
59
+ ),
55
60
  );
56
61
  }
57
62
 
@@ -112,7 +117,11 @@ export function selectOutboxFileTransferRouteCandidates(args: {
112
117
  );
113
118
  const ownerConnId =
114
119
  args.ownerConnId && !attemptedConnIds.has(args.ownerConnId) ? args.ownerConnId : undefined;
115
- let connIds = ownerConnId ? [ownerConnId] : filteredCandidates.length > 0 ? filteredCandidates : routeCandidates;
120
+ let connIds = ownerConnId
121
+ ? [ownerConnId]
122
+ : filteredCandidates.length > 0
123
+ ? filteredCandidates
124
+ : routeCandidates;
116
125
  let routeReason: OutboxFileTransferRouteSelection['routeReason'] = ownerConnId
117
126
  ? 'owner'
118
127
  : connIds.length > 0
@@ -128,7 +137,8 @@ export function selectOutboxFileTransferRouteCandidates(args: {
128
137
  const filteredRecentInboundConnIds = recentInboundConnIds.filter(
129
138
  (connId) => !attemptedConnIds.has(connId),
130
139
  );
131
- connIds = filteredRecentInboundConnIds.length > 0 ? filteredRecentInboundConnIds : recentInboundConnIds;
140
+ connIds =
141
+ filteredRecentInboundConnIds.length > 0 ? filteredRecentInboundConnIds : recentInboundConnIds;
132
142
  routeReason = connIds.length > 0 ? 'recent-inbound-fallback' : 'none';
133
143
  }
134
144
 
@@ -154,9 +164,12 @@ export function selectOutboxRouteCandidates(args: {
154
164
  const revalidatedCandidates = routeCandidates.filter(
155
165
  (connId) => attemptedConnIds.has(connId) && args.isRevalidatedAttemptedConn(connId),
156
166
  );
157
- const preferredCandidates = unattemptedCandidates.length > 0 ? unattemptedCandidates : routeCandidates;
167
+ const preferredCandidates =
168
+ unattemptedCandidates.length > 0 ? unattemptedCandidates : routeCandidates;
158
169
  const ownerConnId =
159
- args.ownerConnId && preferredCandidates.includes(args.ownerConnId) ? args.ownerConnId : undefined;
170
+ args.ownerConnId && preferredCandidates.includes(args.ownerConnId)
171
+ ? args.ownerConnId
172
+ : undefined;
160
173
  let connIds = ownerConnId ? [ownerConnId] : preferredCandidates;
161
174
  let routeReason: OutboxRouteSelection['routeReason'] = ownerConnId
162
175
  ? 'owner'
@@ -38,6 +38,8 @@ export const OUTBOUND_SCHEDULE_SOURCE = {
38
38
  RETRY_REROUTE_WAIT: 'retry-reroute-wait',
39
39
  // Direct push failure kept entry in outbox and scheduled backoff.
40
40
  PUSH_FAIL_WAIT: 'push-fail-wait',
41
+ // Pre-push guard deferred delivery before an actual send attempt.
42
+ PRE_PUSH_GUARD_WAIT: 'pre-push-guard-wait',
41
43
  // Per-account flush processed its single-run item budget and yielded to the next drain.
42
44
  ACCOUNT_BUDGET_YIELD: 'account-budget-yield',
43
45
  // Per-account flush spent its single-run time budget and yielded to the next drain.
@@ -146,7 +146,11 @@ export function enqueueReplyTextEntry(
146
146
  export function enqueueReplyMediaFallbackTextEntry(
147
147
  params: ReplyMediaFallbackTextEntryParams,
148
148
  helpers: {
149
- logInfo: (scope: string | undefined, message: string, options?: { debugOnly?: boolean }) => void;
149
+ logInfo: (
150
+ scope: string | undefined,
151
+ message: string,
152
+ options?: { debugOnly?: boolean },
153
+ ) => void;
150
154
  enqueueOutbound: (entry: OutboxEntry) => void;
151
155
  buildTextOutboxEntry: (args: {
152
156
  accountId: string;
@@ -1,5 +1,3 @@
1
- import type { BncrRoute } from '../../channel.ts';
2
-
3
1
  function finiteNonNegativeIntegerOr(value: unknown, fallback: number): number {
4
2
  const n = Number(value);
5
3
  if (!Number.isFinite(n) || n < 0) return fallback;
@@ -48,14 +46,18 @@ export function computeRetryRerouteDecision(
48
46
  ? input.attemptedConnIds.filter((v): v is string => typeof v === 'string' && !!v)
49
47
  : [];
50
48
  const currentConnId = `${input.currentConnId || ''}`.trim();
51
- if (currentConnId && !attemptedConnIds.includes(currentConnId)) attemptedConnIds.push(currentConnId);
49
+ if (currentConnId && !attemptedConnIds.includes(currentConnId))
50
+ attemptedConnIds.push(currentConnId);
52
51
 
53
52
  const availableConnIds = Array.isArray(input.availableConnIds)
54
53
  ? input.availableConnIds.filter((v): v is string => typeof v === 'string' && !!v)
55
54
  : [];
56
55
  const revalidatedConnIds = attemptedConnIds.filter((connId) => availableConnIds.includes(connId));
57
- const hasUntriedAlternative = availableConnIds.some((connId) => !attemptedConnIds.includes(connId));
58
- const shouldFastReroute = input.requireAck && input.currentFastReroutePending !== true && hasUntriedAlternative;
56
+ const hasUntriedAlternative = availableConnIds.some(
57
+ (connId) => !attemptedConnIds.includes(connId),
58
+ );
59
+ const shouldFastReroute =
60
+ input.requireAck && input.currentFastReroutePending !== true && hasUntriedAlternative;
59
61
 
60
62
  const currentRetryCount = finiteNonNegativeIntegerOr(input.currentRetryCount, 0);
61
63
  const currentRouteAttemptRound = finiteNonNegativeIntegerOr(input.currentRouteAttemptRound, 0);
@@ -73,10 +75,16 @@ export function computeRetryRerouteDecision(
73
75
  };
74
76
  }
75
77
 
76
- const nextAttemptAt = shouldFastReroute ? input.nowMs + 1_000 : input.nowMs + deps.backoffMs(nextRetryCount);
78
+ const nextAttemptAt = shouldFastReroute
79
+ ? input.nowMs + 1_000
80
+ : input.nowMs + deps.backoffMs(nextRetryCount);
77
81
  const lastError = input.requireAck ? 'push-ack-timeout' : 'push-delivery-unconfirmed';
78
- const routeAttemptRound = hasUntriedAlternative ? currentRouteAttemptRound : currentRouteAttemptRound + 1;
79
- const fastReroutePending = hasUntriedAlternative ? shouldFastReroute || input.currentFastReroutePending === true : false;
82
+ const routeAttemptRound = hasUntriedAlternative
83
+ ? currentRouteAttemptRound
84
+ : currentRouteAttemptRound + 1;
85
+ const fastReroutePending = hasUntriedAlternative
86
+ ? shouldFastReroute || input.currentFastReroutePending === true
87
+ : false;
80
88
 
81
89
  return {
82
90
  kind: 'retry',
@@ -1,4 +1,3 @@
1
- import { buildOpenClawChannelOutboundSessionRoute } from '../../openclaw/session-route-runtime.ts';
2
1
  import {
3
2
  buildCanonicalBncrSessionKey,
4
3
  formatDisplayScope,
@@ -7,6 +6,7 @@ import {
7
6
  routeScopeToHex,
8
7
  } from '../../core/targets.ts';
9
8
  import type { BncrRoute } from '../../core/types.ts';
9
+ import { buildOpenClawChannelOutboundSessionRoute } from '../../openclaw/session-route-runtime.ts';
10
10
 
11
11
  type ResolveBncrOutboundSessionRouteParams = {
12
12
  cfg: any;
@@ -44,10 +44,7 @@ function resolveReplyApi(api: RuntimeApiHolder): RuntimeReplyApi {
44
44
  return reply;
45
45
  }
46
46
 
47
- export function resolveOpenClawEnvelopeFormatOptions(
48
- api: RuntimeApiHolder,
49
- cfg: unknown,
50
- ): unknown {
47
+ export function resolveOpenClawEnvelopeFormatOptions(api: RuntimeApiHolder, cfg: unknown): unknown {
51
48
  const reply = resolveReplyApi(api);
52
49
  if (typeof reply.resolveEnvelopeFormatOptions !== 'function') {
53
50
  throw new Error('OpenClaw channel reply resolveEnvelopeFormatOptions API is unavailable');
@@ -98,7 +95,9 @@ export async function dispatchOpenClawReplyWithBufferedBlockDispatcher(
98
95
  ): Promise<unknown> {
99
96
  const reply = resolveReplyApi(api);
100
97
  if (typeof reply.dispatchReplyWithBufferedBlockDispatcher !== 'function') {
101
- throw new Error('OpenClaw channel reply dispatchReplyWithBufferedBlockDispatcher API is unavailable');
98
+ throw new Error(
99
+ 'OpenClaw channel reply dispatchReplyWithBufferedBlockDispatcher API is unavailable',
100
+ );
102
101
  }
103
102
  return reply.dispatchReplyWithBufferedBlockDispatcher(params);
104
103
  }
@@ -45,4 +45,3 @@ export function resolveOpenClawInboundLastRouteSessionKey(params: {
45
45
  }): string {
46
46
  return resolveInboundLastRouteSessionKey(params as any);
47
47
  }
48
-
@@ -4,7 +4,10 @@ import {
4
4
  jsonResult as sdkJsonResult,
5
5
  setAccountEnabledInConfigSection as sdkSetAccountEnabledInConfigSection,
6
6
  } from 'openclaw/plugin-sdk/core';
7
- import { readJsonFileWithFallback as sdkReadJsonFileWithFallback, writeJsonFileAtomically as sdkWriteJsonFileAtomically } from 'openclaw/plugin-sdk/json-store';
7
+ import {
8
+ readJsonFileWithFallback as sdkReadJsonFileWithFallback,
9
+ writeJsonFileAtomically as sdkWriteJsonFileAtomically,
10
+ } from 'openclaw/plugin-sdk/json-store';
8
11
  import { readStringParam as sdkReadStringParam } from 'openclaw/plugin-sdk/param-readers';
9
12
  import { createDefaultChannelRuntimeState as sdkCreateDefaultChannelRuntimeState } from 'openclaw/plugin-sdk/status-helpers';
10
13
  import { extractToolSend as sdkExtractToolSend } from 'openclaw/plugin-sdk/tool-send';
@@ -1,9 +1,5 @@
1
1
  import { BNCR_DEFAULT_ACCOUNT_ID, normalizeAccountId } from '../core/accounts.ts';
2
- import {
3
- formatDisplayScope,
4
- formatTargetDisplay,
5
- parseExplicitTarget,
6
- } from '../core/targets.ts';
2
+ import { formatDisplayScope, formatTargetDisplay, parseExplicitTarget } from '../core/targets.ts';
7
3
  import { resolveBncrOutboundSessionRoute } from '../messaging/outbound/session-route.ts';
8
4
  import {
9
5
  looksLikeBncrExplicitTarget,
@@ -38,10 +34,7 @@ function resolveMessagingCanonicalAgentId(
38
34
  cfg: any,
39
35
  accountId: string,
40
36
  ) {
41
- return (
42
- runtimeBridge.canonicalAgentId ||
43
- runtimeBridge.ensureCanonicalAgentId({ cfg, accountId })
44
- );
37
+ return runtimeBridge.canonicalAgentId || runtimeBridge.ensureCanonicalAgentId({ cfg, accountId });
45
38
  }
46
39
 
47
40
  export function createBncrMessagingExplicitTargetParser(
@@ -1,4 +1,8 @@
1
- import { BNCR_DEFAULT_ACCOUNT_ID, resolveAccount, resolveDefaultDisplayName } from '../core/accounts.ts';
1
+ import {
2
+ BNCR_DEFAULT_ACCOUNT_ID,
3
+ resolveAccount,
4
+ resolveDefaultDisplayName,
5
+ } from '../core/accounts.ts';
2
6
  import { buildAccountStatusSnapshot } from '../core/status.ts';
3
7
  import { createOpenClawDefaultChannelRuntimeState } from '../openclaw/sdk-helpers.ts';
4
8
 
@@ -1,4 +1,4 @@
1
- import { CHANNEL_ID, BNCR_DEFAULT_ACCOUNT_ID, normalizeAccountId } from '../core/accounts.ts';
1
+ import { BNCR_DEFAULT_ACCOUNT_ID, CHANNEL_ID, normalizeAccountId } from '../core/accounts.ts';
2
2
  import { getOpenClawRuntimeConfig } from '../openclaw/config-runtime.ts';
3
3
 
4
4
  type RuntimeApiHolder = { api: unknown };
@@ -103,9 +103,7 @@ export function buildBncrAckOkTelemetryPatch(args: {
103
103
  const defaultAckTimeoutMs = Math.max(0, finiteNumberOr(args.defaultAckTimeoutMs, 0));
104
104
  const ackQueueLatencyMs = Math.max(0, ackAt - finiteNumberOr(args.entry.createdAt, ackAt));
105
105
  const ackPushLatencyMs =
106
- typeof args.entry.lastPushAt === 'number'
107
- ? Math.max(0, ackAt - args.entry.lastPushAt)
108
- : null;
106
+ typeof args.entry.lastPushAt === 'number' ? Math.max(0, ackAt - args.entry.lastPushAt) : null;
109
107
  const lateAccepted = args.entry.awaitingRetryPush === true;
110
108
  return {
111
109
  ackAt,
@@ -114,6 +112,8 @@ export function buildBncrAckOkTelemetryPatch(args: {
114
112
  lateAccepted,
115
113
  shouldResetAdaptiveAckRecovery: lateAccepted,
116
114
  shouldIncrementAdaptiveAckRecovery:
117
- !lateAccepted && typeof ackPushLatencyMs === 'number' && ackPushLatencyMs <= defaultAckTimeoutMs,
115
+ !lateAccepted &&
116
+ typeof ackPushLatencyMs === 'number' &&
117
+ ackPushLatencyMs <= defaultAckTimeoutMs,
118
118
  };
119
119
  }
@@ -15,7 +15,9 @@ export function buildRuntimeQueueSnapshot(args: {
15
15
  }) {
16
16
  const accountId = normalizeAccountId(args.accountId);
17
17
  const pending = Array.from(args.outboxEntries).filter((v) => v.accountId === accountId).length;
18
- const deadLetter = Array.from(args.deadLetterEntries).filter((v) => v.accountId === accountId).length;
18
+ const deadLetter = Array.from(args.deadLetterEntries).filter(
19
+ (v) => v.accountId === accountId,
20
+ ).length;
19
21
  const sessionRoutesCount = Array.from(args.sessionRouteEntries).filter(
20
22
  (v) => v.accountId === accountId,
21
23
  ).length;
@@ -60,7 +60,10 @@ export function clearAllBncrStatusWorkers(runtime: StatusWorkerRuntime, reason:
60
60
  }
61
61
  }
62
62
 
63
- export async function startBncrStatusWorker(runtime: StatusWorkerRuntime, ctx: StatusWorkerContext) {
63
+ export async function startBncrStatusWorker(
64
+ runtime: StatusWorkerRuntime,
65
+ ctx: StatusWorkerContext,
66
+ ) {
64
67
  const accountId = normalizeAccountId(ctx.accountId);
65
68
  clearBncrStatusWorker(runtime, accountId, 'start-replace');
66
69
 
@@ -151,7 +154,10 @@ export async function startBncrStatusWorker(runtime: StatusWorkerRuntime, ctx: S
151
154
  await done;
152
155
  }
153
156
 
154
- export async function stopBncrStatusWorker(runtime: StatusWorkerRuntime, ctx: Partial<StatusWorkerContext>) {
157
+ export async function stopBncrStatusWorker(
158
+ runtime: StatusWorkerRuntime,
159
+ ctx: Partial<StatusWorkerContext>,
160
+ ) {
155
161
  const accountId = normalizeAccountId(ctx?.accountId);
156
162
  const cleared = clearBncrStatusWorker(runtime, accountId, 'explicit-stop');
157
163
  const previous = ctx?.getStatus?.() || {};