@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
@@ -1,512 +0,0 @@
1
- /**
2
- * ZhinAgent — 全局持久 AI 大脑
3
- *
4
- * 核心能力:
5
- * 1. 全局单例,应用生命周期内常驻
6
- * 2. Skill 感知:两级过滤 Skill → Tool
7
- * 3. 双层记忆:per-scene(对话上下文)+ per-user(长期偏好)
8
- * 4. 任务规划:复杂请求自动分解为子步骤
9
- * 5. 多模态输出:结构化 OutputElement[]
10
- * 6. 智能路径选择:纯闲聊走轻量路径,工具请求走完整路径
11
- * 7. 用户画像:跨会话个性化记忆
12
- * 8. 速率限制:防止单用户过度消耗资源
13
- * 9. 流式输出:onChunk 回调实时推送部分文本
14
- * 10. 情绪感知:根据用户语气调整回复风格
15
- * 11. 主动跟进:schedule_followup 定时回查
16
- * 12. 多模态输入:图片/音频直接传给视觉模型
17
- */
18
-
19
- import { Logger } from '@zhin.js/logger';
20
- import type { Tool, ToolContext } from '../../types.js';
21
- import type { SkillFeature } from '../../built/skill.js';
22
- import type {
23
- AIProvider,
24
- AgentTool,
25
- ChatMessage,
26
- ContentPart,
27
- } from '../types.js';
28
- import { createAgent } from '../agent.js';
29
- import { SessionManager, createMemorySessionManager } from '../session.js';
30
- import type { ContextManager } from '../context-manager.js';
31
- import { ConversationMemory } from '../conversation-memory.js';
32
- import type { OutputElement } from '../output.js';
33
- import { parseOutput } from '../output.js';
34
- import { UserProfileStore } from '../user-profile.js';
35
- import { RateLimiter } from '../rate-limiter.js';
36
- import { detectTone } from '../tone-detector.js';
37
- import { FollowUpManager, type FollowUpSender } from '../follow-up.js';
38
- import { SubagentManager, type SubagentResultSender } from '../subagent.js';
39
- import {
40
- pruneHistoryForContext,
41
- DEFAULT_CONTEXT_TOKENS,
42
- } from '../compaction.js';
43
- import { triggerAIHook, createAIHookEvent } from '../hooks.js';
44
-
45
- // ── Sub-modules ─────────────────────────────────────────────────────
46
- import {
47
- type ZhinAgentConfig,
48
- type OnChunkCallback,
49
- DEFAULT_CONFIG,
50
- } from './config.js';
51
- import { applyExecPolicyToTools } from './exec-policy.js';
52
- import { collectRelevantTools } from './tool-collector.js';
53
- import {
54
- buildEnhancedPersona,
55
- buildContextHint,
56
- buildRichSystemPrompt,
57
- buildUserMessageWithHistory,
58
- } from './prompt.js';
59
- import {
60
- createChatHistoryTool,
61
- createUserProfileTool,
62
- createScheduleFollowUpTool,
63
- createSpawnTaskTool,
64
- } from './builtin-tools.js';
65
-
66
- // Re-export public types for backward compat
67
- export type { ZhinAgentConfig, OnChunkCallback } from './config.js';
68
-
69
- const logger = new Logger(null, 'ZhinAgent');
70
- const now = () => performance.now();
71
-
72
- // ============================================================================
73
- // ZhinAgent
74
- // ============================================================================
75
-
76
- export class ZhinAgent {
77
- private provider: AIProvider;
78
- private config: Required<ZhinAgentConfig>;
79
- private skillRegistry: SkillFeature | null = null;
80
- private sessions: SessionManager;
81
- private contextManager: ContextManager | null = null;
82
- private memory: ConversationMemory;
83
- private externalTools: Map<string, AgentTool> = new Map();
84
- private userProfiles: UserProfileStore;
85
- private rateLimiter: RateLimiter;
86
- private followUps: FollowUpManager;
87
- private subagentManager: SubagentManager | null = null;
88
- private bootstrapContext: string = '';
89
- private activeSkillsContext: string = '';
90
- private skillsSummaryXML: string = '';
91
-
92
- constructor(provider: AIProvider, config?: ZhinAgentConfig) {
93
- this.provider = provider;
94
- this.config = { ...DEFAULT_CONFIG, ...config } as Required<ZhinAgentConfig>;
95
- this.sessions = createMemorySessionManager();
96
- this.memory = new ConversationMemory({
97
- minTopicRounds: this.config.minTopicRounds,
98
- slidingWindowSize: this.config.slidingWindowSize,
99
- topicChangeThreshold: this.config.topicChangeThreshold,
100
- });
101
- this.memory.setProvider(provider);
102
- this.userProfiles = new UserProfileStore();
103
- this.rateLimiter = new RateLimiter(this.config.rateLimit);
104
- this.followUps = new FollowUpManager();
105
- }
106
-
107
- // ── DI setters ──────────────────────────────────────────────────────
108
-
109
- setSkillRegistry(registry: SkillFeature): void {
110
- this.skillRegistry = registry;
111
- logger.debug(`SkillRegistry connected (${registry.size} skills)`);
112
- }
113
-
114
- setSessionManager(manager: SessionManager): void {
115
- this.sessions.dispose();
116
- this.sessions = manager;
117
- }
118
-
119
- setContextManager(manager: ContextManager): void {
120
- this.contextManager = manager;
121
- manager.setAIProvider(this.provider);
122
- }
123
-
124
- upgradeMemoryToDatabase(msgModel: any, sumModel: any): void {
125
- this.memory.upgradeToDatabase(msgModel, sumModel);
126
- }
127
-
128
- upgradeProfilesToDatabase(model: any): void {
129
- this.userProfiles.upgradeToDatabase(model);
130
- }
131
-
132
- upgradeFollowUpsToDatabase(model: any): void {
133
- this.followUps.upgradeToDatabase(model);
134
- }
135
-
136
- setFollowUpSender(sender: FollowUpSender): void {
137
- this.followUps.setSender(sender);
138
- }
139
-
140
- async restoreFollowUps(): Promise<number> {
141
- return this.followUps.restore();
142
- }
143
-
144
- initSubagentManager(createTools: () => AgentTool[]): void {
145
- this.subagentManager = new SubagentManager({
146
- provider: this.provider,
147
- workspace: process.cwd(),
148
- createTools,
149
- maxIterations: this.config.maxSubagentIterations,
150
- execPolicyConfig: this.config,
151
- });
152
- logger.debug('SubagentManager initialized');
153
- }
154
-
155
- setSubagentSender(sender: SubagentResultSender): void {
156
- if (this.subagentManager) {
157
- this.subagentManager.setSender(sender);
158
- }
159
- }
160
-
161
- getSubagentManager(): SubagentManager | null {
162
- return this.subagentManager;
163
- }
164
-
165
- getUserProfiles(): UserProfileStore {
166
- return this.userProfiles;
167
- }
168
-
169
- registerTool(tool: AgentTool): () => void {
170
- this.externalTools.set(tool.name, tool);
171
- return () => { this.externalTools.delete(tool.name); };
172
- }
173
-
174
- setBootstrapContext(context: string): void {
175
- this.bootstrapContext = context;
176
- logger.debug(`Bootstrap context set (${context.length} chars)`);
177
- }
178
-
179
- setActiveSkillsContext(content: string): void {
180
- this.activeSkillsContext = content || '';
181
- }
182
-
183
- setSkillsSummaryXML(xml: string): void {
184
- this.skillsSummaryXML = xml || '';
185
- }
186
-
187
- // ── Core processing ─────────────────────────────────────────────────
188
-
189
- async process(
190
- content: string,
191
- context: ToolContext,
192
- externalTools: Tool[] = [],
193
- onChunk?: OnChunkCallback,
194
- ): Promise<OutputElement[]> {
195
- const t0 = now();
196
- const { senderId, sceneId, platform } = context;
197
- const sessionId = SessionManager.generateId(platform || '', senderId || '', sceneId);
198
- const userId = senderId || 'unknown';
199
-
200
- // 0. Rate limit
201
- const rateCheck = this.rateLimiter.check(userId);
202
- if (!rateCheck.allowed) {
203
- logger.debug(`[速率限制] 用户 ${userId} 被限制: ${rateCheck.message}`);
204
- return parseOutput(rateCheck.message || '请稍后再试');
205
- }
206
-
207
- triggerAIHook(createAIHookEvent('message', 'received', sessionId, {
208
- userId,
209
- content,
210
- platform: platform || '',
211
- })).catch(() => {});
212
-
213
- // 1. Collect tools
214
- const tFilter = now();
215
- const allTools = collectRelevantTools(content, context, externalTools, {
216
- config: this.config,
217
- skillRegistry: this.skillRegistry,
218
- externalRegistered: this.externalTools,
219
- });
220
-
221
- // Inject context-aware built-in tools on keyword match
222
- if (/之前|上次|历史|回忆|聊过|记录|还记得|曾经/i.test(content)) {
223
- allTools.push(createChatHistoryTool(sessionId, this.memory));
224
- }
225
- if (/偏好|设置|配置|档案|资料|时区|timezone|profile|喜好|我叫|叫我|记住我/i.test(content)) {
226
- allTools.push(createUserProfileTool(userId, this.userProfiles));
227
- }
228
- if (/提醒|定时|过一会|跟进|别忘|取消提醒|reminder|分钟后|小时后/i.test(content)) {
229
- allTools.push(createScheduleFollowUpTool(sessionId, context, this.followUps));
230
- }
231
- if (this.subagentManager && /后台|子任务|spawn|异步|background|并行|独立处理/i.test(content)) {
232
- allTools.push(createSpawnTaskTool(context, this.subagentManager));
233
- }
234
-
235
- const filterMs = (now() - tFilter).toFixed(0);
236
-
237
- // 2. History + profile
238
- const tMem = now();
239
- let historyMessages = await this.buildHistoryMessages(sessionId);
240
-
241
- const contextTokens = this.config.contextTokens ?? DEFAULT_CONTEXT_TOKENS;
242
- const maxHistoryShare = this.config.maxHistoryShare ?? 0.5;
243
- const pruneResult = pruneHistoryForContext({
244
- messages: historyMessages,
245
- maxContextTokens: contextTokens,
246
- maxHistoryShare,
247
- });
248
- historyMessages = pruneResult.messages;
249
- if (pruneResult.droppedCount > 0) {
250
- logger.debug(`[上下文窗口] 丢弃 ${pruneResult.droppedCount} 条历史消息 (${pruneResult.droppedTokens} tokens)`);
251
- }
252
-
253
- const memMs = (now() - tMem).toFixed(0);
254
-
255
- // 2.5 Profile + tone
256
- const profileSummary = await this.userProfiles.buildProfileSummary(userId);
257
- const toneHint = this.config.toneAwareness ? detectTone(content).hint : '';
258
- const personaEnhanced = buildEnhancedPersona(this.config, profileSummary, toneHint);
259
-
260
- // 3. No tools → chat path
261
- if (allTools.length === 0) {
262
- logger.info(`[System Prompt] chat-path: ${personaEnhanced.length} chars ≈ ${Math.ceil(personaEnhanced.length / 2.5)} tokens`);
263
- logger.debug(`[闲聊路径] 过滤=${filterMs}ms, 记忆=${memMs}ms (${historyMessages.length}条), 0 工具`);
264
- const tLLM = now();
265
- const reply = await this.streamChatWithHistory(content, personaEnhanced, historyMessages, onChunk);
266
- const llmMs = (now() - tLLM).toFixed(0);
267
- logger.info(`[闲聊路径] 过滤=${filterMs}ms, 记忆=${memMs}ms, LLM=${llmMs}ms, 总=${(now() - t0).toFixed(0)}ms`);
268
- await this.saveToSession(sessionId, content, reply, sceneId);
269
- return parseOutput(reply);
270
- }
271
-
272
- logger.debug(`[工具路径] 过滤=${filterMs}ms, 记忆=${memMs}ms, ${allTools.length} 工具 (${allTools.map(t => t.name).join(', ')})`);
273
-
274
- // 4. Pre-executable tools
275
- const preExecTools: AgentTool[] = [];
276
- for (const tool of allTools) {
277
- if (tool.preExecutable) preExecTools.push(tool);
278
- }
279
-
280
- // 5. Pre-execution
281
- let preData = '';
282
- if (preExecTools.length > 0) {
283
- const tPre = now();
284
- logger.debug(`预执行: ${preExecTools.map(t => t.name).join(', ')}`);
285
- const results = await Promise.allSettled(
286
- preExecTools.map(async (tool) => {
287
- const result = await Promise.race([
288
- tool.execute({}),
289
- new Promise<never>((_, rej) =>
290
- setTimeout(() => rej(new Error('超时')), this.config.preExecTimeout)),
291
- ]);
292
- return { name: tool.name, result };
293
- }),
294
- );
295
- for (const r of results) {
296
- if (r.status === 'fulfilled') {
297
- let s = typeof r.value.result === 'string' ? r.value.result : JSON.stringify(r.value.result);
298
- if (s.length > 500) {
299
- s = s.slice(0, 500) + `\n... (truncated, ${s.length} chars total)`;
300
- }
301
- preData += `\n【${r.value.name}】${s}`;
302
- }
303
- }
304
- logger.debug(`预执行耗时: ${(now() - tPre).toFixed(0)}ms`);
305
- }
306
-
307
- // 6. Path selection
308
- let reply: string;
309
- const hasNonPreExecTools = allTools.some(t => !t.preExecutable);
310
-
311
- if (!hasNonPreExecTools && preData) {
312
- // Fast path
313
- const tLLM = now();
314
- const prompt = `${personaEnhanced}
315
-
316
- 以下是根据用户问题自动获取的实时数据:
317
- ${preData}
318
-
319
- 请基于以上数据,用自然流畅的中文回答用户问题。突出重点,适当使用 emoji。`;
320
- logger.info(`[System Prompt] fast-path: ${prompt.length} chars ≈ ${Math.ceil(prompt.length / 2.5)} tokens`);
321
- reply = await this.streamChatWithHistory(content, prompt, historyMessages, onChunk);
322
- logger.info(`[快速路径] 过滤=${filterMs}ms, 记忆=${memMs}ms, LLM=${(now() - tLLM).toFixed(0)}ms, 总=${(now() - t0).toFixed(0)}ms`);
323
- } else {
324
- // Agent path
325
- const tAgent = now();
326
- logger.debug(`Agent 路径: ${allTools.length} 个工具`);
327
- const contextHint = buildContextHint(context, content);
328
-
329
- const richPrompt = buildRichSystemPrompt({
330
- config: this.config,
331
- skillRegistry: this.skillRegistry,
332
- skillsSummaryXML: this.skillsSummaryXML,
333
- activeSkillsContext: this.activeSkillsContext,
334
- bootstrapContext: this.bootstrapContext,
335
- });
336
- const systemPrompt = `${richPrompt}
337
- ${contextHint}
338
- ${preData ? `\n已获取数据:${preData}\n` : ''}`;
339
-
340
- const promptChars = systemPrompt.length;
341
- const estimatedTokens = Math.ceil(promptChars / 2.5);
342
- logger.info(`[System Prompt] ${promptChars} chars ≈ ${estimatedTokens} tokens`);
343
- logger.debug(`[System Prompt Preview]\n${systemPrompt.slice(0, 500)}...\n---END PREVIEW---`);
344
-
345
- const agentTools = applyExecPolicyToTools(this.config, allTools);
346
-
347
- // Adaptive maxIterations: boost when skills are active (multi-step skill flows)
348
- const SKILL_ITERATION_BOOST = 3;
349
- const hasSkillActivation = agentTools.some(t => t.name === 'activate_skill' || t.name === 'install_skill');
350
- const effectiveMaxIterations = hasSkillActivation
351
- ? this.config.maxIterations + SKILL_ITERATION_BOOST
352
- : this.config.maxIterations;
353
-
354
- const agent = createAgent(this.provider, {
355
- systemPrompt,
356
- tools: agentTools,
357
- maxIterations: effectiveMaxIterations,
358
- });
359
-
360
- const userMessageWithHistory = buildUserMessageWithHistory(historyMessages, content);
361
- const result = await agent.run(userMessageWithHistory, []);
362
- reply = result.content || this.fallbackFormat(result.toolCalls);
363
- logger.info(`[Agent 路径] 过滤=${filterMs}ms, 记忆=${memMs}ms, Agent=${(now() - tAgent).toFixed(0)}ms, 总=${(now() - t0).toFixed(0)}ms`);
364
- }
365
-
366
- await this.saveToSession(sessionId, content, reply, sceneId);
367
-
368
- triggerAIHook(createAIHookEvent('message', 'sent', sessionId, {
369
- userId,
370
- content: reply,
371
- platform: platform || '',
372
- })).catch(() => {});
373
-
374
- return parseOutput(reply);
375
- }
376
-
377
- async processMultimodal(
378
- parts: ContentPart[],
379
- context: ToolContext,
380
- onChunk?: OnChunkCallback,
381
- ): Promise<OutputElement[]> {
382
- const { senderId, sceneId, platform } = context;
383
- const sessionId = SessionManager.generateId(platform || '', senderId || '', sceneId);
384
- const userId = senderId || 'unknown';
385
-
386
- const rateCheck = this.rateLimiter.check(userId);
387
- if (!rateCheck.allowed) {
388
- return parseOutput(rateCheck.message || '请稍后再试');
389
- }
390
-
391
- const historyMessages = await this.buildHistoryMessages(sessionId);
392
- const profileSummary = await this.userProfiles.buildProfileSummary(userId);
393
- const personaEnhanced = buildEnhancedPersona(this.config, profileSummary, '');
394
-
395
- const textContent = parts
396
- .filter((p): p is Extract<ContentPart, { type: 'text' }> => p.type === 'text')
397
- .map(p => p.text)
398
- .join(' ') || '[多模态消息]';
399
-
400
- const visionModel = this.config.visionModel || this.provider.models[0];
401
-
402
- const messages: ChatMessage[] = [
403
- { role: 'system', content: personaEnhanced },
404
- ...historyMessages,
405
- { role: 'user', content: parts },
406
- ];
407
-
408
- let reply = '';
409
- try {
410
- for await (const chunk of this.provider.chatStream({ model: visionModel, messages })) {
411
- const delta = chunk.choices?.[0]?.delta?.content;
412
- if (delta && typeof delta === 'string') {
413
- reply += delta;
414
- if (onChunk) onChunk(delta, reply);
415
- }
416
- }
417
- } catch {
418
- const response = await this.provider.chat({ model: visionModel, messages });
419
- const msg = response.choices[0]?.message?.content;
420
- reply = typeof msg === 'string' ? msg : '';
421
- }
422
-
423
- if (!reply) reply = '抱歉,我无法理解这张图片。';
424
- await this.saveToSession(sessionId, textContent, reply, sceneId);
425
- return parseOutput(reply);
426
- }
427
-
428
- // ── Internal helpers ────────────────────────────────────────────────
429
-
430
- private async buildHistoryMessages(sessionId: string): Promise<ChatMessage[]> {
431
- return this.memory.buildContext(sessionId);
432
- }
433
-
434
- private async streamChatWithHistory(
435
- content: string,
436
- systemPrompt: string,
437
- history: ChatMessage[],
438
- onChunk?: OnChunkCallback,
439
- ): Promise<string> {
440
- const model = this.provider.models[0];
441
- const userContent = history.length > 0
442
- ? buildUserMessageWithHistory(history, content)
443
- : content;
444
- const messages: ChatMessage[] = [
445
- { role: 'system', content: systemPrompt },
446
- { role: 'user', content: userContent },
447
- ];
448
-
449
- try {
450
- let result = '';
451
- for await (const chunk of this.provider.chatStream({ model, messages })) {
452
- const delta = chunk.choices?.[0]?.delta?.content;
453
- if (delta && typeof delta === 'string') {
454
- result += delta;
455
- if (onChunk) onChunk(delta, result);
456
- }
457
- }
458
- return result;
459
- } catch {
460
- const response = await this.provider.chat({ model, messages });
461
- const msg = response.choices[0]?.message?.content;
462
- const result = typeof msg === 'string' ? msg : '';
463
- if (onChunk && result) onChunk(result, result);
464
- return result;
465
- }
466
- }
467
-
468
- private async saveToSession(
469
- sessionId: string,
470
- userContent: string,
471
- assistantContent: string,
472
- sceneId?: string,
473
- ): Promise<void> {
474
- await this.memory.saveRound(sessionId, userContent, assistantContent);
475
- await this.sessions.addMessage(sessionId, { role: 'user', content: userContent });
476
- await this.sessions.addMessage(sessionId, { role: 'assistant', content: assistantContent });
477
- if (this.contextManager && sceneId) {
478
- this.contextManager.autoSummarizeIfNeeded(sceneId).catch(() => {});
479
- }
480
- }
481
-
482
- private fallbackFormat(toolCalls: { tool: string; args: any; result: any }[]): string {
483
- if (toolCalls.length === 0) return '处理完成。';
484
- const userFacing = toolCalls.filter(tc => tc.tool !== 'activate_skill');
485
- if (userFacing.length === 0) {
486
- return '技能已激活但未能完成后续操作,请重试或换一种方式描述你的需求。';
487
- }
488
- return userFacing.map(tc => {
489
- const s = typeof tc.result === 'string' ? tc.result : JSON.stringify(tc.result, null, 2);
490
- return `【${tc.tool}】\n${s}`;
491
- }).join('\n\n');
492
- }
493
-
494
- // ── Lifecycle ───────────────────────────────────────────────────────
495
-
496
- isReady(): boolean {
497
- return true;
498
- }
499
-
500
- dispose(): void {
501
- this.memory.dispose();
502
- this.sessions.dispose();
503
- this.externalTools.clear();
504
- this.userProfiles.dispose();
505
- this.rateLimiter.dispose();
506
- this.followUps.dispose();
507
- if (this.subagentManager) {
508
- this.subagentManager.dispose();
509
- this.subagentManager = null;
510
- }
511
- }
512
- }
@@ -1,131 +0,0 @@
1
- /**
2
- * ZhinAgent System Prompt builder + message helpers
3
- */
4
-
5
- import * as path from 'path';
6
- import type { ContentPart } from '../types.js';
7
- import type { SkillFeature } from '../../built/skill.js';
8
- import type { ZhinAgentConfig } from './config.js';
9
- import type { ToolContext } from '../../types.js';
10
- import { SECTION_SEP, HISTORY_CONTEXT_MARKER, CURRENT_MESSAGE_MARKER } from './config.js';
11
- import type { ChatMessage } from '../types.js';
12
- import { getFileMemoryContext } from '../bootstrap.js';
13
-
14
- export function contentToText(c: string | ContentPart[]): string {
15
- if (typeof c === 'string') return c;
16
- return (c as ContentPart[]).map(p => (p.type === 'text' ? p.text : '')).join('');
17
- }
18
-
19
- export function buildUserMessageWithHistory(history: ChatMessage[], currentContent: string): string {
20
- if (history.length === 0) return currentContent;
21
- const roleLabel = (role: string) => (role === 'user' ? 'User' : role === 'assistant' ? 'Assistant' : 'System');
22
- const lines = history
23
- .filter(m => m.role === 'user' || m.role === 'assistant' || m.role === 'system')
24
- .map(m => `${roleLabel(m.role)}: ${contentToText(m.content)}`);
25
- const historyBlock = lines.join('\n');
26
- return `${HISTORY_CONTEXT_MARKER}\n${historyBlock}\n\n${CURRENT_MESSAGE_MARKER}\n${currentContent}`;
27
- }
28
-
29
- export function buildEnhancedPersona(
30
- config: Required<ZhinAgentConfig>,
31
- profileSummary: string,
32
- toneHint: string,
33
- ): string {
34
- let persona = config.persona;
35
- if (profileSummary) {
36
- persona += `\n\n${profileSummary}`;
37
- }
38
- if (toneHint) {
39
- persona += `\n\n[Tone hint] ${toneHint}`;
40
- }
41
- const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
42
- const timeStr = new Date().toLocaleString('zh-CN', { timeZone: tz });
43
- persona += `\n\nCurrent time: ${timeStr} (${tz})`;
44
- return persona;
45
- }
46
-
47
- export function buildContextHint(context: ToolContext, _content: string): string {
48
- const parts: string[] = [];
49
- if (context.platform) parts.push(`platform:${context.platform}`);
50
- if (context.botId) parts.push(`bot:${context.botId}`);
51
- if (context.senderId) parts.push(`user:${context.senderId}`);
52
- if (context.scope) parts.push(`scope:${context.scope}`);
53
- if (context.sceneId) parts.push(`scene:${context.sceneId}`);
54
- if (parts.length === 0) return '';
55
- return `\nContext: ${parts.join(' | ')}`;
56
- }
57
-
58
- export interface RichSystemPromptContext {
59
- config: Required<ZhinAgentConfig>;
60
- skillRegistry: SkillFeature | null;
61
- skillsSummaryXML: string;
62
- activeSkillsContext: string;
63
- bootstrapContext: string;
64
- }
65
-
66
- export function buildRichSystemPrompt(ctx: RichSystemPromptContext): string {
67
- const { config, skillRegistry, skillsSummaryXML, activeSkillsContext, bootstrapContext } = ctx;
68
- const parts: string[] = [];
69
- const cwd = process.cwd();
70
- const dataDir = path.join(cwd, 'data');
71
-
72
- // §1 Identity
73
- const now = new Date();
74
- const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
75
- const timeStr = now.toLocaleString('zh-CN', { timeZone: tz });
76
- const memoryDir = path.join(dataDir, 'memory');
77
- const todayStr = now.toISOString().split('T')[0];
78
- parts.push([
79
- config.persona,
80
- '',
81
- `Current time: ${timeStr} (${tz})`,
82
- `Workspace: ${cwd}`,
83
- `Data dir: ${dataDir}`,
84
- `Long-term memory: ${path.join(memoryDir, 'MEMORY.md')}; today's notes: ${path.join(memoryDir, todayStr + '.md')}. Use write_file to persist important info.`,
85
- ].join('\n'));
86
-
87
- // §2 Rules
88
- parts.push([
89
- '## Rules',
90
- '1. Call tools directly — do not describe steps or explain intent',
91
- '2. For time/date questions, use "Current time" above — no tool needed',
92
- '3. File changes must use edit_file/write_file — never give manual instructions',
93
- '4. After activate_skill returns, continue calling the tools it specifies — do not stop',
94
- '5. All answers must be based on actual tool output',
95
- '6. On tool failure, try alternatives — do not dump raw errors to user',
96
- '7. Answer based on the user\'s **last message** only; prior messages are context',
97
- '8. Use spawn_task for long/complex independent tasks — do not block the conversation',
98
- '9. When user asks to install/learn a skill from URL, use install_skill(url) then activate_skill',
99
- ].join('\n'));
100
-
101
- // §3 Skills
102
- if (skillsSummaryXML) {
103
- parts.push('## Available Skills\n\n' + skillsSummaryXML + '\n\nUser mentions skill → activate_skill(name) → follow returned instructions');
104
- } else if (skillRegistry && skillRegistry.size > 0) {
105
- const skills = skillRegistry.getAll();
106
- const lines: string[] = ['## Available Skills'];
107
- for (const skill of skills) {
108
- lines.push(`- ${skill.name}: ${skill.description}`);
109
- }
110
- lines.push('User mentions skill → activate_skill(name) → follow returned instructions');
111
- parts.push(lines.join('\n'));
112
- }
113
-
114
- // §4 Active skills
115
- if (activeSkillsContext) {
116
- parts.push('## Active Skills\n\n' + activeSkillsContext);
117
- }
118
-
119
- // §5 Memory
120
- const fileMemory = getFileMemoryContext();
121
- if (fileMemory) {
122
- parts.push('## Memory\n\n' + fileMemory);
123
- }
124
-
125
- // §6 Bootstrap
126
- if (bootstrapContext) {
127
- parts.push(bootstrapContext);
128
- }
129
-
130
- return parts.filter(Boolean).join(SECTION_SEP);
131
- }