@xmoxmo/bncr 0.1.6 → 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;
|
|
@@ -1556,77 +1563,35 @@ class BncrBridgeRuntime {
|
|
|
1556
1563
|
);
|
|
1557
1564
|
}
|
|
1558
1565
|
|
|
1559
|
-
const
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
)
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
if (normalizeAccountId(info.accountId) !== acc) continue;
|
|
1573
|
-
const parsed = parseStrictBncrSessionKey(key);
|
|
1574
|
-
if (!parsed) continue;
|
|
1575
|
-
if (routeKey(acc, parsed.route) !== wantedRouteKey) continue;
|
|
1576
|
-
|
|
1577
|
-
const updatedAt = Number(info.updatedAt || 0);
|
|
1578
|
-
if (!best || updatedAt >= best.updatedAt) {
|
|
1579
|
-
best = {
|
|
1580
|
-
sessionKey: key,
|
|
1581
|
-
route: parsed.route,
|
|
1582
|
-
updatedAt,
|
|
1583
|
-
};
|
|
1584
|
-
}
|
|
1585
|
-
}
|
|
1586
|
-
|
|
1587
|
-
if (!best) {
|
|
1588
|
-
const updatedAt = 0;
|
|
1589
|
-
const canonicalAgentId =
|
|
1590
|
-
this.canonicalAgentId ||
|
|
1591
|
-
this.ensureCanonicalAgentId({
|
|
1592
|
-
cfg: this.api.runtime.config?.get?.() || {},
|
|
1593
|
-
accountId: acc,
|
|
1594
|
-
channelId: CHANNEL_ID,
|
|
1595
|
-
peer: { kind: 'direct', id: route.groupId === '0' ? route.userId : route.groupId },
|
|
1596
|
-
});
|
|
1597
|
-
best = {
|
|
1598
|
-
sessionKey: buildCanonicalBncrSessionKey(route, canonicalAgentId),
|
|
1599
|
-
route,
|
|
1600
|
-
updatedAt,
|
|
1601
|
-
};
|
|
1602
|
-
}
|
|
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
|
+
};
|
|
1603
1579
|
|
|
1604
1580
|
if (BNCR_DEBUG_VERBOSE) {
|
|
1605
1581
|
this.api.logger.info?.(
|
|
1606
|
-
`[bncr-target-incoming-
|
|
1582
|
+
`[bncr-target-incoming-canonical] raw=${raw} accountId=${acc} verified=${JSON.stringify(verified)}`,
|
|
1607
1583
|
);
|
|
1608
1584
|
}
|
|
1609
1585
|
|
|
1610
|
-
if (!best) {
|
|
1611
|
-
this.api.logger.warn?.(
|
|
1612
|
-
`[bncr-target-miss] raw=${raw} accountId=${acc} sessionRoutes=${this.sessionRoutes.size}`,
|
|
1613
|
-
);
|
|
1614
|
-
throw new Error(`bncr target not found in known sessions: ${raw}`);
|
|
1615
|
-
}
|
|
1616
|
-
|
|
1617
1586
|
// 发送链路命中目标时,同步刷新 lastSession,避免状态页显示过期会话。
|
|
1618
1587
|
this.lastSessionByAccount.set(acc, {
|
|
1619
|
-
sessionKey:
|
|
1620
|
-
scope:
|
|
1588
|
+
sessionKey: verified.sessionKey,
|
|
1589
|
+
scope: verified.displayScope,
|
|
1621
1590
|
updatedAt: now(),
|
|
1622
1591
|
});
|
|
1623
1592
|
this.scheduleSave();
|
|
1624
1593
|
|
|
1625
|
-
return
|
|
1626
|
-
sessionKey: best.sessionKey,
|
|
1627
|
-
route: best.route,
|
|
1628
|
-
displayScope: formatDisplayScope(best.route),
|
|
1629
|
-
};
|
|
1594
|
+
return verified;
|
|
1630
1595
|
}
|
|
1631
1596
|
|
|
1632
1597
|
private markActivity(accountId: string, at = now()) {
|
|
@@ -3070,9 +3035,68 @@ export function createBncrChannelPlugin(bridge: BncrBridgeRuntime) {
|
|
|
3070
3035
|
const input = asString(raw).trim();
|
|
3071
3036
|
return input || undefined;
|
|
3072
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
|
+
},
|
|
3073
3082
|
targetResolver: {
|
|
3074
3083
|
looksLikeId: (raw: string, normalized?: string) => {
|
|
3075
|
-
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
|
+
};
|
|
3076
3100
|
},
|
|
3077
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>',
|
|
3078
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
|
+
}
|