evolclaw 2.6.3 → 2.7.0
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 +1 -1
- package/data/evolclaw.sample.json +3 -4
- package/dist/agents/claude-runner.js +15 -6
- package/dist/agents/codex-runner.js +0 -1
- package/dist/agents/gemini-runner.js +3 -1
- package/dist/channels/aun.js +99 -28
- package/dist/channels/feishu.js +2 -0
- package/dist/cli.js +29 -1
- package/dist/config.js +66 -40
- package/dist/core/command-handler.js +51 -41
- package/dist/core/message/message-processor.js +93 -72
- package/dist/core/message/thought-emitter.js +1 -0
- package/dist/core/session/session-manager.js +23 -2
- package/dist/index.js +24 -25
- package/dist/prompts/templates.js +122 -0
- package/dist/templates/prompts.md +103 -0
- package/dist/utils/channel-fingerprint.js +59 -0
- package/dist/utils/init.js +1 -1
- package/dist/utils/logger.js +15 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -152,7 +152,7 @@ evolclaw init aun
|
|
|
152
152
|
}
|
|
153
153
|
```
|
|
154
154
|
|
|
155
|
-
**API 继承机制**:`agents.
|
|
155
|
+
**API 继承机制**:`agents.claude` 整个 section 可省略,系统自动按以下优先级继承:
|
|
156
156
|
- `apiKey`:配置文件 → `ANTHROPIC_AUTH_TOKEN` 环境变量 → `~/.claude/settings.json`
|
|
157
157
|
- `baseUrl`:配置文件 → `ANTHROPIC_BASE_URL` 环境变量 → `~/.claude/settings.json`
|
|
158
158
|
- `model`:配置文件 → `~/.claude/settings.json` → 默认 `sonnet`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"agents": {
|
|
3
|
-
"
|
|
3
|
+
"claude": {
|
|
4
4
|
"apiKey": "",
|
|
5
5
|
"baseUrl": "",
|
|
6
6
|
"model": "sonnet",
|
|
@@ -8,15 +8,14 @@
|
|
|
8
8
|
"useSettingSources": true,
|
|
9
9
|
"agentProgressSummaries": true
|
|
10
10
|
},
|
|
11
|
-
"
|
|
11
|
+
"codex": {
|
|
12
12
|
"apiKey": "",
|
|
13
13
|
"baseUrl": "",
|
|
14
14
|
"model": "gpt-5.2-codex",
|
|
15
15
|
"effort": "medium"
|
|
16
16
|
},
|
|
17
|
-
"
|
|
17
|
+
"gemini": {
|
|
18
18
|
"apiKey": "",
|
|
19
|
-
"baseUrl": "",
|
|
20
19
|
"model": "gemini-2.5-flash",
|
|
21
20
|
"cliPath": "",
|
|
22
21
|
"mode": "cli"
|
|
@@ -130,6 +130,8 @@ export class AgentRunner {
|
|
|
130
130
|
return this.permissionMode;
|
|
131
131
|
}
|
|
132
132
|
listModes() {
|
|
133
|
+
// readonly 模式暂时禁用:与 proactive 模式系统提示词存在语义冲突,
|
|
134
|
+
// 且 READONLY_WRITE_PATTERNS 未覆盖 evolclaw ctl send/file,契约不稳固
|
|
133
135
|
return [
|
|
134
136
|
{ key: 'auto', nameZh: '自动', description: 'AI 分类器自动判断', available: true },
|
|
135
137
|
{ key: 'bypass', nameZh: '放行', description: '全部自动放行', available: true },
|
|
@@ -137,7 +139,6 @@ export class AgentRunner {
|
|
|
137
139
|
{ key: 'edit', nameZh: '编辑', description: '自动接受编辑,其他询问', available: true },
|
|
138
140
|
{ key: 'plan', nameZh: '规划', description: '只规划不执行', available: true },
|
|
139
141
|
{ key: 'noask', nameZh: '静默', description: '未批准则拒绝', available: true },
|
|
140
|
-
{ key: 'readonly', nameZh: '只读', description: '禁止修改项目文件,可在临时目录生成文件', available: true },
|
|
141
142
|
];
|
|
142
143
|
}
|
|
143
144
|
setPermissionGateway(gateway) {
|
|
@@ -172,12 +173,12 @@ export class AgentRunner {
|
|
|
172
173
|
return;
|
|
173
174
|
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
174
175
|
// evolclaw.json 显式配置优先,不被 settings.json 覆盖
|
|
175
|
-
const configModel = this.config?.agents?.
|
|
176
|
+
const configModel = this.config?.agents?.claude?.model;
|
|
176
177
|
if (!configModel && settings.model && settings.model !== this.model) {
|
|
177
178
|
logger.info(`[AgentRunner] Synced model from ~/.claude/settings.json: ${settings.model}`);
|
|
178
179
|
this.model = settings.model;
|
|
179
180
|
}
|
|
180
|
-
const configEffort = this.config?.agents?.
|
|
181
|
+
const configEffort = this.config?.agents?.claude?.effort;
|
|
181
182
|
if (!configEffort) {
|
|
182
183
|
const newEffort = settings.effortLevel || undefined;
|
|
183
184
|
if (newEffort !== this.effort) {
|
|
@@ -592,6 +593,14 @@ export class AgentRunner {
|
|
|
592
593
|
if (this.permissionMode === 'bypass') {
|
|
593
594
|
return { behavior: 'allow', updatedInput: input, decisionClassification: 'user_permanent' };
|
|
594
595
|
}
|
|
596
|
+
// evolclaw ctl send/file 白名单:proactive 模式下 agent 必须通过这些命令发送消息,
|
|
597
|
+
// 任何权限模式下都不应拦截,否则 agent 无法回复用户
|
|
598
|
+
if (toolName === 'Bash') {
|
|
599
|
+
const cmd = input.command || '';
|
|
600
|
+
if (/^\s*evolclaw\s+ctl\s+(send|file)\b/.test(cmd)) {
|
|
601
|
+
return { behavior: 'allow', updatedInput: input, decisionClassification: 'user_permanent' };
|
|
602
|
+
}
|
|
603
|
+
}
|
|
595
604
|
// readonly 模式:二次拦截(belt-and-suspenders)
|
|
596
605
|
if (this.permissionMode === 'readonly') {
|
|
597
606
|
const roResult = checkReadonly(toolName, input, projectPath);
|
|
@@ -626,9 +635,9 @@ export class AgentRunner {
|
|
|
626
635
|
decisionClassification: decision === 'always' ? 'user_permanent' : 'user_temporary'
|
|
627
636
|
};
|
|
628
637
|
};
|
|
629
|
-
const useSettingSources = this.config?.agents?.
|
|
630
|
-
const enableSummaries = this.config?.agents?.
|
|
631
|
-
const excludeDynamic = this.config?.agents?.
|
|
638
|
+
const useSettingSources = this.config?.agents?.claude?.useSettingSources !== false;
|
|
639
|
+
const enableSummaries = this.config?.agents?.claude?.agentProgressSummaries !== false;
|
|
640
|
+
const excludeDynamic = this.config?.agents?.claude?.excludeDynamicSections === true;
|
|
632
641
|
// 公共 options(新旧模式共用)
|
|
633
642
|
const sdkPermissionMode = this.toSdkPermissionMode();
|
|
634
643
|
logger.info(`[AgentRunner] runQuery model=${this.model} effort=${this.effort ?? 'auto'} permMode=${this.permissionMode} sdkMode=${sdkPermissionMode}`);
|
|
@@ -77,7 +77,6 @@ export class CodexRunner {
|
|
|
77
77
|
{ key: 'bypass', nameZh: '放行', description: '全部自动(受 sandbox 约束)', available: true },
|
|
78
78
|
{ key: 'request', nameZh: '审批', description: '需要审批时询问', available: true },
|
|
79
79
|
{ key: 'noask', nameZh: '静默', description: '只执行已知安全操作', available: true },
|
|
80
|
-
{ key: 'readonly', nameZh: '只读', description: '禁止修改项目文件,可在临时目录生成文件', available: true },
|
|
81
80
|
];
|
|
82
81
|
}
|
|
83
82
|
setSendPrompt(_fn) { }
|
|
@@ -70,7 +70,6 @@ export class GeminiRunner {
|
|
|
70
70
|
{ key: 'edit', nameZh: '编辑', description: '仅 Claude 支持', available: false, unavailableReason: 'Gemini CLI 不支持此模式' },
|
|
71
71
|
{ key: 'plan', nameZh: '规划', description: 'Gemini 规划模式', available: true },
|
|
72
72
|
{ key: 'noask', nameZh: '静默', description: '仅 Claude 支持', available: false, unavailableReason: 'Gemini CLI 不支持此模式' },
|
|
73
|
-
{ key: 'readonly', nameZh: '只读', description: '禁止修改项目文件,可在临时目录生成文件', available: true },
|
|
74
73
|
];
|
|
75
74
|
}
|
|
76
75
|
setSendPrompt(_fn) { }
|
|
@@ -121,6 +120,9 @@ export class GeminiRunner {
|
|
|
121
120
|
if (this.currentMode === 'plan') {
|
|
122
121
|
args.push('--approval-mode=plan');
|
|
123
122
|
}
|
|
123
|
+
else if (this.currentMode === 'noask') {
|
|
124
|
+
args.push('--approval-mode=default');
|
|
125
|
+
}
|
|
124
126
|
else {
|
|
125
127
|
args.push('--yolo');
|
|
126
128
|
}
|
package/dist/channels/aun.js
CHANGED
|
@@ -45,6 +45,33 @@ export class AUNChannel {
|
|
|
45
45
|
const line = JSON.stringify({ ts: localTimestamp(), dir, event, data });
|
|
46
46
|
this.traceStream.write(line + '\n');
|
|
47
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* 统一的 RPC 调用包装:自动记录 OUT 发送、.ok 结果、.error 错误(含 trace + evolclaw.log 失败日志)。
|
|
50
|
+
* 所有 client.call() 都应通过此方法调用,保证 aun-trace 里每个 OUT 调用都有"发+收/错"成对记录。
|
|
51
|
+
*/
|
|
52
|
+
async callAndTrace(method, params, opts) {
|
|
53
|
+
this.trace('OUT', method, params);
|
|
54
|
+
try {
|
|
55
|
+
const result = await this.client.call(method, params);
|
|
56
|
+
if (!opts?.silentOk) {
|
|
57
|
+
const r = result;
|
|
58
|
+
const snap = r && typeof r === 'object'
|
|
59
|
+
? { message_id: r.message_id, ok: r.ok, thought_id: r.thought_id }
|
|
60
|
+
: undefined;
|
|
61
|
+
this.trace('OUT', `${method}.ok`, snap ?? {});
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
catch (e) {
|
|
66
|
+
this.trace('OUT', `${method}.error`, {
|
|
67
|
+
error: e?.message ?? String(e),
|
|
68
|
+
code: e?.code,
|
|
69
|
+
name: e?.name,
|
|
70
|
+
});
|
|
71
|
+
logger.warn(`[AUN] rpc ${method} failed: ${e?.name ?? ''}(${e?.code ?? ''}) ${e?.message ?? e}`);
|
|
72
|
+
throw e;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
48
75
|
rotateTraceIfNeeded() {
|
|
49
76
|
const d = new Date();
|
|
50
77
|
const today = `${d.getFullYear()}${String(d.getMonth() + 1).padStart(2, '0')}${String(d.getDate()).padStart(2, '0')}`;
|
|
@@ -167,10 +194,14 @@ export class AUNChannel {
|
|
|
167
194
|
async initClient() {
|
|
168
195
|
// Clean up existing client if any
|
|
169
196
|
if (this.client) {
|
|
197
|
+
this.trace('OUT', 'client.close', { reason: 'initClient' });
|
|
170
198
|
try {
|
|
171
199
|
await this.client.close();
|
|
200
|
+
this.trace('OUT', 'client.close.ok', { reason: 'initClient' });
|
|
201
|
+
}
|
|
202
|
+
catch (e) {
|
|
203
|
+
this.trace('OUT', 'client.close.error', { reason: 'initClient', error: String(e) });
|
|
172
204
|
}
|
|
173
|
-
catch { /* ignore */ }
|
|
174
205
|
this.client = null;
|
|
175
206
|
}
|
|
176
207
|
this.connected = false;
|
|
@@ -211,14 +242,14 @@ export class AUNChannel {
|
|
|
211
242
|
this.trace('IN', 'message.received', data);
|
|
212
243
|
const kind = (data && typeof data === 'object') ? data.kind ?? '' : '';
|
|
213
244
|
const keys = (data && typeof data === 'object') ? Object.keys(data).join(',') : typeof data;
|
|
214
|
-
logger.
|
|
245
|
+
logger.debug(`[AUN][DIAG] message.received: kind=${kind} keys=${keys}`);
|
|
215
246
|
this.handleIncomingPrivateMessage(data);
|
|
216
247
|
});
|
|
217
248
|
this.client.on('group.message_created', (data) => {
|
|
218
249
|
this.trace('IN', 'group.message_created', data);
|
|
219
250
|
const gid = (data && typeof data === 'object') ? data.group_id ?? '' : '';
|
|
220
251
|
const sender = (data && typeof data === 'object') ? data.sender_aid ?? '' : '';
|
|
221
|
-
logger.
|
|
252
|
+
logger.debug(`[AUN][DIAG] group.message_created: group_id=${gid} sender=${sender}`);
|
|
222
253
|
this.handleIncomingGroupMessage(data);
|
|
223
254
|
});
|
|
224
255
|
this.client.on('connection.state', (data) => {
|
|
@@ -265,7 +296,9 @@ export class AUNChannel {
|
|
|
265
296
|
let accessToken;
|
|
266
297
|
try {
|
|
267
298
|
logger.info(`[AUN] Authenticating as ${aidName}...`);
|
|
299
|
+
this.trace('OUT', 'auth.authenticate', { aid: aidName });
|
|
268
300
|
const auth = await this.client.auth.authenticate(aidName ? { aid: aidName } : undefined);
|
|
301
|
+
this.trace('OUT', 'auth.authenticate.ok', { aid: auth.aid, gateway: auth.gateway, hasToken: !!auth.access_token });
|
|
269
302
|
this.trace('IN', 'auth.result', { aid: auth.aid, gateway: auth.gateway, hasToken: !!auth.access_token });
|
|
270
303
|
accessToken = auth.access_token;
|
|
271
304
|
const resolvedGateway = auth.gateway || gateway;
|
|
@@ -275,6 +308,7 @@ export class AUNChannel {
|
|
|
275
308
|
catch (e) {
|
|
276
309
|
const errMsg = e.message || String(e);
|
|
277
310
|
const errName = e.constructor?.name || 'Error';
|
|
311
|
+
this.trace('OUT', 'auth.authenticate.error', { error: errMsg, name: errName });
|
|
278
312
|
logger.error(`[AUN] Authentication failed (${errName}): ${errMsg}`);
|
|
279
313
|
if (e.stack)
|
|
280
314
|
logger.debug(`[AUN] Auth stack: ${e.stack}`);
|
|
@@ -289,7 +323,9 @@ export class AUNChannel {
|
|
|
289
323
|
}
|
|
290
324
|
// Connect (SDK auto_reconnect handles transient failures)
|
|
291
325
|
try {
|
|
326
|
+
this.trace('OUT', 'client.connect', { gateway: this.client._gatewayUrl });
|
|
292
327
|
await this.client.connect({ access_token: accessToken, gateway: this.client._gatewayUrl }, { auto_reconnect: true, retry: { max_attempts: 5, initial_delay: 1.0, max_delay: 30.0 } });
|
|
328
|
+
this.trace('OUT', 'client.connect.ok', { aid: this.client.aid });
|
|
293
329
|
this._aid = this.client.aid ?? undefined;
|
|
294
330
|
const deviceId = this.client._device_id ?? '';
|
|
295
331
|
this._chatId = this._aid ? `${this._aid}:${deviceId}:` : '';
|
|
@@ -312,6 +348,7 @@ export class AUNChannel {
|
|
|
312
348
|
await this.sendWelcomeMessage();
|
|
313
349
|
}
|
|
314
350
|
catch (e) {
|
|
351
|
+
this.trace('OUT', 'client.connect.error', { error: String(e) });
|
|
315
352
|
logger.error(`[AUN] Connection failed: ${e}`);
|
|
316
353
|
this.scheduleReconnect();
|
|
317
354
|
throw e;
|
|
@@ -429,7 +466,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
429
466
|
logger.warn('[AUN] Client disconnected before welcome message could be sent');
|
|
430
467
|
return;
|
|
431
468
|
}
|
|
432
|
-
await this.
|
|
469
|
+
await this.callAndTrace('message.send', {
|
|
433
470
|
to: owner,
|
|
434
471
|
payload: { type: 'text', text: welcomeText },
|
|
435
472
|
encrypt: true,
|
|
@@ -452,7 +489,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
452
489
|
}
|
|
453
490
|
let downloadUrl;
|
|
454
491
|
try {
|
|
455
|
-
const ticket = await this.
|
|
492
|
+
const ticket = await this.callAndTrace('storage.create_download_ticket', {
|
|
456
493
|
owner_aid: ownerAid,
|
|
457
494
|
object_key: objectKey,
|
|
458
495
|
});
|
|
@@ -515,6 +552,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
515
552
|
const msgChatId = typeof payload === 'object' && payload !== null && payload.chat_id;
|
|
516
553
|
if (this._aid && fromAid === this._aid && (!msgChatId || !this._chatId || msgChatId !== this._chatId)) {
|
|
517
554
|
this.acknowledgeImmediately(messageId, seq);
|
|
555
|
+
logger.debug(`[AUN] P2P dropped: echo from self (from=${fromAid} mid=${messageId})`);
|
|
518
556
|
return;
|
|
519
557
|
}
|
|
520
558
|
// E2EE 能力探测:收到加密消息则标记对端支持,明文则计数审计
|
|
@@ -562,6 +600,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
562
600
|
const peerInfo = await this.fetchPeerInfo(fromAid);
|
|
563
601
|
const shortAid = this.getShortAid(fromAid);
|
|
564
602
|
const displayName = peerInfo.name || shortAid;
|
|
603
|
+
logger.info(`[AUN] P2P dispatched: from=${shortAid}(${displayName}) mid=${messageId} text=${finalText.slice(0, 60)}`);
|
|
565
604
|
this.dispatchMessage({
|
|
566
605
|
channelId: chatId,
|
|
567
606
|
userId: fromAid,
|
|
@@ -590,13 +629,15 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
590
629
|
const payloadMentions = Array.isArray(payload?.mentions)
|
|
591
630
|
? payload.mentions.filter((m) => typeof m === 'string')
|
|
592
631
|
: [];
|
|
593
|
-
logger.
|
|
632
|
+
logger.debug(`[AUN][DIAG-GRP] full_msg=${JSON.stringify(msg).substring(0, 500)}`);
|
|
594
633
|
if (!groupId || !senderAid) {
|
|
595
634
|
this.acknowledgeImmediately(messageId, seq);
|
|
635
|
+
logger.debug(`[AUN] Group dropped: missing groupId or senderAid (mid=${messageId})`);
|
|
596
636
|
return;
|
|
597
637
|
}
|
|
598
638
|
if (this._aid && senderAid === this._aid) {
|
|
599
639
|
this.acknowledgeImmediately(messageId, seq);
|
|
640
|
+
logger.debug(`[AUN] Group dropped: own message (group=${groupId} mid=${messageId})`);
|
|
600
641
|
return;
|
|
601
642
|
}
|
|
602
643
|
// E2EE 能力探测:收到加密群消息则标记发送者支持
|
|
@@ -618,6 +659,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
618
659
|
// In mention mode, only respond when explicitly mentioned; in broadcast mode, respond to all
|
|
619
660
|
if (dispatchMode === 'mention' && !mentionedSelf && !mentionedAll) {
|
|
620
661
|
this.acknowledgeImmediately(messageId, seq);
|
|
662
|
+
logger.debug(`[AUN] Group missed: unmentioned in mention-mode (group=${groupId} sender=${senderAid} mid=${messageId})`);
|
|
621
663
|
return;
|
|
622
664
|
}
|
|
623
665
|
const strippedText = this.stripSelfMentionIfOnly(text, this._aid);
|
|
@@ -629,6 +671,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
629
671
|
// Allow through if there's text OR attachments; both-empty messages are silently dropped
|
|
630
672
|
if (!strippedText && !hasAttachments) {
|
|
631
673
|
this.acknowledgeImmediately(messageId, seq);
|
|
674
|
+
logger.debug(`[AUN] Group dropped: empty text and no attachments (group=${groupId} sender=${senderAid} mid=${messageId})`);
|
|
632
675
|
return;
|
|
633
676
|
}
|
|
634
677
|
const mentions = mentionedAll
|
|
@@ -657,6 +700,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
657
700
|
const peerInfo = await this.fetchPeerInfo(senderAid);
|
|
658
701
|
const shortAid = this.getShortAid(senderAid);
|
|
659
702
|
const displayName = peerInfo.name || shortAid;
|
|
703
|
+
logger.info(`[AUN] Group dispatched: group=${groupId} sender=${shortAid}(${displayName}) mode=${dispatchMode} mid=${messageId} text=${finalText.slice(0, 60)}`);
|
|
660
704
|
this.dispatchMessage({
|
|
661
705
|
channelId: groupId,
|
|
662
706
|
userId: senderAid,
|
|
@@ -716,10 +760,12 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
716
760
|
this.reconnectAttempt = 0;
|
|
717
761
|
this.lastReconnectLogTime = 0;
|
|
718
762
|
this.lastReconnectLogAttempt = 0;
|
|
763
|
+
this.trace('IN', 'connection.state', data);
|
|
719
764
|
logger.info('[AUN] Connected');
|
|
720
765
|
}
|
|
721
766
|
else if (state === 'disconnected') {
|
|
722
767
|
this.connected = false;
|
|
768
|
+
this.trace('IN', 'connection.state', data);
|
|
723
769
|
logger.warn(`[AUN] Disconnected: ${data.error ?? 'unknown'}`);
|
|
724
770
|
}
|
|
725
771
|
else if (state === 'reconnecting') {
|
|
@@ -742,6 +788,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
742
788
|
logger.warn(`[AUN] SDK reconnect stuck at attempt ${attempt}, forcing TS-layer reconnect with backoff`);
|
|
743
789
|
this.connected = false;
|
|
744
790
|
if (this.client) {
|
|
791
|
+
this.trace('OUT', 'client.close', { reason: 'sdk_reconnect_stuck' });
|
|
745
792
|
this.client.close().catch(() => { });
|
|
746
793
|
this.client = null;
|
|
747
794
|
}
|
|
@@ -750,6 +797,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
750
797
|
}
|
|
751
798
|
else if (state === 'terminal_failed') {
|
|
752
799
|
this.connected = false;
|
|
800
|
+
this.trace('IN', 'connection.state', data);
|
|
753
801
|
const reason = data.reason ?? '';
|
|
754
802
|
logger.error(`[AUN] Terminal failure: ${data.error ?? 'unknown'}${reason ? ` (${reason})` : ''}`);
|
|
755
803
|
if (!this.intentionalDisconnect) {
|
|
@@ -808,19 +856,23 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
808
856
|
try {
|
|
809
857
|
if (isGroup) {
|
|
810
858
|
params.group_id = channelId;
|
|
811
|
-
this.
|
|
812
|
-
const result = await this.client.call('group.send', params);
|
|
859
|
+
const result = await this.callAndTrace('group.send', params);
|
|
813
860
|
if (!result || !result.message_id) {
|
|
814
861
|
logger.warn(`[AUN] group.send returned no message_id: ${JSON.stringify(result)}`);
|
|
815
862
|
}
|
|
863
|
+
else {
|
|
864
|
+
logger.info(`[AUN] group.send ok: group=${channelId} mid=${result.message_id} text=${finalText.slice(0, 60)}`);
|
|
865
|
+
}
|
|
816
866
|
}
|
|
817
867
|
else {
|
|
818
868
|
params.to = targetAid;
|
|
819
|
-
this.
|
|
820
|
-
const result = await this.client.call('message.send', params);
|
|
869
|
+
const result = await this.callAndTrace('message.send', params);
|
|
821
870
|
if (!result || !result.message_id) {
|
|
822
871
|
logger.warn(`[AUN] message.send returned no message_id: ${JSON.stringify(result)}`);
|
|
823
872
|
}
|
|
873
|
+
else {
|
|
874
|
+
logger.info(`[AUN] message.send ok: to=${targetAid} mid=${result.message_id} text=${finalText.slice(0, 60)}`);
|
|
875
|
+
}
|
|
824
876
|
}
|
|
825
877
|
}
|
|
826
878
|
catch (e) {
|
|
@@ -832,6 +884,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
832
884
|
if (isGroup) {
|
|
833
885
|
this.trace('OUT', 'group.send.fallback', params);
|
|
834
886
|
const result = await this.client.call('group.send', params);
|
|
887
|
+
this.trace('OUT', 'group.send.fallback.ok', { message_id: result?.message_id });
|
|
835
888
|
if (!result || !result.message_id) {
|
|
836
889
|
logger.warn(`[AUN] group.send fallback returned no message_id: ${JSON.stringify(result)}`);
|
|
837
890
|
}
|
|
@@ -839,6 +892,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
839
892
|
else {
|
|
840
893
|
this.trace('OUT', 'message.send.fallback', params);
|
|
841
894
|
const result = await this.client.call('message.send', params);
|
|
895
|
+
this.trace('OUT', 'message.send.fallback.ok', { message_id: result?.message_id });
|
|
842
896
|
if (!result || !result.message_id) {
|
|
843
897
|
logger.warn(`[AUN] message.send fallback returned no message_id: ${JSON.stringify(result)}`);
|
|
844
898
|
}
|
|
@@ -877,25 +931,20 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
877
931
|
encrypt: true,
|
|
878
932
|
};
|
|
879
933
|
try {
|
|
934
|
+
const stage = payload?.stage ?? 'unknown';
|
|
880
935
|
if (this.isGroupId(channelId)) {
|
|
881
936
|
params.group_id = targetId;
|
|
882
|
-
this.
|
|
883
|
-
|
|
937
|
+
await this.callAndTrace('group.thought.put', params);
|
|
938
|
+
logger.info(`[AUN] thought.put ok group=${targetId} task=${taskId} stage=${stage}`);
|
|
884
939
|
}
|
|
885
940
|
else {
|
|
886
941
|
params.to = targetId;
|
|
887
|
-
this.
|
|
888
|
-
|
|
942
|
+
await this.callAndTrace('message.thought.put', params);
|
|
943
|
+
logger.info(`[AUN] thought.put ok p2p=${targetId} task=${taskId} stage=${stage}`);
|
|
889
944
|
}
|
|
890
945
|
}
|
|
891
946
|
catch (e) {
|
|
892
947
|
const err = e;
|
|
893
|
-
this.trace('OUT', 'thought.put.error', {
|
|
894
|
-
channelId,
|
|
895
|
-
errorName: err?.name,
|
|
896
|
-
errorCode: err?.code,
|
|
897
|
-
errorMessage: err?.message,
|
|
898
|
-
});
|
|
899
948
|
logger.debug(`[AUN] thought.put failed to ${channelId}: ${err?.name}(${err?.code})=${err?.message}`);
|
|
900
949
|
}
|
|
901
950
|
}
|
|
@@ -927,7 +976,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
927
976
|
// Upload to storage
|
|
928
977
|
if (stat.size <= 64 * 1024) {
|
|
929
978
|
// Inline upload for small files (≤64KB)
|
|
930
|
-
await this.
|
|
979
|
+
await this.callAndTrace('storage.put_object', {
|
|
931
980
|
object_key: objectKey,
|
|
932
981
|
content: fileData.toString('base64'),
|
|
933
982
|
content_type: contentType,
|
|
@@ -937,7 +986,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
937
986
|
}
|
|
938
987
|
else {
|
|
939
988
|
// Ticket upload for large files
|
|
940
|
-
const session = await this.
|
|
989
|
+
const session = await this.callAndTrace('storage.create_upload_session', {
|
|
941
990
|
object_key: objectKey,
|
|
942
991
|
size_bytes: stat.size,
|
|
943
992
|
content_type: contentType,
|
|
@@ -945,10 +994,12 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
945
994
|
const uploadUrl = session.upload_url;
|
|
946
995
|
if (!uploadUrl)
|
|
947
996
|
throw new Error('No upload_url in session response');
|
|
997
|
+
this.trace('OUT', 'http.put.upload_url', { object_key: objectKey, size: stat.size });
|
|
948
998
|
const uploadResp = await fetch(uploadUrl, { method: 'PUT', body: fileData });
|
|
999
|
+
this.trace('OUT', uploadResp.ok ? 'http.put.upload_url.ok' : 'http.put.upload_url.error', { status: uploadResp.status });
|
|
949
1000
|
if (!uploadResp.ok)
|
|
950
1001
|
throw new Error(`HTTP upload failed: ${uploadResp.status}`);
|
|
951
|
-
await this.
|
|
1002
|
+
await this.callAndTrace('storage.complete_upload', {
|
|
952
1003
|
object_key: objectKey,
|
|
953
1004
|
sha256,
|
|
954
1005
|
content_type: contentType,
|
|
@@ -991,6 +1042,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
991
1042
|
params.group_id = channelId;
|
|
992
1043
|
this.trace('OUT', 'group.send.file', params);
|
|
993
1044
|
const result = await this.client.call('group.send', params);
|
|
1045
|
+
this.trace('OUT', 'group.send.file.ok', { message_id: result?.message_id });
|
|
994
1046
|
if (!result || !result.message_id) {
|
|
995
1047
|
logger.warn(`[AUN] group.send.file returned no message_id: ${JSON.stringify(result)}`);
|
|
996
1048
|
}
|
|
@@ -999,12 +1051,17 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
999
1051
|
params.to = fileTargetAid;
|
|
1000
1052
|
this.trace('OUT', 'message.send.file', params);
|
|
1001
1053
|
const result = await this.client.call('message.send', params);
|
|
1054
|
+
this.trace('OUT', 'message.send.file.ok', { message_id: result?.message_id });
|
|
1002
1055
|
if (!result || !result.message_id) {
|
|
1003
1056
|
logger.warn(`[AUN] message.send.file returned no message_id: ${JSON.stringify(result)}`);
|
|
1004
1057
|
}
|
|
1005
1058
|
}
|
|
1006
1059
|
}
|
|
1007
1060
|
catch (sendErr) {
|
|
1061
|
+
this.trace('OUT', isGroup ? 'group.send.file.error' : 'message.send.file.error', {
|
|
1062
|
+
error: sendErr?.message ?? String(sendErr),
|
|
1063
|
+
code: sendErr?.code,
|
|
1064
|
+
});
|
|
1008
1065
|
if (encrypt && sendErr instanceof E2EEError) {
|
|
1009
1066
|
this.peerE2ee.set(encryptTarget, { ok: false, ts: Date.now() });
|
|
1010
1067
|
logger.warn(`[AUN] E2EE sendFile failed to ${channelId}, retrying plaintext: ${sendErr}`);
|
|
@@ -1012,6 +1069,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1012
1069
|
if (isGroup) {
|
|
1013
1070
|
this.trace('OUT', 'group.send.file.fallback', params);
|
|
1014
1071
|
const result = await this.client.call('group.send', params);
|
|
1072
|
+
this.trace('OUT', 'group.send.file.fallback.ok', { message_id: result?.message_id });
|
|
1015
1073
|
if (!result || !result.message_id) {
|
|
1016
1074
|
logger.warn(`[AUN] group.send.file fallback returned no message_id: ${JSON.stringify(result)}`);
|
|
1017
1075
|
}
|
|
@@ -1019,6 +1077,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1019
1077
|
else {
|
|
1020
1078
|
this.trace('OUT', 'message.send.file.fallback', params);
|
|
1021
1079
|
const result = await this.client.call('message.send', params);
|
|
1080
|
+
this.trace('OUT', 'message.send.file.fallback.ok', { message_id: result?.message_id });
|
|
1022
1081
|
if (!result || !result.message_id) {
|
|
1023
1082
|
logger.warn(`[AUN] message.send.file fallback returned no message_id: ${JSON.stringify(result)}`);
|
|
1024
1083
|
}
|
|
@@ -1095,6 +1154,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1095
1154
|
this.trace('OUT', 'message.send.status', params);
|
|
1096
1155
|
sendWithFallback('message.send');
|
|
1097
1156
|
}
|
|
1157
|
+
logger.info(`[AUN] task.${status} task=${taskId} session=${sessionId} target=${channelId}`);
|
|
1098
1158
|
}
|
|
1099
1159
|
sendCustomPayload(channelId, payload) {
|
|
1100
1160
|
if (!this.client || !this.connected)
|
|
@@ -1120,8 +1180,11 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1120
1180
|
encrypt: true,
|
|
1121
1181
|
};
|
|
1122
1182
|
this.trace('OUT', 'message.send.custom', sendParams);
|
|
1123
|
-
this.client.call('message.send', sendParams).
|
|
1124
|
-
|
|
1183
|
+
this.client.call('message.send', sendParams).then((result) => {
|
|
1184
|
+
this.trace('OUT', 'message.send.custom.ok', { message_id: result?.message_id });
|
|
1185
|
+
}).catch(e => {
|
|
1186
|
+
this.trace('OUT', 'message.send.custom.error', { error: String(e) });
|
|
1187
|
+
logger.warn(`[AUN] Custom payload failed: ${e}`);
|
|
1125
1188
|
});
|
|
1126
1189
|
}
|
|
1127
1190
|
async disconnect() {
|
|
@@ -1131,13 +1194,18 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1131
1194
|
this.reconnectTimer = null;
|
|
1132
1195
|
}
|
|
1133
1196
|
if (this.client) {
|
|
1197
|
+
this.trace('OUT', 'client.close', { reason: 'disconnect' });
|
|
1134
1198
|
try {
|
|
1135
1199
|
await this.client.close();
|
|
1200
|
+
this.trace('OUT', 'client.close.ok', { reason: 'disconnect' });
|
|
1201
|
+
}
|
|
1202
|
+
catch (e) {
|
|
1203
|
+
this.trace('OUT', 'client.close.error', { reason: 'disconnect', error: String(e) });
|
|
1136
1204
|
}
|
|
1137
|
-
catch { /* ignore */ }
|
|
1138
1205
|
this.client = null;
|
|
1139
1206
|
}
|
|
1140
1207
|
this.connected = false;
|
|
1208
|
+
logger.info('[AUN] Disconnected');
|
|
1141
1209
|
if (this.traceStream) {
|
|
1142
1210
|
this.traceStream.end();
|
|
1143
1211
|
this.traceStream = null;
|
|
@@ -1163,10 +1231,13 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1163
1231
|
this.reconnectTimer = null;
|
|
1164
1232
|
try {
|
|
1165
1233
|
logger.info(`[AUN] Reconnect #${this.reconnectAttempt} starting...`);
|
|
1234
|
+
this.trace('OUT', 'reconnect.start', { attempt: this.reconnectAttempt });
|
|
1166
1235
|
await this.initClient();
|
|
1236
|
+
this.trace('OUT', 'reconnect.ok', { attempt: this.reconnectAttempt });
|
|
1167
1237
|
logger.info(`[AUN] Reconnect #${this.reconnectAttempt} succeeded`);
|
|
1168
1238
|
}
|
|
1169
1239
|
catch (err) {
|
|
1240
|
+
this.trace('OUT', 'reconnect.error', { attempt: this.reconnectAttempt, error: String(err) });
|
|
1170
1241
|
logger.error(`[AUN] Reconnect #${this.reconnectAttempt} failed:`, err);
|
|
1171
1242
|
this.scheduleReconnect();
|
|
1172
1243
|
}
|
|
@@ -1242,7 +1313,8 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1242
1313
|
setTimeout(() => this.peerInfoCache.delete(aid), 30 * 60 * 1000);
|
|
1243
1314
|
return info;
|
|
1244
1315
|
}
|
|
1245
|
-
catch {
|
|
1316
|
+
catch (e) {
|
|
1317
|
+
logger.debug(`[AUN] fetchPeerInfo failed for ${aid}: ${e}`);
|
|
1246
1318
|
return { type: null }; // no agent.md → unknown
|
|
1247
1319
|
}
|
|
1248
1320
|
}
|
|
@@ -1331,7 +1403,6 @@ export class AUNChannelPlugin {
|
|
|
1331
1403
|
const options = {
|
|
1332
1404
|
flushDelay: inst.flushDelay ?? 3,
|
|
1333
1405
|
fileMarkerPattern: /\[SEND_FILE:(?:(\w+):)?([^\]]+)\]/g,
|
|
1334
|
-
sessionMode: inst.sessionMode,
|
|
1335
1406
|
};
|
|
1336
1407
|
result.push({
|
|
1337
1408
|
channelType: 'aun',
|
package/dist/channels/feishu.js
CHANGED
|
@@ -65,6 +65,8 @@ export class FeishuChannel {
|
|
|
65
65
|
if (msg.thread_id) {
|
|
66
66
|
logger.info('[Feishu] Thread message, thread_id:', msg.thread_id, 'root_id:', msg.root_id);
|
|
67
67
|
}
|
|
68
|
+
// [DEBUG] 临时:记录所有消息的 root_id/thread_id,用于排查图片回复带引用问题
|
|
69
|
+
logger.info('[Feishu][DEBUG] msg_type:', msg.message_type, 'root_id:', msg.root_id ?? '(empty)', 'thread_id:', msg.thread_id ?? '(empty)', 'parent_id:', msg.parent_id ?? '(empty)');
|
|
68
70
|
// 提取 @ 提及列表(排除机器人自身)
|
|
69
71
|
const mentions = (msg.mentions || []).map((m) => ({
|
|
70
72
|
userId: m.id?.open_id || '',
|
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import path from 'path';
|
|
|
4
4
|
import { spawn, execFile } from 'child_process';
|
|
5
5
|
import { promisify } from 'util';
|
|
6
6
|
import { resolveRoot, resolvePaths, ensureDataDirs, getPackageRoot } from './paths.js';
|
|
7
|
-
import { loadConfig, validateConfigIntegrity, resolveAnthropicConfig } from './config.js';
|
|
7
|
+
import { loadConfig, validateConfigIntegrity, resolveAnthropicConfig, normalizeChannelInstances, channelTypes } from './config.js';
|
|
8
8
|
import { migrateProject } from './utils/migrate-project.js';
|
|
9
9
|
import readline from 'readline';
|
|
10
10
|
import { cmdInit } from './utils/init.js';
|
|
@@ -479,6 +479,30 @@ async function cmdStatus() {
|
|
|
479
479
|
ORDER BY updated_at DESC
|
|
480
480
|
LIMIT 5
|
|
481
481
|
`).all();
|
|
482
|
+
// Detect orphan sessions (channel no longer in config)
|
|
483
|
+
let orphanCount = 0;
|
|
484
|
+
try {
|
|
485
|
+
const config = loadConfig(p.config);
|
|
486
|
+
const configChannelNames = new Set();
|
|
487
|
+
for (const type of channelTypes) {
|
|
488
|
+
const raw = config.channels?.[type];
|
|
489
|
+
const instances = normalizeChannelInstances(raw, type);
|
|
490
|
+
for (const inst of instances) {
|
|
491
|
+
configChannelNames.add(inst.name);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
const channelCounts = db.prepare(`
|
|
495
|
+
SELECT channel, COUNT(*) as c FROM sessions
|
|
496
|
+
WHERE deleted_at IS NULL
|
|
497
|
+
GROUP BY channel
|
|
498
|
+
`).all();
|
|
499
|
+
for (const row of channelCounts) {
|
|
500
|
+
if (!configChannelNames.has(row.channel)) {
|
|
501
|
+
orphanCount += row.c;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
catch { }
|
|
482
506
|
db.close();
|
|
483
507
|
if (recentSessions.length > 0) {
|
|
484
508
|
console.log('');
|
|
@@ -496,6 +520,10 @@ async function cmdStatus() {
|
|
|
496
520
|
console.log(` ${dot} [${agentType}] ${projectName} / ${sessionName} (${sessionType}, ${chatType})${agentId} - ${timeAgo}`);
|
|
497
521
|
}
|
|
498
522
|
}
|
|
523
|
+
if (orphanCount > 0) {
|
|
524
|
+
console.log('');
|
|
525
|
+
console.log(`⚠ Orphan sessions: ${orphanCount}`);
|
|
526
|
+
}
|
|
499
527
|
}
|
|
500
528
|
catch { }
|
|
501
529
|
}
|