@xmoxmo/bncr 0.1.2 → 0.1.3
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/index.ts +60 -15
- package/package.json +7 -2
- package/scripts/check-register-drift.mjs +17 -5
- package/src/channel.ts +572 -252
- package/src/core/accounts.ts +7 -1
- package/src/core/permissions.ts +3 -1
- package/src/core/probe.ts +2 -1
- package/src/core/status.ts +9 -3
- package/src/core/targets.ts +151 -41
- package/src/messaging/inbound/commands.ts +34 -10
- package/src/messaging/inbound/dispatch.ts +40 -4
- package/src/messaging/inbound/gate.ts +1 -3
- package/src/messaging/inbound/parse.ts +5 -2
- package/src/messaging/outbound/media.ts +24 -5
- package/src/messaging/outbound/send.ts +25 -5
package/src/core/accounts.ts
CHANGED
|
@@ -17,7 +17,13 @@ export function normalizeAccountId(accountId?: string | null): string {
|
|
|
17
17
|
|
|
18
18
|
export function resolveDefaultDisplayName(rawName: unknown, accountId: string): string {
|
|
19
19
|
const raw = asString(rawName || '').trim();
|
|
20
|
-
if (
|
|
20
|
+
if (
|
|
21
|
+
!raw ||
|
|
22
|
+
raw === accountId ||
|
|
23
|
+
/^bncr$/i.test(raw) ||
|
|
24
|
+
/^status$/i.test(raw) ||
|
|
25
|
+
/^runtime$/i.test(raw)
|
|
26
|
+
) {
|
|
21
27
|
return 'Monitor';
|
|
22
28
|
}
|
|
23
29
|
return raw;
|
package/src/core/permissions.ts
CHANGED
|
@@ -7,7 +7,9 @@ function asString(v: unknown, fallback = ''): string {
|
|
|
7
7
|
export function getBncrElevatedConfig(rootCfg: any) {
|
|
8
8
|
const elevated = rootCfg?.tools?.elevated || {};
|
|
9
9
|
const allowFrom = elevated?.allowFrom || {};
|
|
10
|
-
const bncrRules = Array.isArray(allowFrom?.bncr)
|
|
10
|
+
const bncrRules = Array.isArray(allowFrom?.bncr)
|
|
11
|
+
? allowFrom.bncr.map((x: unknown) => asString(x).trim()).filter(Boolean)
|
|
12
|
+
: [];
|
|
11
13
|
|
|
12
14
|
return {
|
|
13
15
|
enabled: elevated?.enabled === true,
|
package/src/core/probe.ts
CHANGED
|
@@ -24,7 +24,8 @@ export function probeBncrAccount(params: {
|
|
|
24
24
|
|
|
25
25
|
let level: 'ok' | 'warn' | 'error' = 'ok';
|
|
26
26
|
if (issues.length > 0) level = 'warn';
|
|
27
|
-
if (!params.connected && (params.deadLetter > 0 || params.invalidOutboxSessionKeys > 0))
|
|
27
|
+
if (!params.connected && (params.deadLetter > 0 || params.invalidOutboxSessionKeys > 0))
|
|
28
|
+
level = 'error';
|
|
28
29
|
|
|
29
30
|
return {
|
|
30
31
|
ok: level === 'ok',
|
package/src/core/status.ts
CHANGED
|
@@ -49,7 +49,9 @@ export function buildIntegratedDiagnostics(input: RuntimeStatusInput): BncrDiagn
|
|
|
49
49
|
health: {
|
|
50
50
|
connected: input.connected,
|
|
51
51
|
pending: input.pending,
|
|
52
|
-
pendingAdmissions: Array.isArray(input.pendingAdmissions)
|
|
52
|
+
pendingAdmissions: Array.isArray(input.pendingAdmissions)
|
|
53
|
+
? input.pendingAdmissions.length
|
|
54
|
+
: 0,
|
|
53
55
|
deadLetter: input.deadLetter,
|
|
54
56
|
activeConnections: input.activeConnections,
|
|
55
57
|
connectEvents: input.connectEvents,
|
|
@@ -94,11 +96,15 @@ export function buildStatusMetaFromRuntime(input: RuntimeStatusInput) {
|
|
|
94
96
|
const diagnostics = buildIntegratedDiagnostics(input);
|
|
95
97
|
return {
|
|
96
98
|
pending: input.pending,
|
|
97
|
-
pendingAdmissionsCount: Array.isArray(input.pendingAdmissions)
|
|
99
|
+
pendingAdmissionsCount: Array.isArray(input.pendingAdmissions)
|
|
100
|
+
? input.pendingAdmissions.length
|
|
101
|
+
: 0,
|
|
98
102
|
pendingAdmissions: Array.isArray(input.pendingAdmissions)
|
|
99
103
|
? input.pendingAdmissions.map((item) => ({
|
|
100
104
|
clientId: item.clientId,
|
|
101
|
-
scope: item.route
|
|
105
|
+
scope: item.route
|
|
106
|
+
? `${item.route.platform}:${item.route.groupId}:${item.route.userId}`
|
|
107
|
+
: null,
|
|
102
108
|
scopes: Array.isArray(item.routes)
|
|
103
109
|
? item.routes.map((route) => `${route.platform}:${route.groupId}:${route.userId}`)
|
|
104
110
|
: [],
|
package/src/core/targets.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { BncrRoute } from './types.ts';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
export type BncrSessionKind = 'direct' | 'group';
|
|
4
4
|
|
|
5
5
|
function asString(v: unknown, fallback = ''): string {
|
|
6
6
|
if (typeof v === 'string') return v;
|
|
@@ -84,31 +84,76 @@ export function parseRouteLike(input: unknown): BncrRoute | null {
|
|
|
84
84
|
return { platform, groupId, userId };
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
export function
|
|
87
|
+
export function resolveCanonicalSessionKind(_input?: {
|
|
88
|
+
route?: BncrRoute | null;
|
|
89
|
+
scope?: string | null;
|
|
90
|
+
sessionKey?: string | null;
|
|
91
|
+
}): BncrSessionKind {
|
|
92
|
+
return 'direct';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function buildCanonicalBncrSessionKey(route: BncrRoute, canonicalAgentId: string): string {
|
|
96
|
+
const agentId = asString(canonicalAgentId).trim() || 'main';
|
|
97
|
+
const kind = resolveCanonicalSessionKind({ route });
|
|
98
|
+
return `agent:${agentId}:bncr:${kind}:${routeScopeToHex(route)}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function parseLegacySessionKey(input: string): {
|
|
102
|
+
route: BncrRoute;
|
|
103
|
+
inputKind: BncrSessionKind;
|
|
104
|
+
inputAgentId?: string;
|
|
105
|
+
source: 'legacy-direct' | 'legacy-bncr' | 'legacy-agent' | 'hex';
|
|
106
|
+
} | null {
|
|
88
107
|
const raw = asString(input).trim();
|
|
89
108
|
if (!raw) return null;
|
|
90
109
|
|
|
91
|
-
const directLegacy = raw.match(/^agent:
|
|
92
|
-
if (directLegacy?.[1]) {
|
|
93
|
-
const route = parseRouteFromHexScope(directLegacy[
|
|
94
|
-
if (route)
|
|
110
|
+
const directLegacy = raw.match(/^agent:([^:]+):bncr:direct:([0-9a-fA-F]+):0$/);
|
|
111
|
+
if (directLegacy?.[1] && directLegacy?.[2]) {
|
|
112
|
+
const route = parseRouteFromHexScope(directLegacy[2].toLowerCase());
|
|
113
|
+
if (route) {
|
|
114
|
+
return {
|
|
115
|
+
route,
|
|
116
|
+
inputKind: 'direct',
|
|
117
|
+
inputAgentId: directLegacy[1],
|
|
118
|
+
source: 'legacy-direct',
|
|
119
|
+
};
|
|
120
|
+
}
|
|
95
121
|
}
|
|
96
122
|
|
|
97
123
|
const bncrLegacy = raw.match(/^bncr:([0-9a-fA-F]+):0$/);
|
|
98
124
|
if (bncrLegacy?.[1]) {
|
|
99
125
|
const route = parseRouteFromHexScope(bncrLegacy[1].toLowerCase());
|
|
100
|
-
if (route)
|
|
126
|
+
if (route) {
|
|
127
|
+
return {
|
|
128
|
+
route,
|
|
129
|
+
inputKind: 'direct',
|
|
130
|
+
source: 'legacy-bncr',
|
|
131
|
+
};
|
|
132
|
+
}
|
|
101
133
|
}
|
|
102
134
|
|
|
103
|
-
const agentLegacy = raw.match(/^agent:
|
|
104
|
-
if (agentLegacy?.[1]) {
|
|
105
|
-
const route = parseRouteFromHexScope(agentLegacy[
|
|
106
|
-
if (route)
|
|
135
|
+
const agentLegacy = raw.match(/^agent:([^:]+):bncr:([0-9a-fA-F]+):0$/);
|
|
136
|
+
if (agentLegacy?.[1] && agentLegacy?.[2]) {
|
|
137
|
+
const route = parseRouteFromHexScope(agentLegacy[2].toLowerCase());
|
|
138
|
+
if (route) {
|
|
139
|
+
return {
|
|
140
|
+
route,
|
|
141
|
+
inputKind: 'direct',
|
|
142
|
+
inputAgentId: agentLegacy[1],
|
|
143
|
+
source: 'legacy-agent',
|
|
144
|
+
};
|
|
145
|
+
}
|
|
107
146
|
}
|
|
108
147
|
|
|
109
148
|
if (isLowerHex(raw.toLowerCase())) {
|
|
110
149
|
const route = parseRouteFromHexScope(raw.toLowerCase());
|
|
111
|
-
if (route)
|
|
150
|
+
if (route) {
|
|
151
|
+
return {
|
|
152
|
+
route,
|
|
153
|
+
inputKind: 'direct',
|
|
154
|
+
source: 'hex',
|
|
155
|
+
};
|
|
156
|
+
}
|
|
112
157
|
}
|
|
113
158
|
|
|
114
159
|
return null;
|
|
@@ -124,20 +169,28 @@ export function isLegacyNoiseRoute(route: BncrRoute): boolean {
|
|
|
124
169
|
return false;
|
|
125
170
|
}
|
|
126
171
|
|
|
127
|
-
export function parseStrictBncrSessionKey(input: string): {
|
|
172
|
+
export function parseStrictBncrSessionKey(input: string): {
|
|
173
|
+
inputSessionKey: string;
|
|
174
|
+
inputAgentId: string;
|
|
175
|
+
inputKind: BncrSessionKind;
|
|
176
|
+
scopeHex: string;
|
|
177
|
+
route: BncrRoute;
|
|
178
|
+
} | null {
|
|
128
179
|
const raw = asString(input).trim();
|
|
129
180
|
if (!raw) return null;
|
|
130
181
|
|
|
131
|
-
const m = raw.match(/^agent:
|
|
132
|
-
if (!m?.[1] || !m?.[2]) return null;
|
|
182
|
+
const m = raw.match(/^agent:([^:]+):bncr:(direct|group):(.+)$/);
|
|
183
|
+
if (!m?.[1] || !m?.[2] || !m?.[3]) return null;
|
|
133
184
|
|
|
134
|
-
const
|
|
185
|
+
const inputAgentId = asString(m[1]).trim();
|
|
186
|
+
const inputKind = m[2] as BncrSessionKind;
|
|
187
|
+
const payload = asString(m[3]).trim();
|
|
135
188
|
let route: BncrRoute | null = null;
|
|
136
189
|
let scopeHex = '';
|
|
137
190
|
|
|
138
191
|
if (isLowerHex(payload)) {
|
|
139
|
-
scopeHex = payload;
|
|
140
|
-
route = parseRouteFromHexScope(
|
|
192
|
+
scopeHex = payload.toLowerCase();
|
|
193
|
+
route = parseRouteFromHexScope(scopeHex);
|
|
141
194
|
} else {
|
|
142
195
|
route = parseRouteFromScope(payload);
|
|
143
196
|
if (route) scopeHex = routeScopeToHex(route);
|
|
@@ -146,7 +199,9 @@ export function parseStrictBncrSessionKey(input: string): { sessionKey: string;
|
|
|
146
199
|
if (!route || !scopeHex) return null;
|
|
147
200
|
|
|
148
201
|
return {
|
|
149
|
-
|
|
202
|
+
inputSessionKey: raw,
|
|
203
|
+
inputAgentId,
|
|
204
|
+
inputKind,
|
|
150
205
|
scopeHex,
|
|
151
206
|
route,
|
|
152
207
|
};
|
|
@@ -155,11 +210,17 @@ export function parseStrictBncrSessionKey(input: string): { sessionKey: string;
|
|
|
155
210
|
export function normalizeTaskKey(input: unknown): string | null {
|
|
156
211
|
const raw = asString(input).trim().toLowerCase();
|
|
157
212
|
if (!raw) return null;
|
|
158
|
-
const normalized = raw
|
|
213
|
+
const normalized = raw
|
|
214
|
+
.replace(/[^a-z0-9_-]+/g, '-')
|
|
215
|
+
.replace(/^-+|-+$/g, '')
|
|
216
|
+
.slice(0, 32);
|
|
159
217
|
return normalized || null;
|
|
160
218
|
}
|
|
161
219
|
|
|
162
|
-
export function normalizeStoredSessionKey(
|
|
220
|
+
export function normalizeStoredSessionKey(
|
|
221
|
+
input: string,
|
|
222
|
+
canonicalAgentId?: string | null,
|
|
223
|
+
): { sessionKey: string; route: BncrRoute } | null {
|
|
163
224
|
const raw = asString(input).trim();
|
|
164
225
|
if (!raw) return null;
|
|
165
226
|
|
|
@@ -172,42 +233,91 @@ export function normalizeStoredSessionKey(input: string): { sessionKey: string;
|
|
|
172
233
|
taskKey = normalizeTaskKey(taskTagged[2]);
|
|
173
234
|
}
|
|
174
235
|
|
|
236
|
+
let route: BncrRoute | null = null;
|
|
237
|
+
let passthroughAgentId: string | null = null;
|
|
238
|
+
|
|
175
239
|
const strict = parseStrictBncrSessionKey(base);
|
|
176
240
|
if (strict) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
241
|
+
route = strict.route;
|
|
242
|
+
passthroughAgentId = strict.inputAgentId;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (!route) {
|
|
246
|
+
const legacy = parseLegacySessionKey(base);
|
|
247
|
+
if (legacy) {
|
|
248
|
+
route = legacy.route;
|
|
249
|
+
passthroughAgentId = legacy.inputAgentId || null;
|
|
250
|
+
}
|
|
182
251
|
}
|
|
183
252
|
|
|
184
|
-
|
|
185
|
-
if (
|
|
253
|
+
if (!route) return null;
|
|
254
|
+
if (isLegacyNoiseRoute(route)) return null;
|
|
186
255
|
|
|
187
|
-
const
|
|
188
|
-
if (!
|
|
189
|
-
if (isLegacyNoiseRoute(parsed.route)) return null;
|
|
256
|
+
const finalAgentId = asString(canonicalAgentId).trim() || passthroughAgentId;
|
|
257
|
+
if (!finalAgentId) return null;
|
|
190
258
|
|
|
259
|
+
const finalSessionKey = buildCanonicalBncrSessionKey(route, finalAgentId);
|
|
191
260
|
return {
|
|
192
|
-
sessionKey: taskKey ? `${
|
|
193
|
-
route
|
|
261
|
+
sessionKey: taskKey ? `${finalSessionKey}:task:${taskKey}` : finalSessionKey,
|
|
262
|
+
route,
|
|
194
263
|
};
|
|
195
264
|
}
|
|
196
265
|
|
|
197
|
-
export function normalizeInboundSessionKey(
|
|
266
|
+
export function normalizeInboundSessionKey(
|
|
267
|
+
scope: string,
|
|
268
|
+
route: BncrRoute,
|
|
269
|
+
canonicalAgentId: string,
|
|
270
|
+
): string | null {
|
|
198
271
|
const raw = asString(scope).trim();
|
|
199
|
-
|
|
272
|
+
let finalRoute: BncrRoute | null = null;
|
|
273
|
+
|
|
274
|
+
if (!raw) {
|
|
275
|
+
finalRoute = route;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (!finalRoute) {
|
|
279
|
+
const strict = parseStrictBncrSessionKey(raw);
|
|
280
|
+
if (strict?.route) {
|
|
281
|
+
finalRoute = strict.route;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (!finalRoute) {
|
|
286
|
+
const legacy = parseLegacySessionKey(raw);
|
|
287
|
+
if (legacy?.route) {
|
|
288
|
+
finalRoute = legacy.route;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (!finalRoute) {
|
|
293
|
+
const displayRoute = parseRouteFromDisplayScope(raw);
|
|
294
|
+
if (displayRoute) {
|
|
295
|
+
finalRoute = displayRoute;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (!finalRoute) {
|
|
300
|
+
const scopedRoute = parseRouteFromScope(raw);
|
|
301
|
+
if (scopedRoute) {
|
|
302
|
+
finalRoute = scopedRoute;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (!finalRoute && route) {
|
|
307
|
+
finalRoute = route;
|
|
308
|
+
}
|
|
200
309
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
return parsed.sessionKey;
|
|
310
|
+
if (!finalRoute) return null;
|
|
311
|
+
return buildCanonicalBncrSessionKey(finalRoute, canonicalAgentId);
|
|
204
312
|
}
|
|
205
313
|
|
|
206
314
|
export function extractInlineTaskKey(text: string): { taskKey: string | null; text: string } {
|
|
207
315
|
const raw = asString(text);
|
|
208
316
|
if (!raw) return { taskKey: null, text: '' };
|
|
209
317
|
|
|
210
|
-
const tagged = raw.match(
|
|
318
|
+
const tagged = raw.match(
|
|
319
|
+
/^\s*(?:#task|\/task)\s*[:=]\s*([a-zA-Z0-9_-]{1,32})\s*\n?\s*([\s\S]*)$/i,
|
|
320
|
+
);
|
|
211
321
|
if (tagged) {
|
|
212
322
|
return {
|
|
213
323
|
taskKey: normalizeTaskKey(tagged[1]),
|
|
@@ -234,8 +344,8 @@ export function withTaskSessionKey(sessionKey: string, taskKey?: string | null):
|
|
|
234
344
|
return `${base}:task:${tk}`;
|
|
235
345
|
}
|
|
236
346
|
|
|
237
|
-
export function buildFallbackSessionKey(route: BncrRoute): string {
|
|
238
|
-
return
|
|
347
|
+
export function buildFallbackSessionKey(route: BncrRoute, canonicalAgentId: string): string {
|
|
348
|
+
return buildCanonicalBncrSessionKey(route, canonicalAgentId);
|
|
239
349
|
}
|
|
240
350
|
|
|
241
351
|
export function routeKey(accountId: string, route: BncrRoute): string {
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
formatDisplayScope,
|
|
3
|
+
normalizeInboundSessionKey,
|
|
4
|
+
withTaskSessionKey,
|
|
5
|
+
} from '../../core/targets.ts';
|
|
2
6
|
|
|
3
7
|
type ParsedInbound = ReturnType<typeof import('./parse.ts')['parseBncrInboundParams']>;
|
|
4
8
|
|
|
@@ -14,13 +18,13 @@ export function parseBncrNativeCommand(text: string): NativeCommand | null {
|
|
|
14
18
|
const match = raw.match(/^\/([^\s]+)(?:\s+([\s\S]*))?$/i);
|
|
15
19
|
if (!match) return null;
|
|
16
20
|
|
|
17
|
-
const command = String(match[1] || '')
|
|
21
|
+
const command = String(match[1] || '')
|
|
22
|
+
.trim()
|
|
23
|
+
.toLowerCase();
|
|
18
24
|
if (!command) return null;
|
|
19
25
|
|
|
20
26
|
const rest = String(match[2] || '').trim();
|
|
21
|
-
const body = command === 'help'
|
|
22
|
-
? ['/commands', rest].filter(Boolean).join(' ')
|
|
23
|
-
: raw;
|
|
27
|
+
const body = command === 'help' ? ['/commands', rest].filter(Boolean).join(' ') : raw;
|
|
24
28
|
return { command, raw, body };
|
|
25
29
|
}
|
|
26
30
|
|
|
@@ -29,6 +33,7 @@ export async function handleBncrNativeCommand(params: {
|
|
|
29
33
|
channelId: string;
|
|
30
34
|
cfg: any;
|
|
31
35
|
parsed: ParsedInbound;
|
|
36
|
+
canonicalAgentId: string;
|
|
32
37
|
rememberSessionRoute: (sessionKey: string, accountId: string, route: any) => void;
|
|
33
38
|
enqueueFromReply: (args: {
|
|
34
39
|
accountId: string;
|
|
@@ -39,7 +44,16 @@ export async function handleBncrNativeCommand(params: {
|
|
|
39
44
|
}) => Promise<void>;
|
|
40
45
|
logger?: { warn?: (msg: string) => void; error?: (msg: string) => void };
|
|
41
46
|
}): Promise<{ handled: false } | { handled: true; command: string; sessionKey: string }> {
|
|
42
|
-
const {
|
|
47
|
+
const {
|
|
48
|
+
api,
|
|
49
|
+
channelId,
|
|
50
|
+
cfg,
|
|
51
|
+
parsed,
|
|
52
|
+
canonicalAgentId,
|
|
53
|
+
rememberSessionRoute,
|
|
54
|
+
enqueueFromReply,
|
|
55
|
+
logger,
|
|
56
|
+
} = params;
|
|
43
57
|
const { accountId, route, peer, sessionKeyfromroute, clientId, extracted, msgId } = parsed;
|
|
44
58
|
const command = parseBncrNativeCommand(extracted.text);
|
|
45
59
|
if (!command) return { handled: false };
|
|
@@ -51,11 +65,14 @@ export async function handleBncrNativeCommand(params: {
|
|
|
51
65
|
peer,
|
|
52
66
|
});
|
|
53
67
|
|
|
54
|
-
const baseSessionKey =
|
|
68
|
+
const baseSessionKey =
|
|
69
|
+
normalizeInboundSessionKey(sessionKeyfromroute, route, canonicalAgentId) ||
|
|
70
|
+
resolvedRoute.sessionKey;
|
|
55
71
|
const taskSessionKey = withTaskSessionKey(baseSessionKey, extracted.taskKey);
|
|
56
72
|
const sessionKey = taskSessionKey || baseSessionKey;
|
|
57
73
|
rememberSessionRoute(baseSessionKey, accountId, route);
|
|
58
|
-
if (taskSessionKey && taskSessionKey !== baseSessionKey)
|
|
74
|
+
if (taskSessionKey && taskSessionKey !== baseSessionKey)
|
|
75
|
+
rememberSessionRoute(taskSessionKey, accountId, route);
|
|
59
76
|
|
|
60
77
|
const displayTo = formatDisplayScope(route);
|
|
61
78
|
const body = command.body;
|
|
@@ -113,10 +130,17 @@ export async function handleBncrNativeCommand(params: {
|
|
|
113
130
|
disableBlockStreaming: true,
|
|
114
131
|
},
|
|
115
132
|
dispatcherOptions: {
|
|
116
|
-
deliver: async (
|
|
133
|
+
deliver: async (
|
|
134
|
+
payload: { text?: string; mediaUrl?: string; mediaUrls?: string[]; audioAsVoice?: boolean },
|
|
135
|
+
info?: { kind?: 'tool' | 'block' | 'final' },
|
|
136
|
+
) => {
|
|
117
137
|
const kind = info?.kind;
|
|
118
138
|
if (kind && kind !== 'final') return;
|
|
119
|
-
const hasPayload = Boolean(
|
|
139
|
+
const hasPayload = Boolean(
|
|
140
|
+
payload?.text ||
|
|
141
|
+
payload?.mediaUrl ||
|
|
142
|
+
(Array.isArray(payload?.mediaUrls) && payload.mediaUrls.length > 0),
|
|
143
|
+
);
|
|
120
144
|
if (!hasPayload) return;
|
|
121
145
|
responded = true;
|
|
122
146
|
await enqueueFromReply({
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
formatDisplayScope,
|
|
4
|
+
normalizeInboundSessionKey,
|
|
5
|
+
withTaskSessionKey,
|
|
6
|
+
} from '../../core/targets.ts';
|
|
3
7
|
import { handleBncrNativeCommand } from './commands.ts';
|
|
4
8
|
|
|
5
9
|
type ParsedInbound = ReturnType<typeof import('./parse.ts')['parseBncrInboundParams']>;
|
|
@@ -9,6 +13,7 @@ export async function dispatchBncrInbound(params: {
|
|
|
9
13
|
channelId: string;
|
|
10
14
|
cfg: any;
|
|
11
15
|
parsed: ParsedInbound;
|
|
16
|
+
canonicalAgentId: string;
|
|
12
17
|
rememberSessionRoute: (sessionKey: string, accountId: string, route: any) => void;
|
|
13
18
|
enqueueFromReply: (args: {
|
|
14
19
|
accountId: string;
|
|
@@ -21,14 +26,43 @@ export async function dispatchBncrInbound(params: {
|
|
|
21
26
|
scheduleSave: () => void;
|
|
22
27
|
logger?: { warn?: (msg: string) => void; error?: (msg: string) => void };
|
|
23
28
|
}) {
|
|
24
|
-
const {
|
|
25
|
-
|
|
29
|
+
const {
|
|
30
|
+
api,
|
|
31
|
+
channelId,
|
|
32
|
+
cfg,
|
|
33
|
+
parsed,
|
|
34
|
+
canonicalAgentId,
|
|
35
|
+
rememberSessionRoute,
|
|
36
|
+
enqueueFromReply,
|
|
37
|
+
setInboundActivity,
|
|
38
|
+
scheduleSave,
|
|
39
|
+
logger,
|
|
40
|
+
} = params;
|
|
41
|
+
const {
|
|
42
|
+
accountId,
|
|
43
|
+
route,
|
|
44
|
+
peer,
|
|
45
|
+
sessionKeyfromroute,
|
|
46
|
+
clientId,
|
|
47
|
+
text,
|
|
48
|
+
msgType,
|
|
49
|
+
mediaBase64,
|
|
50
|
+
mediaPathFromTransfer,
|
|
51
|
+
mimeType,
|
|
52
|
+
fileName,
|
|
53
|
+
msgId,
|
|
54
|
+
extracted,
|
|
55
|
+
platform,
|
|
56
|
+
groupId,
|
|
57
|
+
userId,
|
|
58
|
+
} = parsed;
|
|
26
59
|
|
|
27
60
|
const nativeCommand = await handleBncrNativeCommand({
|
|
28
61
|
api,
|
|
29
62
|
channelId,
|
|
30
63
|
cfg,
|
|
31
64
|
parsed,
|
|
65
|
+
canonicalAgentId,
|
|
32
66
|
rememberSessionRoute,
|
|
33
67
|
enqueueFromReply,
|
|
34
68
|
logger,
|
|
@@ -52,7 +86,9 @@ export async function dispatchBncrInbound(params: {
|
|
|
52
86
|
peer,
|
|
53
87
|
});
|
|
54
88
|
|
|
55
|
-
const baseSessionKey =
|
|
89
|
+
const baseSessionKey =
|
|
90
|
+
normalizeInboundSessionKey(sessionKeyfromroute, route, canonicalAgentId) ||
|
|
91
|
+
resolvedRoute.sessionKey;
|
|
56
92
|
const agentText = extracted.text;
|
|
57
93
|
const taskSessionKey = withTaskSessionKey(baseSessionKey, extracted.taskKey);
|
|
58
94
|
const sessionKey = taskSessionKey || baseSessionKey;
|
|
@@ -2,9 +2,7 @@ import { normalizeAccountId } from '../../core/accounts.ts';
|
|
|
2
2
|
import { resolveBncrChannelPolicy } from '../../core/policy.ts';
|
|
3
3
|
import { buildDisplayScopeCandidates } from '../../core/targets.ts';
|
|
4
4
|
|
|
5
|
-
export type BncrGateResult =
|
|
6
|
-
| { allowed: true }
|
|
7
|
-
| { allowed: false; reason: string };
|
|
5
|
+
export type BncrGateResult = { allowed: true } | { allowed: false; reason: string };
|
|
8
6
|
|
|
9
7
|
function asString(v: unknown, fallback = ''): string {
|
|
10
8
|
if (typeof v === 'string') return v;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { normalizeAccountId } from '../../core/accounts.ts';
|
|
3
|
-
import type { BncrRoute } from '../../core/types.ts';
|
|
4
3
|
import { extractInlineTaskKey } from '../../core/targets.ts';
|
|
4
|
+
import type { BncrRoute } from '../../core/types.ts';
|
|
5
5
|
|
|
6
6
|
function asString(v: unknown, fallback = ''): string {
|
|
7
7
|
if (typeof v === 'string') return v;
|
|
@@ -28,7 +28,10 @@ export function inboundDedupKey(params: {
|
|
|
28
28
|
|
|
29
29
|
const text = asString(params.text || '').trim();
|
|
30
30
|
const media = asString(params.mediaBase64 || '');
|
|
31
|
-
const digest = createHash('sha1')
|
|
31
|
+
const digest = createHash('sha1')
|
|
32
|
+
.update(`${text}\n${media.slice(0, 256)}`)
|
|
33
|
+
.digest('hex')
|
|
34
|
+
.slice(0, 16);
|
|
32
35
|
return `${accountId}|${platform}|${groupId}|${userId}|hash:${digest}`;
|
|
33
36
|
}
|
|
34
37
|
|
|
@@ -9,21 +9,34 @@ function isAudioMimeType(mimeType?: string): boolean {
|
|
|
9
9
|
return mt.startsWith('audio/');
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
export function resolveBncrOutboundMessageType(params: {
|
|
12
|
+
export function resolveBncrOutboundMessageType(params: {
|
|
13
|
+
mimeType?: string;
|
|
14
|
+
fileName?: string;
|
|
15
|
+
hintedType?: string;
|
|
16
|
+
hasPayload?: boolean;
|
|
17
|
+
}): 'text' | 'image' | 'video' | 'voice' | 'audio' | 'file' {
|
|
13
18
|
const hinted = asString(params.hintedType || '').toLowerCase();
|
|
14
19
|
const hasPayload = !!params.hasPayload;
|
|
15
20
|
const mt = asString(params.mimeType || '').toLowerCase();
|
|
16
21
|
const major = mt.split('/')[0] || '';
|
|
17
|
-
const isStandard =
|
|
22
|
+
const isStandard =
|
|
23
|
+
hinted === 'text' ||
|
|
24
|
+
hinted === 'image' ||
|
|
25
|
+
hinted === 'video' ||
|
|
26
|
+
hinted === 'voice' ||
|
|
27
|
+
hinted === 'audio' ||
|
|
28
|
+
hinted === 'file';
|
|
18
29
|
|
|
19
30
|
if (hasPayload && major === 'text' && (hinted === 'text' || !isStandard)) return 'file';
|
|
20
31
|
if (hinted === 'voice') {
|
|
21
32
|
if (isAudioMimeType(mt)) return 'voice';
|
|
22
|
-
if (major === 'text' || major === 'image' || major === 'video' || major === 'audio')
|
|
33
|
+
if (major === 'text' || major === 'image' || major === 'video' || major === 'audio')
|
|
34
|
+
return major as any;
|
|
23
35
|
return 'file';
|
|
24
36
|
}
|
|
25
37
|
if (isStandard) return hinted as any;
|
|
26
|
-
if (major === 'text' || major === 'image' || major === 'video' || major === 'audio')
|
|
38
|
+
if (major === 'text' || major === 'image' || major === 'video' || major === 'audio')
|
|
39
|
+
return major as any;
|
|
27
40
|
return 'file';
|
|
28
41
|
}
|
|
29
42
|
|
|
@@ -31,7 +44,13 @@ export function buildBncrMediaOutboundFrame(params: {
|
|
|
31
44
|
messageId: string;
|
|
32
45
|
sessionKey: string;
|
|
33
46
|
route: { platform: string; groupId: string; userId: string };
|
|
34
|
-
media: {
|
|
47
|
+
media: {
|
|
48
|
+
mode: 'base64' | 'chunk';
|
|
49
|
+
mimeType?: string;
|
|
50
|
+
fileName?: string;
|
|
51
|
+
mediaBase64?: string;
|
|
52
|
+
path?: string;
|
|
53
|
+
};
|
|
35
54
|
mediaUrl: string;
|
|
36
55
|
mediaMsg: string;
|
|
37
56
|
fileName: string;
|
|
@@ -4,7 +4,10 @@ export async function sendBncrText(params: {
|
|
|
4
4
|
to: string;
|
|
5
5
|
text: string;
|
|
6
6
|
mediaLocalRoots?: readonly string[];
|
|
7
|
-
resolveVerifiedTarget: (
|
|
7
|
+
resolveVerifiedTarget: (
|
|
8
|
+
to: string,
|
|
9
|
+
accountId: string,
|
|
10
|
+
) => { sessionKey: string; route: any; displayScope: string };
|
|
8
11
|
rememberSessionRoute: (sessionKey: string, accountId: string, route: any) => void;
|
|
9
12
|
enqueueFromReply: (args: {
|
|
10
13
|
accountId: string;
|
|
@@ -28,7 +31,11 @@ export async function sendBncrText(params: {
|
|
|
28
31
|
mediaLocalRoots: params.mediaLocalRoots,
|
|
29
32
|
});
|
|
30
33
|
|
|
31
|
-
return {
|
|
34
|
+
return {
|
|
35
|
+
channel: params.channelId,
|
|
36
|
+
messageId: params.createMessageId(),
|
|
37
|
+
chatId: verified.sessionKey,
|
|
38
|
+
};
|
|
32
39
|
}
|
|
33
40
|
|
|
34
41
|
export async function sendBncrMedia(params: {
|
|
@@ -40,13 +47,22 @@ export async function sendBncrMedia(params: {
|
|
|
40
47
|
asVoice?: boolean;
|
|
41
48
|
audioAsVoice?: boolean;
|
|
42
49
|
mediaLocalRoots?: readonly string[];
|
|
43
|
-
resolveVerifiedTarget: (
|
|
50
|
+
resolveVerifiedTarget: (
|
|
51
|
+
to: string,
|
|
52
|
+
accountId: string,
|
|
53
|
+
) => { sessionKey: string; route: any; displayScope: string };
|
|
44
54
|
rememberSessionRoute: (sessionKey: string, accountId: string, route: any) => void;
|
|
45
55
|
enqueueFromReply: (args: {
|
|
46
56
|
accountId: string;
|
|
47
57
|
sessionKey: string;
|
|
48
58
|
route: any;
|
|
49
|
-
payload: {
|
|
59
|
+
payload: {
|
|
60
|
+
text?: string;
|
|
61
|
+
mediaUrl?: string;
|
|
62
|
+
mediaUrls?: string[];
|
|
63
|
+
asVoice?: boolean;
|
|
64
|
+
audioAsVoice?: boolean;
|
|
65
|
+
};
|
|
50
66
|
mediaLocalRoots?: readonly string[];
|
|
51
67
|
}) => Promise<void>;
|
|
52
68
|
createMessageId: () => string;
|
|
@@ -67,5 +83,9 @@ export async function sendBncrMedia(params: {
|
|
|
67
83
|
mediaLocalRoots: params.mediaLocalRoots,
|
|
68
84
|
});
|
|
69
85
|
|
|
70
|
-
return {
|
|
86
|
+
return {
|
|
87
|
+
channel: params.channelId,
|
|
88
|
+
messageId: params.createMessageId(),
|
|
89
|
+
chatId: verified.sessionKey,
|
|
90
|
+
};
|
|
71
91
|
}
|