kernelbot 1.0.26 → 1.0.30
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/README.md +198 -124
- package/bin/kernel.js +208 -4
- package/config.example.yaml +14 -1
- package/package.json +1 -1
- package/src/agent.js +839 -209
- 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 +1001 -18
- package/src/claude-auth.js +93 -0
- package/src/coder.js +48 -6
- 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 +124 -0
- package/src/prompts/persona.md +21 -0
- package/src/prompts/system.js +59 -6
- package/src/prompts/workers.js +148 -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/audit.js +0 -0
- package/src/security/auth.js +0 -0
- package/src/security/confirm.js +7 -2
- package/src/self.js +122 -0
- package/src/services/stt.js +139 -0
- package/src/services/tts.js +124 -0
- package/src/skills/catalog.js +506 -0
- package/src/skills/custom.js +128 -0
- package/src/swarm/job-manager.js +216 -0
- package/src/swarm/job.js +85 -0
- package/src/swarm/worker-registry.js +79 -0
- package/src/tools/browser.js +458 -335
- package/src/tools/categories.js +3 -3
- package/src/tools/coding.js +5 -0
- package/src/tools/docker.js +0 -0
- package/src/tools/git.js +0 -0
- package/src/tools/github.js +0 -0
- package/src/tools/index.js +3 -0
- package/src/tools/jira.js +0 -0
- package/src/tools/monitor.js +0 -0
- package/src/tools/network.js +0 -0
- package/src/tools/orchestrator-tools.js +428 -0
- package/src/tools/os.js +14 -1
- package/src/tools/persona.js +32 -0
- package/src/tools/process.js +0 -0
- package/src/utils/config.js +153 -15
- package/src/utils/display.js +0 -0
- package/src/utils/logger.js +0 -0
- package/src/worker.js +396 -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/coding.js
CHANGED
|
@@ -10,6 +10,10 @@ function getSpawner(config) {
|
|
|
10
10
|
return spawner;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
export function resetClaudeCodeSpawner() {
|
|
14
|
+
spawner = null;
|
|
15
|
+
}
|
|
16
|
+
|
|
13
17
|
export const definitions = [
|
|
14
18
|
{
|
|
15
19
|
name: 'spawn_claude_code',
|
|
@@ -57,6 +61,7 @@ export const handlers = {
|
|
|
57
61
|
prompt: params.prompt,
|
|
58
62
|
maxTurns: params.max_turns,
|
|
59
63
|
onOutput: onUpdate,
|
|
64
|
+
signal: context.signal || null,
|
|
60
65
|
});
|
|
61
66
|
|
|
62
67
|
// Show stderr if any
|
package/src/tools/docker.js
CHANGED
|
File without changes
|
package/src/tools/git.js
CHANGED
|
File without changes
|
package/src/tools/github.js
CHANGED
|
File without changes
|
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) {
|
package/src/tools/jira.js
CHANGED
|
File without changes
|
package/src/tools/monitor.js
CHANGED
|
File without changes
|
package/src/tools/network.js
CHANGED
|
File without changes
|
|
@@ -0,0 +1,428 @@
|
|
|
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
|
+
context: {
|
|
27
|
+
type: 'string',
|
|
28
|
+
description: 'Optional background context for the worker — relevant conversation details, goals, constraints. The worker cannot see the chat history, so include anything important here.',
|
|
29
|
+
},
|
|
30
|
+
depends_on: {
|
|
31
|
+
type: 'array',
|
|
32
|
+
items: { type: 'string' },
|
|
33
|
+
description: 'Optional array of job IDs that must complete before this worker starts. The worker will receive dependency results as context.',
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
required: ['worker_type', 'task'],
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'list_jobs',
|
|
41
|
+
description: 'List all jobs for the current chat with their status, type, and summary.',
|
|
42
|
+
input_schema: {
|
|
43
|
+
type: 'object',
|
|
44
|
+
properties: {},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'cancel_job',
|
|
49
|
+
description: 'Cancel a running or queued job by its ID.',
|
|
50
|
+
input_schema: {
|
|
51
|
+
type: 'object',
|
|
52
|
+
properties: {
|
|
53
|
+
job_id: {
|
|
54
|
+
type: 'string',
|
|
55
|
+
description: 'The ID of the job to cancel.',
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
required: ['job_id'],
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: 'create_automation',
|
|
63
|
+
description: 'Create a recurring automation that runs on a schedule. The task description will be executed as a standalone prompt each time it fires.',
|
|
64
|
+
input_schema: {
|
|
65
|
+
type: 'object',
|
|
66
|
+
properties: {
|
|
67
|
+
name: {
|
|
68
|
+
type: 'string',
|
|
69
|
+
description: 'Short name for the automation (e.g. "Server Health Check").',
|
|
70
|
+
},
|
|
71
|
+
description: {
|
|
72
|
+
type: 'string',
|
|
73
|
+
description: 'Detailed task prompt that will be executed each time. Must be standalone and self-contained.',
|
|
74
|
+
},
|
|
75
|
+
schedule_type: {
|
|
76
|
+
type: 'string',
|
|
77
|
+
enum: ['cron', 'interval', 'random'],
|
|
78
|
+
description: 'Schedule type: cron (fixed times), interval (every N minutes), random (human-like random intervals).',
|
|
79
|
+
},
|
|
80
|
+
cron_expression: {
|
|
81
|
+
type: 'string',
|
|
82
|
+
description: 'Cron expression for schedule_type=cron (e.g. "0 9 * * *" for 9am daily). 5 fields: minute hour dayOfMonth month dayOfWeek.',
|
|
83
|
+
},
|
|
84
|
+
interval_minutes: {
|
|
85
|
+
type: 'number',
|
|
86
|
+
description: 'Interval in minutes for schedule_type=interval (minimum 5).',
|
|
87
|
+
},
|
|
88
|
+
min_minutes: {
|
|
89
|
+
type: 'number',
|
|
90
|
+
description: 'Minimum interval in minutes for schedule_type=random.',
|
|
91
|
+
},
|
|
92
|
+
max_minutes: {
|
|
93
|
+
type: 'number',
|
|
94
|
+
description: 'Maximum interval in minutes for schedule_type=random.',
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
required: ['name', 'description', 'schedule_type'],
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: 'list_automations',
|
|
102
|
+
description: 'List all automations for the current chat with their status, schedule, and next run time.',
|
|
103
|
+
input_schema: {
|
|
104
|
+
type: 'object',
|
|
105
|
+
properties: {},
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: 'update_automation',
|
|
110
|
+
description: 'Update an existing automation — change its name, description, schedule, or enable/disable it.',
|
|
111
|
+
input_schema: {
|
|
112
|
+
type: 'object',
|
|
113
|
+
properties: {
|
|
114
|
+
automation_id: {
|
|
115
|
+
type: 'string',
|
|
116
|
+
description: 'The ID of the automation to update.',
|
|
117
|
+
},
|
|
118
|
+
enabled: {
|
|
119
|
+
type: 'boolean',
|
|
120
|
+
description: 'Enable or disable the automation.',
|
|
121
|
+
},
|
|
122
|
+
name: {
|
|
123
|
+
type: 'string',
|
|
124
|
+
description: 'New name for the automation.',
|
|
125
|
+
},
|
|
126
|
+
description: {
|
|
127
|
+
type: 'string',
|
|
128
|
+
description: 'New task prompt for the automation.',
|
|
129
|
+
},
|
|
130
|
+
schedule_type: {
|
|
131
|
+
type: 'string',
|
|
132
|
+
enum: ['cron', 'interval', 'random'],
|
|
133
|
+
description: 'New schedule type.',
|
|
134
|
+
},
|
|
135
|
+
cron_expression: { type: 'string' },
|
|
136
|
+
interval_minutes: { type: 'number' },
|
|
137
|
+
min_minutes: { type: 'number' },
|
|
138
|
+
max_minutes: { type: 'number' },
|
|
139
|
+
},
|
|
140
|
+
required: ['automation_id'],
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: 'delete_automation',
|
|
145
|
+
description: 'Permanently delete an automation by its ID.',
|
|
146
|
+
input_schema: {
|
|
147
|
+
type: 'object',
|
|
148
|
+
properties: {
|
|
149
|
+
automation_id: {
|
|
150
|
+
type: 'string',
|
|
151
|
+
description: 'The ID of the automation to delete.',
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
required: ['automation_id'],
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Execute an orchestrator meta-tool.
|
|
161
|
+
* The dispatch_task handler is async and fire-and-forget — it returns immediately.
|
|
162
|
+
*
|
|
163
|
+
* @param {string} name - Tool name
|
|
164
|
+
* @param {object} input - Tool input
|
|
165
|
+
* @param {object} context - { chatId, jobManager, config, spawnWorker }
|
|
166
|
+
* @returns {object} Tool result
|
|
167
|
+
*/
|
|
168
|
+
export async function executeOrchestratorTool(name, input, context) {
|
|
169
|
+
const logger = getLogger();
|
|
170
|
+
const { chatId, jobManager, config, spawnWorker, automationManager, user } = context;
|
|
171
|
+
|
|
172
|
+
switch (name) {
|
|
173
|
+
case 'dispatch_task': {
|
|
174
|
+
const { worker_type, task, context: taskContext, depends_on } = input;
|
|
175
|
+
logger.info(`[dispatch_task] Request: type=${worker_type}, task="${task.slice(0, 120)}", deps=${depends_on?.length || 0}`);
|
|
176
|
+
|
|
177
|
+
// Validate worker type
|
|
178
|
+
if (!WORKER_TYPES[worker_type]) {
|
|
179
|
+
logger.warn(`[dispatch_task] Invalid worker type: ${worker_type}`);
|
|
180
|
+
return { error: `Unknown worker type: ${worker_type}. Valid: ${workerTypeEnum.join(', ')}` };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Validate dependency IDs
|
|
184
|
+
const depIds = Array.isArray(depends_on) ? depends_on : [];
|
|
185
|
+
for (const depId of depIds) {
|
|
186
|
+
const depJob = jobManager.getJob(depId);
|
|
187
|
+
if (!depJob) {
|
|
188
|
+
logger.warn(`[dispatch_task] Unknown dependency job: ${depId}`);
|
|
189
|
+
return { error: `Dependency job not found: ${depId}` };
|
|
190
|
+
}
|
|
191
|
+
if (depJob.status === 'failed' || depJob.status === 'cancelled') {
|
|
192
|
+
logger.warn(`[dispatch_task] Dependency job ${depId} already ${depJob.status}`);
|
|
193
|
+
return { error: `Dependency job ${depId} already ${depJob.status}. Cannot dispatch.` };
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Check concurrent job limit (only if we'll spawn immediately)
|
|
198
|
+
const hasUnmetDeps = depIds.some(id => {
|
|
199
|
+
const dep = jobManager.getJob(id);
|
|
200
|
+
return dep && dep.status !== 'completed';
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
if (!hasUnmetDeps) {
|
|
204
|
+
const running = jobManager.getRunningJobsForChat(chatId);
|
|
205
|
+
const maxConcurrent = config.swarm?.max_concurrent_jobs || 3;
|
|
206
|
+
logger.debug(`[dispatch_task] Running jobs: ${running.length}/${maxConcurrent} for chat ${chatId}`);
|
|
207
|
+
if (running.length >= maxConcurrent) {
|
|
208
|
+
logger.warn(`[dispatch_task] Rejected — concurrent limit reached: ${running.length}/${maxConcurrent} jobs for chat ${chatId}`);
|
|
209
|
+
return { error: `Maximum concurrent jobs (${maxConcurrent}) reached. Wait for a job to finish or cancel one.` };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Pre-check credentials for the worker's tools
|
|
214
|
+
const toolNames = getToolNamesForWorkerType(worker_type);
|
|
215
|
+
for (const toolName of toolNames) {
|
|
216
|
+
const missing = getMissingCredential(toolName, config);
|
|
217
|
+
if (missing) {
|
|
218
|
+
logger.warn(`[dispatch_task] Missing credential for ${worker_type}: ${missing.envKey}`);
|
|
219
|
+
return {
|
|
220
|
+
error: `Missing credential for ${worker_type} worker: ${missing.label} (${missing.envKey}). Ask the user to provide it.`,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Create the job with context and dependencies
|
|
226
|
+
const job = jobManager.createJob(chatId, worker_type, task);
|
|
227
|
+
job.context = taskContext || null;
|
|
228
|
+
job.dependsOn = depIds;
|
|
229
|
+
job.userId = user?.id || null;
|
|
230
|
+
const workerConfig = WORKER_TYPES[worker_type];
|
|
231
|
+
|
|
232
|
+
// If dependencies are not all met, leave job queued (job:ready will spawn it later)
|
|
233
|
+
if (hasUnmetDeps) {
|
|
234
|
+
logger.info(`[dispatch_task] Job ${job.id} queued — waiting for dependencies: ${depIds.join(', ')}`);
|
|
235
|
+
return {
|
|
236
|
+
job_id: job.id,
|
|
237
|
+
worker_type,
|
|
238
|
+
status: 'queued_waiting',
|
|
239
|
+
depends_on: depIds,
|
|
240
|
+
message: `${workerConfig.emoji} ${workerConfig.label} queued — waiting for ${depIds.length} dependency job(s) to complete.`,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
logger.info(`[dispatch_task] Dispatching job ${job.id} — ${workerConfig.emoji} ${workerConfig.label}: "${task.slice(0, 100)}"`);
|
|
245
|
+
|
|
246
|
+
// Fire and forget — spawnWorker handles lifecycle
|
|
247
|
+
spawnWorker(job).catch((err) => {
|
|
248
|
+
logger.error(`[dispatch_task] Worker spawn error for job ${job.id}: ${err.message}`);
|
|
249
|
+
if (!job.isTerminal) {
|
|
250
|
+
jobManager.failJob(job.id, err.message);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
job_id: job.id,
|
|
256
|
+
worker_type,
|
|
257
|
+
status: 'dispatched',
|
|
258
|
+
message: `${workerConfig.emoji} ${workerConfig.label} started.`,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
case 'list_jobs': {
|
|
263
|
+
const jobs = jobManager.getJobsForChat(chatId);
|
|
264
|
+
logger.info(`[list_jobs] Chat ${chatId} — ${jobs.length} jobs found`);
|
|
265
|
+
if (jobs.length > 0) {
|
|
266
|
+
logger.debug(`[list_jobs] Jobs: ${jobs.slice(0, 5).map(j => `${j.id}[${j.status}]`).join(', ')}${jobs.length > 5 ? '...' : ''}`);
|
|
267
|
+
}
|
|
268
|
+
if (jobs.length === 0) {
|
|
269
|
+
return { message: 'No jobs for this chat.' };
|
|
270
|
+
}
|
|
271
|
+
return {
|
|
272
|
+
jobs: jobs.slice(0, 20).map((j) => {
|
|
273
|
+
const entry = {
|
|
274
|
+
id: j.id,
|
|
275
|
+
worker_type: j.workerType,
|
|
276
|
+
status: j.status,
|
|
277
|
+
task: j.task.slice(0, 100),
|
|
278
|
+
duration: j.duration,
|
|
279
|
+
recent_activity: j.progress.slice(-5),
|
|
280
|
+
last_activity_seconds_ago: j.lastActivity ? Math.round((Date.now() - j.lastActivity) / 1000) : null,
|
|
281
|
+
summary: j.toSummary(),
|
|
282
|
+
};
|
|
283
|
+
if (j.dependsOn.length > 0) entry.depends_on = j.dependsOn;
|
|
284
|
+
if (j.structuredResult) {
|
|
285
|
+
entry.result_summary = j.structuredResult.summary;
|
|
286
|
+
entry.result_status = j.structuredResult.status;
|
|
287
|
+
if (j.structuredResult.artifacts?.length > 0) entry.artifacts = j.structuredResult.artifacts;
|
|
288
|
+
if (j.structuredResult.followUp) entry.follow_up = j.structuredResult.followUp;
|
|
289
|
+
}
|
|
290
|
+
return entry;
|
|
291
|
+
}),
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
case 'cancel_job': {
|
|
296
|
+
const { job_id } = input;
|
|
297
|
+
logger.info(`[cancel_job] Request to cancel job ${job_id} in chat ${chatId}`);
|
|
298
|
+
const job = jobManager.cancelJob(job_id);
|
|
299
|
+
if (!job) {
|
|
300
|
+
logger.warn(`[cancel_job] Job ${job_id} not found or already finished`);
|
|
301
|
+
return { error: `Job ${job_id} not found or already finished.` };
|
|
302
|
+
}
|
|
303
|
+
logger.info(`[cancel_job] Successfully cancelled job ${job_id} [${job.workerType}]`);
|
|
304
|
+
return {
|
|
305
|
+
job_id: job.id,
|
|
306
|
+
status: 'cancelled',
|
|
307
|
+
message: `Cancelled ${WORKER_TYPES[job.workerType]?.emoji || ''} ${job.workerType} worker.`,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
case 'create_automation': {
|
|
312
|
+
if (!automationManager) return { error: 'Automation system not available.' };
|
|
313
|
+
|
|
314
|
+
const { name: autoName, description, schedule_type, cron_expression, interval_minutes, min_minutes, max_minutes } = input;
|
|
315
|
+
logger.info(`[create_automation] Request: name="${autoName}", type=${schedule_type}`);
|
|
316
|
+
|
|
317
|
+
let schedule;
|
|
318
|
+
switch (schedule_type) {
|
|
319
|
+
case 'cron':
|
|
320
|
+
schedule = { type: 'cron', expression: cron_expression };
|
|
321
|
+
break;
|
|
322
|
+
case 'interval':
|
|
323
|
+
schedule = { type: 'interval', minutes: interval_minutes };
|
|
324
|
+
break;
|
|
325
|
+
case 'random':
|
|
326
|
+
schedule = { type: 'random', minMinutes: min_minutes, maxMinutes: max_minutes };
|
|
327
|
+
break;
|
|
328
|
+
default:
|
|
329
|
+
return { error: `Unknown schedule type: ${schedule_type}` };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
try {
|
|
333
|
+
const auto = automationManager.create(chatId, { name: autoName, description, schedule });
|
|
334
|
+
return {
|
|
335
|
+
automation_id: auto.id,
|
|
336
|
+
name: auto.name,
|
|
337
|
+
schedule: auto.schedule,
|
|
338
|
+
next_run: auto.nextRun ? new Date(auto.nextRun).toLocaleString() : null,
|
|
339
|
+
message: `Automation "${auto.name}" created and armed.`,
|
|
340
|
+
};
|
|
341
|
+
} catch (err) {
|
|
342
|
+
logger.warn(`[create_automation] Failed: ${err.message}`);
|
|
343
|
+
return { error: err.message };
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
case 'list_automations': {
|
|
348
|
+
if (!automationManager) return { error: 'Automation system not available.' };
|
|
349
|
+
|
|
350
|
+
const autos = automationManager.listForChat(chatId);
|
|
351
|
+
logger.info(`[list_automations] Chat ${chatId} — ${autos.length} automation(s)`);
|
|
352
|
+
|
|
353
|
+
if (autos.length === 0) {
|
|
354
|
+
return { message: 'No automations for this chat.' };
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
automations: autos.map((a) => ({
|
|
359
|
+
id: a.id,
|
|
360
|
+
name: a.name,
|
|
361
|
+
description: a.description.slice(0, 100),
|
|
362
|
+
schedule: a.schedule,
|
|
363
|
+
enabled: a.enabled,
|
|
364
|
+
next_run: a.nextRun ? new Date(a.nextRun).toLocaleString() : null,
|
|
365
|
+
run_count: a.runCount,
|
|
366
|
+
last_error: a.lastError,
|
|
367
|
+
summary: a.toSummary(),
|
|
368
|
+
})),
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
case 'update_automation': {
|
|
373
|
+
if (!automationManager) return { error: 'Automation system not available.' };
|
|
374
|
+
|
|
375
|
+
const { automation_id, enabled, name: newName, description: newDesc, schedule_type: newSchedType, cron_expression: newCron, interval_minutes: newInterval, min_minutes: newMin, max_minutes: newMax } = input;
|
|
376
|
+
logger.info(`[update_automation] Request: id=${automation_id}`);
|
|
377
|
+
|
|
378
|
+
const changes = {};
|
|
379
|
+
if (enabled !== undefined) changes.enabled = enabled;
|
|
380
|
+
if (newName !== undefined) changes.name = newName;
|
|
381
|
+
if (newDesc !== undefined) changes.description = newDesc;
|
|
382
|
+
|
|
383
|
+
if (newSchedType !== undefined) {
|
|
384
|
+
switch (newSchedType) {
|
|
385
|
+
case 'cron':
|
|
386
|
+
changes.schedule = { type: 'cron', expression: newCron };
|
|
387
|
+
break;
|
|
388
|
+
case 'interval':
|
|
389
|
+
changes.schedule = { type: 'interval', minutes: newInterval };
|
|
390
|
+
break;
|
|
391
|
+
case 'random':
|
|
392
|
+
changes.schedule = { type: 'random', minMinutes: newMin, maxMinutes: newMax };
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
try {
|
|
398
|
+
const auto = automationManager.update(automation_id, changes);
|
|
399
|
+
if (!auto) return { error: `Automation ${automation_id} not found.` };
|
|
400
|
+
return {
|
|
401
|
+
automation_id: auto.id,
|
|
402
|
+
name: auto.name,
|
|
403
|
+
enabled: auto.enabled,
|
|
404
|
+
schedule: auto.schedule,
|
|
405
|
+
next_run: auto.nextRun ? new Date(auto.nextRun).toLocaleString() : null,
|
|
406
|
+
message: `Automation "${auto.name}" updated.`,
|
|
407
|
+
};
|
|
408
|
+
} catch (err) {
|
|
409
|
+
logger.warn(`[update_automation] Failed: ${err.message}`);
|
|
410
|
+
return { error: err.message };
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
case 'delete_automation': {
|
|
415
|
+
if (!automationManager) return { error: 'Automation system not available.' };
|
|
416
|
+
|
|
417
|
+
const { automation_id } = input;
|
|
418
|
+
logger.info(`[delete_automation] Request: id=${automation_id}`);
|
|
419
|
+
|
|
420
|
+
const deleted = automationManager.delete(automation_id);
|
|
421
|
+
if (!deleted) return { error: `Automation ${automation_id} not found.` };
|
|
422
|
+
return { automation_id, status: 'deleted', message: `Automation deleted.` };
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
default:
|
|
426
|
+
return { error: `Unknown orchestrator tool: ${name}` };
|
|
427
|
+
}
|
|
428
|
+
}
|
package/src/tools/os.js
CHANGED
|
@@ -122,12 +122,15 @@ export const handlers = {
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
return new Promise((res) => {
|
|
125
|
+
let abortHandler = null;
|
|
126
|
+
|
|
125
127
|
const child = exec(
|
|
126
128
|
command,
|
|
127
129
|
{ timeout: timeout_seconds * 1000, maxBuffer: 10 * 1024 * 1024 },
|
|
128
130
|
(error, stdout, stderr) => {
|
|
131
|
+
if (abortHandler && context.signal) context.signal.removeEventListener('abort', abortHandler);
|
|
129
132
|
if (error && error.killed) {
|
|
130
|
-
return res({ error: `Command timed out after ${timeout_seconds}s` });
|
|
133
|
+
return res({ error: `Command timed out or was cancelled after ${timeout_seconds}s` });
|
|
131
134
|
}
|
|
132
135
|
const result = {
|
|
133
136
|
stdout: stdout || '',
|
|
@@ -147,6 +150,16 @@ export const handlers = {
|
|
|
147
150
|
res(result);
|
|
148
151
|
},
|
|
149
152
|
);
|
|
153
|
+
|
|
154
|
+
// Wire abort signal to kill the child process
|
|
155
|
+
if (context.signal) {
|
|
156
|
+
if (context.signal.aborted) {
|
|
157
|
+
child.kill('SIGTERM');
|
|
158
|
+
} else {
|
|
159
|
+
abortHandler = () => child.kill('SIGTERM');
|
|
160
|
+
context.signal.addEventListener('abort', abortHandler, { once: true });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
150
163
|
});
|
|
151
164
|
},
|
|
152
165
|
|
|
@@ -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/tools/process.js
CHANGED
|
File without changes
|