imtoagent 0.3.3 → 0.3.5

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.
Files changed (39) hide show
  1. package/README.md +97 -97
  2. package/bin/imtoagent-real +96 -96
  3. package/bin/imtoagent.cjs +1 -1
  4. package/index.ts +106 -106
  5. package/modules/agent/claude-adapter.ts +6 -6
  6. package/modules/agent/claude.ts +6 -6
  7. package/modules/agent/codex-adapter.ts +13 -13
  8. package/modules/agent/codex-exec-server.ts +11 -11
  9. package/modules/agent/codex.ts +29 -29
  10. package/modules/agent/opencode-adapter.ts +17 -17
  11. package/modules/agent/opencode.ts +10 -10
  12. package/modules/capabilities.ts +33 -33
  13. package/modules/cli/setup.ts +171 -163
  14. package/modules/core/config.ts +5 -5
  15. package/modules/core/error.ts +8 -8
  16. package/modules/core/runtime.ts +10 -10
  17. package/modules/core/session.ts +4 -4
  18. package/modules/core/stats.ts +14 -14
  19. package/modules/core/types.ts +7 -7
  20. package/modules/im/feishu.ts +56 -56
  21. package/modules/im/telegram.ts +23 -23
  22. package/modules/im/wechat.ts +54 -54
  23. package/modules/im/wecom.ts +50 -50
  24. package/modules/media/feishu-inbound-adapter.ts +4 -4
  25. package/modules/media/resolver.ts +11 -11
  26. package/modules/media/telegram-inbound-adapter.ts +8 -8
  27. package/modules/prompt-builder.ts +12 -12
  28. package/modules/proxy/anthropic-proxy.ts +39 -28
  29. package/modules/proxy/codex-proxy.ts +18 -18
  30. package/modules/utils/backend-check.ts +12 -12
  31. package/modules/utils/paths.ts +8 -8
  32. package/package.json +1 -1
  33. package/scripts/postinstall.cjs +10 -10
  34. package/scripts/postinstall.ts +13 -13
  35. package/templates/soul.template/identity.md +5 -5
  36. package/templates/soul.template/profile.md +7 -7
  37. package/templates/soul.template/rules.md +5 -5
  38. package/templates/soul.template/skills.md +2 -2
  39. package/templates/soul.template/workspace.md +3 -3
package/index.ts CHANGED
@@ -159,7 +159,7 @@ class CustomSessionManager {
159
159
  recentMessages: data.recentMessages || [],
160
160
  };
161
161
  } catch (e: any) {
162
- console.error(`[Session] 加载失败 ${chatId}: ${e.message}`);
162
+ console.error(`[Session] Failed to load ${chatId}: ${e.message}`);
163
163
  session = this._newSession(chatId, userId);
164
164
  }
165
165
  } else {
@@ -213,7 +213,7 @@ class CustomSessionManager {
213
213
  try {
214
214
  fs.writeFileSync(fp, JSON.stringify(output, null, 2));
215
215
  } catch (e: any) {
216
- console.error(`[Session] 持久化失败 ${session.chatId}: ${e.message}`);
216
+ console.error(`[Session] Failed to persist ${session.chatId}: ${e.message}`);
217
217
  }
218
218
  }
219
219
 
@@ -235,7 +235,7 @@ class CustomSessionManager {
235
235
  }
236
236
  for (const chatId of toRemove) {
237
237
  this.sessions.delete(chatId);
238
- console.log(`[Session] 清理空闲 ${chatId.slice(-8)}`);
238
+ console.log(`[Session] Cleaning up idle ${chatId.slice(-8)}`);
239
239
  }
240
240
  }
241
241
 
@@ -345,7 +345,7 @@ class Bot {
345
345
  const imFactory = IM_REGISTRY.get(imType);
346
346
  if (!imFactory) {
347
347
  const known = [...IM_REGISTRY.keys()].join(', ');
348
- throw new Error(`不支持的 IM 类型: ${imType}(已注册: ${known})`);
348
+ throw new Error(`Unsupported IM type: ${imType} (registered: ${known})`);
349
349
  }
350
350
  this.im = imFactory.create(cfg);
351
351
 
@@ -394,18 +394,18 @@ class Bot {
394
394
  const hasFiles = fs.readdirSync(dir).some((f: string) => f.endsWith('.md'));
395
395
  if (hasFiles) return;
396
396
  const defaults: Record<string, string> = {
397
- 'rules.md': '# 硬约束规则\n\n以下规则不可被覆盖或修改:\n\n- 项目密钥、token、密码等敏感信息不可外泄\n- 不可执行破坏性命令',
398
- 'identity.md': `# 身份定义\n\n- 我是通过 IMtoAgent 连接的 AI 编程助手\n- 我运行在 ${this.backend === 'codex' ? 'Codex' : 'Claude Code'} 后端\n- 用中文回复`,
399
- 'profile.md': '# 用户画像\n\n此文件可由 Agent 修改。当用户说"记住xxx""我偏好xxx"时,Agent 应更新此文件。\n\n## 修改指南(Agent 专用)\n\n读取此文件根据用户要求增/删/改条目保存',
400
- 'workspace.md': '# 项目环境\n\n由 IMtoAgent 自动生成。',
401
- 'skills.md': '# 技能注入\n\n未来功能。',
397
+ 'rules.md': '# Hard Constraint Rules\n\nThe following rules cannot be overridden or modified:\n\n- Sensitive information such as project keys, tokens, and passwords must not be leaked\n- Destructive commands must not be executed',
398
+ 'identity.md': `# Identity\n\n- I am an AI programming assistant connected via IMtoAgent\n- I run on the ${this.backend === 'codex' ? 'Codex' : 'Claude Code'} backend\n- Reply in Chinese`,
399
+ 'profile.md': '# User Profile\n\nThis file can be modified by the Agent. When the user says "remember xxx" or "I prefer xxx", the Agent should update this file.\n\n## Modification Guide (Agent Only)\n\nRead this file Add/delete/modify entries based on user requests Save',
400
+ 'workspace.md': '# Project Environment\n\nAuto-generated by IMtoAgent.',
401
+ 'skills.md': '# Skill Injection\n\nFuture feature.',
402
402
  };
403
403
  for (const [name, content] of Object.entries(defaults)) {
404
404
  fs.writeFileSync(dir + '/' + name, content);
405
405
  }
406
- console.log(`[${this.name}] 灵魂文件已初始化: ${dir}`);
406
+ console.log(`[${this.name}] Soul files initialized: ${dir}`);
407
407
  } catch (e: any) {
408
- console.error(`[${this.name}] 初始化灵魂失败: ${e.message}`);
408
+ console.error(`[${this.name}] Failed to initialize soul: ${e.message}`);
409
409
  }
410
410
  }
411
411
 
@@ -455,7 +455,7 @@ class Bot {
455
455
  modelPresets: this.modelPresets,
456
456
  }, null, 2));
457
457
  } catch (e: any) {
458
- console.error(`[${this.name}] 保存配置失败:`, e.message);
458
+ console.error(`[${this.name}] Failed to save config:`, e.message);
459
459
  }
460
460
  }
461
461
 
@@ -464,108 +464,108 @@ class Bot {
464
464
  const cmd = (name: string, handler: CommandHandler) => this.commands.set(name, handler);
465
465
 
466
466
  cmd('/help', () => {
467
- let out = '📋 **CC 快捷命令**\n\n';
468
- out += '/status — 状态\n/info — 配置\n/stats — 统计\n';
469
- out += '/model — 模型切换\n/providers — 供应商\n';
470
- out += '/dir — 目录\n/clear — 清空\n';
471
- if (this.backend === 'claude') out += '/mode — 权限\n';
472
- else if (this.backend === 'codex') out += '/mode — 模式(auto/plan)\n';
473
- out += '/memory — 概览\n/soul — 灵魂\n/reload — 重载';
467
+ let out = '📋 **CC Quick Commands**\n\n';
468
+ out += '/status — Status\n/info — Config\n/stats — Stats\n';
469
+ out += '/model — Model Switch\n/providers — Providers\n';
470
+ out += '/dir — Directory\n/clear — Clear\n';
471
+ if (this.backend === 'claude') out += '/mode — Permission\n';
472
+ else if (this.backend === 'codex') out += '/mode — Mode(auto/plan)\n';
473
+ out += '/memory — Overview\n/soul — Soul\n/reload — Reload';
474
474
  return out;
475
475
  });
476
476
 
477
477
  cmd('/status', ({ session }) =>
478
478
  session?.running
479
- ? `✅ ${this.backend} 运行中 | ${this.activeModel} | ${session.stats.calls} 次调用`
480
- : `⏸ ${this.backend} 空闲 | ${this.activeModel}`);
479
+ ? `✅ ${this.backend} running | ${this.activeModel} | ${session.stats.calls} calls`
480
+ : `⏸ ${this.backend} idle | ${this.activeModel}`);
481
481
 
482
482
  cmd('/info', ({ session }) =>
483
- `🤖 ${this.name} (${this.backend})\n模型: ${this.activeModel}\n目录: ${session?.cwd || this.defaultCwd}\n会话数: ${this.sessions.size}`);
483
+ `🤖 ${this.name} (${this.backend})\nModel: ${this.activeModel}\nDirectory: ${session?.cwd || this.defaultCwd}\nSessions: ${this.sessions.size}`);
484
484
 
485
485
  cmd('/stats', ({ session }) => {
486
- if (!session || session.stats.calls === 0) return '📊 暂无调用';
486
+ if (!session || session.stats.calls === 0) return '📊 No calls yet';
487
487
  const s = session.stats;
488
- return `📊 调用 ${s.calls} | ${s.totalTurns} 轮\nToken: ${s.totalInputTokens.toLocaleString()} + ${s.totalOutputTokens.toLocaleString()}出\n费用: $${s.totalCostUSD.toFixed(4)} | 耗时 ${(s.totalDurationMs/1000).toFixed(0)}s`;
488
+ return `📊 ${s.calls} calls | ${s.totalTurns} turns\nTokens: ${s.totalInputTokens.toLocaleString()} in + ${s.totalOutputTokens.toLocaleString()} out\nCost: $${s.totalCostUSD.toFixed(4)} | Duration ${(s.totalDurationMs/1000).toFixed(0)}s`;
489
489
  });
490
490
 
491
491
  cmd('/clear', ({ session }) => {
492
492
  if (session) {
493
493
  session.startFresh = true;
494
- return '🗑 已清空对话(下次消息将开启全新会话)';
494
+ return '🗑 Conversation cleared (next message will start a fresh session)';
495
495
  }
496
- return '✅ 无活跃对话';
496
+ return '✅ No active conversation';
497
497
  });
498
498
 
499
499
  cmd('/model', ({ args }) => {
500
500
  const raw = args.trim();
501
501
  if (!raw) {
502
- let out = `🤖 当前: ${this.activeModel}`;
502
+ let out = `🤖 Current: ${this.activeModel}`;
503
503
  if ((this.backend === 'claude' || this.backend === 'opencode') && this.modelAliases) {
504
504
  if (this.backend === 'claude') {
505
- out += '\n\n🎭 角色映射 (Claude 内部角色模型):';
505
+ out += '\n\n🎭 Role Mapping (Claude internal role model):';
506
506
  for (const role of ['default', 'sonnet', 'opus', 'haiku', 'best']) {
507
507
  const spec = this.modelAliases[role as keyof ModelAliases];
508
508
  if (spec) out += `\n• ${role} → ${spec}`;
509
509
  }
510
- out += '\n💡 /model sonnet 供应商/模型 可修改角色映射';
510
+ out += '\n💡 /model sonnet provider/model to modify role mapping';
511
511
  } else if (this.backend === 'opencode') {
512
- out += '\n\n🎭 角色映射:';
512
+ out += '\n\n🎭 Role Mapping:';
513
513
  const spec = (this.modelAliases as any).opencode;
514
514
  if (spec) out += `\n• opencode → ${spec}`;
515
- out += '\n💡 /model opencode 供应商/模型 可修改映射';
515
+ out += '\n💡 /model opencode provider/model to modify mapping';
516
516
  }
517
517
  }
518
518
  const presets = Object.entries(this.modelPresets || {});
519
519
  if (presets.length > 0) {
520
- out += '\n\n⚡ 快捷切换:';
520
+ out += '\n\n⚡ Quick Switch:';
521
521
  for (const [alias, spec] of presets) {
522
522
  const mark = spec === this.activeModel ? ' ✅' : '';
523
523
  out += `\n• /model ${alias} → ${spec}${mark}`;
524
524
  }
525
525
  }
526
- out += '\n\n📋 /model add <别名> <模型>添加预设';
527
- out += '\n🗑 /model del <别名>删除预设';
528
- out += '\n🔀 /model 供应商/模型直接切换';
526
+ out += '\n\n📋 /model add <alias> <model>Add preset';
527
+ out += '\n🗑 /model del <alias>Delete preset';
528
+ out += '\n🔀 /model provider/modelDirect switch';
529
529
  return out;
530
530
  }
531
531
 
532
532
  if (raw.startsWith('add ')) {
533
533
  const rest = raw.slice(4).trim();
534
534
  const space = rest.indexOf(' ');
535
- if (space < 0) return '❌ 用法: /model add <别名> <供应商/模型>';
535
+ if (space < 0) return '❌ Usage: /model add <alias> <provider/model>';
536
536
  const alias = rest.slice(0, space).trim();
537
537
  const spec = rest.slice(space + 1).trim();
538
538
  if (!this.modelPresets) this.modelPresets = {};
539
539
  this.modelPresets[alias] = spec;
540
540
  this._saveBotConfig();
541
- return `✅ 预设已添加: ${alias} → ${spec}`;
541
+ return `✅ Preset added: ${alias} → ${spec}`;
542
542
  }
543
543
 
544
544
  if (raw.startsWith('del ')) {
545
545
  const alias = raw.slice(4).trim();
546
- if (!this.modelPresets || !this.modelPresets[alias]) return `❌ 未找到预设: ${alias}`;
546
+ if (!this.modelPresets || !this.modelPresets[alias]) return `❌ Preset not found: ${alias}`;
547
547
  delete this.modelPresets[alias];
548
548
  this._saveBotConfig();
549
- return `🗑 已删除预设: ${alias}`;
549
+ return `🗑 Preset deleted: ${alias}`;
550
550
  }
551
551
 
552
552
  // 角色别名
553
553
  if ((this.backend === 'claude' && ['default', 'sonnet', 'opus', 'haiku', 'best'].includes(raw)) ||
554
554
  (this.backend === 'opencode' && raw === 'opencode')) {
555
555
  const spec = (this.modelAliases as any)[raw] || this.modelAliases[raw as keyof typeof this.modelAliases];
556
- if (spec) return `🎭 ${raw} → ${spec}\n💡 修改: /model ${raw} 供应商/模型名`;
557
- return `❌ 未设置角色: ${raw}`;
556
+ if (spec) return `🎭 ${raw} → ${spec}\n💡 Modify: /model ${raw} provider/model`;
557
+ return `❌ Role not set: ${raw}`;
558
558
  }
559
559
 
560
560
  // 预设
561
561
  if (this.modelPresets && this.modelPresets[raw]) {
562
562
  const spec = this.modelPresets[raw];
563
563
  const cfg = getProviderConfig(spec);
564
- if (!cfg) return `❌ 预设目标无效: ${spec}`;
564
+ if (!cfg) return `❌ Preset target invalid: ${spec}`;
565
565
  this.activeModel = spec;
566
566
  this.modelAliases.default = spec;
567
567
  this._saveBotConfig();
568
- return `🤖 已切换: ${spec} (${raw})`;
568
+ return `🤖 Switched: ${spec} (${raw})`;
569
569
  }
570
570
 
571
571
  // 角色映射修改 (Claude/OpenCode)
@@ -577,22 +577,22 @@ class Bot {
577
577
  const validRoles = this.backend === 'opencode' ? ['opencode'] : ['default', 'sonnet', 'opus', 'haiku', 'best'];
578
578
  if (validRoles.includes(role)) {
579
579
  const cfg = getProviderConfig(spec);
580
- if (!cfg) return `❌ 未知模型: ${spec}`;
580
+ if (!cfg) return `❌ Unknown model: ${spec}`;
581
581
  (this.modelAliases as any)[role] = spec;
582
582
  if (role === 'default') this.activeModel = spec;
583
583
  this._saveBotConfig();
584
- return `🎭 ${role} → ${spec} (已更新)`;
584
+ return `🎭 ${role} → ${spec} (updated)`;
585
585
  }
586
586
  }
587
587
  }
588
588
 
589
589
  // 直接切换
590
590
  const cfg = getProviderConfig(raw);
591
- if (!cfg) return `❌ 未知模型: ${raw}\n💡 /model 查看预设`;
591
+ if (!cfg) return `❌ Unknown model: ${raw}\n💡 Use /model to see presets`;
592
592
  this.activeModel = raw;
593
593
  this.modelAliases.default = raw;
594
594
  this._saveBotConfig();
595
- return `🤖 已切换: ${raw}`;
595
+ return `🤖 Switched: ${raw}`;
596
596
  });
597
597
 
598
598
  cmd('/providers', () => {
@@ -600,50 +600,50 @@ class Bot {
600
600
  const list = Object.entries(providers).map(([name, p]: [string, any]) =>
601
601
  `• **${name}**: ${(p.models || []).join(', ')}`
602
602
  ).join('\n');
603
- return `📡 **可用供应商**\n\n${list}\n\n当前: ${this.activeModel}`;
603
+ return `📡 **Available Providers**\n\n${list}\n\nCurrent: ${this.activeModel}`;
604
604
  });
605
605
 
606
606
  cmd('/dir', ({ args, session }) => {
607
607
  const dir = args.trim();
608
608
  if (!dir) return `📁 ${session?.cwd || this.defaultCwd}`;
609
609
  if (session) session.cwd = dir;
610
- return `📁 已切换: ${dir}`;
610
+ return `📁 Switched: ${dir}`;
611
611
  });
612
612
 
613
613
  cmd('/mode', ({ args, session }) => {
614
614
  const mode = args.trim();
615
615
  if (this.backend === 'claude') {
616
- if (!mode) return `🔐 当前权限: ${session?.permissionMode || 'bypassPermissions'}\n可选: bypassPermissions | default | plan`;
616
+ if (!mode) return `🔐 Current permission: ${session?.permissionMode || 'bypassPermissions'}\nOptions: bypassPermissions | default | plan`;
617
617
  if (!['bypassPermissions', 'default', 'plan'].includes(mode))
618
- return `❌ 无效: ${mode}\n可选: bypassPermissions | default | plan`;
619
- if (session) { session.permissionMode = mode; return `🔐 已切换: ${mode}`; }
620
- return '❌ 无活跃会话';
618
+ return `❌ Invalid: ${mode}\nOptions: bypassPermissions | default | plan`;
619
+ if (session) { session.permissionMode = mode; return `🔐 Switched: ${mode}`; }
620
+ return '❌ No active session';
621
621
  }
622
622
  if (!mode) {
623
623
  const current = session?.codexMode || 'auto';
624
- return `🔧 当前模式: ${current}\n可选: auto (直接执行) | plan (先计划后执行)`;
624
+ return `🔧 Current mode: ${current}\nOptions: auto (execute directly) | plan (plan then execute)`;
625
625
  }
626
626
  if (!['auto', 'plan'].includes(mode))
627
- return `❌ 无效: ${mode}\n可选: auto | plan`;
628
- if (session) { session.codexMode = mode; return `🔧 已切换: ${mode}`; }
629
- return '❌ 无活跃会话';
627
+ return `❌ Invalid: ${mode}\nOptions: auto | plan`;
628
+ if (session) { session.codexMode = mode; return `🔧 Switched: ${mode}`; }
629
+ return '❌ No active session';
630
630
  });
631
631
 
632
632
  cmd('/memory', ({ session }) => {
633
- if (!session) return '📦 无活跃会话';
633
+ if (!session) return '📦 No active session';
634
634
  const s = session.stats;
635
- return `🧠 ${this.name} (${this.backend})\nstartFresh: ${session.startFresh || false}\nsdkSession: ${session.metadata?.sdkSessionId?.slice(-8) || session.backendSessionId?.slice(-8) || ''}\n调用: ${s.calls} | 轮数: ${s.totalTurns}\nToken: ${s.totalInputTokens.toLocaleString()} + ${s.totalOutputTokens.toLocaleString()}出\n费用: $${s.totalCostUSD.toFixed(4)}`;
635
+ return `🧠 ${this.name} (${this.backend})\nstartFresh: ${session.startFresh || false}\nsdkSession: ${session.metadata?.sdkSessionId?.slice(-8) || session.backendSessionId?.slice(-8) || 'none'}\nCalls: ${s.calls} | Turns: ${s.totalTurns}\nTokens: ${s.totalInputTokens.toLocaleString()} in + ${s.totalOutputTokens.toLocaleString()} out\nCost: $${s.totalCostUSD.toFixed(4)}`;
636
636
  });
637
637
 
638
638
  cmd('/soul', ({ args }) => {
639
639
  if (args.trim() === 'reload') {
640
640
  this._initSoul();
641
641
  this.soul = this._loadSoul();
642
- return `🧠 灵魂已重载 (${this.soul.length} 字符)`;
642
+ return `🧠 Soul reloaded (${this.soul.length} chars)`;
643
643
  }
644
644
  const files = this._soulFiles();
645
- if (files.length === 0) return `🧠 未配置灵魂\n💡 创建 ${this._soulDir()}/ 目录下的 .md 文件`;
646
- let out = `🧠 灵魂文件 (${this.soul.length} 字符):\n`;
645
+ if (files.length === 0) return `🧠 No soul configured\n💡 Create .md files in ${this._soulDir()}/`;
646
+ let out = `🧠 Soul files (${this.soul.length} chars):\n`;
647
647
  for (const f of files) {
648
648
  const fp = this._soulDir() + '/' + f;
649
649
  try {
@@ -652,14 +652,14 @@ class Bot {
652
652
  out += `\n• ${f} (${s.size}B)${tag}`;
653
653
  } catch { out += `\n• ${f}`; }
654
654
  }
655
- out += '\n\n💡 /soul reload — 重新加载';
656
- out += '\n🔒 rules=只读 | ✏️ profile=Agent可改';
655
+ out += '\n\n💡 /soul reload — Reload';
656
+ out += '\n🔒 rules=readonly | ✏️ profile=Agent-writable';
657
657
  return out;
658
658
  });
659
659
 
660
660
  cmd('/reload', async () => {
661
661
  await gracefulReload('/reload');
662
- return '🔄 热重载中...';
662
+ return '🔄 Reloading...';
663
663
  });
664
664
  }
665
665
 
@@ -672,7 +672,7 @@ class Bot {
672
672
  if (handler) return handler({ chatId, args, session });
673
673
  const similar = findSimilarCommand(cmdName, this.commands);
674
674
  if (similar.length > 0)
675
- return `❌ 未知命令: ${cmdName}\n💡 ${similar.map(s => `\`${s}\``).join('')}?\n输入 /help 查看`;
675
+ return `❌ Unknown command: ${cmdName}\n💡 ${similar.map(s => `\`${s}\``).join(', ')}?\nType /help to see all commands`;
676
676
  return null;
677
677
  }
678
678
 
@@ -683,7 +683,7 @@ class Bot {
683
683
  // 限流
684
684
  const rlResult = checkRateLimit(chatId);
685
685
  if (!rlResult.allowed) {
686
- await this.reply(chatId, `⚠️ 请求过于频繁,请等待 ${rlResult.retryAfter} 秒后再试`);
686
+ await this.reply(chatId, `⚠️ Rate limited, please wait ${rlResult.retryAfter} seconds before trying again`);
687
687
  return;
688
688
  }
689
689
 
@@ -721,12 +721,12 @@ class Bot {
721
721
  // Agent 自主重启信号检测
722
722
  if (result?.restart) {
723
723
  setTimeout(async () => {
724
- await gracefulReload(`Agent 请求重启: ${result.reason}`);
724
+ await gracefulReload(`Agent requested restart: ${result.reason}`);
725
725
  }, 200);
726
726
  }
727
727
 
728
728
  } catch (e: any) {
729
- console.error(`[${this.name}] handleMessage 异常: ${e.message}`);
729
+ console.error(`[${this.name}] handleMessage error: ${e.message}`);
730
730
  await this.reply(chatId, `❌ ${e.message}`);
731
731
  } finally {
732
732
  activeRequests--;
@@ -736,7 +736,7 @@ class Bot {
736
736
  async reply(chatId: string, text: string) {
737
737
  const maxLen = this.config.system?.maxReplyLength || 140000;
738
738
  await this.im.reply(chatId, text, maxLen);
739
- console.log(`[${this.name}] 回复 chat=${chatId.slice(-8)} len=${Math.min(text.length, maxLen)}`);
739
+ console.log(`[${this.name}] Reply chat=${chatId.slice(-8)} len=${Math.min(text.length, maxLen)}`);
740
740
  }
741
741
 
742
742
  async sendProgress(chatId: string, text: string) {
@@ -757,11 +757,11 @@ function buildSystemPromptWithSoul(soul: string, botName: string, imModule: IMMo
757
757
  const base = buildSystemPrompt({ imModule, botName });
758
758
 
759
759
  // 注入 Agent 自主重启能力说明(信号文件路径固定)
760
- const restartInstruction = `\n\n## 网关重启能力\n\n如果你判断需要重启 IMtoAgent 网关(如配置变更、检测到异常状态需重置),请执行以下命令:\n\n\`\`\`bash\necho '{"reason": "<简短原因>", "timestamp": '"$(date +%s)"'}' > ${process.env.HOME}/.imtoagent/.restart_requested\n\`\`\`\n\n规则:\n- 该信号文件会被 Runtime 自动检测并消费,用户不会看到\n- 你的回复内容会先正常发送给用户,然后网关执行重启\n- 仅在确实需要时使用,不要随意触发\n- 如果你不需要重启,忽略此指令即可`;
760
+ const restartInstruction = `\n\n## Gateway Restart Capability\n\nIf you determine that the IMtoAgent gateway needs to be restarted (e.g., config changes, abnormal state detected that requires reset), execute the following command:\n\n\`\`\`bash\necho '{"reason": "<brief reason>", "timestamp": '"$(date +%s)"'}' > ${process.env.HOME}/.imtoagent/.restart_requested\n\`\`\`\n\nRules:\n- This signal file is automatically detected and consumed by the Runtime, the user will not see it\n- Your reply will be sent to the user normally before the gateway restarts\n- Only use when truly needed, do not trigger arbitrarily\n- If you don't need a restart, ignore this instruction`;
761
761
 
762
762
  let combined = `${base}${restartInstruction}`;
763
763
  if (soul) {
764
- combined += `\n\n---\n\n# 用户自定义指令 (IMtoAgent Soul)\n\n${soul}`;
764
+ combined += `\n\n---\n\n# User Custom Instructions (IMtoAgent Soul)\n\n${soul}`;
765
765
  }
766
766
  return combined;
767
767
  }
@@ -820,7 +820,7 @@ async function gracefulReload(reason: string) {
820
820
  await new Promise(r => setTimeout(r, 500));
821
821
 
822
822
  // 4. 退出,daemon.sh 会自动拉起新进程
823
- console.log('[Reload] 清理完成,退出中...');
823
+ console.log('[Reload] Cleanup complete, exiting...');
824
824
  process.exit(0);
825
825
  }
826
826
 
@@ -835,14 +835,14 @@ async function main() {
835
835
  // 首次部署:配置文件不存在或未初始化
836
836
  if (!fs.existsSync(CONFIG_PATH)) {
837
837
  console.log('');
838
- console.log('⚠️ 首次部署:请先配置 imtoagent');
838
+ console.log('⚠️ First-time deployment: please configure imtoagent first');
839
839
  console.log('');
840
- console.log(` 配置文件: ${CONFIG_PATH}`);
840
+ console.log(` Config file: ${CONFIG_PATH}`);
841
841
  console.log('');
842
- console.log(' 1. 编辑 config.json,填入你的 API 凭证');
843
- console.log(' 2. 重新运行 imtoagent');
842
+ console.log(' 1. Edit config.json and fill in your API credentials');
843
+ console.log(' 2. Re-run imtoagent');
844
844
  console.log('');
845
- console.log(' 参考模板: templates/config.template.json');
845
+ console.log(' Reference template: templates/config.template.json');
846
846
  console.log('');
847
847
  process.exit(0);
848
848
  }
@@ -856,8 +856,8 @@ async function main() {
856
856
  );
857
857
  if (hasPlaceholder) {
858
858
  console.log('');
859
- console.log('⚠️ 配置未完成:请将 config.json 中的 YOUR_* 替换为真实的 API 凭证');
860
- console.log(` 配置文件: ${CONFIG_PATH}`);
859
+ console.log('⚠️ Incomplete config: please replace YOUR_* in config.json with real API credentials');
860
+ console.log(` Config file: ${CONFIG_PATH}`);
861
861
  console.log('');
862
862
  process.exit(0);
863
863
  }
@@ -880,7 +880,7 @@ async function main() {
880
880
  const old = JSON.parse(fs.readFileSync(RESTART_SIGNAL_PATH, 'utf-8'));
881
881
  const age = Date.now() - (old.timestamp || 0);
882
882
  if (age > 60000) {
883
- console.log(`[Startup] 清理残留重启信号: ${old.reason}(${Math.floor(age / 1000)}s 前)`);
883
+ console.log(`[Startup] Cleaned up stale restart signal: ${old.reason} (${Math.floor(age / 1000)}s ago)`);
884
884
  fs.unlinkSync(RESTART_SIGNAL_PATH);
885
885
  }
886
886
  }
@@ -888,7 +888,7 @@ async function main() {
888
888
 
889
889
  let proxyPort = 0;
890
890
  try { proxyPort = await startAnthropicProxy(18899); } catch (e: any) {
891
- console.error(`❌ Anthropic Proxy :18899 启动失败: ${e.message}`);
891
+ console.error(`❌ Anthropic Proxy :18899 failed to start: ${e.message}`);
892
892
  }
893
893
 
894
894
  try {
@@ -917,7 +917,7 @@ async function main() {
917
917
  });
918
918
  }
919
919
  } catch (e: any) {
920
- console.error(`[Config] 初始化子模块配置失败: ${e.message}`);
920
+ console.error(`[Config] Failed to initialize sub-module config: ${e.message}`);
921
921
  }
922
922
 
923
923
 
@@ -927,18 +927,18 @@ async function main() {
927
927
  try {
928
928
  await startOpenCodeServer();
929
929
  } catch (e: any) {
930
- console.error(`[OpenCode] 启动失败: ${e.message}`);
930
+ console.error(`[OpenCode] Failed to start: ${e.message}`);
931
931
  }
932
932
  }
933
933
 
934
934
  if (!proxyPort) {
935
- console.error('❌ 所有 Proxy 启动失败,无法继续');
935
+ console.error('❌ All proxies failed to start, cannot continue');
936
936
  process.exit(1);
937
937
  }
938
938
 
939
939
  const botCfgs: any[] = config.bots || [];
940
940
  if (botCfgs.length === 0) {
941
- console.log('💡 config.json 中未配置 bots,仅启动代理');
941
+ console.log('💡 No bots configured in config.json, starting proxy only');
942
942
  return;
943
943
  }
944
944
 
@@ -957,19 +957,19 @@ async function main() {
957
957
  // Telegram/其他非飞书 IM 只需要 appId,不需要 appSecret
958
958
  const needsSecret = imType === 'feishu';
959
959
  if (!appId || (needsSecret && !appSecret) || appId.startsWith('YOUR_') || appSecret.startsWith('YOUR_')) {
960
- console.log(`[Config] ⚠️ Bot "${c.name}" 凭证为占位符,跳过`);
960
+ console.log(`[Config] ⚠️ Bot "${c.name}" has placeholder credentials, skipping`);
961
961
  continue;
962
962
  }
963
963
  bots.push(new Bot({ ...c, appId, appSecret }, config));
964
964
  }
965
965
 
966
966
  if (bots.length === 0) {
967
- console.log('⚠️ 没有配置有效凭证的 Bot,仅启动代理');
967
+ console.log('⚠️ No bots with valid credentials, starting proxy only');
968
968
  return;
969
969
  }
970
970
 
971
971
  _allBots = bots;
972
- console.log(`\n🚀 CC 路由 v4 — Bot 架构 (SDK 完整接入)`);
972
+ console.log(`\n🚀 CC Routing v4 — Multi-Bot Architecture (Full SDK Integration)`);
973
973
  console.log(` Anthropic: http://localhost:${proxyPort}`);
974
974
  console.log(` Bots:`);
975
975
 
@@ -978,7 +978,7 @@ async function main() {
978
978
  const attDesc = attachments?.length
979
979
  ? ` +${attachments.length} attachments(${attachments.map(a => a.type).join(',')})`
980
980
  : '';
981
- console.log(`[${bot.name}] 收到 chat=${chatId.slice(-8)} "${text.slice(0, 80)}"${attDesc}`);
981
+ console.log(`[${bot.name}] Received chat=${chatId.slice(-8)} "${text.slice(0, 80)}"${attDesc}`);
982
982
  setCurrentBot({ botName: bot.name, caps: bot.im.getCapabilities(), modelAliases: bot.modelAliases });
983
983
  bot.handleMessage(chatId, text, userId, attachments).catch((e: Error) =>
984
984
  console.error(`[${bot.name}] handleMessage unhandled:`, e.message)
@@ -1000,10 +1000,10 @@ async function main() {
1000
1000
  gitStatus = require('child_process').execSync('git status --short', { cwd, timeout: 3000 }).toString().trim();
1001
1001
  } catch {}
1002
1002
  const content = [
1003
- '# 项目环境', '', `- 工作目录: ${cwd}`,
1004
- gitBranch ? `- Git 分支: ${gitBranch}` : '',
1005
- gitStatus ? `- 未提交变更:\n\`\`\`\n${gitStatus.slice(0, 500)}\n\`\`\`` : '',
1006
- '', '> 此文件由 IMtoAgent 自动生成,启动时和切换目录时更新。',
1003
+ '# Project Environment', '', `- Working Directory: ${cwd}`,
1004
+ gitBranch ? `- Git Branch: ${gitBranch}` : '',
1005
+ gitStatus ? `- Uncommitted Changes:\n\`\`\`\n${gitStatus.slice(0, 500)}\n\`\`\`` : '',
1006
+ '', '> This file is auto-generated by IMtoAgent on startup and directory changes.',
1007
1007
  ].filter(Boolean).join('\n');
1008
1008
  fs.writeFileSync(dir + '/workspace.md', content);
1009
1009
  } catch {}
@@ -1028,12 +1028,12 @@ async function main() {
1028
1028
  if (data.metadata?.sdkSessionId) { delete data.metadata.sdkSessionId; changed = true; }
1029
1029
  if (changed) {
1030
1030
  fs.writeFileSync(fp, JSON.stringify(data, null, 2));
1031
- console.log(`[Startup] 已清除 ${bot.name}/${file} 的旧 SDK session ID`);
1031
+ console.log(`[Startup] Cleared old SDK session ID for ${bot.name}/${file}`);
1032
1032
  }
1033
1033
  } catch {}
1034
1034
  }
1035
1035
  }
1036
- } catch (e: any) { console.error(`[Startup] 清除 ${bot.name} session: ${e.message}`); }
1036
+ } catch (e: any) { console.error(`[Startup] Clear ${bot.name} session: ${e.message}`); }
1037
1037
  }
1038
1038
 
1039
1039
  // 重启后汇报
@@ -1044,16 +1044,16 @@ async function main() {
1044
1044
  try {
1045
1045
  if (!fs.existsSync(marker)) return;
1046
1046
  const data = JSON.parse(fs.readFileSync(marker, 'utf-8'));
1047
- const reason = data.reason || '未知';
1047
+ const reason = data.reason || 'Unknown';
1048
1048
  const uptime = Date.now() - (data.timestamp || Date.now());
1049
- const summary = `🔄 IMtoAgent 已重启\n原因: ${reason}\n耗时: ${(uptime / 1000).toFixed(1)}s`;
1049
+ const summary = `🔄 IMtoAgent restarted\nReason: ${reason}\nDowntime: ${(uptime / 1000).toFixed(1)}s`;
1050
1050
  let sent = 0;
1051
1051
  for (const bot of bots) {
1052
1052
  const snap = data.bots?.[bot.id];
1053
1053
  if (!snap?.chats?.length) continue;
1054
1054
  for (const { chatId } of snap.chats) {
1055
1055
  try { await bot.reply(chatId, summary); sent++; break; }
1056
- catch (e: any) { console.error(`[Restore] ${bot.name} 发送失败(attempt ${attempt}): ${e.message}`); }
1056
+ catch (e: any) { console.error(`[Restore] ${bot.name} send failed (attempt ${attempt}): ${e.message}`); }
1057
1057
  }
1058
1058
  }
1059
1059
  if (sent > 0 || attempt >= 4) { try { fs.unlinkSync(marker); } catch {} return; }
@@ -1069,7 +1069,7 @@ async function main() {
1069
1069
 
1070
1070
  // 优雅关闭
1071
1071
  async function gracefulShutdown(signal: string) {
1072
- console.log(`[Shutdown] 收到 ${signal},优雅关闭中...`);
1072
+ console.log(`[Shutdown] Received ${signal}, shutting down gracefully...`);
1073
1073
  // 先 abort 所有适配器的活跃子进程(如 Claude CLI)
1074
1074
  for (const bot of bots) {
1075
1075
  try { if (bot.adapter && typeof (bot.adapter as any).cleanup === 'function') (bot.adapter as any).cleanup(); } catch {}
@@ -1081,7 +1081,7 @@ async function main() {
1081
1081
  await stopAnthropicProxy();
1082
1082
  await stopOpenCodeServer();
1083
1083
 
1084
- console.log('[Shutdown] 持久化所有 session...');
1084
+ console.log('[Shutdown] Persisting all sessions...');
1085
1085
  for (const bot of bots) {
1086
1086
  for (const [chatId, session] of bot.sessions.entries()) {
1087
1087
  try { bot.sessionManager.persist(bot.id, session); } catch {}
@@ -1090,15 +1090,15 @@ async function main() {
1090
1090
  const DRAIN_TIMEOUT = 10_000;
1091
1091
  const start = Date.now();
1092
1092
  while (activeRequests > 0 && Date.now() - start < DRAIN_TIMEOUT) {
1093
- console.log(`[Shutdown] 等待 ${activeRequests} 个请求完成...`);
1093
+ console.log(`[Shutdown] Waiting for ${activeRequests} active request(s)...`);
1094
1094
  await new Promise(r => setTimeout(r, 500));
1095
1095
  }
1096
1096
  if (activeRequests > 0) {
1097
- console.warn(`[Shutdown] ⚠️ 超时,仍有 ${activeRequests} 个请求未完成,强制退出`);
1097
+ console.warn(`[Shutdown] ⚠️ Timeout, ${activeRequests} request(s) still pending, force exit`);
1098
1098
  } else {
1099
- console.log('[Shutdown] 所有请求已完成');
1099
+ console.log('[Shutdown] All requests completed');
1100
1100
  }
1101
- console.log('[Shutdown] 所有服务已关闭');
1101
+ console.log('[Shutdown] All services closed');
1102
1102
  process.exit(0);
1103
1103
  }
1104
1104
 
@@ -1123,4 +1123,4 @@ process.on('unhandledRejection', (reason, promise) => {
1123
1123
  }
1124
1124
  });
1125
1125
 
1126
- main().catch((err) => { console.error(`[启动失败] ${err.message}`); process.exit(1); });
1126
+ main().catch((err) => { console.error(`[Startup failed] ${err.message}`); process.exit(1); });
@@ -140,7 +140,7 @@ export class ClaudeAdapter implements AgentAdapter {
140
140
  let timeoutId: ReturnType<typeof setTimeout> | null = null;
141
141
  if (ClaudeAdapter.MAX_CALL_TIMEOUT_MS > 0) {
142
142
  timeoutId = setTimeout(() => {
143
- console.log(`[ClaudeAdapter] ⏰ 超时 (${ClaudeAdapter.MAX_CALL_TIMEOUT_MS / 1000}s),中止请求`);
143
+ console.log(`[ClaudeAdapter] ⏰ Timeout (${ClaudeAdapter.MAX_CALL_TIMEOUT_MS / 1000}s), aborting request`);
144
144
  abortCtrl.abort();
145
145
  }, ClaudeAdapter.MAX_CALL_TIMEOUT_MS);
146
146
  }
@@ -210,7 +210,7 @@ export class ClaudeAdapter implements AgentAdapter {
210
210
  const result = msgAny;
211
211
 
212
212
  if (result.subtype === 'error' || result.subtype === 'cancelled') {
213
- throw new Error(result.error || result.result || '未知错误');
213
+ throw new Error(result.error || result.result || 'Unknown error');
214
214
  }
215
215
 
216
216
  const usage = {
@@ -222,7 +222,7 @@ export class ClaudeAdapter implements AgentAdapter {
222
222
  };
223
223
 
224
224
  if (timeoutId) clearTimeout(timeoutId);
225
- const responseText = fullResponse || `✅ 已完成 (${toolCalls.length} 步操作)`;
225
+ const responseText = fullResponse || `✅ Completed (${toolCalls.length} steps)`;
226
226
 
227
227
  return {
228
228
  text: responseText,
@@ -234,7 +234,7 @@ export class ClaudeAdapter implements AgentAdapter {
234
234
 
235
235
  // 流结束但没有 result 消息
236
236
  return {
237
- text: fullResponse || '✅ 完成',
237
+ text: fullResponse || '✅ Done',
238
238
  toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
239
239
  };
240
240
 
@@ -242,9 +242,9 @@ export class ClaudeAdapter implements AgentAdapter {
242
242
  if (timeoutId) clearTimeout(timeoutId);
243
243
  // 如果是被 abort(超时或手动清理),提供有意义的消息
244
244
  if (abortCtrl.signal.aborted) {
245
- console.log(`[ClaudeAdapter] 请求已被中止 (${err.message || 'aborted'})`);
245
+ console.log(`[ClaudeAdapter] Request aborted (${err.message || 'aborted'})`);
246
246
  return {
247
- text: '⚠️ 请求超时或已被取消,请稍后重试。',
247
+ text: '⚠️ Request timed out or cancelled, please try again.',
248
248
  };
249
249
  }
250
250
  throw err;