@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
package/lib/ai/init.js DELETED
@@ -1,686 +0,0 @@
1
- /**
2
- * AI 模块初始化
3
- *
4
- * 将 AI 服务注册到 Zhin 插件系统中:
5
- * - AIService context
6
- * - ZhinAgent 全局大脑
7
- * - AI 触发处理器 (via MessageDispatcher)
8
- * - 数据库会话/上下文持久化
9
- * - 消息记录中间件
10
- * - AI 管理工具
11
- */
12
- import * as fs from 'fs';
13
- import * as os from 'os';
14
- import * as path from 'path';
15
- import { getPlugin } from '../plugin.js';
16
- import { ZhinTool, ToolFeature } from '../built/tool.js';
17
- import { shouldTriggerAI, inferSenderPermissions, parseRichMediaContent, mergeAITriggerConfig, } from '../built/ai-trigger.js';
18
- import { AIService } from './service.js';
19
- import { ZhinAgent } from './zhin-agent/index.js';
20
- import { createBuiltinTools, discoverWorkspaceSkills, loadAlwaysSkillsContent, buildSkillsSummaryXML } from './builtin-tools.js';
21
- import { resolveSkillInstructionMaxChars, DEFAULT_CONFIG } from './zhin-agent/config.js';
22
- import { loadBootstrapFiles, buildContextFiles, buildBootstrapContextSection } from './bootstrap.js';
23
- import { triggerAIHook, createAIHookEvent } from './hooks.js';
24
- import { SessionManager, createDatabaseSessionManager } from './session.js';
25
- import { AI_SESSION_MODEL } from './session.js';
26
- import { createContextManager, CHAT_MESSAGE_MODEL, CONTEXT_SUMMARY_MODEL, } from './context-manager.js';
27
- import { AI_MESSAGE_MODEL, AI_SUMMARY_MODEL } from './conversation-memory.js';
28
- import { AI_USER_PROFILE_MODEL } from './user-profile.js';
29
- import { AI_FOLLOWUP_MODEL } from './follow-up.js';
30
- import { PersistentCronEngine, setCronManager, createCronTools } from './cron-engine.js';
31
- import { Scheduler, getScheduler, setScheduler } from '../scheduler/index.js';
32
- // ============================================================================
33
- // 辅助函数
34
- // ============================================================================
35
- /**
36
- * 从消息中提取图片 URL(支持 XML 标签格式和 raw 对象格式)
37
- */
38
- function extractImageUrls(message) {
39
- const urls = [];
40
- const raw = typeof message.$raw === 'string' ? message.$raw : JSON.stringify(message.$raw || '');
41
- // 匹配 <image url="..." /> 格式
42
- const xmlMatches = raw.match(/<image[^>]+url="([^"]+)"/g);
43
- if (xmlMatches) {
44
- for (const m of xmlMatches) {
45
- const urlMatch = m.match(/url="([^"]+)"/);
46
- if (urlMatch)
47
- urls.push(urlMatch[1]);
48
- }
49
- }
50
- // 匹配 [CQ:image,url=...] 格式 (OneBot)
51
- const cqMatches = raw.match(/\[CQ:image[^\]]*url=([^\],]+)/g);
52
- if (cqMatches) {
53
- for (const m of cqMatches) {
54
- const urlMatch = m.match(/url=([^\],]+)/);
55
- if (urlMatch)
56
- urls.push(urlMatch[1]);
57
- }
58
- }
59
- return urls;
60
- }
61
- // ============================================================================
62
- // 初始化函数
63
- // ============================================================================
64
- /**
65
- * 初始化 AI 模块
66
- *
67
- * 在 setup.ts 中调用:
68
- * ```ts
69
- * import { initAIModule } from '@zhin.js/core';
70
- * initAIModule();
71
- * ```
72
- */
73
- export function initAIModule() {
74
- const plugin = getPlugin();
75
- const { provide, useContext, root, logger } = plugin;
76
- // ── 工具服务 ──
77
- provide(new ToolFeature());
78
- // ── 数据库模型定义 ──
79
- // provide(defineDatabaseService) 之后 defineModel 即可用,直接在顶层定义
80
- const defineModel = plugin.defineModel;
81
- if (typeof defineModel === 'function') {
82
- defineModel('chat_messages', CHAT_MESSAGE_MODEL);
83
- defineModel('context_summaries', CONTEXT_SUMMARY_MODEL);
84
- defineModel('ai_sessions', AI_SESSION_MODEL);
85
- defineModel('ai_messages', AI_MESSAGE_MODEL);
86
- defineModel('ai_summaries', AI_SUMMARY_MODEL);
87
- defineModel('ai_user_profiles', AI_USER_PROFILE_MODEL);
88
- defineModel('ai_followups', AI_FOLLOWUP_MODEL);
89
- logger.debug('AI database models registered (7 tables)');
90
- }
91
- else {
92
- logger.debug('defineModel not available, AI will use in-memory storage');
93
- }
94
- // ── AI 服务实例 ──
95
- let aiServiceInstance = null;
96
- let zhinAgentInstance = null;
97
- provide({
98
- name: 'ai',
99
- description: 'AI Service - Multi-model LLM integration',
100
- async mounted(p) {
101
- const configService = root.inject('config');
102
- const appConfig = configService?.getPrimary() || {};
103
- const config = appConfig.ai || {};
104
- if (config.enabled === false) {
105
- logger.info('AI Service is disabled');
106
- return null;
107
- }
108
- const service = new AIService(config);
109
- aiServiceInstance = service;
110
- service.setPlugin(root);
111
- const providers = service.listProviders();
112
- if (providers.length === 0) {
113
- logger.warn('No AI providers configured. Please add API keys in zhin.config (yml/json/toml)');
114
- }
115
- else {
116
- logger.info(`AI Service started with providers: ${providers.join(', ')}`);
117
- }
118
- return service;
119
- },
120
- async dispose(service) {
121
- if (service) {
122
- service.dispose();
123
- aiServiceInstance = null;
124
- logger.info('AI Service stopped');
125
- }
126
- },
127
- });
128
- // ── ZhinAgent 全局大脑 ──
129
- useContext('ai', (ai) => {
130
- if (!ai.isReady()) {
131
- logger.warn('AI Service not ready, ZhinAgent not created');
132
- return;
133
- }
134
- const provider = ai.getProvider();
135
- const agentConfig = ai.getAgentConfig();
136
- const agent = new ZhinAgent(provider, agentConfig);
137
- zhinAgentInstance = agent;
138
- const skillRegistry = root.inject('skill');
139
- if (skillRegistry)
140
- agent.setSkillRegistry(skillRegistry);
141
- // 注入跟进提醒的发送回调(不依赖数据库,内存模式也能发)
142
- agent.setFollowUpSender(async (record) => {
143
- const adapter = root.inject(record.platform);
144
- if (!adapter || typeof adapter.sendMessage !== 'function') {
145
- logger.warn(`[跟进提醒] 找不到适配器: ${record.platform}`);
146
- return;
147
- }
148
- const content = `⏰ 定时提醒:${record.message}`;
149
- await adapter.sendMessage({
150
- context: record.platform,
151
- bot: record.bot_id,
152
- id: record.scene_id,
153
- type: record.scene_type,
154
- content,
155
- });
156
- });
157
- // 子任务管理器:让 AI 可以 spawn 后台子 agent 异步执行复杂任务
158
- agent.initSubagentManager(() => {
159
- const modelName = provider.models[0] || '';
160
- const fullConfig = { ...DEFAULT_CONFIG, ...agentConfig };
161
- const zhinTools = createBuiltinTools({ skillInstructionMaxChars: resolveSkillInstructionMaxChars(fullConfig, modelName) });
162
- return zhinTools.map(zt => {
163
- const t = zt.toTool();
164
- return {
165
- name: t.name,
166
- description: t.description,
167
- parameters: t.parameters,
168
- execute: t.execute,
169
- tags: t.tags,
170
- keywords: t.keywords,
171
- };
172
- });
173
- });
174
- agent.setSubagentSender(async (origin, content) => {
175
- const adapter = root.inject(origin.platform);
176
- if (!adapter || typeof adapter.sendMessage !== 'function') {
177
- logger.warn(`[子任务] 找不到适配器: ${origin.platform}`);
178
- return;
179
- }
180
- await adapter.sendMessage({
181
- context: origin.platform,
182
- bot: origin.botId,
183
- id: origin.sceneId,
184
- type: origin.sceneType,
185
- content,
186
- });
187
- });
188
- // 持久化定时任务引擎:加载 data/cron-jobs.json,到点用 prompt 调用 Agent;并暴露给 AI 管理(list/add/remove/pause/resume)
189
- let cronEngine = null;
190
- const cronFeature = root.inject('cron');
191
- if (cronFeature && typeof cronFeature.add === 'function') {
192
- const dataDir = path.join(process.cwd(), 'data');
193
- const addCron = (c) => cronFeature.add(c, 'cron-engine');
194
- const runner = async (prompt) => {
195
- if (!zhinAgentInstance)
196
- return;
197
- await zhinAgentInstance.process(prompt, {
198
- platform: 'cron',
199
- senderId: 'system',
200
- sceneId: 'cron',
201
- });
202
- };
203
- cronEngine = new PersistentCronEngine({ dataDir, addCron, runner });
204
- cronEngine.load();
205
- setCronManager({ cronFeature, engine: cronEngine });
206
- }
207
- // 统一调度器(at/every/cron + Heartbeat),持久化到 data/scheduler-jobs.json
208
- const dataDir = path.join(process.cwd(), 'data');
209
- const workspace = process.cwd();
210
- const scheduler = new Scheduler({
211
- storePath: path.join(dataDir, 'scheduler-jobs.json'),
212
- workspace,
213
- onJob: async (job) => {
214
- if (!zhinAgentInstance)
215
- return;
216
- await zhinAgentInstance.process(job.payload.message, {
217
- platform: 'cron',
218
- senderId: 'system',
219
- sceneId: 'scheduler',
220
- });
221
- },
222
- heartbeatEnabled: true,
223
- heartbeatIntervalMs: 30 * 60 * 1000,
224
- });
225
- setScheduler(scheduler);
226
- scheduler.start().catch((e) => logger.warn('Scheduler start failed: ' + e.message));
227
- logger.debug('ZhinAgent created');
228
- return () => {
229
- setCronManager(null);
230
- if (cronEngine) {
231
- cronEngine.unload();
232
- cronEngine = null;
233
- }
234
- const s = getScheduler();
235
- if (s) {
236
- s.stop();
237
- setScheduler(null);
238
- }
239
- agent.dispose();
240
- zhinAgentInstance = null;
241
- };
242
- });
243
- // ── AI 触发处理器 ──
244
- useContext('ai', (ai) => {
245
- const rawConfig = ai.getTriggerConfig();
246
- const triggerConfig = mergeAITriggerConfig(rawConfig);
247
- if (!triggerConfig.enabled) {
248
- logger.info('AI Trigger is disabled');
249
- return;
250
- }
251
- const renderOutput = (elements) => {
252
- const parts = [];
253
- for (const el of elements) {
254
- switch (el.type) {
255
- case 'text':
256
- if (el.content)
257
- parts.push(el.content);
258
- break;
259
- case 'image':
260
- parts.push(`<image url="${el.url}"/>`);
261
- break;
262
- case 'audio':
263
- parts.push(`<audio url="${el.url}"/>`);
264
- break;
265
- case 'video':
266
- parts.push(`<video url="${el.url}"/>`);
267
- break;
268
- case 'card': {
269
- const cp = [`📋 ${el.title}`];
270
- if (el.description)
271
- cp.push(el.description);
272
- if (el.fields?.length)
273
- for (const f of el.fields)
274
- cp.push(` ${f.label}: ${f.value}`);
275
- if (el.imageUrl)
276
- cp.push(`<image url="${el.imageUrl}"/>`);
277
- parts.push(cp.join('\n'));
278
- break;
279
- }
280
- case 'file':
281
- parts.push(`📎 ${el.name}: ${el.url}`);
282
- break;
283
- }
284
- }
285
- return parts.join('\n') || '';
286
- };
287
- const handleAIMessage = async (message, content) => {
288
- const t0 = performance.now();
289
- if (!ai.isReady())
290
- return;
291
- if (triggerConfig.thinkingMessage)
292
- await message.$reply(triggerConfig.thinkingMessage);
293
- const permissions = inferSenderPermissions(message, triggerConfig);
294
- const toolContext = {
295
- platform: message.$adapter,
296
- botId: message.$bot,
297
- sceneId: message.$channel?.id || message.$sender.id,
298
- senderId: message.$sender.id,
299
- message,
300
- scope: permissions.scope,
301
- senderPermissionLevel: permissions.permissionLevel,
302
- isGroupAdmin: permissions.isGroupAdmin,
303
- isGroupOwner: permissions.isGroupOwner,
304
- isBotAdmin: permissions.isBotAdmin,
305
- isOwner: permissions.isOwner,
306
- };
307
- const tCollect = performance.now();
308
- const toolService = root.inject('tool');
309
- let externalTools = [];
310
- if (toolService) {
311
- externalTools = toolService.collectAll(root);
312
- externalTools = toolService.filterByContext(externalTools, toolContext);
313
- }
314
- logger.debug(`[AI Handler] 工具收集: ${externalTools.length} 个, ${(performance.now() - tCollect).toFixed(0)}ms`);
315
- try {
316
- const timeout = new Promise((_, rej) => setTimeout(() => rej(new Error('AI 响应超时')), triggerConfig.timeout));
317
- let responseText;
318
- if (zhinAgentInstance) {
319
- // 检查消息是否包含图片(多模态路由)
320
- const imageUrls = extractImageUrls(message);
321
- let elements;
322
- if (imageUrls.length > 0) {
323
- const parts = [];
324
- if (content)
325
- parts.push({ type: 'text', text: content });
326
- for (const url of imageUrls) {
327
- parts.push({ type: 'image_url', image_url: { url } });
328
- }
329
- elements = await Promise.race([
330
- zhinAgentInstance.processMultimodal(parts, toolContext),
331
- timeout,
332
- ]);
333
- }
334
- else {
335
- elements = await Promise.race([
336
- zhinAgentInstance.process(content, toolContext, externalTools),
337
- timeout,
338
- ]);
339
- }
340
- responseText = renderOutput(elements);
341
- }
342
- else {
343
- const response = await Promise.race([
344
- ai.process(content, toolContext, externalTools),
345
- timeout,
346
- ]);
347
- responseText = typeof response === 'string' ? response : '';
348
- }
349
- if (responseText)
350
- await message.$reply(parseRichMediaContent(responseText));
351
- logger.info(`[AI Handler] 总耗时: ${(performance.now() - t0).toFixed(0)}ms`);
352
- }
353
- catch (error) {
354
- const msg = error instanceof Error ? error.message : String(error);
355
- logger.warn(`[AI Handler] 失败 (${(performance.now() - t0).toFixed(0)}ms): ${msg}`);
356
- await message.$reply(triggerConfig.errorTemplate.replace('{error}', msg));
357
- }
358
- };
359
- const dispatcher = root.inject('dispatcher');
360
- if (dispatcher && typeof dispatcher.setAIHandler === 'function') {
361
- dispatcher.setAITriggerMatcher((message) => shouldTriggerAI(message, triggerConfig));
362
- dispatcher.setAIHandler(handleAIMessage);
363
- logger.debug('AI Handler registered via MessageDispatcher');
364
- return () => { logger.info('AI Handler unregistered'); };
365
- }
366
- // 回退中间件
367
- const aiMw = async (message, next) => {
368
- const { triggered, content } = shouldTriggerAI(message, triggerConfig);
369
- if (!triggered)
370
- return await next();
371
- await handleAIMessage(message, content);
372
- await next();
373
- };
374
- const dispose = root.addMiddleware(aiMw);
375
- logger.debug('AI Trigger middleware registered (fallback mode)');
376
- return () => { dispose(); };
377
- });
378
- // ── 数据库集成(db 就绪后升级各组件到数据库存储)──
379
- useContext('database', (db) => {
380
- setTimeout(() => {
381
- if (!aiServiceInstance)
382
- return;
383
- const configService = root.inject('config');
384
- const appConfig = configService?.getPrimary() || {};
385
- const config = appConfig.ai || {};
386
- if (config.sessions?.useDatabase === false)
387
- return;
388
- try {
389
- const model = db.models.get('ai_sessions');
390
- if (!model)
391
- return;
392
- const dbSession = createDatabaseSessionManager(model, aiServiceInstance.getSessionConfig());
393
- aiServiceInstance.setSessionManager(dbSession);
394
- if (zhinAgentInstance)
395
- zhinAgentInstance.setSessionManager(dbSession);
396
- const ctxCfg = aiServiceInstance.getContextConfig();
397
- if (ctxCfg.enabled !== false) {
398
- const msgModel = db.models.get('chat_messages');
399
- const sumModel = db.models.get('context_summaries');
400
- if (msgModel && sumModel) {
401
- const ctxMgr = createContextManager(msgModel, sumModel, ctxCfg);
402
- aiServiceInstance.setContextManager(ctxMgr);
403
- if (zhinAgentInstance)
404
- zhinAgentInstance.setContextManager(ctxMgr);
405
- }
406
- }
407
- // ConversationMemory 升级到数据库
408
- if (zhinAgentInstance) {
409
- const aiMsgModel = db.models.get('ai_messages');
410
- const aiSumModel = db.models.get('ai_summaries');
411
- if (aiMsgModel && aiSumModel) {
412
- zhinAgentInstance.upgradeMemoryToDatabase(aiMsgModel, aiSumModel);
413
- }
414
- // UserProfile 升级到数据库
415
- const profileModel = db.models.get('ai_user_profiles');
416
- if (profileModel) {
417
- zhinAgentInstance.upgradeProfilesToDatabase(profileModel);
418
- }
419
- // FollowUp 升级到数据库 + 恢复未完成任务
420
- const followUpModel = db.models.get('ai_followups');
421
- if (followUpModel) {
422
- zhinAgentInstance.upgradeFollowUpsToDatabase(followUpModel);
423
- // 从数据库恢复未完成的跟进任务
424
- zhinAgentInstance.restoreFollowUps().catch(e => {
425
- logger.warn('FollowUp restore failed:', e);
426
- });
427
- }
428
- }
429
- logger.debug('AI database storage activated (session, memory, profile, followup)');
430
- }
431
- catch (e) {
432
- logger.error('AI Session: database setup failed:', e);
433
- }
434
- }, 100);
435
- });
436
- // ── 消息记录中间件 ──
437
- root.addMiddleware(async (message, next) => {
438
- await next();
439
- if (aiServiceInstance?.contextManager) {
440
- const record = {
441
- platform: message.$adapter,
442
- scene_id: message.$channel?.id || message.$sender.id,
443
- scene_type: message.$channel?.type || 'private',
444
- scene_name: message.$channel?.name || '',
445
- sender_id: message.$sender.id,
446
- sender_name: message.$sender.name || message.$sender.id,
447
- message: typeof message.$raw === 'string'
448
- ? message.$raw
449
- : JSON.stringify(message.$raw),
450
- time: message.$timestamp || Date.now(),
451
- };
452
- aiServiceInstance.contextManager.recordMessage(record).catch(() => { });
453
- }
454
- });
455
- // ── AI 管理工具 ──
456
- useContext('ai', 'tool', (ai, toolService) => {
457
- if (!ai || !toolService)
458
- return;
459
- const listModelsTool = new ZhinTool('ai.models')
460
- .desc('列出所有可用的 AI 模型')
461
- .keyword('模型', '可用模型', 'ai模型', 'model', 'models')
462
- .tag('ai', 'management')
463
- .execute(async () => {
464
- const models = await ai.listModels();
465
- return { providers: models.map(({ provider, models: ml }) => ({ name: provider, models: ml.slice(0, 10), total: ml.length })) };
466
- })
467
- .action(async () => {
468
- const models = await ai.listModels();
469
- let r = '🤖 可用模型:\n';
470
- for (const { provider, models: ml } of models) {
471
- r += `\n【${provider}】\n` + ml.slice(0, 5).map(m => ` • ${m}`).join('\n');
472
- if (ml.length > 5)
473
- r += `\n ... 还有 ${ml.length - 5} 个`;
474
- }
475
- return r;
476
- });
477
- const clearSessionTool = new ZhinTool('ai.clear')
478
- .desc('清除当前对话的历史记录')
479
- .keyword('清除', '清空', '重置', 'clear', 'reset')
480
- .tag('ai', 'session')
481
- .execute(async (_args, context) => {
482
- if (!context?.message)
483
- return { success: false, error: '无消息上下文' };
484
- const msg = context.message;
485
- const sid = SessionManager.generateId(msg.$adapter, msg.$sender.id, msg.$channel?.id);
486
- await ai.sessions.reset(sid);
487
- return { success: true, message: '对话历史已清除' };
488
- })
489
- .action(async (message) => {
490
- const sid = SessionManager.generateId(message.$adapter, message.$sender.id, message.$channel?.id);
491
- await ai.sessions.reset(sid);
492
- return '✅ 对话历史已清除';
493
- });
494
- const healthCheckTool = new ZhinTool('ai.health')
495
- .desc('检查 AI 服务的健康状态')
496
- .keyword('健康', '状态', '检查', 'health', 'status')
497
- .tag('ai', 'management')
498
- .execute(async () => {
499
- const h = await ai.healthCheck();
500
- return { providers: Object.entries(h).map(([n, ok]) => ({ name: n, healthy: ok })) };
501
- })
502
- .action(async () => {
503
- const h = await ai.healthCheck();
504
- return ['🏥 AI 服务健康状态:'].concat(Object.entries(h).map(([p, ok]) => ` ${ok ? '✅' : '❌'} ${p}`)).join('\n');
505
- });
506
- const tools = [listModelsTool, clearSessionTool, healthCheckTool];
507
- const disposers = [];
508
- for (const tool of tools)
509
- disposers.push(toolService.addTool(tool, root.name));
510
- logger.debug(`Registered ${tools.length} AI management tools`);
511
- return () => disposers.forEach(d => d());
512
- });
513
- // ── 内置系统工具(文件/Shell/网络/计划/记忆/技能) ──
514
- useContext('ai', 'tool', (ai, toolService) => {
515
- if (!ai || !toolService)
516
- return;
517
- const provider = ai.getProvider();
518
- const agentCfg = ai.getAgentConfig();
519
- const fullCfg = { ...DEFAULT_CONFIG, ...agentCfg };
520
- const modelName = provider.models[0] || '';
521
- const builtinTools = createBuiltinTools({ skillInstructionMaxChars: resolveSkillInstructionMaxChars(fullCfg, modelName) });
522
- const disposers = [];
523
- for (const tool of builtinTools)
524
- disposers.push(toolService.addTool(tool, root.name));
525
- const cronTools = createCronTools();
526
- for (const tool of cronTools)
527
- disposers.push(toolService.addTool(tool, root.name));
528
- logger.info(`Registered ${builtinTools.length} built-in + ${cronTools.length} cron tools`);
529
- let skillWatchers = [];
530
- let skillReloadDebounce = null;
531
- async function syncWorkspaceSkills() {
532
- const skillFeature = root.inject?.('skill');
533
- if (!skillFeature)
534
- return 0;
535
- // 先移除当前插件注册的所有工作区技能(增量更新)
536
- const existing = skillFeature.getByPlugin(root.name);
537
- for (const s of existing)
538
- skillFeature.remove(s);
539
- const skills = await discoverWorkspaceSkills();
540
- if (skills.length === 0)
541
- return 0;
542
- const allRegisteredTools = toolService.getAll();
543
- const toolNameIndex = new Map();
544
- for (const t of allRegisteredTools) {
545
- toolNameIndex.set(t.name, t);
546
- const parts = t.name.split('_');
547
- if (parts.length === 2)
548
- toolNameIndex.set(`${parts[1]}_${parts[0]}`, t);
549
- }
550
- for (const s of skills) {
551
- const associatedTools = [];
552
- const toolNames = s.toolNames || [];
553
- for (const toolName of toolNames) {
554
- let tool = toolService.get(toolName) || toolNameIndex.get(toolName);
555
- if (tool)
556
- associatedTools.push(tool);
557
- }
558
- skillFeature.add({
559
- name: s.name,
560
- description: s.description,
561
- tools: associatedTools,
562
- keywords: s.keywords || [],
563
- tags: s.tags || [],
564
- pluginName: root.name,
565
- }, root.name);
566
- }
567
- return skills.length;
568
- }
569
- // 异步发现工作区技能 + 加载引导文件(不阻塞注册流程)
570
- (async () => {
571
- // ── 第一步:发现和注册工作区技能 ──
572
- try {
573
- const count = await syncWorkspaceSkills();
574
- const skillFeature = root.inject?.('skill');
575
- if (count > 0 && skillFeature) {
576
- logger.info(`✅ Registered ${count} workspace skills`);
577
- }
578
- }
579
- catch (e) {
580
- logger.warn(`Failed to discover workspace skills: ${e.message}`);
581
- }
582
- // ── 第二步:加载引导文件 ──
583
- const loadedFiles = [];
584
- try {
585
- // 使用项目根目录或当前工作目录作为工作区目录
586
- const workspaceDir = process.cwd();
587
- const bootstrapFiles = await loadBootstrapFiles(workspaceDir);
588
- const contextFiles = buildContextFiles(bootstrapFiles);
589
- logger.debug(`Bootstrap files loaded (cwd: ${workspaceDir}): ${bootstrapFiles.map(f => f.name + (f.missing ? ' (missing)' : '')).join(', ')}`);
590
- // SOUL.md → 注入到 agent persona
591
- const soulFile = contextFiles.find(f => f.path === 'SOUL.md');
592
- if (soulFile && zhinAgentInstance) {
593
- logger.info('Loaded SOUL.md persona → agent prompt');
594
- loadedFiles.push('SOUL.md');
595
- }
596
- // TOOLS.md → 记录已加载
597
- const toolsFile = contextFiles.find(f => f.path === 'TOOLS.md');
598
- if (toolsFile) {
599
- logger.info('Loaded TOOLS.md tool guidance → agent prompt');
600
- loadedFiles.push('TOOLS.md');
601
- }
602
- // AGENTS.md → 记录已加载
603
- const agentsFile = contextFiles.find(f => f.path === 'AGENTS.md');
604
- if (agentsFile) {
605
- logger.info('Loaded AGENTS.md memory → agent prompt');
606
- loadedFiles.push('AGENTS.md');
607
- }
608
- // 注入引导上下文到 ZhinAgent
609
- if (zhinAgentInstance && contextFiles.length > 0) {
610
- const contextSection = buildBootstrapContextSection(contextFiles);
611
- zhinAgentInstance.setBootstrapContext(contextSection);
612
- }
613
- }
614
- catch (e) {
615
- logger.debug(`Bootstrap files not loaded: ${e.message}`);
616
- }
617
- // ── 第三步:常驻技能正文 + 技能 XML 摘要注入 Agent ──
618
- try {
619
- const skillsForContext = await discoverWorkspaceSkills();
620
- const alwaysContent = await loadAlwaysSkillsContent(skillsForContext);
621
- const skillsXml = buildSkillsSummaryXML(skillsForContext);
622
- if (zhinAgentInstance) {
623
- zhinAgentInstance.setActiveSkillsContext(alwaysContent);
624
- zhinAgentInstance.setSkillsSummaryXML(skillsXml);
625
- }
626
- }
627
- catch (e) {
628
- logger.debug(`Skills context not set: ${e.message}`);
629
- }
630
- // 触发 agent:bootstrap Hook
631
- const skillFeature2 = root.inject?.('skill');
632
- await triggerAIHook(createAIHookEvent('agent', 'bootstrap', undefined, {
633
- workspaceDir: process.cwd(),
634
- toolCount: builtinTools.length,
635
- skillCount: skillFeature2?.size ?? 0,
636
- bootstrapFiles: loadedFiles,
637
- }));
638
- // ── 技能目录热重载:监听 workspace + local 技能目录,防抖后重新发现并更新 ──
639
- const workspaceSkillDir = path.join(process.cwd(), 'skills');
640
- const localSkillDir = path.join(os.homedir(), '.zhin', 'skills');
641
- const onSkillDirChange = () => {
642
- if (skillReloadDebounce)
643
- clearTimeout(skillReloadDebounce);
644
- skillReloadDebounce = setTimeout(async () => {
645
- skillReloadDebounce = null;
646
- try {
647
- const count = await syncWorkspaceSkills();
648
- const skillsForContext = await discoverWorkspaceSkills();
649
- const alwaysContent = await loadAlwaysSkillsContent(skillsForContext);
650
- const skillsXml = buildSkillsSummaryXML(skillsForContext);
651
- if (zhinAgentInstance) {
652
- zhinAgentInstance.setActiveSkillsContext(alwaysContent);
653
- zhinAgentInstance.setSkillsSummaryXML(skillsXml);
654
- }
655
- await triggerAIHook(createAIHookEvent('agent', 'skills-reloaded', undefined, { skillCount: count }));
656
- if (count >= 0)
657
- logger.info(`[技能热重载] 已更新,当前工作区技能数: ${count}`);
658
- }
659
- catch (e) {
660
- logger.warn(`[技能热重载] 失败: ${e.message}`);
661
- }
662
- }, 400);
663
- };
664
- for (const dir of [workspaceSkillDir, localSkillDir]) {
665
- if (fs.existsSync(dir)) {
666
- try {
667
- const w = fs.watch(dir, { recursive: true }, onSkillDirChange);
668
- skillWatchers.push(w);
669
- logger.debug(`[技能热重载] 监听目录: ${dir}`);
670
- }
671
- catch (e) {
672
- logger.debug(`[技能热重载] 无法监听 ${dir}: ${e.message}`);
673
- }
674
- }
675
- }
676
- })();
677
- return () => {
678
- disposers.forEach(d => d());
679
- skillWatchers.forEach(w => w.close());
680
- skillWatchers = [];
681
- if (skillReloadDebounce)
682
- clearTimeout(skillReloadDebounce);
683
- };
684
- });
685
- }
686
- //# sourceMappingURL=init.js.map