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