kernelbot 1.0.36 → 1.0.38

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 (40) hide show
  1. package/bin/kernel.js +389 -23
  2. package/config.example.yaml +17 -0
  3. package/package.json +2 -1
  4. package/src/agent.js +355 -82
  5. package/src/bot.js +724 -12
  6. package/src/character.js +406 -0
  7. package/src/characters/builder.js +174 -0
  8. package/src/characters/builtins.js +421 -0
  9. package/src/conversation.js +17 -2
  10. package/src/dashboard/agents.css +469 -0
  11. package/src/dashboard/agents.html +184 -0
  12. package/src/dashboard/agents.js +873 -0
  13. package/src/dashboard/dashboard.css +281 -0
  14. package/src/dashboard/dashboard.js +579 -0
  15. package/src/dashboard/index.html +366 -0
  16. package/src/dashboard/server.js +521 -0
  17. package/src/dashboard/shared.css +700 -0
  18. package/src/dashboard/shared.js +209 -0
  19. package/src/life/engine.js +28 -20
  20. package/src/life/evolution.js +7 -5
  21. package/src/life/journal.js +5 -4
  22. package/src/life/memory.js +12 -9
  23. package/src/life/share-queue.js +7 -5
  24. package/src/prompts/orchestrator.js +76 -14
  25. package/src/prompts/workers.js +22 -0
  26. package/src/security/auth.js +42 -1
  27. package/src/self.js +17 -5
  28. package/src/services/linkedin-api.js +190 -0
  29. package/src/services/stt.js +8 -2
  30. package/src/services/tts.js +32 -2
  31. package/src/services/x-api.js +141 -0
  32. package/src/swarm/worker-registry.js +7 -0
  33. package/src/tools/categories.js +4 -0
  34. package/src/tools/index.js +6 -0
  35. package/src/tools/linkedin.js +264 -0
  36. package/src/tools/orchestrator-tools.js +337 -2
  37. package/src/tools/x.js +256 -0
  38. package/src/utils/config.js +104 -57
  39. package/src/utils/display.js +73 -12
  40. package/src/utils/temporal-awareness.js +24 -10
@@ -0,0 +1,521 @@
1
+ /**
2
+ * Dashboard HTTP server — cyberpunk terminal monitoring UI.
3
+ * Zero external dependencies — uses Node.js built-in http, fs, path, os, url.
4
+ *
5
+ * Exports startDashboard(deps) → { server, stop() }
6
+ */
7
+
8
+ import { createServer } from 'http';
9
+ import { readFileSync, statSync } from 'fs';
10
+ import { join, dirname, extname } from 'path';
11
+ import { fileURLToPath } from 'url';
12
+ import { loadavg, totalmem, freemem, cpus } from 'os';
13
+ import { getLogger } from '../utils/logger.js';
14
+ import { WORKER_TYPES } from '../swarm/worker-registry.js';
15
+ import { TOOL_CATEGORIES } from '../tools/categories.js';
16
+
17
+ const __dirname = dirname(fileURLToPath(import.meta.url));
18
+
19
+ /**
20
+ * Start the dashboard HTTP server.
21
+ * @param {object} deps - All system dependencies
22
+ * @returns {{ server: import('http').Server, stop: () => void }}
23
+ */
24
+ export function startDashboard(deps) {
25
+ const {
26
+ port = 3000,
27
+ config,
28
+ jobManager,
29
+ automationManager,
30
+ lifeEngine,
31
+ conversationManager,
32
+ characterManager,
33
+ memoryManager,
34
+ journalManager,
35
+ shareQueue,
36
+ evolutionTracker,
37
+ selfManager,
38
+ } = deps;
39
+
40
+ const logger = getLogger();
41
+
42
+ // --- Static file serving ---
43
+ const MIME_TYPES = {
44
+ '.html': 'text/html; charset=utf-8',
45
+ '.css': 'text/css; charset=utf-8',
46
+ '.js': 'text/javascript; charset=utf-8',
47
+ '.svg': 'image/svg+xml',
48
+ '.png': 'image/png',
49
+ '.json': 'application/json',
50
+ };
51
+
52
+ function serveStaticFile(res, filePath) {
53
+ // Path traversal prevention
54
+ const resolved = join(__dirname, filePath);
55
+ if (!resolved.startsWith(__dirname)) {
56
+ res.writeHead(403, { 'Content-Type': 'text/plain' });
57
+ res.end('Forbidden');
58
+ return true;
59
+ }
60
+ try {
61
+ const content = readFileSync(resolved);
62
+ const ext = extname(resolved);
63
+ const mime = MIME_TYPES[ext] || 'application/octet-stream';
64
+ res.writeHead(200, { 'Content-Type': mime });
65
+ res.end(content);
66
+ return true;
67
+ } catch {
68
+ return false;
69
+ }
70
+ }
71
+
72
+ // --- Log tail ---
73
+ const logPaths = [
74
+ join(process.cwd(), 'kernel.log'),
75
+ join(process.env.HOME || '', '.kernelbot', 'kernel.log'),
76
+ ];
77
+ let logCache = { mtime: 0, lines: [] };
78
+
79
+ function tailLog(count = 100) {
80
+ for (const p of logPaths) {
81
+ try {
82
+ const st = statSync(p);
83
+ if (st.mtimeMs !== logCache.mtime) {
84
+ const content = readFileSync(p, 'utf-8');
85
+ const allLines = content.split('\n').filter(Boolean);
86
+ logCache = { mtime: st.mtimeMs, lines: allLines.slice(-count) };
87
+ }
88
+ return logCache.lines;
89
+ } catch { /* skip */ }
90
+ }
91
+ return [];
92
+ }
93
+
94
+ function parseLogs(lines) {
95
+ return lines.map(line => {
96
+ try {
97
+ const entry = JSON.parse(line);
98
+ return {
99
+ timestamp: entry.timestamp || '',
100
+ level: entry.level || 'info',
101
+ message: entry.message || '',
102
+ };
103
+ } catch {
104
+ return { timestamp: '', level: 'info', message: line };
105
+ }
106
+ });
107
+ }
108
+
109
+ // --- API data builders ---
110
+
111
+ function getSystemData() {
112
+ const load = loadavg();
113
+ const total = totalmem();
114
+ const free = freemem();
115
+ const mem = process.memoryUsage();
116
+ return {
117
+ cpu: { load1: load[0], load5: load[1], load15: load[2], cores: cpus().length },
118
+ ram: { total, free, used: total - free, percent: ((total - free) / total * 100).toFixed(1) },
119
+ process: { heap: mem.heapUsed, heapTotal: mem.heapTotal, rss: mem.rss, external: mem.external },
120
+ uptime: process.uptime(),
121
+ pid: process.pid,
122
+ nodeVersion: process.version,
123
+ startedAt: Date.now() - process.uptime() * 1000,
124
+ };
125
+ }
126
+
127
+ function getConfigData() {
128
+ const mask = (val) => val ? '●●●●●●●●' : 'NOT SET';
129
+ const has = (val) => !!val;
130
+ return {
131
+ orchestrator: {
132
+ provider: config.orchestrator?.provider || 'anthropic',
133
+ model: config.orchestrator?.model || 'default',
134
+ max_tokens: config.orchestrator?.max_tokens,
135
+ temperature: config.orchestrator?.temperature,
136
+ api_key: mask(config.orchestrator?.api_key),
137
+ },
138
+ brain: {
139
+ provider: config.brain?.provider || 'anthropic',
140
+ model: config.brain?.model || 'default',
141
+ max_tokens: config.brain?.max_tokens,
142
+ temperature: config.brain?.temperature,
143
+ max_tool_depth: config.brain?.max_tool_depth,
144
+ api_key: mask(config.brain?.api_key),
145
+ },
146
+ swarm: config.swarm || {},
147
+ life: {
148
+ enabled: config.life?.enabled !== false,
149
+ min_interval: config.life?.min_interval_minutes,
150
+ max_interval: config.life?.max_interval_minutes,
151
+ activity_weights: config.life?.activity_weights,
152
+ quiet_hours: config.life?.quiet_hours,
153
+ self_coding: config.life?.self_coding ? {
154
+ enabled: config.life.self_coding.enabled,
155
+ branch_prefix: config.life.self_coding.branch_prefix,
156
+ cooldown_hours: config.life.self_coding.cooldown_hours,
157
+ max_active_prs: config.life.self_coding.max_active_prs,
158
+ allowed_scopes: config.life.self_coding.allowed_scopes,
159
+ } : null,
160
+ },
161
+ telegram: { allowed_users: config.telegram?.allowed_users?.length || 0 },
162
+ bot: config.bot || {},
163
+ claude_code: {
164
+ model: config.claude_code?.model || 'default',
165
+ max_turns: config.claude_code?.max_turns,
166
+ timeout_seconds: config.claude_code?.timeout_seconds,
167
+ auth_mode: config.claude_code?.auth_mode || 'system',
168
+ },
169
+ integrations: {
170
+ telegram: has(config.telegram?.bot_token),
171
+ github: has(config.github?.token),
172
+ jira: has(config.jira?.api_token),
173
+ linkedin: has(config.linkedin?.access_token),
174
+ x: has(config.x?.consumer_key),
175
+ elevenlabs: has(config.elevenlabs?.api_key),
176
+ claude_code: has(config.claude_code?.api_key || config.claude_code?.oauth_token) || config.claude_code?.auth_mode === 'system',
177
+ },
178
+ };
179
+ }
180
+
181
+ function getJobsData() {
182
+ const jobs = [];
183
+ for (const [id, job] of jobManager.jobs) {
184
+ jobs.push({
185
+ id,
186
+ type: job.workerType,
187
+ status: job.status,
188
+ task: job.task,
189
+ duration: job.duration,
190
+ llmCalls: job.llmCalls,
191
+ toolCalls: job.toolCalls,
192
+ progress: job.progress || [],
193
+ lastThinking: job.lastThinking,
194
+ createdAt: job.createdAt,
195
+ startedAt: job.startedAt,
196
+ completedAt: job.completedAt,
197
+ chatId: job.chatId,
198
+ error: job.error,
199
+ context: job.context,
200
+ dependsOn: job.dependsOn || [],
201
+ timeoutMs: job.timeoutMs,
202
+ lastActivity: job.lastActivity,
203
+ structuredResult: job.structuredResult ? {
204
+ summary: job.structuredResult.summary,
205
+ status: job.structuredResult.status,
206
+ details: job.structuredResult.details,
207
+ artifacts: job.structuredResult.artifacts,
208
+ followUp: job.structuredResult.followUp,
209
+ toolsUsed: job.structuredResult.toolsUsed,
210
+ errors: job.structuredResult.errors,
211
+ } : null,
212
+ });
213
+ }
214
+ return jobs.sort((a, b) => b.createdAt - a.createdAt);
215
+ }
216
+
217
+ function getAutomationsData() {
218
+ try {
219
+ return automationManager.listAll().map(a => ({
220
+ id: a.id,
221
+ chatId: a.chatId,
222
+ name: a.name,
223
+ description: a.description,
224
+ schedule: a.schedule,
225
+ enabled: a.enabled,
226
+ lastRun: a.lastRun,
227
+ nextRun: a.nextRun,
228
+ runCount: a.runCount,
229
+ lastError: a.lastError,
230
+ }));
231
+ } catch { return []; }
232
+ }
233
+
234
+ function getLifeData() {
235
+ try {
236
+ const status = lifeEngine.getStatus();
237
+ const state = lifeEngine._state || {};
238
+ const now = Date.now();
239
+ const scCfg = config.life?.self_coding || {};
240
+ const cooldowns = {
241
+ journal: state.lastJournalTime ? Math.max(0, 4 * 3600000 - (now - state.lastJournalTime)) : 0,
242
+ self_code: state.lastSelfCodeTime ? Math.max(0, (scCfg.cooldown_hours ?? 2) * 3600000 - (now - state.lastSelfCodeTime)) : 0,
243
+ code_review: state.lastCodeReviewTime ? Math.max(0, (scCfg.code_review_cooldown_hours ?? 4) * 3600000 - (now - state.lastCodeReviewTime)) : 0,
244
+ reflect: state.lastReflectTime ? Math.max(0, 4 * 3600000 - (now - state.lastReflectTime)) : 0,
245
+ };
246
+ let ideas = [];
247
+ try { ideas = lifeEngine._loadIdeas(); } catch { /* skip */ }
248
+ const weights = config.life?.activity_weights || {};
249
+ return { ...status, cooldowns, ideas: ideas.slice(-20), weights };
250
+ } catch { return { status: 'unknown' }; }
251
+ }
252
+
253
+ function getMemoriesData() {
254
+ try {
255
+ return memoryManager.getRecentEpisodic(72, 30);
256
+ } catch { return []; }
257
+ }
258
+
259
+ function getJournalData() {
260
+ try {
261
+ return {
262
+ content: journalManager.getToday() || '',
263
+ recent: journalManager.getRecent(7),
264
+ dates: journalManager.list(30),
265
+ };
266
+ } catch { return { content: '', recent: [], dates: [] }; }
267
+ }
268
+
269
+ function getEvolutionData() {
270
+ try {
271
+ return {
272
+ stats: evolutionTracker.getStats(),
273
+ active: evolutionTracker.getActiveProposal(),
274
+ recent: evolutionTracker.getRecentProposals(10),
275
+ lessons: evolutionTracker.getRecentLessons(15),
276
+ prsToCheck: evolutionTracker.getPRsToCheck().length,
277
+ };
278
+ } catch { return { stats: {}, active: null, recent: [], lessons: [], prsToCheck: 0 }; }
279
+ }
280
+
281
+ function getConversationsData() {
282
+ try {
283
+ const summaries = [];
284
+ for (const [chatId, messages] of conversationManager.conversations) {
285
+ const last = messages.length > 0 ? messages[messages.length - 1] : null;
286
+ const skill = conversationManager.activeSkills?.get(chatId) || null;
287
+ const userMsgs = messages.filter(m => m.role === 'user').length;
288
+ const assistantMsgs = messages.filter(m => m.role === 'assistant').length;
289
+ summaries.push({
290
+ chatId,
291
+ messageCount: messages.length,
292
+ userMessages: userMsgs,
293
+ assistantMessages: assistantMsgs,
294
+ lastTimestamp: last?.timestamp || null,
295
+ activeSkill: skill,
296
+ });
297
+ }
298
+ return summaries.sort((a, b) => (b.lastTimestamp || 0) - (a.lastTimestamp || 0));
299
+ } catch { return []; }
300
+ }
301
+
302
+ function getCharacterData() {
303
+ try {
304
+ const activeId = characterManager.getActiveCharacterId();
305
+ const active = characterManager.getCharacter(activeId);
306
+ const all = characterManager.listCharacters();
307
+ return {
308
+ active: active ? {
309
+ id: activeId, name: active.name, emoji: active.emoji,
310
+ type: active.type, tagline: active.tagline,
311
+ origin: active.origin, age: active.age,
312
+ lastActiveAt: active.lastActiveAt,
313
+ } : null,
314
+ characters: all.map(c => ({
315
+ id: c.id, name: c.name, emoji: c.emoji, type: c.type, tagline: c.tagline,
316
+ })),
317
+ };
318
+ } catch { return { active: null, characters: [] }; }
319
+ }
320
+
321
+ function getSharesData() {
322
+ try {
323
+ return {
324
+ pending: shareQueue.getPending(null, 20),
325
+ shared: (shareQueue._data?.shared || []).slice(-15),
326
+ todayCount: shareQueue.getSharedTodayCount(),
327
+ };
328
+ } catch { return { pending: [], shared: [], todayCount: 0 }; }
329
+ }
330
+
331
+ function getSelfData() {
332
+ try {
333
+ return { content: selfManager.loadAll() };
334
+ } catch { return { content: '' }; }
335
+ }
336
+
337
+ function getCapabilitiesData() {
338
+ const workers = {};
339
+ for (const [type, def] of Object.entries(WORKER_TYPES)) {
340
+ const toolNames = [];
341
+ for (const cat of def.categories) {
342
+ const tools = TOOL_CATEGORIES[cat];
343
+ if (tools) toolNames.push(...tools);
344
+ }
345
+ workers[type] = {
346
+ label: def.label,
347
+ emoji: def.emoji,
348
+ description: def.description,
349
+ timeout: def.timeout,
350
+ categories: def.categories,
351
+ tools: [...new Set(toolNames)],
352
+ };
353
+ }
354
+ const totalTools = new Set(Object.values(TOOL_CATEGORIES).flat()).size;
355
+ return { workers, categories: Object.keys(TOOL_CATEGORIES), totalTools };
356
+ }
357
+
358
+ function getKnowledgeData() {
359
+ try {
360
+ const data = memoryManager._loadSemantic();
361
+ return Object.entries(data).map(([topic, val]) => ({
362
+ topic,
363
+ summary: val.summary,
364
+ sources: val.sources || [],
365
+ relatedTopics: val.relatedTopics || [],
366
+ learnedAt: val.learnedAt,
367
+ })).sort((a, b) => (b.learnedAt || 0) - (a.learnedAt || 0));
368
+ } catch { return []; }
369
+ }
370
+
371
+ // --- Full snapshot for SSE ---
372
+ function getSnapshot() {
373
+ return {
374
+ ts: Date.now(),
375
+ system: getSystemData(),
376
+ jobs: getJobsData(),
377
+ automations: getAutomationsData(),
378
+ life: getLifeData(),
379
+ memories: getMemoriesData(),
380
+ journal: getJournalData(),
381
+ evolution: getEvolutionData(),
382
+ conversations: getConversationsData(),
383
+ character: getCharacterData(),
384
+ shares: getSharesData(),
385
+ logs: parseLogs(tailLog(100)),
386
+ capabilities: getCapabilitiesData(),
387
+ knowledge: getKnowledgeData(),
388
+ };
389
+ }
390
+
391
+ // --- SSE ---
392
+ const sseClients = new Set();
393
+
394
+ function broadcastSSE(data) {
395
+ const payload = `data: ${JSON.stringify(data)}\n\n`;
396
+ for (const res of sseClients) {
397
+ try { res.write(payload); } catch { sseClients.delete(res); }
398
+ }
399
+ }
400
+
401
+ const sseInterval = setInterval(() => {
402
+ if (sseClients.size === 0) return;
403
+ try {
404
+ broadcastSSE(getSnapshot());
405
+ } catch (err) {
406
+ logger.warn(`[Dashboard] SSE broadcast error: ${err.message}`);
407
+ }
408
+ }, 3000);
409
+
410
+ // --- HTTP routing ---
411
+ function sendJson(res, data) {
412
+ const body = JSON.stringify(data);
413
+ res.writeHead(200, {
414
+ 'Content-Type': 'application/json',
415
+ 'Access-Control-Allow-Origin': '*',
416
+ 'Cache-Control': 'no-cache',
417
+ });
418
+ res.end(body);
419
+ }
420
+
421
+ const server = createServer((req, res) => {
422
+ const url = new URL(req.url, `http://${req.headers.host}`);
423
+ const path = url.pathname;
424
+
425
+ // CORS preflight
426
+ if (req.method === 'OPTIONS') {
427
+ res.writeHead(204, {
428
+ 'Access-Control-Allow-Origin': '*',
429
+ 'Access-Control-Allow-Methods': 'GET, OPTIONS',
430
+ 'Access-Control-Allow-Headers': 'Content-Type',
431
+ });
432
+ res.end();
433
+ return;
434
+ }
435
+
436
+ // Serve index.html
437
+ if (path === '/' || path === '/index.html') {
438
+ serveStaticFile(res, 'index.html');
439
+ return;
440
+ }
441
+
442
+ // Serve agents page
443
+ if (path === '/agents' || path === '/agents.html') {
444
+ serveStaticFile(res, 'agents.html');
445
+ return;
446
+ }
447
+
448
+ // SSE endpoint
449
+ if (path === '/events') {
450
+ res.writeHead(200, {
451
+ 'Content-Type': 'text/event-stream',
452
+ 'Cache-Control': 'no-cache',
453
+ 'Connection': 'keep-alive',
454
+ 'Access-Control-Allow-Origin': '*',
455
+ });
456
+ res.write(`data: ${JSON.stringify(getSnapshot())}\n\n`);
457
+ sseClients.add(res);
458
+ req.on('close', () => sseClients.delete(res));
459
+ return;
460
+ }
461
+
462
+ // JSON API
463
+ const routes = {
464
+ '/api/system': getSystemData,
465
+ '/api/config': getConfigData,
466
+ '/api/jobs': getJobsData,
467
+ '/api/automations': getAutomationsData,
468
+ '/api/life': getLifeData,
469
+ '/api/memories': getMemoriesData,
470
+ '/api/journal': getJournalData,
471
+ '/api/evolution': getEvolutionData,
472
+ '/api/conversations': getConversationsData,
473
+ '/api/character': getCharacterData,
474
+ '/api/logs': () => parseLogs(tailLog(100)),
475
+ '/api/shares': getSharesData,
476
+ '/api/self': getSelfData,
477
+ '/api/capabilities': getCapabilitiesData,
478
+ '/api/knowledge': getKnowledgeData,
479
+ };
480
+
481
+ if (routes[path]) {
482
+ try {
483
+ sendJson(res, routes[path]());
484
+ } catch (err) {
485
+ res.writeHead(500, { 'Content-Type': 'application/json' });
486
+ res.end(JSON.stringify({ error: err.message }));
487
+ }
488
+ return;
489
+ }
490
+
491
+ // Static files (.css, .js, etc.)
492
+ if (/^\/([\w.-]+\.(css|js|svg|png))$/.test(path)) {
493
+ const fileName = path.slice(1);
494
+ if (serveStaticFile(res, fileName)) return;
495
+ }
496
+
497
+ // 404
498
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
499
+ res.end('Not Found');
500
+ });
501
+
502
+ server.listen(port, () => {
503
+ logger.info(`[Dashboard] Cyberpunk terminal running on http://localhost:${port}`);
504
+ });
505
+
506
+ server.on('error', (err) => {
507
+ logger.error(`[Dashboard] Server error: ${err.message}`);
508
+ });
509
+
510
+ return {
511
+ server,
512
+ stop() {
513
+ clearInterval(sseInterval);
514
+ for (const res of sseClients) {
515
+ try { res.end(); } catch { /* ignore */ }
516
+ }
517
+ sseClients.clear();
518
+ server.close();
519
+ },
520
+ };
521
+ }