@zhin.js/core 1.0.24 → 1.0.26
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 +22 -0
- package/README.md +84 -342
- package/lib/adapter.d.ts +45 -1
- package/lib/adapter.d.ts.map +1 -1
- package/lib/adapter.js +182 -1
- package/lib/adapter.js.map +1 -1
- package/lib/ai/agent.d.ts +126 -0
- package/lib/ai/agent.d.ts.map +1 -0
- package/lib/ai/agent.js +645 -0
- package/lib/ai/agent.js.map +1 -0
- package/lib/ai/context-manager.d.ts +213 -0
- package/lib/ai/context-manager.d.ts.map +1 -0
- package/lib/ai/context-manager.js +313 -0
- package/lib/ai/context-manager.js.map +1 -0
- package/lib/ai/conversation-memory.d.ts +181 -0
- package/lib/ai/conversation-memory.d.ts.map +1 -0
- package/lib/ai/conversation-memory.js +581 -0
- package/lib/ai/conversation-memory.js.map +1 -0
- package/lib/ai/follow-up.d.ts +131 -0
- package/lib/ai/follow-up.d.ts.map +1 -0
- package/lib/ai/follow-up.js +265 -0
- package/lib/ai/follow-up.js.map +1 -0
- package/lib/ai/index.d.ts +29 -0
- package/lib/ai/index.d.ts.map +1 -0
- package/lib/ai/index.js +34 -0
- package/lib/ai/index.js.map +1 -0
- package/lib/ai/init.d.ts +30 -0
- package/lib/ai/init.d.ts.map +1 -0
- package/lib/ai/init.js +424 -0
- package/lib/ai/init.js.map +1 -0
- package/lib/ai/output.d.ts +93 -0
- package/lib/ai/output.d.ts.map +1 -0
- package/lib/ai/output.js +176 -0
- package/lib/ai/output.js.map +1 -0
- package/lib/ai/providers/anthropic.d.ts +23 -0
- package/lib/ai/providers/anthropic.d.ts.map +1 -0
- package/lib/ai/providers/anthropic.js +322 -0
- package/lib/ai/providers/anthropic.js.map +1 -0
- package/lib/ai/providers/base.d.ts +43 -0
- package/lib/ai/providers/base.d.ts.map +1 -0
- package/lib/ai/providers/base.js +135 -0
- package/lib/ai/providers/base.js.map +1 -0
- package/lib/ai/providers/index.d.ts +12 -0
- package/lib/ai/providers/index.d.ts.map +1 -0
- package/lib/ai/providers/index.js +9 -0
- package/lib/ai/providers/index.js.map +1 -0
- package/lib/ai/providers/ollama.d.ts +25 -0
- package/lib/ai/providers/ollama.d.ts.map +1 -0
- package/lib/ai/providers/ollama.js +243 -0
- package/lib/ai/providers/ollama.js.map +1 -0
- package/lib/ai/providers/openai.d.ts +46 -0
- package/lib/ai/providers/openai.d.ts.map +1 -0
- package/lib/ai/providers/openai.js +132 -0
- package/lib/ai/providers/openai.js.map +1 -0
- package/lib/ai/rate-limiter.d.ts +38 -0
- package/lib/ai/rate-limiter.d.ts.map +1 -0
- package/lib/ai/rate-limiter.js +86 -0
- package/lib/ai/rate-limiter.js.map +1 -0
- package/lib/ai/service.d.ts +81 -0
- package/lib/ai/service.d.ts.map +1 -0
- package/lib/ai/service.js +274 -0
- package/lib/ai/service.js.map +1 -0
- package/lib/ai/session.d.ts +186 -0
- package/lib/ai/session.d.ts.map +1 -0
- package/lib/ai/session.js +443 -0
- package/lib/ai/session.js.map +1 -0
- package/lib/ai/tone-detector.d.ts +19 -0
- package/lib/ai/tone-detector.d.ts.map +1 -0
- package/lib/ai/tone-detector.js +72 -0
- package/lib/ai/tone-detector.js.map +1 -0
- package/lib/ai/tools.d.ts +45 -0
- package/lib/ai/tools.d.ts.map +1 -0
- package/lib/ai/tools.js +206 -0
- package/lib/ai/tools.js.map +1 -0
- package/lib/ai/types.d.ts +264 -0
- package/lib/ai/types.d.ts.map +1 -0
- package/lib/ai/types.js +6 -0
- package/lib/ai/types.js.map +1 -0
- package/lib/ai/user-profile.d.ts +56 -0
- package/lib/ai/user-profile.d.ts.map +1 -0
- package/lib/ai/user-profile.js +130 -0
- package/lib/ai/user-profile.js.map +1 -0
- package/lib/ai/zhin-agent.d.ts +165 -0
- package/lib/ai/zhin-agent.d.ts.map +1 -0
- package/lib/ai/zhin-agent.js +707 -0
- package/lib/ai/zhin-agent.js.map +1 -0
- package/lib/built/adapter-process.d.ts +4 -0
- package/lib/built/adapter-process.d.ts.map +1 -1
- package/lib/built/adapter-process.js +94 -0
- package/lib/built/adapter-process.js.map +1 -1
- package/lib/built/ai-trigger.d.ts +89 -0
- package/lib/built/ai-trigger.d.ts.map +1 -0
- package/lib/built/ai-trigger.js +166 -0
- package/lib/built/ai-trigger.js.map +1 -0
- package/lib/built/command.d.ts +33 -17
- package/lib/built/command.d.ts.map +1 -1
- package/lib/built/command.js +71 -44
- package/lib/built/command.js.map +1 -1
- package/lib/built/component.d.ts +42 -15
- package/lib/built/component.d.ts.map +1 -1
- package/lib/built/component.js +84 -52
- package/lib/built/component.js.map +1 -1
- package/lib/built/config.d.ts +54 -5
- package/lib/built/config.d.ts.map +1 -1
- package/lib/built/config.js +76 -10
- package/lib/built/config.js.map +1 -1
- package/lib/built/cron.d.ts +41 -18
- package/lib/built/cron.d.ts.map +1 -1
- package/lib/built/cron.js +106 -63
- package/lib/built/cron.js.map +1 -1
- package/lib/built/database.d.ts +55 -6
- package/lib/built/database.d.ts.map +1 -1
- package/lib/built/database.js +93 -22
- package/lib/built/database.js.map +1 -1
- package/lib/built/dispatcher.d.ts +118 -0
- package/lib/built/dispatcher.d.ts.map +1 -0
- package/lib/built/dispatcher.js +196 -0
- package/lib/built/dispatcher.js.map +1 -0
- package/lib/built/permission.d.ts +45 -5
- package/lib/built/permission.d.ts.map +1 -1
- package/lib/built/permission.js +56 -11
- package/lib/built/permission.js.map +1 -1
- package/lib/built/skill.d.ts +117 -0
- package/lib/built/skill.d.ts.map +1 -0
- package/lib/built/skill.js +191 -0
- package/lib/built/skill.js.map +1 -0
- package/lib/built/tool.d.ts +188 -0
- package/lib/built/tool.d.ts.map +1 -0
- package/lib/built/tool.js +749 -0
- package/lib/built/tool.js.map +1 -0
- package/lib/feature.d.ts +75 -0
- package/lib/feature.d.ts.map +1 -0
- package/lib/feature.js +69 -0
- package/lib/feature.js.map +1 -0
- package/lib/index.d.ts +6 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +11 -0
- package/lib/index.js.map +1 -1
- package/lib/plugin.d.ts +53 -18
- package/lib/plugin.d.ts.map +1 -1
- package/lib/plugin.js +301 -31
- package/lib/plugin.js.map +1 -1
- package/lib/types.d.ts +248 -9
- package/lib/types.d.ts.map +1 -1
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +38 -12
- package/lib/utils.js.map +1 -1
- package/package.json +4 -4
- package/src/adapter.ts +206 -2
- package/src/ai/agent.ts +772 -0
- package/src/ai/context-manager.ts +440 -0
- package/src/ai/conversation-memory.ts +774 -0
- package/src/ai/follow-up.ts +357 -0
- package/src/ai/index.ts +128 -0
- package/src/ai/init.ts +502 -0
- package/src/ai/output.ts +261 -0
- package/src/ai/providers/anthropic.ts +375 -0
- package/src/ai/providers/base.ts +173 -0
- package/src/ai/providers/index.ts +13 -0
- package/src/ai/providers/ollama.ts +292 -0
- package/src/ai/providers/openai.ts +167 -0
- package/src/ai/rate-limiter.ts +129 -0
- package/src/ai/service.ts +319 -0
- package/src/ai/session.ts +544 -0
- package/src/ai/tone-detector.ts +89 -0
- package/src/ai/tools.ts +218 -0
- package/src/ai/types.ts +296 -0
- package/src/ai/user-profile.ts +181 -0
- package/src/ai/zhin-agent.ts +845 -0
- package/src/built/adapter-process.ts +99 -0
- package/src/built/ai-trigger.ts +259 -0
- package/src/built/command.ts +75 -69
- package/src/built/component.ts +94 -76
- package/src/built/config.ts +238 -128
- package/src/built/cron.ts +117 -101
- package/src/built/database.ts +128 -33
- package/src/built/dispatcher.ts +332 -0
- package/src/built/permission.ts +146 -54
- package/src/built/skill.ts +280 -0
- package/src/built/tool.ts +928 -0
- package/src/feature.ts +113 -0
- package/src/index.ts +11 -0
- package/src/plugin.ts +359 -69
- package/src/types.ts +306 -11
- package/src/utils.ts +37 -13
- package/tests/adapter.test.ts +153 -1
- package/tests/ai/agent.test.ts +614 -0
- package/tests/ai/ai-trigger.test.ts +368 -0
- package/tests/ai/context-manager.test.ts +413 -0
- package/tests/ai/conversation-memory.test.ts +128 -0
- package/tests/ai/follow-up.test.ts +175 -0
- package/tests/ai/integration.test.ts +584 -0
- package/tests/ai/output.test.ts +128 -0
- package/tests/ai/providers.integration.test.ts +227 -0
- package/tests/ai/rate-limiter.test.ts +108 -0
- package/tests/ai/session.test.ts +375 -0
- package/tests/ai/setup.ts +308 -0
- package/tests/ai/tone-detector.test.ts +80 -0
- package/tests/ai/tool.test.ts +800 -0
- package/tests/ai/tools-builtin.test.ts +346 -0
- package/tests/ai/user-profile.test.ts +73 -0
- package/tests/ai/zhin-agent.test.ts +177 -0
- package/tests/component-new.test.ts +17 -6
- package/tests/config.test.ts +46 -0
- package/tests/cron.test.ts +94 -5
- package/tests/dispatcher.test.ts +146 -0
- package/tests/feature.test.ts +145 -0
- package/tests/features-builtin.test.ts +191 -0
- package/tests/plugin.test.ts +88 -14
- package/tests/skill-feature.test.ts +179 -0
- package/tests/tool-feature.test.ts +254 -0
package/src/plugin.ts
CHANGED
|
@@ -8,14 +8,17 @@ import { EventEmitter } from "events";
|
|
|
8
8
|
import { createRequire } from "module";
|
|
9
9
|
import type { Database, Definition } from "@zhin.js/database";
|
|
10
10
|
import { Schema } from "@zhin.js/schema";
|
|
11
|
-
import type { Models, RegisteredAdapters } from "./types.js";
|
|
11
|
+
import type { Models, RegisteredAdapters, Tool, ToolContext } from "./types.js";
|
|
12
12
|
import * as fs from "fs";
|
|
13
13
|
import * as path from "path";
|
|
14
14
|
import { fileURLToPath } from "url";
|
|
15
15
|
import logger, { Logger } from "@zhin.js/logger";
|
|
16
16
|
import { compose, remove, resolveEntry } from "./utils.js";
|
|
17
|
-
import { MessageMiddleware, RegisteredAdapter, MaybePromise, ArrayItem,
|
|
17
|
+
import { MessageMiddleware, RegisteredAdapter, MaybePromise, ArrayItem, SendOptions } from "./types.js";
|
|
18
|
+
import type { ConfigFeature } from "./built/config.js";
|
|
19
|
+
import type { PermissionFeature } from "./built/permission.js";
|
|
18
20
|
import { Adapter, Adapters } from "./adapter.js";
|
|
21
|
+
import { Feature, FeatureJSON } from "./feature.js";
|
|
19
22
|
import { createHash } from "crypto";
|
|
20
23
|
const contextsKey = Symbol("contexts");
|
|
21
24
|
const loadedModules = new Map<string, Plugin>(); // 记录已加载的模块
|
|
@@ -63,10 +66,19 @@ function getCurrentFile(metaUrl = import.meta.url): string {
|
|
|
63
66
|
/**
|
|
64
67
|
* usePlugin - 获取或创建当前插件实例
|
|
65
68
|
* 类似 React Hooks 的设计,根据调用文件自动创建插件树
|
|
69
|
+
*
|
|
70
|
+
* 同一个文件多次调用 usePlugin() 返回同一个实例,
|
|
71
|
+
* 避免 Plugin.create() + usePlugin() 产生不必要的双层包装。
|
|
66
72
|
*/
|
|
67
73
|
export function usePlugin(): Plugin {
|
|
68
74
|
const callerFile = getCurrentFile();
|
|
69
75
|
const parentPlugin = storage.getStore();
|
|
76
|
+
|
|
77
|
+
// 同一文件再次调用 usePlugin(),直接复用已有实例
|
|
78
|
+
if (parentPlugin && parentPlugin.filePath === callerFile) {
|
|
79
|
+
return parentPlugin;
|
|
80
|
+
}
|
|
81
|
+
|
|
70
82
|
const newPlugin = new Plugin(callerFile, parentPlugin);
|
|
71
83
|
storage.enterWith(newPlugin);
|
|
72
84
|
return newPlugin;
|
|
@@ -126,7 +138,7 @@ export interface Plugin extends Plugin.Extensions { }
|
|
|
126
138
|
*/
|
|
127
139
|
export class Plugin extends EventEmitter<Plugin.Lifecycle> {
|
|
128
140
|
static [contextsKey] = [] as string[];
|
|
129
|
-
|
|
141
|
+
|
|
130
142
|
#cachedName?: string;
|
|
131
143
|
adapters: (keyof Plugin.Contexts)[] = [];
|
|
132
144
|
started = false;
|
|
@@ -157,10 +169,16 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
|
|
|
157
169
|
};
|
|
158
170
|
// 插件功能
|
|
159
171
|
#middlewares: MessageMiddleware<RegisteredAdapter>[] = [this.#messageMiddleware];
|
|
160
|
-
|
|
172
|
+
|
|
173
|
+
// 本地工具存储(当 ToolService 不可用时使用)
|
|
174
|
+
#tools: Map<string, Tool> = new Map();
|
|
175
|
+
|
|
161
176
|
// 统一的清理函数集合
|
|
162
177
|
#disposables: Set<() => void | Promise<void>> = new Set();
|
|
163
|
-
|
|
178
|
+
|
|
179
|
+
// 记录当前插件向哪些 Feature 贡献了哪些 item
|
|
180
|
+
#featureContributions = new Map<string, Set<string>>();
|
|
181
|
+
|
|
164
182
|
get middleware(): MessageMiddleware<RegisteredAdapter> {
|
|
165
183
|
return compose<RegisteredAdapter>(this.#middlewares);
|
|
166
184
|
}
|
|
@@ -169,7 +187,7 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
|
|
|
169
187
|
*/
|
|
170
188
|
constructor(filePath: string = "", public parent?: Plugin) {
|
|
171
189
|
super();
|
|
172
|
-
|
|
190
|
+
|
|
173
191
|
// 增加 EventEmitter 监听器限制,避免警告
|
|
174
192
|
// 因为插件可能注册多个命令/组件/中间件,每个都会添加 dispose 监听器
|
|
175
193
|
this.setMaxListeners(50);
|
|
@@ -185,15 +203,24 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
|
|
|
185
203
|
// 绑定方法以支持解构使用
|
|
186
204
|
this.$bindMethods();
|
|
187
205
|
}
|
|
188
|
-
|
|
206
|
+
|
|
189
207
|
// 标记是否已绑定方法
|
|
190
208
|
#methodsBound = false;
|
|
191
209
|
|
|
192
210
|
/**
|
|
193
211
|
* 添加中间件
|
|
194
212
|
* 中间件用于处理消息流转
|
|
213
|
+
* @param middleware 中间件函数
|
|
195
214
|
*/
|
|
196
|
-
addMiddleware<T extends RegisteredAdapter>(middleware: MessageMiddleware<T
|
|
215
|
+
addMiddleware<T extends RegisteredAdapter>(middleware: MessageMiddleware<T>): () => void {
|
|
216
|
+
if(this.parent){
|
|
217
|
+
const dispose= this.parent.addMiddleware(middleware);
|
|
218
|
+
this.#disposables.add(dispose);
|
|
219
|
+
return () => {
|
|
220
|
+
dispose();
|
|
221
|
+
this.#disposables.delete(dispose);
|
|
222
|
+
};
|
|
223
|
+
}
|
|
197
224
|
this.#middlewares.push(middleware as MessageMiddleware<RegisteredAdapter>);
|
|
198
225
|
const dispose = () => {
|
|
199
226
|
remove(this.#middlewares, middleware);
|
|
@@ -203,22 +230,135 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
|
|
|
203
230
|
return dispose;
|
|
204
231
|
}
|
|
205
232
|
|
|
233
|
+
/**
|
|
234
|
+
* 添加工具
|
|
235
|
+
* 工具可以被 AI 服务调用,也会自动生成对应的命令
|
|
236
|
+
* @param tool 工具定义
|
|
237
|
+
* @param generateCommand 是否生成对应命令(默认 true)
|
|
238
|
+
* @returns 返回一个移除工具的函数
|
|
239
|
+
*/
|
|
240
|
+
addTool(
|
|
241
|
+
tool: Tool,
|
|
242
|
+
generateCommand: boolean = true
|
|
243
|
+
): () => void {
|
|
244
|
+
// 尝试使用 ToolFeature
|
|
245
|
+
const toolService = this.root.inject('tool' as any) as any;
|
|
246
|
+
if (toolService && typeof toolService.addTool === 'function') {
|
|
247
|
+
const dispose = toolService.addTool(tool, this.name, generateCommand);
|
|
248
|
+
this.#disposables.add(dispose);
|
|
249
|
+
return () => {
|
|
250
|
+
dispose();
|
|
251
|
+
this.#disposables.delete(dispose);
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// 回退到本地存储
|
|
256
|
+
const toolWithSource: Tool = {
|
|
257
|
+
...tool,
|
|
258
|
+
source: tool.source || `plugin:${this.name}`,
|
|
259
|
+
tags: [...(tool.tags || []), 'plugin', this.name],
|
|
260
|
+
};
|
|
261
|
+
this.#tools.set(tool.name, toolWithSource);
|
|
262
|
+
const dispose = () => {
|
|
263
|
+
this.#tools.delete(tool.name);
|
|
264
|
+
this.#disposables.delete(dispose);
|
|
265
|
+
};
|
|
266
|
+
this.#disposables.add(dispose);
|
|
267
|
+
return dispose;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* 获取当前插件注册的所有工具
|
|
272
|
+
*/
|
|
273
|
+
getTools(): Tool[] {
|
|
274
|
+
return Array.from(this.#tools.values());
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* 获取当前插件及所有子插件注册的工具
|
|
279
|
+
*/
|
|
280
|
+
getAllTools(): Tool[] {
|
|
281
|
+
const tools: Tool[] = [...this.getTools()];
|
|
282
|
+
for (const child of this.children) {
|
|
283
|
+
tools.push(...child.getAllTools());
|
|
284
|
+
}
|
|
285
|
+
return tools;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* 根据名称获取工具
|
|
290
|
+
*/
|
|
291
|
+
getTool(name: string): Tool | undefined {
|
|
292
|
+
// 先在当前插件查找
|
|
293
|
+
const tool = this.#tools.get(name);
|
|
294
|
+
if (tool) return tool;
|
|
295
|
+
// 再在子插件中查找
|
|
296
|
+
for (const child of this.children) {
|
|
297
|
+
const childTool = child.getTool(name);
|
|
298
|
+
if (childTool) return childTool;
|
|
299
|
+
}
|
|
300
|
+
return undefined;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* 收集所有可用的工具
|
|
305
|
+
* 优先使用 ToolService,否则回退到本地收集
|
|
306
|
+
*/
|
|
307
|
+
collectAllTools(): Tool[] {
|
|
308
|
+
// 尝试使用 ToolService
|
|
309
|
+
const toolService = this.root.inject('tool' as any) as any;
|
|
310
|
+
if (toolService && typeof toolService.collectAll === 'function') {
|
|
311
|
+
return toolService.collectAll(this.root);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// 回退到本地收集
|
|
315
|
+
const tools: Tool[] = [];
|
|
316
|
+
|
|
317
|
+
// 收集插件树中的所有工具
|
|
318
|
+
const rootPlugin = this.root;
|
|
319
|
+
tools.push(...rootPlugin.getAllTools());
|
|
320
|
+
|
|
321
|
+
// 收集所有适配器的工具
|
|
322
|
+
for (const [name, context] of rootPlugin.contexts) {
|
|
323
|
+
const value = context.value;
|
|
324
|
+
if (value && typeof value === 'object' && 'getTools' in value) {
|
|
325
|
+
const adapter = value as Adapter;
|
|
326
|
+
tools.push(...adapter.getTools());
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return tools;
|
|
331
|
+
}
|
|
332
|
+
|
|
206
333
|
/**
|
|
207
334
|
* 插件名称
|
|
208
335
|
*/
|
|
209
336
|
get name(): string {
|
|
210
337
|
if (this.#cachedName) return this.#cachedName;
|
|
211
338
|
|
|
212
|
-
|
|
339
|
+
let name = path
|
|
213
340
|
.relative(process.cwd(), this.filePath)
|
|
214
341
|
.replace(/\?t=\d+$/, "")
|
|
215
342
|
.replace(/\\/g, "/")
|
|
216
343
|
.replace(/\/index\.(js|ts)x?$/, "")
|
|
217
|
-
.replace(/\/(lib|src|dist)$/, "")
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
344
|
+
.replace(/\/(lib|src|dist)$/, "");
|
|
345
|
+
|
|
346
|
+
// 安全地提取 node_modules 后的包名或最后的文件名
|
|
347
|
+
const nodeModulesIndex = name.indexOf('node_modules/');
|
|
348
|
+
if (nodeModulesIndex !== -1) {
|
|
349
|
+
name = name.substring(nodeModulesIndex + 'node_modules/'.length);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// 提取最后一个路径段
|
|
353
|
+
const lastSlash = name.lastIndexOf('/');
|
|
354
|
+
if (lastSlash !== -1) {
|
|
355
|
+
name = name.substring(lastSlash + 1);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// 移除文件扩展名
|
|
359
|
+
name = name.replace(/\.(js|ts)x?$/, "");
|
|
360
|
+
|
|
361
|
+
this.#cachedName = name;
|
|
222
362
|
return this.#cachedName;
|
|
223
363
|
}
|
|
224
364
|
|
|
@@ -277,7 +417,7 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
|
|
|
277
417
|
if (!this.#contextsIsReady(contexts)) return
|
|
278
418
|
contextReadyCallback()
|
|
279
419
|
}
|
|
280
|
-
inject<T extends keyof Plugin.Contexts>(name: T): Plugin.Contexts[T]|undefined {
|
|
420
|
+
inject<T extends keyof Plugin.Contexts>(name: T): Plugin.Contexts[T] | undefined {
|
|
281
421
|
const context = this.root.contexts.get(name as string);
|
|
282
422
|
return context?.value as Plugin.Contexts[T];
|
|
283
423
|
}
|
|
@@ -299,22 +439,16 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
|
|
|
299
439
|
/**
|
|
300
440
|
* 启动插件
|
|
301
441
|
*/
|
|
302
|
-
async start(t?:number): Promise<void> {
|
|
442
|
+
async start(t?: number): Promise<void> {
|
|
303
443
|
if (this.started) return;
|
|
304
444
|
this.started = true; // 提前设置,防止重复启动
|
|
305
|
-
|
|
445
|
+
|
|
306
446
|
// 启动所有服务
|
|
307
447
|
for (const context of this.$contexts.values()) {
|
|
308
|
-
if (typeof context.mounted === "function"
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
if (context.extensions) {
|
|
313
|
-
for (const [name, fn] of Object.entries(context.extensions)) {
|
|
314
|
-
if (typeof fn === 'function') {
|
|
315
|
-
Reflect.set(Plugin.prototype, name, fn);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
448
|
+
if (typeof context.mounted === "function") {
|
|
449
|
+
const result = await context.mounted(this);
|
|
450
|
+
// 仅当 value 未预设时才赋值(mounted 始终执行,以支持副作用如设置内部引用)
|
|
451
|
+
if (!context.value) context.value = result;
|
|
318
452
|
}
|
|
319
453
|
this.dispatch('context.mounted', context.name)
|
|
320
454
|
}
|
|
@@ -326,32 +460,163 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
|
|
|
326
460
|
// 输出启动日志(使用 debug 级别,避免重复输出)
|
|
327
461
|
// 只在根插件或重要插件时使用 info 级别
|
|
328
462
|
if (!this.parent || this.name === 'setup') {
|
|
329
|
-
|
|
463
|
+
this.logger.info(`Plugin "${this.name}" ${t ? `reloaded in ${Date.now() - t}ms` : "started"}`);
|
|
330
464
|
} else {
|
|
331
465
|
this.logger.debug(`Plugin "${this.name}" ${t ? `reloaded in ${Date.now() - t}ms` : "started"}`);
|
|
332
466
|
}
|
|
333
467
|
}
|
|
334
468
|
/**
|
|
335
|
-
*
|
|
336
|
-
*
|
|
469
|
+
* 记录 Feature 贡献(由 Feature extensions 内部调用)
|
|
470
|
+
* @param featureName Feature 名称(如 'command')
|
|
471
|
+
* @param itemName item 标识(如命令 pattern)
|
|
337
472
|
*/
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
473
|
+
recordFeatureContribution(featureName: string, itemName: string): void {
|
|
474
|
+
if (!this.#featureContributions.has(featureName)) {
|
|
475
|
+
this.#featureContributions.set(featureName, new Set());
|
|
476
|
+
}
|
|
477
|
+
this.#featureContributions.get(featureName)!.add(itemName);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* 收集本插件及所有后代插件的 Feature 贡献名称
|
|
482
|
+
* 解决 Plugin.create() 包装问题:外层插件的 #featureContributions 为空,
|
|
483
|
+
* 实际贡献记录在 usePlugin() 创建的内层子插件上
|
|
484
|
+
*/
|
|
485
|
+
#collectAllFeatureNames(names: Set<string>): void {
|
|
486
|
+
for (const name of this.#featureContributions.keys()) {
|
|
487
|
+
names.add(name);
|
|
488
|
+
}
|
|
489
|
+
for (const child of this.children) {
|
|
490
|
+
child.#collectAllFeatureNames(names);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* 收集本插件及所有后代插件的 plugin name 集合
|
|
496
|
+
* 用于 Feature.toJSON(pluginName) 匹配
|
|
497
|
+
*/
|
|
498
|
+
#collectAllPluginNames(names: Set<string>): void {
|
|
499
|
+
names.add(this.name);
|
|
500
|
+
for (const child of this.children) {
|
|
501
|
+
child.#collectAllPluginNames(names);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* 获取当前插件的所有 Feature 数据(用于 HTTP API)
|
|
507
|
+
* 遍历插件贡献的 Feature,调用各 Feature 的 toJSON(pluginName) 获取序列化数据
|
|
508
|
+
* 同时包含 middleware(方案 B: 本地构造)
|
|
509
|
+
*
|
|
510
|
+
* 注意:Plugin.create() 创建 "外层" 插件,usePlugin() 创建 "内层" 子插件。
|
|
511
|
+
* extension 方法 (addCommand 等) 通过 getPlugin() 记录在内层插件上。
|
|
512
|
+
* 因此需要遍历整个子树来收集 feature 贡献名称。
|
|
513
|
+
*/
|
|
514
|
+
getFeatures(): FeatureJSON[] {
|
|
515
|
+
const result: FeatureJSON[] = [];
|
|
516
|
+
|
|
517
|
+
// 收集本插件及所有后代插件的 feature 名称和 plugin 名称
|
|
518
|
+
const featureNames = new Set<string>();
|
|
519
|
+
this.#collectAllFeatureNames(featureNames);
|
|
520
|
+
|
|
521
|
+
const pluginNames = new Set<string>();
|
|
522
|
+
this.#collectAllPluginNames(pluginNames);
|
|
523
|
+
|
|
524
|
+
// 从 Feature 贡献中收集
|
|
525
|
+
for (const featureName of featureNames) {
|
|
526
|
+
const feature = this.inject(featureName as keyof Plugin.Contexts);
|
|
527
|
+
if (feature instanceof Feature) {
|
|
528
|
+
// 先用当前插件名尝试
|
|
529
|
+
let json = feature.toJSON(this.name);
|
|
530
|
+
if (json.count === 0) {
|
|
531
|
+
// 当前插件名匹配不到(可能名称不同),尝试后代插件名
|
|
532
|
+
for (const pName of pluginNames) {
|
|
533
|
+
if (pName === this.name) continue;
|
|
534
|
+
json = feature.toJSON(pName);
|
|
535
|
+
if (json.count > 0) break;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
if (json.count > 0) {
|
|
539
|
+
result.push(json);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// middleware(方案 B: 本地构造,因为 middleware 是 Plugin 私有属性)
|
|
545
|
+
// 同样需要收集子插件树中的 middleware
|
|
546
|
+
const allMiddlewareNames: string[] = [];
|
|
547
|
+
const collectMiddlewares = (plugin: Plugin) => {
|
|
548
|
+
const mws = plugin.#middlewares
|
|
549
|
+
.filter(m => m !== plugin.#messageMiddleware)
|
|
550
|
+
.map((m, i) => m.name || `middleware_${i}`);
|
|
551
|
+
allMiddlewareNames.push(...mws);
|
|
552
|
+
for (const child of plugin.children) {
|
|
553
|
+
collectMiddlewares(child);
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
collectMiddlewares(this);
|
|
557
|
+
|
|
558
|
+
if (allMiddlewareNames.length > 0) {
|
|
559
|
+
result.push({
|
|
560
|
+
name: 'middleware',
|
|
561
|
+
icon: 'Layers',
|
|
562
|
+
desc: '中间件',
|
|
563
|
+
count: allMiddlewareNames.length,
|
|
564
|
+
items: allMiddlewareNames.map(name => ({ name })),
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// 自动检测适配器和服务上下文(从 $contexts 中发现非 Feature 的贡献)
|
|
569
|
+
const adapterItems: { name: string; bots: number; online: number; tools: number }[] = [];
|
|
570
|
+
const serviceItems: { name: string; desc: string }[] = [];
|
|
571
|
+
|
|
572
|
+
const scanContexts = (plugin: Plugin) => {
|
|
573
|
+
for (const [name, context] of plugin.$contexts) {
|
|
574
|
+
const value = context.value;
|
|
575
|
+
if (value instanceof Adapter) {
|
|
576
|
+
adapterItems.push({
|
|
577
|
+
name,
|
|
578
|
+
bots: value.bots.size,
|
|
579
|
+
online: Array.from(value.bots.values()).filter(b => b.$connected).length,
|
|
580
|
+
tools: value.tools.size,
|
|
581
|
+
});
|
|
582
|
+
} else if (value !== undefined && !(value instanceof Feature)) {
|
|
583
|
+
// 非 Feature、非 Adapter 的上下文 = 服务
|
|
584
|
+
serviceItems.push({ name, desc: context.description || name });
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
for (const child of plugin.children) {
|
|
588
|
+
scanContexts(child);
|
|
589
|
+
}
|
|
348
590
|
};
|
|
591
|
+
scanContexts(this);
|
|
592
|
+
|
|
593
|
+
if (adapterItems.length > 0) {
|
|
594
|
+
result.push({
|
|
595
|
+
name: 'adapter',
|
|
596
|
+
icon: 'Plug',
|
|
597
|
+
desc: '适配器',
|
|
598
|
+
count: adapterItems.length,
|
|
599
|
+
items: adapterItems,
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (serviceItems.length > 0) {
|
|
604
|
+
result.push({
|
|
605
|
+
name: 'service',
|
|
606
|
+
icon: 'Server',
|
|
607
|
+
desc: '服务',
|
|
608
|
+
count: serviceItems.length,
|
|
609
|
+
items: serviceItems,
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
return result;
|
|
349
614
|
}
|
|
350
615
|
|
|
351
616
|
info(): Record<string, any> {
|
|
352
617
|
return {
|
|
353
618
|
[this.name]: {
|
|
354
|
-
features: this.
|
|
619
|
+
features: this.getFeatures(),
|
|
355
620
|
children: this.children.map(child => child.info())
|
|
356
621
|
}
|
|
357
622
|
}
|
|
@@ -387,12 +652,12 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
|
|
|
387
652
|
// 清理 contexts Map
|
|
388
653
|
this.$contexts.clear();
|
|
389
654
|
|
|
390
|
-
// 清空缓存的名称
|
|
655
|
+
// 清空缓存的名称
|
|
391
656
|
this.#cachedName = undefined;
|
|
392
657
|
|
|
393
658
|
// 触发 dispose 事件
|
|
394
659
|
this.emit("dispose");
|
|
395
|
-
|
|
660
|
+
|
|
396
661
|
// 执行所有清理函数
|
|
397
662
|
for (const dispose of this.#disposables) {
|
|
398
663
|
try {
|
|
@@ -402,14 +667,17 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
|
|
|
402
667
|
}
|
|
403
668
|
}
|
|
404
669
|
this.#disposables.clear();
|
|
405
|
-
|
|
670
|
+
|
|
406
671
|
// 清理 middlewares 数组(保留默认的消息中间件)
|
|
407
672
|
this.#middlewares.length = 1;
|
|
408
|
-
|
|
673
|
+
|
|
674
|
+
// 清理 feature 贡献记录
|
|
675
|
+
this.#featureContributions.clear();
|
|
676
|
+
|
|
409
677
|
if (this.parent) {
|
|
410
678
|
remove(this.parent?.children, this);
|
|
411
679
|
}
|
|
412
|
-
|
|
680
|
+
|
|
413
681
|
// 从全局 loadedModules Map 中移除,防止内存泄漏
|
|
414
682
|
if (this.filePath) {
|
|
415
683
|
try {
|
|
@@ -419,7 +687,7 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
|
|
|
419
687
|
// 文件可能已不存在,忽略错误
|
|
420
688
|
}
|
|
421
689
|
}
|
|
422
|
-
|
|
690
|
+
|
|
423
691
|
this.removeAllListeners();
|
|
424
692
|
this.logger.debug(`Plugin "${this.name}" stopped`);
|
|
425
693
|
}
|
|
@@ -478,13 +746,44 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
|
|
|
478
746
|
// ============================================================================
|
|
479
747
|
|
|
480
748
|
/**
|
|
481
|
-
*
|
|
749
|
+
* 注册上下文(支持 Feature 实例或传统 Context 对象)
|
|
482
750
|
*/
|
|
483
|
-
provide<T extends keyof Plugin.Contexts>(
|
|
751
|
+
provide<T extends keyof Plugin.Contexts>(target: Feature | Context<T>): this {
|
|
752
|
+
if (target instanceof Feature) {
|
|
753
|
+
// Feature → 自动转为内部 Context 格式存储
|
|
754
|
+
const feature = target;
|
|
755
|
+
const context: Context<T> = {
|
|
756
|
+
name: feature.name as T,
|
|
757
|
+
description: feature.desc,
|
|
758
|
+
value: feature as any,
|
|
759
|
+
mounted: feature.mounted
|
|
760
|
+
? async (plugin: Plugin) => {
|
|
761
|
+
await feature.mounted!(plugin);
|
|
762
|
+
return feature as any;
|
|
763
|
+
}
|
|
764
|
+
: undefined,
|
|
765
|
+
dispose: feature.dispose
|
|
766
|
+
? async () => { await feature.dispose!(); }
|
|
767
|
+
: undefined,
|
|
768
|
+
extensions: feature.extensions,
|
|
769
|
+
};
|
|
770
|
+
return this.provide(context);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// 传统 Context 路径
|
|
774
|
+
const context = target;
|
|
484
775
|
if (!Plugin[contextsKey].includes(context.name as string)) {
|
|
485
776
|
Plugin[contextsKey].push(context.name as string);
|
|
486
777
|
}
|
|
487
778
|
this.logger.debug(`Context "${context.name as string}" provided`);
|
|
779
|
+
// 注册扩展方法到 Plugin.prototype
|
|
780
|
+
if (context.extensions) {
|
|
781
|
+
for (const [name, fn] of Object.entries(context.extensions)) {
|
|
782
|
+
if (typeof fn === 'function') {
|
|
783
|
+
Reflect.set(Plugin.prototype, name, fn);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
488
787
|
this.$contexts.set(context.name as string, context);
|
|
489
788
|
return this;
|
|
490
789
|
}
|
|
@@ -496,7 +795,7 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
|
|
|
496
795
|
/**
|
|
497
796
|
* 导入插件
|
|
498
797
|
*/
|
|
499
|
-
async import(entry: string,t?:number): Promise<Plugin> {
|
|
798
|
+
async import(entry: string, t?: number): Promise<Plugin> {
|
|
500
799
|
if (!entry) throw new Error(`Plugin entry not found: ${entry}`);
|
|
501
800
|
const resolved = resolveEntry(path.isAbsolute(entry) ?
|
|
502
801
|
entry :
|
|
@@ -510,7 +809,7 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
|
|
|
510
809
|
|
|
511
810
|
// 避免重复加载同一路径的插件
|
|
512
811
|
const normalized = realPath.replace(/\?t=\d+$/, '').replace(/\\/g, '/');
|
|
513
|
-
const existing = this.children.find(child =>
|
|
812
|
+
const existing = this.children.find(child =>
|
|
514
813
|
child.filePath.replace(/\?t=\d+$/, '').replace(/\\/g, '/') === normalized
|
|
515
814
|
);
|
|
516
815
|
if (existing) {
|
|
@@ -533,7 +832,7 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
|
|
|
533
832
|
*/
|
|
534
833
|
async reload(plugin: Plugin = this): Promise<void> {
|
|
535
834
|
this.logger.info(`Plugin "${plugin.name}" reloading...`);
|
|
536
|
-
const now=Date.now();
|
|
835
|
+
const now = Date.now();
|
|
537
836
|
if (!plugin.parent) {
|
|
538
837
|
// 根插件重载 = 退出进程(由 CLI 重启)
|
|
539
838
|
return process.exit(51);
|
|
@@ -580,7 +879,8 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
|
|
|
580
879
|
static #coreMethods = new Set([
|
|
581
880
|
'addMiddleware', 'useContext', 'inject', 'contextIsReady',
|
|
582
881
|
'start', 'stop', 'onMounted', 'onDispose',
|
|
583
|
-
'dispatch', 'broadcast', 'provide', 'import', 'reload', 'watch', 'info'
|
|
882
|
+
'dispatch', 'broadcast', 'provide', 'import', 'reload', 'watch', 'info',
|
|
883
|
+
'recordFeatureContribution', 'getFeatures'
|
|
584
884
|
]);
|
|
585
885
|
|
|
586
886
|
/**
|
|
@@ -589,7 +889,7 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
|
|
|
589
889
|
$bindMethods(): void {
|
|
590
890
|
if (this.#methodsBound) return;
|
|
591
891
|
this.#methodsBound = true;
|
|
592
|
-
|
|
892
|
+
|
|
593
893
|
const proto = Object.getPrototypeOf(this);
|
|
594
894
|
for (const key of Plugin.#coreMethods) {
|
|
595
895
|
const value = proto[key];
|
|
@@ -622,7 +922,7 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
|
|
|
622
922
|
|
|
623
923
|
const plugin = new Plugin(realPath, parent);
|
|
624
924
|
plugin.fileHash = getFileHash(entryFile);
|
|
625
|
-
|
|
925
|
+
|
|
626
926
|
// 先记录,防止循环依赖时重复加载
|
|
627
927
|
loadedModules.set(realPath, plugin);
|
|
628
928
|
|
|
@@ -649,16 +949,6 @@ export interface Context<T extends keyof Plugin.Contexts = keyof Plugin.Contexts
|
|
|
649
949
|
// 类型定义
|
|
650
950
|
// ============================================================================
|
|
651
951
|
export namespace Plugin {
|
|
652
|
-
/**
|
|
653
|
-
* 插件提供的功能
|
|
654
|
-
*/
|
|
655
|
-
export interface Features {
|
|
656
|
-
commands: string[];
|
|
657
|
-
components: string[];
|
|
658
|
-
crons: string[];
|
|
659
|
-
middlewares: string[];
|
|
660
|
-
}
|
|
661
|
-
|
|
662
952
|
/**
|
|
663
953
|
* 生命周期事件
|
|
664
954
|
*/
|
|
@@ -680,14 +970,14 @@ export namespace Plugin {
|
|
|
680
970
|
* 各个 Context 通过 declare module 扩展此接口
|
|
681
971
|
*/
|
|
682
972
|
export interface Contexts extends Adapters {
|
|
683
|
-
config:
|
|
684
|
-
permission:
|
|
973
|
+
config: ConfigFeature;
|
|
974
|
+
permission: PermissionFeature;
|
|
685
975
|
}
|
|
686
|
-
|
|
976
|
+
|
|
687
977
|
/**
|
|
688
978
|
* Service 扩展方法类型
|
|
689
979
|
* 这些方法由各个 Context 的 extensions 提供
|
|
690
980
|
* 在 Plugin.start() 时自动注册到 Plugin.prototype
|
|
691
981
|
*/
|
|
692
|
-
export interface Extensions {}
|
|
982
|
+
export interface Extensions { }
|
|
693
983
|
}
|