@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/subagent.ts
DELETED
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SubagentManager — 后台子任务执行管理器
|
|
3
|
-
*
|
|
4
|
-
* 职责:
|
|
5
|
-
* 1. 接收主 agent 发起的 spawn 请求,创建后台子 agent
|
|
6
|
-
* 2. 为子 agent 配备受限工具集(文件/Shell/网络,不含消息/spawn/技能)
|
|
7
|
-
* 3. 管理子 agent 生命周期(运行跟踪、完成/失败回告)
|
|
8
|
-
* 4. 完成后将结果投递回主流程,由主 agent 摘要后发送到原始频道
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { randomUUID } from 'crypto';
|
|
12
|
-
import { Logger } from '@zhin.js/logger';
|
|
13
|
-
import type { AIProvider, AgentTool } from './types.js';
|
|
14
|
-
import { createAgent } from './agent.js';
|
|
15
|
-
import type { ZhinAgentConfig } from './zhin-agent/config.js';
|
|
16
|
-
import { applyExecPolicyToTools } from './zhin-agent/exec-policy.js';
|
|
17
|
-
|
|
18
|
-
const logger = new Logger(null, 'Subagent');
|
|
19
|
-
|
|
20
|
-
// ============================================================================
|
|
21
|
-
// 类型
|
|
22
|
-
// ============================================================================
|
|
23
|
-
|
|
24
|
-
export interface SubagentOrigin {
|
|
25
|
-
platform: string;
|
|
26
|
-
botId: string;
|
|
27
|
-
sceneId: string;
|
|
28
|
-
senderId: string;
|
|
29
|
-
sceneType: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface SpawnOptions {
|
|
33
|
-
task: string;
|
|
34
|
-
label?: string;
|
|
35
|
-
origin: SubagentOrigin;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export type SubagentResultSender = (origin: SubagentOrigin, content: string) => Promise<void>;
|
|
39
|
-
|
|
40
|
-
export interface SubagentManagerOptions {
|
|
41
|
-
provider: AIProvider;
|
|
42
|
-
workspace: string;
|
|
43
|
-
createTools: () => AgentTool[];
|
|
44
|
-
maxIterations?: number;
|
|
45
|
-
/** Exec policy config to enforce on subagent bash tools */
|
|
46
|
-
execPolicyConfig?: Required<ZhinAgentConfig>;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// ============================================================================
|
|
50
|
-
// 子 agent 允许使用的工具名单
|
|
51
|
-
// ============================================================================
|
|
52
|
-
|
|
53
|
-
const SUBAGENT_ALLOWED_TOOLS = new Set([
|
|
54
|
-
'read_file',
|
|
55
|
-
'write_file',
|
|
56
|
-
'edit_file',
|
|
57
|
-
'list_dir',
|
|
58
|
-
'glob',
|
|
59
|
-
'grep',
|
|
60
|
-
'bash',
|
|
61
|
-
'web_search',
|
|
62
|
-
'web_fetch',
|
|
63
|
-
]);
|
|
64
|
-
|
|
65
|
-
// ============================================================================
|
|
66
|
-
// SubagentManager
|
|
67
|
-
// ============================================================================
|
|
68
|
-
|
|
69
|
-
export class SubagentManager {
|
|
70
|
-
private provider: AIProvider;
|
|
71
|
-
private workspace: string;
|
|
72
|
-
private createTools: () => AgentTool[];
|
|
73
|
-
private maxIterations: number;
|
|
74
|
-
private execPolicyConfig: Required<ZhinAgentConfig> | null;
|
|
75
|
-
private runningTasks: Map<string, AbortController> = new Map();
|
|
76
|
-
private resultSender: SubagentResultSender | null = null;
|
|
77
|
-
|
|
78
|
-
constructor(options: SubagentManagerOptions) {
|
|
79
|
-
this.provider = options.provider;
|
|
80
|
-
this.workspace = options.workspace;
|
|
81
|
-
this.createTools = options.createTools;
|
|
82
|
-
this.maxIterations = options.maxIterations ?? 15;
|
|
83
|
-
this.execPolicyConfig = options.execPolicyConfig ?? null;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
setSender(sender: SubagentResultSender): void {
|
|
87
|
-
this.resultSender = sender;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async spawn(options: SpawnOptions): Promise<string> {
|
|
91
|
-
const taskId = randomUUID().slice(0, 8);
|
|
92
|
-
const displayLabel =
|
|
93
|
-
options.label ||
|
|
94
|
-
options.task.slice(0, 30) + (options.task.length > 30 ? '...' : '');
|
|
95
|
-
|
|
96
|
-
const abortController = new AbortController();
|
|
97
|
-
this.runningTasks.set(taskId, abortController);
|
|
98
|
-
|
|
99
|
-
this.runSubagent(taskId, options.task, displayLabel, options.origin)
|
|
100
|
-
.catch((error) => {
|
|
101
|
-
logger.error({ error, taskId }, 'Subagent failed');
|
|
102
|
-
})
|
|
103
|
-
.finally(() => {
|
|
104
|
-
this.runningTasks.delete(taskId);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
logger.info({ taskId, label: displayLabel }, 'Spawned subagent');
|
|
108
|
-
return `子任务 [${displayLabel}] 已启动 (id: ${taskId}),完成后会自动通知你。`;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
getRunningCount(): number {
|
|
112
|
-
return this.runningTasks.size;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// ── 内部方法 ──────────────────────────────────────────────────────
|
|
116
|
-
|
|
117
|
-
private async runSubagent(
|
|
118
|
-
taskId: string,
|
|
119
|
-
task: string,
|
|
120
|
-
label: string,
|
|
121
|
-
origin: SubagentOrigin,
|
|
122
|
-
): Promise<void> {
|
|
123
|
-
logger.info({ taskId, label }, 'Subagent starting task');
|
|
124
|
-
|
|
125
|
-
try {
|
|
126
|
-
const allTools = this.createTools();
|
|
127
|
-
let tools = allTools.filter(t => SUBAGENT_ALLOWED_TOOLS.has(t.name));
|
|
128
|
-
if (this.execPolicyConfig) {
|
|
129
|
-
tools = applyExecPolicyToTools(this.execPolicyConfig, tools);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const systemPrompt = this.buildSubagentPrompt(task);
|
|
133
|
-
const agent = createAgent(this.provider, {
|
|
134
|
-
systemPrompt,
|
|
135
|
-
tools,
|
|
136
|
-
maxIterations: this.maxIterations,
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
const result = await agent.run(task);
|
|
140
|
-
const finalResult = result.content || '任务已完成,但未生成最终响应。';
|
|
141
|
-
|
|
142
|
-
logger.info({ taskId }, 'Subagent completed successfully');
|
|
143
|
-
await this.announceResult(taskId, label, task, finalResult, origin, 'ok');
|
|
144
|
-
} catch (error) {
|
|
145
|
-
const errorMsg = `Error: ${error}`;
|
|
146
|
-
logger.error({ taskId, error }, 'Subagent failed');
|
|
147
|
-
await this.announceResult(taskId, label, task, errorMsg, origin, 'error');
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
private async announceResult(
|
|
152
|
-
taskId: string,
|
|
153
|
-
label: string,
|
|
154
|
-
task: string,
|
|
155
|
-
result: string,
|
|
156
|
-
origin: SubagentOrigin,
|
|
157
|
-
status: 'ok' | 'error',
|
|
158
|
-
): Promise<void> {
|
|
159
|
-
if (!this.resultSender) {
|
|
160
|
-
logger.warn({ taskId }, 'No result sender configured, discarding subagent result');
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const statusText = status === 'ok' ? '已完成' : '执行失败';
|
|
165
|
-
const announceContent = `[后台任务 '${label}' ${statusText}]\n\n任务: ${task}\n\n结果:\n${result}`;
|
|
166
|
-
|
|
167
|
-
try {
|
|
168
|
-
await this.resultSender(origin, announceContent);
|
|
169
|
-
logger.debug({ taskId, origin }, 'Subagent announced result');
|
|
170
|
-
} catch (e) {
|
|
171
|
-
logger.error({ taskId, error: e }, 'Failed to announce subagent result');
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
private buildSubagentPrompt(task: string): string {
|
|
176
|
-
return `# 子任务 Agent
|
|
177
|
-
|
|
178
|
-
你是一个被主 agent 派生出来执行特定任务的子 agent。
|
|
179
|
-
|
|
180
|
-
## 你的任务
|
|
181
|
-
${task}
|
|
182
|
-
|
|
183
|
-
## 规则
|
|
184
|
-
1. 专注完成分配的任务,不做其他事情
|
|
185
|
-
2. 你的最终回复会被报告给主 agent,并转达给用户
|
|
186
|
-
3. 不要发起对话或承担额外任务
|
|
187
|
-
4. 回复要简洁但信息充分
|
|
188
|
-
|
|
189
|
-
## 你可以做的
|
|
190
|
-
- 读写工作区内的文件
|
|
191
|
-
- 执行 Shell 命令
|
|
192
|
-
- 搜索和抓取网页
|
|
193
|
-
- 彻底完成任务
|
|
194
|
-
|
|
195
|
-
## 你不能做的
|
|
196
|
-
- 直接向用户发送消息
|
|
197
|
-
- 派生其他子任务
|
|
198
|
-
- 访问主 agent 的对话历史
|
|
199
|
-
|
|
200
|
-
## 工作区
|
|
201
|
-
你的工作区路径: ${this.workspace}
|
|
202
|
-
|
|
203
|
-
完成任务后,请提供清晰的发现或操作摘要。`;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
dispose(): void {
|
|
207
|
-
this.runningTasks.clear();
|
|
208
|
-
}
|
|
209
|
-
}
|
package/src/ai/tone-detector.ts
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ToneDetector — 轻量级情绪/语气检测
|
|
3
|
-
*
|
|
4
|
-
* 通过标点符号、emoji 密度、关键词分析用户语气,
|
|
5
|
-
* 生成一条 hint 注入 system prompt,让 AI 的回复匹配用户情绪。
|
|
6
|
-
*
|
|
7
|
-
* 零 LLM 开销,纯正则/统计分析。
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
export type Tone = 'neutral' | 'frustrated' | 'excited' | 'questioning' | 'sad' | 'urgent';
|
|
11
|
-
|
|
12
|
-
interface ToneResult {
|
|
13
|
-
tone: Tone;
|
|
14
|
-
hint: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// 常见负面情绪词
|
|
18
|
-
const FRUSTRATED_WORDS = /不行|不对|又错|还是不|怎么回事|搞不定|烦死|崩溃|无语|什么鬼|bug|报错|失败|出问题/;
|
|
19
|
-
const SAD_WORDS = /难过|伤心|失落|遗憾|可惜|唉|哎|不开心|郁闷|心累/;
|
|
20
|
-
const URGENT_WORDS = /急|赶紧|马上|立刻|紧急|尽快|快点|asap|hurry/i;
|
|
21
|
-
const EXCITED_WORDS = /太好了|太棒了|厉害|牛|可以|成功|搞定|完美|赞|nice|amazing|awesome|cool/i;
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* 检测用户消息的情绪语气
|
|
25
|
-
*/
|
|
26
|
-
export function detectTone(message: string): ToneResult {
|
|
27
|
-
const len = message.length;
|
|
28
|
-
if (len === 0) return { tone: 'neutral', hint: '' };
|
|
29
|
-
|
|
30
|
-
// 统计特征
|
|
31
|
-
const exclamations = (message.match(/!/g) || []).length + (message.match(/!/g) || []).length;
|
|
32
|
-
const questions = (message.match(/\?/g) || []).length + (message.match(/?/g) || []).length;
|
|
33
|
-
const ellipsis = (message.match(/\.\.\./g) || []).length + (message.match(/…/g) || []).length;
|
|
34
|
-
const capsRatio = len > 5 ? (message.match(/[A-Z]/g) || []).length / len : 0;
|
|
35
|
-
|
|
36
|
-
// emoji 检测(常见 Unicode 范围)
|
|
37
|
-
const emojiCount = (message.match(/[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/gu) || []).length;
|
|
38
|
-
|
|
39
|
-
// 关键词检测
|
|
40
|
-
const isFrustrated = FRUSTRATED_WORDS.test(message);
|
|
41
|
-
const isSad = SAD_WORDS.test(message);
|
|
42
|
-
const isUrgent = URGENT_WORDS.test(message);
|
|
43
|
-
const isExcited = EXCITED_WORDS.test(message);
|
|
44
|
-
|
|
45
|
-
// 判定优先级: frustrated > urgent > sad > excited > questioning > neutral
|
|
46
|
-
if (isFrustrated || (exclamations >= 3 && !isExcited)) {
|
|
47
|
-
return {
|
|
48
|
-
tone: 'frustrated',
|
|
49
|
-
hint: '用户似乎有些沮丧或受挫,请用耐心、理解的语气回复,先表示共情再提供帮助。',
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (isUrgent) {
|
|
54
|
-
return {
|
|
55
|
-
tone: 'urgent',
|
|
56
|
-
hint: '用户似乎很着急,请直接给出解决方案,减少寒暄,优先效率。',
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (isSad || ellipsis >= 2) {
|
|
61
|
-
return {
|
|
62
|
-
tone: 'sad',
|
|
63
|
-
hint: '用户的语气似乎有些低落,请用温暖、关心的语气回复。',
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (isExcited || (emojiCount >= 2 && exclamations >= 1)) {
|
|
68
|
-
return {
|
|
69
|
-
tone: 'excited',
|
|
70
|
-
hint: '用户的心情不错,可以用更活泼、热情的语气回复。',
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (questions >= 2 || (questions >= 1 && len < 20)) {
|
|
75
|
-
return {
|
|
76
|
-
tone: 'questioning',
|
|
77
|
-
hint: '', // 提问是正常的,不需要特殊 hint
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (capsRatio > 0.5 && len > 10) {
|
|
82
|
-
return {
|
|
83
|
-
tone: 'frustrated',
|
|
84
|
-
hint: '用户使用了大量大写字母,可能在表达强烈情绪,请注意语气。',
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return { tone: 'neutral', hint: '' };
|
|
89
|
-
}
|
package/src/ai/tools.ts
DELETED
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @zhin.js/ai - Built-in Tools
|
|
3
|
-
* 内置工具集合 - 使用 ZhinTool 类定义
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { ZhinTool } from '../built/tool.js';
|
|
7
|
-
import type { Tool } from '../types.js';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* 计算器工具
|
|
11
|
-
* 支持基本运算和数学函数
|
|
12
|
-
*/
|
|
13
|
-
export const calculatorTool = new ZhinTool('calculator')
|
|
14
|
-
.desc('执行数学计算。支持基本运算(+、-、*、/、^)和数学函数(sin、cos、sqrt 等)')
|
|
15
|
-
.keyword('计算', '算', 'calc', '数学', '求值', '运算')
|
|
16
|
-
.tag('math', 'utility')
|
|
17
|
-
.param('expression', { type: 'string', description: '数学表达式,例如 "2 + 3 * 4" 或 "sqrt(16)"' }, true)
|
|
18
|
-
.execute(async ({ expression }) => {
|
|
19
|
-
try {
|
|
20
|
-
// 安全的数学表达式求值
|
|
21
|
-
const sanitized = (expression as string)
|
|
22
|
-
.replace(/[^0-9+\-*/().^eEsincosqrtabspowlogxMathPI\s]/g, '')
|
|
23
|
-
.replace(/\bsqrt\b/g, 'Math.sqrt')
|
|
24
|
-
.replace(/\bsin\b/g, 'Math.sin')
|
|
25
|
-
.replace(/\bcos\b/g, 'Math.cos')
|
|
26
|
-
.replace(/\btan\b/g, 'Math.tan')
|
|
27
|
-
.replace(/\blog\b/g, 'Math.log')
|
|
28
|
-
.replace(/\babs\b/g, 'Math.abs')
|
|
29
|
-
.replace(/\bpow\b/g, 'Math.pow')
|
|
30
|
-
.replace(/\bPI\b/g, 'Math.PI')
|
|
31
|
-
.replace(/\bE\b(?![0-9])/g, 'Math.E')
|
|
32
|
-
.replace(/\^/g, '**');
|
|
33
|
-
|
|
34
|
-
const result = new Function(`return ${sanitized}`)();
|
|
35
|
-
return { result, expression };
|
|
36
|
-
} catch (error) {
|
|
37
|
-
return { error: '无法计算表达式', expression };
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* 时间工具
|
|
43
|
-
* 获取当前时间和日期
|
|
44
|
-
*/
|
|
45
|
-
export const timeTool = new ZhinTool('get_time')
|
|
46
|
-
.desc('获取当前时间和日期信息')
|
|
47
|
-
.keyword('时间', '日期', '几点', '今天', '现在', 'time', 'date')
|
|
48
|
-
.tag('time', 'utility')
|
|
49
|
-
.param('timezone', { type: 'string', description: '时区,例如 "Asia/Shanghai" 或 "UTC"' })
|
|
50
|
-
.param('format', { type: 'string', description: '输出格式: full(完整), date(日期), time(时间), timestamp(时间戳)' })
|
|
51
|
-
.execute(async ({ timezone, format = 'full' }) => {
|
|
52
|
-
const now = new Date();
|
|
53
|
-
const options: Intl.DateTimeFormatOptions = {
|
|
54
|
-
timeZone: (timezone as string) || 'Asia/Shanghai',
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
switch (format) {
|
|
58
|
-
case 'date':
|
|
59
|
-
options.dateStyle = 'full';
|
|
60
|
-
break;
|
|
61
|
-
case 'time':
|
|
62
|
-
options.timeStyle = 'long';
|
|
63
|
-
break;
|
|
64
|
-
case 'timestamp':
|
|
65
|
-
return { timestamp: now.getTime(), iso: now.toISOString() };
|
|
66
|
-
default:
|
|
67
|
-
options.dateStyle = 'full';
|
|
68
|
-
options.timeStyle = 'long';
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return {
|
|
72
|
-
formatted: now.toLocaleString('zh-CN', options),
|
|
73
|
-
timestamp: now.getTime(),
|
|
74
|
-
iso: now.toISOString(),
|
|
75
|
-
};
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* 网页搜索工具
|
|
80
|
-
* 需要配置搜索 API
|
|
81
|
-
*/
|
|
82
|
-
export const searchTool = new ZhinTool('web_search')
|
|
83
|
-
.desc('在互联网上搜索信息(需要配置搜索 API)')
|
|
84
|
-
.keyword('搜索', '搜一下', '查找', '查一查', 'search', 'google')
|
|
85
|
-
.tag('search', 'web')
|
|
86
|
-
.param('query', { type: 'string', description: '搜索关键词' }, true)
|
|
87
|
-
.param('limit', { type: 'number', description: '返回结果数量(默认 5)' })
|
|
88
|
-
.execute(async ({ query, limit = 5 }) => {
|
|
89
|
-
// 默认实现提示需要配置
|
|
90
|
-
return {
|
|
91
|
-
query,
|
|
92
|
-
error: '搜索功能未配置,请提供搜索 API',
|
|
93
|
-
hint: '可以集成 SerpAPI、Bing Search API 或 Google Custom Search',
|
|
94
|
-
};
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* 代码执行工具
|
|
99
|
-
* 在安全沙箱中执行 JavaScript
|
|
100
|
-
*/
|
|
101
|
-
export const codeRunnerTool = new ZhinTool('run_code')
|
|
102
|
-
.desc('执行 JavaScript 代码(在安全沙箱中)')
|
|
103
|
-
.keyword('代码', '执行', '运行', 'code', 'js', 'javascript')
|
|
104
|
-
.tag('code', 'dev')
|
|
105
|
-
.param('code', { type: 'string', description: 'JavaScript 代码' }, true)
|
|
106
|
-
.execute(async ({ code }) => {
|
|
107
|
-
try {
|
|
108
|
-
// 简单的沙箱执行(生产环境应使用 vm2 或 isolated-vm)
|
|
109
|
-
const result = new Function(`
|
|
110
|
-
'use strict';
|
|
111
|
-
const console = { log: (...args) => args.join(' ') };
|
|
112
|
-
return (function() { ${code} })();
|
|
113
|
-
`)();
|
|
114
|
-
|
|
115
|
-
return {
|
|
116
|
-
success: true,
|
|
117
|
-
result: result !== undefined ? String(result) : 'undefined',
|
|
118
|
-
};
|
|
119
|
-
} catch (error) {
|
|
120
|
-
return {
|
|
121
|
-
success: false,
|
|
122
|
-
error: error instanceof Error ? error.message : String(error),
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* HTTP 请求工具
|
|
129
|
-
* 发送 HTTP 请求获取数据
|
|
130
|
-
*/
|
|
131
|
-
export const httpTool = new ZhinTool('http_request')
|
|
132
|
-
.desc('发送 HTTP 请求获取数据')
|
|
133
|
-
.keyword('http', 'api', '请求', '接口', 'url', 'fetch')
|
|
134
|
-
.tag('http', 'web')
|
|
135
|
-
.param('url', { type: 'string', description: '请求 URL' }, true)
|
|
136
|
-
.param('method', { type: 'string', description: 'HTTP 方法: GET, POST, PUT, DELETE(默认 GET)' })
|
|
137
|
-
.param('headers', { type: 'object', description: '请求头(JSON 对象)' })
|
|
138
|
-
.param('body', { type: 'string', description: '请求体(JSON 字符串)' })
|
|
139
|
-
.execute(async ({ url, method = 'GET', headers = {}, body }) => {
|
|
140
|
-
try {
|
|
141
|
-
const response = await fetch(url as string, {
|
|
142
|
-
method: method as string,
|
|
143
|
-
headers: {
|
|
144
|
-
'Content-Type': 'application/json',
|
|
145
|
-
...(headers as Record<string, string>),
|
|
146
|
-
},
|
|
147
|
-
body: body ? (body as string) : undefined,
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
const contentType = response.headers.get('content-type') || '';
|
|
151
|
-
let data: any;
|
|
152
|
-
|
|
153
|
-
if (contentType.includes('application/json')) {
|
|
154
|
-
data = await response.json();
|
|
155
|
-
} else {
|
|
156
|
-
data = await response.text();
|
|
157
|
-
// 限制文本长度
|
|
158
|
-
if (data.length > 5000) {
|
|
159
|
-
data = data.substring(0, 5000) + '... (truncated)';
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return {
|
|
164
|
-
status: response.status,
|
|
165
|
-
statusText: response.statusText,
|
|
166
|
-
data,
|
|
167
|
-
};
|
|
168
|
-
} catch (error) {
|
|
169
|
-
return {
|
|
170
|
-
error: error instanceof Error ? error.message : String(error),
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* 记忆工具
|
|
177
|
-
* 让 AI 记住重要信息
|
|
178
|
-
*/
|
|
179
|
-
export const memoryTool = new ZhinTool('remember')
|
|
180
|
-
.desc('记住用户告诉你的重要信息,以便后续对话中使用')
|
|
181
|
-
.keyword('记住', '记忆', '记下', 'remember', '别忘了')
|
|
182
|
-
.tag('memory', 'context')
|
|
183
|
-
.param('key', { type: 'string', description: '记忆的标识符,如 "user_name", "preference"' }, true)
|
|
184
|
-
.param('value', { type: 'string', description: '要记住的内容' }, true)
|
|
185
|
-
.execute(async ({ key, value }, context) => {
|
|
186
|
-
// 这里需要与 session/context manager 集成
|
|
187
|
-
return {
|
|
188
|
-
success: true,
|
|
189
|
-
message: `已记住 ${key}: ${value}`,
|
|
190
|
-
key,
|
|
191
|
-
value,
|
|
192
|
-
};
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* 获取所有内置工具(ZhinTool 实例)
|
|
197
|
-
* 注意:天气工具已移除,请使用 weather-tool 插件,支持多平台配置
|
|
198
|
-
*/
|
|
199
|
-
export function getBuiltinTools(): ZhinTool[] {
|
|
200
|
-
return [
|
|
201
|
-
calculatorTool,
|
|
202
|
-
timeTool,
|
|
203
|
-
];
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* 获取所有可用的内置工具(包括可选工具)
|
|
208
|
-
*/
|
|
209
|
-
export function getAllBuiltinTools(): ZhinTool[] {
|
|
210
|
-
return [
|
|
211
|
-
calculatorTool,
|
|
212
|
-
timeTool,
|
|
213
|
-
searchTool,
|
|
214
|
-
codeRunnerTool,
|
|
215
|
-
httpTool,
|
|
216
|
-
memoryTool,
|
|
217
|
-
];
|
|
218
|
-
}
|
package/src/ai/user-profile.ts
DELETED
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* UserProfileStore — 用户画像存储
|
|
3
|
-
*
|
|
4
|
-
* 持久化存储用户偏好和特征,让 AI 跨会话记住用户的个性化信息。
|
|
5
|
-
*
|
|
6
|
-
* ai_user_profiles 表:
|
|
7
|
-
* ┌──────────────────────────────────────────────────────────┐
|
|
8
|
-
* │ user_id | key | value | updated_at │
|
|
9
|
-
* │ u1 | name | 小明 | 1700000000 │
|
|
10
|
-
* │ u1 | style | 简洁正式 | 1700000001 │
|
|
11
|
-
* │ u1 | interests | 编程,天气 | 1700000010 │
|
|
12
|
-
* └──────────────────────────────────────────────────────────┘
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { Logger } from '@zhin.js/logger';
|
|
16
|
-
|
|
17
|
-
const logger = new Logger(null, 'UserProfile');
|
|
18
|
-
|
|
19
|
-
// ============================================================================
|
|
20
|
-
// 数据库模型
|
|
21
|
-
// ============================================================================
|
|
22
|
-
|
|
23
|
-
export const AI_USER_PROFILE_MODEL = {
|
|
24
|
-
user_id: { type: 'text' as const, nullable: false },
|
|
25
|
-
key: { type: 'text' as const, nullable: false },
|
|
26
|
-
value: { type: 'text' as const, nullable: false },
|
|
27
|
-
updated_at: { type: 'integer' as const, default: 0 },
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
// ============================================================================
|
|
31
|
-
// 类型
|
|
32
|
-
// ============================================================================
|
|
33
|
-
|
|
34
|
-
interface ProfileRecord {
|
|
35
|
-
id?: number;
|
|
36
|
-
user_id: string;
|
|
37
|
-
key: string;
|
|
38
|
-
value: string;
|
|
39
|
-
updated_at: number;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* 数据库模型接口(与 RelatedModel 的链式查询 API 对齐)
|
|
44
|
-
*/
|
|
45
|
-
interface DbModel {
|
|
46
|
-
select(...fields: string[]): any; // 返回 Selection (thenable, 支持 .where())
|
|
47
|
-
create(data: Record<string, any>): Promise<any>;
|
|
48
|
-
update(data: Partial<any>): any; // 返回 Updation (thenable, 支持 .where())
|
|
49
|
-
delete(condition: Record<string, any>): any; // 返回 Deletion (thenable, 支持 .where())
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// ============================================================================
|
|
53
|
-
// Store 接口
|
|
54
|
-
// ============================================================================
|
|
55
|
-
|
|
56
|
-
interface IProfileStore {
|
|
57
|
-
get(userId: string, key: string): Promise<string | null>;
|
|
58
|
-
getAll(userId: string): Promise<Record<string, string>>;
|
|
59
|
-
set(userId: string, key: string, value: string): Promise<void>;
|
|
60
|
-
delete(userId: string, key: string): Promise<boolean>;
|
|
61
|
-
dispose(): void;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// ============================================================================
|
|
65
|
-
// 内存实现
|
|
66
|
-
// ============================================================================
|
|
67
|
-
|
|
68
|
-
class MemoryProfileStore implements IProfileStore {
|
|
69
|
-
private data: Map<string, Map<string, string>> = new Map();
|
|
70
|
-
|
|
71
|
-
async get(userId: string, key: string): Promise<string | null> {
|
|
72
|
-
return this.data.get(userId)?.get(key) ?? null;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async getAll(userId: string): Promise<Record<string, string>> {
|
|
76
|
-
const map = this.data.get(userId);
|
|
77
|
-
if (!map) return {};
|
|
78
|
-
return Object.fromEntries(map);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async set(userId: string, key: string, value: string): Promise<void> {
|
|
82
|
-
let map = this.data.get(userId);
|
|
83
|
-
if (!map) { map = new Map(); this.data.set(userId, map); }
|
|
84
|
-
map.set(key, value);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
async delete(userId: string, key: string): Promise<boolean> {
|
|
88
|
-
return this.data.get(userId)?.delete(key) ?? false;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
dispose(): void { this.data.clear(); }
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// ============================================================================
|
|
95
|
-
// 数据库实现
|
|
96
|
-
// ============================================================================
|
|
97
|
-
|
|
98
|
-
class DatabaseProfileStore implements IProfileStore {
|
|
99
|
-
constructor(private model: DbModel) {}
|
|
100
|
-
|
|
101
|
-
async get(userId: string, key: string): Promise<string | null> {
|
|
102
|
-
const records = await this.model.select().where({ user_id: userId, key }) as ProfileRecord[];
|
|
103
|
-
return records.length > 0 ? records[0].value : null;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
async getAll(userId: string): Promise<Record<string, string>> {
|
|
107
|
-
const records = await this.model.select().where({ user_id: userId }) as ProfileRecord[];
|
|
108
|
-
const result: Record<string, string> = {};
|
|
109
|
-
for (const r of records) result[r.key] = r.value;
|
|
110
|
-
return result;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
async set(userId: string, key: string, value: string): Promise<void> {
|
|
114
|
-
const existing = await this.model.select().where({ user_id: userId, key });
|
|
115
|
-
if (existing.length > 0) {
|
|
116
|
-
await this.model.update({ value, updated_at: Date.now() }).where({ user_id: userId, key });
|
|
117
|
-
} else {
|
|
118
|
-
await this.model.create({ user_id: userId, key, value, updated_at: Date.now() });
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
async delete(userId: string, key: string): Promise<boolean> {
|
|
123
|
-
const existing = await this.model.select().where({ user_id: userId, key });
|
|
124
|
-
if (existing.length === 0) return false;
|
|
125
|
-
await this.model.delete({ user_id: userId, key });
|
|
126
|
-
return true;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
dispose(): void {}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// ============================================================================
|
|
133
|
-
// UserProfileStore
|
|
134
|
-
// ============================================================================
|
|
135
|
-
|
|
136
|
-
export class UserProfileStore {
|
|
137
|
-
private store: IProfileStore;
|
|
138
|
-
|
|
139
|
-
constructor() {
|
|
140
|
-
this.store = new MemoryProfileStore();
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
upgradeToDatabase(model: DbModel): void {
|
|
144
|
-
const old = this.store;
|
|
145
|
-
this.store = new DatabaseProfileStore(model);
|
|
146
|
-
old.dispose();
|
|
147
|
-
logger.debug('UserProfileStore: upgraded to database storage');
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
async get(userId: string, key: string): Promise<string | null> {
|
|
151
|
-
return this.store.get(userId, key);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
async getAll(userId: string): Promise<Record<string, string>> {
|
|
155
|
-
return this.store.getAll(userId);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
async set(userId: string, key: string, value: string): Promise<void> {
|
|
159
|
-
return this.store.set(userId, key, value);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
async delete(userId: string, key: string): Promise<boolean> {
|
|
163
|
-
return this.store.delete(userId, key);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* 构建用户画像摘要,注入 system prompt
|
|
168
|
-
*/
|
|
169
|
-
async buildProfileSummary(userId: string): Promise<string> {
|
|
170
|
-
const profile = await this.store.getAll(userId);
|
|
171
|
-
const entries = Object.entries(profile);
|
|
172
|
-
if (entries.length === 0) return '';
|
|
173
|
-
|
|
174
|
-
const lines = entries.map(([k, v]) => `- ${k}: ${v}`).join('\n');
|
|
175
|
-
return `[用户画像]\n${lines}`;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
dispose(): void {
|
|
179
|
-
this.store.dispose();
|
|
180
|
-
}
|
|
181
|
-
}
|