@kernel.chat/kbot 3.73.3 → 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.
- package/README.md +9 -0
- package/dist/cli.js +241 -4
- package/dist/ide/mcp-server.js +58 -43
- package/dist/tools/ghost.d.ts +2 -0
- package/dist/tools/ghost.js +713 -0
- package/dist/tools/index.js +4 -0
- package/dist/tools/render-engine.d.ts +158 -0
- package/dist/tools/render-engine.js +1361 -0
- package/dist/tools/sprite-engine.d.ts +41 -0
- package/dist/tools/sprite-engine.js +1601 -0
- package/dist/tools/stream-brain.d.ts +70 -0
- package/dist/tools/stream-brain.js +699 -0
- package/dist/tools/stream-character.d.ts +2 -0
- package/dist/tools/stream-character.js +619 -0
- package/dist/tools/stream-intelligence.d.ts +172 -0
- package/dist/tools/stream-intelligence.js +2237 -0
- package/dist/tools/stream-renderer.d.ts +2 -0
- package/dist/tools/stream-renderer.js +3473 -0
- package/dist/tools/streaming.d.ts +2 -0
- package/dist/tools/streaming.js +491 -0
- package/package.json +1 -1
|
@@ -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
|