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 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"
@@ -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?.anthropic?.model;
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?.anthropic?.effort;
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?.anthropic?.useSettingSources !== false;
630
- const enableSummaries = this.config?.agents?.anthropic?.agentProgressSummaries !== false;
631
- const excludeDynamic = this.config?.agents?.anthropic?.excludeDynamicSections === true;
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
  }
@@ -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
  }
@@ -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.trace('OUT', 'group.thought.put', params);
883
- await this.client.call('group.thought.put', params);
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.trace('OUT', 'message.thought.put', params);
888
- await this.client.call('message.thought.put', params);
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.client.call('storage.put_object', {
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.client.call('storage.create_upload_session', {
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.client.call('storage.complete_upload', {
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).catch(e => {
1124
- 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}`);
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',
@@ -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
  }