evolclaw 2.0.7 → 2.1.1

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.
@@ -0,0 +1,340 @@
1
+ import { loadConfig, ensureDataDirs, resolvePaths, resolveAnthropicConfig } from './config.js';
2
+ import { SessionManager } from './core/session-manager.js';
3
+ import { AgentRunner } from './core/agent-runner.js';
4
+ import { FeishuChannel } from './channels/feishu.js';
5
+ import { AUNChannel } from './channels/aun.js';
6
+ import { WechatChannel } from './channels/wechat.js';
7
+ import { MessageProcessor } from './core/message-processor.js';
8
+ import { MessageQueue } from './core/message-queue.js';
9
+ import { MessageCache } from './core/message-cache.js';
10
+ import { CommandHandler } from './core/command-handler.js';
11
+ import { logger } from './utils/logger.js';
12
+ import path from 'path';
13
+ import fs from 'fs';
14
+ async function main() {
15
+ // 过滤飞书 SDK 的 info 日志
16
+ const originalLog = console.log;
17
+ const originalInfo = console.info;
18
+ const filter = (...args) => {
19
+ const firstArg = String(args[0] || '');
20
+ return firstArg.includes('[info]') || firstArg.includes('[ws]');
21
+ };
22
+ console.log = (...args) => {
23
+ if (filter(...args))
24
+ return;
25
+ originalLog(...args);
26
+ };
27
+ console.info = (...args) => {
28
+ if (filter(...args))
29
+ return;
30
+ originalInfo(...args);
31
+ };
32
+ logger.info('EvolClaw starting...');
33
+ // 确保数据目录存在
34
+ ensureDataDirs();
35
+ // 加载配置
36
+ const config = loadConfig();
37
+ const anthropic = resolveAnthropicConfig(config);
38
+ logger.info('✓ Config loaded (API keys hidden)');
39
+ if (anthropic.baseUrl) {
40
+ logger.info(`✓ Using custom API base URL: ${anthropic.baseUrl}`);
41
+ }
42
+ // 初始化数据库
43
+ const sessionManager = new SessionManager();
44
+ logger.info('✓ Database initialized');
45
+ // 初始化 Agent Runner(带持久化回调)
46
+ const agentRunner = new AgentRunner(anthropic.apiKey, anthropic.model, async (sessionId, agentSessionId) => {
47
+ await sessionManager.updateAgentSessionIdBySessionId(sessionId, agentSessionId);
48
+ }, anthropic.baseUrl, config);
49
+ if (anthropic.effort) {
50
+ agentRunner.setEffort(anthropic.effort);
51
+ }
52
+ logger.info('✓ Agent runner ready');
53
+ // 创建消息缓存
54
+ const messageCache = new MessageCache();
55
+ logger.info('✓ Message cache initialized');
56
+ // 定期清理过期消息(每小时)
57
+ setInterval(() => {
58
+ messageCache.cleanupExpired();
59
+ }, 60 * 60 * 1000);
60
+ // 飞书渠道(条件初始化)
61
+ let feishu = null;
62
+ if (config.channels?.feishu?.enabled !== false && config.channels?.feishu?.appId) {
63
+ feishu = new FeishuChannel({
64
+ appId: config.channels.feishu.appId,
65
+ appSecret: config.channels.feishu.appSecret,
66
+ db: sessionManager.getDatabase()
67
+ });
68
+ // 设置项目路径提供器
69
+ feishu.onProjectPathRequest(async (chatId) => {
70
+ const session = await sessionManager.getOrCreateSession('feishu', chatId, config.projects?.defaultPath || process.cwd());
71
+ return path.isAbsolute(session.projectPath)
72
+ ? session.projectPath
73
+ : path.resolve(process.cwd(), session.projectPath);
74
+ });
75
+ }
76
+ // AUN 渠道(条件初始化)
77
+ let aun = null;
78
+ if (config.channels?.aun?.enabled !== false && config.channels?.aun?.domain) {
79
+ aun = new AUNChannel({ domain: config.channels.aun.domain, agentName: config.channels.aun.agentName });
80
+ }
81
+ // 创建命令处理器
82
+ const cmdHandler = new CommandHandler(sessionManager, agentRunner, config, messageCache);
83
+ // 创建消息处理器
84
+ const processor = new MessageProcessor(agentRunner, sessionManager, config, messageCache, (content, channel, channelId, userId, threadId) => {
85
+ const sendFn = async (id, text, opts) => {
86
+ const adapter = cmdHandler.getAdapter(channel);
87
+ if (!adapter)
88
+ return;
89
+ // 文件标记处理(通过 adapter.sendFile 能力判断,不按渠道名分支)
90
+ if (adapter.sendFile) {
91
+ const fileMarkerPattern = /\[SEND_FILE:([^\]]+)\]/g;
92
+ const fileMatches = [...text.matchAll(fileMarkerPattern)];
93
+ for (const match of fileMatches) {
94
+ const filePath = match[1].trim();
95
+ // 跳过占位符/代码片段中的伪路径
96
+ if (!filePath || /[\\[\]{}*+?|^$]/.test(filePath))
97
+ continue;
98
+ const session = await sessionManager.getActiveSession(channel, channelId);
99
+ const projectPath = session?.projectPath || process.cwd();
100
+ const absoluteFilePath = path.isAbsolute(filePath) ? filePath : path.join(projectPath, filePath);
101
+ try {
102
+ await adapter.sendFile(id, absoluteFilePath);
103
+ }
104
+ catch (error) {
105
+ logger.error(`[${channel}] Failed to send file: ${absoluteFilePath}`, error);
106
+ }
107
+ }
108
+ text = text.replace(fileMarkerPattern, '').trim();
109
+ }
110
+ if (text) {
111
+ await adapter.sendText(id, text, opts);
112
+ }
113
+ };
114
+ return cmdHandler.handle(content, channel, channelId, sendFn, userId, threadId);
115
+ });
116
+ // 回填 processor 和 messageQueue 的引用
117
+ cmdHandler.setProcessor(processor);
118
+ // 设置 compact 开始回调
119
+ agentRunner.setCompactStartCallback((sessionId) => {
120
+ processor.handleCompactStart();
121
+ });
122
+ // 创建消息队列
123
+ const messageQueue = new MessageQueue(async (message) => {
124
+ await processor.processMessage(message);
125
+ });
126
+ // 设置中断回调
127
+ messageQueue.setInterruptCallback(async (sessionKey) => {
128
+ await agentRunner.interrupt(sessionKey);
129
+ });
130
+ // 回填 messageQueue 引用
131
+ cmdHandler.setMessageQueue(messageQueue);
132
+ // 注册 Feishu 适配器(如果已初始化)
133
+ if (feishu) {
134
+ const feishuAdapter = {
135
+ name: 'feishu',
136
+ sendText: (channelId, text, options) => feishu.sendMessage(channelId, text, options),
137
+ sendFile: (channelId, filePath) => feishu.sendFile(channelId, filePath),
138
+ isGroupChat: (channelId) => feishu.getChatMode(channelId).then(m => m === 'group'),
139
+ };
140
+ const feishuOptions = {
141
+ systemPromptAppend: '[重要系统功能] 你可以通过飞书发送文件给用户。方法:在响应中使用 [SEND_FILE:文件路径] 标记。示例:文件已准备好![SEND_FILE:./report.txt] 路径支持相对路径(相对项目目录)或绝对路径。系统会自动上传并发送。',
142
+ fileMarkerPattern: /\[SEND_FILE:([^\]]+)\]/g,
143
+ supportsImages: true,
144
+ };
145
+ processor.registerChannel(feishuAdapter, feishuOptions);
146
+ cmdHandler.registerAdapter(feishuAdapter);
147
+ }
148
+ // 注册 AUN 适配器(如果已初始化)
149
+ if (aun) {
150
+ const aunAdapter = {
151
+ name: 'aun',
152
+ sendText: (channelId, text) => aun.sendMessage(channelId, text),
153
+ };
154
+ processor.registerChannel(aunAdapter);
155
+ cmdHandler.registerAdapter(aunAdapter);
156
+ }
157
+ // ── WeChat 渠道(条件初始化)──
158
+ let wechat = null;
159
+ if (config.channels?.wechat?.enabled && config.channels?.wechat?.token) {
160
+ wechat = new WechatChannel({
161
+ baseUrl: config.channels.wechat.baseUrl || 'https://ilinkai.weixin.qq.com',
162
+ token: config.channels.wechat.token,
163
+ });
164
+ // 设置项目路径提供器(用于接收文件保存)
165
+ wechat.onProjectPathRequest(async (channelId) => {
166
+ const session = await sessionManager.getOrCreateSession('wechat', channelId, config.projects?.defaultPath || process.cwd());
167
+ return path.isAbsolute(session.projectPath)
168
+ ? session.projectPath
169
+ : path.resolve(process.cwd(), session.projectPath);
170
+ });
171
+ const wechatAdapter = {
172
+ name: 'wechat',
173
+ sendText: (channelId, text) => wechat.sendMessage(channelId, text),
174
+ sendFile: (channelId, filePath) => wechat.sendFile(channelId, filePath),
175
+ };
176
+ const wechatOptions = {
177
+ systemPromptAppend: '[系统功能] 你可以发送文件给用户。方法:在响应中使用 [SEND_FILE:文件路径] 标记。示例:文件已准备好![SEND_FILE:./report.txt]',
178
+ fileMarkerPattern: /\[SEND_FILE:([^\]]+)\]/g,
179
+ };
180
+ processor.registerChannel(wechatAdapter, wechatOptions);
181
+ cmdHandler.registerAdapter(wechatAdapter);
182
+ // Session 过期通知(通过 Feishu 等其他渠道告知用户)
183
+ wechat.onSessionExpiredNotify(async (message) => {
184
+ // 尝试通过已注册的 Feishu owner 通知
185
+ const feishuOwner = config.channels?.feishu?.owner;
186
+ if (feishuOwner) {
187
+ try {
188
+ // Feishu owner ID 是 open_id,但 sendMessage 需要 chat_id
189
+ // 这里只记日志,因为 owner 的 chat_id 需要从 session 中获取
190
+ logger.warn(`[WeChat] ${message}`);
191
+ }
192
+ catch { }
193
+ }
194
+ else {
195
+ logger.warn(`[WeChat] ${message}`);
196
+ }
197
+ });
198
+ wechat.onMessage(async (channelId, content, userId, images) => {
199
+ content = content.trim();
200
+ // 首次交互自动绑定主人
201
+ if (userId && !config.channels?.wechat?.owner) {
202
+ const { setOwner } = await import('./config.js');
203
+ setOwner(config, 'wechat', userId);
204
+ logger.info(`[Owner] Auto-bound WeChat owner: ${userId}`);
205
+ }
206
+ // 命令快速路径
207
+ if (cmdHandler.isCommand(content)) {
208
+ const cmdResult = await cmdHandler.handle(content, 'wechat', channelId, undefined, userId);
209
+ if (cmdResult !== null) {
210
+ if (cmdResult) {
211
+ try {
212
+ await wechat.sendMessage(channelId, cmdResult);
213
+ }
214
+ catch (error) {
215
+ logger.error('[WeChat] Failed to send command response:', error);
216
+ }
217
+ }
218
+ return;
219
+ }
220
+ }
221
+ // 获取当前项目路径
222
+ const session = await sessionManager.getOrCreateSession('wechat', channelId, config.projects?.defaultPath || process.cwd());
223
+ // 普通消息进入队列
224
+ await messageQueue.enqueue(`wechat-${channelId}`, { channel: 'wechat', channelId, content, images, timestamp: Date.now(), userId }, session.projectPath);
225
+ });
226
+ }
227
+ // Feishu 消息处理
228
+ if (feishu) {
229
+ feishu.onMessage(async ({ channelId: chatId, content: rawContent, images, userId, userName, messageId, mentions, threadId, rootId }) => {
230
+ let content = rawContent.trim();
231
+ // 首次交互自动绑定主人
232
+ if (userId && !config.channels?.feishu?.owner) {
233
+ const { setOwner } = await import('./config.js');
234
+ setOwner(config, 'feishu', userId);
235
+ logger.info(`[Owner] Auto-bound owner: ${userName} (${userId})`);
236
+ }
237
+ // 命令立即处理,不进入队列
238
+ if (cmdHandler.isCommand(content)) {
239
+ const cmdResult = await cmdHandler.handle(content, 'feishu', chatId, undefined, userId, threadId);
240
+ if (cmdResult !== null) {
241
+ if (cmdResult) {
242
+ try {
243
+ await feishu.sendMessage(chatId, cmdResult, { forceText: true, replyToMessageId: rootId, replyInThread: true });
244
+ }
245
+ catch (error) {
246
+ logger.error('[Feishu] Failed to send command response:', error);
247
+ }
248
+ }
249
+ return;
250
+ }
251
+ }
252
+ // 获取当前项目路径(话题会话自动创建,携带 metadata)
253
+ const metadata = rootId ? { feishu: { rootId } } : undefined;
254
+ const session = await sessionManager.getOrCreateSession('feishu', chatId, config.projects?.defaultPath || process.cwd(), threadId, metadata);
255
+ // 群聊消息添加用户名前缀
256
+ const chatMode = await feishu.getChatMode(chatId);
257
+ if (chatMode === 'group' && userName) {
258
+ content = `[${userName}] ${content}`;
259
+ }
260
+ // 普通消息进入队列(使用 session.id 作为 key,话题间可并行)
261
+ await messageQueue.enqueue(session.id, { channel: 'feishu', channelId: chatId, content, images, timestamp: Date.now(), userId, userName, messageId, isGroup: chatMode === 'group', mentions, threadId }, session.projectPath);
262
+ });
263
+ }
264
+ // AUN 消息处理
265
+ if (aun) {
266
+ aun.onMessage(async (sessionId, content) => {
267
+ content = content.trim();
268
+ // 首次交互自动绑定主人
269
+ if (!config.channels?.aun?.owner) {
270
+ const { setOwner } = await import('./config.js');
271
+ setOwner(config, 'aun', sessionId);
272
+ logger.info(`[Owner] Auto-bound AUN owner: ${sessionId}`);
273
+ }
274
+ // 命令立即处理,不进入队列
275
+ if (cmdHandler.isCommand(content)) {
276
+ const cmdResult = await cmdHandler.handle(content, 'aun', sessionId, undefined, sessionId);
277
+ if (cmdResult) {
278
+ await aun.sendMessage(sessionId, cmdResult);
279
+ return;
280
+ }
281
+ }
282
+ // 获取当前项目路径
283
+ const session = await sessionManager.getOrCreateSession('aun', sessionId, config.projects?.defaultPath || process.cwd());
284
+ // 普通消息进入队列
285
+ await messageQueue.enqueue(`aun-${sessionId}`, { channel: 'aun', channelId: sessionId, content, timestamp: Date.now(), userId: sessionId }, session.projectPath);
286
+ });
287
+ }
288
+ // 连接渠道
289
+ const channels = [];
290
+ const channelInstances = [
291
+ ...(feishu ? [{ name: 'Feishu', instance: feishu, timeout: 5000 }] : []),
292
+ ...(aun ? [{ name: 'AUN', instance: aun }] : []),
293
+ ...(wechat ? [{ name: 'WeChat', instance: wechat }] : []),
294
+ ];
295
+ for (const { name, instance, timeout } of channelInstances) {
296
+ try {
297
+ if (timeout) {
298
+ await Promise.race([
299
+ instance.connect(),
300
+ new Promise((_, reject) => setTimeout(() => reject(new Error('Connection timeout')), timeout))
301
+ ]);
302
+ }
303
+ else {
304
+ await instance.connect();
305
+ }
306
+ logger.info(`✓ ${name} connected`);
307
+ channels.push(name);
308
+ }
309
+ catch (error) {
310
+ logger.warn(`⚠ ${name} connection failed (will continue without it)`);
311
+ if (error instanceof Error) {
312
+ logger.warn(` Reason: ${error.message}`);
313
+ }
314
+ }
315
+ }
316
+ logger.info(`\n🚀 EvolClaw is running with ${channels.length} channel(s): ${channels.join(', ')}\n`);
317
+ // 写入 ready 信号,供 restart-monitor 检测启动成功
318
+ const readySignalPath = resolvePaths().readySignal;
319
+ fs.writeFileSync(readySignalPath, String(Date.now()));
320
+ logger.info(`✓ Ready signal written: ${readySignalPath}`);
321
+ // 优雅关闭
322
+ const shutdown = async () => {
323
+ logger.info('\n\nShutting down gracefully...');
324
+ if (feishu)
325
+ await feishu.disconnect();
326
+ if (aun)
327
+ await aun.disconnect();
328
+ if (wechat)
329
+ await wechat.disconnect();
330
+ sessionManager.close();
331
+ logger.info('✓ Shutdown complete');
332
+ process.exit(0);
333
+ };
334
+ process.on('SIGINT', shutdown);
335
+ process.on('SIGTERM', shutdown);
336
+ }
337
+ main().catch((error) => {
338
+ logger.error('Fatal error:', error);
339
+ process.exit(1);
340
+ });
@@ -16,9 +16,9 @@ async function fileExists(filePath) {
16
16
  /**
17
17
  * 检查会话文件健康度
18
18
  */
19
- export async function checkSessionFileHealth(projectPath, claudeSessionId) {
19
+ export async function checkSessionFileHealth(projectPath, agentSessionId) {
20
20
  const issues = [];
21
- const sessionFile = path.join(projectPath, '.claude', `${claudeSessionId}.jsonl`);
21
+ const sessionFile = path.join(projectPath, '.claude', `${agentSessionId}.jsonl`);
22
22
  // 检查文件是否存在
23
23
  if (!(await fileExists(sessionFile))) {
24
24
  // 新会话没有文件是正常的
@@ -60,7 +60,8 @@ export async function checkSessionFileHealth(projectPath, claudeSessionId) {
60
60
  */
61
61
  export async function backupClaudeDir(projectPath) {
62
62
  const claudeDir = path.join(projectPath, '.claude');
63
- const backupDir = path.join(claudeDir, `backup-${Date.now()}`);
63
+ const dirName = path.basename(claudeDir);
64
+ const backupDir = path.join(path.dirname(claudeDir), `${dirName}-backup-${Date.now()}`);
64
65
  await fs.cp(claudeDir, backupDir, { recursive: true });
65
66
  logger.info(`[SessionFileHealth] Backup created: ${backupDir}`);
66
67
  return backupDir;
@@ -1,4 +1,19 @@
1
- import { logger } from './logger.js';
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { resolvePaths } from '../paths.js';
4
+ // 诊断日志(按需启用,通过 config.debug.flusherDiag 控制)
5
+ let diagStream = null;
6
+ function getDiagStream() {
7
+ if (!diagStream) {
8
+ const logDir = resolvePaths().logs;
9
+ diagStream = fs.createWriteStream(path.join(logDir, 'flusher-diag.log'), { flags: 'a' });
10
+ }
11
+ return diagStream;
12
+ }
13
+ function diag(instanceId, action, meta = {}) {
14
+ const line = JSON.stringify({ ts: new Date().toISOString(), id: instanceId, action, ...meta });
15
+ getDiagStream().write(line + '\n');
16
+ }
2
17
  /**
3
18
  * 流式输出缓冲器
4
19
  * 按时间窗口批量推送文本和活动事件
@@ -12,6 +27,7 @@ import { logger } from './logger.js';
12
27
  * - 下限:interval(额定值)
13
28
  * - 上限:interval * 2.5
14
29
  */
30
+ let instanceCounter = 0;
15
31
  export class StreamFlusher {
16
32
  send;
17
33
  interval;
@@ -24,19 +40,27 @@ export class StreamFlusher {
24
40
  fileMarkerPattern;
25
41
  flushCount = 0;
26
42
  messageTimestamps = [];
27
- constructor(send, interval = 4000, fileMarkerPattern) {
43
+ instanceId;
44
+ createTime = Date.now();
45
+ diagEnabled;
46
+ constructor(send, interval = 4000, fileMarkerPattern, diagEnabled = false) {
28
47
  this.send = send;
29
48
  this.interval = interval;
30
49
  this.fileMarkerPattern = fileMarkerPattern;
50
+ this.diagEnabled = diagEnabled;
51
+ this.instanceId = `F${++instanceCounter}`;
52
+ if (this.diagEnabled)
53
+ diag(this.instanceId, 'created', { interval });
31
54
  }
32
55
  addText(text) {
33
56
  this.buffer += text;
34
57
  this.allText += text;
35
58
  this.messageTimestamps.push(Date.now());
59
+ if (this.diagEnabled)
60
+ diag(this.instanceId, 'addText', { len: text.length, preview: text.substring(0, 60), bufLen: this.buffer.length, actCount: this.activities.length });
36
61
  this.scheduleFlush();
37
62
  }
38
63
  addTextBlock(text) {
39
- // 用于 assistant 事件的完整文本块,需要换行分隔
40
64
  if (this.buffer && !this.buffer.endsWith('\n')) {
41
65
  this.buffer += '\n\n';
42
66
  this.allText += '\n\n';
@@ -44,79 +68,69 @@ export class StreamFlusher {
44
68
  this.buffer += text;
45
69
  this.allText += text;
46
70
  this.messageTimestamps.push(Date.now());
71
+ if (this.diagEnabled)
72
+ diag(this.instanceId, 'addTextBlock', { len: text.length, preview: text.substring(0, 60), bufLen: this.buffer.length });
47
73
  this.scheduleFlush();
48
74
  }
49
75
  addActivity(desc) {
50
76
  this.activities.push(desc);
51
77
  this.messageTimestamps.push(Date.now());
78
+ if (this.diagEnabled)
79
+ diag(this.instanceId, 'addActivity', { desc: desc.substring(0, 80), actCount: this.activities.length });
52
80
  this.scheduleFlush();
53
81
  }
54
- /** 当前 buffer 中是否有待发送内容 */
55
82
  hasContent() {
56
83
  return this.buffer.length > 0 || this.activities.length > 0;
57
84
  }
58
- /** 是否曾经发送过任何内容 */
59
85
  hasSentContent() {
60
86
  return this.sentContent;
61
87
  }
62
- /** 获取完整累积文本(用于文件标记提取) */
63
88
  getFinalText() {
64
89
  return this.allText;
65
90
  }
66
- /** 获取当前未发送的剩余文本 */
67
91
  getRemainingText() {
68
92
  return this.buffer;
69
93
  }
70
- /** 从当前 buffer 中移除匹配的模式 */
71
94
  stripFromBuffer(pattern) {
72
95
  this.buffer = this.buffer.replace(pattern, '').trim();
73
96
  }
74
97
  scheduleFlush() {
75
- if (this.timer)
98
+ if (this.timer) {
99
+ if (this.diagEnabled)
100
+ diag(this.instanceId, 'scheduleFlush:skip', { reason: 'timer_exists' });
76
101
  return;
77
- // 计算目标延迟
102
+ }
78
103
  let targetDelay;
79
104
  if (this.flushCount === 0) {
80
- // 第1次:立即发送
81
105
  targetDelay = 0;
82
106
  }
83
107
  else if (this.flushCount <= 3) {
84
- // 第2-4次:半延迟
85
108
  targetDelay = Math.ceil(this.interval / 2);
86
109
  }
87
110
  else if (this.messageTimestamps.length >= 5) {
88
- // 第5次起:动态自适应
89
111
  targetDelay = this.calculateDynamicDelay();
90
112
  }
91
113
  else {
92
- // 样本不足,使用额定延迟
93
114
  targetDelay = this.interval;
94
115
  }
95
116
  const elapsed = Date.now() - this.lastFlush;
96
117
  const delay = Math.max(0, targetDelay - elapsed);
118
+ if (this.diagEnabled)
119
+ diag(this.instanceId, 'scheduleFlush:set', { flushCount: this.flushCount, targetDelay, elapsed, actualDelay: delay });
97
120
  this.timer = setTimeout(() => this.flush(), delay);
98
121
  }
99
- /**
100
- * 计算动态延迟
101
- * 基于最近10条消息的平均间隔
102
- */
103
122
  calculateDynamicDelay() {
104
- // 取最近10条(或实际条数)
105
123
  const recent = this.messageTimestamps.slice(-10);
106
- // 计算平均间隔
107
124
  const intervals = [];
108
125
  for (let i = 1; i < recent.length; i++) {
109
126
  intervals.push(recent[i] - recent[i - 1]);
110
127
  }
111
- if (intervals.length === 0) {
128
+ if (intervals.length === 0)
112
129
  return this.interval;
113
- }
114
130
  const avgInterval = intervals.reduce((a, b) => a + b) / intervals.length;
115
- // 动态延迟 = 平均间隔 * 3
116
131
  let dynamicDelay = avgInterval * 3;
117
- // 边界限制
118
- const minDelay = this.interval; // 下限:额定值
119
- const maxDelay = this.interval * 2.5; // 上限:额定值 * 2.5
132
+ const minDelay = this.interval;
133
+ const maxDelay = this.interval * 2.5;
120
134
  return Math.max(minDelay, Math.min(maxDelay, dynamicDelay));
121
135
  }
122
136
  async flush(isFinal) {
@@ -133,15 +147,11 @@ export class StreamFlusher {
133
147
  output += this.buffer;
134
148
  this.buffer = '';
135
149
  }
136
- // 移除文件标记(如果配置了)
137
150
  if (output && this.fileMarkerPattern) {
138
- const before = output;
139
151
  output = output.replace(this.fileMarkerPattern, '').trim();
140
- if (before !== output) {
141
- logger.debug('[StreamFlusher] Removed file markers, before length:', before.length, 'after:', output.length);
142
- }
143
152
  }
144
- logger.debug('[StreamFlusher] flush called, output length:', output.length, 'isEmpty:', !output, 'preview:', output.substring(0, 100));
153
+ if (this.diagEnabled)
154
+ diag(this.instanceId, 'flush', { isFinal, outputLen: output.length, flushCount: this.flushCount, sinceLastFlush: Date.now() - this.lastFlush, preview: output.substring(0, 80) });
145
155
  if (output) {
146
156
  await this.send(output, isFinal);
147
157
  this.sentContent = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "evolclaw",
3
- "version": "2.0.7",
3
+ "version": "2.1.1",
4
4
  "description": "Lightweight AI Agent gateway connecting Claude Agent SDK to messaging channels (Feishu, ACP) with multi-project session management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",