kernelbot 1.0.39 → 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 +5 -5
- package/config.example.yaml +1 -1
- package/package.json +1 -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/life/codebase.js +2 -1
- package/src/life/daydream_engine.js +386 -0
- package/src/life/engine.js +1 -0
- 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 +1 -1
- package/src/utils/display.js +1 -1
- 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/agent.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { createProvider, PROVIDERS } from './providers/index.js';
|
|
1
|
+
import { createProvider, PROVIDERS, MODEL_FALLBACKS } from './providers/index.js';
|
|
2
|
+
import { BaseProvider } from './providers/base.js';
|
|
2
3
|
import { orchestratorToolDefinitions, executeOrchestratorTool } from './tools/orchestrator-tools.js';
|
|
3
4
|
import { getToolsForWorker } from './swarm/worker-registry.js';
|
|
4
5
|
import { WORKER_TYPES } from './swarm/worker-registry.js';
|
|
5
6
|
import { getOrchestratorPrompt } from './prompts/orchestrator.js';
|
|
6
7
|
import { getWorkerPrompt } from './prompts/workers.js';
|
|
7
|
-
import {
|
|
8
|
+
import { getSkillById, buildSkillPrompt, filterSkillsForWorker } from './skills/loader.js';
|
|
8
9
|
import { WorkerAgent } from './worker.js';
|
|
9
10
|
import { getLogger } from './utils/logger.js';
|
|
10
11
|
import { getMissingCredential, saveCredential, saveProviderToYaml, saveOrchestratorToYaml, saveClaudeCodeModelToYaml, saveClaudeCodeAuth } from './utils/config.js';
|
|
@@ -79,8 +80,8 @@ export class OrchestratorAgent {
|
|
|
79
80
|
_getSystemPrompt(chatId, user, temporalContext = null) {
|
|
80
81
|
const logger = getLogger();
|
|
81
82
|
const key = this._chatKey(chatId);
|
|
82
|
-
const
|
|
83
|
-
const skillPrompt =
|
|
83
|
+
const skillIds = this.conversationManager.getSkills(key);
|
|
84
|
+
const skillPrompt = buildSkillPrompt(skillIds);
|
|
84
85
|
|
|
85
86
|
let userPersona = null;
|
|
86
87
|
if (this.personaManager && user?.id) {
|
|
@@ -104,21 +105,47 @@ export class OrchestratorAgent {
|
|
|
104
105
|
sharesBlock = this.shareQueue.buildShareBlock(user?.id || null);
|
|
105
106
|
}
|
|
106
107
|
|
|
107
|
-
logger.debug(`Orchestrator building system prompt for chat ${chatId} |
|
|
108
|
+
logger.debug(`Orchestrator building system prompt for chat ${chatId} | skills=${skillIds.length > 0 ? skillIds.join(',') : 'none'} | persona=${userPersona ? 'yes' : 'none'} | self=${selfData ? 'yes' : 'none'} | memories=${memoriesBlock ? 'yes' : 'none'} | shares=${sharesBlock ? 'yes' : 'none'} | temporal=${temporalContext ? 'yes' : 'none'} | character=${this._activeCharacterId || 'default'}`);
|
|
108
109
|
return getOrchestratorPrompt(this.config, skillPrompt || null, userPersona, selfData, memoriesBlock, sharesBlock, temporalContext, this._activePersonaMd, this._activeCharacterName);
|
|
109
110
|
}
|
|
110
111
|
|
|
112
|
+
// ── Multi-skill methods ─────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
/** Toggle a skill on/off for a chat. Returns { added: bool, skills: string[] }. */
|
|
115
|
+
toggleSkill(chatId, skillId) {
|
|
116
|
+
const key = this._chatKey(chatId);
|
|
117
|
+
const current = this.conversationManager.getSkills(key);
|
|
118
|
+
if (current.includes(skillId)) {
|
|
119
|
+
this.conversationManager.removeSkill(key, skillId);
|
|
120
|
+
return { added: false, skills: this.conversationManager.getSkills(key) };
|
|
121
|
+
}
|
|
122
|
+
const added = this.conversationManager.addSkill(key, skillId);
|
|
123
|
+
return { added, skills: this.conversationManager.getSkills(key) };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Get all active skill IDs for a chat. */
|
|
127
|
+
getActiveSkillIds(chatId) {
|
|
128
|
+
return this.conversationManager.getSkills(this._chatKey(chatId));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Get all active skill objects for a chat. */
|
|
132
|
+
getActiveSkills(chatId) {
|
|
133
|
+
const ids = this.getActiveSkillIds(chatId);
|
|
134
|
+
return ids.map(id => getSkillById(id)).filter(Boolean);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Backward-compat aliases
|
|
111
138
|
setSkill(chatId, skillId) {
|
|
112
139
|
this.conversationManager.setSkill(this._chatKey(chatId), skillId);
|
|
113
140
|
}
|
|
114
141
|
|
|
115
142
|
clearSkill(chatId) {
|
|
116
|
-
this.conversationManager.
|
|
143
|
+
this.conversationManager.clearSkills(this._chatKey(chatId));
|
|
117
144
|
}
|
|
118
145
|
|
|
119
146
|
getActiveSkill(chatId) {
|
|
120
|
-
const
|
|
121
|
-
return
|
|
147
|
+
const skills = this.getActiveSkills(chatId);
|
|
148
|
+
return skills.length > 0 ? skills[0] : null;
|
|
122
149
|
}
|
|
123
150
|
|
|
124
151
|
/**
|
|
@@ -508,44 +535,52 @@ export class OrchestratorAgent {
|
|
|
508
535
|
|
|
509
536
|
logger.info(`[Orchestrator] Job completed event: ${job.id} [${job.workerType}] in chat ${chatId} (${job.duration}s) — result length: ${(job.result || '').length} chars, structured: ${!!job.structuredResult}`);
|
|
510
537
|
|
|
511
|
-
// 1.
|
|
512
|
-
const
|
|
513
|
-
|
|
538
|
+
// 1. Store the job result in conversation history so the Orchestrator has full context
|
|
539
|
+
const resultEntry = this._buildResultHistoryEntry(job);
|
|
540
|
+
this.conversationManager.addMessage(convKey, 'assistant', resultEntry);
|
|
514
541
|
|
|
515
|
-
// 2.
|
|
542
|
+
// 2. Trigger a proactive Orchestrator generation — inject the job result as context
|
|
543
|
+
// and let the Orchestrator speak naturally (present results, suggest next steps, etc.)
|
|
516
544
|
try {
|
|
517
|
-
const
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
await this._sendUpdate(chatId, summary, { editMessageId: notifyMsgId });
|
|
524
|
-
} catch {
|
|
525
|
-
await this._sendUpdate(chatId, summary).catch(() => {});
|
|
545
|
+
const sr = job.structuredResult;
|
|
546
|
+
let resultContext;
|
|
547
|
+
if (sr?.structured) {
|
|
548
|
+
const parts = [`Summary: ${sr.summary}`, `Status: ${sr.status}`];
|
|
549
|
+
if (sr.artifacts?.length > 0) {
|
|
550
|
+
parts.push(`Artifacts: ${sr.artifacts.map(a => `${a.title || a.type}: ${a.url || a.path || ''}`).join(', ')}`);
|
|
526
551
|
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
this.conversationManager.addMessage(convKey, 'assistant', fallback);
|
|
532
|
-
try {
|
|
533
|
-
await this._sendUpdate(chatId, fallback, { editMessageId: notifyMsgId });
|
|
534
|
-
} catch {
|
|
535
|
-
await this._sendUpdate(chatId, fallback).catch(() => {});
|
|
552
|
+
if (sr.followUp) parts.push(`Follow-up: ${sr.followUp}`);
|
|
553
|
+
if (sr.details) {
|
|
554
|
+
const d = typeof sr.details === 'string' ? sr.details : JSON.stringify(sr.details, null, 2);
|
|
555
|
+
parts.push(`Details:\n${d.slice(0, 4000)}`);
|
|
536
556
|
}
|
|
557
|
+
resultContext = parts.join('\n');
|
|
558
|
+
} else {
|
|
559
|
+
resultContext = (job.result || 'Done.').slice(0, 4000);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const triggerMessage = `[System: The ${label} worker just finished job \`${job.id}\` (took ${job.duration}s). Results:\n\n${resultContext}\n\nProactively notify the user about the completed task. Present the results naturally, celebrate if appropriate, and suggest next steps. Do NOT mention "worker" or internal job details — speak as if you did the work yourself.]`;
|
|
563
|
+
|
|
564
|
+
// Add trigger as a transient user message (not stored in persistent history)
|
|
565
|
+
const messages = [...this.conversationManager.getSummarizedHistory(convKey)];
|
|
566
|
+
messages.push({ role: 'user', content: triggerMessage });
|
|
567
|
+
|
|
568
|
+
logger.info(`[Orchestrator] Triggering proactive follow-up for job ${job.id} in chat ${chatId}`);
|
|
569
|
+
|
|
570
|
+
const { max_tool_depth } = this.config.orchestrator;
|
|
571
|
+
const reply = await this._runLoop(chatId, messages, null, 0, max_tool_depth);
|
|
572
|
+
|
|
573
|
+
if (reply) {
|
|
574
|
+
logger.info(`[Orchestrator] Proactive reply for job ${job.id}: "${reply.slice(0, 200)}"`);
|
|
575
|
+
// _runLoop already stores the reply in conversation history
|
|
576
|
+
await this._sendUpdate(chatId, reply);
|
|
537
577
|
}
|
|
538
578
|
} catch (err) {
|
|
539
|
-
logger.error(`[Orchestrator]
|
|
540
|
-
//
|
|
579
|
+
logger.error(`[Orchestrator] Proactive follow-up failed for job ${job.id}: ${err.message}`);
|
|
580
|
+
// Fallback: send a simple formatted summary so the user isn't left hanging
|
|
541
581
|
const fallback = this._buildSummaryFallback(job, label);
|
|
542
582
|
this.conversationManager.addMessage(convKey, 'assistant', fallback);
|
|
543
|
-
|
|
544
|
-
try {
|
|
545
|
-
await this._sendUpdate(chatId, fallback, { editMessageId: notifyMsgId });
|
|
546
|
-
} catch {
|
|
547
|
-
await this._sendUpdate(chatId, fallback).catch(() => {});
|
|
548
|
-
}
|
|
583
|
+
await this._sendUpdate(chatId, fallback).catch(() => {});
|
|
549
584
|
}
|
|
550
585
|
});
|
|
551
586
|
|
|
@@ -564,7 +599,7 @@ export class OrchestratorAgent {
|
|
|
564
599
|
}
|
|
565
600
|
});
|
|
566
601
|
|
|
567
|
-
this.jobManager.on('job:failed', (job) => {
|
|
602
|
+
this.jobManager.on('job:failed', async (job) => {
|
|
568
603
|
const chatId = job.chatId;
|
|
569
604
|
const convKey = this._chatKey(chatId);
|
|
570
605
|
const workerDef = WORKER_TYPES[job.workerType] || {};
|
|
@@ -572,9 +607,32 @@ export class OrchestratorAgent {
|
|
|
572
607
|
|
|
573
608
|
logger.error(`[Orchestrator] Job failed event: ${job.id} [${job.workerType}] in chat ${chatId} — ${job.error}`);
|
|
574
609
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
this.
|
|
610
|
+
// Store failure context in history
|
|
611
|
+
const failMsg = `[${label} failed — job ${job.id}]: ${job.error}`;
|
|
612
|
+
this.conversationManager.addMessage(convKey, 'assistant', failMsg);
|
|
613
|
+
|
|
614
|
+
// Trigger proactive Orchestrator follow-up for the failure
|
|
615
|
+
try {
|
|
616
|
+
const triggerMessage = `[System: The ${label} worker failed on job \`${job.id}\`. Error: ${job.error}\n\nProactively notify the user about the failure. Explain what went wrong in simple terms, apologize if appropriate, and suggest how to fix or retry. Do NOT mention "worker" or internal job details — speak as if you encountered the issue yourself.]`;
|
|
617
|
+
|
|
618
|
+
const messages = [...this.conversationManager.getSummarizedHistory(convKey)];
|
|
619
|
+
messages.push({ role: 'user', content: triggerMessage });
|
|
620
|
+
|
|
621
|
+
logger.info(`[Orchestrator] Triggering proactive follow-up for failed job ${job.id} in chat ${chatId}`);
|
|
622
|
+
|
|
623
|
+
const { max_tool_depth } = this.config.orchestrator;
|
|
624
|
+
const reply = await this._runLoop(chatId, messages, null, 0, max_tool_depth);
|
|
625
|
+
|
|
626
|
+
if (reply) {
|
|
627
|
+
logger.info(`[Orchestrator] Proactive failure reply for job ${job.id}: "${reply.slice(0, 200)}"`);
|
|
628
|
+
await this._sendUpdate(chatId, reply);
|
|
629
|
+
}
|
|
630
|
+
} catch (err) {
|
|
631
|
+
logger.error(`[Orchestrator] Proactive failure follow-up failed for job ${job.id}: ${err.message}`);
|
|
632
|
+
// Fallback: send the simple failure message
|
|
633
|
+
const fallback = `❌ **${label} failed** (\`${job.id}\`): ${job.error}`;
|
|
634
|
+
await this._sendUpdate(chatId, fallback).catch(() => {});
|
|
635
|
+
}
|
|
578
636
|
});
|
|
579
637
|
|
|
580
638
|
this.jobManager.on('job:cancelled', (job) => {
|
|
@@ -589,73 +647,6 @@ export class OrchestratorAgent {
|
|
|
589
647
|
});
|
|
590
648
|
}
|
|
591
649
|
|
|
592
|
-
/**
|
|
593
|
-
* Auto-summarize a completed job result via the orchestrator LLM.
|
|
594
|
-
* Uses structured data for focused summarization when available.
|
|
595
|
-
* Short results (<500 chars) skip the LLM call entirely.
|
|
596
|
-
* Protected by the provider's built-in timeout (30s).
|
|
597
|
-
* Returns the summary text, or null. Caller handles delivery.
|
|
598
|
-
*/
|
|
599
|
-
async _summarizeJobResult(chatId, job) {
|
|
600
|
-
const logger = getLogger();
|
|
601
|
-
const workerDef = WORKER_TYPES[job.workerType] || {};
|
|
602
|
-
const label = workerDef.label || job.workerType;
|
|
603
|
-
|
|
604
|
-
logger.info(`[Orchestrator] Summarizing job ${job.id} [${job.workerType}] result for user`);
|
|
605
|
-
|
|
606
|
-
const sr = job.structuredResult;
|
|
607
|
-
const resultLen = (job.result || '').length;
|
|
608
|
-
|
|
609
|
-
// Direct coding jobs: Claude Code already produces clean, human-readable output.
|
|
610
|
-
// Skip LLM summarization to avoid timeouts and latency — use the result directly.
|
|
611
|
-
if (job.workerType === 'coding') {
|
|
612
|
-
logger.info(`[Orchestrator] Job ${job.id} is a coding job — using direct result (no LLM summary)`);
|
|
613
|
-
return this._buildSummaryFallback(job, label);
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
// Short results don't need LLM summarization
|
|
617
|
-
if (sr?.structured && resultLen < 500) {
|
|
618
|
-
logger.info(`[Orchestrator] Job ${job.id} result short enough — skipping LLM summary`);
|
|
619
|
-
return this._buildSummaryFallback(job, label);
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
// Build a focused prompt using structured data if available
|
|
623
|
-
let resultContext;
|
|
624
|
-
if (sr?.structured) {
|
|
625
|
-
const parts = [`Summary: ${sr.summary}`, `Status: ${sr.status}`];
|
|
626
|
-
if (sr.artifacts?.length > 0) {
|
|
627
|
-
parts.push(`Artifacts: ${sr.artifacts.map(a => `${a.title || a.type}: ${a.url || a.path}`).join(', ')}`);
|
|
628
|
-
}
|
|
629
|
-
if (sr.followUp) parts.push(`Follow-up: ${sr.followUp}`);
|
|
630
|
-
// Include details up to 8000 chars
|
|
631
|
-
if (sr.details) {
|
|
632
|
-
const d = typeof sr.details === 'string' ? sr.details : JSON.stringify(sr.details, null, 2);
|
|
633
|
-
parts.push(`Details:\n${d.slice(0, 8000)}`);
|
|
634
|
-
}
|
|
635
|
-
resultContext = parts.join('\n');
|
|
636
|
-
} else {
|
|
637
|
-
resultContext = (job.result || 'Done.').slice(0, 8000);
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
const history = this.conversationManager.getSummarizedHistory(this._chatKey(chatId));
|
|
641
|
-
|
|
642
|
-
const response = await this.orchestratorProvider.chat({
|
|
643
|
-
system: this._getSystemPrompt(chatId, null),
|
|
644
|
-
messages: [
|
|
645
|
-
...history,
|
|
646
|
-
{
|
|
647
|
-
role: 'user',
|
|
648
|
-
content: `The ${label} worker just finished job \`${job.id}\` (took ${job.duration}s). Here are the results:\n\n${resultContext}\n\nPresent these results to the user in a clean, well-formatted way. Don't mention "worker" or technical job details — just present the findings naturally as if you did the work yourself.`,
|
|
649
|
-
},
|
|
650
|
-
],
|
|
651
|
-
});
|
|
652
|
-
|
|
653
|
-
const summary = response.text || '';
|
|
654
|
-
logger.info(`[Orchestrator] Job ${job.id} summary: "${summary.slice(0, 200)}"`);
|
|
655
|
-
|
|
656
|
-
return summary || null;
|
|
657
|
-
}
|
|
658
|
-
|
|
659
650
|
/**
|
|
660
651
|
* Build a compact history entry for a completed job result.
|
|
661
652
|
* Stored as role: 'assistant' (not fake 'user') with up to 6000 chars of detail.
|
|
@@ -862,9 +853,10 @@ export class OrchestratorAgent {
|
|
|
862
853
|
}
|
|
863
854
|
};
|
|
864
855
|
|
|
865
|
-
// Get scoped tools and
|
|
856
|
+
// Get scoped tools and skills (filtered by worker affinity)
|
|
866
857
|
const tools = getToolsForWorker(job.workerType);
|
|
867
|
-
const
|
|
858
|
+
const allSkillIds = this.conversationManager.getSkills(this._chatKey(chatId));
|
|
859
|
+
const workerSkillIds = filterSkillsForWorker(allSkillIds, job.workerType);
|
|
868
860
|
|
|
869
861
|
// Build worker context (conversation history, persona, dependency results)
|
|
870
862
|
const workerContext = this._buildWorkerContext(job);
|
|
@@ -880,14 +872,14 @@ export class OrchestratorAgent {
|
|
|
880
872
|
}
|
|
881
873
|
}
|
|
882
874
|
|
|
883
|
-
logger.debug(`[Orchestrator] Worker ${job.id} config: ${tools.length} tools,
|
|
875
|
+
logger.debug(`[Orchestrator] Worker ${job.id} config: ${tools.length} tools, skills=${workerSkillIds.length > 0 ? workerSkillIds.join(',') : 'none'}, brain=${this.config.brain.provider}/${this.config.brain.model}, context=${workerContext ? 'yes' : 'none'}`);
|
|
884
876
|
|
|
885
877
|
const worker = new WorkerAgent({
|
|
886
878
|
config: workerConfig,
|
|
887
879
|
workerType: job.workerType,
|
|
888
880
|
jobId: job.id,
|
|
889
881
|
tools,
|
|
890
|
-
|
|
882
|
+
skillIds: workerSkillIds,
|
|
891
883
|
workerContext,
|
|
892
884
|
callbacks: {
|
|
893
885
|
onProgress: (text) => addActivity(text),
|
|
@@ -1129,11 +1121,54 @@ export class OrchestratorAgent {
|
|
|
1129
1121
|
workingMessages = [{ role: 'user', content: `[Worker Status]\n${digest}` }, ...workingMessages];
|
|
1130
1122
|
}
|
|
1131
1123
|
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1124
|
+
let response;
|
|
1125
|
+
let activeProvider = this.orchestratorProvider;
|
|
1126
|
+
|
|
1127
|
+
try {
|
|
1128
|
+
response = await activeProvider.chat({
|
|
1129
|
+
system: this._getSystemPrompt(chatId, user, temporalContext),
|
|
1130
|
+
messages: workingMessages,
|
|
1131
|
+
tools: orchestratorToolDefinitions,
|
|
1132
|
+
});
|
|
1133
|
+
} catch (err) {
|
|
1134
|
+
// If this is a model limitation, try falling back to a simpler model
|
|
1135
|
+
const currentModel = activeProvider.model;
|
|
1136
|
+
const fallbackModel = MODEL_FALLBACKS[currentModel];
|
|
1137
|
+
|
|
1138
|
+
if (fallbackModel && BaseProvider.isModelLimitation(err)) {
|
|
1139
|
+
logger.warn(`[Orchestrator] Model ${currentModel} hit limitation: ${err.message} — falling back to ${fallbackModel}`);
|
|
1140
|
+
this._sendUpdate(chatId, `⚠️ ${currentModel} hit a limitation, switching to ${fallbackModel}...`).catch(() => {});
|
|
1141
|
+
|
|
1142
|
+
try {
|
|
1143
|
+
const orchProviderKey = this.config.orchestrator.provider || 'anthropic';
|
|
1144
|
+
const orchProviderDef = PROVIDERS[orchProviderKey];
|
|
1145
|
+
const orchApiKey = this.config.orchestrator.api_key || (orchProviderDef && process.env[orchProviderDef.envKey]);
|
|
1146
|
+
|
|
1147
|
+
const fallbackProvider = createProvider({
|
|
1148
|
+
brain: {
|
|
1149
|
+
provider: orchProviderKey,
|
|
1150
|
+
model: fallbackModel,
|
|
1151
|
+
max_tokens: this.config.orchestrator.max_tokens,
|
|
1152
|
+
temperature: this.config.orchestrator.temperature,
|
|
1153
|
+
api_key: orchApiKey,
|
|
1154
|
+
timeout: 30_000,
|
|
1155
|
+
},
|
|
1156
|
+
});
|
|
1157
|
+
|
|
1158
|
+
response = await fallbackProvider.chat({
|
|
1159
|
+
system: this._getSystemPrompt(chatId, user, temporalContext),
|
|
1160
|
+
messages: workingMessages,
|
|
1161
|
+
tools: orchestratorToolDefinitions,
|
|
1162
|
+
});
|
|
1163
|
+
activeProvider = fallbackProvider;
|
|
1164
|
+
} catch (fallbackErr) {
|
|
1165
|
+
logger.error(`[Orchestrator] Fallback model ${fallbackModel} also failed: ${fallbackErr.message}`);
|
|
1166
|
+
throw err; // Throw the original error
|
|
1167
|
+
}
|
|
1168
|
+
} else {
|
|
1169
|
+
throw err;
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1137
1172
|
|
|
1138
1173
|
logger.info(`[Orchestrator] LLM response: stopReason=${response.stopReason}, text=${(response.text || '').length} chars, toolCalls=${(response.toolCalls || []).length}`);
|
|
1139
1174
|
|
|
@@ -108,7 +108,13 @@ function nextCronTime(expression, after) {
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
/**
|
|
111
|
-
* Parse a single cron field into a Set of valid values.
|
|
111
|
+
* Parse a single cron field into a Set of valid integer values.
|
|
112
|
+
*
|
|
113
|
+
* @param {string} field - Cron field expression (e.g. '*', '1-5', '*\/10', '1,3,5')
|
|
114
|
+
* @param {number} min - Minimum allowed value for this field (inclusive)
|
|
115
|
+
* @param {number} max - Maximum allowed value for this field (inclusive)
|
|
116
|
+
* @returns {Set<number>} Set of matching integer values clamped to [min, max]
|
|
117
|
+
* @throws {Error} If the field contains invalid syntax or out-of-range values
|
|
112
118
|
*/
|
|
113
119
|
function parseField(field, min, max) {
|
|
114
120
|
const values = new Set();
|
|
@@ -119,21 +125,48 @@ function parseField(field, min, max) {
|
|
|
119
125
|
} else if (part.includes('/')) {
|
|
120
126
|
const [range, stepStr] = part.split('/');
|
|
121
127
|
const step = parseInt(stepStr, 10);
|
|
128
|
+
if (isNaN(step) || step <= 0) {
|
|
129
|
+
throw new Error(`Invalid cron step value "${stepStr}" in field "${field}"`);
|
|
130
|
+
}
|
|
122
131
|
let start = min;
|
|
123
132
|
let end = max;
|
|
124
133
|
if (range !== '*') {
|
|
125
134
|
if (range.includes('-')) {
|
|
126
|
-
|
|
135
|
+
const parts = range.split('-').map(Number);
|
|
136
|
+
start = parts[0];
|
|
137
|
+
end = parts[1];
|
|
138
|
+
if (isNaN(start) || isNaN(end)) {
|
|
139
|
+
throw new Error(`Invalid cron range "${range}" in field "${field}"`);
|
|
140
|
+
}
|
|
127
141
|
} else {
|
|
128
142
|
start = parseInt(range, 10);
|
|
143
|
+
if (isNaN(start)) {
|
|
144
|
+
throw new Error(`Invalid cron value "${range}" in field "${field}"`);
|
|
145
|
+
}
|
|
129
146
|
}
|
|
130
147
|
}
|
|
148
|
+
if (start < min || end > max) {
|
|
149
|
+
throw new Error(`Cron field value out of range [${min}-${max}] in "${field}"`);
|
|
150
|
+
}
|
|
131
151
|
for (let i = start; i <= end; i += step) values.add(i);
|
|
132
152
|
} else if (part.includes('-')) {
|
|
133
153
|
const [s, e] = part.split('-').map(Number);
|
|
154
|
+
if (isNaN(s) || isNaN(e)) {
|
|
155
|
+
throw new Error(`Invalid cron range "${part}" in field "${field}"`);
|
|
156
|
+
}
|
|
157
|
+
if (s < min || e > max) {
|
|
158
|
+
throw new Error(`Cron field value out of range [${min}-${max}] in "${field}"`);
|
|
159
|
+
}
|
|
134
160
|
for (let i = s; i <= e; i++) values.add(i);
|
|
135
161
|
} else {
|
|
136
|
-
|
|
162
|
+
const val = parseInt(part, 10);
|
|
163
|
+
if (isNaN(val)) {
|
|
164
|
+
throw new Error(`Invalid cron value "${part}" in field "${field}"`);
|
|
165
|
+
}
|
|
166
|
+
if (val < min || val > max) {
|
|
167
|
+
throw new Error(`Cron field value ${val} out of range [${min}-${max}] in "${field}"`);
|
|
168
|
+
}
|
|
169
|
+
values.add(val);
|
|
137
170
|
}
|
|
138
171
|
}
|
|
139
172
|
|