kernelbot 1.0.28 → 1.0.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +4 -0
- package/bin/kernel.js +68 -7
- package/config.example.yaml +45 -1
- package/package.json +1 -1
- package/src/agent.js +613 -28
- package/src/bot.js +643 -7
- package/src/claude-auth.js +93 -0
- package/src/coder.js +48 -6
- package/src/life/codebase.js +388 -0
- package/src/life/engine.js +1317 -0
- package/src/life/evolution.js +244 -0
- package/src/life/improvements.js +81 -0
- package/src/life/journal.js +109 -0
- package/src/life/memory.js +283 -0
- package/src/life/share-queue.js +136 -0
- package/src/prompts/orchestrator.js +71 -5
- package/src/prompts/workers.js +65 -5
- package/src/providers/models.js +8 -1
- package/src/self.js +122 -0
- package/src/services/stt.js +139 -0
- package/src/services/tts.js +124 -0
- package/src/swarm/job-manager.js +54 -7
- package/src/swarm/job.js +19 -1
- package/src/swarm/worker-registry.js +5 -0
- package/src/tools/coding.js +6 -1
- package/src/tools/orchestrator-tools.js +93 -21
- package/src/tools/os.js +14 -1
- package/src/utils/config.js +105 -2
- package/src/worker.js +98 -5
package/src/utils/config.js
CHANGED
|
@@ -13,6 +13,7 @@ const DEFAULTS = {
|
|
|
13
13
|
description: 'AI engineering agent with full OS control',
|
|
14
14
|
},
|
|
15
15
|
orchestrator: {
|
|
16
|
+
provider: 'anthropic',
|
|
16
17
|
model: 'claude-opus-4-6',
|
|
17
18
|
max_tokens: 2048,
|
|
18
19
|
temperature: 0.3,
|
|
@@ -38,6 +39,7 @@ const DEFAULTS = {
|
|
|
38
39
|
max_turns: 50,
|
|
39
40
|
timeout_seconds: 600,
|
|
40
41
|
workspace_dir: null, // defaults to ~/.kernelbot/workspaces
|
|
42
|
+
auth_mode: 'system', // system | api_key | oauth_token
|
|
41
43
|
},
|
|
42
44
|
github: {
|
|
43
45
|
default_branch: 'main',
|
|
@@ -190,6 +192,84 @@ export function saveProviderToYaml(providerKey, modelId) {
|
|
|
190
192
|
return configPath;
|
|
191
193
|
}
|
|
192
194
|
|
|
195
|
+
/**
|
|
196
|
+
* Save orchestrator provider and model to config.yaml.
|
|
197
|
+
*/
|
|
198
|
+
export function saveOrchestratorToYaml(providerKey, modelId) {
|
|
199
|
+
const configDir = getConfigDir();
|
|
200
|
+
mkdirSync(configDir, { recursive: true });
|
|
201
|
+
const configPath = join(configDir, 'config.yaml');
|
|
202
|
+
|
|
203
|
+
let existing = {};
|
|
204
|
+
if (existsSync(configPath)) {
|
|
205
|
+
existing = yaml.load(readFileSync(configPath, 'utf-8')) || {};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
existing.orchestrator = {
|
|
209
|
+
...(existing.orchestrator || {}),
|
|
210
|
+
provider: providerKey,
|
|
211
|
+
model: modelId,
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
writeFileSync(configPath, yaml.dump(existing, { lineWidth: -1 }));
|
|
215
|
+
return configPath;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Save Claude Code model to config.yaml.
|
|
220
|
+
*/
|
|
221
|
+
export function saveClaudeCodeModelToYaml(modelId) {
|
|
222
|
+
const configDir = getConfigDir();
|
|
223
|
+
mkdirSync(configDir, { recursive: true });
|
|
224
|
+
const configPath = join(configDir, 'config.yaml');
|
|
225
|
+
|
|
226
|
+
let existing = {};
|
|
227
|
+
if (existsSync(configPath)) {
|
|
228
|
+
existing = yaml.load(readFileSync(configPath, 'utf-8')) || {};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
existing.claude_code = {
|
|
232
|
+
...(existing.claude_code || {}),
|
|
233
|
+
model: modelId,
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
writeFileSync(configPath, yaml.dump(existing, { lineWidth: -1 }));
|
|
237
|
+
return configPath;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Save Claude Code auth mode + credential to config.yaml and .env.
|
|
242
|
+
*/
|
|
243
|
+
export function saveClaudeCodeAuth(config, mode, value) {
|
|
244
|
+
const configDir = getConfigDir();
|
|
245
|
+
mkdirSync(configDir, { recursive: true });
|
|
246
|
+
const configPath = join(configDir, 'config.yaml');
|
|
247
|
+
|
|
248
|
+
let existing = {};
|
|
249
|
+
if (existsSync(configPath)) {
|
|
250
|
+
existing = yaml.load(readFileSync(configPath, 'utf-8')) || {};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
existing.claude_code = {
|
|
254
|
+
...(existing.claude_code || {}),
|
|
255
|
+
auth_mode: mode,
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
writeFileSync(configPath, yaml.dump(existing, { lineWidth: -1 }));
|
|
259
|
+
|
|
260
|
+
// Update live config
|
|
261
|
+
config.claude_code.auth_mode = mode;
|
|
262
|
+
|
|
263
|
+
if (mode === 'api_key' && value) {
|
|
264
|
+
saveCredential(config, 'CLAUDE_CODE_API_KEY', value);
|
|
265
|
+
config.claude_code.api_key = value;
|
|
266
|
+
} else if (mode === 'oauth_token' && value) {
|
|
267
|
+
saveCredential(config, 'CLAUDE_CODE_OAUTH_TOKEN', value);
|
|
268
|
+
config.claude_code.oauth_token = value;
|
|
269
|
+
}
|
|
270
|
+
// mode === 'system' — no credentials to save
|
|
271
|
+
}
|
|
272
|
+
|
|
193
273
|
/**
|
|
194
274
|
* Full interactive flow: change brain model + optionally enter API key.
|
|
195
275
|
*/
|
|
@@ -329,8 +409,13 @@ export function loadConfig() {
|
|
|
329
409
|
|
|
330
410
|
const config = deepMerge(DEFAULTS, fileConfig);
|
|
331
411
|
|
|
332
|
-
// Orchestrator
|
|
333
|
-
|
|
412
|
+
// Orchestrator — resolve API key based on configured provider
|
|
413
|
+
const orchProvider = PROVIDERS[config.orchestrator.provider];
|
|
414
|
+
if (orchProvider && process.env[orchProvider.envKey]) {
|
|
415
|
+
config.orchestrator.api_key = process.env[orchProvider.envKey];
|
|
416
|
+
}
|
|
417
|
+
// Legacy fallback: ANTHROPIC_API_KEY for anthropic orchestrator
|
|
418
|
+
if (config.orchestrator.provider === 'anthropic' && !config.orchestrator.api_key && process.env.ANTHROPIC_API_KEY) {
|
|
334
419
|
config.orchestrator.api_key = process.env.ANTHROPIC_API_KEY;
|
|
335
420
|
}
|
|
336
421
|
|
|
@@ -351,6 +436,16 @@ export function loadConfig() {
|
|
|
351
436
|
if (!config.github) config.github = {};
|
|
352
437
|
config.github.token = process.env.GITHUB_TOKEN;
|
|
353
438
|
}
|
|
439
|
+
// ElevenLabs voice credentials
|
|
440
|
+
if (process.env.ELEVENLABS_API_KEY) {
|
|
441
|
+
if (!config.elevenlabs) config.elevenlabs = {};
|
|
442
|
+
config.elevenlabs.api_key = process.env.ELEVENLABS_API_KEY;
|
|
443
|
+
}
|
|
444
|
+
if (process.env.ELEVENLABS_VOICE_ID) {
|
|
445
|
+
if (!config.elevenlabs) config.elevenlabs = {};
|
|
446
|
+
config.elevenlabs.voice_id = process.env.ELEVENLABS_VOICE_ID;
|
|
447
|
+
}
|
|
448
|
+
|
|
354
449
|
if (process.env.JIRA_BASE_URL || process.env.JIRA_EMAIL || process.env.JIRA_API_TOKEN) {
|
|
355
450
|
if (!config.jira) config.jira = {};
|
|
356
451
|
if (process.env.JIRA_BASE_URL) config.jira.base_url = process.env.JIRA_BASE_URL;
|
|
@@ -358,6 +453,14 @@ export function loadConfig() {
|
|
|
358
453
|
if (process.env.JIRA_API_TOKEN) config.jira.api_token = process.env.JIRA_API_TOKEN;
|
|
359
454
|
}
|
|
360
455
|
|
|
456
|
+
// Claude Code auth credentials from env
|
|
457
|
+
if (process.env.CLAUDE_CODE_API_KEY) {
|
|
458
|
+
config.claude_code.api_key = process.env.CLAUDE_CODE_API_KEY;
|
|
459
|
+
}
|
|
460
|
+
if (process.env.CLAUDE_CODE_OAUTH_TOKEN) {
|
|
461
|
+
config.claude_code.oauth_token = process.env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
462
|
+
}
|
|
463
|
+
|
|
361
464
|
return config;
|
|
362
465
|
}
|
|
363
466
|
|
package/src/worker.js
CHANGED
|
@@ -26,18 +26,22 @@ export class WorkerAgent {
|
|
|
26
26
|
* @param {string} opts.jobId - Job ID for logging
|
|
27
27
|
* @param {Array} opts.tools - Scoped tool definitions
|
|
28
28
|
* @param {string|null} opts.skillId - Active skill ID (for worker prompt)
|
|
29
|
+
* @param {string|null} opts.workerContext - Structured context (conversation history, persona, dependency results)
|
|
29
30
|
* @param {object} opts.callbacks - { onProgress, onComplete, onError }
|
|
30
31
|
* @param {AbortController} opts.abortController - For cancellation
|
|
31
32
|
*/
|
|
32
|
-
constructor({ config, workerType, jobId, tools, skillId, callbacks, abortController }) {
|
|
33
|
+
constructor({ config, workerType, jobId, tools, skillId, workerContext, callbacks, abortController }) {
|
|
33
34
|
this.config = config;
|
|
34
35
|
this.workerType = workerType;
|
|
35
36
|
this.jobId = jobId;
|
|
36
37
|
this.tools = tools;
|
|
37
38
|
this.skillId = skillId;
|
|
39
|
+
this.workerContext = workerContext || null;
|
|
38
40
|
this.callbacks = callbacks || {};
|
|
39
41
|
this.abortController = abortController || new AbortController();
|
|
40
42
|
this._cancelled = false;
|
|
43
|
+
this._toolCallCount = 0;
|
|
44
|
+
this._errors = [];
|
|
41
45
|
|
|
42
46
|
// Create provider from worker brain config
|
|
43
47
|
this.provider = createProvider(config);
|
|
@@ -51,7 +55,7 @@ export class WorkerAgent {
|
|
|
51
55
|
this.maxIterations = 200;
|
|
52
56
|
|
|
53
57
|
const logger = getLogger();
|
|
54
|
-
logger.info(`[Worker ${jobId}] Created: type=${workerType}, provider=${config.brain.provider}/${config.brain.model}, tools=${tools.length}, skill=${skillId || 'none'}`);
|
|
58
|
+
logger.info(`[Worker ${jobId}] Created: type=${workerType}, provider=${config.brain.provider}/${config.brain.model}, tools=${tools.length}, skill=${skillId || 'none'}, context=${workerContext ? 'yes' : 'none'}`);
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
/** Cancel this worker. */
|
|
@@ -66,7 +70,14 @@ export class WorkerAgent {
|
|
|
66
70
|
const logger = getLogger();
|
|
67
71
|
logger.info(`[Worker ${this.jobId}] Starting task: "${task.slice(0, 150)}"`);
|
|
68
72
|
|
|
69
|
-
|
|
73
|
+
// Build first message: context sections + task
|
|
74
|
+
let firstMessage = '';
|
|
75
|
+
if (this.workerContext) {
|
|
76
|
+
firstMessage += this.workerContext + '\n\n---\n\n';
|
|
77
|
+
}
|
|
78
|
+
firstMessage += task;
|
|
79
|
+
|
|
80
|
+
const messages = [{ role: 'user', content: firstMessage }];
|
|
70
81
|
|
|
71
82
|
try {
|
|
72
83
|
const result = await this._runLoop(messages);
|
|
@@ -74,8 +85,9 @@ export class WorkerAgent {
|
|
|
74
85
|
logger.info(`[Worker ${this.jobId}] Run completed but worker was cancelled — skipping callbacks`);
|
|
75
86
|
return;
|
|
76
87
|
}
|
|
77
|
-
|
|
78
|
-
|
|
88
|
+
const parsed = this._parseResult(result);
|
|
89
|
+
logger.info(`[Worker ${this.jobId}] Run finished successfully — structured=${!!parsed.structured}, result: "${(result || '').slice(0, 150)}"`);
|
|
90
|
+
if (this.callbacks.onComplete) this.callbacks.onComplete(result, parsed);
|
|
79
91
|
} catch (err) {
|
|
80
92
|
if (this._cancelled) {
|
|
81
93
|
logger.info(`[Worker ${this.jobId}] Run threw error but worker was cancelled — ignoring: ${err.message}`);
|
|
@@ -144,6 +156,8 @@ export class WorkerAgent {
|
|
|
144
156
|
logger.debug(`[Worker ${this.jobId}] Tool input: ${JSON.stringify(block.input).slice(0, 300)}`);
|
|
145
157
|
this._reportProgress(`🔧 ${summary}`);
|
|
146
158
|
|
|
159
|
+
this._toolCallCount++;
|
|
160
|
+
|
|
147
161
|
const result = await executeTool(block.name, block.input, {
|
|
148
162
|
config: this.config,
|
|
149
163
|
user: null, // workers don't have user context
|
|
@@ -151,8 +165,14 @@ export class WorkerAgent {
|
|
|
151
165
|
onUpdate: this.callbacks.onUpdate || null, // Real bot onUpdate (returns message_id for coder.js smart output)
|
|
152
166
|
sendPhoto: this.callbacks.sendPhoto || null,
|
|
153
167
|
sessionId: this.jobId, // Per-worker browser session isolation
|
|
168
|
+
signal: this.abortController.signal, // For killing child processes on cancellation
|
|
154
169
|
});
|
|
155
170
|
|
|
171
|
+
// Track errors
|
|
172
|
+
if (result && typeof result === 'object' && result.error) {
|
|
173
|
+
this._errors.push({ tool: block.name, error: result.error });
|
|
174
|
+
}
|
|
175
|
+
|
|
156
176
|
const resultStr = this._truncateResult(block.name, result);
|
|
157
177
|
logger.info(`[Worker ${this.jobId}] Tool ${block.name} result: ${resultStr.slice(0, 200)}`);
|
|
158
178
|
|
|
@@ -254,10 +274,83 @@ export class WorkerAgent {
|
|
|
254
274
|
return null;
|
|
255
275
|
}
|
|
256
276
|
|
|
277
|
+
/**
|
|
278
|
+
* Parse the worker's final text into a structured WorkerResult.
|
|
279
|
+
* Attempts JSON parse from ```json fences, falls back to wrapping raw text.
|
|
280
|
+
*/
|
|
281
|
+
_parseResult(text) {
|
|
282
|
+
if (!text) {
|
|
283
|
+
return {
|
|
284
|
+
structured: false,
|
|
285
|
+
summary: 'Task completed.',
|
|
286
|
+
status: 'success',
|
|
287
|
+
details: '',
|
|
288
|
+
artifacts: [],
|
|
289
|
+
followUp: null,
|
|
290
|
+
toolsUsed: this._toolCallCount,
|
|
291
|
+
errors: this._errors,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const _str = (v) => typeof v === 'string' ? v : (v ? JSON.stringify(v, null, 2) : '');
|
|
296
|
+
|
|
297
|
+
// Try to extract JSON from ```json ... ``` fences
|
|
298
|
+
const fenceMatch = text.match(/```json\s*\n?([\s\S]*?)\n?\s*```/);
|
|
299
|
+
if (fenceMatch) {
|
|
300
|
+
try {
|
|
301
|
+
const parsed = JSON.parse(fenceMatch[1]);
|
|
302
|
+
if (parsed.summary && parsed.status) {
|
|
303
|
+
return {
|
|
304
|
+
structured: true,
|
|
305
|
+
summary: String(parsed.summary || ''),
|
|
306
|
+
status: String(parsed.status || 'success'),
|
|
307
|
+
details: _str(parsed.details),
|
|
308
|
+
artifacts: Array.isArray(parsed.artifacts) ? parsed.artifacts : [],
|
|
309
|
+
followUp: parsed.followUp ? String(parsed.followUp) : null,
|
|
310
|
+
toolsUsed: this._toolCallCount,
|
|
311
|
+
errors: this._errors,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
} catch { /* fall through */ }
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Try raw JSON parse (no fences)
|
|
318
|
+
try {
|
|
319
|
+
const parsed = JSON.parse(text);
|
|
320
|
+
if (parsed.summary && parsed.status) {
|
|
321
|
+
return {
|
|
322
|
+
structured: true,
|
|
323
|
+
summary: String(parsed.summary || ''),
|
|
324
|
+
status: String(parsed.status || 'success'),
|
|
325
|
+
details: _str(parsed.details),
|
|
326
|
+
artifacts: Array.isArray(parsed.artifacts) ? parsed.artifacts : [],
|
|
327
|
+
followUp: parsed.followUp ? String(parsed.followUp) : null,
|
|
328
|
+
toolsUsed: this._toolCallCount,
|
|
329
|
+
errors: this._errors,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
} catch { /* fall through */ }
|
|
333
|
+
|
|
334
|
+
// Fallback: wrap raw text
|
|
335
|
+
return {
|
|
336
|
+
structured: false,
|
|
337
|
+
summary: text.slice(0, 200),
|
|
338
|
+
status: 'success',
|
|
339
|
+
details: text,
|
|
340
|
+
artifacts: [],
|
|
341
|
+
followUp: null,
|
|
342
|
+
toolsUsed: this._toolCallCount,
|
|
343
|
+
errors: this._errors,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
257
347
|
_reportProgress(text) {
|
|
258
348
|
if (this.callbacks.onProgress) {
|
|
259
349
|
try { this.callbacks.onProgress(text); } catch {}
|
|
260
350
|
}
|
|
351
|
+
if (this.callbacks.onHeartbeat) {
|
|
352
|
+
try { this.callbacks.onHeartbeat(text); } catch {}
|
|
353
|
+
}
|
|
261
354
|
}
|
|
262
355
|
|
|
263
356
|
_truncateResult(name, result) {
|