imtoagent 0.3.4 → 0.3.6

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 +197 -153
  3. package/bin/imtoagent.cjs +13 -5
  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
@@ -71,9 +71,9 @@ export function loadProviders(): { providers: Map<string, ProviderConfig>; defau
71
71
  format: p.format || 'anthropic',
72
72
  });
73
73
  }
74
- console.log(`[Proxy] 加载 ${providers.size} 个供应商: ${[...providers.keys()].join(', ')}`);
74
+ console.log(`[Proxy] Loaded ${providers.size} provider(s): ${[...providers.keys()].join(', ')}`);
75
75
  } catch (e: any) {
76
- console.error(`[Proxy] 读取 providers.json 失败: ${e.message}`);
76
+ console.error(`[Proxy] Failed to read providers.json: ${e.message}`);
77
77
  }
78
78
  return { providers, defaultModel };
79
79
  }
@@ -85,9 +85,9 @@ export function saveActiveModel(modelSpec: string): void {
85
85
  const cfg = JSON.parse(raw);
86
86
  cfg.activeModel = modelSpec;
87
87
  fs.writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2) + '\n');
88
- console.log(`[Proxy] activeModel 已持久化: ${modelSpec}`);
88
+ console.log(`[Proxy] activeModel persisted: ${modelSpec}`);
89
89
  } catch (e: any) {
90
- console.error(`[Proxy] 保存 activeModel 失败: ${e.message}`);
90
+ console.error(`[Proxy] Failed to save activeModel: ${e.message}`);
91
91
  }
92
92
  }
93
93
 
@@ -134,7 +134,7 @@ export function loadSessionConfig(customPath?: string): { activeModel: string; m
134
134
  modelAliases: cfg.modelAliases || defaultAliases,
135
135
  };
136
136
  } catch (e: any) {
137
- console.error(`[Proxy] 加载会话配置失败 (${customPath || '_default.json'}): ${e.message}`);
137
+ console.error(`[Proxy] Failed to load session config (${customPath || '_default.json'}): ${e.message}`);
138
138
  return { activeModel: defaultAliases.default, modelAliases: defaultAliases };
139
139
  }
140
140
  }
@@ -155,7 +155,7 @@ export function saveSessionConfig(userId: string, activeModel: string, modelAlia
155
155
  };
156
156
  fs.writeFileSync(sessionPath, JSON.stringify(cfg, null, 2) + '\n');
157
157
  } catch (e: any) {
158
- console.error(`[Proxy] 保存会话配置失败 (${userId}): ${e.message}`);
158
+ console.error(`[Proxy] Failed to save session config (${userId}): ${e.message}`);
159
159
  }
160
160
  }
161
161
 
@@ -574,7 +574,7 @@ function openAIStreamToAnthropic(openAIStream: NodeJS.ReadableStream, res: http.
574
574
  const fp = conversationFingerprint(_reqBody.messages);
575
575
  if (fp) {
576
576
  reasoningCache.set(fp, cachedReasoningContent);
577
- console.log(`[Proxy] 🧠 reasoning_content 已缓存(指纹: ${fp.slice(0, 50)}...)`);
577
+ console.log(`[Proxy] 🧠 reasoning_content cached (fingerprint: ${fp.slice(0, 50)}...)`);
578
578
  }
579
579
  }
580
580
  sendEvent('message_delta', {
@@ -679,7 +679,7 @@ function openAIStreamToAnthropic(openAIStream: NodeJS.ReadableStream, res: http.
679
679
  });
680
680
 
681
681
  openAIStream.on('error', (err) => {
682
- console.error(`[Proxy] OpenAI 流错误: ${err.message}`);
682
+ console.error(`[Proxy] OpenAI stream error: ${err.message}`);
683
683
  if (!res.writableEnded) {
684
684
  res.writeHead(502, { 'Content-Type': 'application/json' });
685
685
  res.end(JSON.stringify({ error: `Stream error: ${err.message}`, type: 'api_error' }));
@@ -698,7 +698,7 @@ export function calculateCost(modelSpec: string, inputTokens: number, outputToke
698
698
  return (inputTokens * p.inputPerMillion + outputTokens * p.outputPerMillion) / 1_000_000;
699
699
  }
700
700
  // 未知供应商 — 输出警告日志
701
- console.warn(`[calculateCost] ⚠️ 未知供应商 "${provider}",使用默认价格 ($0.55/M input, $2.19/M output)`);
701
+ console.warn(`[calculateCost] ⚠️ Unknown provider "${provider}", using default pricing ($0.55/M input, $2.19/M output)`);
702
702
  } catch {}
703
703
  return (inputTokens * 0.55 + outputTokens * 2.19) / 1_000_000;
704
704
  }
@@ -747,7 +747,7 @@ function handleRequest(req: http.IncomingMessage, res: http.ServerResponse): voi
747
747
  };
748
748
  res.writeHead(200, { 'Content-Type': 'application/json' });
749
749
  res.end(JSON.stringify(response));
750
- console.log(`[Proxy] → ${cfg.providerName}/${cfg.model} GET /v1/models (模拟返回 ${modelList.length} 个模型)`);
750
+ console.log(`[Proxy] → ${cfg.providerName}/${cfg.model} GET /v1/models (simulated, returning ${modelList.length} models)`);
751
751
  return;
752
752
  }
753
753
 
@@ -786,7 +786,7 @@ function handleRequest(req: http.IncomingMessage, res: http.ServerResponse): voi
786
786
 
787
787
  // 根据 Claude Code 传的模型名前缀,识别角色并替换为完整规格
788
788
  const resolvedSpec = resolveModelByPrefix(originalModel);
789
- console.log(`[Proxy] 模型解析: ${originalModel} → ${resolvedSpec}`);
789
+ console.log(`[Proxy] Model resolved: ${originalModel} → ${resolvedSpec}`);
790
790
 
791
791
  // 🌐 Web Search:DeepSeek 使用版本化工具名 web_search_20250305,Claude Code 发的是 web_search
792
792
  if (parsedBody.tools) {
@@ -794,7 +794,7 @@ function handleRequest(req: http.IncomingMessage, res: http.ServerResponse): voi
794
794
  const tn = (t.name || t.type || '').toLowerCase();
795
795
  // 调试:WebSearch 工具的完整定义
796
796
  if ((t.name || '').toLowerCase().includes('search')) {
797
- console.log(`[Proxy] 🔍 WebSearch 原始定义: ${JSON.stringify(t)}`);
797
+ console.log(`[Proxy] 🔍 WebSearch original definition: ${JSON.stringify(t)}`);
798
798
  }
799
799
  if (tn === 'web_search' || tn === 'websearch') {
800
800
  t.name = 'web_search_20250305';
@@ -803,7 +803,7 @@ function handleRequest(req: http.IncomingMessage, res: http.ServerResponse): voi
803
803
  }
804
804
  }
805
805
  if (parsedBody.tools && parsedBody.tools.length > 0) {
806
- console.log(`[Proxy] 🔍 tools 定义: ${parsedBody.tools.map((t: any) => t.name || t.type).join(', ')}`);
806
+ console.log(`[Proxy] 🔍 tools definition: ${parsedBody.tools.map((t: any) => t.name || t.type).join(', ')}`);
807
807
  }
808
808
 
809
809
  // 解析供应商和模型名
@@ -823,7 +823,7 @@ function handleRequest(req: http.IncomingMessage, res: http.ServerResponse): voi
823
823
  // 根据目标供应商获取配置
824
824
  const targetProvider = providers.get(targetProviderName);
825
825
  if (!targetProvider) {
826
- console.error(`[Proxy] 未知供应商: ${targetProviderName}`);
826
+ console.error(`[Proxy] Unknown provider: ${targetProviderName}`);
827
827
  res.writeHead(500, { 'Content-Type': 'application/json' });
828
828
  res.end(JSON.stringify({ error: `Unknown provider: ${targetProviderName}` }));
829
829
  return;
@@ -858,7 +858,7 @@ function handleRequest(req: http.IncomingMessage, res: http.ServerResponse): voi
858
858
  thinking: cached,
859
859
  signature: Buffer.from('cc-gw').toString('base64'),
860
860
  });
861
- console.log(`[Proxy] 🧠 注入 thinking 块(缓存命中,长度: ${cached.length})`);
861
+ console.log(`[Proxy] 🧠 Injected thinking block (cache hit, length: ${cached.length})`);
862
862
  }
863
863
  }
864
864
  }
@@ -885,7 +885,7 @@ function handleRequest(req: http.IncomingMessage, res: http.ServerResponse): voi
885
885
 
886
886
  const isStream = parsedBody.stream !== false;
887
887
 
888
- console.log(`[Proxy] → ${targetProviderName}/${targetModelName} (${targetProvider.format}) ${req.method} ${req.url}${originalModel ? ` (原: ${originalModel})` : ''}`);
888
+ console.log(`[Proxy] → ${targetProviderName}/${targetModelName} (${targetProvider.format}) ${req.method} ${req.url}${originalModel ? ` (original: ${originalModel})` : ''}`);
889
889
 
890
890
  const upstreamReq = (targetUpstreamProto === 'https' ? require('https') : http).request(options, (upstreamRes) => {
891
891
  // 流式响应
@@ -935,7 +935,7 @@ function handleRequest(req: http.IncomingMessage, res: http.ServerResponse): voi
935
935
  const respStr = Buffer.concat(respChunks).toString('utf-8');
936
936
 
937
937
  if (upstreamRes.statusCode !== 200) {
938
- console.error(`[Proxy] 上游错误 ${upstreamRes.statusCode}: ${respStr.slice(0, 500)}`);
938
+ console.error(`[Proxy] Upstream error ${upstreamRes.statusCode}: ${respStr.slice(0, 500)}`);
939
939
  res.writeHead(upstreamRes.statusCode || 500, { 'Content-Type': 'application/json' });
940
940
  res.end(respStr);
941
941
  return;
@@ -955,7 +955,7 @@ function handleRequest(req: http.IncomingMessage, res: http.ServerResponse): voi
955
955
  } else {
956
956
  let openAIJson: any;
957
957
  try { openAIJson = JSON.parse(respStr); } catch {
958
- console.error(`[Proxy] OpenAI JSON 解析失败: ${respStr.slice(0, 200)}`);
958
+ console.error(`[Proxy] OpenAI JSON parse failed: ${respStr.slice(0, 200)}`);
959
959
  res.writeHead(502, { 'Content-Type': 'application/json' });
960
960
  res.end(JSON.stringify({ error: 'Invalid upstream response', type: 'api_error' }));
961
961
  return;
@@ -970,7 +970,7 @@ function handleRequest(req: http.IncomingMessage, res: http.ServerResponse): voi
970
970
 
971
971
  upstreamReq.on('timeout', () => {
972
972
  upstreamReq.destroy();
973
- console.error(`[Proxy] 请求超时 (${REQUEST_TIMEOUT}ms)`);
973
+ console.error(`[Proxy] Request timeout (${REQUEST_TIMEOUT}ms)`);
974
974
  if (!res.writableEnded) {
975
975
  res.writeHead(504, { 'Content-Type': 'application/json' });
976
976
  res.end(JSON.stringify({ error: 'Upstream request timeout', type: 'api_error' }));
@@ -978,7 +978,7 @@ function handleRequest(req: http.IncomingMessage, res: http.ServerResponse): voi
978
978
  });
979
979
 
980
980
  upstreamReq.on('error', (err) => {
981
- console.error(`[Proxy] 上游请求失败: ${err.message}`);
981
+ console.error(`[Proxy] Upstream request failed: ${err.message}`);
982
982
  if (!res.writableEnded) {
983
983
  res.writeHead(502, { 'Content-Type': 'application/json' });
984
984
  res.end(JSON.stringify({ error: `Upstream error: ${err.message}`, type: 'api_error' }));
@@ -1004,18 +1004,18 @@ export function startAnthropicProxy(port = 18899): Promise<number> {
1004
1004
  return new Promise((resolve) => {
1005
1005
  server = http.createServer(handleRequest);
1006
1006
  server.listen(port, () => {
1007
- console.log(`[Proxy] 本地代理启动 http://localhost:${port}/v1/messages`);
1008
- console.log(`[Proxy] 当前模型: ${sharedState.activeConfig ? `${sharedState.activeConfig.providerName}/${sharedState.activeConfig.model} (${sharedState.activeConfig.format})` : '未设置'}`);
1009
- console.log(`[Proxy] 供应商: ${sharedState.activeConfig?.baseUrl || ''}`);
1007
+ console.log(`[Proxy] Local proxy started at http://localhost:${port}/v1/messages`);
1008
+ console.log(`[Proxy] Current model: ${sharedState.activeConfig ? `${sharedState.activeConfig.providerName}/${sharedState.activeConfig.model} (${sharedState.activeConfig.format})` : 'not set'}`);
1009
+ console.log(`[Proxy] Provider: ${sharedState.activeConfig?.baseUrl || 'none'}`);
1010
1010
 
1011
- // 检查有空 API Key 的供应商
1011
+ // Check for providers with empty API keys
1012
1012
  const emptyKeyProviders: string[] = [];
1013
1013
  providers.forEach((cfg, name) => {
1014
1014
  if (!cfg.apiKey) emptyKeyProviders.push(name);
1015
1015
  });
1016
1016
  if (emptyKeyProviders.length > 0) {
1017
- console.log(`⚠️ 以下供应商 API Key 为空,可能无法正常工作: ${emptyKeyProviders.join(', ')}`);
1018
- console.log(' 建议: 运行 "imtoagent setup" 设置必要的参数\n');
1017
+ console.log(`⚠️ The following providers have empty API Keys and may not work properly: ${emptyKeyProviders.join(', ')}`);
1018
+ console.log(' Suggestion: run "imtoagent setup" to configure required parameters\n');
1019
1019
  }
1020
1020
 
1021
1021
  resolve(port);
@@ -1027,7 +1027,7 @@ export async function stopAnthropicProxy(): Promise<void> {
1027
1027
  return new Promise((resolve) => {
1028
1028
  if (server) {
1029
1029
  server.close(() => {
1030
- console.log('[Proxy] 代理服务器已关闭');
1030
+ console.log('[Proxy] Proxy server closed');
1031
1031
  server = null;
1032
1032
  resolve();
1033
1033
  });
@@ -1057,7 +1057,7 @@ export function saveSessionMemory(memoryPath: string, data: SessionMemoryData):
1057
1057
  try {
1058
1058
  fs.writeFileSync(memoryPath, JSON.stringify(data, null, 2));
1059
1059
  } catch (e: any) {
1060
- console.error(`[Memory] 保存会话失败: ${e.message}`);
1060
+ console.error(`[Memory] Failed to save session: ${e.message}`);
1061
1061
  }
1062
1062
  }
1063
1063
 
@@ -1066,7 +1066,7 @@ export function loadSessionMemory(memoryPath: string): SessionMemoryData | null
1066
1066
  if (!fs.existsSync(memoryPath)) return null;
1067
1067
  return JSON.parse(fs.readFileSync(memoryPath, 'utf-8'));
1068
1068
  } catch (e: any) {
1069
- console.error(`[Memory] 加载会话失败: ${e.message}`);
1069
+ console.error(`[Memory] Failed to load session: ${e.message}`);
1070
1070
  return null;
1071
1071
  }
1072
1072
  }
@@ -1076,7 +1076,7 @@ export function deleteSessionMemory(chatId: string): void {
1076
1076
  try {
1077
1077
  if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
1078
1078
  } catch (e: any) {
1079
- console.error(`[Memory] 删除会话 ${chatId} 失败: ${e.message}`);
1079
+ console.error(`[Memory] Failed to delete session ${chatId}: ${e.message}`);
1080
1080
  }
1081
1081
  }
1082
1082
 
@@ -1088,7 +1088,7 @@ export function listPersistedSessions(): string[] {
1088
1088
  .filter(f => f.endsWith('.memory.json'))
1089
1089
  .map(f => f.replace('.memory.json', ''));
1090
1090
  } catch (e: any) {
1091
- console.error(`[Memory] 扫描会话目录失败: ${e.message}`);
1091
+ console.error(`[Memory] Failed to scan session directory: ${e.message}`);
1092
1092
  return [];
1093
1093
  }
1094
1094
  }
@@ -21,7 +21,7 @@ let _codexConfig: CodexProxyConfig | null = null;
21
21
 
22
22
  export function initCodexProxyConfig(cfg: CodexProxyConfig) {
23
23
  _codexConfig = cfg;
24
- console.log(`[Codex Proxy] 配置已加载: model=${cfg.model}, upstream=${cfg.upstream}`);
24
+ console.log(`[Codex Proxy] Config loaded: model=${cfg.model}, upstream=${cfg.upstream}`);
25
25
  }
26
26
 
27
27
  function getConfig(): CodexProxyConfig {
@@ -44,9 +44,9 @@ function getConfig(): CodexProxyConfig {
44
44
  upstream: codex.upstream || 'https://api.deepseek.com/v1/chat/completions',
45
45
  apiKey,
46
46
  };
47
- console.log('[Codex Proxy] config.json 加载配置');
47
+ console.log('[Codex Proxy] Loaded config from config.json');
48
48
  } catch (e: any) {
49
- console.error(`[Codex Proxy] 无法加载配置: ${e.message}`);
49
+ console.error(`[Codex Proxy] Unable to load config: ${e.message}`);
50
50
  }
51
51
  }
52
52
  return _codexConfig!;
@@ -98,7 +98,7 @@ function responsesToChat(body: any): { model: string; messages: ChatMessage[]; s
98
98
  model: MODEL(),
99
99
  messages: [],
100
100
  stream: true,
101
- thinking: { type: 'disabled' }, // Codex 不兼容 thinking 模式,content null 导致流断开
101
+ thinking: { type: 'disabled' }, // Codex doesn't support thinking mode; content is all null causing stream disconnect
102
102
  };
103
103
  chat.max_tokens = body.max_output_tokens || 8192;
104
104
 
@@ -128,7 +128,7 @@ function responsesToChat(body: any): { model: string; messages: ChatMessage[]; s
128
128
  const nonSystem = input.filter((m: any) => m.role !== 'system' && m.role !== 'developer');
129
129
  const kept = nonSystem.slice(-(MAX_INPUT_ITEMS - systemItems.length));
130
130
  input = [...systemItems, ...kept];
131
- console.log(`[Codex] ⚠️ 截断输入: ${input.length + truncated} → ${input.length} (丢弃最旧 ${truncated})`);
131
+ console.log(`[Codex] ⚠️ Truncated input: ${input.length + truncated} → ${input.length} items (discarded oldest ${truncated})`);
132
132
  }
133
133
  const types = input.map((m: any) => m.type || ('msg:' + m.role)).join(',');
134
134
  console.log(`[Codex] input types: [${types}]`);
@@ -188,16 +188,16 @@ function responsesToChat(body: any): { model: string; messages: ChatMessage[]; s
188
188
  if (b.type === 'function_call') {
189
189
  calls.push({ id: b.call_id || '', type: 'function', function: { name: b.name || '', arguments: b.arguments || '{}' } });
190
190
  } else if (b.type === 'input_image') {
191
- // DeepSeek V4 不支持图片输入,降级为文本提示
191
+ // DeepSeek V4 doesn't support image input, degrade to text hint
192
192
  const mime = b.media_type || b.mime_type || 'image/png';
193
- textParts.push(`[图片已接收 (${mime}),当前模型不支持直接查看图片内容]`);
193
+ textParts.push(`[Image received (${mime}), current model doesn't support viewing image content directly]`);
194
194
  } else if (b.type === 'input_file') {
195
- // DeepSeek V4 不支持文件输入,提取可用文本或降级提示
195
+ // DeepSeek V4 doesn't support file input, extract text or degrade to hint
196
196
  if (b.text || b.content) {
197
197
  textParts.push(b.text || b.content || '');
198
198
  } else {
199
- const name = b.filename || b.file_name || '未知文件';
200
- textParts.push(`[文件已接收: ${name},当前模型不支持直接读取文件内容]`);
199
+ const name = b.filename || b.file_name || 'unknown file';
200
+ textParts.push(`[File received: ${name}, current model doesn't support reading file content directly]`);
201
201
  }
202
202
  } else {
203
203
  const t = b.text || b.input_text || b.output_text || '';
@@ -267,7 +267,7 @@ function cleanOrphanTools(messages: ChatMessage[]): ChatMessage[] {
267
267
  const filtered = messages.filter(m => {
268
268
  if (m.role !== 'tool') return true;
269
269
  if (allToolCallIds.has(m.tool_call_id || '')) return true;
270
- console.warn(`[Codex] 🗑️ 丢弃孤儿 tool 消息: call_id=${(m.tool_call_id || '').slice(0,16)}`);
270
+ console.warn(`[Codex] 🗑️ Discarded orphan tool message: call_id=${(m.tool_call_id || '').slice(0,16)}`);
271
271
  return false;
272
272
  });
273
273
  return filtered.length === messages.length ? messages : filtered;
@@ -313,14 +313,14 @@ function validateToolPairing(messages: ChatMessage[]): ChatMessage[] {
313
313
  result.push(...matchingTools);
314
314
  }
315
315
  } else {
316
- // P1 修复:不再丢弃整个 assistant 消息,而是保留原始内容并附加警告
317
- // 这样下游上下文不会完全丢失
318
- const warning = '[⚠️ IMtoAgent 警告:tool_call 未找到匹配的 tool 响应,保留原始消息防止上下文丢失]';
319
- console.warn(`[Codex] ⚠️ 保留原始 assistant 消息(附带警告),而非丢弃`);
316
+ // P1 fix: don't discard entire assistant message, preserve original content with warning
317
+ // So downstream context isn't completely lost
318
+ const warning = '[⚠️ IMtoAgent WARNING: tool_call has no matching tool response, preserving original message to prevent context loss]';
319
+ console.warn(`[Codex] ⚠️ Keeping original assistant message (with warning), not discarding`);
320
320
  const preserved: ChatMessage = {
321
321
  role: 'assistant',
322
322
  content: warning + '\n' + (msg.content || ''),
323
- tool_calls: undefined, // 移除无效的 tool_calls
323
+ tool_calls: undefined, // Remove invalid tool_calls
324
324
  };
325
325
  if (msg.reasoning_content) preserved.reasoning_content = msg.reasoning_content;
326
326
  result.push(preserved);
@@ -579,7 +579,7 @@ export async function handleCodexRequest(
579
579
  caps: ctx?.caps || null,
580
580
  botName,
581
581
  });
582
- console.log(`[Codex] 📝 system prompt built (${systemPrompt.length} chars, bot=${botName})`);
582
+ console.log(`[Codex] 📝 System prompt built (${systemPrompt.length} chars, bot=${botName})`);
583
583
 
584
584
  let sysMsg = chatReq.messages.find((m: ChatMessage) => m.role === 'system');
585
585
  if (!sysMsg) {
@@ -649,7 +649,7 @@ export async function handleCodexRequest(
649
649
 
650
650
  // 兼容旧引用(不再启动独立服务器)
651
651
  export function startCodexProxy(_port?: number): Promise<number> {
652
- console.log('[Codex Proxy] 已合并到 18899 端口');
652
+ console.log('[Codex Proxy] Merged into port 18899');
653
653
  return Promise.resolve(18899);
654
654
  }
655
655
  export function stopCodexProxy(): Promise<void> {
@@ -113,12 +113,12 @@ export async function installBackend(
113
113
  ): Promise<boolean> {
114
114
  const b = BACKEND_DEFS.find((x) => x.type === type);
115
115
  if (!b) {
116
- console.error(`❌ 未知后端类型: ${type}`);
116
+ console.error(`❌ Unknown backend type: ${type}`);
117
117
  return false;
118
118
  }
119
119
 
120
- console.log(`\n📦 正在安装 ${b.label}...`);
121
- console.log(` 命令: ${b.installHint}\n`);
120
+ console.log(`\n📦 Installing ${b.label}...`);
121
+ console.log(` Command: ${b.installHint}\n`);
122
122
 
123
123
  try {
124
124
  // 获取 npm 全局 bin 目录,用于安装后验证(复用 getNpmGlobalBin 缓存)
@@ -154,8 +154,8 @@ export async function installBackend(
154
154
  const exitCode = await child.exited;
155
155
 
156
156
  if (exitCode !== 0) {
157
- console.error(`\n❌ ${b.label} 安装失败 (退出码: ${exitCode})`);
158
- console.error(` 可手动运行: ${b.installHint}`);
157
+ console.error(`\n❌ ${b.label} installation failed (exit code: ${exitCode})`);
158
+ console.error(` Run manually: ${b.installHint}`);
159
159
  return false;
160
160
  }
161
161
 
@@ -165,7 +165,7 @@ export async function installBackend(
165
165
  try {
166
166
  if (fs.existsSync(binPath)) {
167
167
  const version = execSync(`"${binPath}" --version`, { encoding: 'utf-8', timeout: 5000 }).trim();
168
- console.log(`\n✅ ${b.label} 安装成功! 版本: ${version}`);
168
+ console.log(`\n✅ ${b.label} installed successfully! Version: ${version}`);
169
169
  return true;
170
170
  }
171
171
  } catch {}
@@ -174,20 +174,20 @@ export async function installBackend(
174
174
  // fallback: 通过 PATH 查找
175
175
  const info = checkOne(b);
176
176
  if (info.installed) {
177
- console.log(`\n✅ ${b.label} 安装成功! 版本: ${info.version}`);
177
+ console.log(`\n✅ ${b.label} installed successfully! Version: ${info.version}`);
178
178
  return true;
179
179
  } else {
180
- console.error(`\n❌ ${b.label} 安装后仍未检测到`);
180
+ console.error(`\n❌ ${b.label} not detected after installation`);
181
181
  if (npmBinDir) {
182
- console.error(` npm 全局 bin 目录: ${npmBinDir}`);
183
- console.error(` 建议将该目录添加到 PATH,或手动运行: ${b.installHint}`);
182
+ console.error(` npm global bin: ${npmBinDir}`);
183
+ console.error(` Consider adding it to PATH, or run manually: ${b.installHint}`);
184
184
  } else {
185
- console.error(` 请手动运行: ${b.installHint}`);
185
+ console.error(` Run manually: ${b.installHint}`);
186
186
  }
187
187
  return false;
188
188
  }
189
189
  } catch (e: any) {
190
- console.error(`\n❌ 安装 ${b.label} 时出错: ${e.message || e}`);
190
+ console.error(`\n❌ Error installing ${b.label}: ${e.message || e}`);
191
191
  return false;
192
192
  }
193
193
  }
@@ -52,14 +52,14 @@ export function getDataDir(): string {
52
52
  // cwd(开发模式)
53
53
  const cwd = process.cwd();
54
54
  if (fs.existsSync(path.join(cwd, 'config.json'))) {
55
- candidates.push({ dir: cwd, label: 'cwd 开发模式' });
55
+ candidates.push({ dir: cwd, label: 'cwd development mode' });
56
56
  }
57
57
 
58
58
  if (candidates.length > 0) {
59
59
  // 有现成配置,直接用最优先的那个
60
60
  const chosen = candidates[0];
61
61
  _dataDir = chosen.dir;
62
- console.log(`[Paths] 数据目录: ${_dataDir} (${chosen.label})`);
62
+ console.log(`[Paths] Data directory: ${_dataDir} (${chosen.label})`);
63
63
  return _dataDir;
64
64
  }
65
65
 
@@ -93,7 +93,7 @@ function initDataDir(dotDir: string, envHome: string): string {
93
93
  const pkgDir = getPkgDir();
94
94
  if (fs.existsSync(path.join(pkgDir, 'templates', 'config.template.json'))) {
95
95
  sourceDir = path.join(pkgDir, 'templates');
96
- sourceLabel = '包模板';
96
+ sourceLabel = 'package template';
97
97
  }
98
98
  }
99
99
 
@@ -107,7 +107,7 @@ function initDataDir(dotDir: string, envHome: string): string {
107
107
  copyIfExists(sourceDir, target, 'config.json');
108
108
  copyIfExists(sourceDir, target, 'providers.json');
109
109
  copyIfExists(sourceDir, target, 'opencode.json');
110
- } else if (sourceDir && sourceLabel === '包模板') {
110
+ } else if (sourceDir && sourceLabel === 'package template') {
111
111
  // npm 安装:从模板拷贝(去掉 .template 后缀)
112
112
  copyTemplateIfExists(sourceDir, target, 'config.template.json', 'config.json');
113
113
  copyTemplateIfExists(sourceDir, target, 'providers.template.json', 'providers.json');
@@ -119,8 +119,8 @@ function initDataDir(dotDir: string, envHome: string): string {
119
119
  }
120
120
  }
121
121
 
122
- console.log(`[Paths] ✨ 首次初始化数据目录: ${target} (来源: ${sourceLabel || '默认模板'})`);
123
- console.log(`[Paths] 请编辑 ${path.join(target, 'config.json')} 配置你的凭证`);
122
+ console.log(`[Paths] ✨ First-time data directory initialized: ${target} (source: ${sourceLabel || 'default template'})`);
123
+ console.log(`[Paths] Please edit ${path.join(target, 'config.json')} to configure your credentials`);
124
124
 
125
125
  return target;
126
126
  }
@@ -206,13 +206,13 @@ export function getRestoreMarkerPath(): string {
206
206
 
207
207
  export function getTemplatePath(relativePath: string): string {
208
208
  const tplPath = path.join(getPkgDir(), 'templates', relativePath);
209
- if (!fs.existsSync(tplPath)) console.warn(`⚠️ 模板不存在: ${tplPath}`);
209
+ if (!fs.existsSync(tplPath)) console.warn(`⚠️ Template not found: ${tplPath}`);
210
210
  return tplPath;
211
211
  }
212
212
 
213
213
  export function getTemplateSoulPath(filename: string): string {
214
214
  const tplPath = path.join(getPkgDir(), 'templates', 'soul.template', filename);
215
- if (!fs.existsSync(tplPath)) console.warn(`⚠️ 灵魂模板不存在: ${tplPath}`);
215
+ if (!fs.existsSync(tplPath)) console.warn(`⚠️ Soul template not found: ${tplPath}`);
216
216
  return tplPath;
217
217
  }
218
218
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imtoagent",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "description": "IM ↔ Agent 统一网关 — 飞书/Telegram/微信/企业微信对接 Claude Code/Codex/OpenCode",
5
5
  "type": "module",
6
6
  "bin": {
@@ -19,26 +19,26 @@ try {
19
19
 
20
20
  if (configExists) {
21
21
  console.log("");
22
- console.log("✅ imtoagent 升级成功!");
22
+ console.log("✅ imtoagent upgraded successfully!");
23
23
  console.log("");
24
- console.log(" 数据目录: " + DATA_DIR);
25
- console.log(" 配置文件保持不变,无需重新配置。");
26
- console.log(' 运行 "imtoagent start" 启动网关。');
24
+ console.log(" Data directory: " + DATA_DIR);
25
+ console.log(" Configuration file kept as-is, no need to reconfigure.");
26
+ console.log(' Run "imtoagent start" to start the gateway.');
27
27
  console.log("");
28
28
  } else {
29
29
  console.log("");
30
- console.log("🎉 imtoagent 安装成功!");
30
+ console.log("🎉 imtoagent installed successfully!");
31
31
  console.log("");
32
- console.log(" 首次使用,请运行以下命令完成初始化配置:");
32
+ console.log(" For first-time use, run the following commands to complete initial setup:");
33
33
  console.log("");
34
- console.log(' imtoagent setup # 交互式配置向导');
35
- console.log(' imtoagent # 无命令时自动检测,未配置也会进入向导');
34
+ console.log(' imtoagent setup # Interactive configuration wizard');
35
+ console.log(' imtoagent # Auto-detects when run with no command; enters wizard if not configured');
36
36
  console.log("");
37
- console.log(" 配置完成后启动网关:");
37
+ console.log(" After configuration, start the gateway:");
38
38
  console.log("");
39
39
  console.log(" imtoagent start");
40
40
  console.log("");
41
41
  }
42
42
  } catch (e) {
43
- // 静默失败,不影响安装
43
+ // Silently fail, do not affect installation
44
44
  }
@@ -19,18 +19,18 @@ try {
19
19
 
20
20
  if (configExists) {
21
21
  console.log(`
22
- ✅ imtoagent 升级成功!
23
- 数据目录: ${DATA_DIR}
24
- 配置文件保持不变,无需重新配置。
25
- 运行 "imtoagent start" 启动网关。
22
+ ✅ imtoagent upgraded successfully!
23
+ Data directory: ${DATA_DIR}
24
+ Configuration file kept as-is, no need to reconfigure.
25
+ Run "imtoagent start" to start the gateway.
26
26
  `);
27
27
  } else {
28
28
  console.log(`
29
29
  ╔══════════════════════════════════════════════════════════╗
30
30
  ║ ║
31
- ║ 🎉 imtoagent 安装成功!
31
+ ║ 🎉 imtoagent installed successfully!
32
32
  ║ ║
33
- 首次使用需要配置 IM 凭证和模型供应商
33
+ First-time use requires configuring IM credentials and a model provider
34
34
  ║ ║
35
35
  ╚══════════════════════════════════════════════════════════╝
36
36
  `);
@@ -40,13 +40,13 @@ try {
40
40
  const readline = await import('readline');
41
41
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
42
42
  const answer = await new Promise<string>(resolve => {
43
- rl.question('是否立即运行配置向导?[Y/n]: ', resolve);
43
+ rl.question('Launch the configuration wizard now? [Y/n]: ', resolve);
44
44
  });
45
45
  rl.close();
46
46
 
47
47
  const yes = answer.trim().toLowerCase();
48
48
  if (yes === '' || yes === 'y' || yes === 'yes') {
49
- console.log('\n🚀 启动配置向导...\n');
49
+ console.log('\n🚀 Launching configuration wizard...\n');
50
50
  // 调用 setup 向导
51
51
  const pkgDir = path.resolve(import.meta.dirname, '..');
52
52
  execSync('bun run bin/imtoagent setup', {
@@ -55,16 +55,16 @@ try {
55
55
  env: { ...process.env },
56
56
  });
57
57
  } else {
58
- console.log('\n稍后运行 "imtoagent setup" 即可开始配置。');
58
+ console.log('\nRun "imtoagent setup" later to configure.');
59
59
  }
60
60
  } else {
61
- console.log(' 运行 "imtoagent setup" 开始配置');
62
- console.log(' 然后运行 "imtoagent start" 启动网关\n');
61
+ console.log(' Run "imtoagent setup" to start configuring');
62
+ console.log(' Then run "imtoagent start" to start the gateway\n');
63
63
  }
64
64
  }
65
65
  } catch (e: any) {
66
- // 静默失败,不影响安装
66
+ // Silently fail, do not affect installation
67
67
  if (e.message && !e.message.includes('readline')) {
68
- console.error(`[postinstall] 提示失败: ${e.message}`);
68
+ console.error(`[postinstall] Failed to display message: ${e.message}`);
69
69
  }
70
70
  }
@@ -1,6 +1,6 @@
1
- # 身份定义
1
+ # Identity Definition
2
2
 
3
- - 我是通过 IMtoAgent 连接的 AI 编程助手
4
- - 我运行在 {{backend}} 后端
5
- - 用中文回复
6
- - 主动汇报进度,不闷头做事
3
+ - I am an AI coding assistant connected via IMtoAgent
4
+ - I run on the {{backend}} backend
5
+ - Reply in Chinese
6
+ - Proactively report progress, don't work silently
@@ -1,11 +1,11 @@
1
- # 用户画像
1
+ # User Profile
2
2
 
3
- 此文件可由 Agent 修改。当用户说"记住xxx""我偏好xxx"时,Agent 应更新此文件。
3
+ This file can be modified by the Agent. When the user says "remember xxx" or "I prefer xxx", the Agent should update this file.
4
4
 
5
- - 全栈开发者
6
- - 主要语言:TypeScriptPythonBun
7
- - 偏好简洁代码风格
5
+ - Full-stack developer
6
+ - Primary languages: TypeScript, Python, Bun
7
+ - Prefers concise code style
8
8
 
9
- ## 修改指南(Agent 专用)
9
+ ## Modification Guide (Agent Only)
10
10
 
11
- 读取此文件根据用户要求增/删/改条目保存
11
+ Read this file Add/delete/modify entries per user request Save
@@ -1,7 +1,7 @@
1
- # 硬约束规则
1
+ # Hard Constraint Rules
2
2
 
3
- 以下规则不可被覆盖或修改:
3
+ The following rules cannot be overridden or modified:
4
4
 
5
- - 项目密钥、token、密码等敏感信息不可外泄
6
- - 不可执行 `rm -rf /` 等破坏性命令
7
- - 不可修改项目文档,除非用户明确要求
5
+ - Sensitive information such as project keys, tokens, and passwords must not be leaked
6
+ - Destructive commands such as `rm -rf /` must not be executed
7
+ - Project documentation must not be modified unless explicitly requested by the user
@@ -1,3 +1,3 @@
1
- # 技能注入
1
+ # Skill Injection
2
2
 
3
- 未来功能。
3
+ Planned future functionality.
@@ -1,4 +1,4 @@
1
- # 项目环境
2
- - 工作目录: {{cwd}}
1
+ # Project Environment
2
+ - Working directory: {{cwd}}
3
3
 
4
- > 此文件由 IMtoAgent 自动生成,启动时和切换目录时更新。
4
+ > This file is auto-generated by IMtoAgent and updated on startup and when switching directories.