@xmoxmo/bncr 0.2.5 → 0.2.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.
- package/README.md +9 -3
- package/index.ts +30 -15
- package/package.json +4 -3
- package/scripts/check-pack.mjs +61 -0
- package/scripts/selfcheck.mjs +10 -0
- package/src/channel.ts +892 -255
- package/src/core/connection-reachability.ts +41 -14
- package/src/core/diagnostics.ts +7 -2
- package/src/core/downlink-health.ts +7 -2
- package/src/core/outbox-entry-builders.ts +3 -2
- package/src/core/policy.ts +9 -0
- package/src/core/register-trace.ts +6 -1
- package/src/core/status.ts +7 -2
- package/src/core/targets.ts +10 -1
- package/src/core/types.ts +1 -0
- package/src/messaging/inbound/commands.ts +330 -77
- package/src/messaging/inbound/context-facts.ts +200 -0
- package/src/messaging/inbound/dispatch.ts +429 -119
- package/src/messaging/inbound/gate.ts +66 -26
- package/src/messaging/inbound/parse.ts +8 -0
- package/src/messaging/inbound/runtime-compat.ts +39 -0
- package/src/messaging/inbound/session-label.ts +115 -0
- package/src/messaging/outbound/diagnostics.ts +16 -0
- package/src/messaging/outbound/durable-message-adapter.ts +107 -0
- package/src/messaging/outbound/durable-queue-adapter.ts +157 -0
- package/src/messaging/outbound/media.ts +3 -1
- package/src/messaging/outbound/queue-selectors.ts +7 -2
- package/src/messaging/outbound/reasons.ts +4 -0
- package/src/messaging/outbound/reply-enqueue.ts +2 -2
- package/src/messaging/outbound/reply-target-policy.ts +13 -0
- package/src/messaging/outbound/retry-policy.ts +12 -3
- package/src/messaging/outbound/send.ts +6 -0
- package/src/messaging/outbound/session-route.ts +2 -2
- package/src/openclaw/config-runtime.ts +52 -0
- package/src/openclaw/inbound-session-runtime.ts +94 -0
- package/src/openclaw/ingress-runtime.ts +35 -0
- package/src/openclaw/media-runtime.ts +73 -0
- package/src/openclaw/reply-runtime.ts +104 -0
- package/src/openclaw/routing-runtime.ts +48 -0
- package/src/openclaw/sdk-helpers.ts +20 -0
- package/src/openclaw/session-route-runtime.ts +15 -0
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
import type { BncrConnection, OutboxEntry } from './types.ts';
|
|
2
2
|
|
|
3
|
+
function finiteNumberOr(value: unknown, fallback: number): number {
|
|
4
|
+
const n = Number(value);
|
|
5
|
+
return Number.isFinite(n) ? n : fallback;
|
|
6
|
+
}
|
|
7
|
+
|
|
3
8
|
export function hasRecentInboundReachability(args: {
|
|
4
9
|
now: number;
|
|
5
10
|
windowMs: number;
|
|
6
11
|
lastInboundAt: number;
|
|
7
12
|
lastActivityAt: number;
|
|
8
13
|
}) {
|
|
9
|
-
const
|
|
10
|
-
|
|
14
|
+
const nowMs = finiteNumberOr(args.now, 0);
|
|
15
|
+
const windowMs = finiteNumberOr(args.windowMs, 0);
|
|
16
|
+
const lastReachableAt = Math.max(
|
|
17
|
+
finiteNumberOr(args.lastInboundAt, 0),
|
|
18
|
+
finiteNumberOr(args.lastActivityAt, 0),
|
|
19
|
+
);
|
|
20
|
+
return nowMs > 0 && windowMs > 0 && lastReachableAt > 0 && nowMs - lastReachableAt <= windowMs;
|
|
11
21
|
}
|
|
12
22
|
|
|
13
23
|
export function resolveRecentInboundConnIds(args: {
|
|
@@ -20,10 +30,16 @@ export function resolveRecentInboundConnIds(args: {
|
|
|
20
30
|
const connIds = new Set<string>();
|
|
21
31
|
if (!args.recentInboundReachable) return connIds;
|
|
22
32
|
|
|
33
|
+
const nowMs = finiteNumberOr(args.now, 0);
|
|
34
|
+
const connectTtlMs = finiteNumberOr(args.connectTtlMs, 0);
|
|
35
|
+
if (!nowMs || !connectTtlMs) return connIds;
|
|
36
|
+
|
|
23
37
|
for (const c of args.connections) {
|
|
24
38
|
if (c.accountId !== args.accountId) continue;
|
|
25
39
|
if (!c.connId) continue;
|
|
26
|
-
|
|
40
|
+
const lastSeenAt = finiteNumberOr(c.lastSeenAt, 0);
|
|
41
|
+
if (!lastSeenAt) continue;
|
|
42
|
+
if (nowMs - lastSeenAt > connectTtlMs * 2) continue;
|
|
27
43
|
connIds.add(c.connId);
|
|
28
44
|
}
|
|
29
45
|
|
|
@@ -60,11 +76,16 @@ export function hasAlternativeLiveConnection(args: {
|
|
|
60
76
|
}) {
|
|
61
77
|
const currentConn = String(args.currentConnId || '').trim();
|
|
62
78
|
const currentClient = String(args.currentClientId || '').trim() || undefined;
|
|
79
|
+
const nowMs = finiteNumberOr(args.now, 0);
|
|
80
|
+
const connectTtlMs = finiteNumberOr(args.connectTtlMs, 0);
|
|
81
|
+
if (!nowMs || !connectTtlMs) return false;
|
|
63
82
|
|
|
64
83
|
for (const conn of args.connections) {
|
|
65
84
|
if (conn.accountId !== args.accountId) continue;
|
|
66
85
|
if (!conn.connId) continue;
|
|
67
|
-
|
|
86
|
+
const lastSeenAt = finiteNumberOr(conn.lastSeenAt, 0);
|
|
87
|
+
if (!lastSeenAt) continue;
|
|
88
|
+
if (nowMs - lastSeenAt > connectTtlMs) continue;
|
|
68
89
|
const sameConn = !!currentConn && conn.connId === currentConn;
|
|
69
90
|
const sameClient = !currentConn && !!currentClient && conn.clientId === currentClient;
|
|
70
91
|
if (sameConn || sameClient) continue;
|
|
@@ -93,27 +114,33 @@ export function getRevalidatedAttemptReason(args: {
|
|
|
93
114
|
const targetConnId = String(args.connId || '').trim();
|
|
94
115
|
if (!targetConnId) return null;
|
|
95
116
|
|
|
96
|
-
const
|
|
117
|
+
const nowMs = finiteNumberOr(args.now, 0);
|
|
118
|
+
const connectTtlMs = finiteNumberOr(args.connectTtlMs, 0);
|
|
119
|
+
if (!nowMs || !connectTtlMs) return null;
|
|
120
|
+
|
|
121
|
+
const lastAttemptAt = finiteNumberOr(args.entry.lastAttemptAt, 0);
|
|
97
122
|
for (const rawConn of args.connections) {
|
|
98
123
|
const conn = rawConn as ReachableConnection;
|
|
99
124
|
if (conn.accountId !== args.accountId) continue;
|
|
100
125
|
if (conn.connId !== targetConnId) continue;
|
|
101
|
-
|
|
126
|
+
const lastSeenAt = finiteNumberOr(conn.lastSeenAt, 0);
|
|
127
|
+
if (!lastSeenAt) continue;
|
|
128
|
+
if (nowMs - lastSeenAt > connectTtlMs) continue;
|
|
102
129
|
if (conn.inboundOnly === true) continue;
|
|
103
130
|
|
|
104
|
-
const preferredForOutboundUntil =
|
|
105
|
-
const outboundReadyUntil =
|
|
106
|
-
const lastAckOkAt =
|
|
107
|
-
const lastPushTimeoutAt =
|
|
131
|
+
const preferredForOutboundUntil = finiteNumberOr(conn.preferredForOutboundUntil, 0);
|
|
132
|
+
const outboundReadyUntil = finiteNumberOr(conn.outboundReadyUntil, 0);
|
|
133
|
+
const lastAckOkAt = finiteNumberOr(conn.lastAckOkAt, 0);
|
|
134
|
+
const lastPushTimeoutAt = finiteNumberOr(conn.lastPushTimeoutAt, 0);
|
|
108
135
|
|
|
109
|
-
const revalidatedByPreferred = preferredForOutboundUntil >
|
|
110
|
-
const revalidatedByReady = outboundReadyUntil >
|
|
136
|
+
const revalidatedByPreferred = preferredForOutboundUntil > nowMs;
|
|
137
|
+
const revalidatedByReady = outboundReadyUntil > nowMs;
|
|
111
138
|
const revalidatedByAck = lastAckOkAt > 0 && lastAckOkAt > lastAttemptAt;
|
|
112
139
|
const revalidatedByFreshReachability =
|
|
113
140
|
args.recentInboundReachable &&
|
|
114
141
|
lastPushTimeoutAt > 0 &&
|
|
115
142
|
lastPushTimeoutAt <= lastAttemptAt &&
|
|
116
|
-
|
|
143
|
+
lastSeenAt > lastPushTimeoutAt;
|
|
117
144
|
|
|
118
145
|
if (!revalidatedByPreferred && !revalidatedByReady && !revalidatedByAck && !revalidatedByFreshReachability) {
|
|
119
146
|
return null;
|
|
@@ -132,7 +159,7 @@ export function getRevalidatedAttemptReason(args: {
|
|
|
132
159
|
lastPushTimeoutAt: lastPushTimeoutAt || null,
|
|
133
160
|
outboundReadyUntil: outboundReadyUntil || null,
|
|
134
161
|
preferredForOutboundUntil: preferredForOutboundUntil || null,
|
|
135
|
-
lastSeenAt
|
|
162
|
+
lastSeenAt,
|
|
136
163
|
recentInboundReachable: args.recentInboundReachable,
|
|
137
164
|
};
|
|
138
165
|
}
|
package/src/core/diagnostics.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { buildBncrPermissionSummary } from './permissions.ts';
|
|
2
2
|
import { probeBncrAccount } from './probe.ts';
|
|
3
3
|
|
|
4
|
+
function finiteNumberOr(value: unknown, fallback: number): number {
|
|
5
|
+
const n = Number(value);
|
|
6
|
+
return Number.isFinite(n) ? n : fallback;
|
|
7
|
+
}
|
|
8
|
+
|
|
4
9
|
type DiagnosticsPayloadArgs = {
|
|
5
10
|
cfg: any;
|
|
6
11
|
channelId: string;
|
|
@@ -21,8 +26,8 @@ export function buildDiagnosticsPayload(args: DiagnosticsPayloadArgs) {
|
|
|
21
26
|
const probe = probeBncrAccount({
|
|
22
27
|
accountId: args.accountId,
|
|
23
28
|
connected: Boolean(args.runtime?.connected),
|
|
24
|
-
pending:
|
|
25
|
-
deadLetter:
|
|
29
|
+
pending: finiteNumberOr(args.runtime?.meta?.pending, 0),
|
|
30
|
+
deadLetter: finiteNumberOr(args.runtime?.meta?.deadLetter, 0),
|
|
26
31
|
activeConnections: args.activeConnections,
|
|
27
32
|
invalidOutboxSessionKeys: args.invalidOutboxSessionKeys,
|
|
28
33
|
legacyAccountResidue: args.legacyAccountResidue,
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { OutboxEntry } from './types.ts';
|
|
2
2
|
|
|
3
|
+
function finiteNumberOr(value: unknown, fallback: number): number {
|
|
4
|
+
const n = Number(value);
|
|
5
|
+
return Number.isFinite(n) ? n : fallback;
|
|
6
|
+
}
|
|
7
|
+
|
|
3
8
|
type DownlinkHealthInput = {
|
|
4
9
|
accountId: string;
|
|
5
10
|
now: number;
|
|
@@ -17,13 +22,13 @@ export function buildDownlinkHealth(input: DownlinkHealthInput) {
|
|
|
17
22
|
const pending = Array.from(input.outboxEntries).filter((v) => v.accountId === input.accountId);
|
|
18
23
|
const pendingCount = pending.length;
|
|
19
24
|
const oldestPendingCreatedAt = pending.length
|
|
20
|
-
? Math.min(...pending.map((entry) =>
|
|
25
|
+
? Math.min(...pending.map((entry) => finiteNumberOr(entry.createdAt, input.now)))
|
|
21
26
|
: null;
|
|
22
27
|
const oldestPendingAgeMs = oldestPendingCreatedAt
|
|
23
28
|
? Math.max(0, input.now - oldestPendingCreatedAt)
|
|
24
29
|
: 0;
|
|
25
30
|
const lastSignalAt =
|
|
26
|
-
Math.max(
|
|
31
|
+
Math.max(finiteNumberOr(input.lastInboundAt, 0), finiteNumberOr(input.lastActivityAt, 0)) || null;
|
|
27
32
|
const inboundHealthy = !!lastSignalAt && input.now - lastSignalAt <= 5 * 60 * 1000;
|
|
28
33
|
const ackRecentlyHealthy =
|
|
29
34
|
!!input.lastAckOkAt && input.now - input.lastAckOkAt <= 5 * 60 * 1000;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizeOutboundReplyToId } from '../messaging/outbound/reply-target-policy.ts';
|
|
1
2
|
import type { BncrRoute, OutboxEntry } from './types.ts';
|
|
2
3
|
|
|
3
4
|
export function buildFileTransferOutboxEntry(args: {
|
|
@@ -34,7 +35,7 @@ export function buildFileTransferOutboxEntry(args: {
|
|
|
34
35
|
asVoice: args.asVoice === true,
|
|
35
36
|
audioAsVoice: args.audioAsVoice === true,
|
|
36
37
|
finalEvent: args.pushEvent,
|
|
37
|
-
replyToId: args.replyToId,
|
|
38
|
+
replyToId: normalizeOutboundReplyToId({ kind: args.kind, replyToId: args.replyToId }) || undefined,
|
|
38
39
|
messageKind: args.kind,
|
|
39
40
|
},
|
|
40
41
|
},
|
|
@@ -63,7 +64,7 @@ export function buildTextOutboxEntry(args: {
|
|
|
63
64
|
messageId,
|
|
64
65
|
idempotencyKey: messageId,
|
|
65
66
|
sessionKey: args.sessionKey,
|
|
66
|
-
replyToId: args.
|
|
67
|
+
replyToId: normalizeOutboundReplyToId({ kind: args.kind, replyToId: args.replyToId }) || undefined,
|
|
67
68
|
message: {
|
|
68
69
|
platform: args.route.platform,
|
|
69
70
|
groupId: args.route.groupId,
|
package/src/core/policy.ts
CHANGED
|
@@ -25,3 +25,12 @@ export function resolveBncrChannelPolicy(channelCfg: any) {
|
|
|
25
25
|
requireMention: asBoolean(channelCfg?.requireMention, false),
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
+
|
|
29
|
+
export function resolveBncrConfigWarnings(channelCfg: any): string[] {
|
|
30
|
+
const policy = resolveBncrChannelPolicy(channelCfg || {});
|
|
31
|
+
const warnings: string[] = [];
|
|
32
|
+
if (policy.requireMention) {
|
|
33
|
+
warnings.push('requireMention configured but not enforced yet');
|
|
34
|
+
}
|
|
35
|
+
return warnings;
|
|
36
|
+
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
const DEFAULT_REGISTER_WARMUP_WINDOW_MS = 30_000;
|
|
2
2
|
|
|
3
|
+
function finiteNumberOr(value: unknown, fallback: number): number {
|
|
4
|
+
const n = Number(value);
|
|
5
|
+
return Number.isFinite(n) ? n : fallback;
|
|
6
|
+
}
|
|
7
|
+
|
|
3
8
|
export type RegisterTraceEntry = {
|
|
4
9
|
ts: number;
|
|
5
10
|
bridgeId: string;
|
|
@@ -70,7 +75,7 @@ export function buildRegisterTraceSummary(args: {
|
|
|
70
75
|
}): RegisterTraceSummary {
|
|
71
76
|
const warmupWindowMs = Math.max(
|
|
72
77
|
0,
|
|
73
|
-
|
|
78
|
+
finiteNumberOr(args.warmupWindowMs, DEFAULT_REGISTER_WARMUP_WINDOW_MS),
|
|
74
79
|
);
|
|
75
80
|
const buckets: Record<string, number> = {};
|
|
76
81
|
let warmupCount = 0;
|
package/src/core/status.ts
CHANGED
|
@@ -30,6 +30,11 @@ function now() {
|
|
|
30
30
|
return Date.now();
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
function finiteNumberOr(value: unknown, fallback: number): number {
|
|
34
|
+
const n = Number(value);
|
|
35
|
+
return Number.isFinite(n) ? n : fallback;
|
|
36
|
+
}
|
|
37
|
+
|
|
33
38
|
function fmtAgo(ts?: number | null): string {
|
|
34
39
|
if (!ts || !Number.isFinite(ts) || ts <= 0) return '-';
|
|
35
40
|
const diff = Math.max(0, now() - ts);
|
|
@@ -151,8 +156,8 @@ export function buildAccountStatusSnapshot(input: {
|
|
|
151
156
|
const rt = input.runtime || {};
|
|
152
157
|
const meta = rt?.meta || {};
|
|
153
158
|
|
|
154
|
-
const pending =
|
|
155
|
-
const deadLetter =
|
|
159
|
+
const pending = finiteNumberOr(rt?.pending ?? meta.pending, 0);
|
|
160
|
+
const deadLetter = finiteNumberOr(rt?.deadLetter ?? meta.deadLetter, 0);
|
|
156
161
|
const lastSessionKey = rt?.lastSessionKey ?? meta.lastSessionKey ?? null;
|
|
157
162
|
const lastSessionScope = rt?.lastSessionScope ?? meta.lastSessionScope ?? null;
|
|
158
163
|
const lastSessionAt = rt?.lastSessionAt ?? meta.lastSessionAt ?? null;
|
package/src/core/targets.ts
CHANGED
|
@@ -51,8 +51,17 @@ function parseRouteFromStandardDisplayScope(scope: string): BncrRoute | null {
|
|
|
51
51
|
return null;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
function normalizeDisplayScopePrefix(scope: string): string {
|
|
55
55
|
const raw = asString(scope).trim();
|
|
56
|
+
if (!raw) return '';
|
|
57
|
+
if (raw.startsWith('Bncr:')) return raw;
|
|
58
|
+
if (/^bncr[:-]/i.test(raw)) return raw;
|
|
59
|
+
if (!parseRouteFromStandardDisplayScope(raw)) return raw;
|
|
60
|
+
return `Bncr:${raw}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function parseRouteFromDisplayScope(scope: string): BncrRoute | null {
|
|
64
|
+
const raw = normalizeDisplayScopePrefix(scope);
|
|
56
65
|
if (!raw) return null;
|
|
57
66
|
|
|
58
67
|
const payload = raw.match(/^Bncr:(.+)$/)?.[1];
|