@xmoxmo/bncr 0.3.6 → 0.3.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 +5 -0
- package/dist/index.js +28 -5
- package/index.ts +55 -721
- package/package.json +8 -4
- package/scripts/check-pack.mjs +93 -18
- package/scripts/check-register-drift.mjs +35 -13
- package/scripts/selfcheck.mjs +80 -11
- package/src/bootstrap/channel-plugin-runtime.ts +81 -0
- package/src/bootstrap/cli.ts +97 -0
- package/src/bootstrap/register-runtime-gateway.ts +129 -0
- package/src/bootstrap/register-runtime-helpers.ts +140 -0
- package/src/bootstrap/register-runtime-singleton.ts +137 -0
- package/src/bootstrap/register-runtime.ts +201 -0
- package/src/bootstrap/runtime-discovery.ts +187 -0
- package/src/bootstrap/runtime-loader.ts +54 -0
- package/src/channel.ts +1590 -4967
- package/src/core/accounts.ts +23 -4
- package/src/core/dead-letter-diagnostics.ts +37 -5
- package/src/core/diagnostics.ts +31 -15
- package/src/core/downlink-health.ts +3 -11
- package/src/core/extended-diagnostics.ts +78 -36
- package/src/core/file-transfer-payloads.ts +1 -1
- package/src/core/logging.ts +1 -0
- package/src/core/outbox-enqueue.ts +13 -2
- package/src/core/outbox-entry-builders.ts +2 -0
- package/src/core/outbox-summary.ts +75 -3
- package/src/core/permissions.ts +15 -2
- package/src/core/persisted-outbox-entry.ts +21 -6
- package/src/core/policy.ts +45 -4
- package/src/core/probe.ts +3 -15
- package/src/core/register-trace.ts +3 -3
- package/src/core/status.ts +43 -4
- package/src/core/targets.ts +216 -205
- package/src/core/types.ts +221 -0
- package/src/core/value-sanitize.ts +29 -0
- package/src/messaging/inbound/commands.ts +147 -172
- package/src/messaging/inbound/context-facts.ts +4 -2
- package/src/messaging/inbound/contracts.ts +70 -0
- package/src/messaging/inbound/dispatch-prep.ts +303 -0
- package/src/messaging/inbound/dispatch.ts +49 -462
- package/src/messaging/inbound/gate.ts +18 -5
- package/src/messaging/inbound/last-route.ts +10 -4
- package/src/messaging/inbound/media-url-download.ts +109 -0
- package/src/messaging/inbound/native-command-runtime.ts +225 -0
- package/src/messaging/inbound/parse.ts +2 -1
- package/src/messaging/inbound/remote-media.ts +49 -0
- package/src/messaging/inbound/reply-config.ts +16 -4
- package/src/messaging/inbound/reply-dispatch.ts +162 -0
- package/src/messaging/inbound/runtime-compat.ts +31 -10
- package/src/messaging/inbound/session-label.ts +15 -7
- package/src/messaging/inbound/turn-context.ts +131 -0
- package/src/messaging/outbound/actions.ts +24 -10
- package/src/messaging/outbound/diagnostics-debug-builders.ts +365 -0
- package/src/messaging/outbound/diagnostics.ts +31 -355
- package/src/messaging/outbound/durable-message-adapter.ts +20 -16
- package/src/messaging/outbound/durable-queue-adapter.ts +20 -7
- package/src/messaging/outbound/media.ts +24 -13
- package/src/messaging/outbound/reply-enqueue-media.ts +181 -0
- package/src/messaging/outbound/reply-enqueue.ts +46 -155
- package/src/messaging/outbound/send-params.ts +3 -0
- package/src/messaging/outbound/send.ts +19 -10
- package/src/messaging/outbound/session-route.ts +18 -3
- package/src/openclaw/channel-runtime-contracts.ts +76 -0
- package/src/openclaw/config-runtime.ts +13 -7
- package/src/openclaw/inbound-session-runtime.ts +7 -3
- package/src/openclaw/ingress-runtime.ts +17 -27
- package/src/openclaw/reply-runtime.ts +54 -59
- package/src/openclaw/routing-runtime.ts +35 -18
- package/src/openclaw/runtime-surface.ts +156 -12
- package/src/openclaw/sdk-helpers.ts +8 -1
- package/src/openclaw/session-route-runtime.ts +12 -12
- package/src/plugin/ack-outbox-runtime-group.ts +264 -0
- package/src/plugin/bridge-ack-facade.ts +137 -0
- package/src/plugin/bridge-connection-facade.ts +111 -0
- package/src/plugin/bridge-diagnostics-facade.ts +23 -0
- package/src/plugin/bridge-drain-facade.ts +98 -0
- package/src/plugin/bridge-extended-diagnostics-facade.ts +149 -0
- package/src/plugin/bridge-file-transfer-push-facade.ts +140 -0
- package/src/plugin/bridge-lifecycle.ts +156 -0
- package/src/plugin/bridge-media-facade.ts +241 -0
- package/src/plugin/bridge-outbox-facade.ts +182 -0
- package/src/plugin/bridge-runtime-helpers.ts +266 -0
- package/src/plugin/bridge-runtime-snapshots.ts +104 -0
- package/src/plugin/bridge-runtime-surface-facade.ts +8 -0
- package/src/plugin/bridge-status-facade.ts +76 -0
- package/src/plugin/bridge-status-worker-facade.ts +72 -0
- package/src/plugin/bridge-support-runtime.ts +137 -0
- package/src/plugin/bridge-surface-handlers-group.ts +242 -0
- package/src/plugin/bridge-surface-helpers.ts +28 -0
- package/src/plugin/capabilities.ts +1 -3
- package/src/plugin/channel-components.ts +289 -0
- package/src/plugin/channel-inbound-helpers.ts +149 -0
- package/src/plugin/channel-plugin-bridge-group.ts +129 -0
- package/src/plugin/channel-plugin-surface-group.ts +202 -0
- package/src/plugin/channel-runtime-builders-delivery.ts +513 -0
- package/src/plugin/channel-runtime-builders-status.ts +331 -0
- package/src/plugin/channel-runtime-builders.ts +25 -0
- package/src/plugin/channel-runtime-constants.ts +40 -0
- package/src/plugin/channel-runtime-types.ts +146 -0
- package/src/plugin/channel-send-runtime-group.ts +37 -0
- package/src/plugin/channel-send.ts +226 -0
- package/src/plugin/channel-utils.ts +102 -0
- package/src/plugin/config.ts +24 -3
- package/src/plugin/connection-handlers-helpers.ts +254 -0
- package/src/plugin/connection-handlers.ts +440 -0
- package/src/plugin/connection-state-helpers.ts +159 -0
- package/src/plugin/connection-state-runtime-group.ts +51 -0
- package/src/plugin/connection-state.ts +527 -0
- package/src/plugin/diagnostics-handlers.ts +211 -0
- package/src/plugin/error-message.ts +15 -0
- package/src/plugin/file-ack-runtime.ts +284 -0
- package/src/plugin/file-inbound-abort.ts +112 -0
- package/src/plugin/file-inbound-chunk.ts +146 -0
- package/src/plugin/file-inbound-complete.ts +153 -0
- package/src/plugin/file-inbound-handlers.ts +19 -0
- package/src/plugin/file-inbound-init.ts +122 -0
- package/src/plugin/file-inbound-runtime.ts +51 -0
- package/src/plugin/file-inbound-state.ts +62 -0
- package/src/plugin/file-transfer-logs.ts +227 -0
- package/src/plugin/file-transfer-orchestrator-chunk.ts +135 -0
- package/src/plugin/file-transfer-orchestrator.ts +304 -0
- package/src/plugin/file-transfer-runtime-group.ts +102 -0
- package/src/plugin/file-transfer-send.ts +89 -0
- package/src/plugin/file-transfer-setup.ts +206 -0
- package/src/plugin/gateway-event-context.ts +41 -0
- package/src/plugin/gateway-runtime.ts +14 -4
- package/src/plugin/inbound-acceptance.ts +107 -0
- package/src/plugin/inbound-handlers.ts +248 -0
- package/src/plugin/inbound-surface-handlers-group.ts +152 -0
- package/src/plugin/media-dedupe-runtime.ts +90 -0
- package/src/plugin/media-orchestrators-runtime-group.ts +316 -0
- package/src/plugin/message-ack-runtime.ts +284 -0
- package/src/plugin/message-send.ts +16 -6
- package/src/plugin/messaging.ts +98 -36
- package/src/plugin/outbound.ts +50 -8
- package/src/plugin/outbox-ack-logs.ts +136 -0
- package/src/plugin/outbox-ack-outcome.ts +128 -0
- package/src/plugin/outbox-drain-ack.ts +145 -0
- package/src/plugin/outbox-drain-failure.ts +84 -0
- package/src/plugin/outbox-drain-loop.ts +554 -0
- package/src/plugin/outbox-drain-post-push.ts +159 -0
- package/src/plugin/outbox-drain-runtime.ts +141 -0
- package/src/plugin/outbox-drain-schedule.ts +116 -0
- package/src/plugin/outbox-file-push-flow.ts +69 -0
- package/src/plugin/outbox-push-route-runtime-group.ts +81 -0
- package/src/plugin/outbox-push.ts +267 -0
- package/src/plugin/outbox-route.ts +181 -0
- package/src/plugin/outbox-text-push-flow.ts +90 -0
- package/src/plugin/runtime-diagnostics-assembler.ts +183 -0
- package/src/plugin/runtime-diagnostics-helpers.ts +302 -0
- package/src/plugin/runtime-diagnostics-payload-builders.ts +171 -0
- package/src/plugin/runtime-diagnostics-snapshot.ts +31 -0
- package/src/plugin/setup.ts +33 -6
- package/src/plugin/state-store.ts +249 -0
- package/src/plugin/state-transient-runtime-group.ts +105 -0
- package/src/plugin/status-runtime.ts +251 -0
- package/src/plugin/status.ts +33 -7
- package/src/plugin/target-runtime.ts +141 -0
- package/src/plugin/target-status-runtime-group.ts +130 -0
- package/src/plugin/transient-state-runtime.ts +82 -0
- package/src/runtime/outbound-ack-timeout.ts +5 -3
- package/src/runtime/outbound-flags.ts +24 -8
- package/src/runtime/status-snapshots.ts +36 -7
- package/src/runtime/status-worker.ts +34 -4
package/src/core/probe.ts
CHANGED
|
@@ -1,18 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
if (value == null) return fallback;
|
|
3
|
-
const n = Number(value);
|
|
4
|
-
return Number.isFinite(n) ? n : fallback;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
function finiteNumberOrNull(value: unknown): number | null {
|
|
8
|
-
if (value == null) return null;
|
|
9
|
-
const n = Number(value);
|
|
10
|
-
return Number.isFinite(n) ? n : null;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function nonNegativeFiniteNumberOr(value: unknown, fallback: number): number {
|
|
14
|
-
return Math.max(0, finiteNumberOr(value, fallback));
|
|
15
|
-
}
|
|
1
|
+
import { finiteNumberOrNull, nonNegativeFiniteNumberOr } from './value-sanitize.ts';
|
|
16
2
|
|
|
17
3
|
export function probeBncrAccount(params: {
|
|
18
4
|
accountId: string;
|
|
@@ -64,3 +50,5 @@ export function probeBncrAccount(params: {
|
|
|
64
50
|
},
|
|
65
51
|
};
|
|
66
52
|
}
|
|
53
|
+
|
|
54
|
+
export type BncrAccountProbe = ReturnType<typeof probeBncrAccount>;
|
|
@@ -35,9 +35,9 @@ export type RegisterTraceSummary = {
|
|
|
35
35
|
|
|
36
36
|
export type RegisterDriftSnapshot = {
|
|
37
37
|
capturedAt: number;
|
|
38
|
-
registerCount: number;
|
|
39
|
-
apiGeneration: number;
|
|
40
|
-
postWarmupRegisterCount: number;
|
|
38
|
+
registerCount: number | null;
|
|
39
|
+
apiGeneration: number | null;
|
|
40
|
+
postWarmupRegisterCount: number | null;
|
|
41
41
|
apiInstanceId: string | null;
|
|
42
42
|
registryFingerprint: string | null;
|
|
43
43
|
dominantBucket: string | null;
|
package/src/core/status.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { buildRuntimeStatusMetaDisplay } from './status-meta.ts';
|
|
4
|
-
import type { BncrDiagnosticsSummary, PendingAdmission } from './types.ts';
|
|
4
|
+
import type { BncrDiagnosticsSummary, BncrRuntimeLastSession, PendingAdmission } from './types.ts';
|
|
5
5
|
|
|
6
6
|
type RuntimeStatusInput = {
|
|
7
7
|
accountId: string;
|
|
@@ -15,7 +15,7 @@ type RuntimeStatusInput = {
|
|
|
15
15
|
ackEvents: number;
|
|
16
16
|
startedAt: number;
|
|
17
17
|
pendingAdmissions?: PendingAdmission[];
|
|
18
|
-
lastSession?:
|
|
18
|
+
lastSession?: BncrRuntimeLastSession | null;
|
|
19
19
|
lastActivityAt?: number | null;
|
|
20
20
|
lastInboundAt?: number | null;
|
|
21
21
|
lastOutboundAt?: number | null;
|
|
@@ -140,9 +140,48 @@ export function buildAccountRuntimeSnapshot(input: RuntimeStatusInput) {
|
|
|
140
140
|
};
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
export type BncrAccountRuntimeSnapshot = ReturnType<typeof buildAccountRuntimeSnapshot>;
|
|
144
|
+
|
|
143
145
|
export function buildAccountStatusSnapshot(input: {
|
|
144
146
|
account: { accountId: string; name?: string; enabled?: boolean };
|
|
145
|
-
runtime:
|
|
147
|
+
runtime:
|
|
148
|
+
| {
|
|
149
|
+
connected?: boolean;
|
|
150
|
+
running?: boolean;
|
|
151
|
+
mode?: string;
|
|
152
|
+
pending?: number | null;
|
|
153
|
+
deadLetter?: number | null;
|
|
154
|
+
lastEventAt?: number | null;
|
|
155
|
+
lastError?: string | null;
|
|
156
|
+
lastSessionKey?: string | null;
|
|
157
|
+
lastSessionScope?: string | null;
|
|
158
|
+
lastSessionAt?: number | null;
|
|
159
|
+
lastActivityAt?: number | null;
|
|
160
|
+
lastInboundAt?: number | null;
|
|
161
|
+
lastOutboundAt?: number | null;
|
|
162
|
+
lastSessionAgo?: string | null;
|
|
163
|
+
lastActivityAgo?: string | null;
|
|
164
|
+
lastInboundAgo?: string | null;
|
|
165
|
+
lastOutboundAgo?: string | null;
|
|
166
|
+
diagnostics?: BncrDiagnosticsSummary | Record<string, unknown> | null;
|
|
167
|
+
meta?: {
|
|
168
|
+
pending?: number | null;
|
|
169
|
+
deadLetter?: number | null;
|
|
170
|
+
lastSessionKey?: string | null;
|
|
171
|
+
lastSessionScope?: string | null;
|
|
172
|
+
lastSessionAt?: number | null;
|
|
173
|
+
lastActivityAt?: number | null;
|
|
174
|
+
lastInboundAt?: number | null;
|
|
175
|
+
lastOutboundAt?: number | null;
|
|
176
|
+
lastSessionAgo?: string | null;
|
|
177
|
+
lastActivityAgo?: string | null;
|
|
178
|
+
lastInboundAgo?: string | null;
|
|
179
|
+
lastOutboundAgo?: string | null;
|
|
180
|
+
diagnostics?: BncrDiagnosticsSummary | Record<string, unknown> | null;
|
|
181
|
+
} | null;
|
|
182
|
+
}
|
|
183
|
+
| null
|
|
184
|
+
| undefined;
|
|
146
185
|
healthSummary: string;
|
|
147
186
|
displayName: string;
|
|
148
187
|
}) {
|
|
@@ -162,7 +201,7 @@ export function buildAccountStatusSnapshot(input: {
|
|
|
162
201
|
const lastOutboundAt = rt?.lastOutboundAt ?? meta.lastOutboundAt ?? null;
|
|
163
202
|
const lastOutboundAgo = rt?.lastOutboundAgo ?? meta.lastOutboundAgo ?? '-';
|
|
164
203
|
const diagnostics = rt?.diagnostics ?? meta.diagnostics ?? null;
|
|
165
|
-
const normalizedMode = rt?.mode === 'linked' ? 'linked' : '
|
|
204
|
+
const normalizedMode = rt?.mode === 'linked' ? 'linked' : 'configured';
|
|
166
205
|
|
|
167
206
|
return {
|
|
168
207
|
accountId: input.account.accountId,
|
package/src/core/targets.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import type { BncrRoute } from './types.ts';
|
|
2
2
|
|
|
3
|
+
type RouteInputLike = {
|
|
4
|
+
platform?: unknown;
|
|
5
|
+
groupId?: unknown;
|
|
6
|
+
userId?: unknown;
|
|
7
|
+
route?: unknown;
|
|
8
|
+
};
|
|
9
|
+
|
|
3
10
|
export type BncrSessionKind = 'direct' | 'group';
|
|
11
|
+
|
|
4
12
|
export type BncrExplicitTarget = {
|
|
5
13
|
raw: string;
|
|
6
14
|
normalized: string;
|
|
@@ -20,22 +28,14 @@ export type BncrExplicitTarget = {
|
|
|
20
28
|
groupId?: string;
|
|
21
29
|
};
|
|
22
30
|
|
|
23
|
-
function
|
|
24
|
-
if (typeof
|
|
25
|
-
if (
|
|
26
|
-
return String(
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function parseRouteFromScope(scope: string): BncrRoute | null {
|
|
30
|
-
const parts = asString(scope).trim().split(':');
|
|
31
|
-
if (parts.length < 3) return null;
|
|
32
|
-
const [platform, groupId, userId] = parts;
|
|
33
|
-
if (!platform || !groupId || !userId) return null;
|
|
34
|
-
return { platform, groupId, userId };
|
|
31
|
+
export function asTargetString(value: unknown, fallback = ''): string {
|
|
32
|
+
if (typeof value === 'string') return value;
|
|
33
|
+
if (value == null) return fallback;
|
|
34
|
+
return String(value);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
function parseRouteFromStandardDisplayScope(scope: string): BncrRoute | null {
|
|
38
|
-
const parts =
|
|
37
|
+
export function parseRouteFromStandardDisplayScope(scope: string): BncrRoute | null {
|
|
38
|
+
const parts = asTargetString(scope).trim().split(':');
|
|
39
39
|
if (parts.length === 2) {
|
|
40
40
|
const [platform, userId] = parts;
|
|
41
41
|
if (!platform || !userId) return null;
|
|
@@ -51,8 +51,8 @@ function parseRouteFromStandardDisplayScope(scope: string): BncrRoute | null {
|
|
|
51
51
|
return null;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
function normalizeDisplayScopePrefix(scope: string): string {
|
|
55
|
-
const raw =
|
|
54
|
+
export function normalizeDisplayScopePrefix(scope: string): string {
|
|
55
|
+
const raw = asTargetString(scope).trim();
|
|
56
56
|
if (!raw) return '';
|
|
57
57
|
if (raw.startsWith('Bncr:')) return raw;
|
|
58
58
|
if (/^bncr[:-]/i.test(raw)) return raw;
|
|
@@ -60,108 +60,40 @@ function normalizeDisplayScopePrefix(scope: string): string {
|
|
|
60
60
|
return `Bncr:${raw}`;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
export function
|
|
64
|
-
|
|
65
|
-
if (!raw) return null;
|
|
66
|
-
|
|
67
|
-
const payload = raw.match(/^Bncr:(.+)$/)?.[1];
|
|
68
|
-
if (!payload) return null;
|
|
69
|
-
return parseRouteFromStandardDisplayScope(payload);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function formatDisplayScope(route: BncrRoute): string {
|
|
73
|
-
if (route.groupId === '0' && route.userId !== '0') {
|
|
74
|
-
return `Bncr:${route.platform}:${route.userId}`;
|
|
75
|
-
}
|
|
76
|
-
return `Bncr:${route.platform}:${route.groupId}:${route.userId}`;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export function buildDisplayScopeCandidates(route: BncrRoute): string[] {
|
|
80
|
-
const candidates = [formatDisplayScope(route)].filter(Boolean);
|
|
81
|
-
return Array.from(new Set(candidates.map((x) => asString(x).trim()).filter(Boolean)));
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export function formatTargetDisplay(
|
|
85
|
-
input: BncrRoute | BncrExplicitTarget | null | undefined,
|
|
86
|
-
): string {
|
|
87
|
-
if (!input) return '';
|
|
88
|
-
const route = parseRouteLike((input as any)?.route) || parseRouteLike(input);
|
|
89
|
-
if (!route) return '';
|
|
90
|
-
return formatDisplayScope(route);
|
|
63
|
+
export function resolveCanonicalSessionKind(): BncrSessionKind {
|
|
64
|
+
return 'direct';
|
|
91
65
|
}
|
|
92
66
|
|
|
93
|
-
export function
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
route = strict.route;
|
|
107
|
-
source = 'strict-session-key';
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (!route) {
|
|
111
|
-
const displayRoute = parseRouteFromDisplayScope(raw);
|
|
112
|
-
if (displayRoute) {
|
|
113
|
-
route = displayRoute;
|
|
114
|
-
source = 'display-scope';
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (!route) {
|
|
119
|
-
const legacy = parseLegacySessionKey(raw);
|
|
120
|
-
if (legacy?.route) {
|
|
121
|
-
route = legacy.route;
|
|
122
|
-
source = legacy.source === 'hex' ? 'hex-scope' : 'legacy-session-key';
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (!route) {
|
|
127
|
-
const hexRoute = parseRouteFromHexScope(raw);
|
|
128
|
-
if (hexRoute) {
|
|
129
|
-
route = hexRoute;
|
|
130
|
-
source = 'hex-scope';
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (!route) {
|
|
135
|
-
const scopedRoute = parseRouteFromScope(raw);
|
|
136
|
-
if (scopedRoute) {
|
|
137
|
-
route = scopedRoute;
|
|
138
|
-
source = 'route-scope';
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (!route || !source) return null;
|
|
143
|
-
|
|
144
|
-
const kind: BncrSessionKind = route.groupId === '0' ? 'direct' : 'group';
|
|
145
|
-
const displayScope = formatDisplayScope(route);
|
|
67
|
+
export function buildExplicitTargetResult(args: {
|
|
68
|
+
raw: string;
|
|
69
|
+
source:
|
|
70
|
+
| 'display-scope'
|
|
71
|
+
| 'strict-session-key'
|
|
72
|
+
| 'legacy-session-key'
|
|
73
|
+
| 'hex-scope'
|
|
74
|
+
| 'route-scope';
|
|
75
|
+
route: BncrRoute;
|
|
76
|
+
displayScope: string;
|
|
77
|
+
canonicalSessionKey?: string;
|
|
78
|
+
}) {
|
|
79
|
+
const kind: BncrSessionKind = args.route.groupId === '0' ? 'direct' : 'group';
|
|
146
80
|
return {
|
|
147
|
-
raw,
|
|
148
|
-
normalized: displayScope,
|
|
149
|
-
source,
|
|
81
|
+
raw: args.raw,
|
|
82
|
+
normalized: args.displayScope,
|
|
83
|
+
source: args.source,
|
|
150
84
|
kind,
|
|
151
|
-
chatType: 'direct',
|
|
152
|
-
displayScope,
|
|
153
|
-
route,
|
|
154
|
-
...(
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
userId: route.userId,
|
|
159
|
-
...(route.groupId === '0' ? {} : { groupId: route.groupId }),
|
|
85
|
+
chatType: 'direct' as const,
|
|
86
|
+
displayScope: args.displayScope,
|
|
87
|
+
route: args.route,
|
|
88
|
+
...(args.canonicalSessionKey ? { canonicalSessionKey: args.canonicalSessionKey } : {}),
|
|
89
|
+
platform: args.route.platform,
|
|
90
|
+
userId: args.route.userId,
|
|
91
|
+
...(args.route.groupId === '0' ? {} : { groupId: args.route.groupId }),
|
|
160
92
|
};
|
|
161
93
|
}
|
|
162
94
|
|
|
163
95
|
export function isLowerHex(input: string): boolean {
|
|
164
|
-
const raw =
|
|
96
|
+
const raw = asTargetString(input).trim();
|
|
165
97
|
return !!raw && /^[0-9a-fA-F]+$/.test(raw) && raw.length % 2 === 0;
|
|
166
98
|
}
|
|
167
99
|
|
|
@@ -170,8 +102,16 @@ export function routeScopeToHex(route: BncrRoute): string {
|
|
|
170
102
|
return Buffer.from(raw, 'utf8').toString('hex').toLowerCase();
|
|
171
103
|
}
|
|
172
104
|
|
|
105
|
+
export function parseRouteFromScope(scope: string): BncrRoute | null {
|
|
106
|
+
const parts = asTargetString(scope).trim().split(':');
|
|
107
|
+
if (parts.length < 3) return null;
|
|
108
|
+
const [platform, groupId, userId] = parts;
|
|
109
|
+
if (!platform || !groupId || !userId) return null;
|
|
110
|
+
return { platform, groupId, userId };
|
|
111
|
+
}
|
|
112
|
+
|
|
173
113
|
export function parseRouteFromHexScope(scopeHex: string): BncrRoute | null {
|
|
174
|
-
const rawHex =
|
|
114
|
+
const rawHex = asTargetString(scopeHex).trim();
|
|
175
115
|
if (!isLowerHex(rawHex)) return null;
|
|
176
116
|
|
|
177
117
|
try {
|
|
@@ -182,26 +122,42 @@ export function parseRouteFromHexScope(scopeHex: string): BncrRoute | null {
|
|
|
182
122
|
}
|
|
183
123
|
}
|
|
184
124
|
|
|
185
|
-
export function
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
125
|
+
export function parseStrictBncrSessionKey(input: string): {
|
|
126
|
+
inputSessionKey: string;
|
|
127
|
+
inputAgentId: string;
|
|
128
|
+
inputKind: BncrSessionKind;
|
|
129
|
+
scopeHex: string;
|
|
130
|
+
route: BncrRoute;
|
|
131
|
+
} | null {
|
|
132
|
+
const raw = asTargetString(input).trim();
|
|
133
|
+
if (!raw) return null;
|
|
192
134
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
scope?: string | null;
|
|
196
|
-
sessionKey?: string | null;
|
|
197
|
-
}): BncrSessionKind {
|
|
198
|
-
return 'direct';
|
|
199
|
-
}
|
|
135
|
+
const m = raw.match(/^agent:([^:]+):bncr:(direct|group):(.+)$/);
|
|
136
|
+
if (!m?.[1] || !m?.[2] || !m?.[3]) return null;
|
|
200
137
|
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
const
|
|
204
|
-
|
|
138
|
+
const inputAgentId = asTargetString(m[1]).trim();
|
|
139
|
+
const inputKind = m[2] as BncrSessionKind;
|
|
140
|
+
const payload = asTargetString(m[3]).trim();
|
|
141
|
+
let route: BncrRoute | null = null;
|
|
142
|
+
let scopeHex = '';
|
|
143
|
+
|
|
144
|
+
if (isLowerHex(payload)) {
|
|
145
|
+
scopeHex = payload.toLowerCase();
|
|
146
|
+
route = parseRouteFromHexScope(scopeHex);
|
|
147
|
+
} else {
|
|
148
|
+
route = parseRouteFromScope(payload);
|
|
149
|
+
if (route) scopeHex = routeScopeToHex(route);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (!route || !scopeHex) return null;
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
inputSessionKey: raw,
|
|
156
|
+
inputAgentId,
|
|
157
|
+
inputKind,
|
|
158
|
+
scopeHex,
|
|
159
|
+
route,
|
|
160
|
+
};
|
|
205
161
|
}
|
|
206
162
|
|
|
207
163
|
export function parseLegacySessionKey(input: string): {
|
|
@@ -210,7 +166,7 @@ export function parseLegacySessionKey(input: string): {
|
|
|
210
166
|
inputAgentId?: string;
|
|
211
167
|
source: 'legacy-direct' | 'legacy-bncr' | 'legacy-agent' | 'hex';
|
|
212
168
|
} | null {
|
|
213
|
-
const raw =
|
|
169
|
+
const raw = asTargetString(input).trim();
|
|
214
170
|
if (!raw) return null;
|
|
215
171
|
|
|
216
172
|
const directLegacy = raw.match(/^agent:([^:]+):bncr:direct:([0-9a-fA-F]+):0$/);
|
|
@@ -266,68 +222,36 @@ export function parseLegacySessionKey(input: string): {
|
|
|
266
222
|
}
|
|
267
223
|
|
|
268
224
|
export function isLegacyNoiseRoute(route: BncrRoute): boolean {
|
|
269
|
-
const platform =
|
|
270
|
-
const groupId =
|
|
271
|
-
const userId =
|
|
225
|
+
const platform = asTargetString(route.platform).trim().toLowerCase();
|
|
226
|
+
const groupId = asTargetString(route.groupId).trim().toLowerCase();
|
|
227
|
+
const userId = asTargetString(route.userId).trim().toLowerCase();
|
|
272
228
|
|
|
273
229
|
if (platform === 'agent' && groupId === 'main' && userId === 'bncr') return true;
|
|
274
230
|
if (platform === 'bncr' && userId === '0' && isLowerHex(groupId)) return true;
|
|
275
231
|
return false;
|
|
276
232
|
}
|
|
277
233
|
|
|
278
|
-
export function
|
|
279
|
-
|
|
280
|
-
inputAgentId: string;
|
|
281
|
-
inputKind: BncrSessionKind;
|
|
282
|
-
scopeHex: string;
|
|
283
|
-
route: BncrRoute;
|
|
284
|
-
} | null {
|
|
285
|
-
const raw = asString(input).trim();
|
|
234
|
+
export function parseRouteFromDisplayScope(scope: string): BncrRoute | null {
|
|
235
|
+
const raw = normalizeDisplayScopePrefix(scope);
|
|
286
236
|
if (!raw) return null;
|
|
287
237
|
|
|
288
|
-
const
|
|
289
|
-
if (!
|
|
290
|
-
|
|
291
|
-
const inputAgentId = asString(m[1]).trim();
|
|
292
|
-
const inputKind = m[2] as BncrSessionKind;
|
|
293
|
-
const payload = asString(m[3]).trim();
|
|
294
|
-
let route: BncrRoute | null = null;
|
|
295
|
-
let scopeHex = '';
|
|
296
|
-
|
|
297
|
-
if (isLowerHex(payload)) {
|
|
298
|
-
scopeHex = payload.toLowerCase();
|
|
299
|
-
route = parseRouteFromHexScope(scopeHex);
|
|
300
|
-
} else {
|
|
301
|
-
route = parseRouteFromScope(payload);
|
|
302
|
-
if (route) scopeHex = routeScopeToHex(route);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
if (!route || !scopeHex) return null;
|
|
306
|
-
|
|
307
|
-
return {
|
|
308
|
-
inputSessionKey: raw,
|
|
309
|
-
inputAgentId,
|
|
310
|
-
inputKind,
|
|
311
|
-
scopeHex,
|
|
312
|
-
route,
|
|
313
|
-
};
|
|
238
|
+
const payload = raw.match(/^Bncr:(.+)$/)?.[1];
|
|
239
|
+
if (!payload) return null;
|
|
240
|
+
return parseRouteFromStandardDisplayScope(payload);
|
|
314
241
|
}
|
|
315
242
|
|
|
316
|
-
export function
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
.replace(/[^a-z0-9_-]+/g, '-')
|
|
321
|
-
.replace(/^-+|-+$/g, '')
|
|
322
|
-
.slice(0, 32);
|
|
323
|
-
return normalized || null;
|
|
243
|
+
export function buildCanonicalBncrSessionKey(route: BncrRoute, canonicalAgentId: string): string {
|
|
244
|
+
const agentId = asTargetString(canonicalAgentId).trim() || 'main';
|
|
245
|
+
const kind = resolveCanonicalSessionKind();
|
|
246
|
+
return `agent:${agentId}:bncr:${kind}:${routeScopeToHex(route)}`;
|
|
324
247
|
}
|
|
325
248
|
|
|
326
249
|
export function normalizeStoredSessionKey(
|
|
327
250
|
input: string,
|
|
328
251
|
canonicalAgentId?: string | null,
|
|
252
|
+
helpers?: { normalizeTaskKey?: (input: unknown) => string | null },
|
|
329
253
|
): { sessionKey: string; route: BncrRoute } | null {
|
|
330
|
-
const raw =
|
|
254
|
+
const raw = asTargetString(input).trim();
|
|
331
255
|
if (!raw) return null;
|
|
332
256
|
|
|
333
257
|
let taskKey: string | null = null;
|
|
@@ -335,8 +259,8 @@ export function normalizeStoredSessionKey(
|
|
|
335
259
|
|
|
336
260
|
const taskTagged = raw.match(/^(.*):task:([a-z0-9_-]{1,32})$/i);
|
|
337
261
|
if (taskTagged) {
|
|
338
|
-
base =
|
|
339
|
-
taskKey = normalizeTaskKey(taskTagged[2]);
|
|
262
|
+
base = asTargetString(taskTagged[1]).trim();
|
|
263
|
+
taskKey = helpers?.normalizeTaskKey?.(taskTagged[2]) || null;
|
|
340
264
|
}
|
|
341
265
|
|
|
342
266
|
let route: BncrRoute | null = null;
|
|
@@ -359,7 +283,7 @@ export function normalizeStoredSessionKey(
|
|
|
359
283
|
if (!route) return null;
|
|
360
284
|
if (isLegacyNoiseRoute(route)) return null;
|
|
361
285
|
|
|
362
|
-
const finalAgentId =
|
|
286
|
+
const finalAgentId = asTargetString(canonicalAgentId).trim() || passthroughAgentId;
|
|
363
287
|
if (!finalAgentId) return null;
|
|
364
288
|
|
|
365
289
|
const finalSessionKey = buildCanonicalBncrSessionKey(route, finalAgentId);
|
|
@@ -374,51 +298,132 @@ export function normalizeInboundSessionKey(
|
|
|
374
298
|
route: BncrRoute,
|
|
375
299
|
canonicalAgentId: string,
|
|
376
300
|
): string | null {
|
|
377
|
-
const raw =
|
|
301
|
+
const raw = asTargetString(scope).trim();
|
|
378
302
|
let finalRoute: BncrRoute | null = null;
|
|
379
303
|
|
|
380
|
-
if (!raw)
|
|
381
|
-
|
|
304
|
+
if (!raw) finalRoute = route;
|
|
305
|
+
if (!finalRoute) finalRoute = parseStrictBncrSessionKey(raw)?.route || null;
|
|
306
|
+
if (!finalRoute) finalRoute = parseLegacySessionKey(raw)?.route || null;
|
|
307
|
+
if (!finalRoute) finalRoute = parseRouteFromDisplayScope(raw);
|
|
308
|
+
if (!finalRoute) finalRoute = parseRouteFromScope(raw);
|
|
309
|
+
if (!finalRoute && route) finalRoute = route;
|
|
310
|
+
if (!finalRoute) return null;
|
|
311
|
+
|
|
312
|
+
return buildCanonicalBncrSessionKey(finalRoute, canonicalAgentId);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export function buildFallbackSessionKey(route: BncrRoute, canonicalAgentId: string): string {
|
|
316
|
+
return buildCanonicalBncrSessionKey(route, canonicalAgentId);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export function parseRouteLike(input: unknown): BncrRoute | null {
|
|
320
|
+
const routeInput = input && typeof input === 'object' ? (input as RouteInputLike) : null;
|
|
321
|
+
const platform = asTargetString(routeInput?.platform || '').trim();
|
|
322
|
+
const groupId = asTargetString(routeInput?.groupId || '').trim();
|
|
323
|
+
const userId = asTargetString(routeInput?.userId || '').trim();
|
|
324
|
+
if (!platform || !groupId || !userId) return null;
|
|
325
|
+
return { platform, groupId, userId };
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export type BncrExplicitTargetSource =
|
|
329
|
+
| 'display-scope'
|
|
330
|
+
| 'strict-session-key'
|
|
331
|
+
| 'legacy-session-key'
|
|
332
|
+
| 'hex-scope'
|
|
333
|
+
| 'route-scope';
|
|
334
|
+
|
|
335
|
+
export function formatDisplayScope(route: BncrRoute): string {
|
|
336
|
+
if (route.groupId === '0' && route.userId !== '0') {
|
|
337
|
+
return `Bncr:${route.platform}:${route.userId}`;
|
|
338
|
+
}
|
|
339
|
+
return `Bncr:${route.platform}:${route.groupId}:${route.userId}`;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
export function buildDisplayScopeCandidates(route: BncrRoute): string[] {
|
|
343
|
+
const candidates = [formatDisplayScope(route)].filter(Boolean);
|
|
344
|
+
return Array.from(new Set(candidates.map((x) => asTargetString(x).trim()).filter(Boolean)));
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export function resolveExplicitTargetRoute(raw: string): {
|
|
348
|
+
route: BncrRoute | null;
|
|
349
|
+
source: BncrExplicitTargetSource | null;
|
|
350
|
+
} {
|
|
351
|
+
let route: BncrRoute | null = null;
|
|
352
|
+
let source: BncrExplicitTargetSource | null = null;
|
|
353
|
+
|
|
354
|
+
const strict = parseStrictBncrSessionKey(raw);
|
|
355
|
+
if (strict?.route) {
|
|
356
|
+
route = strict.route;
|
|
357
|
+
source = 'strict-session-key';
|
|
382
358
|
}
|
|
383
359
|
|
|
384
|
-
if (!
|
|
385
|
-
const
|
|
386
|
-
if (
|
|
387
|
-
|
|
360
|
+
if (!route) {
|
|
361
|
+
const displayRoute = parseRouteFromDisplayScope(raw);
|
|
362
|
+
if (displayRoute) {
|
|
363
|
+
route = displayRoute;
|
|
364
|
+
source = 'display-scope';
|
|
388
365
|
}
|
|
389
366
|
}
|
|
390
367
|
|
|
391
|
-
if (!
|
|
368
|
+
if (!route) {
|
|
392
369
|
const legacy = parseLegacySessionKey(raw);
|
|
393
370
|
if (legacy?.route) {
|
|
394
|
-
|
|
371
|
+
route = legacy.route;
|
|
372
|
+
source = legacy.source === 'hex' ? 'hex-scope' : 'legacy-session-key';
|
|
395
373
|
}
|
|
396
374
|
}
|
|
397
375
|
|
|
398
|
-
if (!
|
|
399
|
-
const
|
|
400
|
-
if (
|
|
401
|
-
|
|
376
|
+
if (!route) {
|
|
377
|
+
const hexRoute = parseRouteFromHexScope(raw);
|
|
378
|
+
if (hexRoute) {
|
|
379
|
+
route = hexRoute;
|
|
380
|
+
source = 'hex-scope';
|
|
402
381
|
}
|
|
403
382
|
}
|
|
404
383
|
|
|
405
|
-
if (!
|
|
384
|
+
if (!route) {
|
|
406
385
|
const scopedRoute = parseRouteFromScope(raw);
|
|
407
386
|
if (scopedRoute) {
|
|
408
|
-
|
|
387
|
+
route = scopedRoute;
|
|
388
|
+
source = 'route-scope';
|
|
409
389
|
}
|
|
410
390
|
}
|
|
411
391
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
}
|
|
392
|
+
return { route, source };
|
|
393
|
+
}
|
|
415
394
|
|
|
416
|
-
|
|
417
|
-
|
|
395
|
+
export function parseExplicitTarget(input: string, options?: { canonicalAgentId?: string | null }) {
|
|
396
|
+
const raw = asTargetString(input).trim();
|
|
397
|
+
if (!raw) return null;
|
|
398
|
+
|
|
399
|
+
const canonicalAgentId = asTargetString(options?.canonicalAgentId).trim() || undefined;
|
|
400
|
+
const { route, source } = resolveExplicitTargetRoute(raw);
|
|
401
|
+
if (!route || !source) return null;
|
|
402
|
+
|
|
403
|
+
const displayScope = formatDisplayScope(route);
|
|
404
|
+
return buildExplicitTargetResult({
|
|
405
|
+
raw,
|
|
406
|
+
source,
|
|
407
|
+
route,
|
|
408
|
+
displayScope,
|
|
409
|
+
...(canonicalAgentId
|
|
410
|
+
? { canonicalSessionKey: buildCanonicalBncrSessionKey(route, canonicalAgentId) }
|
|
411
|
+
: {}),
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export function normalizeTaskKey(input: unknown): string | null {
|
|
416
|
+
const raw = asTargetString(input).trim().toLowerCase();
|
|
417
|
+
if (!raw) return null;
|
|
418
|
+
const normalized = raw
|
|
419
|
+
.replace(/[^a-z0-9_-]+/g, '-')
|
|
420
|
+
.replace(/^-+|-+$/g, '')
|
|
421
|
+
.slice(0, 32);
|
|
422
|
+
return normalized || null;
|
|
418
423
|
}
|
|
419
424
|
|
|
420
425
|
export function extractInlineTaskKey(text: string): { taskKey: string | null; text: string } {
|
|
421
|
-
const raw =
|
|
426
|
+
const raw = asTargetString(text);
|
|
422
427
|
if (!raw) return { taskKey: null, text: '' };
|
|
423
428
|
|
|
424
429
|
const tagged = raw.match(
|
|
@@ -427,7 +432,7 @@ export function extractInlineTaskKey(text: string): { taskKey: string | null; te
|
|
|
427
432
|
if (tagged) {
|
|
428
433
|
return {
|
|
429
434
|
taskKey: normalizeTaskKey(tagged[1]),
|
|
430
|
-
text:
|
|
435
|
+
text: asTargetString(tagged[2]),
|
|
431
436
|
};
|
|
432
437
|
}
|
|
433
438
|
|
|
@@ -435,7 +440,7 @@ export function extractInlineTaskKey(text: string): { taskKey: string | null; te
|
|
|
435
440
|
if (spaced) {
|
|
436
441
|
return {
|
|
437
442
|
taskKey: normalizeTaskKey(spaced[1]),
|
|
438
|
-
text:
|
|
443
|
+
text: asTargetString(spaced[2]),
|
|
439
444
|
};
|
|
440
445
|
}
|
|
441
446
|
|
|
@@ -443,15 +448,21 @@ export function extractInlineTaskKey(text: string): { taskKey: string | null; te
|
|
|
443
448
|
}
|
|
444
449
|
|
|
445
450
|
export function withTaskSessionKey(sessionKey: string, taskKey?: string | null): string {
|
|
446
|
-
const base =
|
|
451
|
+
const base = asTargetString(sessionKey).trim();
|
|
447
452
|
const tk = normalizeTaskKey(taskKey);
|
|
448
453
|
if (!base || !tk) return base;
|
|
449
454
|
if (/:task:[a-z0-9_-]+(?:$|:)/i.test(base)) return base;
|
|
450
455
|
return `${base}:task:${tk}`;
|
|
451
456
|
}
|
|
452
457
|
|
|
453
|
-
export function
|
|
454
|
-
|
|
458
|
+
export function formatTargetDisplay(
|
|
459
|
+
input: BncrRoute | BncrExplicitTarget | null | undefined,
|
|
460
|
+
): string {
|
|
461
|
+
if (!input) return '';
|
|
462
|
+
const routeInput = input && typeof input === 'object' ? (input as RouteInputLike) : null;
|
|
463
|
+
const route = parseRouteLike(routeInput?.route) || parseRouteLike(input);
|
|
464
|
+
if (!route) return '';
|
|
465
|
+
return formatDisplayScope(route);
|
|
455
466
|
}
|
|
456
467
|
|
|
457
468
|
export function routeKey(accountId: string, route: BncrRoute): string {
|