@xmoxmo/bncr 0.5.6 → 0.5.8
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 +1 -9
- package/index.ts +1 -9
- package/package.json +1 -1
- package/scripts/check-pack.mjs +0 -8
- package/scripts/selfcheck.mjs +0 -10
- package/src/bootstrap/runtime-loader.ts +1 -1
- package/src/channel.ts +118 -171
- package/src/core/accounts.ts +1 -6
- package/src/core/connection-reachability.ts +2 -6
- package/src/core/dead-letter-diagnostics.ts +4 -10
- package/src/core/logging.ts +1 -1
- package/src/core/outbox-entry-builders.ts +78 -84
- package/src/core/outbox-file-transfer-failure.ts +5 -0
- package/src/core/outbox-file-transfer-guards.ts +6 -0
- package/src/core/outbox-file-transfer-prep.ts +8 -5
- package/src/core/outbox-file-transfer-success.ts +5 -0
- package/src/core/outbox-queue.ts +1 -7
- package/src/core/outbox-summary.ts +10 -56
- package/src/core/permissions.ts +2 -10
- package/src/core/persisted-outbox-entry.ts +54 -11
- package/src/core/policy.ts +1 -11
- package/src/core/register-trace.ts +4 -7
- package/src/core/status-meta.ts +1 -9
- package/src/core/status.ts +1 -21
- package/src/core/targets.ts +52 -70
- package/src/core/types.ts +1 -17
- package/src/core/value-sanitize.ts +38 -1
- package/src/messaging/inbound/commands.ts +2 -24
- package/src/messaging/inbound/dispatch-prep.ts +1 -1
- package/src/messaging/inbound/gate.ts +2 -31
- package/src/messaging/inbound/group-history.ts +0 -75
- package/src/messaging/inbound/parse.ts +2 -32
- package/src/messaging/inbound/scene-admin.ts +8 -8
- package/src/messaging/outbound/diagnostics-debug-builders.ts +0 -15
- package/src/messaging/outbound/diagnostics.ts +1 -7
- package/src/messaging/outbound/durable-queue-adapter.ts +17 -11
- package/src/messaging/outbound/marker-parser.ts +277 -28
- package/src/messaging/outbound/media-dedupe.ts +3 -10
- package/src/messaging/outbound/media.ts +1 -6
- package/src/messaging/outbound/normalize-outbound-send.ts +329 -0
- package/src/messaging/outbound/queue-selectors.ts +1 -5
- package/src/messaging/outbound/reply-enqueue-media.ts +1 -184
- package/src/messaging/outbound/reply-enqueue.ts +313 -175
- package/src/messaging/outbound/session-route.ts +6 -11
- package/src/messaging/outbound/target-resolver.ts +1 -6
- package/src/openclaw/media-runtime.ts +1 -1
- package/src/openclaw/routing-runtime.ts +0 -11
- package/src/openclaw/sdk-helpers.ts +0 -2
- package/src/plugin/bridge-media-facade.ts +0 -4
- package/src/plugin/bridge-outbox-facade.ts +0 -1
- package/src/plugin/channel-components.ts +0 -13
- package/src/plugin/channel-plugin-surface-group.ts +52 -65
- package/src/plugin/channel-runtime-builders-delivery.ts +3 -362
- package/src/plugin/channel-runtime-builders-status.ts +0 -160
- package/src/plugin/channel-runtime-builders.ts +3 -17
- package/src/plugin/channel-runtime-constants.ts +1 -1
- package/src/plugin/channel-runtime-types.ts +2 -24
- package/src/plugin/channel-send.ts +206 -221
- package/src/plugin/channel-utils.ts +8 -29
- package/src/plugin/connection-handlers-helpers.ts +0 -15
- package/src/plugin/connection-handlers.ts +6 -5
- package/src/plugin/file-inbound-abort.ts +5 -0
- package/src/plugin/file-inbound-chunk.ts +5 -0
- package/src/plugin/file-inbound-complete.ts +5 -0
- package/src/plugin/file-transfer-orchestrator.ts +39 -2
- package/src/plugin/media-dedupe-runtime.ts +1 -6
- package/src/plugin/media-orchestrators-runtime-group.ts +20 -149
- package/src/plugin/message-send.ts +7 -8
- package/src/plugin/messaging.ts +3 -3
- package/src/plugin/outbound.ts +6 -26
- package/src/plugin/outbox-drain-loop.ts +2 -2
- package/src/plugin/outbox-push.ts +1 -52
- package/src/plugin/outbox-unified-push-flow.ts +307 -0
- package/src/plugin/scene-registry.ts +1 -1
- package/src/plugin/target-runtime.ts +1 -6
- package/src/runtime/log-dedupe.ts +3 -3
- package/src/runtime/outbound-ack-timeout.ts +3 -12
- package/src/runtime/outbox-transitions.ts +1 -4
- package/src/runtime/status-snapshots.ts +2 -2
- package/src/runtime/status-worker.ts +3 -15
- package/src/core/outbox-text-push-prep.ts +0 -36
- package/src/messaging/outbound/build-send-action.ts +0 -130
- package/src/messaging/outbound/durable-message-adapter.ts +0 -121
- package/src/messaging/outbound/send-params.ts +0 -114
- package/src/messaging/outbound/send.ts +0 -127
- package/src/openclaw/ingress-runtime.ts +0 -25
- package/src/plugin/bridge-file-transfer-push-facade.ts +0 -142
- package/src/plugin/channel-send-runtime-group.ts +0 -37
- package/src/plugin/outbox-file-push-flow.ts +0 -69
- package/src/plugin/outbox-text-push-flow.ts +0 -90
package/src/core/targets.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { BncrRoute } from './types.ts';
|
|
2
|
+
import { asString } from './value-sanitize.ts';
|
|
2
3
|
|
|
3
4
|
type RouteInputLike = {
|
|
4
5
|
platform?: unknown;
|
|
@@ -28,14 +29,8 @@ export type BncrExplicitTarget = {
|
|
|
28
29
|
groupId?: string;
|
|
29
30
|
};
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (value == null) return fallback;
|
|
34
|
-
return String(value);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function parseRouteFromStandardDisplayScope(scope: string): BncrRoute | null {
|
|
38
|
-
const parts = asTargetString(scope).trim().split(':');
|
|
32
|
+
function parseRouteFromStandardDisplayScope(scope: string): BncrRoute | null {
|
|
33
|
+
const parts = asString(scope).trim().split(':');
|
|
39
34
|
if (parts.length === 3) {
|
|
40
35
|
const [platform, kind, id] = parts;
|
|
41
36
|
if (!platform || !kind || !id) return null;
|
|
@@ -65,7 +60,7 @@ export function parseRouteFromStandardDisplayScope(scope: string): BncrRoute | n
|
|
|
65
60
|
}
|
|
66
61
|
|
|
67
62
|
function parseGroupScope(scope: string): { platform: string; groupId: string } | null {
|
|
68
|
-
const parts =
|
|
63
|
+
const parts = asString(scope).trim().split(':');
|
|
69
64
|
if (parts.length !== 2) return null;
|
|
70
65
|
const [platform, groupId] = parts;
|
|
71
66
|
if (!platform || !groupId) return null;
|
|
@@ -73,15 +68,15 @@ function parseGroupScope(scope: string): { platform: string; groupId: string } |
|
|
|
73
68
|
}
|
|
74
69
|
|
|
75
70
|
function parseDirectScope(scope: string): { platform: string; userId: string } | null {
|
|
76
|
-
const parts =
|
|
71
|
+
const parts = asString(scope).trim().split(':');
|
|
77
72
|
if (parts.length !== 2) return null;
|
|
78
73
|
const [platform, userId] = parts;
|
|
79
74
|
if (!platform || !userId) return null;
|
|
80
75
|
return { platform, userId };
|
|
81
76
|
}
|
|
82
77
|
|
|
83
|
-
|
|
84
|
-
const raw =
|
|
78
|
+
function normalizeDisplayScopePrefix(scope: string): string {
|
|
79
|
+
const raw = asString(scope).trim();
|
|
85
80
|
if (!raw) return '';
|
|
86
81
|
if (raw.startsWith('Bncr:')) return raw;
|
|
87
82
|
if (/^bncr[:-]/i.test(raw)) return raw;
|
|
@@ -89,11 +84,7 @@ export function normalizeDisplayScopePrefix(scope: string): string {
|
|
|
89
84
|
return `Bncr:${raw}`;
|
|
90
85
|
}
|
|
91
86
|
|
|
92
|
-
|
|
93
|
-
return 'direct';
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export function buildExplicitTargetResult(args: {
|
|
87
|
+
function buildExplicitTargetResult(args: {
|
|
97
88
|
raw: string;
|
|
98
89
|
source:
|
|
99
90
|
| 'display-scope'
|
|
@@ -121,8 +112,8 @@ export function buildExplicitTargetResult(args: {
|
|
|
121
112
|
};
|
|
122
113
|
}
|
|
123
114
|
|
|
124
|
-
|
|
125
|
-
const raw =
|
|
115
|
+
function isLowerHex(input: string): boolean {
|
|
116
|
+
const raw = asString(input).trim();
|
|
126
117
|
return !!raw && /^[0-9a-fA-F]+$/.test(raw) && raw.length % 2 === 0;
|
|
127
118
|
}
|
|
128
119
|
|
|
@@ -131,18 +122,18 @@ export function routeScopeToHex(route: BncrRoute): string {
|
|
|
131
122
|
return Buffer.from(raw, 'utf8').toString('hex').toLowerCase();
|
|
132
123
|
}
|
|
133
124
|
|
|
134
|
-
|
|
125
|
+
function groupScopeToHex(route: BncrRoute): string {
|
|
135
126
|
const raw = `${route.platform}:${route.groupId}`;
|
|
136
127
|
return Buffer.from(raw, 'utf8').toString('hex').toLowerCase();
|
|
137
128
|
}
|
|
138
129
|
|
|
139
|
-
|
|
130
|
+
function directScopeToHex(route: BncrRoute): string {
|
|
140
131
|
const raw = `${route.platform}:${route.userId}`;
|
|
141
132
|
return Buffer.from(raw, 'utf8').toString('hex').toLowerCase();
|
|
142
133
|
}
|
|
143
134
|
|
|
144
|
-
|
|
145
|
-
const parts =
|
|
135
|
+
function parseRouteFromScope(scope: string): BncrRoute | null {
|
|
136
|
+
const parts = asString(scope).trim().split(':');
|
|
146
137
|
if (parts.length < 3) return null;
|
|
147
138
|
const [platform, groupId, userId] = parts;
|
|
148
139
|
if (!platform || !groupId || !userId) return null;
|
|
@@ -150,8 +141,8 @@ export function parseRouteFromScope(scope: string): BncrRoute | null {
|
|
|
150
141
|
return { platform, groupId, userId };
|
|
151
142
|
}
|
|
152
143
|
|
|
153
|
-
|
|
154
|
-
const rawHex =
|
|
144
|
+
function parseRouteFromHexScope(scopeHex: string): BncrRoute | null {
|
|
145
|
+
const rawHex = asString(scopeHex).trim();
|
|
155
146
|
if (!isLowerHex(rawHex)) return null;
|
|
156
147
|
|
|
157
148
|
try {
|
|
@@ -162,8 +153,8 @@ export function parseRouteFromHexScope(scopeHex: string): BncrRoute | null {
|
|
|
162
153
|
}
|
|
163
154
|
}
|
|
164
155
|
|
|
165
|
-
|
|
166
|
-
const rawHex =
|
|
156
|
+
function parseGroupRouteFromHexScope(scopeHex: string): BncrRoute | null {
|
|
157
|
+
const rawHex = asString(scopeHex).trim();
|
|
167
158
|
if (!isLowerHex(rawHex)) return null;
|
|
168
159
|
|
|
169
160
|
try {
|
|
@@ -176,8 +167,8 @@ export function parseGroupRouteFromHexScope(scopeHex: string): BncrRoute | null
|
|
|
176
167
|
}
|
|
177
168
|
}
|
|
178
169
|
|
|
179
|
-
|
|
180
|
-
const rawHex =
|
|
170
|
+
function parseDirectRouteFromHexScope(scopeHex: string): BncrRoute | null {
|
|
171
|
+
const rawHex = asString(scopeHex).trim();
|
|
181
172
|
if (!isLowerHex(rawHex)) return null;
|
|
182
173
|
|
|
183
174
|
try {
|
|
@@ -197,15 +188,15 @@ export function parseStrictBncrSessionKey(input: string): {
|
|
|
197
188
|
scopeHex: string;
|
|
198
189
|
route: BncrRoute;
|
|
199
190
|
} | null {
|
|
200
|
-
const raw =
|
|
191
|
+
const raw = asString(input).trim();
|
|
201
192
|
if (!raw) return null;
|
|
202
193
|
|
|
203
194
|
const m = raw.match(/^agent:([^:]+):bncr:(direct|group):(.+)$/);
|
|
204
195
|
if (!m?.[1] || !m?.[2] || !m?.[3]) return null;
|
|
205
196
|
|
|
206
|
-
const inputAgentId =
|
|
197
|
+
const inputAgentId = asString(m[1]).trim();
|
|
207
198
|
const inputKind = m[2] as BncrSessionKind;
|
|
208
|
-
const payload =
|
|
199
|
+
const payload = asString(m[3]).trim();
|
|
209
200
|
let route: BncrRoute | null = null;
|
|
210
201
|
let scopeHex = '';
|
|
211
202
|
|
|
@@ -243,13 +234,13 @@ export function parseStrictBncrSessionKey(input: string): {
|
|
|
243
234
|
};
|
|
244
235
|
}
|
|
245
236
|
|
|
246
|
-
|
|
237
|
+
function parseLegacySessionKey(input: string): {
|
|
247
238
|
route: BncrRoute;
|
|
248
239
|
inputKind: BncrSessionKind;
|
|
249
240
|
inputAgentId?: string;
|
|
250
241
|
source: 'legacy-direct' | 'legacy-bncr' | 'legacy-agent' | 'hex';
|
|
251
242
|
} | null {
|
|
252
|
-
const raw =
|
|
243
|
+
const raw = asString(input).trim();
|
|
253
244
|
if (!raw) return null;
|
|
254
245
|
|
|
255
246
|
const directLegacy = raw.match(/^agent:([^:]+):bncr:direct:([0-9a-fA-F]+):0$/);
|
|
@@ -304,10 +295,10 @@ export function parseLegacySessionKey(input: string): {
|
|
|
304
295
|
return null;
|
|
305
296
|
}
|
|
306
297
|
|
|
307
|
-
|
|
308
|
-
const platform =
|
|
309
|
-
const groupId =
|
|
310
|
-
const userId =
|
|
298
|
+
function isLegacyNoiseRoute(route: BncrRoute): boolean {
|
|
299
|
+
const platform = asString(route.platform).trim().toLowerCase();
|
|
300
|
+
const groupId = asString(route.groupId).trim().toLowerCase();
|
|
301
|
+
const userId = asString(route.userId).trim().toLowerCase();
|
|
311
302
|
|
|
312
303
|
if (platform === 'agent' && groupId === 'main' && userId === 'bncr') return true;
|
|
313
304
|
if (platform === 'bncr' && userId === '0' && isLowerHex(groupId)) return true;
|
|
@@ -326,7 +317,7 @@ export function parseRouteFromDisplayScope(scope: string): BncrRoute | null {
|
|
|
326
317
|
}
|
|
327
318
|
|
|
328
319
|
export function buildCanonicalBncrSessionKey(route: BncrRoute, canonicalAgentId: string): string {
|
|
329
|
-
const agentId =
|
|
320
|
+
const agentId = asString(canonicalAgentId).trim() || 'main';
|
|
330
321
|
if (route.groupId !== '0') {
|
|
331
322
|
return `agent:${agentId}:bncr:group:${groupScopeToHex(route)}`;
|
|
332
323
|
}
|
|
@@ -338,7 +329,7 @@ export function normalizeStoredSessionKey(
|
|
|
338
329
|
canonicalAgentId?: string | null,
|
|
339
330
|
helpers?: { normalizeTaskKey?: (input: unknown) => string | null },
|
|
340
331
|
): { sessionKey: string; route: BncrRoute } | null {
|
|
341
|
-
const raw =
|
|
332
|
+
const raw = asString(input).trim();
|
|
342
333
|
if (!raw) return null;
|
|
343
334
|
|
|
344
335
|
let taskKey: string | null = null;
|
|
@@ -346,7 +337,7 @@ export function normalizeStoredSessionKey(
|
|
|
346
337
|
|
|
347
338
|
const taskTagged = raw.match(/^(.*):task:([a-z0-9_-]{1,32})$/i);
|
|
348
339
|
if (taskTagged) {
|
|
349
|
-
base =
|
|
340
|
+
base = asString(taskTagged[1]).trim();
|
|
350
341
|
taskKey = helpers?.normalizeTaskKey?.(taskTagged[2]) || null;
|
|
351
342
|
}
|
|
352
343
|
|
|
@@ -370,7 +361,7 @@ export function normalizeStoredSessionKey(
|
|
|
370
361
|
if (!route) return null;
|
|
371
362
|
if (isLegacyNoiseRoute(route)) return null;
|
|
372
363
|
|
|
373
|
-
const finalAgentId =
|
|
364
|
+
const finalAgentId = asString(canonicalAgentId).trim() || passthroughAgentId;
|
|
374
365
|
if (!finalAgentId) return null;
|
|
375
366
|
|
|
376
367
|
const finalSessionKey = buildCanonicalBncrSessionKey(route, finalAgentId);
|
|
@@ -385,7 +376,7 @@ export function normalizeInboundSessionKey(
|
|
|
385
376
|
route: BncrRoute,
|
|
386
377
|
canonicalAgentId: string,
|
|
387
378
|
): string | null {
|
|
388
|
-
const raw =
|
|
379
|
+
const raw = asString(scope).trim();
|
|
389
380
|
let finalRoute: BncrRoute | null = null;
|
|
390
381
|
|
|
391
382
|
if (!raw) finalRoute = route;
|
|
@@ -399,15 +390,11 @@ export function normalizeInboundSessionKey(
|
|
|
399
390
|
return buildCanonicalBncrSessionKey(finalRoute, canonicalAgentId);
|
|
400
391
|
}
|
|
401
392
|
|
|
402
|
-
export function buildFallbackSessionKey(route: BncrRoute, canonicalAgentId: string): string {
|
|
403
|
-
return buildCanonicalBncrSessionKey(route, canonicalAgentId);
|
|
404
|
-
}
|
|
405
|
-
|
|
406
393
|
export function parseRouteLike(input: unknown): BncrRoute | null {
|
|
407
394
|
const routeInput = input && typeof input === 'object' ? (input as RouteInputLike) : null;
|
|
408
|
-
const platform =
|
|
409
|
-
const groupId =
|
|
410
|
-
const userId =
|
|
395
|
+
const platform = asString(routeInput?.platform || '').trim();
|
|
396
|
+
const groupId = asString(routeInput?.groupId || '').trim();
|
|
397
|
+
const userId = asString(routeInput?.userId || '').trim();
|
|
411
398
|
if (!platform || !groupId || !userId) return null;
|
|
412
399
|
if (groupId !== '0' && userId !== '0') return null;
|
|
413
400
|
return { platform, groupId, userId };
|
|
@@ -421,28 +408,23 @@ export type BncrExplicitTargetSource =
|
|
|
421
408
|
| 'route-scope';
|
|
422
409
|
|
|
423
410
|
export function formatDisplayScope(route: BncrRoute): string {
|
|
424
|
-
const platform =
|
|
425
|
-
const groupId =
|
|
426
|
-
const userId =
|
|
411
|
+
const platform = asString(route?.platform).trim();
|
|
412
|
+
const groupId = asString(route?.groupId || '0').trim() || '0';
|
|
413
|
+
const userId = asString(route?.userId || '0').trim() || '0';
|
|
427
414
|
if (groupId !== '0') return `Bncr:${platform}:${groupId}:0`;
|
|
428
415
|
return `Bncr:${platform}:0:${userId}`;
|
|
429
416
|
}
|
|
430
417
|
|
|
431
418
|
export function formatHumanDisplayScope(route: BncrRoute): string {
|
|
432
|
-
const platform =
|
|
433
|
-
const groupId =
|
|
434
|
-
const userId =
|
|
419
|
+
const platform = asString(route?.platform).trim();
|
|
420
|
+
const groupId = asString(route?.groupId || '0').trim() || '0';
|
|
421
|
+
const userId = asString(route?.userId || '0').trim() || '0';
|
|
435
422
|
if (groupId !== '0') return `Bncr:${platform}:Group:${groupId}`;
|
|
436
423
|
if (userId !== '0') return `Bncr:${platform}:User:${userId}`;
|
|
437
424
|
return formatDisplayScope(route);
|
|
438
425
|
}
|
|
439
426
|
|
|
440
|
-
|
|
441
|
-
const candidates = [formatDisplayScope(route)].filter(Boolean);
|
|
442
|
-
return Array.from(new Set(candidates.map((x) => asTargetString(x).trim()).filter(Boolean)));
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
export function resolveExplicitTargetRoute(raw: string): {
|
|
427
|
+
function resolveExplicitTargetRoute(raw: string): {
|
|
446
428
|
route: BncrRoute | null;
|
|
447
429
|
source: BncrExplicitTargetSource | null;
|
|
448
430
|
} {
|
|
@@ -491,10 +473,10 @@ export function resolveExplicitTargetRoute(raw: string): {
|
|
|
491
473
|
}
|
|
492
474
|
|
|
493
475
|
export function parseExplicitTarget(input: string, options?: { canonicalAgentId?: string | null }) {
|
|
494
|
-
const raw =
|
|
476
|
+
const raw = asString(input).trim();
|
|
495
477
|
if (!raw) return null;
|
|
496
478
|
|
|
497
|
-
const canonicalAgentId =
|
|
479
|
+
const canonicalAgentId = asString(options?.canonicalAgentId).trim() || undefined;
|
|
498
480
|
const { route, source } = resolveExplicitTargetRoute(raw);
|
|
499
481
|
if (!route || !source) return null;
|
|
500
482
|
|
|
@@ -510,8 +492,8 @@ export function parseExplicitTarget(input: string, options?: { canonicalAgentId?
|
|
|
510
492
|
});
|
|
511
493
|
}
|
|
512
494
|
|
|
513
|
-
|
|
514
|
-
const raw =
|
|
495
|
+
function normalizeTaskKey(input: unknown): string | null {
|
|
496
|
+
const raw = asString(input).trim().toLowerCase();
|
|
515
497
|
if (!raw) return null;
|
|
516
498
|
const normalized = raw
|
|
517
499
|
.replace(/[^a-z0-9_-]+/g, '-')
|
|
@@ -521,7 +503,7 @@ export function normalizeTaskKey(input: unknown): string | null {
|
|
|
521
503
|
}
|
|
522
504
|
|
|
523
505
|
export function extractInlineTaskKey(text: string): { taskKey: string | null; text: string } {
|
|
524
|
-
const raw =
|
|
506
|
+
const raw = asString(text);
|
|
525
507
|
if (!raw) return { taskKey: null, text: '' };
|
|
526
508
|
|
|
527
509
|
const tagged = raw.match(
|
|
@@ -530,7 +512,7 @@ export function extractInlineTaskKey(text: string): { taskKey: string | null; te
|
|
|
530
512
|
if (tagged) {
|
|
531
513
|
return {
|
|
532
514
|
taskKey: normalizeTaskKey(tagged[1]),
|
|
533
|
-
text:
|
|
515
|
+
text: asString(tagged[2]),
|
|
534
516
|
};
|
|
535
517
|
}
|
|
536
518
|
|
|
@@ -538,7 +520,7 @@ export function extractInlineTaskKey(text: string): { taskKey: string | null; te
|
|
|
538
520
|
if (spaced) {
|
|
539
521
|
return {
|
|
540
522
|
taskKey: normalizeTaskKey(spaced[1]),
|
|
541
|
-
text:
|
|
523
|
+
text: asString(spaced[2]),
|
|
542
524
|
};
|
|
543
525
|
}
|
|
544
526
|
|
|
@@ -546,7 +528,7 @@ export function extractInlineTaskKey(text: string): { taskKey: string | null; te
|
|
|
546
528
|
}
|
|
547
529
|
|
|
548
530
|
export function withTaskSessionKey(sessionKey: string, taskKey?: string | null): string {
|
|
549
|
-
const base =
|
|
531
|
+
const base = asString(sessionKey).trim();
|
|
550
532
|
const tk = normalizeTaskKey(taskKey);
|
|
551
533
|
if (!base || !tk) return base;
|
|
552
534
|
if (/:task:[a-z0-9_-]+(?:$|:)/i.test(base)) return base;
|
package/src/core/types.ts
CHANGED
|
@@ -21,8 +21,6 @@ export type BncrGatewayCapabilityFlags = {
|
|
|
21
21
|
inboundOnly: boolean;
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
-
export type FileRecvTransferStatus = 'init' | 'transferring' | 'completed' | 'aborted';
|
|
25
|
-
|
|
26
24
|
type FileRecvTransferBase = {
|
|
27
25
|
transferId: string;
|
|
28
26
|
accountId: string;
|
|
@@ -105,21 +103,7 @@ export type OutboxEntry = {
|
|
|
105
103
|
accountId: string;
|
|
106
104
|
sessionKey: string;
|
|
107
105
|
route: BncrRoute;
|
|
108
|
-
payload: Record<string, unknown
|
|
109
|
-
_meta?: {
|
|
110
|
-
kind?: 'message' | 'file-transfer';
|
|
111
|
-
retryCount?: number;
|
|
112
|
-
nextAttemptAt?: number;
|
|
113
|
-
mediaUrl?: string;
|
|
114
|
-
mediaLocalRoots?: string[];
|
|
115
|
-
text?: string;
|
|
116
|
-
asVoice?: boolean;
|
|
117
|
-
audioAsVoice?: boolean;
|
|
118
|
-
replyToId?: string;
|
|
119
|
-
finalEvent?: string;
|
|
120
|
-
[key: string]: unknown;
|
|
121
|
-
};
|
|
122
|
-
};
|
|
106
|
+
payload: Record<string, unknown>;
|
|
123
107
|
createdAt: number;
|
|
124
108
|
retryCount: number;
|
|
125
109
|
nextAttemptAt: number;
|
|
@@ -1,15 +1,39 @@
|
|
|
1
|
-
export function
|
|
1
|
+
export function asString(value: unknown, fallback = ''): string {
|
|
2
2
|
if (typeof value === 'string') return value;
|
|
3
3
|
if (value == null) return fallback;
|
|
4
4
|
return String(value);
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
/** Alias: same as asString, named for call-sites that want a sanitization signal. */
|
|
8
|
+
export const asSanitizedString = asString;
|
|
9
|
+
|
|
7
10
|
export function finiteNumberOr(value: unknown, fallback: number): number {
|
|
8
11
|
const n = Number(value);
|
|
9
12
|
return Number.isFinite(n) ? n : fallback;
|
|
10
13
|
}
|
|
11
14
|
|
|
15
|
+
export function now(): number {
|
|
16
|
+
return Date.now();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function asBoolean(value: unknown, fallback = false): boolean {
|
|
20
|
+
if (typeof value === 'boolean') return value;
|
|
21
|
+
if (typeof value === 'number') return value !== 0;
|
|
22
|
+
if (typeof value === 'string') {
|
|
23
|
+
const raw = value.trim().toLowerCase();
|
|
24
|
+
if (!raw) return fallback;
|
|
25
|
+
if (['true', '1', 'yes', 'y', 'on'].includes(raw)) return true;
|
|
26
|
+
if (['false', '0', 'no', 'n', 'off'].includes(raw)) return false;
|
|
27
|
+
}
|
|
28
|
+
return fallback;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
32
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
33
|
+
}
|
|
34
|
+
|
|
12
35
|
export function finiteNumberOrNull(value: unknown): number | null {
|
|
36
|
+
if (value === null || value === undefined) return null;
|
|
13
37
|
const n = Number(value);
|
|
14
38
|
return Number.isFinite(n) ? n : null;
|
|
15
39
|
}
|
|
@@ -27,3 +51,16 @@ export function clampFiniteNumber(
|
|
|
27
51
|
const finite = finiteNumberOr(value, fallback);
|
|
28
52
|
return Math.max(min, Math.min(finite, max));
|
|
29
53
|
}
|
|
54
|
+
|
|
55
|
+
export function asStringArray(v: unknown): string[] {
|
|
56
|
+
if (Array.isArray(v)) {
|
|
57
|
+
return v.map((item) => asString(item).trim()).filter(Boolean);
|
|
58
|
+
}
|
|
59
|
+
if (typeof v === 'string') {
|
|
60
|
+
return v
|
|
61
|
+
.split(',')
|
|
62
|
+
.map((item) => item.trim())
|
|
63
|
+
.filter(Boolean);
|
|
64
|
+
}
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
@@ -7,10 +7,7 @@ import {
|
|
|
7
7
|
resolveBncrPinnedMainDmOwnerFromAllowlist,
|
|
8
8
|
} from '../../openclaw/inbound-session-runtime.ts';
|
|
9
9
|
import { dispatchOpenClawReplyWithBufferedBlockDispatcher } from '../../openclaw/reply-runtime.ts';
|
|
10
|
-
import {
|
|
11
|
-
type OpenClawResolvedAgentRoute,
|
|
12
|
-
resolveOpenClawAgentRoute,
|
|
13
|
-
} from '../../openclaw/routing-runtime.ts';
|
|
10
|
+
import { resolveOpenClawAgentRoute } from '../../openclaw/routing-runtime.ts';
|
|
14
11
|
import type {
|
|
15
12
|
BncrEnqueueFromReply,
|
|
16
13
|
BncrInboundApi,
|
|
@@ -18,7 +15,7 @@ import type {
|
|
|
18
15
|
BncrInboundLogger,
|
|
19
16
|
BncrRememberSessionRoute,
|
|
20
17
|
} from './contracts.ts';
|
|
21
|
-
import type
|
|
18
|
+
import { assertResolvedAgentRoute, type ParsedInbound } from './dispatch-prep.ts';
|
|
22
19
|
import { buildBncrInboundRecordUpdateLastRoute } from './last-route.ts';
|
|
23
20
|
import {
|
|
24
21
|
parseBncrNativeCommand,
|
|
@@ -50,25 +47,6 @@ import {
|
|
|
50
47
|
} from './session-label.ts';
|
|
51
48
|
import { createBncrSessionMetaTaskBarrier } from './session-meta-task.ts';
|
|
52
49
|
|
|
53
|
-
function assertResolvedAgentRoute(resolvedRoute: OpenClawResolvedAgentRoute): {
|
|
54
|
-
sessionKey: string;
|
|
55
|
-
agentId: string;
|
|
56
|
-
mainSessionKey?: string;
|
|
57
|
-
} {
|
|
58
|
-
const sessionKey =
|
|
59
|
-
typeof resolvedRoute.sessionKey === 'string' ? resolvedRoute.sessionKey.trim() : '';
|
|
60
|
-
const agentId = typeof resolvedRoute.agentId === 'string' ? resolvedRoute.agentId.trim() : '';
|
|
61
|
-
if (!sessionKey) throw new Error('OpenClaw resolveAgentRoute returned empty sessionKey');
|
|
62
|
-
if (!agentId) throw new Error('OpenClaw resolveAgentRoute returned empty agentId');
|
|
63
|
-
return {
|
|
64
|
-
sessionKey,
|
|
65
|
-
agentId,
|
|
66
|
-
...(typeof resolvedRoute.mainSessionKey === 'string' && resolvedRoute.mainSessionKey.trim()
|
|
67
|
-
? { mainSessionKey: resolvedRoute.mainSessionKey }
|
|
68
|
-
: {}),
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
|
|
72
50
|
function buildBncrNativeCommandResolvedRoute(args: {
|
|
73
51
|
api: BncrInboundApi;
|
|
74
52
|
cfg: BncrInboundConfig;
|
|
@@ -95,7 +95,7 @@ const BNCR_GENERIC_MEDIA_TEXTS = new Set([
|
|
|
95
95
|
'[文件]',
|
|
96
96
|
]);
|
|
97
97
|
|
|
98
|
-
function assertResolvedAgentRoute(resolvedRoute: OpenClawResolvedAgentRoute): {
|
|
98
|
+
export function assertResolvedAgentRoute(resolvedRoute: OpenClawResolvedAgentRoute): {
|
|
99
99
|
sessionKey: string;
|
|
100
100
|
agentId: string;
|
|
101
101
|
mainSessionKey?: string;
|
|
@@ -6,36 +6,7 @@ export type BncrGateResult = { allowed: true } | { allowed: false; reason: strin
|
|
|
6
6
|
const REQUIRED_PROTOCOL_VERSION = 'scene-routing-v1';
|
|
7
7
|
const REQUIRED_CAPABILITY = 'scene-routing-v1';
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
if (typeof v === 'string') return v;
|
|
11
|
-
if (v == null) return fallback;
|
|
12
|
-
return String(v);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function asBoolean(v: unknown, fallback = false): boolean {
|
|
16
|
-
if (typeof v === 'boolean') return v;
|
|
17
|
-
if (typeof v === 'number') return v !== 0;
|
|
18
|
-
if (typeof v === 'string') {
|
|
19
|
-
const raw = v.trim().toLowerCase();
|
|
20
|
-
if (!raw) return fallback;
|
|
21
|
-
if (['true', '1', 'yes', 'y', 'on'].includes(raw)) return true;
|
|
22
|
-
if (['false', '0', 'no', 'n', 'off'].includes(raw)) return false;
|
|
23
|
-
}
|
|
24
|
-
return fallback;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function asCapabilities(v: unknown): string[] {
|
|
28
|
-
if (Array.isArray(v)) {
|
|
29
|
-
return v.map((item) => asString(item).trim()).filter(Boolean);
|
|
30
|
-
}
|
|
31
|
-
if (typeof v === 'string') {
|
|
32
|
-
return v
|
|
33
|
-
.split(',')
|
|
34
|
-
.map((item) => item.trim())
|
|
35
|
-
.filter(Boolean);
|
|
36
|
-
}
|
|
37
|
-
return [];
|
|
38
|
-
}
|
|
9
|
+
import { asBoolean, asString, asStringArray } from '../../core/value-sanitize.ts';
|
|
39
10
|
|
|
40
11
|
export async function checkBncrMessageGate(params: {
|
|
41
12
|
parsed: BncrInboundParamsInput & {
|
|
@@ -57,7 +28,7 @@ export async function checkBncrMessageGate(params: {
|
|
|
57
28
|
}
|
|
58
29
|
|
|
59
30
|
const protocolVersion = asString(parsed?.protocolVersion || '').trim();
|
|
60
|
-
const capabilities =
|
|
31
|
+
const capabilities = asStringArray(parsed?.capabilities);
|
|
61
32
|
if (
|
|
62
33
|
protocolVersion !== REQUIRED_PROTOCOL_VERSION ||
|
|
63
34
|
!capabilities.includes(REQUIRED_CAPABILITY)
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
1
|
import {
|
|
3
2
|
createChannelHistoryWindow,
|
|
4
3
|
DEFAULT_GROUP_HISTORY_LIMIT,
|
|
@@ -245,80 +244,6 @@ export function collectBncrHistoryMediaFromEntries(args: {
|
|
|
245
244
|
}): HistoryMediaEntry[] {
|
|
246
245
|
return args.entries.flatMap((entry) => (Array.isArray(entry.media) ? entry.media : []));
|
|
247
246
|
}
|
|
248
|
-
|
|
249
|
-
function toBncrPromptMediaPath(value: string | undefined): string | undefined {
|
|
250
|
-
const normalized = String(value || '').trim();
|
|
251
|
-
if (!normalized) return undefined;
|
|
252
|
-
const slashNormalized = normalized.replace(/\\/g, '/');
|
|
253
|
-
if (!slashNormalized.includes('/media/inbound/')) return undefined;
|
|
254
|
-
const base = path.posix.basename(slashNormalized);
|
|
255
|
-
if (!base || base === '.' || base === '..') return undefined;
|
|
256
|
-
return `media://inbound/${encodeURIComponent(base)}`;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function buildHistoryWindowMediaSummary(
|
|
260
|
-
media: HistoryMediaEntry[] | undefined,
|
|
261
|
-
): string | undefined {
|
|
262
|
-
if (!Array.isArray(media) || media.length === 0) return undefined;
|
|
263
|
-
const kinds = media
|
|
264
|
-
.map((item) =>
|
|
265
|
-
String(item?.kind || '')
|
|
266
|
-
.trim()
|
|
267
|
-
.toLowerCase(),
|
|
268
|
-
)
|
|
269
|
-
.filter(Boolean);
|
|
270
|
-
const uniformKind = kinds[0] && kinds.every((item) => item === kinds[0]) ? kinds[0] : 'document';
|
|
271
|
-
const count = media.length;
|
|
272
|
-
if (uniformKind === 'image')
|
|
273
|
-
return count === 1 ? '<media:image>' : `<media:image> (${count} images)`;
|
|
274
|
-
if (uniformKind === 'video')
|
|
275
|
-
return count === 1 ? '<media:video>' : `<media:video> (${count} videos)`;
|
|
276
|
-
if (uniformKind === 'audio') {
|
|
277
|
-
return count === 1 ? '<media:audio>' : `<media:audio> (${count} audio attachments)`;
|
|
278
|
-
}
|
|
279
|
-
return count === 1 ? '<media:document>' : `<media:document> (${count} attachments)`;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
function buildHistoryWindowMedias(media: HistoryMediaEntry[] | undefined) {
|
|
283
|
-
if (!Array.isArray(media) || media.length === 0) return [];
|
|
284
|
-
return media.flatMap((item) => {
|
|
285
|
-
const promptPath = toBncrPromptMediaPath(item?.path);
|
|
286
|
-
const payload = {
|
|
287
|
-
...(promptPath ? { path: promptPath } : {}),
|
|
288
|
-
...(item?.contentType ? { contentType: item.contentType } : {}),
|
|
289
|
-
...(item?.kind ? { kind: item.kind } : {}),
|
|
290
|
-
...(item?.messageId ? { messageId: item.messageId } : {}),
|
|
291
|
-
};
|
|
292
|
-
return Object.keys(payload).length > 0 ? [payload] : [];
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
export function buildBncrHistoryWindowContextFromEntries(args: {
|
|
297
|
-
entries: readonly BncrHistoryEntry[];
|
|
298
|
-
channelId: string;
|
|
299
|
-
}) {
|
|
300
|
-
if (args.entries.length === 0) return undefined;
|
|
301
|
-
const messages = args.entries.map((entry) => ({
|
|
302
|
-
...(entry.messageId ? { messageId: entry.messageId } : {}),
|
|
303
|
-
sender: entry.sender,
|
|
304
|
-
senderId: entry.senderId,
|
|
305
|
-
...(typeof entry.timestamp === 'number' ? { timestampMs: entry.timestamp } : {}),
|
|
306
|
-
body: entry.body,
|
|
307
|
-
mediaSummary: buildHistoryWindowMediaSummary(entry.media),
|
|
308
|
-
medias: buildHistoryWindowMedias(entry.media),
|
|
309
|
-
}));
|
|
310
|
-
return {
|
|
311
|
-
label: 'Bncr history window',
|
|
312
|
-
source: args.channelId,
|
|
313
|
-
type: 'bncr.history_window',
|
|
314
|
-
payload: {
|
|
315
|
-
relation: 'before_current_message',
|
|
316
|
-
order: 'chronological',
|
|
317
|
-
messages,
|
|
318
|
-
},
|
|
319
|
-
};
|
|
320
|
-
}
|
|
321
|
-
|
|
322
247
|
export function clearBncrPendingGroupHistory(args: {
|
|
323
248
|
historyMap: BncrGroupHistoryMap;
|
|
324
249
|
parsed: ParsedInbound;
|
|
@@ -2,39 +2,9 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { normalizeAccountId } from '../../core/accounts.ts';
|
|
3
3
|
import { extractInlineTaskKey } from '../../core/targets.ts';
|
|
4
4
|
import type { BncrRoute } from '../../core/types.ts';
|
|
5
|
+
import { asBoolean, asString, asStringArray } from '../../core/value-sanitize.ts';
|
|
5
6
|
import type { BncrInboundMediaItem, BncrInboundParamsInput } from './contracts.ts';
|
|
6
7
|
|
|
7
|
-
function asString(v: unknown, fallback = ''): string {
|
|
8
|
-
if (typeof v === 'string') return v;
|
|
9
|
-
if (v == null) return fallback;
|
|
10
|
-
return String(v);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function asBoolean(v: unknown, fallback = false): boolean {
|
|
14
|
-
if (typeof v === 'boolean') return v;
|
|
15
|
-
if (typeof v === 'number') return v !== 0;
|
|
16
|
-
if (typeof v === 'string') {
|
|
17
|
-
const raw = v.trim().toLowerCase();
|
|
18
|
-
if (!raw) return fallback;
|
|
19
|
-
if (['true', '1', 'yes', 'y', 'on'].includes(raw)) return true;
|
|
20
|
-
if (['false', '0', 'no', 'n', 'off'].includes(raw)) return false;
|
|
21
|
-
}
|
|
22
|
-
return fallback;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function asStringArray(v: unknown): string[] {
|
|
26
|
-
if (Array.isArray(v)) {
|
|
27
|
-
return v.map((item) => asString(item).trim()).filter(Boolean);
|
|
28
|
-
}
|
|
29
|
-
if (typeof v === 'string') {
|
|
30
|
-
return v
|
|
31
|
-
.split(',')
|
|
32
|
-
.map((item) => item.trim())
|
|
33
|
-
.filter(Boolean);
|
|
34
|
-
}
|
|
35
|
-
return [];
|
|
36
|
-
}
|
|
37
|
-
|
|
38
8
|
function asInboundMediaItems(params: BncrInboundParamsInput): BncrInboundMediaItem[] {
|
|
39
9
|
const normalized: BncrInboundMediaItem[] = [];
|
|
40
10
|
const rawList = Array.isArray(params?.mediaList) ? params.mediaList : [];
|
|
@@ -88,7 +58,7 @@ function asInboundMediaItems(params: BncrInboundParamsInput): BncrInboundMediaIt
|
|
|
88
58
|
}));
|
|
89
59
|
}
|
|
90
60
|
|
|
91
|
-
|
|
61
|
+
function inboundDedupKey(params: {
|
|
92
62
|
accountId: string;
|
|
93
63
|
platform: string;
|
|
94
64
|
groupId: string;
|