@xmoxmo/bncr 0.4.1 → 0.4.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/dist/index.js CHANGED
@@ -1,73 +1,129 @@
1
1
  // index.ts
2
- import { execFileSync } from "node:child_process";
3
- import fs from "node:fs";
4
- import { createRequire } from "node:module";
5
- import path from "node:path";
6
- import { fileURLToPath } from "node:url";
2
+ import path3 from "node:path";
7
3
 
8
- // src/core/config-schema.ts
9
- var BncrConfigSchema = {
10
- schema: {
11
- type: "object",
12
- additionalProperties: true,
13
- properties: {
14
- enabled: { type: "boolean" },
15
- dmPolicy: {
16
- type: "string",
17
- enum: ["open", "allowlist", "disabled"]
18
- },
19
- groupPolicy: {
20
- type: "string",
21
- enum: ["open", "allowlist", "disabled"]
22
- },
23
- allowFrom: {
24
- type: "array",
25
- items: { type: "string" }
26
- },
27
- groupAllowFrom: {
28
- type: "array",
29
- items: { type: "string" }
30
- },
31
- debug: {
32
- type: "object",
33
- additionalProperties: true,
34
- properties: {
35
- verbose: {
36
- type: "boolean",
37
- default: false,
38
- description: "Enable verbose debug logs for bncr channel runtime."
39
- }
40
- }
41
- },
42
- allowTool: {
43
- type: "boolean",
44
- default: false,
45
- 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."
46
- },
47
- requireMention: {
48
- type: "boolean",
49
- default: false,
50
- 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."
51
- },
52
- outboundRequireAck: {
53
- type: "boolean",
54
- default: true,
55
- description: "Whether outbound text waits for bncr.ack before leaving the retry queue. Default true to preserve current ack/dead-letter behavior."
56
- },
57
- accounts: {
58
- type: "object",
59
- additionalProperties: {
60
- type: "object",
61
- additionalProperties: true,
62
- properties: {
63
- enabled: { type: "boolean" },
64
- name: { type: "string" }
65
- }
66
- }
67
- }
4
+ // src/bootstrap/channel-plugin-runtime.ts
5
+ function createDynamicChannelPlugin(args) {
6
+ const { loaded, getCurrentBridge: getCurrentBridge2 } = args;
7
+ const base = loaded.createBncrChannelPlugin(() => getCurrentBridge2());
8
+ const plugin2 = { ...base };
9
+ const outbound = base.outbound;
10
+ const baseStatus = base.status;
11
+ const baseGateway = base.gateway;
12
+ plugin2.outbound = {
13
+ ...outbound,
14
+ sendText: (async (ctx) => await getCurrentBridge2().channelSendText(ctx)),
15
+ sendMedia: (async (ctx) => await getCurrentBridge2().channelSendMedia(ctx))
16
+ };
17
+ plugin2.status = {
18
+ ...baseStatus,
19
+ buildChannelSummary: async ({ defaultAccountId }) => getCurrentBridge2().getChannelSummary(defaultAccountId || "Primary"),
20
+ buildAccountSnapshot: async ({ account, runtime: runtime2 }) => {
21
+ const bridgeNow = getCurrentBridge2();
22
+ return baseStatus.buildAccountSnapshot({
23
+ account,
24
+ runtime: runtime2 || bridgeNow.getAccountRuntimeSnapshot(account?.accountId || "Primary")
25
+ });
26
+ },
27
+ resolveAccountState: ({
28
+ enabled,
29
+ configured,
30
+ account,
31
+ cfg,
32
+ runtime: runtime2
33
+ }) => {
34
+ const bridgeNow = getCurrentBridge2();
35
+ return baseStatus.resolveAccountState({
36
+ enabled,
37
+ configured,
38
+ account,
39
+ cfg,
40
+ runtime: runtime2 || bridgeNow.getAccountRuntimeSnapshot(account?.accountId || "Primary")
41
+ });
68
42
  }
43
+ };
44
+ plugin2.gateway = {
45
+ ...baseGateway,
46
+ startAccount: (ctx) => getCurrentBridge2().channelStartAccount(
47
+ ctx
48
+ ),
49
+ stopAccount: (ctx) => getCurrentBridge2().channelStopAccount(
50
+ ctx
51
+ )
52
+ };
53
+ return plugin2;
54
+ }
55
+
56
+ // src/openclaw/config-runtime.ts
57
+ function resolveConfigApi(api) {
58
+ const config = api?.runtime?.config;
59
+ if (!config || typeof config !== "object") {
60
+ throw new Error("OpenClaw runtime config API is unavailable");
69
61
  }
70
- };
62
+ return config;
63
+ }
64
+ function getOpenClawRuntimeConfig(api) {
65
+ const config = resolveConfigApi(api);
66
+ if (typeof config.current === "function") return config.current();
67
+ if (typeof config.get === "function") return config.get();
68
+ throw new Error("OpenClaw runtime config read API is unavailable");
69
+ }
70
+ async function mutateOpenClawRuntimeConfigFile(api, params) {
71
+ const config = resolveConfigApi(api);
72
+ if (typeof config.mutateConfigFile !== "function") {
73
+ throw new Error("OpenClaw runtime config mutate API is unavailable");
74
+ }
75
+ return config.mutateConfigFile(params);
76
+ }
77
+
78
+ // src/bootstrap/cli.ts
79
+ var isPlainObject = (value) => !!value && typeof value === "object" && !Array.isArray(value);
80
+ function registerBncrCli(api) {
81
+ if (typeof api.registerCli !== "function") return;
82
+ api.registerCli(
83
+ ({ program }) => {
84
+ const bncr = program.command("bncr").description("Bncr channel utilities");
85
+ bncr.command("miniconfig").description(
86
+ "Seed minimal channels.bncr config (adds enabled=true and allowTool=false only when missing)"
87
+ ).action(async () => {
88
+ const cfg = getOpenClawRuntimeConfig(api) || {};
89
+ const channels = isPlainObject(cfg.channels) ? cfg.channels : {};
90
+ const existing = isPlainObject(channels.bncr) ? channels.bncr : {};
91
+ const added = [];
92
+ if (existing.enabled === void 0) {
93
+ added.push("enabled=true");
94
+ }
95
+ if (existing.allowTool === void 0) {
96
+ added.push("allowTool=false");
97
+ }
98
+ if (added.length === 0) {
99
+ console.log("Minimal bncr config already present. No changes made.");
100
+ return;
101
+ }
102
+ await mutateOpenClawRuntimeConfigFile(api, {
103
+ afterWrite: { mode: "auto" },
104
+ mutate(draft) {
105
+ if (!isPlainObject(draft.channels)) draft.channels = {};
106
+ const draftChannels = draft.channels;
107
+ const draftExisting = isPlainObject(draftChannels.bncr) ? draftChannels.bncr : {};
108
+ const draftBncrCfg = { ...draftExisting };
109
+ if (draftBncrCfg.enabled === void 0) {
110
+ draftBncrCfg.enabled = true;
111
+ }
112
+ if (draftBncrCfg.allowTool === void 0) {
113
+ draftBncrCfg.allowTool = false;
114
+ }
115
+ draftChannels.bncr = draftBncrCfg;
116
+ }
117
+ });
118
+ console.log("Seeded minimal bncr config at channels.bncr.");
119
+ console.log(`Added missing fields: ${added.join(", ")}`);
120
+ console.log("Gateway will apply the config using the host afterWrite policy.");
121
+ });
122
+ },
123
+ { commands: ["bncr"] }
124
+ );
125
+ }
126
+ var shouldSkipNonRuntimeRegister = (mode) => mode === "cli-metadata" || mode === "discovery";
71
127
 
72
128
  // src/core/logging.ts
73
129
  var BNCR_PREFIX = "[bncr]";
@@ -102,34 +158,244 @@ function emitBncrLogLine(level, line, options, isDebugEnabled) {
102
158
  emitConsole(resolveConsoleMethod(level), normalizeBncrLogLine(line));
103
159
  }
104
160
 
105
- // src/openclaw/config-runtime.ts
106
- function resolveConfigApi(api) {
107
- const config = api?.runtime?.config;
108
- if (!config) throw new Error("OpenClaw runtime config API is unavailable");
109
- return config;
161
+ // src/bootstrap/register-runtime-gateway.ts
162
+ function createBncrGatewayMethodRegistry(runtime2) {
163
+ const dispatchGatewayMethod = (name, opts) => {
164
+ const gatewayRuntime = runtime2.getGatewayRuntime();
165
+ const bridge = gatewayRuntime.currentBridge;
166
+ if (!bridge) {
167
+ throw new Error(`bncr gateway runtime unavailable for ${name}`);
168
+ }
169
+ try {
170
+ return runtime2.gatewayMethodDispatchers[name](bridge, opts);
171
+ } catch (error) {
172
+ const state = runtime2.getBridgeRegisterStateCarrier(bridge);
173
+ const detail = error instanceof Error ? { name: error.name, message: error.message, stack: error.stack || null } : { name: "NonError", message: String(error), stack: null };
174
+ emitBncrLogLine(
175
+ "error",
176
+ `[bncr] gateway method error method=${name}|bridgeId=${state.getBridgeId?.() || "-"}|gatewayPid=${state.gatewayPid ?? "-"}|err=${detail.message}`
177
+ );
178
+ emitBncrLogLine(
179
+ "error",
180
+ `[bncr] gateway method error ${JSON.stringify({
181
+ method: name,
182
+ bridgeId: state.getBridgeId?.() || null,
183
+ gatewayPid: state.gatewayPid ?? null,
184
+ detail
185
+ })}`,
186
+ { debugOnly: true },
187
+ () => false
188
+ );
189
+ throw error;
190
+ }
191
+ };
192
+ const mirrorGatewayMethodForMockApi = (api, name) => {
193
+ if (!Array.isArray(api?.methods)) return;
194
+ if (api.methods.some((item) => item?.name === name)) return;
195
+ api.methods.push({ name, handler: (opts) => dispatchGatewayMethod(name, opts) });
196
+ };
197
+ const ensureGatewayMethodRegistered2 = (api, name, debugLog) => {
198
+ const meta = runtime2.getRegisterMeta(api);
199
+ const gatewayRuntime = runtime2.getGatewayRuntime();
200
+ const registryFingerprint = meta.registryFingerprint || runtime2.getRegistryFingerprint(api);
201
+ let registryMethods = gatewayRuntime.registeredMethodsByRegistry.get(registryFingerprint);
202
+ if (!registryMethods) {
203
+ registryMethods = /* @__PURE__ */ new Set();
204
+ gatewayRuntime.registeredMethodsByRegistry.set(registryFingerprint, registryMethods);
205
+ }
206
+ if (meta.methods?.has(name)) {
207
+ debugLog(`register method skip ${name} (already registered on this api)`);
208
+ return;
209
+ }
210
+ if (registryMethods.has(name)) {
211
+ mirrorGatewayMethodForMockApi(api, name);
212
+ meta.methods?.add(name);
213
+ debugLog(`register method reuse ${name} (already registered in registry)`);
214
+ return;
215
+ }
216
+ api.registerGatewayMethod(
217
+ name,
218
+ (opts) => dispatchGatewayMethod(name, opts)
219
+ );
220
+ mirrorGatewayMethodForMockApi(api, name);
221
+ registryMethods.add(name);
222
+ meta.methods?.add(name);
223
+ debugLog(`register method ok ${name}`);
224
+ };
225
+ return {
226
+ dispatchGatewayMethod,
227
+ ensureGatewayMethodRegistered: ensureGatewayMethodRegistered2
228
+ };
110
229
  }
111
- function getOpenClawRuntimeConfig(api) {
112
- const config = resolveConfigApi(api);
113
- if (typeof config.current === "function") return config.current();
114
- if (typeof config.get === "function") return config.get();
115
- throw new Error("OpenClaw runtime config read API is unavailable");
230
+
231
+ // src/bootstrap/register-runtime-helpers.ts
232
+ function getProcessOwnerApiInstanceId(args) {
233
+ return args.serviceOwnerApiInstanceId || args.channelOwnerApiInstanceId || void 0;
116
234
  }
117
- async function mutateOpenClawRuntimeConfigFile(api, params) {
118
- const config = resolveConfigApi(api);
119
- if (typeof config.mutateConfigFile !== "function") {
120
- throw new Error("OpenClaw runtime config mutate API is unavailable");
235
+ function sameBridgeOwner(left, right) {
236
+ if (!left || !right) return false;
237
+ return left.moduleEpoch === right.moduleEpoch && left.bridgeFactoryId === right.bridgeFactoryId && left.apiInstanceId === right.apiInstanceId && left.registryFingerprint === right.registryFingerprint;
238
+ }
239
+ function snapshotBridgeRegisterState(bridge) {
240
+ if (!bridge) return null;
241
+ return {
242
+ registerCount: Number(bridge.registerCount || 0),
243
+ apiGeneration: Number(bridge.apiGeneration || 0),
244
+ firstRegisterAt: typeof bridge.firstRegisterAt === "number" ? bridge.firstRegisterAt : bridge.firstRegisterAt ?? null,
245
+ lastRegisterAt: typeof bridge.lastRegisterAt === "number" ? bridge.lastRegisterAt : bridge.lastRegisterAt ?? null,
246
+ lastApiRebindAt: typeof bridge.lastApiRebindAt === "number" ? bridge.lastApiRebindAt : bridge.lastApiRebindAt ?? null,
247
+ pluginSource: typeof bridge.pluginSource === "string" ? bridge.pluginSource : null,
248
+ pluginVersion: typeof bridge.pluginVersion === "string" ? bridge.pluginVersion : null,
249
+ lastApiInstanceId: typeof bridge.lastApiInstanceId === "string" ? bridge.lastApiInstanceId : null,
250
+ lastRegistryFingerprint: typeof bridge.lastRegistryFingerprint === "string" ? bridge.lastRegistryFingerprint : null,
251
+ lastDriftSnapshot: bridge.lastDriftSnapshot ?? null,
252
+ registerTraceRecent: Array.isArray(bridge.registerTraceRecent) ? bridge.registerTraceRecent.map((trace) => ({ ...trace })) : []
253
+ };
254
+ }
255
+ function hydrateBridgeRegisterState(bridge, snapshot) {
256
+ if (!snapshot) return bridge;
257
+ bridge.registerCount = snapshot.registerCount;
258
+ bridge.apiGeneration = snapshot.apiGeneration;
259
+ bridge.firstRegisterAt = snapshot.firstRegisterAt;
260
+ bridge.lastRegisterAt = snapshot.lastRegisterAt;
261
+ bridge.lastApiRebindAt = snapshot.lastApiRebindAt;
262
+ bridge.pluginSource = snapshot.pluginSource;
263
+ bridge.pluginVersion = snapshot.pluginVersion;
264
+ bridge.lastApiInstanceId = snapshot.lastApiInstanceId;
265
+ bridge.lastRegistryFingerprint = snapshot.lastRegistryFingerprint;
266
+ bridge.lastDriftSnapshot = snapshot.lastDriftSnapshot;
267
+ bridge.registerTraceRecent = snapshot.registerTraceRecent.map((trace) => ({ ...trace }));
268
+ return bridge;
269
+ }
270
+ function shouldAdoptProcessOwner(args) {
271
+ const existingOwnerApiInstanceId = getProcessOwnerApiInstanceId({
272
+ serviceOwnerApiInstanceId: args.serviceOwnerApiInstanceId,
273
+ channelOwnerApiInstanceId: args.channelOwnerApiInstanceId
274
+ });
275
+ const hasSingletonOwner = Boolean(args.serviceRegistered) || Boolean(args.channelRegistered);
276
+ if (!hasSingletonOwner) {
277
+ return {
278
+ adoptOwner: true,
279
+ existingOwnerApiInstanceId,
280
+ reason: "no-singleton-owner"
281
+ };
121
282
  }
122
- return config.mutateConfigFile(params);
283
+ if (existingOwnerApiInstanceId && existingOwnerApiInstanceId === args.apiInstanceId) {
284
+ return {
285
+ adoptOwner: true,
286
+ existingOwnerApiInstanceId,
287
+ reason: "same-owner-api"
288
+ };
289
+ }
290
+ return {
291
+ adoptOwner: false,
292
+ existingOwnerApiInstanceId,
293
+ reason: "singleton-owned-by-other-api"
294
+ };
123
295
  }
124
296
 
125
- // index.ts
126
- var pluginFile = fileURLToPath(import.meta.url);
127
- var pluginDir = path.dirname(pluginFile);
128
- var pluginRequire = createRequire(import.meta.url);
297
+ // src/bootstrap/register-runtime-singleton.ts
298
+ function isBridgeOwner(value) {
299
+ return Boolean(
300
+ value && typeof value === "object" && "moduleEpoch" in value && "bridgeFactoryId" in value && "apiInstanceId" in value && "registryFingerprint" in value
301
+ );
302
+ }
303
+ function getBridgeOwnedCarrier(bridge) {
304
+ return bridge;
305
+ }
306
+ function getBridgeRegisterStateCarrier(bridge) {
307
+ return bridge;
308
+ }
309
+ function createBncrBridgeSingletonManager(runtime2) {
310
+ const assignBridgeOwner = (bridge, owner) => {
311
+ getBridgeOwnedCarrier(bridge)[runtime2.bridgeOwnerSymbol] = owner;
312
+ return bridge;
313
+ };
314
+ const getBridgeSingleton2 = (api) => {
315
+ const loaded = runtime2.loadBncrRuntimeSync();
316
+ const g = globalThis;
317
+ const owner = runtime2.getBridgeOwner(api, loaded);
318
+ const previousOwnerRaw = g.__bncrBridge ? getBridgeOwnedCarrier(g.__bncrBridge)[runtime2.bridgeOwnerSymbol] : void 0;
319
+ const previousOwner = isBridgeOwner(previousOwnerRaw) ? previousOwnerRaw : void 0;
320
+ let created = false;
321
+ let rebuilt = false;
322
+ if (g.__bncrBridge) {
323
+ const mustRebuild = !sameBridgeOwner(previousOwner, owner) && (previousOwner?.moduleEpoch !== owner.moduleEpoch || previousOwner?.bridgeFactoryId !== owner.bridgeFactoryId || previousOwner?.registrationMode !== owner.registrationMode || previousOwner?.apiInstanceId !== owner.apiInstanceId || previousOwner?.registryFingerprint !== owner.registryFingerprint);
324
+ if (mustRebuild) {
325
+ const registerState = snapshotBridgeRegisterState(
326
+ getBridgeRegisterStateCarrier(g.__bncrBridge)
327
+ );
328
+ try {
329
+ g.__bncrBridge.stopService?.();
330
+ } catch {
331
+ }
332
+ const rebuiltBridge = assignBridgeOwner(
333
+ loaded.createBncrBridge(api, {
334
+ pluginRoot: runtime2.pluginRoot,
335
+ pluginFile: runtime2.pluginFile
336
+ }),
337
+ owner
338
+ );
339
+ hydrateBridgeRegisterState(getBridgeRegisterStateCarrier(rebuiltBridge), registerState);
340
+ g.__bncrBridge = rebuiltBridge;
341
+ created = true;
342
+ rebuilt = true;
343
+ } else {
344
+ g.__bncrBridge.bindApi?.(api);
345
+ assignBridgeOwner(g.__bncrBridge, owner);
346
+ }
347
+ } else {
348
+ g.__bncrBridge = assignBridgeOwner(
349
+ loaded.createBncrBridge(api, {
350
+ pluginRoot: runtime2.pluginRoot,
351
+ pluginFile: runtime2.pluginFile
352
+ }),
353
+ owner
354
+ );
355
+ created = true;
356
+ }
357
+ g.__bncrBridge?.bindRuntimePaths?.({
358
+ pluginRoot: runtime2.pluginRoot,
359
+ pluginFile: runtime2.pluginFile
360
+ });
361
+ return { bridge: g.__bncrBridge, runtime: loaded, created, rebuilt, owner, previousOwner };
362
+ };
363
+ const getExistingBridgeSingleton2 = () => {
364
+ const g = globalThis;
365
+ return g.__bncrBridge;
366
+ };
367
+ const getBridgeOwnerFromBridge2 = (bridge) => {
368
+ if (!bridge) return void 0;
369
+ const bridgeCarrier = getBridgeOwnedCarrier(bridge);
370
+ for (const symbol of Object.getOwnPropertySymbols(bridge)) {
371
+ const owner = bridgeCarrier[symbol];
372
+ if (isBridgeOwner(owner)) return owner;
373
+ }
374
+ return void 0;
375
+ };
376
+ return {
377
+ assignBridgeOwner,
378
+ getBridgeRegisterStateCarrier,
379
+ getBridgeSingleton: getBridgeSingleton2,
380
+ getExistingBridgeSingleton: getExistingBridgeSingleton2,
381
+ getBridgeOwnerFromBridge: getBridgeOwnerFromBridge2
382
+ };
383
+ }
384
+
385
+ // src/bootstrap/runtime-loader.ts
386
+ import fs2 from "node:fs";
387
+ import { createRequire } from "node:module";
388
+ import path2 from "node:path";
389
+ import { fileURLToPath } from "node:url";
390
+
391
+ // src/bootstrap/runtime-discovery.ts
392
+ import { execFileSync } from "node:child_process";
393
+ import fs from "node:fs";
394
+ import path from "node:path";
129
395
  var pluginPackageName = "@xmoxmo/bncr";
130
396
  var sdkCoreSpecifier = "openclaw/plugin-sdk/core";
131
397
  var linkType = process.platform === "win32" ? "junction" : "dir";
132
- var resolvePluginRoot = (filePath) => {
398
+ function resolveBncrPluginRoot(filePath) {
133
399
  let current = fs.existsSync(filePath) && fs.statSync(filePath).isDirectory() ? filePath : path.dirname(filePath);
134
400
  while (true) {
135
401
  const pkgPath = path.join(current, "package.json");
@@ -145,17 +411,8 @@ var resolvePluginRoot = (filePath) => {
145
411
  if (parent === current) return path.dirname(filePath);
146
412
  current = parent;
147
413
  }
148
- };
149
- var pluginRoot = resolvePluginRoot(pluginFile);
150
- var BNCR_REGISTER_META = /* @__PURE__ */ Symbol.for("bncr.register.meta");
151
- var BNCR_GLOBAL_REGISTER_TRACE = /* @__PURE__ */ Symbol.for("bncr.global.register.trace");
152
- var BNCR_BRIDGE_OWNER = /* @__PURE__ */ Symbol.for("bncr.bridge.owner");
153
- var BNCR_GATEWAY_RUNTIME = /* @__PURE__ */ Symbol.for("bncr.gateway.runtime");
154
- var MODULE_EPOCH = `${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
155
- var runtime = null;
156
- var identityIds = /* @__PURE__ */ new WeakMap();
157
- var identitySeq = 0;
158
- var tryExec = (command, args) => {
414
+ }
415
+ function tryExec(command, args) {
159
416
  try {
160
417
  return execFileSync(command, args, {
161
418
  encoding: "utf8",
@@ -164,8 +421,8 @@ var tryExec = (command, args) => {
164
421
  } catch {
165
422
  return "";
166
423
  }
167
- };
168
- var readOpenClawPackageName = (pkgPath) => {
424
+ }
425
+ function readOpenClawPackageName(pkgPath) {
169
426
  try {
170
427
  const raw = fs.readFileSync(pkgPath, "utf8");
171
428
  const parsed = JSON.parse(raw);
@@ -173,18 +430,20 @@ var readOpenClawPackageName = (pkgPath) => {
173
430
  } catch {
174
431
  return "";
175
432
  }
176
- };
177
- var readPluginVersion = (rootDir = pluginRoot) => {
178
- try {
179
- const raw = fs.readFileSync(path.join(rootDir, "package.json"), "utf8");
180
- const parsed = JSON.parse(raw);
181
- return typeof parsed?.version === "string" ? parsed.version : "unknown";
182
- } catch {
183
- return "unknown";
433
+ }
434
+ function unique(items) {
435
+ const seen = /* @__PURE__ */ new Set();
436
+ const out = [];
437
+ for (const item of items) {
438
+ if (!item) continue;
439
+ const normalized = path.normalize(item);
440
+ if (seen.has(normalized)) continue;
441
+ seen.add(normalized);
442
+ out.push(normalized);
184
443
  }
185
- };
186
- var pluginVersion = readPluginVersion();
187
- var findOpenClawPackageRoot = (startPath) => {
444
+ return out;
445
+ }
446
+ function findOpenClawPackageRoot(startPath) {
188
447
  let current = startPath;
189
448
  try {
190
449
  current = fs.realpathSync(startPath);
@@ -202,22 +461,10 @@ var findOpenClawPackageRoot = (startPath) => {
202
461
  cursor = parent;
203
462
  }
204
463
  return "";
205
- };
206
- var unique = (items) => {
207
- const seen = /* @__PURE__ */ new Set();
208
- const out = [];
209
- for (const item of items) {
210
- if (!item) continue;
211
- const normalized = path.normalize(item);
212
- if (seen.has(normalized)) continue;
213
- seen.add(normalized);
214
- out.push(normalized);
215
- }
216
- return out;
217
- };
218
- var collectOpenClawCandidates = () => {
464
+ }
465
+ function collectOpenClawCandidates(pluginDir2) {
219
466
  const directCandidates = [
220
- path.join(pluginDir, "node_modules", "openclaw"),
467
+ path.join(pluginDir2, "node_modules", "openclaw"),
221
468
  path.join("/usr/lib/node_modules", "openclaw"),
222
469
  path.join("/usr/local/lib/node_modules", "openclaw"),
223
470
  path.join("/opt/homebrew/lib/node_modules", "openclaw"),
@@ -241,17 +488,17 @@ var collectOpenClawCandidates = () => {
241
488
  const pkgJson = path.join(candidate, "package.json");
242
489
  return fs.existsSync(pkgJson) && readOpenClawPackageName(pkgJson) === "openclaw";
243
490
  });
244
- };
245
- var canResolveSdkCore = () => {
491
+ }
492
+ function canResolveSdkCore(pluginRequire2) {
246
493
  try {
247
- pluginRequire.resolve(sdkCoreSpecifier);
494
+ pluginRequire2.resolve(sdkCoreSpecifier);
248
495
  return true;
249
496
  } catch {
250
497
  return false;
251
498
  }
252
- };
253
- var ensurePluginNodeModulesLink = (targetRoot) => {
254
- const nodeModulesDir = path.join(pluginDir, "node_modules");
499
+ }
500
+ function ensurePluginNodeModulesLink(pluginDir2, targetRoot) {
501
+ const nodeModulesDir = path.join(pluginDir2, "node_modules");
255
502
  const linkPath = path.join(nodeModulesDir, "openclaw");
256
503
  fs.mkdirSync(nodeModulesDir, { recursive: true });
257
504
  try {
@@ -268,22 +515,25 @@ var ensurePluginNodeModulesLink = (targetRoot) => {
268
515
  } catch {
269
516
  }
270
517
  fs.symlinkSync(targetRoot, linkPath, linkType);
271
- };
272
- var runtimeSourceDir = (() => {
273
- const direct = path.join(pluginDir, "src");
518
+ }
519
+ function resolveBncrRuntimeSourceDir(pluginDir2) {
520
+ const pluginRoot2 = resolveBncrPluginRoot(pluginDir2);
521
+ const rootSource = path.join(pluginRoot2, "src");
522
+ if (fs.existsSync(path.join(rootSource, "channel.ts"))) return rootSource;
523
+ const direct = path.join(pluginDir2, "src");
274
524
  if (fs.existsSync(path.join(direct, "channel.ts"))) return direct;
275
- const parent = path.join(pluginDir, "..", "src");
525
+ const parent = path.join(pluginDir2, "..", "src");
276
526
  if (fs.existsSync(path.join(parent, "channel.ts"))) return parent;
277
527
  return direct;
278
- })();
279
- var ensureOpenClawSdkResolution = () => {
280
- if (canResolveSdkCore()) return;
528
+ }
529
+ function ensureBncrOpenClawSdkResolution(pluginDir2, pluginRequire2) {
530
+ if (canResolveSdkCore(pluginRequire2)) return;
281
531
  let lastError = "";
282
- const candidates = collectOpenClawCandidates();
532
+ const candidates = collectOpenClawCandidates(pluginDir2);
283
533
  for (const candidate of candidates) {
284
534
  try {
285
- ensurePluginNodeModulesLink(candidate);
286
- if (canResolveSdkCore()) return;
535
+ ensurePluginNodeModulesLink(pluginDir2, candidate);
536
+ if (canResolveSdkCore(pluginRequire2)) return;
287
537
  } catch (error) {
288
538
  lastError = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
289
539
  }
@@ -291,14 +541,47 @@ var ensureOpenClawSdkResolution = () => {
291
541
  const suffix = candidates.length ? ` Tried candidates: ${candidates.join(", ")}.` : " No openclaw package root candidates were found from npm root, NODE_PATH, common global paths, or the openclaw binary path.";
292
542
  const extra = lastError ? ` Last repair error: ${lastError}.` : "";
293
543
  throw new Error(
294
- `bncr failed to resolve ${sdkCoreSpecifier} from ${pluginDir}.${suffix}${extra} You can repair manually with: mkdir -p ${path.join(pluginDir, "node_modules")} && ln -s "$(npm root -g)/openclaw" ${path.join(pluginDir, "node_modules", "openclaw")}`
544
+ `bncr failed to resolve ${sdkCoreSpecifier} from ${pluginDir2}.${suffix}${extra} You can repair manually with: mkdir -p ${path.join(pluginDir2, "node_modules")} && ln -s "$(npm root -g)/openclaw" ${path.join(pluginDir2, "node_modules", "openclaw")}`
295
545
  );
546
+ }
547
+
548
+ // src/bootstrap/runtime-loader.ts
549
+ function resolvePluginEntryFileFromModule(moduleUrl) {
550
+ const currentFile = fileURLToPath(moduleUrl);
551
+ const pluginRoot2 = resolveBncrPluginRoot(currentFile);
552
+ const currentDir = path2.dirname(currentFile);
553
+ const distEntry = path2.join(pluginRoot2, "dist", "index.js");
554
+ if (currentFile === distEntry && fs2.existsSync(distEntry)) return distEntry;
555
+ const sourceEntry = path2.join(pluginRoot2, "index.ts");
556
+ if (fs2.existsSync(sourceEntry)) return sourceEntry;
557
+ if (fs2.existsSync(distEntry)) return distEntry;
558
+ if (path2.basename(currentDir) === "dist") return distEntry;
559
+ return sourceEntry;
560
+ }
561
+ function resolvePluginEntryFile() {
562
+ return resolvePluginEntryFileFromModule(import.meta.url);
563
+ }
564
+ var pluginFile = resolvePluginEntryFile();
565
+ var pluginDir = path2.dirname(pluginFile);
566
+ var pluginRequire = createRequire(pluginFile);
567
+ var pluginRoot = resolveBncrPluginRoot(pluginFile);
568
+ var runtimeSourceDir = resolveBncrRuntimeSourceDir(pluginDir);
569
+ var runtime = null;
570
+ var readPluginVersion = (rootDir = pluginRoot) => {
571
+ try {
572
+ const raw = fs2.readFileSync(path2.join(rootDir, "package.json"), "utf8");
573
+ const parsed = JSON.parse(raw);
574
+ return typeof parsed?.version === "string" ? parsed.version : "unknown";
575
+ } catch {
576
+ return "unknown";
577
+ }
296
578
  };
297
- var loadRuntimeSync = () => {
579
+ var pluginVersion = readPluginVersion();
580
+ var loadBncrRuntimeSync = () => {
298
581
  if (runtime) return runtime;
299
- ensureOpenClawSdkResolution();
582
+ ensureBncrOpenClawSdkResolution(pluginDir, pluginRequire);
300
583
  try {
301
- const mod = pluginRequire(path.join(runtimeSourceDir, "channel.ts"));
584
+ const mod = pluginRequire(path2.join(runtimeSourceDir, "channel.ts"));
302
585
  runtime = {
303
586
  createBncrBridge: mod.createBncrBridge,
304
587
  createBncrChannelPlugin: mod.createBncrChannelPlugin
@@ -311,10 +594,19 @@ var loadRuntimeSync = () => {
311
594
  );
312
595
  }
313
596
  };
597
+
598
+ // src/bootstrap/register-runtime.ts
599
+ var registerMetaSymbol = /* @__PURE__ */ Symbol.for("bncr.register.meta");
600
+ var globalRegisterTraceSymbol = /* @__PURE__ */ Symbol.for("bncr.global.register.trace");
601
+ var bridgeOwnerSymbol = /* @__PURE__ */ Symbol.for("bncr.bridge.owner");
602
+ var gatewayRuntimeSymbol = /* @__PURE__ */ Symbol.for("bncr.gateway.runtime");
603
+ var moduleEpoch = `${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
604
+ var identityIds = /* @__PURE__ */ new WeakMap();
605
+ var identitySeq = 0;
314
606
  var getIdentityId = (obj, prefix) => {
315
607
  const existing = identityIds.get(obj);
316
608
  if (existing) return existing;
317
- const next = `${prefix}_${MODULE_EPOCH}_${++identitySeq}`;
609
+ const next = `${prefix}_${moduleEpoch}_${++identitySeq}`;
318
610
  identityIds.set(obj, next);
319
611
  return next;
320
612
  };
@@ -324,324 +616,194 @@ var getRegistryFingerprint = (api) => {
324
616
  const methodId = getIdentityId(api.registerGatewayMethod, "mth");
325
617
  return `${serviceId}:${channelId}:${methodId}`;
326
618
  };
327
- var getRegisterMeta = (api) => {
328
- const host = api;
329
- if (!host[BNCR_REGISTER_META]) {
330
- host[BNCR_REGISTER_META] = { methods: /* @__PURE__ */ new Set() };
331
- }
332
- if (!host[BNCR_REGISTER_META].methods) {
333
- host[BNCR_REGISTER_META].methods = /* @__PURE__ */ new Set();
334
- }
335
- if (!host[BNCR_REGISTER_META].apiInstanceId) {
336
- host[BNCR_REGISTER_META].apiInstanceId = getIdentityId(api, "api");
337
- }
338
- if (!host[BNCR_REGISTER_META].registryFingerprint) {
339
- host[BNCR_REGISTER_META].registryFingerprint = getRegistryFingerprint(api);
340
- }
341
- return host[BNCR_REGISTER_META];
342
- };
343
619
  var getProcessStore = () => {
344
620
  const p = process;
345
621
  return p;
346
622
  };
347
- var getGlobalRegisterTrace = () => {
348
- const p = getProcessStore();
349
- if (!p[BNCR_GLOBAL_REGISTER_TRACE]) {
350
- p[BNCR_GLOBAL_REGISTER_TRACE] = {
351
- seenRegistryFingerprints: /* @__PURE__ */ new Set(),
352
- seenApiInstanceIds: /* @__PURE__ */ new Set()
353
- };
354
- }
355
- return p[BNCR_GLOBAL_REGISTER_TRACE];
356
- };
357
- var getGatewayRuntime = () => {
358
- const p = getProcessStore();
359
- if (!p[BNCR_GATEWAY_RUNTIME]) {
360
- p[BNCR_GATEWAY_RUNTIME] = {
361
- registeredMethodsByRegistry: /* @__PURE__ */ new Map(),
362
- serviceRegistered: false,
363
- channelRegistered: false
364
- };
365
- }
366
- return p[BNCR_GATEWAY_RUNTIME];
367
- };
368
- var getProcessOwnerApiInstanceId = (gatewayRuntime) => gatewayRuntime.serviceOwnerApiInstanceId || gatewayRuntime.channelOwnerApiInstanceId || void 0;
369
- var shouldAdoptProcessOwner = (apiInstanceId, gatewayRuntime) => {
370
- const existingOwnerApiInstanceId = getProcessOwnerApiInstanceId(gatewayRuntime);
371
- const hasSingletonOwner = Boolean(gatewayRuntime.serviceRegistered) || Boolean(gatewayRuntime.channelRegistered);
372
- if (!hasSingletonOwner) {
373
- return {
374
- adoptOwner: true,
375
- existingOwnerApiInstanceId,
376
- reason: "no-singleton-owner"
377
- };
378
- }
379
- if (existingOwnerApiInstanceId && existingOwnerApiInstanceId === apiInstanceId) {
623
+ function createBncrRegisterRuntime() {
624
+ const gatewayMethodDispatchers = {
625
+ "bncr.connect": (bridge, opts) => bridge.handleConnect(opts),
626
+ "bncr.inbound": (bridge, opts) => bridge.handleInbound(opts),
627
+ "bncr.activity": (bridge, opts) => bridge.handleActivity(opts),
628
+ "bncr.ack": (bridge, opts) => bridge.handleAck(opts),
629
+ "bncr.diagnostics": (bridge, opts) => bridge.handleDiagnostics(opts),
630
+ "bncr.deadLetter.inspect": (bridge, opts) => bridge.handleDeadLetterInspect(opts),
631
+ "bncr.deadLetter.prune": (bridge, opts) => bridge.handleDeadLetterPrune(opts),
632
+ "bncr.file.init": (bridge, opts) => bridge.handleFileInit(opts),
633
+ "bncr.file.chunk": (bridge, opts) => bridge.handleFileChunk(opts),
634
+ "bncr.file.complete": (bridge, opts) => bridge.handleFileComplete(opts),
635
+ "bncr.file.abort": (bridge, opts) => bridge.handleFileAbort(opts),
636
+ "bncr.file.ack": (bridge, opts) => bridge.handleFileAck(opts)
637
+ };
638
+ const getRegisterMeta2 = (api) => {
639
+ const host = api;
640
+ if (!host[registerMetaSymbol]) {
641
+ host[registerMetaSymbol] = { methods: /* @__PURE__ */ new Set() };
642
+ }
643
+ if (!host[registerMetaSymbol].methods) {
644
+ host[registerMetaSymbol].methods = /* @__PURE__ */ new Set();
645
+ }
646
+ if (!host[registerMetaSymbol].apiInstanceId) {
647
+ host[registerMetaSymbol].apiInstanceId = getIdentityId(api, "api");
648
+ }
649
+ if (!host[registerMetaSymbol].registryFingerprint) {
650
+ host[registerMetaSymbol].registryFingerprint = getRegistryFingerprint(api);
651
+ }
652
+ return host[registerMetaSymbol];
653
+ };
654
+ const getGlobalRegisterTrace2 = () => {
655
+ const p = getProcessStore();
656
+ if (!p[globalRegisterTraceSymbol]) {
657
+ p[globalRegisterTraceSymbol] = {
658
+ seenRegistryFingerprints: /* @__PURE__ */ new Set(),
659
+ seenApiInstanceIds: /* @__PURE__ */ new Set()
660
+ };
661
+ }
662
+ return p[globalRegisterTraceSymbol];
663
+ };
664
+ const getGatewayRuntime2 = () => {
665
+ const p = getProcessStore();
666
+ if (!p[gatewayRuntimeSymbol]) {
667
+ p[gatewayRuntimeSymbol] = {
668
+ registeredMethodsByRegistry: /* @__PURE__ */ new Map(),
669
+ serviceRegistered: false,
670
+ channelRegistered: false
671
+ };
672
+ }
673
+ return p[gatewayRuntimeSymbol];
674
+ };
675
+ const getBridgeRegisterStateCarrier2 = (bridge) => bridge;
676
+ const gatewayMethodRegistry = createBncrGatewayMethodRegistry({
677
+ getRegisterMeta: getRegisterMeta2,
678
+ getRegistryFingerprint,
679
+ getGatewayRuntime: getGatewayRuntime2,
680
+ gatewayMethodDispatchers,
681
+ getBridgeRegisterStateCarrier: getBridgeRegisterStateCarrier2
682
+ });
683
+ const getBridgeOwner = (api, loaded) => {
684
+ const meta = getRegisterMeta2(api);
380
685
  return {
381
- adoptOwner: true,
382
- existingOwnerApiInstanceId,
383
- reason: "same-owner-api"
686
+ moduleEpoch,
687
+ bridgeFactoryId: getIdentityId(loaded.createBncrBridge, "bridgeFactory"),
688
+ apiInstanceId: meta.apiInstanceId || "unknown",
689
+ registryFingerprint: meta.registryFingerprint || "unknown",
690
+ registrationMode: meta.registrationMode
384
691
  };
385
- }
386
- return {
387
- adoptOwner: false,
388
- existingOwnerApiInstanceId,
389
- reason: "singleton-owned-by-other-api"
390
692
  };
391
- };
392
- var gatewayMethodDispatchers = {
393
- "bncr.connect": (bridge, opts) => bridge.handleConnect(opts),
394
- "bncr.inbound": (bridge, opts) => bridge.handleInbound(opts),
395
- "bncr.activity": (bridge, opts) => bridge.handleActivity(opts),
396
- "bncr.ack": (bridge, opts) => bridge.handleAck(opts),
397
- "bncr.diagnostics": (bridge, opts) => bridge.handleDiagnostics(opts),
398
- "bncr.deadLetter.inspect": (bridge, opts) => bridge.handleDeadLetterInspect(opts),
399
- "bncr.deadLetter.prune": (bridge, opts) => bridge.handleDeadLetterPrune(opts),
400
- "bncr.file.init": (bridge, opts) => bridge.handleFileInit(opts),
401
- "bncr.file.chunk": (bridge, opts) => bridge.handleFileChunk(opts),
402
- "bncr.file.complete": (bridge, opts) => bridge.handleFileComplete(opts),
403
- "bncr.file.abort": (bridge, opts) => bridge.handleFileAbort(opts),
404
- "bncr.file.ack": (bridge, opts) => bridge.handleFileAck(opts)
405
- };
406
- var dispatchGatewayMethod = (name, opts) => {
407
- const gatewayRuntime = getGatewayRuntime();
408
- const bridge = gatewayRuntime.currentBridge;
409
- if (!bridge) {
410
- throw new Error(`bncr gateway runtime unavailable for ${name}`);
411
- }
412
- try {
413
- return gatewayMethodDispatchers[name](bridge, opts);
414
- } catch (error) {
415
- const detail = error instanceof Error ? {
416
- name: error.name,
417
- message: error.message,
418
- stack: error.stack || null
419
- } : { name: "NonError", message: String(error), stack: null };
420
- emitBncrLogLine(
421
- "error",
422
- `[bncr] gateway method error ${JSON.stringify({
423
- method: name,
424
- bridgeId: bridge.getBridgeId?.() || null,
425
- gatewayPid: bridge.gatewayPid || null,
426
- detail
427
- })}`,
428
- { debugOnly: true },
429
- () => true
430
- );
431
- throw error;
432
- }
433
- };
434
- var mirrorGatewayMethodForMockApi = (api, name) => {
435
- const host = api;
436
- if (!Array.isArray(host.methods)) return;
437
- if (host.methods.some((item) => item?.name === name)) return;
438
- host.methods.push({ name, handler: (opts) => dispatchGatewayMethod(name, opts) });
439
- };
440
- var ensureGatewayMethodRegistered = (api, name, debugLog) => {
441
- const meta = getRegisterMeta(api);
442
- const gatewayRuntime = getGatewayRuntime();
443
- const registryFingerprint = meta.registryFingerprint || getRegistryFingerprint(api);
444
- let registryMethods = gatewayRuntime.registeredMethodsByRegistry.get(registryFingerprint);
445
- if (!registryMethods) {
446
- registryMethods = /* @__PURE__ */ new Set();
447
- gatewayRuntime.registeredMethodsByRegistry.set(registryFingerprint, registryMethods);
448
- }
449
- if (meta.methods?.has(name)) {
450
- debugLog(`register method skip ${name} (already registered on this api)`);
451
- return;
452
- }
453
- if (registryMethods.has(name)) {
454
- mirrorGatewayMethodForMockApi(api, name);
455
- meta.methods?.add(name);
456
- debugLog(`register method reuse ${name} (already registered in registry)`);
457
- return;
458
- }
459
- api.registerGatewayMethod(name, (opts) => dispatchGatewayMethod(name, opts));
460
- mirrorGatewayMethodForMockApi(api, name);
461
- registryMethods.add(name);
462
- meta.methods?.add(name);
463
- debugLog(`register method ok ${name}`);
464
- };
465
- var getBridgeOwner = (api, loaded) => {
466
- const meta = getRegisterMeta(api);
467
- return {
468
- moduleEpoch: MODULE_EPOCH,
469
- bridgeFactoryId: getIdentityId(loaded.createBncrBridge, "bridgeFactory"),
470
- apiInstanceId: meta.apiInstanceId || "unknown",
471
- registryFingerprint: meta.registryFingerprint || "unknown",
472
- registrationMode: meta.registrationMode
693
+ const bridgeSingletonManager = createBncrBridgeSingletonManager({
694
+ bridgeOwnerSymbol,
695
+ pluginRoot,
696
+ pluginFile,
697
+ loadBncrRuntimeSync,
698
+ getBridgeOwner
699
+ });
700
+ const getCurrentBridge2 = () => {
701
+ const bridge = getGatewayRuntime2().currentBridge;
702
+ if (!bridge) throw new Error("bncr current bridge unavailable");
703
+ return bridge;
473
704
  };
474
- };
475
- var sameBridgeOwner = (left, right) => {
476
- if (!left || !right) return false;
477
- return left.moduleEpoch === right.moduleEpoch && left.bridgeFactoryId === right.bridgeFactoryId && left.apiInstanceId === right.apiInstanceId && left.registryFingerprint === right.registryFingerprint;
478
- };
479
- var snapshotBridgeRegisterState = (bridge) => {
480
- if (!bridge) return null;
481
705
  return {
482
- registerCount: Number(bridge.registerCount || 0),
483
- apiGeneration: Number(bridge.apiGeneration || 0),
484
- firstRegisterAt: typeof bridge.firstRegisterAt === "number" ? bridge.firstRegisterAt : bridge.firstRegisterAt ?? null,
485
- lastRegisterAt: typeof bridge.lastRegisterAt === "number" ? bridge.lastRegisterAt : bridge.lastRegisterAt ?? null,
486
- lastApiRebindAt: typeof bridge.lastApiRebindAt === "number" ? bridge.lastApiRebindAt : bridge.lastApiRebindAt ?? null,
487
- pluginSource: typeof bridge.pluginSource === "string" ? bridge.pluginSource : null,
488
- pluginVersion: typeof bridge.pluginVersion === "string" ? bridge.pluginVersion : null,
489
- lastApiInstanceId: typeof bridge.lastApiInstanceId === "string" ? bridge.lastApiInstanceId : null,
490
- lastRegistryFingerprint: typeof bridge.lastRegistryFingerprint === "string" ? bridge.lastRegistryFingerprint : null,
491
- lastDriftSnapshot: bridge.lastDriftSnapshot ?? null,
492
- registerTraceRecent: Array.isArray(bridge.registerTraceRecent) ? bridge.registerTraceRecent.map((trace) => ({ ...trace })) : []
706
+ getRegisterMeta: getRegisterMeta2,
707
+ getGlobalRegisterTrace: getGlobalRegisterTrace2,
708
+ getGatewayRuntime: getGatewayRuntime2,
709
+ shouldAdoptProcessOwner: (apiInstanceId, gatewayRuntime) => shouldAdoptProcessOwner({
710
+ apiInstanceId,
711
+ serviceRegistered: gatewayRuntime.serviceRegistered,
712
+ channelRegistered: gatewayRuntime.channelRegistered,
713
+ serviceOwnerApiInstanceId: gatewayRuntime.serviceOwnerApiInstanceId,
714
+ channelOwnerApiInstanceId: gatewayRuntime.channelOwnerApiInstanceId
715
+ }),
716
+ ensureGatewayMethodRegistered: gatewayMethodRegistry.ensureGatewayMethodRegistered,
717
+ getBridgeSingleton: bridgeSingletonManager.getBridgeSingleton,
718
+ getBridgeOwnerFromBridge: bridgeSingletonManager.getBridgeOwnerFromBridge,
719
+ getExistingBridgeSingleton: bridgeSingletonManager.getExistingBridgeSingleton,
720
+ getCurrentBridge: getCurrentBridge2
493
721
  };
494
- };
495
- var hydrateBridgeRegisterState = (bridge, snapshot) => {
496
- if (!snapshot) return bridge;
497
- bridge.registerCount = snapshot.registerCount;
498
- bridge.apiGeneration = snapshot.apiGeneration;
499
- bridge.firstRegisterAt = snapshot.firstRegisterAt;
500
- bridge.lastRegisterAt = snapshot.lastRegisterAt;
501
- bridge.lastApiRebindAt = snapshot.lastApiRebindAt;
502
- bridge.pluginSource = snapshot.pluginSource;
503
- bridge.pluginVersion = snapshot.pluginVersion;
504
- bridge.lastApiInstanceId = snapshot.lastApiInstanceId;
505
- bridge.lastRegistryFingerprint = snapshot.lastRegistryFingerprint;
506
- bridge.lastDriftSnapshot = snapshot.lastDriftSnapshot;
507
- bridge.registerTraceRecent = snapshot.registerTraceRecent.map((trace) => ({ ...trace }));
508
- return bridge;
509
- };
510
- var assignBridgeOwner = (bridge, owner) => {
511
- bridge[BNCR_BRIDGE_OWNER] = owner;
512
- return bridge;
513
- };
514
- var getBridgeSingleton = (api) => {
515
- const loaded = loadRuntimeSync();
516
- const g = globalThis;
517
- const owner = getBridgeOwner(api, loaded);
518
- const previousOwner = g.__bncrBridge?.[BNCR_BRIDGE_OWNER];
519
- let created = false;
520
- let rebuilt = false;
521
- if (g.__bncrBridge) {
522
- const mustRebuild = !sameBridgeOwner(previousOwner, owner) && (previousOwner?.moduleEpoch !== owner.moduleEpoch || previousOwner?.bridgeFactoryId !== owner.bridgeFactoryId || previousOwner?.registrationMode !== owner.registrationMode || previousOwner?.apiInstanceId !== owner.apiInstanceId || previousOwner?.registryFingerprint !== owner.registryFingerprint);
523
- if (mustRebuild) {
524
- const registerState = snapshotBridgeRegisterState(g.__bncrBridge);
525
- try {
526
- g.__bncrBridge.stopService?.();
527
- } catch {
528
- }
529
- g.__bncrBridge = hydrateBridgeRegisterState(
530
- assignBridgeOwner(loaded.createBncrBridge(api, { pluginRoot, pluginFile }), owner),
531
- registerState
532
- );
533
- created = true;
534
- rebuilt = true;
535
- } else {
536
- g.__bncrBridge.bindApi?.(api);
537
- assignBridgeOwner(g.__bncrBridge, owner);
538
- created = false;
539
- rebuilt = false;
540
- }
541
- } else {
542
- g.__bncrBridge = assignBridgeOwner(
543
- loaded.createBncrBridge(api, { pluginRoot, pluginFile }),
544
- owner
545
- );
546
- created = true;
547
- }
548
- g.__bncrBridge.bindRuntimePaths?.({ pluginRoot, pluginFile });
549
- return { bridge: g.__bncrBridge, runtime: loaded, created, rebuilt, owner, previousOwner };
550
- };
551
- var getExistingBridgeSingleton = () => {
552
- const g = globalThis;
553
- return g.__bncrBridge;
554
- };
555
- var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
556
- var getCurrentBridge = () => {
557
- const bridge = getGatewayRuntime().currentBridge;
558
- if (!bridge) throw new Error("bncr current bridge unavailable");
559
- return bridge;
560
- };
561
- var createDynamicChannelPlugin = (loaded) => {
562
- const base = loaded.createBncrChannelPlugin(() => getCurrentBridge());
563
- return {
564
- ...base,
565
- outbound: {
566
- ...base.outbound,
567
- sendText: (ctx) => getCurrentBridge().channelSendText(ctx),
568
- sendMedia: (ctx) => getCurrentBridge().channelSendMedia(ctx)
569
- },
570
- status: {
571
- ...base.status,
572
- buildChannelSummary: async ({ defaultAccountId }) => getCurrentBridge().getChannelSummary(defaultAccountId || "Primary"),
573
- buildAccountSnapshot: async ({ account, runtime: runtime2 }) => {
574
- const bridgeNow = getCurrentBridge();
575
- return base.status.buildAccountSnapshot({
576
- account,
577
- runtime: runtime2 || bridgeNow.getAccountRuntimeSnapshot(account?.accountId)
578
- });
722
+ }
723
+
724
+ // src/core/config-schema.ts
725
+ var BncrConfigSchema = {
726
+ schema: {
727
+ type: "object",
728
+ additionalProperties: true,
729
+ properties: {
730
+ enabled: { type: "boolean" },
731
+ dmPolicy: {
732
+ type: "string",
733
+ enum: ["open", "allowlist", "disabled"]
579
734
  },
580
- resolveAccountState: ({ enabled, configured, account, cfg, runtime: runtime2 }) => {
581
- const bridgeNow = getCurrentBridge();
582
- return base.status.resolveAccountState({
583
- enabled,
584
- configured,
585
- account,
586
- cfg,
587
- runtime: runtime2 || bridgeNow.getAccountRuntimeSnapshot(account?.accountId)
588
- });
589
- }
590
- },
591
- gateway: {
592
- ...base.gateway,
593
- startAccount: (ctx) => getCurrentBridge().channelStartAccount(ctx),
594
- stopAccount: (ctx) => getCurrentBridge().channelStopAccount(ctx)
595
- }
596
- };
597
- };
598
- var registerBncrCli = (api) => {
599
- if (typeof api.registerCli !== "function") return;
600
- api.registerCli(
601
- ({ program }) => {
602
- const bncr = program.command("bncr").description("Bncr channel utilities");
603
- bncr.command("miniconfig").description(
604
- "Seed minimal channels.bncr config (adds enabled=true and allowTool=false only when missing)"
605
- ).action(async () => {
606
- const cfg = getOpenClawRuntimeConfig(api);
607
- const channels = isPlainObject(cfg.channels) ? cfg.channels : {};
608
- const existing = isPlainObject(channels.bncr) ? channels.bncr : {};
609
- const added = [];
610
- if (existing.enabled === void 0) {
611
- added.push("enabled=true");
612
- }
613
- if (existing.allowTool === void 0) {
614
- added.push("allowTool=false");
615
- }
616
- if (added.length === 0) {
617
- console.log("Minimal bncr config already present. No changes made.");
618
- return;
735
+ groupPolicy: {
736
+ type: "string",
737
+ enum: ["open", "allowlist", "disabled"]
738
+ },
739
+ allowFrom: {
740
+ type: "array",
741
+ items: { type: "string" }
742
+ },
743
+ groupAllowFrom: {
744
+ type: "array",
745
+ items: { type: "string" }
746
+ },
747
+ debug: {
748
+ type: "object",
749
+ additionalProperties: true,
750
+ properties: {
751
+ verbose: {
752
+ type: "boolean",
753
+ default: false,
754
+ description: "Enable verbose debug logs for bncr channel runtime."
755
+ }
619
756
  }
620
- await mutateOpenClawRuntimeConfigFile(api, {
621
- afterWrite: { mode: "auto" },
622
- mutate(draft) {
623
- if (!isPlainObject(draft.channels)) draft.channels = {};
624
- const draftChannels = draft.channels;
625
- const draftExisting = isPlainObject(draftChannels.bncr) ? draftChannels.bncr : {};
626
- const draftBncrCfg = { ...draftExisting };
627
- if (draftBncrCfg.enabled === void 0) {
628
- draftBncrCfg.enabled = true;
629
- }
630
- if (draftBncrCfg.allowTool === void 0) {
631
- draftBncrCfg.allowTool = false;
632
- }
633
- draftChannels.bncr = draftBncrCfg;
757
+ },
758
+ allowTool: {
759
+ type: "boolean",
760
+ default: false,
761
+ 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."
762
+ },
763
+ requireMention: {
764
+ type: "boolean",
765
+ default: false,
766
+ 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."
767
+ },
768
+ outboundRequireAck: {
769
+ type: "boolean",
770
+ default: true,
771
+ description: "Whether outbound text waits for bncr.ack before leaving the retry queue. Default true to preserve current ack/dead-letter behavior."
772
+ },
773
+ accounts: {
774
+ type: "object",
775
+ additionalProperties: {
776
+ type: "object",
777
+ additionalProperties: true,
778
+ properties: {
779
+ enabled: { type: "boolean" },
780
+ name: { type: "string" }
634
781
  }
635
- });
636
- console.log("Seeded minimal bncr config at channels.bncr.");
637
- console.log(`Added missing fields: ${added.join(", ")}`);
638
- console.log("Gateway will apply the config using the host afterWrite policy.");
639
- });
640
- },
641
- { commands: ["bncr"] }
642
- );
782
+ }
783
+ }
784
+ }
785
+ }
643
786
  };
644
- var shouldSkipNonRuntimeRegister = (mode) => mode === "cli-metadata" || mode === "discovery";
787
+
788
+ // index.ts
789
+ var readPluginVersion2 = (rootDir = pluginRoot) => {
790
+ const packageJsonPath = path3.join(rootDir, "package.json");
791
+ void packageJsonPath;
792
+ return pluginVersion;
793
+ };
794
+ var pluginVersion2 = readPluginVersion2();
795
+ var registerRuntime = createBncrRegisterRuntime();
796
+ var {
797
+ ensureGatewayMethodRegistered,
798
+ getBridgeSingleton,
799
+ getBridgeOwnerFromBridge,
800
+ getCurrentBridge,
801
+ getExistingBridgeSingleton,
802
+ getGatewayRuntime,
803
+ getGlobalRegisterTrace,
804
+ getRegisterMeta,
805
+ shouldAdoptProcessOwner: shouldAdoptProcessOwner2
806
+ } = registerRuntime;
645
807
  var plugin = {
646
808
  id: "bncr",
647
809
  name: "Bncr",
@@ -662,7 +824,7 @@ var plugin = {
662
824
  const firstSeenApi = !globalTrace.seenApiInstanceIds.has(apiInstanceId);
663
825
  const firstSeenRegistry = !globalTrace.seenRegistryFingerprints.has(registryFingerprint);
664
826
  const gatewayRuntime = getGatewayRuntime();
665
- const ownerDecision = shouldAdoptProcessOwner(apiInstanceId, gatewayRuntime);
827
+ const ownerDecision = shouldAdoptProcessOwner2(apiInstanceId, gatewayRuntime);
666
828
  let bridge;
667
829
  let runtime2;
668
830
  let created = false;
@@ -679,9 +841,9 @@ var plugin = {
679
841
  previousOwner = adopted.previousOwner;
680
842
  gatewayRuntime.currentBridge = bridge;
681
843
  } else {
682
- runtime2 = loadRuntimeSync();
844
+ runtime2 = loadBncrRuntimeSync();
683
845
  bridge = gatewayRuntime.currentBridge || getExistingBridgeSingleton();
684
- previousOwner = getExistingBridgeSingleton()?.[BNCR_BRIDGE_OWNER];
846
+ previousOwner = getBridgeOwnerFromBridge(bridge);
685
847
  owner = previousOwner;
686
848
  if (bridge && !gatewayRuntime.currentBridge) {
687
849
  gatewayRuntime.currentBridge = bridge;
@@ -693,7 +855,7 @@ var plugin = {
693
855
  globalTrace.lastRegistryFingerprint = registryFingerprint;
694
856
  bridge?.noteRegister?.({
695
857
  source: "@xmoxmo/bncr",
696
- pluginVersion,
858
+ pluginVersion: pluginVersion2,
697
859
  apiRebound: ownerDecision.adoptOwner ? !created && !rebuilt : false,
698
860
  apiInstanceId: meta.apiInstanceId,
699
861
  registryFingerprint: meta.registryFingerprint
@@ -756,7 +918,9 @@ var plugin = {
756
918
  );
757
919
  }
758
920
  if (!gatewayRuntime.channelRegistered) {
759
- api.registerChannel({ plugin: createDynamicChannelPlugin(runtime2) });
921
+ api.registerChannel({
922
+ plugin: createDynamicChannelPlugin({ loaded: runtime2, getCurrentBridge })
923
+ });
760
924
  gatewayRuntime.channelRegistered = true;
761
925
  gatewayRuntime.channelOwnerApiInstanceId = apiInstanceId;
762
926
  meta.channel = true;