@xmoxmo/bncr 0.6.7 → 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/plugin/bridge-lifecycle.ts +13 -2
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
export const REGISTRY_RUNTIME_OBSERVATION_TIMEOUT_MS = 30_000;
|
|
2
|
+
export const REGISTRY_RUNTIME_OBSERVATION_POLL_MS = 5_000;
|
|
3
|
+
|
|
4
|
+
export type RegistryRuntimeObservationPhase = 'awaiting' | 'polling' | 'ready' | 'retired';
|
|
5
|
+
|
|
6
|
+
export type RegistryRuntimeProbe = {
|
|
7
|
+
serviceRunning?: boolean;
|
|
8
|
+
channelRunning?: boolean;
|
|
9
|
+
activeChannelAccounts?: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type RegistryRuntimeObservation = {
|
|
13
|
+
phase: RegistryRuntimeObservationPhase;
|
|
14
|
+
startedAt: number;
|
|
15
|
+
deadlineAt: number;
|
|
16
|
+
pollingStartedAt: number | null;
|
|
17
|
+
readyAt: number | null;
|
|
18
|
+
retiredAt: number | null;
|
|
19
|
+
lastProbeAt: number | null;
|
|
20
|
+
probeCount: number;
|
|
21
|
+
serviceObservedAt: number | null;
|
|
22
|
+
channelObservedAt: number | null;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type RegistryRuntimeObservationDecision = {
|
|
26
|
+
observation: RegistryRuntimeObservation;
|
|
27
|
+
action: 'wait' | 'poll' | 'ready' | 'retired';
|
|
28
|
+
transitionedToPolling: boolean;
|
|
29
|
+
transitionedToReady: boolean;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function finiteTimestamp(value: unknown): number | null {
|
|
33
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function nonNegativeInteger(value: unknown): number {
|
|
37
|
+
return typeof value === 'number' && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function createRegistryRuntimeObservation(now: number): RegistryRuntimeObservation {
|
|
41
|
+
return {
|
|
42
|
+
phase: 'awaiting',
|
|
43
|
+
startedAt: now,
|
|
44
|
+
deadlineAt: now + REGISTRY_RUNTIME_OBSERVATION_TIMEOUT_MS,
|
|
45
|
+
pollingStartedAt: null,
|
|
46
|
+
readyAt: null,
|
|
47
|
+
retiredAt: null,
|
|
48
|
+
lastProbeAt: null,
|
|
49
|
+
probeCount: 0,
|
|
50
|
+
serviceObservedAt: null,
|
|
51
|
+
channelObservedAt: null,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function normalizeRegistryRuntimeObservation(
|
|
56
|
+
value: unknown,
|
|
57
|
+
): RegistryRuntimeObservation | undefined {
|
|
58
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined;
|
|
59
|
+
const source = value as Record<string, unknown>;
|
|
60
|
+
const phase = source.phase;
|
|
61
|
+
if (phase !== 'awaiting' && phase !== 'polling' && phase !== 'ready' && phase !== 'retired') {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
const startedAt = finiteTimestamp(source.startedAt);
|
|
65
|
+
const deadlineAt = finiteTimestamp(source.deadlineAt);
|
|
66
|
+
if (startedAt === null || deadlineAt === null) return undefined;
|
|
67
|
+
return {
|
|
68
|
+
phase,
|
|
69
|
+
startedAt,
|
|
70
|
+
deadlineAt,
|
|
71
|
+
pollingStartedAt: finiteTimestamp(source.pollingStartedAt),
|
|
72
|
+
readyAt: finiteTimestamp(source.readyAt),
|
|
73
|
+
retiredAt: finiteTimestamp(source.retiredAt),
|
|
74
|
+
lastProbeAt: finiteTimestamp(source.lastProbeAt),
|
|
75
|
+
probeCount: nonNegativeInteger(source.probeCount),
|
|
76
|
+
serviceObservedAt: finiteTimestamp(source.serviceObservedAt),
|
|
77
|
+
channelObservedAt: finiteTimestamp(source.channelObservedAt),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function retireObservation(
|
|
82
|
+
observation: RegistryRuntimeObservation,
|
|
83
|
+
now: number,
|
|
84
|
+
): RegistryRuntimeObservationDecision {
|
|
85
|
+
return {
|
|
86
|
+
observation: {
|
|
87
|
+
...observation,
|
|
88
|
+
phase: 'retired',
|
|
89
|
+
retiredAt: observation.retiredAt ?? now,
|
|
90
|
+
lastProbeAt: now,
|
|
91
|
+
probeCount: observation.probeCount + 1,
|
|
92
|
+
},
|
|
93
|
+
action: 'retired',
|
|
94
|
+
transitionedToPolling: false,
|
|
95
|
+
transitionedToReady: false,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function evaluateRegistryRuntimeObservation(args: {
|
|
100
|
+
observation: RegistryRuntimeObservation;
|
|
101
|
+
now: number;
|
|
102
|
+
lifecycleActive: boolean;
|
|
103
|
+
probe: RegistryRuntimeProbe;
|
|
104
|
+
}): RegistryRuntimeObservationDecision {
|
|
105
|
+
const { observation, now, lifecycleActive, probe } = args;
|
|
106
|
+
if (observation.phase === 'retired') {
|
|
107
|
+
return {
|
|
108
|
+
observation,
|
|
109
|
+
action: 'retired',
|
|
110
|
+
transitionedToPolling: false,
|
|
111
|
+
transitionedToReady: false,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
if (!lifecycleActive) {
|
|
115
|
+
return retireObservation(observation, now);
|
|
116
|
+
}
|
|
117
|
+
if (observation.phase === 'ready') {
|
|
118
|
+
return {
|
|
119
|
+
observation,
|
|
120
|
+
action: 'ready',
|
|
121
|
+
transitionedToPolling: false,
|
|
122
|
+
transitionedToReady: false,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const serviceRunning = probe.serviceRunning === true;
|
|
127
|
+
const channelRunning = probe.channelRunning === true || (probe.activeChannelAccounts ?? 0) > 0;
|
|
128
|
+
const serviceObservedAt = observation.serviceObservedAt ?? (serviceRunning ? now : null);
|
|
129
|
+
const channelObservedAt = observation.channelObservedAt ?? (channelRunning ? now : null);
|
|
130
|
+
const observed = {
|
|
131
|
+
...observation,
|
|
132
|
+
lastProbeAt: now,
|
|
133
|
+
probeCount: observation.probeCount + 1,
|
|
134
|
+
serviceObservedAt,
|
|
135
|
+
channelObservedAt,
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
if (serviceRunning && channelRunning) {
|
|
139
|
+
return {
|
|
140
|
+
observation: {
|
|
141
|
+
...observed,
|
|
142
|
+
phase: 'ready',
|
|
143
|
+
readyAt: observed.readyAt ?? now,
|
|
144
|
+
},
|
|
145
|
+
action: 'ready',
|
|
146
|
+
transitionedToPolling: false,
|
|
147
|
+
transitionedToReady: true,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (now >= observation.deadlineAt) {
|
|
152
|
+
return {
|
|
153
|
+
observation: {
|
|
154
|
+
...observed,
|
|
155
|
+
phase: 'polling',
|
|
156
|
+
pollingStartedAt: observed.pollingStartedAt ?? now,
|
|
157
|
+
},
|
|
158
|
+
action: 'poll',
|
|
159
|
+
transitionedToPolling: observation.phase === 'awaiting',
|
|
160
|
+
transitionedToReady: false,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
observation: observed,
|
|
166
|
+
action: 'wait',
|
|
167
|
+
transitionedToPolling: false,
|
|
168
|
+
transitionedToReady: false,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
@@ -2,7 +2,6 @@ import {
|
|
|
2
2
|
type BridgeOwner,
|
|
3
3
|
type BridgeRegisterStateCarrier,
|
|
4
4
|
hydrateBridgeRegisterState,
|
|
5
|
-
sameBridgeOwner,
|
|
6
5
|
snapshotBridgeRegisterState,
|
|
7
6
|
} from './register-runtime-helpers.ts';
|
|
8
7
|
import type { ChannelModule, LoadedRuntime } from './runtime-loader.ts';
|
|
@@ -17,6 +16,8 @@ type BridgeOwnedCarrier = BridgeRegisterStateCarrier & {
|
|
|
17
16
|
};
|
|
18
17
|
type GlobalBridgeStore = typeof globalThis & { __bncrBridge?: BridgeSingleton };
|
|
19
18
|
|
|
19
|
+
export type BridgeAdoptionMode = 'create' | 'reuse' | 'replace';
|
|
20
|
+
|
|
20
21
|
function isBridgeOwner(value: unknown): value is BridgeOwner {
|
|
21
22
|
return Boolean(
|
|
22
23
|
value &&
|
|
@@ -48,10 +49,13 @@ export function createBncrBridgeSingletonManager(runtime: {
|
|
|
48
49
|
return bridge;
|
|
49
50
|
};
|
|
50
51
|
|
|
51
|
-
const
|
|
52
|
+
const adoptBridgeSingleton = (
|
|
53
|
+
api: OpenClawPluginApi,
|
|
54
|
+
owner: BridgeOwner,
|
|
55
|
+
mode: BridgeAdoptionMode,
|
|
56
|
+
) => {
|
|
52
57
|
const loaded = runtime.loadBncrRuntimeSync();
|
|
53
58
|
const g = globalThis as GlobalBridgeStore;
|
|
54
|
-
const owner = runtime.getBridgeOwner(api, loaded);
|
|
55
59
|
const previousOwnerRaw = g.__bncrBridge
|
|
56
60
|
? getBridgeOwnedCarrier(g.__bncrBridge)[runtime.bridgeOwnerSymbol]
|
|
57
61
|
: undefined;
|
|
@@ -59,57 +63,56 @@ export function createBncrBridgeSingletonManager(runtime: {
|
|
|
59
63
|
|
|
60
64
|
let created = false;
|
|
61
65
|
let rebuilt = false;
|
|
66
|
+
let bridge: BridgeSingleton | undefined;
|
|
62
67
|
|
|
63
|
-
if (g.__bncrBridge) {
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
previousOwner?.apiInstanceId !== owner.apiInstanceId ||
|
|
70
|
-
previousOwner?.registryFingerprint !== owner.registryFingerprint);
|
|
71
|
-
|
|
72
|
-
if (mustRebuild) {
|
|
73
|
-
const registerState = snapshotBridgeRegisterState(
|
|
74
|
-
getBridgeRegisterStateCarrier(g.__bncrBridge),
|
|
75
|
-
);
|
|
76
|
-
try {
|
|
77
|
-
g.__bncrBridge.stopService?.();
|
|
78
|
-
} catch {
|
|
79
|
-
// ignore stop errors during hot-restart recovery
|
|
80
|
-
}
|
|
81
|
-
const rebuiltBridge = assignBridgeOwner(
|
|
82
|
-
loaded.createBncrBridge(api, {
|
|
83
|
-
pluginRoot: runtime.pluginRoot,
|
|
84
|
-
pluginFile: runtime.pluginFile,
|
|
85
|
-
}),
|
|
86
|
-
owner,
|
|
87
|
-
);
|
|
88
|
-
hydrateBridgeRegisterState(getBridgeRegisterStateCarrier(rebuiltBridge), registerState);
|
|
89
|
-
g.__bncrBridge = rebuiltBridge;
|
|
90
|
-
created = true;
|
|
91
|
-
rebuilt = true;
|
|
92
|
-
} else {
|
|
93
|
-
g.__bncrBridge.bindApi?.(api);
|
|
94
|
-
assignBridgeOwner(g.__bncrBridge, owner);
|
|
95
|
-
}
|
|
96
|
-
} else {
|
|
97
|
-
g.__bncrBridge = assignBridgeOwner(
|
|
68
|
+
if (!g.__bncrBridge || mode === 'create' || mode === 'replace') {
|
|
69
|
+
const registerState =
|
|
70
|
+
mode === 'replace' && g.__bncrBridge
|
|
71
|
+
? snapshotBridgeRegisterState(getBridgeRegisterStateCarrier(g.__bncrBridge))
|
|
72
|
+
: null;
|
|
73
|
+
bridge = assignBridgeOwner(
|
|
98
74
|
loaded.createBncrBridge(api, {
|
|
99
75
|
pluginRoot: runtime.pluginRoot,
|
|
100
76
|
pluginFile: runtime.pluginFile,
|
|
101
77
|
}),
|
|
102
78
|
owner,
|
|
103
79
|
);
|
|
80
|
+
hydrateBridgeRegisterState(getBridgeRegisterStateCarrier(bridge), registerState);
|
|
81
|
+
g.__bncrBridge = bridge;
|
|
104
82
|
created = true;
|
|
83
|
+
rebuilt = mode === 'replace' && Boolean(previousOwner);
|
|
84
|
+
} else {
|
|
85
|
+
bridge = g.__bncrBridge;
|
|
86
|
+
bridge.bindApi?.(api);
|
|
87
|
+
assignBridgeOwner(bridge, owner);
|
|
105
88
|
}
|
|
106
89
|
|
|
107
|
-
|
|
90
|
+
bridge.bindRuntimePaths?.({
|
|
108
91
|
pluginRoot: runtime.pluginRoot,
|
|
109
92
|
pluginFile: runtime.pluginFile,
|
|
110
93
|
});
|
|
111
94
|
|
|
112
|
-
return { bridge
|
|
95
|
+
return { bridge, runtime: loaded, created, rebuilt, owner, previousOwner };
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const getBridgeSingleton = (api: OpenClawPluginApi) => {
|
|
99
|
+
const loaded = runtime.loadBncrRuntimeSync();
|
|
100
|
+
const owner = runtime.getBridgeOwner(api, loaded);
|
|
101
|
+
const g = globalThis as GlobalBridgeStore;
|
|
102
|
+
const previous = getBridgeOwnerFromBridge(g.__bncrBridge);
|
|
103
|
+
const sameGeneration =
|
|
104
|
+
previous?.moduleEpoch === owner.moduleEpoch &&
|
|
105
|
+
previous?.bridgeFactoryId === owner.bridgeFactoryId &&
|
|
106
|
+
previous?.pluginVersion === owner.pluginVersion &&
|
|
107
|
+
previous?.registrationMode === owner.registrationMode &&
|
|
108
|
+
previous?.pluginRoot === owner.pluginRoot &&
|
|
109
|
+
previous?.pluginFile === owner.pluginFile;
|
|
110
|
+
const mode: BridgeAdoptionMode = !g.__bncrBridge
|
|
111
|
+
? 'create'
|
|
112
|
+
: sameGeneration
|
|
113
|
+
? 'reuse'
|
|
114
|
+
: 'replace';
|
|
115
|
+
return adoptBridgeSingleton(api, owner, mode);
|
|
113
116
|
};
|
|
114
117
|
|
|
115
118
|
const getExistingBridgeSingleton = () => {
|
|
@@ -129,6 +132,7 @@ export function createBncrBridgeSingletonManager(runtime: {
|
|
|
129
132
|
|
|
130
133
|
return {
|
|
131
134
|
assignBridgeOwner,
|
|
135
|
+
adoptBridgeSingleton,
|
|
132
136
|
getBridgeRegisterStateCarrier,
|
|
133
137
|
getBridgeSingleton,
|
|
134
138
|
getExistingBridgeSingleton,
|