kernelbot 1.0.38 → 1.0.40
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/bin/kernel.js +335 -451
- package/config.example.yaml +1 -1
- package/knowledge_base/active_inference_foraging.md +126 -0
- package/knowledge_base/index.md +1 -1
- package/package.json +2 -1
- package/skills/business/business-analyst.md +32 -0
- package/skills/business/product-manager.md +32 -0
- package/skills/business/project-manager.md +32 -0
- package/skills/business/startup-advisor.md +32 -0
- package/skills/creative/music-producer.md +32 -0
- package/skills/creative/photographer.md +32 -0
- package/skills/creative/video-producer.md +32 -0
- package/skills/data/bi-analyst.md +37 -0
- package/skills/data/data-scientist.md +38 -0
- package/skills/data/ml-engineer.md +38 -0
- package/skills/design/graphic-designer.md +38 -0
- package/skills/design/product-designer.md +41 -0
- package/skills/design/ui-ux.md +38 -0
- package/skills/education/curriculum-designer.md +32 -0
- package/skills/education/language-teacher.md +32 -0
- package/skills/education/tutor.md +32 -0
- package/skills/engineering/data-eng.md +55 -0
- package/skills/engineering/devops.md +56 -0
- package/skills/engineering/mobile-dev.md +55 -0
- package/skills/engineering/security-eng.md +55 -0
- package/skills/engineering/sr-backend.md +55 -0
- package/skills/engineering/sr-frontend.md +55 -0
- package/skills/finance/accountant.md +35 -0
- package/skills/finance/crypto-defi.md +39 -0
- package/skills/finance/financial-analyst.md +35 -0
- package/skills/healthcare/health-wellness.md +32 -0
- package/skills/healthcare/medical-researcher.md +33 -0
- package/skills/legal/contract-reviewer.md +35 -0
- package/skills/legal/legal-advisor.md +36 -0
- package/skills/marketing/content-marketer.md +38 -0
- package/skills/marketing/growth.md +38 -0
- package/skills/marketing/seo.md +43 -0
- package/skills/marketing/social-media.md +43 -0
- package/skills/writing/academic-writer.md +33 -0
- package/skills/writing/copywriter.md +32 -0
- package/skills/writing/creative-writer.md +32 -0
- package/skills/writing/tech-writer.md +33 -0
- package/src/agent.js +153 -118
- package/src/automation/scheduler.js +36 -3
- package/src/bot.js +147 -64
- package/src/coder.js +30 -8
- package/src/conversation.js +96 -19
- package/src/dashboard/dashboard.css +6 -0
- package/src/dashboard/dashboard.js +28 -1
- package/src/dashboard/index.html +12 -0
- package/src/dashboard/server.js +77 -15
- package/src/dashboard/shared.js +10 -1
- package/src/life/codebase.js +2 -1
- package/src/life/daydream_engine.js +386 -0
- package/src/life/engine.js +88 -6
- package/src/life/evolution.js +4 -3
- package/src/prompts/orchestrator.js +1 -1
- package/src/prompts/system.js +1 -1
- package/src/prompts/workers.js +8 -1
- package/src/providers/anthropic.js +3 -1
- package/src/providers/base.js +33 -0
- package/src/providers/index.js +1 -1
- package/src/providers/models.js +22 -0
- package/src/providers/openai-compat.js +3 -0
- package/src/services/x-api.js +14 -3
- package/src/skills/loader.js +382 -0
- package/src/swarm/worker-registry.js +2 -2
- package/src/tools/browser.js +10 -3
- package/src/tools/coding.js +16 -0
- package/src/tools/docker.js +13 -0
- package/src/tools/git.js +31 -29
- package/src/tools/jira.js +11 -2
- package/src/tools/monitor.js +9 -1
- package/src/tools/network.js +34 -0
- package/src/tools/orchestrator-tools.js +2 -1
- package/src/tools/os.js +20 -6
- package/src/utils/config.js +87 -83
- package/src/utils/display.js +118 -66
- package/src/utils/logger.js +1 -1
- package/src/utils/timeAwareness.js +72 -0
- package/src/worker.js +26 -33
- package/src/skills/catalog.js +0 -506
- package/src/skills/custom.js +0 -128
package/src/bot.js
CHANGED
|
@@ -4,14 +4,14 @@ import { isAllowedUser, getUnauthorizedMessage, alertAdmin } from './security/au
|
|
|
4
4
|
import { getLogger } from './utils/logger.js';
|
|
5
5
|
import { PROVIDERS } from './providers/models.js';
|
|
6
6
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
getSkillById,
|
|
8
|
+
getCategoryList,
|
|
9
|
+
getSkillsByCategory,
|
|
10
|
+
loadAllSkills,
|
|
11
|
+
saveCustomSkill,
|
|
12
12
|
deleteCustomSkill,
|
|
13
13
|
getCustomSkills,
|
|
14
|
-
} from './skills/
|
|
14
|
+
} from './skills/loader.js';
|
|
15
15
|
import { TTSService } from './services/tts.js';
|
|
16
16
|
import { STTService } from './services/stt.js';
|
|
17
17
|
import { getClaudeAuthStatus, claudeLogout } from './claude-auth.js';
|
|
@@ -206,6 +206,39 @@ class ChatQueue {
|
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
+
/**
|
|
210
|
+
* Convert raw errors into user-friendly messages.
|
|
211
|
+
* Keeps technical details in logs, shows something helpful in chat.
|
|
212
|
+
*/
|
|
213
|
+
function _friendlyError(err) {
|
|
214
|
+
const msg = (err?.message || '').toLowerCase();
|
|
215
|
+
|
|
216
|
+
if (msg.includes('api key') || msg.includes('authentication') || msg.includes('unauthorized')) {
|
|
217
|
+
return '🔑 Authentication issue — please check your API key configuration.';
|
|
218
|
+
}
|
|
219
|
+
if (msg.includes('rate limit') || msg.includes('429') || msg.includes('quota')) {
|
|
220
|
+
return '⏳ Rate limited — too many requests. I\'ll be back in a moment, try again shortly.';
|
|
221
|
+
}
|
|
222
|
+
if (msg.includes('timed out') || msg.includes('timeout')) {
|
|
223
|
+
return '⏱️ The request timed out. Try again or simplify your request.';
|
|
224
|
+
}
|
|
225
|
+
if (msg.includes('context length') || msg.includes('too long') || msg.includes('too large') || msg.includes('token limit')) {
|
|
226
|
+
return '📏 Message too long for the current model. Try a shorter message or switch to a model with a larger context window.';
|
|
227
|
+
}
|
|
228
|
+
if (msg.includes('safety') || msg.includes('blocked') || msg.includes('recitation')) {
|
|
229
|
+
return '🛡️ The model declined this request due to safety filters. Try rephrasing.';
|
|
230
|
+
}
|
|
231
|
+
if (msg.includes('not found') || msg.includes('not available') || msg.includes('does not exist')) {
|
|
232
|
+
return '❓ The configured model is not available. Try switching to a different model with /brain.';
|
|
233
|
+
}
|
|
234
|
+
if (msg.includes('connection') || msg.includes('network') || msg.includes('fetch failed')) {
|
|
235
|
+
return '🌐 Network issue — couldn\'t reach the AI provider. Check your connection and try again.';
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Generic fallback — still don't expose raw internals
|
|
239
|
+
return '⚠️ Something went wrong processing your message. Try again, or switch models with /brain if the issue persists.';
|
|
240
|
+
}
|
|
241
|
+
|
|
209
242
|
export function startBot(config, agent, conversationManager, jobManager, automationManager, lifeDeps = {}) {
|
|
210
243
|
let { lifeEngine, memoryManager, journalManager, shareQueue, evolutionTracker, codebaseKnowledge, characterManager, dashboardHandle, dashboardDeps } = lifeDeps;
|
|
211
244
|
const logger = getLogger();
|
|
@@ -281,7 +314,7 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
281
314
|
}
|
|
282
315
|
|
|
283
316
|
// Load custom skills from disk
|
|
284
|
-
|
|
317
|
+
loadAllSkills();
|
|
285
318
|
|
|
286
319
|
// Register commands in Telegram's menu button
|
|
287
320
|
bot.setMyCommands([
|
|
@@ -392,7 +425,10 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
392
425
|
|
|
393
426
|
} else if (data.startsWith('brain_model:')) {
|
|
394
427
|
// User picked a model — attempt switch
|
|
395
|
-
|
|
428
|
+
// Use split with limit to avoid truncating model IDs containing colons
|
|
429
|
+
const parts = data.split(':');
|
|
430
|
+
const providerKey = parts[1];
|
|
431
|
+
const modelId = parts.slice(2).join(':');
|
|
396
432
|
const providerDef = PROVIDERS[providerKey];
|
|
397
433
|
const modelEntry = providerDef?.models.find((m) => m.id === modelId);
|
|
398
434
|
const modelLabel = modelEntry ? modelEntry.label : modelId;
|
|
@@ -454,29 +490,34 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
454
490
|
});
|
|
455
491
|
await bot.answerCallbackQuery(query.id);
|
|
456
492
|
|
|
457
|
-
// ── Skill callbacks
|
|
493
|
+
// ── Skill callbacks (multi-skill toggle) ─────────────────────
|
|
458
494
|
} else if (data.startsWith('skill_category:')) {
|
|
459
495
|
const categoryKey = data.split(':')[1];
|
|
460
|
-
const skills =
|
|
461
|
-
const categories =
|
|
496
|
+
const skills = getSkillsByCategory(categoryKey);
|
|
497
|
+
const categories = getCategoryList();
|
|
462
498
|
const cat = categories.find((c) => c.key === categoryKey);
|
|
463
499
|
if (!skills.length) {
|
|
464
500
|
await bot.answerCallbackQuery(query.id, { text: 'No skills in this category' });
|
|
465
501
|
return;
|
|
466
502
|
}
|
|
467
503
|
|
|
468
|
-
const
|
|
504
|
+
const activeIds = new Set(agent.getActiveSkillIds(chatId));
|
|
469
505
|
const buttons = skills.map((s) => ([{
|
|
470
|
-
text: `${
|
|
471
|
-
callback_data: `
|
|
506
|
+
text: `${activeIds.has(s.id) ? '✅ ' : ''}${s.emoji} ${s.name}`,
|
|
507
|
+
callback_data: `skill_toggle:${s.id}:${categoryKey}`,
|
|
472
508
|
}]));
|
|
473
509
|
buttons.push([
|
|
474
510
|
{ text: '« Back', callback_data: 'skill_back' },
|
|
475
|
-
{ text: '
|
|
511
|
+
{ text: 'Done', callback_data: 'skill_cancel' },
|
|
476
512
|
]);
|
|
477
513
|
|
|
514
|
+
const activeSkills = agent.getActiveSkills(chatId);
|
|
515
|
+
const activeLine = activeSkills.length > 0
|
|
516
|
+
? `Active (${activeSkills.length}): ${activeSkills.map(s => `${s.emoji} ${s.name}`).join(', ')}\n\n`
|
|
517
|
+
: '';
|
|
518
|
+
|
|
478
519
|
await bot.editMessageText(
|
|
479
|
-
`${cat ? cat.emoji : ''} *${cat ? cat.name : categoryKey}* —
|
|
520
|
+
`${activeLine}${cat ? cat.emoji : ''} *${cat ? cat.name : categoryKey}* — tap to toggle:`,
|
|
480
521
|
{
|
|
481
522
|
chat_id: chatId,
|
|
482
523
|
message_id: query.message.message_id,
|
|
@@ -486,31 +527,60 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
486
527
|
);
|
|
487
528
|
await bot.answerCallbackQuery(query.id);
|
|
488
529
|
|
|
489
|
-
} else if (data.startsWith('
|
|
490
|
-
const
|
|
491
|
-
const
|
|
530
|
+
} else if (data.startsWith('skill_toggle:')) {
|
|
531
|
+
const parts = data.split(':');
|
|
532
|
+
const skillId = parts[1];
|
|
533
|
+
const categoryKey = parts[2]; // to refresh the category view
|
|
534
|
+
const skill = getSkillById(skillId);
|
|
492
535
|
if (!skill) {
|
|
493
|
-
logger.warn(`[Bot] Unknown skill selected: ${skillId}`);
|
|
494
536
|
await bot.answerCallbackQuery(query.id, { text: 'Unknown skill' });
|
|
495
537
|
return;
|
|
496
538
|
}
|
|
497
539
|
|
|
498
|
-
|
|
499
|
-
|
|
540
|
+
const { added, skills: currentSkills } = agent.toggleSkill(chatId, skillId);
|
|
541
|
+
if (!added && currentSkills.includes(skillId)) {
|
|
542
|
+
// Wasn't added because at max
|
|
543
|
+
await bot.answerCallbackQuery(query.id, { text: `Max ${5} skills reached. Remove one first.` });
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
logger.info(`[Bot] Skill ${added ? 'activated' : 'deactivated'}: ${skill.name} (${skillId}) for chat ${chatId} — now ${currentSkills.length} active`);
|
|
548
|
+
|
|
549
|
+
// Refresh the category view with updated toggles
|
|
550
|
+
const catSkills = getSkillsByCategory(categoryKey);
|
|
551
|
+
const categories = getCategoryList();
|
|
552
|
+
const cat = categories.find((c) => c.key === categoryKey);
|
|
553
|
+
const activeIds = new Set(agent.getActiveSkillIds(chatId));
|
|
554
|
+
|
|
555
|
+
const buttons = catSkills.map((s) => ([{
|
|
556
|
+
text: `${activeIds.has(s.id) ? '✅ ' : ''}${s.emoji} ${s.name}`,
|
|
557
|
+
callback_data: `skill_toggle:${s.id}:${categoryKey}`,
|
|
558
|
+
}]));
|
|
559
|
+
buttons.push([
|
|
560
|
+
{ text: '« Back', callback_data: 'skill_back' },
|
|
561
|
+
{ text: 'Done', callback_data: 'skill_cancel' },
|
|
562
|
+
]);
|
|
563
|
+
|
|
564
|
+
const activeSkills = agent.getActiveSkills(chatId);
|
|
565
|
+
const activeLine = activeSkills.length > 0
|
|
566
|
+
? `Active (${activeSkills.length}): ${activeSkills.map(s => `${s.emoji} ${s.name}`).join(', ')}\n\n`
|
|
567
|
+
: '';
|
|
568
|
+
|
|
500
569
|
await bot.editMessageText(
|
|
501
|
-
`${
|
|
570
|
+
`${activeLine}${cat ? cat.emoji : ''} *${cat ? cat.name : categoryKey}* — tap to toggle:`,
|
|
502
571
|
{
|
|
503
572
|
chat_id: chatId,
|
|
504
573
|
message_id: query.message.message_id,
|
|
505
574
|
parse_mode: 'Markdown',
|
|
575
|
+
reply_markup: { inline_keyboard: buttons },
|
|
506
576
|
},
|
|
507
577
|
);
|
|
508
|
-
await bot.answerCallbackQuery(query.id);
|
|
578
|
+
await bot.answerCallbackQuery(query.id, { text: added ? `✅ ${skill.name} on` : `❌ ${skill.name} off` });
|
|
509
579
|
|
|
510
580
|
} else if (data === 'skill_reset') {
|
|
511
|
-
logger.info(`[Bot]
|
|
581
|
+
logger.info(`[Bot] Skills cleared for chat ${chatId}`);
|
|
512
582
|
agent.clearSkill(chatId);
|
|
513
|
-
await bot.editMessageText('🔄
|
|
583
|
+
await bot.editMessageText('🔄 All skills cleared — back to default persona.', {
|
|
514
584
|
chat_id: chatId,
|
|
515
585
|
message_id: query.message.message_id,
|
|
516
586
|
});
|
|
@@ -551,10 +621,11 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
551
621
|
} else if (data.startsWith('skill_custom_delete:')) {
|
|
552
622
|
const skillId = data.slice('skill_custom_delete:'.length);
|
|
553
623
|
logger.info(`[Bot] Custom skill delete request: ${skillId} from chat ${chatId}`);
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
624
|
+
// Remove from active skills if present
|
|
625
|
+
const activeIds = agent.getActiveSkillIds(chatId);
|
|
626
|
+
if (activeIds.includes(skillId)) {
|
|
627
|
+
logger.info(`[Bot] Removing deleted skill from active set: ${skillId}`);
|
|
628
|
+
agent.toggleSkill(chatId, skillId);
|
|
558
629
|
}
|
|
559
630
|
const deleted = deleteCustomSkill(skillId);
|
|
560
631
|
const msg = deleted ? '🗑️ Custom skill deleted.' : 'Skill not found.';
|
|
@@ -566,8 +637,8 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
566
637
|
|
|
567
638
|
} else if (data === 'skill_back') {
|
|
568
639
|
// Re-show category list
|
|
569
|
-
const categories =
|
|
570
|
-
const
|
|
640
|
+
const categories = getCategoryList();
|
|
641
|
+
const activeSkills = agent.getActiveSkills(chatId);
|
|
571
642
|
const buttons = categories.map((cat) => ([{
|
|
572
643
|
text: `${cat.emoji} ${cat.name} (${cat.count})`,
|
|
573
644
|
callback_data: `skill_category:${cat.key}`,
|
|
@@ -578,15 +649,16 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
578
649
|
customRow.push({ text: '🗑️ Manage Custom', callback_data: 'skill_custom_manage' });
|
|
579
650
|
}
|
|
580
651
|
buttons.push(customRow);
|
|
581
|
-
const footerRow = [{ text: '
|
|
582
|
-
if (
|
|
583
|
-
footerRow.unshift({ text: '🔄
|
|
652
|
+
const footerRow = [{ text: 'Done', callback_data: 'skill_cancel' }];
|
|
653
|
+
if (activeSkills.length > 0) {
|
|
654
|
+
footerRow.unshift({ text: '🔄 Clear All', callback_data: 'skill_reset' });
|
|
584
655
|
}
|
|
585
656
|
buttons.push(footerRow);
|
|
586
657
|
|
|
587
|
-
const
|
|
588
|
-
?
|
|
589
|
-
: '
|
|
658
|
+
const activeLine = activeSkills.length > 0
|
|
659
|
+
? `Active (${activeSkills.length}): ${activeSkills.map(s => `${s.emoji} ${s.name}`).join(', ')}\n\n`
|
|
660
|
+
: '';
|
|
661
|
+
const header = `${activeLine}🎭 *Skills* — select a category:`;
|
|
590
662
|
|
|
591
663
|
await bot.editMessageText(header, {
|
|
592
664
|
chat_id: chatId,
|
|
@@ -597,9 +669,14 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
597
669
|
await bot.answerCallbackQuery(query.id);
|
|
598
670
|
|
|
599
671
|
} else if (data === 'skill_cancel') {
|
|
600
|
-
|
|
672
|
+
const activeSkills = agent.getActiveSkills(chatId);
|
|
673
|
+
const msg = activeSkills.length > 0
|
|
674
|
+
? `🎭 Active skills (${activeSkills.length}): ${activeSkills.map(s => `${s.emoji} ${s.name}`).join(', ')}`
|
|
675
|
+
: 'No skills active.';
|
|
676
|
+
await bot.editMessageText(msg, {
|
|
601
677
|
chat_id: chatId,
|
|
602
678
|
message_id: query.message.message_id,
|
|
679
|
+
parse_mode: 'Markdown',
|
|
603
680
|
});
|
|
604
681
|
await bot.answerCallbackQuery(query.id);
|
|
605
682
|
|
|
@@ -685,7 +762,10 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
685
762
|
await bot.answerCallbackQuery(query.id);
|
|
686
763
|
|
|
687
764
|
} else if (data.startsWith('orch_model:')) {
|
|
688
|
-
|
|
765
|
+
// Use split with limit to avoid truncating model IDs containing colons
|
|
766
|
+
const parts = data.split(':');
|
|
767
|
+
const providerKey = parts[1];
|
|
768
|
+
const modelId = parts.slice(2).join(':');
|
|
689
769
|
const providerDef = PROVIDERS[providerKey];
|
|
690
770
|
const modelEntry = providerDef?.models.find((m) => m.id === modelId);
|
|
691
771
|
const modelLabel = modelEntry ? modelEntry.label : modelId;
|
|
@@ -1136,12 +1216,12 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
1136
1216
|
return;
|
|
1137
1217
|
}
|
|
1138
1218
|
pendingCustomSkill.delete(chatId);
|
|
1139
|
-
const skill =
|
|
1219
|
+
const skill = saveCustomSkill({ name: pending.name, body: content });
|
|
1140
1220
|
logger.info(`[Bot] Custom skill created from file: "${skill.name}" (${skill.id}) — ${content.length} chars, by ${username} in chat ${chatId}`);
|
|
1141
|
-
agent.
|
|
1221
|
+
agent.toggleSkill(chatId, skill.id);
|
|
1142
1222
|
await bot.sendMessage(
|
|
1143
1223
|
chatId,
|
|
1144
|
-
`✅ Custom skill *${skill.name}* created and
|
|
1224
|
+
`✅ Custom skill *${skill.name}* created and added to active skills!\n\n_Prompt loaded from file (${content.length} chars)_`,
|
|
1145
1225
|
{ parse_mode: 'Markdown' },
|
|
1146
1226
|
);
|
|
1147
1227
|
} catch (err) {
|
|
@@ -1323,12 +1403,12 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
1323
1403
|
|
|
1324
1404
|
if (pending.step === 'prompt') {
|
|
1325
1405
|
pendingCustomSkill.delete(chatId);
|
|
1326
|
-
const skill =
|
|
1406
|
+
const skill = saveCustomSkill({ name: pending.name, body: text });
|
|
1327
1407
|
logger.info(`[Bot] Custom skill created: "${skill.name}" (${skill.id}) by ${username} in chat ${chatId}`);
|
|
1328
|
-
agent.
|
|
1408
|
+
agent.toggleSkill(chatId, skill.id);
|
|
1329
1409
|
await bot.sendMessage(
|
|
1330
1410
|
chatId,
|
|
1331
|
-
`✅ Custom skill *${skill.name}* created and
|
|
1411
|
+
`✅ Custom skill *${skill.name}* created and added to active skills!`,
|
|
1332
1412
|
{ parse_mode: 'Markdown' },
|
|
1333
1413
|
);
|
|
1334
1414
|
return;
|
|
@@ -1536,14 +1616,14 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
1536
1616
|
if (text === '/skills reset' || text === '/skill reset') {
|
|
1537
1617
|
logger.info(`[Bot] /skills reset from ${username} (${userId}) in chat ${chatId}`);
|
|
1538
1618
|
agent.clearSkill(chatId);
|
|
1539
|
-
await bot.sendMessage(chatId, '🔄
|
|
1619
|
+
await bot.sendMessage(chatId, '🔄 All skills cleared — back to default persona.');
|
|
1540
1620
|
return;
|
|
1541
1621
|
}
|
|
1542
1622
|
|
|
1543
1623
|
if (text === '/skills' || text === '/skill') {
|
|
1544
1624
|
logger.info(`[Bot] /skills command from ${username} (${userId}) in chat ${chatId}`);
|
|
1545
|
-
const categories =
|
|
1546
|
-
const
|
|
1625
|
+
const categories = getCategoryList();
|
|
1626
|
+
const activeSkills = agent.getActiveSkills(chatId);
|
|
1547
1627
|
const buttons = categories.map((cat) => ([{
|
|
1548
1628
|
text: `${cat.emoji} ${cat.name} (${cat.count})`,
|
|
1549
1629
|
callback_data: `skill_category:${cat.key}`,
|
|
@@ -1555,14 +1635,15 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
1555
1635
|
}
|
|
1556
1636
|
buttons.push(customRow);
|
|
1557
1637
|
const footerRow = [{ text: 'Cancel', callback_data: 'skill_cancel' }];
|
|
1558
|
-
if (
|
|
1559
|
-
footerRow.unshift({ text: '🔄
|
|
1638
|
+
if (activeSkills.length > 0) {
|
|
1639
|
+
footerRow.unshift({ text: '🔄 Clear All', callback_data: 'skill_reset' });
|
|
1560
1640
|
}
|
|
1561
1641
|
buttons.push(footerRow);
|
|
1562
1642
|
|
|
1563
|
-
const
|
|
1564
|
-
?
|
|
1565
|
-
: '
|
|
1643
|
+
const activeLine = activeSkills.length > 0
|
|
1644
|
+
? `Active (${activeSkills.length}): ${activeSkills.map(s => `${s.emoji} ${s.name}`).join(', ')}\n\n`
|
|
1645
|
+
: '';
|
|
1646
|
+
const header = `${activeLine}🎭 *Skills* — select a category:`;
|
|
1566
1647
|
|
|
1567
1648
|
await bot.sendMessage(chatId, header, {
|
|
1568
1649
|
parse_mode: 'Markdown',
|
|
@@ -1589,7 +1670,7 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
1589
1670
|
const orchInfo = agent.getOrchestratorInfo();
|
|
1590
1671
|
const ccInfo = agent.getClaudeCodeInfo();
|
|
1591
1672
|
const authConfig = agent.getClaudeAuthConfig();
|
|
1592
|
-
const
|
|
1673
|
+
const activeSkills = agent.getActiveSkills(chatId);
|
|
1593
1674
|
const msgCount = agent.getMessageCount(chatId);
|
|
1594
1675
|
const history = agent.getConversationHistory(chatId);
|
|
1595
1676
|
const maxHistory = conversationManager.maxHistory;
|
|
@@ -1615,9 +1696,9 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
1615
1696
|
`🎛️ *Orchestrator:* ${orchInfo.providerName} / ${orchInfo.modelLabel}`,
|
|
1616
1697
|
`🧠 *Brain (Workers):* ${info.providerName} / ${info.modelLabel}`,
|
|
1617
1698
|
`💻 *Claude Code:* ${ccInfo.modelLabel} (auth: ${authConfig.mode})`,
|
|
1618
|
-
|
|
1619
|
-
? `🎭 *
|
|
1620
|
-
: '🎭 *
|
|
1699
|
+
activeSkills.length > 0
|
|
1700
|
+
? `🎭 *Skills (${activeSkills.length}):* ${activeSkills.map(s => `${s.emoji} ${s.name}`).join(', ')}`
|
|
1701
|
+
: '🎭 *Skills:* None (default persona)',
|
|
1621
1702
|
`💬 *Messages in memory:* ${msgCount} / ${maxHistory}`,
|
|
1622
1703
|
`📌 *Recent window:* ${recentWindow} messages`,
|
|
1623
1704
|
].filter(Boolean);
|
|
@@ -2251,9 +2332,9 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
2251
2332
|
}
|
|
2252
2333
|
|
|
2253
2334
|
if (text === '/help') {
|
|
2254
|
-
const
|
|
2255
|
-
const skillLine =
|
|
2256
|
-
? `\n🎭 *Active
|
|
2335
|
+
const activeSkills = agent.getActiveSkills(chatId);
|
|
2336
|
+
const skillLine = activeSkills.length > 0
|
|
2337
|
+
? `\n🎭 *Active skills:* ${activeSkills.map(s => `${s.emoji} ${s.name}`).join(', ')}\n`
|
|
2257
2338
|
: '';
|
|
2258
2339
|
await bot.sendMessage(chatId, [
|
|
2259
2340
|
'*KernelBot Commands*',
|
|
@@ -2263,8 +2344,8 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
2263
2344
|
'/orchestrator — Switch orchestrator AI model/provider',
|
|
2264
2345
|
'/claudemodel — Switch Claude Code model',
|
|
2265
2346
|
'/claude — Manage Claude Code authentication',
|
|
2266
|
-
'/skills — Browse and
|
|
2267
|
-
'/skills reset — Clear active
|
|
2347
|
+
'/skills — Browse and toggle persona skills (multi-skill)',
|
|
2348
|
+
'/skills reset — Clear all active skills',
|
|
2268
2349
|
'/jobs — List running and recent jobs',
|
|
2269
2350
|
'/cancel — Cancel running job(s)',
|
|
2270
2351
|
'/auto — Manage recurring automations',
|
|
@@ -2477,7 +2558,9 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
2477
2558
|
} catch (err) {
|
|
2478
2559
|
clearInterval(typingInterval);
|
|
2479
2560
|
logger.error(`[Bot] Error processing message in chat ${chatId}: ${err.message}`);
|
|
2480
|
-
|
|
2561
|
+
// Show a friendly message instead of raw error details
|
|
2562
|
+
const friendly = _friendlyError(err);
|
|
2563
|
+
await bot.sendMessage(chatId, friendly);
|
|
2481
2564
|
}
|
|
2482
2565
|
});
|
|
2483
2566
|
});
|
package/src/coder.js
CHANGED
|
@@ -137,6 +137,8 @@ export class ClaudeCodeSpawner {
|
|
|
137
137
|
constructor(config) {
|
|
138
138
|
this.config = config;
|
|
139
139
|
this.maxTurns = config.claude_code?.max_turns || 50;
|
|
140
|
+
// Default 24 hours — background workers can legitimately run for extended periods.
|
|
141
|
+
// Stuck process detection is handled separately by idle/loop heuristics.
|
|
140
142
|
this.timeout = (config.claude_code?.timeout_seconds || 86400) * 1000;
|
|
141
143
|
}
|
|
142
144
|
|
|
@@ -160,12 +162,18 @@ export class ClaudeCodeSpawner {
|
|
|
160
162
|
}
|
|
161
163
|
// authMode === 'system' — pass env as-is
|
|
162
164
|
|
|
163
|
-
|
|
165
|
+
|
|
166
|
+
// FIX: Ensure system auth uses local credentials, not the bot's API key
|
|
167
|
+
if (authMode === 'system') {
|
|
168
|
+
delete env.ANTHROPIC_API_KEY;
|
|
169
|
+
}
|
|
170
|
+
return env;
|
|
164
171
|
}
|
|
165
172
|
|
|
166
|
-
async run({ workingDirectory, prompt, maxTurns, onOutput, signal }) {
|
|
173
|
+
async run({ workingDirectory, prompt, maxTurns, timeoutMs, onOutput, signal }) {
|
|
167
174
|
const logger = getLogger();
|
|
168
175
|
const turns = maxTurns || this.maxTurns;
|
|
176
|
+
const effectiveTimeout = timeoutMs || this.timeout;
|
|
169
177
|
|
|
170
178
|
ensureClaudeCodeSetup();
|
|
171
179
|
|
|
@@ -185,7 +193,7 @@ export class ClaudeCodeSpawner {
|
|
|
185
193
|
|
|
186
194
|
const cmd = `claude ${args.map((a) => a.includes(' ') ? `"${a}"` : a).join(' ')}`;
|
|
187
195
|
logger.info(`Spawning: ${cmd.slice(0, 300)}`);
|
|
188
|
-
logger.info(`CWD: ${workingDirectory}`);
|
|
196
|
+
logger.info(`CWD: ${workingDirectory} | Timeout: ${effectiveTimeout / 1000}s | Max turns: ${turns}`);
|
|
189
197
|
|
|
190
198
|
// --- Smart output: consolidate tool activity into one editable message ---
|
|
191
199
|
let statusMsgId = null;
|
|
@@ -216,7 +224,9 @@ export class ClaudeCodeSpawner {
|
|
|
216
224
|
} else {
|
|
217
225
|
statusMsgId = await onOutput(buildStatusText());
|
|
218
226
|
}
|
|
219
|
-
} catch {
|
|
227
|
+
} catch (err) {
|
|
228
|
+
logger.debug(`flushStatus failed: ${err.message}`);
|
|
229
|
+
}
|
|
220
230
|
};
|
|
221
231
|
|
|
222
232
|
const addActivity = (line) => {
|
|
@@ -301,10 +311,20 @@ export class ClaudeCodeSpawner {
|
|
|
301
311
|
});
|
|
302
312
|
|
|
303
313
|
const timer = setTimeout(() => {
|
|
314
|
+
logger.warn(`Claude Code timed out after ${effectiveTimeout / 1000}s — sending SIGTERM`);
|
|
304
315
|
child.kill('SIGTERM');
|
|
305
|
-
if (smartOutput) smartOutput(`▸ Claude Code timed out after ${
|
|
306
|
-
|
|
307
|
-
|
|
316
|
+
if (smartOutput) smartOutput(`▸ Claude Code timed out after ${effectiveTimeout / 1000}s`).catch(() => {});
|
|
317
|
+
|
|
318
|
+
// Give it 10s to exit gracefully after SIGTERM, then force-kill
|
|
319
|
+
setTimeout(() => {
|
|
320
|
+
if (!child.killed) {
|
|
321
|
+
logger.warn('Claude Code did not exit after SIGTERM — sending SIGKILL');
|
|
322
|
+
child.kill('SIGKILL');
|
|
323
|
+
}
|
|
324
|
+
}, 10_000);
|
|
325
|
+
|
|
326
|
+
reject(new Error(`Claude Code timed out after ${effectiveTimeout / 1000}s`));
|
|
327
|
+
}, effectiveTimeout);
|
|
308
328
|
|
|
309
329
|
child.on('close', async (code) => {
|
|
310
330
|
clearTimeout(timer);
|
|
@@ -333,7 +353,9 @@ export class ClaudeCodeSpawner {
|
|
|
333
353
|
const finalState = code === 0 ? 'done' : 'error';
|
|
334
354
|
try {
|
|
335
355
|
await onOutput(buildStatusText(finalState), { editMessageId: statusMsgId });
|
|
336
|
-
} catch {
|
|
356
|
+
} catch (err) {
|
|
357
|
+
logger.debug(`Final status update failed: ${err.message}`);
|
|
358
|
+
}
|
|
337
359
|
}
|
|
338
360
|
|
|
339
361
|
logger.info(`Claude Code exited with code ${code} | stdout: ${fullOutput.length} chars | stderr: ${stderr.length} chars`);
|
package/src/conversation.js
CHANGED
|
@@ -43,10 +43,14 @@ export class ConversationManager {
|
|
|
43
43
|
const raw = readFileSync(this.filePath, 'utf-8');
|
|
44
44
|
const data = JSON.parse(raw);
|
|
45
45
|
|
|
46
|
-
// Restore per-chat skills
|
|
46
|
+
// Restore per-chat skills (backward compat: string → string[])
|
|
47
47
|
if (data._skills && typeof data._skills === 'object') {
|
|
48
|
-
for (const [chatId,
|
|
49
|
-
|
|
48
|
+
for (const [chatId, value] of Object.entries(data._skills)) {
|
|
49
|
+
// Old format stored a single string; new format stores an array
|
|
50
|
+
const skills = Array.isArray(value) ? value : (typeof value === 'string' ? [value] : []);
|
|
51
|
+
if (skills.length > 0) {
|
|
52
|
+
this.activeSkills.set(String(chatId), skills);
|
|
53
|
+
}
|
|
50
54
|
}
|
|
51
55
|
}
|
|
52
56
|
|
|
@@ -72,11 +76,11 @@ export class ConversationManager {
|
|
|
72
76
|
for (const [chatId, messages] of this.conversations) {
|
|
73
77
|
data[chatId] = messages;
|
|
74
78
|
}
|
|
75
|
-
// Persist active skills under a reserved key
|
|
79
|
+
// Persist active skills under a reserved key (stored as arrays)
|
|
76
80
|
if (this.activeSkills.size > 0) {
|
|
77
81
|
const skills = {};
|
|
78
|
-
for (const [chatId,
|
|
79
|
-
skills[chatId] =
|
|
82
|
+
for (const [chatId, skillIds] of this.activeSkills) {
|
|
83
|
+
skills[chatId] = skillIds;
|
|
80
84
|
}
|
|
81
85
|
data._skills = skills;
|
|
82
86
|
}
|
|
@@ -235,34 +239,107 @@ export class ConversationManager {
|
|
|
235
239
|
return history.length;
|
|
236
240
|
}
|
|
237
241
|
|
|
242
|
+
// ── Multi-skill methods ─────────────────────────────────────────────
|
|
243
|
+
|
|
244
|
+
/** Max active skills per chat. */
|
|
245
|
+
static MAX_SKILLS = 5;
|
|
246
|
+
|
|
238
247
|
/**
|
|
239
|
-
*
|
|
240
|
-
* @param {string|number} chatId
|
|
241
|
-
* @param {string}
|
|
248
|
+
* Replace all active skills for a chat.
|
|
249
|
+
* @param {string|number} chatId
|
|
250
|
+
* @param {string[]} skillIds
|
|
242
251
|
*/
|
|
243
|
-
|
|
244
|
-
|
|
252
|
+
setSkills(chatId, skillIds) {
|
|
253
|
+
const ids = skillIds.slice(0, ConversationManager.MAX_SKILLS);
|
|
254
|
+
this.activeSkills.set(String(chatId), ids);
|
|
245
255
|
this.save();
|
|
246
256
|
}
|
|
247
257
|
|
|
248
258
|
/**
|
|
249
|
-
*
|
|
250
|
-
* @param {string|number} chatId
|
|
251
|
-
* @
|
|
259
|
+
* Add a skill to a chat's active set. No-op if already active or at max.
|
|
260
|
+
* @param {string|number} chatId
|
|
261
|
+
* @param {string} skillId
|
|
262
|
+
* @returns {boolean} true if added, false if already active or at max
|
|
252
263
|
*/
|
|
253
|
-
|
|
254
|
-
|
|
264
|
+
addSkill(chatId, skillId) {
|
|
265
|
+
const key = String(chatId);
|
|
266
|
+
const current = this.activeSkills.get(key) || [];
|
|
267
|
+
if (current.includes(skillId)) return false;
|
|
268
|
+
if (current.length >= ConversationManager.MAX_SKILLS) return false;
|
|
269
|
+
current.push(skillId);
|
|
270
|
+
this.activeSkills.set(key, current);
|
|
271
|
+
this.save();
|
|
272
|
+
return true;
|
|
255
273
|
}
|
|
256
274
|
|
|
257
275
|
/**
|
|
258
|
-
*
|
|
259
|
-
* @param {string|number} chatId
|
|
276
|
+
* Remove a skill from a chat's active set.
|
|
277
|
+
* @param {string|number} chatId
|
|
278
|
+
* @param {string} skillId
|
|
279
|
+
* @returns {boolean} true if removed
|
|
260
280
|
*/
|
|
261
|
-
|
|
281
|
+
removeSkill(chatId, skillId) {
|
|
282
|
+
const key = String(chatId);
|
|
283
|
+
const current = this.activeSkills.get(key) || [];
|
|
284
|
+
const idx = current.indexOf(skillId);
|
|
285
|
+
if (idx === -1) return false;
|
|
286
|
+
current.splice(idx, 1);
|
|
287
|
+
if (current.length === 0) {
|
|
288
|
+
this.activeSkills.delete(key);
|
|
289
|
+
} else {
|
|
290
|
+
this.activeSkills.set(key, current);
|
|
291
|
+
}
|
|
292
|
+
this.save();
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Get all active skill IDs for a chat.
|
|
298
|
+
* @param {string|number} chatId
|
|
299
|
+
* @returns {string[]} Array of active skill IDs (empty if none).
|
|
300
|
+
*/
|
|
301
|
+
getSkills(chatId) {
|
|
302
|
+
return this.activeSkills.get(String(chatId)) || [];
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Clear all active skills for a chat.
|
|
307
|
+
* @param {string|number} chatId
|
|
308
|
+
*/
|
|
309
|
+
clearSkills(chatId) {
|
|
262
310
|
this.activeSkills.delete(String(chatId));
|
|
263
311
|
this.save();
|
|
264
312
|
}
|
|
265
313
|
|
|
314
|
+
// ── Backward-compatible single-skill aliases ───────────────────────
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Activate a single skill (replaces all). Backward compat.
|
|
318
|
+
* @param {string|number} chatId
|
|
319
|
+
* @param {string} skillId
|
|
320
|
+
*/
|
|
321
|
+
setSkill(chatId, skillId) {
|
|
322
|
+
this.setSkills(chatId, [skillId]);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Get the first active skill ID (or null). Backward compat.
|
|
327
|
+
* @param {string|number} chatId
|
|
328
|
+
* @returns {string|null}
|
|
329
|
+
*/
|
|
330
|
+
getSkill(chatId) {
|
|
331
|
+
const skills = this.getSkills(chatId);
|
|
332
|
+
return skills.length > 0 ? skills[0] : null;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Deactivate all skills. Backward compat alias for clearSkills.
|
|
337
|
+
* @param {string|number} chatId
|
|
338
|
+
*/
|
|
339
|
+
clearSkill(chatId) {
|
|
340
|
+
this.clearSkills(chatId);
|
|
341
|
+
}
|
|
342
|
+
|
|
266
343
|
/**
|
|
267
344
|
* Switch the backing file for this manager.
|
|
268
345
|
* Saves current data, clears in-memory state, then loads from the new file.
|
|
@@ -156,6 +156,12 @@
|
|
|
156
156
|
.auto-name { color: var(--accent); font-weight: 600; font-family: var(--font-hud); font-size: 9px; letter-spacing: 1px; }
|
|
157
157
|
.auto-detail { color: var(--dim); font-size: 10px; }
|
|
158
158
|
|
|
159
|
+
/* ═══════ SKILLS ═══════ */
|
|
160
|
+
.skill-item { padding: 2px 0; border-bottom: 1px solid rgba(57,255,20,0.03); }
|
|
161
|
+
.skill-emoji { margin-right: 4px; }
|
|
162
|
+
.skill-name { color: var(--accent); font-family: var(--font-hud); font-size: 10px; letter-spacing: 0.5px; }
|
|
163
|
+
.skill-desc { color: var(--dim); font-size: 9px; margin-top: 1px; }
|
|
164
|
+
|
|
159
165
|
/* ═══════ MARKDOWN CONTENT ═══════ */
|
|
160
166
|
.md-content { white-space: pre-wrap; word-wrap: break-word; color: var(--text); font-size: 11px; }
|
|
161
167
|
.md-content .ts-line { color: var(--accent); }
|