@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.
@@ -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 (!raw || raw === accountId || /^bncr$/i.test(raw) || /^status$/i.test(raw) || /^runtime$/i.test(raw)) {
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;
@@ -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) ? allowFrom.bncr.map((x: unknown) => asString(x).trim()).filter(Boolean) : [];
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)) level = 'error';
27
+ if (!params.connected && (params.deadLetter > 0 || params.invalidOutboxSessionKeys > 0))
28
+ level = 'error';
28
29
 
29
30
  return {
30
31
  ok: level === 'ok',
@@ -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) ? input.pendingAdmissions.length : 0,
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) ? input.pendingAdmissions.length : 0,
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 ? `${item.route.platform}:${item.route.groupId}:${item.route.userId}` : null,
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
  : [],
@@ -1,6 +1,6 @@
1
1
  import type { BncrRoute } from './types.ts';
2
2
 
3
- const BNCR_SESSION_KEY_PREFIX = 'agent:main:bncr:direct:';
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 parseLegacySessionKeyToStrict(input: string): string | null {
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:main:bncr:direct:([0-9a-fA-F]+):0$/);
92
- if (directLegacy?.[1]) {
93
- const route = parseRouteFromHexScope(directLegacy[1].toLowerCase());
94
- if (route) return buildFallbackSessionKey(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) return buildFallbackSessionKey(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:main:bncr:([0-9a-fA-F]+):0$/);
104
- if (agentLegacy?.[1]) {
105
- const route = parseRouteFromHexScope(agentLegacy[1].toLowerCase());
106
- if (route) return buildFallbackSessionKey(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) return buildFallbackSessionKey(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): { sessionKey: string; scopeHex: string; route: BncrRoute } | null {
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:main:bncr:(direct|group):(.+)$/);
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 payload = asString(m[2]).trim();
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(payload);
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
- sessionKey: `${BNCR_SESSION_KEY_PREFIX}${scopeHex}`,
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.replace(/[^a-z0-9_-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 32);
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(input: string): { sessionKey: string; route: BncrRoute } | null {
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
- if (isLegacyNoiseRoute(strict.route)) return null;
178
- return {
179
- sessionKey: taskKey ? `${strict.sessionKey}:task:${taskKey}` : strict.sessionKey,
180
- route: strict.route,
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
- const migrated = parseLegacySessionKeyToStrict(base);
185
- if (!migrated) return null;
253
+ if (!route) return null;
254
+ if (isLegacyNoiseRoute(route)) return null;
186
255
 
187
- const parsed = parseStrictBncrSessionKey(migrated);
188
- if (!parsed) return null;
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 ? `${parsed.sessionKey}:task:${taskKey}` : parsed.sessionKey,
193
- route: parsed.route,
261
+ sessionKey: taskKey ? `${finalSessionKey}:task:${taskKey}` : finalSessionKey,
262
+ route,
194
263
  };
195
264
  }
196
265
 
197
- export function normalizeInboundSessionKey(scope: string, route: BncrRoute): string | null {
266
+ export function normalizeInboundSessionKey(
267
+ scope: string,
268
+ route: BncrRoute,
269
+ canonicalAgentId: string,
270
+ ): string | null {
198
271
  const raw = asString(scope).trim();
199
- if (!raw) return buildFallbackSessionKey(route);
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
- const parsed = parseStrictBncrSessionKey(raw);
202
- if (!parsed) return null;
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(/^\s*(?:#task|\/task)\s*[:=]\s*([a-zA-Z0-9_-]{1,32})\s*\n?\s*([\s\S]*)$/i);
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 `${BNCR_SESSION_KEY_PREFIX}${routeScopeToHex(route)}`;
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 { formatDisplayScope, normalizeInboundSessionKey, withTaskSessionKey } from '../../core/targets.ts';
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] || '').trim().toLowerCase();
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 { api, channelId, cfg, parsed, rememberSessionRoute, enqueueFromReply, logger } = params;
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 = normalizeInboundSessionKey(sessionKeyfromroute, route) || resolvedRoute.sessionKey;
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) rememberSessionRoute(taskSessionKey, accountId, route);
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 (payload: { text?: string; mediaUrl?: string; mediaUrls?: string[]; audioAsVoice?: boolean }, info?: { kind?: 'tool' | 'block' | 'final' }) => {
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(payload?.text || payload?.mediaUrl || (Array.isArray(payload?.mediaUrls) && payload.mediaUrls.length > 0));
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 { formatDisplayScope, normalizeInboundSessionKey, withTaskSessionKey } from '../../core/targets.ts';
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 { api, channelId, cfg, parsed, rememberSessionRoute, enqueueFromReply, setInboundActivity, scheduleSave, logger } = params;
25
- const { accountId, route, peer, sessionKeyfromroute, clientId, text, msgType, mediaBase64, mediaPathFromTransfer, mimeType, fileName, msgId, extracted, platform, groupId, userId } = parsed;
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 = normalizeInboundSessionKey(sessionKeyfromroute, route) || resolvedRoute.sessionKey;
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').update(`${text}\n${media.slice(0, 256)}`).digest('hex').slice(0, 16);
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: { mimeType?: string; fileName?: string; hintedType?: string; hasPayload?: boolean }): 'text' | 'image' | 'video' | 'voice' | 'audio' | 'file' {
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 = hinted === 'text' || hinted === 'image' || hinted === 'video' || hinted === 'voice' || hinted === 'audio' || hinted === 'file';
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') return major as any;
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') return major as any;
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: { mode: 'base64' | 'chunk'; mimeType?: string; fileName?: string; mediaBase64?: string; path?: string };
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: (to: string, accountId: string) => { sessionKey: string; route: any; displayScope: string };
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 { channel: params.channelId, messageId: params.createMessageId(), chatId: verified.sessionKey };
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: (to: string, accountId: string) => { sessionKey: string; route: any; displayScope: string };
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: { text?: string; mediaUrl?: string; mediaUrls?: string[]; asVoice?: boolean; audioAsVoice?: boolean };
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 { channel: params.channelId, messageId: params.createMessageId(), chatId: verified.sessionKey };
86
+ return {
87
+ channel: params.channelId,
88
+ messageId: params.createMessageId(),
89
+ chatId: verified.sessionKey,
90
+ };
71
91
  }