@timmeck/brain-core 2.2.0 → 2.4.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/api/middleware.js +1 -0
- package/dist/api/middleware.js.map +1 -1
- package/dist/backup/service.d.ts +48 -0
- package/dist/backup/service.js +153 -0
- package/dist/backup/service.js.map +1 -0
- package/dist/causal/engine.d.ts +98 -0
- package/dist/causal/engine.js +265 -0
- package/dist/causal/engine.js.map +1 -0
- package/dist/export/service.d.ts +42 -0
- package/dist/export/service.js +118 -0
- package/dist/export/service.js.map +1 -0
- package/dist/hypothesis/engine.d.ts +104 -0
- package/dist/hypothesis/engine.js +386 -0
- package/dist/hypothesis/engine.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -1
- package/dist/meta-learning/engine.d.ts +108 -0
- package/dist/meta-learning/engine.js +275 -0
- package/dist/meta-learning/engine.js.map +1 -0
- package/dist/webhooks/service.d.ts +65 -0
- package/dist/webhooks/service.js +157 -0
- package/dist/webhooks/service.js.map +1 -0
- package/package.json +8 -2
- package/tsconfig.tsbuildinfo +1 -0
package/dist/index.js
CHANGED
|
@@ -45,4 +45,16 @@ export { CrossBrainSubscriptionManager } from './cross-brain/subscription.js';
|
|
|
45
45
|
export { CrossBrainCorrelator } from './cross-brain/correlator.js';
|
|
46
46
|
// ── Ecosystem ──────────────────────────────────────────────
|
|
47
47
|
export { EcosystemService } from './ecosystem/service.js';
|
|
48
|
+
// ── Webhooks ──────────────────────────────────────────────
|
|
49
|
+
export { WebhookService, runWebhookMigration } from './webhooks/service.js';
|
|
50
|
+
// ── Export ────────────────────────────────────────────────
|
|
51
|
+
export { ExportService } from './export/service.js';
|
|
52
|
+
// ── Backup ────────────────────────────────────────────────
|
|
53
|
+
export { BackupService } from './backup/service.js';
|
|
54
|
+
// ── Meta-Learning ────────────────────────────────────────
|
|
55
|
+
export { MetaLearningEngine, runMetaLearningMigration } from './meta-learning/engine.js';
|
|
56
|
+
// ── Causal Inference ─────────────────────────────────────
|
|
57
|
+
export { CausalGraph, runCausalMigration } from './causal/engine.js';
|
|
58
|
+
// ── Hypothesis Engine ────────────────────────────────────
|
|
59
|
+
export { HypothesisEngine, runHypothesisMigration } from './hypothesis/engine.js';
|
|
48
60
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,8DAA8D;AAC9D,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEzE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD,8DAA8D;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,8DAA8D;AAC9D,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErE,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,aAAa,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAElH,8DAA8D;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGrD,8DAA8D;AAC9D,OAAO,EAAE,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEtH,8DAA8D;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAG3F,8DAA8D;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEvD,8DAA8D;AAC9D,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAQ/D,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAEnE,6DAA6D;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAG/D,2DAA2D;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAU7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAElE,4DAA4D;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAG/D,8DAA8D;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAEpE,OAAO,EAAE,6BAA6B,EAAE,MAAM,+BAA+B,CAAC;AAE9E,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAGnE,8DAA8D;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type Database from 'better-sqlite3';
|
|
2
|
+
export interface HyperParameter {
|
|
3
|
+
name: string;
|
|
4
|
+
value: number;
|
|
5
|
+
min: number;
|
|
6
|
+
max: number;
|
|
7
|
+
step: number;
|
|
8
|
+
}
|
|
9
|
+
export interface LearningSnapshot {
|
|
10
|
+
id?: number;
|
|
11
|
+
cycle: number;
|
|
12
|
+
params: Record<string, number>;
|
|
13
|
+
metrics: Record<string, number>;
|
|
14
|
+
score: number;
|
|
15
|
+
created_at?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface ParameterRecommendation {
|
|
18
|
+
name: string;
|
|
19
|
+
currentValue: number;
|
|
20
|
+
recommendedValue: number;
|
|
21
|
+
expectedImprovement: number;
|
|
22
|
+
confidence: number;
|
|
23
|
+
evidence: number;
|
|
24
|
+
}
|
|
25
|
+
export interface MetaLearningStatus {
|
|
26
|
+
totalSnapshots: number;
|
|
27
|
+
totalOptimizations: number;
|
|
28
|
+
bestScore: number;
|
|
29
|
+
worstScore: number;
|
|
30
|
+
currentScore: number;
|
|
31
|
+
trend: 'improving' | 'stable' | 'declining';
|
|
32
|
+
recommendations: ParameterRecommendation[];
|
|
33
|
+
}
|
|
34
|
+
export declare function runMetaLearningMigration(db: Database.Database): void;
|
|
35
|
+
/**
|
|
36
|
+
* Meta-Learning Engine: observes learning cycle outcomes over time
|
|
37
|
+
* and auto-tunes hyperparameters using gradient-free optimization.
|
|
38
|
+
*
|
|
39
|
+
* Research approach: Bayesian-inspired parameter search.
|
|
40
|
+
* - After each learning cycle, record a snapshot (params + metrics + effectiveness score)
|
|
41
|
+
* - Periodically analyze: which parameter configurations produced the best scores?
|
|
42
|
+
* - Use perturbation: try small changes to parameters, measure improvement, keep what works
|
|
43
|
+
* - Implements "explore vs exploit": 80% exploit best known, 20% explore new configurations
|
|
44
|
+
*/
|
|
45
|
+
export declare class MetaLearningEngine {
|
|
46
|
+
private db;
|
|
47
|
+
private params;
|
|
48
|
+
private logger;
|
|
49
|
+
private cycleCount;
|
|
50
|
+
private analyzeInterval;
|
|
51
|
+
private explorationRate;
|
|
52
|
+
constructor(db: Database.Database, params: HyperParameter[], config?: {
|
|
53
|
+
analyzeInterval?: number;
|
|
54
|
+
explorationRate?: number;
|
|
55
|
+
});
|
|
56
|
+
/**
|
|
57
|
+
* Record a learning cycle's outcome. Called after each learning cycle.
|
|
58
|
+
* @param metrics - the raw metrics from the cycle (e.g. newPatterns, updatedRules, prunedRules)
|
|
59
|
+
* @param score - a composite effectiveness score (higher = better)
|
|
60
|
+
*/
|
|
61
|
+
recordSnapshot(metrics: Record<string, number>, score: number): LearningSnapshot;
|
|
62
|
+
/**
|
|
63
|
+
* Analyze snapshots and recommend parameter changes.
|
|
64
|
+
* Uses a simplified approach inspired by Bayesian optimization:
|
|
65
|
+
* - Group snapshots by parameter ranges
|
|
66
|
+
* - Find which ranges produced the best average scores
|
|
67
|
+
* - Recommend moving towards those ranges
|
|
68
|
+
*/
|
|
69
|
+
analyze(): ParameterRecommendation[];
|
|
70
|
+
/**
|
|
71
|
+
* Apply recommendations: perturb parameters towards better configurations.
|
|
72
|
+
* Returns the parameters that were changed.
|
|
73
|
+
*/
|
|
74
|
+
optimize(): ParameterRecommendation[];
|
|
75
|
+
/**
|
|
76
|
+
* Run analysis + optimization if it's time. Call this after every learning cycle.
|
|
77
|
+
*/
|
|
78
|
+
step(metrics: Record<string, number>, score: number): {
|
|
79
|
+
snapshot: LearningSnapshot;
|
|
80
|
+
optimized: ParameterRecommendation[];
|
|
81
|
+
};
|
|
82
|
+
/** Get current parameter values. */
|
|
83
|
+
getParams(): Record<string, number>;
|
|
84
|
+
/** Update a parameter value externally (e.g. user override). */
|
|
85
|
+
setParam(name: string, value: number): boolean;
|
|
86
|
+
/** Get learning effectiveness status. */
|
|
87
|
+
getStatus(): MetaLearningStatus;
|
|
88
|
+
/** Get optimization history. */
|
|
89
|
+
getHistory(limit?: number): Array<{
|
|
90
|
+
param_name: string;
|
|
91
|
+
old_value: number;
|
|
92
|
+
new_value: number;
|
|
93
|
+
reason: string;
|
|
94
|
+
improvement: number | null;
|
|
95
|
+
created_at: string;
|
|
96
|
+
}>;
|
|
97
|
+
private getSnapshots;
|
|
98
|
+
/**
|
|
99
|
+
* Analyze a single parameter's effect on learning effectiveness.
|
|
100
|
+
*
|
|
101
|
+
* Algorithm:
|
|
102
|
+
* 1. Divide the parameter's range into bins
|
|
103
|
+
* 2. For each bin, calculate the average score of snapshots in that bin
|
|
104
|
+
* 3. Find the bin with the highest average score
|
|
105
|
+
* 4. Recommend moving towards the center of that bin
|
|
106
|
+
*/
|
|
107
|
+
private analyzeParameter;
|
|
108
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { getLogger } from '../utils/logger.js';
|
|
2
|
+
// ── Migration ───────────────────────────────────────────
|
|
3
|
+
export function runMetaLearningMigration(db) {
|
|
4
|
+
db.exec(`
|
|
5
|
+
CREATE TABLE IF NOT EXISTS meta_learning_snapshots (
|
|
6
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
7
|
+
cycle INTEGER NOT NULL,
|
|
8
|
+
params TEXT NOT NULL,
|
|
9
|
+
metrics TEXT NOT NULL,
|
|
10
|
+
score REAL NOT NULL,
|
|
11
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
CREATE TABLE IF NOT EXISTS meta_learning_optimizations (
|
|
15
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
16
|
+
param_name TEXT NOT NULL,
|
|
17
|
+
old_value REAL NOT NULL,
|
|
18
|
+
new_value REAL NOT NULL,
|
|
19
|
+
reason TEXT NOT NULL,
|
|
20
|
+
improvement REAL,
|
|
21
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
CREATE INDEX IF NOT EXISTS idx_meta_snapshots_score ON meta_learning_snapshots(score);
|
|
25
|
+
CREATE INDEX IF NOT EXISTS idx_meta_snapshots_cycle ON meta_learning_snapshots(cycle);
|
|
26
|
+
`);
|
|
27
|
+
}
|
|
28
|
+
// ── Engine ───────────────────────────────────────────────
|
|
29
|
+
/**
|
|
30
|
+
* Meta-Learning Engine: observes learning cycle outcomes over time
|
|
31
|
+
* and auto-tunes hyperparameters using gradient-free optimization.
|
|
32
|
+
*
|
|
33
|
+
* Research approach: Bayesian-inspired parameter search.
|
|
34
|
+
* - After each learning cycle, record a snapshot (params + metrics + effectiveness score)
|
|
35
|
+
* - Periodically analyze: which parameter configurations produced the best scores?
|
|
36
|
+
* - Use perturbation: try small changes to parameters, measure improvement, keep what works
|
|
37
|
+
* - Implements "explore vs exploit": 80% exploit best known, 20% explore new configurations
|
|
38
|
+
*/
|
|
39
|
+
export class MetaLearningEngine {
|
|
40
|
+
db;
|
|
41
|
+
params;
|
|
42
|
+
logger = getLogger();
|
|
43
|
+
cycleCount = 0;
|
|
44
|
+
analyzeInterval; // analyze every N cycles
|
|
45
|
+
explorationRate; // % of cycles spent exploring (0-1)
|
|
46
|
+
constructor(db, params, config) {
|
|
47
|
+
this.db = db;
|
|
48
|
+
this.params = params;
|
|
49
|
+
runMetaLearningMigration(db);
|
|
50
|
+
this.analyzeInterval = config?.analyzeInterval ?? 5;
|
|
51
|
+
this.explorationRate = config?.explorationRate ?? 0.2;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Record a learning cycle's outcome. Called after each learning cycle.
|
|
55
|
+
* @param metrics - the raw metrics from the cycle (e.g. newPatterns, updatedRules, prunedRules)
|
|
56
|
+
* @param score - a composite effectiveness score (higher = better)
|
|
57
|
+
*/
|
|
58
|
+
recordSnapshot(metrics, score) {
|
|
59
|
+
this.cycleCount++;
|
|
60
|
+
const currentParams = {};
|
|
61
|
+
for (const p of this.params) {
|
|
62
|
+
currentParams[p.name] = p.value;
|
|
63
|
+
}
|
|
64
|
+
this.db.prepare(`
|
|
65
|
+
INSERT INTO meta_learning_snapshots (cycle, params, metrics, score)
|
|
66
|
+
VALUES (?, ?, ?, ?)
|
|
67
|
+
`).run(this.cycleCount, JSON.stringify(currentParams), JSON.stringify(metrics), score);
|
|
68
|
+
const snapshot = {
|
|
69
|
+
cycle: this.cycleCount,
|
|
70
|
+
params: currentParams,
|
|
71
|
+
metrics,
|
|
72
|
+
score,
|
|
73
|
+
};
|
|
74
|
+
this.logger.debug(`Meta-learning snapshot #${this.cycleCount}: score=${score.toFixed(3)}`);
|
|
75
|
+
return snapshot;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Analyze snapshots and recommend parameter changes.
|
|
79
|
+
* Uses a simplified approach inspired by Bayesian optimization:
|
|
80
|
+
* - Group snapshots by parameter ranges
|
|
81
|
+
* - Find which ranges produced the best average scores
|
|
82
|
+
* - Recommend moving towards those ranges
|
|
83
|
+
*/
|
|
84
|
+
analyze() {
|
|
85
|
+
const snapshots = this.getSnapshots(50); // last 50 cycles
|
|
86
|
+
if (snapshots.length < 5)
|
|
87
|
+
return []; // need minimum data
|
|
88
|
+
const recommendations = [];
|
|
89
|
+
for (const param of this.params) {
|
|
90
|
+
const rec = this.analyzeParameter(param, snapshots);
|
|
91
|
+
if (rec)
|
|
92
|
+
recommendations.push(rec);
|
|
93
|
+
}
|
|
94
|
+
return recommendations;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Apply recommendations: perturb parameters towards better configurations.
|
|
98
|
+
* Returns the parameters that were changed.
|
|
99
|
+
*/
|
|
100
|
+
optimize() {
|
|
101
|
+
const recommendations = this.analyze();
|
|
102
|
+
const applied = [];
|
|
103
|
+
for (const rec of recommendations) {
|
|
104
|
+
if (rec.confidence < 0.3)
|
|
105
|
+
continue; // skip low-confidence recommendations
|
|
106
|
+
if (Math.abs(rec.expectedImprovement) < 0.01)
|
|
107
|
+
continue; // skip negligible improvements
|
|
108
|
+
const param = this.params.find(p => p.name === rec.name);
|
|
109
|
+
if (!param)
|
|
110
|
+
continue;
|
|
111
|
+
// Explore vs exploit: sometimes try random perturbations
|
|
112
|
+
const exploring = Math.random() < this.explorationRate;
|
|
113
|
+
let newValue;
|
|
114
|
+
if (exploring) {
|
|
115
|
+
// Random perturbation within bounds
|
|
116
|
+
const range = param.max - param.min;
|
|
117
|
+
newValue = param.value + (Math.random() - 0.5) * range * 0.2;
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
// Move towards recommended value
|
|
121
|
+
newValue = rec.recommendedValue;
|
|
122
|
+
}
|
|
123
|
+
// Clamp to bounds
|
|
124
|
+
newValue = Math.max(param.min, Math.min(param.max, newValue));
|
|
125
|
+
// Record optimization
|
|
126
|
+
this.db.prepare(`
|
|
127
|
+
INSERT INTO meta_learning_optimizations (param_name, old_value, new_value, reason, improvement)
|
|
128
|
+
VALUES (?, ?, ?, ?, ?)
|
|
129
|
+
`).run(param.name, param.value, newValue, exploring ? 'exploration' : 'exploitation', rec.expectedImprovement);
|
|
130
|
+
this.logger.info(`Meta-learning: ${param.name} ${param.value.toFixed(4)} → ${newValue.toFixed(4)} (${exploring ? 'explore' : 'exploit'}, expected +${(rec.expectedImprovement * 100).toFixed(1)}%)`);
|
|
131
|
+
param.value = newValue;
|
|
132
|
+
rec.recommendedValue = newValue;
|
|
133
|
+
applied.push(rec);
|
|
134
|
+
}
|
|
135
|
+
return applied;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Run analysis + optimization if it's time. Call this after every learning cycle.
|
|
139
|
+
*/
|
|
140
|
+
step(metrics, score) {
|
|
141
|
+
const snapshot = this.recordSnapshot(metrics, score);
|
|
142
|
+
let optimized = [];
|
|
143
|
+
if (this.cycleCount > 0 && this.cycleCount % this.analyzeInterval === 0) {
|
|
144
|
+
optimized = this.optimize();
|
|
145
|
+
}
|
|
146
|
+
return { snapshot, optimized };
|
|
147
|
+
}
|
|
148
|
+
/** Get current parameter values. */
|
|
149
|
+
getParams() {
|
|
150
|
+
const result = {};
|
|
151
|
+
for (const p of this.params) {
|
|
152
|
+
result[p.name] = p.value;
|
|
153
|
+
}
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
/** Update a parameter value externally (e.g. user override). */
|
|
157
|
+
setParam(name, value) {
|
|
158
|
+
const param = this.params.find(p => p.name === name);
|
|
159
|
+
if (!param)
|
|
160
|
+
return false;
|
|
161
|
+
param.value = Math.max(param.min, Math.min(param.max, value));
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
/** Get learning effectiveness status. */
|
|
165
|
+
getStatus() {
|
|
166
|
+
const snapshots = this.getSnapshots(100);
|
|
167
|
+
const optimizations = this.db.prepare('SELECT COUNT(*) as count FROM meta_learning_optimizations').get();
|
|
168
|
+
const scores = snapshots.map(s => s.score);
|
|
169
|
+
const currentScore = scores.length > 0 ? scores[0] : 0;
|
|
170
|
+
const bestScore = scores.length > 0 ? Math.max(...scores) : 0;
|
|
171
|
+
const worstScore = scores.length > 0 ? Math.min(...scores) : 0;
|
|
172
|
+
// Trend detection: compare average of last 5 vs previous 5
|
|
173
|
+
let trend = 'stable';
|
|
174
|
+
if (scores.length >= 10) {
|
|
175
|
+
const recent = scores.slice(0, 5).reduce((a, b) => a + b, 0) / 5;
|
|
176
|
+
const previous = scores.slice(5, 10).reduce((a, b) => a + b, 0) / 5;
|
|
177
|
+
const delta = recent - previous;
|
|
178
|
+
if (delta > 0.05)
|
|
179
|
+
trend = 'improving';
|
|
180
|
+
else if (delta < -0.05)
|
|
181
|
+
trend = 'declining';
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
totalSnapshots: snapshots.length,
|
|
185
|
+
totalOptimizations: optimizations.count,
|
|
186
|
+
bestScore,
|
|
187
|
+
worstScore,
|
|
188
|
+
currentScore,
|
|
189
|
+
trend,
|
|
190
|
+
recommendations: this.analyze(),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
/** Get optimization history. */
|
|
194
|
+
getHistory(limit = 20) {
|
|
195
|
+
return this.db.prepare('SELECT * FROM meta_learning_optimizations ORDER BY created_at DESC LIMIT ?').all(limit);
|
|
196
|
+
}
|
|
197
|
+
// ── Private ─────────────────────────────────────────
|
|
198
|
+
getSnapshots(limit) {
|
|
199
|
+
const rows = this.db.prepare('SELECT * FROM meta_learning_snapshots ORDER BY cycle DESC LIMIT ?').all(limit);
|
|
200
|
+
return rows.map(r => ({
|
|
201
|
+
id: r.id,
|
|
202
|
+
cycle: r.cycle,
|
|
203
|
+
params: JSON.parse(r.params),
|
|
204
|
+
metrics: JSON.parse(r.metrics),
|
|
205
|
+
score: r.score,
|
|
206
|
+
created_at: r.created_at,
|
|
207
|
+
}));
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Analyze a single parameter's effect on learning effectiveness.
|
|
211
|
+
*
|
|
212
|
+
* Algorithm:
|
|
213
|
+
* 1. Divide the parameter's range into bins
|
|
214
|
+
* 2. For each bin, calculate the average score of snapshots in that bin
|
|
215
|
+
* 3. Find the bin with the highest average score
|
|
216
|
+
* 4. Recommend moving towards the center of that bin
|
|
217
|
+
*/
|
|
218
|
+
analyzeParameter(param, snapshots) {
|
|
219
|
+
const numBins = 5;
|
|
220
|
+
const range = param.max - param.min;
|
|
221
|
+
if (range <= 0)
|
|
222
|
+
return null;
|
|
223
|
+
const binSize = range / numBins;
|
|
224
|
+
// Accumulate scores per bin
|
|
225
|
+
const bins = Array.from({ length: numBins }, () => ({ sum: 0, count: 0 }));
|
|
226
|
+
for (const snap of snapshots) {
|
|
227
|
+
const val = snap.params[param.name];
|
|
228
|
+
if (val === undefined)
|
|
229
|
+
continue;
|
|
230
|
+
const binIdx = Math.min(numBins - 1, Math.floor((val - param.min) / binSize));
|
|
231
|
+
bins[binIdx].sum += snap.score;
|
|
232
|
+
bins[binIdx].count++;
|
|
233
|
+
}
|
|
234
|
+
// Find best bin (minimum 2 samples)
|
|
235
|
+
let bestBin = -1;
|
|
236
|
+
let bestAvg = -Infinity;
|
|
237
|
+
let totalSamples = 0;
|
|
238
|
+
for (let i = 0; i < numBins; i++) {
|
|
239
|
+
if (bins[i].count < 2)
|
|
240
|
+
continue;
|
|
241
|
+
const avg = bins[i].sum / bins[i].count;
|
|
242
|
+
totalSamples += bins[i].count;
|
|
243
|
+
if (avg > bestAvg) {
|
|
244
|
+
bestAvg = avg;
|
|
245
|
+
bestBin = i;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (bestBin === -1)
|
|
249
|
+
return null; // not enough data
|
|
250
|
+
// Calculate recommended value (center of best bin)
|
|
251
|
+
const recommended = param.min + (bestBin + 0.5) * binSize;
|
|
252
|
+
// Skip if current value is already in or near the best bin
|
|
253
|
+
const currentBin = Math.min(numBins - 1, Math.floor((param.value - param.min) / binSize));
|
|
254
|
+
if (currentBin === bestBin)
|
|
255
|
+
return null;
|
|
256
|
+
// Estimate improvement
|
|
257
|
+
const currentBinAvg = bins[currentBin].count > 0
|
|
258
|
+
? bins[currentBin].sum / bins[currentBin].count
|
|
259
|
+
: 0;
|
|
260
|
+
const expectedImprovement = currentBinAvg > 0
|
|
261
|
+
? (bestAvg - currentBinAvg) / currentBinAvg
|
|
262
|
+
: 0;
|
|
263
|
+
// Confidence: based on sample count in best bin
|
|
264
|
+
const confidence = Math.min(1, bins[bestBin].count / 10);
|
|
265
|
+
return {
|
|
266
|
+
name: param.name,
|
|
267
|
+
currentValue: param.value,
|
|
268
|
+
recommendedValue: recommended,
|
|
269
|
+
expectedImprovement,
|
|
270
|
+
confidence,
|
|
271
|
+
evidence: totalSamples,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/meta-learning/engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type Database from 'better-sqlite3';
|
|
2
|
+
export interface WebhookConfig {
|
|
3
|
+
id?: number;
|
|
4
|
+
url: string;
|
|
5
|
+
events: string[];
|
|
6
|
+
secret?: string;
|
|
7
|
+
active?: boolean;
|
|
8
|
+
name?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface WebhookRecord {
|
|
11
|
+
id: number;
|
|
12
|
+
url: string;
|
|
13
|
+
events: string;
|
|
14
|
+
secret: string | null;
|
|
15
|
+
active: number;
|
|
16
|
+
name: string | null;
|
|
17
|
+
created_at: string;
|
|
18
|
+
}
|
|
19
|
+
export interface DeliveryRecord {
|
|
20
|
+
id: number;
|
|
21
|
+
webhook_id: number;
|
|
22
|
+
event: string;
|
|
23
|
+
payload: string;
|
|
24
|
+
status: number;
|
|
25
|
+
response: string | null;
|
|
26
|
+
attempts: number;
|
|
27
|
+
created_at: string;
|
|
28
|
+
}
|
|
29
|
+
export interface WebhookDeliveryResult {
|
|
30
|
+
webhookId: number;
|
|
31
|
+
url: string;
|
|
32
|
+
status: number;
|
|
33
|
+
success: boolean;
|
|
34
|
+
attempts: number;
|
|
35
|
+
}
|
|
36
|
+
export declare function runWebhookMigration(db: Database.Database): void;
|
|
37
|
+
export declare class WebhookService {
|
|
38
|
+
private db;
|
|
39
|
+
private logger;
|
|
40
|
+
private retryDelays;
|
|
41
|
+
constructor(db: Database.Database);
|
|
42
|
+
/** Register a new webhook endpoint. */
|
|
43
|
+
add(config: WebhookConfig): WebhookRecord;
|
|
44
|
+
/** Remove a webhook by ID. */
|
|
45
|
+
remove(id: number): boolean;
|
|
46
|
+
/** Get a single webhook by ID. */
|
|
47
|
+
get(id: number): WebhookRecord | null;
|
|
48
|
+
/** List all webhooks. */
|
|
49
|
+
list(): WebhookRecord[];
|
|
50
|
+
/** Toggle a webhook active/inactive. */
|
|
51
|
+
toggle(id: number, active: boolean): boolean;
|
|
52
|
+
/** Get delivery history for a webhook (most recent first). */
|
|
53
|
+
history(webhookId?: number, limit?: number): DeliveryRecord[];
|
|
54
|
+
/**
|
|
55
|
+
* Fire an event to all matching webhooks.
|
|
56
|
+
* Returns delivery results (non-blocking — fire and forget with retry).
|
|
57
|
+
*/
|
|
58
|
+
fire(event: string, data: unknown): Promise<WebhookDeliveryResult[]>;
|
|
59
|
+
/** Deliver a payload to a single webhook with retries. */
|
|
60
|
+
private deliver;
|
|
61
|
+
private recordDelivery;
|
|
62
|
+
private delay;
|
|
63
|
+
/** Cleanup old delivery records. */
|
|
64
|
+
cleanup(olderThanDays?: number): number;
|
|
65
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { getLogger } from '../utils/logger.js';
|
|
3
|
+
// ── Migration ───────────────────────────────────────────
|
|
4
|
+
export function runWebhookMigration(db) {
|
|
5
|
+
db.exec(`
|
|
6
|
+
CREATE TABLE IF NOT EXISTS webhooks (
|
|
7
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
8
|
+
url TEXT NOT NULL,
|
|
9
|
+
events TEXT NOT NULL DEFAULT '[]',
|
|
10
|
+
secret TEXT,
|
|
11
|
+
active INTEGER NOT NULL DEFAULT 1,
|
|
12
|
+
name TEXT,
|
|
13
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
CREATE TABLE IF NOT EXISTS webhook_deliveries (
|
|
17
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
18
|
+
webhook_id INTEGER NOT NULL REFERENCES webhooks(id) ON DELETE CASCADE,
|
|
19
|
+
event TEXT NOT NULL,
|
|
20
|
+
payload TEXT NOT NULL,
|
|
21
|
+
status INTEGER NOT NULL DEFAULT 0,
|
|
22
|
+
response TEXT,
|
|
23
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
24
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
CREATE INDEX IF NOT EXISTS idx_webhook_deliveries_webhook_id ON webhook_deliveries(webhook_id);
|
|
28
|
+
CREATE INDEX IF NOT EXISTS idx_webhook_deliveries_created_at ON webhook_deliveries(created_at);
|
|
29
|
+
`);
|
|
30
|
+
}
|
|
31
|
+
// ── Service ─────────────────────────────────────────────
|
|
32
|
+
export class WebhookService {
|
|
33
|
+
db;
|
|
34
|
+
logger = getLogger();
|
|
35
|
+
retryDelays = [1000, 3000, 10000]; // 1s, 3s, 10s
|
|
36
|
+
constructor(db) {
|
|
37
|
+
this.db = db;
|
|
38
|
+
runWebhookMigration(db);
|
|
39
|
+
}
|
|
40
|
+
/** Register a new webhook endpoint. */
|
|
41
|
+
add(config) {
|
|
42
|
+
const stmt = this.db.prepare(`
|
|
43
|
+
INSERT INTO webhooks (url, events, secret, active, name)
|
|
44
|
+
VALUES (?, ?, ?, ?, ?)
|
|
45
|
+
`);
|
|
46
|
+
const info = stmt.run(config.url, JSON.stringify(config.events), config.secret ?? null, config.active !== false ? 1 : 0, config.name ?? null);
|
|
47
|
+
this.logger.info(`Webhook #${info.lastInsertRowid} registered: ${config.url}`);
|
|
48
|
+
return this.get(Number(info.lastInsertRowid));
|
|
49
|
+
}
|
|
50
|
+
/** Remove a webhook by ID. */
|
|
51
|
+
remove(id) {
|
|
52
|
+
const info = this.db.prepare('DELETE FROM webhooks WHERE id = ?').run(id);
|
|
53
|
+
return info.changes > 0;
|
|
54
|
+
}
|
|
55
|
+
/** Get a single webhook by ID. */
|
|
56
|
+
get(id) {
|
|
57
|
+
return this.db.prepare('SELECT * FROM webhooks WHERE id = ?').get(id);
|
|
58
|
+
}
|
|
59
|
+
/** List all webhooks. */
|
|
60
|
+
list() {
|
|
61
|
+
return this.db.prepare('SELECT * FROM webhooks ORDER BY created_at DESC').all();
|
|
62
|
+
}
|
|
63
|
+
/** Toggle a webhook active/inactive. */
|
|
64
|
+
toggle(id, active) {
|
|
65
|
+
const info = this.db.prepare('UPDATE webhooks SET active = ? WHERE id = ?').run(active ? 1 : 0, id);
|
|
66
|
+
return info.changes > 0;
|
|
67
|
+
}
|
|
68
|
+
/** Get delivery history for a webhook (most recent first). */
|
|
69
|
+
history(webhookId, limit = 50) {
|
|
70
|
+
if (webhookId) {
|
|
71
|
+
return this.db.prepare('SELECT * FROM webhook_deliveries WHERE webhook_id = ? ORDER BY created_at DESC LIMIT ?').all(webhookId, limit);
|
|
72
|
+
}
|
|
73
|
+
return this.db.prepare('SELECT * FROM webhook_deliveries ORDER BY created_at DESC LIMIT ?').all(limit);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Fire an event to all matching webhooks.
|
|
77
|
+
* Returns delivery results (non-blocking — fire and forget with retry).
|
|
78
|
+
*/
|
|
79
|
+
async fire(event, data) {
|
|
80
|
+
const webhooks = this.db.prepare('SELECT * FROM webhooks WHERE active = 1').all();
|
|
81
|
+
const matching = webhooks.filter(wh => {
|
|
82
|
+
const events = JSON.parse(wh.events);
|
|
83
|
+
return events.includes('*') || events.includes(event);
|
|
84
|
+
});
|
|
85
|
+
if (matching.length === 0)
|
|
86
|
+
return [];
|
|
87
|
+
const payload = JSON.stringify({ event, data, timestamp: new Date().toISOString() });
|
|
88
|
+
const results = [];
|
|
89
|
+
await Promise.all(matching.map(async (wh) => {
|
|
90
|
+
const result = await this.deliver(wh, event, payload);
|
|
91
|
+
results.push(result);
|
|
92
|
+
}));
|
|
93
|
+
return results;
|
|
94
|
+
}
|
|
95
|
+
/** Deliver a payload to a single webhook with retries. */
|
|
96
|
+
async deliver(wh, event, payload) {
|
|
97
|
+
let lastStatus = 0;
|
|
98
|
+
let lastResponse = null;
|
|
99
|
+
let attempts = 0;
|
|
100
|
+
for (let i = 0; i <= this.retryDelays.length; i++) {
|
|
101
|
+
attempts++;
|
|
102
|
+
try {
|
|
103
|
+
const headers = {
|
|
104
|
+
'Content-Type': 'application/json',
|
|
105
|
+
'X-Webhook-Event': event,
|
|
106
|
+
};
|
|
107
|
+
// HMAC signing
|
|
108
|
+
if (wh.secret) {
|
|
109
|
+
const signature = crypto
|
|
110
|
+
.createHmac('sha256', wh.secret)
|
|
111
|
+
.update(payload)
|
|
112
|
+
.digest('hex');
|
|
113
|
+
headers['X-Webhook-Signature'] = `sha256=${signature}`;
|
|
114
|
+
}
|
|
115
|
+
const response = await fetch(wh.url, {
|
|
116
|
+
method: 'POST',
|
|
117
|
+
headers,
|
|
118
|
+
body: payload,
|
|
119
|
+
signal: AbortSignal.timeout(10_000),
|
|
120
|
+
});
|
|
121
|
+
lastStatus = response.status;
|
|
122
|
+
lastResponse = await response.text().catch(() => null);
|
|
123
|
+
if (response.ok) {
|
|
124
|
+
this.recordDelivery(wh.id, event, payload, lastStatus, lastResponse, attempts);
|
|
125
|
+
return { webhookId: wh.id, url: wh.url, status: lastStatus, success: true, attempts };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
lastStatus = 0;
|
|
130
|
+
lastResponse = err instanceof Error ? err.message : String(err);
|
|
131
|
+
}
|
|
132
|
+
// Retry delay (don't delay after last attempt)
|
|
133
|
+
if (i < this.retryDelays.length) {
|
|
134
|
+
await this.delay(this.retryDelays[i]);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// All retries exhausted
|
|
138
|
+
this.recordDelivery(wh.id, event, payload, lastStatus, lastResponse, attempts);
|
|
139
|
+
this.logger.warn(`Webhook #${wh.id} delivery failed after ${attempts} attempts: ${wh.url}`);
|
|
140
|
+
return { webhookId: wh.id, url: wh.url, status: lastStatus, success: false, attempts };
|
|
141
|
+
}
|
|
142
|
+
recordDelivery(webhookId, event, payload, status, response, attempts) {
|
|
143
|
+
this.db.prepare(`
|
|
144
|
+
INSERT INTO webhook_deliveries (webhook_id, event, payload, status, response, attempts)
|
|
145
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
146
|
+
`).run(webhookId, event, payload, status, response, attempts);
|
|
147
|
+
}
|
|
148
|
+
delay(ms) {
|
|
149
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
150
|
+
}
|
|
151
|
+
/** Cleanup old delivery records. */
|
|
152
|
+
cleanup(olderThanDays = 30) {
|
|
153
|
+
const info = this.db.prepare(`DELETE FROM webhook_deliveries WHERE created_at < datetime('now', '-' || ? || ' days')`).run(olderThanDays);
|
|
154
|
+
return info.changes;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/webhooks/service.ts"],"names":[],"mappings":"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"}
|