@zhin.js/core 1.0.32 → 1.0.34
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/lib/ai/agent.d.ts.map +1 -1
- package/lib/ai/agent.js +15 -2
- package/lib/ai/agent.js.map +1 -1
- package/lib/ai/bootstrap.d.ts +11 -2
- package/lib/ai/bootstrap.d.ts.map +1 -1
- package/lib/ai/bootstrap.js +46 -2
- package/lib/ai/bootstrap.js.map +1 -1
- package/lib/ai/builtin-tools.d.ts +28 -6
- package/lib/ai/builtin-tools.d.ts.map +1 -1
- package/lib/ai/builtin-tools.js +265 -76
- package/lib/ai/builtin-tools.js.map +1 -1
- package/lib/ai/index.d.ts +9 -1
- package/lib/ai/index.d.ts.map +1 -1
- package/lib/ai/index.js +8 -0
- package/lib/ai/index.js.map +1 -1
- package/lib/ai/init.d.ts.map +1 -1
- package/lib/ai/init.js +84 -3
- package/lib/ai/init.js.map +1 -1
- package/lib/ai/providers/anthropic.d.ts +7 -0
- package/lib/ai/providers/anthropic.d.ts.map +1 -1
- package/lib/ai/providers/anthropic.js +3 -0
- package/lib/ai/providers/anthropic.js.map +1 -1
- package/lib/ai/providers/ollama.d.ts +10 -0
- package/lib/ai/providers/ollama.d.ts.map +1 -1
- package/lib/ai/providers/ollama.js +11 -3
- package/lib/ai/providers/ollama.js.map +1 -1
- package/lib/ai/providers/openai.d.ts +7 -0
- package/lib/ai/providers/openai.d.ts.map +1 -1
- package/lib/ai/providers/openai.js +3 -0
- package/lib/ai/providers/openai.js.map +1 -1
- package/lib/ai/service.d.ts +4 -0
- package/lib/ai/service.d.ts.map +1 -1
- package/lib/ai/service.js +7 -0
- package/lib/ai/service.js.map +1 -1
- package/lib/ai/subagent.d.ts +50 -0
- package/lib/ai/subagent.d.ts.map +1 -0
- package/lib/ai/subagent.js +144 -0
- package/lib/ai/subagent.js.map +1 -0
- package/lib/ai/types.d.ts +25 -5
- package/lib/ai/types.d.ts.map +1 -1
- package/lib/ai/zhin-agent-builtin-tools.d.ts +17 -0
- package/lib/ai/zhin-agent-builtin-tools.d.ts.map +1 -0
- package/lib/ai/zhin-agent-builtin-tools.js +220 -0
- package/lib/ai/zhin-agent-builtin-tools.js.map +1 -0
- package/lib/ai/zhin-agent-config.d.ts +54 -0
- package/lib/ai/zhin-agent-config.d.ts.map +1 -0
- package/lib/ai/zhin-agent-config.js +76 -0
- package/lib/ai/zhin-agent-config.js.map +1 -0
- package/lib/ai/zhin-agent-exec-policy.d.ts +20 -0
- package/lib/ai/zhin-agent-exec-policy.d.ts.map +1 -0
- package/lib/ai/zhin-agent-exec-policy.js +71 -0
- package/lib/ai/zhin-agent-exec-policy.js.map +1 -0
- package/lib/ai/zhin-agent-prompt.d.ts +21 -0
- package/lib/ai/zhin-agent-prompt.d.ts.map +1 -0
- package/lib/ai/zhin-agent-prompt.js +116 -0
- package/lib/ai/zhin-agent-prompt.js.map +1 -0
- package/lib/ai/zhin-agent-tool-collector.d.ts +22 -0
- package/lib/ai/zhin-agent-tool-collector.d.ts.map +1 -0
- package/lib/ai/zhin-agent-tool-collector.js +218 -0
- package/lib/ai/zhin-agent-tool-collector.js.map +1 -0
- package/lib/ai/zhin-agent.d.ts +11 -155
- package/lib/ai/zhin-agent.d.ts.map +1 -1
- package/lib/ai/zhin-agent.js +84 -684
- package/lib/ai/zhin-agent.js.map +1 -1
- package/lib/component.d.ts.map +1 -1
- package/lib/component.js +19 -19
- package/lib/component.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/scheduler/index.d.ts +10 -0
- package/lib/scheduler/index.d.ts.map +1 -0
- package/lib/scheduler/index.js +12 -0
- package/lib/scheduler/index.js.map +1 -0
- package/lib/scheduler/scheduler.d.ts +49 -0
- package/lib/scheduler/scheduler.d.ts.map +1 -0
- package/lib/scheduler/scheduler.js +352 -0
- package/lib/scheduler/scheduler.js.map +1 -0
- package/lib/scheduler/types.d.ts +71 -0
- package/lib/scheduler/types.d.ts.map +1 -0
- package/lib/scheduler/types.js +8 -0
- package/lib/scheduler/types.js.map +1 -0
- package/lib/tool-zod.d.ts +28 -0
- package/lib/tool-zod.d.ts.map +1 -0
- package/lib/tool-zod.js +98 -0
- package/lib/tool-zod.js.map +1 -0
- package/package.json +9 -4
- package/src/ai/agent.ts +15 -2
- package/src/ai/bootstrap.ts +48 -2
- package/src/ai/builtin-tools.ts +283 -75
- package/src/ai/index.ts +19 -1
- package/src/ai/init.ts +85 -3
- package/src/ai/providers/anthropic.ts +3 -0
- package/src/ai/providers/ollama.ts +13 -3
- package/src/ai/providers/openai.ts +3 -0
- package/src/ai/service.ts +8 -0
- package/src/ai/subagent.ts +209 -0
- package/src/ai/types.ts +29 -2
- package/src/ai/zhin-agent-builtin-tools.ts +247 -0
- package/src/ai/zhin-agent-config.ts +113 -0
- package/src/ai/zhin-agent-exec-policy.ts +78 -0
- package/src/ai/zhin-agent-prompt.ts +136 -0
- package/src/ai/zhin-agent-tool-collector.ts +243 -0
- package/src/ai/zhin-agent.ts +113 -791
- package/src/component.ts +29 -28
- package/src/index.ts +1 -0
- package/src/scheduler/index.ts +28 -0
- package/src/scheduler/scheduler.ts +372 -0
- package/src/scheduler/types.ts +74 -0
- package/src/tool-zod.ts +115 -0
- package/tests/ai/subagent.test.ts +270 -0
package/src/ai/zhin-agent.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ZhinAgent — 全局持久 AI 大脑
|
|
3
3
|
*
|
|
4
|
-
* 取代旧的 AIService.process() 临时创建 Agent 的方式。
|
|
5
|
-
*
|
|
6
4
|
* 核心能力:
|
|
7
5
|
* 1. 全局单例,应用生命周期内常驻
|
|
8
6
|
* 2. Skill 感知:两级过滤 Skill → Tool
|
|
@@ -27,140 +25,49 @@ import type {
|
|
|
27
25
|
ChatMessage,
|
|
28
26
|
ContentPart,
|
|
29
27
|
} from './types.js';
|
|
30
|
-
import {
|
|
28
|
+
import { createAgent } from './agent.js';
|
|
31
29
|
import { SessionManager, createMemorySessionManager } from './session.js';
|
|
32
30
|
import type { ContextManager } from './context-manager.js';
|
|
33
31
|
import { ConversationMemory } from './conversation-memory.js';
|
|
34
32
|
import type { OutputElement } from './output.js';
|
|
35
33
|
import { parseOutput } from './output.js';
|
|
36
34
|
import { UserProfileStore } from './user-profile.js';
|
|
37
|
-
import { RateLimiter
|
|
35
|
+
import { RateLimiter } from './rate-limiter.js';
|
|
38
36
|
import { detectTone } from './tone-detector.js';
|
|
39
37
|
import { FollowUpManager, type FollowUpSender } from './follow-up.js';
|
|
38
|
+
import { SubagentManager, type SubagentResultSender } from './subagent.js';
|
|
40
39
|
import {
|
|
41
|
-
compactSession,
|
|
42
|
-
estimateMessagesTokens,
|
|
43
40
|
pruneHistoryForContext,
|
|
44
|
-
resolveContextWindowTokens,
|
|
45
|
-
evaluateContextWindowGuard,
|
|
46
41
|
DEFAULT_CONTEXT_TOKENS,
|
|
47
42
|
} from './compaction.js';
|
|
48
43
|
import { triggerAIHook, createAIHookEvent } from './hooks.js';
|
|
49
44
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const historyBlock = lines.join('\n');
|
|
71
|
-
return `${HISTORY_CONTEXT_MARKER}\n${historyBlock}\n\n${CURRENT_MESSAGE_MARKER}\n${currentContent}`;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// ============================================================================
|
|
75
|
-
// 配置
|
|
76
|
-
// ============================================================================
|
|
77
|
-
|
|
78
|
-
export interface ZhinAgentConfig {
|
|
79
|
-
/** 默认系统人格 */
|
|
80
|
-
persona?: string;
|
|
81
|
-
/** 最大工具调用轮数 */
|
|
82
|
-
maxIterations?: number;
|
|
83
|
-
/** 单次请求超时 (ms) */
|
|
84
|
-
timeout?: number;
|
|
85
|
-
/** 预执行超时 (ms) */
|
|
86
|
-
preExecTimeout?: number;
|
|
87
|
-
/** Skill 选择最大数量 */
|
|
88
|
-
maxSkills?: number;
|
|
89
|
-
/** Tool 选择最大数量 */
|
|
90
|
-
maxTools?: number;
|
|
91
|
-
/** 一个话题至少持续多少轮才触发摘要(默认 5) */
|
|
92
|
-
minTopicRounds?: number;
|
|
93
|
-
/** 滑动窗口大小:最近 N 轮消息(默认 5) */
|
|
94
|
-
slidingWindowSize?: number;
|
|
95
|
-
/** 话题切换检测阈值(0-1,值越低越敏感,默认 0.15) */
|
|
96
|
-
topicChangeThreshold?: number;
|
|
97
|
-
/** 速率限制配置 */
|
|
98
|
-
rateLimit?: RateLimitConfig;
|
|
99
|
-
/** 是否启用情绪感知(默认 true) */
|
|
100
|
-
toneAwareness?: boolean;
|
|
101
|
-
/** 视觉模型名称(如 llava, bakllava),留空则不启用视觉 */
|
|
102
|
-
visionModel?: string;
|
|
103
|
-
/** 上下文窗口 token 数(默认 128000) */
|
|
104
|
-
contextTokens?: number;
|
|
105
|
-
/** 历史记录最大占比(默认 0.5 = 50%) */
|
|
106
|
-
maxHistoryShare?: number;
|
|
107
|
-
/** 禁用的工具名列表(来自配置,这些工具不会下发给 AI) */
|
|
108
|
-
disabledTools?: string[];
|
|
109
|
-
/** 仅允许的工具名列表;若设置则只下发列表中的工具(与 disabledTools 二选一,allowedTools 优先) */
|
|
110
|
-
allowedTools?: string[];
|
|
111
|
-
/** bash 执行策略:deny=禁止,allowlist=仅允许列表内,full=不限制 */
|
|
112
|
-
execSecurity?: 'deny' | 'allowlist' | 'full';
|
|
113
|
-
/** allowlist 模式下允许的命令(正则字符串) */
|
|
114
|
-
execAllowlist?: string[];
|
|
115
|
-
/** allowlist 未命中时 true=需审批(当前为拒绝并提示),false=直接拒绝 */
|
|
116
|
-
execAsk?: boolean;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const DEFAULT_CONFIG: Required<ZhinAgentConfig> = {
|
|
120
|
-
persona: '你是一个友好的中文 AI 助手,擅长使用工具帮助用户解决问题。',
|
|
121
|
-
maxIterations: 5,
|
|
122
|
-
timeout: 60_000,
|
|
123
|
-
preExecTimeout: 10_000,
|
|
124
|
-
maxSkills: 3,
|
|
125
|
-
maxTools: 8,
|
|
126
|
-
minTopicRounds: 5,
|
|
127
|
-
slidingWindowSize: 5,
|
|
128
|
-
topicChangeThreshold: 0.15,
|
|
129
|
-
rateLimit: {},
|
|
130
|
-
toneAwareness: true,
|
|
131
|
-
visionModel: '',
|
|
132
|
-
contextTokens: DEFAULT_CONTEXT_TOKENS,
|
|
133
|
-
maxHistoryShare: 0.5,
|
|
134
|
-
disabledTools: [],
|
|
135
|
-
allowedTools: [], // 空数组表示不限制;非空时仅允许列表中的工具
|
|
136
|
-
execSecurity: 'deny', // 默认禁止 bash,避免误用
|
|
137
|
-
execAllowlist: [],
|
|
138
|
-
execAsk: false,
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
// ============================================================================
|
|
142
|
-
// 流式回调
|
|
143
|
-
// ============================================================================
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* 流式输出回调 — 适配器可通过此回调实时更新消息
|
|
147
|
-
*
|
|
148
|
-
* @param chunk 增量文本片段
|
|
149
|
-
* @param full 到目前为止的完整文本
|
|
150
|
-
*/
|
|
151
|
-
export type OnChunkCallback = (chunk: string, full: string) => void;
|
|
45
|
+
// ── Extracted modules ───────────────────────────────────────────────
|
|
46
|
+
import {
|
|
47
|
+
type ZhinAgentConfig,
|
|
48
|
+
type OnChunkCallback,
|
|
49
|
+
DEFAULT_CONFIG,
|
|
50
|
+
} from './zhin-agent-config.js';
|
|
51
|
+
import { applyExecPolicyToTools } from './zhin-agent-exec-policy.js';
|
|
52
|
+
import { collectRelevantTools } from './zhin-agent-tool-collector.js';
|
|
53
|
+
import {
|
|
54
|
+
buildEnhancedPersona,
|
|
55
|
+
buildContextHint,
|
|
56
|
+
buildRichSystemPrompt,
|
|
57
|
+
buildUserMessageWithHistory,
|
|
58
|
+
} from './zhin-agent-prompt.js';
|
|
59
|
+
import {
|
|
60
|
+
createChatHistoryTool,
|
|
61
|
+
createUserProfileTool,
|
|
62
|
+
createScheduleFollowUpTool,
|
|
63
|
+
createSpawnTaskTool,
|
|
64
|
+
} from './zhin-agent-builtin-tools.js';
|
|
152
65
|
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
// ============================================================================
|
|
66
|
+
// Re-export public types for backward compat
|
|
67
|
+
export type { ZhinAgentConfig, OnChunkCallback } from './zhin-agent-config.js';
|
|
156
68
|
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
group_admin: 1,
|
|
160
|
-
group_owner: 2,
|
|
161
|
-
bot_admin: 3,
|
|
162
|
-
owner: 4,
|
|
163
|
-
};
|
|
69
|
+
const logger = new Logger(null, 'ZhinAgent');
|
|
70
|
+
const now = () => performance.now();
|
|
164
71
|
|
|
165
72
|
// ============================================================================
|
|
166
73
|
// ZhinAgent
|
|
@@ -177,8 +84,10 @@ export class ZhinAgent {
|
|
|
177
84
|
private userProfiles: UserProfileStore;
|
|
178
85
|
private rateLimiter: RateLimiter;
|
|
179
86
|
private followUps: FollowUpManager;
|
|
180
|
-
|
|
87
|
+
private subagentManager: SubagentManager | null = null;
|
|
181
88
|
private bootstrapContext: string = '';
|
|
89
|
+
private activeSkillsContext: string = '';
|
|
90
|
+
private skillsSummaryXML: string = '';
|
|
182
91
|
|
|
183
92
|
constructor(provider: AIProvider, config?: ZhinAgentConfig) {
|
|
184
93
|
this.provider = provider;
|
|
@@ -195,7 +104,7 @@ export class ZhinAgent {
|
|
|
195
104
|
this.followUps = new FollowUpManager();
|
|
196
105
|
}
|
|
197
106
|
|
|
198
|
-
// ──
|
|
107
|
+
// ── DI setters ──────────────────────────────────────────────────────
|
|
199
108
|
|
|
200
109
|
setSkillRegistry(registry: SkillFeature): void {
|
|
201
110
|
this.skillRegistry = registry;
|
|
@@ -212,35 +121,47 @@ export class ZhinAgent {
|
|
|
212
121
|
manager.setAIProvider(this.provider);
|
|
213
122
|
}
|
|
214
123
|
|
|
215
|
-
/** 将 ConversationMemory 升级为数据库存储 */
|
|
216
124
|
upgradeMemoryToDatabase(msgModel: any, sumModel: any): void {
|
|
217
125
|
this.memory.upgradeToDatabase(msgModel, sumModel);
|
|
218
126
|
}
|
|
219
127
|
|
|
220
|
-
/** 将 UserProfileStore 升级为数据库存储 */
|
|
221
128
|
upgradeProfilesToDatabase(model: any): void {
|
|
222
129
|
this.userProfiles.upgradeToDatabase(model);
|
|
223
130
|
}
|
|
224
131
|
|
|
225
|
-
/** 将 FollowUpManager 升级为数据库存储 */
|
|
226
132
|
upgradeFollowUpsToDatabase(model: any): void {
|
|
227
133
|
this.followUps.upgradeToDatabase(model);
|
|
228
134
|
}
|
|
229
135
|
|
|
230
|
-
/** 注入提醒消息发送回调(由 init.ts 在适配器就绪后调用) */
|
|
231
136
|
setFollowUpSender(sender: FollowUpSender): void {
|
|
232
137
|
this.followUps.setSender(sender);
|
|
233
138
|
}
|
|
234
139
|
|
|
235
|
-
/**
|
|
236
|
-
* 从数据库恢复未完成的跟进任务(启动时调用)
|
|
237
|
-
* @returns 恢复的任务数量
|
|
238
|
-
*/
|
|
239
140
|
async restoreFollowUps(): Promise<number> {
|
|
240
141
|
return this.followUps.restore();
|
|
241
142
|
}
|
|
242
143
|
|
|
243
|
-
|
|
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
|
+
|
|
244
165
|
getUserProfiles(): UserProfileStore {
|
|
245
166
|
return this.userProfiles;
|
|
246
167
|
}
|
|
@@ -250,37 +171,21 @@ export class ZhinAgent {
|
|
|
250
171
|
return () => { this.externalTools.delete(tool.name); };
|
|
251
172
|
}
|
|
252
173
|
|
|
253
|
-
/**
|
|
254
|
-
* 注入引导文件上下文(SOUL.md + TOOLS.md + AGENTS.md 的合并内容)
|
|
255
|
-
* 由 init.ts 在加载引导文件后调用
|
|
256
|
-
*/
|
|
257
174
|
setBootstrapContext(context: string): void {
|
|
258
175
|
this.bootstrapContext = context;
|
|
259
176
|
logger.debug(`Bootstrap context set (${context.length} chars)`);
|
|
260
177
|
}
|
|
261
178
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
* 路径选择策略(按开销从低到高):
|
|
273
|
-
*
|
|
274
|
-
* ┌─ 闲聊路径(最快)────────────────────────────────────────────┐
|
|
275
|
-
* │ 工具过滤 = 0 → 仅 persona prompt → 流式 1 次 LLM 调用 │
|
|
276
|
-
* └──────────────────────────────────────────────────────────────┘
|
|
277
|
-
* ┌─ 快速路径(1 轮 LLM)───────────────────────────────────────┐
|
|
278
|
-
* │ 全部命中无参数工具 → 预执行 → 数据注入 prompt → 1 次 LLM │
|
|
279
|
-
* └──────────────────────────────────────────────────────────────┘
|
|
280
|
-
* ┌─ Agent 路径(多轮 LLM)─────────────────────────────────────┐
|
|
281
|
-
* │ 存在需参数工具 → Agent tool-calling → 多轮 LLM │
|
|
282
|
-
* └──────────────────────────────────────────────────────────────┘
|
|
283
|
-
*/
|
|
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
|
+
|
|
284
189
|
async process(
|
|
285
190
|
content: string,
|
|
286
191
|
context: ToolContext,
|
|
@@ -292,42 +197,47 @@ export class ZhinAgent {
|
|
|
292
197
|
const sessionId = SessionManager.generateId(platform || '', senderId || '', sceneId);
|
|
293
198
|
const userId = senderId || 'unknown';
|
|
294
199
|
|
|
295
|
-
//
|
|
200
|
+
// 0. Rate limit
|
|
296
201
|
const rateCheck = this.rateLimiter.check(userId);
|
|
297
202
|
if (!rateCheck.allowed) {
|
|
298
203
|
logger.debug(`[速率限制] 用户 ${userId} 被限制: ${rateCheck.message}`);
|
|
299
204
|
return parseOutput(rateCheck.message || '请稍后再试');
|
|
300
205
|
}
|
|
301
206
|
|
|
302
|
-
// 触发 message:received hook
|
|
303
207
|
triggerAIHook(createAIHookEvent('message', 'received', sessionId, {
|
|
304
208
|
userId,
|
|
305
209
|
content,
|
|
306
210
|
platform: platform || '',
|
|
307
211
|
})).catch(() => {});
|
|
308
212
|
|
|
309
|
-
//
|
|
213
|
+
// 1. Collect tools
|
|
310
214
|
const tFilter = now();
|
|
311
|
-
const allTools =
|
|
215
|
+
const allTools = collectRelevantTools(content, context, externalTools, {
|
|
216
|
+
config: this.config,
|
|
217
|
+
skillRegistry: this.skillRegistry,
|
|
218
|
+
externalRegistered: this.externalTools,
|
|
219
|
+
});
|
|
312
220
|
|
|
313
|
-
//
|
|
221
|
+
// Inject context-aware built-in tools on keyword match
|
|
314
222
|
if (/之前|上次|历史|回忆|聊过|记录|还记得|曾经/i.test(content)) {
|
|
315
|
-
allTools.push(
|
|
223
|
+
allTools.push(createChatHistoryTool(sessionId, this.memory));
|
|
316
224
|
}
|
|
317
225
|
if (/偏好|设置|配置|档案|资料|时区|timezone|profile|喜好|我叫|叫我|记住我/i.test(content)) {
|
|
318
|
-
allTools.push(
|
|
226
|
+
allTools.push(createUserProfileTool(userId, this.userProfiles));
|
|
319
227
|
}
|
|
320
228
|
if (/提醒|定时|过一会|跟进|别忘|取消提醒|reminder|分钟后|小时后/i.test(content)) {
|
|
321
|
-
allTools.push(
|
|
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));
|
|
322
233
|
}
|
|
323
234
|
|
|
324
235
|
const filterMs = (now() - tFilter).toFixed(0);
|
|
325
236
|
|
|
326
|
-
//
|
|
237
|
+
// 2. History + profile
|
|
327
238
|
const tMem = now();
|
|
328
239
|
let historyMessages = await this.buildHistoryMessages(sessionId);
|
|
329
240
|
|
|
330
|
-
// 上下文窗口保护:按 token 预算修剪历史(借鉴 OpenClaw context-window-guard)
|
|
331
241
|
const contextTokens = this.config.contextTokens ?? DEFAULT_CONTEXT_TOKENS;
|
|
332
242
|
const maxHistoryShare = this.config.maxHistoryShare ?? 0.5;
|
|
333
243
|
const pruneResult = pruneHistoryForContext({
|
|
@@ -342,12 +252,12 @@ export class ZhinAgent {
|
|
|
342
252
|
|
|
343
253
|
const memMs = (now() - tMem).toFixed(0);
|
|
344
254
|
|
|
345
|
-
//
|
|
255
|
+
// 2.5 Profile + tone
|
|
346
256
|
const profileSummary = await this.userProfiles.buildProfileSummary(userId);
|
|
347
257
|
const toneHint = this.config.toneAwareness ? detectTone(content).hint : '';
|
|
348
|
-
const personaEnhanced = this.
|
|
258
|
+
const personaEnhanced = buildEnhancedPersona(this.config, profileSummary, toneHint);
|
|
349
259
|
|
|
350
|
-
//
|
|
260
|
+
// 3. No tools → chat path
|
|
351
261
|
if (allTools.length === 0) {
|
|
352
262
|
logger.debug(`[闲聊路径] 过滤=${filterMs}ms, 记忆=${memMs}ms (${historyMessages.length}条), 0 工具`);
|
|
353
263
|
const tLLM = now();
|
|
@@ -360,14 +270,13 @@ export class ZhinAgent {
|
|
|
360
270
|
|
|
361
271
|
logger.debug(`[工具路径] 过滤=${filterMs}ms, 记忆=${memMs}ms, ${allTools.length} 工具 (${allTools.map(t => t.name).join(', ')})`);
|
|
362
272
|
|
|
363
|
-
//
|
|
364
|
-
// 只有显式标记 preExecutable=true 的工具才会被预执行(opt-in 模式)
|
|
273
|
+
// 4. Pre-executable tools
|
|
365
274
|
const preExecTools: AgentTool[] = [];
|
|
366
275
|
for (const tool of allTools) {
|
|
367
276
|
if (tool.preExecutable) preExecTools.push(tool);
|
|
368
277
|
}
|
|
369
278
|
|
|
370
|
-
//
|
|
279
|
+
// 5. Pre-execution
|
|
371
280
|
let preData = '';
|
|
372
281
|
if (preExecTools.length > 0) {
|
|
373
282
|
const tPre = now();
|
|
@@ -385,7 +294,6 @@ export class ZhinAgent {
|
|
|
385
294
|
for (const r of results) {
|
|
386
295
|
if (r.status === 'fulfilled') {
|
|
387
296
|
let s = typeof r.value.result === 'string' ? r.value.result : JSON.stringify(r.value.result);
|
|
388
|
-
// 限制单条预执行结果的长度,防止注入过多数据干扰模型
|
|
389
297
|
if (s.length > 500) {
|
|
390
298
|
s = s.slice(0, 500) + `\n... (truncated, ${s.length} chars total)`;
|
|
391
299
|
}
|
|
@@ -395,14 +303,12 @@ export class ZhinAgent {
|
|
|
395
303
|
logger.debug(`预执行耗时: ${(now() - tPre).toFixed(0)}ms`);
|
|
396
304
|
}
|
|
397
305
|
|
|
398
|
-
//
|
|
306
|
+
// 6. Path selection
|
|
399
307
|
let reply: string;
|
|
400
|
-
|
|
401
|
-
// 判断是否所有工具都已被预执行(即没有非预执行工具)
|
|
402
308
|
const hasNonPreExecTools = allTools.some(t => !t.preExecutable);
|
|
403
309
|
|
|
404
310
|
if (!hasNonPreExecTools && preData) {
|
|
405
|
-
//
|
|
311
|
+
// Fast path
|
|
406
312
|
const tLLM = now();
|
|
407
313
|
const prompt = `${personaEnhanced}
|
|
408
314
|
|
|
@@ -413,24 +319,35 @@ ${preData}
|
|
|
413
319
|
reply = await this.streamChatWithHistory(content, prompt, historyMessages, onChunk);
|
|
414
320
|
logger.info(`[快速路径] 过滤=${filterMs}ms, 记忆=${memMs}ms, LLM=${(now() - tLLM).toFixed(0)}ms, 总=${(now() - t0).toFixed(0)}ms`);
|
|
415
321
|
} else {
|
|
416
|
-
//
|
|
322
|
+
// Agent path
|
|
417
323
|
const tAgent = now();
|
|
418
324
|
logger.debug(`Agent 路径: ${allTools.length} 个工具`);
|
|
419
|
-
const contextHint =
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
325
|
+
const contextHint = buildContextHint(context, content);
|
|
326
|
+
|
|
327
|
+
const richPrompt = buildRichSystemPrompt({
|
|
328
|
+
config: this.config,
|
|
329
|
+
skillRegistry: this.skillRegistry,
|
|
330
|
+
skillsSummaryXML: this.skillsSummaryXML,
|
|
331
|
+
activeSkillsContext: this.activeSkillsContext,
|
|
332
|
+
bootstrapContext: this.bootstrapContext,
|
|
333
|
+
});
|
|
423
334
|
const systemPrompt = `${richPrompt}
|
|
424
335
|
${contextHint}
|
|
425
336
|
${preData ? `\n已获取数据:${preData}\n` : ''}`;
|
|
426
337
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
338
|
+
const agentTools = applyExecPolicyToTools(this.config, allTools);
|
|
339
|
+
|
|
340
|
+
// Adaptive maxIterations: boost when skills are active (multi-step skill flows)
|
|
341
|
+
const SKILL_ITERATION_BOOST = 3;
|
|
342
|
+
const hasSkillActivation = agentTools.some(t => t.name === 'activate_skill' || t.name === 'install_skill');
|
|
343
|
+
const effectiveMaxIterations = hasSkillActivation
|
|
344
|
+
? this.config.maxIterations + SKILL_ITERATION_BOOST
|
|
345
|
+
: this.config.maxIterations;
|
|
346
|
+
|
|
430
347
|
const agent = createAgent(this.provider, {
|
|
431
348
|
systemPrompt,
|
|
432
349
|
tools: agentTools,
|
|
433
|
-
maxIterations:
|
|
350
|
+
maxIterations: effectiveMaxIterations,
|
|
434
351
|
});
|
|
435
352
|
|
|
436
353
|
const userMessageWithHistory = buildUserMessageWithHistory(historyMessages, content);
|
|
@@ -441,7 +358,6 @@ ${preData ? `\n已获取数据:${preData}\n` : ''}`;
|
|
|
441
358
|
|
|
442
359
|
await this.saveToSession(sessionId, content, reply, sceneId);
|
|
443
360
|
|
|
444
|
-
// 触发 message:sent hook
|
|
445
361
|
triggerAIHook(createAIHookEvent('message', 'sent', sessionId, {
|
|
446
362
|
userId,
|
|
447
363
|
content: reply,
|
|
@@ -451,11 +367,6 @@ ${preData ? `\n已获取数据:${preData}\n` : ''}`;
|
|
|
451
367
|
return parseOutput(reply);
|
|
452
368
|
}
|
|
453
369
|
|
|
454
|
-
/**
|
|
455
|
-
* 处理多模态消息(图片+文字)
|
|
456
|
-
*
|
|
457
|
-
* 当用户发送图片时,走视觉模型路径。
|
|
458
|
-
*/
|
|
459
370
|
async processMultimodal(
|
|
460
371
|
parts: ContentPart[],
|
|
461
372
|
context: ToolContext,
|
|
@@ -465,24 +376,20 @@ ${preData ? `\n已获取数据:${preData}\n` : ''}`;
|
|
|
465
376
|
const sessionId = SessionManager.generateId(platform || '', senderId || '', sceneId);
|
|
466
377
|
const userId = senderId || 'unknown';
|
|
467
378
|
|
|
468
|
-
// 速率限制
|
|
469
379
|
const rateCheck = this.rateLimiter.check(userId);
|
|
470
380
|
if (!rateCheck.allowed) {
|
|
471
381
|
return parseOutput(rateCheck.message || '请稍后再试');
|
|
472
382
|
}
|
|
473
383
|
|
|
474
|
-
// 构建记忆
|
|
475
384
|
const historyMessages = await this.buildHistoryMessages(sessionId);
|
|
476
385
|
const profileSummary = await this.userProfiles.buildProfileSummary(userId);
|
|
477
|
-
const personaEnhanced = this.
|
|
386
|
+
const personaEnhanced = buildEnhancedPersona(this.config, profileSummary, '');
|
|
478
387
|
|
|
479
|
-
// 提取文本部分用于保存
|
|
480
388
|
const textContent = parts
|
|
481
389
|
.filter((p): p is Extract<ContentPart, { type: 'text' }> => p.type === 'text')
|
|
482
390
|
.map(p => p.text)
|
|
483
391
|
.join(' ') || '[多模态消息]';
|
|
484
392
|
|
|
485
|
-
// 选择模型:优先视觉模型
|
|
486
393
|
const visionModel = this.config.visionModel || this.provider.models[0];
|
|
487
394
|
|
|
488
395
|
const messages: ChatMessage[] = [
|
|
@@ -501,7 +408,6 @@ ${preData ? `\n已获取数据:${preData}\n` : ''}`;
|
|
|
501
408
|
}
|
|
502
409
|
}
|
|
503
410
|
} catch {
|
|
504
|
-
// fallback 非流式
|
|
505
411
|
const response = await this.provider.chat({ model: visionModel, messages });
|
|
506
412
|
const msg = response.choices[0]?.message?.content;
|
|
507
413
|
reply = typeof msg === 'string' ? msg : '';
|
|
@@ -512,592 +418,12 @@ ${preData ? `\n已获取数据:${preData}\n` : ''}`;
|
|
|
512
418
|
return parseOutput(reply);
|
|
513
419
|
}
|
|
514
420
|
|
|
515
|
-
// ──
|
|
516
|
-
|
|
517
|
-
private buildEnhancedPersona(profileSummary: string, toneHint: string): string {
|
|
518
|
-
let persona = this.config.persona;
|
|
519
|
-
if (profileSummary) {
|
|
520
|
-
persona += `\n\n${profileSummary}`;
|
|
521
|
-
}
|
|
522
|
-
if (toneHint) {
|
|
523
|
-
persona += `\n\n[语气提示] ${toneHint}`;
|
|
524
|
-
}
|
|
525
|
-
// 注入当前时间(所有路径都需要,闲聊/快速/Agent 路径共用)
|
|
526
|
-
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
527
|
-
const timeStr = new Date().toLocaleString('zh-CN', { timeZone: tz });
|
|
528
|
-
persona += `\n\n当前时间: ${timeStr} (${tz})`;
|
|
529
|
-
return persona;
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
/**
|
|
533
|
-
* 构建上下文提示 — 告诉 AI 当前身份和场景,帮助工具参数填充
|
|
534
|
-
*/
|
|
535
|
-
private buildContextHint(context: ToolContext, _content: string): string {
|
|
536
|
-
const parts: string[] = [];
|
|
537
|
-
if (context.platform) parts.push(`平台:${context.platform}`);
|
|
538
|
-
if (context.botId) parts.push(`Bot:${context.botId}`);
|
|
539
|
-
if (context.senderId) parts.push(`用户:${context.senderId}`);
|
|
540
|
-
if (context.scope) parts.push(`场景类型:${context.scope}`);
|
|
541
|
-
if (context.sceneId) parts.push(`场景ID:${context.sceneId}`);
|
|
542
|
-
if (parts.length === 0) return '';
|
|
543
|
-
return `\n上下文: ${parts.join(' | ')}`;
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
// ── 工具收集: 两级过滤 (Skill → Tool) ─────────────────────────────────
|
|
547
|
-
|
|
548
|
-
private collectTools(
|
|
549
|
-
message: string,
|
|
550
|
-
context: ToolContext,
|
|
551
|
-
externalTools: Tool[],
|
|
552
|
-
): AgentTool[] {
|
|
553
|
-
const callerPerm = context.senderPermissionLevel
|
|
554
|
-
? (PERM_MAP[context.senderPermissionLevel] ?? 0)
|
|
555
|
-
: (context.isOwner ? 4 : context.isBotAdmin ? 3 : context.isGroupOwner ? 2 : context.isGroupAdmin ? 1 : 0);
|
|
556
|
-
|
|
557
|
-
const collected: AgentTool[] = [];
|
|
558
|
-
const collectedNames = new Set<string>(); // 用 Set 加速去重
|
|
559
|
-
|
|
560
|
-
// 0. 检测用户是否明确提到了已知技能名称
|
|
561
|
-
// 若是,优先包含 activate_skill 以确保 Agent 可以激活该技能
|
|
562
|
-
let mentionedSkill: string | null = null;
|
|
563
|
-
if (this.skillRegistry && this.skillRegistry.size > 0) {
|
|
564
|
-
const msgLower = message.toLowerCase();
|
|
565
|
-
for (const skill of this.skillRegistry.getAll()) {
|
|
566
|
-
// 检查用户消息是否包含技能名称(精确或模糊匹配)
|
|
567
|
-
if (msgLower.includes(skill.name.toLowerCase())) {
|
|
568
|
-
mentionedSkill = skill.name;
|
|
569
|
-
logger.debug(`[技能检测] 用户提到技能: ${mentionedSkill}`);
|
|
570
|
-
break; // 只检测第一个匹配的技能
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
// 如果检测到技能名称,从 externalTools 中找 activate_skill 并优先加入
|
|
576
|
-
if (mentionedSkill) {
|
|
577
|
-
const activateSkillTool = externalTools.find(t => t.name === 'activate_skill');
|
|
578
|
-
if (activateSkillTool) {
|
|
579
|
-
const toolPerm = activateSkillTool.permissionLevel ? (PERM_MAP[activateSkillTool.permissionLevel] ?? 0) : 0;
|
|
580
|
-
if (toolPerm <= callerPerm) {
|
|
581
|
-
collected.push(this.toAgentTool(activateSkillTool, context));
|
|
582
|
-
collectedNames.add('activate_skill');
|
|
583
|
-
logger.debug(`[技能激活] 已提前加入 activate_skill 工具(优先级最高)`);
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
// 1. 从 SkillRegistry 两级过滤(包含适配器通过 declareSkill 注册的 Skill)
|
|
589
|
-
if (this.skillRegistry) {
|
|
590
|
-
const skills = this.skillRegistry.search(message, { maxResults: this.config.maxSkills });
|
|
591
|
-
const skillStr = skills.length > 0
|
|
592
|
-
? skills.map(s => `${s.name}(${s.tools?.length || 0}工具)`).join(', ')
|
|
593
|
-
: '(无匹配技能)';
|
|
594
|
-
logger.debug(`[Skill 匹配] ${skillStr}`);
|
|
595
|
-
|
|
596
|
-
for (const skill of skills) {
|
|
597
|
-
for (const tool of skill.tools) {
|
|
598
|
-
// 平台过滤:确保 Skill 中的工具也只保留当前平台支持的
|
|
599
|
-
if (tool.platforms?.length && context.platform && !tool.platforms.includes(context.platform)) continue;
|
|
600
|
-
// 场景过滤
|
|
601
|
-
if (tool.scopes?.length && context.scope && !tool.scopes.includes(context.scope)) continue;
|
|
602
|
-
// 权限检查
|
|
603
|
-
const toolPerm = tool.permissionLevel ? (PERM_MAP[tool.permissionLevel] ?? 0) : 0;
|
|
604
|
-
if (toolPerm > callerPerm) continue;
|
|
605
|
-
if (collectedNames.has(tool.name)) continue;
|
|
606
|
-
collected.push(this.toAgentTool(tool, context));
|
|
607
|
-
collectedNames.add(tool.name);
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
// 2. 外部传入的工具(ToolService 收集的),跳过已通过 Skill 收集的同名工具
|
|
613
|
-
let deduped = 0;
|
|
614
|
-
for (const tool of externalTools) {
|
|
615
|
-
if (tool.name.startsWith('cmd_') || tool.name.startsWith('process_')) continue;
|
|
616
|
-
const toolPerm = tool.permissionLevel ? (PERM_MAP[tool.permissionLevel] ?? 0) : 0;
|
|
617
|
-
if (toolPerm > callerPerm) continue;
|
|
618
|
-
if (collectedNames.has(tool.name)) {
|
|
619
|
-
deduped++;
|
|
620
|
-
continue;
|
|
621
|
-
}
|
|
622
|
-
collected.push(this.toAgentTool(tool, context));
|
|
623
|
-
collectedNames.add(tool.name);
|
|
624
|
-
}
|
|
625
|
-
if (deduped > 0) {
|
|
626
|
-
logger.debug(`externalTools 去重: 跳过 ${deduped} 个已由 Skill 提供的工具`);
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
// 3. 额外注册的工具
|
|
630
|
-
for (const tool of this.externalTools.values()) {
|
|
631
|
-
if (tool.permissionLevel != null && tool.permissionLevel > callerPerm) continue;
|
|
632
|
-
if (collectedNames.has(tool.name)) continue;
|
|
633
|
-
collected.push(tool);
|
|
634
|
-
collectedNames.add(tool.name);
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
// 4. 用 Agent.filterTools 做最终相关性排序(阈值 0.3 减少噪音)
|
|
638
|
-
const filtered = Agent.filterTools(message, collected, {
|
|
639
|
-
callerPermissionLevel: callerPerm,
|
|
640
|
-
maxTools: this.config.maxTools,
|
|
641
|
-
minScore: 0.3,
|
|
642
|
-
});
|
|
643
|
-
|
|
644
|
-
// 特殊处理:如果检测到了技能名称,确保 activate_skill 排在最前面
|
|
645
|
-
if (mentionedSkill && filtered.length > 0) {
|
|
646
|
-
const activateSkillIdx = filtered.findIndex(t => t.name === 'activate_skill');
|
|
647
|
-
if (activateSkillIdx > 0) { // 若存在但不在最前
|
|
648
|
-
// 将 activate_skill 移到最前面
|
|
649
|
-
const activateSkillTool = filtered[activateSkillIdx];
|
|
650
|
-
filtered.splice(activateSkillIdx, 1);
|
|
651
|
-
filtered.unshift(activateSkillTool);
|
|
652
|
-
logger.debug(`[工具排序] activate_skill 提升至首位(因检测到技能: ${mentionedSkill})`);
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
// 5. 配置级工具开关:disabledTools / allowedTools(权限与安全)
|
|
657
|
-
let final = filtered;
|
|
658
|
-
const allowed = this.config.allowedTools;
|
|
659
|
-
const disabled = this.config.disabledTools ?? [];
|
|
660
|
-
if (allowed && allowed.length > 0) {
|
|
661
|
-
const allowSet = new Set(allowed.map(n => n.toLowerCase()));
|
|
662
|
-
final = final.filter(t => allowSet.has(t.name.toLowerCase()));
|
|
663
|
-
if (final.length < filtered.length) {
|
|
664
|
-
logger.debug(`[工具开关] allowedTools 限制: ${filtered.length} -> ${final.length}`);
|
|
665
|
-
}
|
|
666
|
-
} else if (disabled.length > 0) {
|
|
667
|
-
const disabledSet = new Set(disabled.map(n => n.toLowerCase()));
|
|
668
|
-
final = final.filter(t => !disabledSet.has(t.name.toLowerCase()));
|
|
669
|
-
if (final.length < filtered.length) {
|
|
670
|
-
logger.debug(`[工具开关] disabledTools 过滤: ${filtered.length} -> ${final.length}`);
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
// 诊断日志:显示收集的工具总数、过滤后的数量、以及列表
|
|
675
|
-
if (final.length > 0) {
|
|
676
|
-
logger.debug(
|
|
677
|
-
`[工具收集] 收集了 ${collected.length} 个工具,过滤后 ${final.length} 个,` +
|
|
678
|
-
`用户消息相关性最高的: ${final.slice(0, 3).map(t => t.name).join(', ')}`
|
|
679
|
-
);
|
|
680
|
-
} else {
|
|
681
|
-
logger.debug(`[工具收集] 收集了 ${collected.length} 个工具,但过滤后 0 个(没有超过相关性阈值的)`);
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
return final;
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
/**
|
|
688
|
-
* bash 执行策略检查:未通过时抛出 Error(供上层返回给用户)。
|
|
689
|
-
*/
|
|
690
|
-
private checkExecPolicy(command: string): void {
|
|
691
|
-
const security = this.config.execSecurity ?? 'deny';
|
|
692
|
-
if (security === 'full') return;
|
|
693
|
-
if (security === 'deny') {
|
|
694
|
-
throw new Error('当前配置禁止执行 Shell 命令(execSecurity=deny)。如需开放请在配置中设置 ai.agent.execSecurity。');
|
|
695
|
-
}
|
|
696
|
-
// allowlist
|
|
697
|
-
const list = this.config.execAllowlist ?? [];
|
|
698
|
-
const cmd = (command || '').trim();
|
|
699
|
-
const allowed = list.some(pattern => {
|
|
700
|
-
try {
|
|
701
|
-
const re = new RegExp(pattern);
|
|
702
|
-
return re.test(cmd);
|
|
703
|
-
} catch {
|
|
704
|
-
return cmd === pattern || cmd.startsWith(pattern);
|
|
705
|
-
}
|
|
706
|
-
});
|
|
707
|
-
if (!allowed) {
|
|
708
|
-
const ask = this.config.execAsk;
|
|
709
|
-
throw new Error(
|
|
710
|
-
ask
|
|
711
|
-
? '该命令不在允许列表中,需要审批后执行。当前版本请将命令加入 ai.agent.execAllowlist 或联系管理员。'
|
|
712
|
-
: '该命令不在允许列表中,已被拒绝执行。可将允许的命令模式加入 ai.agent.execAllowlist。',
|
|
713
|
-
);
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
/**
|
|
718
|
-
* 对传入的 Agent 工具列表应用 bash 执行策略(仅包装 bash 工具)。
|
|
719
|
-
*/
|
|
720
|
-
private applyExecPolicyToTools(tools: AgentTool[]): AgentTool[] {
|
|
721
|
-
return tools.map(t => {
|
|
722
|
-
if (t.name !== 'bash') return t;
|
|
723
|
-
const original = t.execute;
|
|
724
|
-
return {
|
|
725
|
-
...t,
|
|
726
|
-
execute: async (args: Record<string, any>) => {
|
|
727
|
-
const cmd = args?.command != null ? String(args.command) : '';
|
|
728
|
-
this.checkExecPolicy(cmd);
|
|
729
|
-
return original(args);
|
|
730
|
-
},
|
|
731
|
-
};
|
|
732
|
-
});
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
// ── 辅助方法 ─────────────────────────────────────────────────────────
|
|
736
|
-
|
|
737
|
-
/**
|
|
738
|
-
* 将 Tool 转为 AgentTool,注入 ToolContext 以确保执行时鉴权生效。
|
|
739
|
-
*
|
|
740
|
-
* 当参数定义了 contextKey 时:
|
|
741
|
-
* 1. 从 AI 可见的 parameters 中移除该参数(减少 token、避免填错)
|
|
742
|
-
* 2. 执行时自动从 ToolContext 注入对应值,并按声明类型做类型转换
|
|
743
|
-
*/
|
|
744
|
-
private toAgentTool(tool: Tool, context?: ToolContext): AgentTool {
|
|
745
|
-
const originalExecute = tool.execute;
|
|
746
|
-
|
|
747
|
-
// ── 收集需要自动注入的参数 ──────────────────────────────────
|
|
748
|
-
const contextInjections: Array<{
|
|
749
|
-
paramName: string;
|
|
750
|
-
contextKey: string;
|
|
751
|
-
paramType: string; // 目标参数的 JSON Schema type,用于类型转换
|
|
752
|
-
}> = [];
|
|
753
|
-
let cleanParameters: any = tool.parameters;
|
|
754
|
-
|
|
755
|
-
if (context && tool.parameters?.properties) {
|
|
756
|
-
const props = tool.parameters.properties as Record<string, any>;
|
|
757
|
-
const filteredProps: Record<string, any> = {};
|
|
758
|
-
const filteredRequired: string[] = [];
|
|
759
|
-
|
|
760
|
-
for (const [key, schema] of Object.entries(props)) {
|
|
761
|
-
if (schema.contextKey && (context as any)[schema.contextKey] != null) {
|
|
762
|
-
// 记录需要注入的映射
|
|
763
|
-
contextInjections.push({
|
|
764
|
-
paramName: key,
|
|
765
|
-
contextKey: schema.contextKey,
|
|
766
|
-
paramType: schema.type || 'string',
|
|
767
|
-
});
|
|
768
|
-
} else {
|
|
769
|
-
// 保留给 AI 的参数
|
|
770
|
-
filteredProps[key] = schema;
|
|
771
|
-
if (tool.parameters.required?.includes(key)) {
|
|
772
|
-
filteredRequired.push(key);
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
if (contextInjections.length > 0) {
|
|
778
|
-
cleanParameters = {
|
|
779
|
-
...tool.parameters,
|
|
780
|
-
properties: filteredProps,
|
|
781
|
-
required: filteredRequired.length > 0 ? filteredRequired : undefined,
|
|
782
|
-
};
|
|
783
|
-
}
|
|
784
|
-
}
|
|
421
|
+
// ── Internal helpers ────────────────────────────────────────────────
|
|
785
422
|
|
|
786
|
-
// ── 组装 AgentTool ──────────────────────────────────────────
|
|
787
|
-
const at: AgentTool = {
|
|
788
|
-
name: tool.name,
|
|
789
|
-
description: tool.description,
|
|
790
|
-
parameters: cleanParameters as any,
|
|
791
|
-
execute: context
|
|
792
|
-
? (args: Record<string, any>) => {
|
|
793
|
-
// 自动注入 context 值,按目标 type 做类型转换
|
|
794
|
-
const enrichedArgs = { ...args };
|
|
795
|
-
for (const { paramName, contextKey, paramType } of contextInjections) {
|
|
796
|
-
let value = (context as any)[contextKey];
|
|
797
|
-
if (paramType === 'number' && typeof value === 'string') {
|
|
798
|
-
value = Number(value);
|
|
799
|
-
} else if (paramType === 'string' && typeof value !== 'string') {
|
|
800
|
-
value = String(value);
|
|
801
|
-
}
|
|
802
|
-
enrichedArgs[paramName] = value;
|
|
803
|
-
}
|
|
804
|
-
return originalExecute(enrichedArgs, context);
|
|
805
|
-
}
|
|
806
|
-
: originalExecute,
|
|
807
|
-
};
|
|
808
|
-
if (tool.tags?.length) at.tags = tool.tags;
|
|
809
|
-
if (tool.keywords?.length) at.keywords = tool.keywords;
|
|
810
|
-
if (tool.permissionLevel) at.permissionLevel = PERM_MAP[tool.permissionLevel] ?? 0;
|
|
811
|
-
if (tool.preExecutable) at.preExecutable = true;
|
|
812
|
-
if ((tool as any).kind) at.kind = (tool as any).kind;
|
|
813
|
-
return at;
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
/**
|
|
817
|
-
* 构建结构化 System Prompt(借鉴 OpenClaw 的分段式设计)
|
|
818
|
-
*
|
|
819
|
-
* 段落结构:
|
|
820
|
-
* 1. 身份 + 人格
|
|
821
|
-
* 2. 安全准则
|
|
822
|
-
* 3. 工具调用风格
|
|
823
|
-
* 4. 技能列表(XML 格式)
|
|
824
|
-
* 5. 当前时间
|
|
825
|
-
* 6. 引导文件上下文(SOUL.md, TOOLS.md, AGENTS.md)
|
|
826
|
-
*/
|
|
827
|
-
/**
|
|
828
|
-
* 构建精简的 System Prompt — 专为小模型(8B/14B 级)优化
|
|
829
|
-
*
|
|
830
|
-
* 设计原则:
|
|
831
|
-
* - 控制在 300-500 token 内,为工具定义和历史留足空间
|
|
832
|
-
* - 规则用短句,不用段落
|
|
833
|
-
* - 不重复,不举例(模型能从工具定义中推断用法)
|
|
834
|
-
*/
|
|
835
|
-
private buildRichSystemPrompt(): string {
|
|
836
|
-
const lines: string[] = [];
|
|
837
|
-
|
|
838
|
-
// §1 身份
|
|
839
|
-
lines.push(this.config.persona);
|
|
840
|
-
lines.push('');
|
|
841
|
-
|
|
842
|
-
// §2 核心规则(精简为 7 条短句)
|
|
843
|
-
lines.push('## 规则');
|
|
844
|
-
lines.push('1. 直接调用工具执行操作,不要描述步骤或解释意图');
|
|
845
|
-
lines.push('2. 时间/日期问题:直接用下方"当前时间"回答,不调工具');
|
|
846
|
-
lines.push('3. 修改文件必须调用 edit_file/write_file,禁止给手动教程');
|
|
847
|
-
lines.push('4. activate_skill 返回后,必须继续调用其中指导的工具,不要停');
|
|
848
|
-
lines.push('5. 所有回答必须基于工具返回的实际数据');
|
|
849
|
-
lines.push('6. 工具失败时尝试替代方案,不要直接把错误丢给用户');
|
|
850
|
-
lines.push('7. 只根据用户**最后一条**消息作答,前面的对话仅作背景');
|
|
851
|
-
lines.push('');
|
|
852
|
-
|
|
853
|
-
// §3 技能列表(紧凑格式)
|
|
854
|
-
if (this.skillRegistry && this.skillRegistry.size > 0) {
|
|
855
|
-
const skills = this.skillRegistry.getAll();
|
|
856
|
-
lines.push('## 可用技能');
|
|
857
|
-
for (const skill of skills) {
|
|
858
|
-
lines.push(`- ${skill.name}: ${skill.description}`);
|
|
859
|
-
}
|
|
860
|
-
lines.push('用户提到技能名 → 调用 activate_skill(name) → 按返回的指导执行工具');
|
|
861
|
-
lines.push('');
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
// §4 当前时间
|
|
865
|
-
const now = new Date();
|
|
866
|
-
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
867
|
-
const timeStr = now.toLocaleString('zh-CN', { timeZone: tz });
|
|
868
|
-
lines.push(`当前时间: ${timeStr} (${tz})`);
|
|
869
|
-
lines.push('');
|
|
870
|
-
|
|
871
|
-
// §5 引导文件上下文(SOUL.md, TOOLS.md, AGENTS.md)
|
|
872
|
-
if (this.bootstrapContext) {
|
|
873
|
-
lines.push(this.bootstrapContext);
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
return lines.filter(Boolean).join('\n');
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
// ── 内置工具 ─────────────────────────────────────────────────────────
|
|
880
|
-
|
|
881
|
-
/**
|
|
882
|
-
* 创建 chat_history 工具 — 让 AI 能主动搜索历史聊天记录
|
|
883
|
-
*/
|
|
884
|
-
private createChatHistoryTool(sessionId: string): AgentTool {
|
|
885
|
-
const memory = this.memory;
|
|
886
|
-
|
|
887
|
-
return {
|
|
888
|
-
name: 'chat_history',
|
|
889
|
-
description: '搜索与用户的历史聊天记录。可以按关键词搜索,也可以按对话轮次范围查询。当用户问到"之前聊过什么""我们讨论过什么"等回忆类问题时使用。',
|
|
890
|
-
parameters: {
|
|
891
|
-
type: 'object',
|
|
892
|
-
properties: {
|
|
893
|
-
keyword: {
|
|
894
|
-
type: 'string',
|
|
895
|
-
description: '搜索关键词(模糊匹配消息内容和摘要)。留空则返回最近几轮记录',
|
|
896
|
-
},
|
|
897
|
-
from_round: {
|
|
898
|
-
type: 'number',
|
|
899
|
-
description: '起始轮次(与 to_round 配合使用,精确查询某段对话)',
|
|
900
|
-
},
|
|
901
|
-
to_round: {
|
|
902
|
-
type: 'number',
|
|
903
|
-
description: '结束轮次',
|
|
904
|
-
},
|
|
905
|
-
},
|
|
906
|
-
required: ['keyword'],
|
|
907
|
-
},
|
|
908
|
-
tags: ['memory', 'history', '聊天记录', '回忆', '之前'],
|
|
909
|
-
keywords: ['之前', '历史', '聊过', '讨论过', '记得', '上次', '以前', '回忆'],
|
|
910
|
-
async execute(args: Record<string, any>) {
|
|
911
|
-
const { keyword, from_round, to_round } = args;
|
|
912
|
-
|
|
913
|
-
// 获取当前轮次用于提示
|
|
914
|
-
const currentRound = await memory.getCurrentRound(sessionId);
|
|
915
|
-
|
|
916
|
-
if (keyword) {
|
|
917
|
-
const result = await memory.traceByKeyword(sessionId, keyword);
|
|
918
|
-
const msgs = result.messages.map(m => {
|
|
919
|
-
const role = m.role === 'user' ? '用户' : '助手';
|
|
920
|
-
const time = new Date(m.time).toLocaleString('zh-CN');
|
|
921
|
-
return `[第${m.round}轮 ${time}] ${role}: ${m.content}`;
|
|
922
|
-
}).join('\n');
|
|
923
|
-
|
|
924
|
-
let output = `当前是第 ${currentRound} 轮对话。\n\n`;
|
|
925
|
-
if (result.summary) {
|
|
926
|
-
output += `📋 找到相关摘要(覆盖第${result.summary.fromRound}-${result.summary.toRound}轮):\n${result.summary.summary}\n\n`;
|
|
927
|
-
}
|
|
928
|
-
output += msgs ? `💬 相关聊天记录:\n${msgs}` : '未找到包含该关键词的聊天记录。';
|
|
929
|
-
return output;
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
if (from_round != null && to_round != null) {
|
|
933
|
-
const messages = await memory.getMessagesByRound(sessionId, from_round, to_round);
|
|
934
|
-
if (messages.length === 0) {
|
|
935
|
-
return `第 ${from_round}-${to_round} 轮没有聊天记录。当前是第 ${currentRound} 轮。`;
|
|
936
|
-
}
|
|
937
|
-
const msgs = messages.map(m => {
|
|
938
|
-
const role = m.role === 'user' ? '用户' : '助手';
|
|
939
|
-
const time = new Date(m.time).toLocaleString('zh-CN');
|
|
940
|
-
return `[第${m.round}轮 ${time}] ${role}: ${m.content}`;
|
|
941
|
-
}).join('\n');
|
|
942
|
-
return `第 ${from_round}-${to_round} 轮聊天记录(当前第 ${currentRound} 轮):\n${msgs}`;
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
// 无参数 → 返回最近几轮
|
|
946
|
-
const messages = await memory.getMessagesByRound(
|
|
947
|
-
sessionId,
|
|
948
|
-
Math.max(1, currentRound - 4),
|
|
949
|
-
currentRound,
|
|
950
|
-
);
|
|
951
|
-
if (messages.length === 0) {
|
|
952
|
-
return '暂无聊天记录。';
|
|
953
|
-
}
|
|
954
|
-
const msgs = messages.map(m => {
|
|
955
|
-
const role = m.role === 'user' ? '用户' : '助手';
|
|
956
|
-
return `[第${m.round}轮] ${role}: ${m.content}`;
|
|
957
|
-
}).join('\n');
|
|
958
|
-
return `最近的聊天记录(当前第 ${currentRound} 轮):\n${msgs}`;
|
|
959
|
-
},
|
|
960
|
-
};
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
/**
|
|
964
|
-
* 创建 user_profile 工具 — 让 AI 读写用户画像
|
|
965
|
-
*/
|
|
966
|
-
private createUserProfileTool(userId: string): AgentTool {
|
|
967
|
-
const profiles = this.userProfiles;
|
|
968
|
-
|
|
969
|
-
return {
|
|
970
|
-
name: 'user_profile',
|
|
971
|
-
description: '读取或保存用户的个人偏好和信息。当用户告诉你他的名字、偏好、兴趣、习惯等个人信息时,用 set 操作保存。当需要了解用户偏好时,用 get 操作读取。',
|
|
972
|
-
parameters: {
|
|
973
|
-
type: 'object',
|
|
974
|
-
properties: {
|
|
975
|
-
action: {
|
|
976
|
-
type: 'string',
|
|
977
|
-
description: '操作类型: get(读取所有偏好), set(保存偏好), delete(删除偏好)',
|
|
978
|
-
enum: ['get', 'set', 'delete'],
|
|
979
|
-
},
|
|
980
|
-
key: {
|
|
981
|
-
type: 'string',
|
|
982
|
-
description: '偏好名称,如: name, style, interests, timezone, language 等',
|
|
983
|
-
},
|
|
984
|
-
value: {
|
|
985
|
-
type: 'string',
|
|
986
|
-
description: '偏好值(仅 set 操作需要)',
|
|
987
|
-
},
|
|
988
|
-
},
|
|
989
|
-
required: ['action'],
|
|
990
|
-
},
|
|
991
|
-
tags: ['profile', '偏好', '用户', '个性化', '记住'],
|
|
992
|
-
keywords: ['我叫', '我的名字', '记住我', '我喜欢', '我偏好', '我习惯', '叫我', '我是'],
|
|
993
|
-
async execute(args: Record<string, any>) {
|
|
994
|
-
const { action, key, value } = args;
|
|
995
|
-
|
|
996
|
-
switch (action) {
|
|
997
|
-
case 'get': {
|
|
998
|
-
const all = await profiles.getAll(userId);
|
|
999
|
-
const entries = Object.entries(all);
|
|
1000
|
-
if (entries.length === 0) return '暂无保存的用户偏好。';
|
|
1001
|
-
return '用户偏好:\n' + entries.map(([k, v]) => ` ${k}: ${v}`).join('\n');
|
|
1002
|
-
}
|
|
1003
|
-
case 'set': {
|
|
1004
|
-
if (!key || !value) return '需要提供 key 和 value';
|
|
1005
|
-
await profiles.set(userId, key, value);
|
|
1006
|
-
return `已保存: ${key} = ${value}`;
|
|
1007
|
-
}
|
|
1008
|
-
case 'delete': {
|
|
1009
|
-
if (!key) return '需要提供 key';
|
|
1010
|
-
const deleted = await profiles.delete(userId, key);
|
|
1011
|
-
return deleted ? `已删除: ${key}` : `未找到偏好: ${key}`;
|
|
1012
|
-
}
|
|
1013
|
-
default:
|
|
1014
|
-
return '不支持的操作,请使用 get/set/delete';
|
|
1015
|
-
}
|
|
1016
|
-
},
|
|
1017
|
-
};
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
/**
|
|
1021
|
-
* 创建 schedule_followup 工具 — 让 AI 主动安排跟进
|
|
1022
|
-
*
|
|
1023
|
-
* 任务持久化到数据库,机器人重启后自动恢复。
|
|
1024
|
-
* 同一会话创建新提醒时,旧的 pending 提醒会被自动取消。
|
|
1025
|
-
*/
|
|
1026
|
-
private createScheduleFollowUpTool(sessionId: string, context: ToolContext): AgentTool {
|
|
1027
|
-
const followUps = this.followUps;
|
|
1028
|
-
const platform = context.platform || '';
|
|
1029
|
-
const botId = context.botId || '';
|
|
1030
|
-
const senderId = context.senderId || '';
|
|
1031
|
-
const sceneId = context.sceneId || '';
|
|
1032
|
-
const sceneType = (context.message as any)?.$channel?.type || 'private';
|
|
1033
|
-
|
|
1034
|
-
return {
|
|
1035
|
-
name: 'schedule_followup',
|
|
1036
|
-
description: '安排或取消定时跟进提醒。创建新提醒会自动取消之前的提醒。提醒持久保存,重启不丢失。',
|
|
1037
|
-
parameters: {
|
|
1038
|
-
type: 'object',
|
|
1039
|
-
properties: {
|
|
1040
|
-
action: {
|
|
1041
|
-
type: 'string',
|
|
1042
|
-
description: '操作类型: create(创建提醒,默认)或 cancel(取消当前会话所有提醒)',
|
|
1043
|
-
enum: ['create', 'cancel'],
|
|
1044
|
-
},
|
|
1045
|
-
delay_minutes: {
|
|
1046
|
-
type: 'number',
|
|
1047
|
-
description: '延迟时间,单位是分钟。注意:3 就是 3 分钟,不是 3 小时。举例: 3 = 3分钟后, 60 = 1小时后, 1440 = 1天后',
|
|
1048
|
-
},
|
|
1049
|
-
message: {
|
|
1050
|
-
type: 'string',
|
|
1051
|
-
description: '提醒消息内容',
|
|
1052
|
-
},
|
|
1053
|
-
},
|
|
1054
|
-
required: ['action'],
|
|
1055
|
-
},
|
|
1056
|
-
tags: ['reminder', '提醒', '跟进', '定时'],
|
|
1057
|
-
keywords: ['提醒', '提醒我', '过一会', '过一小时', '明天', '跟进', '别忘了', '记得提醒', '取消提醒'],
|
|
1058
|
-
async execute(args: Record<string, any>) {
|
|
1059
|
-
const { action = 'create', delay_minutes, message: msg } = args;
|
|
1060
|
-
|
|
1061
|
-
if (action === 'cancel') {
|
|
1062
|
-
const count = await followUps.cancelBySession(sessionId);
|
|
1063
|
-
return count > 0
|
|
1064
|
-
? `✅ 已取消 ${count} 个待执行的提醒`
|
|
1065
|
-
: '当前没有待执行的提醒';
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
// create
|
|
1069
|
-
if (!delay_minutes || delay_minutes <= 0) return '延迟时间必须大于 0 分钟';
|
|
1070
|
-
if (!msg) return '请提供提醒内容';
|
|
1071
|
-
|
|
1072
|
-
return followUps.schedule({
|
|
1073
|
-
sessionId,
|
|
1074
|
-
platform,
|
|
1075
|
-
botId,
|
|
1076
|
-
senderId,
|
|
1077
|
-
sceneId,
|
|
1078
|
-
sceneType,
|
|
1079
|
-
message: msg,
|
|
1080
|
-
delayMinutes: delay_minutes,
|
|
1081
|
-
});
|
|
1082
|
-
},
|
|
1083
|
-
};
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
|
-
// ── 会话记忆(基于 ConversationMemory) ─────────────────────────────
|
|
1087
|
-
|
|
1088
|
-
/**
|
|
1089
|
-
* 从 ConversationMemory 构建上下文
|
|
1090
|
-
*/
|
|
1091
423
|
private async buildHistoryMessages(sessionId: string): Promise<ChatMessage[]> {
|
|
1092
424
|
return this.memory.buildContext(sessionId);
|
|
1093
425
|
}
|
|
1094
426
|
|
|
1095
|
-
/**
|
|
1096
|
-
* 流式聊天(带历史记忆) — 利用 chatStream 减少 TTFT
|
|
1097
|
-
*
|
|
1098
|
-
* 新增 onChunk 回调:每收到一个 token 立即通知调用方,
|
|
1099
|
-
* 支持适配器(Telegram/Discord/Kook)实时编辑消息。
|
|
1100
|
-
*/
|
|
1101
427
|
private async streamChatWithHistory(
|
|
1102
428
|
content: string,
|
|
1103
429
|
systemPrompt: string,
|
|
@@ -1113,7 +439,6 @@ ${preData ? `\n已获取数据:${preData}\n` : ''}`;
|
|
|
1113
439
|
{ role: 'user', content: userContent },
|
|
1114
440
|
];
|
|
1115
441
|
|
|
1116
|
-
// 优先流式(对 Ollama 等本地模型有明显提速)
|
|
1117
442
|
try {
|
|
1118
443
|
let result = '';
|
|
1119
444
|
for await (const chunk of this.provider.chatStream({ model, messages })) {
|
|
@@ -1125,7 +450,6 @@ ${preData ? `\n已获取数据:${preData}\n` : ''}`;
|
|
|
1125
450
|
}
|
|
1126
451
|
return result;
|
|
1127
452
|
} catch {
|
|
1128
|
-
// fallback 非流式
|
|
1129
453
|
const response = await this.provider.chat({ model, messages });
|
|
1130
454
|
const msg = response.choices[0]?.message?.content;
|
|
1131
455
|
const result = typeof msg === 'string' ? msg : '';
|
|
@@ -1140,14 +464,9 @@ ${preData ? `\n已获取数据:${preData}\n` : ''}`;
|
|
|
1140
464
|
assistantContent: string,
|
|
1141
465
|
sceneId?: string,
|
|
1142
466
|
): Promise<void> {
|
|
1143
|
-
// 1. 保存到 ConversationMemory(含异步摘要判断)
|
|
1144
467
|
await this.memory.saveRound(sessionId, userContent, assistantContent);
|
|
1145
|
-
|
|
1146
|
-
// 2. 保存到 SessionManager(兼容旧逻辑)
|
|
1147
468
|
await this.sessions.addMessage(sessionId, { role: 'user', content: userContent });
|
|
1148
469
|
await this.sessions.addMessage(sessionId, { role: 'assistant', content: assistantContent });
|
|
1149
|
-
|
|
1150
|
-
// 3. ContextManager 场景摘要(如有)
|
|
1151
470
|
if (this.contextManager && sceneId) {
|
|
1152
471
|
this.contextManager.autoSummarizeIfNeeded(sceneId).catch(() => {});
|
|
1153
472
|
}
|
|
@@ -1155,10 +474,8 @@ ${preData ? `\n已获取数据:${preData}\n` : ''}`;
|
|
|
1155
474
|
|
|
1156
475
|
private fallbackFormat(toolCalls: { tool: string; args: any; result: any }[]): string {
|
|
1157
476
|
if (toolCalls.length === 0) return '处理完成。';
|
|
1158
|
-
// 过滤掉 activate_skill 的结果(是 SKILL.md 指令,不应暴露给用户)
|
|
1159
477
|
const userFacing = toolCalls.filter(tc => tc.tool !== 'activate_skill');
|
|
1160
478
|
if (userFacing.length === 0) {
|
|
1161
|
-
// 只有 activate_skill 被调用但后续工具未执行 — 说明技能激活后流程中断
|
|
1162
479
|
return '技能已激活但未能完成后续操作,请重试或换一种方式描述你的需求。';
|
|
1163
480
|
}
|
|
1164
481
|
return userFacing.map(tc => {
|
|
@@ -1167,10 +484,10 @@ ${preData ? `\n已获取数据:${preData}\n` : ''}`;
|
|
|
1167
484
|
}).join('\n\n');
|
|
1168
485
|
}
|
|
1169
486
|
|
|
1170
|
-
// ──
|
|
487
|
+
// ── Lifecycle ───────────────────────────────────────────────────────
|
|
1171
488
|
|
|
1172
489
|
isReady(): boolean {
|
|
1173
|
-
return true;
|
|
490
|
+
return true;
|
|
1174
491
|
}
|
|
1175
492
|
|
|
1176
493
|
dispose(): void {
|
|
@@ -1180,5 +497,10 @@ ${preData ? `\n已获取数据:${preData}\n` : ''}`;
|
|
|
1180
497
|
this.userProfiles.dispose();
|
|
1181
498
|
this.rateLimiter.dispose();
|
|
1182
499
|
this.followUps.dispose();
|
|
500
|
+
if (this.subagentManager) {
|
|
501
|
+
this.subagentManager.dispose();
|
|
502
|
+
this.subagentManager = null;
|
|
503
|
+
}
|
|
1183
504
|
}
|
|
1184
505
|
}
|
|
506
|
+
|