imtoagent 0.3.4 → 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 +164 -164
  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 +31 -31
  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
@@ -86,7 +86,7 @@ async function fetchQRCode(): Promise<{ scode: string; authUrl: string }> {
86
86
  const raw = await httpsGet(url);
87
87
  const resp = JSON.parse(raw);
88
88
  if (!resp?.data?.scode || !resp?.data?.auth_url) {
89
- throw new Error(`获取二维码失败: ${raw.slice(0, 200)}`);
89
+ throw new Error(`Failed to get QR code: ${raw.slice(0, 200)}`);
90
90
  }
91
91
  return { scode: resp.data.scode, authUrl: resp.data.auth_url };
92
92
  }
@@ -110,7 +110,7 @@ async function pollResult(scode: string): Promise<{ botId: string; secret: strin
110
110
  if (status === 'success') {
111
111
  const bi = resp.data.bot_info;
112
112
  if (!bi?.botid || !bi?.secret) {
113
- throw new Error('扫码成功但未获取到 Bot 信息');
113
+ throw new Error('QR scan successful but Bot info not received');
114
114
  }
115
115
  return { botId: bi.botid, secret: bi.secret };
116
116
  }
@@ -118,7 +118,7 @@ async function pollResult(scode: string): Promise<{ botId: string; secret: strin
118
118
  await new Promise(r => setTimeout(r, POLL_INTERVAL_MS));
119
119
  }
120
120
 
121
- console.log('\n⏱ 扫码超时(5 分钟),请重试');
121
+ console.log('\n⏱ QR scan timed out (5 min), please retry');
122
122
  process.exit(1);
123
123
  }
124
124
 
@@ -127,17 +127,17 @@ async function pollResult(scode: string): Promise<{ botId: string; secret: strin
127
127
  * 调用后会在终端显示二维码,用户扫码后自动获取 botId 和 secret 并保存到本地
128
128
  */
129
129
  export async function bindWeComQR(): Promise<{ botId: string; secret: string }> {
130
- console.log('\n📱 企业微信扫码绑定');
131
- console.log('正在获取二维码...');
130
+ console.log('\n📱 WeCom QR Code Binding');
131
+ console.log('Fetching QR code...');
132
132
 
133
133
  const { scode, authUrl } = await fetchQRCode();
134
134
 
135
- console.log('请使用企业微信扫描以下二维码:');
135
+ console.log('Please scan the following QR code with WeCom:');
136
136
  await renderQR(authUrl);
137
- console.log('等待扫码中...');
137
+ console.log('Waiting for scan...');
138
138
 
139
139
  const result = await pollResult(scode);
140
- console.log('\n✅ 扫码成功!Bot ID Secret 已保存');
140
+ console.log('\n✅ QR scan successful! Bot ID and Secret saved');
141
141
 
142
142
  const creds: StoredCreds = {
143
143
  botId: result.botId,
@@ -192,12 +192,12 @@ export class WeComIMModule implements IMModule {
192
192
  getCapabilities(): IMCapabilities {
193
193
  return {
194
194
  text: true,
195
- codeBlock: false, // 企微不支持代码块
196
- cardMessage: true, // 模板卡片消息
195
+ codeBlock: false, // WeCom doesn't support code blocks
196
+ cardMessage: true, // Template card messages
197
197
  fileSend: true,
198
198
  imageSend: true,
199
199
  audioSend: false,
200
- buttonAction: true, // 模板卡片按钮回调
200
+ buttonAction: true, // Template card button callbacks
201
201
  maxTextLength: TEXT_MAX,
202
202
  };
203
203
  }
@@ -206,7 +206,7 @@ export class WeComIMModule implements IMModule {
206
206
 
207
207
  start(handler: MessageHandler): void {
208
208
  if (this.running) {
209
- console.warn('[WeCom] 已在运行中');
209
+ console.warn('[WeCom] Already running');
210
210
  return;
211
211
  }
212
212
  this.handler = handler;
@@ -221,7 +221,7 @@ export class WeComIMModule implements IMModule {
221
221
  this.ws = null;
222
222
  }
223
223
  this.handler = null;
224
- console.log('[WeCom] 已断开');
224
+ console.log('[WeCom] Disconnected');
225
225
  }
226
226
 
227
227
  // ── WebSocket 连接 ──
@@ -236,18 +236,18 @@ export class WeComIMModule implements IMModule {
236
236
  if (stored) {
237
237
  botId = stored.botId;
238
238
  secret = stored.secret;
239
- console.log('[WeCom] 已加载本地凭证');
239
+ console.log('[WeCom] Loaded local credentials');
240
240
  }
241
241
  }
242
242
 
243
243
  if (!botId || !secret) {
244
- console.log('[WeCom] 未找到凭证,启动扫码绑定...');
244
+ console.log('[WeCom] No credentials found, starting QR binding...');
245
245
  const bound = await bindWeComQR();
246
246
  botId = bound.botId;
247
247
  secret = bound.secret;
248
248
  }
249
249
 
250
- console.log(`[WeCom] 正在连接 WebSocket (bot: ${botId.slice(0, 6)}...)`);
250
+ console.log(`[WeCom] Connecting WebSocket (bot: ${botId.slice(0, 6)}...)`);
251
251
 
252
252
  // 2. 创建 WSClient
253
253
  this.ws = new WSClient({
@@ -266,23 +266,23 @@ export class WeComIMModule implements IMModule {
266
266
 
267
267
  // 3. 事件监听
268
268
  this.ws.on('connected', () => {
269
- console.log('[WeCom] WebSocket 已连接');
269
+ console.log('[WeCom] WebSocket connected');
270
270
  });
271
271
 
272
272
  this.ws.on('authenticated', () => {
273
- console.log('[WeCom] 认证成功');
273
+ console.log('[WeCom] Authenticated');
274
274
  });
275
275
 
276
276
  this.ws.on('disconnected', (reason: string) => {
277
- console.log(`[WeCom] 断开连接: ${reason}`);
277
+ console.log(`[WeCom] Disconnected: ${reason}`);
278
278
  if (this.running) {
279
- console.log('[WeCom] 5 秒后重连...');
279
+ console.log('[WeCom] Reconnecting in 5s...');
280
280
  setTimeout(() => { if (this.running) this._connect(); }, 5000);
281
281
  }
282
282
  });
283
283
 
284
284
  this.ws.on('error', (err: any) => {
285
- console.error(`[WeCom] 错误: ${err?.message || err}`);
285
+ console.error(`[WeCom] Error: ${err?.message || err}`);
286
286
  });
287
287
 
288
288
  // 4. 接收消息
@@ -290,12 +290,12 @@ export class WeComIMModule implements IMModule {
290
290
  try {
291
291
  await this._handleMessage(frame);
292
292
  } catch (e: any) {
293
- console.error(`[WeCom] 消息处理异常: ${e.message}`);
293
+ console.error(`[WeCom] Message processing error: ${e.message}`);
294
294
  }
295
295
  });
296
296
  }
297
297
 
298
- // ── 消息解析 ──
298
+ // ── Message Parsing ──
299
299
 
300
300
  private async _handleMessage(frame: any): Promise<void> {
301
301
  const body = frame.body || {};
@@ -309,10 +309,10 @@ export class WeComIMModule implements IMModule {
309
309
  const items = evt.selected_items?.selected_item ?? [];
310
310
  const lines = items.map((it: any) => {
311
311
  const ids = it.option_ids?.option_id?.filter(Boolean) ?? [];
312
- return `- ${it.question_key || '?'}: ${ids.join(', ') || '(未选择)'}`;
312
+ return `- ${it.question_key || '?'}: ${ids.join(', ') || '(not selected)'}`;
313
313
  });
314
314
  const text = [
315
- '[模板卡片回调]',
315
+ '[Template card callback]',
316
316
  `card_type: ${evt.card_type || '?'}`,
317
317
  `event_key: ${evt.event_key || '?'}`,
318
318
  ...lines,
@@ -339,28 +339,28 @@ export class WeComIMModule implements IMModule {
339
339
  text = body.content?.text || body.text?.content || body.content || '';
340
340
  break;
341
341
  case 'image':
342
- text = '[图片]';
342
+ text = '[Image]';
343
343
  if (body.image?.mediaid) {
344
344
  const localPath = await this._downloadMedia(body.image.mediaid, body.image.aeskey, 'image.png');
345
345
  attachments.push({ type: 'image', localPath: localPath || '', sourceKey: body.image.mediaid, mimeType: 'image/png' });
346
346
  }
347
347
  break;
348
348
  case 'voice':
349
- text = body.voice?.recognition || body.recognition || '[语音]';
349
+ text = body.voice?.recognition || body.recognition || '[Voice]';
350
350
  if (body.voice?.mediaid) {
351
351
  const localPath = await this._downloadMedia(body.voice.mediaid, body.voice.aeskey, 'voice.amr');
352
352
  attachments.push({ type: 'file', localPath: localPath || '', filename: 'voice.amr', sourceKey: body.voice.mediaid });
353
353
  }
354
354
  break;
355
355
  case 'video':
356
- text = '[视频]';
356
+ text = '[Video]';
357
357
  if (body.video?.mediaid) {
358
358
  const localPath = await this._downloadMedia(body.video.mediaid, body.video.aeskey, 'video.mp4');
359
359
  attachments.push({ type: 'file', localPath: localPath || '', filename: 'video.mp4', sourceKey: body.video.mediaid });
360
360
  }
361
361
  break;
362
362
  case 'file':
363
- text = `[文件: ${body.file?.title || body.title || '未知'}]`;
363
+ text = `[File: ${body.file?.title || body.title || 'unknown'}]`;
364
364
  if (body.file?.mediaid) {
365
365
  const localPath = await this._downloadMedia(body.file.mediaid, body.file.aeskey, body.file.title || 'file');
366
366
  attachments.push({ type: 'file', localPath: localPath || '', filename: body.file.title || 'file', sourceKey: body.file.mediaid });
@@ -370,11 +370,11 @@ export class WeComIMModule implements IMModule {
370
370
  text = body.markdown?.content || '';
371
371
  break;
372
372
  default:
373
- text = `[${msgType}消息]`;
373
+ text = `[${msgType} message]`;
374
374
  }
375
375
 
376
376
  const preview = text.length > 80 ? text.slice(0, 80) + '...' : text;
377
- console.log(`[WeCom] ${chatType === 'group' ? '' : '私聊'} ${fromUser}@${chatId}: ${preview}`);
377
+ console.log(`[WeCom] ${chatType === 'group' ? 'Group' : 'DM'} ${fromUser}@${chatId}: ${preview}`);
378
378
 
379
379
  // 保存 frame 用于被动回复
380
380
  this.pendingFrames.set(chatId, frame);
@@ -388,10 +388,10 @@ export class WeComIMModule implements IMModule {
388
388
 
389
389
  async reply(chatId: string, text: string): Promise<void> {
390
390
  if (!this.ws?.isConnected) {
391
- console.error('[WeCom] WS 未连接');
391
+ console.error('[WeCom] WS not connected');
392
392
  return;
393
393
  }
394
- const safe = text.length > TEXT_MAX ? text.slice(0, TEXT_MAX) + '\n…截断' : text;
394
+ const safe = text.length > TEXT_MAX ? text.slice(0, TEXT_MAX) + '\n…truncated' : text;
395
395
  const body = {
396
396
  msgtype: 'markdown',
397
397
  markdown: { content: safe },
@@ -404,7 +404,7 @@ export class WeComIMModule implements IMModule {
404
404
  await this.ws.reply(frame, body);
405
405
  return;
406
406
  } catch (e: any) {
407
- console.warn(`[WeCom] 被动回复失败,fallback 到主动推送: ${e.message}`);
407
+ console.warn(`[WeCom] Passive reply failed, falling back to push: ${e.message}`);
408
408
  }
409
409
  }
410
410
 
@@ -412,7 +412,7 @@ export class WeComIMModule implements IMModule {
412
412
  try {
413
413
  await this.ws.sendMessage(chatId, body);
414
414
  } catch (e: any) {
415
- console.error(`[WeCom] 发送失败: ${e.message}`);
415
+ console.error(`[WeCom] Send failed: ${e.message}`);
416
416
  }
417
417
  }
418
418
 
@@ -436,12 +436,12 @@ export class WeComIMModule implements IMModule {
436
436
  try {
437
437
  await this.ws.replyStream(frame, streamId, content, finish);
438
438
  } catch (e: any) {
439
- console.error(`[WeCom] 流式发送失败: ${e.message}`);
439
+ console.error(`[WeCom] Stream send failed: ${e.message}`);
440
440
  }
441
441
  }
442
442
 
443
443
  /**
444
- * 非阻塞流式回复
444
+ * Non-blocking streaming reply
445
445
  * 当上一条消息还未收到 ACK 时跳过中间帧,避免慢连接下排队积压
446
446
  * finish=true 的最终帧不受限制,始终发送
447
447
  */
@@ -458,7 +458,7 @@ export class WeComIMModule implements IMModule {
458
458
  // 静默跳过中间帧(非阻塞保护生效)
459
459
  }
460
460
  } catch (e: any) {
461
- console.error(`[WeCom] 非阻塞流式发送失败: ${e.message}`);
461
+ console.error(`[WeCom] Non-blocking stream send failed: ${e.message}`);
462
462
  }
463
463
  }
464
464
 
@@ -482,7 +482,7 @@ export class WeComIMModule implements IMModule {
482
482
  try {
483
483
  const mediaId = await this._uploadMediaFromSource(b.url, 'image', b.title || 'image.png');
484
484
  if (mediaId) await this.ws!.sendMediaMessage(chatId, 'image', mediaId);
485
- } catch (e: any) { console.error(`[WeCom] 图片上传失败: ${e.message}`); }
485
+ } catch (e: any) { console.error(`[WeCom] Image upload failed: ${e.message}`); }
486
486
  }
487
487
  break;
488
488
  case 'file':
@@ -490,7 +490,7 @@ export class WeComIMModule implements IMModule {
490
490
  try {
491
491
  const mediaId = await this._uploadMediaFromSource(b.url, 'file', b.title || 'file');
492
492
  if (mediaId) await this.ws!.sendMediaMessage(chatId, 'file', mediaId);
493
- } catch (e: any) { console.error(`[WeCom] 文件上传失败: ${e.message}`); }
493
+ } catch (e: any) { console.error(`[WeCom] File upload failed: ${e.message}`); }
494
494
  }
495
495
  break;
496
496
  }
@@ -499,19 +499,19 @@ export class WeComIMModule implements IMModule {
499
499
  }
500
500
 
501
501
  async sendImage(chatId: string, imageKey: string, _alt?: string): Promise<void> {
502
- if (!this.ws?.isConnected) { console.error('[WeCom] WS 未连接'); return; }
502
+ if (!this.ws?.isConnected) { console.error('[WeCom] WS not connected'); return; }
503
503
  try {
504
504
  const mediaId = await this._uploadMediaFromSource(imageKey, 'image', this._basename(imageKey));
505
505
  if (mediaId) await this.ws.sendMediaMessage(chatId, 'image', mediaId);
506
- } catch (e: any) { console.error(`[WeCom] 图片发送失败: ${e.message}`); }
506
+ } catch (e: any) { console.error(`[WeCom] Image send failed: ${e.message}`); }
507
507
  }
508
508
 
509
509
  async sendFile(chatId: string, fileKey: string, fileName: string): Promise<void> {
510
- if (!this.ws?.isConnected) { console.error('[WeCom] WS 未连接'); return; }
510
+ if (!this.ws?.isConnected) { console.error('[WeCom] WS not connected'); return; }
511
511
  try {
512
512
  const mediaId = await this._uploadMediaFromSource(fileKey, 'file', fileName);
513
513
  if (mediaId) await this.ws.sendMediaMessage(chatId, 'file', mediaId);
514
- } catch (e: any) { console.error(`[WeCom] 文件发送失败: ${e.message}`); }
514
+ } catch (e: any) { console.error(`[WeCom] File send failed: ${e.message}`); }
515
515
  }
516
516
 
517
517
  // ── 媒体上传 ──
@@ -529,17 +529,17 @@ export class WeComIMModule implements IMModule {
529
529
  const b64 = source.substring(commaIdx + 1);
530
530
  buffer = Buffer.from(b64, 'base64');
531
531
  } else {
532
- // 本地文件路径
532
+ // local file path
533
533
  if (!fs.existsSync(source)) {
534
- throw new Error(`文件不存在: ${source}`);
534
+ throw new Error(`File not found: ${source}`);
535
535
  }
536
536
  buffer = fs.readFileSync(source);
537
537
  }
538
538
 
539
- if (buffer.length === 0) throw new Error('文件为空');
539
+ if (buffer.length === 0) throw new Error('File is empty');
540
540
 
541
541
  const result = await this.ws!.uploadMedia(buffer, { type: mediaType, filename: fileName });
542
- console.log(`[WeCom] 媒体上传成功: ${fileName} → ${result.media_id}`);
542
+ console.log(`[WeCom] Media uploaded: ${fileName} → ${result.media_id}`);
543
543
  return result.media_id;
544
544
  }
545
545
 
@@ -593,10 +593,10 @@ export class WeComIMModule implements IMModule {
593
593
  fs.mkdirSync(tempDir, { recursive: true });
594
594
  const filePath = path.join(tempDir, `${Date.now()}_${finalName}`);
595
595
  fs.writeFileSync(filePath, buffer);
596
- console.log(`[WeCom] 媒体下载成功: ${mediaId} → ${filePath}`);
596
+ console.log(`[WeCom] Media downloaded: ${mediaId} → ${filePath}`);
597
597
  return filePath;
598
598
  } catch (e: any) {
599
- console.error(`[WeCom] 媒体下载失败 (${mediaId}): ${e.message}`);
599
+ console.error(`[WeCom] Media download failed (${mediaId}): ${e.message}`);
600
600
  return null;
601
601
  }
602
602
  }
@@ -44,7 +44,7 @@ export class FeishuInboundAdapter implements InboundMediaAdapter {
44
44
  if (!resp.ok) {
45
45
  // 容错:文件类型 502 时尝试用 media 类型重试
46
46
  if (resp.status === 502 && type === 'file') {
47
- console.log(`[FeishuInbound] file 类型返回 502,尝试 media 类型重试`);
47
+ console.log(`[FeishuInbound] file type returned 502, retrying with media type`);
48
48
  const retryUrl = `https://open.feishu.cn/open-apis/im/v1/messages/${messageId}/resources/${resourceKey}?type=media`;
49
49
  const retryResp = await fetch(retryUrl, {
50
50
  headers: { Authorization: `Bearer ${token}` },
@@ -60,7 +60,7 @@ export class FeishuInboundAdapter implements InboundMediaAdapter {
60
60
  };
61
61
  }
62
62
  }
63
- console.error(`[FeishuInbound] 下载消息资源失败: HTTP ${resp.status} (key=${resourceKey})`);
63
+ console.error(`[FeishuInbound] download message resource failed: HTTP ${resp.status} (key=${resourceKey})`);
64
64
  return null;
65
65
  }
66
66
 
@@ -74,7 +74,7 @@ export class FeishuInboundAdapter implements InboundMediaAdapter {
74
74
  sourceKey: resourceKey,
75
75
  };
76
76
  } catch (e: any) {
77
- console.error(`[FeishuInbound] 下载消息资源异常: ${e.message}`);
77
+ console.error(`[FeishuInbound] download message resource exception: ${e.message}`);
78
78
  return null;
79
79
  }
80
80
  }
@@ -97,7 +97,7 @@ export class FeishuInboundAdapter implements InboundMediaAdapter {
97
97
 
98
98
  const data = await resp.json();
99
99
  if (data.code !== 0 || !data.tenant_access_token) {
100
- throw new Error(`获取飞书 tenant_access_token 失败: ${JSON.stringify(data)}`);
100
+ throw new Error(`Failed to get Feishu tenant_access_token: ${JSON.stringify(data)}`);
101
101
  }
102
102
 
103
103
  this._appToken = data.tenant_access_token;
@@ -50,31 +50,31 @@ function buildHintForCategory(entry: MediaEntry): string {
50
50
 
51
51
  switch (category) {
52
52
  case 'image':
53
- return `图片已保存到本地,路径: \`${localPath}\`,格式: ${mimeType},可使用图片查看工具打开`;
53
+ return `Image saved locally, path: \`${localPath}\`, format: ${mimeType}, can be opened with an image viewer`;
54
54
 
55
55
  case 'audio':
56
- return `音频文件路径: \`${localPath}\`,格式: ${mimeType},可用语音识别工具处理`;
56
+ return `Audio file path: \`${localPath}\`, format: ${mimeType}, can be processed with speech recognition tools`;
57
57
 
58
58
  case 'video':
59
- return `视频文件路径: \`${localPath}\`,格式: ${mimeType}`;
59
+ return `Video file path: \`${localPath}\`, format: ${mimeType}`;
60
60
 
61
61
  case 'document':
62
- return `文档文件路径: \`${localPath}\`,类型: ${mimeType},可直接读取(如果是文本/PDF)或用相应工具处理`;
62
+ return `Document file path: \`${localPath}\`, type: ${mimeType}, can be read directly (if text/PDF) or processed with appropriate tools`;
63
63
 
64
64
  case 'text':
65
- return `文本文件路径: \`${localPath}\`,可直接用文件读取工具读取内容`;
65
+ return `Text file path: \`${localPath}\`, can be read directly with a file reading tool`;
66
66
 
67
67
  case 'spreadsheet':
68
- return `表格文件路径: \`${localPath}\`,类型: ${mimeType},可用表格解析工具(如 Python pandas/openpyxl)处理`;
68
+ return `Spreadsheet file path: \`${localPath}\`, type: ${mimeType}, can be processed with spreadsheet tools (e.g. Python pandas/openpyxl)`;
69
69
 
70
70
  case 'presentation':
71
- return `演示文稿路径: \`${localPath}\`,类型: ${mimeType}`;
71
+ return `Presentation file path: \`${localPath}\`, type: ${mimeType}`;
72
72
 
73
73
  case 'archive':
74
- return `压缩文件路径: \`${localPath}\`,类型: ${mimeType},需要先解压再处理`;
74
+ return `Archive file path: \`${localPath}\`, type: ${mimeType}, needs to be extracted before processing`;
75
75
 
76
76
  default:
77
- return `文件路径: \`${localPath}\`,格式: ${mimeType},可用文件工具分析或直接读取`;
77
+ return `File path: \`${localPath}\`, format: ${mimeType}, can be analyzed with file tools or read directly`;
78
78
  }
79
79
  }
80
80
 
@@ -109,7 +109,7 @@ export class InboundMediaResolver {
109
109
  );
110
110
 
111
111
  if (!downloaded) {
112
- console.log(`[${this.adapter.platform}] 下载资源失败: ${request.resourceKey}`);
112
+ console.log(`[${this.adapter.platform}] Failed to download resource: ${request.resourceKey}`);
113
113
  return null;
114
114
  }
115
115
 
@@ -126,7 +126,7 @@ export class InboundMediaResolver {
126
126
 
127
127
  return { attachment, entry };
128
128
  } catch (e: any) {
129
- console.error(`[${this.adapter.platform}] 解析媒体异常: ${e.message}`);
129
+ console.error(`[${this.adapter.platform}] Media resolution error: ${e.message}`);
130
130
  return null;
131
131
  }
132
132
  }
@@ -40,12 +40,12 @@ export class TelegramInboundAdapter implements InboundMediaAdapter {
40
40
  const ProxyAgent = (globalThis as any).ProxyAgent;
41
41
  if (ProxyAgent) {
42
42
  this.dispatcher = new ProxyAgent(this.proxy);
43
- console.log(`[TelegramInbound] 已配置代理 dispatcher: ${this.proxy}`);
43
+ console.log(`[TelegramInbound] Proxy dispatcher configured: ${this.proxy}`);
44
44
  } else {
45
- console.log(`[TelegramInbound] ⚠️ 代理已配置但 ProxyAgent 不可用,将尝试直接连接`);
45
+ console.log(`[TelegramInbound] ⚠️ Proxy configured but ProxyAgent unavailable, will try direct connection`);
46
46
  }
47
47
  } catch (e: any) {
48
- console.log(`[TelegramInbound] ⚠️ 代理 dispatcher 初始化失败: ${e.message}`);
48
+ console.log(`[TelegramInbound] ⚠️ Proxy dispatcher init failed: ${e.message}`);
49
49
  }
50
50
  }
51
51
  }
@@ -65,13 +65,13 @@ export class TelegramInboundAdapter implements InboundMediaAdapter {
65
65
  });
66
66
 
67
67
  if (!fileResp.ok) {
68
- console.error(`[TelegramInbound] getFile 失败: HTTP ${fileResp.status} (file_id=${resourceKey.slice(-10)})`);
68
+ console.error(`[TelegramInbound] getFile failed: HTTP ${fileResp.status} (file_id=${resourceKey.slice(-10)})`);
69
69
  return null;
70
70
  }
71
71
 
72
72
  const fileData = await fileResp.json();
73
73
  if (!fileData.ok || !fileData.result?.file_path) {
74
- console.error(`[TelegramInbound] getFile 返回无效: ${JSON.stringify(fileData).slice(0, 200)}`);
74
+ console.error(`[TelegramInbound] getFile returned invalid: ${JSON.stringify(fileData).slice(0, 200)}`);
75
75
  return null;
76
76
  }
77
77
 
@@ -79,7 +79,7 @@ export class TelegramInboundAdapter implements InboundMediaAdapter {
79
79
  const fileSize = fileData.result.file_size;
80
80
 
81
81
  if (fileSize && fileSize > 20 * 1024 * 1024) {
82
- console.log(`[TelegramInbound] 文件过大 (${fileSize} bytes),跳过下载`);
82
+ console.log(`[TelegramInbound] File too large (${fileSize} bytes), skipping download`);
83
83
  return null;
84
84
  }
85
85
 
@@ -88,7 +88,7 @@ export class TelegramInboundAdapter implements InboundMediaAdapter {
88
88
  const contentResp = await this._fetch(downloadUrl);
89
89
 
90
90
  if (!contentResp.ok) {
91
- console.error(`[TelegramInbound] 下载文件失败: HTTP ${contentResp.status} (path=${filePath})`);
91
+ console.error(`[TelegramInbound] Download failed: HTTP ${contentResp.status} (path=${filePath})`);
92
92
  return null;
93
93
  }
94
94
 
@@ -105,7 +105,7 @@ export class TelegramInboundAdapter implements InboundMediaAdapter {
105
105
  sourceKey: resourceKey,
106
106
  };
107
107
  } catch (e: any) {
108
- console.error(`[TelegramInbound] 下载资源异常: ${e.message}`);
108
+ console.error(`[TelegramInbound] download resource exception: ${e.message}`);
109
109
  return null;
110
110
  }
111
111
  }
@@ -88,32 +88,32 @@ export function buildSystemPrompt(ctx: PromptBuilderContext): string {
88
88
  // 2. IM 能力
89
89
  const caps = ctx.imModule?.getCapabilities() ?? ctx.caps ?? DEFAULT_TERMINAL_CAPS;
90
90
  const capSection = buildCapabilityPrompt(caps);
91
- sections.push('# 当前对接 IM 能力\n\n' + capSection);
91
+ sections.push('# Current IM Capabilities\n\n' + capSection);
92
92
 
93
- // 3. 网关运行日志(Agent 可主动查询)
94
- sections.push(`# 网关运行日志
93
+ // 3. Gateway logs (Agent can proactively query)
94
+ sections.push(`# Gateway Runtime Logs
95
95
 
96
- 网关运行日志: ~/.imtoagent/logs/imtoagent.log
96
+ Gateway runtime logs: ~/.imtoagent/logs/imtoagent.log
97
97
 
98
- 你可以通过查看日志来了解网关状态、排查问题、感知重启事件:
99
- - \`tail -n 30 ~/.imtoagent/logs/imtoagent.log\` — 最近 30
100
- - \`grep -i "restart\|reload\|shutdown\|SIGTERM" ~/.imtoagent/logs/imtoagent.log | tail -n 10\` — 重启/关闭记录
101
- - \`grep -i "error\|fail\|crash" ~/.imtoagent/logs/imtoagent.log | tail -n 10\` — 错误记录
102
- - \`grep -i "online\|connected\|disconnected" ~/.imtoagent/logs/imtoagent.log | tail -n 10\` — Bot 连接状态
98
+ You can check logs to understand gateway status, troubleshoot issues, and detect restart events:
99
+ - \`tail -n 30 ~/.imtoagent/logs/imtoagent.log\` — Last 30 lines
100
+ - \`grep -i "restart\|reload\|shutdown\|SIGTERM" ~/.imtoagent/logs/imtoagent.log | tail -n 10\` — Restart/shutdown records
101
+ - \`grep -i "error\|fail\|crash" ~/.imtoagent/logs/imtoagent.log | tail -n 10\` — Error records
102
+ - \`grep -i "online\|connected\|disconnected" ~/.imtoagent/logs/imtoagent.log | tail -n 10\` — Bot connection status
103
103
 
104
- 注意:你启动后第一条消息的对话记忆可能已丢失(如果网关重启过),请先检查日志了解上下文。`);
104
+ Note: Your first message after startup may have lost conversation memory (if the gateway restarted). Check logs first to understand the context.`);
105
105
 
106
106
  // 4. Soul
107
107
  const soul = loadSoul(ctx.botName);
108
108
  if (soul) {
109
- sections.push('# 用户自定义指令 (IMtoAgent Soul)\n\n' + soul);
109
+ sections.push('# User-Defined Instructions (IMtoAgent Soul)\n\n' + soul);
110
110
  }
111
111
 
112
112
  return sections.join('\n\n---\n\n');
113
113
  }
114
114
 
115
115
  // ================================================================
116
- // 便捷函数:直接获取能力(消除各处 inline fallback)
116
+ // Convenience: resolve capabilities directly (eliminate inline fallbacks)
117
117
  // ================================================================
118
118
  export function resolveCapabilities(
119
119
  imModule?: { getCapabilities(): IMCapabilities } | null,