@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.
- package/CHANGELOG.md +18 -0
- package/README.md +57 -3
- package/lib/adapter.d.ts +11 -0
- package/lib/adapter.d.ts.map +1 -1
- package/lib/adapter.js +61 -0
- package/lib/adapter.js.map +1 -1
- package/lib/ai/index.d.ts +3 -39
- package/lib/ai/index.d.ts.map +1 -1
- package/lib/ai/index.js +2 -44
- package/lib/ai/index.js.map +1 -1
- package/lib/ai/providers/anthropic.d.ts.map +1 -1
- package/lib/ai/providers/anthropic.js +2 -0
- package/lib/ai/providers/anthropic.js.map +1 -1
- package/lib/ai/providers/openai.d.ts.map +1 -1
- package/lib/ai/providers/openai.js +8 -0
- package/lib/ai/providers/openai.js.map +1 -1
- package/lib/ai/types.d.ts +5 -3
- package/lib/ai/types.d.ts.map +1 -1
- package/lib/built/ai-trigger.js.map +1 -1
- package/lib/built/common-adapter-tools.d.ts +55 -0
- package/lib/built/common-adapter-tools.d.ts.map +1 -0
- package/lib/built/common-adapter-tools.js +158 -0
- package/lib/built/common-adapter-tools.js.map +1 -0
- package/lib/built/dispatcher.d.ts.map +1 -1
- package/lib/built/dispatcher.js +50 -46
- package/lib/built/dispatcher.js.map +1 -1
- package/lib/built/skill.d.ts.map +1 -1
- package/lib/built/skill.js +0 -1
- package/lib/built/skill.js.map +1 -1
- package/lib/built/tool.d.ts +3 -3
- package/lib/built/tool.d.ts.map +1 -1
- package/lib/built/tool.js.map +1 -1
- package/lib/feature.d.ts +16 -1
- package/lib/feature.d.ts.map +1 -1
- package/lib/feature.js +41 -2
- package/lib/feature.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -1
- package/lib/plugin.d.ts +38 -1
- package/lib/plugin.d.ts.map +1 -1
- package/lib/plugin.js +73 -22
- package/lib/plugin.js.map +1 -1
- package/lib/scheduler/scheduler.js +1 -1
- package/lib/scheduler/scheduler.js.map +1 -1
- package/lib/types.d.ts +43 -28
- package/lib/types.d.ts.map +1 -1
- package/lib/utils.d.ts +12 -3
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +64 -54
- package/lib/utils.js.map +1 -1
- package/package.json +5 -5
- package/src/adapter.ts +85 -5
- package/src/ai/index.ts +8 -186
- package/src/ai/providers/anthropic.ts +1 -0
- package/src/ai/providers/openai.ts +5 -1
- package/src/ai/types.ts +6 -4
- package/src/built/ai-trigger.ts +2 -2
- package/src/built/common-adapter-tools.ts +207 -0
- package/src/built/dispatcher.ts +51 -52
- package/src/built/skill.ts +3 -4
- package/src/built/tool.ts +3 -3
- package/src/feature.ts +45 -2
- package/src/index.ts +2 -0
- package/src/plugin.ts +92 -31
- package/src/scheduler/scheduler.ts +1 -1
- package/src/types.ts +39 -28
- package/src/utils.ts +63 -52
- package/tests/ai/setup.ts +2 -2
- package/tests/utils.test.ts +1 -3
- package/lib/ai/agent.d.ts +0 -130
- package/lib/ai/agent.d.ts.map +0 -1
- package/lib/ai/agent.js +0 -702
- package/lib/ai/agent.js.map +0 -1
- package/lib/ai/bootstrap.d.ts +0 -91
- package/lib/ai/bootstrap.d.ts.map +0 -1
- package/lib/ai/bootstrap.js +0 -243
- package/lib/ai/bootstrap.js.map +0 -1
- package/lib/ai/builtin-tools.d.ts +0 -59
- package/lib/ai/builtin-tools.d.ts.map +0 -1
- package/lib/ai/builtin-tools.js +0 -777
- package/lib/ai/builtin-tools.js.map +0 -1
- package/lib/ai/compaction.d.ts +0 -132
- package/lib/ai/compaction.d.ts.map +0 -1
- package/lib/ai/compaction.js +0 -370
- package/lib/ai/compaction.js.map +0 -1
- package/lib/ai/context-manager.d.ts +0 -213
- package/lib/ai/context-manager.d.ts.map +0 -1
- package/lib/ai/context-manager.js +0 -313
- package/lib/ai/context-manager.js.map +0 -1
- package/lib/ai/conversation-memory.d.ts +0 -181
- package/lib/ai/conversation-memory.d.ts.map +0 -1
- package/lib/ai/conversation-memory.js +0 -581
- package/lib/ai/conversation-memory.js.map +0 -1
- package/lib/ai/cron-engine.d.ts +0 -92
- package/lib/ai/cron-engine.d.ts.map +0 -1
- package/lib/ai/cron-engine.js +0 -278
- package/lib/ai/cron-engine.js.map +0 -1
- package/lib/ai/follow-up.d.ts +0 -131
- package/lib/ai/follow-up.d.ts.map +0 -1
- package/lib/ai/follow-up.js +0 -265
- package/lib/ai/follow-up.js.map +0 -1
- package/lib/ai/hooks.d.ts +0 -143
- package/lib/ai/hooks.d.ts.map +0 -1
- package/lib/ai/hooks.js +0 -108
- package/lib/ai/hooks.js.map +0 -1
- package/lib/ai/init.d.ts +0 -30
- package/lib/ai/init.d.ts.map +0 -1
- package/lib/ai/init.js +0 -686
- package/lib/ai/init.js.map +0 -1
- package/lib/ai/output.d.ts +0 -93
- package/lib/ai/output.d.ts.map +0 -1
- package/lib/ai/output.js +0 -176
- package/lib/ai/output.js.map +0 -1
- package/lib/ai/rate-limiter.d.ts +0 -38
- package/lib/ai/rate-limiter.d.ts.map +0 -1
- package/lib/ai/rate-limiter.js +0 -86
- package/lib/ai/rate-limiter.js.map +0 -1
- package/lib/ai/service.d.ts +0 -88
- package/lib/ai/service.d.ts.map +0 -1
- package/lib/ai/service.js +0 -285
- package/lib/ai/service.js.map +0 -1
- package/lib/ai/session.d.ts +0 -186
- package/lib/ai/session.d.ts.map +0 -1
- package/lib/ai/session.js +0 -443
- package/lib/ai/session.js.map +0 -1
- package/lib/ai/subagent.d.ts +0 -50
- package/lib/ai/subagent.d.ts.map +0 -1
- package/lib/ai/subagent.js +0 -144
- package/lib/ai/subagent.js.map +0 -1
- package/lib/ai/tone-detector.d.ts +0 -19
- package/lib/ai/tone-detector.d.ts.map +0 -1
- package/lib/ai/tone-detector.js +0 -72
- package/lib/ai/tone-detector.js.map +0 -1
- package/lib/ai/tools.d.ts +0 -45
- package/lib/ai/tools.d.ts.map +0 -1
- package/lib/ai/tools.js +0 -206
- package/lib/ai/tools.js.map +0 -1
- package/lib/ai/user-profile.d.ts +0 -56
- package/lib/ai/user-profile.d.ts.map +0 -1
- package/lib/ai/user-profile.js +0 -130
- package/lib/ai/user-profile.js.map +0 -1
- package/lib/ai/zhin-agent/builtin-tools.d.ts +0 -17
- package/lib/ai/zhin-agent/builtin-tools.d.ts.map +0 -1
- package/lib/ai/zhin-agent/builtin-tools.js +0 -220
- package/lib/ai/zhin-agent/builtin-tools.js.map +0 -1
- package/lib/ai/zhin-agent/config.d.ts +0 -54
- package/lib/ai/zhin-agent/config.d.ts.map +0 -1
- package/lib/ai/zhin-agent/config.js +0 -76
- package/lib/ai/zhin-agent/config.js.map +0 -1
- package/lib/ai/zhin-agent/exec-policy.d.ts +0 -20
- package/lib/ai/zhin-agent/exec-policy.d.ts.map +0 -1
- package/lib/ai/zhin-agent/exec-policy.js +0 -71
- package/lib/ai/zhin-agent/exec-policy.js.map +0 -1
- package/lib/ai/zhin-agent/index.d.ts +0 -70
- package/lib/ai/zhin-agent/index.d.ts.map +0 -1
- package/lib/ai/zhin-agent/index.js +0 -404
- package/lib/ai/zhin-agent/index.js.map +0 -1
- package/lib/ai/zhin-agent/prompt.d.ts +0 -21
- package/lib/ai/zhin-agent/prompt.d.ts.map +0 -1
- package/lib/ai/zhin-agent/prompt.js +0 -111
- package/lib/ai/zhin-agent/prompt.js.map +0 -1
- package/lib/ai/zhin-agent/tool-collector.d.ts +0 -22
- package/lib/ai/zhin-agent/tool-collector.d.ts.map +0 -1
- package/lib/ai/zhin-agent/tool-collector.js +0 -218
- package/lib/ai/zhin-agent/tool-collector.js.map +0 -1
- package/src/ai/agent.ts +0 -831
- package/src/ai/bootstrap.ts +0 -309
- package/src/ai/builtin-tools.ts +0 -849
- package/src/ai/compaction.ts +0 -529
- package/src/ai/context-manager.ts +0 -440
- package/src/ai/conversation-memory.ts +0 -774
- package/src/ai/cron-engine.ts +0 -337
- package/src/ai/follow-up.ts +0 -357
- package/src/ai/hooks.ts +0 -223
- package/src/ai/init.ts +0 -762
- package/src/ai/output.ts +0 -261
- package/src/ai/rate-limiter.ts +0 -129
- package/src/ai/service.ts +0 -331
- package/src/ai/session.ts +0 -544
- package/src/ai/subagent.ts +0 -209
- package/src/ai/tone-detector.ts +0 -89
- package/src/ai/tools.ts +0 -218
- package/src/ai/user-profile.ts +0 -181
- package/src/ai/zhin-agent/builtin-tools.ts +0 -247
- package/src/ai/zhin-agent/config.ts +0 -113
- package/src/ai/zhin-agent/exec-policy.ts +0 -78
- package/src/ai/zhin-agent/index.ts +0 -512
- package/src/ai/zhin-agent/prompt.ts +0 -131
- package/src/ai/zhin-agent/tool-collector.ts +0 -243
- package/tests/ai/agent.test.ts +0 -614
- package/tests/ai/context-manager.test.ts +0 -413
- package/tests/ai/conversation-memory.test.ts +0 -128
- package/tests/ai/follow-up.test.ts +0 -175
- package/tests/ai/integration.test.ts +0 -584
- package/tests/ai/output.test.ts +0 -128
- package/tests/ai/rate-limiter.test.ts +0 -108
- package/tests/ai/session.test.ts +0 -375
- package/tests/ai/subagent.test.ts +0 -270
- package/tests/ai/tone-detector.test.ts +0 -80
- package/tests/ai/tools-builtin.test.ts +0 -346
- package/tests/ai/user-profile.test.ts +0 -73
- 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
|
+
}
|
package/src/built/dispatcher.ts
CHANGED
|
@@ -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
|
-
*
|
|
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
|
-
|
|
175
|
-
|
|
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,
|
|
195
|
+
await guardrail(message, async () => { nextCalled = true; });
|
|
182
196
|
} catch {
|
|
183
|
-
|
|
184
|
-
passed = false;
|
|
197
|
+
return false;
|
|
185
198
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
//
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
if (
|
|
208
|
-
|
|
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
|
-
//
|
|
286
|
-
//
|
|
287
|
-
|
|
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) {
|
package/src/built/skill.ts
CHANGED
|
@@ -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
|
|
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 (
|
|
249
|
-
for (const kw of
|
|
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,
|
|
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:
|
|
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
|
-
|
|
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
|
|