@xmoxmo/bncr 0.2.1 → 0.2.2
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 -1
- package/index.ts +314 -76
- package/openclaw.plugin.json +59 -0
- package/package.json +3 -3
- package/src/channel.ts +515 -101
- package/src/core/config-schema.ts +6 -0
- package/src/core/types.ts +3 -0
package/README.md
CHANGED
|
@@ -122,7 +122,8 @@ plugins/bncr/src/
|
|
|
122
122
|
补充:
|
|
123
123
|
|
|
124
124
|
- `dmPolicy` / `groupPolicy` 支持:`open | allowlist | disabled`
|
|
125
|
-
- `outboundRequireAck`
|
|
125
|
+
- `outboundRequireAck` 是当前**单账号场景**使用的顶层字段:`channels.bncr.outboundRequireAck`
|
|
126
|
+
- `outboundRequireAck=true` 时,文本外发会等待 `bncr.ack` 再出队;关闭后不再强制等待文本 ACK,超时类错误会显示为 `push-delivery-unconfirmed`
|
|
126
127
|
- `requireMention` 当前仍是保留字段
|
|
127
128
|
|
|
128
129
|
---
|
|
@@ -142,6 +143,9 @@ openclaw health --json
|
|
|
142
143
|
- `pending`
|
|
143
144
|
- `deadLetter`
|
|
144
145
|
- diagnostics / probe / status 摘要
|
|
146
|
+
- diagnostics 里的 `runtimeFlags.outboundRequireAck`
|
|
147
|
+
- diagnostics 里的 `runtimeFlags.ackPolicySource`
|
|
148
|
+
- diagnostics 里的 `waiters.messageAck` / `waiters.fileAck`
|
|
145
149
|
|
|
146
150
|
---
|
|
147
151
|
|
package/index.ts
CHANGED
|
@@ -22,6 +22,10 @@ type LoadedRuntime = {
|
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
const BNCR_REGISTER_META = Symbol.for('bncr.register.meta');
|
|
25
|
+
const BNCR_GLOBAL_REGISTER_TRACE = Symbol.for('bncr.global.register.trace');
|
|
26
|
+
const BNCR_BRIDGE_OWNER = Symbol.for('bncr.bridge.owner');
|
|
27
|
+
const BNCR_GATEWAY_RUNTIME = Symbol.for('bncr.gateway.runtime');
|
|
28
|
+
const MODULE_EPOCH = `${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
25
29
|
|
|
26
30
|
type RegisterMeta = {
|
|
27
31
|
service?: boolean;
|
|
@@ -29,12 +33,74 @@ type RegisterMeta = {
|
|
|
29
33
|
methods?: Set<string>;
|
|
30
34
|
apiInstanceId?: string;
|
|
31
35
|
registryFingerprint?: string;
|
|
36
|
+
registrationMode?: string;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
type GlobalRegisterTrace = {
|
|
40
|
+
lastApiInstanceId?: string;
|
|
41
|
+
lastRegistryFingerprint?: string;
|
|
42
|
+
seenRegistryFingerprints: Set<string>;
|
|
43
|
+
seenApiInstanceIds: Set<string>;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
type BridgeOwner = {
|
|
47
|
+
moduleEpoch: string;
|
|
48
|
+
bridgeFactoryId: string;
|
|
49
|
+
apiInstanceId: string;
|
|
50
|
+
registryFingerprint: string;
|
|
51
|
+
registrationMode?: string;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
type BridgeRegisterStateSnapshot = {
|
|
55
|
+
registerCount: number;
|
|
56
|
+
apiGeneration: number;
|
|
57
|
+
firstRegisterAt: number | null;
|
|
58
|
+
lastRegisterAt: number | null;
|
|
59
|
+
lastApiRebindAt: number | null;
|
|
60
|
+
pluginSource: string | null;
|
|
61
|
+
pluginVersion: string | null;
|
|
62
|
+
lastApiInstanceId: string | null;
|
|
63
|
+
lastRegistryFingerprint: string | null;
|
|
64
|
+
lastDriftSnapshot: unknown;
|
|
65
|
+
registerTraceRecent: Array<Record<string, unknown>>;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
type GatewayMethodName =
|
|
69
|
+
| 'bncr.connect'
|
|
70
|
+
| 'bncr.inbound'
|
|
71
|
+
| 'bncr.activity'
|
|
72
|
+
| 'bncr.ack'
|
|
73
|
+
| 'bncr.diagnostics'
|
|
74
|
+
| 'bncr.file.init'
|
|
75
|
+
| 'bncr.file.chunk'
|
|
76
|
+
| 'bncr.file.complete'
|
|
77
|
+
| 'bncr.file.abort'
|
|
78
|
+
| 'bncr.file.ack';
|
|
79
|
+
|
|
80
|
+
type BridgeSingletonWithOwner = BridgeSingleton & {
|
|
81
|
+
[BNCR_BRIDGE_OWNER]?: BridgeOwner;
|
|
82
|
+
registerCount?: number;
|
|
83
|
+
apiGeneration?: number;
|
|
84
|
+
firstRegisterAt?: number | null;
|
|
85
|
+
lastRegisterAt?: number | null;
|
|
86
|
+
lastApiRebindAt?: number | null;
|
|
87
|
+
pluginSource?: string | null;
|
|
88
|
+
pluginVersion?: string | null;
|
|
89
|
+
lastApiInstanceId?: string | null;
|
|
90
|
+
lastRegistryFingerprint?: string | null;
|
|
91
|
+
lastDriftSnapshot?: unknown;
|
|
92
|
+
registerTraceRecent?: Array<Record<string, unknown>>;
|
|
32
93
|
};
|
|
33
94
|
|
|
34
95
|
type OpenClawPluginApiWithMeta = OpenClawPluginApi & {
|
|
35
96
|
[BNCR_REGISTER_META]?: RegisterMeta;
|
|
36
97
|
};
|
|
37
98
|
|
|
99
|
+
type BncrGatewayRuntime = {
|
|
100
|
+
currentBridge?: BridgeSingletonWithOwner;
|
|
101
|
+
registeredMethodsByRegistry: Map<string, Set<GatewayMethodName>>;
|
|
102
|
+
};
|
|
103
|
+
|
|
38
104
|
let runtime: LoadedRuntime | null = null;
|
|
39
105
|
const identityIds = new WeakMap<object, string>();
|
|
40
106
|
let identitySeq = 0;
|
|
@@ -218,7 +284,7 @@ const loadRuntimeSync = (): LoadedRuntime => {
|
|
|
218
284
|
const getIdentityId = (obj: object, prefix: string) => {
|
|
219
285
|
const existing = identityIds.get(obj);
|
|
220
286
|
if (existing) return existing;
|
|
221
|
-
const next = `${prefix}_${++identitySeq}`;
|
|
287
|
+
const next = `${prefix}_${MODULE_EPOCH}_${++identitySeq}`;
|
|
222
288
|
identityIds.set(obj, next);
|
|
223
289
|
return next;
|
|
224
290
|
};
|
|
@@ -247,33 +313,219 @@ const getRegisterMeta = (api: OpenClawPluginApi): RegisterMeta => {
|
|
|
247
313
|
return host[BNCR_REGISTER_META]!;
|
|
248
314
|
};
|
|
249
315
|
|
|
316
|
+
const getProcessStore = () => {
|
|
317
|
+
const p = process as NodeJS.Process & {
|
|
318
|
+
[BNCR_GLOBAL_REGISTER_TRACE]?: GlobalRegisterTrace;
|
|
319
|
+
[BNCR_GATEWAY_RUNTIME]?: BncrGatewayRuntime;
|
|
320
|
+
};
|
|
321
|
+
return p;
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
const getGlobalRegisterTrace = () => {
|
|
325
|
+
const p = getProcessStore();
|
|
326
|
+
if (!p[BNCR_GLOBAL_REGISTER_TRACE]) {
|
|
327
|
+
p[BNCR_GLOBAL_REGISTER_TRACE] = {
|
|
328
|
+
seenRegistryFingerprints: new Set<string>(),
|
|
329
|
+
seenApiInstanceIds: new Set<string>(),
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
return p[BNCR_GLOBAL_REGISTER_TRACE]!;
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
const getGatewayRuntime = (): BncrGatewayRuntime => {
|
|
336
|
+
const p = getProcessStore();
|
|
337
|
+
if (!p[BNCR_GATEWAY_RUNTIME]) {
|
|
338
|
+
p[BNCR_GATEWAY_RUNTIME] = {
|
|
339
|
+
registeredMethodsByRegistry: new Map<string, Set<GatewayMethodName>>(),
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
return p[BNCR_GATEWAY_RUNTIME]!;
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
const gatewayMethodDispatchers: Record<
|
|
346
|
+
GatewayMethodName,
|
|
347
|
+
(bridge: BridgeSingletonWithOwner, opts: any) => any
|
|
348
|
+
> = {
|
|
349
|
+
'bncr.connect': (bridge, opts) => bridge.handleConnect(opts),
|
|
350
|
+
'bncr.inbound': (bridge, opts) => bridge.handleInbound(opts),
|
|
351
|
+
'bncr.activity': (bridge, opts) => bridge.handleActivity(opts),
|
|
352
|
+
'bncr.ack': (bridge, opts) => bridge.handleAck(opts),
|
|
353
|
+
'bncr.diagnostics': (bridge, opts) => bridge.handleDiagnostics(opts),
|
|
354
|
+
'bncr.file.init': (bridge, opts) => bridge.handleFileInit(opts),
|
|
355
|
+
'bncr.file.chunk': (bridge, opts) => bridge.handleFileChunk(opts),
|
|
356
|
+
'bncr.file.complete': (bridge, opts) => bridge.handleFileComplete(opts),
|
|
357
|
+
'bncr.file.abort': (bridge, opts) => bridge.handleFileAbort(opts),
|
|
358
|
+
'bncr.file.ack': (bridge, opts) => bridge.handleFileAck(opts),
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
const dispatchGatewayMethod = (name: GatewayMethodName, opts: any) => {
|
|
362
|
+
const gatewayRuntime = getGatewayRuntime();
|
|
363
|
+
const bridge = gatewayRuntime.currentBridge;
|
|
364
|
+
if (!bridge) {
|
|
365
|
+
throw new Error(`bncr gateway runtime unavailable for ${name}`);
|
|
366
|
+
}
|
|
367
|
+
return gatewayMethodDispatchers[name](bridge, opts);
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
const mirrorGatewayMethodForMockApi = (api: OpenClawPluginApi, name: GatewayMethodName) => {
|
|
371
|
+
const host = api as OpenClawPluginApi & {
|
|
372
|
+
methods?: Array<{ name: string; handler: (opts: any) => any }>;
|
|
373
|
+
};
|
|
374
|
+
if (!Array.isArray(host.methods)) return;
|
|
375
|
+
if (host.methods.some((item) => item?.name === name)) return;
|
|
376
|
+
host.methods.push({ name, handler: (opts) => dispatchGatewayMethod(name, opts) });
|
|
377
|
+
};
|
|
378
|
+
|
|
250
379
|
const ensureGatewayMethodRegistered = (
|
|
251
380
|
api: OpenClawPluginApi,
|
|
252
|
-
name:
|
|
253
|
-
handler: (opts: any) => any,
|
|
381
|
+
name: GatewayMethodName,
|
|
254
382
|
debugLog: (...args: any[]) => void,
|
|
255
383
|
) => {
|
|
256
384
|
const meta = getRegisterMeta(api);
|
|
385
|
+
const gatewayRuntime = getGatewayRuntime();
|
|
386
|
+
const registryFingerprint = meta.registryFingerprint || getRegistryFingerprint(api);
|
|
387
|
+
let registryMethods = gatewayRuntime.registeredMethodsByRegistry.get(registryFingerprint);
|
|
388
|
+
if (!registryMethods) {
|
|
389
|
+
registryMethods = new Set<GatewayMethodName>();
|
|
390
|
+
gatewayRuntime.registeredMethodsByRegistry.set(registryFingerprint, registryMethods);
|
|
391
|
+
}
|
|
257
392
|
if (meta.methods?.has(name)) {
|
|
258
393
|
debugLog(`register method skip ${name} (already registered on this api)`);
|
|
259
394
|
return;
|
|
260
395
|
}
|
|
261
|
-
|
|
396
|
+
if (registryMethods.has(name)) {
|
|
397
|
+
mirrorGatewayMethodForMockApi(api, name);
|
|
398
|
+
meta.methods?.add(name);
|
|
399
|
+
debugLog(`register method reuse ${name} (already registered in registry)`);
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
api.registerGatewayMethod(name, (opts) => dispatchGatewayMethod(name, opts));
|
|
403
|
+
mirrorGatewayMethodForMockApi(api, name);
|
|
404
|
+
registryMethods.add(name);
|
|
262
405
|
meta.methods?.add(name);
|
|
263
406
|
debugLog(`register method ok ${name}`);
|
|
264
407
|
};
|
|
265
408
|
|
|
409
|
+
const getBridgeOwner = (api: OpenClawPluginApi, loaded: LoadedRuntime): BridgeOwner => {
|
|
410
|
+
const meta = getRegisterMeta(api);
|
|
411
|
+
return {
|
|
412
|
+
moduleEpoch: MODULE_EPOCH,
|
|
413
|
+
bridgeFactoryId: getIdentityId(loaded.createBncrBridge as object, 'bridgeFactory'),
|
|
414
|
+
apiInstanceId: meta.apiInstanceId || 'unknown',
|
|
415
|
+
registryFingerprint: meta.registryFingerprint || 'unknown',
|
|
416
|
+
registrationMode: meta.registrationMode,
|
|
417
|
+
};
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
const sameBridgeOwner = (left?: BridgeOwner, right?: BridgeOwner) => {
|
|
421
|
+
if (!left || !right) return false;
|
|
422
|
+
return (
|
|
423
|
+
left.moduleEpoch === right.moduleEpoch &&
|
|
424
|
+
left.bridgeFactoryId === right.bridgeFactoryId &&
|
|
425
|
+
left.apiInstanceId === right.apiInstanceId &&
|
|
426
|
+
left.registryFingerprint === right.registryFingerprint
|
|
427
|
+
);
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
const snapshotBridgeRegisterState = (
|
|
431
|
+
bridge?: BridgeSingletonWithOwner,
|
|
432
|
+
): BridgeRegisterStateSnapshot | null => {
|
|
433
|
+
if (!bridge) return null;
|
|
434
|
+
return {
|
|
435
|
+
registerCount: Number(bridge.registerCount || 0),
|
|
436
|
+
apiGeneration: Number(bridge.apiGeneration || 0),
|
|
437
|
+
firstRegisterAt:
|
|
438
|
+
typeof bridge.firstRegisterAt === 'number'
|
|
439
|
+
? bridge.firstRegisterAt
|
|
440
|
+
: (bridge.firstRegisterAt ?? null),
|
|
441
|
+
lastRegisterAt:
|
|
442
|
+
typeof bridge.lastRegisterAt === 'number'
|
|
443
|
+
? bridge.lastRegisterAt
|
|
444
|
+
: (bridge.lastRegisterAt ?? null),
|
|
445
|
+
lastApiRebindAt:
|
|
446
|
+
typeof bridge.lastApiRebindAt === 'number'
|
|
447
|
+
? bridge.lastApiRebindAt
|
|
448
|
+
: (bridge.lastApiRebindAt ?? null),
|
|
449
|
+
pluginSource: typeof bridge.pluginSource === 'string' ? bridge.pluginSource : null,
|
|
450
|
+
pluginVersion: typeof bridge.pluginVersion === 'string' ? bridge.pluginVersion : null,
|
|
451
|
+
lastApiInstanceId:
|
|
452
|
+
typeof bridge.lastApiInstanceId === 'string' ? bridge.lastApiInstanceId : null,
|
|
453
|
+
lastRegistryFingerprint:
|
|
454
|
+
typeof bridge.lastRegistryFingerprint === 'string' ? bridge.lastRegistryFingerprint : null,
|
|
455
|
+
lastDriftSnapshot: bridge.lastDriftSnapshot ?? null,
|
|
456
|
+
registerTraceRecent: Array.isArray(bridge.registerTraceRecent)
|
|
457
|
+
? bridge.registerTraceRecent.map((trace) => ({ ...trace }))
|
|
458
|
+
: [],
|
|
459
|
+
};
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
const hydrateBridgeRegisterState = (
|
|
463
|
+
bridge: BridgeSingletonWithOwner,
|
|
464
|
+
snapshot: BridgeRegisterStateSnapshot | null,
|
|
465
|
+
) => {
|
|
466
|
+
if (!snapshot) return bridge;
|
|
467
|
+
bridge.registerCount = snapshot.registerCount;
|
|
468
|
+
bridge.apiGeneration = snapshot.apiGeneration;
|
|
469
|
+
bridge.firstRegisterAt = snapshot.firstRegisterAt;
|
|
470
|
+
bridge.lastRegisterAt = snapshot.lastRegisterAt;
|
|
471
|
+
bridge.lastApiRebindAt = snapshot.lastApiRebindAt;
|
|
472
|
+
bridge.pluginSource = snapshot.pluginSource;
|
|
473
|
+
bridge.pluginVersion = snapshot.pluginVersion;
|
|
474
|
+
bridge.lastApiInstanceId = snapshot.lastApiInstanceId;
|
|
475
|
+
bridge.lastRegistryFingerprint = snapshot.lastRegistryFingerprint;
|
|
476
|
+
bridge.lastDriftSnapshot = snapshot.lastDriftSnapshot;
|
|
477
|
+
bridge.registerTraceRecent = snapshot.registerTraceRecent.map((trace) => ({ ...trace }));
|
|
478
|
+
return bridge;
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
const assignBridgeOwner = (bridge: BridgeSingleton, owner: BridgeOwner) => {
|
|
482
|
+
(bridge as BridgeSingletonWithOwner)[BNCR_BRIDGE_OWNER] = owner;
|
|
483
|
+
return bridge as BridgeSingletonWithOwner;
|
|
484
|
+
};
|
|
485
|
+
|
|
266
486
|
const getBridgeSingleton = (api: OpenClawPluginApi) => {
|
|
267
487
|
const loaded = loadRuntimeSync();
|
|
268
|
-
const g = globalThis as typeof globalThis & { __bncrBridge?:
|
|
488
|
+
const g = globalThis as typeof globalThis & { __bncrBridge?: BridgeSingletonWithOwner };
|
|
489
|
+
const owner = getBridgeOwner(api, loaded);
|
|
490
|
+
const previousOwner = g.__bncrBridge?.[BNCR_BRIDGE_OWNER];
|
|
491
|
+
|
|
269
492
|
let created = false;
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
493
|
+
let rebuilt = false;
|
|
494
|
+
|
|
495
|
+
if (g.__bncrBridge) {
|
|
496
|
+
const mustRebuild =
|
|
497
|
+
!sameBridgeOwner(previousOwner, owner) &&
|
|
498
|
+
(previousOwner?.moduleEpoch !== owner.moduleEpoch ||
|
|
499
|
+
previousOwner?.bridgeFactoryId !== owner.bridgeFactoryId ||
|
|
500
|
+
previousOwner?.registrationMode !== owner.registrationMode ||
|
|
501
|
+
previousOwner?.apiInstanceId !== owner.apiInstanceId ||
|
|
502
|
+
previousOwner?.registryFingerprint !== owner.registryFingerprint);
|
|
503
|
+
|
|
504
|
+
if (mustRebuild) {
|
|
505
|
+
const registerState = snapshotBridgeRegisterState(g.__bncrBridge);
|
|
506
|
+
try {
|
|
507
|
+
g.__bncrBridge.stopService?.();
|
|
508
|
+
} catch {
|
|
509
|
+
// ignore stop errors during hot-restart recovery
|
|
510
|
+
}
|
|
511
|
+
g.__bncrBridge = hydrateBridgeRegisterState(
|
|
512
|
+
assignBridgeOwner(loaded.createBncrBridge(api), owner),
|
|
513
|
+
registerState,
|
|
514
|
+
);
|
|
515
|
+
created = true;
|
|
516
|
+
rebuilt = true;
|
|
517
|
+
} else {
|
|
518
|
+
g.__bncrBridge.bindApi?.(api);
|
|
519
|
+
assignBridgeOwner(g.__bncrBridge, owner);
|
|
520
|
+
created = false;
|
|
521
|
+
rebuilt = false;
|
|
522
|
+
}
|
|
273
523
|
} else {
|
|
274
|
-
g.__bncrBridge.
|
|
524
|
+
g.__bncrBridge = assignBridgeOwner(loaded.createBncrBridge(api), owner);
|
|
525
|
+
created = true;
|
|
275
526
|
}
|
|
276
|
-
|
|
527
|
+
|
|
528
|
+
return { bridge: g.__bncrBridge, runtime: loaded, created, rebuilt, owner, previousOwner };
|
|
277
529
|
};
|
|
278
530
|
|
|
279
531
|
const isPlainObject = (value: unknown): value is Record<string, unknown> =>
|
|
@@ -290,10 +542,11 @@ const registerBncrCli = (api: OpenClawPluginApi & { registerCli?: (...args: any[
|
|
|
290
542
|
'Seed minimal channels.bncr config (adds enabled=true and allowTool=false only when missing)',
|
|
291
543
|
)
|
|
292
544
|
.action(async () => {
|
|
293
|
-
const cfg =
|
|
294
|
-
|
|
545
|
+
const cfg = api.runtime.config.current() as Record<string, unknown>;
|
|
546
|
+
const next = structuredClone(cfg);
|
|
547
|
+
if (!isPlainObject(next.channels)) next.channels = {};
|
|
295
548
|
|
|
296
|
-
const existing = isPlainObject(
|
|
549
|
+
const existing = isPlainObject(next.channels.bncr) ? next.channels.bncr : {};
|
|
297
550
|
const bncrCfg: Record<string, unknown> = { ...existing };
|
|
298
551
|
const added: string[] = [];
|
|
299
552
|
|
|
@@ -307,14 +560,14 @@ const registerBncrCli = (api: OpenClawPluginApi & { registerCli?: (...args: any[
|
|
|
307
560
|
added.push('allowTool=false');
|
|
308
561
|
}
|
|
309
562
|
|
|
310
|
-
|
|
563
|
+
next.channels.bncr = bncrCfg;
|
|
311
564
|
|
|
312
565
|
if (added.length === 0) {
|
|
313
566
|
console.log('Minimal bncr config already present. No changes made.');
|
|
314
567
|
return;
|
|
315
568
|
}
|
|
316
569
|
|
|
317
|
-
await api.runtime.config.writeConfigFile(
|
|
570
|
+
await api.runtime.config.writeConfigFile(next);
|
|
318
571
|
console.log('Seeded minimal bncr config at channels.bncr.');
|
|
319
572
|
console.log(`Added missing fields: ${added.join(', ')}`);
|
|
320
573
|
console.log('Restart the gateway to apply changes.');
|
|
@@ -324,6 +577,9 @@ const registerBncrCli = (api: OpenClawPluginApi & { registerCli?: (...args: any[
|
|
|
324
577
|
);
|
|
325
578
|
};
|
|
326
579
|
|
|
580
|
+
const shouldSkipNonRuntimeRegister = (mode?: string) =>
|
|
581
|
+
mode === 'cli-metadata' || mode === 'discovery';
|
|
582
|
+
|
|
327
583
|
const plugin = {
|
|
328
584
|
id: 'bncr',
|
|
329
585
|
name: 'Bncr',
|
|
@@ -333,14 +589,31 @@ const plugin = {
|
|
|
333
589
|
api: OpenClawPluginApi & { registerCli?: (...args: any[]) => void; registrationMode?: string },
|
|
334
590
|
) {
|
|
335
591
|
registerBncrCli(api);
|
|
336
|
-
if (api.registrationMode
|
|
592
|
+
if (shouldSkipNonRuntimeRegister(api.registrationMode)) return;
|
|
337
593
|
|
|
338
594
|
const meta = getRegisterMeta(api);
|
|
339
|
-
|
|
595
|
+
meta.registrationMode = api.registrationMode;
|
|
596
|
+
const globalTrace = getGlobalRegisterTrace();
|
|
597
|
+
const previousApiInstanceId = globalTrace.lastApiInstanceId;
|
|
598
|
+
const previousRegistryFingerprint = globalTrace.lastRegistryFingerprint;
|
|
599
|
+
const apiInstanceId = meta.apiInstanceId || 'unknown';
|
|
600
|
+
const registryFingerprint = meta.registryFingerprint || 'unknown';
|
|
601
|
+
const sameApiAsPrevious = previousApiInstanceId === apiInstanceId;
|
|
602
|
+
const sameRegistryAsPrevious = previousRegistryFingerprint === registryFingerprint;
|
|
603
|
+
const firstSeenApi = !globalTrace.seenApiInstanceIds.has(apiInstanceId);
|
|
604
|
+
const firstSeenRegistry = !globalTrace.seenRegistryFingerprints.has(registryFingerprint);
|
|
605
|
+
|
|
606
|
+
const { bridge, runtime, created, rebuilt, owner, previousOwner } = getBridgeSingleton(api);
|
|
607
|
+
getGatewayRuntime().currentBridge = bridge;
|
|
608
|
+
|
|
609
|
+
globalTrace.seenApiInstanceIds.add(apiInstanceId);
|
|
610
|
+
globalTrace.seenRegistryFingerprints.add(registryFingerprint);
|
|
611
|
+
globalTrace.lastApiInstanceId = apiInstanceId;
|
|
612
|
+
globalTrace.lastRegistryFingerprint = registryFingerprint;
|
|
340
613
|
bridge.noteRegister?.({
|
|
341
614
|
source: '~/.openclaw/workspace/plugins/bncr/index.ts',
|
|
342
615
|
pluginVersion,
|
|
343
|
-
apiRebound: !created,
|
|
616
|
+
apiRebound: !created && !rebuilt,
|
|
344
617
|
apiInstanceId: meta.apiInstanceId,
|
|
345
618
|
registryFingerprint: meta.registryFingerprint,
|
|
346
619
|
});
|
|
@@ -355,12 +628,22 @@ const plugin = {
|
|
|
355
628
|
);
|
|
356
629
|
};
|
|
357
630
|
|
|
358
|
-
debugLog(
|
|
359
|
-
|
|
631
|
+
debugLog(
|
|
632
|
+
`register begin bridge=${bridge.getBridgeId?.() || 'unknown'} created=${created} rebuilt=${rebuilt} ` +
|
|
633
|
+
`ownerApi=${owner.apiInstanceId} ownerRegistry=${owner.registryFingerprint} ` +
|
|
634
|
+
`previousOwnerApi=${previousOwner?.apiInstanceId || 'none'} previousOwnerRegistry=${previousOwner?.registryFingerprint || 'none'}`,
|
|
635
|
+
);
|
|
636
|
+
debugLog(
|
|
637
|
+
`register classify mode=${meta.registrationMode || 'unknown'} api=${apiInstanceId} registry=${registryFingerprint} ` +
|
|
638
|
+
`sameApiAsPrevious=${sameApiAsPrevious} sameRegistryAsPrevious=${sameRegistryAsPrevious} ` +
|
|
639
|
+
`firstSeenApi=${firstSeenApi} firstSeenRegistry=${firstSeenRegistry}`,
|
|
640
|
+
);
|
|
641
|
+
if (!created && !rebuilt) debugLog('bridge api rebound');
|
|
642
|
+
if (rebuilt) debugLog('bridge rebuilt due to owner/runtime change');
|
|
360
643
|
|
|
361
644
|
const resolveDebug = async () => {
|
|
362
645
|
try {
|
|
363
|
-
const cfg =
|
|
646
|
+
const cfg = api.runtime.config.current();
|
|
364
647
|
return Boolean((cfg as any)?.channels?.bncr?.debug?.verbose);
|
|
365
648
|
} catch {
|
|
366
649
|
return false;
|
|
@@ -390,61 +673,16 @@ const plugin = {
|
|
|
390
673
|
debugLog('register channel skip (already registered on this api)');
|
|
391
674
|
}
|
|
392
675
|
|
|
393
|
-
ensureGatewayMethodRegistered(
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
);
|
|
399
|
-
ensureGatewayMethodRegistered(
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
debugLog,
|
|
404
|
-
);
|
|
405
|
-
ensureGatewayMethodRegistered(
|
|
406
|
-
api,
|
|
407
|
-
'bncr.activity',
|
|
408
|
-
(opts) => bridge.handleActivity(opts),
|
|
409
|
-
debugLog,
|
|
410
|
-
);
|
|
411
|
-
ensureGatewayMethodRegistered(api, 'bncr.ack', (opts) => bridge.handleAck(opts), debugLog);
|
|
412
|
-
ensureGatewayMethodRegistered(
|
|
413
|
-
api,
|
|
414
|
-
'bncr.diagnostics',
|
|
415
|
-
(opts) => bridge.handleDiagnostics(opts),
|
|
416
|
-
debugLog,
|
|
417
|
-
);
|
|
418
|
-
ensureGatewayMethodRegistered(
|
|
419
|
-
api,
|
|
420
|
-
'bncr.file.init',
|
|
421
|
-
(opts) => bridge.handleFileInit(opts),
|
|
422
|
-
debugLog,
|
|
423
|
-
);
|
|
424
|
-
ensureGatewayMethodRegistered(
|
|
425
|
-
api,
|
|
426
|
-
'bncr.file.chunk',
|
|
427
|
-
(opts) => bridge.handleFileChunk(opts),
|
|
428
|
-
debugLog,
|
|
429
|
-
);
|
|
430
|
-
ensureGatewayMethodRegistered(
|
|
431
|
-
api,
|
|
432
|
-
'bncr.file.complete',
|
|
433
|
-
(opts) => bridge.handleFileComplete(opts),
|
|
434
|
-
debugLog,
|
|
435
|
-
);
|
|
436
|
-
ensureGatewayMethodRegistered(
|
|
437
|
-
api,
|
|
438
|
-
'bncr.file.abort',
|
|
439
|
-
(opts) => bridge.handleFileAbort(opts),
|
|
440
|
-
debugLog,
|
|
441
|
-
);
|
|
442
|
-
ensureGatewayMethodRegistered(
|
|
443
|
-
api,
|
|
444
|
-
'bncr.file.ack',
|
|
445
|
-
(opts) => bridge.handleFileAck(opts),
|
|
446
|
-
debugLog,
|
|
447
|
-
);
|
|
676
|
+
ensureGatewayMethodRegistered(api, 'bncr.connect', debugLog);
|
|
677
|
+
ensureGatewayMethodRegistered(api, 'bncr.inbound', debugLog);
|
|
678
|
+
ensureGatewayMethodRegistered(api, 'bncr.activity', debugLog);
|
|
679
|
+
ensureGatewayMethodRegistered(api, 'bncr.ack', debugLog);
|
|
680
|
+
ensureGatewayMethodRegistered(api, 'bncr.diagnostics', debugLog);
|
|
681
|
+
ensureGatewayMethodRegistered(api, 'bncr.file.init', debugLog);
|
|
682
|
+
ensureGatewayMethodRegistered(api, 'bncr.file.chunk', debugLog);
|
|
683
|
+
ensureGatewayMethodRegistered(api, 'bncr.file.complete', debugLog);
|
|
684
|
+
ensureGatewayMethodRegistered(api, 'bncr.file.abort', debugLog);
|
|
685
|
+
ensureGatewayMethodRegistered(api, 'bncr.file.ack', debugLog);
|
|
448
686
|
debugLog('register done');
|
|
449
687
|
},
|
|
450
688
|
};
|
package/openclaw.plugin.json
CHANGED
|
@@ -36,5 +36,64 @@
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
+
},
|
|
40
|
+
"channelConfigs": {
|
|
41
|
+
"bncr": {
|
|
42
|
+
"schema": {
|
|
43
|
+
"type": "object",
|
|
44
|
+
"additionalProperties": true,
|
|
45
|
+
"properties": {
|
|
46
|
+
"enabled": { "type": "boolean" },
|
|
47
|
+
"dmPolicy": {
|
|
48
|
+
"type": "string",
|
|
49
|
+
"enum": ["open", "allowlist", "disabled"]
|
|
50
|
+
},
|
|
51
|
+
"groupPolicy": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"enum": ["open", "allowlist", "disabled"]
|
|
54
|
+
},
|
|
55
|
+
"allowFrom": {
|
|
56
|
+
"type": "array",
|
|
57
|
+
"items": { "type": "string" }
|
|
58
|
+
},
|
|
59
|
+
"groupAllowFrom": {
|
|
60
|
+
"type": "array",
|
|
61
|
+
"items": { "type": "string" }
|
|
62
|
+
},
|
|
63
|
+
"debug": {
|
|
64
|
+
"type": "object",
|
|
65
|
+
"additionalProperties": true,
|
|
66
|
+
"properties": {
|
|
67
|
+
"verbose": {
|
|
68
|
+
"type": "boolean",
|
|
69
|
+
"default": false,
|
|
70
|
+
"description": "Enable verbose debug logs for bncr channel runtime."
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"allowTool": {
|
|
75
|
+
"type": "boolean",
|
|
76
|
+
"default": false,
|
|
77
|
+
"description": "Allow tool messages to be forwarded when streaming is enabled. Defaults to false; only explicit true enables forwarding. When enabled, bncr also requests upstream tool summaries/results."
|
|
78
|
+
},
|
|
79
|
+
"requireMention": {
|
|
80
|
+
"type": "boolean",
|
|
81
|
+
"default": false,
|
|
82
|
+
"description": "Whether group messages must explicitly mention the bot before bncr handles them. Default false. Current version keeps this as a reserved field and does not enforce it yet."
|
|
83
|
+
},
|
|
84
|
+
"accounts": {
|
|
85
|
+
"type": "object",
|
|
86
|
+
"additionalProperties": {
|
|
87
|
+
"type": "object",
|
|
88
|
+
"additionalProperties": true,
|
|
89
|
+
"properties": {
|
|
90
|
+
"enabled": { "type": "boolean" },
|
|
91
|
+
"name": { "type": "string" }
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
39
98
|
}
|
|
40
99
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xmoxmo/bncr",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"openclaw.plugin.json",
|
|
21
21
|
"README.md",
|
|
22
22
|
"LICENSE",
|
|
23
|
-
"src",
|
|
24
|
-
"scripts"
|
|
23
|
+
"src/**/*.ts",
|
|
24
|
+
"scripts/**/*.mjs"
|
|
25
25
|
],
|
|
26
26
|
"scripts": {
|
|
27
27
|
"selfcheck": "node ./scripts/selfcheck.mjs",
|