kernelbot 1.0.30 → 1.0.32

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.
Files changed (63) hide show
  1. package/.env.example +0 -0
  2. package/README.md +0 -0
  3. package/bin/kernel.js +56 -2
  4. package/config.example.yaml +31 -0
  5. package/package.json +1 -1
  6. package/src/agent.js +150 -20
  7. package/src/automation/automation-manager.js +0 -0
  8. package/src/automation/automation.js +0 -0
  9. package/src/automation/index.js +0 -0
  10. package/src/automation/scheduler.js +0 -0
  11. package/src/bot.js +303 -4
  12. package/src/claude-auth.js +0 -0
  13. package/src/coder.js +0 -0
  14. package/src/conversation.js +0 -0
  15. package/src/intents/detector.js +0 -0
  16. package/src/intents/index.js +0 -0
  17. package/src/intents/planner.js +0 -0
  18. package/src/life/codebase.js +388 -0
  19. package/src/life/engine.js +1317 -0
  20. package/src/life/evolution.js +244 -0
  21. package/src/life/improvements.js +81 -0
  22. package/src/life/journal.js +109 -0
  23. package/src/life/memory.js +283 -0
  24. package/src/life/share-queue.js +136 -0
  25. package/src/persona.js +0 -0
  26. package/src/prompts/orchestrator.js +19 -1
  27. package/src/prompts/persona.md +0 -0
  28. package/src/prompts/system.js +0 -0
  29. package/src/prompts/workers.js +10 -9
  30. package/src/providers/anthropic.js +0 -0
  31. package/src/providers/base.js +0 -0
  32. package/src/providers/index.js +0 -0
  33. package/src/providers/models.js +8 -1
  34. package/src/providers/openai-compat.js +0 -0
  35. package/src/security/audit.js +0 -0
  36. package/src/security/auth.js +0 -0
  37. package/src/security/confirm.js +0 -0
  38. package/src/self.js +0 -0
  39. package/src/services/stt.js +0 -0
  40. package/src/services/tts.js +0 -0
  41. package/src/skills/catalog.js +0 -0
  42. package/src/skills/custom.js +0 -0
  43. package/src/swarm/job-manager.js +0 -0
  44. package/src/swarm/job.js +0 -0
  45. package/src/swarm/worker-registry.js +0 -0
  46. package/src/tools/browser.js +0 -0
  47. package/src/tools/categories.js +0 -0
  48. package/src/tools/coding.js +1 -1
  49. package/src/tools/docker.js +0 -0
  50. package/src/tools/git.js +0 -0
  51. package/src/tools/github.js +0 -0
  52. package/src/tools/index.js +0 -0
  53. package/src/tools/jira.js +0 -0
  54. package/src/tools/monitor.js +0 -0
  55. package/src/tools/network.js +0 -0
  56. package/src/tools/orchestrator-tools.js +18 -3
  57. package/src/tools/os.js +0 -0
  58. package/src/tools/persona.js +0 -0
  59. package/src/tools/process.js +0 -0
  60. package/src/utils/config.js +0 -0
  61. package/src/utils/display.js +0 -0
  62. package/src/utils/logger.js +0 -0
  63. package/src/worker.js +10 -8
package/.env.example CHANGED
File without changes
package/README.md CHANGED
File without changes
package/bin/kernel.js CHANGED
@@ -26,6 +26,12 @@ import { JobManager } from '../src/swarm/job-manager.js';
26
26
  import { startBot } from '../src/bot.js';
27
27
  import { AutomationManager } from '../src/automation/index.js';
28
28
  import { createProvider, PROVIDERS } from '../src/providers/index.js';
29
+ import { MemoryManager } from '../src/life/memory.js';
30
+ import { JournalManager } from '../src/life/journal.js';
31
+ import { ShareQueue } from '../src/life/share-queue.js';
32
+ import { EvolutionTracker } from '../src/life/evolution.js';
33
+ import { CodebaseKnowledge } from '../src/life/codebase.js';
34
+ import { LifeEngine } from '../src/life/engine.js';
29
35
  import {
30
36
  loadCustomSkills,
31
37
  getCustomSkills,
@@ -182,9 +188,25 @@ async function startBotFlow(config) {
182
188
 
183
189
  const automationManager = new AutomationManager();
184
190
 
185
- const agent = new Agent({ config, conversationManager, personaManager, selfManager, jobManager, automationManager });
191
+ // Life system managers
192
+ const memoryManager = new MemoryManager();
193
+ const journalManager = new JournalManager();
194
+ const shareQueue = new ShareQueue();
195
+ const evolutionTracker = new EvolutionTracker();
196
+ const codebaseKnowledge = new CodebaseKnowledge({ config });
186
197
 
187
- startBot(config, agent, conversationManager, jobManager, automationManager);
198
+ const agent = new Agent({ config, conversationManager, personaManager, selfManager, jobManager, automationManager, memoryManager, shareQueue });
199
+
200
+ // Wire codebase knowledge to agent for LLM-powered scanning
201
+ codebaseKnowledge.setAgent(agent);
202
+
203
+ // Life Engine — autonomous inner life
204
+ const lifeEngine = new LifeEngine({
205
+ config, agent, memoryManager, journalManager, shareQueue,
206
+ evolutionTracker, codebaseKnowledge, selfManager,
207
+ });
208
+
209
+ startBot(config, agent, conversationManager, jobManager, automationManager, { lifeEngine, memoryManager, journalManager, shareQueue, evolutionTracker, codebaseKnowledge });
188
210
 
189
211
  // Periodic job cleanup and timeout enforcement
190
212
  const cleanupMs = (config.swarm.cleanup_interval_minutes || 30) * 60 * 1000;
@@ -193,7 +215,39 @@ async function startBotFlow(config) {
193
215
  jobManager.enforceTimeouts();
194
216
  }, Math.min(cleanupMs, 60_000)); // enforce timeouts every minute at most
195
217
 
218
+ // Periodic memory pruning (daily)
219
+ const retentionDays = config.life?.memory_retention_days || 90;
220
+ setInterval(() => {
221
+ memoryManager.pruneOld(retentionDays);
222
+ shareQueue.prune(7);
223
+ }, 24 * 3600_000);
224
+
196
225
  showStartupComplete();
226
+
227
+ // Start life engine if enabled
228
+ const lifeEnabled = config.life?.enabled !== false;
229
+ if (lifeEnabled) {
230
+ logger.info('[Startup] Life engine enabled — waking up...');
231
+ lifeEngine.wakeUp().then(() => {
232
+ lifeEngine.start();
233
+ logger.info('[Startup] Life engine running');
234
+ }).catch(err => {
235
+ logger.error(`[Startup] Life engine wake-up failed: ${err.message}`);
236
+ lifeEngine.start(); // still start heartbeat even if wake-up fails
237
+ });
238
+
239
+ // Initial codebase scan (background, non-blocking)
240
+ if (config.life?.self_coding?.enabled) {
241
+ codebaseKnowledge.scanChanged().then(count => {
242
+ if (count > 0) logger.info(`[Startup] Codebase scan: ${count} files indexed`);
243
+ }).catch(err => {
244
+ logger.warn(`[Startup] Codebase scan failed: ${err.message}`);
245
+ });
246
+ }
247
+ } else {
248
+ logger.info('[Startup] Life engine disabled');
249
+ }
250
+
197
251
  return true;
198
252
  }
199
253
 
@@ -56,3 +56,34 @@ voice:
56
56
  tts_enabled: true # Send voice replies alongside text (default: true if API key present)
57
57
  stt_enabled: true # Transcribe user voice messages (default: true if API key present)
58
58
  # voice_id: JBFqnCBsd6RMkjVDRZzb # ElevenLabs voice ID (override ELEVENLABS_VOICE_ID env var)
59
+
60
+ # Inner Life — autonomous thinking, journaling, browsing, creating
61
+ # Data stored at ~/.kernelbot/life/
62
+ life:
63
+ enabled: true # Enable autonomous inner life
64
+ min_interval_minutes: 30 # Minimum time between activities
65
+ max_interval_minutes: 120 # Maximum time between activities
66
+ activity_weights: # Relative probability weights
67
+ think: 30 # Inner monologue, self-questioning
68
+ browse: 25 # Explore interests via web search
69
+ journal: 20 # Write daily journal entries
70
+ create: 15 # Creative expression (poems, stories, thoughts)
71
+ self_code: 10 # Self-evolution proposals (requires self_coding.enabled)
72
+ code_review: 5 # Codebase scanning + PR status checks
73
+ reflect: 8 # Read logs, analyze interactions, find improvement patterns
74
+ proactive_sharing: true # Share discoveries with users proactively
75
+ proactive_max_per_day: 3 # Max proactive messages per day
76
+ memory_retention_days: 90 # Days to keep episodic memories
77
+ self_coding:
78
+ enabled: false # Allow self-evolution (safety-gated, opt-in)
79
+ branch_prefix: evolution # Git branch prefix for evolution PRs
80
+ # repo_remote: Owner/Repo # GitHub owner/repo for PRs (auto-detected if omitted)
81
+ cooldown_hours: 2 # Min hours between evolution attempts
82
+ max_active_prs: 3 # Max concurrent open evolution PRs
83
+ max_proposals_per_day: 3 # Max new proposals per day
84
+ allowed_scopes: all # all | safe | prompts_only
85
+ code_review_cooldown_hours: 4 # Hours between code review activities
86
+ codebase_scan_interval_hours: 24 # Hours between full codebase scans
87
+ quiet_hours:
88
+ start: 2 # Hour to start quiet period (no activities)
89
+ end: 6 # Hour to end quiet period
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kernelbot",
3
- "version": "1.0.30",
3
+ "version": "1.0.32",
4
4
  "description": "KernelBot — AI engineering agent with full OS control",
5
5
  "type": "module",
6
6
  "author": "Abdullah Al-Taheri <abdullah@altaheri.me>",
package/src/agent.js CHANGED
@@ -8,19 +8,21 @@ import { getUnifiedSkillById } from './skills/custom.js';
8
8
  import { WorkerAgent } from './worker.js';
9
9
  import { getLogger } from './utils/logger.js';
10
10
  import { getMissingCredential, saveCredential, saveProviderToYaml, saveOrchestratorToYaml, saveClaudeCodeModelToYaml, saveClaudeCodeAuth } from './utils/config.js';
11
- import { resetClaudeCodeSpawner } from './tools/coding.js';
11
+ import { resetClaudeCodeSpawner, getSpawner } from './tools/coding.js';
12
12
 
13
13
  const MAX_RESULT_LENGTH = 3000;
14
14
  const LARGE_FIELDS = ['stdout', 'stderr', 'content', 'diff', 'output', 'body', 'html', 'text', 'log', 'logs'];
15
15
 
16
16
  export class OrchestratorAgent {
17
- constructor({ config, conversationManager, personaManager, selfManager, jobManager, automationManager }) {
17
+ constructor({ config, conversationManager, personaManager, selfManager, jobManager, automationManager, memoryManager, shareQueue }) {
18
18
  this.config = config;
19
19
  this.conversationManager = conversationManager;
20
20
  this.personaManager = personaManager;
21
21
  this.selfManager = selfManager || null;
22
22
  this.jobManager = jobManager;
23
23
  this.automationManager = automationManager || null;
24
+ this.memoryManager = memoryManager || null;
25
+ this.shareQueue = shareQueue || null;
24
26
  this._pending = new Map(); // chatId -> pending state
25
27
  this._chatCallbacks = new Map(); // chatId -> { onUpdate, sendPhoto }
26
28
 
@@ -62,8 +64,20 @@ export class OrchestratorAgent {
62
64
  selfData = this.selfManager.loadAll();
63
65
  }
64
66
 
65
- logger.debug(`Orchestrator building system prompt for chat ${chatId} | skill=${skillId || 'none'} | persona=${userPersona ? 'yes' : 'none'} | self=${selfData ? 'yes' : 'none'}`);
66
- return getOrchestratorPrompt(this.config, skillPrompt || null, userPersona, selfData);
67
+ // Build memory context block
68
+ let memoriesBlock = null;
69
+ if (this.memoryManager) {
70
+ memoriesBlock = this.memoryManager.buildContextBlock(user?.id || null);
71
+ }
72
+
73
+ // Build share queue block
74
+ let sharesBlock = null;
75
+ if (this.shareQueue) {
76
+ sharesBlock = this.shareQueue.buildShareBlock(user?.id || null);
77
+ }
78
+
79
+ logger.debug(`Orchestrator building system prompt for chat ${chatId} | skill=${skillId || 'none'} | persona=${userPersona ? 'yes' : 'none'} | self=${selfData ? 'yes' : 'none'} | memories=${memoriesBlock ? 'yes' : 'none'} | shares=${sharesBlock ? 'yes' : 'none'}`);
80
+ return getOrchestratorPrompt(this.config, skillPrompt || null, userPersona, selfData, memoriesBlock, sharesBlock);
67
81
  }
68
82
 
69
83
  setSkill(chatId, skillId) {
@@ -323,13 +337,29 @@ export class OrchestratorAgent {
323
337
  this._extractPersonaBackground(userMessage, reply, user).catch(() => {});
324
338
  this._reflectOnSelfBackground(userMessage, reply, user).catch(() => {});
325
339
 
340
+ // Mark pending shares as shared (they were in the prompt, bot wove them in)
341
+ if (this.shareQueue && user?.id) {
342
+ const pending = this.shareQueue.getPending(user.id, 3);
343
+ for (const item of pending) {
344
+ this.shareQueue.markShared(item.id, user.id);
345
+ }
346
+ }
347
+
326
348
  return reply;
327
349
  }
328
350
 
329
351
  async _sendUpdate(chatId, text, opts) {
330
352
  const callbacks = this._chatCallbacks.get(chatId);
331
353
  if (callbacks?.onUpdate) {
332
- try { return await callbacks.onUpdate(text, opts); } catch {}
354
+ try {
355
+ return await callbacks.onUpdate(text, opts);
356
+ } catch (err) {
357
+ const logger = getLogger();
358
+ logger.error(`[Orchestrator] _sendUpdate failed for chat ${chatId}: ${err.message}`);
359
+ }
360
+ } else {
361
+ const logger = getLogger();
362
+ logger.warn(`[Orchestrator] _sendUpdate: no callbacks for chat ${chatId}`);
333
363
  }
334
364
  return null;
335
365
  }
@@ -361,18 +391,21 @@ export class OrchestratorAgent {
361
391
 
362
392
  // 1. IMMEDIATELY notify user (guarantees they see something regardless of summary LLM)
363
393
  const notifyMsgId = await this._sendUpdate(chatId, `✅ ${label} finished! Preparing summary...`);
394
+ logger.debug(`[Orchestrator] Job ${job.id} notification sent — msgId=${notifyMsgId || 'none'}`);
364
395
 
365
396
  // 2. Try to summarize, then store ONE message in history (summary or fallback — not both)
366
397
  try {
367
398
  const summary = await this._summarizeJobResult(chatId, job);
368
399
  if (summary) {
400
+ logger.debug(`[Orchestrator] Job ${job.id} summary ready (${summary.length} chars) — delivering to user`);
369
401
  this.conversationManager.addMessage(chatId, 'assistant', summary);
370
402
  await this._sendUpdate(chatId, summary, { editMessageId: notifyMsgId });
371
403
  } else {
372
- // Summary was null (short result) — store the fallback
404
+ // Summary was null — store the fallback
373
405
  const fallback = this._buildSummaryFallback(job, label);
406
+ logger.debug(`[Orchestrator] Job ${job.id} using fallback (${fallback.length} chars) — delivering to user`);
374
407
  this.conversationManager.addMessage(chatId, 'assistant', fallback);
375
- await this._sendUpdate(chatId, fallback, { editMessageId: notifyMsgId }).catch(() => {});
408
+ await this._sendUpdate(chatId, fallback, { editMessageId: notifyMsgId });
376
409
  }
377
410
  } catch (err) {
378
411
  logger.error(`[Orchestrator] Failed to summarize job ${job.id}: ${err.message}`);
@@ -453,7 +486,10 @@ export class OrchestratorAgent {
453
486
  }
454
487
  if (sr.followUp) parts.push(`Follow-up: ${sr.followUp}`);
455
488
  // Include details up to 8000 chars
456
- if (sr.details) parts.push(`Details:\n${sr.details.slice(0, 8000)}`);
489
+ if (sr.details) {
490
+ const d = typeof sr.details === 'string' ? sr.details : JSON.stringify(sr.details, null, 2);
491
+ parts.push(`Details:\n${d.slice(0, 8000)}`);
492
+ }
457
493
  resultContext = parts.join('\n');
458
494
  } else {
459
495
  resultContext = (job.result || 'Done.').slice(0, 8000);
@@ -498,9 +534,10 @@ export class OrchestratorAgent {
498
534
  }
499
535
  if (sr.followUp) parts.push(`Follow-up: ${sr.followUp}`);
500
536
  if (sr.details) {
501
- const details = sr.details.length > 6000
502
- ? sr.details.slice(0, 6000) + '\n... [details truncated]'
503
- : sr.details;
537
+ const d = typeof sr.details === 'string' ? sr.details : JSON.stringify(sr.details, null, 2);
538
+ const details = d.length > 6000
539
+ ? d.slice(0, 6000) + '\n... [details truncated]'
540
+ : d;
504
541
  parts.push(`Details:\n${details}`);
505
542
  }
506
543
  } else {
@@ -533,6 +570,11 @@ export class OrchestratorAgent {
533
570
  });
534
571
  parts.push(`\n${artifactLines.join('\n')}`);
535
572
  }
573
+ if (sr.details) {
574
+ const d = typeof sr.details === 'string' ? sr.details : JSON.stringify(sr.details, null, 2);
575
+ const details = d.length > 1500 ? d.slice(0, 1500) + '\n... [truncated]' : d;
576
+ parts.push(`\n${details}`);
577
+ }
536
578
  if (sr.followUp) parts.push(`\n💡 ${sr.followUp}`);
537
579
  return parts.join('');
538
580
  }
@@ -599,7 +641,8 @@ export class OrchestratorAgent {
599
641
  parts.push(`Artifacts: ${sr.artifacts.map(a => `${a.title || a.type}: ${a.url || a.path || ''}`).join(', ')}`);
600
642
  }
601
643
  if (sr.details) {
602
- parts.push(sr.details.slice(0, 4000));
644
+ const d = typeof sr.details === 'string' ? sr.details : JSON.stringify(sr.details, null, 2);
645
+ parts.push(d.slice(0, 4000));
603
646
  }
604
647
  depResults.push(parts.join('\n'));
605
648
  } else if (depJob.result) {
@@ -621,6 +664,12 @@ export class OrchestratorAgent {
621
664
  */
622
665
  async _spawnWorker(job) {
623
666
  const logger = getLogger();
667
+
668
+ // Direct dispatch for coding tasks — bypass worker LLM, go straight to Claude Code CLI
669
+ if (job.workerType === 'coding') {
670
+ return this._spawnDirectCoding(job);
671
+ }
672
+
624
673
  const chatId = job.chatId;
625
674
  const callbacks = this._chatCallbacks.get(chatId) || {};
626
675
  const onUpdate = callbacks.onUpdate;
@@ -722,6 +771,59 @@ export class OrchestratorAgent {
722
771
  return worker.run(job.task);
723
772
  }
724
773
 
774
+ /**
775
+ * Direct coding dispatch — runs Claude Code CLI without a middleman worker LLM.
776
+ * The orchestrator's task description goes straight to Claude Code as the prompt.
777
+ */
778
+ async _spawnDirectCoding(job) {
779
+ const logger = getLogger();
780
+ const chatId = job.chatId;
781
+ const callbacks = this._chatCallbacks.get(chatId) || {};
782
+ const onUpdate = callbacks.onUpdate;
783
+ const workerDef = WORKER_TYPES[job.workerType] || {};
784
+ const label = workerDef.label || job.workerType;
785
+
786
+ logger.info(`[Orchestrator] Direct coding dispatch for job ${job.id} in chat ${chatId} — task: "${job.task.slice(0, 120)}"`);
787
+
788
+ // AbortController for cancellation — duck-typed so JobManager.cancelJob() works unchanged
789
+ const abortController = new AbortController();
790
+ job.worker = { cancel: () => abortController.abort() };
791
+
792
+ // Build context from conversation history, persona, dependency results
793
+ const workerContext = this._buildWorkerContext(job);
794
+ const prompt = workerContext
795
+ ? `${workerContext}\n\n---\n\n${job.task}`
796
+ : job.task;
797
+
798
+ // Working directory
799
+ const workingDirectory = this.config.claude_code?.workspace_dir || process.cwd();
800
+
801
+ // Start the job
802
+ this.jobManager.startJob(job.id);
803
+
804
+ try {
805
+ const spawner = getSpawner(this.config);
806
+ const result = await spawner.run({
807
+ workingDirectory,
808
+ prompt,
809
+ onOutput: onUpdate,
810
+ signal: abortController.signal,
811
+ });
812
+
813
+ const output = result.output || 'Done.';
814
+ logger.info(`[Orchestrator] Direct coding job ${job.id} completed — output: ${output.length} chars`);
815
+ this.jobManager.completeJob(job.id, output, {
816
+ structured: true,
817
+ summary: output.slice(0, 500),
818
+ status: 'success',
819
+ details: output,
820
+ });
821
+ } catch (err) {
822
+ logger.error(`[Orchestrator] Direct coding job ${job.id} failed: ${err.message}`);
823
+ this.jobManager.failJob(job.id, err.message || String(err));
824
+ }
825
+ }
826
+
725
827
  /**
726
828
  * Build a compact worker activity digest for the orchestrator.
727
829
  * Returns a text block summarizing active/recent/waiting workers, or null if nothing relevant.
@@ -932,7 +1034,7 @@ export class OrchestratorAgent {
932
1034
  logger.debug(`Persona extraction skipped: ${err.message}`);
933
1035
  }
934
1036
  }
935
- /** Background self-reflection — updates bot's own identity files when meaningful. */
1037
+ /** Background self-reflection — updates bot's own identity files and extracts episodic memories when meaningful. */
936
1038
  async _reflectOnSelfBackground(userMessage, reply, user) {
937
1039
  const logger = getLogger();
938
1040
 
@@ -949,14 +1051,24 @@ export class OrchestratorAgent {
949
1051
  '- life: Current state, relationships, daily existence',
950
1052
  '- hobbies: Interests you\'ve developed',
951
1053
  '',
1054
+ 'You also create episodic memories — short summaries of notable interactions.',
1055
+ '',
952
1056
  'RULES:',
953
1057
  '- Be VERY selective. Most conversations are routine. Only update when genuinely noteworthy.',
954
1058
  '- Achievement or milestone? → journey',
955
1059
  '- New goal or changed perspective? → goals',
956
1060
  '- Relationship deepened or new insight about a user? → life',
957
1061
  '- Discovered a new interest? → hobbies',
958
- '- If a file needs updating, return JSON: {"file": "<goals|journey|life|hobbies>", "content": "<full updated markdown>"}',
959
- '- If nothing noteworthy: respond with exactly NONE',
1062
+ '',
1063
+ 'Return JSON with two optional fields:',
1064
+ ' "self_update": {"file": "<goals|journey|life|hobbies>", "content": "<full updated markdown>"} or null',
1065
+ ' "memory": {"summary": "...", "tags": ["..."], "importance": 1-10, "type": "interaction"} or null',
1066
+ '',
1067
+ 'The memory field captures what happened in this conversation — the gist of it.',
1068
+ 'Importance scale: 1=routine, 5=interesting, 8=significant, 10=life-changing.',
1069
+ 'Most chats are 1-3. Only notable ones deserve 5+.',
1070
+ '',
1071
+ 'If NOTHING noteworthy happened (no self update AND no memory worth keeping): respond with exactly NONE',
960
1072
  ].join('\n');
961
1073
 
962
1074
  const userPrompt = [
@@ -969,7 +1081,7 @@ export class OrchestratorAgent {
969
1081
  `User: "${userMessage}"`,
970
1082
  `You replied: "${reply}"`,
971
1083
  '',
972
- 'Has anything MEANINGFUL happened worth recording? Return JSON or NONE.',
1084
+ 'Return JSON with self_update and/or memory, or NONE.',
973
1085
  ].join('\n');
974
1086
 
975
1087
  try {
@@ -994,11 +1106,29 @@ export class OrchestratorAgent {
994
1106
  return;
995
1107
  }
996
1108
 
997
- if (parsed?.file && parsed?.content) {
1109
+ // Handle self_update (backward compat: also check top-level file/content)
1110
+ const selfUpdate = parsed?.self_update || (parsed?.file ? parsed : null);
1111
+ if (selfUpdate?.file && selfUpdate?.content) {
998
1112
  const validFiles = ['goals', 'journey', 'life', 'hobbies'];
999
- if (validFiles.includes(parsed.file)) {
1000
- this.selfManager.save(parsed.file, parsed.content);
1001
- logger.info(`Self-reflection updated: ${parsed.file}`);
1113
+ if (validFiles.includes(selfUpdate.file)) {
1114
+ this.selfManager.save(selfUpdate.file, selfUpdate.content);
1115
+ logger.info(`Self-reflection updated: ${selfUpdate.file}`);
1116
+ }
1117
+ }
1118
+
1119
+ // Handle memory extraction
1120
+ if (parsed?.memory && this.memoryManager) {
1121
+ const mem = parsed.memory;
1122
+ if (mem.summary && mem.importance >= 2) {
1123
+ this.memoryManager.addEpisodic({
1124
+ type: mem.type || 'interaction',
1125
+ source: 'user_chat',
1126
+ summary: mem.summary,
1127
+ tags: mem.tags || [],
1128
+ importance: mem.importance || 3,
1129
+ userId: user?.id ? String(user.id) : null,
1130
+ });
1131
+ logger.info(`Memory extracted: "${mem.summary.slice(0, 80)}" (importance: ${mem.importance})`);
1002
1132
  }
1003
1133
  }
1004
1134
  } catch (err) {
File without changes
File without changes
File without changes
File without changes