@timmeck/brain 3.36.54 → 3.36.56

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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"}
@@ -160,6 +160,9 @@ export interface Services {
160
160
  contentForge?: import('@timmeck/brain-core').ContentForge;
161
161
  codeForge?: import('@timmeck/brain-core').CodeForge;
162
162
  strategyForge?: import('@timmeck/brain-core').StrategyForge;
163
+ signalRouter?: import('@timmeck/brain-core').CrossBrainSignalRouter;
164
+ chatEngine?: import('@timmeck/brain-core').ChatEngine;
165
+ subAgentFactory?: import('@timmeck/brain-core').SubAgentFactory;
163
166
  }
164
167
  type MethodHandler = (params: unknown) => unknown | Promise<unknown>;
165
168
  export declare class IpcRouter {
@@ -1416,6 +1416,34 @@ export class IpcRouter {
1416
1416
  throw new Error('StrategyForge not available'); s.strategyForge.retire(p(params).id, p(params).reason); return { ok: true }; }],
1417
1417
  ['strategy.status', () => { if (!s.strategyForge)
1418
1418
  throw new Error('StrategyForge not available'); return s.strategyForge.getStatus(); }],
1419
+ // ─── Cross-Brain Signals ────────────────────────────────────
1420
+ ['signal.cross.emit', async (params) => { if (!s.signalRouter)
1421
+ throw new Error('SignalRouter not available'); return { signalId: await s.signalRouter.emit(p(params)) }; }],
1422
+ ['signal.cross.history', (params) => { if (!s.signalRouter)
1423
+ throw new Error('SignalRouter not available'); return s.signalRouter.getHistory(p(params).limit); }],
1424
+ ['signal.cross.status', () => { if (!s.signalRouter)
1425
+ throw new Error('SignalRouter not available'); return s.signalRouter.getStatus(); }],
1426
+ // ─── Chat Interface ──────────────────────────────────────────
1427
+ ['chat.message', async (params) => { if (!s.chatEngine)
1428
+ throw new Error('ChatEngine not available'); return s.chatEngine.processMessage(p(params).sessionId ?? 'default', p(params).content); }],
1429
+ ['chat.history', (params) => { if (!s.chatEngine)
1430
+ throw new Error('ChatEngine not available'); return s.chatEngine.getHistory(p(params).sessionId ?? 'default', p(params).limit); }],
1431
+ ['chat.status', () => { if (!s.chatEngine)
1432
+ throw new Error('ChatEngine not available'); return s.chatEngine.getStatus(); }],
1433
+ // ─── Sub-Agent Factory ───────────────────────────────────────
1434
+ ['subagent.list', () => { if (!s.subAgentFactory)
1435
+ throw new Error('SubAgentFactory not available'); return s.subAgentFactory.getStatus(); }],
1436
+ ['subagent.create', (params) => { if (!s.subAgentFactory)
1437
+ throw new Error('SubAgentFactory not available'); const pp = p(params); if (pp.preset)
1438
+ return s.subAgentFactory.createFromPreset(pp.preset, pp.name)?.getStatus(); return s.subAgentFactory.create(pp)?.getStatus(); }],
1439
+ ['subagent.status', (params) => { if (!s.subAgentFactory)
1440
+ throw new Error('SubAgentFactory not available'); const agent = s.subAgentFactory.get(p(params).name); if (!agent)
1441
+ throw new Error(`Agent not found: ${p(params).name}`); return agent.getStatus(); }],
1442
+ ['subagent.run', async (params) => { if (!s.subAgentFactory)
1443
+ throw new Error('SubAgentFactory not available'); const pp = p(params); const agent = s.subAgentFactory.get(pp.name); if (!agent)
1444
+ throw new Error(`Agent not found: ${pp.name}`); return agent.execute(pp.input); }],
1445
+ ['subagent.presets', () => { if (!s.subAgentFactory)
1446
+ throw new Error('SubAgentFactory not available'); return s.subAgentFactory.getPresets(); }],
1419
1447
  // Status (cross-brain)
1420
1448
  ['status', () => ({
1421
1449
  name: 'brain',