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.
- package/README.md +97 -97
- package/bin/imtoagent-real +197 -153
- package/bin/imtoagent.cjs +13 -5
- package/index.ts +106 -106
- package/modules/agent/claude-adapter.ts +6 -6
- package/modules/agent/claude.ts +6 -6
- package/modules/agent/codex-adapter.ts +13 -13
- package/modules/agent/codex-exec-server.ts +11 -11
- package/modules/agent/codex.ts +29 -29
- package/modules/agent/opencode-adapter.ts +17 -17
- package/modules/agent/opencode.ts +10 -10
- package/modules/capabilities.ts +33 -33
- package/modules/cli/setup.ts +164 -164
- package/modules/core/config.ts +5 -5
- package/modules/core/error.ts +8 -8
- package/modules/core/runtime.ts +10 -10
- package/modules/core/session.ts +4 -4
- package/modules/core/stats.ts +14 -14
- package/modules/core/types.ts +7 -7
- package/modules/im/feishu.ts +56 -56
- package/modules/im/telegram.ts +23 -23
- package/modules/im/wechat.ts +54 -54
- package/modules/im/wecom.ts +50 -50
- package/modules/media/feishu-inbound-adapter.ts +4 -4
- package/modules/media/resolver.ts +11 -11
- package/modules/media/telegram-inbound-adapter.ts +8 -8
- package/modules/prompt-builder.ts +12 -12
- package/modules/proxy/anthropic-proxy.ts +31 -31
- package/modules/proxy/codex-proxy.ts +18 -18
- package/modules/utils/backend-check.ts +12 -12
- package/modules/utils/paths.ts +8 -8
- package/package.json +1 -1
- package/scripts/postinstall.cjs +10 -10
- package/scripts/postinstall.ts +13 -13
- package/templates/soul.template/identity.md +5 -5
- package/templates/soul.template/profile.md +7 -7
- package/templates/soul.template/rules.md +5 -5
- package/templates/soul.template/skills.md +2 -2
- 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]
|
|
74
|
+
console.log(`[Proxy] Loaded ${providers.size} provider(s): ${[...providers.keys()].join(', ')}`);
|
|
75
75
|
} catch (e: any) {
|
|
76
|
-
console.error(`[Proxy]
|
|
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
|
|
88
|
+
console.log(`[Proxy] activeModel persisted: ${modelSpec}`);
|
|
89
89
|
} catch (e: any) {
|
|
90
|
-
console.error(`[Proxy]
|
|
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]
|
|
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]
|
|
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
|
|
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
|
|
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] ⚠️
|
|
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 (
|
|
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]
|
|
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
|
|
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
|
|
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]
|
|
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] 🧠
|
|
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 ? ` (
|
|
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]
|
|
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
|
|
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]
|
|
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]
|
|
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]
|
|
1008
|
-
console.log(`[Proxy]
|
|
1009
|
-
console.log(`[Proxy]
|
|
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
|
-
//
|
|
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(`⚠️
|
|
1018
|
-
console.log('
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
47
|
+
console.log('[Codex Proxy] Loaded config from config.json');
|
|
48
48
|
} catch (e: any) {
|
|
49
|
-
console.error(`[Codex Proxy]
|
|
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
|
|
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] ⚠️
|
|
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(`[
|
|
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(`[
|
|
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] 🗑️
|
|
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
|
|
317
|
-
//
|
|
318
|
-
const warning = '[⚠️ IMtoAgent
|
|
319
|
-
console.warn(`[Codex] ⚠️
|
|
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, //
|
|
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] 📝
|
|
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]
|
|
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(`❌
|
|
116
|
+
console.error(`❌ Unknown backend type: ${type}`);
|
|
117
117
|
return false;
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
console.log(`\n📦
|
|
121
|
-
console.log(`
|
|
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}
|
|
158
|
-
console.error(`
|
|
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}
|
|
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}
|
|
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
|
|
183
|
-
console.error(`
|
|
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(`
|
|
185
|
+
console.error(` Run manually: ${b.installHint}`);
|
|
186
186
|
}
|
|
187
187
|
return false;
|
|
188
188
|
}
|
|
189
189
|
} catch (e: any) {
|
|
190
|
-
console.error(`\n❌
|
|
190
|
+
console.error(`\n❌ Error installing ${b.label}: ${e.message || e}`);
|
|
191
191
|
return false;
|
|
192
192
|
}
|
|
193
193
|
}
|
package/modules/utils/paths.ts
CHANGED
|
@@ -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]
|
|
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] ✨
|
|
123
|
-
console.log(`[Paths]
|
|
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(`⚠️
|
|
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(`⚠️
|
|
215
|
+
if (!fs.existsSync(tplPath)) console.warn(`⚠️ Soul template not found: ${tplPath}`);
|
|
216
216
|
return tplPath;
|
|
217
217
|
}
|
|
218
218
|
|
package/package.json
CHANGED
package/scripts/postinstall.cjs
CHANGED
|
@@ -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("
|
|
25
|
-
console.log("
|
|
26
|
-
console.log('
|
|
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
|
}
|
package/scripts/postinstall.ts
CHANGED
|
@@ -19,18 +19,18 @@ try {
|
|
|
19
19
|
|
|
20
20
|
if (configExists) {
|
|
21
21
|
console.log(`
|
|
22
|
-
✅ imtoagent
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
║
|
|
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('
|
|
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🚀
|
|
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('\
|
|
58
|
+
console.log('\nRun "imtoagent setup" later to configure.');
|
|
59
59
|
}
|
|
60
60
|
} else {
|
|
61
|
-
console.log('
|
|
62
|
-
console.log('
|
|
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]
|
|
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
|
-
-
|
|
4
|
-
-
|
|
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
|
-
|
|
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
|
-
-
|
|
7
|
-
-
|
|
5
|
+
- Full-stack developer
|
|
6
|
+
- Primary languages: TypeScript, Python, Bun
|
|
7
|
+
- Prefers concise code style
|
|
8
8
|
|
|
9
|
-
##
|
|
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
|
-
-
|
|
6
|
-
-
|
|
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
|
-
-
|
|
1
|
+
# Project Environment
|
|
2
|
+
- Working directory: {{cwd}}
|
|
3
3
|
|
|
4
|
-
>
|
|
4
|
+
> This file is auto-generated by IMtoAgent and updated on startup and when switching directories.
|