@xmoxmo/bncr 0.6.6 → 0.6.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1386 -206
- package/index.ts +291 -131
- package/package.json +1 -1
- package/scripts/check-pack.mjs +2 -0
- package/scripts/selfcheck.mjs +2 -0
- package/src/bootstrap/channel-plugin-runtime.ts +28 -7
- package/src/bootstrap/register-lifecycle.ts +492 -0
- package/src/bootstrap/register-runtime-gateway.ts +64 -3
- package/src/bootstrap/register-runtime-helpers.ts +8 -44
- package/src/bootstrap/register-runtime-observation.ts +170 -0
- package/src/bootstrap/register-runtime-singleton.ts +44 -40
- package/src/bootstrap/register-runtime.ts +918 -15
- package/src/channel.ts +25 -2
- package/src/messaging/inbound/commands.ts +75 -65
- package/src/messaging/inbound/conversation-history.ts +28 -1
- package/src/plugin/bridge-lifecycle.ts +13 -2
- package/src/plugin/config.ts +18 -0
- package/src/plugin/inbound-handlers.ts +11 -5
- package/src/plugin/sqlite-state.ts +6 -23
- package/src/plugin/state-store.ts +3 -30
package/index.ts
CHANGED
|
@@ -26,15 +26,32 @@ type BridgeSingletonWithOwner = NonNullable<
|
|
|
26
26
|
>;
|
|
27
27
|
type BridgeOwner = ReturnType<typeof registerRuntime.getBridgeOwnerFromBridge>;
|
|
28
28
|
const {
|
|
29
|
+
adoptLifecycleBridge,
|
|
30
|
+
beginRetirement,
|
|
31
|
+
canStartLifecycleRegistry,
|
|
32
|
+
canStopLifecycleRegistry,
|
|
33
|
+
commitLifecycle,
|
|
29
34
|
ensureGatewayMethodRegistered,
|
|
30
|
-
getBridgeSingleton,
|
|
31
35
|
getBridgeOwnerFromBridge,
|
|
36
|
+
getBridgeGenerationKey,
|
|
32
37
|
getCurrentBridge,
|
|
33
38
|
getExistingBridgeSingleton,
|
|
34
39
|
getGatewayRuntime,
|
|
35
40
|
getGlobalRegisterTrace,
|
|
41
|
+
getLifecycleBridgeForStart,
|
|
36
42
|
getRegisterMeta,
|
|
37
|
-
|
|
43
|
+
getRegistryDeclaration,
|
|
44
|
+
isLifecycleRegistryCurrent,
|
|
45
|
+
markRegistryChannelDeclared,
|
|
46
|
+
markRegistryRegistrationComplete,
|
|
47
|
+
markRegistryServiceDeclared,
|
|
48
|
+
noteRegisterTraceValue,
|
|
49
|
+
planLifecycle,
|
|
50
|
+
probeRegistryRuntimeObservation,
|
|
51
|
+
pruneRegistryLedgers,
|
|
52
|
+
recoverLifecycleRegistration,
|
|
53
|
+
settleRetirement,
|
|
54
|
+
failLifecycleStart,
|
|
38
55
|
} = registerRuntime;
|
|
39
56
|
|
|
40
57
|
type BncrDebugConfigRoot = {
|
|
@@ -71,150 +88,293 @@ const plugin = {
|
|
|
71
88
|
const registryFingerprint = meta.registryFingerprint || 'unknown';
|
|
72
89
|
const sameApiAsPrevious = previousApiInstanceId === apiInstanceId;
|
|
73
90
|
const sameRegistryAsPrevious = previousRegistryFingerprint === registryFingerprint;
|
|
74
|
-
const
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
91
|
+
const apiSeenRecently = globalTrace.seenApiInstanceIds.has(apiInstanceId);
|
|
92
|
+
const registrySeenRecently = globalTrace.seenRegistryFingerprints.has(registryFingerprint);
|
|
93
|
+
|
|
94
|
+
let ownerDecision: ReturnType<typeof planLifecycle> | undefined;
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const gatewayRuntime = getGatewayRuntime();
|
|
98
|
+
ownerDecision = planLifecycle(api);
|
|
99
|
+
const lifecycleOwner = commitLifecycle(ownerDecision);
|
|
100
|
+
let bridge: BridgeSingletonWithOwner | undefined;
|
|
101
|
+
const runtime: LoadedRuntime = loadBncrRuntimeSync();
|
|
102
|
+
let created = false;
|
|
103
|
+
let rebuilt = false;
|
|
104
|
+
let owner: BridgeOwner | undefined;
|
|
105
|
+
let previousOwner: BridgeOwner | undefined;
|
|
106
|
+
const shouldDeclareLifecycle =
|
|
107
|
+
ownerDecision.kind === 'initialize' ||
|
|
108
|
+
ownerDecision.kind === 'takeover' ||
|
|
109
|
+
ownerDecision.kind === 'defer';
|
|
110
|
+
|
|
111
|
+
if (ownerDecision.kind === 'initialize') {
|
|
112
|
+
if (lifecycleOwner) {
|
|
113
|
+
const adopted = adoptLifecycleBridge(api, lifecycleOwner, 'create');
|
|
114
|
+
bridge = adopted.bridge;
|
|
115
|
+
created = adopted.created;
|
|
116
|
+
rebuilt = adopted.rebuilt;
|
|
117
|
+
owner = adopted.owner;
|
|
118
|
+
previousOwner = adopted.previousOwner;
|
|
119
|
+
}
|
|
120
|
+
} else if (ownerDecision.kind === 'takeover') {
|
|
121
|
+
if (lifecycleOwner) {
|
|
122
|
+
const adopted = adoptLifecycleBridge(api, lifecycleOwner, ownerDecision.mode);
|
|
123
|
+
bridge = adopted.bridge;
|
|
124
|
+
created = adopted.created;
|
|
125
|
+
rebuilt = adopted.rebuilt;
|
|
126
|
+
owner = adopted.owner;
|
|
127
|
+
previousOwner = adopted.previousOwner;
|
|
128
|
+
}
|
|
129
|
+
} else if (ownerDecision.kind === 'defer') {
|
|
130
|
+
bridge = gatewayRuntime.currentBridge || getExistingBridgeSingleton();
|
|
131
|
+
previousOwner = getBridgeOwnerFromBridge(bridge);
|
|
132
|
+
owner = previousOwner;
|
|
133
|
+
} else {
|
|
134
|
+
bridge = gatewayRuntime.currentBridge || getExistingBridgeSingleton();
|
|
135
|
+
previousOwner = getBridgeOwnerFromBridge(bridge);
|
|
136
|
+
owner = previousOwner;
|
|
137
|
+
if (bridge && !gatewayRuntime.currentBridge) {
|
|
138
|
+
gatewayRuntime.currentBridge = bridge;
|
|
139
|
+
}
|
|
101
140
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
141
|
+
|
|
142
|
+
/*
|
|
143
|
+
* A duplicate or rejected registration must never adopt or rebuild the
|
|
144
|
+
* process bridge. It still registers methods for its own registry.
|
|
145
|
+
*/
|
|
107
146
|
if (bridge && !gatewayRuntime.currentBridge) {
|
|
108
147
|
gatewayRuntime.currentBridge = bridge;
|
|
109
148
|
}
|
|
110
|
-
}
|
|
111
149
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
150
|
+
noteRegisterTraceValue(globalTrace.seenApiInstanceIds, apiInstanceId);
|
|
151
|
+
noteRegisterTraceValue(globalTrace.seenRegistryFingerprints, registryFingerprint);
|
|
152
|
+
globalTrace.lastApiInstanceId = apiInstanceId;
|
|
153
|
+
globalTrace.lastRegistryFingerprint = registryFingerprint;
|
|
154
|
+
bridge?.noteRegister?.({
|
|
155
|
+
source: '@xmoxmo/bncr',
|
|
156
|
+
pluginVersion,
|
|
157
|
+
apiRebound: ownerDecision.kind === 'takeover' && !created && !rebuilt,
|
|
158
|
+
apiInstanceId: meta.apiInstanceId,
|
|
159
|
+
registryFingerprint: meta.registryFingerprint,
|
|
160
|
+
});
|
|
161
|
+
const debugLog = (...args: unknown[]) => {
|
|
162
|
+
const rendered = args
|
|
163
|
+
.map((arg) => (typeof arg === 'string' ? arg : JSON.stringify(arg)))
|
|
164
|
+
.join(' ')
|
|
165
|
+
.trim();
|
|
166
|
+
if (!rendered) return;
|
|
167
|
+
emitBncrLogLine('info', `[bncr] debug ${rendered}`, { debugOnly: true }, () =>
|
|
168
|
+
Boolean(bridge?.isDebugEnabled?.()),
|
|
169
|
+
);
|
|
170
|
+
};
|
|
133
171
|
|
|
134
|
-
debugLog(
|
|
135
|
-
`register begin bridge=${bridge?.getBridgeId?.() || 'unknown'} created=${created} rebuilt=${rebuilt} ` +
|
|
136
|
-
`ownerApi=${owner?.apiInstanceId || 'none'} ownerRegistry=${owner?.registryFingerprint || 'none'} ` +
|
|
137
|
-
`previousOwnerApi=${previousOwner?.apiInstanceId || 'none'} previousOwnerRegistry=${previousOwner?.registryFingerprint || 'none'}`,
|
|
138
|
-
);
|
|
139
|
-
debugLog(
|
|
140
|
-
`register classify mode=${meta.registrationMode || 'unknown'} api=${apiInstanceId} registry=${registryFingerprint} ` +
|
|
141
|
-
`sameApiAsPrevious=${sameApiAsPrevious} sameRegistryAsPrevious=${sameRegistryAsPrevious} ` +
|
|
142
|
-
`firstSeenApi=${firstSeenApi} firstSeenRegistry=${firstSeenRegistry}`,
|
|
143
|
-
);
|
|
144
|
-
debugLog(
|
|
145
|
-
`register owner adopt=${ownerDecision.adoptOwner} reason=${ownerDecision.reason} ` +
|
|
146
|
-
`existingOwnerApi=${ownerDecision.existingOwnerApiInstanceId || 'none'}`,
|
|
147
|
-
);
|
|
148
|
-
if (!ownerDecision.adoptOwner) {
|
|
149
172
|
debugLog(
|
|
150
|
-
`
|
|
173
|
+
`register begin bridge=${bridge?.getBridgeId?.() || 'unknown'} created=${created} rebuilt=${rebuilt} ` +
|
|
174
|
+
`ownerApi=${owner?.apiInstanceId || 'none'} ownerRegistry=${owner?.registryFingerprint || 'none'} ` +
|
|
175
|
+
`previousOwnerApi=${previousOwner?.apiInstanceId || 'none'} previousOwnerRegistry=${previousOwner?.registryFingerprint || 'none'}`,
|
|
151
176
|
);
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
177
|
+
debugLog(
|
|
178
|
+
`register classify mode=${meta.registrationMode || 'unknown'} api=${apiInstanceId} registry=${registryFingerprint} ` +
|
|
179
|
+
`sameApiAsPrevious=${sameApiAsPrevious} sameRegistryAsPrevious=${sameRegistryAsPrevious} ` +
|
|
180
|
+
`apiSeenRecently=${apiSeenRecently} registrySeenRecently=${registrySeenRecently}`,
|
|
181
|
+
);
|
|
182
|
+
debugLog(
|
|
183
|
+
`register lifecycle decision=${ownerDecision.kind} reason=${ownerDecision.reason} ` +
|
|
184
|
+
`generation=${ownerDecision.owner.key.generation} ` +
|
|
185
|
+
`existingOwnerApi=${previousOwner?.apiInstanceId || 'none'}`,
|
|
186
|
+
);
|
|
187
|
+
if (!shouldDeclareLifecycle) {
|
|
188
|
+
debugLog(
|
|
189
|
+
`service/channel declaration suppressed for ${ownerDecision.kind} registry=${registryFingerprint}`,
|
|
190
|
+
);
|
|
163
191
|
}
|
|
164
|
-
};
|
|
165
192
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
193
|
+
const resolveDebug = async () => {
|
|
194
|
+
try {
|
|
195
|
+
const cfg = getOpenClawRuntimeConfig(api) as BncrDebugConfigRoot | null | undefined;
|
|
196
|
+
return Boolean(cfg?.channels?.bncr?.debug?.verbose);
|
|
197
|
+
} catch {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
169
200
|
};
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
await getCurrentBridge().startService(ctx, debug);
|
|
175
|
-
},
|
|
176
|
-
stop: serviceStopHandler,
|
|
177
|
-
});
|
|
178
|
-
gatewayRuntime.serviceRegistered = true;
|
|
179
|
-
gatewayRuntime.serviceOwnerApiInstanceId = apiInstanceId;
|
|
180
|
-
meta.service = true;
|
|
181
|
-
debugLog(`register service ok ownerApi=${apiInstanceId}`);
|
|
182
|
-
} else {
|
|
183
|
-
meta.service = true;
|
|
184
|
-
debugLog(
|
|
185
|
-
`register service skip (process singleton already registered by api ${gatewayRuntime.serviceOwnerApiInstanceId || 'unknown'})`,
|
|
201
|
+
|
|
202
|
+
const registryGeneration = ownerDecision.owner.key.generation;
|
|
203
|
+
const registryBridgeGenerationKey = getBridgeGenerationKey(
|
|
204
|
+
ownerDecision.owner.bridgeGeneration,
|
|
186
205
|
);
|
|
187
|
-
|
|
206
|
+
/*
|
|
207
|
+
* A rejected registration must remain fail-closed. Creating a shadow
|
|
208
|
+
* declaration here would make its gateway methods dispatch to the
|
|
209
|
+
* current bridge even though the registration never owned a lifecycle.
|
|
210
|
+
*/
|
|
211
|
+
const registryDeclaration =
|
|
212
|
+
ownerDecision.kind === 'reject'
|
|
213
|
+
? gatewayRuntime.registryDeclarations.get(registryFingerprint)
|
|
214
|
+
: getRegistryDeclaration(
|
|
215
|
+
registryFingerprint,
|
|
216
|
+
registryBridgeGenerationKey,
|
|
217
|
+
registryGeneration,
|
|
218
|
+
);
|
|
219
|
+
const serviceDeclaredForGeneration = registryDeclaration?.service === 'declared';
|
|
220
|
+
const channelDeclaredForGeneration = registryDeclaration?.channel === 'declared';
|
|
188
221
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
222
|
+
if (shouldDeclareLifecycle && !serviceDeclaredForGeneration) {
|
|
223
|
+
const serviceStopHandler = async () => {
|
|
224
|
+
const retirement = beginRetirement(registryFingerprint, registryGeneration);
|
|
225
|
+
if (!retirement) {
|
|
226
|
+
debugLog(`service stop suppressed for stale registry=${registryFingerprint}`);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
try {
|
|
231
|
+
await getCurrentBridge().stopService?.();
|
|
232
|
+
settleRetirement(registryFingerprint, { ok: true }, registryGeneration);
|
|
233
|
+
} catch (error) {
|
|
234
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
235
|
+
settleRetirement(registryFingerprint, { ok: false, error: detail }, registryGeneration);
|
|
236
|
+
throw error;
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
api.registerService({
|
|
240
|
+
id: 'bncr-bridge-service',
|
|
241
|
+
start: async (ctx) => {
|
|
242
|
+
const lifecycleBridge = await getLifecycleBridgeForStart(
|
|
243
|
+
api,
|
|
244
|
+
registryFingerprint,
|
|
245
|
+
registryGeneration,
|
|
246
|
+
);
|
|
247
|
+
if (!lifecycleBridge) {
|
|
248
|
+
debugLog(`service start suppressed for stale registry=${registryFingerprint}`);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const debug = await resolveDebug();
|
|
252
|
+
if (!canStartLifecycleRegistry(registryFingerprint, registryGeneration)) {
|
|
253
|
+
debugLog(
|
|
254
|
+
`service start suppressed after await for stale registry=${registryFingerprint}`,
|
|
255
|
+
);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
try {
|
|
259
|
+
const started = await lifecycleBridge.startService(ctx, debug, () =>
|
|
260
|
+
isLifecycleRegistryCurrent(registryFingerprint, registryGeneration),
|
|
261
|
+
);
|
|
262
|
+
if (
|
|
263
|
+
started === false ||
|
|
264
|
+
!canStartLifecycleRegistry(registryFingerprint, registryGeneration)
|
|
265
|
+
) {
|
|
266
|
+
debugLog(
|
|
267
|
+
`service start suppressed after await for stale registry=${registryFingerprint}`,
|
|
268
|
+
);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
probeRegistryRuntimeObservation(
|
|
272
|
+
registryFingerprint,
|
|
273
|
+
registryBridgeGenerationKey,
|
|
274
|
+
registryGeneration,
|
|
275
|
+
);
|
|
276
|
+
} catch (error) {
|
|
277
|
+
failLifecycleStart(registryFingerprint, error, registryGeneration);
|
|
278
|
+
throw error;
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
stop: serviceStopHandler,
|
|
282
|
+
});
|
|
283
|
+
markRegistryServiceDeclared(
|
|
284
|
+
registryFingerprint,
|
|
285
|
+
registryBridgeGenerationKey,
|
|
286
|
+
registryGeneration,
|
|
287
|
+
);
|
|
288
|
+
meta.service = true;
|
|
289
|
+
debugLog(`register service ok ownerApi=${apiInstanceId}`);
|
|
290
|
+
} else {
|
|
291
|
+
meta.service = registryDeclaration?.service === 'declared';
|
|
292
|
+
debugLog(
|
|
293
|
+
`register service skip registry=${registryFingerprint} declared=${registryDeclaration?.service || 'missing'}`,
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (shouldDeclareLifecycle && !channelDeclaredForGeneration) {
|
|
298
|
+
api.registerChannel({
|
|
299
|
+
plugin: createDynamicChannelPlugin({
|
|
300
|
+
loaded: runtime,
|
|
301
|
+
getCurrentBridge,
|
|
302
|
+
resolveBridgeForStart: async () => {
|
|
303
|
+
const lifecycleBridge = await getLifecycleBridgeForStart(
|
|
304
|
+
api,
|
|
305
|
+
registryFingerprint,
|
|
306
|
+
registryGeneration,
|
|
307
|
+
);
|
|
308
|
+
if (!lifecycleBridge) {
|
|
309
|
+
throw new Error(`bncr lifecycle registry is inactive: ${registryFingerprint}`);
|
|
310
|
+
}
|
|
311
|
+
return lifecycleBridge;
|
|
312
|
+
},
|
|
313
|
+
isBridgeForStartCurrent: () =>
|
|
314
|
+
canStartLifecycleRegistry(registryFingerprint, registryGeneration),
|
|
315
|
+
onBridgeStartObserved: () =>
|
|
316
|
+
probeRegistryRuntimeObservation(
|
|
317
|
+
registryFingerprint,
|
|
318
|
+
registryBridgeGenerationKey,
|
|
319
|
+
registryGeneration,
|
|
320
|
+
),
|
|
321
|
+
resolveBridgeForStop: () => {
|
|
322
|
+
if (!canStopLifecycleRegistry(registryFingerprint, registryGeneration)) {
|
|
323
|
+
debugLog(`channel stop suppressed for stale registry=${registryFingerprint}`);
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
return getCurrentBridge();
|
|
327
|
+
},
|
|
328
|
+
isBridgeForStopCurrent: () =>
|
|
329
|
+
canStopLifecycleRegistry(registryFingerprint, registryGeneration),
|
|
330
|
+
}) as ChannelPlugin,
|
|
331
|
+
});
|
|
332
|
+
markRegistryChannelDeclared(
|
|
333
|
+
registryFingerprint,
|
|
334
|
+
registryBridgeGenerationKey,
|
|
335
|
+
registryGeneration,
|
|
336
|
+
);
|
|
337
|
+
meta.channel = true;
|
|
338
|
+
debugLog(`register channel ok ownerApi=${apiInstanceId}`);
|
|
339
|
+
} else {
|
|
340
|
+
meta.channel = registryDeclaration?.channel === 'declared';
|
|
341
|
+
debugLog(
|
|
342
|
+
`register channel skip registry=${registryFingerprint} declared=${registryDeclaration?.channel || 'missing'}`,
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
ensureGatewayMethodRegistered(api, 'bncr.connect', debugLog);
|
|
347
|
+
ensureGatewayMethodRegistered(api, 'bncr.inbound', debugLog);
|
|
348
|
+
ensureGatewayMethodRegistered(api, 'bncr.activity', debugLog);
|
|
349
|
+
ensureGatewayMethodRegistered(api, 'bncr.ack', debugLog);
|
|
350
|
+
ensureGatewayMethodRegistered(api, 'bncr.diagnostics', debugLog);
|
|
351
|
+
ensureGatewayMethodRegistered(api, 'bncr.deadLetter.inspect', debugLog);
|
|
352
|
+
ensureGatewayMethodRegistered(api, 'bncr.deadLetter.prune', debugLog);
|
|
353
|
+
ensureGatewayMethodRegistered(api, 'bncr.rpc.response', debugLog);
|
|
354
|
+
ensureGatewayMethodRegistered(api, 'bncr.file.init', debugLog);
|
|
355
|
+
ensureGatewayMethodRegistered(api, 'bncr.file.chunk', debugLog);
|
|
356
|
+
ensureGatewayMethodRegistered(api, 'bncr.file.complete', debugLog);
|
|
357
|
+
ensureGatewayMethodRegistered(api, 'bncr.file.abort', debugLog);
|
|
358
|
+
ensureGatewayMethodRegistered(api, 'bncr.file.ack', debugLog);
|
|
359
|
+
if (ownerDecision.kind !== 'reject') {
|
|
360
|
+
markRegistryRegistrationComplete(
|
|
361
|
+
registryFingerprint,
|
|
362
|
+
registryBridgeGenerationKey,
|
|
363
|
+
registryGeneration,
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
pruneRegistryLedgers();
|
|
367
|
+
debugLog('register done');
|
|
368
|
+
} catch (error) {
|
|
369
|
+
const recovered = ownerDecision
|
|
370
|
+
? recoverLifecycleRegistration(ownerDecision, error)
|
|
371
|
+
: 'plan-not-committed';
|
|
372
|
+
emitBncrLogLine(
|
|
373
|
+
'error',
|
|
374
|
+
`[bncr] register failed recovered=${recovered} error=${error instanceof Error ? error.message : String(error)}`,
|
|
201
375
|
);
|
|
376
|
+
throw error;
|
|
202
377
|
}
|
|
203
|
-
|
|
204
|
-
ensureGatewayMethodRegistered(api, 'bncr.connect', debugLog);
|
|
205
|
-
ensureGatewayMethodRegistered(api, 'bncr.inbound', debugLog);
|
|
206
|
-
ensureGatewayMethodRegistered(api, 'bncr.activity', debugLog);
|
|
207
|
-
ensureGatewayMethodRegistered(api, 'bncr.ack', debugLog);
|
|
208
|
-
ensureGatewayMethodRegistered(api, 'bncr.diagnostics', debugLog);
|
|
209
|
-
ensureGatewayMethodRegistered(api, 'bncr.deadLetter.inspect', debugLog);
|
|
210
|
-
ensureGatewayMethodRegistered(api, 'bncr.deadLetter.prune', debugLog);
|
|
211
|
-
ensureGatewayMethodRegistered(api, 'bncr.rpc.response', debugLog);
|
|
212
|
-
ensureGatewayMethodRegistered(api, 'bncr.file.init', debugLog);
|
|
213
|
-
ensureGatewayMethodRegistered(api, 'bncr.file.chunk', debugLog);
|
|
214
|
-
ensureGatewayMethodRegistered(api, 'bncr.file.complete', debugLog);
|
|
215
|
-
ensureGatewayMethodRegistered(api, 'bncr.file.abort', debugLog);
|
|
216
|
-
ensureGatewayMethodRegistered(api, 'bncr.file.ack', debugLog);
|
|
217
|
-
debugLog('register done');
|
|
218
378
|
},
|
|
219
379
|
};
|
|
220
380
|
|
package/package.json
CHANGED
package/scripts/check-pack.mjs
CHANGED
|
@@ -18,7 +18,9 @@ const requiredScriptFiles = [
|
|
|
18
18
|
const requiredSourceFiles = [
|
|
19
19
|
'src/bootstrap/channel-plugin-runtime.ts',
|
|
20
20
|
'src/bootstrap/cli.ts',
|
|
21
|
+
'src/bootstrap/register-lifecycle.ts',
|
|
21
22
|
'src/bootstrap/register-runtime-helpers.ts',
|
|
23
|
+
'src/bootstrap/register-runtime-observation.ts',
|
|
22
24
|
'src/bootstrap/register-runtime.ts',
|
|
23
25
|
'src/bootstrap/runtime-discovery.ts',
|
|
24
26
|
'src/bootstrap/runtime-loader.ts',
|
package/scripts/selfcheck.mjs
CHANGED
|
@@ -14,8 +14,10 @@ const requiredRootFiles = ['index.ts', 'openclaw.plugin.json'];
|
|
|
14
14
|
const requiredSourceFiles = [
|
|
15
15
|
'src/bootstrap/channel-plugin-runtime.ts',
|
|
16
16
|
'src/bootstrap/cli.ts',
|
|
17
|
+
'src/bootstrap/register-lifecycle.ts',
|
|
17
18
|
'src/bootstrap/register-runtime-gateway.ts',
|
|
18
19
|
'src/bootstrap/register-runtime-helpers.ts',
|
|
20
|
+
'src/bootstrap/register-runtime-observation.ts',
|
|
19
21
|
'src/bootstrap/register-runtime-singleton.ts',
|
|
20
22
|
'src/bootstrap/register-runtime.ts',
|
|
21
23
|
'src/bootstrap/runtime-discovery.ts',
|
|
@@ -10,8 +10,17 @@ type LoadedRuntime = {
|
|
|
10
10
|
export function createDynamicChannelPlugin(args: {
|
|
11
11
|
loaded: LoadedRuntime;
|
|
12
12
|
getCurrentBridge: () => BridgeSingleton;
|
|
13
|
+
resolveBridgeForStart?: () => BridgeSingleton | Promise<BridgeSingleton>;
|
|
14
|
+
isBridgeForStartCurrent?: () => boolean;
|
|
15
|
+
onBridgeStartObserved?: () => void;
|
|
16
|
+
resolveBridgeForStop?: () => BridgeSingleton | null | Promise<BridgeSingleton | null>;
|
|
17
|
+
isBridgeForStopCurrent?: () => boolean;
|
|
13
18
|
}): ChannelPlugin {
|
|
14
19
|
const { loaded, getCurrentBridge } = args;
|
|
20
|
+
const resolveBridgeForStart = args.resolveBridgeForStart || getCurrentBridge;
|
|
21
|
+
const isBridgeForStartCurrent = args.isBridgeForStartCurrent || (() => true);
|
|
22
|
+
const resolveBridgeForStop = args.resolveBridgeForStop || getCurrentBridge;
|
|
23
|
+
const isBridgeForStopCurrent = args.isBridgeForStopCurrent || (() => true);
|
|
15
24
|
const base = loaded.createBncrChannelPlugin(() => getCurrentBridge());
|
|
16
25
|
const plugin = { ...base } as ChannelPlugin;
|
|
17
26
|
const outbound = base.outbound as ChannelPlugin['outbound'];
|
|
@@ -67,14 +76,26 @@ export function createDynamicChannelPlugin(args: {
|
|
|
67
76
|
|
|
68
77
|
plugin.gateway = {
|
|
69
78
|
...baseGateway,
|
|
70
|
-
startAccount: (ctx: GatewayStartAccountArgs) =>
|
|
71
|
-
|
|
79
|
+
startAccount: async (ctx: GatewayStartAccountArgs) => {
|
|
80
|
+
const bridge = await resolveBridgeForStart();
|
|
81
|
+
if (!isBridgeForStartCurrent()) return;
|
|
82
|
+
const task = bridge.channelStartAccount(
|
|
72
83
|
ctx as Parameters<BridgeSingleton['channelStartAccount']>[0],
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
84
|
+
);
|
|
85
|
+
if (isBridgeForStartCurrent()) {
|
|
86
|
+
try {
|
|
87
|
+
args.onBridgeStartObserved?.();
|
|
88
|
+
} catch {
|
|
89
|
+
// Observation is diagnostic-only and must not fail channel startup.
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return task;
|
|
93
|
+
},
|
|
94
|
+
stopAccount: async (ctx: GatewayStopAccountArgs) => {
|
|
95
|
+
const bridge = await resolveBridgeForStop();
|
|
96
|
+
if (!bridge || !isBridgeForStopCurrent()) return;
|
|
97
|
+
return bridge.channelStopAccount(ctx as Parameters<BridgeSingleton['channelStopAccount']>[0]);
|
|
98
|
+
},
|
|
78
99
|
};
|
|
79
100
|
|
|
80
101
|
return plugin;
|