@xmoxmo/bncr 0.3.6 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/index.js +28 -5
- package/index.ts +55 -721
- package/package.json +8 -4
- package/scripts/check-pack.mjs +93 -18
- package/scripts/check-register-drift.mjs +35 -13
- package/scripts/selfcheck.mjs +80 -11
- package/src/bootstrap/channel-plugin-runtime.ts +81 -0
- package/src/bootstrap/cli.ts +97 -0
- package/src/bootstrap/register-runtime-gateway.ts +129 -0
- package/src/bootstrap/register-runtime-helpers.ts +140 -0
- package/src/bootstrap/register-runtime-singleton.ts +137 -0
- package/src/bootstrap/register-runtime.ts +201 -0
- package/src/bootstrap/runtime-discovery.ts +187 -0
- package/src/bootstrap/runtime-loader.ts +54 -0
- package/src/channel.ts +1590 -4967
- package/src/core/accounts.ts +23 -4
- package/src/core/dead-letter-diagnostics.ts +37 -5
- package/src/core/diagnostics.ts +31 -15
- package/src/core/downlink-health.ts +3 -11
- package/src/core/extended-diagnostics.ts +78 -36
- package/src/core/file-transfer-payloads.ts +1 -1
- package/src/core/logging.ts +1 -0
- package/src/core/outbox-enqueue.ts +13 -2
- package/src/core/outbox-entry-builders.ts +2 -0
- package/src/core/outbox-summary.ts +75 -3
- package/src/core/permissions.ts +15 -2
- package/src/core/persisted-outbox-entry.ts +21 -6
- package/src/core/policy.ts +45 -4
- package/src/core/probe.ts +3 -15
- package/src/core/register-trace.ts +3 -3
- package/src/core/status.ts +43 -4
- package/src/core/targets.ts +216 -205
- package/src/core/types.ts +221 -0
- package/src/core/value-sanitize.ts +29 -0
- package/src/messaging/inbound/commands.ts +147 -172
- package/src/messaging/inbound/context-facts.ts +4 -2
- package/src/messaging/inbound/contracts.ts +70 -0
- package/src/messaging/inbound/dispatch-prep.ts +303 -0
- package/src/messaging/inbound/dispatch.ts +49 -462
- package/src/messaging/inbound/gate.ts +18 -5
- package/src/messaging/inbound/last-route.ts +10 -4
- package/src/messaging/inbound/media-url-download.ts +109 -0
- package/src/messaging/inbound/native-command-runtime.ts +225 -0
- package/src/messaging/inbound/parse.ts +2 -1
- package/src/messaging/inbound/remote-media.ts +49 -0
- package/src/messaging/inbound/reply-config.ts +16 -4
- package/src/messaging/inbound/reply-dispatch.ts +162 -0
- package/src/messaging/inbound/runtime-compat.ts +31 -10
- package/src/messaging/inbound/session-label.ts +15 -7
- package/src/messaging/inbound/turn-context.ts +131 -0
- package/src/messaging/outbound/actions.ts +24 -10
- package/src/messaging/outbound/diagnostics-debug-builders.ts +365 -0
- package/src/messaging/outbound/diagnostics.ts +31 -355
- package/src/messaging/outbound/durable-message-adapter.ts +20 -16
- package/src/messaging/outbound/durable-queue-adapter.ts +20 -7
- package/src/messaging/outbound/media.ts +24 -13
- package/src/messaging/outbound/reply-enqueue-media.ts +181 -0
- package/src/messaging/outbound/reply-enqueue.ts +46 -155
- package/src/messaging/outbound/send-params.ts +3 -0
- package/src/messaging/outbound/send.ts +19 -10
- package/src/messaging/outbound/session-route.ts +18 -3
- package/src/openclaw/channel-runtime-contracts.ts +76 -0
- package/src/openclaw/config-runtime.ts +13 -7
- package/src/openclaw/inbound-session-runtime.ts +7 -3
- package/src/openclaw/ingress-runtime.ts +17 -27
- package/src/openclaw/reply-runtime.ts +54 -59
- package/src/openclaw/routing-runtime.ts +35 -18
- package/src/openclaw/runtime-surface.ts +156 -12
- package/src/openclaw/sdk-helpers.ts +8 -1
- package/src/openclaw/session-route-runtime.ts +12 -12
- package/src/plugin/ack-outbox-runtime-group.ts +264 -0
- package/src/plugin/bridge-ack-facade.ts +137 -0
- package/src/plugin/bridge-connection-facade.ts +111 -0
- package/src/plugin/bridge-diagnostics-facade.ts +23 -0
- package/src/plugin/bridge-drain-facade.ts +98 -0
- package/src/plugin/bridge-extended-diagnostics-facade.ts +149 -0
- package/src/plugin/bridge-file-transfer-push-facade.ts +140 -0
- package/src/plugin/bridge-lifecycle.ts +156 -0
- package/src/plugin/bridge-media-facade.ts +241 -0
- package/src/plugin/bridge-outbox-facade.ts +182 -0
- package/src/plugin/bridge-runtime-helpers.ts +266 -0
- package/src/plugin/bridge-runtime-snapshots.ts +104 -0
- package/src/plugin/bridge-runtime-surface-facade.ts +8 -0
- package/src/plugin/bridge-status-facade.ts +76 -0
- package/src/plugin/bridge-status-worker-facade.ts +72 -0
- package/src/plugin/bridge-support-runtime.ts +137 -0
- package/src/plugin/bridge-surface-handlers-group.ts +242 -0
- package/src/plugin/bridge-surface-helpers.ts +28 -0
- package/src/plugin/capabilities.ts +1 -3
- package/src/plugin/channel-components.ts +289 -0
- package/src/plugin/channel-inbound-helpers.ts +149 -0
- package/src/plugin/channel-plugin-bridge-group.ts +129 -0
- package/src/plugin/channel-plugin-surface-group.ts +202 -0
- package/src/plugin/channel-runtime-builders-delivery.ts +513 -0
- package/src/plugin/channel-runtime-builders-status.ts +331 -0
- package/src/plugin/channel-runtime-builders.ts +25 -0
- package/src/plugin/channel-runtime-constants.ts +40 -0
- package/src/plugin/channel-runtime-types.ts +146 -0
- package/src/plugin/channel-send-runtime-group.ts +37 -0
- package/src/plugin/channel-send.ts +226 -0
- package/src/plugin/channel-utils.ts +102 -0
- package/src/plugin/config.ts +24 -3
- package/src/plugin/connection-handlers-helpers.ts +254 -0
- package/src/plugin/connection-handlers.ts +440 -0
- package/src/plugin/connection-state-helpers.ts +159 -0
- package/src/plugin/connection-state-runtime-group.ts +51 -0
- package/src/plugin/connection-state.ts +527 -0
- package/src/plugin/diagnostics-handlers.ts +211 -0
- package/src/plugin/error-message.ts +15 -0
- package/src/plugin/file-ack-runtime.ts +284 -0
- package/src/plugin/file-inbound-abort.ts +112 -0
- package/src/plugin/file-inbound-chunk.ts +146 -0
- package/src/plugin/file-inbound-complete.ts +153 -0
- package/src/plugin/file-inbound-handlers.ts +19 -0
- package/src/plugin/file-inbound-init.ts +122 -0
- package/src/plugin/file-inbound-runtime.ts +51 -0
- package/src/plugin/file-inbound-state.ts +62 -0
- package/src/plugin/file-transfer-logs.ts +227 -0
- package/src/plugin/file-transfer-orchestrator-chunk.ts +135 -0
- package/src/plugin/file-transfer-orchestrator.ts +304 -0
- package/src/plugin/file-transfer-runtime-group.ts +102 -0
- package/src/plugin/file-transfer-send.ts +89 -0
- package/src/plugin/file-transfer-setup.ts +206 -0
- package/src/plugin/gateway-event-context.ts +41 -0
- package/src/plugin/gateway-runtime.ts +14 -4
- package/src/plugin/inbound-acceptance.ts +107 -0
- package/src/plugin/inbound-handlers.ts +248 -0
- package/src/plugin/inbound-surface-handlers-group.ts +152 -0
- package/src/plugin/media-dedupe-runtime.ts +90 -0
- package/src/plugin/media-orchestrators-runtime-group.ts +316 -0
- package/src/plugin/message-ack-runtime.ts +284 -0
- package/src/plugin/message-send.ts +16 -6
- package/src/plugin/messaging.ts +98 -36
- package/src/plugin/outbound.ts +50 -8
- package/src/plugin/outbox-ack-logs.ts +136 -0
- package/src/plugin/outbox-ack-outcome.ts +128 -0
- package/src/plugin/outbox-drain-ack.ts +145 -0
- package/src/plugin/outbox-drain-failure.ts +84 -0
- package/src/plugin/outbox-drain-loop.ts +554 -0
- package/src/plugin/outbox-drain-post-push.ts +159 -0
- package/src/plugin/outbox-drain-runtime.ts +141 -0
- package/src/plugin/outbox-drain-schedule.ts +116 -0
- package/src/plugin/outbox-file-push-flow.ts +69 -0
- package/src/plugin/outbox-push-route-runtime-group.ts +81 -0
- package/src/plugin/outbox-push.ts +267 -0
- package/src/plugin/outbox-route.ts +181 -0
- package/src/plugin/outbox-text-push-flow.ts +90 -0
- package/src/plugin/runtime-diagnostics-assembler.ts +183 -0
- package/src/plugin/runtime-diagnostics-helpers.ts +302 -0
- package/src/plugin/runtime-diagnostics-payload-builders.ts +171 -0
- package/src/plugin/runtime-diagnostics-snapshot.ts +31 -0
- package/src/plugin/setup.ts +33 -6
- package/src/plugin/state-store.ts +249 -0
- package/src/plugin/state-transient-runtime-group.ts +105 -0
- package/src/plugin/status-runtime.ts +251 -0
- package/src/plugin/status.ts +33 -7
- package/src/plugin/target-runtime.ts +141 -0
- package/src/plugin/target-status-runtime-group.ts +130 -0
- package/src/plugin/transient-state-runtime.ts +82 -0
- package/src/runtime/outbound-ack-timeout.ts +5 -3
- package/src/runtime/outbound-flags.ts +24 -8
- package/src/runtime/status-snapshots.ts +36 -7
- package/src/runtime/status-worker.ts +34 -4
package/index.ts
CHANGED
|
@@ -1,736 +1,68 @@
|
|
|
1
|
-
import { execFileSync } from 'node:child_process';
|
|
2
|
-
import fs from 'node:fs';
|
|
3
|
-
import { createRequire } from 'node:module';
|
|
4
1
|
import path from 'node:path';
|
|
5
|
-
import {
|
|
2
|
+
import { createDynamicChannelPlugin } from './src/bootstrap/channel-plugin-runtime.ts';
|
|
3
|
+
import {
|
|
4
|
+
type BncrRegistrationApi,
|
|
5
|
+
registerBncrCli,
|
|
6
|
+
shouldSkipNonRuntimeRegister,
|
|
7
|
+
} from './src/bootstrap/cli.ts';
|
|
8
|
+
import { createBncrRegisterRuntime } from './src/bootstrap/register-runtime.ts';
|
|
9
|
+
import {
|
|
10
|
+
type ChannelModule,
|
|
11
|
+
type LoadedRuntime,
|
|
12
|
+
loadBncrRuntimeSync,
|
|
13
|
+
pluginRoot,
|
|
14
|
+
pluginVersion as runtimePluginVersion,
|
|
15
|
+
} from './src/bootstrap/runtime-loader.ts';
|
|
6
16
|
import { BncrConfigSchema } from './src/core/config-schema.ts';
|
|
7
17
|
import { emitBncrLogLine } from './src/core/logging.ts';
|
|
8
|
-
import {
|
|
9
|
-
getOpenClawRuntimeConfig,
|
|
10
|
-
mutateOpenClawRuntimeConfigFile,
|
|
11
|
-
} from './src/openclaw/config-runtime.ts';
|
|
18
|
+
import { getOpenClawRuntimeConfig } from './src/openclaw/config-runtime.ts';
|
|
12
19
|
|
|
13
|
-
const pluginFile = fileURLToPath(import.meta.url);
|
|
14
|
-
const pluginDir = path.dirname(pluginFile);
|
|
15
|
-
const pluginRequire = createRequire(import.meta.url);
|
|
16
|
-
const sdkCoreSpecifier = 'openclaw/plugin-sdk/core';
|
|
17
|
-
const linkType = process.platform === 'win32' ? 'junction' : 'dir';
|
|
18
|
-
|
|
19
|
-
type ChannelModule = typeof import('./src/channel.ts');
|
|
20
|
-
type OpenClawPluginApi = Parameters<ChannelModule['createBncrBridge']>[0];
|
|
21
|
-
type BridgeSingleton = ReturnType<ChannelModule['createBncrBridge']>;
|
|
22
20
|
type ChannelPlugin = ReturnType<ChannelModule['createBncrChannelPlugin']>;
|
|
23
21
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const BNCR_REGISTER_META = Symbol.for('bncr.register.meta');
|
|
30
|
-
const BNCR_GLOBAL_REGISTER_TRACE = Symbol.for('bncr.global.register.trace');
|
|
31
|
-
const BNCR_BRIDGE_OWNER = Symbol.for('bncr.bridge.owner');
|
|
32
|
-
const BNCR_GATEWAY_RUNTIME = Symbol.for('bncr.gateway.runtime');
|
|
33
|
-
const MODULE_EPOCH = `${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
34
|
-
|
|
35
|
-
type RegisterMeta = {
|
|
36
|
-
service?: boolean;
|
|
37
|
-
channel?: boolean;
|
|
38
|
-
methods?: Set<string>;
|
|
39
|
-
apiInstanceId?: string;
|
|
40
|
-
registryFingerprint?: string;
|
|
41
|
-
registrationMode?: string;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
type GlobalRegisterTrace = {
|
|
45
|
-
lastApiInstanceId?: string;
|
|
46
|
-
lastRegistryFingerprint?: string;
|
|
47
|
-
seenRegistryFingerprints: Set<string>;
|
|
48
|
-
seenApiInstanceIds: Set<string>;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
type BridgeOwner = {
|
|
52
|
-
moduleEpoch: string;
|
|
53
|
-
bridgeFactoryId: string;
|
|
54
|
-
apiInstanceId: string;
|
|
55
|
-
registryFingerprint: string;
|
|
56
|
-
registrationMode?: string;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
type BridgeRegisterStateSnapshot = {
|
|
60
|
-
registerCount: number;
|
|
61
|
-
apiGeneration: number;
|
|
62
|
-
firstRegisterAt: number | null;
|
|
63
|
-
lastRegisterAt: number | null;
|
|
64
|
-
lastApiRebindAt: number | null;
|
|
65
|
-
pluginSource: string | null;
|
|
66
|
-
pluginVersion: string | null;
|
|
67
|
-
lastApiInstanceId: string | null;
|
|
68
|
-
lastRegistryFingerprint: string | null;
|
|
69
|
-
lastDriftSnapshot: unknown;
|
|
70
|
-
registerTraceRecent: Array<Record<string, unknown>>;
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
type GatewayMethodName =
|
|
74
|
-
| 'bncr.connect'
|
|
75
|
-
| 'bncr.inbound'
|
|
76
|
-
| 'bncr.activity'
|
|
77
|
-
| 'bncr.ack'
|
|
78
|
-
| 'bncr.diagnostics'
|
|
79
|
-
| 'bncr.deadLetter.inspect'
|
|
80
|
-
| 'bncr.deadLetter.prune'
|
|
81
|
-
| 'bncr.file.init'
|
|
82
|
-
| 'bncr.file.chunk'
|
|
83
|
-
| 'bncr.file.complete'
|
|
84
|
-
| 'bncr.file.abort'
|
|
85
|
-
| 'bncr.file.ack';
|
|
86
|
-
|
|
87
|
-
type BridgeSingletonWithOwner = BridgeSingleton & {
|
|
88
|
-
[BNCR_BRIDGE_OWNER]?: BridgeOwner;
|
|
89
|
-
registerCount?: number;
|
|
90
|
-
apiGeneration?: number;
|
|
91
|
-
firstRegisterAt?: number | null;
|
|
92
|
-
lastRegisterAt?: number | null;
|
|
93
|
-
lastApiRebindAt?: number | null;
|
|
94
|
-
pluginSource?: string | null;
|
|
95
|
-
pluginVersion?: string | null;
|
|
96
|
-
lastApiInstanceId?: string | null;
|
|
97
|
-
lastRegistryFingerprint?: string | null;
|
|
98
|
-
lastDriftSnapshot?: unknown;
|
|
99
|
-
registerTraceRecent?: Array<Record<string, unknown>>;
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
type OpenClawPluginApiWithMeta = OpenClawPluginApi & {
|
|
103
|
-
[BNCR_REGISTER_META]?: RegisterMeta;
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
type BncrGatewayRuntime = {
|
|
107
|
-
currentBridge?: BridgeSingletonWithOwner;
|
|
108
|
-
registeredMethodsByRegistry: Map<string, Set<GatewayMethodName>>;
|
|
109
|
-
serviceRegistered?: boolean;
|
|
110
|
-
channelRegistered?: boolean;
|
|
111
|
-
serviceOwnerApiInstanceId?: string;
|
|
112
|
-
channelOwnerApiInstanceId?: string;
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
let runtime: LoadedRuntime | null = null;
|
|
116
|
-
const identityIds = new WeakMap<object, string>();
|
|
117
|
-
let identitySeq = 0;
|
|
118
|
-
|
|
119
|
-
const tryExec = (command: string, args: string[]) => {
|
|
120
|
-
try {
|
|
121
|
-
return execFileSync(command, args, {
|
|
122
|
-
encoding: 'utf8',
|
|
123
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
124
|
-
}).trim();
|
|
125
|
-
} catch {
|
|
126
|
-
return '';
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
const readOpenClawPackageName = (pkgPath: string) => {
|
|
131
|
-
try {
|
|
132
|
-
const raw = fs.readFileSync(pkgPath, 'utf8');
|
|
133
|
-
const parsed = JSON.parse(raw);
|
|
134
|
-
return typeof parsed?.name === 'string' ? parsed.name : '';
|
|
135
|
-
} catch {
|
|
136
|
-
return '';
|
|
137
|
-
}
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
const readPluginVersion = () => {
|
|
141
|
-
try {
|
|
142
|
-
const raw = fs.readFileSync(path.join(pluginDir, 'package.json'), 'utf8');
|
|
143
|
-
const parsed = JSON.parse(raw);
|
|
144
|
-
return typeof parsed?.version === 'string' ? parsed.version : 'unknown';
|
|
145
|
-
} catch {
|
|
146
|
-
return 'unknown';
|
|
147
|
-
}
|
|
22
|
+
const readPluginVersion = (rootDir = pluginRoot) => {
|
|
23
|
+
const packageJsonPath = path.join(rootDir, 'package.json');
|
|
24
|
+
void packageJsonPath;
|
|
25
|
+
return runtimePluginVersion;
|
|
148
26
|
};
|
|
149
27
|
|
|
150
28
|
const pluginVersion = readPluginVersion();
|
|
151
29
|
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
if (!item) continue;
|
|
179
|
-
const normalized = path.normalize(item);
|
|
180
|
-
if (seen.has(normalized)) continue;
|
|
181
|
-
seen.add(normalized);
|
|
182
|
-
out.push(normalized);
|
|
183
|
-
}
|
|
184
|
-
return out;
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
const collectOpenClawCandidates = () => {
|
|
188
|
-
const directCandidates = [
|
|
189
|
-
path.join(pluginDir, 'node_modules', 'openclaw'),
|
|
190
|
-
path.join('/usr/lib/node_modules', 'openclaw'),
|
|
191
|
-
path.join('/usr/local/lib/node_modules', 'openclaw'),
|
|
192
|
-
path.join('/opt/homebrew/lib/node_modules', 'openclaw'),
|
|
193
|
-
path.join(process.env.HOME || '', '.npm-global/lib/node_modules', 'openclaw'),
|
|
194
|
-
];
|
|
195
|
-
|
|
196
|
-
const npmRoot = tryExec('npm', ['root', '-g']);
|
|
197
|
-
if (npmRoot) directCandidates.push(path.join(npmRoot, 'openclaw'));
|
|
198
|
-
|
|
199
|
-
const nodePathEntries = (process.env.NODE_PATH || '')
|
|
200
|
-
.split(path.delimiter)
|
|
201
|
-
.map((entry) => entry.trim())
|
|
202
|
-
.filter(Boolean);
|
|
203
|
-
for (const entry of nodePathEntries) {
|
|
204
|
-
directCandidates.push(path.join(entry, 'openclaw'));
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
const openclawBin = tryExec('which', ['openclaw']);
|
|
208
|
-
if (openclawBin) {
|
|
209
|
-
directCandidates.push(openclawBin);
|
|
210
|
-
directCandidates.push(path.dirname(openclawBin));
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const packageRoots = unique(
|
|
214
|
-
directCandidates.map((candidate) => findOpenClawPackageRoot(candidate)).filter(Boolean),
|
|
215
|
-
);
|
|
216
|
-
|
|
217
|
-
return packageRoots.filter((candidate) => {
|
|
218
|
-
const pkgJson = path.join(candidate, 'package.json');
|
|
219
|
-
return fs.existsSync(pkgJson) && readOpenClawPackageName(pkgJson) === 'openclaw';
|
|
220
|
-
});
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
const canResolveSdkCore = () => {
|
|
224
|
-
try {
|
|
225
|
-
pluginRequire.resolve(sdkCoreSpecifier);
|
|
226
|
-
return true;
|
|
227
|
-
} catch {
|
|
228
|
-
return false;
|
|
229
|
-
}
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
const ensurePluginNodeModulesLink = (targetRoot: string) => {
|
|
233
|
-
const nodeModulesDir = path.join(pluginDir, 'node_modules');
|
|
234
|
-
const linkPath = path.join(nodeModulesDir, 'openclaw');
|
|
235
|
-
fs.mkdirSync(nodeModulesDir, { recursive: true });
|
|
236
|
-
|
|
237
|
-
try {
|
|
238
|
-
const stat = fs.lstatSync(linkPath);
|
|
239
|
-
if (stat.isSymbolicLink()) {
|
|
240
|
-
const existingTarget = fs.realpathSync(linkPath);
|
|
241
|
-
const normalizedExisting = path.normalize(existingTarget);
|
|
242
|
-
const normalizedTarget = path.normalize(fs.realpathSync(targetRoot));
|
|
243
|
-
if (normalizedExisting === normalizedTarget) return;
|
|
244
|
-
fs.unlinkSync(linkPath);
|
|
245
|
-
} else {
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
} catch {
|
|
249
|
-
// missing link is fine
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
fs.symlinkSync(targetRoot, linkPath, linkType as fs.symlink.Type);
|
|
253
|
-
};
|
|
254
|
-
|
|
255
|
-
const runtimeSourceDir = (() => {
|
|
256
|
-
const direct = path.join(pluginDir, 'src');
|
|
257
|
-
if (fs.existsSync(path.join(direct, 'channel.ts'))) return direct;
|
|
258
|
-
|
|
259
|
-
const parent = path.join(pluginDir, '..', 'src');
|
|
260
|
-
if (fs.existsSync(path.join(parent, 'channel.ts'))) return parent;
|
|
261
|
-
|
|
262
|
-
return direct;
|
|
263
|
-
})();
|
|
264
|
-
|
|
265
|
-
const ensureOpenClawSdkResolution = () => {
|
|
266
|
-
if (canResolveSdkCore()) return;
|
|
267
|
-
|
|
268
|
-
let lastError = '';
|
|
269
|
-
const candidates = collectOpenClawCandidates();
|
|
270
|
-
for (const candidate of candidates) {
|
|
271
|
-
try {
|
|
272
|
-
ensurePluginNodeModulesLink(candidate);
|
|
273
|
-
if (canResolveSdkCore()) return;
|
|
274
|
-
} catch (error) {
|
|
275
|
-
lastError = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
const suffix = candidates.length
|
|
280
|
-
? ` Tried candidates: ${candidates.join(', ')}.`
|
|
281
|
-
: ' No openclaw package root candidates were found from npm root, NODE_PATH, common global paths, or the openclaw binary path.';
|
|
282
|
-
const extra = lastError ? ` Last repair error: ${lastError}.` : '';
|
|
283
|
-
throw new Error(
|
|
284
|
-
`bncr failed to resolve ${sdkCoreSpecifier} from ${pluginDir}.${suffix}${extra} ` +
|
|
285
|
-
`You can repair manually with: mkdir -p ${path.join(pluginDir, 'node_modules')} && ln -s "$(npm root -g)/openclaw" ${path.join(pluginDir, 'node_modules', 'openclaw')}`,
|
|
286
|
-
);
|
|
287
|
-
};
|
|
288
|
-
|
|
289
|
-
const loadRuntimeSync = (): LoadedRuntime => {
|
|
290
|
-
if (runtime) return runtime;
|
|
291
|
-
ensureOpenClawSdkResolution();
|
|
292
|
-
try {
|
|
293
|
-
const mod = pluginRequire(path.join(runtimeSourceDir, 'channel.ts')) as ChannelModule;
|
|
294
|
-
runtime = {
|
|
295
|
-
createBncrBridge: mod.createBncrBridge,
|
|
296
|
-
createBncrChannelPlugin: mod.createBncrChannelPlugin,
|
|
297
|
-
};
|
|
298
|
-
return runtime;
|
|
299
|
-
} catch (error) {
|
|
300
|
-
const detail = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
301
|
-
throw new Error(
|
|
302
|
-
`bncr failed to load channel runtime after dependency bootstrap from ${runtimeSourceDir}: ${detail}`,
|
|
303
|
-
);
|
|
304
|
-
}
|
|
305
|
-
};
|
|
306
|
-
|
|
307
|
-
const getIdentityId = (obj: object, prefix: string) => {
|
|
308
|
-
const existing = identityIds.get(obj);
|
|
309
|
-
if (existing) return existing;
|
|
310
|
-
const next = `${prefix}_${MODULE_EPOCH}_${++identitySeq}`;
|
|
311
|
-
identityIds.set(obj, next);
|
|
312
|
-
return next;
|
|
313
|
-
};
|
|
314
|
-
|
|
315
|
-
const getRegistryFingerprint = (api: OpenClawPluginApi) => {
|
|
316
|
-
const serviceId = getIdentityId(api.registerService as object, 'svc');
|
|
317
|
-
const channelId = getIdentityId(api.registerChannel as object, 'chn');
|
|
318
|
-
const methodId = getIdentityId(api.registerGatewayMethod as object, 'mth');
|
|
319
|
-
return `${serviceId}:${channelId}:${methodId}`;
|
|
320
|
-
};
|
|
321
|
-
|
|
322
|
-
const getRegisterMeta = (api: OpenClawPluginApi): RegisterMeta => {
|
|
323
|
-
const host = api as OpenClawPluginApiWithMeta;
|
|
324
|
-
if (!host[BNCR_REGISTER_META]) {
|
|
325
|
-
host[BNCR_REGISTER_META] = { methods: new Set<string>() };
|
|
326
|
-
}
|
|
327
|
-
if (!host[BNCR_REGISTER_META]!.methods) {
|
|
328
|
-
host[BNCR_REGISTER_META]!.methods = new Set<string>();
|
|
329
|
-
}
|
|
330
|
-
if (!host[BNCR_REGISTER_META]!.apiInstanceId) {
|
|
331
|
-
host[BNCR_REGISTER_META]!.apiInstanceId = getIdentityId(api as object, 'api');
|
|
332
|
-
}
|
|
333
|
-
if (!host[BNCR_REGISTER_META]!.registryFingerprint) {
|
|
334
|
-
host[BNCR_REGISTER_META]!.registryFingerprint = getRegistryFingerprint(api);
|
|
335
|
-
}
|
|
336
|
-
return host[BNCR_REGISTER_META]!;
|
|
337
|
-
};
|
|
338
|
-
|
|
339
|
-
const getProcessStore = () => {
|
|
340
|
-
const p = process as NodeJS.Process & {
|
|
341
|
-
[BNCR_GLOBAL_REGISTER_TRACE]?: GlobalRegisterTrace;
|
|
342
|
-
[BNCR_GATEWAY_RUNTIME]?: BncrGatewayRuntime;
|
|
343
|
-
};
|
|
344
|
-
return p;
|
|
345
|
-
};
|
|
346
|
-
|
|
347
|
-
const getGlobalRegisterTrace = () => {
|
|
348
|
-
const p = getProcessStore();
|
|
349
|
-
if (!p[BNCR_GLOBAL_REGISTER_TRACE]) {
|
|
350
|
-
p[BNCR_GLOBAL_REGISTER_TRACE] = {
|
|
351
|
-
seenRegistryFingerprints: new Set<string>(),
|
|
352
|
-
seenApiInstanceIds: new Set<string>(),
|
|
353
|
-
};
|
|
354
|
-
}
|
|
355
|
-
return p[BNCR_GLOBAL_REGISTER_TRACE]!;
|
|
356
|
-
};
|
|
357
|
-
|
|
358
|
-
const getGatewayRuntime = (): BncrGatewayRuntime => {
|
|
359
|
-
const p = getProcessStore();
|
|
360
|
-
if (!p[BNCR_GATEWAY_RUNTIME]) {
|
|
361
|
-
p[BNCR_GATEWAY_RUNTIME] = {
|
|
362
|
-
registeredMethodsByRegistry: new Map<string, Set<GatewayMethodName>>(),
|
|
363
|
-
serviceRegistered: false,
|
|
364
|
-
channelRegistered: false,
|
|
365
|
-
};
|
|
366
|
-
}
|
|
367
|
-
return p[BNCR_GATEWAY_RUNTIME]!;
|
|
368
|
-
};
|
|
369
|
-
|
|
370
|
-
const getProcessOwnerApiInstanceId = (gatewayRuntime: BncrGatewayRuntime) =>
|
|
371
|
-
gatewayRuntime.serviceOwnerApiInstanceId || gatewayRuntime.channelOwnerApiInstanceId || undefined;
|
|
372
|
-
|
|
373
|
-
const shouldAdoptProcessOwner = (apiInstanceId: string, gatewayRuntime: BncrGatewayRuntime) => {
|
|
374
|
-
const existingOwnerApiInstanceId = getProcessOwnerApiInstanceId(gatewayRuntime);
|
|
375
|
-
const hasSingletonOwner =
|
|
376
|
-
Boolean(gatewayRuntime.serviceRegistered) || Boolean(gatewayRuntime.channelRegistered);
|
|
377
|
-
|
|
378
|
-
if (!hasSingletonOwner) {
|
|
379
|
-
return {
|
|
380
|
-
adoptOwner: true,
|
|
381
|
-
existingOwnerApiInstanceId,
|
|
382
|
-
reason: 'no-singleton-owner',
|
|
383
|
-
};
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
if (existingOwnerApiInstanceId && existingOwnerApiInstanceId === apiInstanceId) {
|
|
387
|
-
return {
|
|
388
|
-
adoptOwner: true,
|
|
389
|
-
existingOwnerApiInstanceId,
|
|
390
|
-
reason: 'same-owner-api',
|
|
30
|
+
const registerRuntime = createBncrRegisterRuntime();
|
|
31
|
+
|
|
32
|
+
type BridgeSingletonWithOwner = NonNullable<
|
|
33
|
+
ReturnType<typeof registerRuntime.getExistingBridgeSingleton>
|
|
34
|
+
>;
|
|
35
|
+
type BridgeOwner = ReturnType<typeof registerRuntime.getBridgeOwnerFromBridge>;
|
|
36
|
+
const {
|
|
37
|
+
ensureGatewayMethodRegistered,
|
|
38
|
+
getBridgeSingleton,
|
|
39
|
+
getBridgeOwnerFromBridge,
|
|
40
|
+
getCurrentBridge,
|
|
41
|
+
getExistingBridgeSingleton,
|
|
42
|
+
getGatewayRuntime,
|
|
43
|
+
getGlobalRegisterTrace,
|
|
44
|
+
getRegisterMeta,
|
|
45
|
+
shouldAdoptProcessOwner,
|
|
46
|
+
} = registerRuntime;
|
|
47
|
+
|
|
48
|
+
type BncrDebugConfigRoot = {
|
|
49
|
+
channels?: {
|
|
50
|
+
bncr?: {
|
|
51
|
+
enabled?: boolean;
|
|
52
|
+
allowTool?: boolean;
|
|
53
|
+
debug?: {
|
|
54
|
+
verbose?: unknown;
|
|
55
|
+
};
|
|
391
56
|
};
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
return {
|
|
395
|
-
adoptOwner: false,
|
|
396
|
-
existingOwnerApiInstanceId,
|
|
397
|
-
reason: 'singleton-owned-by-other-api',
|
|
398
|
-
};
|
|
399
|
-
};
|
|
400
|
-
|
|
401
|
-
const gatewayMethodDispatchers: Record<
|
|
402
|
-
GatewayMethodName,
|
|
403
|
-
(bridge: BridgeSingletonWithOwner, opts: any) => any
|
|
404
|
-
> = {
|
|
405
|
-
'bncr.connect': (bridge, opts) => bridge.handleConnect(opts),
|
|
406
|
-
'bncr.inbound': (bridge, opts) => bridge.handleInbound(opts),
|
|
407
|
-
'bncr.activity': (bridge, opts) => bridge.handleActivity(opts),
|
|
408
|
-
'bncr.ack': (bridge, opts) => bridge.handleAck(opts),
|
|
409
|
-
'bncr.diagnostics': (bridge, opts) => bridge.handleDiagnostics(opts),
|
|
410
|
-
'bncr.deadLetter.inspect': (bridge, opts) => bridge.handleDeadLetterInspect(opts),
|
|
411
|
-
'bncr.deadLetter.prune': (bridge, opts) => bridge.handleDeadLetterPrune(opts),
|
|
412
|
-
'bncr.file.init': (bridge, opts) => bridge.handleFileInit(opts),
|
|
413
|
-
'bncr.file.chunk': (bridge, opts) => bridge.handleFileChunk(opts),
|
|
414
|
-
'bncr.file.complete': (bridge, opts) => bridge.handleFileComplete(opts),
|
|
415
|
-
'bncr.file.abort': (bridge, opts) => bridge.handleFileAbort(opts),
|
|
416
|
-
'bncr.file.ack': (bridge, opts) => bridge.handleFileAck(opts),
|
|
417
|
-
};
|
|
418
|
-
|
|
419
|
-
const dispatchGatewayMethod = (name: GatewayMethodName, opts: any) => {
|
|
420
|
-
const gatewayRuntime = getGatewayRuntime();
|
|
421
|
-
const bridge = gatewayRuntime.currentBridge;
|
|
422
|
-
if (!bridge) {
|
|
423
|
-
throw new Error(`bncr gateway runtime unavailable for ${name}`);
|
|
424
|
-
}
|
|
425
|
-
try {
|
|
426
|
-
return gatewayMethodDispatchers[name](bridge, opts);
|
|
427
|
-
} catch (error) {
|
|
428
|
-
const detail =
|
|
429
|
-
error instanceof Error
|
|
430
|
-
? {
|
|
431
|
-
name: error.name,
|
|
432
|
-
message: error.message,
|
|
433
|
-
stack: error.stack || null,
|
|
434
|
-
}
|
|
435
|
-
: { name: 'NonError', message: String(error), stack: null };
|
|
436
|
-
emitBncrLogLine(
|
|
437
|
-
'error',
|
|
438
|
-
`[bncr] gateway method error ${JSON.stringify({
|
|
439
|
-
method: name,
|
|
440
|
-
bridgeId: bridge.getBridgeId?.() || null,
|
|
441
|
-
gatewayPid: bridge.gatewayPid || null,
|
|
442
|
-
detail,
|
|
443
|
-
})}`,
|
|
444
|
-
{ debugOnly: true },
|
|
445
|
-
() => true,
|
|
446
|
-
);
|
|
447
|
-
throw error;
|
|
448
|
-
}
|
|
449
|
-
};
|
|
450
|
-
|
|
451
|
-
const mirrorGatewayMethodForMockApi = (api: OpenClawPluginApi, name: GatewayMethodName) => {
|
|
452
|
-
const host = api as OpenClawPluginApi & {
|
|
453
|
-
methods?: Array<{ name: string; handler: (opts: any) => any }>;
|
|
454
|
-
};
|
|
455
|
-
if (!Array.isArray(host.methods)) return;
|
|
456
|
-
if (host.methods.some((item) => item?.name === name)) return;
|
|
457
|
-
host.methods.push({ name, handler: (opts) => dispatchGatewayMethod(name, opts) });
|
|
458
|
-
};
|
|
459
|
-
|
|
460
|
-
const ensureGatewayMethodRegistered = (
|
|
461
|
-
api: OpenClawPluginApi,
|
|
462
|
-
name: GatewayMethodName,
|
|
463
|
-
debugLog: (...args: any[]) => void,
|
|
464
|
-
) => {
|
|
465
|
-
const meta = getRegisterMeta(api);
|
|
466
|
-
const gatewayRuntime = getGatewayRuntime();
|
|
467
|
-
const registryFingerprint = meta.registryFingerprint || getRegistryFingerprint(api);
|
|
468
|
-
let registryMethods = gatewayRuntime.registeredMethodsByRegistry.get(registryFingerprint);
|
|
469
|
-
if (!registryMethods) {
|
|
470
|
-
registryMethods = new Set<GatewayMethodName>();
|
|
471
|
-
gatewayRuntime.registeredMethodsByRegistry.set(registryFingerprint, registryMethods);
|
|
472
|
-
}
|
|
473
|
-
if (meta.methods?.has(name)) {
|
|
474
|
-
debugLog(`register method skip ${name} (already registered on this api)`);
|
|
475
|
-
return;
|
|
476
|
-
}
|
|
477
|
-
if (registryMethods.has(name)) {
|
|
478
|
-
mirrorGatewayMethodForMockApi(api, name);
|
|
479
|
-
meta.methods?.add(name);
|
|
480
|
-
debugLog(`register method reuse ${name} (already registered in registry)`);
|
|
481
|
-
return;
|
|
482
|
-
}
|
|
483
|
-
api.registerGatewayMethod(name, (opts) => dispatchGatewayMethod(name, opts));
|
|
484
|
-
mirrorGatewayMethodForMockApi(api, name);
|
|
485
|
-
registryMethods.add(name);
|
|
486
|
-
meta.methods?.add(name);
|
|
487
|
-
debugLog(`register method ok ${name}`);
|
|
488
|
-
};
|
|
489
|
-
|
|
490
|
-
const getBridgeOwner = (api: OpenClawPluginApi, loaded: LoadedRuntime): BridgeOwner => {
|
|
491
|
-
const meta = getRegisterMeta(api);
|
|
492
|
-
return {
|
|
493
|
-
moduleEpoch: MODULE_EPOCH,
|
|
494
|
-
bridgeFactoryId: getIdentityId(loaded.createBncrBridge as object, 'bridgeFactory'),
|
|
495
|
-
apiInstanceId: meta.apiInstanceId || 'unknown',
|
|
496
|
-
registryFingerprint: meta.registryFingerprint || 'unknown',
|
|
497
|
-
registrationMode: meta.registrationMode,
|
|
498
57
|
};
|
|
499
58
|
};
|
|
500
59
|
|
|
501
|
-
const sameBridgeOwner = (left?: BridgeOwner, right?: BridgeOwner) => {
|
|
502
|
-
if (!left || !right) return false;
|
|
503
|
-
return (
|
|
504
|
-
left.moduleEpoch === right.moduleEpoch &&
|
|
505
|
-
left.bridgeFactoryId === right.bridgeFactoryId &&
|
|
506
|
-
left.apiInstanceId === right.apiInstanceId &&
|
|
507
|
-
left.registryFingerprint === right.registryFingerprint
|
|
508
|
-
);
|
|
509
|
-
};
|
|
510
|
-
|
|
511
|
-
const snapshotBridgeRegisterState = (
|
|
512
|
-
bridge?: BridgeSingletonWithOwner,
|
|
513
|
-
): BridgeRegisterStateSnapshot | null => {
|
|
514
|
-
if (!bridge) return null;
|
|
515
|
-
return {
|
|
516
|
-
registerCount: Number(bridge.registerCount || 0),
|
|
517
|
-
apiGeneration: Number(bridge.apiGeneration || 0),
|
|
518
|
-
firstRegisterAt:
|
|
519
|
-
typeof bridge.firstRegisterAt === 'number'
|
|
520
|
-
? bridge.firstRegisterAt
|
|
521
|
-
: (bridge.firstRegisterAt ?? null),
|
|
522
|
-
lastRegisterAt:
|
|
523
|
-
typeof bridge.lastRegisterAt === 'number'
|
|
524
|
-
? bridge.lastRegisterAt
|
|
525
|
-
: (bridge.lastRegisterAt ?? null),
|
|
526
|
-
lastApiRebindAt:
|
|
527
|
-
typeof bridge.lastApiRebindAt === 'number'
|
|
528
|
-
? bridge.lastApiRebindAt
|
|
529
|
-
: (bridge.lastApiRebindAt ?? null),
|
|
530
|
-
pluginSource: typeof bridge.pluginSource === 'string' ? bridge.pluginSource : null,
|
|
531
|
-
pluginVersion: typeof bridge.pluginVersion === 'string' ? bridge.pluginVersion : null,
|
|
532
|
-
lastApiInstanceId:
|
|
533
|
-
typeof bridge.lastApiInstanceId === 'string' ? bridge.lastApiInstanceId : null,
|
|
534
|
-
lastRegistryFingerprint:
|
|
535
|
-
typeof bridge.lastRegistryFingerprint === 'string' ? bridge.lastRegistryFingerprint : null,
|
|
536
|
-
lastDriftSnapshot: bridge.lastDriftSnapshot ?? null,
|
|
537
|
-
registerTraceRecent: Array.isArray(bridge.registerTraceRecent)
|
|
538
|
-
? bridge.registerTraceRecent.map((trace) => ({ ...trace }))
|
|
539
|
-
: [],
|
|
540
|
-
};
|
|
541
|
-
};
|
|
542
|
-
|
|
543
|
-
const hydrateBridgeRegisterState = (
|
|
544
|
-
bridge: BridgeSingletonWithOwner,
|
|
545
|
-
snapshot: BridgeRegisterStateSnapshot | null,
|
|
546
|
-
) => {
|
|
547
|
-
if (!snapshot) return bridge;
|
|
548
|
-
bridge.registerCount = snapshot.registerCount;
|
|
549
|
-
bridge.apiGeneration = snapshot.apiGeneration;
|
|
550
|
-
bridge.firstRegisterAt = snapshot.firstRegisterAt;
|
|
551
|
-
bridge.lastRegisterAt = snapshot.lastRegisterAt;
|
|
552
|
-
bridge.lastApiRebindAt = snapshot.lastApiRebindAt;
|
|
553
|
-
bridge.pluginSource = snapshot.pluginSource;
|
|
554
|
-
bridge.pluginVersion = snapshot.pluginVersion;
|
|
555
|
-
bridge.lastApiInstanceId = snapshot.lastApiInstanceId;
|
|
556
|
-
bridge.lastRegistryFingerprint = snapshot.lastRegistryFingerprint;
|
|
557
|
-
bridge.lastDriftSnapshot = snapshot.lastDriftSnapshot;
|
|
558
|
-
bridge.registerTraceRecent = snapshot.registerTraceRecent.map((trace) => ({ ...trace }));
|
|
559
|
-
return bridge;
|
|
560
|
-
};
|
|
561
|
-
|
|
562
|
-
const assignBridgeOwner = (bridge: BridgeSingleton, owner: BridgeOwner) => {
|
|
563
|
-
(bridge as BridgeSingletonWithOwner)[BNCR_BRIDGE_OWNER] = owner;
|
|
564
|
-
return bridge as BridgeSingletonWithOwner;
|
|
565
|
-
};
|
|
566
|
-
|
|
567
|
-
const getBridgeSingleton = (api: OpenClawPluginApi) => {
|
|
568
|
-
const loaded = loadRuntimeSync();
|
|
569
|
-
const g = globalThis as typeof globalThis & { __bncrBridge?: BridgeSingletonWithOwner };
|
|
570
|
-
const owner = getBridgeOwner(api, loaded);
|
|
571
|
-
const previousOwner = g.__bncrBridge?.[BNCR_BRIDGE_OWNER];
|
|
572
|
-
|
|
573
|
-
let created = false;
|
|
574
|
-
let rebuilt = false;
|
|
575
|
-
|
|
576
|
-
if (g.__bncrBridge) {
|
|
577
|
-
const mustRebuild =
|
|
578
|
-
!sameBridgeOwner(previousOwner, owner) &&
|
|
579
|
-
(previousOwner?.moduleEpoch !== owner.moduleEpoch ||
|
|
580
|
-
previousOwner?.bridgeFactoryId !== owner.bridgeFactoryId ||
|
|
581
|
-
previousOwner?.registrationMode !== owner.registrationMode ||
|
|
582
|
-
previousOwner?.apiInstanceId !== owner.apiInstanceId ||
|
|
583
|
-
previousOwner?.registryFingerprint !== owner.registryFingerprint);
|
|
584
|
-
|
|
585
|
-
if (mustRebuild) {
|
|
586
|
-
const registerState = snapshotBridgeRegisterState(g.__bncrBridge);
|
|
587
|
-
try {
|
|
588
|
-
g.__bncrBridge.stopService?.();
|
|
589
|
-
} catch {
|
|
590
|
-
// ignore stop errors during hot-restart recovery
|
|
591
|
-
}
|
|
592
|
-
g.__bncrBridge = hydrateBridgeRegisterState(
|
|
593
|
-
assignBridgeOwner(loaded.createBncrBridge(api), owner),
|
|
594
|
-
registerState,
|
|
595
|
-
);
|
|
596
|
-
created = true;
|
|
597
|
-
rebuilt = true;
|
|
598
|
-
} else {
|
|
599
|
-
g.__bncrBridge.bindApi?.(api);
|
|
600
|
-
assignBridgeOwner(g.__bncrBridge, owner);
|
|
601
|
-
created = false;
|
|
602
|
-
rebuilt = false;
|
|
603
|
-
}
|
|
604
|
-
} else {
|
|
605
|
-
g.__bncrBridge = assignBridgeOwner(loaded.createBncrBridge(api), owner);
|
|
606
|
-
created = true;
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
return { bridge: g.__bncrBridge, runtime: loaded, created, rebuilt, owner, previousOwner };
|
|
610
|
-
};
|
|
611
|
-
|
|
612
|
-
const getExistingBridgeSingleton = (): BridgeSingletonWithOwner | undefined => {
|
|
613
|
-
const g = globalThis as typeof globalThis & { __bncrBridge?: BridgeSingletonWithOwner };
|
|
614
|
-
return g.__bncrBridge;
|
|
615
|
-
};
|
|
616
|
-
|
|
617
|
-
const isPlainObject = (value: unknown): value is Record<string, unknown> =>
|
|
618
|
-
typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
619
|
-
|
|
620
|
-
const getCurrentBridge = (): BridgeSingletonWithOwner => {
|
|
621
|
-
const bridge = getGatewayRuntime().currentBridge;
|
|
622
|
-
if (!bridge) throw new Error('bncr current bridge unavailable');
|
|
623
|
-
return bridge;
|
|
624
|
-
};
|
|
625
|
-
|
|
626
|
-
const createDynamicChannelPlugin = (loaded: LoadedRuntime): ChannelPlugin => {
|
|
627
|
-
const base = loaded.createBncrChannelPlugin(() => getCurrentBridge());
|
|
628
|
-
|
|
629
|
-
return {
|
|
630
|
-
...base,
|
|
631
|
-
outbound: {
|
|
632
|
-
...base.outbound,
|
|
633
|
-
sendText: (ctx: any) => getCurrentBridge().channelSendText(ctx),
|
|
634
|
-
sendMedia: (ctx: any) => getCurrentBridge().channelSendMedia(ctx),
|
|
635
|
-
},
|
|
636
|
-
status: {
|
|
637
|
-
...base.status,
|
|
638
|
-
buildChannelSummary: async ({ defaultAccountId }: any) =>
|
|
639
|
-
getCurrentBridge().getChannelSummary(defaultAccountId || 'Primary'),
|
|
640
|
-
buildAccountSnapshot: async ({ account, runtime }: any) => {
|
|
641
|
-
const bridgeNow = getCurrentBridge();
|
|
642
|
-
return base.status.buildAccountSnapshot({
|
|
643
|
-
account,
|
|
644
|
-
runtime: runtime || bridgeNow.getAccountRuntimeSnapshot(account?.accountId),
|
|
645
|
-
});
|
|
646
|
-
},
|
|
647
|
-
resolveAccountState: ({ enabled, configured, account, cfg, runtime }: any) => {
|
|
648
|
-
const bridgeNow = getCurrentBridge();
|
|
649
|
-
return base.status.resolveAccountState({
|
|
650
|
-
enabled,
|
|
651
|
-
configured,
|
|
652
|
-
account,
|
|
653
|
-
cfg,
|
|
654
|
-
runtime: runtime || bridgeNow.getAccountRuntimeSnapshot(account?.accountId),
|
|
655
|
-
});
|
|
656
|
-
},
|
|
657
|
-
},
|
|
658
|
-
gateway: {
|
|
659
|
-
...base.gateway,
|
|
660
|
-
startAccount: (ctx: any) => getCurrentBridge().channelStartAccount(ctx),
|
|
661
|
-
stopAccount: (ctx: any) => getCurrentBridge().channelStopAccount(ctx),
|
|
662
|
-
},
|
|
663
|
-
};
|
|
664
|
-
};
|
|
665
|
-
|
|
666
|
-
const registerBncrCli = (api: OpenClawPluginApi & { registerCli?: (...args: any[]) => void }) => {
|
|
667
|
-
if (typeof api.registerCli !== 'function') return;
|
|
668
|
-
api.registerCli(
|
|
669
|
-
({ program }: any) => {
|
|
670
|
-
const bncr = program.command('bncr').description('Bncr channel utilities');
|
|
671
|
-
bncr
|
|
672
|
-
.command('miniconfig')
|
|
673
|
-
.description(
|
|
674
|
-
'Seed minimal channels.bncr config (adds enabled=true and allowTool=false only when missing)',
|
|
675
|
-
)
|
|
676
|
-
.action(async () => {
|
|
677
|
-
const cfg = getOpenClawRuntimeConfig(api) as Record<string, unknown>;
|
|
678
|
-
const channels = isPlainObject(cfg.channels) ? cfg.channels : {};
|
|
679
|
-
const existing = isPlainObject(channels.bncr) ? channels.bncr : {};
|
|
680
|
-
const added: string[] = [];
|
|
681
|
-
|
|
682
|
-
if (existing.enabled === undefined) {
|
|
683
|
-
added.push('enabled=true');
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
if (existing.allowTool === undefined) {
|
|
687
|
-
added.push('allowTool=false');
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
if (added.length === 0) {
|
|
691
|
-
console.log('Minimal bncr config already present. No changes made.');
|
|
692
|
-
return;
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
await mutateOpenClawRuntimeConfigFile(api, {
|
|
696
|
-
afterWrite: { mode: 'auto' },
|
|
697
|
-
mutate(draft: Record<string, unknown>) {
|
|
698
|
-
if (!isPlainObject(draft.channels)) draft.channels = {};
|
|
699
|
-
const draftChannels = draft.channels as Record<string, unknown>;
|
|
700
|
-
const draftExisting = isPlainObject(draftChannels.bncr) ? draftChannels.bncr : {};
|
|
701
|
-
const draftBncrCfg: Record<string, unknown> = { ...draftExisting };
|
|
702
|
-
|
|
703
|
-
if (draftBncrCfg.enabled === undefined) {
|
|
704
|
-
draftBncrCfg.enabled = true;
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
if (draftBncrCfg.allowTool === undefined) {
|
|
708
|
-
draftBncrCfg.allowTool = false;
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
draftChannels.bncr = draftBncrCfg;
|
|
712
|
-
},
|
|
713
|
-
});
|
|
714
|
-
console.log('Seeded minimal bncr config at channels.bncr.');
|
|
715
|
-
console.log(`Added missing fields: ${added.join(', ')}`);
|
|
716
|
-
console.log('Gateway will apply the config using the host afterWrite policy.');
|
|
717
|
-
});
|
|
718
|
-
},
|
|
719
|
-
{ commands: ['bncr'] },
|
|
720
|
-
);
|
|
721
|
-
};
|
|
722
|
-
|
|
723
|
-
const shouldSkipNonRuntimeRegister = (mode?: string) =>
|
|
724
|
-
mode === 'cli-metadata' || mode === 'discovery';
|
|
725
|
-
|
|
726
60
|
const plugin = {
|
|
727
61
|
id: 'bncr',
|
|
728
62
|
name: 'Bncr',
|
|
729
63
|
description: 'Bncr channel plugin',
|
|
730
64
|
configSchema: BncrConfigSchema,
|
|
731
|
-
register(
|
|
732
|
-
api: OpenClawPluginApi & { registerCli?: (...args: any[]) => void; registrationMode?: string },
|
|
733
|
-
) {
|
|
65
|
+
register(api: BncrRegistrationApi) {
|
|
734
66
|
registerBncrCli(api);
|
|
735
67
|
if (shouldSkipNonRuntimeRegister(api.registrationMode)) return;
|
|
736
68
|
|
|
@@ -770,9 +102,9 @@ const plugin = {
|
|
|
770
102
|
previousOwner = adopted.previousOwner;
|
|
771
103
|
gatewayRuntime.currentBridge = bridge;
|
|
772
104
|
} else {
|
|
773
|
-
runtime =
|
|
105
|
+
runtime = loadBncrRuntimeSync();
|
|
774
106
|
bridge = gatewayRuntime.currentBridge || getExistingBridgeSingleton();
|
|
775
|
-
previousOwner =
|
|
107
|
+
previousOwner = getBridgeOwnerFromBridge(bridge);
|
|
776
108
|
owner = previousOwner;
|
|
777
109
|
if (bridge && !gatewayRuntime.currentBridge) {
|
|
778
110
|
gatewayRuntime.currentBridge = bridge;
|
|
@@ -784,13 +116,13 @@ const plugin = {
|
|
|
784
116
|
globalTrace.lastApiInstanceId = apiInstanceId;
|
|
785
117
|
globalTrace.lastRegistryFingerprint = registryFingerprint;
|
|
786
118
|
bridge?.noteRegister?.({
|
|
787
|
-
source: '
|
|
119
|
+
source: '@xmoxmo/bncr',
|
|
788
120
|
pluginVersion,
|
|
789
121
|
apiRebound: ownerDecision.adoptOwner ? !created && !rebuilt : false,
|
|
790
122
|
apiInstanceId: meta.apiInstanceId,
|
|
791
123
|
registryFingerprint: meta.registryFingerprint,
|
|
792
124
|
});
|
|
793
|
-
const debugLog = (...args:
|
|
125
|
+
const debugLog = (...args: unknown[]) => {
|
|
794
126
|
const rendered = args
|
|
795
127
|
.map((arg) => (typeof arg === 'string' ? arg : JSON.stringify(arg)))
|
|
796
128
|
.join(' ')
|
|
@@ -826,8 +158,8 @@ const plugin = {
|
|
|
826
158
|
|
|
827
159
|
const resolveDebug = async () => {
|
|
828
160
|
try {
|
|
829
|
-
const cfg = getOpenClawRuntimeConfig(api);
|
|
830
|
-
return Boolean(
|
|
161
|
+
const cfg = getOpenClawRuntimeConfig(api) as BncrDebugConfigRoot | null | undefined;
|
|
162
|
+
return Boolean(cfg?.channels?.bncr?.debug?.verbose);
|
|
831
163
|
} catch {
|
|
832
164
|
return false;
|
|
833
165
|
}
|
|
@@ -857,7 +189,9 @@ const plugin = {
|
|
|
857
189
|
}
|
|
858
190
|
|
|
859
191
|
if (!gatewayRuntime.channelRegistered) {
|
|
860
|
-
api.registerChannel({
|
|
192
|
+
api.registerChannel({
|
|
193
|
+
plugin: createDynamicChannelPlugin({ loaded: runtime, getCurrentBridge }) as ChannelPlugin,
|
|
194
|
+
});
|
|
861
195
|
gatewayRuntime.channelRegistered = true;
|
|
862
196
|
gatewayRuntime.channelOwnerApiInstanceId = apiInstanceId;
|
|
863
197
|
meta.channel = true;
|