@xmoxmo/bncr 0.1.5 → 0.1.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/package.json
CHANGED
package/src/channel.ts
CHANGED
|
@@ -38,9 +38,11 @@ import {
|
|
|
38
38
|
import {
|
|
39
39
|
buildCanonicalBncrSessionKey,
|
|
40
40
|
formatDisplayScope,
|
|
41
|
+
formatTargetDisplay,
|
|
41
42
|
isLowerHex,
|
|
42
43
|
normalizeInboundSessionKey,
|
|
43
44
|
normalizeStoredSessionKey,
|
|
45
|
+
parseExplicitTarget,
|
|
44
46
|
parseRouteFromDisplayScope,
|
|
45
47
|
parseRouteFromHexScope,
|
|
46
48
|
parseRouteFromScope,
|
|
@@ -65,6 +67,11 @@ import {
|
|
|
65
67
|
resolveBncrOutboundMessageType,
|
|
66
68
|
} from './messaging/outbound/media.ts';
|
|
67
69
|
import { sendBncrMedia, sendBncrText } from './messaging/outbound/send.ts';
|
|
70
|
+
import { resolveBncrOutboundSessionRoute } from './messaging/outbound/session-route.ts';
|
|
71
|
+
import {
|
|
72
|
+
looksLikeBncrExplicitTarget,
|
|
73
|
+
resolveBncrOutboundTarget,
|
|
74
|
+
} from './messaging/outbound/target-resolver.ts';
|
|
68
75
|
const BRIDGE_VERSION = 2;
|
|
69
76
|
const BNCR_PUSH_EVENT = 'bncr.push';
|
|
70
77
|
const CONNECT_TTL_MS = 120_000;
|
|
@@ -502,7 +509,9 @@ class BncrBridgeRuntime {
|
|
|
502
509
|
const summary = this.buildRegisterTraceSummary();
|
|
503
510
|
if (summary.postWarmupRegisterCount > 0) this.captureDriftSnapshot(summary);
|
|
504
511
|
|
|
505
|
-
|
|
512
|
+
if (BNCR_DEBUG_VERBOSE) {
|
|
513
|
+
this.api.logger.info?.(`[bncr-register-trace] ${JSON.stringify(trace)}`);
|
|
514
|
+
}
|
|
506
515
|
}
|
|
507
516
|
|
|
508
517
|
private createLeaseId() {
|
|
@@ -1554,77 +1563,35 @@ class BncrBridgeRuntime {
|
|
|
1554
1563
|
);
|
|
1555
1564
|
}
|
|
1556
1565
|
|
|
1557
|
-
const
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
)
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
if (normalizeAccountId(info.accountId) !== acc) continue;
|
|
1571
|
-
const parsed = parseStrictBncrSessionKey(key);
|
|
1572
|
-
if (!parsed) continue;
|
|
1573
|
-
if (routeKey(acc, parsed.route) !== wantedRouteKey) continue;
|
|
1574
|
-
|
|
1575
|
-
const updatedAt = Number(info.updatedAt || 0);
|
|
1576
|
-
if (!best || updatedAt >= best.updatedAt) {
|
|
1577
|
-
best = {
|
|
1578
|
-
sessionKey: key,
|
|
1579
|
-
route: parsed.route,
|
|
1580
|
-
updatedAt,
|
|
1581
|
-
};
|
|
1582
|
-
}
|
|
1583
|
-
}
|
|
1584
|
-
|
|
1585
|
-
if (!best) {
|
|
1586
|
-
const updatedAt = 0;
|
|
1587
|
-
const canonicalAgentId =
|
|
1588
|
-
this.canonicalAgentId ||
|
|
1589
|
-
this.ensureCanonicalAgentId({
|
|
1590
|
-
cfg: this.api.runtime.config?.get?.() || {},
|
|
1591
|
-
accountId: acc,
|
|
1592
|
-
channelId: CHANNEL_ID,
|
|
1593
|
-
peer: { kind: 'direct', id: route.groupId === '0' ? route.userId : route.groupId },
|
|
1594
|
-
});
|
|
1595
|
-
best = {
|
|
1596
|
-
sessionKey: buildCanonicalBncrSessionKey(route, canonicalAgentId),
|
|
1597
|
-
route,
|
|
1598
|
-
updatedAt,
|
|
1599
|
-
};
|
|
1600
|
-
}
|
|
1566
|
+
const canonicalAgentId =
|
|
1567
|
+
this.canonicalAgentId ||
|
|
1568
|
+
this.ensureCanonicalAgentId({
|
|
1569
|
+
cfg: this.api.runtime.config?.get?.() || {},
|
|
1570
|
+
accountId: acc,
|
|
1571
|
+
channelId: CHANNEL_ID,
|
|
1572
|
+
peer: { kind: 'direct', id: route.groupId === '0' ? route.userId : route.groupId },
|
|
1573
|
+
});
|
|
1574
|
+
const verified = {
|
|
1575
|
+
sessionKey: buildCanonicalBncrSessionKey(route, canonicalAgentId),
|
|
1576
|
+
route,
|
|
1577
|
+
displayScope: formatDisplayScope(route),
|
|
1578
|
+
};
|
|
1601
1579
|
|
|
1602
1580
|
if (BNCR_DEBUG_VERBOSE) {
|
|
1603
1581
|
this.api.logger.info?.(
|
|
1604
|
-
`[bncr-target-incoming-
|
|
1582
|
+
`[bncr-target-incoming-canonical] raw=${raw} accountId=${acc} verified=${JSON.stringify(verified)}`,
|
|
1605
1583
|
);
|
|
1606
1584
|
}
|
|
1607
1585
|
|
|
1608
|
-
if (!best) {
|
|
1609
|
-
this.api.logger.warn?.(
|
|
1610
|
-
`[bncr-target-miss] raw=${raw} accountId=${acc} sessionRoutes=${this.sessionRoutes.size}`,
|
|
1611
|
-
);
|
|
1612
|
-
throw new Error(`bncr target not found in known sessions: ${raw}`);
|
|
1613
|
-
}
|
|
1614
|
-
|
|
1615
1586
|
// 发送链路命中目标时,同步刷新 lastSession,避免状态页显示过期会话。
|
|
1616
1587
|
this.lastSessionByAccount.set(acc, {
|
|
1617
|
-
sessionKey:
|
|
1618
|
-
scope:
|
|
1588
|
+
sessionKey: verified.sessionKey,
|
|
1589
|
+
scope: verified.displayScope,
|
|
1619
1590
|
updatedAt: now(),
|
|
1620
1591
|
});
|
|
1621
1592
|
this.scheduleSave();
|
|
1622
1593
|
|
|
1623
|
-
return
|
|
1624
|
-
sessionKey: best.sessionKey,
|
|
1625
|
-
route: best.route,
|
|
1626
|
-
displayScope: formatDisplayScope(best.route),
|
|
1627
|
-
};
|
|
1594
|
+
return verified;
|
|
1628
1595
|
}
|
|
1629
1596
|
|
|
1630
1597
|
private markActivity(accountId: string, at = now()) {
|
|
@@ -3068,9 +3035,68 @@ export function createBncrChannelPlugin(bridge: BncrBridgeRuntime) {
|
|
|
3068
3035
|
const input = asString(raw).trim();
|
|
3069
3036
|
return input || undefined;
|
|
3070
3037
|
},
|
|
3038
|
+
parseExplicitTarget: ({ raw, accountId, cfg }: any) => {
|
|
3039
|
+
const resolvedAccountId = normalizeAccountId(
|
|
3040
|
+
asString(accountId || BNCR_DEFAULT_ACCOUNT_ID),
|
|
3041
|
+
);
|
|
3042
|
+
const canonicalAgentId =
|
|
3043
|
+
bridge.canonicalAgentId ||
|
|
3044
|
+
bridge.ensureCanonicalAgentId({ cfg, accountId: resolvedAccountId });
|
|
3045
|
+
return parseExplicitTarget(asString(raw).trim(), { canonicalAgentId });
|
|
3046
|
+
},
|
|
3047
|
+
formatTargetDisplay: ({ target }: any) => {
|
|
3048
|
+
return formatTargetDisplay(target);
|
|
3049
|
+
},
|
|
3050
|
+
resolveSessionTarget: ({ id, accountId, cfg }: any) => {
|
|
3051
|
+
const raw = asString(id).trim();
|
|
3052
|
+
if (!raw) return undefined;
|
|
3053
|
+
const resolvedAccountId = normalizeAccountId(
|
|
3054
|
+
asString(accountId || BNCR_DEFAULT_ACCOUNT_ID),
|
|
3055
|
+
);
|
|
3056
|
+
const canonicalAgentId =
|
|
3057
|
+
bridge.canonicalAgentId ||
|
|
3058
|
+
bridge.ensureCanonicalAgentId({ cfg, accountId: resolvedAccountId });
|
|
3059
|
+
|
|
3060
|
+
let parsed = parseExplicitTarget(raw, { canonicalAgentId });
|
|
3061
|
+
if (!parsed) {
|
|
3062
|
+
const route = bridge.resolveRouteBySession(raw, resolvedAccountId);
|
|
3063
|
+
if (route) {
|
|
3064
|
+
parsed = parseExplicitTarget(formatDisplayScope(route), { canonicalAgentId });
|
|
3065
|
+
}
|
|
3066
|
+
}
|
|
3067
|
+
return parsed?.displayScope || undefined;
|
|
3068
|
+
},
|
|
3069
|
+
resolveOutboundSessionRoute: (params: any) => {
|
|
3070
|
+
const accountId = normalizeAccountId(
|
|
3071
|
+
asString(params?.accountId || BNCR_DEFAULT_ACCOUNT_ID),
|
|
3072
|
+
);
|
|
3073
|
+
const canonicalAgentId =
|
|
3074
|
+
bridge.canonicalAgentId || bridge.ensureCanonicalAgentId({ cfg: params?.cfg, accountId });
|
|
3075
|
+
return resolveBncrOutboundSessionRoute({
|
|
3076
|
+
...params,
|
|
3077
|
+
canonicalAgentId,
|
|
3078
|
+
resolveRouteBySession: (raw: string, acc: string) =>
|
|
3079
|
+
bridge.resolveRouteBySession(raw, acc),
|
|
3080
|
+
});
|
|
3081
|
+
},
|
|
3071
3082
|
targetResolver: {
|
|
3072
3083
|
looksLikeId: (raw: string, normalized?: string) => {
|
|
3073
|
-
return
|
|
3084
|
+
return looksLikeBncrExplicitTarget(asString(normalized || raw).trim());
|
|
3085
|
+
},
|
|
3086
|
+
resolveTarget: async ({ accountId, input, normalized }) => {
|
|
3087
|
+
const resolved = resolveBncrOutboundTarget({
|
|
3088
|
+
target: asString(normalized || input).trim(),
|
|
3089
|
+
accountId: normalizeAccountId(asString(accountId || BNCR_DEFAULT_ACCOUNT_ID)),
|
|
3090
|
+
resolveRouteBySession: (raw: string, acc: string) =>
|
|
3091
|
+
this.resolveRouteBySession(raw, acc),
|
|
3092
|
+
});
|
|
3093
|
+
if (!resolved) return null;
|
|
3094
|
+
return {
|
|
3095
|
+
to: resolved.displayScope,
|
|
3096
|
+
kind: resolved.kind,
|
|
3097
|
+
display: resolved.displayScope,
|
|
3098
|
+
source: 'normalized' as const,
|
|
3099
|
+
};
|
|
3074
3100
|
},
|
|
3075
3101
|
hint: 'Standard to=Bncr:<platform>:<group>:<user> or Bncr:<platform>:<user>; sessionKey keeps existing strict/legacy compatibility, canonical sessionKey=agent:<agentId>:bncr:direct:<hex>',
|
|
3076
3102
|
},
|
package/src/core/targets.ts
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
import type { BncrRoute } from './types.ts';
|
|
2
2
|
|
|
3
3
|
export type BncrSessionKind = 'direct' | 'group';
|
|
4
|
+
export type BncrExplicitTarget = {
|
|
5
|
+
raw: string;
|
|
6
|
+
normalized: string;
|
|
7
|
+
source:
|
|
8
|
+
| 'display-scope'
|
|
9
|
+
| 'strict-session-key'
|
|
10
|
+
| 'legacy-session-key'
|
|
11
|
+
| 'hex-scope'
|
|
12
|
+
| 'route-scope';
|
|
13
|
+
kind: BncrSessionKind;
|
|
14
|
+
chatType: 'direct';
|
|
15
|
+
displayScope: string;
|
|
16
|
+
route: BncrRoute;
|
|
17
|
+
canonicalSessionKey?: string;
|
|
18
|
+
platform: string;
|
|
19
|
+
userId: string;
|
|
20
|
+
groupId?: string;
|
|
21
|
+
};
|
|
4
22
|
|
|
5
23
|
function asString(v: unknown, fallback = ''): string {
|
|
6
24
|
if (typeof v === 'string') return v;
|
|
@@ -54,6 +72,85 @@ export function buildDisplayScopeCandidates(route: BncrRoute): string[] {
|
|
|
54
72
|
return Array.from(new Set(candidates.map((x) => asString(x).trim()).filter(Boolean)));
|
|
55
73
|
}
|
|
56
74
|
|
|
75
|
+
export function formatTargetDisplay(
|
|
76
|
+
input: BncrRoute | BncrExplicitTarget | null | undefined,
|
|
77
|
+
): string {
|
|
78
|
+
if (!input) return '';
|
|
79
|
+
const route = parseRouteLike((input as any)?.route) || parseRouteLike(input);
|
|
80
|
+
if (!route) return '';
|
|
81
|
+
return formatDisplayScope(route);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function parseExplicitTarget(
|
|
85
|
+
input: string,
|
|
86
|
+
options?: { canonicalAgentId?: string | null },
|
|
87
|
+
): BncrExplicitTarget | null {
|
|
88
|
+
const raw = asString(input).trim();
|
|
89
|
+
if (!raw) return null;
|
|
90
|
+
|
|
91
|
+
const canonicalAgentId = asString(options?.canonicalAgentId).trim() || undefined;
|
|
92
|
+
let route: BncrRoute | null = null;
|
|
93
|
+
let source: BncrExplicitTarget['source'] | null = null;
|
|
94
|
+
|
|
95
|
+
const strict = parseStrictBncrSessionKey(raw);
|
|
96
|
+
if (strict?.route) {
|
|
97
|
+
route = strict.route;
|
|
98
|
+
source = 'strict-session-key';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!route) {
|
|
102
|
+
const displayRoute = parseRouteFromDisplayScope(raw);
|
|
103
|
+
if (displayRoute) {
|
|
104
|
+
route = displayRoute;
|
|
105
|
+
source = 'display-scope';
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!route) {
|
|
110
|
+
const legacy = parseLegacySessionKey(raw);
|
|
111
|
+
if (legacy?.route) {
|
|
112
|
+
route = legacy.route;
|
|
113
|
+
source = legacy.source === 'hex' ? 'hex-scope' : 'legacy-session-key';
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!route) {
|
|
118
|
+
const hexRoute = parseRouteFromHexScope(raw);
|
|
119
|
+
if (hexRoute) {
|
|
120
|
+
route = hexRoute;
|
|
121
|
+
source = 'hex-scope';
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!route) {
|
|
126
|
+
const scopedRoute = parseRouteFromScope(raw);
|
|
127
|
+
if (scopedRoute) {
|
|
128
|
+
route = scopedRoute;
|
|
129
|
+
source = 'route-scope';
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (!route || !source) return null;
|
|
134
|
+
|
|
135
|
+
const kind: BncrSessionKind = route.groupId === '0' ? 'direct' : 'group';
|
|
136
|
+
const displayScope = formatDisplayScope(route);
|
|
137
|
+
return {
|
|
138
|
+
raw,
|
|
139
|
+
normalized: displayScope,
|
|
140
|
+
source,
|
|
141
|
+
kind,
|
|
142
|
+
chatType: 'direct',
|
|
143
|
+
displayScope,
|
|
144
|
+
route,
|
|
145
|
+
...(canonicalAgentId
|
|
146
|
+
? { canonicalSessionKey: buildCanonicalBncrSessionKey(route, canonicalAgentId) }
|
|
147
|
+
: {}),
|
|
148
|
+
platform: route.platform,
|
|
149
|
+
userId: route.userId,
|
|
150
|
+
...(route.groupId === '0' ? {} : { groupId: route.groupId }),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
57
154
|
export function isLowerHex(input: string): boolean {
|
|
58
155
|
const raw = asString(input).trim();
|
|
59
156
|
return !!raw && /^[0-9a-fA-F]+$/.test(raw) && raw.length % 2 === 0;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { buildChannelOutboundSessionRoute } from 'openclaw/plugin-sdk/core';
|
|
2
|
+
import {
|
|
3
|
+
buildCanonicalBncrSessionKey,
|
|
4
|
+
formatDisplayScope,
|
|
5
|
+
parseRouteFromDisplayScope,
|
|
6
|
+
parseStrictBncrSessionKey,
|
|
7
|
+
routeScopeToHex,
|
|
8
|
+
} from '../../core/targets.ts';
|
|
9
|
+
import type { BncrRoute } from '../../core/types.ts';
|
|
10
|
+
|
|
11
|
+
type ResolveBncrOutboundSessionRouteParams = {
|
|
12
|
+
cfg: any;
|
|
13
|
+
channel: string;
|
|
14
|
+
agentId: string;
|
|
15
|
+
accountId?: string;
|
|
16
|
+
target: string;
|
|
17
|
+
resolvedTarget?: { to?: string } | null;
|
|
18
|
+
threadId?: string;
|
|
19
|
+
canonicalAgentId: string;
|
|
20
|
+
resolveRouteBySession?: (raw: string, accountId: string) => BncrRoute | null;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function asString(v: unknown, fallback = ''): string {
|
|
24
|
+
if (typeof v === 'string') return v;
|
|
25
|
+
if (v == null) return fallback;
|
|
26
|
+
return String(v);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function resolveBncrOutboundSessionRoute(params: ResolveBncrOutboundSessionRouteParams) {
|
|
30
|
+
const raw = asString(params.resolvedTarget?.to || params.target).trim();
|
|
31
|
+
if (!raw) return null;
|
|
32
|
+
|
|
33
|
+
let route: BncrRoute | null = null;
|
|
34
|
+
|
|
35
|
+
const strict = parseStrictBncrSessionKey(raw);
|
|
36
|
+
if (strict) {
|
|
37
|
+
route = strict.route;
|
|
38
|
+
} else {
|
|
39
|
+
route = parseRouteFromDisplayScope(raw);
|
|
40
|
+
if (!route && params.accountId && params.resolveRouteBySession) {
|
|
41
|
+
route = params.resolveRouteBySession(raw, params.accountId);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!route) return null;
|
|
46
|
+
|
|
47
|
+
const canonicalAgentId =
|
|
48
|
+
asString(params.canonicalAgentId).trim() || asString(params.agentId).trim() || 'main';
|
|
49
|
+
const peerId = routeScopeToHex(route);
|
|
50
|
+
const sessionKey = buildCanonicalBncrSessionKey(route, canonicalAgentId);
|
|
51
|
+
const displayTo = formatDisplayScope(route);
|
|
52
|
+
|
|
53
|
+
const built = buildChannelOutboundSessionRoute({
|
|
54
|
+
cfg: params.cfg,
|
|
55
|
+
agentId: canonicalAgentId,
|
|
56
|
+
channel: params.channel,
|
|
57
|
+
accountId: params.accountId,
|
|
58
|
+
peer: {
|
|
59
|
+
kind: 'direct',
|
|
60
|
+
id: peerId,
|
|
61
|
+
},
|
|
62
|
+
chatType: 'direct',
|
|
63
|
+
from: displayTo,
|
|
64
|
+
to: displayTo,
|
|
65
|
+
...(params.threadId !== undefined ? { threadId: params.threadId } : {}),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
...built,
|
|
70
|
+
sessionKey,
|
|
71
|
+
baseSessionKey: sessionKey,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatDisplayScope,
|
|
3
|
+
parseRouteFromDisplayScope,
|
|
4
|
+
parseStrictBncrSessionKey,
|
|
5
|
+
} from '../../core/targets.ts';
|
|
6
|
+
import type { BncrRoute } from '../../core/types.ts';
|
|
7
|
+
|
|
8
|
+
type ResolveBncrOutboundTargetParams = {
|
|
9
|
+
target: string;
|
|
10
|
+
accountId?: string | null;
|
|
11
|
+
resolveRouteBySession?: (raw: string, accountId: string) => BncrRoute | null;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function asString(v: unknown, fallback = ''): string {
|
|
15
|
+
if (typeof v === 'string') return v;
|
|
16
|
+
if (v == null) return fallback;
|
|
17
|
+
return String(v);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function looksLikeBncrExplicitTarget(input: string): boolean {
|
|
21
|
+
const raw = asString(input).trim();
|
|
22
|
+
if (!raw) return false;
|
|
23
|
+
return Boolean(parseRouteFromDisplayScope(raw) || parseStrictBncrSessionKey(raw));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function resolveBncrOutboundTarget(params: ResolveBncrOutboundTargetParams): {
|
|
27
|
+
route: BncrRoute;
|
|
28
|
+
displayScope: string;
|
|
29
|
+
kind: 'user' | 'group';
|
|
30
|
+
} | null {
|
|
31
|
+
const raw = asString(params.target).trim();
|
|
32
|
+
if (!raw) return null;
|
|
33
|
+
|
|
34
|
+
let route: BncrRoute | null = null;
|
|
35
|
+
|
|
36
|
+
const strict = parseStrictBncrSessionKey(raw);
|
|
37
|
+
if (strict?.route) {
|
|
38
|
+
route = strict.route;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!route) {
|
|
42
|
+
route = parseRouteFromDisplayScope(raw);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!route && params.accountId && params.resolveRouteBySession) {
|
|
46
|
+
route = params.resolveRouteBySession(raw, params.accountId);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!route) return null;
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
route,
|
|
53
|
+
displayScope: formatDisplayScope(route),
|
|
54
|
+
kind: route.groupId === '0' ? 'user' : 'group',
|
|
55
|
+
};
|
|
56
|
+
}
|