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.
package/README.md CHANGED
@@ -128,10 +128,10 @@ evolclaw init wechat
128
128
  },
129
129
  "idleMonitor": {
130
130
  "enabled": true, // 任务超时监控开关
131
- "timeout": 120000, // 超时阈值(ms),默认 2 分钟
131
+ "timeout": 120, // 超时阈值(秒),默认 120
132
132
  "safeModeThreshold": 3 // 连续超时 N 次后进入安全模式(设为 0 禁用安全模式)
133
133
  },
134
- "flushDelay": 4000 // 工具活动消息聚合发送间隔(ms),默认 4 秒
134
+ "flushDelay": 4 // 工具活动消息聚合发送间隔(秒),默认 4 秒
135
135
  }
136
136
  ```
137
137
 
@@ -139,6 +139,7 @@ evolclaw init wechat
139
139
  - `apiKey`:配置文件 → `ANTHROPIC_AUTH_TOKEN` 环境变量 → `~/.claude/settings.json`
140
140
  - `baseUrl`:配置文件 → `ANTHROPIC_BASE_URL` 环境变量 → `~/.claude/settings.json`
141
141
  - `model`:配置文件 → `~/.claude/settings.json` → 默认 `sonnet`
142
+ - `effort`:配置文件 → `~/.claude/settings.json` → SDK 默认值(`auto`)
142
143
 
143
144
  ### 3. 运行
144
145
 
@@ -192,6 +193,7 @@ evolclaw/
192
193
  - `/slist` - 列出当前项目的所有会话
193
194
  - `/s <名称>` - 切换到指定会话
194
195
  - `/name <新名称>` - 重命名当前会话
196
+ - `/del <名称>` - 删除指定会话(仅解绑,不删除文件)
195
197
  - `/status` - 显示会话状态
196
198
  - `/help` - 显示所有命令
197
199
 
@@ -212,8 +214,11 @@ evolclaw/
212
214
  - `/safe` - 进入安全模式
213
215
 
214
216
  **模型管理**:
215
- - `/model` - 显示当前模型和可用列表
216
- - `/model <model-id>` - 切换模型
217
+ - `/model` - 显示当前模型和推理强度
218
+ - `/model <model>` - 切换模型
219
+ - `/model <effort>` - 切换推理强度(low / medium / high / max)
220
+ - `/model <model> <effort>` - 同时切换模型和推理强度
221
+ - `/model auto` - 恢复 SDK 默认推理强度
217
222
 
218
223
  ## 技术栈
219
224
 
@@ -28,8 +28,9 @@
28
28
  },
29
29
  "idleMonitor": {
30
30
  "enabled": true,
31
- "timeout": 120000,
31
+ "timeout": 120,
32
32
  "safeModeThreshold": 3
33
33
  },
34
- "flushDelay": 4000
34
+ "flushDelay": 4,
35
+ "showActivities": "all"
35
36
  }
@@ -46,10 +46,6 @@ export class FeishuChannel {
46
46
  const msg = data.message;
47
47
  logger.debug('[Feishu] Received message, message_id:', msg.message_id, 'type:', msg.message_type);
48
48
  logger.debug('[Feishu] Full data object:', JSON.stringify(data, null, 2));
49
- // 诊断:话题消息检测
50
- if (msg.thread_id) {
51
- logger.info('[Feishu] Thread message detected, thread_id:', msg.thread_id, 'parent_id:', msg.parent_id, 'root_id:', msg.root_id);
52
- }
53
49
  if (!msg.message_id || this.isDuplicate(msg.message_id)) {
54
50
  logger.debug('[Feishu] Duplicate message ignored:', msg.message_id);
55
51
  return;
@@ -58,6 +54,10 @@ export class FeishuChannel {
58
54
  this.addAckReaction(msg.message_id);
59
55
  if (!this.messageHandler)
60
56
  return;
57
+ // 话题消息检测日志(去重后)
58
+ if (msg.thread_id) {
59
+ logger.info('[Feishu] Thread message, thread_id:', msg.thread_id, 'root_id:', msg.root_id);
60
+ }
61
61
  // 提取 @ 提及列表(排除机器人自身)
62
62
  const mentions = (msg.mentions || []).map((m) => ({
63
63
  userId: m.id?.open_id || '',
@@ -74,10 +74,15 @@ export class FeishuChannel {
74
74
  userName = undefined;
75
75
  }
76
76
  try {
77
- // 处理引用消息
77
+ // 提取话题信息
78
+ const threadId = msg.thread_id || undefined;
79
+ const rootId = msg.root_id || undefined;
80
+ // 处理引用消息(话题内消息跳过,避免每条都拼接引用前缀)
78
81
  let quotedText = '';
79
82
  let quotedImages = [];
80
- if (msg.parent_id && this.client) {
83
+ // 话题创建消息检测:DB 中无对应 thread session 时为首条消息
84
+ const isThreadCreating = threadId && !this.hasThreadSession(threadId);
85
+ if (msg.parent_id && (!msg.thread_id || isThreadCreating) && this.client) {
81
86
  try {
82
87
  const res = await this.client.im.message.get({
83
88
  path: { message_id: msg.parent_id }
@@ -151,7 +156,7 @@ export class FeishuChannel {
151
156
  // 优先使用 text_without_at_bot(去除机器人 @),否则使用 text
152
157
  const content = parsed.text_without_at_bot || parsed.text;
153
158
  const finalContent = quotedText + content;
154
- await this.messageHandler(msg.chat_id, finalContent, quotedImages.length > 0 ? quotedImages : undefined, userId, userName, msg.message_id, mentions.length > 0 ? mentions : undefined);
159
+ await this.messageHandler({ channelId: msg.chat_id, content: finalContent, images: quotedImages.length > 0 ? quotedImages : undefined, userId, userName, messageId: msg.message_id, mentions: mentions.length > 0 ? mentions : undefined, threadId, rootId });
155
160
  }
156
161
  // 处理图片消息
157
162
  else if (msg.message_type === 'image') {
@@ -165,11 +170,11 @@ export class FeishuChannel {
165
170
  if (imageData) {
166
171
  const allImages = [...quotedImages, imageData];
167
172
  const prompt = quotedText + '用户发送了一张图片,请分析这张图片的内容。';
168
- await this.messageHandler(msg.chat_id, prompt, allImages, userId, userName, msg.message_id);
173
+ await this.messageHandler({ channelId: msg.chat_id, content: prompt, images: allImages, userId, userName, messageId: msg.message_id, threadId, rootId });
169
174
  }
170
175
  else {
171
176
  const prompt = quotedText + '[图片下载失败] 应用可能缺少 im:message 或 im:message:readonly 权限';
172
- await this.messageHandler(msg.chat_id, prompt, quotedImages.length > 0 ? quotedImages : undefined, userId, userName, msg.message_id);
177
+ await this.messageHandler({ channelId: msg.chat_id, content: prompt, images: quotedImages.length > 0 ? quotedImages : undefined, userId, userName, messageId: msg.message_id, threadId, rootId });
173
178
  }
174
179
  }
175
180
  // 处理文件消息
@@ -184,11 +189,11 @@ export class FeishuChannel {
184
189
  const filePath = await this.downloadFile(fileKey, fileName, msg.message_id, projectPath);
185
190
  if (filePath) {
186
191
  const prompt = quotedText + `用户发送了文件:${fileName}\n文件已保存到:${filePath}\n请使用 Read 工具读取并分析文件内容。`;
187
- await this.messageHandler(msg.chat_id, prompt, quotedImages.length > 0 ? quotedImages : undefined, userId, userName, msg.message_id);
192
+ await this.messageHandler({ channelId: msg.chat_id, content: prompt, images: quotedImages.length > 0 ? quotedImages : undefined, userId, userName, messageId: msg.message_id, threadId, rootId });
188
193
  }
189
194
  else {
190
195
  const prompt = quotedText + '[文件下载失败] 应用可能缺少 im:resource 权限';
191
- await this.messageHandler(msg.chat_id, prompt, quotedImages.length > 0 ? quotedImages : undefined, userId, userName, msg.message_id);
196
+ await this.messageHandler({ channelId: msg.chat_id, content: prompt, images: quotedImages.length > 0 ? quotedImages : undefined, userId, userName, messageId: msg.message_id, threadId, rootId });
192
197
  }
193
198
  }
194
199
  // 处理富文本消息
@@ -210,20 +215,21 @@ export class FeishuChannel {
210
215
  if (title)
211
216
  finalContent = `${title}\n${finalContent}`;
212
217
  finalContent = quotedText + finalContent;
213
- await this.messageHandler(msg.chat_id, finalContent, quotedImages.length > 0 ? quotedImages : undefined, userId, userName, msg.message_id);
218
+ await this.messageHandler({ channelId: msg.chat_id, content: finalContent, images: quotedImages.length > 0 ? quotedImages : undefined, userId, userName, messageId: msg.message_id, threadId, rootId });
214
219
  }
215
220
  // 处理其他类型消息
216
221
  else {
217
222
  logger.debug('[Feishu] Unsupported message type:', msg.message_type);
218
223
  const prompt = quotedText + `[不支持的消息类型: ${msg.message_type}]`;
219
- await this.messageHandler(msg.chat_id, prompt, quotedImages.length > 0 ? quotedImages : undefined, userId, userName, msg.message_id);
224
+ await this.messageHandler({ channelId: msg.chat_id, content: prompt, images: quotedImages.length > 0 ? quotedImages : undefined, userId, userName, messageId: msg.message_id, threadId, rootId });
220
225
  }
221
226
  }
222
227
  catch (error) {
223
228
  logger.error('[Feishu] Failed to process message:', error);
224
229
  }
225
230
  },
226
- 'im.message.message_read_v1': async () => { }
231
+ 'im.message.message_read_v1': async () => { },
232
+ 'im.message.reaction.created_v1': async () => { }
227
233
  });
228
234
  this.wsClient = new lark.WSClient({
229
235
  appId: this.config.appId,
@@ -313,9 +319,13 @@ export class FeishuChannel {
313
319
  : JSON.stringify({ text: content });
314
320
  }
315
321
  if (options?.replyToMessageId) {
322
+ const replyData = { msg_type: msgType, content: msgContent };
323
+ if (options.replyInThread) {
324
+ replyData.reply_in_thread = true;
325
+ }
316
326
  await this.client.im.message.reply({
317
327
  path: { message_id: options.replyToMessageId },
318
- data: { msg_type: msgType, content: msgContent }
328
+ data: replyData
319
329
  });
320
330
  }
321
331
  else {
@@ -327,6 +337,11 @@ export class FeishuChannel {
327
337
  logger.debug(`[Feishu] Sent message as ${useMarkdown ? 'post (Markdown)' : 'text'}`);
328
338
  }
329
339
  catch (error) {
340
+ // 230011: 消息已被撤回,降级为普通消息重试
341
+ if (error.response?.data?.code === 230011 && options?.replyToMessageId) {
342
+ logger.warn('[Feishu] Message withdrawn (230011), retrying without reply');
343
+ return this.sendMessage(chatId, content, { ...options, replyToMessageId: undefined });
344
+ }
330
345
  logger.error('[Feishu] Failed to send message:', error);
331
346
  throw error;
332
347
  }
@@ -366,6 +381,15 @@ export class FeishuChannel {
366
381
  throw error;
367
382
  }
368
383
  }
384
+ hasThreadSession(threadId) {
385
+ try {
386
+ const row = this.db.prepare('SELECT 1 FROM sessions WHERE thread_id = ? LIMIT 1').get(threadId);
387
+ return !!row;
388
+ }
389
+ catch {
390
+ return false;
391
+ }
392
+ }
369
393
  async disconnect() {
370
394
  if (this.cleanupInterval) {
371
395
  clearInterval(this.cleanupInterval);
package/dist/cli.js CHANGED
@@ -537,8 +537,9 @@ async function cmdRestartMonitor() {
537
537
  for (let attempt = 1; attempt <= MAX_HEAL_ATTEMPTS; attempt++) {
538
538
  log(`Self-heal attempt ${attempt}/${MAX_HEAL_ATTEMPTS}`);
539
539
  await notifyChannel(p, pendingInfo, `🔧 自动修复中(第 ${attempt}/${MAX_HEAL_ATTEMPTS} 次)...`, log);
540
- // 调用 claude CLI 修复
541
- const healed = await invokeClaude(p, attempt, MAX_HEAL_ATTEMPTS, log);
540
+ // 调用 claude CLI 修复(递增超时:3/4/5 分钟)
541
+ const timeout = (2 + attempt) * 60 * 1000;
542
+ const healed = await invokeClaude(p, attempt, MAX_HEAL_ATTEMPTS, timeout, log);
542
543
  if (!healed) {
543
544
  log(`Self-heal attempt ${attempt} failed (claude invocation error)`);
544
545
  continue;
@@ -633,7 +634,7 @@ async function spawnAndWaitReady(p, log, timeout) {
633
634
  /**
634
635
  * 调用 claude CLI 进行自动修复
635
636
  */
636
- async function invokeClaude(p, attempt, maxAttempts, log) {
637
+ async function invokeClaude(p, attempt, maxAttempts, timeout, log) {
637
638
  const projectDir = getPackageRoot();
638
639
  const selfHealLog = p.selfHealLog;
639
640
  const stdoutLog = path.join(p.logs, 'stdout.log');
@@ -659,26 +660,31 @@ async function invokeClaude(p, attempt, maxAttempts, log) {
659
660
 
660
661
  注意:只修复导致启动失败的问题,不要做额外的重构或优化。`;
661
662
  try {
662
- log(`Invoking claude CLI (attempt ${attempt})...`);
663
+ log(`Invoking claude CLI (attempt ${attempt}, timeout ${timeout / 60000}min)...`);
663
664
  const { stdout, stderr } = await execFileAsync('claude', [
664
665
  '-p', prompt,
665
666
  '--allowedTools', 'Read,Write,Edit,Bash,Glob,Grep',
666
667
  '--output-format', 'text',
667
668
  ], {
668
669
  cwd: projectDir,
669
- timeout: 5 * 60 * 1000, // 5 分钟超时
670
+ timeout,
670
671
  env: { ...process.env, CLAUDE_CODE_ENTRYPOINT: 'cli' },
671
672
  maxBuffer: 10 * 1024 * 1024,
672
673
  });
673
674
  if (stdout)
674
675
  log(`Claude output: ${stdout.slice(0, 500)}`);
675
676
  if (stderr)
676
- log(`Claude stderr: ${stderr.slice(0, 200)}`);
677
+ log(`Claude stderr: ${stderr.slice(0, 500)}`);
677
678
  log(`Claude CLI completed (attempt ${attempt})`);
678
679
  return true;
679
680
  }
680
681
  catch (error) {
681
- log(`Claude CLI error: ${error.message?.slice(0, 300) || error}`);
682
+ const msg = error.message || String(error);
683
+ log(`Claude CLI error: ${msg.slice(0, 800)}`);
684
+ if (error.stdout)
685
+ log(`Stdout: ${String(error.stdout).slice(0, 500)}`);
686
+ if (error.stderr)
687
+ log(`Stderr: ${String(error.stderr).slice(0, 500)}`);
682
688
  return false;
683
689
  }
684
690
  }
@@ -713,14 +719,26 @@ async function notifyChannel(p, pendingInfo, message, log) {
713
719
  appId: config.channels.feishu.appId,
714
720
  appSecret: config.channels.feishu.appSecret,
715
721
  });
716
- await client.im.message.create({
717
- params: { receive_id_type: 'chat_id' },
718
- data: {
719
- receive_id: pendingInfo.channelId,
720
- msg_type: 'text',
721
- content: JSON.stringify({ text: message }),
722
- },
723
- });
722
+ if (pendingInfo.rootId) {
723
+ await client.im.message.reply({
724
+ path: { message_id: pendingInfo.rootId },
725
+ data: {
726
+ msg_type: 'text',
727
+ content: JSON.stringify({ text: message }),
728
+ reply_in_thread: true,
729
+ },
730
+ });
731
+ }
732
+ else {
733
+ await client.im.message.create({
734
+ params: { receive_id_type: 'chat_id' },
735
+ data: {
736
+ receive_id: pendingInfo.channelId,
737
+ msg_type: 'text',
738
+ content: JSON.stringify({ text: message }),
739
+ },
740
+ });
741
+ }
724
742
  log(`Feishu notification sent: ${message.slice(0, 50)}`);
725
743
  }
726
744
  catch (error) {
package/dist/config.js CHANGED
@@ -37,7 +37,10 @@ export function resolveAnthropicConfig(config) {
37
37
  const model = config.agents?.anthropic?.model
38
38
  || settings.model
39
39
  || 'sonnet';
40
- return { apiKey, baseUrl, model };
40
+ const effort = config.agents?.anthropic?.effort
41
+ || settings.effortLevel
42
+ || undefined;
43
+ return { apiKey, baseUrl, model, effort };
41
44
  }
42
45
  export function loadConfig(configPath = resolvePaths().config) {
43
46
  if (!fs.existsSync(configPath)) {
@@ -10,6 +10,7 @@ import { encodePath } from '../utils/platform.js';
10
10
  export class AgentRunner {
11
11
  apiKey;
12
12
  model;
13
+ effort;
13
14
  baseUrl;
14
15
  config;
15
16
  activeSessions = new Map();
@@ -19,6 +20,7 @@ export class AgentRunner {
19
20
  constructor(apiKey, model, onSessionIdUpdate, baseUrl, config) {
20
21
  this.apiKey = apiKey;
21
22
  this.model = model || 'sonnet';
23
+ this.effort = undefined;
22
24
  this.baseUrl = baseUrl;
23
25
  this.config = config;
24
26
  this.onSessionIdUpdate = onSessionIdUpdate;
@@ -38,30 +40,58 @@ export class AgentRunner {
38
40
  getModel() {
39
41
  return this.model;
40
42
  }
43
+ setEffort(effort) {
44
+ this.effort = effort;
45
+ }
46
+ getEffort() {
47
+ return this.effort;
48
+ }
49
+ syncFromUserSettings() {
50
+ try {
51
+ const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
52
+ if (!fs.existsSync(settingsPath))
53
+ return;
54
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
55
+ if (settings.model && settings.model !== this.model) {
56
+ logger.info(`[AgentRunner] Synced model from ~/.claude/settings.json: ${settings.model}`);
57
+ this.model = settings.model;
58
+ }
59
+ const newEffort = settings.effortLevel || undefined;
60
+ if (newEffort !== this.effort) {
61
+ logger.info(`[AgentRunner] Synced effort from ~/.claude/settings.json: ${newEffort ?? 'auto'}`);
62
+ this.effort = newEffort;
63
+ }
64
+ }
65
+ catch (error) {
66
+ logger.debug(`[AgentRunner] Failed to sync from ~/.claude/settings.json:`, error);
67
+ }
68
+ }
41
69
  setCompactStartCallback(callback) {
42
70
  this.onCompactStart = callback;
43
71
  }
44
72
  async runQuery(sessionId, prompt, projectPath, initialClaudeSessionId, images, systemPromptAppend, sessionManager) {
73
+ // 同步用户级配置到内存
74
+ this.syncFromUserSettings();
45
75
  ensureDir(projectPath);
46
76
  ensureDir(path.join(projectPath, '.claude'));
47
- // 优先使用传入的 claudeSessionId(从数据库恢复),否则使用内存中的
48
- let claudeSessionId = initialClaudeSessionId || this.activeSessions.get(sessionId);
77
+ // 优先使用传入的 agentSessionId(从数据库恢复),否则使用内存中的
78
+ let agentSessionId = initialClaudeSessionId || this.activeSessions.get(sessionId);
49
79
  // 检查是否在安全模式
50
80
  let skipResume = false;
51
81
  if (sessionManager) {
52
82
  const health = await sessionManager.getHealthStatus(sessionId);
53
83
  if (health.safeMode) {
54
84
  // 安全模式:不使用 resume,每次都是新对话
55
- claudeSessionId = undefined;
85
+ agentSessionId = undefined;
56
86
  skipResume = true;
57
87
  logger.warn(`[AgentRunner] Safe mode enabled for ${sessionId}, not resuming session`);
58
88
  }
59
89
  }
60
- // 验证会话文件是否存在且有效(仅在非安全模式且有 claudeSessionId 时)
61
- if (claudeSessionId && !skipResume) {
90
+ // 验证会话文件是否存在且有效(仅在非安全模式且有 agentSessionId 时)
91
+ if (agentSessionId && !skipResume) {
62
92
  const homeDir = os.homedir();
63
93
  const encodedProjectPath = encodePath(projectPath);
64
- const sessionFile = path.join(homeDir, '.claude', 'projects', encodedProjectPath, `${claudeSessionId}.jsonl`);
94
+ const sessionFile = path.join(homeDir, '.claude', 'projects', encodedProjectPath, `${agentSessionId}.jsonl`);
65
95
  let isValid = false;
66
96
  if (fs.existsSync(sessionFile)) {
67
97
  try {
@@ -88,7 +118,7 @@ export class AgentRunner {
88
118
  }
89
119
  if (!isValid) {
90
120
  logger.warn(`[AgentRunner] Invalid session file, starting new session`);
91
- claudeSessionId = undefined;
121
+ agentSessionId = undefined;
92
122
  this.activeSessions.delete(sessionId);
93
123
  if (this.onSessionIdUpdate) {
94
124
  this.onSessionIdUpdate(sessionId, '');
@@ -117,9 +147,11 @@ export class AgentRunner {
117
147
  const useSettingSources = this.config?.agents?.anthropic?.useSettingSources !== false;
118
148
  const enableSummaries = this.config?.agents?.anthropic?.agentProgressSummaries !== false;
119
149
  // 公共 options(新旧模式共用)
150
+ logger.info(`[AgentRunner] runQuery model=${this.model} effort=${this.effort ?? 'auto'}`);
120
151
  const commonOptions = {
121
152
  cwd: projectPath,
122
153
  model: this.model,
154
+ ...(this.effort ? { effort: this.effort } : {}),
123
155
  canUseTool,
124
156
  permissionMode: 'default',
125
157
  persistSession: true,
@@ -220,8 +252,8 @@ export class AgentRunner {
220
252
  queryStream = createQuery(stream);
221
253
  }
222
254
  else {
223
- logger.debug('[AgentRunner] Creating query with text only, claudeSessionId:', initialClaudeSessionId);
224
- queryStream = createQuery(prompt, claudeSessionId);
255
+ logger.debug('[AgentRunner] Creating query with text only, agentSessionId:', initialClaudeSessionId);
256
+ queryStream = createQuery(prompt, agentSessionId);
225
257
  }
226
258
  this.activeStreams.set(sessionId, queryStream);
227
259
  return queryStream;
@@ -243,26 +275,29 @@ export class AgentRunner {
243
275
  logger.info(`[AgentRunner] Interrupted session: ${sessionId}`);
244
276
  }
245
277
  }
278
+ hasActiveStream(sessionId) {
279
+ return this.activeStreams.has(sessionId);
280
+ }
246
281
  registerStream(key, stream) {
247
282
  this.activeStreams.set(key, stream);
248
283
  }
249
284
  cleanupStream(sessionId) {
250
285
  this.activeStreams.delete(sessionId);
251
286
  }
252
- updateSessionId(sessionId, claudeSessionId) {
253
- logger.info(`[AgentRunner] updateSessionId called: sessionId=${sessionId}, claudeSessionId=${claudeSessionId}`);
254
- this.activeSessions.set(sessionId, claudeSessionId);
287
+ updateSessionId(sessionId, agentSessionId) {
288
+ logger.info(`[AgentRunner] updateSessionId called: sessionId=${sessionId}, agentSessionId=${agentSessionId}`);
289
+ this.activeSessions.set(sessionId, agentSessionId);
255
290
  if (this.onSessionIdUpdate) {
256
- this.onSessionIdUpdate(sessionId, claudeSessionId);
291
+ this.onSessionIdUpdate(sessionId, agentSessionId);
257
292
  }
258
293
  }
259
- runSessionCommand(prompt, claudeSessionId, projectPath) {
294
+ runSessionCommand(prompt, agentSessionId, projectPath) {
260
295
  return query({
261
296
  prompt,
262
297
  options: {
263
298
  cwd: projectPath,
264
299
  model: this.model,
265
- resume: claudeSessionId,
300
+ resume: agentSessionId,
266
301
  maxTurns: 1,
267
302
  permissionMode: 'default',
268
303
  env: this.getAgentEnv()
@@ -272,10 +307,10 @@ export class AgentRunner {
272
307
  /**
273
308
  * 主动压缩会话上下文
274
309
  */
275
- async compactSession(sessionId, claudeSessionId, projectPath) {
310
+ async compactSession(sessionId, agentSessionId, projectPath) {
276
311
  try {
277
- logger.info(`[AgentRunner] Compacting session: ${claudeSessionId}`);
278
- const stream = this.runSessionCommand('/compact', claudeSessionId, projectPath);
312
+ logger.info(`[AgentRunner] Compacting session: ${agentSessionId}`);
313
+ const stream = this.runSessionCommand('/compact', agentSessionId, projectPath);
279
314
  for await (const event of stream) {
280
315
  if (event.type === 'system' && event.subtype === 'compact_boundary') {
281
316
  logger.info(`[AgentRunner] Compact completed, pre_tokens: ${event.compact_metadata?.pre_tokens}`);
@@ -292,10 +327,10 @@ export class AgentRunner {
292
327
  /**
293
328
  * 通过 SDK /clear 命令清空会话历史
294
329
  */
295
- async clearSession(claudeSessionId, projectPath) {
330
+ async clearSession(agentSessionId, projectPath) {
296
331
  try {
297
- logger.info(`[AgentRunner] Clearing session via SDK: ${claudeSessionId}`);
298
- const stream = this.runSessionCommand('/clear', claudeSessionId, projectPath);
332
+ logger.info(`[AgentRunner] Clearing session via SDK: ${agentSessionId}`);
333
+ const stream = this.runSessionCommand('/clear', agentSessionId, projectPath);
299
334
  for await (const event of stream) {
300
335
  logger.debug(`[AgentRunner] Clear event: type=${event.type}, subtype=${event.subtype || 'none'}`);
301
336
  }