@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,176 +0,0 @@
1
- /**
2
- * SQLite schema for the learning loop extension.
3
- * All tables use WAL mode for concurrent read/write performance.
4
- */
5
-
6
- import type { DatabaseSync } from "node:sqlite";
7
-
8
- export function initializeSchema(db: DatabaseSync): void {
9
- db.exec("PRAGMA journal_mode = WAL");
10
- db.exec("PRAGMA busy_timeout = 5000");
11
- db.exec("PRAGMA synchronous = NORMAL");
12
-
13
- db.exec(`
14
- CREATE TABLE IF NOT EXISTS runs (
15
- run_id TEXT PRIMARY KEY,
16
- session_id TEXT NOT NULL,
17
- session_key TEXT NOT NULL,
18
- agent_id TEXT NOT NULL,
19
- provider TEXT NOT NULL,
20
- model TEXT NOT NULL,
21
- prompt_hash TEXT NOT NULL,
22
- prompt_length INTEGER NOT NULL DEFAULT 0,
23
- response_length INTEGER NOT NULL DEFAULT 0,
24
- response_tool_call_count INTEGER NOT NULL DEFAULT 0,
25
- usage_input INTEGER NOT NULL DEFAULT 0,
26
- usage_output INTEGER NOT NULL DEFAULT 0,
27
- usage_cache_read INTEGER NOT NULL DEFAULT 0,
28
- usage_cache_write INTEGER NOT NULL DEFAULT 0,
29
- usage_total INTEGER NOT NULL DEFAULT 0,
30
- success INTEGER NOT NULL DEFAULT 0,
31
- error TEXT,
32
- duration_ms INTEGER NOT NULL DEFAULT 0,
33
- quality_score REAL,
34
- algorithm_version INTEGER,
35
- started_at INTEGER NOT NULL,
36
- completed_at INTEGER NOT NULL
37
- )
38
- `);
39
-
40
- db.exec(`
41
- CREATE TABLE IF NOT EXISTS tool_calls (
42
- id INTEGER PRIMARY KEY AUTOINCREMENT,
43
- run_id TEXT NOT NULL REFERENCES runs(run_id) ON DELETE CASCADE,
44
- tool_name TEXT NOT NULL,
45
- duration_ms INTEGER,
46
- success INTEGER NOT NULL DEFAULT 1,
47
- error TEXT,
48
- param_hash TEXT NOT NULL
49
- )
50
- `);
51
-
52
- db.exec(`
53
- CREATE TABLE IF NOT EXISTS quality_signals (
54
- id INTEGER PRIMARY KEY AUTOINCREMENT,
55
- run_id TEXT NOT NULL REFERENCES runs(run_id) ON DELETE CASCADE,
56
- signal_name TEXT NOT NULL,
57
- value REAL NOT NULL,
58
- confidence REAL NOT NULL,
59
- weight REAL NOT NULL
60
- )
61
- `);
62
-
63
- db.exec(`
64
- CREATE TABLE IF NOT EXISTS learnings (
65
- id TEXT PRIMARY KEY,
66
- run_id TEXT NOT NULL REFERENCES runs(run_id) ON DELETE CASCADE,
67
- category TEXT NOT NULL,
68
- content TEXT NOT NULL,
69
- embedding TEXT,
70
- confidence REAL NOT NULL DEFAULT 0.5,
71
- applied_count INTEGER NOT NULL DEFAULT 0,
72
- created_at INTEGER NOT NULL,
73
- updated_at INTEGER NOT NULL
74
- )
75
- `);
76
-
77
- db.exec(`
78
- CREATE TABLE IF NOT EXISTS feedback (
79
- id TEXT PRIMARY KEY,
80
- run_id TEXT NOT NULL REFERENCES runs(run_id) ON DELETE CASCADE,
81
- source TEXT NOT NULL,
82
- score REAL NOT NULL,
83
- created_at INTEGER NOT NULL
84
- )
85
- `);
86
-
87
- db.exec(`
88
- CREATE TABLE IF NOT EXISTS metrics_buckets (
89
- provider TEXT NOT NULL,
90
- model TEXT NOT NULL,
91
- bucket_hour TEXT NOT NULL,
92
- run_count INTEGER NOT NULL DEFAULT 0,
93
- success_count INTEGER NOT NULL DEFAULT 0,
94
- quality_sum REAL NOT NULL DEFAULT 0,
95
- quality_sum_sq REAL NOT NULL DEFAULT 0,
96
- latency_sum REAL NOT NULL DEFAULT 0,
97
- latency_sum_sq REAL NOT NULL DEFAULT 0,
98
- token_input_sum INTEGER NOT NULL DEFAULT 0,
99
- token_output_sum INTEGER NOT NULL DEFAULT 0,
100
- PRIMARY KEY (provider, model, bucket_hour)
101
- )
102
- `);
103
-
104
- db.exec(`
105
- CREATE TABLE IF NOT EXISTS ewma_state (
106
- provider TEXT NOT NULL,
107
- model TEXT NOT NULL,
108
- metric TEXT NOT NULL,
109
- value REAL NOT NULL DEFAULT 0,
110
- count INTEGER NOT NULL DEFAULT 0,
111
- PRIMARY KEY (provider, model, metric)
112
- )
113
- `);
114
-
115
- // --- Graph tables ---
116
-
117
- db.exec(`
118
- CREATE TABLE IF NOT EXISTS learning_edges (
119
- id INTEGER PRIMARY KEY AUTOINCREMENT,
120
- source_id TEXT NOT NULL,
121
- target_id TEXT NOT NULL,
122
- edge_type TEXT NOT NULL CHECK(edge_type IN ('T','S','C','U','X','R')),
123
- weight REAL NOT NULL DEFAULT 1.0,
124
- created_at INTEGER NOT NULL,
125
- UNIQUE(source_id, target_id, edge_type)
126
- )
127
- `);
128
-
129
- db.exec(`
130
- CREATE TABLE IF NOT EXISTS centrality_scores (
131
- learning_id TEXT PRIMARY KEY,
132
- score REAL NOT NULL DEFAULT 0.0,
133
- updated_at INTEGER NOT NULL
134
- )
135
- `);
136
-
137
- // Indexes for efficient queries
138
- db.exec(`CREATE INDEX IF NOT EXISTS idx_runs_provider_model ON runs(provider, model)`);
139
- db.exec(`CREATE INDEX IF NOT EXISTS idx_runs_completed_at ON runs(completed_at)`);
140
- db.exec(`CREATE INDEX IF NOT EXISTS idx_runs_quality_score ON runs(quality_score)`);
141
- db.exec(`CREATE INDEX IF NOT EXISTS idx_tool_calls_run_id ON tool_calls(run_id)`);
142
- db.exec(`CREATE INDEX IF NOT EXISTS idx_quality_signals_run_id ON quality_signals(run_id)`);
143
- db.exec(`CREATE INDEX IF NOT EXISTS idx_learnings_run_id ON learnings(run_id)`);
144
- db.exec(`CREATE INDEX IF NOT EXISTS idx_learnings_category ON learnings(category)`);
145
- db.exec(`CREATE INDEX IF NOT EXISTS idx_feedback_run_id ON feedback(run_id)`);
146
- db.exec(`CREATE INDEX IF NOT EXISTS idx_edges_source ON learning_edges(source_id)`);
147
- db.exec(`CREATE INDEX IF NOT EXISTS idx_edges_target ON learning_edges(target_id)`);
148
- db.exec(`CREATE INDEX IF NOT EXISTS idx_edges_type ON learning_edges(edge_type)`);
149
-
150
- // FTS5 virtual table for text search over learnings
151
- db.exec(`
152
- CREATE VIRTUAL TABLE IF NOT EXISTS learnings_fts USING fts5(
153
- content,
154
- content='learnings',
155
- content_rowid='rowid'
156
- )
157
- `);
158
-
159
- // Triggers to keep FTS in sync
160
- db.exec(`
161
- CREATE TRIGGER IF NOT EXISTS learnings_ai AFTER INSERT ON learnings BEGIN
162
- INSERT INTO learnings_fts(rowid, content) VALUES (new.rowid, new.content);
163
- END
164
- `);
165
- db.exec(`
166
- CREATE TRIGGER IF NOT EXISTS learnings_ad AFTER DELETE ON learnings BEGIN
167
- INSERT INTO learnings_fts(learnings_fts, rowid, content) VALUES('delete', old.rowid, old.content);
168
- END
169
- `);
170
- db.exec(`
171
- CREATE TRIGGER IF NOT EXISTS learnings_au AFTER UPDATE ON learnings BEGIN
172
- INSERT INTO learnings_fts(learnings_fts, rowid, content) VALUES('delete', old.rowid, old.content);
173
- INSERT INTO learnings_fts(rowid, content) VALUES (new.rowid, new.content);
174
- END
175
- `);
176
- }
@@ -1,32 +0,0 @@
1
- /**
2
- * Signal normalization and combination.
3
- * Wraps the weighted scorer with signal-specific logic.
4
- */
5
-
6
- import { computeWeightedScore } from "../math/weighted-scorer.js";
7
- import type { QualitySignal, QualityScore } from "../types.js";
8
- import { ALGORITHM_VERSION } from "../types.js";
9
-
10
- /**
11
- * Combine quality signals into a single score using confidence-weighted
12
- * arithmetic mean.
13
- *
14
- * Formula: score = sum(w_i * c_i * s_i) / sum(w_i * c_i)
15
- *
16
- * Complexity: O(k) where k = number of signals (5)
17
- */
18
- export function combineSignals(signals: QualitySignal[]): QualityScore {
19
- const score = computeWeightedScore(
20
- signals.map((s) => ({
21
- value: s.value,
22
- weight: s.weight,
23
- confidence: s.confidence,
24
- })),
25
- );
26
-
27
- return {
28
- score,
29
- signals,
30
- algorithmVersion: ALGORITHM_VERSION,
31
- };
32
- }
@@ -1,78 +0,0 @@
1
- /**
2
- * Quality scoring engine.
3
- * Orchestrates signal extraction, EWMA updates, and score combination.
4
- * Purely mathematical, deterministic, zero LLM calls.
5
- */
6
-
7
- import type { DatabaseManager } from "../db.js";
8
- import type { CompletedRun, LearningLoopConfig, QualityScore, FeedbackRecord } from "../types.js";
9
- import { combineSignals } from "./normalization.js";
10
- import {
11
- extractTaskCompletion,
12
- extractToolEfficiency,
13
- extractResponseAppropriateness,
14
- extractLatencyRelative,
15
- extractUserFeedback,
16
- } from "./signal-extractors.js";
17
-
18
- export type QualityEngine = ReturnType<typeof createQualityEngine>;
19
-
20
- export function createQualityEngine(params: { config: LearningLoopConfig; db: DatabaseManager }) {
21
- const { config, db } = params;
22
- const weights = config.scoring.weights;
23
-
24
- /**
25
- * Score a completed run. Updates EWMA state for latency tracking.
26
- *
27
- * @param run - The completed run to score
28
- * @param feedback - Optional feedback (explicit or implicit)
29
- * @returns Quality score with all signal breakdowns
30
- */
31
- function scoreRun(
32
- run: CompletedRun,
33
- feedback?: { source: "explicit" | "implicit"; score: number },
34
- ): QualityScore {
35
- // Get EWMA state for latency comparison
36
- const ewmaState = db.getEwmaState(run.provider, run.model, "latency");
37
-
38
- // Extract all five signals
39
- const signals = [
40
- extractTaskCompletion(run, weights.taskCompletion),
41
- extractToolEfficiency(run, weights.toolEfficiency),
42
- extractResponseAppropriateness(run, weights.responseAppropriateLength),
43
- extractLatencyRelative(run, weights.latencyRelative, ewmaState),
44
- extractUserFeedback(weights.userFeedback, feedback),
45
- ];
46
-
47
- // Update EWMA for latency after scoring
48
- db.updateEwmaState(run.provider, run.model, "latency", run.durationMs);
49
-
50
- return combineSignals(signals);
51
- }
52
-
53
- /**
54
- * Re-score a run with new feedback (e.g., when implicit or explicit feedback arrives later).
55
- */
56
- function rescoreWithFeedback(
57
- run: CompletedRun,
58
- feedback: { source: "explicit" | "implicit"; score: number },
59
- ): QualityScore {
60
- // Re-extract all signals with the feedback
61
- const ewmaState = db.getEwmaState(run.provider, run.model, "latency");
62
-
63
- const signals = [
64
- extractTaskCompletion(run, weights.taskCompletion),
65
- extractToolEfficiency(run, weights.toolEfficiency),
66
- extractResponseAppropriateness(run, weights.responseAppropriateLength),
67
- extractLatencyRelative(run, weights.latencyRelative, ewmaState),
68
- extractUserFeedback(weights.userFeedback, feedback),
69
- ];
70
-
71
- return combineSignals(signals);
72
- }
73
-
74
- return {
75
- scoreRun,
76
- rescoreWithFeedback,
77
- };
78
- }
@@ -1,155 +0,0 @@
1
- /**
2
- * Five independent signal extraction functions.
3
- * Each returns a value in [0, 1] and a confidence in [0, 1].
4
- * All are deterministic and require zero LLM calls.
5
- */
6
-
7
- import { type EWMAState, getEWMAValue } from "../math/ewma.js";
8
- import type { CompletedRun, QualitySignal, ScoringWeights } from "../types.js";
9
-
10
- /**
11
- * Signal 1: Task Completion
12
- *
13
- * success && !error => 1.0
14
- * success && error => 0.5 (partial success with warnings)
15
- * fail => 0.0
16
- *
17
- * Confidence: always 1.0 (deterministic from agent_end)
18
- * Complexity: O(1)
19
- */
20
- export function extractTaskCompletion(run: CompletedRun, weight: number): QualitySignal {
21
- let value: number;
22
- if (run.success && !run.error) {
23
- value = 1.0;
24
- } else if (run.success && run.error) {
25
- value = 0.5;
26
- } else {
27
- value = 0.0;
28
- }
29
-
30
- return { name: "taskCompletion", value, confidence: 1.0, weight };
31
- }
32
-
33
- /**
34
- * Signal 2: Tool Efficiency
35
- *
36
- * Formula: 1.0 - 0.6*(errors/total) - 0.4*(redundant/total)
37
- * Redundant = consecutive calls with same tool+paramHash (likely retries of the same thing)
38
- *
39
- * Confidence: scales with tool call count (min 0.3 for 1 call, 1.0 for 5+)
40
- * Complexity: O(T) where T = number of tool calls
41
- */
42
- export function extractToolEfficiency(run: CompletedRun, weight: number): QualitySignal {
43
- const total = run.toolCalls.length;
44
- if (total === 0) {
45
- return { name: "toolEfficiency", value: 1.0, confidence: 0.3, weight };
46
- }
47
-
48
- let errors = 0;
49
- let redundant = 0;
50
-
51
- for (let i = 0; i < total; i++) {
52
- const tc = run.toolCalls[i]!;
53
- if (!tc.success) errors++;
54
-
55
- if (i > 0) {
56
- const prev = run.toolCalls[i - 1]!;
57
- if (tc.toolName === prev.toolName && tc.paramHash === prev.paramHash) {
58
- redundant++;
59
- }
60
- }
61
- }
62
-
63
- const value = Math.max(0, 1.0 - 0.6 * (errors / total) - 0.4 * (redundant / total));
64
- const confidence = Math.min(1.0, 0.3 + 0.7 * Math.min(1, (total - 1) / 4));
65
-
66
- return { name: "toolEfficiency", value, confidence, weight };
67
- }
68
-
69
- /**
70
- * Signal 3: Response Appropriateness (length)
71
- *
72
- * Log-normal penalty around a target ratio of response/prompt.
73
- * Formula: exp(-0.5 * (ln(ratio) / sigma)^2)
74
- *
75
- * The target ratio is 1.0 (response length ~ prompt length).
76
- * sigma = 1.5 allows wide variance before heavy penalty.
77
- *
78
- * Confidence: 0.5 (weak signal, length is a rough proxy)
79
- * Complexity: O(1)
80
- */
81
- export function extractResponseAppropriateness(run: CompletedRun, weight: number): QualitySignal {
82
- if (run.promptLength === 0 || run.responseLength === 0) {
83
- return { name: "responseAppropriateLength", value: 0.5, confidence: 0.3, weight };
84
- }
85
-
86
- const ratio = run.responseLength / run.promptLength;
87
- const sigma = 1.5;
88
- const lnRatio = Math.log(ratio);
89
- const value = Math.exp(-0.5 * (lnRatio / sigma) ** 2);
90
-
91
- return { name: "responseAppropriateLength", value, confidence: 0.5, weight };
92
- }
93
-
94
- /**
95
- * Signal 4: Latency Relative
96
- *
97
- * Compares this run's duration against the per-model EWMA.
98
- * Formula: min(1.0, ewma / duration)
99
- *
100
- * If the run is faster than average, score = 1.0.
101
- * If slower, score degrades linearly.
102
- *
103
- * Confidence: scales with EWMA observation count (0.3 for <3, 0.7 for 3-10, 1.0 for 10+)
104
- * Complexity: O(1)
105
- */
106
- export function extractLatencyRelative(
107
- run: CompletedRun,
108
- weight: number,
109
- ewmaState: EWMAState,
110
- ): QualitySignal {
111
- if (run.durationMs <= 0) {
112
- return { name: "latencyRelative", value: 1.0, confidence: 0.3, weight };
113
- }
114
-
115
- const ewmaValue = getEWMAValue(ewmaState, run.durationMs);
116
- const value = Math.min(1.0, ewmaValue / run.durationMs);
117
-
118
- let confidence: number;
119
- if (ewmaState.count < 3) confidence = 0.3;
120
- else if (ewmaState.count < 10) confidence = 0.7;
121
- else confidence = 1.0;
122
-
123
- return { name: "latencyRelative", value, confidence, weight };
124
- }
125
-
126
- /**
127
- * Signal 5: User Feedback
128
- *
129
- * Explicit: 1-5 rating mapped to [0, 1] via (score - 1) / 4
130
- * Implicit: re-ask = 0.0, topic change = 1.0, continuation = 0.5
131
- * No feedback: returns confidence 0 (signal is excluded from combination)
132
- *
133
- * Complexity: O(1)
134
- */
135
- export function extractUserFeedback(
136
- weight: number,
137
- feedback?: { source: "explicit" | "implicit"; score: number },
138
- ): QualitySignal {
139
- if (!feedback) {
140
- return { name: "userFeedback", value: 0.5, confidence: 0.0, weight };
141
- }
142
-
143
- let value: number;
144
- let confidence: number;
145
-
146
- if (feedback.source === "explicit") {
147
- value = Math.max(0, Math.min(1, (feedback.score - 1) / 4));
148
- confidence = 1.0;
149
- } else {
150
- value = Math.max(0, Math.min(1, feedback.score));
151
- confidence = 0.6;
152
- }
153
-
154
- return { name: "userFeedback", value, confidence, weight };
155
- }
@@ -1,142 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { createLRUCache } from "../injection/cache.js";
3
- import { formatLearningsContext } from "../injection/prompt-builder.js";
4
- import type { RetrievalResult } from "../learning/retrieval.js";
5
- import type { LearningLoopConfig } from "../types.js";
6
-
7
- describe("LRU Cache", () => {
8
- it("should return undefined for missing keys", () => {
9
- const cache = createLRUCache<string>();
10
- expect(cache.get("nonexistent")).toBeUndefined();
11
- });
12
-
13
- it("should store and retrieve values", () => {
14
- const cache = createLRUCache<string>();
15
- cache.set("key1", "value1");
16
- expect(cache.get("key1")).toBe("value1");
17
- });
18
-
19
- it("should evict oldest entries when at capacity", () => {
20
- const cache = createLRUCache<string>(2);
21
- cache.set("a", "1");
22
- cache.set("b", "2");
23
- cache.set("c", "3"); // evicts "a"
24
- expect(cache.get("a")).toBeUndefined();
25
- expect(cache.get("b")).toBe("2");
26
- expect(cache.get("c")).toBe("3");
27
- });
28
-
29
- it("should expire entries after TTL", () => {
30
- const cache = createLRUCache<string>(10, 1); // 1ms TTL
31
- cache.set("key", "value");
32
- // Wait for TTL (synchronous, but Date.now() should advance)
33
- const start = Date.now();
34
- while (Date.now() - start < 5) {
35
- // busy wait
36
- }
37
- expect(cache.get("key")).toBeUndefined();
38
- });
39
-
40
- it("should track hit rate", () => {
41
- const cache = createLRUCache<string>();
42
- cache.set("a", "1");
43
- cache.get("a"); // hit
44
- cache.get("b"); // miss
45
- const stats = cache.getStats();
46
- expect(stats.hits).toBe(1);
47
- expect(stats.misses).toBe(1);
48
- expect(stats.hitRate).toBeCloseTo(0.5, 5);
49
- });
50
-
51
- it("should refresh LRU order on access", () => {
52
- const cache = createLRUCache<string>(2);
53
- cache.set("a", "1");
54
- cache.set("b", "2");
55
- cache.get("a"); // refresh "a"
56
- cache.set("c", "3"); // evicts "b" (oldest)
57
- expect(cache.get("a")).toBe("1");
58
- expect(cache.get("b")).toBeUndefined();
59
- });
60
-
61
- it("should clear all entries and reset stats", () => {
62
- const cache = createLRUCache<string>();
63
- cache.set("a", "1");
64
- cache.get("a");
65
- cache.clear();
66
- expect(cache.get("a")).toBeUndefined();
67
- expect(cache.getStats().hits).toBe(0);
68
- });
69
- });
70
-
71
- describe("Prompt Builder", () => {
72
- const config: LearningLoopConfig = {
73
- capture: { embedPrompts: false, maxRuns: 10000 },
74
- scoring: {
75
- weights: {
76
- taskCompletion: 0.35,
77
- toolEfficiency: 0.25,
78
- responseAppropriateLength: 0.1,
79
- latencyRelative: 0.1,
80
- userFeedback: 0.2,
81
- },
82
- },
83
- injection: { maxLearnings: 5, minRelevance: 0.3, maxTokens: 500, cacheTtlMs: 60000 },
84
- decay: { halfLifeDays: 30 },
85
- };
86
-
87
- it("should return empty string for no results", () => {
88
- expect(formatLearningsContext([], config)).toBe("");
89
- });
90
-
91
- it("should format learnings with category tags", () => {
92
- const results: RetrievalResult[] = [
93
- {
94
- learning: {
95
- id: "lrn-1",
96
- runId: "run-1",
97
- category: "tool_pattern",
98
- content: "Use search before read",
99
- embedding: null,
100
- confidence: 0.8,
101
- appliedCount: 3,
102
- createdAt: Date.now(),
103
- updatedAt: Date.now(),
104
- },
105
- score: 0.9,
106
- decayedScore: 0.85,
107
- },
108
- ];
109
-
110
- const context = formatLearningsContext(results, config);
111
- expect(context).toContain("<learnings>");
112
- expect(context).toContain("</learnings>");
113
- expect(context).toContain("[tool_pattern]");
114
- expect(context).toContain("Use search before read");
115
- });
116
-
117
- it("should respect maxTokens limit", () => {
118
- const longContent = "A".repeat(5000);
119
- const results: RetrievalResult[] = [
120
- {
121
- learning: {
122
- id: "lrn-1",
123
- runId: "run-1",
124
- category: "tool_pattern",
125
- content: longContent,
126
- embedding: null,
127
- confidence: 0.8,
128
- appliedCount: 0,
129
- createdAt: Date.now(),
130
- updatedAt: Date.now(),
131
- },
132
- score: 0.9,
133
- decayedScore: 0.85,
134
- },
135
- ];
136
-
137
- const smallConfig = { ...config, injection: { ...config.injection, maxTokens: 50 } };
138
- const context = formatLearningsContext(results, smallConfig);
139
- // Should be empty because the single learning exceeds maxTokens
140
- expect(context).toBe("");
141
- });
142
- });