@zhin.js/core 1.0.37 → 1.0.39

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 (204) hide show
  1. package/CHANGELOG.md +18 -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/providers/anthropic.d.ts.map +1 -1
  12. package/lib/ai/providers/anthropic.js +2 -0
  13. package/lib/ai/providers/anthropic.js.map +1 -1
  14. package/lib/ai/providers/openai.d.ts.map +1 -1
  15. package/lib/ai/providers/openai.js +8 -0
  16. package/lib/ai/providers/openai.js.map +1 -1
  17. package/lib/ai/types.d.ts +5 -3
  18. package/lib/ai/types.d.ts.map +1 -1
  19. package/lib/built/ai-trigger.js.map +1 -1
  20. package/lib/built/common-adapter-tools.d.ts +55 -0
  21. package/lib/built/common-adapter-tools.d.ts.map +1 -0
  22. package/lib/built/common-adapter-tools.js +158 -0
  23. package/lib/built/common-adapter-tools.js.map +1 -0
  24. package/lib/built/dispatcher.d.ts.map +1 -1
  25. package/lib/built/dispatcher.js +50 -46
  26. package/lib/built/dispatcher.js.map +1 -1
  27. package/lib/built/skill.d.ts.map +1 -1
  28. package/lib/built/skill.js +0 -1
  29. package/lib/built/skill.js.map +1 -1
  30. package/lib/built/tool.d.ts +3 -3
  31. package/lib/built/tool.d.ts.map +1 -1
  32. package/lib/built/tool.js.map +1 -1
  33. package/lib/feature.d.ts +16 -1
  34. package/lib/feature.d.ts.map +1 -1
  35. package/lib/feature.js +41 -2
  36. package/lib/feature.js.map +1 -1
  37. package/lib/index.d.ts +1 -0
  38. package/lib/index.d.ts.map +1 -1
  39. package/lib/index.js +2 -0
  40. package/lib/index.js.map +1 -1
  41. package/lib/plugin.d.ts +38 -1
  42. package/lib/plugin.d.ts.map +1 -1
  43. package/lib/plugin.js +73 -22
  44. package/lib/plugin.js.map +1 -1
  45. package/lib/scheduler/scheduler.js +1 -1
  46. package/lib/scheduler/scheduler.js.map +1 -1
  47. package/lib/types.d.ts +43 -28
  48. package/lib/types.d.ts.map +1 -1
  49. package/lib/utils.d.ts +12 -3
  50. package/lib/utils.d.ts.map +1 -1
  51. package/lib/utils.js +64 -54
  52. package/lib/utils.js.map +1 -1
  53. package/package.json +5 -5
  54. package/src/adapter.ts +85 -5
  55. package/src/ai/index.ts +8 -186
  56. package/src/ai/providers/anthropic.ts +1 -0
  57. package/src/ai/providers/openai.ts +5 -1
  58. package/src/ai/types.ts +6 -4
  59. package/src/built/ai-trigger.ts +2 -2
  60. package/src/built/common-adapter-tools.ts +207 -0
  61. package/src/built/dispatcher.ts +51 -52
  62. package/src/built/skill.ts +3 -4
  63. package/src/built/tool.ts +3 -3
  64. package/src/feature.ts +45 -2
  65. package/src/index.ts +2 -0
  66. package/src/plugin.ts +92 -31
  67. package/src/scheduler/scheduler.ts +1 -1
  68. package/src/types.ts +39 -28
  69. package/src/utils.ts +63 -52
  70. package/tests/ai/setup.ts +2 -2
  71. package/tests/utils.test.ts +1 -3
  72. package/lib/ai/agent.d.ts +0 -130
  73. package/lib/ai/agent.d.ts.map +0 -1
  74. package/lib/ai/agent.js +0 -702
  75. package/lib/ai/agent.js.map +0 -1
  76. package/lib/ai/bootstrap.d.ts +0 -91
  77. package/lib/ai/bootstrap.d.ts.map +0 -1
  78. package/lib/ai/bootstrap.js +0 -243
  79. package/lib/ai/bootstrap.js.map +0 -1
  80. package/lib/ai/builtin-tools.d.ts +0 -59
  81. package/lib/ai/builtin-tools.d.ts.map +0 -1
  82. package/lib/ai/builtin-tools.js +0 -777
  83. package/lib/ai/builtin-tools.js.map +0 -1
  84. package/lib/ai/compaction.d.ts +0 -132
  85. package/lib/ai/compaction.d.ts.map +0 -1
  86. package/lib/ai/compaction.js +0 -370
  87. package/lib/ai/compaction.js.map +0 -1
  88. package/lib/ai/context-manager.d.ts +0 -213
  89. package/lib/ai/context-manager.d.ts.map +0 -1
  90. package/lib/ai/context-manager.js +0 -313
  91. package/lib/ai/context-manager.js.map +0 -1
  92. package/lib/ai/conversation-memory.d.ts +0 -181
  93. package/lib/ai/conversation-memory.d.ts.map +0 -1
  94. package/lib/ai/conversation-memory.js +0 -581
  95. package/lib/ai/conversation-memory.js.map +0 -1
  96. package/lib/ai/cron-engine.d.ts +0 -92
  97. package/lib/ai/cron-engine.d.ts.map +0 -1
  98. package/lib/ai/cron-engine.js +0 -278
  99. package/lib/ai/cron-engine.js.map +0 -1
  100. package/lib/ai/follow-up.d.ts +0 -131
  101. package/lib/ai/follow-up.d.ts.map +0 -1
  102. package/lib/ai/follow-up.js +0 -265
  103. package/lib/ai/follow-up.js.map +0 -1
  104. package/lib/ai/hooks.d.ts +0 -143
  105. package/lib/ai/hooks.d.ts.map +0 -1
  106. package/lib/ai/hooks.js +0 -108
  107. package/lib/ai/hooks.js.map +0 -1
  108. package/lib/ai/init.d.ts +0 -30
  109. package/lib/ai/init.d.ts.map +0 -1
  110. package/lib/ai/init.js +0 -686
  111. package/lib/ai/init.js.map +0 -1
  112. package/lib/ai/output.d.ts +0 -93
  113. package/lib/ai/output.d.ts.map +0 -1
  114. package/lib/ai/output.js +0 -176
  115. package/lib/ai/output.js.map +0 -1
  116. package/lib/ai/rate-limiter.d.ts +0 -38
  117. package/lib/ai/rate-limiter.d.ts.map +0 -1
  118. package/lib/ai/rate-limiter.js +0 -86
  119. package/lib/ai/rate-limiter.js.map +0 -1
  120. package/lib/ai/service.d.ts +0 -88
  121. package/lib/ai/service.d.ts.map +0 -1
  122. package/lib/ai/service.js +0 -285
  123. package/lib/ai/service.js.map +0 -1
  124. package/lib/ai/session.d.ts +0 -186
  125. package/lib/ai/session.d.ts.map +0 -1
  126. package/lib/ai/session.js +0 -443
  127. package/lib/ai/session.js.map +0 -1
  128. package/lib/ai/subagent.d.ts +0 -50
  129. package/lib/ai/subagent.d.ts.map +0 -1
  130. package/lib/ai/subagent.js +0 -144
  131. package/lib/ai/subagent.js.map +0 -1
  132. package/lib/ai/tone-detector.d.ts +0 -19
  133. package/lib/ai/tone-detector.d.ts.map +0 -1
  134. package/lib/ai/tone-detector.js +0 -72
  135. package/lib/ai/tone-detector.js.map +0 -1
  136. package/lib/ai/tools.d.ts +0 -45
  137. package/lib/ai/tools.d.ts.map +0 -1
  138. package/lib/ai/tools.js +0 -206
  139. package/lib/ai/tools.js.map +0 -1
  140. package/lib/ai/user-profile.d.ts +0 -56
  141. package/lib/ai/user-profile.d.ts.map +0 -1
  142. package/lib/ai/user-profile.js +0 -130
  143. package/lib/ai/user-profile.js.map +0 -1
  144. package/lib/ai/zhin-agent/builtin-tools.d.ts +0 -17
  145. package/lib/ai/zhin-agent/builtin-tools.d.ts.map +0 -1
  146. package/lib/ai/zhin-agent/builtin-tools.js +0 -220
  147. package/lib/ai/zhin-agent/builtin-tools.js.map +0 -1
  148. package/lib/ai/zhin-agent/config.d.ts +0 -54
  149. package/lib/ai/zhin-agent/config.d.ts.map +0 -1
  150. package/lib/ai/zhin-agent/config.js +0 -76
  151. package/lib/ai/zhin-agent/config.js.map +0 -1
  152. package/lib/ai/zhin-agent/exec-policy.d.ts +0 -20
  153. package/lib/ai/zhin-agent/exec-policy.d.ts.map +0 -1
  154. package/lib/ai/zhin-agent/exec-policy.js +0 -71
  155. package/lib/ai/zhin-agent/exec-policy.js.map +0 -1
  156. package/lib/ai/zhin-agent/index.d.ts +0 -70
  157. package/lib/ai/zhin-agent/index.d.ts.map +0 -1
  158. package/lib/ai/zhin-agent/index.js +0 -404
  159. package/lib/ai/zhin-agent/index.js.map +0 -1
  160. package/lib/ai/zhin-agent/prompt.d.ts +0 -21
  161. package/lib/ai/zhin-agent/prompt.d.ts.map +0 -1
  162. package/lib/ai/zhin-agent/prompt.js +0 -111
  163. package/lib/ai/zhin-agent/prompt.js.map +0 -1
  164. package/lib/ai/zhin-agent/tool-collector.d.ts +0 -22
  165. package/lib/ai/zhin-agent/tool-collector.d.ts.map +0 -1
  166. package/lib/ai/zhin-agent/tool-collector.js +0 -218
  167. package/lib/ai/zhin-agent/tool-collector.js.map +0 -1
  168. package/src/ai/agent.ts +0 -831
  169. package/src/ai/bootstrap.ts +0 -309
  170. package/src/ai/builtin-tools.ts +0 -849
  171. package/src/ai/compaction.ts +0 -529
  172. package/src/ai/context-manager.ts +0 -440
  173. package/src/ai/conversation-memory.ts +0 -774
  174. package/src/ai/cron-engine.ts +0 -337
  175. package/src/ai/follow-up.ts +0 -357
  176. package/src/ai/hooks.ts +0 -223
  177. package/src/ai/init.ts +0 -762
  178. package/src/ai/output.ts +0 -261
  179. package/src/ai/rate-limiter.ts +0 -129
  180. package/src/ai/service.ts +0 -331
  181. package/src/ai/session.ts +0 -544
  182. package/src/ai/subagent.ts +0 -209
  183. package/src/ai/tone-detector.ts +0 -89
  184. package/src/ai/tools.ts +0 -218
  185. package/src/ai/user-profile.ts +0 -181
  186. package/src/ai/zhin-agent/builtin-tools.ts +0 -247
  187. package/src/ai/zhin-agent/config.ts +0 -113
  188. package/src/ai/zhin-agent/exec-policy.ts +0 -78
  189. package/src/ai/zhin-agent/index.ts +0 -512
  190. package/src/ai/zhin-agent/prompt.ts +0 -131
  191. package/src/ai/zhin-agent/tool-collector.ts +0 -243
  192. package/tests/ai/agent.test.ts +0 -614
  193. package/tests/ai/context-manager.test.ts +0 -413
  194. package/tests/ai/conversation-memory.test.ts +0 -128
  195. package/tests/ai/follow-up.test.ts +0 -175
  196. package/tests/ai/integration.test.ts +0 -584
  197. package/tests/ai/output.test.ts +0 -128
  198. package/tests/ai/rate-limiter.test.ts +0 -108
  199. package/tests/ai/session.test.ts +0 -375
  200. package/tests/ai/subagent.test.ts +0 -270
  201. package/tests/ai/tone-detector.test.ts +0 -80
  202. package/tests/ai/tools-builtin.test.ts +0 -346
  203. package/tests/ai/user-profile.test.ts +0 -73
  204. package/tests/ai/zhin-agent.test.ts +0 -177
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Group Management Skill — 方法规范与元数据
3
+ *
4
+ * 设计理念:
5
+ * 群管理是 IM 系统的一种"技能"(Skill),而非一组零散的工具。
6
+ * Adapter 基类声明群管理操作的方法规范(可选),
7
+ * 具体适配器(ICQQ/Discord/Telegram 等)选择性覆写。
8
+ * Adapter.start() 自动检测哪些方法已被子类实现,
9
+ * 生成对应的 Tool 并注册为 "群聊管理" Skill。
10
+ *
11
+ * 子类无需任何额外调用:
12
+ *
13
+ * class IcqqAdapter extends Adapter<IcqqBot> {
14
+ * async kickMember(botId, sceneId, userId) { ... }
15
+ * async muteMember(botId, sceneId, userId, duration) { ... }
16
+ * // start() 中自动检测 → 自动注册 Skill,零样板代码
17
+ * }
18
+ */
19
+
20
+ import type { ToolPermissionLevel } from '../types.js';
21
+
22
+ // ============================================================================
23
+ // Adapter 群管理方法规范
24
+ // ============================================================================
25
+
26
+ /**
27
+ * 群管理能力接口。
28
+ * Adapter 基类通过此接口声明方法签名,子类选择性覆写。
29
+ */
30
+ export interface IGroupManagement {
31
+ kickMember?(botId: string, sceneId: string, userId: string): Promise<any>;
32
+ muteMember?(botId: string, sceneId: string, userId: string, duration?: number): Promise<any>;
33
+ setMemberNickname?(botId: string, sceneId: string, userId: string, nickname: string): Promise<any>;
34
+ setAdmin?(botId: string, sceneId: string, userId: string, enable?: boolean): Promise<any>;
35
+ listMembers?(botId: string, sceneId: string): Promise<any>;
36
+ banMember?(botId: string, sceneId: string, userId: string, reason?: string): Promise<any>;
37
+ unbanMember?(botId: string, sceneId: string, userId: string): Promise<any>;
38
+ setGroupName?(botId: string, sceneId: string, name: string): Promise<any>;
39
+ muteAll?(botId: string, sceneId: string, enable?: boolean): Promise<any>;
40
+ getGroupInfo?(botId: string, sceneId: string): Promise<any>;
41
+ }
42
+
43
+ // ============================================================================
44
+ // 方法 → Tool 元数据映射
45
+ // ============================================================================
46
+
47
+ export interface GroupMethodSpec {
48
+ method: keyof IGroupManagement;
49
+ toolSuffix: string;
50
+ description: string;
51
+ keywords: string[];
52
+ permissionLevel: ToolPermissionLevel;
53
+ extraParams: Record<string, { type: string; description: string; default?: any }>;
54
+ extraRequired?: string[];
55
+ preExecutable?: boolean;
56
+ }
57
+
58
+ export const GROUP_METHOD_SPECS: GroupMethodSpec[] = [
59
+ {
60
+ method: 'kickMember',
61
+ toolSuffix: 'kick_member',
62
+ description: '将成员踢出群/服务器',
63
+ keywords: ['踢', 'kick', '移除', '踢出'],
64
+ permissionLevel: 'group_admin',
65
+ extraParams: {
66
+ user_id: { type: 'string', description: '目标用户 ID' },
67
+ },
68
+ extraRequired: ['user_id'],
69
+ },
70
+ {
71
+ method: 'muteMember',
72
+ toolSuffix: 'mute_member',
73
+ description: '禁言群成员(duration=0 解除)',
74
+ keywords: ['禁言', 'mute', '静音', '解除禁言'],
75
+ permissionLevel: 'group_admin',
76
+ extraParams: {
77
+ user_id: { type: 'string', description: '目标用户 ID' },
78
+ duration: { type: 'number', description: '禁言时长(秒),0=解除', default: 600 },
79
+ },
80
+ extraRequired: ['user_id'],
81
+ },
82
+ {
83
+ method: 'setMemberNickname',
84
+ toolSuffix: 'set_nickname',
85
+ description: '设置群成员昵称/名片',
86
+ keywords: ['昵称', '名片', 'nickname', 'card'],
87
+ permissionLevel: 'group_admin',
88
+ extraParams: {
89
+ user_id: { type: 'string', description: '目标用户 ID' },
90
+ nickname: { type: 'string', description: '新昵称' },
91
+ },
92
+ extraRequired: ['user_id', 'nickname'],
93
+ },
94
+ {
95
+ method: 'setAdmin',
96
+ toolSuffix: 'set_admin',
97
+ description: '设置/取消群管理员',
98
+ keywords: ['管理员', 'admin', '设置管理'],
99
+ permissionLevel: 'group_owner',
100
+ extraParams: {
101
+ user_id: { type: 'string', description: '目标用户 ID' },
102
+ enable: { type: 'boolean', description: '设置(true)/取消(false)', default: true },
103
+ },
104
+ extraRequired: ['user_id'],
105
+ },
106
+ {
107
+ method: 'listMembers',
108
+ toolSuffix: 'list_members',
109
+ description: '获取群/服务器成员列表',
110
+ keywords: ['成员', '列表', 'members', 'list'],
111
+ permissionLevel: 'user',
112
+ extraParams: {},
113
+ preExecutable: true,
114
+ },
115
+ {
116
+ method: 'banMember',
117
+ toolSuffix: 'ban_member',
118
+ description: '封禁成员',
119
+ keywords: ['封禁', 'ban', '拉黑'],
120
+ permissionLevel: 'group_admin',
121
+ extraParams: {
122
+ user_id: { type: 'string', description: '目标用户 ID' },
123
+ reason: { type: 'string', description: '封禁原因' },
124
+ },
125
+ extraRequired: ['user_id'],
126
+ },
127
+ {
128
+ method: 'unbanMember',
129
+ toolSuffix: 'unban_member',
130
+ description: '解除封禁',
131
+ keywords: ['解封', 'unban', '解除封禁'],
132
+ permissionLevel: 'group_admin',
133
+ extraParams: {
134
+ user_id: { type: 'string', description: '目标用户 ID' },
135
+ },
136
+ extraRequired: ['user_id'],
137
+ },
138
+ {
139
+ method: 'setGroupName',
140
+ toolSuffix: 'set_group_name',
141
+ description: '修改群名称',
142
+ keywords: ['群名', '改名', 'group name'],
143
+ permissionLevel: 'group_admin',
144
+ extraParams: {
145
+ name: { type: 'string', description: '新群名' },
146
+ },
147
+ extraRequired: ['name'],
148
+ },
149
+ {
150
+ method: 'muteAll',
151
+ toolSuffix: 'mute_all',
152
+ description: '全员禁言/解除全员禁言',
153
+ keywords: ['全员禁言', 'mute all', '全体禁言'],
154
+ permissionLevel: 'group_admin',
155
+ extraParams: {
156
+ enable: { type: 'boolean', description: '开启(true)/解除(false)', default: true },
157
+ },
158
+ },
159
+ {
160
+ method: 'getGroupInfo',
161
+ toolSuffix: 'get_group_info',
162
+ description: '获取群/服务器基本信息',
163
+ keywords: ['群信息', 'group info', '群资料'],
164
+ permissionLevel: 'user',
165
+ extraParams: {},
166
+ preExecutable: true,
167
+ },
168
+ ];
169
+
170
+ // ============================================================================
171
+ // Skill 常量
172
+ // ============================================================================
173
+
174
+ export const GROUP_MANAGEMENT_SKILL_DESCRIPTION =
175
+ '群聊管理能力:在 IM 系统中对群/服务器进行管理,包括踢人、禁言、封禁、' +
176
+ '设置管理员、修改群名、查看成员列表等操作。具体可用的操作取决于平台和 Bot 权限。';
177
+
178
+ export const GROUP_MANAGEMENT_SKILL_TAGS = ['group', 'management', 'im', 'admin'];
179
+ export const GROUP_MANAGEMENT_SKILL_KEYWORDS = [
180
+ '群管理', '踢人', '禁言', '封禁', '管理员', '群名',
181
+ '成员', 'kick', 'mute', 'ban', 'admin', 'members',
182
+ ];
183
+
184
+ // ============================================================================
185
+ // 参数映射(method → 有序参数列表)
186
+ // ============================================================================
187
+
188
+ export function buildMethodArgs(
189
+ method: keyof IGroupManagement,
190
+ botId: string,
191
+ sceneId: string,
192
+ rest: Record<string, any>,
193
+ ): any[] {
194
+ switch (method) {
195
+ case 'kickMember': return [botId, sceneId, rest.user_id];
196
+ case 'muteMember': return [botId, sceneId, rest.user_id, rest.duration ?? 600];
197
+ case 'setMemberNickname': return [botId, sceneId, rest.user_id, rest.nickname];
198
+ case 'setAdmin': return [botId, sceneId, rest.user_id, rest.enable ?? true];
199
+ case 'listMembers': return [botId, sceneId];
200
+ case 'banMember': return [botId, sceneId, rest.user_id, rest.reason];
201
+ case 'unbanMember': return [botId, sceneId, rest.user_id];
202
+ case 'setGroupName': return [botId, sceneId, rest.name];
203
+ case 'muteAll': return [botId, sceneId, rest.enable ?? true];
204
+ case 'getGroupInfo': return [botId, sceneId];
205
+ default: return [botId, sceneId, ...Object.values(rest)];
206
+ }
207
+ }
@@ -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