@timmeck/brain 3.36.55 → 3.36.57

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,117 @@
1
+ /**
2
+ * Lifecycle helpers — extracted from BrainCore.logCrash, runRetentionCleanup, cleanup, restart, stop.
3
+ * Pure extraction, no logic changes.
4
+ */
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { getLogger } from '../utils/logger.js';
8
+ // ── Standalone helpers (no BrainCore refs needed) ──────────
9
+ export function logCrash(config, type, err) {
10
+ if (!config)
11
+ return;
12
+ const crashLog = path.join(path.dirname(config.dbPath), 'crashes.log');
13
+ const entry = `[${new Date().toISOString()}] ${type}: ${err.message}\n${err.stack ?? ''}\n\n`;
14
+ try {
15
+ // Rotate crash log if > 5MB (max 1 rotation = 10MB total)
16
+ try {
17
+ const stat = fs.statSync(crashLog);
18
+ if (stat.size > 5 * 1024 * 1024) {
19
+ const rotated = crashLog.replace('.log', '.1.log');
20
+ try {
21
+ fs.unlinkSync(rotated);
22
+ }
23
+ catch { /* no previous rotation */ }
24
+ fs.renameSync(crashLog, rotated);
25
+ }
26
+ }
27
+ catch { /* file doesn't exist yet */ }
28
+ fs.appendFileSync(crashLog, entry);
29
+ }
30
+ catch { /* best effort */ }
31
+ }
32
+ export function runRetentionCleanup(db, config) {
33
+ const logger = getLogger();
34
+ try {
35
+ const now = Date.now();
36
+ const errorCutoff = new Date(now - config.retention.errorDays * 86_400_000).toISOString();
37
+ const insightCutoff = new Date(now - config.retention.insightDays * 2 * 86_400_000).toISOString();
38
+ // Delete resolved errors older than retention period
39
+ const errResult = db.prepare("DELETE FROM errors WHERE status = 'resolved' AND created_at < ?").run(errorCutoff);
40
+ // Delete inactive insights older than 2× insightDays
41
+ const insResult = db.prepare("DELETE FROM insights WHERE status = 'inactive' AND created_at < ?").run(insightCutoff);
42
+ if (Number(errResult.changes) > 0 || Number(insResult.changes) > 0) {
43
+ logger.info(`[retention] Cleaned up ${errResult.changes} old errors, ${insResult.changes} old insights`);
44
+ }
45
+ // Optimize DB
46
+ db.pragma('optimize');
47
+ logger.debug('[retention] DB optimized');
48
+ }
49
+ catch (err) {
50
+ logger.warn(`[retention] Cleanup failed (non-critical): ${err.message}`);
51
+ }
52
+ }
53
+ export function cleanup(refs) {
54
+ if (refs.cleanupTimer) {
55
+ clearInterval(refs.cleanupTimer);
56
+ refs.cleanupTimer = null;
57
+ }
58
+ if (refs.retentionTimer) {
59
+ clearInterval(refs.retentionTimer);
60
+ refs.retentionTimer = null;
61
+ }
62
+ refs.borgSync?.stop();
63
+ // Stop messaging bots
64
+ refs.telegramBot?.stop().catch(() => { });
65
+ refs.discordBot?.stop().catch(() => { });
66
+ refs.peerNetwork?.stopDiscovery();
67
+ // Unload all plugins gracefully
68
+ if (refs.pluginRegistry?.size) {
69
+ for (const p of refs.pluginRegistry.list()) {
70
+ refs.pluginRegistry.unloadPlugin(p.name).catch(() => { });
71
+ }
72
+ }
73
+ refs.subscriptionManager?.disconnectAll();
74
+ refs.attentionEngine?.stop();
75
+ refs.commandCenter?.stop();
76
+ refs.orchestrator?.stop();
77
+ refs.researchScheduler?.stop();
78
+ refs.researchEngine?.stop();
79
+ refs.embeddingEngine?.stop();
80
+ refs.learningEngine?.stop();
81
+ refs.mcpHttpServer?.stop();
82
+ refs.apiServer?.stop();
83
+ refs.ipcServer?.stop();
84
+ refs.db?.close();
85
+ }
86
+ // ── Crash recovery: process-level error handlers ──────────
87
+ export function setupCrashRecovery(config, onRestart) {
88
+ const logger = getLogger();
89
+ process.on('uncaughtException', (err) => {
90
+ // EPIPE = writing to closed stdout/stderr (daemon mode) — ignore silently
91
+ if (err.code === 'EPIPE')
92
+ return;
93
+ try {
94
+ logger.error('Uncaught exception', { error: err.message, stack: err.stack });
95
+ }
96
+ catch { /* logger may be broken */ }
97
+ logCrash(config, 'uncaughtException', err);
98
+ // Don't restart on port conflicts — it will just loop
99
+ if (err.code === 'EADDRINUSE') {
100
+ try {
101
+ logger.error('Port conflict during restart — stopping to prevent crash loop');
102
+ }
103
+ catch { /* ignore */ }
104
+ return;
105
+ }
106
+ onRestart();
107
+ });
108
+ process.on('unhandledRejection', (reason) => {
109
+ try {
110
+ logger.error('Unhandled rejection', { reason: String(reason) });
111
+ }
112
+ catch { /* logger may be broken */ }
113
+ logCrash(config, 'unhandledRejection', reason instanceof Error ? reason : new Error(String(reason)));
114
+ onRestart();
115
+ });
116
+ }
117
+ //# sourceMappingURL=lifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../../src/init/lifecycle.ts"],"names":[],"mappings":"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"}
@@ -33,6 +33,11 @@ export class IpcRouter {
33
33
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
34
  const { source, event, data } = params;
35
35
  logger.info(`Cross-brain event from ${source}: ${event}`);
36
+ // Route teaching lessons to TeachingProtocol
37
+ if (event === 'teaching.learn' && this.services.teachingProtocol) {
38
+ this.services.teachingProtocol.learn(data);
39
+ logger.info(`[cross-brain] Learned lesson from ${source}`);
40
+ }
36
41
  manager.handleIncomingEvent(source, event, data);
37
42
  return { received: true, source, event };
38
43
  });
@@ -506,6 +511,8 @@ export class IpcRouter {
506
511
  throw new Error('Prediction engine not available'); return { resolved: s.predictionEngine.resolveExpired() }; }],
507
512
  ['predict.record', (params) => { if (!s.predictionEngine)
508
513
  throw new Error('Prediction engine not available'); s.predictionEngine.recordMetric(p(params).metric, p(params).value, p(params)?.domain); return { recorded: true }; }],
514
+ ['predict.calibration', () => { if (!s.predictionEngine)
515
+ throw new Error('Prediction engine not available'); return s.predictionEngine.getCalibration(); }],
509
516
  // ─── Consciousness ──────────────────────────────────────
510
517
  ['consciousness.status', () => { if (!s.thoughtStream)
511
518
  throw new Error('ThoughtStream not available'); return { ...s.thoughtStream.getStats(), engines: s.thoughtStream.getEngineActivity(), clients: s.consciousnessServer?.getClientCount() ?? 0 }; }],
@@ -1444,6 +1451,17 @@ export class IpcRouter {
1444
1451
  throw new Error(`Agent not found: ${pp.name}`); return agent.execute(pp.input); }],
1445
1452
  ['subagent.presets', () => { if (!s.subAgentFactory)
1446
1453
  throw new Error('SubAgentFactory not available'); return s.subAgentFactory.getPresets(); }],
1454
+ // ─── Self-Improvement Desires ─────────────────────────────
1455
+ ['desires.suggestions', () => { if (!s.orchestrator)
1456
+ throw new Error('Orchestrator not available'); return s.orchestrator.generateSelfImprovementSuggestions(); }],
1457
+ ['desires.structured', () => { if (!s.orchestrator)
1458
+ throw new Error('Orchestrator not available'); return s.orchestrator.getDesires(); }],
1459
+ ['desires.status', () => {
1460
+ if (!s.orchestrator)
1461
+ throw new Error('Orchestrator not available');
1462
+ const desires = s.orchestrator.getDesires();
1463
+ return { count: desires.length, topPriority: desires[0]?.priority ?? 0, top: desires[0]?.suggestion ?? 'none' };
1464
+ }],
1447
1465
  // Status (cross-brain)
1448
1466
  ['status', () => ({
1449
1467
  name: 'brain',