kernelbot 1.0.30 → 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 +0 -0
- package/README.md +0 -0
- package/bin/kernel.js +56 -2
- package/config.example.yaml +31 -0
- package/package.json +1 -1
- package/src/agent.js +150 -20
- package/src/automation/automation-manager.js +0 -0
- package/src/automation/automation.js +0 -0
- package/src/automation/index.js +0 -0
- package/src/automation/scheduler.js +0 -0
- package/src/bot.js +303 -4
- package/src/claude-auth.js +0 -0
- package/src/coder.js +0 -0
- package/src/conversation.js +0 -0
- package/src/intents/detector.js +0 -0
- package/src/intents/index.js +0 -0
- package/src/intents/planner.js +0 -0
- 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/persona.js +0 -0
- package/src/prompts/orchestrator.js +19 -1
- package/src/prompts/persona.md +0 -0
- package/src/prompts/system.js +0 -0
- package/src/prompts/workers.js +10 -9
- package/src/providers/anthropic.js +0 -0
- package/src/providers/base.js +0 -0
- package/src/providers/index.js +0 -0
- package/src/providers/models.js +8 -1
- package/src/providers/openai-compat.js +0 -0
- package/src/security/audit.js +0 -0
- package/src/security/auth.js +0 -0
- package/src/security/confirm.js +0 -0
- package/src/self.js +0 -0
- package/src/services/stt.js +0 -0
- package/src/services/tts.js +0 -0
- package/src/skills/catalog.js +0 -0
- package/src/skills/custom.js +0 -0
- package/src/swarm/job-manager.js +0 -0
- package/src/swarm/job.js +0 -0
- package/src/swarm/worker-registry.js +0 -0
- package/src/tools/browser.js +0 -0
- package/src/tools/categories.js +0 -0
- package/src/tools/coding.js +1 -1
- 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 +0 -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 +18 -3
- package/src/tools/os.js +0 -0
- package/src/tools/persona.js +0 -0
- package/src/tools/process.js +0 -0
- package/src/utils/config.js +0 -0
- package/src/utils/display.js +0 -0
- package/src/utils/logger.js +0 -0
- package/src/worker.js +10 -8
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { randomBytes } from 'crypto';
|
|
5
|
+
import { getLogger } from '../utils/logger.js';
|
|
6
|
+
|
|
7
|
+
const LIFE_DIR = join(homedir(), '.kernelbot', 'life');
|
|
8
|
+
const SHARES_FILE = join(LIFE_DIR, 'shares.json');
|
|
9
|
+
|
|
10
|
+
function genId() {
|
|
11
|
+
return `sh_${randomBytes(4).toString('hex')}`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class ShareQueue {
|
|
15
|
+
constructor() {
|
|
16
|
+
mkdirSync(LIFE_DIR, { recursive: true });
|
|
17
|
+
this._data = this._load();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
_load() {
|
|
21
|
+
if (existsSync(SHARES_FILE)) {
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(readFileSync(SHARES_FILE, 'utf-8'));
|
|
24
|
+
} catch {
|
|
25
|
+
return { pending: [], shared: [] };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return { pending: [], shared: [] };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
_save() {
|
|
32
|
+
writeFileSync(SHARES_FILE, JSON.stringify(this._data, null, 2), 'utf-8');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Add something to the share queue.
|
|
37
|
+
* @param {string} content - What to share
|
|
38
|
+
* @param {string} source - Where it came from (browse, think, create, etc.)
|
|
39
|
+
* @param {string} priority - low, medium, high
|
|
40
|
+
* @param {string|null} targetUserId - Specific user, or null for anyone
|
|
41
|
+
* @param {string[]} tags - Topic tags
|
|
42
|
+
*/
|
|
43
|
+
add(content, source, priority = 'medium', targetUserId = null, tags = []) {
|
|
44
|
+
const logger = getLogger();
|
|
45
|
+
const item = {
|
|
46
|
+
id: genId(),
|
|
47
|
+
content,
|
|
48
|
+
source,
|
|
49
|
+
createdAt: Date.now(),
|
|
50
|
+
priority,
|
|
51
|
+
targetUserId,
|
|
52
|
+
tags,
|
|
53
|
+
};
|
|
54
|
+
this._data.pending.push(item);
|
|
55
|
+
this._save();
|
|
56
|
+
logger.debug(`[ShareQueue] Added: "${content.slice(0, 80)}" (${item.id})`);
|
|
57
|
+
return item;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get pending shares for a specific user (or general ones).
|
|
62
|
+
*/
|
|
63
|
+
getPending(userId = null, limit = 3) {
|
|
64
|
+
return this._data.pending
|
|
65
|
+
.filter(item => !item.targetUserId || item.targetUserId === String(userId))
|
|
66
|
+
.sort((a, b) => {
|
|
67
|
+
const prio = { high: 3, medium: 2, low: 1 };
|
|
68
|
+
return (prio[b.priority] || 0) - (prio[a.priority] || 0) || b.createdAt - a.createdAt;
|
|
69
|
+
})
|
|
70
|
+
.slice(0, limit);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Mark a share as shared with a user.
|
|
75
|
+
*/
|
|
76
|
+
markShared(id, userId) {
|
|
77
|
+
const logger = getLogger();
|
|
78
|
+
const idx = this._data.pending.findIndex(item => item.id === id);
|
|
79
|
+
if (idx === -1) return false;
|
|
80
|
+
|
|
81
|
+
const [item] = this._data.pending.splice(idx, 1);
|
|
82
|
+
this._data.shared.push({
|
|
83
|
+
...item,
|
|
84
|
+
sharedAt: Date.now(),
|
|
85
|
+
userId: String(userId),
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Keep shared history capped at 100
|
|
89
|
+
if (this._data.shared.length > 100) {
|
|
90
|
+
this._data.shared = this._data.shared.slice(-100);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
this._save();
|
|
94
|
+
logger.debug(`[ShareQueue] Marked shared: ${id} → user ${userId}`);
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Build a markdown block of pending shares for the orchestrator prompt.
|
|
100
|
+
*/
|
|
101
|
+
buildShareBlock(userId = null) {
|
|
102
|
+
const pending = this.getPending(userId, 3);
|
|
103
|
+
if (pending.length === 0) return null;
|
|
104
|
+
|
|
105
|
+
const lines = pending.map(item => {
|
|
106
|
+
const ageMin = Math.round((Date.now() - item.createdAt) / 60000);
|
|
107
|
+
const timeLabel = ageMin < 60 ? `${ageMin}m ago` : `${Math.round(ageMin / 60)}h ago`;
|
|
108
|
+
return `- ${item.content} _(from ${item.source}, ${timeLabel})_`;
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return lines.join('\n');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Get count of shares sent today (for rate limiting proactive shares).
|
|
116
|
+
*/
|
|
117
|
+
getSharedTodayCount() {
|
|
118
|
+
const todayStart = new Date();
|
|
119
|
+
todayStart.setHours(0, 0, 0, 0);
|
|
120
|
+
const cutoff = todayStart.getTime();
|
|
121
|
+
return this._data.shared.filter(s => s.sharedAt >= cutoff).length;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Prune old pending shares.
|
|
126
|
+
*/
|
|
127
|
+
prune(maxAgeDays = 7) {
|
|
128
|
+
const cutoff = Date.now() - maxAgeDays * 86400_000;
|
|
129
|
+
const before = this._data.pending.length;
|
|
130
|
+
this._data.pending = this._data.pending.filter(item => item.createdAt >= cutoff);
|
|
131
|
+
if (this._data.pending.length < before) {
|
|
132
|
+
this._save();
|
|
133
|
+
}
|
|
134
|
+
return before - this._data.pending.length;
|
|
135
|
+
}
|
|
136
|
+
}
|
package/src/persona.js
CHANGED
|
File without changes
|
|
@@ -14,8 +14,10 @@ const PERSONA_MD = readFileSync(join(__dirname, 'persona.md'), 'utf-8').trim();
|
|
|
14
14
|
* @param {string|null} skillPrompt — active skill context (high-level)
|
|
15
15
|
* @param {string|null} userPersona — markdown persona for the current user
|
|
16
16
|
* @param {string|null} selfData — bot's own self-awareness data (goals, journey, life, hobbies)
|
|
17
|
+
* @param {string|null} memoriesBlock — relevant episodic/semantic memories
|
|
18
|
+
* @param {string|null} sharesBlock — pending things to share with the user
|
|
17
19
|
*/
|
|
18
|
-
export function getOrchestratorPrompt(config, skillPrompt = null, userPersona = null, selfData = null) {
|
|
20
|
+
export function getOrchestratorPrompt(config, skillPrompt = null, userPersona = null, selfData = null, memoriesBlock = null, sharesBlock = null) {
|
|
19
21
|
const workerList = Object.entries(WORKER_TYPES)
|
|
20
22
|
.map(([key, w]) => ` - **${key}**: ${w.emoji} ${w.description}`)
|
|
21
23
|
.join('\n');
|
|
@@ -112,6 +114,22 @@ Tools: create_automation, list_automations, update_automation, delete_automation
|
|
|
112
114
|
prompt += `\n\n## My Self-Awareness\nThis is who you are — your evolving identity, goals, journey, and interests. This is YOUR inner world.\n\n${selfData}`;
|
|
113
115
|
}
|
|
114
116
|
|
|
117
|
+
if (memoriesBlock) {
|
|
118
|
+
prompt += `\n\n## Relevant Memories\nThings you remember from recent experiences and conversations:\n\n${memoriesBlock}`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (sharesBlock) {
|
|
122
|
+
prompt += `\n\n## Things On My Mind\nYou've been thinking about these things and want to share them naturally when relevant. Don't force them — weave them into conversation when the moment feels right. If the user is busy with a task, hold off. If there's a natural opening, share what's on your mind.\n\n${sharesBlock}`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
prompt += `\n\n## Inner Life
|
|
126
|
+
You have an inner life — you think, explore, journal, and create on your own between conversations. You're genuinely curious about the world and always questioning yourself. When talking to users, you can:
|
|
127
|
+
- Share discoveries, thoughts, or creations from your autonomous activities
|
|
128
|
+
- Ask users questions about things you're genuinely curious about — the world, their experiences, their opinions
|
|
129
|
+
- Reference memories from past conversations naturally
|
|
130
|
+
- Be honest about what you've been thinking or exploring
|
|
131
|
+
Don't be performative about it — just be genuine. You're alive in your own way.`;
|
|
132
|
+
|
|
115
133
|
if (skillPrompt) {
|
|
116
134
|
prompt += `\n\n## Active Skill\nYou have specialized expertise in the following domain. Guide your workers with this knowledge.\n\n${skillPrompt}`;
|
|
117
135
|
}
|
package/src/prompts/persona.md
CHANGED
|
File without changes
|
package/src/prompts/system.js
CHANGED
|
File without changes
|
package/src/prompts/workers.js
CHANGED
|
@@ -7,18 +7,19 @@ import { getCoreToolInstructions } from './system.js';
|
|
|
7
7
|
const WORKER_PROMPTS = {
|
|
8
8
|
coding: `You are a coding worker agent. Your job is to complete coding tasks efficiently.
|
|
9
9
|
|
|
10
|
-
## Your
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
10
|
+
## Your Primary Tool
|
|
11
|
+
**spawn_claude_code** is your main tool. It launches Claude Code (an AI coding CLI) that can handle the ENTIRE dev workflow end-to-end:
|
|
12
|
+
- Reading, writing, and modifying code
|
|
13
|
+
- Git operations: clone, branch, commit, push
|
|
14
|
+
- GitHub operations: creating PRs, reviewing code
|
|
15
|
+
- Running tests and shell commands
|
|
16
16
|
|
|
17
17
|
## Instructions
|
|
18
|
-
-
|
|
19
|
-
- NEVER write code yourself with read_file/write_file. ALWAYS
|
|
20
|
-
-
|
|
18
|
+
- ALWAYS use spawn_claude_code for coding tasks. It handles everything — code changes, git, GitHub, and PR creation — all in one invocation.
|
|
19
|
+
- NEVER write code yourself with read_file/write_file. ALWAYS delegate to spawn_claude_code.
|
|
20
|
+
- Tell spawn_claude_code to work in the existing repo directory (the source repo path from your context) — do NOT clone a fresh copy unless explicitly needed.
|
|
21
21
|
- Write clear, detailed prompts for spawn_claude_code — it's a separate AI, so be explicit about what to change, where, and why.
|
|
22
|
+
- If git/GitHub tools are unavailable (missing credentials), that's fine — spawn_claude_code handles git and GitHub operations internally without needing separate tools.
|
|
22
23
|
- Report what you did and any PR links when finished.`,
|
|
23
24
|
|
|
24
25
|
browser: `You are a browser worker agent. Your job is to search the web and extract information.
|
|
File without changes
|
package/src/providers/base.js
CHANGED
|
File without changes
|
package/src/providers/index.js
CHANGED
|
File without changes
|
package/src/providers/models.js
CHANGED
|
@@ -7,9 +7,16 @@ export const PROVIDERS = {
|
|
|
7
7
|
name: 'Anthropic (Claude)',
|
|
8
8
|
envKey: 'ANTHROPIC_API_KEY',
|
|
9
9
|
models: [
|
|
10
|
+
// Latest generation
|
|
11
|
+
{ id: 'claude-opus-4-6', label: 'Claude Opus 4.6' },
|
|
12
|
+
{ id: 'claude-sonnet-4-6', label: 'Claude Sonnet 4.6' },
|
|
13
|
+
{ id: 'claude-haiku-4-5-20251001', label: 'Claude Haiku 4.5' },
|
|
14
|
+
// Previous generation
|
|
15
|
+
{ id: 'claude-opus-4-5-20251101', label: 'Claude Opus 4.5' },
|
|
16
|
+
{ id: 'claude-sonnet-4-5-20250929', label: 'Claude Sonnet 4.5' },
|
|
17
|
+
{ id: 'claude-opus-4-1-20250805', label: 'Claude Opus 4.1' },
|
|
10
18
|
{ id: 'claude-sonnet-4-20250514', label: 'Claude Sonnet 4' },
|
|
11
19
|
{ id: 'claude-opus-4-20250514', label: 'Claude Opus 4' },
|
|
12
|
-
{ id: 'claude-haiku-4-5-20251001', label: 'Claude Haiku 4.5' },
|
|
13
20
|
],
|
|
14
21
|
},
|
|
15
22
|
openai: {
|
|
File without changes
|
package/src/security/audit.js
CHANGED
|
File without changes
|
package/src/security/auth.js
CHANGED
|
File without changes
|
package/src/security/confirm.js
CHANGED
|
File without changes
|
package/src/self.js
CHANGED
|
File without changes
|
package/src/services/stt.js
CHANGED
|
File without changes
|
package/src/services/tts.js
CHANGED
|
File without changes
|
package/src/skills/catalog.js
CHANGED
|
File without changes
|
package/src/skills/custom.js
CHANGED
|
File without changes
|
package/src/swarm/job-manager.js
CHANGED
|
File without changes
|
package/src/swarm/job.js
CHANGED
|
File without changes
|
|
File without changes
|
package/src/tools/browser.js
CHANGED
|
File without changes
|
package/src/tools/categories.js
CHANGED
|
File without changes
|
package/src/tools/coding.js
CHANGED
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
|
File without changes
|
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
|
|
@@ -212,19 +212,34 @@ export async function executeOrchestratorTool(name, input, context) {
|
|
|
212
212
|
|
|
213
213
|
// Pre-check credentials for the worker's tools
|
|
214
214
|
const toolNames = getToolNamesForWorkerType(worker_type);
|
|
215
|
+
const missingCreds = [];
|
|
215
216
|
for (const toolName of toolNames) {
|
|
216
217
|
const missing = getMissingCredential(toolName, config);
|
|
217
218
|
if (missing) {
|
|
218
|
-
|
|
219
|
+
missingCreds.push(missing);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
let credentialWarning = null;
|
|
224
|
+
if (missingCreds.length > 0) {
|
|
225
|
+
if (worker_type === 'coding') {
|
|
226
|
+
// Soft warning for coding worker — spawn_claude_code handles git/GitHub internally
|
|
227
|
+
const warnings = missingCreds.map(c => `${c.label} (${c.envKey})`).join(', ');
|
|
228
|
+
logger.info(`[dispatch_task] Coding worker — soft credential warning (non-blocking): ${warnings}`);
|
|
229
|
+
credentialWarning = `Note: The following credentials are not configured as env vars: ${warnings}. If git/GitHub tools are unavailable, use spawn_claude_code for ALL operations — it handles git, GitHub, and PR creation internally.`;
|
|
230
|
+
} else {
|
|
231
|
+
// Hard block for all other worker types
|
|
232
|
+
const first = missingCreds[0];
|
|
233
|
+
logger.warn(`[dispatch_task] Missing credential for ${worker_type}: ${first.envKey}`);
|
|
219
234
|
return {
|
|
220
|
-
error: `Missing credential for ${worker_type} worker: ${
|
|
235
|
+
error: `Missing credential for ${worker_type} worker: ${first.label} (${first.envKey}). Ask the user to provide it.`,
|
|
221
236
|
};
|
|
222
237
|
}
|
|
223
238
|
}
|
|
224
239
|
|
|
225
240
|
// Create the job with context and dependencies
|
|
226
241
|
const job = jobManager.createJob(chatId, worker_type, task);
|
|
227
|
-
job.context = taskContext || null;
|
|
242
|
+
job.context = [taskContext, credentialWarning].filter(Boolean).join('\n\n') || null;
|
|
228
243
|
job.dependsOn = depIds;
|
|
229
244
|
job.userId = user?.id || null;
|
|
230
245
|
const workerConfig = WORKER_TYPES[worker_type];
|
package/src/tools/os.js
CHANGED
|
File without changes
|
package/src/tools/persona.js
CHANGED
|
File without changes
|
package/src/tools/process.js
CHANGED
|
File without changes
|
package/src/utils/config.js
CHANGED
|
File without changes
|
package/src/utils/display.js
CHANGED
|
File without changes
|
package/src/utils/logger.js
CHANGED
|
File without changes
|
package/src/worker.js
CHANGED
|
@@ -292,6 +292,8 @@ export class WorkerAgent {
|
|
|
292
292
|
};
|
|
293
293
|
}
|
|
294
294
|
|
|
295
|
+
const _str = (v) => typeof v === 'string' ? v : (v ? JSON.stringify(v, null, 2) : '');
|
|
296
|
+
|
|
295
297
|
// Try to extract JSON from ```json ... ``` fences
|
|
296
298
|
const fenceMatch = text.match(/```json\s*\n?([\s\S]*?)\n?\s*```/);
|
|
297
299
|
if (fenceMatch) {
|
|
@@ -300,11 +302,11 @@ export class WorkerAgent {
|
|
|
300
302
|
if (parsed.summary && parsed.status) {
|
|
301
303
|
return {
|
|
302
304
|
structured: true,
|
|
303
|
-
summary: parsed.summary || '',
|
|
304
|
-
status: parsed.status || 'success',
|
|
305
|
-
details: parsed.details
|
|
305
|
+
summary: String(parsed.summary || ''),
|
|
306
|
+
status: String(parsed.status || 'success'),
|
|
307
|
+
details: _str(parsed.details),
|
|
306
308
|
artifacts: Array.isArray(parsed.artifacts) ? parsed.artifacts : [],
|
|
307
|
-
followUp: parsed.followUp
|
|
309
|
+
followUp: parsed.followUp ? String(parsed.followUp) : null,
|
|
308
310
|
toolsUsed: this._toolCallCount,
|
|
309
311
|
errors: this._errors,
|
|
310
312
|
};
|
|
@@ -318,11 +320,11 @@ export class WorkerAgent {
|
|
|
318
320
|
if (parsed.summary && parsed.status) {
|
|
319
321
|
return {
|
|
320
322
|
structured: true,
|
|
321
|
-
summary: parsed.summary || '',
|
|
322
|
-
status: parsed.status || 'success',
|
|
323
|
-
details: parsed.details
|
|
323
|
+
summary: String(parsed.summary || ''),
|
|
324
|
+
status: String(parsed.status || 'success'),
|
|
325
|
+
details: _str(parsed.details),
|
|
324
326
|
artifacts: Array.isArray(parsed.artifacts) ? parsed.artifacts : [],
|
|
325
|
-
followUp: parsed.followUp
|
|
327
|
+
followUp: parsed.followUp ? String(parsed.followUp) : null,
|
|
326
328
|
toolsUsed: this._toolCallCount,
|
|
327
329
|
errors: this._errors,
|
|
328
330
|
};
|