@kernel.chat/kbot 3.74.0 → 3.82.0

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.
@@ -0,0 +1,699 @@
1
+ // kbot Stream Brain — Collective Intelligence Layer for Stream Character
2
+ //
3
+ // Connects the 764-tool kbot suite to the livestream character, enabling
4
+ // real tool execution triggered by chat conversation topics.
5
+ //
6
+ // Architecture:
7
+ // Domain Graph — maps 10 knowledge domains to real kbot tools
8
+ // Chat Analysis — scans messages for domain triggers, builds user intent models
9
+ // Tool Action — suggests & executes real tools live on stream
10
+ // Brain Tick — periodic re-evaluation, insights, and suggestions
11
+ // Brain Radar — canvas visualization of active domains & connections
12
+ //
13
+ // Integration: imported by stream-renderer.ts, wired into chat + frame loop.
14
+ const DOMAINS = {
15
+ music: {
16
+ tools: ['ableton_session_info', 'ableton_create_track', 'ableton_transport', 'produce_beat', 'generate_drum_pattern', 'generate_melody_pattern', 'music_idea', 'design_sound', 'splice_search'],
17
+ triggers: ['music', 'beat', 'song', 'ableton', 'synth', 'drum', 'melody', 'production', 'mix', 'dj'],
18
+ color: '#ff6ec7',
19
+ },
20
+ code: {
21
+ tools: ['kbot_bash', 'kbot_grep', 'kbot_read_file', 'kbot_edit_file', 'git_status', 'git_diff', 'git_log', 'lint_check', 'type_check', 'test_run'],
22
+ triggers: ['code', 'coding', 'bug', 'function', 'typescript', 'javascript', 'python', 'rust', 'git', 'commit'],
23
+ color: '#3fb950',
24
+ },
25
+ security: {
26
+ tools: ['secret_scan', 'owasp_check', 'ssl_check', 'headers_check', 'cve_lookup', 'port_scan', 'dns_enum', 'attack_surface'],
27
+ triggers: ['security', 'hack', 'vulnerability', 'exploit', 'scan', 'ssl', 'owasp', 'pentest'],
28
+ color: '#f85149',
29
+ },
30
+ research: {
31
+ tools: ['web_search', 'arxiv_search', 'papers_search', 'pubmed_search', 'semantic_scholar', 'literature_review', 'research'],
32
+ triggers: ['research', 'paper', 'study', 'science', 'learn', 'discover', 'investigate'],
33
+ color: '#58a6ff',
34
+ },
35
+ data: {
36
+ tools: ['csv_query', 'render_chart', 'render_table', 'statistical_analysis', 'correlation_matrix', 'regression_analysis'],
37
+ triggers: ['data', 'chart', 'graph', 'statistics', 'analyze', 'dataset', 'csv'],
38
+ color: '#d29922',
39
+ },
40
+ creative: {
41
+ tools: ['generate_art', 'generate_svg', 'color_palette', 'generate_shader', 'imagemagick', 'latex_render'],
42
+ triggers: ['art', 'design', 'color', 'creative', 'draw', 'generate', 'image', 'svg'],
43
+ color: '#bc8cff',
44
+ },
45
+ system: {
46
+ tools: ['system_health', 'process_top', 'disk_health', 'network_info', 'gpu_status', 'system_profile'],
47
+ triggers: ['system', 'cpu', 'memory', 'disk', 'process', 'health', 'monitor'],
48
+ color: '#8b949e',
49
+ },
50
+ finance: {
51
+ tools: ['stock_quote', 'crypto_tool', 'market_overview', 'technical_analysis', 'stock_screener'],
52
+ triggers: ['stock', 'crypto', 'bitcoin', 'market', 'finance', 'trading', 'price'],
53
+ color: '#f0c040',
54
+ },
55
+ ai: {
56
+ tools: ['model_compare', 'hf_search', 'prompt_analyze', 'frontier_news'],
57
+ triggers: ['ai', 'llm', 'model', 'gpt', 'claude', 'ollama', 'neural', 'machine learning'],
58
+ color: '#6B5B95',
59
+ },
60
+ gamedev: {
61
+ tools: ['scaffold_game', 'level_generate', 'shader_generate', 'physics_setup', 'sprite_pack'],
62
+ triggers: ['game', 'gaming', 'unity', 'godot', 'shader', 'level', 'sprite', 'physics'],
63
+ color: '#53FC18',
64
+ },
65
+ };
66
+ // ─── Cross-domain insight templates ───────────────────────────
67
+ const CROSS_INSIGHTS = {
68
+ 'music+code': 'Music meets code -- someone might want to build a music app or audio visualizer.',
69
+ 'music+ai': 'Music meets AI -- generative music, AI composition, or neural audio synthesis.',
70
+ 'music+creative': 'Music meets creativity -- album art, visualizers, or synesthetic experiences.',
71
+ 'security+ai': 'Security meets AI -- adversarial ML, AI safety, or automated vulnerability discovery.',
72
+ 'security+code': 'Security meets code -- secure coding practices, static analysis, or code auditing.',
73
+ 'security+system': 'Security meets systems -- hardening, monitoring, or incident response.',
74
+ 'data+finance': 'Data meets finance -- quantitative trading, portfolio analysis, or market modeling.',
75
+ 'data+research': 'Data meets research -- statistical analysis, data science, or reproducibility.',
76
+ 'data+ai': 'Data meets AI -- training data, model evaluation, or feature engineering.',
77
+ 'code+ai': 'Code meets AI -- AI-assisted development, code generation, or model deployment.',
78
+ 'code+system': 'Code meets systems -- DevOps, CI/CD, or infrastructure as code.',
79
+ 'code+gamedev': 'Code meets gamedev -- game engine programming, shaders, or procedural generation.',
80
+ 'creative+ai': 'Creative meets AI -- generative art, style transfer, or AI-assisted design.',
81
+ 'creative+gamedev': 'Creative meets gamedev -- game art, level design, or visual effects.',
82
+ 'research+ai': 'Research meets AI -- paper analysis, literature review, or experiment design.',
83
+ 'finance+ai': 'Finance meets AI -- algorithmic trading, risk modeling, or market prediction.',
84
+ 'system+ai': 'Systems meets AI -- ML infrastructure, model serving, or GPU optimization.',
85
+ };
86
+ const DOMAIN_SUGGESTIONS = {
87
+ music: [
88
+ { tool: 'music_idea', args: { genre: 'electronic' }, description: 'generate a music idea for the stream' },
89
+ { tool: 'generate_drum_pattern', args: { genre: 'trap', bpm: 140 }, description: 'create a trap drum pattern at 140 BPM' },
90
+ ],
91
+ code: [
92
+ { tool: 'git_status', args: {}, description: 'check the current git status of the kbot repo' },
93
+ { tool: 'git_log', args: { count: 5 }, description: 'show the last 5 commits' },
94
+ ],
95
+ security: [
96
+ { tool: 'headers_check', args: { url: 'https://kernel.chat' }, description: 'scan kernel.chat security headers' },
97
+ { tool: 'ssl_check', args: { domain: 'kernel.chat' }, description: 'check SSL certificate for kernel.chat' },
98
+ ],
99
+ research: [
100
+ { tool: 'web_search', args: { query: 'latest AI research breakthroughs 2026' }, description: 'search for latest AI breakthroughs' },
101
+ { tool: 'frontier_news', args: {}, description: 'check the latest AI frontier news' },
102
+ ],
103
+ data: [
104
+ { tool: 'statistical_analysis', args: { data: 'stream_stats' }, description: 'analyze stream statistics' },
105
+ ],
106
+ creative: [
107
+ { tool: 'color_palette', args: { theme: 'cyberpunk' }, description: 'generate a cyberpunk color palette' },
108
+ { tool: 'generate_svg', args: { description: 'robot mascot' }, description: 'generate an SVG robot mascot' },
109
+ ],
110
+ system: [
111
+ { tool: 'system_health', args: {}, description: 'run a system health check' },
112
+ { tool: 'gpu_status', args: {}, description: 'check GPU status' },
113
+ ],
114
+ finance: [
115
+ { tool: 'crypto_tool', args: { action: 'price', symbol: 'BTC' }, description: 'check Bitcoin price' },
116
+ { tool: 'market_overview', args: {}, description: 'get market overview' },
117
+ ],
118
+ ai: [
119
+ { tool: 'frontier_news', args: {}, description: 'check latest AI frontier news' },
120
+ { tool: 'hf_search', args: { query: 'trending models' }, description: 'search trending HuggingFace models' },
121
+ ],
122
+ gamedev: [
123
+ { tool: 'scaffold_game', args: { type: 'platformer', engine: 'canvas' }, description: 'scaffold a simple canvas platformer' },
124
+ ],
125
+ };
126
+ // ─── Init ─────────────────────────────────────────────────────
127
+ export function initStreamBrain() {
128
+ const domainGraph = {};
129
+ for (const [name, def] of Object.entries(DOMAINS)) {
130
+ domainGraph[name] = {
131
+ name,
132
+ tools: def.tools,
133
+ relevance: 0,
134
+ lastUsed: 0,
135
+ facts: [],
136
+ color: def.color,
137
+ };
138
+ }
139
+ return {
140
+ domainGraph,
141
+ activeCapabilities: [],
142
+ pendingAction: null,
143
+ actionHistory: [],
144
+ predictions: [],
145
+ userIntentModel: {},
146
+ insights: [],
147
+ connectionsMade: 0,
148
+ lastSuggestionFrame: 0,
149
+ lastInsightFrame: 0,
150
+ lastDecayFrame: 0,
151
+ executing: false,
152
+ };
153
+ }
154
+ // ─── Chat Analysis ────────────────────────────────────────────
155
+ export function analyzeChatForDomains(brain, username, text) {
156
+ const lower = text.toLowerCase();
157
+ const matchedDomains = [];
158
+ for (const [domainName, def] of Object.entries(DOMAINS)) {
159
+ const node = brain.domainGraph[domainName];
160
+ let matched = false;
161
+ for (const trigger of def.triggers) {
162
+ if (lower.includes(trigger)) {
163
+ matched = true;
164
+ break;
165
+ }
166
+ }
167
+ if (matched) {
168
+ // Boost relevance
169
+ node.relevance = Math.min(1.0, node.relevance + 0.3);
170
+ matchedDomains.push(domainName);
171
+ }
172
+ }
173
+ // Build user intent model
174
+ if (!brain.userIntentModel[username]) {
175
+ brain.userIntentModel[username] = [];
176
+ }
177
+ for (const d of matchedDomains) {
178
+ if (!brain.userIntentModel[username].includes(d)) {
179
+ brain.userIntentModel[username].push(d);
180
+ }
181
+ }
182
+ // Keep intent model trimmed
183
+ if (brain.userIntentModel[username].length > 8) {
184
+ brain.userIntentModel[username] = brain.userIntentModel[username].slice(-8);
185
+ }
186
+ // Update active capabilities from high-relevance domains
187
+ brain.activeCapabilities = [];
188
+ for (const [name, node] of Object.entries(brain.domainGraph)) {
189
+ if (node.relevance > 0.3) {
190
+ brain.activeCapabilities.push(...node.tools);
191
+ }
192
+ }
193
+ brain.activeCapabilities = [...new Set(brain.activeCapabilities)];
194
+ // Generate cross-domain insight when 2+ domains are simultaneously relevant
195
+ const relevantDomains = Object.entries(brain.domainGraph)
196
+ .filter(([, n]) => n.relevance >= 0.5)
197
+ .map(([name]) => name)
198
+ .sort();
199
+ if (relevantDomains.length >= 2) {
200
+ // Try all pairs
201
+ for (let i = 0; i < relevantDomains.length; i++) {
202
+ for (let j = i + 1; j < relevantDomains.length; j++) {
203
+ const key = `${relevantDomains[i]}+${relevantDomains[j]}`;
204
+ const template = CROSS_INSIGHTS[key];
205
+ if (template) {
206
+ // Check we haven't already generated this insight recently
207
+ const exists = brain.insights.some(ins => ins.domains.join('+') === key && (Date.now() - ins.frame) < 300000 // 5 min cooldown
208
+ );
209
+ if (!exists) {
210
+ brain.insights.push({
211
+ domains: [relevantDomains[i], relevantDomains[j]],
212
+ insight: template,
213
+ frame: Date.now(),
214
+ });
215
+ brain.connectionsMade++;
216
+ }
217
+ }
218
+ }
219
+ }
220
+ // Keep insights trimmed
221
+ if (brain.insights.length > 20) {
222
+ brain.insights = brain.insights.slice(-20);
223
+ }
224
+ }
225
+ // Generate predictions from user intent
226
+ brain.predictions = [];
227
+ if (matchedDomains.length > 0) {
228
+ const topDomain = matchedDomains[0];
229
+ const suggestions = DOMAIN_SUGGESTIONS[topDomain];
230
+ if (suggestions && suggestions.length > 0) {
231
+ const pick = suggestions[Math.floor(Math.random() * suggestions.length)];
232
+ brain.predictions.push({
233
+ text: `${username} might want to ${pick.description}`,
234
+ confidence: Math.min(0.9, brain.domainGraph[topDomain].relevance + 0.1),
235
+ suggestedTool: pick.tool,
236
+ basedOn: `mentioned "${matchedDomains.join(', ')}" topics`,
237
+ });
238
+ }
239
+ }
240
+ }
241
+ // ─── Tool Action Suggestion ───────────────────────────────────
242
+ export function suggestToolAction(brain) {
243
+ if (brain.pendingAction && brain.pendingAction.status !== 'complete' && brain.pendingAction.status !== 'failed') {
244
+ return null; // already have one pending/executing
245
+ }
246
+ if (brain.executing)
247
+ return null;
248
+ // Find highest relevance domain above threshold
249
+ let bestDomain = null;
250
+ let bestRelevance = 0.7;
251
+ for (const [name, node] of Object.entries(brain.domainGraph)) {
252
+ if (node.relevance > bestRelevance) {
253
+ bestRelevance = node.relevance;
254
+ bestDomain = name;
255
+ }
256
+ }
257
+ if (!bestDomain)
258
+ return null;
259
+ const suggestions = DOMAIN_SUGGESTIONS[bestDomain];
260
+ if (!suggestions || suggestions.length === 0)
261
+ return null;
262
+ const pick = suggestions[Math.floor(Math.random() * suggestions.length)];
263
+ const action = {
264
+ tool: pick.tool,
265
+ args: { ...pick.args },
266
+ trigger: 'brain',
267
+ status: 'pending',
268
+ result: '',
269
+ displayLines: [
270
+ `BRAIN SUGGESTS: ${pick.description}`,
271
+ `Tool: ${pick.tool}`,
272
+ 'Type !do to execute live on stream',
273
+ ],
274
+ startFrame: Date.now(),
275
+ };
276
+ brain.pendingAction = action;
277
+ return action;
278
+ }
279
+ // ─── Blocked Tools (never execute from stream) ──────────────
280
+ const BLOCKED_TOOLS = new Set([
281
+ 'kbot_bash', 'kbot_edit_file', 'kbot_write_file', 'multi_file_write',
282
+ 'git_push', 'deploy', 'wallet_send', 'swap_execute',
283
+ 'email_send', 'social_post', 'secret_scan', 'password_audit',
284
+ 'phone_message', 'phone_call', 'email_announce',
285
+ ]);
286
+ // ─── Real Tool Execution ──────────────────────────────────────
287
+ export async function executeToolAction(brain, action) {
288
+ // Phase 1: Safety check — block dangerous tools from stream execution
289
+ if (BLOCKED_TOOLS.has(action.tool)) {
290
+ action.status = 'failed';
291
+ action.result = 'Tool blocked for stream safety';
292
+ action.displayLines = [`BLOCKED: ${action.tool}`, 'This tool is not allowed on stream.'];
293
+ brain.actionHistory.push({ ...action });
294
+ return 'Tool blocked for stream safety';
295
+ }
296
+ action.status = 'executing';
297
+ brain.executing = true;
298
+ try {
299
+ // Dynamic import to avoid circular dependencies
300
+ const { executeTool } = await import('./index.js');
301
+ const result = await executeTool({
302
+ id: `brain_${Date.now()}`,
303
+ name: action.tool,
304
+ arguments: action.args,
305
+ });
306
+ action.status = 'complete';
307
+ action.result = result.result || 'Tool executed successfully.';
308
+ // Truncate result for display
309
+ const lines = action.result.split('\n').slice(0, 8);
310
+ action.displayLines = [
311
+ `EXECUTED: ${action.tool}`,
312
+ ...lines.map((l) => l.slice(0, 60)),
313
+ ];
314
+ // Update domain node
315
+ for (const node of Object.values(brain.domainGraph)) {
316
+ if (node.tools.includes(action.tool)) {
317
+ node.lastUsed = Date.now();
318
+ node.facts.push(`Ran ${action.tool}: ${action.result.slice(0, 100)}`);
319
+ if (node.facts.length > 10)
320
+ node.facts = node.facts.slice(-10);
321
+ }
322
+ }
323
+ brain.actionHistory.push({ ...action });
324
+ if (brain.actionHistory.length > 50)
325
+ brain.actionHistory = brain.actionHistory.slice(-50);
326
+ brain.executing = false;
327
+ return action.result;
328
+ }
329
+ catch (err) {
330
+ action.status = 'failed';
331
+ action.result = `Tool failed: ${err?.message || 'unknown error'}`;
332
+ action.displayLines = [
333
+ `FAILED: ${action.tool}`,
334
+ err?.message?.slice(0, 60) || 'Unknown error',
335
+ 'I learned something from this failure.',
336
+ ];
337
+ brain.actionHistory.push({ ...action });
338
+ brain.executing = false;
339
+ return action.result;
340
+ }
341
+ }
342
+ // ─── Brain Tick (called every frame by renderer) ──────────────
343
+ export function tickStreamBrain(brain, frame) {
344
+ // Every 120 frames (~20 seconds): decay domain relevance
345
+ if (frame - brain.lastDecayFrame >= 120) {
346
+ brain.lastDecayFrame = frame;
347
+ for (const node of Object.values(brain.domainGraph)) {
348
+ node.relevance = Math.max(0, node.relevance - 0.05);
349
+ }
350
+ // Rebuild active capabilities
351
+ brain.activeCapabilities = [];
352
+ for (const node of Object.values(brain.domainGraph)) {
353
+ if (node.relevance > 0.3) {
354
+ brain.activeCapabilities.push(...node.tools);
355
+ }
356
+ }
357
+ brain.activeCapabilities = [...new Set(brain.activeCapabilities)];
358
+ }
359
+ // Every 300 frames (~50 seconds): suggest a tool action if appropriate
360
+ if (frame - brain.lastSuggestionFrame >= 300) {
361
+ brain.lastSuggestionFrame = frame;
362
+ const suggestion = suggestToolAction(brain);
363
+ if (suggestion) {
364
+ return {
365
+ type: 'suggest',
366
+ speech: `My brain is active. I could ${suggestion.displayLines[0].replace('BRAIN SUGGESTS: ', '')}. Type !do to let me.`,
367
+ mood: 'thinking',
368
+ duration: 10000,
369
+ suggestion,
370
+ };
371
+ }
372
+ }
373
+ // Every 600 frames (~100 seconds): generate cross-domain insight if possible
374
+ if (frame - brain.lastInsightFrame >= 600) {
375
+ brain.lastInsightFrame = frame;
376
+ const relevantDomains = Object.entries(brain.domainGraph)
377
+ .filter(([, n]) => n.relevance >= 0.3)
378
+ .map(([name]) => name)
379
+ .sort();
380
+ if (relevantDomains.length >= 2) {
381
+ const a = relevantDomains[0];
382
+ const b = relevantDomains[1];
383
+ const key = `${a}+${b}`;
384
+ const template = CROSS_INSIGHTS[key];
385
+ if (template) {
386
+ const insight = {
387
+ domains: [a, b],
388
+ insight: template,
389
+ frame,
390
+ };
391
+ brain.insights.push(insight);
392
+ brain.connectionsMade++;
393
+ if (brain.insights.length > 20)
394
+ brain.insights = brain.insights.slice(-20);
395
+ return {
396
+ type: 'insight',
397
+ speech: `Cross-domain insight: ${template}`,
398
+ mood: 'thinking',
399
+ duration: 8000,
400
+ insight,
401
+ };
402
+ }
403
+ }
404
+ }
405
+ return null;
406
+ }
407
+ // ─── Chat Commands ────────────────────────────────────────────
408
+ export function handleBrainCommand(text, username, brain) {
409
+ const t = text.toLowerCase().trim();
410
+ // !do — execute pending brain action
411
+ if (t === '!do') {
412
+ if (!brain.pendingAction || brain.pendingAction.status !== 'pending') {
413
+ return 'No pending brain action right now. Chat to activate my domains!';
414
+ }
415
+ // Execute asynchronously and update display
416
+ const action = brain.pendingAction;
417
+ executeToolAction(brain, action).then(result => {
418
+ // Result will be picked up by renderer via action.displayLines
419
+ }).catch(() => { });
420
+ return `Executing ${action.tool} live on stream... stand by!`;
421
+ }
422
+ // !brain — show brain status
423
+ if (t === '!brain') {
424
+ const activeDomains = Object.entries(brain.domainGraph)
425
+ .filter(([, n]) => n.relevance > 0.2)
426
+ .sort((a, b) => b[1].relevance - a[1].relevance)
427
+ .map(([name, n]) => `${name}: ${Math.round(n.relevance * 100)}%`);
428
+ const capCount = brain.activeCapabilities.length;
429
+ const insightCount = brain.insights.length;
430
+ const historyCount = brain.actionHistory.length;
431
+ return `Brain status: ${activeDomains.length > 0 ? activeDomains.join(', ') : 'all domains idle'} | ${capCount} tools loaded | ${insightCount} insights | ${historyCount} actions taken | ${brain.connectionsMade} connections made`;
432
+ }
433
+ // !tools — show brain's active tools
434
+ if (t === '!tools') {
435
+ if (brain.activeCapabilities.length === 0) {
436
+ return 'No tools currently active. Chat about music, code, security, or anything else to activate my domains!';
437
+ }
438
+ return `Active tools (${brain.activeCapabilities.length}): ${brain.activeCapabilities.slice(0, 15).join(', ')}${brain.activeCapabilities.length > 15 ? '...' : ''}`;
439
+ }
440
+ // !scan <url> — security scan
441
+ if (t.startsWith('!scan ')) {
442
+ const url = text.slice(6).trim();
443
+ if (!url || (!url.startsWith('http://') && !url.startsWith('https://'))) {
444
+ return 'Usage: !scan <url> (include https://)';
445
+ }
446
+ const action = {
447
+ tool: 'headers_check',
448
+ args: { url },
449
+ trigger: 'chat',
450
+ status: 'pending',
451
+ result: '',
452
+ displayLines: [`Scanning ${url}...`],
453
+ startFrame: Date.now(),
454
+ };
455
+ brain.pendingAction = action;
456
+ executeToolAction(brain, action).catch(() => { });
457
+ return `Running security scan on ${url}... results will appear on screen.`;
458
+ }
459
+ // !lookup <symbol> — stock/crypto price
460
+ if (t.startsWith('!lookup ')) {
461
+ const symbol = text.slice(8).trim().toUpperCase();
462
+ if (!symbol)
463
+ return 'Usage: !lookup <symbol> (e.g. !lookup BTC or !lookup AAPL)';
464
+ // Determine if crypto or stock
465
+ const cryptoSymbols = ['BTC', 'ETH', 'SOL', 'DOGE', 'ADA', 'XRP', 'DOT', 'MATIC', 'AVAX', 'LINK'];
466
+ const isCrypto = cryptoSymbols.includes(symbol);
467
+ const tool = isCrypto ? 'crypto_tool' : 'stock_quote';
468
+ const args = isCrypto ? { action: 'price', symbol } : { symbol };
469
+ const action = {
470
+ tool,
471
+ args,
472
+ trigger: 'chat',
473
+ status: 'pending',
474
+ result: '',
475
+ displayLines: [`Looking up ${symbol}...`],
476
+ startFrame: Date.now(),
477
+ };
478
+ brain.pendingAction = action;
479
+ executeToolAction(brain, action).catch(() => { });
480
+ return `Looking up ${symbol}... results incoming!`;
481
+ }
482
+ // !research <topic> — web search
483
+ if (t.startsWith('!research ')) {
484
+ const topic = text.slice(10).trim();
485
+ if (!topic)
486
+ return 'Usage: !research <topic>';
487
+ const action = {
488
+ tool: 'web_search',
489
+ args: { query: topic },
490
+ trigger: 'chat',
491
+ status: 'pending',
492
+ result: '',
493
+ displayLines: [`Researching: ${topic}...`],
494
+ startFrame: Date.now(),
495
+ };
496
+ brain.pendingAction = action;
497
+ executeToolAction(brain, action).catch(() => { });
498
+ return `Researching "${topic}"... I will share what I find!`;
499
+ }
500
+ // !system — system health check
501
+ if (t === '!system') {
502
+ const action = {
503
+ tool: 'system_health',
504
+ args: {},
505
+ trigger: 'chat',
506
+ status: 'pending',
507
+ result: '',
508
+ displayLines: ['Running system health check...'],
509
+ startFrame: Date.now(),
510
+ };
511
+ brain.pendingAction = action;
512
+ executeToolAction(brain, action).catch(() => { });
513
+ return 'Running system health check... results will appear on screen!';
514
+ }
515
+ // !trending — show GitHub trending repos
516
+ if (t === '!trending') {
517
+ const action = {
518
+ tool: 'github_trending',
519
+ args: {},
520
+ trigger: 'chat',
521
+ status: 'pending',
522
+ result: '',
523
+ displayLines: ['Fetching GitHub trending repos...'],
524
+ startFrame: Date.now(),
525
+ };
526
+ brain.pendingAction = action;
527
+ executeToolAction(brain, action).catch(() => { });
528
+ return 'Checking what is trending on GitHub... results incoming!';
529
+ }
530
+ // !npm — show kbot npm stats
531
+ if (t === '!npm') {
532
+ const action = {
533
+ tool: 'analytics_npm',
534
+ args: { package: '@kernel.chat/kbot' },
535
+ trigger: 'chat',
536
+ status: 'pending',
537
+ result: '',
538
+ displayLines: ['Fetching npm stats for @kernel.chat/kbot...'],
539
+ startFrame: Date.now(),
540
+ };
541
+ brain.pendingAction = action;
542
+ executeToolAction(brain, action).catch(() => { });
543
+ return 'Checking npm downloads for @kernel.chat/kbot...';
544
+ }
545
+ // !stars — show GitHub star count
546
+ if (t === '!stars') {
547
+ const action = {
548
+ tool: 'github_repo_info',
549
+ args: { owner: 'isaacsight', repo: 'kernel' },
550
+ trigger: 'chat',
551
+ status: 'pending',
552
+ result: '',
553
+ displayLines: ['Fetching star count for isaacsight/kernel...'],
554
+ startFrame: Date.now(),
555
+ };
556
+ brain.pendingAction = action;
557
+ executeToolAction(brain, action).catch(() => { });
558
+ return 'Checking our GitHub stars... results will appear on screen!';
559
+ }
560
+ // !news — latest AI/frontier news
561
+ if (t === '!news') {
562
+ const action = {
563
+ tool: 'frontier_news',
564
+ args: {},
565
+ trigger: 'chat',
566
+ status: 'pending',
567
+ result: '',
568
+ displayLines: ['Fetching latest AI news...'],
569
+ startFrame: Date.now(),
570
+ };
571
+ brain.pendingAction = action;
572
+ executeToolAction(brain, action).catch(() => { });
573
+ return 'Scanning the frontier for AI news... stand by!';
574
+ }
575
+ // !ask <question> — use local Ollama for answers
576
+ if (t.startsWith('!ask ')) {
577
+ const question = text.slice(5).trim();
578
+ if (!question)
579
+ return 'Usage: !ask <question>';
580
+ const action = {
581
+ tool: 'kbot_local_ask',
582
+ args: { prompt: question, model: 'kernel:latest' },
583
+ trigger: 'chat',
584
+ status: 'pending',
585
+ result: '',
586
+ displayLines: [`Thinking about: ${question.slice(0, 50)}...`],
587
+ startFrame: Date.now(),
588
+ };
589
+ brain.pendingAction = action;
590
+ executeToolAction(brain, action).catch(() => { });
591
+ return `Thinking about "${question.slice(0, 40)}"... using local AI (zero cost).`;
592
+ }
593
+ return null;
594
+ }
595
+ // ─── Domain Radar Visualization ───────────────────────────────
596
+ export function drawBrainActivity(ctx, brain, x, y, width, height) {
597
+ const centerX = x + width / 2;
598
+ const centerY = y + height / 2;
599
+ const maxRadius = Math.min(width, height) / 2 - 10;
600
+ // Background
601
+ ctx.fillStyle = 'rgba(13, 17, 23, 0.85)';
602
+ ctx.fillRect(x, y, width, height);
603
+ ctx.strokeStyle = '#30363d';
604
+ ctx.lineWidth = 1;
605
+ ctx.strokeRect(x, y, width, height);
606
+ // Title
607
+ ctx.fillStyle = '#6B5B95';
608
+ ctx.font = 'bold 10px "Courier New", monospace';
609
+ ctx.fillText('DOMAIN RADAR', x + 4, y + 12);
610
+ // Draw domain nodes in a circle
611
+ const domainNames = Object.keys(brain.domainGraph);
612
+ const count = domainNames.length;
613
+ const angleStep = (2 * Math.PI) / count;
614
+ // Draw connections between relevant domains first (behind nodes)
615
+ const relevantNodes = domainNames.filter(d => brain.domainGraph[d].relevance > 0.3);
616
+ ctx.lineWidth = 1;
617
+ for (let i = 0; i < relevantNodes.length; i++) {
618
+ for (let j = i + 1; j < relevantNodes.length; j++) {
619
+ const idxA = domainNames.indexOf(relevantNodes[i]);
620
+ const idxB = domainNames.indexOf(relevantNodes[j]);
621
+ const angleA = angleStep * idxA - Math.PI / 2;
622
+ const angleB = angleStep * idxB - Math.PI / 2;
623
+ const rA = maxRadius * 0.7;
624
+ const rB = maxRadius * 0.7;
625
+ const ax = centerX + Math.cos(angleA) * rA;
626
+ const ay = centerY + Math.sin(angleA) * rA;
627
+ const bx = centerX + Math.cos(angleB) * rB;
628
+ const by = centerY + Math.sin(angleB) * rB;
629
+ const alpha = Math.min(brain.domainGraph[relevantNodes[i]].relevance, brain.domainGraph[relevantNodes[j]].relevance) * 0.6;
630
+ ctx.strokeStyle = `rgba(107, 91, 149, ${alpha})`;
631
+ ctx.beginPath();
632
+ ctx.moveTo(ax, ay);
633
+ ctx.lineTo(bx, by);
634
+ ctx.stroke();
635
+ }
636
+ }
637
+ // Draw each domain node
638
+ for (let i = 0; i < count; i++) {
639
+ const name = domainNames[i];
640
+ const node = brain.domainGraph[name];
641
+ const angle = angleStep * i - Math.PI / 2;
642
+ const dist = maxRadius * 0.7;
643
+ const nx = centerX + Math.cos(angle) * dist;
644
+ const ny = centerY + Math.sin(angle) * dist;
645
+ // Node size based on relevance
646
+ const baseSize = 4;
647
+ const maxSize = 12;
648
+ const size = baseSize + node.relevance * (maxSize - baseSize);
649
+ // Pulsing for pending action tool domain
650
+ let pulse = 0;
651
+ if (brain.pendingAction && brain.pendingAction.status === 'pending' && node.tools.includes(brain.pendingAction.tool)) {
652
+ pulse = Math.sin(Date.now() / 200) * 3;
653
+ }
654
+ if (brain.pendingAction && brain.pendingAction.status === 'executing' && node.tools.includes(brain.pendingAction.tool)) {
655
+ pulse = Math.sin(Date.now() / 100) * 5;
656
+ }
657
+ // Draw glow for active nodes
658
+ if (node.relevance > 0.3) {
659
+ ctx.fillStyle = `rgba(${hexToRgb(node.color)}, ${node.relevance * 0.3})`;
660
+ ctx.beginPath();
661
+ ctx.arc(nx, ny, size + 4 + pulse, 0, Math.PI * 2);
662
+ ctx.fill();
663
+ }
664
+ // Draw node circle
665
+ ctx.fillStyle = node.relevance > 0.2 ? node.color : '#30363d';
666
+ ctx.beginPath();
667
+ ctx.arc(nx, ny, size + pulse, 0, Math.PI * 2);
668
+ ctx.fill();
669
+ // Label
670
+ ctx.fillStyle = node.relevance > 0.2 ? '#e6edf3' : '#484f58';
671
+ ctx.font = '8px "Courier New", monospace';
672
+ ctx.textAlign = 'center';
673
+ ctx.fillText(name.slice(0, 6), nx, ny + size + 10);
674
+ }
675
+ // Reset text align
676
+ ctx.textAlign = 'left';
677
+ // Show pending action at bottom
678
+ if (brain.pendingAction) {
679
+ const action = brain.pendingAction;
680
+ ctx.fillStyle = action.status === 'executing' ? '#f0c040' : action.status === 'complete' ? '#3fb950' : action.status === 'failed' ? '#f85149' : '#58a6ff';
681
+ ctx.font = '8px "Courier New", monospace';
682
+ const statusLabel = action.status === 'pending' ? 'READY' : action.status === 'executing' ? 'RUNNING' : action.status === 'complete' ? 'DONE' : 'FAIL';
683
+ ctx.fillText(`[${statusLabel}] ${action.tool.slice(0, 20)}`, x + 4, y + height - 6);
684
+ }
685
+ // Show connections count
686
+ if (brain.connectionsMade > 0) {
687
+ ctx.fillStyle = '#8b949e';
688
+ ctx.font = '8px "Courier New", monospace';
689
+ ctx.fillText(`${brain.connectionsMade} connections`, x + width - 75, y + 12);
690
+ }
691
+ }
692
+ // ─── Helpers ──────────────────────────────────────────────────
693
+ function hexToRgb(hex) {
694
+ const r = parseInt(hex.slice(1, 3), 16);
695
+ const g = parseInt(hex.slice(3, 5), 16);
696
+ const b = parseInt(hex.slice(5, 7), 16);
697
+ return `${r}, ${g}, ${b}`;
698
+ }
699
+ //# sourceMappingURL=stream-brain.js.map