kernelbot 1.0.28 → 1.0.32
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/.env.example +4 -0
- package/bin/kernel.js +68 -7
- package/config.example.yaml +45 -1
- package/package.json +1 -1
- package/src/agent.js +613 -28
- package/src/bot.js +643 -7
- package/src/claude-auth.js +93 -0
- package/src/coder.js +48 -6
- package/src/life/codebase.js +388 -0
- package/src/life/engine.js +1317 -0
- package/src/life/evolution.js +244 -0
- package/src/life/improvements.js +81 -0
- package/src/life/journal.js +109 -0
- package/src/life/memory.js +283 -0
- package/src/life/share-queue.js +136 -0
- package/src/prompts/orchestrator.js +71 -5
- package/src/prompts/workers.js +65 -5
- package/src/providers/models.js +8 -1
- package/src/self.js +122 -0
- package/src/services/stt.js +139 -0
- package/src/services/tts.js +124 -0
- package/src/swarm/job-manager.js +54 -7
- package/src/swarm/job.js +19 -1
- package/src/swarm/worker-registry.js +5 -0
- package/src/tools/coding.js +6 -1
- package/src/tools/orchestrator-tools.js +93 -21
- package/src/tools/os.js +14 -1
- package/src/utils/config.js +105 -2
- package/src/worker.js +98 -5
package/src/bot.js
CHANGED
|
@@ -12,6 +12,9 @@ import {
|
|
|
12
12
|
deleteCustomSkill,
|
|
13
13
|
getCustomSkills,
|
|
14
14
|
} from './skills/custom.js';
|
|
15
|
+
import { TTSService } from './services/tts.js';
|
|
16
|
+
import { STTService } from './services/stt.js';
|
|
17
|
+
import { getClaudeAuthStatus, claudeLogout } from './claude-auth.js';
|
|
15
18
|
|
|
16
19
|
function splitMessage(text, maxLength = 4096) {
|
|
17
20
|
if (text.length <= maxLength) return [text];
|
|
@@ -50,12 +53,19 @@ class ChatQueue {
|
|
|
50
53
|
}
|
|
51
54
|
}
|
|
52
55
|
|
|
53
|
-
export function startBot(config, agent, conversationManager, jobManager, automationManager) {
|
|
56
|
+
export function startBot(config, agent, conversationManager, jobManager, automationManager, lifeDeps = {}) {
|
|
57
|
+
const { lifeEngine, memoryManager, journalManager, shareQueue, evolutionTracker, codebaseKnowledge } = lifeDeps;
|
|
54
58
|
const logger = getLogger();
|
|
55
59
|
const bot = new TelegramBot(config.telegram.bot_token, { polling: true });
|
|
56
60
|
const chatQueue = new ChatQueue();
|
|
57
61
|
const batchWindowMs = config.telegram.batch_window_ms || 3000;
|
|
58
62
|
|
|
63
|
+
// Initialize voice services
|
|
64
|
+
const ttsService = new TTSService(config);
|
|
65
|
+
const sttService = new STTService(config);
|
|
66
|
+
if (ttsService.isAvailable()) logger.info('[Bot] TTS service enabled (ElevenLabs)');
|
|
67
|
+
if (sttService.isAvailable()) logger.info('[Bot] STT service enabled');
|
|
68
|
+
|
|
59
69
|
// Per-chat message batching: chatId -> { messages[], timer, resolve }
|
|
60
70
|
const chatBatches = new Map();
|
|
61
71
|
|
|
@@ -68,6 +78,26 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
68
78
|
// Load custom skills from disk
|
|
69
79
|
loadCustomSkills();
|
|
70
80
|
|
|
81
|
+
// Register commands in Telegram's menu button
|
|
82
|
+
bot.setMyCommands([
|
|
83
|
+
{ command: 'brain', description: 'Switch worker AI model/provider' },
|
|
84
|
+
{ command: 'orchestrator', description: 'Switch orchestrator AI model/provider' },
|
|
85
|
+
{ command: 'claudemodel', description: 'Switch Claude Code model' },
|
|
86
|
+
{ command: 'claude', description: 'Manage Claude Code authentication' },
|
|
87
|
+
{ command: 'skills', description: 'Browse and activate persona skills' },
|
|
88
|
+
{ command: 'jobs', description: 'List running and recent jobs' },
|
|
89
|
+
{ command: 'cancel', description: 'Cancel running job(s)' },
|
|
90
|
+
{ command: 'auto', description: 'Manage recurring automations' },
|
|
91
|
+
{ command: 'life', description: 'Inner life engine status and control' },
|
|
92
|
+
{ command: 'journal', description: 'View today\'s journal or a past date' },
|
|
93
|
+
{ command: 'memories', description: 'View recent memories or search' },
|
|
94
|
+
{ command: 'evolution', description: 'Self-evolution status, history, and lessons' },
|
|
95
|
+
{ command: 'context', description: 'Show all models, auth, and context info' },
|
|
96
|
+
{ command: 'clean', description: 'Clear conversation and start fresh' },
|
|
97
|
+
{ command: 'history', description: 'Show message count in memory' },
|
|
98
|
+
{ command: 'help', description: 'Show all available commands' },
|
|
99
|
+
]).catch((err) => logger.warn(`Failed to set bot commands menu: ${err.message}`));
|
|
100
|
+
|
|
71
101
|
logger.info('Telegram bot started with polling');
|
|
72
102
|
|
|
73
103
|
// Initialize automation manager with bot context
|
|
@@ -100,7 +130,7 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
100
130
|
});
|
|
101
131
|
return edited.message_id;
|
|
102
132
|
} catch {
|
|
103
|
-
|
|
133
|
+
// Edit failed — fall through to send new message
|
|
104
134
|
}
|
|
105
135
|
}
|
|
106
136
|
}
|
|
@@ -142,6 +172,12 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
142
172
|
// Track pending brain API key input: chatId -> { providerKey, modelId }
|
|
143
173
|
const pendingBrainKey = new Map();
|
|
144
174
|
|
|
175
|
+
// Track pending orchestrator API key input: chatId -> { providerKey, modelId }
|
|
176
|
+
const pendingOrchKey = new Map();
|
|
177
|
+
|
|
178
|
+
// Track pending Claude Code auth input: chatId -> { type: 'api_key' | 'oauth_token' }
|
|
179
|
+
const pendingClaudeAuth = new Map();
|
|
180
|
+
|
|
145
181
|
// Track pending custom skill creation: chatId -> { step: 'name' | 'prompt', name?: string }
|
|
146
182
|
const pendingCustomSkill = new Map();
|
|
147
183
|
|
|
@@ -451,6 +487,140 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
451
487
|
const msg = deleted ? `🗑️ Deleted automation \`${autoId}\`` : `Automation \`${autoId}\` not found.`;
|
|
452
488
|
await bot.editMessageText(msg, { chat_id: chatId, message_id: query.message.message_id, parse_mode: 'Markdown' });
|
|
453
489
|
await bot.answerCallbackQuery(query.id);
|
|
490
|
+
|
|
491
|
+
// ── Orchestrator callbacks ─────────────────────────────────────
|
|
492
|
+
} else if (data.startsWith('orch_provider:')) {
|
|
493
|
+
const providerKey = data.split(':')[1];
|
|
494
|
+
const providerDef = PROVIDERS[providerKey];
|
|
495
|
+
if (!providerDef) {
|
|
496
|
+
await bot.answerCallbackQuery(query.id, { text: 'Unknown provider' });
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const modelButtons = providerDef.models.map((m) => ([{
|
|
501
|
+
text: m.label,
|
|
502
|
+
callback_data: `orch_model:${providerKey}:${m.id}`,
|
|
503
|
+
}]));
|
|
504
|
+
modelButtons.push([{ text: 'Cancel', callback_data: 'orch_cancel' }]);
|
|
505
|
+
|
|
506
|
+
await bot.editMessageText(`Select a *${providerDef.name}* model for orchestrator:`, {
|
|
507
|
+
chat_id: chatId,
|
|
508
|
+
message_id: query.message.message_id,
|
|
509
|
+
parse_mode: 'Markdown',
|
|
510
|
+
reply_markup: { inline_keyboard: modelButtons },
|
|
511
|
+
});
|
|
512
|
+
await bot.answerCallbackQuery(query.id);
|
|
513
|
+
|
|
514
|
+
} else if (data.startsWith('orch_model:')) {
|
|
515
|
+
const [, providerKey, modelId] = data.split(':');
|
|
516
|
+
const providerDef = PROVIDERS[providerKey];
|
|
517
|
+
const modelEntry = providerDef?.models.find((m) => m.id === modelId);
|
|
518
|
+
const modelLabel = modelEntry ? modelEntry.label : modelId;
|
|
519
|
+
|
|
520
|
+
await bot.editMessageText(
|
|
521
|
+
`⏳ Verifying *${providerDef.name}* / *${modelLabel}* for orchestrator...`,
|
|
522
|
+
{ chat_id: chatId, message_id: query.message.message_id, parse_mode: 'Markdown' },
|
|
523
|
+
);
|
|
524
|
+
|
|
525
|
+
logger.info(`[Bot] Orchestrator switch request: ${providerKey}/${modelId} from chat ${chatId}`);
|
|
526
|
+
const result = await agent.switchOrchestrator(providerKey, modelId);
|
|
527
|
+
if (result && typeof result === 'object' && result.error) {
|
|
528
|
+
const current = agent.getOrchestratorInfo();
|
|
529
|
+
await bot.editMessageText(
|
|
530
|
+
`❌ Failed to switch: ${result.error}\n\nKeeping *${current.providerName}* / *${current.modelLabel}*`,
|
|
531
|
+
{ chat_id: chatId, message_id: query.message.message_id, parse_mode: 'Markdown' },
|
|
532
|
+
);
|
|
533
|
+
} else if (result) {
|
|
534
|
+
// API key missing
|
|
535
|
+
logger.info(`[Bot] Orchestrator switch needs API key: ${result} for ${providerKey}/${modelId}`);
|
|
536
|
+
pendingOrchKey.set(chatId, { providerKey, modelId });
|
|
537
|
+
await bot.editMessageText(
|
|
538
|
+
`🔑 *${providerDef.name}* API key is required.\n\nPlease send your \`${result}\` now.\n\nOr send *cancel* to abort.`,
|
|
539
|
+
{ chat_id: chatId, message_id: query.message.message_id, parse_mode: 'Markdown' },
|
|
540
|
+
);
|
|
541
|
+
} else {
|
|
542
|
+
const info = agent.getOrchestratorInfo();
|
|
543
|
+
await bot.editMessageText(
|
|
544
|
+
`🎛️ Orchestrator switched to *${info.providerName}* / *${info.modelLabel}*`,
|
|
545
|
+
{ chat_id: chatId, message_id: query.message.message_id, parse_mode: 'Markdown' },
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
await bot.answerCallbackQuery(query.id);
|
|
549
|
+
|
|
550
|
+
} else if (data === 'orch_cancel') {
|
|
551
|
+
pendingOrchKey.delete(chatId);
|
|
552
|
+
await bot.editMessageText('Orchestrator change cancelled.', {
|
|
553
|
+
chat_id: chatId, message_id: query.message.message_id,
|
|
554
|
+
});
|
|
555
|
+
await bot.answerCallbackQuery(query.id);
|
|
556
|
+
|
|
557
|
+
// ── Claude Code model callbacks ────────────────────────────────
|
|
558
|
+
} else if (data.startsWith('ccmodel:')) {
|
|
559
|
+
const modelId = data.slice('ccmodel:'.length);
|
|
560
|
+
agent.switchClaudeCodeModel(modelId);
|
|
561
|
+
const info = agent.getClaudeCodeInfo();
|
|
562
|
+
logger.info(`[Bot] Claude Code model switched to ${info.modelLabel} from chat ${chatId}`);
|
|
563
|
+
await bot.editMessageText(
|
|
564
|
+
`💻 Claude Code model switched to *${info.modelLabel}*`,
|
|
565
|
+
{ chat_id: chatId, message_id: query.message.message_id, parse_mode: 'Markdown' },
|
|
566
|
+
);
|
|
567
|
+
await bot.answerCallbackQuery(query.id);
|
|
568
|
+
|
|
569
|
+
} else if (data === 'ccmodel_cancel') {
|
|
570
|
+
await bot.editMessageText('Claude Code model change cancelled.', {
|
|
571
|
+
chat_id: chatId, message_id: query.message.message_id,
|
|
572
|
+
});
|
|
573
|
+
await bot.answerCallbackQuery(query.id);
|
|
574
|
+
|
|
575
|
+
// ── Claude Code auth callbacks ─────────────────────────────────
|
|
576
|
+
} else if (data === 'claude_apikey') {
|
|
577
|
+
pendingClaudeAuth.set(chatId, { type: 'api_key' });
|
|
578
|
+
await bot.editMessageText(
|
|
579
|
+
'🔑 Send your *Anthropic API key* for Claude Code.\n\nOr send *cancel* to abort.',
|
|
580
|
+
{ chat_id: chatId, message_id: query.message.message_id, parse_mode: 'Markdown' },
|
|
581
|
+
);
|
|
582
|
+
await bot.answerCallbackQuery(query.id);
|
|
583
|
+
|
|
584
|
+
} else if (data === 'claude_oauth') {
|
|
585
|
+
pendingClaudeAuth.set(chatId, { type: 'oauth_token' });
|
|
586
|
+
await bot.editMessageText(
|
|
587
|
+
'🔑 Run `claude setup-token` locally and paste the *OAuth token* here.\n\nThis uses your Pro/Max subscription instead of an API key.\n\nOr send *cancel* to abort.',
|
|
588
|
+
{ chat_id: chatId, message_id: query.message.message_id, parse_mode: 'Markdown' },
|
|
589
|
+
);
|
|
590
|
+
await bot.answerCallbackQuery(query.id);
|
|
591
|
+
|
|
592
|
+
} else if (data === 'claude_system') {
|
|
593
|
+
agent.setClaudeCodeAuth('system', null);
|
|
594
|
+
logger.info(`[Bot] Claude Code auth set to system from chat ${chatId}`);
|
|
595
|
+
await bot.editMessageText(
|
|
596
|
+
'🔓 Claude Code set to *system auth* — using host machine credentials.',
|
|
597
|
+
{ chat_id: chatId, message_id: query.message.message_id, parse_mode: 'Markdown' },
|
|
598
|
+
);
|
|
599
|
+
await bot.answerCallbackQuery(query.id);
|
|
600
|
+
|
|
601
|
+
} else if (data === 'claude_status') {
|
|
602
|
+
await bot.answerCallbackQuery(query.id, { text: 'Checking...' });
|
|
603
|
+
const status = await getClaudeAuthStatus();
|
|
604
|
+
const authConfig = agent.getClaudeAuthConfig();
|
|
605
|
+
await bot.editMessageText(
|
|
606
|
+
`🔐 *Claude Code Auth*\n\n*Mode:* ${authConfig.mode}\n*Credential:* ${authConfig.credential}\n\n*CLI Status:*\n\`\`\`\n${status.output.slice(0, 500)}\n\`\`\``,
|
|
607
|
+
{ chat_id: chatId, message_id: query.message.message_id, parse_mode: 'Markdown' },
|
|
608
|
+
);
|
|
609
|
+
|
|
610
|
+
} else if (data === 'claude_logout') {
|
|
611
|
+
await bot.answerCallbackQuery(query.id, { text: 'Logging out...' });
|
|
612
|
+
const result = await claudeLogout();
|
|
613
|
+
await bot.editMessageText(
|
|
614
|
+
`🚪 Claude Code logout: ${result.output || 'Done.'}`,
|
|
615
|
+
{ chat_id: chatId, message_id: query.message.message_id, parse_mode: 'Markdown' },
|
|
616
|
+
);
|
|
617
|
+
|
|
618
|
+
} else if (data === 'claude_cancel') {
|
|
619
|
+
pendingClaudeAuth.delete(chatId);
|
|
620
|
+
await bot.editMessageText('Claude Code auth management dismissed.', {
|
|
621
|
+
chat_id: chatId, message_id: query.message.message_id,
|
|
622
|
+
});
|
|
623
|
+
await bot.answerCallbackQuery(query.id);
|
|
454
624
|
}
|
|
455
625
|
} catch (err) {
|
|
456
626
|
logger.error(`[Bot] Callback query error for "${data}" in chat ${chatId}: ${err.message}`);
|
|
@@ -546,6 +716,32 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
546
716
|
}
|
|
547
717
|
}
|
|
548
718
|
|
|
719
|
+
// Handle voice messages — transcribe and process as text
|
|
720
|
+
if (msg.voice && sttService.isAvailable()) {
|
|
721
|
+
logger.info(`[Bot] Voice message from ${username} (${userId}) in chat ${chatId}, duration: ${msg.voice.duration}s`);
|
|
722
|
+
let tmpPath = null;
|
|
723
|
+
try {
|
|
724
|
+
const fileUrl = await bot.getFileLink(msg.voice.file_id);
|
|
725
|
+
tmpPath = await sttService.downloadAudio(fileUrl);
|
|
726
|
+
const transcribed = await sttService.transcribe(tmpPath);
|
|
727
|
+
if (!transcribed) {
|
|
728
|
+
await bot.sendMessage(chatId, 'Could not transcribe the voice message. Please try again or send text.');
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
logger.info(`[Bot] Transcribed voice: "${transcribed.slice(0, 100)}" from ${username} in chat ${chatId}`);
|
|
732
|
+
// Show the user what was heard
|
|
733
|
+
await bot.sendMessage(chatId, `🎤 _"${transcribed}"_`, { parse_mode: 'Markdown' });
|
|
734
|
+
// Process as a normal text message (fall through below)
|
|
735
|
+
msg.text = transcribed;
|
|
736
|
+
} catch (err) {
|
|
737
|
+
logger.error(`[Bot] Voice transcription failed: ${err.message}`);
|
|
738
|
+
await bot.sendMessage(chatId, 'Failed to process voice message. Please try sending text instead.');
|
|
739
|
+
return;
|
|
740
|
+
} finally {
|
|
741
|
+
if (tmpPath) sttService.cleanup(tmpPath);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
549
745
|
if (!msg.text) return; // ignore non-text (and non-document) messages
|
|
550
746
|
|
|
551
747
|
let text = msg.text.trim();
|
|
@@ -582,6 +778,60 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
582
778
|
return;
|
|
583
779
|
}
|
|
584
780
|
|
|
781
|
+
// Handle pending orchestrator API key input
|
|
782
|
+
if (pendingOrchKey.has(chatId)) {
|
|
783
|
+
const pending = pendingOrchKey.get(chatId);
|
|
784
|
+
pendingOrchKey.delete(chatId);
|
|
785
|
+
|
|
786
|
+
if (text.toLowerCase() === 'cancel') {
|
|
787
|
+
logger.info(`[Bot] Orchestrator key input cancelled by ${username} in chat ${chatId}`);
|
|
788
|
+
await bot.sendMessage(chatId, 'Orchestrator change cancelled.');
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
logger.info(`[Bot] Orchestrator key received for ${pending.providerKey}/${pending.modelId} from ${username} in chat ${chatId}`);
|
|
793
|
+
await bot.sendMessage(chatId, '⏳ Verifying API key...');
|
|
794
|
+
const switchResult = await agent.switchOrchestratorWithKey(pending.providerKey, pending.modelId, text);
|
|
795
|
+
if (switchResult && switchResult.error) {
|
|
796
|
+
const current = agent.getOrchestratorInfo();
|
|
797
|
+
await bot.sendMessage(
|
|
798
|
+
chatId,
|
|
799
|
+
`❌ Failed to switch: ${switchResult.error}\n\nKeeping *${current.providerName}* / *${current.modelLabel}*`,
|
|
800
|
+
{ parse_mode: 'Markdown' },
|
|
801
|
+
);
|
|
802
|
+
} else {
|
|
803
|
+
const info = agent.getOrchestratorInfo();
|
|
804
|
+
await bot.sendMessage(
|
|
805
|
+
chatId,
|
|
806
|
+
`🎛️ Orchestrator switched to *${info.providerName}* / *${info.modelLabel}*\n\nAPI key saved.`,
|
|
807
|
+
{ parse_mode: 'Markdown' },
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// Handle pending Claude Code auth input
|
|
814
|
+
if (pendingClaudeAuth.has(chatId)) {
|
|
815
|
+
const pending = pendingClaudeAuth.get(chatId);
|
|
816
|
+
pendingClaudeAuth.delete(chatId);
|
|
817
|
+
|
|
818
|
+
if (text.toLowerCase() === 'cancel') {
|
|
819
|
+
logger.info(`[Bot] Claude Code auth input cancelled by ${username} in chat ${chatId}`);
|
|
820
|
+
await bot.sendMessage(chatId, 'Claude Code auth setup cancelled.');
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
agent.setClaudeCodeAuth(pending.type, text);
|
|
825
|
+
const label = pending.type === 'api_key' ? 'API Key' : 'OAuth Token';
|
|
826
|
+
logger.info(`[Bot] Claude Code ${label} saved from ${username} in chat ${chatId}`);
|
|
827
|
+
await bot.sendMessage(
|
|
828
|
+
chatId,
|
|
829
|
+
`🔐 Claude Code *${label}* saved and activated.\n\nNext Claude Code spawn will use this credential.`,
|
|
830
|
+
{ parse_mode: 'Markdown' },
|
|
831
|
+
);
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
|
|
585
835
|
// Handle pending custom skill creation (text input for name or prompt)
|
|
586
836
|
if (pendingCustomSkill.has(chatId)) {
|
|
587
837
|
const pending = pendingCustomSkill.get(chatId);
|
|
@@ -640,6 +890,78 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
640
890
|
return;
|
|
641
891
|
}
|
|
642
892
|
|
|
893
|
+
if (text === '/orchestrator') {
|
|
894
|
+
logger.info(`[Bot] /orchestrator command from ${username} (${userId}) in chat ${chatId}`);
|
|
895
|
+
const info = agent.getOrchestratorInfo();
|
|
896
|
+
const providerKeys = Object.keys(PROVIDERS);
|
|
897
|
+
const buttons = providerKeys.map((key) => ([{
|
|
898
|
+
text: `${PROVIDERS[key].name}${key === info.provider ? ' ✓' : ''}`,
|
|
899
|
+
callback_data: `orch_provider:${key}`,
|
|
900
|
+
}]));
|
|
901
|
+
buttons.push([{ text: 'Cancel', callback_data: 'orch_cancel' }]);
|
|
902
|
+
|
|
903
|
+
await bot.sendMessage(
|
|
904
|
+
chatId,
|
|
905
|
+
`🎛️ *Current orchestrator:* ${info.providerName} / ${info.modelLabel}\n\nSelect a provider to switch:`,
|
|
906
|
+
{
|
|
907
|
+
parse_mode: 'Markdown',
|
|
908
|
+
reply_markup: { inline_keyboard: buttons },
|
|
909
|
+
},
|
|
910
|
+
);
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
if (text === '/claudemodel') {
|
|
915
|
+
logger.info(`[Bot] /claudemodel command from ${username} (${userId}) in chat ${chatId}`);
|
|
916
|
+
const info = agent.getClaudeCodeInfo();
|
|
917
|
+
const anthropicModels = PROVIDERS.anthropic.models;
|
|
918
|
+
const buttons = anthropicModels.map((m) => ([{
|
|
919
|
+
text: `${m.label}${m.id === info.model ? ' ✓' : ''}`,
|
|
920
|
+
callback_data: `ccmodel:${m.id}`,
|
|
921
|
+
}]));
|
|
922
|
+
buttons.push([{ text: 'Cancel', callback_data: 'ccmodel_cancel' }]);
|
|
923
|
+
|
|
924
|
+
await bot.sendMessage(
|
|
925
|
+
chatId,
|
|
926
|
+
`💻 *Current Claude Code model:* ${info.modelLabel}\n\nSelect a model:`,
|
|
927
|
+
{
|
|
928
|
+
parse_mode: 'Markdown',
|
|
929
|
+
reply_markup: { inline_keyboard: buttons },
|
|
930
|
+
},
|
|
931
|
+
);
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
if (text === '/claude') {
|
|
936
|
+
logger.info(`[Bot] /claude command from ${username} (${userId}) in chat ${chatId}`);
|
|
937
|
+
const authConfig = agent.getClaudeAuthConfig();
|
|
938
|
+
const ccInfo = agent.getClaudeCodeInfo();
|
|
939
|
+
|
|
940
|
+
const modeLabels = { system: '🔓 System Login', api_key: '🔑 API Key', oauth_token: '🎫 OAuth Token (Pro/Max)' };
|
|
941
|
+
const modeLabel = modeLabels[authConfig.mode] || authConfig.mode;
|
|
942
|
+
|
|
943
|
+
const buttons = [
|
|
944
|
+
[{ text: '🔑 Set API Key', callback_data: 'claude_apikey' }],
|
|
945
|
+
[{ text: '🎫 Set OAuth Token (Pro/Max)', callback_data: 'claude_oauth' }],
|
|
946
|
+
[{ text: '🔓 Use System Auth', callback_data: 'claude_system' }],
|
|
947
|
+
[
|
|
948
|
+
{ text: '🔄 Refresh Status', callback_data: 'claude_status' },
|
|
949
|
+
{ text: '🚪 Logout', callback_data: 'claude_logout' },
|
|
950
|
+
],
|
|
951
|
+
[{ text: 'Cancel', callback_data: 'claude_cancel' }],
|
|
952
|
+
];
|
|
953
|
+
|
|
954
|
+
await bot.sendMessage(
|
|
955
|
+
chatId,
|
|
956
|
+
`🔐 *Claude Code Auth*\n\n*Auth Mode:* ${modeLabel}\n*Credential:* ${authConfig.credential}\n*Model:* ${ccInfo.modelLabel}\n\nSelect an action:`,
|
|
957
|
+
{
|
|
958
|
+
parse_mode: 'Markdown',
|
|
959
|
+
reply_markup: { inline_keyboard: buttons },
|
|
960
|
+
},
|
|
961
|
+
);
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
|
|
643
965
|
if (text === '/skills reset' || text === '/skill reset') {
|
|
644
966
|
logger.info(`[Bot] /skills reset from ${username} (${userId}) in chat ${chatId}`);
|
|
645
967
|
agent.clearSkill(chatId);
|
|
@@ -693,6 +1015,9 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
693
1015
|
|
|
694
1016
|
if (text === '/context') {
|
|
695
1017
|
const info = agent.getBrainInfo();
|
|
1018
|
+
const orchInfo = agent.getOrchestratorInfo();
|
|
1019
|
+
const ccInfo = agent.getClaudeCodeInfo();
|
|
1020
|
+
const authConfig = agent.getClaudeAuthConfig();
|
|
696
1021
|
const activeSkill = agent.getActiveSkill(chatId);
|
|
697
1022
|
const msgCount = conversationManager.getMessageCount(chatId);
|
|
698
1023
|
const history = conversationManager.getHistory(chatId);
|
|
@@ -711,7 +1036,9 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
711
1036
|
const lines = [
|
|
712
1037
|
'📋 *Conversation Context*',
|
|
713
1038
|
'',
|
|
714
|
-
|
|
1039
|
+
`🎛️ *Orchestrator:* ${orchInfo.providerName} / ${orchInfo.modelLabel}`,
|
|
1040
|
+
`🧠 *Brain (Workers):* ${info.providerName} / ${info.modelLabel}`,
|
|
1041
|
+
`💻 *Claude Code:* ${ccInfo.modelLabel} (auth: ${authConfig.mode})`,
|
|
715
1042
|
activeSkill
|
|
716
1043
|
? `🎭 *Skill:* ${activeSkill.emoji} ${activeSkill.name}`
|
|
717
1044
|
: '🎭 *Skill:* Default persona',
|
|
@@ -778,6 +1105,296 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
778
1105
|
return;
|
|
779
1106
|
}
|
|
780
1107
|
|
|
1108
|
+
// ── /life command ──────────────────────────────────────────────
|
|
1109
|
+
if (text === '/life' || text.startsWith('/life ')) {
|
|
1110
|
+
logger.info(`[Bot] /life command from ${username} (${userId}) in chat ${chatId}`);
|
|
1111
|
+
const args = text.slice('/life'.length).trim();
|
|
1112
|
+
|
|
1113
|
+
if (!lifeEngine) {
|
|
1114
|
+
await bot.sendMessage(chatId, 'Life engine is not available.');
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
if (args === 'pause') {
|
|
1119
|
+
lifeEngine.pause();
|
|
1120
|
+
await bot.sendMessage(chatId, '⏸️ Inner life paused. Use `/life resume` to restart.', { parse_mode: 'Markdown' });
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
if (args === 'resume') {
|
|
1124
|
+
lifeEngine.resume();
|
|
1125
|
+
await bot.sendMessage(chatId, '▶️ Inner life resumed!');
|
|
1126
|
+
return;
|
|
1127
|
+
}
|
|
1128
|
+
if (args.startsWith('trigger')) {
|
|
1129
|
+
const activityType = args.split(/\s+/)[1] || null;
|
|
1130
|
+
const validTypes = ['think', 'browse', 'journal', 'create', 'self_code', 'code_review', 'reflect'];
|
|
1131
|
+
if (activityType && !validTypes.includes(activityType)) {
|
|
1132
|
+
await bot.sendMessage(chatId, `Unknown activity type. Available: ${validTypes.join(', ')}`);
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
await bot.sendMessage(chatId, `⚡ Triggering ${activityType || 'random'} activity...`);
|
|
1136
|
+
lifeEngine.triggerNow(activityType).catch(err => {
|
|
1137
|
+
logger.error(`[Bot] Life trigger failed: ${err.message}`);
|
|
1138
|
+
});
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
if (args === 'review') {
|
|
1142
|
+
if (evolutionTracker) {
|
|
1143
|
+
const active = evolutionTracker.getActiveProposal();
|
|
1144
|
+
const openPRs = evolutionTracker.getPRsToCheck();
|
|
1145
|
+
const lines = ['*Evolution Status*', ''];
|
|
1146
|
+
if (active) {
|
|
1147
|
+
lines.push(`Active: \`${active.id}\` — ${active.status}`);
|
|
1148
|
+
lines.push(` ${(active.triggerContext || '').slice(0, 150)}`);
|
|
1149
|
+
} else {
|
|
1150
|
+
lines.push('_No active proposals._');
|
|
1151
|
+
}
|
|
1152
|
+
if (openPRs.length > 0) {
|
|
1153
|
+
lines.push('', '*Open PRs:*');
|
|
1154
|
+
for (const p of openPRs) {
|
|
1155
|
+
lines.push(` • PR #${p.prNumber}: ${p.prUrl || 'no URL'}`);
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
lines.push('', '_Use `/evolution` for full evolution status._');
|
|
1159
|
+
await bot.sendMessage(chatId, lines.join('\n'), { parse_mode: 'Markdown' });
|
|
1160
|
+
} else {
|
|
1161
|
+
await bot.sendMessage(chatId, 'Evolution system not available. Use `/evolution` for details.', { parse_mode: 'Markdown' });
|
|
1162
|
+
}
|
|
1163
|
+
return;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
// Default: show status
|
|
1167
|
+
const status = lifeEngine.getStatus();
|
|
1168
|
+
const lines = [
|
|
1169
|
+
'🌱 *Inner Life*',
|
|
1170
|
+
'',
|
|
1171
|
+
`*Status:* ${status.paused ? '⏸️ Paused' : status.status === 'active' ? '🟢 Active' : '⚪ Idle'}`,
|
|
1172
|
+
`*Total activities:* ${status.totalActivities}`,
|
|
1173
|
+
`*Last activity:* ${status.lastActivity || 'none'} (${status.lastActivityAgo})`,
|
|
1174
|
+
`*Last wake-up:* ${status.lastWakeUpAgo}`,
|
|
1175
|
+
'',
|
|
1176
|
+
'*Activity counts:*',
|
|
1177
|
+
` 💭 Think: ${status.activityCounts.think || 0}`,
|
|
1178
|
+
` 🌐 Browse: ${status.activityCounts.browse || 0}`,
|
|
1179
|
+
` 📓 Journal: ${status.activityCounts.journal || 0}`,
|
|
1180
|
+
` 🎨 Create: ${status.activityCounts.create || 0}`,
|
|
1181
|
+
` 🔧 Self-code: ${status.activityCounts.self_code || 0}`,
|
|
1182
|
+
` 🔍 Code review: ${status.activityCounts.code_review || 0}`,
|
|
1183
|
+
` 🪞 Reflect: ${status.activityCounts.reflect || 0}`,
|
|
1184
|
+
'',
|
|
1185
|
+
'_Commands:_',
|
|
1186
|
+
'`/life pause` — Pause activities',
|
|
1187
|
+
'`/life resume` — Resume activities',
|
|
1188
|
+
'`/life trigger [think|browse|journal|create|self_code|code_review|reflect]` — Trigger now',
|
|
1189
|
+
];
|
|
1190
|
+
await bot.sendMessage(chatId, lines.join('\n'), { parse_mode: 'Markdown' });
|
|
1191
|
+
return;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
// ── /journal command ─────────────────────────────────────────
|
|
1195
|
+
if (text === '/journal' || text.startsWith('/journal ')) {
|
|
1196
|
+
logger.info(`[Bot] /journal command from ${username} (${userId}) in chat ${chatId}`);
|
|
1197
|
+
|
|
1198
|
+
if (!journalManager) {
|
|
1199
|
+
await bot.sendMessage(chatId, 'Journal system is not available.');
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
const args = text.slice('/journal'.length).trim();
|
|
1204
|
+
|
|
1205
|
+
if (args && /^\d{4}-\d{2}-\d{2}$/.test(args)) {
|
|
1206
|
+
const entry = journalManager.getEntry(args);
|
|
1207
|
+
if (!entry) {
|
|
1208
|
+
await bot.sendMessage(chatId, `No journal entry for ${args}.`);
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1211
|
+
const chunks = splitMessage(entry);
|
|
1212
|
+
for (const chunk of chunks) {
|
|
1213
|
+
try { await bot.sendMessage(chatId, chunk, { parse_mode: 'Markdown' }); }
|
|
1214
|
+
catch { await bot.sendMessage(chatId, chunk); }
|
|
1215
|
+
}
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
if (args === 'list') {
|
|
1220
|
+
const dates = journalManager.list(15);
|
|
1221
|
+
if (dates.length === 0) {
|
|
1222
|
+
await bot.sendMessage(chatId, 'No journal entries yet.');
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
const lines = ['📓 *Journal Entries*', '', ...dates.map(d => ` • \`${d}\``)];
|
|
1226
|
+
lines.push('', '_Use `/journal YYYY-MM-DD` to read an entry._');
|
|
1227
|
+
await bot.sendMessage(chatId, lines.join('\n'), { parse_mode: 'Markdown' });
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
// Default: show today's journal
|
|
1232
|
+
const today = journalManager.getToday();
|
|
1233
|
+
if (!today) {
|
|
1234
|
+
await bot.sendMessage(chatId, '📓 No journal entries today yet.\n\n_Use `/journal list` to see past entries._', { parse_mode: 'Markdown' });
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
const chunks = splitMessage(today);
|
|
1238
|
+
for (const chunk of chunks) {
|
|
1239
|
+
try { await bot.sendMessage(chatId, chunk, { parse_mode: 'Markdown' }); }
|
|
1240
|
+
catch { await bot.sendMessage(chatId, chunk); }
|
|
1241
|
+
}
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
// ── /memories command ────────────────────────────────────────
|
|
1246
|
+
if (text === '/memories' || text.startsWith('/memories ')) {
|
|
1247
|
+
logger.info(`[Bot] /memories command from ${username} (${userId}) in chat ${chatId}`);
|
|
1248
|
+
|
|
1249
|
+
if (!memoryManager) {
|
|
1250
|
+
await bot.sendMessage(chatId, 'Memory system is not available.');
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
const args = text.slice('/memories'.length).trim();
|
|
1255
|
+
|
|
1256
|
+
if (args.startsWith('about ')) {
|
|
1257
|
+
const query = args.slice('about '.length).trim();
|
|
1258
|
+
const results = memoryManager.searchEpisodic(query, 10);
|
|
1259
|
+
if (results.length === 0) {
|
|
1260
|
+
await bot.sendMessage(chatId, `No memories matching "${query}".`);
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
const lines = [`🧠 *Memories about "${query}"*`, ''];
|
|
1264
|
+
for (const m of results) {
|
|
1265
|
+
const date = new Date(m.timestamp).toLocaleDateString();
|
|
1266
|
+
lines.push(`• ${m.summary} _(${date}, importance: ${m.importance})_`);
|
|
1267
|
+
}
|
|
1268
|
+
await bot.sendMessage(chatId, lines.join('\n'), { parse_mode: 'Markdown' });
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
// Default: show last 10 memories
|
|
1273
|
+
const recent = memoryManager.getRecentEpisodic(168, 10); // last 7 days
|
|
1274
|
+
if (recent.length === 0) {
|
|
1275
|
+
await bot.sendMessage(chatId, '🧠 No memories yet.');
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
const lines = ['🧠 *Recent Memories*', ''];
|
|
1279
|
+
for (const m of recent) {
|
|
1280
|
+
const ago = Math.round((Date.now() - m.timestamp) / 60000);
|
|
1281
|
+
const timeLabel = ago < 60 ? `${ago}m ago` : ago < 1440 ? `${Math.round(ago / 60)}h ago` : `${Math.round(ago / 1440)}d ago`;
|
|
1282
|
+
const icon = { interaction: '💬', discovery: '🔍', thought: '💭', creation: '🎨' }[m.type] || '•';
|
|
1283
|
+
lines.push(`${icon} ${m.summary} _(${timeLabel})_`);
|
|
1284
|
+
}
|
|
1285
|
+
lines.push('', '_Use `/memories about <topic>` to search._');
|
|
1286
|
+
await bot.sendMessage(chatId, lines.join('\n'), { parse_mode: 'Markdown' });
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
// ── /evolution command ──────────────────────────────────────────
|
|
1291
|
+
if (text === '/evolution' || text.startsWith('/evolution ')) {
|
|
1292
|
+
logger.info(`[Bot] /evolution command from ${username} (${userId}) in chat ${chatId}`);
|
|
1293
|
+
const args = text.slice('/evolution'.length).trim();
|
|
1294
|
+
|
|
1295
|
+
if (!evolutionTracker) {
|
|
1296
|
+
await bot.sendMessage(chatId, 'Evolution system is not available.');
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
if (args === 'history') {
|
|
1301
|
+
const proposals = evolutionTracker.getRecentProposals(10);
|
|
1302
|
+
if (proposals.length === 0) {
|
|
1303
|
+
await bot.sendMessage(chatId, 'No evolution proposals yet.');
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
const lines = ['*Evolution History*', ''];
|
|
1307
|
+
for (const p of proposals.reverse()) {
|
|
1308
|
+
const statusIcon = { research: '🔬', planned: '📋', coding: '💻', pr_open: '🔄', merged: '✅', rejected: '❌', failed: '💥' }[p.status] || '•';
|
|
1309
|
+
const age = Math.round((Date.now() - p.createdAt) / 3600_000);
|
|
1310
|
+
const ageLabel = age < 24 ? `${age}h ago` : `${Math.round(age / 24)}d ago`;
|
|
1311
|
+
lines.push(`${statusIcon} \`${p.id}\` — ${p.status} (${ageLabel})`);
|
|
1312
|
+
lines.push(` ${(p.triggerContext || '').slice(0, 100)}`);
|
|
1313
|
+
if (p.prUrl) lines.push(` PR: ${p.prUrl}`);
|
|
1314
|
+
lines.push('');
|
|
1315
|
+
}
|
|
1316
|
+
await bot.sendMessage(chatId, lines.join('\n'), { parse_mode: 'Markdown' });
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
if (args === 'lessons') {
|
|
1321
|
+
const lessons = evolutionTracker.getRecentLessons(15);
|
|
1322
|
+
if (lessons.length === 0) {
|
|
1323
|
+
await bot.sendMessage(chatId, 'No evolution lessons learned yet.');
|
|
1324
|
+
return;
|
|
1325
|
+
}
|
|
1326
|
+
const lines = ['*Evolution Lessons*', ''];
|
|
1327
|
+
for (const l of lessons.reverse()) {
|
|
1328
|
+
lines.push(`• [${l.category}] ${l.lesson}`);
|
|
1329
|
+
if (l.fromProposal) lines.push(` _from ${l.fromProposal}_`);
|
|
1330
|
+
lines.push('');
|
|
1331
|
+
}
|
|
1332
|
+
await bot.sendMessage(chatId, lines.join('\n'), { parse_mode: 'Markdown' });
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
if (args === 'trigger') {
|
|
1337
|
+
if (!lifeEngine) {
|
|
1338
|
+
await bot.sendMessage(chatId, 'Life engine is not available.');
|
|
1339
|
+
return;
|
|
1340
|
+
}
|
|
1341
|
+
await bot.sendMessage(chatId, '⚡ Triggering evolution cycle...');
|
|
1342
|
+
lifeEngine.triggerNow('self_code').catch(err => {
|
|
1343
|
+
logger.error(`[Bot] Evolution trigger failed: ${err.message}`);
|
|
1344
|
+
});
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
if (args === 'scan') {
|
|
1349
|
+
if (!codebaseKnowledge) {
|
|
1350
|
+
await bot.sendMessage(chatId, 'Codebase knowledge is not available.');
|
|
1351
|
+
return;
|
|
1352
|
+
}
|
|
1353
|
+
await bot.sendMessage(chatId, '🔍 Scanning codebase...');
|
|
1354
|
+
codebaseKnowledge.scanChanged().then(count => {
|
|
1355
|
+
bot.sendMessage(chatId, `✅ Scanned ${count} changed files.`).catch(() => {});
|
|
1356
|
+
}).catch(err => {
|
|
1357
|
+
bot.sendMessage(chatId, `Failed: ${err.message}`).catch(() => {});
|
|
1358
|
+
});
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
// Default: show status
|
|
1363
|
+
const active = evolutionTracker.getActiveProposal();
|
|
1364
|
+
const stats = evolutionTracker.getStats();
|
|
1365
|
+
const openPRs = evolutionTracker.getPRsToCheck();
|
|
1366
|
+
|
|
1367
|
+
const lines = [
|
|
1368
|
+
'🧬 *Self-Evolution*',
|
|
1369
|
+
'',
|
|
1370
|
+
`*Stats:* ${stats.totalProposals} total | ${stats.merged} merged | ${stats.rejected} rejected | ${stats.failed} failed`,
|
|
1371
|
+
`*Success rate:* ${stats.successRate}%`,
|
|
1372
|
+
`*Open PRs:* ${openPRs.length}`,
|
|
1373
|
+
];
|
|
1374
|
+
|
|
1375
|
+
if (active) {
|
|
1376
|
+
const statusIcon = { research: '🔬', planned: '📋', coding: '💻', pr_open: '🔄' }[active.status] || '•';
|
|
1377
|
+
lines.push('');
|
|
1378
|
+
lines.push(`*Active proposal:* ${statusIcon} \`${active.id}\` — ${active.status}`);
|
|
1379
|
+
lines.push(` ${(active.triggerContext || '').slice(0, 120)}`);
|
|
1380
|
+
if (active.prUrl) lines.push(` PR: ${active.prUrl}`);
|
|
1381
|
+
} else {
|
|
1382
|
+
lines.push('', '_No active proposal_');
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
lines.push(
|
|
1386
|
+
'',
|
|
1387
|
+
'_Commands:_',
|
|
1388
|
+
'`/evolution history` — Recent proposals',
|
|
1389
|
+
'`/evolution lessons` — Learned lessons',
|
|
1390
|
+
'`/evolution trigger` — Trigger evolution now',
|
|
1391
|
+
'`/evolution scan` — Scan codebase',
|
|
1392
|
+
);
|
|
1393
|
+
|
|
1394
|
+
await bot.sendMessage(chatId, lines.join('\n'), { parse_mode: 'Markdown' });
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
|
|
781
1398
|
if (text === '/help') {
|
|
782
1399
|
const activeSkill = agent.getActiveSkill(chatId);
|
|
783
1400
|
const skillLine = activeSkill
|
|
@@ -786,13 +1403,20 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
786
1403
|
await bot.sendMessage(chatId, [
|
|
787
1404
|
'*KernelBot Commands*',
|
|
788
1405
|
skillLine,
|
|
789
|
-
'/brain —
|
|
1406
|
+
'/brain — Switch worker AI model/provider',
|
|
1407
|
+
'/orchestrator — Switch orchestrator AI model/provider',
|
|
1408
|
+
'/claudemodel — Switch Claude Code model',
|
|
1409
|
+
'/claude — Manage Claude Code authentication',
|
|
790
1410
|
'/skills — Browse and activate persona skills',
|
|
791
1411
|
'/skills reset — Clear active skill back to default',
|
|
792
1412
|
'/jobs — List running and recent jobs',
|
|
793
1413
|
'/cancel — Cancel running job(s)',
|
|
794
1414
|
'/auto — Manage recurring automations',
|
|
795
|
-
'/
|
|
1415
|
+
'/life — Inner life engine status & control',
|
|
1416
|
+
'/journal — View today\'s journal or a past date',
|
|
1417
|
+
'/memories — View recent memories or search',
|
|
1418
|
+
'/evolution — Self-evolution status, history, lessons',
|
|
1419
|
+
'/context — Show all models, auth, and context info',
|
|
796
1420
|
'/clean — Clear conversation and start fresh',
|
|
797
1421
|
'/history — Show message count in memory',
|
|
798
1422
|
'/browse <url> — Browse a website and get a summary',
|
|
@@ -968,12 +1592,12 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
968
1592
|
});
|
|
969
1593
|
return edited.message_id;
|
|
970
1594
|
} catch {
|
|
971
|
-
|
|
1595
|
+
// Edit failed — fall through to send new message
|
|
972
1596
|
}
|
|
973
1597
|
}
|
|
974
1598
|
}
|
|
975
1599
|
|
|
976
|
-
// Send new message(s)
|
|
1600
|
+
// Send new message(s) — also reached when edit fails
|
|
977
1601
|
const parts = splitMessage(update);
|
|
978
1602
|
let lastMsgId = null;
|
|
979
1603
|
for (const part of parts) {
|
|
@@ -1024,6 +1648,18 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
1024
1648
|
await bot.sendMessage(chatId, chunk);
|
|
1025
1649
|
}
|
|
1026
1650
|
}
|
|
1651
|
+
|
|
1652
|
+
// Send voice reply if TTS is available and the reply isn't too short
|
|
1653
|
+
if (ttsService.isAvailable() && reply && reply.length > 5) {
|
|
1654
|
+
try {
|
|
1655
|
+
const audioPath = await ttsService.synthesize(reply);
|
|
1656
|
+
if (audioPath) {
|
|
1657
|
+
await bot.sendVoice(chatId, createReadStream(audioPath));
|
|
1658
|
+
}
|
|
1659
|
+
} catch (err) {
|
|
1660
|
+
logger.warn(`[Bot] TTS voice reply failed: ${err.message}`);
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1027
1663
|
} catch (err) {
|
|
1028
1664
|
clearInterval(typingInterval);
|
|
1029
1665
|
logger.error(`[Bot] Error processing message in chat ${chatId}: ${err.message}`);
|