@symerian/symi 3.0.18 → 3.0.19

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 (116) hide show
  1. package/dist/build-info.json +3 -3
  2. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  3. package/package.json +1 -1
  4. package/extensions/copilot-proxy/README.md +0 -24
  5. package/extensions/copilot-proxy/index.ts +0 -154
  6. package/extensions/copilot-proxy/node_modules/.bin/symi +0 -21
  7. package/extensions/copilot-proxy/package.json +0 -15
  8. package/extensions/copilot-proxy/symi.plugin.json +0 -9
  9. package/extensions/device-pair/index.ts +0 -642
  10. package/extensions/device-pair/symi.plugin.json +0 -20
  11. package/extensions/diagnostics-otel/index.ts +0 -15
  12. package/extensions/diagnostics-otel/node_modules/.bin/acorn +0 -21
  13. package/extensions/diagnostics-otel/node_modules/.bin/symi +0 -21
  14. package/extensions/diagnostics-otel/package.json +0 -27
  15. package/extensions/diagnostics-otel/src/service.test.ts +0 -290
  16. package/extensions/diagnostics-otel/src/service.ts +0 -666
  17. package/extensions/diagnostics-otel/symi.plugin.json +0 -8
  18. package/extensions/google-antigravity-auth/README.md +0 -24
  19. package/extensions/google-antigravity-auth/index.ts +0 -424
  20. package/extensions/google-antigravity-auth/node_modules/.bin/symi +0 -21
  21. package/extensions/google-antigravity-auth/package.json +0 -15
  22. package/extensions/google-antigravity-auth/symi.plugin.json +0 -9
  23. package/extensions/google-gemini-cli-auth/README.md +0 -35
  24. package/extensions/google-gemini-cli-auth/index.ts +0 -75
  25. package/extensions/google-gemini-cli-auth/node_modules/.bin/symi +0 -21
  26. package/extensions/google-gemini-cli-auth/oauth.test.ts +0 -162
  27. package/extensions/google-gemini-cli-auth/oauth.ts +0 -636
  28. package/extensions/google-gemini-cli-auth/package.json +0 -15
  29. package/extensions/google-gemini-cli-auth/symi.plugin.json +0 -9
  30. package/extensions/learning-loop/index.ts +0 -159
  31. package/extensions/learning-loop/node_modules/.bin/symi +0 -21
  32. package/extensions/learning-loop/package.json +0 -18
  33. package/extensions/learning-loop/src/analytics/gateway-methods.ts +0 -230
  34. package/extensions/learning-loop/src/analytics/metrics-aggregator.ts +0 -153
  35. package/extensions/learning-loop/src/capture/run-tracker.ts +0 -181
  36. package/extensions/learning-loop/src/capture/serializer.ts +0 -74
  37. package/extensions/learning-loop/src/db.ts +0 -583
  38. package/extensions/learning-loop/src/feedback/explicit-feedback.ts +0 -58
  39. package/extensions/learning-loop/src/feedback/implicit-signals.ts +0 -89
  40. package/extensions/learning-loop/src/graph/edge-inference.ts +0 -189
  41. package/extensions/learning-loop/src/graph/graph-retrieval.ts +0 -144
  42. package/extensions/learning-loop/src/graph/graph-store.ts +0 -183
  43. package/extensions/learning-loop/src/hooks.ts +0 -244
  44. package/extensions/learning-loop/src/injection/cache.ts +0 -73
  45. package/extensions/learning-loop/src/injection/context-injector.ts +0 -104
  46. package/extensions/learning-loop/src/injection/prompt-builder.ts +0 -43
  47. package/extensions/learning-loop/src/learning/embedding-bridge.ts +0 -54
  48. package/extensions/learning-loop/src/learning/learning-extractor.ts +0 -217
  49. package/extensions/learning-loop/src/learning/learning-store.ts +0 -158
  50. package/extensions/learning-loop/src/learning/retrieval.ts +0 -87
  51. package/extensions/learning-loop/src/math/confidence-intervals.ts +0 -62
  52. package/extensions/learning-loop/src/math/ewma.ts +0 -51
  53. package/extensions/learning-loop/src/math/weighted-scorer.ts +0 -42
  54. package/extensions/learning-loop/src/schema.ts +0 -176
  55. package/extensions/learning-loop/src/scoring/normalization.ts +0 -32
  56. package/extensions/learning-loop/src/scoring/quality-engine.ts +0 -78
  57. package/extensions/learning-loop/src/scoring/signal-extractors.ts +0 -155
  58. package/extensions/learning-loop/src/test/context-injector.test.ts +0 -142
  59. package/extensions/learning-loop/src/test/fixes.test.ts +0 -1286
  60. package/extensions/learning-loop/src/test/graph.test.ts +0 -711
  61. package/extensions/learning-loop/src/test/integration.test.ts +0 -312
  62. package/extensions/learning-loop/src/test/learning-store.test.ts +0 -191
  63. package/extensions/learning-loop/src/test/math.test.ts +0 -148
  64. package/extensions/learning-loop/src/test/quality-engine.test.ts +0 -231
  65. package/extensions/learning-loop/src/test/run-tracker.test.ts +0 -143
  66. package/extensions/learning-loop/src/types.ts +0 -281
  67. package/extensions/learning-loop/symi.plugin.json +0 -46
  68. package/extensions/llm-task/README.md +0 -97
  69. package/extensions/llm-task/index.ts +0 -6
  70. package/extensions/llm-task/package.json +0 -12
  71. package/extensions/llm-task/src/llm-task-tool.test.ts +0 -138
  72. package/extensions/llm-task/src/llm-task-tool.ts +0 -249
  73. package/extensions/llm-task/symi.plugin.json +0 -21
  74. package/extensions/memory-lancedb/config.ts +0 -161
  75. package/extensions/memory-lancedb/index.test.ts +0 -330
  76. package/extensions/memory-lancedb/index.ts +0 -670
  77. package/extensions/memory-lancedb/node_modules/.bin/arrow2csv +0 -21
  78. package/extensions/memory-lancedb/node_modules/.bin/openai +0 -21
  79. package/extensions/memory-lancedb/node_modules/.bin/symi +0 -21
  80. package/extensions/memory-lancedb/package.json +0 -20
  81. package/extensions/memory-lancedb/symi.plugin.json +0 -71
  82. package/extensions/minimax-portal-auth/README.md +0 -33
  83. package/extensions/minimax-portal-auth/index.ts +0 -161
  84. package/extensions/minimax-portal-auth/node_modules/.bin/symi +0 -21
  85. package/extensions/minimax-portal-auth/oauth.ts +0 -247
  86. package/extensions/minimax-portal-auth/package.json +0 -15
  87. package/extensions/minimax-portal-auth/symi.plugin.json +0 -9
  88. package/extensions/model-equalizer/index.ts +0 -80
  89. package/extensions/model-equalizer/skills/model-equalizer/SKILL.md +0 -58
  90. package/extensions/model-equalizer/src/detection.ts +0 -62
  91. package/extensions/model-equalizer/src/enhancer.ts +0 -63
  92. package/extensions/model-equalizer/src/test/detection.test.ts +0 -218
  93. package/extensions/model-equalizer/src/test/enhancer.test.ts +0 -137
  94. package/extensions/model-equalizer/src/test/integration.test.ts +0 -185
  95. package/extensions/model-equalizer/src/types.ts +0 -24
  96. package/extensions/model-equalizer/symi.plugin.json +0 -12
  97. package/extensions/phone-control/index.ts +0 -421
  98. package/extensions/phone-control/symi.plugin.json +0 -10
  99. package/extensions/pipeline/README.md +0 -75
  100. package/extensions/pipeline/SKILL.md +0 -97
  101. package/extensions/pipeline/index.ts +0 -18
  102. package/extensions/pipeline/package.json +0 -11
  103. package/extensions/pipeline/src/pipeline-tool.test.ts +0 -345
  104. package/extensions/pipeline/src/pipeline-tool.ts +0 -266
  105. package/extensions/pipeline/src/windows-spawn.test.ts +0 -148
  106. package/extensions/pipeline/src/windows-spawn.ts +0 -193
  107. package/extensions/pipeline/symi.plugin.json +0 -10
  108. package/extensions/qwen-portal-auth/README.md +0 -24
  109. package/extensions/qwen-portal-auth/index.ts +0 -134
  110. package/extensions/qwen-portal-auth/oauth.ts +0 -190
  111. package/extensions/qwen-portal-auth/symi.plugin.json +0 -9
  112. package/extensions/talk-voice/index.ts +0 -150
  113. package/extensions/talk-voice/symi.plugin.json +0 -10
  114. package/extensions/thread-ownership/index.test.ts +0 -180
  115. package/extensions/thread-ownership/index.ts +0 -133
  116. package/extensions/thread-ownership/symi.plugin.json +0 -28
@@ -1,583 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import type { DatabaseSync } from "node:sqlite";
4
- import { requireNodeSqlite } from "../../../src/memory/sqlite.js";
5
- import type { PluginLogger } from "../../../src/plugins/types.js";
6
- import { type EWMAState, updateEWMA, getEWMAValue } from "./math/ewma.js";
7
- import { initializeSchema } from "./schema.js";
8
- import type {
9
- LearningLoopConfig,
10
- RunRow,
11
- ToolCallRow,
12
- LearningRow,
13
- FeedbackRow,
14
- MetricsBucketRow,
15
- CompletedRun,
16
- QualityScore,
17
- LearningRecord,
18
- FeedbackRecord,
19
- EdgeRow,
20
- } from "./types.js";
21
- import { ALGORITHM_VERSION } from "./types.js";
22
-
23
- export type DatabaseManager = ReturnType<typeof createDatabaseManager>;
24
-
25
- export function createDatabaseManager(params: {
26
- stateDir: string;
27
- config: LearningLoopConfig;
28
- logger: PluginLogger;
29
- }) {
30
- const { stateDir, config, logger } = params;
31
- const dbPath = path.join(stateDir, "learning-loop.db");
32
-
33
- // Ensure state directory exists
34
- fs.mkdirSync(stateDir, { recursive: true });
35
-
36
- const { DatabaseSync } = requireNodeSqlite();
37
- const db = new DatabaseSync(dbPath);
38
- initializeSchema(db);
39
-
40
- // --- Runs ---
41
-
42
- function insertRun(run: CompletedRun, score: QualityScore): void {
43
- db.prepare(`
44
- INSERT INTO runs (
45
- run_id, session_id, session_key, agent_id,
46
- provider, model, prompt_hash, prompt_length,
47
- response_length, response_tool_call_count,
48
- usage_input, usage_output, usage_cache_read, usage_cache_write, usage_total,
49
- success, error, duration_ms,
50
- quality_score, algorithm_version,
51
- started_at, completed_at
52
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
53
- `).run(
54
- run.runId,
55
- run.sessionId,
56
- run.sessionKey,
57
- run.agentId,
58
- run.provider,
59
- run.model,
60
- run.promptHash,
61
- run.promptLength,
62
- run.responseLength,
63
- run.responseToolCallCount,
64
- run.usage.input ?? 0,
65
- run.usage.output ?? 0,
66
- run.usage.cacheRead ?? 0,
67
- run.usage.cacheWrite ?? 0,
68
- run.usage.total ?? 0,
69
- run.success ? 1 : 0,
70
- run.error,
71
- run.durationMs,
72
- score.score,
73
- score.algorithmVersion,
74
- run.startedAt,
75
- run.completedAt,
76
- );
77
-
78
- // Insert tool calls
79
- if (run.toolCalls.length > 0) {
80
- const stmt = db.prepare(`
81
- INSERT INTO tool_calls (run_id, tool_name, duration_ms, success, error, param_hash)
82
- VALUES (?, ?, ?, ?, ?, ?)
83
- `);
84
- for (const tc of run.toolCalls) {
85
- stmt.run(run.runId, tc.toolName, tc.durationMs, tc.success ? 1 : 0, tc.error, tc.paramHash);
86
- }
87
- }
88
-
89
- // Insert quality signals
90
- if (score.signals.length > 0) {
91
- const stmt = db.prepare(`
92
- INSERT INTO quality_signals (run_id, signal_name, value, confidence, weight)
93
- VALUES (?, ?, ?, ?, ?)
94
- `);
95
- for (const sig of score.signals) {
96
- stmt.run(run.runId, sig.name, sig.value, sig.confidence, sig.weight);
97
- }
98
- }
99
- }
100
-
101
- function getRun(runId: string): RunRow | undefined {
102
- return db.prepare(`SELECT * FROM runs WHERE run_id = ?`).get(runId) as RunRow | undefined;
103
- }
104
-
105
- function getRunCount(): number {
106
- const row = db
107
- .prepare(`SELECT COUNT(*) as cnt FROM runs WHERE run_id != '__pruned__'`)
108
- .get() as { cnt: number };
109
- return row.cnt;
110
- }
111
-
112
- function getToolCalls(runId: string): ToolCallRow[] {
113
- return db.prepare(`SELECT * FROM tool_calls WHERE run_id = ?`).all(runId) as ToolCallRow[];
114
- }
115
-
116
- function getRecentRuns(limit: number = 100): RunRow[] {
117
- return db
118
- .prepare(`SELECT * FROM runs ORDER BY completed_at DESC LIMIT ?`)
119
- .all(limit) as RunRow[];
120
- }
121
-
122
- function getRunsByModel(provider: string, model: string, limit: number = 100): RunRow[] {
123
- return db
124
- .prepare(
125
- `SELECT * FROM runs WHERE provider = ? AND model = ? ORDER BY completed_at DESC LIMIT ?`,
126
- )
127
- .all(provider, model, limit) as RunRow[];
128
- }
129
-
130
- // --- EWMA State ---
131
-
132
- function getEwmaState(provider: string, model: string, metric: string): EWMAState {
133
- const row = db
134
- .prepare(
135
- `SELECT value, count FROM ewma_state WHERE provider = ? AND model = ? AND metric = ?`,
136
- )
137
- .get(provider, model, metric) as { value: number; count: number } | undefined;
138
- return row ?? { value: 0, count: 0 };
139
- }
140
-
141
- function updateEwmaState(
142
- provider: string,
143
- model: string,
144
- metric: string,
145
- observation: number,
146
- alpha: number = 0.1,
147
- ): EWMAState {
148
- const current = getEwmaState(provider, model, metric);
149
- const updated = updateEWMA(current, observation, alpha);
150
- db.prepare(`
151
- INSERT INTO ewma_state (provider, model, metric, value, count)
152
- VALUES (?, ?, ?, ?, ?)
153
- ON CONFLICT(provider, model, metric) DO UPDATE SET
154
- value = excluded.value,
155
- count = excluded.count
156
- `).run(provider, model, metric, updated.value, updated.count);
157
- return updated;
158
- }
159
-
160
- // --- Learnings ---
161
-
162
- function insertLearning(learning: LearningRecord): void {
163
- db.prepare(`
164
- INSERT INTO learnings (id, run_id, category, content, embedding, confidence, applied_count, created_at, updated_at)
165
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
166
- `).run(
167
- learning.id,
168
- learning.runId,
169
- learning.category,
170
- learning.content,
171
- learning.embedding ? JSON.stringify(learning.embedding) : null,
172
- learning.confidence,
173
- learning.appliedCount,
174
- learning.createdAt,
175
- learning.updatedAt,
176
- );
177
- }
178
-
179
- function getLearning(id: string): LearningRow | undefined {
180
- return db.prepare(`SELECT * FROM learnings WHERE id = ?`).get(id) as LearningRow | undefined;
181
- }
182
-
183
- function getAllLearnings(limit: number = 1000): LearningRow[] {
184
- return db
185
- .prepare(`SELECT * FROM learnings ORDER BY updated_at DESC LIMIT ?`)
186
- .all(limit) as LearningRow[];
187
- }
188
-
189
- function getLearningsByCategory(category: string, limit: number = 100): LearningRow[] {
190
- return db
191
- .prepare(`SELECT * FROM learnings WHERE category = ? ORDER BY updated_at DESC LIMIT ?`)
192
- .all(category, limit) as LearningRow[];
193
- }
194
-
195
- function searchLearningsFts(query: string, limit: number = 20): LearningRow[] {
196
- return db
197
- .prepare(`
198
- SELECT l.* FROM learnings l
199
- JOIN learnings_fts fts ON l.rowid = fts.rowid
200
- WHERE learnings_fts MATCH ?
201
- ORDER BY rank
202
- LIMIT ?
203
- `)
204
- .all(query, limit) as LearningRow[];
205
- }
206
-
207
- function incrementAppliedCount(id: string): void {
208
- db.prepare(
209
- `UPDATE learnings SET applied_count = applied_count + 1, updated_at = ? WHERE id = ?`,
210
- ).run(Date.now(), id);
211
- }
212
-
213
- function deleteLearning(id: string): void {
214
- db.prepare(`DELETE FROM learnings WHERE id = ?`).run(id);
215
- }
216
-
217
- function getLearningsWithoutEmbeddings(limit: number): LearningRow[] {
218
- return db
219
- .prepare(`SELECT * FROM learnings WHERE embedding IS NULL LIMIT ?`)
220
- .all(limit) as LearningRow[];
221
- }
222
-
223
- function updateLearningEmbedding(id: string, embedding: number[]): void {
224
- db.prepare(`UPDATE learnings SET embedding = ? WHERE id = ?`).run(
225
- JSON.stringify(embedding),
226
- id,
227
- );
228
- }
229
-
230
- // --- Feedback ---
231
-
232
- function insertFeedback(feedback: FeedbackRecord): void {
233
- db.prepare(`
234
- INSERT INTO feedback (id, run_id, source, score, created_at)
235
- VALUES (?, ?, ?, ?, ?)
236
- `).run(feedback.id, feedback.runId, feedback.source, feedback.score, feedback.createdAt);
237
- }
238
-
239
- function getFeedback(runId: string): FeedbackRow[] {
240
- return db.prepare(`SELECT * FROM feedback WHERE run_id = ?`).all(runId) as FeedbackRow[];
241
- }
242
-
243
- // --- Metrics Buckets ---
244
-
245
- function upsertMetricsBucket(
246
- provider: string,
247
- model: string,
248
- completedAt: number,
249
- qualityScore: number,
250
- durationMs: number,
251
- success: boolean,
252
- usageInput: number,
253
- usageOutput: number,
254
- ): void {
255
- const bucketHour = new Date(completedAt).toISOString().slice(0, 13);
256
- db.prepare(`
257
- INSERT INTO metrics_buckets (
258
- provider, model, bucket_hour,
259
- run_count, success_count,
260
- quality_sum, quality_sum_sq,
261
- latency_sum, latency_sum_sq,
262
- token_input_sum, token_output_sum
263
- ) VALUES (?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?)
264
- ON CONFLICT(provider, model, bucket_hour) DO UPDATE SET
265
- run_count = run_count + 1,
266
- success_count = success_count + ?,
267
- quality_sum = quality_sum + ?,
268
- quality_sum_sq = quality_sum_sq + ?,
269
- latency_sum = latency_sum + ?,
270
- latency_sum_sq = latency_sum_sq + ?,
271
- token_input_sum = token_input_sum + ?,
272
- token_output_sum = token_output_sum + ?
273
- `).run(
274
- provider,
275
- model,
276
- bucketHour,
277
- success ? 1 : 0,
278
- qualityScore,
279
- qualityScore * qualityScore,
280
- durationMs,
281
- durationMs * durationMs,
282
- usageInput,
283
- usageOutput,
284
- // ON CONFLICT values
285
- success ? 1 : 0,
286
- qualityScore,
287
- qualityScore * qualityScore,
288
- durationMs,
289
- durationMs * durationMs,
290
- usageInput,
291
- usageOutput,
292
- );
293
- }
294
-
295
- function getMetricsBuckets(
296
- provider?: string,
297
- model?: string,
298
- fromHour?: string,
299
- toHour?: string,
300
- ): MetricsBucketRow[] {
301
- const conditions: string[] = [];
302
- const params: unknown[] = [];
303
-
304
- if (provider) {
305
- conditions.push("provider = ?");
306
- params.push(provider);
307
- }
308
- if (model) {
309
- conditions.push("model = ?");
310
- params.push(model);
311
- }
312
- if (fromHour) {
313
- conditions.push("bucket_hour >= ?");
314
- params.push(fromHour);
315
- }
316
- if (toHour) {
317
- conditions.push("bucket_hour <= ?");
318
- params.push(toHour);
319
- }
320
-
321
- const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
322
- return db
323
- .prepare(`SELECT * FROM metrics_buckets ${where} ORDER BY bucket_hour DESC`)
324
- .all(...params) as MetricsBucketRow[];
325
- }
326
-
327
- function getModelLeaderboard(): Array<{
328
- provider: string;
329
- model: string;
330
- runCount: number;
331
- successRate: number;
332
- avgQuality: number;
333
- avgLatency: number;
334
- }> {
335
- return db
336
- .prepare(`
337
- SELECT
338
- provider,
339
- model,
340
- SUM(run_count) as runCount,
341
- CASE WHEN SUM(run_count) > 0
342
- THEN CAST(SUM(success_count) AS REAL) / SUM(run_count)
343
- ELSE 0 END as successRate,
344
- CASE WHEN SUM(run_count) > 0
345
- THEN SUM(quality_sum) / SUM(run_count)
346
- ELSE 0 END as avgQuality,
347
- CASE WHEN SUM(run_count) > 0
348
- THEN SUM(latency_sum) / SUM(run_count)
349
- ELSE 0 END as avgLatency
350
- FROM metrics_buckets
351
- GROUP BY provider, model
352
- ORDER BY avgQuality DESC
353
- `)
354
- .all() as Array<{
355
- provider: string;
356
- model: string;
357
- runCount: number;
358
- successRate: number;
359
- avgQuality: number;
360
- avgLatency: number;
361
- }>;
362
- }
363
-
364
- // --- Graph Edges ---
365
-
366
- function insertEdge(sourceId: string, targetId: string, edgeType: string, weight: number): void {
367
- db.prepare(`
368
- INSERT INTO learning_edges (source_id, target_id, edge_type, weight, created_at)
369
- VALUES (?, ?, ?, ?, ?)
370
- `).run(sourceId, targetId, edgeType, weight, Date.now());
371
- }
372
-
373
- function upsertEdge(sourceId: string, targetId: string, edgeType: string, weight: number): void {
374
- db.prepare(`
375
- INSERT INTO learning_edges (source_id, target_id, edge_type, weight, created_at)
376
- VALUES (?, ?, ?, ?, ?)
377
- ON CONFLICT(source_id, target_id, edge_type) DO UPDATE SET
378
- weight = excluded.weight,
379
- created_at = excluded.created_at
380
- `).run(sourceId, targetId, edgeType, weight, Date.now());
381
- }
382
-
383
- function getEdgesBySource(id: string): EdgeRow[] {
384
- return db.prepare(`SELECT * FROM learning_edges WHERE source_id = ?`).all(id) as EdgeRow[];
385
- }
386
-
387
- function getEdgesByTarget(id: string): EdgeRow[] {
388
- return db.prepare(`SELECT * FROM learning_edges WHERE target_id = ?`).all(id) as EdgeRow[];
389
- }
390
-
391
- function getEdgesByNode(id: string): EdgeRow[] {
392
- return db
393
- .prepare(`SELECT * FROM learning_edges WHERE source_id = ? OR target_id = ?`)
394
- .all(id, id) as EdgeRow[];
395
- }
396
-
397
- function getEdgesByType(edgeType: string, limit: number = 1000): EdgeRow[] {
398
- return db
399
- .prepare(`SELECT * FROM learning_edges WHERE edge_type = ? LIMIT ?`)
400
- .all(edgeType, limit) as EdgeRow[];
401
- }
402
-
403
- function deleteEdgesForLearning(id: string): void {
404
- db.prepare(`DELETE FROM learning_edges WHERE source_id = ? OR target_id = ?`).run(id, id);
405
- }
406
-
407
- function getCentralityScore(id: string): number {
408
- const row = db.prepare(`SELECT score FROM centrality_scores WHERE learning_id = ?`).get(id) as
409
- | { score: number }
410
- | undefined;
411
- return row?.score ?? 0;
412
- }
413
-
414
- function upsertCentralityScore(id: string, score: number): void {
415
- db.prepare(`
416
- INSERT INTO centrality_scores (learning_id, score, updated_at)
417
- VALUES (?, ?, ?)
418
- ON CONFLICT(learning_id) DO UPDATE SET
419
- score = excluded.score,
420
- updated_at = excluded.updated_at
421
- `).run(id, score, Date.now());
422
- }
423
-
424
- function deleteCentralityScore(id: string): void {
425
- db.prepare(`DELETE FROM centrality_scores WHERE learning_id = ?`).run(id);
426
- }
427
-
428
- // --- Pruning ---
429
-
430
- function ensurePrunedSentinel(): void {
431
- const exists = db.prepare(`SELECT 1 FROM runs WHERE run_id = '__pruned__'`).get();
432
- if (!exists) {
433
- db.prepare(`
434
- INSERT INTO runs (
435
- run_id, session_id, session_key, agent_id,
436
- provider, model, prompt_hash, prompt_length,
437
- response_length, response_tool_call_count,
438
- usage_input, usage_output, usage_cache_read, usage_cache_write, usage_total,
439
- success, error, duration_ms,
440
- quality_score, algorithm_version,
441
- started_at, completed_at
442
- ) VALUES ('__pruned__','','','','','','',0,0,0,0,0,0,0,0,1,NULL,0,NULL,NULL,0,0)
443
- `).run();
444
- }
445
- }
446
-
447
- function pruneOldRuns(maxRuns: number): number {
448
- const count = getRunCount();
449
- if (count <= maxRuns) return 0;
450
-
451
- const toDelete = count - maxRuns;
452
- const oldestRuns = db
453
- .prepare(
454
- `SELECT run_id FROM runs WHERE run_id != '__pruned__' ORDER BY completed_at ASC LIMIT ?`,
455
- )
456
- .all(toDelete) as Array<{ run_id: string }>;
457
-
458
- if (oldestRuns.length === 0) return 0;
459
-
460
- db.exec("BEGIN");
461
- try {
462
- ensurePrunedSentinel();
463
- for (const row of oldestRuns) {
464
- db.prepare(`DELETE FROM tool_calls WHERE run_id = ?`).run(row.run_id);
465
- db.prepare(`DELETE FROM quality_signals WHERE run_id = ?`).run(row.run_id);
466
- db.prepare(`DELETE FROM feedback WHERE run_id = ?`).run(row.run_id);
467
- db.prepare(`UPDATE learnings SET run_id = '__pruned__' WHERE run_id = ?`).run(row.run_id);
468
- db.prepare(`DELETE FROM runs WHERE run_id = ?`).run(row.run_id);
469
- }
470
- db.exec("COMMIT");
471
- } catch (err) {
472
- db.exec("ROLLBACK");
473
- throw err;
474
- }
475
-
476
- return oldestRuns.length;
477
- }
478
-
479
- /**
480
- * Remove learnings that are genuinely unused: never applied, low confidence,
481
- * and older than maxAge (ms). Also cleans up associated graph edges.
482
- */
483
- function pruneStaleLearnings(maxAge: number): number {
484
- const cutoff = Date.now() - maxAge;
485
- const stale = db
486
- .prepare(
487
- `SELECT id FROM learnings WHERE applied_count = 0 AND confidence < 0.5 AND updated_at < ?`,
488
- )
489
- .all(cutoff) as Array<{ id: string }>;
490
-
491
- if (stale.length === 0) return 0;
492
-
493
- db.exec("BEGIN");
494
- try {
495
- for (const row of stale) {
496
- deleteEdgesForLearning(row.id);
497
- deleteCentralityScore(row.id);
498
- db.prepare(`DELETE FROM learnings WHERE id = ?`).run(row.id);
499
- }
500
- db.exec("COMMIT");
501
- } catch (err) {
502
- db.exec("ROLLBACK");
503
- throw err;
504
- }
505
-
506
- return stale.length;
507
- }
508
-
509
- /**
510
- * Update the stored quality score for a run (used when feedback arrives after initial scoring).
511
- */
512
- function updateRunScore(runId: string, score: number): void {
513
- db.prepare(`UPDATE runs SET quality_score = ? WHERE run_id = ?`).run(score, runId);
514
- }
515
-
516
- // --- Utility ---
517
-
518
- function getStats(): {
519
- runCount: number;
520
- learningCount: number;
521
- feedbackCount: number;
522
- } {
523
- const runs = (db.prepare(`SELECT COUNT(*) as cnt FROM runs`).get() as { cnt: number }).cnt;
524
- const learnings = (db.prepare(`SELECT COUNT(*) as cnt FROM learnings`).get() as { cnt: number })
525
- .cnt;
526
- const feedbacks = (db.prepare(`SELECT COUNT(*) as cnt FROM feedback`).get() as { cnt: number })
527
- .cnt;
528
- return { runCount: runs, learningCount: learnings, feedbackCount: feedbacks };
529
- }
530
-
531
- function close(): void {
532
- try {
533
- db.close();
534
- } catch {
535
- // Already closed
536
- }
537
- }
538
-
539
- function raw(): DatabaseSync {
540
- return db;
541
- }
542
-
543
- return {
544
- insertRun,
545
- getRun,
546
- getRunCount,
547
- getToolCalls,
548
- getRecentRuns,
549
- getRunsByModel,
550
- getEwmaState,
551
- updateEwmaState,
552
- insertLearning,
553
- getLearning,
554
- getAllLearnings,
555
- getLearningsByCategory,
556
- searchLearningsFts,
557
- incrementAppliedCount,
558
- deleteLearning,
559
- getLearningsWithoutEmbeddings,
560
- updateLearningEmbedding,
561
- insertFeedback,
562
- getFeedback,
563
- upsertMetricsBucket,
564
- getMetricsBuckets,
565
- getModelLeaderboard,
566
- pruneOldRuns,
567
- pruneStaleLearnings,
568
- updateRunScore,
569
- getStats,
570
- close,
571
- raw,
572
- insertEdge,
573
- upsertEdge,
574
- getEdgesBySource,
575
- getEdgesByTarget,
576
- getEdgesByNode,
577
- getEdgesByType,
578
- deleteEdgesForLearning,
579
- getCentralityScore,
580
- upsertCentralityScore,
581
- deleteCentralityScore,
582
- };
583
- }
@@ -1,58 +0,0 @@
1
- /**
2
- * Explicit feedback handler for user-submitted ratings.
3
- * Accepts a 1-5 score via gateway RPC and stores it as feedback.
4
- */
5
-
6
- import crypto from "node:crypto";
7
- import { reconstructCompletedRun } from "../capture/serializer.js";
8
- import type { DatabaseManager } from "../db.js";
9
- import type { QualityEngine } from "../scoring/quality-engine.js";
10
- import type { FeedbackRecord } from "../types.js";
11
-
12
- export type ExplicitFeedbackHandler = ReturnType<typeof createExplicitFeedbackHandler>;
13
-
14
- export function createExplicitFeedbackHandler(params: {
15
- db: DatabaseManager;
16
- qualityEngine?: QualityEngine;
17
- }) {
18
- const { db, qualityEngine } = params;
19
-
20
- /**
21
- * Submit explicit user feedback for a run.
22
- *
23
- * @param runId - The run to provide feedback for
24
- * @param score - Rating from 1 to 5
25
- * @returns The created feedback record, or null if the run doesn't exist
26
- */
27
- function submitFeedback(runId: string, score: number): FeedbackRecord | null {
28
- const runRow = db.getRun(runId);
29
- if (!runRow) return null;
30
-
31
- const clampedScore = Math.max(1, Math.min(5, Math.round(score)));
32
-
33
- const feedback: FeedbackRecord = {
34
- id: `fb_${Date.now()}_${crypto.randomBytes(4).toString("hex")}`,
35
- runId,
36
- source: "explicit",
37
- score: clampedScore,
38
- createdAt: Date.now(),
39
- };
40
-
41
- db.insertFeedback(feedback);
42
-
43
- // Close the feedback loop: rescore the run with explicit feedback
44
- if (qualityEngine) {
45
- const toolCalls = db.getToolCalls(runId);
46
- const completedRun = reconstructCompletedRun(runRow, toolCalls);
47
- const newScore = qualityEngine.rescoreWithFeedback(completedRun, {
48
- source: "explicit",
49
- score: clampedScore,
50
- });
51
- db.updateRunScore(runId, newScore.score);
52
- }
53
-
54
- return feedback;
55
- }
56
-
57
- return { submitFeedback };
58
- }