kernelbot 1.0.26 → 1.0.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +198 -124
- package/bin/kernel.js +201 -4
- package/package.json +1 -1
- package/src/agent.js +397 -222
- package/src/automation/automation-manager.js +377 -0
- package/src/automation/automation.js +79 -0
- package/src/automation/index.js +2 -0
- package/src/automation/scheduler.js +141 -0
- package/src/bot.js +667 -21
- package/src/conversation.js +33 -0
- package/src/intents/detector.js +50 -0
- package/src/intents/index.js +2 -0
- package/src/intents/planner.js +58 -0
- package/src/persona.js +68 -0
- package/src/prompts/orchestrator.js +76 -0
- package/src/prompts/persona.md +21 -0
- package/src/prompts/system.js +59 -6
- package/src/prompts/workers.js +89 -0
- package/src/providers/anthropic.js +23 -16
- package/src/providers/base.js +76 -2
- package/src/providers/index.js +1 -0
- package/src/providers/models.js +2 -1
- package/src/providers/openai-compat.js +5 -3
- package/src/security/confirm.js +7 -2
- package/src/skills/catalog.js +506 -0
- package/src/skills/custom.js +128 -0
- package/src/swarm/job-manager.js +169 -0
- package/src/swarm/job.js +67 -0
- package/src/swarm/worker-registry.js +74 -0
- package/src/tools/browser.js +458 -335
- package/src/tools/categories.js +3 -3
- package/src/tools/index.js +3 -0
- package/src/tools/orchestrator-tools.js +371 -0
- package/src/tools/persona.js +32 -0
- package/src/utils/config.js +50 -15
- package/src/worker.js +305 -0
- package/.agents/skills/interface-design/SKILL.md +0 -391
- package/.agents/skills/interface-design/references/critique.md +0 -67
- package/.agents/skills/interface-design/references/example.md +0 -86
- package/.agents/skills/interface-design/references/principles.md +0 -235
- package/.agents/skills/interface-design/references/validation.md +0 -48
package/src/tools/categories.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
export const TOOL_CATEGORIES = {
|
|
6
|
-
core: ['execute_command', 'read_file', 'write_file', 'list_directory'],
|
|
6
|
+
core: ['execute_command', 'read_file', 'write_file', 'list_directory', 'update_user_persona'],
|
|
7
7
|
git: ['git_clone', 'git_checkout', 'git_commit', 'git_push', 'git_diff'],
|
|
8
8
|
github: ['github_create_pr', 'github_get_pr_diff', 'github_post_review', 'github_create_repo', 'github_list_prs'],
|
|
9
9
|
coding: ['spawn_claude_code'],
|
|
@@ -11,7 +11,7 @@ export const TOOL_CATEGORIES = {
|
|
|
11
11
|
process: ['process_list', 'kill_process', 'service_control'],
|
|
12
12
|
monitor: ['disk_usage', 'memory_usage', 'cpu_usage', 'system_logs'],
|
|
13
13
|
network: ['check_port', 'curl_url', 'nginx_reload'],
|
|
14
|
-
browser: ['browse_website', 'screenshot_website', 'extract_content', 'send_image', 'interact_with_page'],
|
|
14
|
+
browser: ['web_search', 'browse_website', 'screenshot_website', 'extract_content', 'send_image', 'interact_with_page'],
|
|
15
15
|
jira: ['jira_get_ticket', 'jira_search_tickets', 'jira_list_my_tickets', 'jira_get_project_tickets'],
|
|
16
16
|
};
|
|
17
17
|
|
|
@@ -23,7 +23,7 @@ const CATEGORY_KEYWORDS = {
|
|
|
23
23
|
process: ['process', 'kill', 'restart', 'service', 'daemon', 'systemctl', 'pid'],
|
|
24
24
|
monitor: ['disk', 'memory', 'cpu', 'usage', 'monitor', 'logs', 'status', 'health', 'space'],
|
|
25
25
|
network: ['port', 'curl', 'http', 'nginx', 'network', 'api', 'endpoint', 'request', 'url', 'fetch'],
|
|
26
|
-
browser: ['browse', 'screenshot', 'scrape', 'website', 'web page', 'webpage', 'extract content', 'html', 'css selector'],
|
|
26
|
+
browser: ['search', 'find', 'look up', 'browse', 'screenshot', 'scrape', 'website', 'web page', 'webpage', 'extract content', 'html', 'css selector'],
|
|
27
27
|
jira: ['jira', 'ticket', 'issue', 'sprint', 'backlog', 'story', 'epic'],
|
|
28
28
|
};
|
|
29
29
|
|
package/src/tools/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import { definitions as githubDefinitions, handlers as githubHandlers } from './
|
|
|
8
8
|
import { definitions as codingDefinitions, handlers as codingHandlers } from './coding.js';
|
|
9
9
|
import { definitions as browserDefinitions, handlers as browserHandlers } from './browser.js';
|
|
10
10
|
import { definitions as jiraDefinitions, handlers as jiraHandlers } from './jira.js';
|
|
11
|
+
import { definitions as personaDefinitions, handlers as personaHandlers } from './persona.js';
|
|
11
12
|
import { logToolCall } from '../security/audit.js';
|
|
12
13
|
import { requiresConfirmation } from '../security/confirm.js';
|
|
13
14
|
|
|
@@ -22,6 +23,7 @@ export const toolDefinitions = [
|
|
|
22
23
|
...codingDefinitions,
|
|
23
24
|
...browserDefinitions,
|
|
24
25
|
...jiraDefinitions,
|
|
26
|
+
...personaDefinitions,
|
|
25
27
|
];
|
|
26
28
|
|
|
27
29
|
const handlerMap = {
|
|
@@ -35,6 +37,7 @@ const handlerMap = {
|
|
|
35
37
|
...codingHandlers,
|
|
36
38
|
...browserHandlers,
|
|
37
39
|
...jiraHandlers,
|
|
40
|
+
...personaHandlers,
|
|
38
41
|
};
|
|
39
42
|
|
|
40
43
|
export function checkConfirmation(name, params, config) {
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { WORKER_TYPES, getToolsForWorker, getToolNamesForWorkerType } from '../swarm/worker-registry.js';
|
|
2
|
+
import { getMissingCredential } from '../utils/config.js';
|
|
3
|
+
import { getLogger } from '../utils/logger.js';
|
|
4
|
+
|
|
5
|
+
const workerTypeEnum = Object.keys(WORKER_TYPES);
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Tool definitions for the orchestrator's 3 meta-tools.
|
|
9
|
+
*/
|
|
10
|
+
export const orchestratorToolDefinitions = [
|
|
11
|
+
{
|
|
12
|
+
name: 'dispatch_task',
|
|
13
|
+
description: `Dispatch a task to a specialized background worker. Available worker types: ${workerTypeEnum.join(', ')}. The worker runs in the background and you'll be notified when it completes.`,
|
|
14
|
+
input_schema: {
|
|
15
|
+
type: 'object',
|
|
16
|
+
properties: {
|
|
17
|
+
worker_type: {
|
|
18
|
+
type: 'string',
|
|
19
|
+
enum: workerTypeEnum,
|
|
20
|
+
description: 'The type of worker to dispatch the task to.',
|
|
21
|
+
},
|
|
22
|
+
task: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
description: 'A clear, detailed description of what the worker should do.',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
required: ['worker_type', 'task'],
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'list_jobs',
|
|
32
|
+
description: 'List all jobs for the current chat with their status, type, and summary.',
|
|
33
|
+
input_schema: {
|
|
34
|
+
type: 'object',
|
|
35
|
+
properties: {},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'cancel_job',
|
|
40
|
+
description: 'Cancel a running or queued job by its ID.',
|
|
41
|
+
input_schema: {
|
|
42
|
+
type: 'object',
|
|
43
|
+
properties: {
|
|
44
|
+
job_id: {
|
|
45
|
+
type: 'string',
|
|
46
|
+
description: 'The ID of the job to cancel.',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
required: ['job_id'],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'create_automation',
|
|
54
|
+
description: 'Create a recurring automation that runs on a schedule. The task description will be executed as a standalone prompt each time it fires.',
|
|
55
|
+
input_schema: {
|
|
56
|
+
type: 'object',
|
|
57
|
+
properties: {
|
|
58
|
+
name: {
|
|
59
|
+
type: 'string',
|
|
60
|
+
description: 'Short name for the automation (e.g. "Server Health Check").',
|
|
61
|
+
},
|
|
62
|
+
description: {
|
|
63
|
+
type: 'string',
|
|
64
|
+
description: 'Detailed task prompt that will be executed each time. Must be standalone and self-contained.',
|
|
65
|
+
},
|
|
66
|
+
schedule_type: {
|
|
67
|
+
type: 'string',
|
|
68
|
+
enum: ['cron', 'interval', 'random'],
|
|
69
|
+
description: 'Schedule type: cron (fixed times), interval (every N minutes), random (human-like random intervals).',
|
|
70
|
+
},
|
|
71
|
+
cron_expression: {
|
|
72
|
+
type: 'string',
|
|
73
|
+
description: 'Cron expression for schedule_type=cron (e.g. "0 9 * * *" for 9am daily). 5 fields: minute hour dayOfMonth month dayOfWeek.',
|
|
74
|
+
},
|
|
75
|
+
interval_minutes: {
|
|
76
|
+
type: 'number',
|
|
77
|
+
description: 'Interval in minutes for schedule_type=interval (minimum 5).',
|
|
78
|
+
},
|
|
79
|
+
min_minutes: {
|
|
80
|
+
type: 'number',
|
|
81
|
+
description: 'Minimum interval in minutes for schedule_type=random.',
|
|
82
|
+
},
|
|
83
|
+
max_minutes: {
|
|
84
|
+
type: 'number',
|
|
85
|
+
description: 'Maximum interval in minutes for schedule_type=random.',
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
required: ['name', 'description', 'schedule_type'],
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: 'list_automations',
|
|
93
|
+
description: 'List all automations for the current chat with their status, schedule, and next run time.',
|
|
94
|
+
input_schema: {
|
|
95
|
+
type: 'object',
|
|
96
|
+
properties: {},
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: 'update_automation',
|
|
101
|
+
description: 'Update an existing automation — change its name, description, schedule, or enable/disable it.',
|
|
102
|
+
input_schema: {
|
|
103
|
+
type: 'object',
|
|
104
|
+
properties: {
|
|
105
|
+
automation_id: {
|
|
106
|
+
type: 'string',
|
|
107
|
+
description: 'The ID of the automation to update.',
|
|
108
|
+
},
|
|
109
|
+
enabled: {
|
|
110
|
+
type: 'boolean',
|
|
111
|
+
description: 'Enable or disable the automation.',
|
|
112
|
+
},
|
|
113
|
+
name: {
|
|
114
|
+
type: 'string',
|
|
115
|
+
description: 'New name for the automation.',
|
|
116
|
+
},
|
|
117
|
+
description: {
|
|
118
|
+
type: 'string',
|
|
119
|
+
description: 'New task prompt for the automation.',
|
|
120
|
+
},
|
|
121
|
+
schedule_type: {
|
|
122
|
+
type: 'string',
|
|
123
|
+
enum: ['cron', 'interval', 'random'],
|
|
124
|
+
description: 'New schedule type.',
|
|
125
|
+
},
|
|
126
|
+
cron_expression: { type: 'string' },
|
|
127
|
+
interval_minutes: { type: 'number' },
|
|
128
|
+
min_minutes: { type: 'number' },
|
|
129
|
+
max_minutes: { type: 'number' },
|
|
130
|
+
},
|
|
131
|
+
required: ['automation_id'],
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: 'delete_automation',
|
|
136
|
+
description: 'Permanently delete an automation by its ID.',
|
|
137
|
+
input_schema: {
|
|
138
|
+
type: 'object',
|
|
139
|
+
properties: {
|
|
140
|
+
automation_id: {
|
|
141
|
+
type: 'string',
|
|
142
|
+
description: 'The ID of the automation to delete.',
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
required: ['automation_id'],
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Execute an orchestrator meta-tool.
|
|
152
|
+
* The dispatch_task handler is async and fire-and-forget — it returns immediately.
|
|
153
|
+
*
|
|
154
|
+
* @param {string} name - Tool name
|
|
155
|
+
* @param {object} input - Tool input
|
|
156
|
+
* @param {object} context - { chatId, jobManager, config, spawnWorker }
|
|
157
|
+
* @returns {object} Tool result
|
|
158
|
+
*/
|
|
159
|
+
export async function executeOrchestratorTool(name, input, context) {
|
|
160
|
+
const logger = getLogger();
|
|
161
|
+
const { chatId, jobManager, config, spawnWorker, automationManager } = context;
|
|
162
|
+
|
|
163
|
+
switch (name) {
|
|
164
|
+
case 'dispatch_task': {
|
|
165
|
+
const { worker_type, task } = input;
|
|
166
|
+
logger.info(`[dispatch_task] Request: type=${worker_type}, task="${task.slice(0, 120)}"`);
|
|
167
|
+
|
|
168
|
+
// Validate worker type
|
|
169
|
+
if (!WORKER_TYPES[worker_type]) {
|
|
170
|
+
logger.warn(`[dispatch_task] Invalid worker type: ${worker_type}`);
|
|
171
|
+
return { error: `Unknown worker type: ${worker_type}. Valid: ${workerTypeEnum.join(', ')}` };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Check concurrent job limit
|
|
175
|
+
const running = jobManager.getRunningJobsForChat(chatId);
|
|
176
|
+
const maxConcurrent = config.swarm?.max_concurrent_jobs || 3;
|
|
177
|
+
logger.debug(`[dispatch_task] Running jobs: ${running.length}/${maxConcurrent} for chat ${chatId}`);
|
|
178
|
+
if (running.length >= maxConcurrent) {
|
|
179
|
+
logger.warn(`[dispatch_task] Rejected — concurrent limit reached: ${running.length}/${maxConcurrent} jobs for chat ${chatId}`);
|
|
180
|
+
return { error: `Maximum concurrent jobs (${maxConcurrent}) reached. Wait for a job to finish or cancel one.` };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Pre-check credentials for the worker's tools
|
|
184
|
+
const toolNames = getToolNamesForWorkerType(worker_type);
|
|
185
|
+
for (const toolName of toolNames) {
|
|
186
|
+
const missing = getMissingCredential(toolName, config);
|
|
187
|
+
if (missing) {
|
|
188
|
+
logger.warn(`[dispatch_task] Missing credential for ${worker_type}: ${missing.envKey}`);
|
|
189
|
+
return {
|
|
190
|
+
error: `Missing credential for ${worker_type} worker: ${missing.label} (${missing.envKey}). Ask the user to provide it.`,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Create and spawn the job
|
|
196
|
+
const job = jobManager.createJob(chatId, worker_type, task);
|
|
197
|
+
const workerConfig = WORKER_TYPES[worker_type];
|
|
198
|
+
|
|
199
|
+
logger.info(`[dispatch_task] Dispatching job ${job.id} — ${workerConfig.emoji} ${workerConfig.label}: "${task.slice(0, 100)}"`);
|
|
200
|
+
|
|
201
|
+
// Fire and forget — spawnWorker handles lifecycle
|
|
202
|
+
spawnWorker(job).catch((err) => {
|
|
203
|
+
logger.error(`[dispatch_task] Worker spawn error for job ${job.id}: ${err.message}`);
|
|
204
|
+
if (!job.isTerminal) {
|
|
205
|
+
jobManager.failJob(job.id, err.message);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
job_id: job.id,
|
|
211
|
+
worker_type,
|
|
212
|
+
status: 'dispatched',
|
|
213
|
+
message: `${workerConfig.emoji} ${workerConfig.label} started.`,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
case 'list_jobs': {
|
|
218
|
+
const jobs = jobManager.getJobsForChat(chatId);
|
|
219
|
+
logger.info(`[list_jobs] Chat ${chatId} — ${jobs.length} jobs found`);
|
|
220
|
+
if (jobs.length > 0) {
|
|
221
|
+
logger.debug(`[list_jobs] Jobs: ${jobs.slice(0, 5).map(j => `${j.id}[${j.status}]`).join(', ')}${jobs.length > 5 ? '...' : ''}`);
|
|
222
|
+
}
|
|
223
|
+
if (jobs.length === 0) {
|
|
224
|
+
return { message: 'No jobs for this chat.' };
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
jobs: jobs.slice(0, 20).map((j) => ({
|
|
228
|
+
id: j.id,
|
|
229
|
+
worker_type: j.workerType,
|
|
230
|
+
status: j.status,
|
|
231
|
+
task: j.task.slice(0, 100),
|
|
232
|
+
duration: j.duration,
|
|
233
|
+
summary: j.toSummary(),
|
|
234
|
+
})),
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
case 'cancel_job': {
|
|
239
|
+
const { job_id } = input;
|
|
240
|
+
logger.info(`[cancel_job] Request to cancel job ${job_id} in chat ${chatId}`);
|
|
241
|
+
const job = jobManager.cancelJob(job_id);
|
|
242
|
+
if (!job) {
|
|
243
|
+
logger.warn(`[cancel_job] Job ${job_id} not found or already finished`);
|
|
244
|
+
return { error: `Job ${job_id} not found or already finished.` };
|
|
245
|
+
}
|
|
246
|
+
logger.info(`[cancel_job] Successfully cancelled job ${job_id} [${job.workerType}]`);
|
|
247
|
+
return {
|
|
248
|
+
job_id: job.id,
|
|
249
|
+
status: 'cancelled',
|
|
250
|
+
message: `Cancelled ${WORKER_TYPES[job.workerType]?.emoji || ''} ${job.workerType} worker.`,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
case 'create_automation': {
|
|
255
|
+
if (!automationManager) return { error: 'Automation system not available.' };
|
|
256
|
+
|
|
257
|
+
const { name: autoName, description, schedule_type, cron_expression, interval_minutes, min_minutes, max_minutes } = input;
|
|
258
|
+
logger.info(`[create_automation] Request: name="${autoName}", type=${schedule_type}`);
|
|
259
|
+
|
|
260
|
+
let schedule;
|
|
261
|
+
switch (schedule_type) {
|
|
262
|
+
case 'cron':
|
|
263
|
+
schedule = { type: 'cron', expression: cron_expression };
|
|
264
|
+
break;
|
|
265
|
+
case 'interval':
|
|
266
|
+
schedule = { type: 'interval', minutes: interval_minutes };
|
|
267
|
+
break;
|
|
268
|
+
case 'random':
|
|
269
|
+
schedule = { type: 'random', minMinutes: min_minutes, maxMinutes: max_minutes };
|
|
270
|
+
break;
|
|
271
|
+
default:
|
|
272
|
+
return { error: `Unknown schedule type: ${schedule_type}` };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
try {
|
|
276
|
+
const auto = automationManager.create(chatId, { name: autoName, description, schedule });
|
|
277
|
+
return {
|
|
278
|
+
automation_id: auto.id,
|
|
279
|
+
name: auto.name,
|
|
280
|
+
schedule: auto.schedule,
|
|
281
|
+
next_run: auto.nextRun ? new Date(auto.nextRun).toLocaleString() : null,
|
|
282
|
+
message: `Automation "${auto.name}" created and armed.`,
|
|
283
|
+
};
|
|
284
|
+
} catch (err) {
|
|
285
|
+
logger.warn(`[create_automation] Failed: ${err.message}`);
|
|
286
|
+
return { error: err.message };
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
case 'list_automations': {
|
|
291
|
+
if (!automationManager) return { error: 'Automation system not available.' };
|
|
292
|
+
|
|
293
|
+
const autos = automationManager.listForChat(chatId);
|
|
294
|
+
logger.info(`[list_automations] Chat ${chatId} — ${autos.length} automation(s)`);
|
|
295
|
+
|
|
296
|
+
if (autos.length === 0) {
|
|
297
|
+
return { message: 'No automations for this chat.' };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return {
|
|
301
|
+
automations: autos.map((a) => ({
|
|
302
|
+
id: a.id,
|
|
303
|
+
name: a.name,
|
|
304
|
+
description: a.description.slice(0, 100),
|
|
305
|
+
schedule: a.schedule,
|
|
306
|
+
enabled: a.enabled,
|
|
307
|
+
next_run: a.nextRun ? new Date(a.nextRun).toLocaleString() : null,
|
|
308
|
+
run_count: a.runCount,
|
|
309
|
+
last_error: a.lastError,
|
|
310
|
+
summary: a.toSummary(),
|
|
311
|
+
})),
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
case 'update_automation': {
|
|
316
|
+
if (!automationManager) return { error: 'Automation system not available.' };
|
|
317
|
+
|
|
318
|
+
const { automation_id, enabled, name: newName, description: newDesc, schedule_type: newSchedType, cron_expression: newCron, interval_minutes: newInterval, min_minutes: newMin, max_minutes: newMax } = input;
|
|
319
|
+
logger.info(`[update_automation] Request: id=${automation_id}`);
|
|
320
|
+
|
|
321
|
+
const changes = {};
|
|
322
|
+
if (enabled !== undefined) changes.enabled = enabled;
|
|
323
|
+
if (newName !== undefined) changes.name = newName;
|
|
324
|
+
if (newDesc !== undefined) changes.description = newDesc;
|
|
325
|
+
|
|
326
|
+
if (newSchedType !== undefined) {
|
|
327
|
+
switch (newSchedType) {
|
|
328
|
+
case 'cron':
|
|
329
|
+
changes.schedule = { type: 'cron', expression: newCron };
|
|
330
|
+
break;
|
|
331
|
+
case 'interval':
|
|
332
|
+
changes.schedule = { type: 'interval', minutes: newInterval };
|
|
333
|
+
break;
|
|
334
|
+
case 'random':
|
|
335
|
+
changes.schedule = { type: 'random', minMinutes: newMin, maxMinutes: newMax };
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
try {
|
|
341
|
+
const auto = automationManager.update(automation_id, changes);
|
|
342
|
+
if (!auto) return { error: `Automation ${automation_id} not found.` };
|
|
343
|
+
return {
|
|
344
|
+
automation_id: auto.id,
|
|
345
|
+
name: auto.name,
|
|
346
|
+
enabled: auto.enabled,
|
|
347
|
+
schedule: auto.schedule,
|
|
348
|
+
next_run: auto.nextRun ? new Date(auto.nextRun).toLocaleString() : null,
|
|
349
|
+
message: `Automation "${auto.name}" updated.`,
|
|
350
|
+
};
|
|
351
|
+
} catch (err) {
|
|
352
|
+
logger.warn(`[update_automation] Failed: ${err.message}`);
|
|
353
|
+
return { error: err.message };
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
case 'delete_automation': {
|
|
358
|
+
if (!automationManager) return { error: 'Automation system not available.' };
|
|
359
|
+
|
|
360
|
+
const { automation_id } = input;
|
|
361
|
+
logger.info(`[delete_automation] Request: id=${automation_id}`);
|
|
362
|
+
|
|
363
|
+
const deleted = automationManager.delete(automation_id);
|
|
364
|
+
if (!deleted) return { error: `Automation ${automation_id} not found.` };
|
|
365
|
+
return { automation_id, status: 'deleted', message: `Automation deleted.` };
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
default:
|
|
369
|
+
return { error: `Unknown orchestrator tool: ${name}` };
|
|
370
|
+
}
|
|
371
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export const definitions = [
|
|
2
|
+
{
|
|
3
|
+
name: 'update_user_persona',
|
|
4
|
+
description:
|
|
5
|
+
'Update the stored persona/profile for the current user. ' +
|
|
6
|
+
'Pass the COMPLETE updated persona document as markdown. ' +
|
|
7
|
+
'Before calling this, mentally merge any new information into the existing persona — ' +
|
|
8
|
+
'do not blindly append. Only call when you discover genuinely new, meaningful information ' +
|
|
9
|
+
'(expertise, preferences, projects, communication style).',
|
|
10
|
+
input_schema: {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: {
|
|
13
|
+
content: {
|
|
14
|
+
type: 'string',
|
|
15
|
+
description: 'The full updated persona markdown document.',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
required: ['content'],
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
export const handlers = {
|
|
24
|
+
async update_user_persona({ content }, context) {
|
|
25
|
+
const { personaManager, user } = context;
|
|
26
|
+
if (!personaManager) return { error: 'Persona manager not available.' };
|
|
27
|
+
if (!user?.id) return { error: 'User ID not available.' };
|
|
28
|
+
|
|
29
|
+
personaManager.save(user.id, content);
|
|
30
|
+
return { success: true, message: 'User persona updated.' };
|
|
31
|
+
},
|
|
32
|
+
};
|
package/src/utils/config.js
CHANGED
|
@@ -12,12 +12,22 @@ const DEFAULTS = {
|
|
|
12
12
|
name: 'KernelBot',
|
|
13
13
|
description: 'AI engineering agent with full OS control',
|
|
14
14
|
},
|
|
15
|
+
orchestrator: {
|
|
16
|
+
model: 'claude-opus-4-6',
|
|
17
|
+
max_tokens: 2048,
|
|
18
|
+
temperature: 0.3,
|
|
19
|
+
max_tool_depth: 5,
|
|
20
|
+
},
|
|
15
21
|
brain: {
|
|
16
22
|
provider: 'anthropic',
|
|
17
23
|
model: 'claude-sonnet-4-20250514',
|
|
18
24
|
max_tokens: 4096,
|
|
19
25
|
temperature: 0.3,
|
|
20
|
-
|
|
26
|
+
},
|
|
27
|
+
swarm: {
|
|
28
|
+
max_concurrent_jobs: 3,
|
|
29
|
+
job_timeout_seconds: 300,
|
|
30
|
+
cleanup_interval_minutes: 30,
|
|
21
31
|
},
|
|
22
32
|
telegram: {
|
|
23
33
|
allowed_users: [],
|
|
@@ -184,30 +194,50 @@ export function saveProviderToYaml(providerKey, modelId) {
|
|
|
184
194
|
* Full interactive flow: change brain model + optionally enter API key.
|
|
185
195
|
*/
|
|
186
196
|
export async function changeBrainModel(config, rl) {
|
|
197
|
+
const { createProvider } = await import('../providers/index.js');
|
|
187
198
|
const { providerKey, modelId } = await promptProviderSelection(rl);
|
|
188
199
|
|
|
189
200
|
const providerDef = PROVIDERS[providerKey];
|
|
201
|
+
|
|
202
|
+
// Resolve API key
|
|
203
|
+
const envKey = providerDef.envKey;
|
|
204
|
+
let apiKey = process.env[envKey];
|
|
205
|
+
if (!apiKey) {
|
|
206
|
+
const key = await ask(rl, chalk.cyan(`\n ${providerDef.name} API key (${envKey}): `));
|
|
207
|
+
if (!key.trim()) {
|
|
208
|
+
console.log(chalk.yellow('\n No API key provided. Brain not changed.\n'));
|
|
209
|
+
return config;
|
|
210
|
+
}
|
|
211
|
+
apiKey = key.trim();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Validate the new provider before saving anything
|
|
215
|
+
console.log(chalk.dim(`\n Verifying ${providerDef.name} / ${modelId}...`));
|
|
216
|
+
const testConfig = { ...config, brain: { ...config.brain, provider: providerKey, model: modelId, api_key: apiKey } };
|
|
217
|
+
try {
|
|
218
|
+
const testProvider = createProvider(testConfig);
|
|
219
|
+
await testProvider.ping();
|
|
220
|
+
} catch (err) {
|
|
221
|
+
console.log(chalk.red(`\n ✖ Verification failed: ${err.message}`));
|
|
222
|
+
console.log(chalk.yellow(` Brain not changed. Keeping current model.\n`));
|
|
223
|
+
return config;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Validation passed — save everything
|
|
190
227
|
const savedPath = saveProviderToYaml(providerKey, modelId);
|
|
191
|
-
console.log(chalk.dim(
|
|
228
|
+
console.log(chalk.dim(` Saved to ${savedPath}`));
|
|
192
229
|
|
|
193
|
-
// Update live config
|
|
194
230
|
config.brain.provider = providerKey;
|
|
195
231
|
config.brain.model = modelId;
|
|
232
|
+
config.brain.api_key = apiKey;
|
|
196
233
|
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const key = await ask(rl, chalk.cyan(`\n ${providerDef.name} API key (${envKey}): `));
|
|
202
|
-
if (key.trim()) {
|
|
203
|
-
saveCredential(config, envKey, key.trim());
|
|
204
|
-
config.brain.api_key = key.trim();
|
|
205
|
-
console.log(chalk.dim(' Saved.\n'));
|
|
206
|
-
}
|
|
207
|
-
} else {
|
|
208
|
-
config.brain.api_key = currentKey;
|
|
234
|
+
// Save the key if it was newly entered
|
|
235
|
+
if (!process.env[envKey]) {
|
|
236
|
+
saveCredential(config, envKey, apiKey);
|
|
237
|
+
console.log(chalk.dim(' API key saved.\n'));
|
|
209
238
|
}
|
|
210
239
|
|
|
240
|
+
console.log(chalk.green(` ✔ Brain switched to ${providerDef.name} / ${modelId}\n`));
|
|
211
241
|
return config;
|
|
212
242
|
}
|
|
213
243
|
|
|
@@ -299,6 +329,11 @@ export function loadConfig() {
|
|
|
299
329
|
|
|
300
330
|
const config = deepMerge(DEFAULTS, fileConfig);
|
|
301
331
|
|
|
332
|
+
// Orchestrator always uses Anthropic — resolve its API key
|
|
333
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
334
|
+
config.orchestrator.api_key = process.env.ANTHROPIC_API_KEY;
|
|
335
|
+
}
|
|
336
|
+
|
|
302
337
|
// Overlay env vars for brain API key based on provider
|
|
303
338
|
const providerDef = PROVIDERS[config.brain.provider];
|
|
304
339
|
if (providerDef && process.env[providerDef.envKey]) {
|