@zhin.js/core 1.0.51 → 1.0.52

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.
@@ -11,42 +11,88 @@
11
11
  * ▼
12
12
  * ┌────────────────────────────────────────┐
13
13
  * │ Stage 2: Route(路径判定) │
14
- * │ 精确命令 Command 快速路径
15
- * │ 其它消息 AI Agent 路径
14
+ * │ exclusive:命令与 AI 互斥(旧行为)
15
+ * │ dual:命令与 AI 独立判定,可同时命中
16
16
  * └──────────────┬─────────────────────────┘
17
17
  * ▼
18
18
  * ┌────────────────────────────────────────┐
19
19
  * │ Stage 3: Handle(处理) │
20
20
  * │ Command: commandService.handle() │
21
21
  * │ AI: aiHandler (由 AI 模块注册) │
22
+ * │ 出站:replyWithPolish → $reply → Adapter.sendMessage → before.sendMessage │
22
23
  * └────────────────────────────────────────┘
23
24
  *
24
25
  * 注意:Context key 为 'dispatcher',避免与 HTTP 模块的 'router' 冲突。
26
+ *
27
+ * 默认路由为 exclusive(命令与 AI 互斥);需双轨时请显式 dualRoute.mode: 'dual'。
25
28
  */
26
29
 
30
+ import { AsyncLocalStorage } from 'node:async_hooks';
27
31
  import { Message } from '../message.js';
28
32
  import { Plugin, getPlugin } from '../plugin.js';
29
33
  import type {
30
34
  MessageMiddleware,
31
35
  RegisteredAdapter,
32
- AdapterMessage,
33
36
  MaybePromise,
34
- ToolContext,
35
- Tool,
37
+ SendContent,
38
+ OutboundReplySource,
39
+ OutboundPolishContext,
40
+ OutboundPolishMiddleware,
41
+ BeforeSendHandler,
36
42
  } from '../types.js';
37
43
  import type { Context } from '../plugin.js';
38
44
 
45
+ /** Dispatcher 管理的「会话回复」异步上下文,供 `before.sendMessage` 内读取(与 Adapter.renderSendMessage 同链) */
46
+ const outboundReplyAls = new AsyncLocalStorage<{ message: Message<any>; source: OutboundReplySource }>();
47
+
48
+ export function getOutboundReplyStore(): { message: Message<any>; source: OutboundReplySource } | undefined {
49
+ return outboundReplyAls.getStore();
50
+ }
51
+
39
52
  // ============================================================================
40
53
  // 类型定义
41
54
  // ============================================================================
42
55
 
43
56
  /**
44
- * 路由判定结果
57
+ * 路由判定结果(互斥模式 legacy)
45
58
  */
46
59
  export type RouteResult =
47
- | { type: 'command' } // 精确命令 → Command 快速路径
48
- | { type: 'ai'; content: string } // 自然语言 → AI Agent 路径
49
- | { type: 'skip' }; // 不处理(被 Guardrail 拦截等)
60
+ | { type: 'command' }
61
+ | { type: 'ai'; content: string }
62
+ | { type: 'skip' };
63
+
64
+ /**
65
+ * 双轨分流配置
66
+ */
67
+ export interface DualRouteConfig {
68
+ /**
69
+ * exclusive:与旧版一致,命中命令则不再走 AI;
70
+ * dual:命令与 AI 独立判定,可同时执行(顺序由 order 决定)
71
+ */
72
+ mode?: 'exclusive' | 'dual';
73
+ /** 同时命中时的执行顺序,默认先指令后 AI */
74
+ order?: 'command-first' | 'ai-first';
75
+ /**
76
+ * 是否允许在双命中时各回复一次;为 false 时仅执行 order 中的第一个分支
77
+ */
78
+ allowDualReply?: boolean;
79
+ }
80
+
81
+ export type ResolvedDualRouteConfig = Required<DualRouteConfig>;
82
+
83
+ const DUAL_ROUTE_DEFAULTS: ResolvedDualRouteConfig = {
84
+ mode: 'exclusive',
85
+ order: 'command-first',
86
+ allowDualReply: false,
87
+ };
88
+
89
+ function resolveDualRouteConfig(partial?: Partial<DualRouteConfig>): ResolvedDualRouteConfig {
90
+ return {
91
+ mode: partial?.mode ?? DUAL_ROUTE_DEFAULTS.mode,
92
+ order: partial?.order ?? DUAL_ROUTE_DEFAULTS.order,
93
+ allowDualReply: partial?.allowDualReply ?? DUAL_ROUTE_DEFAULTS.allowDualReply,
94
+ };
95
+ }
50
96
 
51
97
  /**
52
98
  * AI 处理函数签名
@@ -59,68 +105,66 @@ export type AIHandler = (
59
105
 
60
106
  /**
61
107
  * 命令前缀判定函数
62
- * 返回 true 表示该消息是精确命令调用
63
108
  */
64
109
  export type CommandMatcher = (text: string, message: Message<any>) => boolean;
65
110
 
66
111
  /**
67
112
  * AI 触发判定函数
68
- * 返回 { triggered, content } 表示是否应该触发 AI 以及提取的内容
69
113
  */
70
114
  export type AITriggerMatcher = (message: Message<any>) => { triggered: boolean; content: string };
71
115
 
72
- /**
73
- * Guardrail 中间件
74
- * 与 MessageMiddleware 签名一致,但语义上只用于鉴权/限流/安全/日志
75
- * 返回 false 或抛异常表示拦截消息
76
- */
77
116
  export type GuardrailMiddleware = MessageMiddleware<RegisteredAdapter>;
78
117
 
118
+ /** @alias OutboundReplySource:出站回复来源(指令 / AI) */
119
+ export type ReplySource = OutboundReplySource;
120
+
121
+ export interface CreateMessageDispatcherOptions {
122
+ dualRoute?: Partial<DualRouteConfig>;
123
+ }
124
+
79
125
  // ============================================================================
80
126
  // MessageDispatcher 服务
81
127
  // ============================================================================
82
128
 
83
- /**
84
- * MessageDispatcher 服务接口
85
- */
86
129
  export interface MessageDispatcherService {
87
- /**
88
- * 调度一条消息 — 这是唯一的入口
89
- * Adapter 的 message.receive 事件应该调用此方法
90
- */
91
130
  dispatch(message: Message<any>): Promise<void>;
92
131
 
93
- /**
94
- * 注册 Guardrail(护栏中间件)
95
- * Guardrail 始终执行,用于鉴权、限流、安全过滤、日志等
96
- * @returns 移除函数
97
- */
98
132
  addGuardrail(guardrail: GuardrailMiddleware): () => void;
99
133
 
100
- /**
101
- * 设置命令匹配器
102
- * 用于判定消息是否为精确命令调用
103
- * 默认:检查消息是否以已注册命令的 pattern 开头
104
- */
105
134
  setCommandMatcher(matcher: CommandMatcher): void;
106
135
 
136
+ setAITriggerMatcher(matcher: AITriggerMatcher): void;
137
+
138
+ setAIHandler(handler: AIHandler): void;
139
+
140
+ hasAIHandler(): boolean;
141
+
142
+ /** 合并更新双轨配置 */
143
+ setDualRouteConfig(config: Partial<DualRouteConfig>): void;
144
+
145
+ getDualRouteConfig(): Readonly<ResolvedDualRouteConfig>;
146
+
147
+ /** 注册出站润色:挂到根插件 `before.sendMessage`;仅在 `replyWithPolish` 触发的发送中生效(见 getOutboundReplyStore) */
148
+ addOutboundPolish(handler: OutboundPolishMiddleware): () => void;
149
+
107
150
  /**
108
- * 设置 AI 触发判定器
109
- * 用于判定消息是否应该触发 AI 处理
110
- * 由 AI 模块注册
151
+ * `before.sendMessage` 管道内调用 `message.$reply`(与 Adapter#sendMessage 同一出站链)
111
152
  */
112
- setAITriggerMatcher(matcher: AITriggerMatcher): void;
153
+ replyWithPolish(
154
+ message: Message<any>,
155
+ source: ReplySource,
156
+ content: SendContent,
157
+ ): Promise<unknown>;
113
158
 
114
159
  /**
115
- * 注册 AI 处理函数
116
- * 由 AI 模块注册,当消息路由到 AI 路径时调用
160
+ * 是否匹配为指令路径(与 dispatch 内判定一致)
117
161
  */
118
- setAIHandler(handler: AIHandler): void;
162
+ matchCommand(message: Message<any>): boolean;
119
163
 
120
164
  /**
121
- * 获取当前是否已注册 AI 处理能力
165
+ * AI 触发判定结果
122
166
  */
123
- hasAIHandler(): boolean;
167
+ matchAI(message: Message<any>): { triggered: boolean; content: string };
124
168
  }
125
169
 
126
170
  // ============================================================================
@@ -128,8 +172,8 @@ export interface MessageDispatcherService {
128
172
  // ============================================================================
129
173
 
130
174
  export interface DispatcherContextExtensions {
131
- /** 注册 Guardrail(护栏中间件) */
132
175
  addGuardrail(guardrail: GuardrailMiddleware): () => void;
176
+ addOutboundPolish(handler: OutboundPolishMiddleware): () => void;
133
177
  }
134
178
 
135
179
  declare module '../plugin.js' {
@@ -145,17 +189,18 @@ declare module '../plugin.js' {
145
189
  // 实现
146
190
  // ============================================================================
147
191
 
148
- /**
149
- * 创建 MessageDispatcher Context
150
- */
151
- export function createMessageDispatcher(): Context<'dispatcher', DispatcherContextExtensions> {
192
+ export function createMessageDispatcher(
193
+ options?: CreateMessageDispatcherOptions,
194
+ ): Context<'dispatcher', DispatcherContextExtensions> {
152
195
  const guardrails: GuardrailMiddleware[] = [];
196
+ /** mounted 前注册的润色,在 mounted 时挂到 root.before.sendMessage */
197
+ const pendingOutboundPolish: OutboundPolishMiddleware[] = [];
153
198
  let aiHandler: AIHandler | null = null;
154
199
  let aiTriggerMatcher: AITriggerMatcher | null = null;
155
200
  let commandMatcher: CommandMatcher | null = null;
156
201
  let rootPlugin: Plugin | null = null;
202
+ let dualRoute = resolveDualRouteConfig(options?.dualRoute);
157
203
 
158
- // Command prefix index — rebuilt lazily for O(1) lookup
159
204
  let commandPrefixIndex: Map<string, boolean> | null = null;
160
205
  let lastCommandCount = -1;
161
206
 
@@ -183,16 +228,15 @@ export function createMessageDispatcher(): Context<'dispatcher', DispatcherConte
183
228
  return commandPrefixIndex;
184
229
  }
185
230
 
186
- /**
187
- * Guardrail pipeline — a guardrail that does NOT call next() blocks the message.
188
- */
189
231
  async function runGuardrails(message: Message<any>): Promise<boolean> {
190
232
  if (guardrails.length === 0) return true;
191
233
 
192
234
  for (const guardrail of guardrails) {
193
235
  let nextCalled = false;
194
236
  try {
195
- await guardrail(message, async () => { nextCalled = true; });
237
+ await guardrail(message, async () => {
238
+ nextCalled = true;
239
+ });
196
240
  } catch {
197
241
  return false;
198
242
  }
@@ -201,14 +245,41 @@ export function createMessageDispatcher(): Context<'dispatcher', DispatcherConte
201
245
  return true;
202
246
  }
203
247
 
204
- function route(message: Message<any>): RouteResult {
248
+ function extractText(message: Message<any>): string {
249
+ if (!message.$content) return '';
250
+ return message.$content
251
+ .map((seg: any) => {
252
+ if (typeof seg === 'string') return seg;
253
+ if (seg.type === 'text') return seg.data?.text || '';
254
+ return '';
255
+ })
256
+ .join('')
257
+ .trim();
258
+ }
259
+
260
+ function matchCommandInternal(message: Message<any>): boolean {
261
+ const text = extractText(message);
262
+ if (commandMatcher && commandMatcher(text, message)) return true;
263
+ const index = getCommandIndex();
264
+ for (const [prefix] of index) {
265
+ if (text.startsWith(prefix)) return true;
266
+ }
267
+ return false;
268
+ }
269
+
270
+ function matchAIInternal(message: Message<any>): { triggered: boolean; content: string } {
271
+ if (!aiTriggerMatcher) return { triggered: false, content: '' };
272
+ return aiTriggerMatcher(message);
273
+ }
274
+
275
+ /** 互斥路由(与旧版 route 一致) */
276
+ function routeExclusive(message: Message<any>): RouteResult {
205
277
  const text = extractText(message);
206
278
 
207
279
  if (commandMatcher && commandMatcher(text, message)) {
208
280
  return { type: 'command' };
209
281
  }
210
282
 
211
- // Use indexed lookup instead of O(N) scan
212
283
  const index = getCommandIndex();
213
284
  for (const [prefix] of index) {
214
285
  if (text.startsWith(prefix)) {
@@ -226,64 +297,117 @@ export function createMessageDispatcher(): Context<'dispatcher', DispatcherConte
226
297
  return { type: 'skip' };
227
298
  }
228
299
 
229
- function extractText(message: Message<any>): string {
230
- if (!message.$content) return '';
231
- return message.$content
232
- .map((seg: any) => {
233
- if (typeof seg === 'string') return seg;
234
- if (seg.type === 'text') return seg.data?.text || '';
235
- return '';
236
- })
237
- .join('')
238
- .trim();
300
+ function wrapPolishAsBeforeSend(handler: OutboundPolishMiddleware): BeforeSendHandler {
301
+ return async (options) => {
302
+ const store = outboundReplyAls.getStore();
303
+ if (!store) return;
304
+ const ctx: OutboundPolishContext = {
305
+ message: store.message,
306
+ content: options.content,
307
+ source: store.source,
308
+ };
309
+ const next = await handler(ctx);
310
+ if (next !== undefined) return { ...options, content: next };
311
+ };
312
+ }
313
+
314
+ function flushPendingOutboundPolish(): void {
315
+ if (!rootPlugin) return;
316
+ const root = rootPlugin.root;
317
+ for (const mw of pendingOutboundPolish) {
318
+ const fn = wrapPolishAsBeforeSend(mw);
319
+ root.on('before.sendMessage', fn);
320
+ }
321
+ pendingOutboundPolish.length = 0;
322
+ }
323
+
324
+ async function replyWithPolishInternal(
325
+ message: Message<any>,
326
+ source: ReplySource,
327
+ content: SendContent,
328
+ ): Promise<unknown> {
329
+ if (!rootPlugin) {
330
+ return message.$reply(content);
331
+ }
332
+ return outboundReplyAls.run({ message, source }, () => message.$reply(content));
333
+ }
334
+
335
+ async function runCommandBranch(message: Message<any>): Promise<void> {
336
+ if (!rootPlugin) return;
337
+ const commandService = rootPlugin.inject('command');
338
+ if (!commandService) return;
339
+ const response = await commandService.handle(message, rootPlugin);
340
+ if (response) {
341
+ await replyWithPolishInternal(message, 'command', response);
342
+ }
343
+ }
344
+
345
+ async function runCustomMiddlewares(message: Message<any>): Promise<void> {
346
+ if (!rootPlugin) return;
347
+ const customMiddlewares = (rootPlugin as any)._getCustomMiddlewares?.() as
348
+ | MessageMiddleware<RegisteredAdapter>[]
349
+ | undefined;
350
+ if (customMiddlewares && customMiddlewares.length > 0) {
351
+ const { compose } = await import('../utils.js');
352
+ const composed = compose(customMiddlewares);
353
+ await composed(message, async () => {});
354
+ }
239
355
  }
240
356
 
241
357
  const service: MessageDispatcherService = {
242
358
  async dispatch(message: Message<any>) {
243
- // Stage 1: Guardrail
244
359
  const passed = await runGuardrails(message);
245
360
  if (!passed) return;
246
361
 
247
- // Stage 2: Route
248
- const result = route(message);
249
-
250
- // Stage 3: Handle
251
- switch (result.type) {
252
- case 'command': {
253
- if (rootPlugin) {
254
- const commandService = rootPlugin.inject('command');
255
- if (commandService) {
256
- const response = await commandService.handle(message, rootPlugin);
257
- if (response) {
258
- await message.$reply(response);
259
- }
260
- }
261
- }
262
- break;
362
+ const cfg = dualRoute;
363
+
364
+ if (cfg.mode === 'exclusive') {
365
+ const result = routeExclusive(message);
366
+ switch (result.type) {
367
+ case 'command':
368
+ await runCommandBranch(message);
369
+ break;
370
+ case 'ai':
371
+ if (aiHandler) await aiHandler(message, result.content);
372
+ break;
373
+ default:
374
+ break;
263
375
  }
376
+ await runCustomMiddlewares(message);
377
+ return;
378
+ }
264
379
 
265
- case 'ai': {
266
- if (aiHandler) {
267
- await aiHandler(message, result.content);
268
- }
269
- break;
270
- }
380
+ // dual 模式
381
+ let wantCmd = matchCommandInternal(message);
382
+ const aiRes = matchAIInternal(message);
383
+ let wantAi = aiRes.triggered;
271
384
 
272
- case 'skip':
273
- default:
274
- break;
385
+ if (!wantCmd && !wantAi) {
386
+ await runCustomMiddlewares(message);
387
+ return;
275
388
  }
276
389
 
277
- // Run legacy custom middlewares (skip the built-in command middleware at index 0).
278
- // This ensures plugins that registered middlewares via addMiddleware() still work.
279
- if (rootPlugin) {
280
- const customMiddlewares = (rootPlugin as any)._getCustomMiddlewares?.() as MessageMiddleware<RegisteredAdapter>[] | undefined;
281
- if (customMiddlewares && customMiddlewares.length > 0) {
282
- const { compose } = await import('../utils.js');
283
- const composed = compose(customMiddlewares);
284
- await composed(message, async () => {});
285
- }
390
+ if (!cfg.allowDualReply && wantCmd && wantAi) {
391
+ if (cfg.order === 'command-first') wantAi = false;
392
+ else wantCmd = false;
286
393
  }
394
+
395
+ const runCmd = async () => {
396
+ if (wantCmd) await runCommandBranch(message);
397
+ };
398
+ const runAi = async () => {
399
+ if (wantAi && aiHandler) await aiHandler(message, aiRes.content);
400
+ };
401
+
402
+ if (cfg.order === 'ai-first') {
403
+ await runAi();
404
+ await runCmd();
405
+ } else {
406
+ await runCmd();
407
+ await runAi();
408
+ }
409
+
410
+ await runCustomMiddlewares(message);
287
411
  },
288
412
 
289
413
  addGuardrail(guardrail: GuardrailMiddleware) {
@@ -309,6 +433,40 @@ export function createMessageDispatcher(): Context<'dispatcher', DispatcherConte
309
433
  hasAIHandler() {
310
434
  return aiHandler !== null;
311
435
  },
436
+
437
+ setDualRouteConfig(config: Partial<DualRouteConfig>) {
438
+ dualRoute = resolveDualRouteConfig({ ...dualRoute, ...config });
439
+ },
440
+
441
+ getDualRouteConfig() {
442
+ return { ...dualRoute };
443
+ },
444
+
445
+ addOutboundPolish(handler: OutboundPolishMiddleware) {
446
+ const fn = wrapPolishAsBeforeSend(handler);
447
+ if (rootPlugin) {
448
+ const root = rootPlugin.root;
449
+ root.on('before.sendMessage', fn);
450
+ return () => root.off('before.sendMessage', fn);
451
+ }
452
+ pendingOutboundPolish.push(handler);
453
+ return () => {
454
+ const i = pendingOutboundPolish.indexOf(handler);
455
+ if (i !== -1) pendingOutboundPolish.splice(i, 1);
456
+ };
457
+ },
458
+
459
+ replyWithPolish(message, source, content) {
460
+ return replyWithPolishInternal(message, source, content);
461
+ },
462
+
463
+ matchCommand(message) {
464
+ return matchCommandInternal(message);
465
+ },
466
+
467
+ matchAI(message) {
468
+ return matchAIInternal(message);
469
+ },
312
470
  };
313
471
 
314
472
  return {
@@ -317,6 +475,7 @@ export function createMessageDispatcher(): Context<'dispatcher', DispatcherConte
317
475
  value: service,
318
476
  mounted(plugin: Plugin) {
319
477
  rootPlugin = plugin.root;
478
+ flushPendingOutboundPolish();
320
479
  return service;
321
480
  },
322
481
  extensions: {
@@ -326,6 +485,12 @@ export function createMessageDispatcher(): Context<'dispatcher', DispatcherConte
326
485
  plugin.onDispose(dispose);
327
486
  return dispose;
328
487
  },
488
+ addOutboundPolish(handler: OutboundPolishMiddleware) {
489
+ const plugin = getPlugin();
490
+ const dispose = service.addOutboundPolish(handler);
491
+ plugin.onDispose(dispose);
492
+ return dispose;
493
+ },
329
494
  },
330
495
  };
331
496
  }
@@ -5,8 +5,7 @@
5
5
  * Plugin = 运行时容器(生命周期、服务注册、中间件)
6
6
  * Skill = AI 可见的能力接口(名称、描述、工具列表)
7
7
  *
8
- * 每个 Plugin 可以声明一个 Skill 描述,告诉 AI Agent
9
- * "我叫什么、我能做什么、我有哪些工具"
8
+ * Skill 记录由运行时注入(如 Agent 从磁盘 SKILL.md 同步),供 Agent 粗筛与工具关联。
10
9
  *
11
10
  * SkillFeature 全局收集所有 Skill,供 Agent 进行两级过滤:
12
11
  * 1. 粗筛:根据用户消息选择相关 Skill
@@ -14,7 +13,6 @@
14
13
  */
15
14
 
16
15
  import { Feature, FeatureJSON } from '../feature.js';
17
- import { Plugin, getPlugin } from '../plugin.js';
18
16
  import type { Tool } from '../types.js';
19
17
 
20
18
  // ============================================================================
@@ -51,35 +49,21 @@ export interface Skill {
51
49
  }
52
50
 
53
51
  /**
54
- * Skill 元数据 开发者在插件中声明
55
- * 由 plugin.declareSkill() 注册
52
+ * SKILL.md frontmatter 常见字段(类型提示;运行时由 Agent 等同步到 SkillFeature)
56
53
  */
57
54
  export interface SkillMetadata {
58
55
  /** 技能描述(必填) */
59
56
  description: string;
60
57
 
61
- /** 触发关键词(可选,自动从工具中聚合) */
58
+ /** 触发关键词(可选) */
62
59
  keywords?: string[];
63
60
 
64
61
  /** 分类标签(可选) */
65
62
  tags?: string[];
66
63
  }
67
64
 
68
- // ============================================================================
69
- // 扩展 Plugin 接口
70
- // ============================================================================
71
-
72
- export interface SkillContextExtensions {
73
- /**
74
- * 声明本插件的 Skill 元数据
75
- * 调用后,插件的工具会自动聚合为一个 Skill 注册到 SkillFeature
76
- */
77
- declareSkill(metadata: SkillMetadata): void;
78
- }
79
-
80
65
  declare module '../plugin.js' {
81
66
  namespace Plugin {
82
- interface Extensions extends SkillContextExtensions {}
83
67
  interface Contexts {
84
68
  skill: SkillFeature;
85
69
  }
@@ -220,60 +204,4 @@ export class SkillFeature extends Feature<Skill> {
220
204
  })),
221
205
  };
222
206
  }
223
-
224
- /**
225
- * 提供给 Plugin.prototype 的扩展方法
226
- */
227
- get extensions() {
228
- const feature = this;
229
- return {
230
- declareSkill(metadata: SkillMetadata) {
231
- const plugin = getPlugin();
232
- const pluginName = plugin.name;
233
-
234
- // 收集该插件注册的工具
235
- const toolService = plugin.root.inject('tool') as { getToolsByPlugin?: (name: string) => Tool[] } | undefined;
236
- let tools: Tool[] = [];
237
-
238
- if (toolService && typeof toolService.getToolsByPlugin === 'function') {
239
- tools = toolService.getToolsByPlugin(pluginName);
240
- } else {
241
- tools = plugin.getAllTools?.() || [];
242
- }
243
-
244
- // 聚合关键词:开发者声明 + 工具自带
245
- const allKeywords = new Set<string>(metadata.keywords || []);
246
- for (const tool of tools) {
247
- if (tool.keywords) {
248
- for (const kw of tool.keywords) {
249
- allKeywords.add(kw);
250
- }
251
- }
252
- }
253
-
254
- // 聚合标签
255
- const allTags = new Set<string>(metadata.tags || []);
256
- for (const tool of tools) {
257
- if (tool.tags) {
258
- for (const tag of tool.tags) {
259
- allTags.add(tag);
260
- }
261
- }
262
- }
263
-
264
- const skill: Skill = {
265
- name: pluginName,
266
- description: metadata.description,
267
- tools,
268
- keywords: Array.from(allKeywords),
269
- tags: Array.from(allTags),
270
- pluginName,
271
- };
272
-
273
- const dispose = feature.add(skill, pluginName);
274
- plugin.recordFeatureContribution(feature.name, pluginName);
275
- plugin.onDispose(dispose);
276
- },
277
- };
278
- }
279
207
  }
package/src/plugin.ts CHANGED
@@ -6,19 +6,24 @@
6
6
  import { AsyncLocalStorage } from "async_hooks";
7
7
  import { EventEmitter } from "events";
8
8
  import { createRequire } from "module";
9
- import type { Database, Definition } from "@zhin.js/database";
10
- import { Schema } from "@zhin.js/schema";
11
- import type { Models, RegisteredAdapters, Tool, ToolContext } from "./types.js";
9
+ import type { Tool } from "./types.js";
12
10
  import * as fs from "fs";
13
11
  import * as path from "path";
14
12
  import { fileURLToPath, pathToFileURL } from "url";
15
13
  import logger, { Logger } from "@zhin.js/logger";
16
14
  import { compose, remove, resolveEntry } from "./utils.js";
17
- import { MessageMiddleware, RegisteredAdapter, MaybePromise, ArrayItem, SendOptions } from "./types.js";
15
+ import {
16
+ MessageMiddleware,
17
+ RegisteredAdapter,
18
+ MaybePromise,
19
+ ArrayItem,
20
+ SendOptions,
21
+ } from "./types.js";
18
22
  import type { ConfigFeature } from "./built/config.js";
19
23
  import type { PermissionFeature } from "./built/permission.js";
20
24
  import { Adapter, Adapters } from "./adapter.js";
21
25
  import { Notice } from "./notice.js";
26
+ import { Message } from "./message.js";
22
27
  import { Request } from "./request.js";
23
28
  import { Feature, FeatureJSON } from "./feature.js";
24
29
  import { createHash } from "crypto";
@@ -910,7 +915,7 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
910
915
  'addMiddleware', 'useContext', 'inject', 'contextIsReady',
911
916
  'start', 'stop', 'onMounted', 'onDispose',
912
917
  'dispatch', 'broadcast', 'provide', 'import', 'reload', 'watch', 'info',
913
- 'recordFeatureContribution', 'getFeatures'
918
+ 'recordFeatureContribution', 'getFeatures',
914
919
  ]);
915
920
 
916
921
  /**
@@ -1041,6 +1046,7 @@ export namespace Plugin {
1041
1046
  'bot.login.pending': [BotLoginPendingTask];
1042
1047
  // Notice 事件
1043
1048
  'notice.receive': [Notice];
1049
+ 'message.receive': [Message];
1044
1050
  [key: `notice.${string}`]: [Notice];
1045
1051
  // Request 事件
1046
1052
  'request.receive': [Request];