@xmoxmo/bncr 0.4.1 → 0.4.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/dist/index.js +619 -455
- package/package.json +1 -1
- package/src/bootstrap/runtime-discovery.ts +4 -0
- package/src/bootstrap/runtime-loader.ts +23 -2
- package/src/messaging/outbound/build-send-action.ts +21 -5
- package/src/messaging/outbound/send-params.ts +23 -5
- package/src/plugin/channel-plugin-surface-group.ts +33 -31
package/dist/index.js
CHANGED
|
@@ -1,73 +1,129 @@
|
|
|
1
1
|
// index.ts
|
|
2
|
-
import
|
|
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/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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/
|
|
106
|
-
function
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
-
|
|
187
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
491
|
+
}
|
|
492
|
+
function canResolveSdkCore(pluginRequire2) {
|
|
246
493
|
try {
|
|
247
|
-
|
|
494
|
+
pluginRequire2.resolve(sdkCoreSpecifier);
|
|
248
495
|
return true;
|
|
249
496
|
} catch {
|
|
250
497
|
return false;
|
|
251
498
|
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
const nodeModulesDir = path.join(
|
|
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
|
-
|
|
273
|
-
const
|
|
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(
|
|
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
|
-
|
|
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 ${
|
|
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
|
|
579
|
+
var pluginVersion = readPluginVersion();
|
|
580
|
+
var loadBncrRuntimeSync = () => {
|
|
298
581
|
if (runtime) return runtime;
|
|
299
|
-
|
|
582
|
+
ensureBncrOpenClawSdkResolution(pluginDir, pluginRequire);
|
|
300
583
|
try {
|
|
301
|
-
const mod = pluginRequire(
|
|
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}_${
|
|
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
|
-
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
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
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
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
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
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
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
});
|
|
640
|
-
},
|
|
641
|
-
{ commands: ["bncr"] }
|
|
642
|
-
);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
643
786
|
};
|
|
644
|
-
|
|
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 =
|
|
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 =
|
|
844
|
+
runtime2 = loadBncrRuntimeSync();
|
|
683
845
|
bridge = gatewayRuntime.currentBridge || getExistingBridgeSingleton();
|
|
684
|
-
previousOwner =
|
|
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({
|
|
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;
|