@zhin.js/core 1.0.52 → 1.0.54

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/lib/adapter.d.ts +13 -23
  3. package/lib/adapter.d.ts.map +1 -1
  4. package/lib/adapter.js +26 -71
  5. package/lib/adapter.js.map +1 -1
  6. package/lib/built/adapter-process.d.ts.map +1 -1
  7. package/lib/built/adapter-process.js +1 -0
  8. package/lib/built/adapter-process.js.map +1 -1
  9. package/lib/built/agent-preset.d.ts +40 -0
  10. package/lib/built/agent-preset.d.ts.map +1 -0
  11. package/lib/built/agent-preset.js +13 -0
  12. package/lib/built/agent-preset.js.map +1 -0
  13. package/lib/built/component.d.ts +2 -2
  14. package/lib/built/component.d.ts.map +1 -1
  15. package/lib/built/component.js +3 -2
  16. package/lib/built/component.js.map +1 -1
  17. package/lib/built/database.d.ts +2 -2
  18. package/lib/built/database.d.ts.map +1 -1
  19. package/lib/built/database.js.map +1 -1
  20. package/lib/built/skill.d.ts +10 -0
  21. package/lib/built/skill.d.ts.map +1 -1
  22. package/lib/built/skill.js.map +1 -1
  23. package/lib/index.d.ts +1 -0
  24. package/lib/index.d.ts.map +1 -1
  25. package/lib/index.js +2 -0
  26. package/lib/index.js.map +1 -1
  27. package/lib/plugin.d.ts +43 -95
  28. package/lib/plugin.d.ts.map +1 -1
  29. package/lib/plugin.js +135 -268
  30. package/lib/plugin.js.map +1 -1
  31. package/lib/types.d.ts +11 -0
  32. package/lib/types.d.ts.map +1 -1
  33. package/package.json +6 -6
  34. package/src/adapter.ts +37 -95
  35. package/src/built/adapter-process.ts +1 -0
  36. package/src/built/agent-preset.ts +55 -0
  37. package/src/built/component.ts +5 -3
  38. package/src/built/database.ts +2 -1
  39. package/src/built/skill.ts +14 -0
  40. package/src/index.ts +2 -0
  41. package/src/plugin.ts +198 -373
  42. package/src/types.ts +16 -0
package/src/plugin.ts CHANGED
@@ -6,30 +6,20 @@
6
6
  import { AsyncLocalStorage } from "async_hooks";
7
7
  import { EventEmitter } from "events";
8
8
  import { createRequire } from "module";
9
- import type { Tool } from "./types.js";
9
+ import type { Database, Definition } from "@zhin.js/database";
10
+ import { Schema } from "@zhin.js/schema";
11
+ import type { Models, RegisteredAdapters, AITool, ToolContext, PluginManifest } from "./types.js";
10
12
  import * as fs from "fs";
11
13
  import * as path from "path";
12
- import { fileURLToPath, pathToFileURL } from "url";
14
+ import { fileURLToPath } from "url";
13
15
  import logger, { Logger } from "@zhin.js/logger";
14
16
  import { compose, remove, resolveEntry } from "./utils.js";
15
- import {
16
- MessageMiddleware,
17
- RegisteredAdapter,
18
- MaybePromise,
19
- ArrayItem,
20
- SendOptions,
21
- } from "./types.js";
22
- import type { ConfigFeature } from "./built/config.js";
23
- import type { PermissionFeature } from "./built/permission.js";
17
+ import { MessageMiddleware, RegisteredAdapter, MaybePromise, ArrayItem, SendOptions } from "./types.js";
24
18
  import { Adapter, Adapters } from "./adapter.js";
25
- import { Notice } from "./notice.js";
26
- import { Message } from "./message.js";
27
- import { Request } from "./request.js";
28
- import { Feature, FeatureJSON } from "./feature.js";
29
19
  import { createHash } from "crypto";
20
+ import { Feature } from "./feature.js";
30
21
  const contextsKey = Symbol("contexts");
31
- const extensionOwnersKey = Symbol("extensionOwners");
32
- const loadedModules = new Map<string, Plugin>();
22
+ const loadedModules = new Map<string, Plugin>(); // 记录已加载的模块
33
23
  const require = createRequire(import.meta.url);
34
24
 
35
25
 
@@ -74,19 +64,16 @@ function getCurrentFile(metaUrl = import.meta.url): string {
74
64
  /**
75
65
  * usePlugin - 获取或创建当前插件实例
76
66
  * 类似 React Hooks 的设计,根据调用文件自动创建插件树
77
- *
78
- * 同一个文件多次调用 usePlugin() 返回同一个实例,
79
- * 避免 Plugin.create() + usePlugin() 产生不必要的双层包装。
67
+ * 同一上下文中同一文件多次调用返回同一实例
80
68
  */
81
69
  export function usePlugin(): Plugin {
82
70
  const callerFile = getCurrentFile();
83
- const parentPlugin = storage.getStore();
84
-
85
- // 同一文件再次调用 usePlugin(),直接复用已有实例
86
- if (parentPlugin && parentPlugin.filePath === callerFile) {
87
- return parentPlugin;
71
+ // 如果当前 store 已是同一文件创建的插件,直接返回
72
+ const current = storage.getStore();
73
+ if (current && current.filePath.replace(/\?t=\d+$/, '') === callerFile.replace(/\?t=\d+$/, '')) {
74
+ return current;
88
75
  }
89
-
76
+ const parentPlugin = current;
90
77
  const newPlugin = new Plugin(callerFile, parentPlugin);
91
78
  storage.enterWith(newPlugin);
92
79
  return newPlugin;
@@ -146,11 +133,9 @@ export interface Plugin extends Plugin.Extensions { }
146
133
  */
147
134
  export class Plugin extends EventEmitter<Plugin.Lifecycle> {
148
135
  static [contextsKey] = [] as string[];
149
- /** Maps extension method name → context name that owns it */
150
- static [extensionOwnersKey] = new Map<string, string>();
151
-
136
+
152
137
  #cachedName?: string;
153
- #explicitName?: string;
138
+ #manifest?: PluginManifest | null;
154
139
  adapters: (keyof Plugin.Contexts)[] = [];
155
140
  started = false;
156
141
 
@@ -180,33 +165,32 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
180
165
  };
181
166
  // 插件功能
182
167
  #middlewares: MessageMiddleware<RegisteredAdapter>[] = [this.#messageMiddleware];
168
+
169
+ // AI 工具
170
+ #tools: Map<string, AITool> = new Map();
183
171
 
184
- // 本地工具存储(当 ToolService 不可用时使用)
185
- #tools: Map<string, Tool> = new Map();
186
-
172
+ // Feature 贡献追踪
173
+ #featureContributions = new Map<string, Set<string>>();
174
+
187
175
  // 统一的清理函数集合
188
176
  #disposables: Set<() => void | Promise<void>> = new Set();
189
-
190
- // 记录当前插件向哪些 Feature 贡献了哪些 item
191
- #featureContributions = new Map<string, Set<string>>();
192
-
177
+
193
178
  get middleware(): MessageMiddleware<RegisteredAdapter> {
194
179
  return compose<RegisteredAdapter>(this.#middlewares);
195
180
  }
196
181
 
197
- /**
198
- * Returns custom middlewares (excluding the built-in command middleware).
199
- * Used by MessageDispatcher to run legacy middlewares after dispatch.
200
- */
201
- _getCustomMiddlewares(): MessageMiddleware<RegisteredAdapter>[] {
202
- return this.#middlewares.filter(m => m !== this.#messageMiddleware);
182
+ recordFeatureContribution(featureName: string, itemName: string): void {
183
+ if (!this.#featureContributions.has(featureName)) {
184
+ this.#featureContributions.set(featureName, new Set());
185
+ }
186
+ this.#featureContributions.get(featureName)!.add(itemName);
203
187
  }
204
188
  /**
205
189
  * 构造函数
206
190
  */
207
191
  constructor(filePath: string = "", public parent?: Plugin) {
208
192
  super();
209
-
193
+
210
194
  // 增加 EventEmitter 监听器限制,避免警告
211
195
  // 因为插件可能注册多个命令/组件/中间件,每个都会添加 dispose 监听器
212
196
  this.setMaxListeners(50);
@@ -222,26 +206,15 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
222
206
  // 绑定方法以支持解构使用
223
207
  this.$bindMethods();
224
208
  }
225
-
209
+
226
210
  // 标记是否已绑定方法
227
211
  #methodsBound = false;
228
212
 
229
213
  /**
230
214
  * 添加中间件
231
215
  * 中间件用于处理消息流转
232
- * @param middleware 中间件函数
233
216
  */
234
- addMiddleware<T extends RegisteredAdapter>(middleware: MessageMiddleware<T>): () => void {
235
- // Always register on root so middlewares reach the global chain
236
- const target = this.root;
237
- if (target !== this) {
238
- const dispose = target.addMiddleware(middleware);
239
- this.#disposables.add(dispose);
240
- return () => {
241
- dispose();
242
- this.#disposables.delete(dispose);
243
- };
244
- }
217
+ addMiddleware<T extends RegisteredAdapter>(middleware: MessageMiddleware<T>, name?: string) {
245
218
  this.#middlewares.push(middleware as MessageMiddleware<RegisteredAdapter>);
246
219
  const dispose = () => {
247
220
  remove(this.#middlewares, middleware);
@@ -252,28 +225,14 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
252
225
  }
253
226
 
254
227
  /**
255
- * 添加工具
256
- * 工具可以被 AI 服务调用,也会自动生成对应的命令
228
+ * 添加 AI 工具
229
+ * 工具可以被 AI 服务调用来执行操作
257
230
  * @param tool 工具定义
258
- * @param generateCommand 是否生成对应命令(默认 true)
259
231
  * @returns 返回一个移除工具的函数
260
232
  */
261
- addTool(
262
- tool: Tool,
263
- generateCommand: boolean = true
264
- ): () => void {
265
- const toolService = this.root.inject('tool') as { addTool?: (tool: Tool, name: string, gen: boolean) => () => void } | undefined;
266
- if (toolService && typeof toolService.addTool === 'function') {
267
- const dispose = toolService.addTool(tool, this.name, generateCommand);
268
- this.#disposables.add(dispose);
269
- return () => {
270
- dispose();
271
- this.#disposables.delete(dispose);
272
- };
273
- }
274
-
275
- // 回退到本地存储
276
- const toolWithSource: Tool = {
233
+ addTool(tool: AITool): () => void {
234
+ // 自动添加插件源标识
235
+ const toolWithSource: AITool = {
277
236
  ...tool,
278
237
  source: tool.source || `plugin:${this.name}`,
279
238
  tags: [...(tool.tags || []), 'plugin', this.name],
@@ -286,29 +245,29 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
286
245
  this.#disposables.add(dispose);
287
246
  return dispose;
288
247
  }
289
-
248
+
290
249
  /**
291
250
  * 获取当前插件注册的所有工具
292
251
  */
293
- getTools(): Tool[] {
252
+ getTools(): AITool[] {
294
253
  return Array.from(this.#tools.values());
295
254
  }
296
-
255
+
297
256
  /**
298
257
  * 获取当前插件及所有子插件注册的工具
299
258
  */
300
- getAllTools(): Tool[] {
301
- const tools: Tool[] = [...this.getTools()];
259
+ getAllTools(): AITool[] {
260
+ const tools: AITool[] = [...this.getTools()];
302
261
  for (const child of this.children) {
303
262
  tools.push(...child.getAllTools());
304
263
  }
305
264
  return tools;
306
265
  }
307
-
266
+
308
267
  /**
309
268
  * 根据名称获取工具
310
269
  */
311
- getTool(name: string): Tool | undefined {
270
+ getTool(name: string): AITool | undefined {
312
271
  // 先在当前插件查找
313
272
  const tool = this.#tools.get(name);
314
273
  if (tool) return tool;
@@ -319,24 +278,18 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
319
278
  }
320
279
  return undefined;
321
280
  }
322
-
281
+
323
282
  /**
324
- * 收集所有可用的工具
325
- * 优先使用 ToolService,否则回退到本地收集
283
+ * 收集所有可用的工具(包括适配器提供的)
284
+ * 这是 AI 服务获取工具的主入口
326
285
  */
327
- collectAllTools(): Tool[] {
328
- const toolService = this.root.inject('tool') as { collectAll?: (root: Plugin) => Tool[] } | undefined;
329
- if (toolService && typeof toolService.collectAll === 'function') {
330
- return toolService.collectAll(this.root);
331
- }
332
-
333
- // 回退到本地收集
334
- const tools: Tool[] = [];
335
-
286
+ collectAllTools(): AITool[] {
287
+ const tools: AITool[] = [];
288
+
336
289
  // 收集插件树中的所有工具
337
290
  const rootPlugin = this.root;
338
291
  tools.push(...rootPlugin.getAllTools());
339
-
292
+
340
293
  // 收集所有适配器的工具
341
294
  for (const [name, context] of rootPlugin.contexts) {
342
295
  const value = context.value;
@@ -345,53 +298,74 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
345
298
  tools.push(...adapter.getTools());
346
299
  }
347
300
  }
348
-
301
+
349
302
  return tools;
350
303
  }
351
304
 
352
305
  /**
353
- * 显式设置插件名称(优先级高于路径推导)。
354
- * 可通过以下方式声明:
355
- * 1. 模块导出 `export const pluginName = 'my-plugin'`
356
- * 2. 调用 `plugin.setName('my-plugin')`
357
- */
358
- setName(name: string): void {
359
- this.#explicitName = name;
360
- this.#cachedName = name;
361
- this.logger = logger.getLogger(name);
362
- }
363
-
364
- /**
365
- * 插件名称(显式名称 > 路径推导)
306
+ * 插件名称
366
307
  */
367
308
  get name(): string {
368
- if (this.#explicitName) return this.#explicitName;
369
309
  if (this.#cachedName) return this.#cachedName;
370
310
 
371
- let name = path
311
+ this.#cachedName = path
372
312
  .relative(process.cwd(), this.filePath)
373
313
  .replace(/\?t=\d+$/, "")
374
314
  .replace(/\\/g, "/")
375
315
  .replace(/\/index\.(js|ts)x?$/, "")
376
- .replace(/\/(lib|src|dist)$/, "");
377
-
378
- // 安全地提取 node_modules 后的包名或最后的文件名
379
- const nodeModulesIndex = name.indexOf('node_modules/');
380
- if (nodeModulesIndex !== -1) {
381
- name = name.substring(nodeModulesIndex + 'node_modules/'.length);
316
+ .replace(/\/(lib|src|dist)$/, "")
317
+ .replace(/.*\/node_modules\//, "")
318
+ .replace(/.*\//, "")
319
+ .replace(/\.(js|ts)x?$/, "");
320
+
321
+ return this.#cachedName;
322
+ }
323
+
324
+ /**
325
+ * 插件清单(从 plugin.yml 或 package.json 延迟读取)
326
+ */
327
+ get manifest(): PluginManifest | undefined {
328
+ if (this.#manifest !== undefined) return this.#manifest ?? undefined;
329
+ if (!this.filePath) {
330
+ this.#manifest = null;
331
+ return undefined;
332
+ }
333
+ const dir = path.dirname(this.filePath);
334
+ // 优先读取 plugin.yml
335
+ const ymlPath = path.join(dir, 'plugin.yml');
336
+ if (fs.existsSync(ymlPath)) {
337
+ try {
338
+ const content = fs.readFileSync(ymlPath, 'utf-8');
339
+ const match = content.match(/^name:\s*(.+)$/m);
340
+ const descMatch = content.match(/^description:\s*(.+)$/m);
341
+ const verMatch = content.match(/^version:\s*(.+)$/m);
342
+ if (match) {
343
+ this.#manifest = {
344
+ name: match[1].trim(),
345
+ description: descMatch?.[1]?.trim(),
346
+ version: verMatch?.[1]?.trim(),
347
+ };
348
+ return this.#manifest;
349
+ }
350
+ } catch { /* ignore */ }
382
351
  }
383
-
384
- // 提取最后一个路径段
385
- const lastSlash = name.lastIndexOf('/');
386
- if (lastSlash !== -1) {
387
- name = name.substring(lastSlash + 1);
352
+ // Fallback: package.json
353
+ const pkgPath = path.join(dir, 'package.json');
354
+ if (fs.existsSync(pkgPath)) {
355
+ try {
356
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
357
+ if (pkg.name) {
358
+ this.#manifest = {
359
+ name: pkg.name,
360
+ description: pkg.description,
361
+ version: pkg.version,
362
+ };
363
+ return this.#manifest;
364
+ }
365
+ } catch { /* ignore */ }
388
366
  }
389
-
390
- // 移除文件扩展名
391
- name = name.replace(/\.(js|ts)x?$/, "");
392
-
393
- this.#cachedName = name;
394
- return this.#cachedName;
367
+ this.#manifest = null;
368
+ return undefined;
395
369
  }
396
370
 
397
371
  /**
@@ -426,15 +400,16 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
426
400
  if (!dispose) return;
427
401
  const disposeFn = async (name: keyof Plugin.Contexts) => {
428
402
  if (contexts.includes(name)) {
429
- await dispose(this.inject(name) as ArrayItem<ContextList<T>>)
403
+ await dispose(this.inject(name) as any)
430
404
  }
431
405
  this.off('context.dispose', disposeFn)
432
406
  sideEffect.finished = false;
433
407
  }
434
408
  this.on('context.dispose', disposeFn)
409
+ // 确保 dispose 时清理监听器(只注册一次)
435
410
  const cleanupOnDispose = () => {
436
411
  this.off('context.dispose', disposeFn)
437
- dispose(this.inject(args[0] as unknown as keyof Plugin.Contexts) as unknown as ArrayItem<ContextList<T>>)
412
+ dispose(this.inject(args[0] as any) as any)
438
413
  }
439
414
  this.once('dispose', cleanupOnDispose)
440
415
  }
@@ -448,7 +423,7 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
448
423
  if (!this.#contextsIsReady(contexts)) return
449
424
  contextReadyCallback()
450
425
  }
451
- inject<T extends keyof Plugin.Contexts>(name: T): Plugin.Contexts[T] | undefined;
426
+ inject<T extends keyof Plugin.Contexts>(name: T): Plugin.Contexts[T]|undefined;
452
427
  inject(name: string): unknown;
453
428
  inject(name: string): unknown {
454
429
  const context = this.root.contexts.get(name);
@@ -472,16 +447,26 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
472
447
  /**
473
448
  * 启动插件
474
449
  */
475
- async start(t?: number): Promise<void> {
450
+ async start(t?:number): Promise<void> {
476
451
  if (this.started) return;
477
452
  this.started = true; // 提前设置,防止重复启动
478
-
453
+
479
454
  // 启动所有服务
480
455
  for (const context of this.$contexts.values()) {
481
456
  if (typeof context.mounted === "function") {
482
457
  const result = await context.mounted(this);
483
- // 仅当 value 未预设时才赋值(mounted 始终执行,以支持副作用如设置内部引用)
484
- if (!context.value) context.value = result;
458
+ // 仅在没有预设 value 时才用 mounted 返回值赋值
459
+ if (!context.value) {
460
+ context.value = result;
461
+ }
462
+ }
463
+ // 注册扩展方法到 Plugin.prototype
464
+ if (context.extensions) {
465
+ for (const [name, fn] of Object.entries(context.extensions)) {
466
+ if (typeof fn === 'function') {
467
+ Reflect.set(Plugin.prototype, name, fn);
468
+ }
469
+ }
485
470
  }
486
471
  this.dispatch('context.mounted', context.name)
487
472
  }
@@ -490,160 +475,52 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
490
475
  for (const child of this.children) {
491
476
  await child.start(t);
492
477
  }
493
- this.logger.debug(`Plugin "${this.name}" ${t ? `reloaded in ${Date.now() - t}ms` : "started"}`);
494
- }
495
- /**
496
- * 记录 Feature 贡献(由 Feature extensions 内部调用)
497
- * @param featureName Feature 名称(如 'command')
498
- * @param itemName item 标识(如命令 pattern)
499
- */
500
- recordFeatureContribution(featureName: string, itemName: string): void {
501
- if (!this.#featureContributions.has(featureName)) {
502
- this.#featureContributions.set(featureName, new Set());
478
+ // 输出启动日志(使用 debug 级别,避免重复输出)
479
+ // 只在根插件或重要插件时使用 info 级别
480
+ if (!this.parent || this.name === 'setup') {
481
+ this.logger.info(`Plugin "${this.name}" ${t ? `reloaded in ${Date.now() - t}ms` : "started"}`);
482
+ } else {
483
+ this.logger.debug(`Plugin "${this.name}" ${t ? `reloaded in ${Date.now() - t}ms` : "started"}`);
503
484
  }
504
- this.#featureContributions.get(featureName)!.add(itemName);
505
485
  }
506
-
507
486
  /**
508
- * 收集本插件及所有后代插件的 Feature 贡献名称
509
- * 解决 Plugin.create() 包装问题:外层插件的 #featureContributions 为空,
510
- * 实际贡献记录在 usePlugin() 创建的内层子插件上
487
+ * 获取插件提供的功能
488
+ * 从各个服务中获取数据
511
489
  */
512
- #collectAllFeatureNames(names: Set<string>): void {
513
- for (const name of this.#featureContributions.keys()) {
514
- names.add(name);
515
- }
516
- for (const child of this.children) {
517
- child.#collectAllFeatureNames(names);
518
- }
519
- }
520
-
521
- /**
522
- * 收集本插件及所有后代插件的 plugin name 集合
523
- * 用于 Feature.toJSON(pluginName) 匹配
524
- */
525
- #collectAllPluginNames(names: Set<string>): void {
526
- names.add(this.name);
527
- for (const child of this.children) {
528
- child.#collectAllPluginNames(names);
529
- }
490
+ get features(): Plugin.Features {
491
+ const commandService = this.inject('command');
492
+ const componentService = this.inject('component')
493
+ const cronService = this.inject('cron');
494
+
495
+ return {
496
+ commands: commandService ? commandService.items.map(c => c.pattern) : [],
497
+ components: componentService ? componentService.getAllNames() : [],
498
+ crons: cronService ? cronService.items.map(c => c.cronExpression) : [],
499
+ middlewares: this.#middlewares.map((m, i) => m.name || `middleware_${i}`),
500
+ };
530
501
  }
531
502
 
532
503
  /**
533
- * 获取当前插件的所有 Feature 数据(用于 HTTP API)
534
- * 遍历插件贡献的 Feature,调用各 Feature 的 toJSON(pluginName) 获取序列化数据
535
- * 同时包含 middleware(方案 B: 本地构造)
536
- *
537
- * 注意:Plugin.create() 创建 "外层" 插件,usePlugin() 创建 "内层" 子插件。
538
- * extension 方法 (addCommand 等) 通过 getPlugin() 记录在内层插件上。
539
- * 因此需要遍历整个子树来收集 feature 贡献名称。
504
+ * 获取插件功能摘要(数组形式)
505
+ * 返回各功能类型的名称和数量
540
506
  */
541
- getFeatures(): FeatureJSON[] {
542
- const result: FeatureJSON[] = [];
543
-
544
- // 收集本插件及所有后代插件的 feature 名称和 plugin 名称
545
- const featureNames = new Set<string>();
546
- this.#collectAllFeatureNames(featureNames);
547
-
548
- const pluginNames = new Set<string>();
549
- this.#collectAllPluginNames(pluginNames);
550
-
551
- // 从 Feature 贡献中收集
552
- for (const featureName of featureNames) {
553
- const feature = this.inject(featureName as keyof Plugin.Contexts);
554
- if (feature instanceof Feature) {
555
- // 先用当前插件名尝试
556
- let json = feature.toJSON(this.name);
557
- if (json.count === 0) {
558
- // 当前插件名匹配不到(可能名称不同),尝试后代插件名
559
- for (const pName of pluginNames) {
560
- if (pName === this.name) continue;
561
- json = feature.toJSON(pName);
562
- if (json.count > 0) break;
563
- }
564
- }
565
- if (json.count > 0) {
566
- result.push(json);
567
- }
568
- }
569
- }
570
-
571
- // middleware(方案 B: 本地构造,因为 middleware 是 Plugin 私有属性)
572
- // 同样需要收集子插件树中的 middleware
573
- const allMiddlewareNames: string[] = [];
574
- const collectMiddlewares = (plugin: Plugin) => {
575
- const mws = plugin.#middlewares
576
- .filter(m => m !== plugin.#messageMiddleware)
577
- .map((m, i) => m.name || `middleware_${i}`);
578
- allMiddlewareNames.push(...mws);
579
- for (const child of plugin.children) {
580
- collectMiddlewares(child);
581
- }
582
- };
583
- collectMiddlewares(this);
584
-
585
- if (allMiddlewareNames.length > 0) {
586
- result.push({
587
- name: 'middleware',
588
- icon: 'Layers',
589
- desc: '中间件',
590
- count: allMiddlewareNames.length,
591
- items: allMiddlewareNames.map(name => ({ name })),
592
- });
593
- }
594
-
595
- // 自动检测适配器和服务上下文(从 $contexts 中发现非 Feature 的贡献)
596
- const adapterItems: { name: string; bots: number; online: number; tools: number }[] = [];
597
- const serviceItems: { name: string; desc: string }[] = [];
598
-
599
- const scanContexts = (plugin: Plugin) => {
600
- for (const [name, context] of plugin.$contexts) {
601
- const value = context.value;
602
- if (value instanceof Adapter) {
603
- adapterItems.push({
604
- name,
605
- bots: value.bots.size,
606
- online: Array.from(value.bots.values()).filter(b => b.$connected).length,
607
- tools: value.tools.size,
608
- });
609
- } else if (value !== undefined && !(value instanceof Feature)) {
610
- // 非 Feature、非 Adapter 的上下文 = 服务
611
- serviceItems.push({ name, desc: context.description || name });
612
- }
613
- }
614
- for (const child of plugin.children) {
615
- scanContexts(child);
616
- }
617
- };
618
- scanContexts(this);
619
-
620
- if (adapterItems.length > 0) {
621
- result.push({
622
- name: 'adapter',
623
- icon: 'Plug',
624
- desc: '适配器',
625
- count: adapterItems.length,
626
- items: adapterItems,
627
- });
628
- }
629
-
630
- if (serviceItems.length > 0) {
631
- result.push({
632
- name: 'service',
633
- icon: 'Server',
634
- desc: '服务',
635
- count: serviceItems.length,
636
- items: serviceItems,
637
- });
638
- }
639
-
507
+ getFeatures(): Array<{ name: string; count: number }> {
508
+ const result: Array<{ name: string; count: number }> = [];
509
+ const f = this.features;
510
+ if (f.commands.length > 0) result.push({ name: 'command', count: f.commands.length });
511
+ if (f.components.length > 0) result.push({ name: 'component', count: f.components.length });
512
+ if (f.crons.length > 0) result.push({ name: 'cron', count: f.crons.length });
513
+ // #middlewares includes the default command middleware, only count user-added ones
514
+ const userMiddlewareCount = this.#middlewares.length - 1; // subtract default #messageMiddleware
515
+ if (userMiddlewareCount > 0) result.push({ name: 'middleware', count: userMiddlewareCount });
516
+ if (this.#tools.size > 0) result.push({ name: 'tool', count: this.#tools.size });
640
517
  return result;
641
518
  }
642
519
 
643
520
  info(): Record<string, any> {
644
521
  return {
645
522
  [this.name]: {
646
- features: this.getFeatures(),
523
+ features: this.features,
647
524
  children: this.children.map(child => child.info())
648
525
  }
649
526
  }
@@ -663,15 +540,13 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
663
540
  }
664
541
  this.children = [];
665
542
 
666
- // 停止服务 — only remove extensions owned by this plugin's contexts
543
+ // 停止服务
667
544
  for (const [name, context] of this.$contexts) {
668
545
  remove(Plugin[contextsKey], name);
546
+ // 移除扩展方法
669
547
  if (context.extensions) {
670
548
  for (const key of Object.keys(context.extensions)) {
671
- if (Plugin[extensionOwnersKey].get(key) === name) {
672
- delete (Plugin.prototype as unknown as Record<string, unknown>)[key];
673
- Plugin[extensionOwnersKey].delete(key);
674
- }
549
+ delete (Plugin.prototype as any)[key];
675
550
  }
676
551
  }
677
552
  if (typeof context.dispose === "function") {
@@ -681,12 +556,12 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
681
556
  // 清理 contexts Map
682
557
  this.$contexts.clear();
683
558
 
684
- // 清空缓存的名称
559
+ // 清空缓存的名称
685
560
  this.#cachedName = undefined;
686
561
 
687
562
  // 触发 dispose 事件
688
563
  this.emit("dispose");
689
-
564
+
690
565
  // 执行所有清理函数
691
566
  for (const dispose of this.#disposables) {
692
567
  try {
@@ -696,17 +571,17 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
696
571
  }
697
572
  }
698
573
  this.#disposables.clear();
699
-
574
+
700
575
  // 清理 middlewares 数组(保留默认的消息中间件)
701
576
  this.#middlewares.length = 1;
702
577
 
703
578
  // 清理 feature 贡献记录
704
579
  this.#featureContributions.clear();
705
-
580
+
706
581
  if (this.parent) {
707
582
  remove(this.parent?.children, this);
708
583
  }
709
-
584
+
710
585
  // 从全局 loadedModules Map 中移除,防止内存泄漏
711
586
  if (this.filePath) {
712
587
  try {
@@ -716,7 +591,7 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
716
591
  // 文件可能已不存在,忽略错误
717
592
  }
718
593
  }
719
-
594
+
720
595
  this.removeAllListeners();
721
596
  this.logger.debug(`Plugin "${this.name}" stopped`);
722
597
  }
@@ -775,19 +650,21 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
775
650
  // ============================================================================
776
651
 
777
652
  /**
778
- * 注册上下文(支持 Feature 实例或传统 Context 对象)
653
+ * 注册上下文
779
654
  */
780
- provide<T extends keyof Plugin.Contexts>(target: Feature | Context<T>): this {
655
+ provide<T extends keyof Plugin.Contexts>(context: Context<T>): this;
656
+ provide(feature: Feature<unknown>): this;
657
+ provide(target: Context<keyof Plugin.Contexts> | Feature<unknown>): this {
781
658
  if (target instanceof Feature) {
782
659
  const feature = target;
783
- const context: Context<T> = {
784
- name: feature.name as T,
660
+ const ctx: Context<keyof Plugin.Contexts> = {
661
+ name: feature.name as keyof Plugin.Contexts,
785
662
  description: feature.desc,
786
- value: feature as Plugin.Contexts[T],
663
+ value: feature as Plugin.Contexts[keyof Plugin.Contexts],
787
664
  mounted: feature.mounted
788
665
  ? async (plugin: Plugin) => {
789
666
  await feature.mounted!(plugin);
790
- return feature as Plugin.Contexts[T];
667
+ return feature as Plugin.Contexts[keyof Plugin.Contexts];
791
668
  }
792
669
  : undefined,
793
670
  dispose: feature.dispose
@@ -795,31 +672,22 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
795
672
  : undefined,
796
673
  extensions: feature.extensions,
797
674
  };
798
- return this.provide(context);
675
+ return this.provide(ctx);
799
676
  }
800
-
801
677
  const context = target;
802
678
  if (!Plugin[contextsKey].includes(context.name as string)) {
803
679
  Plugin[contextsKey].push(context.name as string);
804
680
  }
805
681
  this.logger.debug(`Context "${context.name as string}" provided`);
806
-
807
- // Track which extension methods this context registers.
808
- // On collision, log a warning but allow override (last-write-wins).
682
+ this.$contexts.set(context.name as string, context);
683
+ // 立即注册扩展方法到 Plugin.prototype,确保后续 import 的插件可用
809
684
  if (context.extensions) {
810
- const extNames: string[] = [];
811
685
  for (const [name, fn] of Object.entries(context.extensions)) {
812
686
  if (typeof fn === 'function') {
813
- if (Reflect.has(Plugin.prototype, name) && !Plugin[extensionOwnersKey].has(name)) {
814
- this.logger.warn(`Extension method "${name}" shadows an existing Plugin method`);
815
- }
816
687
  Reflect.set(Plugin.prototype, name, fn);
817
- Plugin[extensionOwnersKey].set(name, context.name as string);
818
- extNames.push(name);
819
688
  }
820
689
  }
821
690
  }
822
- this.$contexts.set(context.name as string, context);
823
691
  return this;
824
692
  }
825
693
 
@@ -830,7 +698,7 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
830
698
  /**
831
699
  * 导入插件
832
700
  */
833
- async import(entry: string, t?: number): Promise<Plugin> {
701
+ async import(entry: string,t?:number): Promise<Plugin> {
834
702
  if (!entry) throw new Error(`Plugin entry not found: ${entry}`);
835
703
  const resolved = resolveEntry(path.isAbsolute(entry) ?
836
704
  entry :
@@ -844,7 +712,7 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
844
712
 
845
713
  // 避免重复加载同一路径的插件
846
714
  const normalized = realPath.replace(/\?t=\d+$/, '').replace(/\\/g, '/');
847
- const existing = this.children.find(child =>
715
+ const existing = this.children.find(child =>
848
716
  child.filePath.replace(/\?t=\d+$/, '').replace(/\\/g, '/') === normalized
849
717
  );
850
718
  if (existing) {
@@ -867,7 +735,7 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
867
735
  */
868
736
  async reload(plugin: Plugin = this): Promise<void> {
869
737
  this.logger.info(`Plugin "${plugin.name}" reloading...`);
870
- const now = Date.now();
738
+ const now=Date.now();
871
739
  if (!plugin.parent) {
872
740
  // 根插件重载 = 退出进程(由 CLI 重启)
873
741
  return process.exit(51);
@@ -914,8 +782,7 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
914
782
  static #coreMethods = new Set([
915
783
  'addMiddleware', 'useContext', 'inject', 'contextIsReady',
916
784
  'start', 'stop', 'onMounted', 'onDispose',
917
- 'dispatch', 'broadcast', 'provide', 'import', 'reload', 'watch', 'info',
918
- 'recordFeatureContribution', 'getFeatures',
785
+ 'dispatch', 'broadcast', 'provide', 'import', 'reload', 'watch', 'info'
919
786
  ]);
920
787
 
921
788
  /**
@@ -924,12 +791,12 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
924
791
  $bindMethods(): void {
925
792
  if (this.#methodsBound) return;
926
793
  this.#methodsBound = true;
927
-
794
+
928
795
  const proto = Object.getPrototypeOf(this);
929
796
  for (const key of Plugin.#coreMethods) {
930
797
  const value = proto[key];
931
798
  if (typeof value === "function") {
932
- (this as Record<string, unknown>)[key] = value.bind(this);
799
+ (this as any)[key] = value.bind(this);
933
800
  }
934
801
  }
935
802
  }
@@ -957,53 +824,20 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
957
824
 
958
825
  const plugin = new Plugin(realPath, parent);
959
826
  plugin.fileHash = getFileHash(entryFile);
960
-
827
+
961
828
  // 先记录,防止循环依赖时重复加载
962
829
  loadedModules.set(realPath, plugin);
963
830
 
964
- let mod: any;
965
831
  await storage.run(plugin, async () => {
966
- mod = await import(`${pathToFileURL(entryFile).href}?t=${Date.now()}`);
832
+ await import(`${import.meta.resolve(entryFile)}?t=${Date.now()}`);
967
833
  });
968
834
 
969
- // 支持模块显式声明插件名称:export const pluginName = 'my-plugin'
970
- if (mod?.pluginName && typeof mod.pluginName === 'string') {
971
- plugin.setName(mod.pluginName);
972
- }
973
-
974
835
  return plugin;
975
836
  }
976
837
  }
977
838
  export function defineContext<T extends keyof Plugin.Contexts, E extends Partial<Plugin.Extensions> = {}>(options: Context<T, E>): Context<T, E> {
978
839
  return options;
979
840
  }
980
-
981
- /**
982
- * 声明式定义插件。
983
- * 提供显式名称 + setup 函数,自动设置当前插件名称并执行 setup。
984
- *
985
- * @example
986
- * ```typescript
987
- * // plugins/weather/index.ts
988
- * export default definePlugin({
989
- * name: 'weather',
990
- * setup(plugin) {
991
- * plugin.addTool({ ... });
992
- * },
993
- * });
994
- * ```
995
- */
996
- export function definePlugin(options: {
997
- name: string;
998
- setup: (plugin: Plugin) => MaybePromise<void>;
999
- }): { pluginName: string } {
1000
- const plugin = usePlugin();
1001
- if (options.name) {
1002
- plugin.setName(options.name);
1003
- }
1004
- options.setup(plugin);
1005
- return { pluginName: options.name };
1006
- }
1007
841
  export interface Context<T extends keyof Plugin.Contexts = keyof Plugin.Contexts, E extends Partial<Plugin.Extensions> = {}> {
1008
842
  name: T;
1009
843
  description: string
@@ -1016,18 +850,17 @@ export interface Context<T extends keyof Plugin.Contexts = keyof Plugin.Contexts
1016
850
  // ============================================================================
1017
851
  // 类型定义
1018
852
  // ============================================================================
1019
-
1020
- /** 登录辅助:bot.login.pending 事件 payload */
1021
- export interface BotLoginPendingTask {
1022
- id: string;
1023
- adapter: string;
1024
- botId: string;
1025
- type: string;
1026
- payload?: { message?: string; image?: string; url?: string; [key: string]: unknown };
1027
- createdAt: number;
1028
- }
1029
-
1030
853
  export namespace Plugin {
854
+ /**
855
+ * 插件提供的功能
856
+ */
857
+ export interface Features {
858
+ commands: string[];
859
+ components: string[];
860
+ crons: string[];
861
+ middlewares: string[];
862
+ }
863
+
1031
864
  /**
1032
865
  * 生命周期事件
1033
866
  */
@@ -1042,30 +875,22 @@ export namespace Plugin {
1042
875
  'before.sendMessage': [SendOptions];
1043
876
  "context.mounted": [keyof Plugin.Contexts];
1044
877
  "context.dispose": [keyof Plugin.Contexts];
1045
- /** 登录辅助:有待办时触发 */
1046
- 'bot.login.pending': [BotLoginPendingTask];
1047
- // Notice 事件
1048
- 'notice.receive': [Notice];
1049
- 'message.receive': [Message];
1050
- [key: `notice.${string}`]: [Notice];
1051
- // Request 事件
1052
- 'request.receive': [Request];
1053
- [key: `request.${string}`]: [Request];
878
+ "message.receive": [any];
879
+ "bot.login.pending": [import('./built/login-assist.js').PendingLoginTask];
880
+ "request.receive": [any];
881
+ "notice.receive": [any];
1054
882
  }
1055
883
 
1056
884
  /**
1057
885
  * 服务类型扩展点
1058
886
  * 各个 Context 通过 declare module 扩展此接口
1059
887
  */
1060
- export interface Contexts extends Adapters {
1061
- config: ConfigFeature;
1062
- permission: PermissionFeature;
1063
- }
1064
-
888
+ export interface Contexts extends Adapters {}
889
+
1065
890
  /**
1066
891
  * Service 扩展方法类型
1067
892
  * 这些方法由各个 Context 的 extensions 提供
1068
893
  * 在 Plugin.start() 时自动注册到 Plugin.prototype
1069
894
  */
1070
- export interface Extensions { }
895
+ export interface Extensions {}
1071
896
  }