@ekkos/mcp-server 2.0.3 → 2.0.4
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/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +432 -32
- package/dist/index.js.map +1 -1
- package/dist/local-cache.d.ts +90 -0
- package/dist/local-cache.d.ts.map +1 -0
- package/dist/local-cache.js +525 -0
- package/dist/local-cache.js.map +1 -0
- package/dist/offline-queue.d.ts +25 -0
- package/dist/offline-queue.d.ts.map +1 -0
- package/dist/offline-queue.js +118 -0
- package/dist/offline-queue.js.map +1 -0
- package/ekkOS_CONTEXT.md +62 -0
- package/package.json +5 -1
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local Pattern Cache for ekkOS MCP Server
|
|
3
|
+
*
|
|
4
|
+
* Provides <2ms local-first search via SQLite FTS5 (with BM25 relevance ranking)
|
|
5
|
+
* with automatic fallback to the original JSON + in-memory Map approach when
|
|
6
|
+
* better-sqlite3 is unavailable (e.g. missing native build tools).
|
|
7
|
+
*
|
|
8
|
+
* SQLite backend (preferred):
|
|
9
|
+
* - DB at ~/.ekkos/cache/patterns.db
|
|
10
|
+
* - patterns table + patterns_fts virtual table (FTS5, BM25 ranking)
|
|
11
|
+
* - directives table
|
|
12
|
+
*
|
|
13
|
+
* JSON fallback (when SQLite unavailable):
|
|
14
|
+
* - Disk file at ~/.ekkos/cache/patterns.json
|
|
15
|
+
* - In-memory Map + array for sub-millisecond reads
|
|
16
|
+
*
|
|
17
|
+
* Public API is identical between both backends — callers in index.ts need
|
|
18
|
+
* no changes.
|
|
19
|
+
*/
|
|
20
|
+
export interface CachedPattern {
|
|
21
|
+
id: string;
|
|
22
|
+
title: string;
|
|
23
|
+
similarity?: number;
|
|
24
|
+
success_rate: number | null;
|
|
25
|
+
applied_count: number;
|
|
26
|
+
tags?: string[];
|
|
27
|
+
content?: string;
|
|
28
|
+
updated_at?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface CachedDirective {
|
|
31
|
+
id: string;
|
|
32
|
+
type: string;
|
|
33
|
+
rule: string;
|
|
34
|
+
context?: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Initialise cache from persistent storage on startup.
|
|
38
|
+
* SQLite: the DB file is already open — just read counts and restore warmed_at.
|
|
39
|
+
* JSON fallback: deserialise patterns.json into the in-memory Map.
|
|
40
|
+
*/
|
|
41
|
+
export declare function loadCacheFromDisk(): {
|
|
42
|
+
patterns: number;
|
|
43
|
+
directives: number;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Warm cache from the /api/v1/warmup bundle endpoint (non-blocking).
|
|
47
|
+
*
|
|
48
|
+
* A single HTTP GET replaces the previous multi-step ekkOS_Search approach,
|
|
49
|
+
* returning top patterns + directives + project patterns in one response.
|
|
50
|
+
*
|
|
51
|
+
* With SQLite backend, data is written into patterns / directives tables and
|
|
52
|
+
* FTS index is updated automatically via triggers.
|
|
53
|
+
*
|
|
54
|
+
* With JSON fallback, data is written to the in-memory Map and then flushed
|
|
55
|
+
* to disk as patterns.json.
|
|
56
|
+
*/
|
|
57
|
+
export declare function warmCache(_callGateway: (endpoint: string, method: string, params?: any) => Promise<any>, userId: string, options?: {
|
|
58
|
+
apiKey?: string;
|
|
59
|
+
baseUrl?: string;
|
|
60
|
+
projectId?: string;
|
|
61
|
+
}): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Search local cache by query string.
|
|
64
|
+
*
|
|
65
|
+
* SQLite backend: FTS5 MATCH with bm25() ranking — proper relevance scoring,
|
|
66
|
+
* sub-2ms on typical pattern sets.
|
|
67
|
+
*
|
|
68
|
+
* JSON fallback: simple term-frequency scoring on title + tags + content.
|
|
69
|
+
*/
|
|
70
|
+
export declare function searchLocal(query: string, limit?: number): CachedPattern[];
|
|
71
|
+
/**
|
|
72
|
+
* Return all cached active directives.
|
|
73
|
+
*/
|
|
74
|
+
export declare function getDirectives(): CachedDirective[];
|
|
75
|
+
export declare function isCacheStale(): boolean;
|
|
76
|
+
export declare function hasCacheData(): boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Mark cache as stale — next search will trigger a background re-warm.
|
|
79
|
+
* Does NOT delete SQLite data (that would remove offline fallback data).
|
|
80
|
+
*/
|
|
81
|
+
export declare function invalidateCache(): void;
|
|
82
|
+
export declare function startPeriodicRefresh(callGateway: (endpoint: string, method: string, params?: any) => Promise<any>, userId: string): void;
|
|
83
|
+
export declare function getCacheStats(): {
|
|
84
|
+
patterns: number;
|
|
85
|
+
directives: number;
|
|
86
|
+
age_ms: number;
|
|
87
|
+
stale: boolean;
|
|
88
|
+
backend: 'sqlite' | 'json';
|
|
89
|
+
};
|
|
90
|
+
//# sourceMappingURL=local-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-cache.d.ts","sourceRoot":"","sources":["../src/local-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAiBH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AA8LD;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CA2C5E;AAyBD;;;;;;;;;;;GAWG;AACH,wBAAsB,SAAS,CAC7B,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,EAC9E,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAClE,OAAO,CAAC,IAAI,CAAC,CAsKf;AAID;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,GAAE,MAAU,GAAG,aAAa,EAAE,CAgE7E;AAID;;GAEG;AACH,wBAAgB,aAAa,IAAI,eAAe,EAAE,CAWjD;AAID,wBAAgB,YAAY,IAAI,OAAO,CAGtC;AAID,wBAAgB,YAAY,IAAI,OAAO,CAUtC;AAID;;;GAGG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAStC;AAID,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,EAC7E,MAAM,EAAE,MAAM,GACb,IAAI,CAMN;AAID,wBAAgB,aAAa,IAAI;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,QAAQ,GAAG,MAAM,CAAA;CAAE,CAuBpI"}
|
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local Pattern Cache for ekkOS MCP Server
|
|
3
|
+
*
|
|
4
|
+
* Provides <2ms local-first search via SQLite FTS5 (with BM25 relevance ranking)
|
|
5
|
+
* with automatic fallback to the original JSON + in-memory Map approach when
|
|
6
|
+
* better-sqlite3 is unavailable (e.g. missing native build tools).
|
|
7
|
+
*
|
|
8
|
+
* SQLite backend (preferred):
|
|
9
|
+
* - DB at ~/.ekkos/cache/patterns.db
|
|
10
|
+
* - patterns table + patterns_fts virtual table (FTS5, BM25 ranking)
|
|
11
|
+
* - directives table
|
|
12
|
+
*
|
|
13
|
+
* JSON fallback (when SQLite unavailable):
|
|
14
|
+
* - Disk file at ~/.ekkos/cache/patterns.json
|
|
15
|
+
* - In-memory Map + array for sub-millisecond reads
|
|
16
|
+
*
|
|
17
|
+
* Public API is identical between both backends — callers in index.ts need
|
|
18
|
+
* no changes.
|
|
19
|
+
*/
|
|
20
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
21
|
+
import { join } from 'path';
|
|
22
|
+
import { homedir } from 'os';
|
|
23
|
+
import { createRequire } from 'module';
|
|
24
|
+
// ─── Paths ────────────────────────────────────────────────────────────────────
|
|
25
|
+
const CACHE_DIR = join(homedir(), '.ekkos', 'cache');
|
|
26
|
+
const CACHE_FILE = join(CACHE_DIR, 'patterns.json'); // JSON fallback
|
|
27
|
+
const SQLITE_DB_FILE = join(CACHE_DIR, 'patterns.db'); // SQLite primary
|
|
28
|
+
const CACHE_MAX_AGE_MS = 5 * 60 * 1000; // 5 minutes
|
|
29
|
+
const REFRESH_INTERVAL_MS = 5 * 60 * 1000;
|
|
30
|
+
let db = null;
|
|
31
|
+
try {
|
|
32
|
+
const _require = createRequire(import.meta.url);
|
|
33
|
+
const BetterSqlite3 = _require('better-sqlite3');
|
|
34
|
+
if (!existsSync(CACHE_DIR)) {
|
|
35
|
+
mkdirSync(CACHE_DIR, { recursive: true });
|
|
36
|
+
}
|
|
37
|
+
db = new BetterSqlite3(SQLITE_DB_FILE);
|
|
38
|
+
// WAL mode: concurrent reads, fast writes
|
|
39
|
+
db.pragma('journal_mode = WAL');
|
|
40
|
+
db.pragma('synchronous = NORMAL');
|
|
41
|
+
// Canonical storage table
|
|
42
|
+
db.exec(`
|
|
43
|
+
CREATE TABLE IF NOT EXISTS patterns (
|
|
44
|
+
rowid INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
45
|
+
pattern_id TEXT UNIQUE NOT NULL,
|
|
46
|
+
title TEXT NOT NULL DEFAULT '',
|
|
47
|
+
problem TEXT NOT NULL DEFAULT '',
|
|
48
|
+
solution TEXT NOT NULL DEFAULT '',
|
|
49
|
+
tags TEXT NOT NULL DEFAULT '',
|
|
50
|
+
success_rate REAL,
|
|
51
|
+
layer TEXT NOT NULL DEFAULT '',
|
|
52
|
+
updated_at TEXT,
|
|
53
|
+
full_json TEXT NOT NULL
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS patterns_fts USING fts5(
|
|
57
|
+
pattern_id UNINDEXED,
|
|
58
|
+
title,
|
|
59
|
+
problem,
|
|
60
|
+
solution,
|
|
61
|
+
tags,
|
|
62
|
+
content='patterns',
|
|
63
|
+
content_rowid='rowid'
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
-- Keep FTS in sync via triggers
|
|
67
|
+
CREATE TRIGGER IF NOT EXISTS patterns_ai
|
|
68
|
+
AFTER INSERT ON patterns BEGIN
|
|
69
|
+
INSERT INTO patterns_fts(rowid, pattern_id, title, problem, solution, tags)
|
|
70
|
+
VALUES (new.rowid, new.pattern_id, new.title, new.problem, new.solution, new.tags);
|
|
71
|
+
END;
|
|
72
|
+
|
|
73
|
+
CREATE TRIGGER IF NOT EXISTS patterns_ad
|
|
74
|
+
AFTER DELETE ON patterns BEGIN
|
|
75
|
+
INSERT INTO patterns_fts(patterns_fts, rowid, pattern_id, title, problem, solution, tags)
|
|
76
|
+
VALUES ('delete', old.rowid, old.pattern_id, old.title, old.problem, old.solution, old.tags);
|
|
77
|
+
END;
|
|
78
|
+
|
|
79
|
+
CREATE TRIGGER IF NOT EXISTS patterns_au
|
|
80
|
+
AFTER UPDATE ON patterns BEGIN
|
|
81
|
+
INSERT INTO patterns_fts(patterns_fts, rowid, pattern_id, title, problem, solution, tags)
|
|
82
|
+
VALUES ('delete', old.rowid, old.pattern_id, old.title, old.problem, old.solution, old.tags);
|
|
83
|
+
INSERT INTO patterns_fts(rowid, pattern_id, title, problem, solution, tags)
|
|
84
|
+
VALUES (new.rowid, new.pattern_id, new.title, new.problem, new.solution, new.tags);
|
|
85
|
+
END;
|
|
86
|
+
|
|
87
|
+
CREATE TABLE IF NOT EXISTS directives (
|
|
88
|
+
id TEXT PRIMARY KEY,
|
|
89
|
+
type TEXT NOT NULL DEFAULT 'PREFER',
|
|
90
|
+
rule TEXT NOT NULL DEFAULT '',
|
|
91
|
+
scope TEXT,
|
|
92
|
+
is_active INTEGER NOT NULL DEFAULT 1,
|
|
93
|
+
full_json TEXT NOT NULL
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
CREATE TABLE IF NOT EXISTS cache_meta (
|
|
97
|
+
key TEXT PRIMARY KEY,
|
|
98
|
+
value TEXT NOT NULL
|
|
99
|
+
);
|
|
100
|
+
`);
|
|
101
|
+
console.error('[ekkOS:cache] SQLite FTS5 backend initialised at', SQLITE_DB_FILE);
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
db = null;
|
|
105
|
+
console.error('[ekkOS:cache] SQLite unavailable — using JSON fallback:', err.message);
|
|
106
|
+
}
|
|
107
|
+
// ─── FTS5 query sanitiser ─────────────────────────────────────────────────────
|
|
108
|
+
// FTS5 treats special characters (", *, :, ^, etc.) as operators. We quote
|
|
109
|
+
// each whitespace-separated token individually so arbitrary user queries don't
|
|
110
|
+
// cause a parse error.
|
|
111
|
+
function sanitizeFTS5Query(query) {
|
|
112
|
+
return query
|
|
113
|
+
.trim()
|
|
114
|
+
.split(/\s+/)
|
|
115
|
+
.filter(t => t.length > 0)
|
|
116
|
+
.map(t => `"${t.replace(/"/g, '""')}"`) // escape internal quotes
|
|
117
|
+
.join(' '); // implicit AND between tokens
|
|
118
|
+
}
|
|
119
|
+
// ─── SQLite helper: upsert a single pattern ───────────────────────────────────
|
|
120
|
+
function sqliteUpsertPattern(p) {
|
|
121
|
+
if (!db)
|
|
122
|
+
return;
|
|
123
|
+
const tagsStr = (p.tags || []).join(' ');
|
|
124
|
+
// split content into problem/solution heuristically:
|
|
125
|
+
// store the full content in both fields so FTS matches either way
|
|
126
|
+
const content = p.content || '';
|
|
127
|
+
const stmt = db.prepare(`
|
|
128
|
+
INSERT INTO patterns (pattern_id, title, problem, solution, tags, success_rate, updated_at, full_json)
|
|
129
|
+
VALUES (@pattern_id, @title, @problem, @solution, @tags, @success_rate, @updated_at, @full_json)
|
|
130
|
+
ON CONFLICT(pattern_id) DO UPDATE SET
|
|
131
|
+
title = excluded.title,
|
|
132
|
+
problem = excluded.problem,
|
|
133
|
+
solution = excluded.solution,
|
|
134
|
+
tags = excluded.tags,
|
|
135
|
+
success_rate = excluded.success_rate,
|
|
136
|
+
updated_at = excluded.updated_at,
|
|
137
|
+
full_json = excluded.full_json
|
|
138
|
+
`);
|
|
139
|
+
stmt.run({
|
|
140
|
+
pattern_id: p.id,
|
|
141
|
+
title: p.title || '',
|
|
142
|
+
problem: content,
|
|
143
|
+
solution: content,
|
|
144
|
+
tags: tagsStr,
|
|
145
|
+
success_rate: p.success_rate ?? null,
|
|
146
|
+
updated_at: p.updated_at || null,
|
|
147
|
+
full_json: JSON.stringify(p),
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
// ─── SQLite helper: set cache_meta ────────────────────────────────────────────
|
|
151
|
+
function sqliteSetMeta(key, value) {
|
|
152
|
+
if (!db)
|
|
153
|
+
return;
|
|
154
|
+
db.prepare(`
|
|
155
|
+
INSERT INTO cache_meta (key, value) VALUES (?, ?)
|
|
156
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value
|
|
157
|
+
`).run(key, value);
|
|
158
|
+
}
|
|
159
|
+
function sqliteGetMeta(key) {
|
|
160
|
+
if (!db)
|
|
161
|
+
return null;
|
|
162
|
+
const row = db.prepare('SELECT value FROM cache_meta WHERE key = ?').get(key);
|
|
163
|
+
return row ? row.value : null;
|
|
164
|
+
}
|
|
165
|
+
// ─── JSON fallback state ──────────────────────────────────────────────────────
|
|
166
|
+
const patternMap = new Map();
|
|
167
|
+
const directiveList = [];
|
|
168
|
+
let cacheWarmedAt = 0;
|
|
169
|
+
let isWarming = false;
|
|
170
|
+
// ─── loadCacheFromDisk ────────────────────────────────────────────────────────
|
|
171
|
+
/**
|
|
172
|
+
* Initialise cache from persistent storage on startup.
|
|
173
|
+
* SQLite: the DB file is already open — just read counts and restore warmed_at.
|
|
174
|
+
* JSON fallback: deserialise patterns.json into the in-memory Map.
|
|
175
|
+
*/
|
|
176
|
+
export function loadCacheFromDisk() {
|
|
177
|
+
if (db) {
|
|
178
|
+
// SQLite path
|
|
179
|
+
try {
|
|
180
|
+
const patCount = db.prepare('SELECT COUNT(*) as n FROM patterns').get().n;
|
|
181
|
+
const dirCount = db.prepare('SELECT COUNT(*) as n FROM directives WHERE is_active = 1').get().n;
|
|
182
|
+
const warmedAtStr = sqliteGetMeta('warmed_at');
|
|
183
|
+
if (warmedAtStr) {
|
|
184
|
+
cacheWarmedAt = new Date(warmedAtStr).getTime();
|
|
185
|
+
}
|
|
186
|
+
return { patterns: patCount, directives: dirCount };
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
console.error('[ekkOS:cache] SQLite loadCacheFromDisk error:', err);
|
|
190
|
+
return { patterns: 0, directives: 0 };
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// JSON fallback
|
|
194
|
+
try {
|
|
195
|
+
if (!existsSync(CACHE_FILE)) {
|
|
196
|
+
return { patterns: 0, directives: 0 };
|
|
197
|
+
}
|
|
198
|
+
const raw = readFileSync(CACHE_FILE, 'utf-8');
|
|
199
|
+
const data = JSON.parse(raw);
|
|
200
|
+
patternMap.clear();
|
|
201
|
+
directiveList.length = 0;
|
|
202
|
+
for (const p of data.patterns) {
|
|
203
|
+
patternMap.set(p.id, p);
|
|
204
|
+
}
|
|
205
|
+
for (const d of data.directives) {
|
|
206
|
+
directiveList.push(d);
|
|
207
|
+
}
|
|
208
|
+
cacheWarmedAt = new Date(data.warmed_at).getTime();
|
|
209
|
+
return { patterns: patternMap.size, directives: directiveList.length };
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return { patterns: 0, directives: 0 };
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// ─── saveCacheToDisk (JSON fallback only) ─────────────────────────────────────
|
|
216
|
+
function saveCacheToDisk(userId) {
|
|
217
|
+
try {
|
|
218
|
+
if (!existsSync(CACHE_DIR)) {
|
|
219
|
+
mkdirSync(CACHE_DIR, { recursive: true });
|
|
220
|
+
}
|
|
221
|
+
const data = {
|
|
222
|
+
patterns: Array.from(patternMap.values()),
|
|
223
|
+
directives: [...directiveList],
|
|
224
|
+
warmed_at: new Date().toISOString(),
|
|
225
|
+
user_id: userId,
|
|
226
|
+
};
|
|
227
|
+
writeFileSync(CACHE_FILE, JSON.stringify(data));
|
|
228
|
+
}
|
|
229
|
+
catch (err) {
|
|
230
|
+
console.error('[ekkOS:cache] Failed to save cache to disk:', err);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// ─── warmCache ────────────────────────────────────────────────────────────────
|
|
234
|
+
/**
|
|
235
|
+
* Warm cache from the /api/v1/warmup bundle endpoint (non-blocking).
|
|
236
|
+
*
|
|
237
|
+
* A single HTTP GET replaces the previous multi-step ekkOS_Search approach,
|
|
238
|
+
* returning top patterns + directives + project patterns in one response.
|
|
239
|
+
*
|
|
240
|
+
* With SQLite backend, data is written into patterns / directives tables and
|
|
241
|
+
* FTS index is updated automatically via triggers.
|
|
242
|
+
*
|
|
243
|
+
* With JSON fallback, data is written to the in-memory Map and then flushed
|
|
244
|
+
* to disk as patterns.json.
|
|
245
|
+
*/
|
|
246
|
+
export async function warmCache(_callGateway, userId, options) {
|
|
247
|
+
if (isWarming)
|
|
248
|
+
return;
|
|
249
|
+
isWarming = true;
|
|
250
|
+
try {
|
|
251
|
+
const apiKey = options?.apiKey || process.env.EKKOS_API_KEY || '';
|
|
252
|
+
const baseUrl = (options?.baseUrl || process.env.EKKOS_MCP_URL || 'https://mcp.ekkos.dev/api/v1/mcp')
|
|
253
|
+
.replace(/\/api\/v1\/mcp.*$/, '');
|
|
254
|
+
const params = new URLSearchParams({ user_id: userId });
|
|
255
|
+
if (options?.projectId) {
|
|
256
|
+
params.set('project_id', options.projectId);
|
|
257
|
+
}
|
|
258
|
+
const controller = new AbortController();
|
|
259
|
+
const timeout = setTimeout(() => controller.abort(), 10_000);
|
|
260
|
+
let response;
|
|
261
|
+
try {
|
|
262
|
+
response = await fetch(`${baseUrl}/api/v1/warmup?${params.toString()}`, {
|
|
263
|
+
method: 'GET',
|
|
264
|
+
headers: {
|
|
265
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
266
|
+
'Content-Type': 'application/json',
|
|
267
|
+
},
|
|
268
|
+
signal: controller.signal,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
finally {
|
|
272
|
+
clearTimeout(timeout);
|
|
273
|
+
}
|
|
274
|
+
if (!response.ok) {
|
|
275
|
+
throw new Error(`Warmup HTTP ${response.status}: ${response.statusText}`);
|
|
276
|
+
}
|
|
277
|
+
const bundle = await response.json();
|
|
278
|
+
const allPatterns = [
|
|
279
|
+
...(Array.isArray(bundle.patterns) ? bundle.patterns : []),
|
|
280
|
+
...(Array.isArray(bundle.project_patterns) ? bundle.project_patterns : []),
|
|
281
|
+
];
|
|
282
|
+
if (db) {
|
|
283
|
+
// ── SQLite path ──────────────────────────────────────────────────────
|
|
284
|
+
// Upsert all patterns
|
|
285
|
+
for (const p of allPatterns) {
|
|
286
|
+
const cached = {
|
|
287
|
+
id: p.pattern_id,
|
|
288
|
+
title: p.title || '',
|
|
289
|
+
success_rate: p.success_rate ?? null,
|
|
290
|
+
applied_count: p.applied_count || 0,
|
|
291
|
+
tags: p.tags,
|
|
292
|
+
content: p.solution || p.content || '',
|
|
293
|
+
updated_at: p.updated_at,
|
|
294
|
+
};
|
|
295
|
+
sqliteUpsertPattern(cached);
|
|
296
|
+
}
|
|
297
|
+
// Replace directives (delete active, re-insert)
|
|
298
|
+
if (Array.isArray(bundle.directives)) {
|
|
299
|
+
db.exec('DELETE FROM directives');
|
|
300
|
+
const insertDir = db.prepare(`
|
|
301
|
+
INSERT INTO directives (id, type, rule, scope, is_active, full_json)
|
|
302
|
+
VALUES (@id, @type, @rule, @scope, 1, @full_json)
|
|
303
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
304
|
+
type = excluded.type,
|
|
305
|
+
rule = excluded.rule,
|
|
306
|
+
scope = excluded.scope,
|
|
307
|
+
is_active = 1,
|
|
308
|
+
full_json = excluded.full_json
|
|
309
|
+
`);
|
|
310
|
+
for (const d of bundle.directives) {
|
|
311
|
+
insertDir.run({
|
|
312
|
+
id: d.id,
|
|
313
|
+
type: d.type || 'PREFER',
|
|
314
|
+
rule: d.rule || '',
|
|
315
|
+
scope: d.context || null,
|
|
316
|
+
full_json: JSON.stringify(d),
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
sqliteSetMeta('warmed_at', new Date().toISOString());
|
|
321
|
+
cacheWarmedAt = Date.now();
|
|
322
|
+
const patCount = db.prepare('SELECT COUNT(*) as n FROM patterns').get().n;
|
|
323
|
+
const dirCount = db.prepare('SELECT COUNT(*) as n FROM directives WHERE is_active = 1').get().n;
|
|
324
|
+
console.error(`[ekkOS:cache] SQLite warmed via /api/v1/warmup: ${patCount} patterns, ${dirCount} directives` +
|
|
325
|
+
(bundle.meta?.project_pattern_count ? `, ${bundle.meta.project_pattern_count} project patterns` : ''));
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
// ── JSON fallback path ───────────────────────────────────────────────
|
|
329
|
+
for (const p of allPatterns) {
|
|
330
|
+
patternMap.set(p.pattern_id, {
|
|
331
|
+
id: p.pattern_id,
|
|
332
|
+
title: p.title || '',
|
|
333
|
+
success_rate: p.success_rate ?? null,
|
|
334
|
+
applied_count: p.applied_count || 0,
|
|
335
|
+
tags: p.tags,
|
|
336
|
+
content: p.solution || p.content || '',
|
|
337
|
+
updated_at: p.updated_at,
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
if (Array.isArray(bundle.directives)) {
|
|
341
|
+
directiveList.length = 0;
|
|
342
|
+
for (const d of bundle.directives) {
|
|
343
|
+
directiveList.push({
|
|
344
|
+
id: d.id,
|
|
345
|
+
type: d.type || 'PREFER',
|
|
346
|
+
rule: d.rule || '',
|
|
347
|
+
context: d.context,
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
cacheWarmedAt = Date.now();
|
|
352
|
+
saveCacheToDisk(userId);
|
|
353
|
+
console.error(`[ekkOS:cache] JSON warmed via /api/v1/warmup: ${patternMap.size} patterns, ${directiveList.length} directives` +
|
|
354
|
+
(bundle.meta?.project_pattern_count ? `, ${bundle.meta.project_pattern_count} project patterns` : ''));
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
catch (err) {
|
|
358
|
+
console.error('[ekkOS:cache] Warm failed (will use stale cache):', err);
|
|
359
|
+
}
|
|
360
|
+
finally {
|
|
361
|
+
isWarming = false;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
// ─── searchLocal ──────────────────────────────────────────────────────────────
|
|
365
|
+
/**
|
|
366
|
+
* Search local cache by query string.
|
|
367
|
+
*
|
|
368
|
+
* SQLite backend: FTS5 MATCH with bm25() ranking — proper relevance scoring,
|
|
369
|
+
* sub-2ms on typical pattern sets.
|
|
370
|
+
*
|
|
371
|
+
* JSON fallback: simple term-frequency scoring on title + tags + content.
|
|
372
|
+
*/
|
|
373
|
+
export function searchLocal(query, limit = 5) {
|
|
374
|
+
if (db) {
|
|
375
|
+
// ── SQLite / FTS5 path ───────────────────────────────────────────────
|
|
376
|
+
try {
|
|
377
|
+
const ftsQuery = sanitizeFTS5Query(query);
|
|
378
|
+
if (!ftsQuery)
|
|
379
|
+
return [];
|
|
380
|
+
const rows = db.prepare(`
|
|
381
|
+
SELECT p.full_json, bm25(patterns_fts) AS rank
|
|
382
|
+
FROM patterns_fts f
|
|
383
|
+
JOIN patterns p ON f.rowid = p.rowid
|
|
384
|
+
WHERE patterns_fts MATCH ?
|
|
385
|
+
ORDER BY rank
|
|
386
|
+
LIMIT ?
|
|
387
|
+
`).all(ftsQuery, limit);
|
|
388
|
+
return rows.map(r => JSON.parse(r.full_json));
|
|
389
|
+
}
|
|
390
|
+
catch (err) {
|
|
391
|
+
// FTS parse error or other SQLite issue — fall through to JSON fallback
|
|
392
|
+
console.error('[ekkOS:cache] FTS5 search error, falling back to Map search:', err);
|
|
393
|
+
// Load patterns from SQLite into patternMap for fallback
|
|
394
|
+
if (patternMap.size === 0) {
|
|
395
|
+
try {
|
|
396
|
+
const all = db.prepare('SELECT full_json FROM patterns').all();
|
|
397
|
+
for (const row of all) {
|
|
398
|
+
const p = JSON.parse(row.full_json);
|
|
399
|
+
patternMap.set(p.id, p);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
catch {
|
|
403
|
+
return [];
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
// ── JSON / Map fallback path ─────────────────────────────────────────────
|
|
409
|
+
if (patternMap.size === 0)
|
|
410
|
+
return [];
|
|
411
|
+
const queryLower = query.toLowerCase();
|
|
412
|
+
const queryTerms = queryLower.split(/\s+/).filter(t => t.length > 2);
|
|
413
|
+
const scored = [];
|
|
414
|
+
for (const pattern of patternMap.values()) {
|
|
415
|
+
let score = 0;
|
|
416
|
+
const titleLower = (pattern.title || '').toLowerCase();
|
|
417
|
+
const contentLower = (pattern.content || '').toLowerCase();
|
|
418
|
+
const tagsLower = (pattern.tags || []).join(' ').toLowerCase();
|
|
419
|
+
if (titleLower.includes(queryLower))
|
|
420
|
+
score += 10;
|
|
421
|
+
for (const term of queryTerms) {
|
|
422
|
+
if (titleLower.includes(term))
|
|
423
|
+
score += 3;
|
|
424
|
+
if (contentLower.includes(term))
|
|
425
|
+
score += 1;
|
|
426
|
+
if (tagsLower.includes(term))
|
|
427
|
+
score += 2;
|
|
428
|
+
}
|
|
429
|
+
if (pattern.success_rate !== null && pattern.success_rate > 0.7)
|
|
430
|
+
score += 1;
|
|
431
|
+
if (pattern.applied_count > 5)
|
|
432
|
+
score += 0.5;
|
|
433
|
+
if (score > 0)
|
|
434
|
+
scored.push({ pattern, score });
|
|
435
|
+
}
|
|
436
|
+
scored.sort((a, b) => b.score - a.score);
|
|
437
|
+
return scored.slice(0, limit).map(s => s.pattern);
|
|
438
|
+
}
|
|
439
|
+
// ─── getDirectives ────────────────────────────────────────────────────────────
|
|
440
|
+
/**
|
|
441
|
+
* Return all cached active directives.
|
|
442
|
+
*/
|
|
443
|
+
export function getDirectives() {
|
|
444
|
+
if (db) {
|
|
445
|
+
try {
|
|
446
|
+
const rows = db.prepare('SELECT full_json FROM directives WHERE is_active = 1').all();
|
|
447
|
+
return rows.map(r => JSON.parse(r.full_json));
|
|
448
|
+
}
|
|
449
|
+
catch (err) {
|
|
450
|
+
console.error('[ekkOS:cache] getDirectives SQLite error:', err);
|
|
451
|
+
return [];
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return [...directiveList];
|
|
455
|
+
}
|
|
456
|
+
// ─── isCacheStale ─────────────────────────────────────────────────────────────
|
|
457
|
+
export function isCacheStale() {
|
|
458
|
+
if (cacheWarmedAt === 0)
|
|
459
|
+
return true;
|
|
460
|
+
return (Date.now() - cacheWarmedAt) > CACHE_MAX_AGE_MS;
|
|
461
|
+
}
|
|
462
|
+
// ─── hasCacheData ─────────────────────────────────────────────────────────────
|
|
463
|
+
export function hasCacheData() {
|
|
464
|
+
if (db) {
|
|
465
|
+
try {
|
|
466
|
+
const patCount = db.prepare('SELECT COUNT(*) as n FROM patterns').get().n;
|
|
467
|
+
return patCount > 0;
|
|
468
|
+
}
|
|
469
|
+
catch {
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return patternMap.size > 0 || directiveList.length > 0;
|
|
474
|
+
}
|
|
475
|
+
// ─── invalidateCache ─────────────────────────────────────────────────────────
|
|
476
|
+
/**
|
|
477
|
+
* Mark cache as stale — next search will trigger a background re-warm.
|
|
478
|
+
* Does NOT delete SQLite data (that would remove offline fallback data).
|
|
479
|
+
*/
|
|
480
|
+
export function invalidateCache() {
|
|
481
|
+
cacheWarmedAt = 0;
|
|
482
|
+
if (db) {
|
|
483
|
+
try {
|
|
484
|
+
sqliteSetMeta('warmed_at', '1970-01-01T00:00:00.000Z');
|
|
485
|
+
}
|
|
486
|
+
catch {
|
|
487
|
+
// best-effort
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
// ─── startPeriodicRefresh ─────────────────────────────────────────────────────
|
|
492
|
+
export function startPeriodicRefresh(callGateway, userId) {
|
|
493
|
+
setInterval(() => {
|
|
494
|
+
if (isCacheStale()) {
|
|
495
|
+
warmCache(callGateway, userId).catch(() => { });
|
|
496
|
+
}
|
|
497
|
+
}, REFRESH_INTERVAL_MS);
|
|
498
|
+
}
|
|
499
|
+
// ─── getCacheStats ────────────────────────────────────────────────────────────
|
|
500
|
+
export function getCacheStats() {
|
|
501
|
+
if (db) {
|
|
502
|
+
try {
|
|
503
|
+
const patCount = db.prepare('SELECT COUNT(*) as n FROM patterns').get().n;
|
|
504
|
+
const dirCount = db.prepare('SELECT COUNT(*) as n FROM directives WHERE is_active = 1').get().n;
|
|
505
|
+
return {
|
|
506
|
+
patterns: patCount,
|
|
507
|
+
directives: dirCount,
|
|
508
|
+
age_ms: cacheWarmedAt > 0 ? Date.now() - cacheWarmedAt : -1,
|
|
509
|
+
stale: isCacheStale(),
|
|
510
|
+
backend: 'sqlite',
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
catch {
|
|
514
|
+
// fall through
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
return {
|
|
518
|
+
patterns: patternMap.size,
|
|
519
|
+
directives: directiveList.length,
|
|
520
|
+
age_ms: cacheWarmedAt > 0 ? Date.now() - cacheWarmedAt : -1,
|
|
521
|
+
stale: isCacheStale(),
|
|
522
|
+
backend: 'json',
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
//# sourceMappingURL=local-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-cache.js","sourceRoot":"","sources":["../src/local-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Offline Write Queue for ekkOS MCP Server
|
|
3
|
+
*
|
|
4
|
+
* When the cloud gateway is unreachable, queues write operations
|
|
5
|
+
* (Forge, Track, Outcome, Directive) to a local JSONL file.
|
|
6
|
+
* Flushes the queue when connectivity is restored.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Check if a tool is a write operation that can be queued
|
|
10
|
+
*/
|
|
11
|
+
export declare function isWriteTool(toolName: string): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Queue a write operation for later execution
|
|
14
|
+
*/
|
|
15
|
+
export declare function queueWrite(toolName: string, args: Record<string, unknown>): void;
|
|
16
|
+
/**
|
|
17
|
+
* Flush all pending writes to the cloud gateway
|
|
18
|
+
* Returns the number of successfully flushed writes
|
|
19
|
+
*/
|
|
20
|
+
export declare function flushQueue(callGateway: (endpoint: string, method: string, params?: any) => Promise<any>): Promise<number>;
|
|
21
|
+
/**
|
|
22
|
+
* Get count of pending writes
|
|
23
|
+
*/
|
|
24
|
+
export declare function getPendingCount(): number;
|
|
25
|
+
//# sourceMappingURL=offline-queue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"offline-queue.d.ts","sourceRoot":"","sources":["../src/offline-queue.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA6BH;;GAEG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAErD;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAiBhF;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAC9B,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,GAC5E,OAAO,CAAC,MAAM,CAAC,CA6CjB;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,MAAM,CASxC"}
|