@kernel.chat/kbot 3.64.0 → 3.65.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/dist/cli.js CHANGED
@@ -3167,20 +3167,28 @@ async function main() {
3167
3167
  .description('Run a dream cycle now (uses local Ollama)')
3168
3168
  .action(async () => {
3169
3169
  const { dream } = await import('./dream.js');
3170
- printInfo('Dreaming... consolidating session memories with local AI');
3170
+ console.log();
3171
+ console.log(` ${chalk.hex('#A78BFA')('◆')} ${chalk.bold('Dream Engine')} ${chalk.dim('consolidating memories...')}`);
3172
+ console.log();
3171
3173
  const result = await dream();
3172
3174
  if (result.success) {
3173
- printSuccess(`Dream cycle #${result.cycle} complete`);
3174
- console.log(` New insights: ${result.newInsights}`);
3175
- console.log(` Reinforced: ${result.reinforced}`);
3176
- console.log(` Archived: ${result.archived} aged-out`);
3177
- console.log(` Duration: ${result.duration}ms`);
3175
+ console.log(` ${chalk.hex('#4ADE80')('✓')} ${chalk.bold(`Cycle #${result.cycle} complete`)} ${chalk.dim(`${result.duration}ms`)}`);
3176
+ console.log();
3177
+ console.log(` ${chalk.hex('#4ADE80')('+')} ${chalk.bold(String(result.newInsights))} new insights`);
3178
+ console.log(` ${chalk.hex('#A78BFA')('↻')} ${chalk.bold(String(result.reinforced))} reinforced`);
3179
+ if (result.archived > 0) {
3180
+ console.log(` ${chalk.dim('↓')} ${chalk.dim(`${result.archived} archived (aged out)`)}`);
3181
+ }
3182
+ console.log();
3183
+ console.log(` ${chalk.dim('View results:')} ${chalk.hex('#A78BFA')('kbot dream status')}`);
3178
3184
  }
3179
3185
  else {
3180
- printWarn(result.error || 'Dream cycle failed');
3181
- if (result.archived > 0)
3182
- console.log(` (Still archived ${result.archived} aged-out insights)`);
3186
+ console.log(` ${chalk.hex('#FBBF24')('!')} ${result.error || 'Dream cycle failed'}`);
3187
+ if (result.archived > 0) {
3188
+ console.log(` ${chalk.dim('↓')} ${chalk.dim(`${result.archived} insights archived (aging only)`)}`);
3189
+ }
3183
3190
  }
3191
+ console.log();
3184
3192
  });
3185
3193
  dreamCmd
3186
3194
  .command('status')
@@ -3188,23 +3196,86 @@ async function main() {
3188
3196
  .action(async () => {
3189
3197
  const { getDreamStatus } = await import('./dream.js');
3190
3198
  const { state, insights, archiveCount } = getDreamStatus();
3191
- console.log(chalk.bold('\nDream Engine'));
3192
- console.log(chalk.dim('═══════════════════'));
3193
- console.log(`Cycles: ${state.cycles}`);
3194
- console.log(`Last: ${state.lastDream || 'never'}`);
3195
- console.log(`Active: ${state.activeInsights} insights`);
3196
- console.log(`Archived: ${state.totalArchived} (${archiveCount} files)`);
3199
+ // ── Helper functions ──
3200
+ const W = 56; // inner width
3201
+ const box = {
3202
+ tl: '╭', tr: '', bl: '╰', br: '╯', h: '─', v: '│',
3203
+ pad: (s, w) => {
3204
+ // Pad string to width, accounting for chalk ANSI codes
3205
+ const visible = s.replace(/\x1b\[[0-9;]*m/g, '');
3206
+ const diff = w - visible.length;
3207
+ return diff > 0 ? s + ' '.repeat(diff) : s;
3208
+ },
3209
+ };
3210
+ const relevanceBar = (pct, len = 20) => {
3211
+ const filled = Math.round((pct / 100) * len);
3212
+ const empty = len - filled;
3213
+ const color = pct >= 70 ? chalk.hex('#4ADE80') : pct >= 40 ? chalk.hex('#FBBF24') : chalk.hex('#F87171');
3214
+ return color('█'.repeat(filled)) + chalk.dim('░'.repeat(empty));
3215
+ };
3216
+ const categoryColor = (cat) => {
3217
+ const colors = {
3218
+ pattern: '#A78BFA', preference: '#67E8F9', skill: '#4ADE80',
3219
+ project: '#FB923C', relationship: '#F472B6',
3220
+ };
3221
+ return chalk.hex(colors[cat] || '#A78BFA');
3222
+ };
3223
+ const categoryChip = (cat) => {
3224
+ const c = categoryColor(cat);
3225
+ return c(` ${cat.toUpperCase()} `);
3226
+ };
3227
+ // ── Header box ──
3228
+ console.log();
3229
+ console.log(chalk.hex('#A78BFA')(` ${box.tl}${box.h.repeat(W)}${box.tr}`));
3230
+ console.log(chalk.hex('#A78BFA')(` ${box.v}`) + box.pad(` ${chalk.hex('#A78BFA').bold('◆ DREAM ENGINE')} ${chalk.dim('memory consolidation')}`, W) + chalk.hex('#A78BFA')(box.v));
3231
+ console.log(chalk.hex('#A78BFA')(` ${box.bl}${box.h.repeat(W)}${box.br}`));
3232
+ console.log();
3233
+ // ── Stats row ──
3234
+ const lastDreamDisplay = state.lastDream
3235
+ ? new Date(state.lastDream).toLocaleDateString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })
3236
+ : chalk.dim('never');
3237
+ console.log(` ${chalk.bold('Cycles')} ${chalk.hex('#A78BFA')(String(state.cycles))} ${chalk.bold('Last')} ${lastDreamDisplay} ${chalk.bold('Active')} ${chalk.hex('#4ADE80')(String(state.activeInsights))}`);
3238
+ console.log(` ${chalk.bold('Total')} ${chalk.dim(String(state.totalInsights))} ${chalk.bold('Archived')} ${chalk.dim(`${state.totalArchived} (${archiveCount} files)`)}`);
3239
+ console.log();
3197
3240
  if (insights.length > 0) {
3241
+ // ── Average relevance bar ──
3198
3242
  const avgRel = Math.round(insights.reduce((s, i) => s + i.relevance, 0) / insights.length * 100);
3199
- console.log(`Avg relevance: ${avgRel}%`);
3200
- console.log(chalk.bold('\nTop insights:'));
3243
+ console.log(` ${chalk.bold('Avg Relevance')} ${relevanceBar(avgRel, 24)} ${chalk.bold(`${avgRel}%`)}`);
3244
+ console.log();
3245
+ // ── Category breakdown ──
3246
+ const catCounts = {};
3247
+ for (const i of insights)
3248
+ catCounts[i.category] = (catCounts[i.category] || 0) + 1;
3249
+ const chips = Object.entries(catCounts)
3250
+ .sort((a, b) => b[1] - a[1])
3251
+ .map(([cat, count]) => `${categoryChip(cat)} ${chalk.dim(`${count}`)}`)
3252
+ .join(' ');
3253
+ console.log(` ${chalk.bold('Categories')} ${chips}`);
3254
+ console.log();
3255
+ // ── Divider ──
3256
+ console.log(chalk.dim(` ${'─'.repeat(W)}`));
3257
+ console.log();
3258
+ // ── Top insights ──
3259
+ console.log(` ${chalk.bold('Top Insights')}`);
3260
+ console.log();
3201
3261
  for (const i of insights.slice(0, 8)) {
3202
3262
  const pct = Math.round(i.relevance * 100);
3203
- console.log(` ${chalk.green(`${pct}%`)} [${chalk.cyan(i.category)}] ${i.content}`);
3263
+ const bar = relevanceBar(pct, 12);
3264
+ const tag = categoryChip(i.category);
3265
+ console.log(` ${bar} ${chalk.bold(`${pct}%`)} ${tag}`);
3266
+ console.log(` ${chalk.white(i.content)}`);
3267
+ if (i.keywords.length > 0) {
3268
+ console.log(` ${chalk.dim(i.keywords.map(k => `#${k}`).join(' '))} ${chalk.dim('·')} ${chalk.dim(`${i.sessions} sessions`)}`);
3269
+ }
3270
+ console.log();
3204
3271
  }
3205
3272
  }
3206
3273
  else {
3207
- console.log(chalk.dim('\nNo insights yet. Run: kbot dream run'));
3274
+ console.log(chalk.dim(` ${'─'.repeat(W)}`));
3275
+ console.log();
3276
+ console.log(` ${chalk.dim('No insights yet.')}`);
3277
+ console.log(` ${chalk.dim('Run:')} ${chalk.hex('#A78BFA')('kbot dream run')} ${chalk.dim('to start consolidating memories')}`);
3278
+ console.log();
3208
3279
  }
3209
3280
  });
3210
3281
  dreamCmd
@@ -3213,24 +3284,137 @@ async function main() {
3213
3284
  .action(async (query) => {
3214
3285
  const { searchDreams } = await import('./dream.js');
3215
3286
  const results = searchDreams(query);
3287
+ // Helpers
3288
+ const relevanceBar = (pct, len = 12) => {
3289
+ const filled = Math.round((pct / 100) * len);
3290
+ const empty = len - filled;
3291
+ const color = pct >= 70 ? chalk.hex('#4ADE80') : pct >= 40 ? chalk.hex('#FBBF24') : chalk.hex('#F87171');
3292
+ return color('█'.repeat(filled)) + chalk.dim('░'.repeat(empty));
3293
+ };
3294
+ const categoryColor = (cat) => {
3295
+ const colors = {
3296
+ pattern: '#A78BFA', preference: '#67E8F9', skill: '#4ADE80',
3297
+ project: '#FB923C', relationship: '#F472B6',
3298
+ };
3299
+ return chalk.hex(colors[cat] || '#A78BFA');
3300
+ };
3301
+ const highlightQuery = (text, q) => {
3302
+ const terms = q.toLowerCase().split(/\s+/);
3303
+ let result = text;
3304
+ for (const term of terms) {
3305
+ const regex = new RegExp(`(${term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
3306
+ result = result.replace(regex, chalk.hex('#FBBF24').bold.underline('$1'));
3307
+ }
3308
+ return result;
3309
+ };
3216
3310
  if (results.length === 0) {
3217
- printInfo(`No insights match "${query}"`);
3311
+ console.log();
3312
+ console.log(` ${chalk.hex('#A78BFA')('◆')} ${chalk.bold('Dream Search')} ${chalk.dim(`"${query}"`)}`);
3313
+ console.log();
3314
+ console.log(` ${chalk.dim('No insights match this query.')}`);
3315
+ console.log(` ${chalk.dim('Try broader keywords or run')} ${chalk.hex('#A78BFA')('kbot dream run')} ${chalk.dim('first.')}`);
3316
+ console.log();
3218
3317
  return;
3219
3318
  }
3220
- console.log(chalk.bold(`\n${results.length} insights matching "${query}":\n`));
3319
+ console.log();
3320
+ console.log(` ${chalk.hex('#A78BFA')('◆')} ${chalk.bold('Dream Search')} ${chalk.dim(`"${query}"`)} ${chalk.hex('#4ADE80')(`${results.length} found`)}`);
3321
+ console.log(chalk.dim(` ${'─'.repeat(52)}`));
3322
+ console.log();
3221
3323
  for (const i of results.slice(0, 15)) {
3222
3324
  const pct = Math.round(i.relevance * 100);
3223
- console.log(` ${chalk.green(`${pct}%`)} [${chalk.cyan(i.category)}] ${i.content}`);
3224
- console.log(chalk.dim(` ${i.keywords.join(', ')} | ${i.sessions} sessions | ${i.created.split('T')[0]}`));
3325
+ const bar = relevanceBar(pct);
3326
+ const tag = categoryColor(i.category)(` ${i.category.toUpperCase()} `);
3327
+ console.log(` ${bar} ${chalk.bold(`${pct}%`)} ${tag}`);
3328
+ console.log(` ${highlightQuery(i.content, query)}`);
3329
+ const keywordsHighlighted = i.keywords.map(k => highlightQuery(`#${k}`, query)).join(' ');
3330
+ console.log(` ${keywordsHighlighted} ${chalk.dim('·')} ${chalk.dim(`${i.sessions} sessions`)} ${chalk.dim('·')} ${chalk.dim(i.created.split('T')[0])}`);
3331
+ console.log();
3225
3332
  }
3226
3333
  });
3227
3334
  dreamCmd
3228
3335
  .command('journal')
3229
3336
  .description('Print the full dream journal')
3230
3337
  .action(async () => {
3231
- const { getDreamPrompt } = await import('./dream.js');
3232
- const journal = getDreamPrompt(50);
3233
- console.log(journal || chalk.dim('Dream journal is empty. Run: kbot dream run'));
3338
+ const { getDreamStatus } = await import('./dream.js');
3339
+ const { state, insights } = getDreamStatus();
3340
+ // Helpers
3341
+ const W = 56;
3342
+ const box = {
3343
+ tl: '╭', tr: '╮', bl: '╰', br: '╯', h: '─', v: '│',
3344
+ pad: (s, w) => {
3345
+ const visible = s.replace(/\x1b\[[0-9;]*m/g, '');
3346
+ const diff = w - visible.length;
3347
+ return diff > 0 ? s + ' '.repeat(diff) : s;
3348
+ },
3349
+ };
3350
+ const relevanceBar = (pct, len = 16) => {
3351
+ const filled = Math.round((pct / 100) * len);
3352
+ const empty = len - filled;
3353
+ const color = pct >= 70 ? chalk.hex('#4ADE80') : pct >= 40 ? chalk.hex('#FBBF24') : chalk.hex('#F87171');
3354
+ return color('█'.repeat(filled)) + chalk.dim('░'.repeat(empty));
3355
+ };
3356
+ const categoryColors = {
3357
+ pattern: '#A78BFA', preference: '#67E8F9', skill: '#4ADE80',
3358
+ project: '#FB923C', relationship: '#F472B6',
3359
+ };
3360
+ const categoryIcon = {
3361
+ pattern: '◇', preference: '♡', skill: '⚡', project: '▸', relationship: '◈',
3362
+ };
3363
+ const categoryColor = (cat) => chalk.hex(categoryColors[cat] || '#A78BFA');
3364
+ if (insights.length === 0) {
3365
+ console.log();
3366
+ console.log(` ${chalk.hex('#A78BFA')('◆')} ${chalk.bold('Dream Journal')}`);
3367
+ console.log();
3368
+ console.log(` ${chalk.dim('The journal is empty.')}`);
3369
+ console.log(` ${chalk.dim('Run')} ${chalk.hex('#A78BFA')('kbot dream run')} ${chalk.dim('after a session to consolidate memories.')}`);
3370
+ console.log();
3371
+ return;
3372
+ }
3373
+ // ── Header ──
3374
+ console.log();
3375
+ console.log(chalk.hex('#A78BFA')(` ${box.tl}${box.h.repeat(W)}${box.tr}`));
3376
+ const headerContent = ` ${chalk.hex('#A78BFA').bold('◆ DREAM JOURNAL')} ${chalk.dim(`${insights.length} insights · cycle ${state.cycles}`)}`;
3377
+ console.log(chalk.hex('#A78BFA')(` ${box.v}`) + box.pad(headerContent, W) + chalk.hex('#A78BFA')(box.v));
3378
+ console.log(chalk.hex('#A78BFA')(` ${box.bl}${box.h.repeat(W)}${box.br}`));
3379
+ console.log();
3380
+ // ── Group by category ──
3381
+ const grouped = {};
3382
+ for (const i of insights) {
3383
+ if (!grouped[i.category])
3384
+ grouped[i.category] = [];
3385
+ grouped[i.category].push(i);
3386
+ }
3387
+ // Sort categories by total insight count descending
3388
+ const catOrder = Object.entries(grouped).sort((a, b) => b[1].length - a[1].length);
3389
+ for (const [cat, catInsights] of catOrder) {
3390
+ const icon = categoryIcon[cat] || '●';
3391
+ const cc = categoryColor(cat);
3392
+ // ── Category section header ──
3393
+ console.log(` ${cc(`${icon} ${cat.toUpperCase()}`)} ${chalk.dim(`(${catInsights.length})`)}`);
3394
+ console.log(` ${cc('─'.repeat(W))}`);
3395
+ console.log();
3396
+ for (const i of catInsights) {
3397
+ const pct = Math.round(i.relevance * 100);
3398
+ const bar = relevanceBar(pct);
3399
+ const date = i.created.split('T')[0];
3400
+ const reinforced = i.lastReinforced !== i.created
3401
+ ? chalk.dim(` · reinforced ${i.lastReinforced.split('T')[0]}`)
3402
+ : '';
3403
+ // Card: relevance bar + content + metadata
3404
+ console.log(` ${bar} ${chalk.bold(`${pct}%`)} ${chalk.dim(`${i.sessions} sessions`)}${reinforced}`);
3405
+ console.log(` ${chalk.white(i.content)}`);
3406
+ if (i.keywords.length > 0) {
3407
+ console.log(` ${chalk.dim(i.keywords.map(k => `#${k}`).join(' '))}`);
3408
+ }
3409
+ console.log(` ${chalk.dim(`${date} · ${i.source} · ${i.id}`)}`);
3410
+ console.log();
3411
+ }
3412
+ }
3413
+ // ── Footer ──
3414
+ console.log(chalk.dim(` ${'─'.repeat(W)}`));
3415
+ const avgRel = Math.round(insights.reduce((s, i) => s + i.relevance, 0) / insights.length * 100);
3416
+ console.log(` ${chalk.dim(`${insights.length} active insights · avg relevance ${avgRel}% · ${state.totalArchived} archived`)}`);
3417
+ console.log();
3234
3418
  });
3235
3419
  program.parse(process.argv);
3236
3420
  const opts = program.opts();
package/dist/dream.d.ts CHANGED
@@ -38,6 +38,22 @@ export declare function ageMemories(insights: DreamInsight[]): {
38
38
  aged: DreamInsight[];
39
39
  archived: DreamInsight[];
40
40
  };
41
+ /**
42
+ * Apply dream insights back into the learning system.
43
+ * This is the feedback loop that makes the memory cascade bidirectional:
44
+ * - "preference" insights → update user profile via learnFact()
45
+ * - "pattern" insights → hint the pattern cache via recordPattern()
46
+ * - "skill" insights → record as observed knowledge
47
+ * - "project" insights → record as project context
48
+ *
49
+ * Called at the end of every dream cycle after new insights are extracted.
50
+ */
51
+ export declare function applyDreamInsights(insights: DreamInsight[]): ApplyResult;
52
+ export interface ApplyResult {
53
+ preferencesApplied: number;
54
+ patternsHinted: number;
55
+ factsLearned: number;
56
+ }
41
57
  /** Run a full dream cycle — consolidate, reinforce, age */
42
58
  export declare function dream(sessionId?: string): Promise<DreamResult>;
43
59
  export interface DreamResult {
@@ -48,6 +64,8 @@ export interface DreamResult {
48
64
  cycle: number;
49
65
  duration: number;
50
66
  error: string | null;
67
+ /** Feedback from applying insights back into learning tiers */
68
+ applied: ApplyResult | null;
51
69
  }
52
70
  /** Get dream insights for inclusion in system prompt */
53
71
  export declare function getDreamPrompt(maxInsights?: number): string;
package/dist/dream.js CHANGED
@@ -19,6 +19,8 @@ import { join } from 'node:path';
19
19
  import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from 'node:fs';
20
20
  import { getHistory } from './memory.js';
21
21
  import { loadMemory } from './memory.js';
22
+ import { getTopPatterns, getTopSolutions, getProfileSummary, updateProfile, recordPattern, learnFact, } from './learning.js';
23
+ import { getMemoryScannerStats } from './memory-scanner.js';
22
24
  // ── Constants ──
23
25
  const DREAM_DIR = join(homedir(), '.kbot', 'memory', 'dreams');
24
26
  const JOURNAL_FILE = join(DREAM_DIR, 'journal.json');
@@ -149,22 +151,61 @@ function buildConsolidationPrompt(sessionHistory, existingInsights, existingMemo
149
151
  const existingText = existingInsights.length > 0
150
152
  ? existingInsights.slice(0, 20).map(i => `- [${i.category}] ${i.content}`).join('\n')
151
153
  : '(none yet)';
152
- return `You are a memory consolidation system. Analyze this conversation session and extract durable insights.
154
+ // ── Tier 1: Pattern cache intent tool sequences ──
155
+ const topPatterns = getTopPatterns(10);
156
+ const patternsText = topPatterns.length > 0
157
+ ? topPatterns.map(p => `- "${p.intent}" → ${p.toolSequence.join(' → ')} (${p.hits}x, ${Math.round(p.successRate * 100)}% success)`).join('\n')
158
+ : '(no patterns yet)';
159
+ // ── Tier 2: Solution index — Q&A pairs ──
160
+ const topSolutions = getTopSolutions(5);
161
+ const solutionsText = topSolutions.length > 0
162
+ ? topSolutions.map(s => `- Q: ${s.question.slice(0, 100)} → confidence: ${Math.round(s.confidence * 100)}%, reused ${s.reuses}x`).join('\n')
163
+ : '(no solutions yet)';
164
+ // ── Tier 3: User profile ──
165
+ const profileText = getProfileSummary() || '(no profile data yet)';
166
+ // ── Tier 5: Memory scanner — recent passive detections ──
167
+ const scannerStats = getMemoryScannerStats();
168
+ const scannerText = scannerStats.recentDetections.length > 0
169
+ ? scannerStats.recentDetections
170
+ .slice(-5)
171
+ .map(d => `- [${d.kind}] ${d.content.slice(0, 150)} (confidence: ${Math.round(d.confidence * 100)}%)`)
172
+ .join('\n')
173
+ : '(no recent detections)';
174
+ return `You are a memory consolidation system. Analyze this conversation session and ALL accumulated knowledge tiers to extract durable cross-tier insights.
153
175
 
154
- EXISTING INSIGHTS:
176
+ EXISTING DREAM INSIGHTS (Tier 4 — Dream Journal):
155
177
  ${existingText}
156
178
 
157
179
  EXISTING PERSISTENT MEMORY:
158
180
  ${existingMemory.slice(0, 2000) || '(empty)'}
159
181
 
182
+ LEARNED PATTERNS (Tier 1 — Pattern Cache):
183
+ ${patternsText}
184
+
185
+ PROVEN SOLUTIONS (Tier 2 — Solution Index):
186
+ ${solutionsText}
187
+
188
+ USER PROFILE (Tier 3):
189
+ ${profileText}
190
+
191
+ RECENT MEMORY SCANNER DETECTIONS (Tier 5 — Passive Detection):
192
+ ${scannerText}
193
+
160
194
  SESSION TO CONSOLIDATE:
161
195
  ${historyText}
162
196
 
163
197
  INSTRUCTIONS:
164
- Extract 1-5 insights from this session. Each insight should be:
198
+ Extract 1-5 insights by synthesizing across ALL tiers. Each insight should be:
165
199
  - Durable (useful beyond this session)
166
200
  - Non-obvious (not derivable from reading code)
167
- - About the USER, their preferences, patterns, or project context
201
+ - Cross-tier when possible (e.g., a pattern + preference = workflow insight)
202
+ - About the USER, their preferences, patterns, workflows, or project context
203
+
204
+ Pay special attention to:
205
+ - Patterns that confirm or contradict existing insights
206
+ - Scanner corrections that reveal unrecognized preferences
207
+ - Solution clusters that suggest emerging expertise areas
208
+ - Profile trends that indicate shifting priorities
168
209
 
169
210
  Format each insight as a JSON array of objects:
170
211
  [
@@ -199,6 +240,133 @@ If none are reinforced, return: []
199
240
 
200
241
  Respond ONLY with the JSON array.`;
201
242
  }
243
+ // ── Memory Cascade: Feed Insights Back into Tiers ──
244
+ /**
245
+ * Apply dream insights back into the learning system.
246
+ * This is the feedback loop that makes the memory cascade bidirectional:
247
+ * - "preference" insights → update user profile via learnFact()
248
+ * - "pattern" insights → hint the pattern cache via recordPattern()
249
+ * - "skill" insights → record as observed knowledge
250
+ * - "project" insights → record as project context
251
+ *
252
+ * Called at the end of every dream cycle after new insights are extracted.
253
+ */
254
+ export function applyDreamInsights(insights) {
255
+ const result = {
256
+ preferencesApplied: 0,
257
+ patternsHinted: 0,
258
+ factsLearned: 0,
259
+ };
260
+ for (const insight of insights) {
261
+ switch (insight.category) {
262
+ case 'preference': {
263
+ // Feed preference insights back into the knowledge base as observed facts.
264
+ // This lets the learning context builder surface them in future prompts.
265
+ learnFact(insight.content, 'preference', 'observed');
266
+ // Also nudge the user profile if the insight mentions style/tech preferences
267
+ const lower = insight.content.toLowerCase();
268
+ if (/\b(?:concise|brief|short)\b/.test(lower)) {
269
+ updateProfile({ taskType: 'prefers-concise' });
270
+ }
271
+ else if (/\b(?:detailed|thorough|verbose)\b/.test(lower)) {
272
+ updateProfile({ taskType: 'prefers-detailed' });
273
+ }
274
+ // Extract any tech terms mentioned in the preference
275
+ const techTerms = extractTechTermsFromInsight(insight.content);
276
+ if (techTerms.length > 0) {
277
+ updateProfile({ techTerms });
278
+ }
279
+ result.preferencesApplied++;
280
+ break;
281
+ }
282
+ case 'pattern': {
283
+ // Feed pattern insights into the knowledge base.
284
+ // If the insight describes a tool workflow, hint the pattern cache.
285
+ learnFact(insight.content, 'context', 'observed');
286
+ // Try to extract a tool sequence from the insight text
287
+ // (e.g., "User typically reads files then runs tests" → [read_file, run_tests])
288
+ const toolHints = extractToolHintsFromInsight(insight.content);
289
+ if (toolHints.length >= 2) {
290
+ // Record as a pattern with the insight content as the "intent"
291
+ const intentWords = insight.keywords.join(' ') || insight.content.slice(0, 80);
292
+ recordPattern(intentWords, toolHints, 0);
293
+ }
294
+ result.patternsHinted++;
295
+ break;
296
+ }
297
+ case 'skill': {
298
+ // Skills are knowledge about what the user is good at or learning.
299
+ learnFact(insight.content, 'context', 'observed');
300
+ result.factsLearned++;
301
+ break;
302
+ }
303
+ case 'project': {
304
+ // Project insights become project-scoped knowledge.
305
+ learnFact(insight.content, 'project', 'observed');
306
+ result.factsLearned++;
307
+ break;
308
+ }
309
+ case 'relationship': {
310
+ // Relationship insights (how user interacts, team dynamics) → context facts.
311
+ learnFact(insight.content, 'context', 'observed');
312
+ result.factsLearned++;
313
+ break;
314
+ }
315
+ }
316
+ }
317
+ return result;
318
+ }
319
+ /** Extract tech-related terms from insight text */
320
+ function extractTechTermsFromInsight(text) {
321
+ const techTerms = new Set([
322
+ 'react', 'typescript', 'node', 'python', 'rust', 'go', 'docker',
323
+ 'api', 'database', 'supabase', 'postgres', 'redis', 'mongodb',
324
+ 'css', 'html', 'json', 'sql', 'git', 'npm', 'vite', 'webpack',
325
+ 'tailwind', 'next', 'express', 'fastify', 'deno', 'bun',
326
+ 'playwright', 'vitest', 'jest', 'eslint', 'prettier',
327
+ 'ollama', 'anthropic', 'openai', 'claude', 'gpt',
328
+ 'ableton', 'serum', 'splice', 'osc', 'midi',
329
+ ]);
330
+ return text.toLowerCase()
331
+ .replace(/[^a-z0-9\s]/g, ' ')
332
+ .split(/\s+/)
333
+ .filter(w => techTerms.has(w));
334
+ }
335
+ /** Try to extract tool names from insight text describing a workflow */
336
+ function extractToolHintsFromInsight(text) {
337
+ const toolNames = new Set([
338
+ 'read_file', 'write_file', 'edit_file', 'glob', 'grep', 'bash',
339
+ 'git_status', 'git_diff', 'git_commit', 'git_push', 'git_log',
340
+ 'run_tests', 'test_run', 'build_run', 'type_check', 'lint_check',
341
+ 'web_search', 'url_fetch', 'screenshot', 'browser_navigate',
342
+ 'kbot_agent', 'spawn_agent', 'memory_save', 'memory_search',
343
+ 'research', 'papers_search', 'github_search',
344
+ ]);
345
+ // Match tool-like words (snake_case) in the text
346
+ const words = text.toLowerCase().replace(/[^a-z0-9_\s]/g, ' ').split(/\s+/);
347
+ const found = words.filter(w => toolNames.has(w));
348
+ if (found.length >= 2)
349
+ return found;
350
+ // Fallback: look for verb patterns that map to common tools
351
+ const verbMap = [
352
+ [/\bread(?:s|ing)?\s+(?:file|code|source)/i, 'read_file'],
353
+ [/\bwrit(?:e|es|ing)\s+(?:file|code)/i, 'write_file'],
354
+ [/\bedit(?:s|ing)?\b/i, 'edit_file'],
355
+ [/\bsearch(?:es|ing)?\s+(?:web|online|internet)/i, 'web_search'],
356
+ [/\brun(?:s|ning)?\s+test/i, 'run_tests'],
357
+ [/\bgrep(?:s|ping)?\b/i, 'grep'],
358
+ [/\bgit\s+(?:commit|push|diff|status|log)/i, 'git_commit'],
359
+ [/\bbuild(?:s|ing)?\b/i, 'build_run'],
360
+ [/\bbash\b|\bshell\b|\bcommand\b/i, 'bash'],
361
+ ];
362
+ const mapped = [];
363
+ for (const [pattern, tool] of verbMap) {
364
+ if (pattern.test(text) && !mapped.includes(tool)) {
365
+ mapped.push(tool);
366
+ }
367
+ }
368
+ return mapped;
369
+ }
202
370
  // ── Core Dream Functions ──
203
371
  /** Run a full dream cycle — consolidate, reinforce, age */
204
372
  export async function dream(sessionId = 'default') {
@@ -210,6 +378,7 @@ export async function dream(sessionId = 'default') {
210
378
  cycle: 0,
211
379
  duration: 0,
212
380
  error: null,
381
+ applied: null,
213
382
  };
214
383
  const start = Date.now();
215
384
  // Check Ollama availability
@@ -241,9 +410,10 @@ export async function dream(sessionId = 'default') {
241
410
  archiveInsights(archived);
242
411
  journal = aged;
243
412
  result.archived = archived.length;
244
- // Phase 2: Extract new insights from session
413
+ // Phase 2: Extract new insights from session (cross-tier consolidation)
245
414
  const consolidationPrompt = buildConsolidationPrompt(history, journal, memory);
246
415
  const rawInsights = await ollamaGenerate(consolidationPrompt);
416
+ const newlyCreatedInsights = [];
247
417
  if (rawInsights) {
248
418
  try {
249
419
  const parsed = JSON.parse(rawInsights);
@@ -255,7 +425,7 @@ export async function dream(sessionId = 'default') {
255
425
  p.content.toLowerCase().includes(j.content.toLowerCase().slice(0, 50)));
256
426
  if (isDupe)
257
427
  continue;
258
- journal.push({
428
+ const insight = {
259
429
  id: generateId(),
260
430
  content: p.content,
261
431
  category: p.category || 'pattern',
@@ -265,7 +435,9 @@ export async function dream(sessionId = 'default') {
265
435
  created: now,
266
436
  lastReinforced: now,
267
437
  source: `session_${state.cycles + 1}`,
268
- });
438
+ };
439
+ journal.push(insight);
440
+ newlyCreatedInsights.push(insight);
269
441
  result.newInsights++;
270
442
  }
271
443
  }
@@ -306,6 +478,12 @@ export async function dream(sessionId = 'default') {
306
478
  journal = journal.slice(0, MAX_INSIGHTS);
307
479
  result.archived += overflow.length;
308
480
  }
481
+ // Phase 5: Feed new insights back into learning tiers (memory cascade)
482
+ // This is the key integration — dream insights don't just sit in the journal,
483
+ // they propagate back into the pattern cache, solution index, and user profile.
484
+ if (newlyCreatedInsights.length > 0) {
485
+ result.applied = applyDreamInsights(newlyCreatedInsights);
486
+ }
309
487
  // Save everything
310
488
  saveJournal(journal);
311
489
  state.cycles++;
@@ -179,5 +179,11 @@ export declare function getExtendedStats(): LearningStats & {
179
179
  projectsCount: number;
180
180
  topKnowledge: string[];
181
181
  };
182
+ /** Get the top N patterns from the pattern cache, ranked by effectiveness */
183
+ export declare function getTopPatterns(n?: number): CachedPattern[];
184
+ /** Get the top N solutions from the solution index, ranked by confidence and reuse */
185
+ export declare function getTopSolutions(n?: number): CachedSolution[];
186
+ /** Get a text summary of the user profile for consolidation prompts */
187
+ export declare function getProfileSummary(): string;
182
188
  export {};
183
189
  //# sourceMappingURL=learning.d.ts.map
package/dist/learning.js CHANGED
@@ -878,4 +878,45 @@ export function getExtendedStats() {
878
878
  .map(k => k.fact.slice(0, 80)),
879
879
  };
880
880
  }
881
+ // ═══ 12. MEMORY CASCADE ACCESSORS ══════════════════════════════
882
+ // Read-only accessors for the dream engine to pull data from all tiers.
883
+ /** Get the top N patterns from the pattern cache, ranked by effectiveness */
884
+ export function getTopPatterns(n = 10) {
885
+ return [...patterns]
886
+ .sort((a, b) => (b.hits * b.successRate) - (a.hits * a.successRate))
887
+ .slice(0, n);
888
+ }
889
+ /** Get the top N solutions from the solution index, ranked by confidence and reuse */
890
+ export function getTopSolutions(n = 5) {
891
+ return [...solutions]
892
+ .sort((a, b) => (b.confidence * (b.reuses + 1)) - (a.confidence * (a.reuses + 1)))
893
+ .slice(0, n);
894
+ }
895
+ /** Get a text summary of the user profile for consolidation prompts */
896
+ export function getProfileSummary() {
897
+ const parts = [];
898
+ if (profile.techStack.length > 0) {
899
+ parts.push(`Tech stack: ${profile.techStack.join(', ')}`);
900
+ }
901
+ if (profile.responseStyle !== 'auto') {
902
+ parts.push(`Response style: ${profile.responseStyle}`);
903
+ }
904
+ const topTasks = Object.entries(profile.taskPatterns)
905
+ .sort((a, b) => b[1] - a[1])
906
+ .slice(0, 5);
907
+ if (topTasks.length > 0) {
908
+ parts.push(`Task patterns: ${topTasks.map(([t, n]) => `${t}(${n}x)`).join(', ')}`);
909
+ }
910
+ const topAgents = Object.entries(profile.preferredAgents)
911
+ .sort((a, b) => b[1] - a[1])
912
+ .slice(0, 3);
913
+ if (topAgents.length > 0) {
914
+ parts.push(`Preferred agents: ${topAgents.map(([a, n]) => `${a}(${n}x)`).join(', ')}`);
915
+ }
916
+ parts.push(`Sessions: ${profile.sessions}, Messages: ${profile.totalMessages}`);
917
+ if (profile.tokensSaved > 0) {
918
+ parts.push(`Tokens saved by learning: ${profile.tokensSaved}`);
919
+ }
920
+ return parts.join('\n');
921
+ }
881
922
  //# sourceMappingURL=learning.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kernel.chat/kbot",
3
- "version": "3.64.0",
3
+ "version": "3.65.0",
4
4
  "description": "Open-source terminal AI agent. 686+ tools, 35 agents, 20 providers. Fully local, fully sovereign. MIT.",
5
5
  "type": "module",
6
6
  "repository": {