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 CHANGED
@@ -152,7 +152,7 @@ evolclaw init aun
152
152
  }
153
153
  ```
154
154
 
155
- **API 继承机制**:`agents.anthropic` 整个 section 可省略,系统自动按以下优先级继承:
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
- "anthropic": {
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
- "openai": {
11
+ "codex": {
12
12
  "apiKey": "",
13
13
  "baseUrl": "",
14
14
  "model": "gpt-5.2-codex",
15
15
  "effort": "medium"
16
16
  },
17
- "google": {
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 = 'auto';
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?.anthropic?.model;
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?.anthropic?.effort;
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?.anthropic?.useSettingSources !== false;
631
- const enableSummaries = this.config?.agents?.anthropic?.agentProgressSummaries !== false;
632
- const excludeDynamic = this.config?.agents?.anthropic?.excludeDynamicSections === true;
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}`);
@@ -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.info(`[AUN][DIAG] message.received: kind=${kind} keys=${keys}`);
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.info(`[AUN][DIAG] group.message_created: group_id=${gid} sender=${sender}`);
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.client.call('message.send', {
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.client.call('storage.create_download_ticket', {
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.info(`[AUN][DIAG-GRP] full_msg=${JSON.stringify(msg).substring(0, 500)}`);
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.trace('OUT', 'group.send', params);
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.trace('OUT', 'message.send', params);
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.trace('OUT', 'group.thought.put', params);
884
- await this.client.call('group.thought.put', params);
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.trace('OUT', 'message.thought.put', params);
890
- await this.client.call('message.thought.put', params);
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.client.call('storage.put_object', {
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.client.call('storage.create_upload_session', {
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.client.call('storage.complete_upload', {
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).catch(e => {
1128
- logger.debug(`[AUN] Custom payload failed: ${e}`);
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',
@@ -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
  }