@xmoxmo/bncr 0.4.5 → 0.4.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/dist/index.js +6 -0
- package/index.ts +6 -0
- package/package.json +1 -1
- package/src/channel.ts +41 -2
- package/src/core/targets.ts +106 -17
- package/src/messaging/inbound/commands.ts +263 -51
- package/src/messaging/inbound/context-facts.ts +126 -14
- package/src/messaging/inbound/contracts.ts +24 -0
- package/src/messaging/inbound/dispatch-prep.ts +214 -39
- package/src/messaging/inbound/dispatch.ts +71 -5
- package/src/messaging/inbound/gate.ts +56 -86
- package/src/messaging/inbound/group-history.ts +189 -0
- package/src/messaging/inbound/native-command-runtime.ts +77 -61
- package/src/messaging/inbound/native-command.ts +92 -8
- package/src/messaging/inbound/parse.ts +113 -8
- package/src/messaging/inbound/reply-dispatch-serial.ts +62 -0
- package/src/messaging/inbound/reply-dispatch.ts +252 -77
- package/src/messaging/inbound/scene-admin.ts +269 -0
- package/src/messaging/inbound/session-label.ts +122 -13
- package/src/messaging/inbound/session-meta-task.ts +17 -0
- package/src/messaging/inbound/turn-context.ts +184 -71
- package/src/openclaw/channel-runtime-contracts.ts +1 -0
- package/src/plugin/channel-components.ts +34 -1
- package/src/plugin/channel-inbound-helpers.ts +9 -2
- package/src/plugin/channel-runtime-builders-delivery.ts +24 -1
- package/src/plugin/channel-runtime-types.ts +42 -0
- package/src/plugin/file-inbound-init.ts +27 -12
- package/src/plugin/file-inbound-runtime.ts +2 -0
- package/src/plugin/inbound-acceptance.ts +82 -1
- package/src/plugin/inbound-handlers.ts +55 -2
- package/src/plugin/inbound-surface-handlers-group.ts +16 -0
- package/src/plugin/messaging.ts +22 -5
- package/src/plugin/scene-registry.ts +155 -0
- package/src/plugin/state-store.ts +133 -0
- package/src/plugin/state-transient-runtime-group.ts +5 -0
- package/src/plugin/target-runtime.ts +2 -2
package/dist/index.js
CHANGED
|
@@ -840,6 +840,12 @@ var plugin = {
|
|
|
840
840
|
owner = adopted.owner;
|
|
841
841
|
previousOwner = adopted.previousOwner;
|
|
842
842
|
gatewayRuntime.currentBridge = bridge;
|
|
843
|
+
if (rebuilt) {
|
|
844
|
+
gatewayRuntime.serviceRegistered = false;
|
|
845
|
+
gatewayRuntime.channelRegistered = false;
|
|
846
|
+
gatewayRuntime.serviceOwnerApiInstanceId = void 0;
|
|
847
|
+
gatewayRuntime.channelOwnerApiInstanceId = void 0;
|
|
848
|
+
}
|
|
843
849
|
} else {
|
|
844
850
|
runtime2 = loadBncrRuntimeSync();
|
|
845
851
|
bridge = gatewayRuntime.currentBridge || getExistingBridgeSingleton();
|
package/index.ts
CHANGED
|
@@ -101,6 +101,12 @@ const plugin = {
|
|
|
101
101
|
owner = adopted.owner;
|
|
102
102
|
previousOwner = adopted.previousOwner;
|
|
103
103
|
gatewayRuntime.currentBridge = bridge;
|
|
104
|
+
if (rebuilt) {
|
|
105
|
+
gatewayRuntime.serviceRegistered = false;
|
|
106
|
+
gatewayRuntime.channelRegistered = false;
|
|
107
|
+
gatewayRuntime.serviceOwnerApiInstanceId = undefined;
|
|
108
|
+
gatewayRuntime.channelOwnerApiInstanceId = undefined;
|
|
109
|
+
}
|
|
104
110
|
} else {
|
|
105
111
|
runtime = loadBncrRuntimeSync();
|
|
106
112
|
bridge = gatewayRuntime.currentBridge || getExistingBridgeSingleton();
|
package/package.json
CHANGED
package/src/channel.ts
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
import { summarizeOutboxEntry } from './core/outbox-summary.ts';
|
|
27
27
|
import { normalizePersistedOutboxEntry as normalizePersistedOutboxEntryFromRuntime } from './core/persisted-outbox-entry.ts';
|
|
28
28
|
import {
|
|
29
|
+
buildCanonicalBncrSessionKey,
|
|
29
30
|
formatDisplayScope,
|
|
30
31
|
normalizeStoredSessionKey,
|
|
31
32
|
parseRouteLike,
|
|
@@ -40,6 +41,7 @@ import type {
|
|
|
40
41
|
FileSendTransferState,
|
|
41
42
|
OutboxEntry,
|
|
42
43
|
} from './core/types.ts';
|
|
44
|
+
import type { BncrGroupHistoryMap } from './messaging/inbound/group-history.ts';
|
|
43
45
|
import type { parseBncrInboundParams } from './messaging/inbound/parse.ts';
|
|
44
46
|
import { buildEnqueueFromReplyDebugInfo } from './messaging/outbound/diagnostics.ts';
|
|
45
47
|
import { buildBncrMediaOutboundFrame } from './messaging/outbound/media.ts';
|
|
@@ -149,6 +151,7 @@ import type {
|
|
|
149
151
|
BncrBridgeRuntimePaths,
|
|
150
152
|
BncrChannelConfigRoot,
|
|
151
153
|
BncrChannelSendContext,
|
|
154
|
+
BncrSceneRecord,
|
|
152
155
|
FileAckPayloadState,
|
|
153
156
|
PersistedState,
|
|
154
157
|
} from './plugin/channel-runtime-types.ts';
|
|
@@ -284,6 +287,8 @@ class BncrBridgeRuntime {
|
|
|
284
287
|
string,
|
|
285
288
|
{ accountId: string; route: BncrRoute; updatedAt: number }
|
|
286
289
|
>();
|
|
290
|
+
private sceneRegistry = new Map<string, BncrSceneRecord>();
|
|
291
|
+
private groupHistories: BncrGroupHistoryMap = new Map();
|
|
287
292
|
private routeAliases = new Map<
|
|
288
293
|
string,
|
|
289
294
|
{ accountId: string; route: BncrRoute; updatedAt: number }
|
|
@@ -972,6 +977,19 @@ class BncrBridgeRuntime {
|
|
|
972
977
|
return this.bridgeSupportRuntime.ensureCanonicalAgentId(args);
|
|
973
978
|
}
|
|
974
979
|
|
|
980
|
+
private defaultAdminAgentId(args: {
|
|
981
|
+
cfg: BncrChannelConfigRoot;
|
|
982
|
+
accountId: string;
|
|
983
|
+
peer?: unknown;
|
|
984
|
+
channelId?: string;
|
|
985
|
+
}): string {
|
|
986
|
+
return this.ensureCanonicalAgentId(args);
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
private defaultPublicAgentId(): string {
|
|
990
|
+
return 'public';
|
|
991
|
+
}
|
|
992
|
+
|
|
975
993
|
private countInvalidOutboxSessionKeys(accountId: string): number {
|
|
976
994
|
return countInvalidOutboxSessionKeysFromRuntime({
|
|
977
995
|
accountId,
|
|
@@ -1530,6 +1548,9 @@ class BncrBridgeRuntime {
|
|
|
1530
1548
|
sessionKey: string;
|
|
1531
1549
|
inboundText: string;
|
|
1532
1550
|
hasMedia: boolean;
|
|
1551
|
+
resolvedAgentId: string;
|
|
1552
|
+
shouldDispatch: boolean;
|
|
1553
|
+
shouldAccumulate: boolean;
|
|
1533
1554
|
}
|
|
1534
1555
|
| {
|
|
1535
1556
|
ok: false;
|
|
@@ -1542,11 +1563,20 @@ class BncrBridgeRuntime {
|
|
|
1542
1563
|
parsed: args.parsed,
|
|
1543
1564
|
canonicalAgentId: args.canonicalAgentId,
|
|
1544
1565
|
asString,
|
|
1566
|
+
now,
|
|
1545
1567
|
getRuntimeConfig: (api) => getOpenClawRuntimeConfig(api as OpenClawChannelRuntimeApiHolder),
|
|
1546
1568
|
resolveAgentRoute: (params) =>
|
|
1547
1569
|
resolveOpenClawAgentRoute(this.api as OpenClawChannelRuntimeApiHolder, params),
|
|
1548
1570
|
buildInboundResponsePayload,
|
|
1549
1571
|
markInboundDedupSeen: (key) => this.markInboundDedupSeen(key),
|
|
1572
|
+
sceneRegistry: this.sceneRegistry,
|
|
1573
|
+
defaultAdminAgentId: this.defaultAdminAgentId({
|
|
1574
|
+
cfg: getOpenClawRuntimeConfig(this.api as OpenClawChannelRuntimeApiHolder),
|
|
1575
|
+
accountId: args.parsed.accountId,
|
|
1576
|
+
peer: args.parsed.peer,
|
|
1577
|
+
channelId: CHANNEL_ID,
|
|
1578
|
+
}),
|
|
1579
|
+
defaultPublicAgentId: this.defaultPublicAgentId(),
|
|
1550
1580
|
});
|
|
1551
1581
|
}
|
|
1552
1582
|
|
|
@@ -1867,6 +1897,8 @@ class BncrBridgeRuntime {
|
|
|
1867
1897
|
maxDeadLetterEntries: MAX_DEAD_LETTER_ENTRIES,
|
|
1868
1898
|
maxSessionRouteEntries: MAX_SESSION_ROUTE_ENTRIES,
|
|
1869
1899
|
maxAccountActivityEntries: MAX_ACCOUNT_ACTIVITY_ENTRIES,
|
|
1900
|
+
sceneRegistry: this.sceneRegistry,
|
|
1901
|
+
groupHistories: this.groupHistories,
|
|
1870
1902
|
outbox: this.outbox,
|
|
1871
1903
|
getDeadLetter: () => this.deadLetter,
|
|
1872
1904
|
setDeadLetter: (entries) => {
|
|
@@ -2319,9 +2351,10 @@ class BncrBridgeRuntime {
|
|
|
2319
2351
|
}
|
|
2320
2352
|
|
|
2321
2353
|
// 严谨目标解析:
|
|
2322
|
-
// 1) 标准 to 仅认 Bncr:<platform
|
|
2354
|
+
// 1) 标准 to 仅认 Bncr:<platform>:0:<userId> / Bncr:<platform>:<groupId>:0
|
|
2323
2355
|
// 2) 仍接受 strict sessionKey 作为内部兼容输入
|
|
2324
|
-
// 3)
|
|
2356
|
+
// 3) 输入侧额外兼容 Bncr:<platform>:user:<userId> / Bncr:<platform>:group:<groupId>
|
|
2357
|
+
// 4) 其他旧格式直接失败,并输出标准格式提示日志
|
|
2325
2358
|
resolveVerifiedTarget(
|
|
2326
2359
|
rawTarget: string,
|
|
2327
2360
|
accountId: string,
|
|
@@ -2850,6 +2883,10 @@ class BncrBridgeRuntime {
|
|
|
2850
2883
|
buildInboundAcceptedLifecycleDebugInfo,
|
|
2851
2884
|
...inboundActivityRuntime,
|
|
2852
2885
|
ensureCanonicalAgentId: (args) => this.ensureCanonicalAgentId(args),
|
|
2886
|
+
defaultAdminAgentId: (args) => this.defaultAdminAgentId(args),
|
|
2887
|
+
defaultPublicAgentId: () => this.defaultPublicAgentId(),
|
|
2888
|
+
sceneRegistry: this.sceneRegistry,
|
|
2889
|
+
groupHistories: this.groupHistories,
|
|
2853
2890
|
prepareInboundAcceptance: (args) => this.prepareInboundAcceptance(args),
|
|
2854
2891
|
logInboundSummary: (args) => this.logInboundSummary(args),
|
|
2855
2892
|
flushPushQueueBestEffort: (args) => this.flushPushQueueBestEffort(args),
|
|
@@ -2858,6 +2895,8 @@ class BncrBridgeRuntime {
|
|
|
2858
2895
|
enqueueFromReply: (args: Parameters<BncrBridgeRuntime['enqueueFromReply']>[0]) =>
|
|
2859
2896
|
this.enqueueFromReply(args),
|
|
2860
2897
|
scheduleSave: () => this.scheduleSave(),
|
|
2898
|
+
buildCanonicalSessionKey: (route: BncrRoute) =>
|
|
2899
|
+
buildCanonicalBncrSessionKey(route, this.canonicalAgentId || 'main'),
|
|
2861
2900
|
fileRecvTransfers: this.fileRecvTransfers,
|
|
2862
2901
|
inboundFileTransferMaxBytes: INBOUND_FILE_TRANSFER_MAX_BYTES,
|
|
2863
2902
|
inboundFileTransferMaxChunks: INBOUND_FILE_TRANSFER_MAX_CHUNKS,
|
package/src/core/targets.ts
CHANGED
|
@@ -36,6 +36,14 @@ export function asTargetString(value: unknown, fallback = ''): string {
|
|
|
36
36
|
|
|
37
37
|
export function parseRouteFromStandardDisplayScope(scope: string): BncrRoute | null {
|
|
38
38
|
const parts = asTargetString(scope).trim().split(':');
|
|
39
|
+
if (parts.length === 3) {
|
|
40
|
+
const [platform, kind, id] = parts;
|
|
41
|
+
if (!platform || !kind || !id) return null;
|
|
42
|
+
if (kind === 'User') return { platform, groupId: '0', userId: id };
|
|
43
|
+
if (kind === 'Group') return { platform, groupId: id, userId: '0' };
|
|
44
|
+
if (kind.toLowerCase() === 'user' || kind.toLowerCase() === 'group') return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
39
47
|
if (parts.length === 2) {
|
|
40
48
|
const [platform, userId] = parts;
|
|
41
49
|
if (!platform || !userId) return null;
|
|
@@ -45,12 +53,33 @@ export function parseRouteFromStandardDisplayScope(scope: string): BncrRoute | n
|
|
|
45
53
|
if (parts.length === 3) {
|
|
46
54
|
const [platform, groupId, userId] = parts;
|
|
47
55
|
if (!platform || !groupId || !userId) return null;
|
|
56
|
+
if (groupId !== '0' && userId !== '0') {
|
|
57
|
+
// Legacy group routes may still include the triggering userId; collapse them
|
|
58
|
+
// back to the shared group route so old targets remain deliverable.
|
|
59
|
+
return { platform, groupId, userId: '0' };
|
|
60
|
+
}
|
|
48
61
|
return { platform, groupId, userId };
|
|
49
62
|
}
|
|
50
63
|
|
|
51
64
|
return null;
|
|
52
65
|
}
|
|
53
66
|
|
|
67
|
+
function parseGroupScope(scope: string): { platform: string; groupId: string } | null {
|
|
68
|
+
const parts = asTargetString(scope).trim().split(':');
|
|
69
|
+
if (parts.length !== 2) return null;
|
|
70
|
+
const [platform, groupId] = parts;
|
|
71
|
+
if (!platform || !groupId) return null;
|
|
72
|
+
return { platform, groupId };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function parseDirectScope(scope: string): { platform: string; userId: string } | null {
|
|
76
|
+
const parts = asTargetString(scope).trim().split(':');
|
|
77
|
+
if (parts.length !== 2) return null;
|
|
78
|
+
const [platform, userId] = parts;
|
|
79
|
+
if (!platform || !userId) return null;
|
|
80
|
+
return { platform, userId };
|
|
81
|
+
}
|
|
82
|
+
|
|
54
83
|
export function normalizeDisplayScopePrefix(scope: string): string {
|
|
55
84
|
const raw = asTargetString(scope).trim();
|
|
56
85
|
if (!raw) return '';
|
|
@@ -102,11 +131,22 @@ export function routeScopeToHex(route: BncrRoute): string {
|
|
|
102
131
|
return Buffer.from(raw, 'utf8').toString('hex').toLowerCase();
|
|
103
132
|
}
|
|
104
133
|
|
|
134
|
+
export function groupScopeToHex(route: BncrRoute): string {
|
|
135
|
+
const raw = `${route.platform}:${route.groupId}`;
|
|
136
|
+
return Buffer.from(raw, 'utf8').toString('hex').toLowerCase();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function directScopeToHex(route: BncrRoute): string {
|
|
140
|
+
const raw = `${route.platform}:${route.userId}`;
|
|
141
|
+
return Buffer.from(raw, 'utf8').toString('hex').toLowerCase();
|
|
142
|
+
}
|
|
143
|
+
|
|
105
144
|
export function parseRouteFromScope(scope: string): BncrRoute | null {
|
|
106
145
|
const parts = asTargetString(scope).trim().split(':');
|
|
107
146
|
if (parts.length < 3) return null;
|
|
108
147
|
const [platform, groupId, userId] = parts;
|
|
109
148
|
if (!platform || !groupId || !userId) return null;
|
|
149
|
+
if (groupId !== '0' && userId !== '0') return null;
|
|
110
150
|
return { platform, groupId, userId };
|
|
111
151
|
}
|
|
112
152
|
|
|
@@ -122,6 +162,34 @@ export function parseRouteFromHexScope(scopeHex: string): BncrRoute | null {
|
|
|
122
162
|
}
|
|
123
163
|
}
|
|
124
164
|
|
|
165
|
+
export function parseGroupRouteFromHexScope(scopeHex: string): BncrRoute | null {
|
|
166
|
+
const rawHex = asTargetString(scopeHex).trim();
|
|
167
|
+
if (!isLowerHex(rawHex)) return null;
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
const decoded = Buffer.from(rawHex, 'hex').toString('utf8');
|
|
171
|
+
const parsed = parseGroupScope(decoded);
|
|
172
|
+
if (!parsed) return null;
|
|
173
|
+
return { platform: parsed.platform, groupId: parsed.groupId, userId: '0' };
|
|
174
|
+
} catch {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function parseDirectRouteFromHexScope(scopeHex: string): BncrRoute | null {
|
|
180
|
+
const rawHex = asTargetString(scopeHex).trim();
|
|
181
|
+
if (!isLowerHex(rawHex)) return null;
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
const decoded = Buffer.from(rawHex, 'hex').toString('utf8');
|
|
185
|
+
const parsed = parseDirectScope(decoded);
|
|
186
|
+
if (!parsed) return null;
|
|
187
|
+
return { platform: parsed.platform, groupId: '0', userId: parsed.userId };
|
|
188
|
+
} catch {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
125
193
|
export function parseStrictBncrSessionKey(input: string): {
|
|
126
194
|
inputSessionKey: string;
|
|
127
195
|
inputAgentId: string;
|
|
@@ -143,9 +211,24 @@ export function parseStrictBncrSessionKey(input: string): {
|
|
|
143
211
|
|
|
144
212
|
if (isLowerHex(payload)) {
|
|
145
213
|
scopeHex = payload.toLowerCase();
|
|
146
|
-
route =
|
|
214
|
+
route =
|
|
215
|
+
inputKind === 'group'
|
|
216
|
+
? parseGroupRouteFromHexScope(scopeHex)
|
|
217
|
+
: parseDirectRouteFromHexScope(scopeHex) || parseRouteFromHexScope(scopeHex);
|
|
147
218
|
} else {
|
|
148
|
-
route =
|
|
219
|
+
route =
|
|
220
|
+
inputKind === 'group'
|
|
221
|
+
? (() => {
|
|
222
|
+
const parsed = parseGroupScope(payload);
|
|
223
|
+
return parsed
|
|
224
|
+
? { platform: parsed.platform, groupId: parsed.groupId, userId: '0' }
|
|
225
|
+
: null;
|
|
226
|
+
})()
|
|
227
|
+
: (() => {
|
|
228
|
+
const direct = parseDirectScope(payload);
|
|
229
|
+
if (direct) return { platform: direct.platform, groupId: '0', userId: direct.userId };
|
|
230
|
+
return parseRouteFromScope(payload);
|
|
231
|
+
})();
|
|
149
232
|
if (route) scopeHex = routeScopeToHex(route);
|
|
150
233
|
}
|
|
151
234
|
|
|
@@ -237,13 +320,17 @@ export function parseRouteFromDisplayScope(scope: string): BncrRoute | null {
|
|
|
237
320
|
|
|
238
321
|
const payload = raw.match(/^Bncr:(.+)$/)?.[1];
|
|
239
322
|
if (!payload) return null;
|
|
240
|
-
|
|
323
|
+
const route = parseRouteFromStandardDisplayScope(payload);
|
|
324
|
+
if (!route) return null;
|
|
325
|
+
return route;
|
|
241
326
|
}
|
|
242
327
|
|
|
243
328
|
export function buildCanonicalBncrSessionKey(route: BncrRoute, canonicalAgentId: string): string {
|
|
244
329
|
const agentId = asTargetString(canonicalAgentId).trim() || 'main';
|
|
245
|
-
|
|
246
|
-
|
|
330
|
+
if (route.groupId !== '0') {
|
|
331
|
+
return `agent:${agentId}:bncr:group:${groupScopeToHex(route)}`;
|
|
332
|
+
}
|
|
333
|
+
return `agent:${agentId}:bncr:direct:${directScopeToHex(route)}`;
|
|
247
334
|
}
|
|
248
335
|
|
|
249
336
|
export function normalizeStoredSessionKey(
|
|
@@ -266,17 +353,17 @@ export function normalizeStoredSessionKey(
|
|
|
266
353
|
let route: BncrRoute | null = null;
|
|
267
354
|
let passthroughAgentId: string | null = null;
|
|
268
355
|
|
|
269
|
-
const
|
|
270
|
-
if (
|
|
271
|
-
route =
|
|
272
|
-
passthroughAgentId =
|
|
356
|
+
const legacy = parseLegacySessionKey(base);
|
|
357
|
+
if (legacy) {
|
|
358
|
+
route = legacy.route;
|
|
359
|
+
passthroughAgentId = legacy.inputAgentId || null;
|
|
273
360
|
}
|
|
274
361
|
|
|
275
362
|
if (!route) {
|
|
276
|
-
const
|
|
277
|
-
if (
|
|
278
|
-
route =
|
|
279
|
-
passthroughAgentId =
|
|
363
|
+
const strict = parseStrictBncrSessionKey(base);
|
|
364
|
+
if (strict) {
|
|
365
|
+
route = strict.route;
|
|
366
|
+
passthroughAgentId = strict.inputAgentId;
|
|
280
367
|
}
|
|
281
368
|
}
|
|
282
369
|
|
|
@@ -322,6 +409,7 @@ export function parseRouteLike(input: unknown): BncrRoute | null {
|
|
|
322
409
|
const groupId = asTargetString(routeInput?.groupId || '').trim();
|
|
323
410
|
const userId = asTargetString(routeInput?.userId || '').trim();
|
|
324
411
|
if (!platform || !groupId || !userId) return null;
|
|
412
|
+
if (groupId !== '0' && userId !== '0') return null;
|
|
325
413
|
return { platform, groupId, userId };
|
|
326
414
|
}
|
|
327
415
|
|
|
@@ -333,10 +421,11 @@ export type BncrExplicitTargetSource =
|
|
|
333
421
|
| 'route-scope';
|
|
334
422
|
|
|
335
423
|
export function formatDisplayScope(route: BncrRoute): string {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
return `Bncr:${
|
|
424
|
+
const platform = asTargetString(route?.platform).trim();
|
|
425
|
+
const groupId = asTargetString(route?.groupId || '0').trim() || '0';
|
|
426
|
+
const userId = asTargetString(route?.userId || '0').trim() || '0';
|
|
427
|
+
if (groupId !== '0') return `Bncr:${platform}:${groupId}:0`;
|
|
428
|
+
return `Bncr:${platform}:0:${userId}`;
|
|
340
429
|
}
|
|
341
430
|
|
|
342
431
|
export function buildDisplayScopeCandidates(route: BncrRoute): string[] {
|