evolclaw 2.1.0 → 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.
@@ -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.1.0",
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",