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