foliko 1.1.77 → 1.1.79
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/.env.example +56 -56
- package/cli/src/commands/chat.js +5 -1
- package/cli/src/ui/chat-ui-old.js +299 -101
- package/package.json +1 -1
- package/skills/find-skills/SKILL.md +133 -133
- package/src/core/agent-chat.js +1 -1
- package/src/core/subagent.js +2 -2
- package/src/utils/chat-queue.js +4 -3
- package/website_v2/styles/animations.css +7 -7
package/.env.example
CHANGED
|
@@ -1,56 +1,56 @@
|
|
|
1
|
-
# ========== AI Configuration ==========
|
|
2
|
-
# 最大输出 tokens(影响 AI 回复长度)
|
|
3
|
-
MAX_OUTPUT_TOKENS=8192
|
|
4
|
-
# AI Provider: minimax, deepseek, openai, anthropic 等
|
|
5
|
-
FOLIKO_PROVIDER=minimax
|
|
6
|
-
|
|
7
|
-
# AI Model(如果未设置,使用 provider 默认值)
|
|
8
|
-
# MiniMax: MiniMax-M2.7
|
|
9
|
-
# DeepSeek: deepseek-chat, deepseek-coder 等
|
|
10
|
-
FOLIKO_MODEL=MiniMax-M2.7
|
|
11
|
-
|
|
12
|
-
# API Base URL(如果未设置,使用 provider 默认值)
|
|
13
|
-
# MiniMax: https://api.minimaxi.com/v1
|
|
14
|
-
# DeepSeek: https://api.deepseek.com/v1
|
|
15
|
-
FOLIKO_BASE_URL=https://api.minimaxi.com/v1
|
|
16
|
-
|
|
17
|
-
# API Key(通用,如果未设置则尝试 provider 专用 key)
|
|
18
|
-
FOLIKO_API_KEY=sk-your-api-key
|
|
19
|
-
|
|
20
|
-
# Provider 专用 API Key(可选,如果 FOLIKO_API_KEY 未设置则使用这些)
|
|
21
|
-
DEEPSEEK_API_KEY=sk-your-deepseek-api-key
|
|
22
|
-
MINIMAX_API_KEY=sk-your-minimax-api-key
|
|
23
|
-
|
|
24
|
-
# ========== Email Configuration ==========
|
|
25
|
-
# SMTP Settings (for sending emails)
|
|
26
|
-
SMTP_HOST=smtp.gmail.com
|
|
27
|
-
SMTP_PORT=587
|
|
28
|
-
SMTP_SECURE=false
|
|
29
|
-
SMTP_USER=your-email@gmail.com
|
|
30
|
-
SMTP_PASS=your-app-password
|
|
31
|
-
|
|
32
|
-
# IMAP Settings (for reading emails)
|
|
33
|
-
IMAP_HOST=imap.gmail.com
|
|
34
|
-
IMAP_PORT=993
|
|
35
|
-
IMAP_USER=your-email@gmail.com
|
|
36
|
-
IMAP_PASS=your-app-password
|
|
37
|
-
|
|
38
|
-
# Default sender email address
|
|
39
|
-
FROM_EMAIL=your-email@gmail.com
|
|
40
|
-
|
|
41
|
-
# ========== Telegram Bot (optional) ==========
|
|
42
|
-
TELEGRAM_BOT_TOKEN=your-telegram-bot-token
|
|
43
|
-
|
|
44
|
-
# ========== Feishu Bot (optional) ==========
|
|
45
|
-
FEISHU_APP_ID=cli_xxxxxxxxxxx
|
|
46
|
-
FEISHU_APP_SECRET=app_secret
|
|
47
|
-
|
|
48
|
-
# ========== Web Server (optional) ==========
|
|
49
|
-
# Web 服务端口,默认 8088
|
|
50
|
-
WEB_PORT=3000
|
|
51
|
-
|
|
52
|
-
# Web 服务主机,默认 127.0.0.1
|
|
53
|
-
WEB_HOST=127.0.0.1
|
|
54
|
-
|
|
55
|
-
# 公网访问的 base URL(用于生成 webhook URL 等),不设置则使用 host:port,最好部署在docker中可以暴露自定义域名
|
|
56
|
-
WEB_BASE_URL=https://your-domain.com
|
|
1
|
+
# ========== AI Configuration ==========
|
|
2
|
+
# 最大输出 tokens(影响 AI 回复长度)
|
|
3
|
+
MAX_OUTPUT_TOKENS=8192
|
|
4
|
+
# AI Provider: minimax, deepseek, openai, anthropic 等
|
|
5
|
+
FOLIKO_PROVIDER=minimax
|
|
6
|
+
|
|
7
|
+
# AI Model(如果未设置,使用 provider 默认值)
|
|
8
|
+
# MiniMax: MiniMax-M2.7
|
|
9
|
+
# DeepSeek: deepseek-chat, deepseek-coder 等
|
|
10
|
+
FOLIKO_MODEL=MiniMax-M2.7
|
|
11
|
+
|
|
12
|
+
# API Base URL(如果未设置,使用 provider 默认值)
|
|
13
|
+
# MiniMax: https://api.minimaxi.com/v1
|
|
14
|
+
# DeepSeek: https://api.deepseek.com/v1
|
|
15
|
+
FOLIKO_BASE_URL=https://api.minimaxi.com/v1
|
|
16
|
+
|
|
17
|
+
# API Key(通用,如果未设置则尝试 provider 专用 key)
|
|
18
|
+
FOLIKO_API_KEY=sk-your-api-key
|
|
19
|
+
|
|
20
|
+
# Provider 专用 API Key(可选,如果 FOLIKO_API_KEY 未设置则使用这些)
|
|
21
|
+
DEEPSEEK_API_KEY=sk-your-deepseek-api-key
|
|
22
|
+
MINIMAX_API_KEY=sk-your-minimax-api-key
|
|
23
|
+
|
|
24
|
+
# ========== Email Configuration ==========
|
|
25
|
+
# SMTP Settings (for sending emails)
|
|
26
|
+
SMTP_HOST=smtp.gmail.com
|
|
27
|
+
SMTP_PORT=587
|
|
28
|
+
SMTP_SECURE=false
|
|
29
|
+
SMTP_USER=your-email@gmail.com
|
|
30
|
+
SMTP_PASS=your-app-password
|
|
31
|
+
|
|
32
|
+
# IMAP Settings (for reading emails)
|
|
33
|
+
IMAP_HOST=imap.gmail.com
|
|
34
|
+
IMAP_PORT=993
|
|
35
|
+
IMAP_USER=your-email@gmail.com
|
|
36
|
+
IMAP_PASS=your-app-password
|
|
37
|
+
|
|
38
|
+
# Default sender email address
|
|
39
|
+
FROM_EMAIL=your-email@gmail.com
|
|
40
|
+
|
|
41
|
+
# ========== Telegram Bot (optional) ==========
|
|
42
|
+
TELEGRAM_BOT_TOKEN=your-telegram-bot-token
|
|
43
|
+
|
|
44
|
+
# ========== Feishu Bot (optional) ==========
|
|
45
|
+
FEISHU_APP_ID=cli_xxxxxxxxxxx
|
|
46
|
+
FEISHU_APP_SECRET=app_secret
|
|
47
|
+
|
|
48
|
+
# ========== Web Server (optional) ==========
|
|
49
|
+
# Web 服务端口,默认 8088
|
|
50
|
+
WEB_PORT=3000
|
|
51
|
+
|
|
52
|
+
# Web 服务主机,默认 127.0.0.1
|
|
53
|
+
WEB_HOST=127.0.0.1
|
|
54
|
+
|
|
55
|
+
# 公网访问的 base URL(用于生成 webhook URL 等),不设置则使用 host:port,最好部署在docker中可以暴露自定义域名
|
|
56
|
+
WEB_BASE_URL=https://your-domain.com
|
package/cli/src/commands/chat.js
CHANGED
|
@@ -6,6 +6,7 @@ const path = require('path');
|
|
|
6
6
|
|
|
7
7
|
const { Framework } = require('../../../src');
|
|
8
8
|
const { ChatUI } = require('../ui/chat-ui');
|
|
9
|
+
const { ChatUI: OldChatUI } = require('../ui/chat-ui-old');
|
|
9
10
|
const { logger, LOG_LEVELS } = require('../../../src/utils/logger');
|
|
10
11
|
const { DEFAULT_PROVIDERS } = require('../../../src/core/provider');
|
|
11
12
|
|
|
@@ -78,6 +79,8 @@ function parseArgs(args) {
|
|
|
78
79
|
options.quiet = true;
|
|
79
80
|
} else if (arg === '--no-quiet') {
|
|
80
81
|
options.quiet = false;
|
|
82
|
+
} else if (arg === '--old') {
|
|
83
|
+
options.useOldUI = true;
|
|
81
84
|
}
|
|
82
85
|
}
|
|
83
86
|
|
|
@@ -200,7 +203,8 @@ async function chatCommand(args) {
|
|
|
200
203
|
|
|
201
204
|
|
|
202
205
|
// 初始化 UI
|
|
203
|
-
const
|
|
206
|
+
const UI = options.useOldUI ? OldChatUI : ChatUI;
|
|
207
|
+
const ui = new UI(agent, { stream: options.stream });
|
|
204
208
|
|
|
205
209
|
// 监听通知事件,在 CLI 中显示
|
|
206
210
|
// 显示当前会话的通知,以及没有指定 sessionId 的广播通知
|
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
const readline = require('readline');
|
|
7
7
|
const { CLEAR_LINE, CYAN, DIM, GREEN, RED, YELLOW, colored } = require('../utils/ansi');
|
|
8
8
|
const { renderLine } = require('../utils/markdown');
|
|
9
|
-
const { cleanResponse } = require('../../../src/utils');
|
|
9
|
+
const { cleanResponse, logger } = require('../../../src/utils');
|
|
10
|
+
const { logEmitter } = require('../../../src/utils/logger');
|
|
11
|
+
const Queue = require('js-queue');
|
|
10
12
|
|
|
11
13
|
class ChatUI {
|
|
12
14
|
constructor(agent, options = {}) {
|
|
@@ -21,6 +23,9 @@ class ChatUI {
|
|
|
21
23
|
this.sessionId = options.sessionId || 'cli_default';
|
|
22
24
|
this.sessionPlugin = null;
|
|
23
25
|
|
|
26
|
+
// 队列系统
|
|
27
|
+
this.queue = new Queue();
|
|
28
|
+
|
|
24
29
|
// 中断状态(供监听器访问)
|
|
25
30
|
this.interrupted = false;
|
|
26
31
|
|
|
@@ -30,6 +35,15 @@ class ChatUI {
|
|
|
30
35
|
// 流式输出缓冲区(可重置)
|
|
31
36
|
this._lineBuffer = '';
|
|
32
37
|
|
|
38
|
+
// 基础命令
|
|
39
|
+
this.baseCommands = [
|
|
40
|
+
{ name: "compress", description: "压缩记录" },
|
|
41
|
+
{ name: "clear", description: "清除记录" },
|
|
42
|
+
{ name: "exit", description: "退出" },
|
|
43
|
+
{ name: "help", description: "显示帮助信息" },
|
|
44
|
+
{ name: "reload", description: "重载技能" },
|
|
45
|
+
];
|
|
46
|
+
|
|
33
47
|
// 如果 agent 有 framework,获取 SessionPlugin
|
|
34
48
|
if (agent.framework) {
|
|
35
49
|
this.sessionPlugin = agent.framework.pluginManager.get('session');
|
|
@@ -42,6 +56,246 @@ class ChatUI {
|
|
|
42
56
|
// 创建 session scope 并设置监听器(只需一次)
|
|
43
57
|
this.sessionScope = this.agent.createSessionScope(this.sessionId);
|
|
44
58
|
this._setupSessionListeners();
|
|
59
|
+
this._setupFrameworkListeners();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 清理监听器
|
|
65
|
+
*/
|
|
66
|
+
dispose() {
|
|
67
|
+
if (this._logHandler && this.agent.framework) {
|
|
68
|
+
logger.off('log', this._logHandler);
|
|
69
|
+
this._logHandler = null;
|
|
70
|
+
}
|
|
71
|
+
if (this._notificationHandler && this.agent.framework) {
|
|
72
|
+
this.agent.framework.off('notification', this._notificationHandler);
|
|
73
|
+
this._notificationHandler = null;
|
|
74
|
+
}
|
|
75
|
+
if (this._usageHandler && this.agent.framework) {
|
|
76
|
+
this.agent.framework.off('agent:usage', this._usageHandler);
|
|
77
|
+
this._usageHandler = null;
|
|
78
|
+
}
|
|
79
|
+
if (this._skillReloadedHandler && this.agent.framework) {
|
|
80
|
+
this.agent.framework.off('skill:reloaded', this._skillReloadedHandler);
|
|
81
|
+
this._skillReloadedHandler = null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 设置 framework 事件监听(日志、通知、Usage、热重载)
|
|
87
|
+
*/
|
|
88
|
+
_setupFrameworkListeners() {
|
|
89
|
+
const levelDict = {
|
|
90
|
+
DEBUG: 'cyan',
|
|
91
|
+
INFO: 'dim',
|
|
92
|
+
LOG: 'dim',
|
|
93
|
+
WARN: 'yellow',
|
|
94
|
+
ERROR: 'red',
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// 日志监听
|
|
98
|
+
this._logHandler = (data) => {
|
|
99
|
+
if (!Object.keys(levelDict).includes(data.level)) return;
|
|
100
|
+
const level = `[${data.level}]`;
|
|
101
|
+
const levelKey = levelDict[data.level] || 'dim';
|
|
102
|
+
console.log(colored(level, RED) + colored(data.message, levelKey));
|
|
103
|
+
};
|
|
104
|
+
logger.on('log', this._logHandler);
|
|
105
|
+
|
|
106
|
+
// 通知监听
|
|
107
|
+
this._notificationHandler = (data) => {
|
|
108
|
+
if (data.sessionId && data.sessionId !== this.sessionId) return;
|
|
109
|
+
const time = data.timestamp ? new Date(data.timestamp).toLocaleTimeString('zh-CN') : '';
|
|
110
|
+
const notificationText = `[通知] ${data.title}${time ? ` (${time})` : ''}`;
|
|
111
|
+
console.log(colored(notificationText, CYAN));
|
|
112
|
+
if (data.message) {
|
|
113
|
+
console.log(colored(data.message, DIM));
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
this.agent.framework.on('notification', this._notificationHandler);
|
|
117
|
+
|
|
118
|
+
// Usage 监听
|
|
119
|
+
this._usageHandler = (data) => {
|
|
120
|
+
if (data.usage) {
|
|
121
|
+
const u = data.usage;
|
|
122
|
+
const input = u.promptTokens || u.prompt_tokens || 0;
|
|
123
|
+
const output = u.completionTokens || u.completion_tokens || 0;
|
|
124
|
+
// 更新 footerBar 如果存在
|
|
125
|
+
if (this.footerBar) {
|
|
126
|
+
this.footerBar.updateUsage(input, output);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
this.agent.framework.on('agent:usage', this._usageHandler);
|
|
131
|
+
|
|
132
|
+
// Skill 热重载监听
|
|
133
|
+
this._skillReloadedHandler = () => {
|
|
134
|
+
console.log(colored('[提示] 技能已重载', CYAN));
|
|
135
|
+
};
|
|
136
|
+
this.agent.framework.on('skill:reloaded', this._skillReloadedHandler);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 统一命令执行入口
|
|
141
|
+
*/
|
|
142
|
+
async _executeCommand(trimmed) {
|
|
143
|
+
const cmd = trimmed.toLowerCase();
|
|
144
|
+
|
|
145
|
+
// 基础命令
|
|
146
|
+
if (cmd === '/clear' || cmd === '/clean') {
|
|
147
|
+
this._clearContext();
|
|
148
|
+
console.log(colored('[提示] 对话上下文已清除', CYAN));
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
if (cmd === '/compress') {
|
|
152
|
+
await this._compressContext();
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
if (cmd === '/exit') {
|
|
156
|
+
console.log(colored('[再见] 感谢使用 Foliko!', CYAN));
|
|
157
|
+
this.rl?.close();
|
|
158
|
+
setTimeout(() => process.exit(0), 500);
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
if (cmd === '/help') {
|
|
162
|
+
await this._showHelp();
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
if (cmd === '/reload') {
|
|
166
|
+
await this._reloadSkills();
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Skill 命令 (格式: skillname:cmdname)
|
|
171
|
+
if (trimmed.startsWith('/') && trimmed.includes(':')) {
|
|
172
|
+
const cmdName = trimmed.slice(1).split(' ')[0];
|
|
173
|
+
const cmdArgs = trimmed.slice(cmdName.length + 2);
|
|
174
|
+
try {
|
|
175
|
+
const result = await this.agent.framework.executeTool('ext_call', {
|
|
176
|
+
plugin: 'skill',
|
|
177
|
+
tool: cmdName,
|
|
178
|
+
args: { args: cmdArgs }
|
|
179
|
+
});
|
|
180
|
+
if (result.success !== false) {
|
|
181
|
+
console.log(colored(`[指令] ${cmdName}`, GREEN));
|
|
182
|
+
console.log(result.data || result);
|
|
183
|
+
} else {
|
|
184
|
+
console.log(colored(`[错误] ${result.error || '未知错误'}`, RED));
|
|
185
|
+
}
|
|
186
|
+
} catch (err) {
|
|
187
|
+
console.log(colored(`[错误] ${err.message}`, RED));
|
|
188
|
+
}
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// CommandRegistry 命令
|
|
193
|
+
const { getCommandRegistry } = require('../../../src/core/command-registry');
|
|
194
|
+
const registry = getCommandRegistry();
|
|
195
|
+
const cmdName = trimmed.slice(1).split(' ')[0];
|
|
196
|
+
const cmdArgs = trimmed.slice(cmdName.length + 2);
|
|
197
|
+
if (trimmed.startsWith('/') && cmdName) {
|
|
198
|
+
const cmd = registry.getCommand(cmdName);
|
|
199
|
+
if (cmd) {
|
|
200
|
+
try {
|
|
201
|
+
const context = {
|
|
202
|
+
sessionId: this.sessionId,
|
|
203
|
+
agent: this.agent,
|
|
204
|
+
ui: this,
|
|
205
|
+
framework: this.agent.framework,
|
|
206
|
+
};
|
|
207
|
+
registry.execute(cmdName, cmdArgs, context).catch(err => {
|
|
208
|
+
console.log(colored(`[命令错误] ${err.message}`, RED));
|
|
209
|
+
});
|
|
210
|
+
} catch (err) {
|
|
211
|
+
console.log(colored(`[命令错误] ${err.message}`, RED));
|
|
212
|
+
}
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* 显示帮助信息
|
|
222
|
+
*/
|
|
223
|
+
async _showHelp() {
|
|
224
|
+
const { getCommandRegistry } = require('../../../src/core/command-registry');
|
|
225
|
+
const registry = getCommandRegistry();
|
|
226
|
+
const commands = registry.getAllCommands();
|
|
227
|
+
|
|
228
|
+
console.log(colored('[帮助] 可用命令:', CYAN));
|
|
229
|
+
this.baseCommands.forEach(c => {
|
|
230
|
+
console.log(` ${colored('/' + c.name, CYAN)} - ${c.description}`);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// Skill 命令
|
|
234
|
+
const extExecutor = this.agent.framework?.pluginManager?.get('extension-executor');
|
|
235
|
+
if (extExecutor && typeof extExecutor.getSkillCommands === 'function') {
|
|
236
|
+
const skillCommands = extExecutor.getSkillCommands();
|
|
237
|
+
if (skillCommands.length > 0) {
|
|
238
|
+
console.log(colored('\n[技能命令]', CYAN));
|
|
239
|
+
skillCommands.forEach(cmd => {
|
|
240
|
+
console.log(` ${colored('/' + cmd.name, CYAN)} - ${cmd.description || '无描述'}`);
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (commands.length > 0) {
|
|
246
|
+
console.log(colored('\n[扩展命令]', CYAN));
|
|
247
|
+
for (const cmd of commands) {
|
|
248
|
+
const pluginTag = cmd.registeredBy ? ` [${cmd.registeredBy}]` : '';
|
|
249
|
+
console.log(` ${colored('/' + cmd.name, CYAN)}${pluginTag} - ${cmd.description || '无描述'}`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* 重载技能
|
|
256
|
+
*/
|
|
257
|
+
async _reloadSkills() {
|
|
258
|
+
try {
|
|
259
|
+
const result = await this.agent.framework.executeTool('reloadSkills', {});
|
|
260
|
+
if (result.success !== false) {
|
|
261
|
+
console.log(colored('[提示] 技能已重载', CYAN));
|
|
262
|
+
} else {
|
|
263
|
+
console.log(colored(`[错误] 重载失败: ${result.error}`, RED));
|
|
264
|
+
}
|
|
265
|
+
} catch (err) {
|
|
266
|
+
console.log(colored(`[错误] 重载失败: ${err.message}`, RED));
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* 清除上下文
|
|
272
|
+
*/
|
|
273
|
+
_clearContext() {
|
|
274
|
+
const { sessionId } = this;
|
|
275
|
+
this.agent.clearContext(sessionId);
|
|
276
|
+
|
|
277
|
+
if (this.sessionPlugin) {
|
|
278
|
+
const manager = this.sessionPlugin._getSessionManager(sessionId);
|
|
279
|
+
if (manager) manager.clearMessages();
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* 压缩上下文
|
|
285
|
+
*/
|
|
286
|
+
async _compressContext() {
|
|
287
|
+
console.log(colored('[压缩] 压缩中...', YELLOW));
|
|
288
|
+
try {
|
|
289
|
+
const { sessionId } = this;
|
|
290
|
+
const result = await this.agent.compressContext(sessionId);
|
|
291
|
+
console.log(colored(`[压缩] 压缩后: ${result.after} 条 (保留${result.keepRecent}条)`, YELLOW));
|
|
292
|
+
} catch (err) {
|
|
293
|
+
const msg = err.message || String(err);
|
|
294
|
+
if (msg.includes('No messages')) {
|
|
295
|
+
console.log(colored('[提示] 无消息可压缩', CYAN));
|
|
296
|
+
} else {
|
|
297
|
+
console.log(colored(`[错误] ${msg}`, RED));
|
|
298
|
+
}
|
|
45
299
|
}
|
|
46
300
|
}
|
|
47
301
|
|
|
@@ -67,7 +321,39 @@ class ChatUI {
|
|
|
67
321
|
console.log(renderLine(line, renderState));
|
|
68
322
|
}
|
|
69
323
|
}
|
|
324
|
+
} else if (chunk.type === 'tool-call') {
|
|
325
|
+
const args = chunk.input ? JSON.stringify(chunk.input).slice(0, 30) : '';
|
|
326
|
+
console.log(colored(`[Tool] ${chunk.toolName} ${args}...`, YELLOW));
|
|
327
|
+
} else if (chunk.type === 'tool-result') {
|
|
328
|
+
const result = chunk.result;
|
|
329
|
+
const shortResult = typeof result === 'string' ? result.slice(0, 30) : JSON.stringify(result).slice(0, 30);
|
|
330
|
+
console.log(colored(`[Tool] ${chunk.toolName} ${shortResult}...`, GREEN));
|
|
331
|
+
} else if (chunk.type === 'usage') {
|
|
332
|
+
if (this.footerBar) {
|
|
333
|
+
this.footerBar.updateUsage(chunk.inputTokens || 0, chunk.outputTokens || 0);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
this.sessionScope.on('message:start', async () => {
|
|
339
|
+
console.log(colored('● ', GREEN));
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
this.sessionScope.on('message:complete', async ({ content, requestId }) => {
|
|
343
|
+
try {
|
|
344
|
+
if (!content) {
|
|
345
|
+
const msg = '继续';
|
|
346
|
+
await this.agent.sendMessage(msg, { sessionId: this.sessionId, priority: 1 });
|
|
347
|
+
}
|
|
348
|
+
} catch (err) {}
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
this.sessionScope.on('queue:completed', () => {
|
|
352
|
+
// 清理状态
|
|
353
|
+
if (this._lineBuffer.trim() && !this.interrupted) {
|
|
354
|
+
console.log(renderLine(this._lineBuffer, renderState));
|
|
70
355
|
}
|
|
356
|
+
this._lineBuffer = '';
|
|
71
357
|
});
|
|
72
358
|
|
|
73
359
|
this.sessionScope.on('queue:failed', ({ error }) => {
|
|
@@ -145,53 +431,20 @@ class ChatUI {
|
|
|
145
431
|
return;
|
|
146
432
|
}
|
|
147
433
|
|
|
148
|
-
//
|
|
149
|
-
if (
|
|
150
|
-
|
|
151
|
-
this.rl.close();
|
|
434
|
+
// 尝试执行命令
|
|
435
|
+
if (await this._executeCommand(input.trim())) {
|
|
436
|
+
await this.promptUser();
|
|
152
437
|
return;
|
|
153
438
|
}
|
|
154
439
|
|
|
155
|
-
//
|
|
156
|
-
if (input.toLowerCase() === '
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
if (this.agent._chatSession) {
|
|
160
|
-
this.agent._chatSession.clearSessionMessages(sessionId, true);
|
|
161
|
-
}
|
|
162
|
-
if (this.agent._chatHandler) {
|
|
163
|
-
this.agent._chatHandler.clearHistory(sessionId);
|
|
164
|
-
}
|
|
165
|
-
// 清除 SessionContext 的消息和压缩计数
|
|
166
|
-
if (this.agent.framework) {
|
|
167
|
-
const sessionCtx = this.agent.framework.getSessionContext(sessionId);
|
|
168
|
-
if (sessionCtx) {
|
|
169
|
-
sessionCtx.clearMessages();
|
|
170
|
-
if (sessionCtx.compressionState) {
|
|
171
|
-
sessionCtx.compressionState.count = 0;
|
|
172
|
-
}
|
|
173
|
-
if (sessionCtx.metadata) {
|
|
174
|
-
sessionCtx.metadata.compressionCount = 0;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
// 同步清除 framework 缓存的 sessionContexts
|
|
178
|
-
const cachedCtx = this.agent.framework._sessionContexts?.get(sessionId);
|
|
179
|
-
if (cachedCtx) {
|
|
180
|
-
cachedCtx.clearMessages();
|
|
181
|
-
if (cachedCtx.compressionState) {
|
|
182
|
-
cachedCtx.compressionState.count = 0;
|
|
183
|
-
}
|
|
184
|
-
if (cachedCtx.metadata) {
|
|
185
|
-
cachedCtx.metadata.compressionCount = 0;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
console.log(`${colored('[提示]', CYAN)} 对话上下文已清除\n`);
|
|
190
|
-
await this.promptUser();
|
|
440
|
+
// 退出命令
|
|
441
|
+
if (input.toLowerCase() === 'exit' || input.toLowerCase() === 'quit') {
|
|
442
|
+
console.log(colored('[再见] 感谢使用 Foliko!', CYAN));
|
|
443
|
+
this.rl.close();
|
|
191
444
|
return;
|
|
192
445
|
}
|
|
193
446
|
|
|
194
|
-
//
|
|
447
|
+
// 发送消息(不等待,响应通过事件处理)
|
|
195
448
|
await this.sendMessage(input);
|
|
196
449
|
|
|
197
450
|
// 继续等待下一条消息
|
|
@@ -206,65 +459,10 @@ class ChatUI {
|
|
|
206
459
|
* 发送消息并显示响应
|
|
207
460
|
*/
|
|
208
461
|
async sendMessage(message) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
this.interrupted = true;
|
|
214
|
-
console.log(`\n${colored('[中断]', RED)} 输出已中断`);
|
|
215
|
-
}
|
|
216
|
-
};
|
|
217
|
-
|
|
218
|
-
process.on('SIGINT', interruptHandler);
|
|
219
|
-
|
|
220
|
-
try {
|
|
221
|
-
const { sessionId } = this;
|
|
222
|
-
|
|
223
|
-
if (this.stream) {
|
|
224
|
-
// 流式模式 - 使用已初始化的 sessionScope 监听器
|
|
225
|
-
console.log(colored('● ', GREEN));
|
|
226
|
-
|
|
227
|
-
// 清空缓冲区
|
|
228
|
-
this._lineBuffer = '';
|
|
229
|
-
|
|
230
|
-
// 调用 sendMessage 触发事件
|
|
231
|
-
await this.agent.sendMessage(message, { sessionId });
|
|
232
|
-
|
|
233
|
-
// 输出剩余内容
|
|
234
|
-
if (this._lineBuffer.trim() && !this.interrupted) {
|
|
235
|
-
console.log(renderLine(this._lineBuffer, { inThink: false, inCodeBlock: false }));
|
|
236
|
-
}
|
|
237
|
-
} else {
|
|
238
|
-
// 非流式模式
|
|
239
|
-
console.log(colored('○ ', CYAN) + '处理中...\n');
|
|
240
|
-
|
|
241
|
-
const result = await this.agent.chat(message, { sessionId });
|
|
242
|
-
|
|
243
|
-
if (this.interrupted) return;
|
|
244
|
-
|
|
245
|
-
const lines = result.message.split('\n');
|
|
246
|
-
for (const line of lines) {
|
|
247
|
-
if (line.trim()) {
|
|
248
|
-
console.log(renderLine(line, { inThink: false, inCodeBlock: false }));
|
|
249
|
-
} else {
|
|
250
|
-
console.log();
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
} catch (err) {
|
|
255
|
-
// 只打印简洁错误消息,不打印堆栈
|
|
256
|
-
if (!this.interrupted) {
|
|
257
|
-
const errName = err?.name || '';
|
|
258
|
-
const isRetryError = errName === 'AI_RetryError' || errName === 'RetryError';
|
|
259
|
-
const friendlyMessage = isRetryError
|
|
260
|
-
? 'AI 服务暂时不可用,请稍后重试'
|
|
261
|
-
: (err.message || '未知错误').split('\n')[0];
|
|
262
|
-
|
|
263
|
-
console.error(`\n${colored('[错误]', RED)} ${friendlyMessage}\n`);
|
|
264
|
-
}
|
|
265
|
-
} finally {
|
|
266
|
-
process.removeListener('SIGINT', interruptHandler);
|
|
267
|
-
}
|
|
462
|
+
const self = this;
|
|
463
|
+
const e=await this.agent.sendMessage(message, { sessionId: self.sessionId })
|
|
464
|
+
self._lineBuffer = '';
|
|
465
|
+
// 注意:不等待,直接返回。响应通过 sessionScope 事件处理
|
|
268
466
|
}
|
|
269
467
|
}
|
|
270
468
|
|
package/package.json
CHANGED
|
@@ -1,133 +1,133 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: find-skills
|
|
3
|
-
description: Helps users discover and install agent skills when they ask questions like "how do I do X", "find a skill for X", "is there a skill that can...", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Find Skills
|
|
7
|
-
|
|
8
|
-
This skill helps you discover and install skills from the open agent skills ecosystem.
|
|
9
|
-
|
|
10
|
-
## When to Use This Skill
|
|
11
|
-
|
|
12
|
-
Use this skill when the user:
|
|
13
|
-
|
|
14
|
-
- Asks "how do I do X" where X might be a common task with an existing skill
|
|
15
|
-
- Says "find a skill for X" or "is there a skill for X"
|
|
16
|
-
- Asks "can you do X" where X is a specialized capability
|
|
17
|
-
- Expresses interest in extending agent capabilities
|
|
18
|
-
- Wants to search for tools, templates, or workflows
|
|
19
|
-
- Mentions they wish they had help with a specific domain (design, testing, deployment, etc.)
|
|
20
|
-
|
|
21
|
-
## What is the Skills CLI?
|
|
22
|
-
|
|
23
|
-
The Skills CLI (`npx skills`) is the package manager for the open agent skills ecosystem. Skills are modular packages that extend agent capabilities with specialized knowledge, workflows, and tools.
|
|
24
|
-
|
|
25
|
-
**Key commands:**
|
|
26
|
-
|
|
27
|
-
- `npx skills find [query]` - Search for skills interactively or by keyword
|
|
28
|
-
- `npx skills add <package>` - Install a skill from GitHub or other sources
|
|
29
|
-
- `npx skills check` - Check for skill updates
|
|
30
|
-
- `npx skills update` - Update all installed skills
|
|
31
|
-
|
|
32
|
-
**Browse skills at:** https://skills.sh/
|
|
33
|
-
|
|
34
|
-
## How to Help Users Find Skills
|
|
35
|
-
|
|
36
|
-
### Step 1: Understand What They Need
|
|
37
|
-
|
|
38
|
-
When a user asks for help with something, identify:
|
|
39
|
-
|
|
40
|
-
1. The domain (e.g., React, testing, design, deployment)
|
|
41
|
-
2. The specific task (e.g., writing tests, creating animations, reviewing PRs)
|
|
42
|
-
3. Whether this is a common enough task that a skill likely exists
|
|
43
|
-
|
|
44
|
-
### Step 2: Search for Skills
|
|
45
|
-
|
|
46
|
-
Run the find command with a relevant query:
|
|
47
|
-
|
|
48
|
-
```bash
|
|
49
|
-
npx skills find [query]
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
For example:
|
|
53
|
-
|
|
54
|
-
- User asks "how do I make my React app faster?" → `npx skills find react performance`
|
|
55
|
-
- User asks "can you help me with PR reviews?" → `npx skills find pr review`
|
|
56
|
-
- User asks "I need to create a changelog" → `npx skills find changelog`
|
|
57
|
-
|
|
58
|
-
The command will return results like:
|
|
59
|
-
|
|
60
|
-
```
|
|
61
|
-
Install with npx skills add <owner/repo@skill>
|
|
62
|
-
|
|
63
|
-
vercel-labs/agent-skills@vercel-react-best-practices
|
|
64
|
-
└ https://skills.sh/vercel-labs/agent-skills/vercel-react-best-practices
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
### Step 3: Present Options to the User
|
|
68
|
-
|
|
69
|
-
When you find relevant skills, present them to the user with:
|
|
70
|
-
|
|
71
|
-
1. The skill name and what it does
|
|
72
|
-
2. The install command they can run
|
|
73
|
-
3. A link to learn more at skills.sh
|
|
74
|
-
|
|
75
|
-
Example response:
|
|
76
|
-
|
|
77
|
-
```
|
|
78
|
-
I found a skill that might help! The "vercel-react-best-practices" skill provides
|
|
79
|
-
React and Next.js performance optimization guidelines from Vercel Engineering.
|
|
80
|
-
|
|
81
|
-
To install it:
|
|
82
|
-
npx skills add vercel-labs/agent-skills@vercel-react-best-practices
|
|
83
|
-
|
|
84
|
-
Learn more: https://skills.sh/vercel-labs/agent-skills/vercel-react-best-practices
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### Step 4: Offer to Install
|
|
88
|
-
|
|
89
|
-
If the user wants to proceed, you can install the skill for them:
|
|
90
|
-
|
|
91
|
-
```bash
|
|
92
|
-
npx skills add <owner/repo@skill> -g -y
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
The `-g` flag installs globally (user-level) and `-y` skips confirmation prompts.
|
|
96
|
-
|
|
97
|
-
## Common Skill Categories
|
|
98
|
-
|
|
99
|
-
When searching, consider these common categories:
|
|
100
|
-
|
|
101
|
-
| Category | Example Queries |
|
|
102
|
-
| --------------- | ---------------------------------------- |
|
|
103
|
-
| Web Development | react, nextjs, typescript, css, tailwind |
|
|
104
|
-
| Testing | testing, jest, playwright, e2e |
|
|
105
|
-
| DevOps | deploy, docker, kubernetes, ci-cd |
|
|
106
|
-
| Documentation | docs, readme, changelog, api-docs |
|
|
107
|
-
| Code Quality | review, lint, refactor, best-practices |
|
|
108
|
-
| Design | ui, ux, design-system, accessibility |
|
|
109
|
-
| Productivity | workflow, automation, git |
|
|
110
|
-
|
|
111
|
-
## Tips for Effective Searches
|
|
112
|
-
|
|
113
|
-
1. **Use specific keywords**: "react testing" is better than just "testing"
|
|
114
|
-
2. **Try alternative terms**: If "deploy" doesn't work, try "deployment" or "ci-cd"
|
|
115
|
-
3. **Check popular sources**: Many skills come from `vercel-labs/agent-skills` or `ComposioHQ/awesome-claude-skills`
|
|
116
|
-
|
|
117
|
-
## When No Skills Are Found
|
|
118
|
-
|
|
119
|
-
If no relevant skills exist:
|
|
120
|
-
|
|
121
|
-
1. Acknowledge that no existing skill was found
|
|
122
|
-
2. Offer to help with the task directly using your general capabilities
|
|
123
|
-
3. Suggest the user could create their own skill with `npx skills init`
|
|
124
|
-
|
|
125
|
-
Example:
|
|
126
|
-
|
|
127
|
-
```
|
|
128
|
-
I searched for skills related to "xyz" but didn't find any matches.
|
|
129
|
-
I can still help you with this task directly! Would you like me to proceed?
|
|
130
|
-
|
|
131
|
-
If this is something you do often, you could create your own skill:
|
|
132
|
-
npx skills init my-xyz-skill
|
|
133
|
-
```
|
|
1
|
+
---
|
|
2
|
+
name: find-skills
|
|
3
|
+
description: Helps users discover and install agent skills when they ask questions like "how do I do X", "find a skill for X", "is there a skill that can...", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Find Skills
|
|
7
|
+
|
|
8
|
+
This skill helps you discover and install skills from the open agent skills ecosystem.
|
|
9
|
+
|
|
10
|
+
## When to Use This Skill
|
|
11
|
+
|
|
12
|
+
Use this skill when the user:
|
|
13
|
+
|
|
14
|
+
- Asks "how do I do X" where X might be a common task with an existing skill
|
|
15
|
+
- Says "find a skill for X" or "is there a skill for X"
|
|
16
|
+
- Asks "can you do X" where X is a specialized capability
|
|
17
|
+
- Expresses interest in extending agent capabilities
|
|
18
|
+
- Wants to search for tools, templates, or workflows
|
|
19
|
+
- Mentions they wish they had help with a specific domain (design, testing, deployment, etc.)
|
|
20
|
+
|
|
21
|
+
## What is the Skills CLI?
|
|
22
|
+
|
|
23
|
+
The Skills CLI (`npx skills`) is the package manager for the open agent skills ecosystem. Skills are modular packages that extend agent capabilities with specialized knowledge, workflows, and tools.
|
|
24
|
+
|
|
25
|
+
**Key commands:**
|
|
26
|
+
|
|
27
|
+
- `npx skills find [query]` - Search for skills interactively or by keyword
|
|
28
|
+
- `npx skills add <package>` - Install a skill from GitHub or other sources
|
|
29
|
+
- `npx skills check` - Check for skill updates
|
|
30
|
+
- `npx skills update` - Update all installed skills
|
|
31
|
+
|
|
32
|
+
**Browse skills at:** https://skills.sh/
|
|
33
|
+
|
|
34
|
+
## How to Help Users Find Skills
|
|
35
|
+
|
|
36
|
+
### Step 1: Understand What They Need
|
|
37
|
+
|
|
38
|
+
When a user asks for help with something, identify:
|
|
39
|
+
|
|
40
|
+
1. The domain (e.g., React, testing, design, deployment)
|
|
41
|
+
2. The specific task (e.g., writing tests, creating animations, reviewing PRs)
|
|
42
|
+
3. Whether this is a common enough task that a skill likely exists
|
|
43
|
+
|
|
44
|
+
### Step 2: Search for Skills
|
|
45
|
+
|
|
46
|
+
Run the find command with a relevant query:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npx skills find [query]
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
For example:
|
|
53
|
+
|
|
54
|
+
- User asks "how do I make my React app faster?" → `npx skills find react performance`
|
|
55
|
+
- User asks "can you help me with PR reviews?" → `npx skills find pr review`
|
|
56
|
+
- User asks "I need to create a changelog" → `npx skills find changelog`
|
|
57
|
+
|
|
58
|
+
The command will return results like:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
Install with npx skills add <owner/repo@skill>
|
|
62
|
+
|
|
63
|
+
vercel-labs/agent-skills@vercel-react-best-practices
|
|
64
|
+
└ https://skills.sh/vercel-labs/agent-skills/vercel-react-best-practices
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Step 3: Present Options to the User
|
|
68
|
+
|
|
69
|
+
When you find relevant skills, present them to the user with:
|
|
70
|
+
|
|
71
|
+
1. The skill name and what it does
|
|
72
|
+
2. The install command they can run
|
|
73
|
+
3. A link to learn more at skills.sh
|
|
74
|
+
|
|
75
|
+
Example response:
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
I found a skill that might help! The "vercel-react-best-practices" skill provides
|
|
79
|
+
React and Next.js performance optimization guidelines from Vercel Engineering.
|
|
80
|
+
|
|
81
|
+
To install it:
|
|
82
|
+
npx skills add vercel-labs/agent-skills@vercel-react-best-practices
|
|
83
|
+
|
|
84
|
+
Learn more: https://skills.sh/vercel-labs/agent-skills/vercel-react-best-practices
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Step 4: Offer to Install
|
|
88
|
+
|
|
89
|
+
If the user wants to proceed, you can install the skill for them:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npx skills add <owner/repo@skill> -g -y
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The `-g` flag installs globally (user-level) and `-y` skips confirmation prompts.
|
|
96
|
+
|
|
97
|
+
## Common Skill Categories
|
|
98
|
+
|
|
99
|
+
When searching, consider these common categories:
|
|
100
|
+
|
|
101
|
+
| Category | Example Queries |
|
|
102
|
+
| --------------- | ---------------------------------------- |
|
|
103
|
+
| Web Development | react, nextjs, typescript, css, tailwind |
|
|
104
|
+
| Testing | testing, jest, playwright, e2e |
|
|
105
|
+
| DevOps | deploy, docker, kubernetes, ci-cd |
|
|
106
|
+
| Documentation | docs, readme, changelog, api-docs |
|
|
107
|
+
| Code Quality | review, lint, refactor, best-practices |
|
|
108
|
+
| Design | ui, ux, design-system, accessibility |
|
|
109
|
+
| Productivity | workflow, automation, git |
|
|
110
|
+
|
|
111
|
+
## Tips for Effective Searches
|
|
112
|
+
|
|
113
|
+
1. **Use specific keywords**: "react testing" is better than just "testing"
|
|
114
|
+
2. **Try alternative terms**: If "deploy" doesn't work, try "deployment" or "ci-cd"
|
|
115
|
+
3. **Check popular sources**: Many skills come from `vercel-labs/agent-skills` or `ComposioHQ/awesome-claude-skills`
|
|
116
|
+
|
|
117
|
+
## When No Skills Are Found
|
|
118
|
+
|
|
119
|
+
If no relevant skills exist:
|
|
120
|
+
|
|
121
|
+
1. Acknowledge that no existing skill was found
|
|
122
|
+
2. Offer to help with the task directly using your general capabilities
|
|
123
|
+
3. Suggest the user could create their own skill with `npx skills init`
|
|
124
|
+
|
|
125
|
+
Example:
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
I searched for skills related to "xyz" but didn't find any matches.
|
|
129
|
+
I can still help you with this task directly! Would you like me to proceed?
|
|
130
|
+
|
|
131
|
+
If this is something you do often, you could create your own skill:
|
|
132
|
+
npx skills init my-xyz-skill
|
|
133
|
+
```
|
package/src/core/agent-chat.js
CHANGED
|
@@ -486,7 +486,7 @@ class AgentChatHandler extends EventEmitter {
|
|
|
486
486
|
|
|
487
487
|
// AI SDK 错误回调:仅记录日志,不在这里处理(统一由 catch/yield 处理)
|
|
488
488
|
const handleSDKError = ({ error }) => {
|
|
489
|
-
logger.debug(`[AgentChat] SDK onError: ${error?.message}`);
|
|
489
|
+
//logger.debug(`[AgentChat] SDK onError: ${error?.message}`);
|
|
490
490
|
};
|
|
491
491
|
|
|
492
492
|
// DeepSeek thinking mode: 收集 reasoning_content
|
package/src/core/subagent.js
CHANGED
|
@@ -359,7 +359,7 @@ class Subagent extends EventEmitter {
|
|
|
359
359
|
messages, // 返回完整消息历史,用于持续对话上下文
|
|
360
360
|
};
|
|
361
361
|
} catch (err) {
|
|
362
|
-
|
|
362
|
+
|
|
363
363
|
lastError = err;
|
|
364
364
|
// 统一使用 retry.js 的错误判断
|
|
365
365
|
if (isNetworkError(err) && attempt < maxRetries) {
|
|
@@ -369,7 +369,7 @@ class Subagent extends EventEmitter {
|
|
|
369
369
|
await new Promise((resolve) => setTimeout(resolve, retryDelay * Math.pow(2, attempt)));
|
|
370
370
|
continue;
|
|
371
371
|
}
|
|
372
|
-
|
|
372
|
+
logger.warn(`[Subagent:${this.name}] generateText error: ${err.message}`);
|
|
373
373
|
// 最终失败,转换为友好消息
|
|
374
374
|
const friendlyMessage = this._getFriendlyError(err);
|
|
375
375
|
this.emit('error', { error: friendlyMessage });
|
package/src/utils/chat-queue.js
CHANGED
|
@@ -127,12 +127,13 @@ class ChatQueueManager extends EventEmitter {
|
|
|
127
127
|
|
|
128
128
|
return result;
|
|
129
129
|
} catch (error) {
|
|
130
|
-
|
|
130
|
+
|
|
131
131
|
lastError = error;
|
|
132
132
|
if (attempt < this.retryAttempts && this.isRetryableError(error)) {
|
|
133
133
|
await this.sleep(calculateDelay(attempt, { baseDelay: this.retryDelay }));
|
|
134
134
|
continue;
|
|
135
135
|
}
|
|
136
|
+
log.warn('[ChatQueue] executeWithRetry: ', attempt, 'error:', error.message);
|
|
136
137
|
break;
|
|
137
138
|
}
|
|
138
139
|
}
|
|
@@ -177,7 +178,7 @@ class ChatQueueManager extends EventEmitter {
|
|
|
177
178
|
? 'AI 服务暂时不可用,请稍后重试'
|
|
178
179
|
: (err.message || err.toString()).split('\n')[0];
|
|
179
180
|
|
|
180
|
-
log.warn('[ChatQueue] executeStream Error:', friendlyMessage);
|
|
181
|
+
//log.warn('[ChatQueue] executeStream Error:', friendlyMessage);
|
|
181
182
|
chunks.push({ type: 'error', error: friendlyMessage });
|
|
182
183
|
this.emit('stream:chunk', {
|
|
183
184
|
requestId: item.id,
|
|
@@ -195,7 +196,7 @@ class ChatQueueManager extends EventEmitter {
|
|
|
195
196
|
const error = new Error(errorChunk.error || 'Stream error');
|
|
196
197
|
error.chunks = chunks;
|
|
197
198
|
error.isStreamError = true;
|
|
198
|
-
log.warn('[ChatQueue] executeStream Error:', error.message);
|
|
199
|
+
//log.warn('[ChatQueue] executeStream Error:', error.message);
|
|
199
200
|
return {
|
|
200
201
|
chunks,
|
|
201
202
|
content: cleanResponse(''),
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
/* Foliko v2 - Animations */
|
|
2
|
-
@keyframes fadeInUp { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } }
|
|
3
|
-
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
|
4
|
-
@keyframes glow { 0%, 100% { opacity: 0.4; transform: translateX(-50%) scale(1); } 50% { opacity: 0.7; transform: translateX(-50%) scale(1.1); } }
|
|
5
|
-
@keyframes pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.5; transform: scale(1.2); } }
|
|
6
|
-
.reveal { opacity: 0; transform: translateY(30px); transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1); }
|
|
7
|
-
.reveal.visible { opacity: 1; transform: translateY(0); }
|
|
1
|
+
/* Foliko v2 - Animations */
|
|
2
|
+
@keyframes fadeInUp { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } }
|
|
3
|
+
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
|
4
|
+
@keyframes glow { 0%, 100% { opacity: 0.4; transform: translateX(-50%) scale(1); } 50% { opacity: 0.7; transform: translateX(-50%) scale(1.1); } }
|
|
5
|
+
@keyframes pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.5; transform: scale(1.2); } }
|
|
6
|
+
.reveal { opacity: 0; transform: translateY(30px); transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1); }
|
|
7
|
+
.reveal.visible { opacity: 1; transform: translateY(0); }
|
|
8
8
|
|