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 +9 -4
- package/data/evolclaw.sample.json +3 -2
- package/dist/channels/feishu.js +39 -15
- package/dist/cli.js +33 -15
- package/dist/config.js +4 -1
- package/dist/core/agent-runner.js +56 -21
- package/dist/core/command-handler.js +271 -66
- package/dist/core/message-processor.js +117 -59
- package/dist/core/session-manager.js +156 -130
- package/dist/index.js +18 -14
- package/dist/index.js.bak +340 -0
- package/dist/utils/session-file-health.js +4 -3
- package/dist/utils/stream-flusher.js +42 -32
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -128,10 +128,10 @@ evolclaw init wechat
|
|
|
128
128
|
},
|
|
129
129
|
"idleMonitor": {
|
|
130
130
|
"enabled": true, // 任务超时监控开关
|
|
131
|
-
"timeout":
|
|
131
|
+
"timeout": 120, // 超时阈值(秒),默认 120 秒
|
|
132
132
|
"safeModeThreshold": 3 // 连续超时 N 次后进入安全模式(设为 0 禁用安全模式)
|
|
133
133
|
},
|
|
134
|
-
"flushDelay":
|
|
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
|
|
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
|
|
package/dist/channels/feishu.js
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
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
|
-
|
|
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
|
-
// 优先使用传入的
|
|
48
|
-
let
|
|
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
|
-
|
|
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
|
-
// 验证会话文件是否存在且有效(仅在非安全模式且有
|
|
61
|
-
if (
|
|
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, `${
|
|
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
|
-
|
|
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,
|
|
224
|
-
queryStream = createQuery(prompt,
|
|
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,
|
|
253
|
-
logger.info(`[AgentRunner] updateSessionId called: sessionId=${sessionId},
|
|
254
|
-
this.activeSessions.set(sessionId,
|
|
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,
|
|
291
|
+
this.onSessionIdUpdate(sessionId, agentSessionId);
|
|
257
292
|
}
|
|
258
293
|
}
|
|
259
|
-
runSessionCommand(prompt,
|
|
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:
|
|
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,
|
|
310
|
+
async compactSession(sessionId, agentSessionId, projectPath) {
|
|
276
311
|
try {
|
|
277
|
-
logger.info(`[AgentRunner] Compacting session: ${
|
|
278
|
-
const stream = this.runSessionCommand('/compact',
|
|
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(
|
|
330
|
+
async clearSession(agentSessionId, projectPath) {
|
|
296
331
|
try {
|
|
297
|
-
logger.info(`[AgentRunner] Clearing session via SDK: ${
|
|
298
|
-
const stream = this.runSessionCommand('/clear',
|
|
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
|
}
|