@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.
- package/dist/build-info.json +3 -3
- package/dist/canvas-host/a2ui/.bundle.hash +1 -1
- package/package.json +1 -1
- package/extensions/copilot-proxy/README.md +0 -24
- package/extensions/copilot-proxy/index.ts +0 -154
- package/extensions/copilot-proxy/node_modules/.bin/symi +0 -21
- package/extensions/copilot-proxy/package.json +0 -15
- package/extensions/copilot-proxy/symi.plugin.json +0 -9
- package/extensions/device-pair/index.ts +0 -642
- package/extensions/device-pair/symi.plugin.json +0 -20
- package/extensions/diagnostics-otel/index.ts +0 -15
- package/extensions/diagnostics-otel/node_modules/.bin/acorn +0 -21
- package/extensions/diagnostics-otel/node_modules/.bin/symi +0 -21
- package/extensions/diagnostics-otel/package.json +0 -27
- package/extensions/diagnostics-otel/src/service.test.ts +0 -290
- package/extensions/diagnostics-otel/src/service.ts +0 -666
- package/extensions/diagnostics-otel/symi.plugin.json +0 -8
- package/extensions/google-antigravity-auth/README.md +0 -24
- package/extensions/google-antigravity-auth/index.ts +0 -424
- package/extensions/google-antigravity-auth/node_modules/.bin/symi +0 -21
- package/extensions/google-antigravity-auth/package.json +0 -15
- package/extensions/google-antigravity-auth/symi.plugin.json +0 -9
- package/extensions/google-gemini-cli-auth/README.md +0 -35
- package/extensions/google-gemini-cli-auth/index.ts +0 -75
- package/extensions/google-gemini-cli-auth/node_modules/.bin/symi +0 -21
- package/extensions/google-gemini-cli-auth/oauth.test.ts +0 -162
- package/extensions/google-gemini-cli-auth/oauth.ts +0 -636
- package/extensions/google-gemini-cli-auth/package.json +0 -15
- package/extensions/google-gemini-cli-auth/symi.plugin.json +0 -9
- package/extensions/learning-loop/index.ts +0 -159
- package/extensions/learning-loop/node_modules/.bin/symi +0 -21
- package/extensions/learning-loop/package.json +0 -18
- package/extensions/learning-loop/src/analytics/gateway-methods.ts +0 -230
- package/extensions/learning-loop/src/analytics/metrics-aggregator.ts +0 -153
- package/extensions/learning-loop/src/capture/run-tracker.ts +0 -181
- package/extensions/learning-loop/src/capture/serializer.ts +0 -74
- package/extensions/learning-loop/src/db.ts +0 -583
- package/extensions/learning-loop/src/feedback/explicit-feedback.ts +0 -58
- package/extensions/learning-loop/src/feedback/implicit-signals.ts +0 -89
- package/extensions/learning-loop/src/graph/edge-inference.ts +0 -189
- package/extensions/learning-loop/src/graph/graph-retrieval.ts +0 -144
- package/extensions/learning-loop/src/graph/graph-store.ts +0 -183
- package/extensions/learning-loop/src/hooks.ts +0 -244
- package/extensions/learning-loop/src/injection/cache.ts +0 -73
- package/extensions/learning-loop/src/injection/context-injector.ts +0 -104
- package/extensions/learning-loop/src/injection/prompt-builder.ts +0 -43
- package/extensions/learning-loop/src/learning/embedding-bridge.ts +0 -54
- package/extensions/learning-loop/src/learning/learning-extractor.ts +0 -217
- package/extensions/learning-loop/src/learning/learning-store.ts +0 -158
- package/extensions/learning-loop/src/learning/retrieval.ts +0 -87
- package/extensions/learning-loop/src/math/confidence-intervals.ts +0 -62
- package/extensions/learning-loop/src/math/ewma.ts +0 -51
- package/extensions/learning-loop/src/math/weighted-scorer.ts +0 -42
- package/extensions/learning-loop/src/schema.ts +0 -176
- package/extensions/learning-loop/src/scoring/normalization.ts +0 -32
- package/extensions/learning-loop/src/scoring/quality-engine.ts +0 -78
- package/extensions/learning-loop/src/scoring/signal-extractors.ts +0 -155
- package/extensions/learning-loop/src/test/context-injector.test.ts +0 -142
- package/extensions/learning-loop/src/test/fixes.test.ts +0 -1286
- package/extensions/learning-loop/src/test/graph.test.ts +0 -711
- package/extensions/learning-loop/src/test/integration.test.ts +0 -312
- package/extensions/learning-loop/src/test/learning-store.test.ts +0 -191
- package/extensions/learning-loop/src/test/math.test.ts +0 -148
- package/extensions/learning-loop/src/test/quality-engine.test.ts +0 -231
- package/extensions/learning-loop/src/test/run-tracker.test.ts +0 -143
- package/extensions/learning-loop/src/types.ts +0 -281
- package/extensions/learning-loop/symi.plugin.json +0 -46
- package/extensions/llm-task/README.md +0 -97
- package/extensions/llm-task/index.ts +0 -6
- package/extensions/llm-task/package.json +0 -12
- package/extensions/llm-task/src/llm-task-tool.test.ts +0 -138
- package/extensions/llm-task/src/llm-task-tool.ts +0 -249
- package/extensions/llm-task/symi.plugin.json +0 -21
- package/extensions/memory-lancedb/config.ts +0 -161
- package/extensions/memory-lancedb/index.test.ts +0 -330
- package/extensions/memory-lancedb/index.ts +0 -670
- package/extensions/memory-lancedb/node_modules/.bin/arrow2csv +0 -21
- package/extensions/memory-lancedb/node_modules/.bin/openai +0 -21
- package/extensions/memory-lancedb/node_modules/.bin/symi +0 -21
- package/extensions/memory-lancedb/package.json +0 -20
- package/extensions/memory-lancedb/symi.plugin.json +0 -71
- package/extensions/minimax-portal-auth/README.md +0 -33
- package/extensions/minimax-portal-auth/index.ts +0 -161
- package/extensions/minimax-portal-auth/node_modules/.bin/symi +0 -21
- package/extensions/minimax-portal-auth/oauth.ts +0 -247
- package/extensions/minimax-portal-auth/package.json +0 -15
- package/extensions/minimax-portal-auth/symi.plugin.json +0 -9
- package/extensions/model-equalizer/index.ts +0 -80
- package/extensions/model-equalizer/skills/model-equalizer/SKILL.md +0 -58
- package/extensions/model-equalizer/src/detection.ts +0 -62
- package/extensions/model-equalizer/src/enhancer.ts +0 -63
- package/extensions/model-equalizer/src/test/detection.test.ts +0 -218
- package/extensions/model-equalizer/src/test/enhancer.test.ts +0 -137
- package/extensions/model-equalizer/src/test/integration.test.ts +0 -185
- package/extensions/model-equalizer/src/types.ts +0 -24
- package/extensions/model-equalizer/symi.plugin.json +0 -12
- package/extensions/phone-control/index.ts +0 -421
- package/extensions/phone-control/symi.plugin.json +0 -10
- package/extensions/pipeline/README.md +0 -75
- package/extensions/pipeline/SKILL.md +0 -97
- package/extensions/pipeline/index.ts +0 -18
- package/extensions/pipeline/package.json +0 -11
- package/extensions/pipeline/src/pipeline-tool.test.ts +0 -345
- package/extensions/pipeline/src/pipeline-tool.ts +0 -266
- package/extensions/pipeline/src/windows-spawn.test.ts +0 -148
- package/extensions/pipeline/src/windows-spawn.ts +0 -193
- package/extensions/pipeline/symi.plugin.json +0 -10
- package/extensions/qwen-portal-auth/README.md +0 -24
- package/extensions/qwen-portal-auth/index.ts +0 -134
- package/extensions/qwen-portal-auth/oauth.ts +0 -190
- package/extensions/qwen-portal-auth/symi.plugin.json +0 -9
- package/extensions/talk-voice/index.ts +0 -150
- package/extensions/talk-voice/symi.plugin.json +0 -10
- package/extensions/thread-ownership/index.test.ts +0 -180
- package/extensions/thread-ownership/index.ts +0 -133
- 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
|
-
}
|