@kernel.chat/kbot 3.14.0 → 3.14.5

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,542 @@
1
+ `` `typescript
2
+ import { Tool, ToolResult, z } from 'zod';
3
+ import * as fs from 'fs';
4
+ import * as path from 'path';
5
+
6
+ export interface MemoryEntry {
7
+ id: string;
8
+ timestamp: number;
9
+ type: 'user_message' | 'agent_response' | 'tool_call' | 'tool_result' | 'thought';
10
+ content: string;
11
+ context: {
12
+ sessionId: string;
13
+ userId: string;
14
+ userIntent?: string;
15
+ reasoning?: string;
16
+ };
17
+ tags: string[];
18
+ embeddingVector?: number[];
19
+ relevanceScore?: number;
20
+ }
21
+
22
+ export interface MemoryGraph {
23
+ nodes: Map<string, MemoryEntry>;
24
+ edges: Map<string, Set<string>>;
25
+ }
26
+
27
+ export interface MemoryQuery {
28
+ query: string;
29
+ filters?: {
30
+ type?: string | { $in: string[] };
31
+ tags?: string[];
32
+ timeframe?: {
33
+ from?: Date;
34
+ to?: Date;
35
+ };
36
+ relatedToId?: string;
37
+ };
38
+ limit?: number;
39
+ maxDepth?: number;
40
+ }
41
+
42
+ export class MemoryHarness {
43
+ private memoryPath: string;
44
+ private memoryGraph: MemoryGraph;
45
+ private sessionId: string;
46
+ private userId: string;
47
+ private embeddingModel: (text: string) => Promise<number[]>;
48
+ private vectorStore: Map<string, number[]>;
49
+
50
+ constructor(
51
+ memoryPath: string = './.kbot/memory',
52
+ embeddingModel: (text: string) => Promise<number[]>
53
+ ) {
54
+ this.memoryPath = memoryPath;
55
+ this.embeddingModel = embeddingModel;
56
+ this.vectorStore = new Map();
57
+ this.memoryGraph = { nodes: new Map(), edges: new Map() };
58
+ this.sessionId = Date.now().toString();
59
+ this.ensureDirectory();
60
+ this.loadMemory();
61
+ }
62
+
63
+ private ensureDirectory(): void {
64
+ if (!fs.existsSync(this.memoryPath)) {
65
+ fs.mkdirSync(this.memoryPath, { recursive: true });
66
+ }
67
+ }
68
+
69
+ private async generateEmbedding(text: string): Promise<number[]> {
70
+ const encoded = encodeURIComponent(text);
71
+ // Simple placeholder - in production, use a real embedding model
72
+ // This simulates vector generation for demonstration
73
+ const hash = await this.hashString(encoded);
74
+ return Array.from({ length: 128 }, () =>
75
+ Math.abs(hash.charCodeAt(Math.floor(Math.random() * hash.length)) % 256)
76
+ );
77
+ }
78
+
79
+ private async hashString(str: string): Promise<string> {
80
+ const buffer = Buffer.from(str, 'utf-8');
81
+ const hash = await crypto.subtle.digest('SHA-256', buffer);
82
+ return Array.from(new Uint8Array(hash)).map(b => b.toString(16).padStart(2, '0')).join('');
83
+ }
84
+
85
+ private generateId(): string {
86
+ return `;
87
+ $;
88
+ {
89
+ this.sessionId;
90
+ }
91
+ _$;
92
+ {
93
+ Date.now();
94
+ }
95
+ _$;
96
+ {
97
+ Math.random().toString(36).substring(7);
98
+ }
99
+ `;
100
+ }
101
+
102
+ private extractTags(content: string, context: MemoryEntry['context']): string[] {
103
+ const tags = ['agent', 'memory'];
104
+
105
+ if (context.userIntent) {
106
+ tags.push('intent');
107
+ }
108
+
109
+ if (context.reasoning) {
110
+ tags.push('reasoning');
111
+ }
112
+
113
+ if (content.match(/tool_call|tool_result/i)) {
114
+ tags.push('execution');
115
+ }
116
+
117
+ if (content.match(/thought/i)) {
118
+ tags.push('thought');
119
+ }
120
+
121
+ // Extract keywords
122
+ const keywords = this.extractKeywords(content, context);
123
+ tags.push(...keywords.slice(0, 3));
124
+
125
+ return tags;
126
+ }
127
+
128
+ private extractKeywords(content: string, context: MemoryEntry['context']): string[] {
129
+ const words = content.match(/\b[a-z]{4,}\b/gi)?.map(w => w.toLowerCase()) || [];
130
+ const stopWords = new Set([
131
+ 'the', 'is', 'are', 'was', 'were', 'be', 'been', 'being',
132
+ 'and', 'or', 'but', 'if', 'then', 'else', 'when', 'while',
133
+ 'of', 'at', 'by', 'for', 'with', 'about', 'against',
134
+ 'after', 'before', 'between', 'through', 'during',
135
+ 'into', 'onto', 'upon', 'over', 'under', 'above',
136
+ 'from', 'up', 'down', 'in', 'on', 'off', 'out',
137
+ 'to', 'via', 'per', 'as', 'by',
138
+ 'i', 'you', 'we', 'they', 'he', 'she', 'it',
139
+ 'a', 'an', 'the',
140
+ 'this', 'that', 'these', 'those',
141
+ 'what', 'which', 'who', 'whom', 'whose',
142
+ 'where', 'when', 'why', 'how',
143
+ 'yes', 'no', 'maybe', 'perhaps',
144
+ 'ok', 'okay', 'right', 'sure', 'certainly',
145
+ 'please', 'thank', 'thanks', 'sorry',
146
+ 'i', 'me', 'my', 'mine',
147
+ 'you', 'your', 'yours',
148
+ 'he', 'him', 'his', 'herself', 'himself', 'itself',
149
+ 'she', 'her', 'herself',
150
+ 'they', 'them', 'their', 'theirs', 'themselves'
151
+ ]);
152
+
153
+ return words
154
+ .filter(w => !stopWords.has(w) && w.length > 3)
155
+ .slice(0, 5);
156
+ }
157
+
158
+ async saveEntry(entry: MemoryEntry): Promise<void> {
159
+ entry.id = this.generateId();
160
+ entry.timestamp = Date.now();
161
+ entry.context.sessionId = this.sessionId;
162
+ entry.context.userId = this.userId;
163
+
164
+ // Extract tags from content and context
165
+ entry.tags = this.extractTags(entry.content, entry.context);
166
+
167
+ // Generate embedding if not provided
168
+ if (!entry.embeddingVector) {
169
+ entry.embeddingVector = await this.generateEmbedding(entry.content);
170
+ }
171
+
172
+ this.memoryGraph.nodes.set(entry.id, entry);
173
+ this.vectorStore.set(entry.id, entry.embeddingVector!);
174
+
175
+ // Save to persistent storage
176
+ this.saveToDisk();
177
+ }
178
+
179
+ private async saveToDisk(): Promise<void> {
180
+ const data = JSON.stringify({
181
+ sessionId: this.sessionId,
182
+ userId: this.userId,
183
+ timestamp: Date.now(),
184
+ graph: {
185
+ nodes: Array.from(this.memoryGraph.nodes.entries()),
186
+ edges: Array.from(this.memoryGraph.edges.entries()).map(([from, to]) => ({ from, to }))
187
+ }
188
+ });
189
+
190
+ const filePath = path.join(this.memoryPath, `;
191
+ session_$;
192
+ {
193
+ this.sessionId;
194
+ }
195
+ json `);
196
+ fs.writeFileSync(filePath, data);
197
+ }
198
+
199
+ async loadMemory(): Promise<void> {
200
+ const files = fs.readdirSync(this.memoryPath)
201
+ .filter(f => f.endsWith('.json'));
202
+
203
+ for (const file of files) {
204
+ const filePath = path.join(this.memoryPath, file);
205
+ const data = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
206
+
207
+ this.sessionId = data.sessionId;
208
+ this.userId = data.userId;
209
+
210
+ for (const [id, entry] of data.graph.nodes) {
211
+ this.memoryGraph.nodes.set(id, entry as MemoryEntry);
212
+ this.vectorStore.set(id, entry.embeddingVector || []);
213
+ }
214
+
215
+ for (const edge of data.graph.edges) {
216
+ this.memoryGraph.edges.get(edge.from) = this.memoryGraph.edges.get(edge.from) || new Set();
217
+ this.memoryGraph.edges.get(edge.from)!.add(edge.to);
218
+ }
219
+ }
220
+ }
221
+
222
+ async addEntry(content: string, type: MemoryEntry['type'], context: MemoryEntry['context']): Promise<string> {
223
+ const entry: MemoryEntry = {
224
+ id: '',
225
+ timestamp: Date.now(),
226
+ type,
227
+ content,
228
+ context,
229
+ tags: [],
230
+ embeddingVector: [],
231
+ relevanceScore: 0
232
+ };
233
+
234
+ await this.saveEntry(entry);
235
+ return entry.id;
236
+ }
237
+
238
+ async relatedToId(targetId: string, limit: number = 5): Promise<MemoryEntry[]> {
239
+ const targetEntry = this.memoryGraph.nodes.get(targetId);
240
+ if (!targetEntry) return [];
241
+
242
+ const relatedIds = new Set<string>();
243
+ const visited = new Set<string>();
244
+ const queue: string[] = [targetId];
245
+ const depth = 2; // Limit to 2 hops
246
+
247
+ let currentDepth = 0;
248
+ while (currentDepth < depth && queue.length > 0) {
249
+ const currentQueue = [...queue];
250
+ queue.length = 0;
251
+
252
+ for (const nodeId of currentQueue) {
253
+ if (visited.has(nodeId)) continue;
254
+ visited.add(nodeId);
255
+ relatedIds.add(nodeId);
256
+
257
+ const neighbors = this.memoryGraph.edges.get(nodeId);
258
+ if (neighbors) {
259
+ for (const neighborId of neighbors) {
260
+ if (!visited.has(neighborId)) {
261
+ queue.push(neighborId);
262
+ }
263
+ }
264
+ }
265
+ }
266
+
267
+ currentDepth++;
268
+ }
269
+
270
+ const relatedEntries = Array.from(relatedIds)
271
+ .map(id => this.memoryGraph.nodes.get(id))
272
+ .filter((entry): entry is MemoryEntry => entry !== undefined)
273
+ .slice(0, limit);
274
+
275
+ return relatedEntries;
276
+ }
277
+
278
+ async queryMemory(query: MemoryQuery): Promise<MemoryEntry[]> {
279
+ const { query: queryText, filters, limit = 10, maxDepth = 3 } = query;
280
+
281
+ // Semantic similarity search
282
+ const queryEmbedding = await this.generateEmbedding(queryText);
283
+
284
+ // Calculate relevance scores for all entries
285
+ const scoredEntries = Array.from(this.memoryGraph.nodes.entries())
286
+ .map(([id, entry]) => {
287
+ let similarity = 0;
288
+
289
+ // Text similarity (simple keyword overlap)
290
+ if (entry.content) {
291
+ const queryWords = new Set(queryText.toLowerCase().match(/\b[a-z]{3,}\b/g)?.map(w => w.toLowerCase()) || []);
292
+ const entryWords = new Set(entry.content.toLowerCase().match(/\b[a-z]{3,}\b/g) || []);
293
+ const intersection = Array.from(queryWords).filter(w => entryWords.has(w));
294
+ similarity += Math.min(intersection.length / queryWords.size, 1) * 0.4;
295
+ }
296
+
297
+ // Tag matching
298
+ if (filters?.tags && filters.tags.length > 0) {
299
+ const tagMatch = filters.tags.filter(t => entry.tags.includes(t)).length;
300
+ similarity += Math.min(tagMatch / filters.tags.length, 1) * 0.3;
301
+ }
302
+
303
+ // Type filtering
304
+ if (filters?.type) {
305
+ if (typeof filters.type === 'string') {
306
+ if (entry.type !== filters.type) return null;
307
+ } else if (typeof filters.type === 'object' && '$in' in filters.type) {
308
+ if (!filters.type.$in.includes(entry.type)) return null;
309
+ }
310
+ }
311
+
312
+ // Timeframe filtering
313
+ if (filters?.timeframe) {
314
+ const now = Date.now();
315
+ const minTime = filters.timeframe.from ? filters.timeframe.from.getTime() : -Infinity;
316
+ const maxTime = filters.timeframe.to ? filters.timeframe.to.getTime() : Infinity;
317
+ if (entry.timestamp < minTime || entry.timestamp > maxTime) return null;
318
+ }
319
+
320
+ // Related to specific entry
321
+ if (filters?.relatedToId) {
322
+ const related = this.relatedToId(filters.relatedToId, 100);
323
+ if (related.length === 0) return null;
324
+ // Check if this entry or its ancestors are in related set
325
+ const isRelated = this.isAncestor(entry.id, new Set(related.map(r => r.id)));
326
+ if (!isRelated) return null;
327
+ }
328
+
329
+ return { id, entry, score: similarity };
330
+ })
331
+ .filter((result): result is { id: string; entry: MemoryEntry; score: number } => result !== null);
332
+
333
+ // Sort by score and limit results
334
+ scoredEntries.sort((a, b) => b.score - a.score);
335
+
336
+ // Expand results to include related entries up to maxDepth
337
+ const expandedResults = new Map<string, { id: string; entry: MemoryEntry; score: number }>();
338
+ const visited = new Set<string>();
339
+
340
+ const expand = (seedId: string, currentDepth: number) => {
341
+ if (currentDepth > maxDepth) return;
342
+
343
+ const seedEntry = this.memoryGraph.nodes.get(seedId);
344
+ if (!seedEntry) return;
345
+
346
+ const related = this.relatedToId(seedId, Infinity);
347
+ for (const entry of related) {
348
+ if (visited.has(entry.id)) continue;
349
+ visited.add(entry.id);
350
+ expandedResults.set(entry.id, { id: entry.id, entry, score: a.score * 0.9 });
351
+ expand(entry.id, currentDepth + 1);
352
+ }
353
+ };
354
+
355
+ for (const { id, entry, score } of scoredEntries) {
356
+ expand(id, 0);
357
+ }
358
+
359
+ const finalResults = Array.from(expandedResults.entries())
360
+ .map(([id, { entry, score }]) => ({ id, entry, score }))
361
+ .sort((a, b) => b.score - a.score)
362
+ .map(r => r.entry)
363
+ .slice(0, limit);
364
+
365
+ return finalResults;
366
+ }
367
+
368
+ private isAncestor(candidateId: string, visited: Set<string>): boolean {
369
+ if (visited.has(candidateId)) return false;
370
+ visited.add(candidateId);
371
+
372
+ const entry = this.memoryGraph.nodes.get(candidateId);
373
+ if (!entry) return false;
374
+
375
+ // Check if this entry has edges to any visited nodes
376
+ const neighbors = this.memoryGraph.edges.get(candidateId);
377
+ if (neighbors) {
378
+ for (const neighborId of neighbors) {
379
+ if (visited.has(neighborId)) {
380
+ return true;
381
+ }
382
+ }
383
+ }
384
+
385
+ return false;
386
+ }
387
+
388
+ async recall(
389
+ sessionId: string | null = null,
390
+ context: string = '',
391
+ limit: number = 5
392
+ ): Promise<MemoryEntry[]> {
393
+ const currentSession = sessionId || this.sessionId;
394
+
395
+ // Get all entries from current session
396
+ const sessionEntries = Array.from(this.memoryGraph.nodes.entries())
397
+ .filter(([_, entry]) => entry.context.sessionId === currentSession);
398
+
399
+ // Recursively find related entries across sessions
400
+ const allEntries: MemoryEntry[] = [];
401
+ const visited = new Set<string>();
402
+ const queue: string[] = sessionEntries.map(([id, _]) => id);
403
+
404
+ while (queue.length > 0 && allEntries.length < limit * 3) {
405
+ const currentId = queue.shift()!;
406
+ if (visited.has(currentId)) continue;
407
+ visited.add(currentId);
408
+ allEntries.push(this.memoryGraph.nodes.get(currentId)!);
409
+
410
+ // Add neighbors to queue for further exploration
411
+ const neighbors = this.memoryGraph.edges.get(currentId);
412
+ if (neighbors) {
413
+ for (const neighborId of neighbors) {
414
+ if (!visited.has(neighborId)) {
415
+ queue.push(neighborId);
416
+ }
417
+ }
418
+ }
419
+ }
420
+
421
+ // Score and filter
422
+ const scored = allEntries.map(entry => ({
423
+ entry,
424
+ score: context
425
+ ? this.similarityToContext(entry, context)
426
+ : entry.relevanceScore || 0
427
+ })).sort((a, b) => b.score - a.score).slice(0, limit);
428
+
429
+ return scored.map(r => r.entry);
430
+ }
431
+
432
+ private similarityToContext(entry: MemoryEntry, context: string): number {
433
+ const contextEmbedding = this.generateEmbedding(context);
434
+ const entryEmbedding = entry.embeddingVector || [];
435
+
436
+ // Cosine similarity
437
+ let dotProduct = 0;
438
+ let magnitudeA = 0;
439
+ let magnitudeB = 0;
440
+
441
+ for (let i = 0; i < Math.min(contextEmbedding.length, entryEmbedding.length); i++) {
442
+ dotProduct += contextEmbedding[i] * entryEmbedding[i];
443
+ magnitudeA += contextEmbedding[i] ** 2;
444
+ magnitudeB += entryEmbedding[i] ** 2;
445
+ }
446
+
447
+ if (magnitudeA === 0 || magnitudeB === 0) return 0;
448
+
449
+ return dotProduct / (Math.sqrt(magnitudeA) * Math.sqrt(magnitudeB));
450
+ }
451
+
452
+ async export(): Promise<{
453
+ sessionId: string;
454
+ userId: string;
455
+ entries: MemoryEntry[];
456
+ }> {
457
+ return {
458
+ sessionId: this.sessionId,
459
+ userId: this.userId,
460
+ entries: Array.from(this.memoryGraph.nodes.values())
461
+ };
462
+ }
463
+
464
+ async clear(): Promise<void> {
465
+ this.memoryGraph = { nodes: new Map(), edges: new Map() };
466
+ this.vectorStore.clear();
467
+ await this.saveToDisk();
468
+ }
469
+ }
470
+
471
+ export const memoryHarnessSchema = z.object({
472
+ sessionId: z.string().optional(),
473
+ userId: z.string().optional(),
474
+ context: z.string().optional().default(''),
475
+ limit: z.number().optional().default(5),
476
+ });
477
+
478
+ export const MemoryHarnessTool: Tool<typeof memoryHarnessSchema> = {
479
+ name: 'memory_harness',
480
+ description: 'Persistent, recursive memory system for storing and retrieving agent interactions. Supports semantic search, graph-based related entry finding, and local-first storage.',
481
+ parameters: memoryHarnessSchema,
482
+ execute: async ({ sessionId, userId, context, limit = 5 }: z.infer<typeof memoryHarnessSchema>) => {
483
+ const harness = new MemoryHarness();
484
+ harness.userId = userId || 'anonymous';
485
+
486
+ if (sessionId) {
487
+ harness.sessionId = sessionId;
488
+ await harness.loadMemory();
489
+ }
490
+
491
+ if (context) {
492
+ const results = await harness.recall(sessionId, context, limit);
493
+ return {
494
+ success: true,
495
+ results: results.map(r => ({
496
+ id: r.id,
497
+ type: r.type,
498
+ content: r.content.substring(0, 500) + (r.content.length > 500 ? '...' : ''),
499
+ timestamp: r.timestamp,
500
+ tags: r.tags
501
+ })),
502
+ total: results.length,
503
+ hint: 'Memory query complete. Entries retrieved based on semantic similarity to your context.'
504
+ };
505
+ }
506
+
507
+ return {
508
+ success: true,
509
+ message: 'Memory harness initialized. Use context parameter to query related memories.',
510
+ hint: 'Provide a context string to find memories related to your query.'
511
+ };
512
+ }
513
+ };
514
+
515
+ export async function addMemoryEntry(
516
+ content: string,
517
+ type: MemoryEntry['type'],
518
+ context: MemoryEntry['context']
519
+ ): Promise<string> {
520
+ const harness = new MemoryHarness();
521
+ harness.userId = 'anonymous';
522
+
523
+ const entryId = await harness.addEntry(content, type, context);
524
+
525
+ // Auto-save to disk
526
+ await harness.saveToDisk();
527
+
528
+ return entryId;
529
+ }
530
+
531
+ export async function findRelatedMemories(
532
+ targetId: string,
533
+ limit: number = 5
534
+ ): Promise<MemoryEntry[]> {
535
+ const harness = new MemoryHarness();
536
+
537
+ const related = await harness.relatedToId(targetId, limit);
538
+ return related;
539
+ }
540
+ ` ``;
541
+ export {};
542
+ //# sourceMappingURL=memory-harness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-harness.js","sourceRoot":"","sources":["../../../../../src/packages/kbot/src/tools/memory-harness.ts"],"names":[],"mappings":"AAAA,EAAE,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAqFU,CAAA;AAAA,CAAC,CAAA;AAAA,CAAC;IAAA,IAAI,CAAC,SAAS,CAAA;AAAA,CAAC;AAAA,EAAE,CAAA;AAAA,CAAC;IAAA,IAAI,CAAC,GAAG,EAAE,CAAA;AAAA,CAAC;AAAA,EAAE,CAAA;AAAA,CAAC;IAAA,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAA;AAAA,CAAC;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iDA2FrC,CAAA;AAAA,SAAS,CAAA;AAAA,CAAC;IAAA,IAAI,CAAC,SAAS,CAAA;AAAA,CAAC;AAAC,IAAI,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyV9E,CAAA,EAAE,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kernel.chat/kbot",
3
- "version": "3.14.0",
3
+ "version": "3.14.5",
4
4
  "description": "The only AI agent that builds its own tools. Self-evolving terminal AI: 290 tools, 23 agents, 20 providers. Runtime tool forging via forge_tool, Forge Registry for community tools, autopoietic health monitoring, immune self-audit agent. Cost-aware model routing, fallback chains, Bayesian skill routing. Embedded llama.cpp, MCP server, programmatic SDK. MIT.",
5
5
  "type": "module",
6
6
  "repository": {