@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.
- package/README.md +15 -1
- package/index.ts +5 -10
- package/package.json +4 -4
- package/scripts/check-pack.mjs +15 -5
- package/scripts/selfcheck.mjs +30 -2
- package/src/channel.ts +79 -182
- package/src/core/accounts.ts +1 -1
- package/src/core/connection-reachability.ts +6 -1
- package/src/core/downlink-health.ts +3 -3
- package/src/core/extended-diagnostics.ts +2 -0
- package/src/core/file-transfer-payloads.ts +1 -4
- package/src/core/outbox-entry-builders.ts +4 -2
- package/src/core/outbox-file-transfer-bookkeeping.ts +1 -1
- package/src/core/outbox-file-transfer-failure.ts +2 -5
- package/src/core/outbox-file-transfer-success.ts +1 -4
- package/src/core/outbox-text-push-failure.ts +2 -4
- package/src/core/outbox-text-push-success.ts +1 -1
- package/src/messaging/inbound/commands.ts +34 -25
- package/src/messaging/inbound/dispatch.ts +16 -18
- package/src/messaging/inbound/parse.ts +3 -3
- package/src/messaging/inbound/runtime-compat.ts +8 -2
- package/src/messaging/outbound/build-send-action.ts +1 -2
- package/src/messaging/outbound/durable-message-adapter.ts +16 -6
- package/src/messaging/outbound/durable-queue-adapter.ts +3 -1
- package/src/messaging/outbound/media.ts +2 -1
- package/src/messaging/outbound/queue-selectors.ts +19 -6
- package/src/messaging/outbound/reasons.ts +2 -0
- package/src/messaging/outbound/reply-enqueue.ts +5 -1
- package/src/messaging/outbound/retry-policy.ts +16 -8
- package/src/messaging/outbound/session-route.ts +1 -1
- package/src/openclaw/reply-runtime.ts +4 -5
- package/src/openclaw/routing-runtime.ts +0 -1
- package/src/openclaw/sdk-helpers.ts +4 -1
- package/src/plugin/messaging.ts +2 -9
- package/src/plugin/status.ts +5 -1
- package/src/runtime/outbound-flags.ts +1 -1
- package/src/runtime/outbox-transitions.ts +4 -4
- package/src/runtime/status-snapshots.ts +3 -1
- 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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
} from '
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
} from '
|
|
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) || '')
|
|
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')
|
|
47
|
-
|
|
48
|
-
if (rawLevel === '
|
|
49
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
} =
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
} from '
|
|
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 } =
|
|
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
|
|
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 =
|
|
53
|
-
params?.originatingTo || params?.providedOriginatingTo || params?.to || ''
|
|
54
|
-
|
|
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 =
|
|
24
|
-
|
|
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-
|
|
7
|
+
} from 'openclaw/plugin-sdk/channel-message';
|
|
8
|
+
import { defineChannelMessageAdapter } from 'openclaw/plugin-sdk/channel-message';
|
|
9
9
|
|
|
10
|
-
import {
|
|
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?: (
|
|
17
|
-
|
|
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(
|
|
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 =
|
|
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:
|
|
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(
|
|
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(
|
|
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
|
|
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 =
|
|
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 =
|
|
167
|
+
const preferredCandidates =
|
|
168
|
+
unattemptedCandidates.length > 0 ? unattemptedCandidates : routeCandidates;
|
|
158
169
|
const ownerConnId =
|
|
159
|
-
args.ownerConnId && preferredCandidates.includes(args.ownerConnId)
|
|
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: (
|
|
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))
|
|
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(
|
|
58
|
-
|
|
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
|
|
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
|
|
79
|
-
|
|
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(
|
|
98
|
+
throw new Error(
|
|
99
|
+
'OpenClaw channel reply dispatchReplyWithBufferedBlockDispatcher API is unavailable',
|
|
100
|
+
);
|
|
102
101
|
}
|
|
103
102
|
return reply.dispatchReplyWithBufferedBlockDispatcher(params);
|
|
104
103
|
}
|
|
@@ -4,7 +4,10 @@ import {
|
|
|
4
4
|
jsonResult as sdkJsonResult,
|
|
5
5
|
setAccountEnabledInConfigSection as sdkSetAccountEnabledInConfigSection,
|
|
6
6
|
} from 'openclaw/plugin-sdk/core';
|
|
7
|
-
import {
|
|
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';
|
package/src/plugin/messaging.ts
CHANGED
|
@@ -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(
|
package/src/plugin/status.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
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 &&
|
|
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(
|
|
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(
|
|
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(
|
|
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?.() || {};
|