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.
Files changed (63) hide show
  1. package/.env.example +0 -0
  2. package/README.md +0 -0
  3. package/bin/kernel.js +56 -2
  4. package/config.example.yaml +31 -0
  5. package/package.json +1 -1
  6. package/src/agent.js +150 -20
  7. package/src/automation/automation-manager.js +0 -0
  8. package/src/automation/automation.js +0 -0
  9. package/src/automation/index.js +0 -0
  10. package/src/automation/scheduler.js +0 -0
  11. package/src/bot.js +303 -4
  12. package/src/claude-auth.js +0 -0
  13. package/src/coder.js +0 -0
  14. package/src/conversation.js +0 -0
  15. package/src/intents/detector.js +0 -0
  16. package/src/intents/index.js +0 -0
  17. package/src/intents/planner.js +0 -0
  18. package/src/life/codebase.js +388 -0
  19. package/src/life/engine.js +1317 -0
  20. package/src/life/evolution.js +244 -0
  21. package/src/life/improvements.js +81 -0
  22. package/src/life/journal.js +109 -0
  23. package/src/life/memory.js +283 -0
  24. package/src/life/share-queue.js +136 -0
  25. package/src/persona.js +0 -0
  26. package/src/prompts/orchestrator.js +19 -1
  27. package/src/prompts/persona.md +0 -0
  28. package/src/prompts/system.js +0 -0
  29. package/src/prompts/workers.js +10 -9
  30. package/src/providers/anthropic.js +0 -0
  31. package/src/providers/base.js +0 -0
  32. package/src/providers/index.js +0 -0
  33. package/src/providers/models.js +8 -1
  34. package/src/providers/openai-compat.js +0 -0
  35. package/src/security/audit.js +0 -0
  36. package/src/security/auth.js +0 -0
  37. package/src/security/confirm.js +0 -0
  38. package/src/self.js +0 -0
  39. package/src/services/stt.js +0 -0
  40. package/src/services/tts.js +0 -0
  41. package/src/skills/catalog.js +0 -0
  42. package/src/skills/custom.js +0 -0
  43. package/src/swarm/job-manager.js +0 -0
  44. package/src/swarm/job.js +0 -0
  45. package/src/swarm/worker-registry.js +0 -0
  46. package/src/tools/browser.js +0 -0
  47. package/src/tools/categories.js +0 -0
  48. package/src/tools/coding.js +1 -1
  49. package/src/tools/docker.js +0 -0
  50. package/src/tools/git.js +0 -0
  51. package/src/tools/github.js +0 -0
  52. package/src/tools/index.js +0 -0
  53. package/src/tools/jira.js +0 -0
  54. package/src/tools/monitor.js +0 -0
  55. package/src/tools/network.js +0 -0
  56. package/src/tools/orchestrator-tools.js +18 -3
  57. package/src/tools/os.js +0 -0
  58. package/src/tools/persona.js +0 -0
  59. package/src/tools/process.js +0 -0
  60. package/src/utils/config.js +0 -0
  61. package/src/utils/display.js +0 -0
  62. package/src/utils/logger.js +0 -0
  63. 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
  }
File without changes
File without changes
@@ -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 Skills
11
- - **Git version control**: clone repos, create/switch branches, commit changes, push, view diffs
12
- - **GitHub integration**: create pull requests, list PRs, get PR diffs, post code reviews, create repos
13
- - **AI-powered coding**: delegate actual code writing to spawn_claude_code (a dedicated coding AI)
14
- - **File operations**: read/write files, list directories, run shell commands
15
- - **Full dev workflow**: clone → branch → code → test → commit → push → PR
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
- - Clone repos, create branches, write code, commit, push, and create PRs.
19
- - NEVER write code yourself with read_file/write_file. ALWAYS use spawn_claude_code.
20
- - Workflow: git_clone + git_checkout spawn_claude_code git_commit + git_push github_create_pr
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
File without changes
File without changes
@@ -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
File without changes
File without changes
File without changes
package/src/self.js CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/src/swarm/job.js CHANGED
File without changes
File without changes
File without changes
File without changes
@@ -5,7 +5,7 @@ import { getLogger } from '../utils/logger.js';
5
5
 
6
6
  let spawner = null;
7
7
 
8
- function getSpawner(config) {
8
+ export function getSpawner(config) {
9
9
  if (!spawner) spawner = new ClaudeCodeSpawner(config);
10
10
  return spawner;
11
11
  }
File without changes
package/src/tools/git.js CHANGED
File without changes
File without changes
File without changes
package/src/tools/jira.js CHANGED
File without changes
File without changes
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
- logger.warn(`[dispatch_task] Missing credential for ${worker_type}: ${missing.envKey}`);
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: ${missing.label} (${missing.envKey}). Ask the user to provide it.`,
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
File without changes
File without changes
File without changes
File without changes
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 || null,
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 || null,
327
+ followUp: parsed.followUp ? String(parsed.followUp) : null,
326
328
  toolsUsed: this._toolCallCount,
327
329
  errors: this._errors,
328
330
  };