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.
- package/README.md +97 -97
- package/bin/imtoagent-real +96 -96
- package/bin/imtoagent.cjs +1 -1
- 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 +171 -163
- 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 +39 -28
- 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
package/modules/core/config.ts
CHANGED
|
@@ -68,7 +68,7 @@ export class FileConfigManager implements ConfigManager {
|
|
|
68
68
|
const raw = fs.readFileSync(configPath, 'utf-8');
|
|
69
69
|
this.rawConfig = JSON.parse(raw);
|
|
70
70
|
} catch (e: any) {
|
|
71
|
-
console.error(`[Config]
|
|
71
|
+
console.error(`[Config] Failed to load config.json: ${e.message}`);
|
|
72
72
|
this.rawConfig = {} as RawConfig;
|
|
73
73
|
}
|
|
74
74
|
|
|
@@ -87,7 +87,7 @@ export class FileConfigManager implements ConfigManager {
|
|
|
87
87
|
});
|
|
88
88
|
}
|
|
89
89
|
} catch (e: any) {
|
|
90
|
-
console.error(`[Config]
|
|
90
|
+
console.error(`[Config] Failed to load providers.json: ${e.message}`);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
// 加载默认 providers
|
|
@@ -130,7 +130,7 @@ export class FileConfigManager implements ConfigManager {
|
|
|
130
130
|
this.botConfigs.set(botKey, {});
|
|
131
131
|
}
|
|
132
132
|
} catch (e: any) {
|
|
133
|
-
console.error(`[Config]
|
|
133
|
+
console.error(`[Config] Failed to load bot ${botKey} config: ${e.message}`);
|
|
134
134
|
this.botConfigs.set(botKey, {});
|
|
135
135
|
}
|
|
136
136
|
}
|
|
@@ -147,7 +147,7 @@ export class FileConfigManager implements ConfigManager {
|
|
|
147
147
|
this.botConfigs.set(botKey, config);
|
|
148
148
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
149
149
|
} catch (e: any) {
|
|
150
|
-
console.error(`[Config]
|
|
150
|
+
console.error(`[Config] Failed to save bot ${botKey} config: ${e.message}`);
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
153
|
|
|
@@ -261,7 +261,7 @@ export class FileConfigManager implements ConfigManager {
|
|
|
261
261
|
const configPath = path.join(getDataDir(), 'config.json');
|
|
262
262
|
fs.writeFileSync(configPath, JSON.stringify(this.rawConfig, null, 2) + '\n');
|
|
263
263
|
} catch (e: any) {
|
|
264
|
-
console.error(`[Config]
|
|
264
|
+
console.error(`[Config] Failed to save global activeModel: ${e.message}`);
|
|
265
265
|
}
|
|
266
266
|
}
|
|
267
267
|
}
|
package/modules/core/error.ts
CHANGED
|
@@ -25,7 +25,7 @@ export class DefaultErrorHandler implements ErrorHandler {
|
|
|
25
25
|
const errMsg = error.message || String(error);
|
|
26
26
|
const backend = ctx.backend;
|
|
27
27
|
|
|
28
|
-
console.error(`[Error] ${backend}
|
|
28
|
+
console.error(`[Error] ${backend} call failed (attempt ${ctx.attempt}): ${errMsg}`);
|
|
29
29
|
|
|
30
30
|
// 提取 HTTP 状态码
|
|
31
31
|
const statusCode = this.extractStatusCode(error);
|
|
@@ -35,7 +35,7 @@ export class DefaultErrorHandler implements ErrorHandler {
|
|
|
35
35
|
if (ctx.attempt < 2) {
|
|
36
36
|
// 网络超时、5xx → 重试
|
|
37
37
|
if (isTimeout || statusCode >= 500) {
|
|
38
|
-
console.log(`[Error]
|
|
38
|
+
console.log(`[Error] Will retry ${backend} call (${ctx.attempt + 1}/2)`);
|
|
39
39
|
return { type: 'retry', maxAttempts: 2 };
|
|
40
40
|
}
|
|
41
41
|
|
|
@@ -43,7 +43,7 @@ export class DefaultErrorHandler implements ErrorHandler {
|
|
|
43
43
|
if (statusCode === 429) {
|
|
44
44
|
const retryAfter = this.extractRetryAfter(error);
|
|
45
45
|
if (retryAfter > 0) {
|
|
46
|
-
console.log(`[Error] 429
|
|
46
|
+
console.log(`[Error] 429 rate limited, waiting ${retryAfter}ms before retry`);
|
|
47
47
|
await this.sleep(retryAfter);
|
|
48
48
|
}
|
|
49
49
|
return { type: 'retry', maxAttempts: 2 };
|
|
@@ -98,24 +98,24 @@ export class DefaultErrorHandler implements ErrorHandler {
|
|
|
98
98
|
const statusCode = this.extractStatusCode(error);
|
|
99
99
|
|
|
100
100
|
if (statusCode === 401 || statusCode === 403) {
|
|
101
|
-
return `⚠️ ${backend}
|
|
101
|
+
return `⚠️ ${backend} backend authentication failed. Please ask an admin to check the configuration.`;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
if (statusCode === 429) {
|
|
105
|
-
return `⚠️
|
|
105
|
+
return `⚠️ Too many requests. Please try again later.`;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
if (statusCode >= 500) {
|
|
109
|
-
return `⚠️ ${backend}
|
|
109
|
+
return `⚠️ ${backend} server is temporarily unavailable. Please try again later.`;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
if (this.isTimeoutError(error)) {
|
|
113
|
-
return `⚠️
|
|
113
|
+
return `⚠️ Request timed out. The backend may be processing a complex task. Please try again later.`;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
// 通用错误
|
|
117
117
|
const shortMsg = error.message.slice(0, 100);
|
|
118
|
-
return `⚠️
|
|
118
|
+
return `⚠️ Error processing message: ${shortMsg}`;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
private sleep(ms: number): Promise<void> {
|
package/modules/core/runtime.ts
CHANGED
|
@@ -56,7 +56,7 @@ function checkRestartSignal(): { triggered: boolean; reason: string } {
|
|
|
56
56
|
// 兼容纯文本格式
|
|
57
57
|
signal = { reason: raw, timestamp: Date.now() };
|
|
58
58
|
}
|
|
59
|
-
return { triggered: true, reason: signal.reason || '
|
|
59
|
+
return { triggered: true, reason: signal.reason || 'Unknown reason' };
|
|
60
60
|
} catch {
|
|
61
61
|
return { triggered: false, reason: '' };
|
|
62
62
|
}
|
|
@@ -92,7 +92,7 @@ export class AgentRuntime {
|
|
|
92
92
|
*/
|
|
93
93
|
registerAdapter(backend: string, adapter: AgentAdapter): void {
|
|
94
94
|
this.adapters.set(backend, adapter);
|
|
95
|
-
console.log(`[Runtime]
|
|
95
|
+
console.log(`[Runtime] Registered adapter: ${backend} → ${adapter.name}`);
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
/**
|
|
@@ -136,14 +136,14 @@ export class AgentRuntime {
|
|
|
136
136
|
session.metadata = {};
|
|
137
137
|
session.startFresh = false;
|
|
138
138
|
session.running = false;
|
|
139
|
-
console.log(`[Runtime] startFresh:
|
|
139
|
+
console.log(`[Runtime] startFresh: cleared old session for ${ctx.chatId}`);
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
// 3. 重置统计
|
|
143
143
|
this.config.statsTracker.resetForCall(session);
|
|
144
144
|
|
|
145
145
|
// 4. 发送进度提示
|
|
146
|
-
await ctx.sendProgress('💭
|
|
146
|
+
await ctx.sendProgress('💭 Thinking...');
|
|
147
147
|
|
|
148
148
|
session.running = true;
|
|
149
149
|
|
|
@@ -203,10 +203,10 @@ export class AgentRuntime {
|
|
|
203
203
|
const now = Date.now();
|
|
204
204
|
if (now - lastRestartTime < RESTART_COOLDOWN_MS) {
|
|
205
205
|
const remaining = Math.ceil((RESTART_COOLDOWN_MS - (now - lastRestartTime)) / 1000);
|
|
206
|
-
console.log(`[Runtime] ⏳ Agent
|
|
206
|
+
console.log(`[Runtime] ⏳ Agent restart request ignored (cooldown, retry in ${remaining}s): ${signal.reason}`);
|
|
207
207
|
} else {
|
|
208
208
|
lastRestartTime = now;
|
|
209
|
-
console.log(`[Runtime] 🔄 Agent
|
|
209
|
+
console.log(`[Runtime] 🔄 Agent requested restart: ${signal.reason}`);
|
|
210
210
|
return { restart: true, reason: signal.reason };
|
|
211
211
|
}
|
|
212
212
|
}
|
|
@@ -214,7 +214,7 @@ export class AgentRuntime {
|
|
|
214
214
|
return { restart: false };
|
|
215
215
|
|
|
216
216
|
} catch (error: any) {
|
|
217
|
-
console.error(`[Runtime]
|
|
217
|
+
console.error(`[Runtime] Failed to process message (attempt ${attempt}): ${error.message}`);
|
|
218
218
|
|
|
219
219
|
// 7. 错误处理
|
|
220
220
|
const errorCtx: ErrorContext = {
|
|
@@ -226,12 +226,12 @@ export class AgentRuntime {
|
|
|
226
226
|
const action = await this.config.errorHandler.handle(ctx.chatId, error, errorCtx);
|
|
227
227
|
|
|
228
228
|
if (action.type === 'retry') {
|
|
229
|
-
console.log(`[Runtime]
|
|
229
|
+
console.log(`[Runtime] Retrying ${adapter.name} call`);
|
|
230
230
|
continue;
|
|
231
231
|
}
|
|
232
232
|
|
|
233
233
|
if (action.type === 'fallback') {
|
|
234
|
-
console.log(`[Runtime]
|
|
234
|
+
console.log(`[Runtime] Fallback to ${action.adapter}`);
|
|
235
235
|
// Phase 2 实现 fallback 逻辑
|
|
236
236
|
const fallbackAdapter = this.adapters.get(action.adapter);
|
|
237
237
|
if (fallbackAdapter) {
|
|
@@ -277,6 +277,6 @@ export class AgentRuntime {
|
|
|
277
277
|
if (adapter?.healthCheck) {
|
|
278
278
|
return adapter.healthCheck();
|
|
279
279
|
}
|
|
280
|
-
return true; //
|
|
280
|
+
return true; // Assume healthy by default
|
|
281
281
|
}
|
|
282
282
|
}
|
package/modules/core/session.ts
CHANGED
|
@@ -139,7 +139,7 @@ export class FileSessionManager implements SessionManager {
|
|
|
139
139
|
session = this.createNewSession(chatId, userId);
|
|
140
140
|
}
|
|
141
141
|
} catch (e: any) {
|
|
142
|
-
console.error(`[Session]
|
|
142
|
+
console.error(`[Session] Failed to load ${chatId}: ${e.message}, creating new session`);
|
|
143
143
|
session = this.createNewSession(chatId, userId);
|
|
144
144
|
}
|
|
145
145
|
} else {
|
|
@@ -207,7 +207,7 @@ export class FileSessionManager implements SessionManager {
|
|
|
207
207
|
try {
|
|
208
208
|
fs.writeFileSync(filePath, JSON.stringify(output, null, 2));
|
|
209
209
|
} catch (e: any) {
|
|
210
|
-
console.error(`[Session]
|
|
210
|
+
console.error(`[Session] Failed to persist ${session.chatId}: ${e.message}`);
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
213
|
|
|
@@ -225,7 +225,7 @@ export class FileSessionManager implements SessionManager {
|
|
|
225
225
|
fs.unlinkSync(filePath);
|
|
226
226
|
}
|
|
227
227
|
} catch (e: any) {
|
|
228
|
-
console.error(`[Session]
|
|
228
|
+
console.error(`[Session] Failed to delete ${chatId}: ${e.message}`);
|
|
229
229
|
}
|
|
230
230
|
}
|
|
231
231
|
|
|
@@ -244,7 +244,7 @@ export class FileSessionManager implements SessionManager {
|
|
|
244
244
|
|
|
245
245
|
for (const chatId of toRemove) {
|
|
246
246
|
botCache.delete(chatId);
|
|
247
|
-
console.log(`[Session]
|
|
247
|
+
console.log(`[Session] Cleaning up idle session: ${chatId}`);
|
|
248
248
|
}
|
|
249
249
|
}
|
|
250
250
|
|
package/modules/core/stats.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// ================================================================
|
|
2
|
-
// StatsTracker —
|
|
2
|
+
// StatsTracker — call statistics tracking
|
|
3
3
|
// ================================================================
|
|
4
|
-
//
|
|
4
|
+
// Unified token/cost/duration stats management across agents
|
|
5
5
|
// ================================================================
|
|
6
6
|
|
|
7
7
|
import type { Session, StatsTracker, CallStats } from './types';
|
|
@@ -12,14 +12,14 @@ import type { Session, StatsTracker, CallStats } from './types';
|
|
|
12
12
|
|
|
13
13
|
export class DefaultStatsTracker implements StatsTracker {
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
15
|
+
* Reset call stats (at call start)
|
|
16
16
|
*/
|
|
17
17
|
resetForCall(session: Session): void {
|
|
18
18
|
session.stats.calls += 1;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
22
|
+
* Accumulate stats (after call success)
|
|
23
23
|
*/
|
|
24
24
|
accumulate(session: Session, usage: {
|
|
25
25
|
inputTokens: number;
|
|
@@ -39,35 +39,35 @@ export class DefaultStatsTracker implements StatsTracker {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
|
-
*
|
|
42
|
+
* Generate stats summary string (sent to user)
|
|
43
43
|
*/
|
|
44
44
|
formatSummary(session: Session): string {
|
|
45
45
|
const s = session.stats;
|
|
46
46
|
const parts: string[] = [];
|
|
47
47
|
|
|
48
|
-
//
|
|
49
|
-
parts.push(`📊
|
|
48
|
+
// Call count
|
|
49
|
+
parts.push(`📊 ${s.calls} calls`);
|
|
50
50
|
|
|
51
|
-
// Token
|
|
51
|
+
// Token usage
|
|
52
52
|
const totalTokens = s.totalInputTokens + s.totalOutputTokens;
|
|
53
53
|
if (totalTokens > 0) {
|
|
54
54
|
parts.push(`Token ${this.formatTokens(totalTokens)}`);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
//
|
|
57
|
+
// Cost
|
|
58
58
|
if (s.totalCostUSD > 0) {
|
|
59
|
-
parts.push(
|
|
59
|
+
parts.push(`Cost $${s.totalCostUSD.toFixed(4)}`);
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
//
|
|
62
|
+
// Duration
|
|
63
63
|
if (s.totalDurationMs > 0) {
|
|
64
|
-
parts.push(
|
|
64
|
+
parts.push(`Duration ${this.formatDuration(s.totalDurationMs)}`);
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
return parts.join(' | ');
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
/**
|
|
70
|
+
/** Format token count */
|
|
71
71
|
private formatTokens(count: number): string {
|
|
72
72
|
if (count >= 1_000_000) {
|
|
73
73
|
return `${(count / 1_000_000).toFixed(1)}M`;
|
|
@@ -78,7 +78,7 @@ export class DefaultStatsTracker implements StatsTracker {
|
|
|
78
78
|
return `${count}`;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
/**
|
|
81
|
+
/** Format duration */
|
|
82
82
|
private formatDuration(ms: number): string {
|
|
83
83
|
const secs = ms / 1000;
|
|
84
84
|
if (secs >= 3600) {
|
package/modules/core/types.ts
CHANGED
|
@@ -81,9 +81,9 @@ export interface MessageAttachment {
|
|
|
81
81
|
export function buildAttachmentHint(attachments: MessageAttachment[]): string {
|
|
82
82
|
return attachments.map((att, i) => {
|
|
83
83
|
const icon = att.type === 'image' ? '🖼️' : att.type === 'audio' ? '🎵' : '📎';
|
|
84
|
-
const typeLabel = att.type === 'image' ? '
|
|
84
|
+
const typeLabel = att.type === 'image' ? 'Image' : att.type === 'audio' ? 'Voice' : 'File';
|
|
85
85
|
const detail = att.filename ? ` (${att.filename})` : '';
|
|
86
|
-
const dur = att.durationMs ? ` [
|
|
86
|
+
const dur = att.durationMs ? ` [Duration: ${Math.round(att.durationMs / 1000)}s]` : '';
|
|
87
87
|
const mimeInfo = att.mimeType ? ` [${att.mimeType}]` : '';
|
|
88
88
|
|
|
89
89
|
// 优先使用预计算的提示(由 MediaResolver 按文件类型生成)
|
|
@@ -91,15 +91,15 @@ export function buildAttachmentHint(attachments: MessageAttachment[]): string {
|
|
|
91
91
|
if (att.hint) {
|
|
92
92
|
hint = `\n> 💡 ${att.hint}`;
|
|
93
93
|
} else if (att.type === 'image') {
|
|
94
|
-
hint = `\n> 💡
|
|
94
|
+
hint = `\n> 💡 Image saved locally at: \`${att.localPath}\`, format: ${att.mimeType || 'unknown'}, use the image viewer tool to open`;
|
|
95
95
|
} else if (att.type === 'audio') {
|
|
96
|
-
hint = `\n> 💡
|
|
96
|
+
hint = `\n> 💡 Voice file path: \`${att.localPath}\`, use speech-to-text tool to process`;
|
|
97
97
|
} else {
|
|
98
|
-
const ext = att.filename ?
|
|
99
|
-
hint = `\n> 💡
|
|
98
|
+
const ext = att.filename ? `, extension: ${att.filename.split('.').pop()}` : '';
|
|
99
|
+
hint = `\n> 💡 File path: \`${att.localPath}\`, readable with file reading tool${ext}`;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
return `${icon} [
|
|
102
|
+
return `${icon} [User message with ${typeLabel} #${i + 1}]${detail}${mimeInfo}${dur}${hint}`;
|
|
103
103
|
}).join('\n\n');
|
|
104
104
|
}
|
|
105
105
|
|