@zhin.js/core 1.0.37 → 1.0.38

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 (196) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +57 -3
  3. package/lib/adapter.d.ts +11 -0
  4. package/lib/adapter.d.ts.map +1 -1
  5. package/lib/adapter.js +61 -0
  6. package/lib/adapter.js.map +1 -1
  7. package/lib/ai/index.d.ts +3 -39
  8. package/lib/ai/index.d.ts.map +1 -1
  9. package/lib/ai/index.js +2 -44
  10. package/lib/ai/index.js.map +1 -1
  11. package/lib/ai/types.d.ts +4 -3
  12. package/lib/ai/types.d.ts.map +1 -1
  13. package/lib/built/ai-trigger.js.map +1 -1
  14. package/lib/built/common-adapter-tools.d.ts +55 -0
  15. package/lib/built/common-adapter-tools.d.ts.map +1 -0
  16. package/lib/built/common-adapter-tools.js +158 -0
  17. package/lib/built/common-adapter-tools.js.map +1 -0
  18. package/lib/built/dispatcher.d.ts.map +1 -1
  19. package/lib/built/dispatcher.js +50 -46
  20. package/lib/built/dispatcher.js.map +1 -1
  21. package/lib/built/skill.d.ts.map +1 -1
  22. package/lib/built/skill.js +0 -1
  23. package/lib/built/skill.js.map +1 -1
  24. package/lib/built/tool.d.ts +3 -3
  25. package/lib/built/tool.d.ts.map +1 -1
  26. package/lib/built/tool.js.map +1 -1
  27. package/lib/feature.d.ts +16 -1
  28. package/lib/feature.d.ts.map +1 -1
  29. package/lib/feature.js +41 -2
  30. package/lib/feature.js.map +1 -1
  31. package/lib/index.d.ts +1 -0
  32. package/lib/index.d.ts.map +1 -1
  33. package/lib/index.js +2 -0
  34. package/lib/index.js.map +1 -1
  35. package/lib/plugin.d.ts +38 -1
  36. package/lib/plugin.d.ts.map +1 -1
  37. package/lib/plugin.js +73 -22
  38. package/lib/plugin.js.map +1 -1
  39. package/lib/scheduler/scheduler.js +1 -1
  40. package/lib/scheduler/scheduler.js.map +1 -1
  41. package/lib/types.d.ts +43 -28
  42. package/lib/types.d.ts.map +1 -1
  43. package/lib/utils.d.ts +12 -3
  44. package/lib/utils.d.ts.map +1 -1
  45. package/lib/utils.js +64 -54
  46. package/lib/utils.js.map +1 -1
  47. package/package.json +5 -5
  48. package/src/adapter.ts +85 -5
  49. package/src/ai/index.ts +8 -186
  50. package/src/ai/types.ts +5 -4
  51. package/src/built/ai-trigger.ts +2 -2
  52. package/src/built/common-adapter-tools.ts +207 -0
  53. package/src/built/dispatcher.ts +51 -52
  54. package/src/built/skill.ts +3 -4
  55. package/src/built/tool.ts +3 -3
  56. package/src/feature.ts +45 -2
  57. package/src/index.ts +2 -0
  58. package/src/plugin.ts +92 -31
  59. package/src/scheduler/scheduler.ts +1 -1
  60. package/src/types.ts +39 -28
  61. package/src/utils.ts +63 -52
  62. package/tests/ai/setup.ts +2 -2
  63. package/tests/utils.test.ts +1 -3
  64. package/lib/ai/agent.d.ts +0 -130
  65. package/lib/ai/agent.d.ts.map +0 -1
  66. package/lib/ai/agent.js +0 -702
  67. package/lib/ai/agent.js.map +0 -1
  68. package/lib/ai/bootstrap.d.ts +0 -91
  69. package/lib/ai/bootstrap.d.ts.map +0 -1
  70. package/lib/ai/bootstrap.js +0 -243
  71. package/lib/ai/bootstrap.js.map +0 -1
  72. package/lib/ai/builtin-tools.d.ts +0 -59
  73. package/lib/ai/builtin-tools.d.ts.map +0 -1
  74. package/lib/ai/builtin-tools.js +0 -777
  75. package/lib/ai/builtin-tools.js.map +0 -1
  76. package/lib/ai/compaction.d.ts +0 -132
  77. package/lib/ai/compaction.d.ts.map +0 -1
  78. package/lib/ai/compaction.js +0 -370
  79. package/lib/ai/compaction.js.map +0 -1
  80. package/lib/ai/context-manager.d.ts +0 -213
  81. package/lib/ai/context-manager.d.ts.map +0 -1
  82. package/lib/ai/context-manager.js +0 -313
  83. package/lib/ai/context-manager.js.map +0 -1
  84. package/lib/ai/conversation-memory.d.ts +0 -181
  85. package/lib/ai/conversation-memory.d.ts.map +0 -1
  86. package/lib/ai/conversation-memory.js +0 -581
  87. package/lib/ai/conversation-memory.js.map +0 -1
  88. package/lib/ai/cron-engine.d.ts +0 -92
  89. package/lib/ai/cron-engine.d.ts.map +0 -1
  90. package/lib/ai/cron-engine.js +0 -278
  91. package/lib/ai/cron-engine.js.map +0 -1
  92. package/lib/ai/follow-up.d.ts +0 -131
  93. package/lib/ai/follow-up.d.ts.map +0 -1
  94. package/lib/ai/follow-up.js +0 -265
  95. package/lib/ai/follow-up.js.map +0 -1
  96. package/lib/ai/hooks.d.ts +0 -143
  97. package/lib/ai/hooks.d.ts.map +0 -1
  98. package/lib/ai/hooks.js +0 -108
  99. package/lib/ai/hooks.js.map +0 -1
  100. package/lib/ai/init.d.ts +0 -30
  101. package/lib/ai/init.d.ts.map +0 -1
  102. package/lib/ai/init.js +0 -686
  103. package/lib/ai/init.js.map +0 -1
  104. package/lib/ai/output.d.ts +0 -93
  105. package/lib/ai/output.d.ts.map +0 -1
  106. package/lib/ai/output.js +0 -176
  107. package/lib/ai/output.js.map +0 -1
  108. package/lib/ai/rate-limiter.d.ts +0 -38
  109. package/lib/ai/rate-limiter.d.ts.map +0 -1
  110. package/lib/ai/rate-limiter.js +0 -86
  111. package/lib/ai/rate-limiter.js.map +0 -1
  112. package/lib/ai/service.d.ts +0 -88
  113. package/lib/ai/service.d.ts.map +0 -1
  114. package/lib/ai/service.js +0 -285
  115. package/lib/ai/service.js.map +0 -1
  116. package/lib/ai/session.d.ts +0 -186
  117. package/lib/ai/session.d.ts.map +0 -1
  118. package/lib/ai/session.js +0 -443
  119. package/lib/ai/session.js.map +0 -1
  120. package/lib/ai/subagent.d.ts +0 -50
  121. package/lib/ai/subagent.d.ts.map +0 -1
  122. package/lib/ai/subagent.js +0 -144
  123. package/lib/ai/subagent.js.map +0 -1
  124. package/lib/ai/tone-detector.d.ts +0 -19
  125. package/lib/ai/tone-detector.d.ts.map +0 -1
  126. package/lib/ai/tone-detector.js +0 -72
  127. package/lib/ai/tone-detector.js.map +0 -1
  128. package/lib/ai/tools.d.ts +0 -45
  129. package/lib/ai/tools.d.ts.map +0 -1
  130. package/lib/ai/tools.js +0 -206
  131. package/lib/ai/tools.js.map +0 -1
  132. package/lib/ai/user-profile.d.ts +0 -56
  133. package/lib/ai/user-profile.d.ts.map +0 -1
  134. package/lib/ai/user-profile.js +0 -130
  135. package/lib/ai/user-profile.js.map +0 -1
  136. package/lib/ai/zhin-agent/builtin-tools.d.ts +0 -17
  137. package/lib/ai/zhin-agent/builtin-tools.d.ts.map +0 -1
  138. package/lib/ai/zhin-agent/builtin-tools.js +0 -220
  139. package/lib/ai/zhin-agent/builtin-tools.js.map +0 -1
  140. package/lib/ai/zhin-agent/config.d.ts +0 -54
  141. package/lib/ai/zhin-agent/config.d.ts.map +0 -1
  142. package/lib/ai/zhin-agent/config.js +0 -76
  143. package/lib/ai/zhin-agent/config.js.map +0 -1
  144. package/lib/ai/zhin-agent/exec-policy.d.ts +0 -20
  145. package/lib/ai/zhin-agent/exec-policy.d.ts.map +0 -1
  146. package/lib/ai/zhin-agent/exec-policy.js +0 -71
  147. package/lib/ai/zhin-agent/exec-policy.js.map +0 -1
  148. package/lib/ai/zhin-agent/index.d.ts +0 -70
  149. package/lib/ai/zhin-agent/index.d.ts.map +0 -1
  150. package/lib/ai/zhin-agent/index.js +0 -404
  151. package/lib/ai/zhin-agent/index.js.map +0 -1
  152. package/lib/ai/zhin-agent/prompt.d.ts +0 -21
  153. package/lib/ai/zhin-agent/prompt.d.ts.map +0 -1
  154. package/lib/ai/zhin-agent/prompt.js +0 -111
  155. package/lib/ai/zhin-agent/prompt.js.map +0 -1
  156. package/lib/ai/zhin-agent/tool-collector.d.ts +0 -22
  157. package/lib/ai/zhin-agent/tool-collector.d.ts.map +0 -1
  158. package/lib/ai/zhin-agent/tool-collector.js +0 -218
  159. package/lib/ai/zhin-agent/tool-collector.js.map +0 -1
  160. package/src/ai/agent.ts +0 -831
  161. package/src/ai/bootstrap.ts +0 -309
  162. package/src/ai/builtin-tools.ts +0 -849
  163. package/src/ai/compaction.ts +0 -529
  164. package/src/ai/context-manager.ts +0 -440
  165. package/src/ai/conversation-memory.ts +0 -774
  166. package/src/ai/cron-engine.ts +0 -337
  167. package/src/ai/follow-up.ts +0 -357
  168. package/src/ai/hooks.ts +0 -223
  169. package/src/ai/init.ts +0 -762
  170. package/src/ai/output.ts +0 -261
  171. package/src/ai/rate-limiter.ts +0 -129
  172. package/src/ai/service.ts +0 -331
  173. package/src/ai/session.ts +0 -544
  174. package/src/ai/subagent.ts +0 -209
  175. package/src/ai/tone-detector.ts +0 -89
  176. package/src/ai/tools.ts +0 -218
  177. package/src/ai/user-profile.ts +0 -181
  178. package/src/ai/zhin-agent/builtin-tools.ts +0 -247
  179. package/src/ai/zhin-agent/config.ts +0 -113
  180. package/src/ai/zhin-agent/exec-policy.ts +0 -78
  181. package/src/ai/zhin-agent/index.ts +0 -512
  182. package/src/ai/zhin-agent/prompt.ts +0 -131
  183. package/src/ai/zhin-agent/tool-collector.ts +0 -243
  184. package/tests/ai/agent.test.ts +0 -614
  185. package/tests/ai/context-manager.test.ts +0 -413
  186. package/tests/ai/conversation-memory.test.ts +0 -128
  187. package/tests/ai/follow-up.test.ts +0 -175
  188. package/tests/ai/integration.test.ts +0 -584
  189. package/tests/ai/output.test.ts +0 -128
  190. package/tests/ai/rate-limiter.test.ts +0 -108
  191. package/tests/ai/session.test.ts +0 -375
  192. package/tests/ai/subagent.test.ts +0 -270
  193. package/tests/ai/tone-detector.test.ts +0 -80
  194. package/tests/ai/tools-builtin.test.ts +0 -346
  195. package/tests/ai/user-profile.test.ts +0 -73
  196. package/tests/ai/zhin-agent.test.ts +0 -177
@@ -149,72 +149,73 @@ declare module '../plugin.js' {
149
149
  * 创建 MessageDispatcher Context
150
150
  */
151
151
  export function createMessageDispatcher(): Context<'dispatcher', DispatcherContextExtensions> {
152
- // 护栏中间件列表
153
152
  const guardrails: GuardrailMiddleware[] = [];
154
-
155
- // AI 处理函数(由 AI 模块注册)
156
153
  let aiHandler: AIHandler | null = null;
157
-
158
- // AI 触发判定器(由 AI 模块注册)
159
154
  let aiTriggerMatcher: AITriggerMatcher | null = null;
160
-
161
- // 命令匹配器(默认由 command service 驱动)
162
155
  let commandMatcher: CommandMatcher | null = null;
163
-
164
- // 根插件引用(mounted 时设置)
165
156
  let rootPlugin: Plugin | null = null;
166
157
 
158
+ // Command prefix index — rebuilt lazily for O(1) lookup
159
+ let commandPrefixIndex: Map<string, boolean> | null = null;
160
+ let lastCommandCount = -1;
161
+
162
+ function rebuildCommandIndex(): Map<string, boolean> {
163
+ const index = new Map<string, boolean>();
164
+ if (rootPlugin) {
165
+ const commandService = rootPlugin.inject('command');
166
+ if (commandService?.items) {
167
+ for (const cmd of commandService.items) {
168
+ const prefix = cmd.pattern?.split(/\s/)[0];
169
+ if (prefix) index.set(prefix, true);
170
+ }
171
+ }
172
+ }
173
+ return index;
174
+ }
175
+
176
+ function getCommandIndex(): Map<string, boolean> {
177
+ const commandService = rootPlugin?.inject('command');
178
+ const currentCount = commandService?.items?.length ?? 0;
179
+ if (!commandPrefixIndex || currentCount !== lastCommandCount) {
180
+ commandPrefixIndex = rebuildCommandIndex();
181
+ lastCommandCount = currentCount;
182
+ }
183
+ return commandPrefixIndex;
184
+ }
185
+
167
186
  /**
168
- * 执行 Guardrail 管道
169
- * 返回 true 表示消息通过,false 表示被拦截
187
+ * Guardrail pipeline — a guardrail that does NOT call next() blocks the message.
170
188
  */
171
189
  async function runGuardrails(message: Message<any>): Promise<boolean> {
172
190
  if (guardrails.length === 0) return true;
173
191
 
174
- let passed = true;
175
- let index = 0;
176
-
177
- const runNext = async (): Promise<void> => {
178
- if (index >= guardrails.length) return;
179
- const guardrail = guardrails[index++];
192
+ for (const guardrail of guardrails) {
193
+ let nextCalled = false;
180
194
  try {
181
- await guardrail(message, runNext);
195
+ await guardrail(message, async () => { nextCalled = true; });
182
196
  } catch {
183
- // Guardrail 抛异常 → 拦截消息
184
- passed = false;
197
+ return false;
185
198
  }
186
- };
187
-
188
- await runNext();
189
- return passed;
199
+ if (!nextCalled) return false;
200
+ }
201
+ return true;
190
202
  }
191
203
 
192
- /**
193
- * 路由判定:这条消息该走哪条路径?
194
- */
195
204
  function route(message: Message<any>): RouteResult {
196
- // 提取文本内容
197
205
  const text = extractText(message);
198
206
 
199
- // 1. 如果有自定义命令匹配器,优先使用
200
207
  if (commandMatcher && commandMatcher(text, message)) {
201
208
  return { type: 'command' };
202
209
  }
203
210
 
204
- // 2. 默认命令匹配:检查 commandService
205
- if (rootPlugin) {
206
- const commandService = rootPlugin.inject('command');
207
- if (commandService?.items) {
208
- for (const cmd of commandService.items) {
209
- const pattern = cmd.pattern?.split(/\s/)[0];
210
- if (pattern && text.startsWith(pattern)) {
211
- return { type: 'command' };
212
- }
213
- }
211
+ // Use indexed lookup instead of O(N) scan
212
+ const index = getCommandIndex();
213
+ for (const [prefix] of index) {
214
+ if (text.startsWith(prefix)) {
215
+ return { type: 'command' };
214
216
  }
215
217
  }
216
218
 
217
- // 3. AI 触发判定
218
219
  if (aiTriggerMatcher) {
219
220
  const { triggered, content } = aiTriggerMatcher(message);
220
221
  if (triggered) {
@@ -222,13 +223,9 @@ export function createMessageDispatcher(): Context<'dispatcher', DispatcherConte
222
223
  }
223
224
  }
224
225
 
225
- // 4. 都不匹配 → 跳过
226
226
  return { type: 'skip' };
227
227
  }
228
228
 
229
- /**
230
- * 提取消息文本(简单实现,与 ai-trigger 中的 extractTextContent 对齐)
231
- */
232
229
  function extractText(message: Message<any>): string {
233
230
  if (!message.$content) return '';
234
231
  return message.$content
@@ -241,8 +238,6 @@ export function createMessageDispatcher(): Context<'dispatcher', DispatcherConte
241
238
  .trim();
242
239
  }
243
240
 
244
- // ─── 服务实例 ───────────────────────────────────────────────────────────
245
-
246
241
  const service: MessageDispatcherService = {
247
242
  async dispatch(message: Message<any>) {
248
243
  // Stage 1: Guardrail
@@ -255,7 +250,6 @@ export function createMessageDispatcher(): Context<'dispatcher', DispatcherConte
255
250
  // Stage 3: Handle
256
251
  switch (result.type) {
257
252
  case 'command': {
258
- // 命令快速路径 — 直接委托给 commandService
259
253
  if (rootPlugin) {
260
254
  const commandService = rootPlugin.inject('command');
261
255
  if (commandService) {
@@ -269,7 +263,6 @@ export function createMessageDispatcher(): Context<'dispatcher', DispatcherConte
269
263
  }
270
264
 
271
265
  case 'ai': {
272
- // AI Agent 路径
273
266
  if (aiHandler) {
274
267
  await aiHandler(message, result.content);
275
268
  }
@@ -278,13 +271,19 @@ export function createMessageDispatcher(): Context<'dispatcher', DispatcherConte
278
271
 
279
272
  case 'skip':
280
273
  default:
281
- // 不处理
282
274
  break;
283
275
  }
284
276
 
285
- // 兼容:仍然执行旧的中间件链(除了默认的命令中间件和 AI 触发中间件)
286
- // 这样既有 Guardrail/Dispatcher 新逻辑,又不会丢失已有的自定义中间件
287
- // Phase 1 阶段先保持兼容,后续逐步迁移
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
+ }
286
+ }
288
287
  },
289
288
 
290
289
  addGuardrail(guardrail: GuardrailMiddleware) {
@@ -232,21 +232,20 @@ export class SkillFeature extends Feature<Skill> {
232
232
  const pluginName = plugin.name;
233
233
 
234
234
  // 收集该插件注册的工具
235
- const toolService = plugin.root.inject('tool' as any) as any;
235
+ const toolService = plugin.root.inject('tool') as { getToolsByPlugin?: (name: string) => Tool[] } | undefined;
236
236
  let tools: Tool[] = [];
237
237
 
238
238
  if (toolService && typeof toolService.getToolsByPlugin === 'function') {
239
239
  tools = toolService.getToolsByPlugin(pluginName);
240
240
  } else {
241
- // 回退:从插件本地工具获取
242
241
  tools = plugin.getAllTools?.() || [];
243
242
  }
244
243
 
245
244
  // 聚合关键词:开发者声明 + 工具自带
246
245
  const allKeywords = new Set<string>(metadata.keywords || []);
247
246
  for (const tool of tools) {
248
- if ((tool as any).keywords) {
249
- for (const kw of (tool as any).keywords) {
247
+ if (tool.keywords) {
248
+ for (const kw of tool.keywords) {
250
249
  allKeywords.add(kw);
251
250
  }
252
251
  }
package/src/built/tool.ts CHANGED
@@ -6,7 +6,7 @@ import { Feature, FeatureJSON } from "../feature.js";
6
6
  import { MessageCommand } from "../command.js";
7
7
  import { Message } from "../message.js";
8
8
  import { Plugin, getPlugin } from "../plugin.js";
9
- import type { Tool, ToolDefinition, RegisteredAdapter, AdapterMessage, ToolContext, ToolJsonSchema, ToolParametersSchema, PropertySchema, MaybePromise, ToolPermissionLevel, ToolScope } from "../types.js";
9
+ import type { Tool, RegisteredAdapter, AdapterMessage, ToolContext, ToolJsonSchema, ToolParametersSchema, PropertySchema, MaybePromise, ToolPermissionLevel, ToolScope } from "../types.js";
10
10
  import { MatchResult } from "segment-matcher";
11
11
 
12
12
  // ============================================================================
@@ -147,7 +147,7 @@ export function extractParamInfo(parameters: ToolJsonSchema): Tool.ParamInfo[] {
147
147
  * 定义工具的辅助函数(提供类型推断)
148
148
  */
149
149
  export function defineTool<TArgs extends Record<string, any> = Record<string, any>>(
150
- tool: ToolDefinition<TArgs>
150
+ tool: Tool<TArgs>
151
151
  ): Tool {
152
152
  return tool as Tool;
153
153
  }
@@ -587,7 +587,7 @@ function commandToToolFn(
587
587
 
588
588
  const plugin = getPlugin();
589
589
  const result = await command.handle(tempMessage, plugin);
590
- return result;
590
+ return result as import("../types.js").ToolResult;
591
591
  }
592
592
 
593
593
  return {
package/src/feature.ts CHANGED
@@ -16,6 +16,11 @@ export interface FeatureJSON {
16
16
  items: any[];
17
17
  }
18
18
 
19
+ /**
20
+ * Feature 变更事件监听器类型
21
+ */
22
+ export type FeatureListener<T> = (item: T, pluginName: string) => void;
23
+
19
24
  /**
20
25
  * Feature<T> 抽象类
21
26
  * - name / icon / desc: 自描述元数据
@@ -24,6 +29,7 @@ export interface FeatureJSON {
24
29
  * - toJSON: 控制 HTTP API 返回的数据格式
25
30
  * - extensions: 提供给 Plugin.prototype 的扩展方法(如 addCommand)
26
31
  * - mounted / dispose: 可选生命周期钩子
32
+ * - on('add' | 'remove'): 变更事件通知
27
33
  */
28
34
  export abstract class Feature<T = any> {
29
35
  abstract readonly name: string;
@@ -36,6 +42,32 @@ export abstract class Feature<T = any> {
36
42
  /** 按插件名分组的 item 列表 */
37
43
  protected pluginItems = new Map<string, T[]>();
38
44
 
45
+ /** 事件监听器 */
46
+ #listeners = new Map<string, Set<FeatureListener<T>>>();
47
+
48
+ /**
49
+ * 监听变更事件
50
+ * @param event 'add' | 'remove'
51
+ * @param listener 回调函数
52
+ * @returns 取消监听的函数
53
+ */
54
+ on(event: 'add' | 'remove', listener: FeatureListener<T>): () => void {
55
+ if (!this.#listeners.has(event)) {
56
+ this.#listeners.set(event, new Set());
57
+ }
58
+ this.#listeners.get(event)!.add(listener);
59
+ return () => { this.#listeners.get(event)?.delete(listener); };
60
+ }
61
+
62
+ /** 触发事件 */
63
+ protected emit(event: 'add' | 'remove', item: T, pluginName: string): void {
64
+ const listeners = this.#listeners.get(event);
65
+ if (!listeners) return;
66
+ for (const listener of listeners) {
67
+ try { listener(item, pluginName); } catch { /* 防止监听器异常影响主流程 */ }
68
+ }
69
+ }
70
+
39
71
  /**
40
72
  * 添加 item,同时记录所属插件
41
73
  * @returns dispose 函数,用于移除该 item
@@ -46,25 +78,36 @@ export abstract class Feature<T = any> {
46
78
  this.pluginItems.set(pluginName, []);
47
79
  }
48
80
  this.pluginItems.get(pluginName)!.push(item);
49
- return () => this.remove(item);
81
+ this.emit('add', item, pluginName);
82
+ return () => this.remove(item, pluginName);
50
83
  }
51
84
 
52
85
  /**
53
86
  * 移除 item
54
87
  */
55
- remove(item: T): boolean {
88
+ remove(item: T, pluginName?: string): boolean {
56
89
  const idx = this.items.indexOf(item);
57
90
  if (idx !== -1) {
58
91
  this.items.splice(idx, 1);
92
+ const resolvedPluginName = pluginName ?? this.#findPluginName(item);
59
93
  for (const [, items] of this.pluginItems) {
60
94
  const i = items.indexOf(item);
61
95
  if (i !== -1) items.splice(i, 1);
62
96
  }
97
+ this.emit('remove', item, resolvedPluginName ?? '');
63
98
  return true;
64
99
  }
65
100
  return false;
66
101
  }
67
102
 
103
+ /** 反查 item 所属的 pluginName */
104
+ #findPluginName(item: T): string | undefined {
105
+ for (const [name, items] of this.pluginItems) {
106
+ if (items.includes(item)) return name;
107
+ }
108
+ return undefined;
109
+ }
110
+
68
111
  /**
69
112
  * 获取指定插件注册的 item 列表
70
113
  */
package/src/index.ts CHANGED
@@ -26,6 +26,8 @@ export * from './built/ai-trigger.js'
26
26
  export * from './built/dispatcher.js'
27
27
  // Skill 系统 (AI 能力描述)
28
28
  export * from './built/skill.js'
29
+ // Common adapter tool factories (shared across adapters)
30
+ export * from './built/common-adapter-tools.js'
29
31
  // AI 模块 (原 @zhin.js/ai,已合并至 core)
30
32
  export * from './ai/index.js'
31
33
 
package/src/plugin.ts CHANGED
@@ -21,7 +21,8 @@ import { Adapter, Adapters } from "./adapter.js";
21
21
  import { Feature, FeatureJSON } from "./feature.js";
22
22
  import { createHash } from "crypto";
23
23
  const contextsKey = Symbol("contexts");
24
- const loadedModules = new Map<string, Plugin>(); // 记录已加载的模块
24
+ const extensionOwnersKey = Symbol("extensionOwners");
25
+ const loadedModules = new Map<string, Plugin>();
25
26
  const require = createRequire(import.meta.url);
26
27
 
27
28
 
@@ -138,8 +139,11 @@ export interface Plugin extends Plugin.Extensions { }
138
139
  */
139
140
  export class Plugin extends EventEmitter<Plugin.Lifecycle> {
140
141
  static [contextsKey] = [] as string[];
142
+ /** Maps extension method name → context name that owns it */
143
+ static [extensionOwnersKey] = new Map<string, string>();
141
144
 
142
145
  #cachedName?: string;
146
+ #explicitName?: string;
143
147
  adapters: (keyof Plugin.Contexts)[] = [];
144
148
  started = false;
145
149
 
@@ -182,6 +186,14 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
182
186
  get middleware(): MessageMiddleware<RegisteredAdapter> {
183
187
  return compose<RegisteredAdapter>(this.#middlewares);
184
188
  }
189
+
190
+ /**
191
+ * Returns custom middlewares (excluding the built-in command middleware).
192
+ * Used by MessageDispatcher to run legacy middlewares after dispatch.
193
+ */
194
+ _getCustomMiddlewares(): MessageMiddleware<RegisteredAdapter>[] {
195
+ return this.#middlewares.filter(m => m !== this.#messageMiddleware);
196
+ }
185
197
  /**
186
198
  * 构造函数
187
199
  */
@@ -213,8 +225,10 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
213
225
  * @param middleware 中间件函数
214
226
  */
215
227
  addMiddleware<T extends RegisteredAdapter>(middleware: MessageMiddleware<T>): () => void {
216
- if(this.parent){
217
- const dispose= this.parent.addMiddleware(middleware);
228
+ // Always register on root so middlewares reach the global chain
229
+ const target = this.root;
230
+ if (target !== this) {
231
+ const dispose = target.addMiddleware(middleware);
218
232
  this.#disposables.add(dispose);
219
233
  return () => {
220
234
  dispose();
@@ -241,8 +255,7 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
241
255
  tool: Tool,
242
256
  generateCommand: boolean = true
243
257
  ): () => void {
244
- // 尝试使用 ToolFeature
245
- const toolService = this.root.inject('tool' as any) as any;
258
+ const toolService = this.root.inject('tool') as { addTool?: (tool: Tool, name: string, gen: boolean) => () => void } | undefined;
246
259
  if (toolService && typeof toolService.addTool === 'function') {
247
260
  const dispose = toolService.addTool(tool, this.name, generateCommand);
248
261
  this.#disposables.add(dispose);
@@ -305,8 +318,7 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
305
318
  * 优先使用 ToolService,否则回退到本地收集
306
319
  */
307
320
  collectAllTools(): Tool[] {
308
- // 尝试使用 ToolService
309
- const toolService = this.root.inject('tool' as any) as any;
321
+ const toolService = this.root.inject('tool') as { collectAll?: (root: Plugin) => Tool[] } | undefined;
310
322
  if (toolService && typeof toolService.collectAll === 'function') {
311
323
  return toolService.collectAll(this.root);
312
324
  }
@@ -331,9 +343,22 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
331
343
  }
332
344
 
333
345
  /**
334
- * 插件名称
346
+ * 显式设置插件名称(优先级高于路径推导)。
347
+ * 可通过以下方式声明:
348
+ * 1. 模块导出 `export const pluginName = 'my-plugin'`
349
+ * 2. 调用 `plugin.setName('my-plugin')`
350
+ */
351
+ setName(name: string): void {
352
+ this.#explicitName = name;
353
+ this.#cachedName = name;
354
+ this.logger = logger.getLogger(name);
355
+ }
356
+
357
+ /**
358
+ * 插件名称(显式名称 > 路径推导)
335
359
  */
336
360
  get name(): string {
361
+ if (this.#explicitName) return this.#explicitName;
337
362
  if (this.#cachedName) return this.#cachedName;
338
363
 
339
364
  let name = path
@@ -394,16 +419,15 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
394
419
  if (!dispose) return;
395
420
  const disposeFn = async (name: keyof Plugin.Contexts) => {
396
421
  if (contexts.includes(name)) {
397
- await dispose(this.inject(name) as any)
422
+ await dispose(this.inject(name) as ArrayItem<ContextList<T>>)
398
423
  }
399
424
  this.off('context.dispose', disposeFn)
400
425
  sideEffect.finished = false;
401
426
  }
402
427
  this.on('context.dispose', disposeFn)
403
- // 确保 dispose 时清理监听器(只注册一次)
404
428
  const cleanupOnDispose = () => {
405
429
  this.off('context.dispose', disposeFn)
406
- dispose(this.inject(args[0] as any) as any)
430
+ dispose(this.inject(args[0] as unknown as keyof Plugin.Contexts) as unknown as ArrayItem<ContextList<T>>)
407
431
  }
408
432
  this.once('dispose', cleanupOnDispose)
409
433
  }
@@ -417,9 +441,11 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
417
441
  if (!this.#contextsIsReady(contexts)) return
418
442
  contextReadyCallback()
419
443
  }
420
- inject<T extends keyof Plugin.Contexts>(name: T): Plugin.Contexts[T] | undefined {
421
- const context = this.root.contexts.get(name as string);
422
- return context?.value as Plugin.Contexts[T];
444
+ inject<T extends keyof Plugin.Contexts>(name: T): Plugin.Contexts[T] | undefined;
445
+ inject(name: string): unknown;
446
+ inject(name: string): unknown {
447
+ const context = this.root.contexts.get(name);
448
+ return context?.value;
423
449
  }
424
450
  #contextsIsReady<CS extends (keyof Plugin.Contexts)[]>(contexts: CS) {
425
451
  if (!contexts.length) return true
@@ -457,13 +483,7 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
457
483
  for (const child of this.children) {
458
484
  await child.start(t);
459
485
  }
460
- // 输出启动日志(使用 debug 级别,避免重复输出)
461
- // 只在根插件或重要插件时使用 info 级别
462
- if (!this.parent || this.name === 'setup') {
463
- this.logger.info(`Plugin "${this.name}" ${t ? `reloaded in ${Date.now() - t}ms` : "started"}`);
464
- } else {
465
- this.logger.debug(`Plugin "${this.name}" ${t ? `reloaded in ${Date.now() - t}ms` : "started"}`);
466
- }
486
+ this.logger.debug(`Plugin "${this.name}" ${t ? `reloaded in ${Date.now() - t}ms` : "started"}`);
467
487
  }
468
488
  /**
469
489
  * 记录 Feature 贡献(由 Feature extensions 内部调用)
@@ -636,13 +656,15 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
636
656
  }
637
657
  this.children = [];
638
658
 
639
- // 停止服务
659
+ // 停止服务 — only remove extensions owned by this plugin's contexts
640
660
  for (const [name, context] of this.$contexts) {
641
661
  remove(Plugin[contextsKey], name);
642
- // 移除扩展方法
643
662
  if (context.extensions) {
644
663
  for (const key of Object.keys(context.extensions)) {
645
- delete (Plugin.prototype as any)[key];
664
+ if (Plugin[extensionOwnersKey].get(key) === name) {
665
+ delete (Plugin.prototype as unknown as Record<string, unknown>)[key];
666
+ Plugin[extensionOwnersKey].delete(key);
667
+ }
646
668
  }
647
669
  }
648
670
  if (typeof context.dispose === "function") {
@@ -750,16 +772,15 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
750
772
  */
751
773
  provide<T extends keyof Plugin.Contexts>(target: Feature | Context<T>): this {
752
774
  if (target instanceof Feature) {
753
- // Feature → 自动转为内部 Context 格式存储
754
775
  const feature = target;
755
776
  const context: Context<T> = {
756
777
  name: feature.name as T,
757
778
  description: feature.desc,
758
- value: feature as any,
779
+ value: feature as Plugin.Contexts[T],
759
780
  mounted: feature.mounted
760
781
  ? async (plugin: Plugin) => {
761
782
  await feature.mounted!(plugin);
762
- return feature as any;
783
+ return feature as Plugin.Contexts[T];
763
784
  }
764
785
  : undefined,
765
786
  dispose: feature.dispose
@@ -770,17 +791,24 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
770
791
  return this.provide(context);
771
792
  }
772
793
 
773
- // 传统 Context 路径
774
794
  const context = target;
775
795
  if (!Plugin[contextsKey].includes(context.name as string)) {
776
796
  Plugin[contextsKey].push(context.name as string);
777
797
  }
778
798
  this.logger.debug(`Context "${context.name as string}" provided`);
779
- // 注册扩展方法到 Plugin.prototype
799
+
800
+ // Track which extension methods this context registers.
801
+ // On collision, log a warning but allow override (last-write-wins).
780
802
  if (context.extensions) {
803
+ const extNames: string[] = [];
781
804
  for (const [name, fn] of Object.entries(context.extensions)) {
782
805
  if (typeof fn === 'function') {
806
+ if (Reflect.has(Plugin.prototype, name) && !Plugin[extensionOwnersKey].has(name)) {
807
+ this.logger.warn(`Extension method "${name}" shadows an existing Plugin method`);
808
+ }
783
809
  Reflect.set(Plugin.prototype, name, fn);
810
+ Plugin[extensionOwnersKey].set(name, context.name as string);
811
+ extNames.push(name);
784
812
  }
785
813
  }
786
814
  }
@@ -894,7 +922,7 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
894
922
  for (const key of Plugin.#coreMethods) {
895
923
  const value = proto[key];
896
924
  if (typeof value === "function") {
897
- (this as any)[key] = value.bind(this);
925
+ (this as Record<string, unknown>)[key] = value.bind(this);
898
926
  }
899
927
  }
900
928
  }
@@ -926,16 +954,49 @@ export class Plugin extends EventEmitter<Plugin.Lifecycle> {
926
954
  // 先记录,防止循环依赖时重复加载
927
955
  loadedModules.set(realPath, plugin);
928
956
 
957
+ let mod: any;
929
958
  await storage.run(plugin, async () => {
930
- await import(`${pathToFileURL(entryFile).href}?t=${Date.now()}`);
959
+ mod = await import(`${pathToFileURL(entryFile).href}?t=${Date.now()}`);
931
960
  });
932
961
 
962
+ // 支持模块显式声明插件名称:export const pluginName = 'my-plugin'
963
+ if (mod?.pluginName && typeof mod.pluginName === 'string') {
964
+ plugin.setName(mod.pluginName);
965
+ }
966
+
933
967
  return plugin;
934
968
  }
935
969
  }
936
970
  export function defineContext<T extends keyof Plugin.Contexts, E extends Partial<Plugin.Extensions> = {}>(options: Context<T, E>): Context<T, E> {
937
971
  return options;
938
972
  }
973
+
974
+ /**
975
+ * 声明式定义插件。
976
+ * 提供显式名称 + setup 函数,自动设置当前插件名称并执行 setup。
977
+ *
978
+ * @example
979
+ * ```typescript
980
+ * // plugins/weather/index.ts
981
+ * export default definePlugin({
982
+ * name: 'weather',
983
+ * setup(plugin) {
984
+ * plugin.addTool({ ... });
985
+ * },
986
+ * });
987
+ * ```
988
+ */
989
+ export function definePlugin(options: {
990
+ name: string;
991
+ setup: (plugin: Plugin) => MaybePromise<void>;
992
+ }): { pluginName: string } {
993
+ const plugin = usePlugin();
994
+ if (options.name) {
995
+ plugin.setName(options.name);
996
+ }
997
+ options.setup(plugin);
998
+ return { pluginName: options.name };
999
+ }
939
1000
  export interface Context<T extends keyof Plugin.Contexts = keyof Plugin.Contexts, E extends Partial<Plugin.Extensions> = {}> {
940
1001
  name: T;
941
1002
  description: string
@@ -200,7 +200,7 @@ export class Scheduler implements IScheduler {
200
200
  };
201
201
  this.heartbeatJobId = job.id;
202
202
  this.store.jobs.push(job);
203
- logger.info({ intervalMs: this.heartbeatIntervalMs }, 'Heartbeat job added');
203
+ logger.debug({ intervalMs: this.heartbeatIntervalMs }, 'Heartbeat job added');
204
204
  }
205
205
 
206
206
  private recomputeNextRuns(): void {