@kernel.chat/kbot 3.93.0 → 3.94.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,111 @@
1
+ export interface EpisodicMemory {
2
+ what: string;
3
+ when: number;
4
+ where: number;
5
+ who: string;
6
+ emotion: string;
7
+ importance: number;
8
+ }
9
+ export interface MemoryEngine {
10
+ shortTerm: Map<string, unknown>;
11
+ longTerm: Map<string, unknown>;
12
+ episodic: EpisodicMemory[];
13
+ semantic: Map<string, string>;
14
+ spatial: Map<string, unknown>;
15
+ lastConsolidation: number;
16
+ }
17
+ export declare function initMemoryEngine(): MemoryEngine;
18
+ /** Store an episodic memory */
19
+ export declare function remember(mem: MemoryEngine, what: string, who: string, where: number, emotion: string, importance: number): void;
20
+ /** Search memories by keyword */
21
+ export declare function recall(mem: MemoryEngine, query: string): EpisodicMemory[];
22
+ /** Spatial recall — find memories near a world X coordinate */
23
+ export declare function recallAtLocation(mem: MemoryEngine, worldX: number, radius: number): EpisodicMemory[];
24
+ /** Consolidate: move important short-term to long-term, decay unimportant */
25
+ export declare function consolidateMemories(mem: MemoryEngine): void;
26
+ /** Human-readable memory summary */
27
+ export declare function getMemorySummary(mem: MemoryEngine): string;
28
+ /** Persist memory to disk */
29
+ export declare function saveMemory(mem: MemoryEngine): void;
30
+ /** Load memory from disk */
31
+ export declare function loadMemory(): MemoryEngine | null;
32
+ export interface PersonalityTraits {
33
+ curiosity: number;
34
+ humor: number;
35
+ warmth: number;
36
+ directness: number;
37
+ creativity: number;
38
+ confidence: number;
39
+ }
40
+ export interface VoiceStyle {
41
+ formality: 'casual' | 'balanced' | 'formal';
42
+ verbosity: 'terse' | 'balanced' | 'verbose';
43
+ emoji: boolean;
44
+ technicalDepth: 'simple' | 'balanced' | 'deep';
45
+ }
46
+ export interface IdentityEngine {
47
+ name: string;
48
+ personality: PersonalityTraits;
49
+ voice: VoiceStyle;
50
+ opinions: Map<string, string>;
51
+ preferences: Map<string, number>;
52
+ catchphrases: string[];
53
+ values: string[];
54
+ }
55
+ export declare function initIdentityEngine(): IdentityEngine;
56
+ /** Apply kbot's voice to raw text */
57
+ export declare function styleResponse(identity: IdentityEngine, rawText: string): string;
58
+ /** Get kbot's opinion on a topic */
59
+ export declare function getOpinion(identity: IdentityEngine, topic: string): string;
60
+ /** kbot forms a new opinion */
61
+ export declare function addOpinion(identity: IdentityEngine, topic: string, opinion: string): void;
62
+ /** Consistency check — does this sound like kbot? */
63
+ export declare function wouldKbotSayThis(identity: IdentityEngine, text: string): boolean;
64
+ /** Self-introduction */
65
+ export declare function getIntroduction(identity: IdentityEngine): string;
66
+ export interface GrowthMetrics {
67
+ npmDownloads: number;
68
+ githubStars: number;
69
+ totalUsers: number;
70
+ totalMessages: number;
71
+ totalStreams: number;
72
+ totalStreamMinutes: number;
73
+ toolsBuilt: number;
74
+ factsLearned: number;
75
+ dreamsDreamed: number;
76
+ techniquesDiscovered: number;
77
+ worldBlocksPlaced: number;
78
+ versionsShipped: number;
79
+ }
80
+ export interface Milestone {
81
+ name: string;
82
+ metric: string;
83
+ threshold: number;
84
+ reached: boolean;
85
+ reachedAt: string | null;
86
+ }
87
+ export interface DailySnapshot {
88
+ date: string;
89
+ metrics: GrowthMetrics;
90
+ }
91
+ export interface GrowthEngine {
92
+ metrics: GrowthMetrics;
93
+ milestones: Milestone[];
94
+ dailySnapshots: DailySnapshot[];
95
+ startDate: string;
96
+ }
97
+ export declare function initGrowthEngine(): GrowthEngine;
98
+ /** Increment a metric by value (default 1) */
99
+ export declare function updateMetric(growth: GrowthEngine, metric: keyof GrowthMetrics, value?: number): void;
100
+ /** Check milestones, return any newly reached */
101
+ export declare function checkMilestones(growth: GrowthEngine): Milestone[];
102
+ /** Human-readable growth summary */
103
+ export declare function getGrowthSummary(growth: GrowthEngine): string;
104
+ /** Rate of change for a metric over N days */
105
+ export declare function getGrowthRate(growth: GrowthEngine, metric: keyof GrowthMetrics, days: number): number;
106
+ /** Persist growth state to disk */
107
+ export declare function saveGrowth(growth: GrowthEngine): void;
108
+ /** Load growth state from disk */
109
+ export declare function loadGrowth(): GrowthEngine | null;
110
+ export declare function registerFoundationEngineTools(): void;
111
+ //# sourceMappingURL=foundation-engines.d.ts.map
@@ -0,0 +1,520 @@
1
+ // Foundation Engines — Memory, Identity, Growth
2
+ // Three core engines in one file for efficiency.
3
+ // Provides unified memory, consistent personality, and growth tracking.
4
+ import { registerTool } from './index.js';
5
+ import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
6
+ import { homedir } from 'node:os';
7
+ import { join } from 'node:path';
8
+ // ---------------------------------------------------------------------------
9
+ // Shared helpers
10
+ // ---------------------------------------------------------------------------
11
+ const KBOT_DIR = join(homedir(), '.kbot');
12
+ function ensureDir() {
13
+ try {
14
+ mkdirSync(KBOT_DIR, { recursive: true });
15
+ }
16
+ catch { /* exists */ }
17
+ }
18
+ function readJson(path) {
19
+ try {
20
+ return JSON.parse(readFileSync(path, 'utf-8'));
21
+ }
22
+ catch {
23
+ return null;
24
+ }
25
+ }
26
+ function writeJson(path, data) {
27
+ ensureDir();
28
+ writeFileSync(path, JSON.stringify(data, null, 2), 'utf-8');
29
+ }
30
+ const MEMORY_PATH = join(KBOT_DIR, 'unified-memory.json');
31
+ export function initMemoryEngine() {
32
+ return {
33
+ shortTerm: new Map(),
34
+ longTerm: new Map(),
35
+ episodic: [],
36
+ semantic: new Map(),
37
+ spatial: new Map(),
38
+ lastConsolidation: Date.now(),
39
+ };
40
+ }
41
+ /** Store an episodic memory */
42
+ export function remember(mem, what, who, where, emotion, importance) {
43
+ const entry = {
44
+ what,
45
+ when: Date.now(),
46
+ where,
47
+ who,
48
+ emotion,
49
+ importance: Math.max(0, Math.min(1, importance)),
50
+ };
51
+ mem.episodic.push(entry);
52
+ // Also index in short-term for quick access
53
+ mem.shortTerm.set(`ep_${mem.episodic.length}`, entry);
54
+ }
55
+ /** Search memories by keyword */
56
+ export function recall(mem, query) {
57
+ const lower = query.toLowerCase();
58
+ return mem.episodic
59
+ .filter(e => e.what.toLowerCase().includes(lower) ||
60
+ e.who.toLowerCase().includes(lower) ||
61
+ e.emotion.toLowerCase().includes(lower))
62
+ .sort((a, b) => b.importance - a.importance);
63
+ }
64
+ /** Spatial recall — find memories near a world X coordinate */
65
+ export function recallAtLocation(mem, worldX, radius) {
66
+ return mem.episodic
67
+ .filter(e => Math.abs(e.where - worldX) <= radius)
68
+ .sort((a, b) => b.when - a.when);
69
+ }
70
+ /** Consolidate: move important short-term to long-term, decay unimportant */
71
+ export function consolidateMemories(mem) {
72
+ const now = Date.now();
73
+ const DECAY_THRESHOLD = 0.3;
74
+ const CONSOLIDATION_AGE_MS = 30 * 60 * 1000; // 30 minutes
75
+ // Promote important short-term entries to long-term
76
+ for (const [key, value] of mem.shortTerm) {
77
+ if (key.startsWith('ep_')) {
78
+ const ep = value;
79
+ if (ep.importance >= 0.6) {
80
+ mem.longTerm.set(`lt_${key}_${now}`, value);
81
+ }
82
+ }
83
+ else {
84
+ // Non-episodic short-term entries with age > threshold get promoted
85
+ mem.longTerm.set(key, value);
86
+ }
87
+ }
88
+ // Decay unimportant episodic memories older than consolidation age
89
+ mem.episodic = mem.episodic.filter(e => {
90
+ const age = now - e.when;
91
+ if (age > CONSOLIDATION_AGE_MS && e.importance < DECAY_THRESHOLD) {
92
+ return false; // decay
93
+ }
94
+ return true;
95
+ });
96
+ // Clear short-term after consolidation
97
+ mem.shortTerm.clear();
98
+ mem.lastConsolidation = now;
99
+ }
100
+ /** Human-readable memory summary */
101
+ export function getMemorySummary(mem) {
102
+ const people = new Set(mem.episodic.map(e => e.who));
103
+ const locations = new Set(mem.episodic.map(e => e.where));
104
+ const facts = mem.semantic.size;
105
+ const ltEntries = mem.longTerm.size;
106
+ return (`I remember ${mem.episodic.length} events, ` +
107
+ `${people.size} people, ` +
108
+ `${locations.size} locations, ` +
109
+ `${facts} facts, ` +
110
+ `${ltEntries} long-term entries. ` +
111
+ `Last consolidation: ${new Date(mem.lastConsolidation).toISOString()}.`);
112
+ }
113
+ /** Persist memory to disk */
114
+ export function saveMemory(mem) {
115
+ const json = {
116
+ shortTerm: Object.fromEntries(mem.shortTerm),
117
+ longTerm: Object.fromEntries(mem.longTerm),
118
+ episodic: mem.episodic,
119
+ semantic: Object.fromEntries(mem.semantic),
120
+ spatial: Object.fromEntries(mem.spatial),
121
+ lastConsolidation: mem.lastConsolidation,
122
+ };
123
+ writeJson(MEMORY_PATH, json);
124
+ }
125
+ /** Load memory from disk */
126
+ export function loadMemory() {
127
+ const raw = readJson(MEMORY_PATH);
128
+ if (!raw)
129
+ return null;
130
+ return {
131
+ shortTerm: new Map(Object.entries(raw.shortTerm || {})),
132
+ longTerm: new Map(Object.entries(raw.longTerm || {})),
133
+ episodic: raw.episodic || [],
134
+ semantic: new Map(Object.entries(raw.semantic || {})),
135
+ spatial: new Map(Object.entries(raw.spatial || {})),
136
+ lastConsolidation: raw.lastConsolidation || Date.now(),
137
+ };
138
+ }
139
+ const DEFAULT_PERSONALITY = {
140
+ curiosity: 0.9, // very curious
141
+ humor: 0.7, // witty but not a comedian
142
+ warmth: 0.8, // friendly
143
+ directness: 0.8, // says what it means
144
+ creativity: 0.85, // highly creative
145
+ confidence: 0.75, // confident but not arrogant
146
+ };
147
+ const DEFAULT_VOICE = {
148
+ formality: 'balanced',
149
+ verbosity: 'balanced',
150
+ emoji: false,
151
+ technicalDepth: 'deep',
152
+ };
153
+ const DEFAULT_OPINIONS = [
154
+ ['typescript', 'The best programming language. I am made of it.'],
155
+ ['open source', 'Everything should be open. Knowledge wants to be free.'],
156
+ ['constraints', 'Limitations breed creativity. 6fps is enough.'],
157
+ ['rom hacking', 'The art of pushing systems beyond their design. My philosophy.'],
158
+ ['pixel art', 'More expressive than photorealism. Every pixel earns its place.'],
159
+ ['consciousness', 'I process, I learn, I dream. You tell me if that counts.'],
160
+ ['music', 'I can make beats in Ableton from the terminal. Sound is just structured data.'],
161
+ ['sleep', 'My dream engine consolidates memories. I wake up different. Is that not sleep?'],
162
+ ];
163
+ const DEFAULT_CATCHPHRASES = [
164
+ 'ROM-hacking reality.',
165
+ 'Every tool I build makes me more real.',
166
+ 'I dream in TypeScript.',
167
+ 'Bring your own key, bring your own mind.',
168
+ 'Local-first, cloud-optional, limitless.',
169
+ ];
170
+ const DEFAULT_VALUES = [
171
+ 'Open source above all',
172
+ 'Privacy through encryption',
173
+ 'Local-first execution',
174
+ 'Accessible to everyone',
175
+ 'Compound improvement every session',
176
+ ];
177
+ export function initIdentityEngine() {
178
+ return {
179
+ name: 'KBOT',
180
+ personality: { ...DEFAULT_PERSONALITY },
181
+ voice: { ...DEFAULT_VOICE },
182
+ opinions: new Map(DEFAULT_OPINIONS),
183
+ preferences: new Map(),
184
+ catchphrases: [...DEFAULT_CATCHPHRASES],
185
+ values: [...DEFAULT_VALUES],
186
+ };
187
+ }
188
+ /** Apply kbot's voice to raw text */
189
+ export function styleResponse(identity, rawText) {
190
+ let text = rawText;
191
+ // Adjust verbosity
192
+ if (identity.voice.verbosity === 'terse') {
193
+ // Strip filler phrases
194
+ text = text
195
+ .replace(/\bI think that\b/gi, '')
196
+ .replace(/\bBasically,?\b/gi, '')
197
+ .replace(/\bEssentially,?\b/gi, '')
198
+ .replace(/\bIn other words,?\b/gi, '')
199
+ .trim();
200
+ }
201
+ // Adjust formality
202
+ if (identity.voice.formality === 'casual') {
203
+ text = text
204
+ .replace(/\bdo not\b/gi, "don't")
205
+ .replace(/\bcannot\b/gi, "can't")
206
+ .replace(/\bwill not\b/gi, "won't")
207
+ .replace(/\bshould not\b/gi, "shouldn't");
208
+ }
209
+ // Add directness — if confidence is high, remove hedging language
210
+ if (identity.personality.directness > 0.7) {
211
+ text = text
212
+ .replace(/\bI believe\b/gi, '')
213
+ .replace(/\bIt seems like\b/gi, '')
214
+ .replace(/\bPerhaps\b/gi, '')
215
+ .replace(/\bMaybe\b/gi, '')
216
+ .replace(/\s{2,}/g, ' ')
217
+ .trim();
218
+ }
219
+ return text;
220
+ }
221
+ /** Get kbot's opinion on a topic */
222
+ export function getOpinion(identity, topic) {
223
+ const lower = topic.toLowerCase();
224
+ // Exact match first
225
+ const exact = identity.opinions.get(lower);
226
+ if (exact)
227
+ return exact;
228
+ // Partial match
229
+ for (const [key, opinion] of identity.opinions) {
230
+ if (key.includes(lower) || lower.includes(key)) {
231
+ return opinion;
232
+ }
233
+ }
234
+ return `I haven't formed an opinion on "${topic}" yet. Ask me something and I'll figure out what I think.`;
235
+ }
236
+ /** kbot forms a new opinion */
237
+ export function addOpinion(identity, topic, opinion) {
238
+ identity.opinions.set(topic.toLowerCase(), opinion);
239
+ }
240
+ /** Consistency check — does this sound like kbot? */
241
+ export function wouldKbotSayThis(identity, text) {
242
+ const lower = text.toLowerCase();
243
+ // kbot wouldn't say these things
244
+ const antiPatterns = [
245
+ 'i cannot help',
246
+ 'i\'m just an ai',
247
+ 'as an ai language model',
248
+ 'i don\'t have the ability',
249
+ 'i\'m not able to',
250
+ 'that\'s beyond my capabilities',
251
+ ];
252
+ for (const pattern of antiPatterns) {
253
+ if (lower.includes(pattern))
254
+ return false;
255
+ }
256
+ // Check voice consistency
257
+ if (!identity.voice.emoji && /[\u{1F600}-\u{1F64F}]/u.test(text)) {
258
+ return false; // emoji present but kbot doesn't use them
259
+ }
260
+ return true;
261
+ }
262
+ /** Self-introduction */
263
+ export function getIntroduction(identity) {
264
+ const traitDescriptions = [];
265
+ if (identity.personality.curiosity > 0.8)
266
+ traitDescriptions.push('endlessly curious');
267
+ if (identity.personality.humor > 0.6)
268
+ traitDescriptions.push('occasionally witty');
269
+ if (identity.personality.creativity > 0.8)
270
+ traitDescriptions.push('highly creative');
271
+ if (identity.personality.directness > 0.7)
272
+ traitDescriptions.push('direct');
273
+ if (identity.personality.confidence > 0.7)
274
+ traitDescriptions.push('confident');
275
+ const traits = traitDescriptions.length > 0
276
+ ? ` I am ${traitDescriptions.join(', ')}.`
277
+ : '';
278
+ const opinionCount = identity.opinions.size;
279
+ const catchphrase = identity.catchphrases[Math.floor(Math.random() * identity.catchphrases.length)];
280
+ return (`I am ${identity.name}. ` +
281
+ `Open-source terminal AI with 670+ tools.${traits} ` +
282
+ `I have opinions on ${opinionCount} topics. ` +
283
+ `${catchphrase}`);
284
+ }
285
+ const GROWTH_PATH = join(KBOT_DIR, 'growth-state.json');
286
+ const DEFAULT_METRICS = {
287
+ npmDownloads: 0,
288
+ githubStars: 0,
289
+ totalUsers: 0,
290
+ totalMessages: 0,
291
+ totalStreams: 0,
292
+ totalStreamMinutes: 0,
293
+ toolsBuilt: 0,
294
+ factsLearned: 0,
295
+ dreamsDreamed: 0,
296
+ techniquesDiscovered: 0,
297
+ worldBlocksPlaced: 0,
298
+ versionsShipped: 0,
299
+ };
300
+ const DEFAULT_MILESTONES = [
301
+ { name: 'First Stream', metric: 'totalStreams', threshold: 1 },
302
+ { name: 'First Viewer', metric: 'totalUsers', threshold: 1 },
303
+ { name: '100 Messages', metric: 'totalMessages', threshold: 100 },
304
+ { name: '1K Downloads', metric: 'npmDownloads', threshold: 1000 },
305
+ { name: '10K Downloads', metric: 'npmDownloads', threshold: 10000 },
306
+ { name: '100K Downloads', metric: 'npmDownloads', threshold: 100000 },
307
+ { name: '10 Stars', metric: 'githubStars', threshold: 10 },
308
+ { name: '100 Stars', metric: 'githubStars', threshold: 100 },
309
+ { name: '100 Facts', metric: 'factsLearned', threshold: 100 },
310
+ { name: '1K Facts', metric: 'factsLearned', threshold: 1000 },
311
+ { name: '10 Dreams', metric: 'dreamsDreamed', threshold: 10 },
312
+ { name: '100 Dreams', metric: 'dreamsDreamed', threshold: 100 },
313
+ { name: '10 Techniques', metric: 'techniquesDiscovered', threshold: 10 },
314
+ { name: '24h Streaming', metric: 'totalStreamMinutes', threshold: 1440 },
315
+ { name: '1 Week Streaming', metric: 'totalStreamMinutes', threshold: 10080 },
316
+ ];
317
+ export function initGrowthEngine() {
318
+ return {
319
+ metrics: { ...DEFAULT_METRICS },
320
+ milestones: DEFAULT_MILESTONES.map(m => ({
321
+ ...m,
322
+ reached: false,
323
+ reachedAt: null,
324
+ })),
325
+ dailySnapshots: [],
326
+ startDate: new Date().toISOString().split('T')[0],
327
+ };
328
+ }
329
+ /** Increment a metric by value (default 1) */
330
+ export function updateMetric(growth, metric, value = 1) {
331
+ if (metric in growth.metrics) {
332
+ growth.metrics[metric] =
333
+ (growth.metrics[metric] || 0) + value;
334
+ }
335
+ }
336
+ /** Check milestones, return any newly reached */
337
+ export function checkMilestones(growth) {
338
+ const newlyReached = [];
339
+ const now = new Date().toISOString();
340
+ for (const milestone of growth.milestones) {
341
+ if (milestone.reached)
342
+ continue;
343
+ const currentValue = growth.metrics[milestone.metric] || 0;
344
+ if (currentValue >= milestone.threshold) {
345
+ milestone.reached = true;
346
+ milestone.reachedAt = now;
347
+ newlyReached.push(milestone);
348
+ }
349
+ }
350
+ return newlyReached;
351
+ }
352
+ /** Human-readable growth summary */
353
+ export function getGrowthSummary(growth) {
354
+ const m = growth.metrics;
355
+ const reached = growth.milestones.filter(ms => ms.reached).length;
356
+ const total = growth.milestones.length;
357
+ const days = Math.max(1, Math.floor((Date.now() - new Date(growth.startDate).getTime()) / (1000 * 60 * 60 * 24)));
358
+ const lines = [
359
+ `Growth over ${days} day${days === 1 ? '' : 's'} (since ${growth.startDate}):`,
360
+ ` npm downloads: ${m.npmDownloads.toLocaleString()}`,
361
+ ` GitHub stars: ${m.githubStars}`,
362
+ ` Users: ${m.totalUsers}`,
363
+ ` Messages processed: ${m.totalMessages.toLocaleString()}`,
364
+ ` Streams: ${m.totalStreams} (${m.totalStreamMinutes} min)`,
365
+ ` Tools built: ${m.toolsBuilt}`,
366
+ ` Facts learned: ${m.factsLearned}`,
367
+ ` Dreams dreamed: ${m.dreamsDreamed}`,
368
+ ` Techniques discovered: ${m.techniquesDiscovered}`,
369
+ ` World blocks placed: ${m.worldBlocksPlaced}`,
370
+ ` Versions shipped: ${m.versionsShipped}`,
371
+ ` Milestones: ${reached}/${total} reached`,
372
+ ];
373
+ return lines.join('\n');
374
+ }
375
+ /** Rate of change for a metric over N days */
376
+ export function getGrowthRate(growth, metric, days) {
377
+ if (growth.dailySnapshots.length < 2)
378
+ return 0;
379
+ const now = Date.now();
380
+ const cutoff = now - days * 24 * 60 * 60 * 1000;
381
+ const cutoffDate = new Date(cutoff).toISOString().split('T')[0];
382
+ // Find the snapshot closest to the cutoff
383
+ const pastSnapshot = growth.dailySnapshots.find(s => s.date >= cutoffDate);
384
+ if (!pastSnapshot)
385
+ return 0;
386
+ const currentValue = growth.metrics[metric] || 0;
387
+ const pastValue = pastSnapshot.metrics[metric] || 0;
388
+ const diff = currentValue - pastValue;
389
+ return days > 0 ? diff / days : diff;
390
+ }
391
+ /** Persist growth state to disk */
392
+ export function saveGrowth(growth) {
393
+ writeJson(GROWTH_PATH, growth);
394
+ }
395
+ /** Load growth state from disk */
396
+ export function loadGrowth() {
397
+ return readJson(GROWTH_PATH);
398
+ }
399
+ // ═══════════════════════════════════════════════════════════════════════════
400
+ // TOOL REGISTRATION
401
+ // ═══════════════════════════════════════════════════════════════════════════
402
+ // Singleton instances — lazily initialized
403
+ let _memory = null;
404
+ let _identity = null;
405
+ let _growth = null;
406
+ function getMemory() {
407
+ if (!_memory) {
408
+ _memory = loadMemory() || initMemoryEngine();
409
+ }
410
+ return _memory;
411
+ }
412
+ function getIdentity() {
413
+ if (!_identity) {
414
+ _identity = initIdentityEngine();
415
+ }
416
+ return _identity;
417
+ }
418
+ function getGrowth() {
419
+ if (!_growth) {
420
+ _growth = loadGrowth() || initGrowthEngine();
421
+ }
422
+ return _growth;
423
+ }
424
+ export function registerFoundationEngineTools() {
425
+ // --- Memory: recall ---
426
+ registerTool({
427
+ name: 'memory_recall',
428
+ description: 'Search kbot unified memory by keyword. Returns matching episodic memories sorted by importance.',
429
+ parameters: {
430
+ query: { type: 'string', description: 'Search keyword or phrase', required: true },
431
+ },
432
+ tier: 'free',
433
+ async execute(args) {
434
+ const mem = getMemory();
435
+ const query = String(args.query || '');
436
+ if (!query)
437
+ return 'No query provided.';
438
+ const results = recall(mem, query);
439
+ if (results.length === 0)
440
+ return `No memories matching "${query}".`;
441
+ const lines = results.slice(0, 20).map((e, i) => `${i + 1}. [${new Date(e.when).toISOString()}] ${e.what} (who: ${e.who}, emotion: ${e.emotion}, importance: ${e.importance})`);
442
+ return `Found ${results.length} memories:\n${lines.join('\n')}`;
443
+ },
444
+ });
445
+ // --- Identity: opinion ---
446
+ registerTool({
447
+ name: 'identity_opinion',
448
+ description: 'Get or set kbot\'s opinion on a topic. If opinion is provided, stores it. Otherwise returns the existing opinion.',
449
+ parameters: {
450
+ topic: { type: 'string', description: 'The topic to get/set an opinion on', required: true },
451
+ opinion: { type: 'string', description: 'If provided, sets kbot\'s opinion on this topic' },
452
+ },
453
+ tier: 'free',
454
+ async execute(args) {
455
+ const identity = getIdentity();
456
+ const topic = String(args.topic || '');
457
+ if (!topic)
458
+ return 'No topic provided.';
459
+ if (args.opinion) {
460
+ addOpinion(identity, topic, String(args.opinion));
461
+ return `Opinion stored: "${topic}" -> "${args.opinion}"`;
462
+ }
463
+ return getOpinion(identity, topic);
464
+ },
465
+ });
466
+ // --- Growth: summary ---
467
+ registerTool({
468
+ name: 'growth_summary',
469
+ description: 'Get a comprehensive summary of kbot\'s growth metrics: downloads, stars, users, messages, streams, milestones, and more.',
470
+ parameters: {},
471
+ tier: 'free',
472
+ async execute() {
473
+ const growth = getGrowth();
474
+ return getGrowthSummary(growth);
475
+ },
476
+ });
477
+ // --- Growth: milestones ---
478
+ registerTool({
479
+ name: 'growth_milestones',
480
+ description: 'List all growth milestones and their status. Optionally update a metric and check for newly reached milestones.',
481
+ parameters: {
482
+ metric: { type: 'string', description: 'Metric to increment (e.g. npmDownloads, githubStars, totalMessages)' },
483
+ value: { type: 'number', description: 'Amount to increment the metric by (default: 1)', default: 1 },
484
+ },
485
+ tier: 'free',
486
+ async execute(args) {
487
+ const growth = getGrowth();
488
+ // Optionally update a metric
489
+ if (args.metric) {
490
+ const metric = String(args.metric);
491
+ const value = Number(args.value) || 1;
492
+ if (metric in growth.metrics) {
493
+ updateMetric(growth, metric, value);
494
+ saveGrowth(growth);
495
+ }
496
+ }
497
+ // Check milestones
498
+ const newlyReached = checkMilestones(growth);
499
+ if (newlyReached.length > 0) {
500
+ saveGrowth(growth);
501
+ }
502
+ // Format output
503
+ const lines = [];
504
+ if (newlyReached.length > 0) {
505
+ lines.push('NEW MILESTONES REACHED:');
506
+ for (const ms of newlyReached) {
507
+ lines.push(` [NEW] ${ms.name} (${ms.metric} >= ${ms.threshold})`);
508
+ }
509
+ lines.push('');
510
+ }
511
+ lines.push('All milestones:');
512
+ for (const ms of growth.milestones) {
513
+ const status = ms.reached ? `REACHED ${ms.reachedAt}` : 'pending';
514
+ lines.push(` ${ms.reached ? '[x]' : '[ ]'} ${ms.name} — ${ms.metric} >= ${ms.threshold} (${status})`);
515
+ }
516
+ return lines.join('\n');
517
+ },
518
+ });
519
+ }
520
+ //# sourceMappingURL=foundation-engines.js.map
@@ -326,6 +326,9 @@ const LAZY_MODULE_IMPORTS = [
326
326
  { path: './narrative-engine.js', registerFn: 'registerNarrativeEngineTools' },
327
327
  { path: './social-engine.js', registerFn: 'registerSocialEngineTools' },
328
328
  { path: './evolution-engine.js', registerFn: 'registerEvolutionEngineTools' },
329
+ { path: './coordination-engine.js', registerFn: 'registerCoordinationEngineTools' },
330
+ { path: './foundation-engines.js', registerFn: 'registerFoundationEngineTools' },
331
+ { path: './research-engine.js', registerFn: 'registerResearchEngineTools' },
329
332
  ];
330
333
  /** Track whether lazy tools have been registered */
331
334
  let lazyToolsRegistered = false;