@zhin.js/core 1.0.53 → 1.0.55
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/CHANGELOG.md +23 -0
- package/lib/adapter.d.ts +13 -23
- package/lib/adapter.d.ts.map +1 -1
- package/lib/adapter.js +26 -71
- package/lib/adapter.js.map +1 -1
- package/lib/built/adapter-process.d.ts.map +1 -1
- package/lib/built/adapter-process.js +1 -0
- package/lib/built/adapter-process.js.map +1 -1
- package/lib/built/agent-preset.d.ts +63 -0
- package/lib/built/agent-preset.d.ts.map +1 -0
- package/lib/built/agent-preset.js +76 -0
- package/lib/built/agent-preset.js.map +1 -0
- package/lib/built/component.d.ts +2 -2
- package/lib/built/component.d.ts.map +1 -1
- package/lib/built/component.js +3 -2
- package/lib/built/component.js.map +1 -1
- package/lib/built/database.d.ts +2 -2
- package/lib/built/database.d.ts.map +1 -1
- package/lib/built/database.js.map +1 -1
- package/lib/built/skill.d.ts +10 -0
- package/lib/built/skill.d.ts.map +1 -1
- package/lib/built/skill.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -1
- package/lib/plugin.d.ts +43 -95
- package/lib/plugin.d.ts.map +1 -1
- package/lib/plugin.js +135 -268
- package/lib/plugin.js.map +1 -1
- package/lib/types.d.ts +11 -0
- package/lib/types.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/adapter.ts +37 -95
- package/src/built/adapter-process.ts +1 -0
- package/src/built/agent-preset.ts +136 -0
- package/src/built/component.ts +5 -3
- package/src/built/database.ts +2 -1
- package/src/built/skill.ts +14 -0
- package/src/index.ts +2 -0
- package/src/plugin.ts +198 -373
- package/src/types.ts +16 -0
package/lib/plugin.js
CHANGED
|
@@ -7,15 +7,14 @@ import { EventEmitter } from "events";
|
|
|
7
7
|
import { createRequire } from "module";
|
|
8
8
|
import * as fs from "fs";
|
|
9
9
|
import * as path from "path";
|
|
10
|
-
import { fileURLToPath
|
|
10
|
+
import { fileURLToPath } from "url";
|
|
11
11
|
import logger from "@zhin.js/logger";
|
|
12
12
|
import { compose, remove, resolveEntry } from "./utils.js";
|
|
13
13
|
import { Adapter } from "./adapter.js";
|
|
14
|
-
import { Feature } from "./feature.js";
|
|
15
14
|
import { createHash } from "crypto";
|
|
15
|
+
import { Feature } from "./feature.js";
|
|
16
16
|
const contextsKey = Symbol("contexts");
|
|
17
|
-
const
|
|
18
|
-
const loadedModules = new Map();
|
|
17
|
+
const loadedModules = new Map(); // 记录已加载的模块
|
|
19
18
|
const require = createRequire(import.meta.url);
|
|
20
19
|
// ============================================================================
|
|
21
20
|
// AsyncLocalStorage 上下文
|
|
@@ -46,17 +45,16 @@ function getCurrentFile(metaUrl = import.meta.url) {
|
|
|
46
45
|
/**
|
|
47
46
|
* usePlugin - 获取或创建当前插件实例
|
|
48
47
|
* 类似 React Hooks 的设计,根据调用文件自动创建插件树
|
|
49
|
-
*
|
|
50
|
-
* 同一个文件多次调用 usePlugin() 返回同一个实例,
|
|
51
|
-
* 避免 Plugin.create() + usePlugin() 产生不必要的双层包装。
|
|
48
|
+
* 同一上下文中同一文件多次调用返回同一实例
|
|
52
49
|
*/
|
|
53
50
|
export function usePlugin() {
|
|
54
51
|
const callerFile = getCurrentFile();
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (
|
|
58
|
-
return
|
|
52
|
+
// 如果当前 store 已是同一文件创建的插件,直接返回
|
|
53
|
+
const current = storage.getStore();
|
|
54
|
+
if (current && current.filePath.replace(/\?t=\d+$/, '') === callerFile.replace(/\?t=\d+$/, '')) {
|
|
55
|
+
return current;
|
|
59
56
|
}
|
|
57
|
+
const parentPlugin = current;
|
|
60
58
|
const newPlugin = new Plugin(callerFile, parentPlugin);
|
|
61
59
|
storage.enterWith(newPlugin);
|
|
62
60
|
return newPlugin;
|
|
@@ -109,10 +107,8 @@ function watchFile(filePath, callback) {
|
|
|
109
107
|
export class Plugin extends EventEmitter {
|
|
110
108
|
parent;
|
|
111
109
|
static [contextsKey] = [];
|
|
112
|
-
/** Maps extension method name → context name that owns it */
|
|
113
|
-
static [extensionOwnersKey] = new Map();
|
|
114
110
|
#cachedName;
|
|
115
|
-
#
|
|
111
|
+
#manifest;
|
|
116
112
|
adapters = [];
|
|
117
113
|
started = false;
|
|
118
114
|
// 上下文存储
|
|
@@ -139,21 +135,20 @@ export class Plugin extends EventEmitter {
|
|
|
139
135
|
};
|
|
140
136
|
// 插件功能
|
|
141
137
|
#middlewares = [this.#messageMiddleware];
|
|
142
|
-
//
|
|
138
|
+
// AI 工具
|
|
143
139
|
#tools = new Map();
|
|
140
|
+
// Feature 贡献追踪
|
|
141
|
+
#featureContributions = new Map();
|
|
144
142
|
// 统一的清理函数集合
|
|
145
143
|
#disposables = new Set();
|
|
146
|
-
// 记录当前插件向哪些 Feature 贡献了哪些 item
|
|
147
|
-
#featureContributions = new Map();
|
|
148
144
|
get middleware() {
|
|
149
145
|
return compose(this.#middlewares);
|
|
150
146
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
return this.#middlewares.filter(m => m !== this.#messageMiddleware);
|
|
147
|
+
recordFeatureContribution(featureName, itemName) {
|
|
148
|
+
if (!this.#featureContributions.has(featureName)) {
|
|
149
|
+
this.#featureContributions.set(featureName, new Set());
|
|
150
|
+
}
|
|
151
|
+
this.#featureContributions.get(featureName).add(itemName);
|
|
157
152
|
}
|
|
158
153
|
/**
|
|
159
154
|
* 构造函数
|
|
@@ -178,19 +173,8 @@ export class Plugin extends EventEmitter {
|
|
|
178
173
|
/**
|
|
179
174
|
* 添加中间件
|
|
180
175
|
* 中间件用于处理消息流转
|
|
181
|
-
* @param middleware 中间件函数
|
|
182
176
|
*/
|
|
183
|
-
addMiddleware(middleware) {
|
|
184
|
-
// Always register on root so middlewares reach the global chain
|
|
185
|
-
const target = this.root;
|
|
186
|
-
if (target !== this) {
|
|
187
|
-
const dispose = target.addMiddleware(middleware);
|
|
188
|
-
this.#disposables.add(dispose);
|
|
189
|
-
return () => {
|
|
190
|
-
dispose();
|
|
191
|
-
this.#disposables.delete(dispose);
|
|
192
|
-
};
|
|
193
|
-
}
|
|
177
|
+
addMiddleware(middleware, name) {
|
|
194
178
|
this.#middlewares.push(middleware);
|
|
195
179
|
const dispose = () => {
|
|
196
180
|
remove(this.#middlewares, middleware);
|
|
@@ -200,23 +184,13 @@ export class Plugin extends EventEmitter {
|
|
|
200
184
|
return dispose;
|
|
201
185
|
}
|
|
202
186
|
/**
|
|
203
|
-
*
|
|
204
|
-
* 工具可以被 AI
|
|
187
|
+
* 添加 AI 工具
|
|
188
|
+
* 工具可以被 AI 服务调用来执行操作
|
|
205
189
|
* @param tool 工具定义
|
|
206
|
-
* @param generateCommand 是否生成对应命令(默认 true)
|
|
207
190
|
* @returns 返回一个移除工具的函数
|
|
208
191
|
*/
|
|
209
|
-
addTool(tool
|
|
210
|
-
|
|
211
|
-
if (toolService && typeof toolService.addTool === 'function') {
|
|
212
|
-
const dispose = toolService.addTool(tool, this.name, generateCommand);
|
|
213
|
-
this.#disposables.add(dispose);
|
|
214
|
-
return () => {
|
|
215
|
-
dispose();
|
|
216
|
-
this.#disposables.delete(dispose);
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
// 回退到本地存储
|
|
192
|
+
addTool(tool) {
|
|
193
|
+
// 自动添加插件源标识
|
|
220
194
|
const toolWithSource = {
|
|
221
195
|
...tool,
|
|
222
196
|
source: tool.source || `plugin:${this.name}`,
|
|
@@ -263,15 +237,10 @@ export class Plugin extends EventEmitter {
|
|
|
263
237
|
return undefined;
|
|
264
238
|
}
|
|
265
239
|
/**
|
|
266
|
-
*
|
|
267
|
-
*
|
|
240
|
+
* 收集所有可用的工具(包括适配器提供的)
|
|
241
|
+
* 这是 AI 服务获取工具的主入口
|
|
268
242
|
*/
|
|
269
243
|
collectAllTools() {
|
|
270
|
-
const toolService = this.root.inject('tool');
|
|
271
|
-
if (toolService && typeof toolService.collectAll === 'function') {
|
|
272
|
-
return toolService.collectAll(this.root);
|
|
273
|
-
}
|
|
274
|
-
// 回退到本地收集
|
|
275
244
|
const tools = [];
|
|
276
245
|
// 收集插件树中的所有工具
|
|
277
246
|
const rootPlugin = this.root;
|
|
@@ -287,45 +256,71 @@ export class Plugin extends EventEmitter {
|
|
|
287
256
|
return tools;
|
|
288
257
|
}
|
|
289
258
|
/**
|
|
290
|
-
*
|
|
291
|
-
* 可通过以下方式声明:
|
|
292
|
-
* 1. 模块导出 `export const pluginName = 'my-plugin'`
|
|
293
|
-
* 2. 调用 `plugin.setName('my-plugin')`
|
|
294
|
-
*/
|
|
295
|
-
setName(name) {
|
|
296
|
-
this.#explicitName = name;
|
|
297
|
-
this.#cachedName = name;
|
|
298
|
-
this.logger = logger.getLogger(name);
|
|
299
|
-
}
|
|
300
|
-
/**
|
|
301
|
-
* 插件名称(显式名称 > 路径推导)
|
|
259
|
+
* 插件名称
|
|
302
260
|
*/
|
|
303
261
|
get name() {
|
|
304
|
-
if (this.#explicitName)
|
|
305
|
-
return this.#explicitName;
|
|
306
262
|
if (this.#cachedName)
|
|
307
263
|
return this.#cachedName;
|
|
308
|
-
|
|
264
|
+
this.#cachedName = path
|
|
309
265
|
.relative(process.cwd(), this.filePath)
|
|
310
266
|
.replace(/\?t=\d+$/, "")
|
|
311
267
|
.replace(/\\/g, "/")
|
|
312
268
|
.replace(/\/index\.(js|ts)x?$/, "")
|
|
313
|
-
.replace(/\/(lib|src|dist)$/, "")
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
name = name.substring(nodeModulesIndex + 'node_modules/'.length);
|
|
318
|
-
}
|
|
319
|
-
// 提取最后一个路径段
|
|
320
|
-
const lastSlash = name.lastIndexOf('/');
|
|
321
|
-
if (lastSlash !== -1) {
|
|
322
|
-
name = name.substring(lastSlash + 1);
|
|
323
|
-
}
|
|
324
|
-
// 移除文件扩展名
|
|
325
|
-
name = name.replace(/\.(js|ts)x?$/, "");
|
|
326
|
-
this.#cachedName = name;
|
|
269
|
+
.replace(/\/(lib|src|dist)$/, "")
|
|
270
|
+
.replace(/.*\/node_modules\//, "")
|
|
271
|
+
.replace(/.*\//, "")
|
|
272
|
+
.replace(/\.(js|ts)x?$/, "");
|
|
327
273
|
return this.#cachedName;
|
|
328
274
|
}
|
|
275
|
+
/**
|
|
276
|
+
* 插件清单(从 plugin.yml 或 package.json 延迟读取)
|
|
277
|
+
*/
|
|
278
|
+
get manifest() {
|
|
279
|
+
if (this.#manifest !== undefined)
|
|
280
|
+
return this.#manifest ?? undefined;
|
|
281
|
+
if (!this.filePath) {
|
|
282
|
+
this.#manifest = null;
|
|
283
|
+
return undefined;
|
|
284
|
+
}
|
|
285
|
+
const dir = path.dirname(this.filePath);
|
|
286
|
+
// 优先读取 plugin.yml
|
|
287
|
+
const ymlPath = path.join(dir, 'plugin.yml');
|
|
288
|
+
if (fs.existsSync(ymlPath)) {
|
|
289
|
+
try {
|
|
290
|
+
const content = fs.readFileSync(ymlPath, 'utf-8');
|
|
291
|
+
const match = content.match(/^name:\s*(.+)$/m);
|
|
292
|
+
const descMatch = content.match(/^description:\s*(.+)$/m);
|
|
293
|
+
const verMatch = content.match(/^version:\s*(.+)$/m);
|
|
294
|
+
if (match) {
|
|
295
|
+
this.#manifest = {
|
|
296
|
+
name: match[1].trim(),
|
|
297
|
+
description: descMatch?.[1]?.trim(),
|
|
298
|
+
version: verMatch?.[1]?.trim(),
|
|
299
|
+
};
|
|
300
|
+
return this.#manifest;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
catch { /* ignore */ }
|
|
304
|
+
}
|
|
305
|
+
// Fallback: package.json
|
|
306
|
+
const pkgPath = path.join(dir, 'package.json');
|
|
307
|
+
if (fs.existsSync(pkgPath)) {
|
|
308
|
+
try {
|
|
309
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
310
|
+
if (pkg.name) {
|
|
311
|
+
this.#manifest = {
|
|
312
|
+
name: pkg.name,
|
|
313
|
+
description: pkg.description,
|
|
314
|
+
version: pkg.version,
|
|
315
|
+
};
|
|
316
|
+
return this.#manifest;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
catch { /* ignore */ }
|
|
320
|
+
}
|
|
321
|
+
this.#manifest = null;
|
|
322
|
+
return undefined;
|
|
323
|
+
}
|
|
329
324
|
/**
|
|
330
325
|
* 根插件
|
|
331
326
|
*/
|
|
@@ -365,6 +360,7 @@ export class Plugin extends EventEmitter {
|
|
|
365
360
|
sideEffect.finished = false;
|
|
366
361
|
};
|
|
367
362
|
this.on('context.dispose', disposeFn);
|
|
363
|
+
// 确保 dispose 时清理监听器(只注册一次)
|
|
368
364
|
const cleanupOnDispose = () => {
|
|
369
365
|
this.off('context.dispose', disposeFn);
|
|
370
366
|
dispose(this.inject(args[0]));
|
|
@@ -414,9 +410,18 @@ export class Plugin extends EventEmitter {
|
|
|
414
410
|
for (const context of this.$contexts.values()) {
|
|
415
411
|
if (typeof context.mounted === "function") {
|
|
416
412
|
const result = await context.mounted(this);
|
|
417
|
-
//
|
|
418
|
-
if (!context.value)
|
|
413
|
+
// 仅在没有预设 value 时才用 mounted 返回值赋值
|
|
414
|
+
if (!context.value) {
|
|
419
415
|
context.value = result;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
// 注册扩展方法到 Plugin.prototype
|
|
419
|
+
if (context.extensions) {
|
|
420
|
+
for (const [name, fn] of Object.entries(context.extensions)) {
|
|
421
|
+
if (typeof fn === 'function') {
|
|
422
|
+
Reflect.set(Plugin.prototype, name, fn);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
420
425
|
}
|
|
421
426
|
this.dispatch('context.mounted', context.name);
|
|
422
427
|
}
|
|
@@ -425,149 +430,55 @@ export class Plugin extends EventEmitter {
|
|
|
425
430
|
for (const child of this.children) {
|
|
426
431
|
await child.start(t);
|
|
427
432
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
* @param featureName Feature 名称(如 'command')
|
|
433
|
-
* @param itemName item 标识(如命令 pattern)
|
|
434
|
-
*/
|
|
435
|
-
recordFeatureContribution(featureName, itemName) {
|
|
436
|
-
if (!this.#featureContributions.has(featureName)) {
|
|
437
|
-
this.#featureContributions.set(featureName, new Set());
|
|
438
|
-
}
|
|
439
|
-
this.#featureContributions.get(featureName).add(itemName);
|
|
440
|
-
}
|
|
441
|
-
/**
|
|
442
|
-
* 收集本插件及所有后代插件的 Feature 贡献名称
|
|
443
|
-
* 解决 Plugin.create() 包装问题:外层插件的 #featureContributions 为空,
|
|
444
|
-
* 实际贡献记录在 usePlugin() 创建的内层子插件上
|
|
445
|
-
*/
|
|
446
|
-
#collectAllFeatureNames(names) {
|
|
447
|
-
for (const name of this.#featureContributions.keys()) {
|
|
448
|
-
names.add(name);
|
|
433
|
+
// 输出启动日志(使用 debug 级别,避免重复输出)
|
|
434
|
+
// 只在根插件或重要插件时使用 info 级别
|
|
435
|
+
if (!this.parent || this.name === 'setup') {
|
|
436
|
+
this.logger.info(`Plugin "${this.name}" ${t ? `reloaded in ${Date.now() - t}ms` : "started"}`);
|
|
449
437
|
}
|
|
450
|
-
|
|
451
|
-
|
|
438
|
+
else {
|
|
439
|
+
this.logger.debug(`Plugin "${this.name}" ${t ? `reloaded in ${Date.now() - t}ms` : "started"}`);
|
|
452
440
|
}
|
|
453
441
|
}
|
|
454
442
|
/**
|
|
455
|
-
*
|
|
456
|
-
*
|
|
443
|
+
* 获取插件提供的功能
|
|
444
|
+
* 从各个服务中获取数据
|
|
457
445
|
*/
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
446
|
+
get features() {
|
|
447
|
+
const commandService = this.inject('command');
|
|
448
|
+
const componentService = this.inject('component');
|
|
449
|
+
const cronService = this.inject('cron');
|
|
450
|
+
return {
|
|
451
|
+
commands: commandService ? commandService.items.map(c => c.pattern) : [],
|
|
452
|
+
components: componentService ? componentService.getAllNames() : [],
|
|
453
|
+
crons: cronService ? cronService.items.map(c => c.cronExpression) : [],
|
|
454
|
+
middlewares: this.#middlewares.map((m, i) => m.name || `middleware_${i}`),
|
|
455
|
+
};
|
|
463
456
|
}
|
|
464
457
|
/**
|
|
465
|
-
*
|
|
466
|
-
*
|
|
467
|
-
* 同时包含 middleware(方案 B: 本地构造)
|
|
468
|
-
*
|
|
469
|
-
* 注意:Plugin.create() 创建 "外层" 插件,usePlugin() 创建 "内层" 子插件。
|
|
470
|
-
* extension 方法 (addCommand 等) 通过 getPlugin() 记录在内层插件上。
|
|
471
|
-
* 因此需要遍历整个子树来收集 feature 贡献名称。
|
|
458
|
+
* 获取插件功能摘要(数组形式)
|
|
459
|
+
* 返回各功能类型的名称和数量
|
|
472
460
|
*/
|
|
473
461
|
getFeatures() {
|
|
474
462
|
const result = [];
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
for (const pName of pluginNames) {
|
|
489
|
-
if (pName === this.name)
|
|
490
|
-
continue;
|
|
491
|
-
json = feature.toJSON(pName);
|
|
492
|
-
if (json.count > 0)
|
|
493
|
-
break;
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
if (json.count > 0) {
|
|
497
|
-
result.push(json);
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
// middleware(方案 B: 本地构造,因为 middleware 是 Plugin 私有属性)
|
|
502
|
-
// 同样需要收集子插件树中的 middleware
|
|
503
|
-
const allMiddlewareNames = [];
|
|
504
|
-
const collectMiddlewares = (plugin) => {
|
|
505
|
-
const mws = plugin.#middlewares
|
|
506
|
-
.filter(m => m !== plugin.#messageMiddleware)
|
|
507
|
-
.map((m, i) => m.name || `middleware_${i}`);
|
|
508
|
-
allMiddlewareNames.push(...mws);
|
|
509
|
-
for (const child of plugin.children) {
|
|
510
|
-
collectMiddlewares(child);
|
|
511
|
-
}
|
|
512
|
-
};
|
|
513
|
-
collectMiddlewares(this);
|
|
514
|
-
if (allMiddlewareNames.length > 0) {
|
|
515
|
-
result.push({
|
|
516
|
-
name: 'middleware',
|
|
517
|
-
icon: 'Layers',
|
|
518
|
-
desc: '中间件',
|
|
519
|
-
count: allMiddlewareNames.length,
|
|
520
|
-
items: allMiddlewareNames.map(name => ({ name })),
|
|
521
|
-
});
|
|
522
|
-
}
|
|
523
|
-
// 自动检测适配器和服务上下文(从 $contexts 中发现非 Feature 的贡献)
|
|
524
|
-
const adapterItems = [];
|
|
525
|
-
const serviceItems = [];
|
|
526
|
-
const scanContexts = (plugin) => {
|
|
527
|
-
for (const [name, context] of plugin.$contexts) {
|
|
528
|
-
const value = context.value;
|
|
529
|
-
if (value instanceof Adapter) {
|
|
530
|
-
adapterItems.push({
|
|
531
|
-
name,
|
|
532
|
-
bots: value.bots.size,
|
|
533
|
-
online: Array.from(value.bots.values()).filter(b => b.$connected).length,
|
|
534
|
-
tools: value.tools.size,
|
|
535
|
-
});
|
|
536
|
-
}
|
|
537
|
-
else if (value !== undefined && !(value instanceof Feature)) {
|
|
538
|
-
// 非 Feature、非 Adapter 的上下文 = 服务
|
|
539
|
-
serviceItems.push({ name, desc: context.description || name });
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
for (const child of plugin.children) {
|
|
543
|
-
scanContexts(child);
|
|
544
|
-
}
|
|
545
|
-
};
|
|
546
|
-
scanContexts(this);
|
|
547
|
-
if (adapterItems.length > 0) {
|
|
548
|
-
result.push({
|
|
549
|
-
name: 'adapter',
|
|
550
|
-
icon: 'Plug',
|
|
551
|
-
desc: '适配器',
|
|
552
|
-
count: adapterItems.length,
|
|
553
|
-
items: adapterItems,
|
|
554
|
-
});
|
|
555
|
-
}
|
|
556
|
-
if (serviceItems.length > 0) {
|
|
557
|
-
result.push({
|
|
558
|
-
name: 'service',
|
|
559
|
-
icon: 'Server',
|
|
560
|
-
desc: '服务',
|
|
561
|
-
count: serviceItems.length,
|
|
562
|
-
items: serviceItems,
|
|
563
|
-
});
|
|
564
|
-
}
|
|
463
|
+
const f = this.features;
|
|
464
|
+
if (f.commands.length > 0)
|
|
465
|
+
result.push({ name: 'command', count: f.commands.length });
|
|
466
|
+
if (f.components.length > 0)
|
|
467
|
+
result.push({ name: 'component', count: f.components.length });
|
|
468
|
+
if (f.crons.length > 0)
|
|
469
|
+
result.push({ name: 'cron', count: f.crons.length });
|
|
470
|
+
// #middlewares includes the default command middleware, only count user-added ones
|
|
471
|
+
const userMiddlewareCount = this.#middlewares.length - 1; // subtract default #messageMiddleware
|
|
472
|
+
if (userMiddlewareCount > 0)
|
|
473
|
+
result.push({ name: 'middleware', count: userMiddlewareCount });
|
|
474
|
+
if (this.#tools.size > 0)
|
|
475
|
+
result.push({ name: 'tool', count: this.#tools.size });
|
|
565
476
|
return result;
|
|
566
477
|
}
|
|
567
478
|
info() {
|
|
568
479
|
return {
|
|
569
480
|
[this.name]: {
|
|
570
|
-
features: this.
|
|
481
|
+
features: this.features,
|
|
571
482
|
children: this.children.map(child => child.info())
|
|
572
483
|
}
|
|
573
484
|
};
|
|
@@ -585,15 +496,13 @@ export class Plugin extends EventEmitter {
|
|
|
585
496
|
await child.stop();
|
|
586
497
|
}
|
|
587
498
|
this.children = [];
|
|
588
|
-
// 停止服务
|
|
499
|
+
// 停止服务
|
|
589
500
|
for (const [name, context] of this.$contexts) {
|
|
590
501
|
remove(Plugin[contextsKey], name);
|
|
502
|
+
// 移除扩展方法
|
|
591
503
|
if (context.extensions) {
|
|
592
504
|
for (const key of Object.keys(context.extensions)) {
|
|
593
|
-
|
|
594
|
-
delete Plugin.prototype[key];
|
|
595
|
-
Plugin[extensionOwnersKey].delete(key);
|
|
596
|
-
}
|
|
505
|
+
delete Plugin.prototype[key];
|
|
597
506
|
}
|
|
598
507
|
}
|
|
599
508
|
if (typeof context.dispose === "function") {
|
|
@@ -672,16 +581,10 @@ export class Plugin extends EventEmitter {
|
|
|
672
581
|
await child.broadcast(name, ...args);
|
|
673
582
|
}
|
|
674
583
|
}
|
|
675
|
-
// ============================================================================
|
|
676
|
-
// 依赖注入
|
|
677
|
-
// ============================================================================
|
|
678
|
-
/**
|
|
679
|
-
* 注册上下文(支持 Feature 实例或传统 Context 对象)
|
|
680
|
-
*/
|
|
681
584
|
provide(target) {
|
|
682
585
|
if (target instanceof Feature) {
|
|
683
586
|
const feature = target;
|
|
684
|
-
const
|
|
587
|
+
const ctx = {
|
|
685
588
|
name: feature.name,
|
|
686
589
|
description: feature.desc,
|
|
687
590
|
value: feature,
|
|
@@ -696,29 +599,22 @@ export class Plugin extends EventEmitter {
|
|
|
696
599
|
: undefined,
|
|
697
600
|
extensions: feature.extensions,
|
|
698
601
|
};
|
|
699
|
-
return this.provide(
|
|
602
|
+
return this.provide(ctx);
|
|
700
603
|
}
|
|
701
604
|
const context = target;
|
|
702
605
|
if (!Plugin[contextsKey].includes(context.name)) {
|
|
703
606
|
Plugin[contextsKey].push(context.name);
|
|
704
607
|
}
|
|
705
608
|
this.logger.debug(`Context "${context.name}" provided`);
|
|
706
|
-
|
|
707
|
-
//
|
|
609
|
+
this.$contexts.set(context.name, context);
|
|
610
|
+
// 立即注册扩展方法到 Plugin.prototype,确保后续 import 的插件可用
|
|
708
611
|
if (context.extensions) {
|
|
709
|
-
const extNames = [];
|
|
710
612
|
for (const [name, fn] of Object.entries(context.extensions)) {
|
|
711
613
|
if (typeof fn === 'function') {
|
|
712
|
-
if (Reflect.has(Plugin.prototype, name) && !Plugin[extensionOwnersKey].has(name)) {
|
|
713
|
-
this.logger.warn(`Extension method "${name}" shadows an existing Plugin method`);
|
|
714
|
-
}
|
|
715
614
|
Reflect.set(Plugin.prototype, name, fn);
|
|
716
|
-
Plugin[extensionOwnersKey].set(name, context.name);
|
|
717
|
-
extNames.push(name);
|
|
718
615
|
}
|
|
719
616
|
}
|
|
720
617
|
}
|
|
721
|
-
this.$contexts.set(context.name, context);
|
|
722
618
|
return this;
|
|
723
619
|
}
|
|
724
620
|
// ============================================================================
|
|
@@ -800,8 +696,7 @@ export class Plugin extends EventEmitter {
|
|
|
800
696
|
static #coreMethods = new Set([
|
|
801
697
|
'addMiddleware', 'useContext', 'inject', 'contextIsReady',
|
|
802
698
|
'start', 'stop', 'onMounted', 'onDispose',
|
|
803
|
-
'dispatch', 'broadcast', 'provide', 'import', 'reload', 'watch', 'info'
|
|
804
|
-
'recordFeatureContribution', 'getFeatures',
|
|
699
|
+
'dispatch', 'broadcast', 'provide', 'import', 'reload', 'watch', 'info'
|
|
805
700
|
]);
|
|
806
701
|
/**
|
|
807
702
|
* 自动绑定核心方法(只在构造函数中调用一次)
|
|
@@ -837,41 +732,13 @@ export class Plugin extends EventEmitter {
|
|
|
837
732
|
plugin.fileHash = getFileHash(entryFile);
|
|
838
733
|
// 先记录,防止循环依赖时重复加载
|
|
839
734
|
loadedModules.set(realPath, plugin);
|
|
840
|
-
let mod;
|
|
841
735
|
await storage.run(plugin, async () => {
|
|
842
|
-
|
|
736
|
+
await import(`${import.meta.resolve(entryFile)}?t=${Date.now()}`);
|
|
843
737
|
});
|
|
844
|
-
// 支持模块显式声明插件名称:export const pluginName = 'my-plugin'
|
|
845
|
-
if (mod?.pluginName && typeof mod.pluginName === 'string') {
|
|
846
|
-
plugin.setName(mod.pluginName);
|
|
847
|
-
}
|
|
848
738
|
return plugin;
|
|
849
739
|
}
|
|
850
740
|
}
|
|
851
741
|
export function defineContext(options) {
|
|
852
742
|
return options;
|
|
853
743
|
}
|
|
854
|
-
/**
|
|
855
|
-
* 声明式定义插件。
|
|
856
|
-
* 提供显式名称 + setup 函数,自动设置当前插件名称并执行 setup。
|
|
857
|
-
*
|
|
858
|
-
* @example
|
|
859
|
-
* ```typescript
|
|
860
|
-
* // plugins/weather/index.ts
|
|
861
|
-
* export default definePlugin({
|
|
862
|
-
* name: 'weather',
|
|
863
|
-
* setup(plugin) {
|
|
864
|
-
* plugin.addTool({ ... });
|
|
865
|
-
* },
|
|
866
|
-
* });
|
|
867
|
-
* ```
|
|
868
|
-
*/
|
|
869
|
-
export function definePlugin(options) {
|
|
870
|
-
const plugin = usePlugin();
|
|
871
|
-
if (options.name) {
|
|
872
|
-
plugin.setName(options.name);
|
|
873
|
-
}
|
|
874
|
-
options.setup(plugin);
|
|
875
|
-
return { pluginName: options.name };
|
|
876
|
-
}
|
|
877
744
|
//# sourceMappingURL=plugin.js.map
|