evolclaw 2.6.4 → 2.7.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 +1 -1
- package/data/evolclaw.sample.json +3 -4
- package/dist/agents/claude-runner.js +15 -6
- package/dist/channels/aun.js +97 -30
- 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 +43 -12
- package/dist/core/session/session-manager.js +9 -7
- package/dist/index.js +21 -25
- package/dist/templates/prompts.md +4 -4
- package/dist/types.js +2 -1
- package/dist/utils/channel-fingerprint.js +59 -0
- package/dist/utils/cross-platform.js +23 -12
- 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"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { query, forkSession as sdkForkSession, getSessionMessages as sdkGetSessionMessages } from '@anthropic-ai/claude-agent-sdk';
|
|
2
2
|
import { ensureDir, resolveAnthropicConfig } from '../config.js';
|
|
3
|
+
import { DEFAULT_PERMISSION_MODE } from '../types.js';
|
|
3
4
|
import path from 'path';
|
|
4
5
|
import fs from 'fs';
|
|
5
6
|
import os from 'os';
|
|
@@ -72,7 +73,7 @@ export class AgentRunner {
|
|
|
72
73
|
apiKey;
|
|
73
74
|
model;
|
|
74
75
|
effort;
|
|
75
|
-
permissionMode =
|
|
76
|
+
permissionMode = DEFAULT_PERMISSION_MODE;
|
|
76
77
|
baseUrl;
|
|
77
78
|
config;
|
|
78
79
|
activeSessions = new Map();
|
|
@@ -173,12 +174,12 @@ export class AgentRunner {
|
|
|
173
174
|
return;
|
|
174
175
|
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
175
176
|
// evolclaw.json 显式配置优先,不被 settings.json 覆盖
|
|
176
|
-
const configModel = this.config?.agents?.
|
|
177
|
+
const configModel = this.config?.agents?.claude?.model;
|
|
177
178
|
if (!configModel && settings.model && settings.model !== this.model) {
|
|
178
179
|
logger.info(`[AgentRunner] Synced model from ~/.claude/settings.json: ${settings.model}`);
|
|
179
180
|
this.model = settings.model;
|
|
180
181
|
}
|
|
181
|
-
const configEffort = this.config?.agents?.
|
|
182
|
+
const configEffort = this.config?.agents?.claude?.effort;
|
|
182
183
|
if (!configEffort) {
|
|
183
184
|
const newEffort = settings.effortLevel || undefined;
|
|
184
185
|
if (newEffort !== this.effort) {
|
|
@@ -593,6 +594,14 @@ export class AgentRunner {
|
|
|
593
594
|
if (this.permissionMode === 'bypass') {
|
|
594
595
|
return { behavior: 'allow', updatedInput: input, decisionClassification: 'user_permanent' };
|
|
595
596
|
}
|
|
597
|
+
// evolclaw ctl send/file 白名单:proactive 模式下 agent 必须通过这些命令发送消息,
|
|
598
|
+
// 任何权限模式下都不应拦截,否则 agent 无法回复用户
|
|
599
|
+
if (toolName === 'Bash') {
|
|
600
|
+
const cmd = input.command || '';
|
|
601
|
+
if (/^\s*evolclaw\s+ctl\s+(send|file)\b/.test(cmd)) {
|
|
602
|
+
return { behavior: 'allow', updatedInput: input, decisionClassification: 'user_permanent' };
|
|
603
|
+
}
|
|
604
|
+
}
|
|
596
605
|
// readonly 模式:二次拦截(belt-and-suspenders)
|
|
597
606
|
if (this.permissionMode === 'readonly') {
|
|
598
607
|
const roResult = checkReadonly(toolName, input, projectPath);
|
|
@@ -627,9 +636,9 @@ export class AgentRunner {
|
|
|
627
636
|
decisionClassification: decision === 'always' ? 'user_permanent' : 'user_temporary'
|
|
628
637
|
};
|
|
629
638
|
};
|
|
630
|
-
const useSettingSources = this.config?.agents?.
|
|
631
|
-
const enableSummaries = this.config?.agents?.
|
|
632
|
-
const excludeDynamic = this.config?.agents?.
|
|
639
|
+
const useSettingSources = this.config?.agents?.claude?.useSettingSources !== false;
|
|
640
|
+
const enableSummaries = this.config?.agents?.claude?.agentProgressSummaries !== false;
|
|
641
|
+
const excludeDynamic = this.config?.agents?.claude?.excludeDynamicSections === true;
|
|
633
642
|
// 公共 options(新旧模式共用)
|
|
634
643
|
const sdkPermissionMode = this.toSdkPermissionMode();
|
|
635
644
|
logger.info(`[AgentRunner] runQuery model=${this.model} effort=${this.effort ?? 'auto'} permMode=${this.permissionMode} sdkMode=${sdkPermissionMode}`);
|
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
|
}
|
|
@@ -880,25 +934,17 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
880
934
|
const stage = payload?.stage ?? 'unknown';
|
|
881
935
|
if (this.isGroupId(channelId)) {
|
|
882
936
|
params.group_id = targetId;
|
|
883
|
-
this.
|
|
884
|
-
|
|
885
|
-
logger.debug(`[AUN] thought.put ok group=${targetId} task=${taskId} stage=${stage}`);
|
|
937
|
+
await this.callAndTrace('group.thought.put', params);
|
|
938
|
+
logger.info(`[AUN] thought.put ok group=${targetId} task=${taskId} stage=${stage}`);
|
|
886
939
|
}
|
|
887
940
|
else {
|
|
888
941
|
params.to = targetId;
|
|
889
|
-
this.
|
|
890
|
-
|
|
891
|
-
logger.debug(`[AUN] thought.put ok p2p=${targetId} task=${taskId} stage=${stage}`);
|
|
942
|
+
await this.callAndTrace('message.thought.put', params);
|
|
943
|
+
logger.info(`[AUN] thought.put ok p2p=${targetId} task=${taskId} stage=${stage}`);
|
|
892
944
|
}
|
|
893
945
|
}
|
|
894
946
|
catch (e) {
|
|
895
947
|
const err = e;
|
|
896
|
-
this.trace('OUT', 'thought.put.error', {
|
|
897
|
-
channelId,
|
|
898
|
-
errorName: err?.name,
|
|
899
|
-
errorCode: err?.code,
|
|
900
|
-
errorMessage: err?.message,
|
|
901
|
-
});
|
|
902
948
|
logger.debug(`[AUN] thought.put failed to ${channelId}: ${err?.name}(${err?.code})=${err?.message}`);
|
|
903
949
|
}
|
|
904
950
|
}
|
|
@@ -930,7 +976,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
930
976
|
// Upload to storage
|
|
931
977
|
if (stat.size <= 64 * 1024) {
|
|
932
978
|
// Inline upload for small files (≤64KB)
|
|
933
|
-
await this.
|
|
979
|
+
await this.callAndTrace('storage.put_object', {
|
|
934
980
|
object_key: objectKey,
|
|
935
981
|
content: fileData.toString('base64'),
|
|
936
982
|
content_type: contentType,
|
|
@@ -940,7 +986,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
940
986
|
}
|
|
941
987
|
else {
|
|
942
988
|
// Ticket upload for large files
|
|
943
|
-
const session = await this.
|
|
989
|
+
const session = await this.callAndTrace('storage.create_upload_session', {
|
|
944
990
|
object_key: objectKey,
|
|
945
991
|
size_bytes: stat.size,
|
|
946
992
|
content_type: contentType,
|
|
@@ -948,10 +994,12 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
948
994
|
const uploadUrl = session.upload_url;
|
|
949
995
|
if (!uploadUrl)
|
|
950
996
|
throw new Error('No upload_url in session response');
|
|
997
|
+
this.trace('OUT', 'http.put.upload_url', { object_key: objectKey, size: stat.size });
|
|
951
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 });
|
|
952
1000
|
if (!uploadResp.ok)
|
|
953
1001
|
throw new Error(`HTTP upload failed: ${uploadResp.status}`);
|
|
954
|
-
await this.
|
|
1002
|
+
await this.callAndTrace('storage.complete_upload', {
|
|
955
1003
|
object_key: objectKey,
|
|
956
1004
|
sha256,
|
|
957
1005
|
content_type: contentType,
|
|
@@ -994,6 +1042,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
994
1042
|
params.group_id = channelId;
|
|
995
1043
|
this.trace('OUT', 'group.send.file', params);
|
|
996
1044
|
const result = await this.client.call('group.send', params);
|
|
1045
|
+
this.trace('OUT', 'group.send.file.ok', { message_id: result?.message_id });
|
|
997
1046
|
if (!result || !result.message_id) {
|
|
998
1047
|
logger.warn(`[AUN] group.send.file returned no message_id: ${JSON.stringify(result)}`);
|
|
999
1048
|
}
|
|
@@ -1002,12 +1051,17 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1002
1051
|
params.to = fileTargetAid;
|
|
1003
1052
|
this.trace('OUT', 'message.send.file', params);
|
|
1004
1053
|
const result = await this.client.call('message.send', params);
|
|
1054
|
+
this.trace('OUT', 'message.send.file.ok', { message_id: result?.message_id });
|
|
1005
1055
|
if (!result || !result.message_id) {
|
|
1006
1056
|
logger.warn(`[AUN] message.send.file returned no message_id: ${JSON.stringify(result)}`);
|
|
1007
1057
|
}
|
|
1008
1058
|
}
|
|
1009
1059
|
}
|
|
1010
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
|
+
});
|
|
1011
1065
|
if (encrypt && sendErr instanceof E2EEError) {
|
|
1012
1066
|
this.peerE2ee.set(encryptTarget, { ok: false, ts: Date.now() });
|
|
1013
1067
|
logger.warn(`[AUN] E2EE sendFile failed to ${channelId}, retrying plaintext: ${sendErr}`);
|
|
@@ -1015,6 +1069,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1015
1069
|
if (isGroup) {
|
|
1016
1070
|
this.trace('OUT', 'group.send.file.fallback', params);
|
|
1017
1071
|
const result = await this.client.call('group.send', params);
|
|
1072
|
+
this.trace('OUT', 'group.send.file.fallback.ok', { message_id: result?.message_id });
|
|
1018
1073
|
if (!result || !result.message_id) {
|
|
1019
1074
|
logger.warn(`[AUN] group.send.file fallback returned no message_id: ${JSON.stringify(result)}`);
|
|
1020
1075
|
}
|
|
@@ -1022,6 +1077,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1022
1077
|
else {
|
|
1023
1078
|
this.trace('OUT', 'message.send.file.fallback', params);
|
|
1024
1079
|
const result = await this.client.call('message.send', params);
|
|
1080
|
+
this.trace('OUT', 'message.send.file.fallback.ok', { message_id: result?.message_id });
|
|
1025
1081
|
if (!result || !result.message_id) {
|
|
1026
1082
|
logger.warn(`[AUN] message.send.file fallback returned no message_id: ${JSON.stringify(result)}`);
|
|
1027
1083
|
}
|
|
@@ -1124,8 +1180,11 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1124
1180
|
encrypt: true,
|
|
1125
1181
|
};
|
|
1126
1182
|
this.trace('OUT', 'message.send.custom', sendParams);
|
|
1127
|
-
this.client.call('message.send', sendParams).
|
|
1128
|
-
|
|
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}`);
|
|
1129
1188
|
});
|
|
1130
1189
|
}
|
|
1131
1190
|
async disconnect() {
|
|
@@ -1135,13 +1194,18 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1135
1194
|
this.reconnectTimer = null;
|
|
1136
1195
|
}
|
|
1137
1196
|
if (this.client) {
|
|
1197
|
+
this.trace('OUT', 'client.close', { reason: 'disconnect' });
|
|
1138
1198
|
try {
|
|
1139
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) });
|
|
1140
1204
|
}
|
|
1141
|
-
catch { /* ignore */ }
|
|
1142
1205
|
this.client = null;
|
|
1143
1206
|
}
|
|
1144
1207
|
this.connected = false;
|
|
1208
|
+
logger.info('[AUN] Disconnected');
|
|
1145
1209
|
if (this.traceStream) {
|
|
1146
1210
|
this.traceStream.end();
|
|
1147
1211
|
this.traceStream = null;
|
|
@@ -1167,10 +1231,13 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1167
1231
|
this.reconnectTimer = null;
|
|
1168
1232
|
try {
|
|
1169
1233
|
logger.info(`[AUN] Reconnect #${this.reconnectAttempt} starting...`);
|
|
1234
|
+
this.trace('OUT', 'reconnect.start', { attempt: this.reconnectAttempt });
|
|
1170
1235
|
await this.initClient();
|
|
1236
|
+
this.trace('OUT', 'reconnect.ok', { attempt: this.reconnectAttempt });
|
|
1171
1237
|
logger.info(`[AUN] Reconnect #${this.reconnectAttempt} succeeded`);
|
|
1172
1238
|
}
|
|
1173
1239
|
catch (err) {
|
|
1240
|
+
this.trace('OUT', 'reconnect.error', { attempt: this.reconnectAttempt, error: String(err) });
|
|
1174
1241
|
logger.error(`[AUN] Reconnect #${this.reconnectAttempt} failed:`, err);
|
|
1175
1242
|
this.scheduleReconnect();
|
|
1176
1243
|
}
|
|
@@ -1246,7 +1313,8 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1246
1313
|
setTimeout(() => this.peerInfoCache.delete(aid), 30 * 60 * 1000);
|
|
1247
1314
|
return info;
|
|
1248
1315
|
}
|
|
1249
|
-
catch {
|
|
1316
|
+
catch (e) {
|
|
1317
|
+
logger.debug(`[AUN] fetchPeerInfo failed for ${aid}: ${e}`);
|
|
1250
1318
|
return { type: null }; // no agent.md → unknown
|
|
1251
1319
|
}
|
|
1252
1320
|
}
|
|
@@ -1335,7 +1403,6 @@ export class AUNChannelPlugin {
|
|
|
1335
1403
|
const options = {
|
|
1336
1404
|
flushDelay: inst.flushDelay ?? 3,
|
|
1337
1405
|
fileMarkerPattern: /\[SEND_FILE:(?:(\w+):)?([^\]]+)\]/g,
|
|
1338
|
-
sessionMode: inst.sessionMode,
|
|
1339
1406
|
};
|
|
1340
1407
|
result.push({
|
|
1341
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
|
}
|