nodebench-mcp 2.69.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +95 -39
- package/dist/agents/alertRouter.d.ts +38 -0
- package/dist/agents/alertRouter.js +151 -0
- package/dist/agents/alertRouter.js.map +1 -0
- package/dist/agents/entityMemory.d.ts +40 -0
- package/dist/agents/entityMemory.js +64 -0
- package/dist/agents/entityMemory.js.map +1 -0
- package/dist/agents/subAgents.d.ts +35 -0
- package/dist/agents/subAgents.js +62 -0
- package/dist/agents/subAgents.js.map +1 -0
- package/dist/benchmarks/benchmarkRunner.js +14 -0
- package/dist/benchmarks/benchmarkRunner.js.map +1 -1
- package/dist/benchmarks/chainEval.js +107 -0
- package/dist/benchmarks/chainEval.js.map +1 -1
- package/dist/benchmarks/llmJudgeEval.js +85 -0
- package/dist/benchmarks/llmJudgeEval.js.map +1 -1
- package/dist/benchmarks/searchQualityEval.js +118 -5
- package/dist/benchmarks/searchQualityEval.js.map +1 -1
- package/dist/cli/search.d.ts +13 -0
- package/dist/cli/search.js +130 -0
- package/dist/cli/search.js.map +1 -0
- package/dist/db.d.ts +6 -2
- package/dist/db.js +470 -3
- package/dist/db.js.map +1 -1
- package/dist/index.js +349 -64
- package/dist/index.js.map +1 -1
- package/dist/profiler/behaviorStore.d.ts +97 -0
- package/dist/profiler/behaviorStore.js +276 -0
- package/dist/profiler/behaviorStore.js.map +1 -0
- package/dist/profiler/eventCollector.d.ts +119 -0
- package/dist/profiler/eventCollector.js +267 -0
- package/dist/profiler/eventCollector.js.map +1 -0
- package/dist/profiler/index.d.ts +15 -0
- package/dist/profiler/index.js +16 -0
- package/dist/profiler/index.js.map +1 -0
- package/dist/profiler/mcpProxy.d.ts +49 -0
- package/dist/profiler/mcpProxy.js +123 -0
- package/dist/profiler/mcpProxy.js.map +1 -0
- package/dist/profiler/modelRouter.d.ts +30 -0
- package/dist/profiler/modelRouter.js +99 -0
- package/dist/profiler/modelRouter.js.map +1 -0
- package/dist/profiler/otelReceiver.d.ts +17 -0
- package/dist/profiler/otelReceiver.js +62 -0
- package/dist/profiler/otelReceiver.js.map +1 -0
- package/dist/profiler/proofEngine.d.ts +41 -0
- package/dist/profiler/proofEngine.js +93 -0
- package/dist/profiler/proofEngine.js.map +1 -0
- package/dist/profiler/workflowTemplates.d.ts +41 -0
- package/dist/profiler/workflowTemplates.js +95 -0
- package/dist/profiler/workflowTemplates.js.map +1 -0
- package/dist/providers/localMemoryProvider.js +3 -2
- package/dist/providers/localMemoryProvider.js.map +1 -1
- package/dist/runtimeConfig.d.ts +11 -0
- package/dist/runtimeConfig.js +27 -0
- package/dist/runtimeConfig.js.map +1 -0
- package/dist/security/auditLog.js +8 -3
- package/dist/security/auditLog.js.map +1 -1
- package/dist/subconscious/blocks.d.ts +43 -0
- package/dist/subconscious/blocks.js +158 -0
- package/dist/subconscious/blocks.js.map +1 -0
- package/dist/subconscious/classifier.d.ts +22 -0
- package/dist/subconscious/classifier.js +118 -0
- package/dist/subconscious/classifier.js.map +1 -0
- package/dist/subconscious/graphEngine.d.ts +65 -0
- package/dist/subconscious/graphEngine.js +234 -0
- package/dist/subconscious/graphEngine.js.map +1 -0
- package/dist/subconscious/index.d.ts +19 -0
- package/dist/subconscious/index.js +20 -0
- package/dist/subconscious/index.js.map +1 -0
- package/dist/subconscious/tools.d.ts +5 -0
- package/dist/subconscious/tools.js +255 -0
- package/dist/subconscious/tools.js.map +1 -0
- package/dist/subconscious/whisperPolicy.d.ts +20 -0
- package/dist/subconscious/whisperPolicy.js +171 -0
- package/dist/subconscious/whisperPolicy.js.map +1 -0
- package/dist/sweep/engine.d.ts +27 -0
- package/dist/sweep/engine.js +244 -0
- package/dist/sweep/engine.js.map +1 -0
- package/dist/sweep/index.d.ts +9 -0
- package/dist/sweep/index.js +8 -0
- package/dist/sweep/index.js.map +1 -0
- package/dist/sweep/sources/github_trending.d.ts +6 -0
- package/dist/sweep/sources/github_trending.js +37 -0
- package/dist/sweep/sources/github_trending.js.map +1 -0
- package/dist/sweep/sources/hackernews.d.ts +7 -0
- package/dist/sweep/sources/hackernews.js +57 -0
- package/dist/sweep/sources/hackernews.js.map +1 -0
- package/dist/sweep/sources/openbb_finance.d.ts +9 -0
- package/dist/sweep/sources/openbb_finance.js +46 -0
- package/dist/sweep/sources/openbb_finance.js.map +1 -0
- package/dist/sweep/sources/producthunt.d.ts +6 -0
- package/dist/sweep/sources/producthunt.js +41 -0
- package/dist/sweep/sources/producthunt.js.map +1 -0
- package/dist/sweep/sources/web_signals.d.ts +7 -0
- package/dist/sweep/sources/web_signals.js +63 -0
- package/dist/sweep/sources/web_signals.js.map +1 -0
- package/dist/sweep/sources/yahoo_finance.d.ts +6 -0
- package/dist/sweep/sources/yahoo_finance.js +47 -0
- package/dist/sweep/sources/yahoo_finance.js.map +1 -0
- package/dist/sweep/types.d.ts +50 -0
- package/dist/sweep/types.js +9 -0
- package/dist/sweep/types.js.map +1 -0
- package/dist/sync/founderEpisodeStore.d.ts +98 -0
- package/dist/sync/founderEpisodeStore.js +230 -0
- package/dist/sync/founderEpisodeStore.js.map +1 -0
- package/dist/sync/hyperloopArchive.d.ts +51 -0
- package/dist/sync/hyperloopArchive.js +153 -0
- package/dist/sync/hyperloopArchive.js.map +1 -0
- package/dist/sync/hyperloopEval.d.ts +123 -0
- package/dist/sync/hyperloopEval.js +389 -0
- package/dist/sync/hyperloopEval.js.map +1 -0
- package/dist/sync/hyperloopEval.test.d.ts +4 -0
- package/dist/sync/hyperloopEval.test.js +60 -0
- package/dist/sync/hyperloopEval.test.js.map +1 -0
- package/dist/sync/protocol.d.ts +172 -0
- package/dist/sync/protocol.js +9 -0
- package/dist/sync/protocol.js.map +1 -0
- package/dist/sync/sessionMemory.d.ts +47 -0
- package/dist/sync/sessionMemory.js +138 -0
- package/dist/sync/sessionMemory.js.map +1 -0
- package/dist/sync/store.d.ts +384 -0
- package/dist/sync/store.js +1435 -0
- package/dist/sync/store.js.map +1 -0
- package/dist/sync/store.test.d.ts +4 -0
- package/dist/sync/store.test.js +43 -0
- package/dist/sync/store.test.js.map +1 -0
- package/dist/sync/syncBridgeClient.d.ts +30 -0
- package/dist/sync/syncBridgeClient.js +172 -0
- package/dist/sync/syncBridgeClient.js.map +1 -0
- package/dist/tools/autonomousDeliveryTools.d.ts +2 -0
- package/dist/tools/autonomousDeliveryTools.js +1104 -0
- package/dist/tools/autonomousDeliveryTools.js.map +1 -0
- package/dist/tools/claudeCodeIngestTools.d.ts +10 -0
- package/dist/tools/claudeCodeIngestTools.js +347 -0
- package/dist/tools/claudeCodeIngestTools.js.map +1 -0
- package/dist/tools/coreWorkflowTools.d.ts +2 -0
- package/dist/tools/coreWorkflowTools.js +488 -0
- package/dist/tools/coreWorkflowTools.js.map +1 -0
- package/dist/tools/deltaTools.d.ts +15 -0
- package/dist/tools/deltaTools.js +1522 -0
- package/dist/tools/deltaTools.js.map +1 -0
- package/dist/tools/entityLookupTools.d.ts +14 -0
- package/dist/tools/entityLookupTools.js +159 -0
- package/dist/tools/entityLookupTools.js.map +1 -0
- package/dist/tools/entityTemporalTools.d.ts +12 -0
- package/dist/tools/entityTemporalTools.js +330 -0
- package/dist/tools/entityTemporalTools.js.map +1 -0
- package/dist/tools/founderLocalPipeline.d.ts +215 -0
- package/dist/tools/founderLocalPipeline.js +1516 -2
- package/dist/tools/founderLocalPipeline.js.map +1 -1
- package/dist/tools/founderOperatingModel.d.ts +120 -0
- package/dist/tools/founderOperatingModel.js +469 -0
- package/dist/tools/founderOperatingModel.js.map +1 -0
- package/dist/tools/founderOperatingModelTools.d.ts +2 -0
- package/dist/tools/founderOperatingModelTools.js +169 -0
- package/dist/tools/founderOperatingModelTools.js.map +1 -0
- package/dist/tools/founderStrategicOpsTools.d.ts +2 -0
- package/dist/tools/founderStrategicOpsTools.js +1310 -0
- package/dist/tools/founderStrategicOpsTools.js.map +1 -0
- package/dist/tools/graphifyTools.d.ts +19 -0
- package/dist/tools/graphifyTools.js +375 -0
- package/dist/tools/graphifyTools.js.map +1 -0
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.js +4 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/monteCarloTools.d.ts +16 -0
- package/dist/tools/monteCarloTools.js +225 -0
- package/dist/tools/monteCarloTools.js.map +1 -0
- package/dist/tools/packetCompilerTools.d.ts +12 -0
- package/dist/tools/packetCompilerTools.js +322 -0
- package/dist/tools/packetCompilerTools.js.map +1 -0
- package/dist/tools/planSynthesisTools.d.ts +15 -0
- package/dist/tools/planSynthesisTools.js +455 -0
- package/dist/tools/planSynthesisTools.js.map +1 -0
- package/dist/tools/profilerTools.d.ts +20 -0
- package/dist/tools/profilerTools.js +364 -0
- package/dist/tools/profilerTools.js.map +1 -0
- package/dist/tools/savingsTools.d.ts +11 -0
- package/dist/tools/savingsTools.js +155 -0
- package/dist/tools/savingsTools.js.map +1 -0
- package/dist/tools/scenarioCompilerTools.d.ts +14 -0
- package/dist/tools/scenarioCompilerTools.js +290 -0
- package/dist/tools/scenarioCompilerTools.js.map +1 -0
- package/dist/tools/sharedContextTools.d.ts +2 -0
- package/dist/tools/sharedContextTools.js +423 -0
- package/dist/tools/sharedContextTools.js.map +1 -0
- package/dist/tools/sitemapTools.d.ts +15 -0
- package/dist/tools/sitemapTools.js +560 -0
- package/dist/tools/sitemapTools.js.map +1 -0
- package/dist/tools/sweepTools.d.ts +9 -0
- package/dist/tools/sweepTools.js +112 -0
- package/dist/tools/sweepTools.js.map +1 -0
- package/dist/tools/syncBridgeTools.d.ts +2 -0
- package/dist/tools/syncBridgeTools.js +258 -0
- package/dist/tools/syncBridgeTools.js.map +1 -0
- package/dist/tools/toolRegistry.js +1216 -49
- package/dist/tools/toolRegistry.js.map +1 -1
- package/dist/tools/workspaceTools.d.ts +19 -0
- package/dist/tools/workspaceTools.js +762 -0
- package/dist/tools/workspaceTools.js.map +1 -0
- package/dist/toolsetRegistry.js +88 -2
- package/dist/toolsetRegistry.js.map +1 -1
- package/package.json +36 -36
- package/rules/nodebench-agentic-reliability.md +32 -0
- package/rules/nodebench-analyst-diagnostic.md +25 -0
- package/rules/nodebench-auto-qa.md +31 -0
- package/rules/nodebench-completion-traceability.md +22 -0
- package/rules/nodebench-flywheel-continuous.md +25 -0
- package/rules/nodebench-pre-release-review.md +24 -0
- package/rules/nodebench-qa-dogfood.md +26 -0
- package/rules/nodebench-scenario-testing.md +30 -0
- package/rules/nodebench-self-direction.md +23 -0
- package/rules/nodebench-self-judge-loop.md +24 -0
- package/scripts/install.sh +215 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* behaviorStore.ts — Behavioral Profiling persistence layer.
|
|
3
|
+
*
|
|
4
|
+
* SQLite tables for logging sessions, queries, tool calls, context reuse,
|
|
5
|
+
* and workflow paths. This is the foundation for the Founder Operating Profiler.
|
|
6
|
+
*
|
|
7
|
+
* Architecture:
|
|
8
|
+
* - LLM for meaning (intent inference, pattern detection, optimization suggestions)
|
|
9
|
+
* - Deterministic code for control (logging, storage, privacy, budget enforcement)
|
|
10
|
+
*
|
|
11
|
+
* Tables:
|
|
12
|
+
* - behavior_sessions: session-level aggregates
|
|
13
|
+
* - behavior_queries: every search/prompt with normalized intent
|
|
14
|
+
* - behavior_tool_calls: every MCP tool call with cost/latency
|
|
15
|
+
* - context_reuse_events: when prior packets/context were reused
|
|
16
|
+
* - workflow_paths: end-to-end step sequences with outcomes
|
|
17
|
+
* - optimization_suggestions: generated optimization recommendations
|
|
18
|
+
* - behavior_profiles: compact learned user behavior (updated periodically)
|
|
19
|
+
*/
|
|
20
|
+
import { getDb, genId } from "../db.js";
|
|
21
|
+
// ── Schema initialization ────────────────────────────────────────────
|
|
22
|
+
export function initBehaviorTables() {
|
|
23
|
+
const db = getDb();
|
|
24
|
+
db.exec(`
|
|
25
|
+
CREATE TABLE IF NOT EXISTS behavior_sessions (
|
|
26
|
+
id TEXT PRIMARY KEY,
|
|
27
|
+
user_id TEXT,
|
|
28
|
+
company_id TEXT,
|
|
29
|
+
interface_surface TEXT DEFAULT 'ai_app',
|
|
30
|
+
role_inferred TEXT DEFAULT 'founder',
|
|
31
|
+
start_time TEXT NOT NULL,
|
|
32
|
+
end_time TEXT,
|
|
33
|
+
main_objective TEXT,
|
|
34
|
+
packet_types_used TEXT DEFAULT '[]',
|
|
35
|
+
artifacts_produced TEXT DEFAULT '[]',
|
|
36
|
+
total_tool_calls INTEGER DEFAULT 0,
|
|
37
|
+
total_model_calls INTEGER DEFAULT 0,
|
|
38
|
+
estimated_tokens_in INTEGER DEFAULT 0,
|
|
39
|
+
estimated_tokens_out INTEGER DEFAULT 0,
|
|
40
|
+
estimated_cost_usd REAL DEFAULT 0,
|
|
41
|
+
latency_total_ms INTEGER DEFAULT 0,
|
|
42
|
+
redundant_calls INTEGER DEFAULT 0,
|
|
43
|
+
reused_context_count INTEGER DEFAULT 0,
|
|
44
|
+
optimization_score INTEGER DEFAULT 100
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
CREATE TABLE IF NOT EXISTS behavior_queries (
|
|
48
|
+
id TEXT PRIMARY KEY,
|
|
49
|
+
session_id TEXT NOT NULL,
|
|
50
|
+
raw_query TEXT NOT NULL,
|
|
51
|
+
normalized_intent TEXT,
|
|
52
|
+
classification TEXT,
|
|
53
|
+
entity_targets TEXT DEFAULT '[]',
|
|
54
|
+
own_company_mode INTEGER DEFAULT 0,
|
|
55
|
+
followup_to_query_id TEXT,
|
|
56
|
+
resulting_packet_type TEXT,
|
|
57
|
+
resulting_artifact_type TEXT,
|
|
58
|
+
confidence_score REAL,
|
|
59
|
+
latency_ms INTEGER,
|
|
60
|
+
timestamp TEXT NOT NULL,
|
|
61
|
+
FOREIGN KEY (session_id) REFERENCES behavior_sessions(id)
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
CREATE TABLE IF NOT EXISTS behavior_tool_calls (
|
|
65
|
+
id TEXT PRIMARY KEY,
|
|
66
|
+
session_id TEXT NOT NULL,
|
|
67
|
+
query_id TEXT,
|
|
68
|
+
tool_name TEXT NOT NULL,
|
|
69
|
+
input_summary TEXT,
|
|
70
|
+
output_summary TEXT,
|
|
71
|
+
latency_ms INTEGER DEFAULT 0,
|
|
72
|
+
cost_estimate_usd REAL DEFAULT 0,
|
|
73
|
+
cache_hit INTEGER DEFAULT 0,
|
|
74
|
+
success INTEGER DEFAULT 1,
|
|
75
|
+
model_used TEXT,
|
|
76
|
+
token_estimate INTEGER,
|
|
77
|
+
packet_id TEXT,
|
|
78
|
+
entity_ids TEXT DEFAULT '[]',
|
|
79
|
+
is_redundant INTEGER DEFAULT 0,
|
|
80
|
+
timestamp TEXT NOT NULL,
|
|
81
|
+
FOREIGN KEY (session_id) REFERENCES behavior_sessions(id)
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
CREATE TABLE IF NOT EXISTS context_reuse_events (
|
|
85
|
+
id TEXT PRIMARY KEY,
|
|
86
|
+
session_id TEXT NOT NULL,
|
|
87
|
+
reuse_type TEXT NOT NULL,
|
|
88
|
+
source_packet_id TEXT,
|
|
89
|
+
source_memo_id TEXT,
|
|
90
|
+
source_query_id TEXT,
|
|
91
|
+
fields_reused TEXT DEFAULT '[]',
|
|
92
|
+
tokens_saved_estimate INTEGER DEFAULT 0,
|
|
93
|
+
timestamp TEXT NOT NULL,
|
|
94
|
+
FOREIGN KEY (session_id) REFERENCES behavior_sessions(id)
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
CREATE TABLE IF NOT EXISTS workflow_paths (
|
|
98
|
+
id TEXT PRIMARY KEY,
|
|
99
|
+
session_id TEXT NOT NULL,
|
|
100
|
+
objective TEXT,
|
|
101
|
+
steps TEXT NOT NULL DEFAULT '[]',
|
|
102
|
+
total_steps INTEGER DEFAULT 0,
|
|
103
|
+
decision_points INTEGER DEFAULT 0,
|
|
104
|
+
branches_taken TEXT DEFAULT '[]',
|
|
105
|
+
terminal_output_type TEXT,
|
|
106
|
+
success INTEGER DEFAULT 1,
|
|
107
|
+
total_latency_ms INTEGER DEFAULT 0,
|
|
108
|
+
total_cost_usd REAL DEFAULT 0,
|
|
109
|
+
timestamp TEXT NOT NULL,
|
|
110
|
+
FOREIGN KEY (session_id) REFERENCES behavior_sessions(id)
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
CREATE TABLE IF NOT EXISTS optimization_suggestions (
|
|
114
|
+
id TEXT PRIMARY KEY,
|
|
115
|
+
session_id TEXT,
|
|
116
|
+
target_workflow_id TEXT,
|
|
117
|
+
suggestion_type TEXT NOT NULL,
|
|
118
|
+
current_path_summary TEXT,
|
|
119
|
+
proposed_path_summary TEXT,
|
|
120
|
+
expected_cost_delta_pct REAL,
|
|
121
|
+
expected_latency_delta_pct REAL,
|
|
122
|
+
expected_quality_delta_pct REAL,
|
|
123
|
+
confidence_score REAL DEFAULT 0,
|
|
124
|
+
validation_required INTEGER DEFAULT 1,
|
|
125
|
+
status TEXT DEFAULT 'suggested',
|
|
126
|
+
actionable_text TEXT,
|
|
127
|
+
timestamp TEXT NOT NULL
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
CREATE TABLE IF NOT EXISTS behavior_profiles (
|
|
131
|
+
id TEXT PRIMARY KEY,
|
|
132
|
+
user_id TEXT UNIQUE,
|
|
133
|
+
preferred_artifacts TEXT DEFAULT '[]',
|
|
134
|
+
common_roles TEXT DEFAULT '[]',
|
|
135
|
+
common_workflow_families TEXT DEFAULT '[]',
|
|
136
|
+
cost_sensitivity TEXT DEFAULT 'medium',
|
|
137
|
+
latency_sensitivity TEXT DEFAULT 'medium',
|
|
138
|
+
favorite_model_paths TEXT DEFAULT '[]',
|
|
139
|
+
recurring_entity_clusters TEXT DEFAULT '[]',
|
|
140
|
+
common_delegation_targets TEXT DEFAULT '[]',
|
|
141
|
+
typical_followup_style TEXT DEFAULT 'iterative',
|
|
142
|
+
total_sessions INTEGER DEFAULT 0,
|
|
143
|
+
total_queries INTEGER DEFAULT 0,
|
|
144
|
+
total_tool_calls INTEGER DEFAULT 0,
|
|
145
|
+
lifetime_cost_usd REAL DEFAULT 0,
|
|
146
|
+
last_updated TEXT NOT NULL
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
CREATE INDEX IF NOT EXISTS idx_bq_session ON behavior_queries(session_id);
|
|
150
|
+
CREATE INDEX IF NOT EXISTS idx_btc_session ON behavior_tool_calls(session_id);
|
|
151
|
+
CREATE INDEX IF NOT EXISTS idx_btc_tool ON behavior_tool_calls(tool_name);
|
|
152
|
+
CREATE INDEX IF NOT EXISTS idx_cre_session ON context_reuse_events(session_id);
|
|
153
|
+
CREATE INDEX IF NOT EXISTS idx_wp_session ON workflow_paths(session_id);
|
|
154
|
+
CREATE INDEX IF NOT EXISTS idx_os_status ON optimization_suggestions(status);
|
|
155
|
+
CREATE INDEX IF NOT EXISTS idx_bp_user ON behavior_profiles(user_id);
|
|
156
|
+
`);
|
|
157
|
+
}
|
|
158
|
+
// ── Logging functions ────────────────────────────────────────────────
|
|
159
|
+
export function logSession(data) {
|
|
160
|
+
const db = getDb();
|
|
161
|
+
const id = genId("bsess");
|
|
162
|
+
db.prepare(`
|
|
163
|
+
INSERT INTO behavior_sessions (id, user_id, company_id, interface_surface, role_inferred, start_time, main_objective)
|
|
164
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
165
|
+
`).run(id, data.userId ?? null, data.companyId ?? null, data.interfaceSurface ?? "ai_app", data.roleInferred ?? "founder", new Date().toISOString(), data.mainObjective ?? null);
|
|
166
|
+
return id;
|
|
167
|
+
}
|
|
168
|
+
export function logQuery(data) {
|
|
169
|
+
const db = getDb();
|
|
170
|
+
const id = genId("bqry");
|
|
171
|
+
db.prepare(`
|
|
172
|
+
INSERT INTO behavior_queries (id, session_id, raw_query, normalized_intent, classification, entity_targets, own_company_mode, followup_to_query_id, confidence_score, latency_ms, timestamp)
|
|
173
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
174
|
+
`).run(id, data.sessionId, data.rawQuery, data.normalizedIntent ?? null, data.classification ?? null, JSON.stringify(data.entityTargets ?? []), data.ownCompanyMode ? 1 : 0, data.followupToQueryId ?? null, data.confidenceScore ?? null, data.latencyMs ?? null, new Date().toISOString());
|
|
175
|
+
return id;
|
|
176
|
+
}
|
|
177
|
+
export function logToolCall(data) {
|
|
178
|
+
const db = getDb();
|
|
179
|
+
const id = genId("btc");
|
|
180
|
+
db.prepare(`
|
|
181
|
+
INSERT INTO behavior_tool_calls (id, session_id, query_id, tool_name, input_summary, output_summary, latency_ms, cost_estimate_usd, cache_hit, success, model_used, token_estimate, is_redundant, timestamp)
|
|
182
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
183
|
+
`).run(id, data.sessionId, data.queryId ?? null, data.toolName, data.inputSummary ?? null, data.outputSummary ?? null, data.latencyMs, data.costEstimateUsd, data.cacheHit ? 1 : 0, data.success ? 1 : 0, data.modelUsed ?? null, data.tokenEstimate ?? null, data.isRedundant ? 1 : 0, new Date().toISOString());
|
|
184
|
+
// Update session aggregates
|
|
185
|
+
db.prepare(`
|
|
186
|
+
UPDATE behavior_sessions SET
|
|
187
|
+
total_tool_calls = total_tool_calls + 1,
|
|
188
|
+
estimated_cost_usd = estimated_cost_usd + ?,
|
|
189
|
+
latency_total_ms = latency_total_ms + ?,
|
|
190
|
+
redundant_calls = redundant_calls + ?
|
|
191
|
+
WHERE id = ?
|
|
192
|
+
`).run(data.costEstimateUsd, data.latencyMs, data.isRedundant ? 1 : 0, data.sessionId);
|
|
193
|
+
return id;
|
|
194
|
+
}
|
|
195
|
+
export function logContextReuse(data) {
|
|
196
|
+
const db = getDb();
|
|
197
|
+
const id = genId("bcre");
|
|
198
|
+
db.prepare(`
|
|
199
|
+
INSERT INTO context_reuse_events (id, session_id, reuse_type, source_packet_id, source_memo_id, source_query_id, fields_reused, tokens_saved_estimate, timestamp)
|
|
200
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
201
|
+
`).run(id, data.sessionId, data.reuseType, data.sourcePacketId ?? null, data.sourceMemoId ?? null, data.sourceQueryId ?? null, JSON.stringify(data.fieldsReused ?? []), data.tokensSavedEstimate ?? 0, new Date().toISOString());
|
|
202
|
+
// Update session reuse count
|
|
203
|
+
db.prepare(`UPDATE behavior_sessions SET reused_context_count = reused_context_count + 1 WHERE id = ?`).run(data.sessionId);
|
|
204
|
+
return id;
|
|
205
|
+
}
|
|
206
|
+
// ── Query functions for insights ─────────────────────────────────────
|
|
207
|
+
export function getSessionInsights(sessionId) {
|
|
208
|
+
const db = getDb();
|
|
209
|
+
const session = db.prepare(`SELECT * FROM behavior_sessions WHERE id = ?`).get(sessionId);
|
|
210
|
+
const queries = db.prepare(`SELECT * FROM behavior_queries WHERE session_id = ? ORDER BY timestamp`).all(sessionId);
|
|
211
|
+
const toolCalls = db.prepare(`SELECT * FROM behavior_tool_calls WHERE session_id = ? ORDER BY timestamp`).all(sessionId);
|
|
212
|
+
const reuseEvents = db.prepare(`SELECT * FROM context_reuse_events WHERE session_id = ? ORDER BY timestamp`).all(sessionId);
|
|
213
|
+
// Aggregate tool stats
|
|
214
|
+
const toolStats = db.prepare(`
|
|
215
|
+
SELECT tool_name as tool, COUNT(*) as count,
|
|
216
|
+
CAST(AVG(latency_ms) AS INTEGER) as avgLatencyMs,
|
|
217
|
+
ROUND(SUM(cost_estimate_usd), 4) as totalCost
|
|
218
|
+
FROM behavior_tool_calls WHERE session_id = ?
|
|
219
|
+
GROUP BY tool_name ORDER BY count DESC LIMIT 10
|
|
220
|
+
`).all(sessionId);
|
|
221
|
+
return { session, queries, toolCalls, reuseEvents, topTools: toolStats };
|
|
222
|
+
}
|
|
223
|
+
export function getAggregateInsights(daysBack = 7) {
|
|
224
|
+
const db = getDb();
|
|
225
|
+
const since = new Date(Date.now() - daysBack * 86400000).toISOString();
|
|
226
|
+
const stats = db.prepare(`
|
|
227
|
+
SELECT COUNT(*) as totalSessions,
|
|
228
|
+
COALESCE(SUM(total_tool_calls), 0) as totalToolCalls,
|
|
229
|
+
COALESCE(SUM(estimated_cost_usd), 0) as totalCost,
|
|
230
|
+
COALESCE(SUM(redundant_calls), 0) as redundantCalls,
|
|
231
|
+
COALESCE(SUM(reused_context_count), 0) as reusedCount
|
|
232
|
+
FROM behavior_sessions WHERE start_time >= ?
|
|
233
|
+
`).get(since);
|
|
234
|
+
const totalQueries = db.prepare(`SELECT COUNT(*) as c FROM behavior_queries WHERE timestamp >= ?`).get(since)?.c ?? 0;
|
|
235
|
+
const topTools = db.prepare(`
|
|
236
|
+
SELECT tool_name as tool, COUNT(*) as count,
|
|
237
|
+
CAST(AVG(latency_ms) AS INTEGER) as avgLatencyMs,
|
|
238
|
+
ROUND(SUM(cost_estimate_usd), 4) as totalCost
|
|
239
|
+
FROM behavior_tool_calls WHERE timestamp >= ?
|
|
240
|
+
GROUP BY tool_name ORDER BY count DESC LIMIT 15
|
|
241
|
+
`).all(since);
|
|
242
|
+
// Find repeated queries (normalized)
|
|
243
|
+
const repeatedQueries = db.prepare(`
|
|
244
|
+
SELECT raw_query as query, COUNT(*) as count
|
|
245
|
+
FROM behavior_queries WHERE timestamp >= ?
|
|
246
|
+
GROUP BY LOWER(TRIM(raw_query)) HAVING count > 1
|
|
247
|
+
ORDER BY count DESC LIMIT 10
|
|
248
|
+
`).all(since);
|
|
249
|
+
return {
|
|
250
|
+
totalSessions: stats?.totalSessions ?? 0,
|
|
251
|
+
totalQueries,
|
|
252
|
+
totalToolCalls: stats?.totalToolCalls ?? 0,
|
|
253
|
+
totalCostUsd: Math.round((stats?.totalCost ?? 0) * 1000) / 1000,
|
|
254
|
+
redundantCallRate: stats?.totalToolCalls > 0 ? Math.round((stats?.redundantCalls / stats?.totalToolCalls) * 100) : 0,
|
|
255
|
+
topTools,
|
|
256
|
+
repeatedQueries,
|
|
257
|
+
reuseRate: stats?.totalSessions > 0 ? Math.round((stats?.reusedCount / stats?.totalSessions) * 100) : 0,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
// ── Repeated question detection ──────────────────────────────────────
|
|
261
|
+
export function findSimilarPriorQuery(query, sessionId) {
|
|
262
|
+
const db = getDb();
|
|
263
|
+
const normalized = query.toLowerCase().trim().replace(/[?!.,]+$/g, "");
|
|
264
|
+
// Exact or near-exact match in last 7 days
|
|
265
|
+
const prior = db.prepare(`
|
|
266
|
+
SELECT id, raw_query, resulting_packet_type, classification
|
|
267
|
+
FROM behavior_queries
|
|
268
|
+
WHERE LOWER(TRIM(raw_query)) = ? AND (session_id != ? OR ? IS NULL)
|
|
269
|
+
ORDER BY timestamp DESC LIMIT 1
|
|
270
|
+
`).get(normalized, sessionId ?? null, sessionId ?? null);
|
|
271
|
+
if (prior) {
|
|
272
|
+
return { found: true, priorQueryId: prior.id, similarity: "exact" };
|
|
273
|
+
}
|
|
274
|
+
return { found: false };
|
|
275
|
+
}
|
|
276
|
+
//# sourceMappingURL=behaviorStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"behaviorStore.js","sourceRoot":"","sources":["../../src/profiler/behaviorStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* eventCollector.ts — Unified Event Collector for the Integration Layer.
|
|
3
|
+
*
|
|
4
|
+
* All observation paths (MCP proxy, OTel, hooks, framework wrappers)
|
|
5
|
+
* convert into one canonical event schema and flow through this collector.
|
|
6
|
+
*
|
|
7
|
+
* The collector:
|
|
8
|
+
* 1. Normalizes events from any source into UnifiedEvent
|
|
9
|
+
* 2. Stores in SQLite (bounded, with eviction)
|
|
10
|
+
* 3. Detects duplicates and correlates by session/trace
|
|
11
|
+
* 4. Feeds the pattern engine and proof layer
|
|
12
|
+
*
|
|
13
|
+
* Integration paths:
|
|
14
|
+
* A. Claude Code / local hook → logHookEvent()
|
|
15
|
+
* B. MCP proxy → logMcpProxyEvent()
|
|
16
|
+
* C. OTel receiver → logOtelSpan()
|
|
17
|
+
* D. Framework wrappers → logFrameworkEvent()
|
|
18
|
+
*
|
|
19
|
+
* All converge to: ingestEvent(UnifiedEvent)
|
|
20
|
+
*/
|
|
21
|
+
export interface UnifiedEvent {
|
|
22
|
+
eventId: string;
|
|
23
|
+
timestamp: string;
|
|
24
|
+
surface: "claude_code" | "cursor" | "windsurf" | "openclaw" | "ai_app" | "mcp_direct" | "otel" | "framework" | "unknown";
|
|
25
|
+
integrationPath: "mcp_proxy" | "otel_receiver" | "local_hook" | "framework_wrapper" | "direct";
|
|
26
|
+
sessionId: string;
|
|
27
|
+
traceId?: string;
|
|
28
|
+
spanId?: string;
|
|
29
|
+
companyId?: string;
|
|
30
|
+
userId?: string;
|
|
31
|
+
toolName: string;
|
|
32
|
+
toolInputSummary?: string;
|
|
33
|
+
toolOutputSummary?: string;
|
|
34
|
+
latencyMs: number;
|
|
35
|
+
tokenIn?: number;
|
|
36
|
+
tokenOut?: number;
|
|
37
|
+
estimatedCostUsd: number;
|
|
38
|
+
cacheHit: boolean;
|
|
39
|
+
success: boolean;
|
|
40
|
+
modelUsed?: string;
|
|
41
|
+
packetId?: string;
|
|
42
|
+
entityIds?: string[];
|
|
43
|
+
artifactIds?: string[];
|
|
44
|
+
pathStepIndex?: number;
|
|
45
|
+
fingerprint?: string;
|
|
46
|
+
parentSpanId?: string;
|
|
47
|
+
}
|
|
48
|
+
export declare function estimateEventCost(event: Partial<UnifiedEvent>): number;
|
|
49
|
+
export declare function initEventCollectorTables(): void;
|
|
50
|
+
export declare function ingestEvent(event: Partial<UnifiedEvent>): {
|
|
51
|
+
eventId: string;
|
|
52
|
+
isDuplicate: boolean;
|
|
53
|
+
estimatedCost: number;
|
|
54
|
+
};
|
|
55
|
+
/** Path A: Claude Code / local hook */
|
|
56
|
+
export declare function logHookEvent(data: {
|
|
57
|
+
sessionId: string;
|
|
58
|
+
toolName: string;
|
|
59
|
+
toolInput?: string;
|
|
60
|
+
toolOutput?: string;
|
|
61
|
+
durationMs?: number;
|
|
62
|
+
success?: boolean;
|
|
63
|
+
}): ReturnType<typeof ingestEvent>;
|
|
64
|
+
/** Path B: MCP proxy interception */
|
|
65
|
+
export declare function logMcpProxyEvent(data: {
|
|
66
|
+
sessionId: string;
|
|
67
|
+
toolName: string;
|
|
68
|
+
args: Record<string, unknown>;
|
|
69
|
+
result: unknown;
|
|
70
|
+
durationMs: number;
|
|
71
|
+
success: boolean;
|
|
72
|
+
modelUsed?: string;
|
|
73
|
+
tokenIn?: number;
|
|
74
|
+
tokenOut?: number;
|
|
75
|
+
}): ReturnType<typeof ingestEvent>;
|
|
76
|
+
/** Path C: OTel span */
|
|
77
|
+
export declare function logOtelSpan(data: {
|
|
78
|
+
traceId: string;
|
|
79
|
+
spanId: string;
|
|
80
|
+
parentSpanId?: string;
|
|
81
|
+
toolName: string;
|
|
82
|
+
startTimeMs: number;
|
|
83
|
+
endTimeMs: number;
|
|
84
|
+
attributes?: Record<string, unknown>;
|
|
85
|
+
}): ReturnType<typeof ingestEvent>;
|
|
86
|
+
/** Path D: Framework wrapper (LangChain, CrewAI, etc.) */
|
|
87
|
+
export declare function logFrameworkEvent(data: {
|
|
88
|
+
framework: string;
|
|
89
|
+
sessionId: string;
|
|
90
|
+
toolName: string;
|
|
91
|
+
input?: string;
|
|
92
|
+
output?: string;
|
|
93
|
+
durationMs: number;
|
|
94
|
+
modelUsed?: string;
|
|
95
|
+
tokenIn?: number;
|
|
96
|
+
tokenOut?: number;
|
|
97
|
+
success?: boolean;
|
|
98
|
+
}): ReturnType<typeof ingestEvent>;
|
|
99
|
+
export declare function getRecentEvents(sessionId: string, limit?: number): UnifiedEvent[];
|
|
100
|
+
export declare function getDuplicateRate(daysBack?: number): {
|
|
101
|
+
total: number;
|
|
102
|
+
duplicates: number;
|
|
103
|
+
rate: number;
|
|
104
|
+
};
|
|
105
|
+
export declare function getCostByModel(daysBack?: number): Array<{
|
|
106
|
+
model: string;
|
|
107
|
+
calls: number;
|
|
108
|
+
totalCost: number;
|
|
109
|
+
avgLatency: number;
|
|
110
|
+
}>;
|
|
111
|
+
export declare function getCostBySurface(daysBack?: number): Array<{
|
|
112
|
+
surface: string;
|
|
113
|
+
calls: number;
|
|
114
|
+
totalCost: number;
|
|
115
|
+
}>;
|
|
116
|
+
export declare function getTopToolChains(daysBack?: number, chainLength?: number): Array<{
|
|
117
|
+
chain: string;
|
|
118
|
+
count: number;
|
|
119
|
+
}>;
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* eventCollector.ts — Unified Event Collector for the Integration Layer.
|
|
3
|
+
*
|
|
4
|
+
* All observation paths (MCP proxy, OTel, hooks, framework wrappers)
|
|
5
|
+
* convert into one canonical event schema and flow through this collector.
|
|
6
|
+
*
|
|
7
|
+
* The collector:
|
|
8
|
+
* 1. Normalizes events from any source into UnifiedEvent
|
|
9
|
+
* 2. Stores in SQLite (bounded, with eviction)
|
|
10
|
+
* 3. Detects duplicates and correlates by session/trace
|
|
11
|
+
* 4. Feeds the pattern engine and proof layer
|
|
12
|
+
*
|
|
13
|
+
* Integration paths:
|
|
14
|
+
* A. Claude Code / local hook → logHookEvent()
|
|
15
|
+
* B. MCP proxy → logMcpProxyEvent()
|
|
16
|
+
* C. OTel receiver → logOtelSpan()
|
|
17
|
+
* D. Framework wrappers → logFrameworkEvent()
|
|
18
|
+
*
|
|
19
|
+
* All converge to: ingestEvent(UnifiedEvent)
|
|
20
|
+
*/
|
|
21
|
+
import { getDb, genId } from "../db.js";
|
|
22
|
+
// ── Cost estimation by tool + model ──────────────────────────────────
|
|
23
|
+
const MODEL_COST_PER_1K_TOKENS = {
|
|
24
|
+
"gemini-3.1-flash-lite-preview": { input: 0.00002, output: 0.00008 },
|
|
25
|
+
"gemini-3.1-flash-preview": { input: 0.00015, output: 0.0006 },
|
|
26
|
+
"gemini-2.5-flash-preview": { input: 0.00015, output: 0.0006 },
|
|
27
|
+
"claude-sonnet-4-6": { input: 0.003, output: 0.015 },
|
|
28
|
+
"claude-opus-4-6": { input: 0.015, output: 0.075 },
|
|
29
|
+
"gpt-4o": { input: 0.005, output: 0.015 },
|
|
30
|
+
"gpt-4o-mini": { input: 0.00015, output: 0.0006 },
|
|
31
|
+
_default: { input: 0.001, output: 0.003 },
|
|
32
|
+
};
|
|
33
|
+
const TOOL_BASE_COST = {
|
|
34
|
+
web_search: 0.008,
|
|
35
|
+
fetch_url: 0.002,
|
|
36
|
+
enrich_entity: 0.015,
|
|
37
|
+
run_deep_sim: 0.05,
|
|
38
|
+
build_claim_graph: 0.03,
|
|
39
|
+
render_decision_memo: 0.01,
|
|
40
|
+
_default: 0.003,
|
|
41
|
+
};
|
|
42
|
+
export function estimateEventCost(event) {
|
|
43
|
+
let cost = TOOL_BASE_COST[event.toolName ?? ""] ?? TOOL_BASE_COST._default;
|
|
44
|
+
if (event.modelUsed && (event.tokenIn || event.tokenOut)) {
|
|
45
|
+
const rates = MODEL_COST_PER_1K_TOKENS[event.modelUsed] ?? MODEL_COST_PER_1K_TOKENS._default;
|
|
46
|
+
cost += ((event.tokenIn ?? 0) / 1000) * rates.input;
|
|
47
|
+
cost += ((event.tokenOut ?? 0) / 1000) * rates.output;
|
|
48
|
+
}
|
|
49
|
+
return Math.round(cost * 10000) / 10000;
|
|
50
|
+
}
|
|
51
|
+
// ── Schema initialization ────────────────────────────────────────────
|
|
52
|
+
export function initEventCollectorTables() {
|
|
53
|
+
const db = getDb();
|
|
54
|
+
db.exec(`
|
|
55
|
+
CREATE TABLE IF NOT EXISTS unified_events (
|
|
56
|
+
event_id TEXT PRIMARY KEY,
|
|
57
|
+
timestamp TEXT NOT NULL,
|
|
58
|
+
surface TEXT NOT NULL DEFAULT 'unknown',
|
|
59
|
+
integration_path TEXT NOT NULL DEFAULT 'direct',
|
|
60
|
+
session_id TEXT NOT NULL,
|
|
61
|
+
trace_id TEXT,
|
|
62
|
+
span_id TEXT,
|
|
63
|
+
company_id TEXT,
|
|
64
|
+
user_id TEXT,
|
|
65
|
+
tool_name TEXT NOT NULL,
|
|
66
|
+
tool_input_summary TEXT,
|
|
67
|
+
tool_output_summary TEXT,
|
|
68
|
+
latency_ms INTEGER DEFAULT 0,
|
|
69
|
+
token_in INTEGER DEFAULT 0,
|
|
70
|
+
token_out INTEGER DEFAULT 0,
|
|
71
|
+
estimated_cost_usd REAL DEFAULT 0,
|
|
72
|
+
cache_hit INTEGER DEFAULT 0,
|
|
73
|
+
success INTEGER DEFAULT 1,
|
|
74
|
+
model_used TEXT,
|
|
75
|
+
packet_id TEXT,
|
|
76
|
+
entity_ids TEXT DEFAULT '[]',
|
|
77
|
+
artifact_ids TEXT DEFAULT '[]',
|
|
78
|
+
path_step_index INTEGER,
|
|
79
|
+
fingerprint TEXT,
|
|
80
|
+
parent_span_id TEXT
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
CREATE INDEX IF NOT EXISTS idx_ue_session ON unified_events(session_id);
|
|
84
|
+
CREATE INDEX IF NOT EXISTS idx_ue_tool ON unified_events(tool_name);
|
|
85
|
+
CREATE INDEX IF NOT EXISTS idx_ue_timestamp ON unified_events(timestamp);
|
|
86
|
+
CREATE INDEX IF NOT EXISTS idx_ue_fingerprint ON unified_events(fingerprint);
|
|
87
|
+
CREATE INDEX IF NOT EXISTS idx_ue_surface ON unified_events(surface);
|
|
88
|
+
`);
|
|
89
|
+
}
|
|
90
|
+
// ── Fingerprinting for dedup ─────────────────────────────────────────
|
|
91
|
+
function computeFingerprint(toolName, inputSummary) {
|
|
92
|
+
const raw = `${toolName}:${(inputSummary ?? "").toLowerCase().trim().slice(0, 200)}`;
|
|
93
|
+
// Simple hash — not crypto, just dedup
|
|
94
|
+
let hash = 0;
|
|
95
|
+
for (let i = 0; i < raw.length; i++) {
|
|
96
|
+
hash = ((hash << 5) - hash + raw.charCodeAt(i)) | 0;
|
|
97
|
+
}
|
|
98
|
+
return `fp_${Math.abs(hash).toString(36)}`;
|
|
99
|
+
}
|
|
100
|
+
// ── Core ingestion ───────────────────────────────────────────────────
|
|
101
|
+
const MAX_EVENTS = 10000;
|
|
102
|
+
let eventCount = 0;
|
|
103
|
+
export function ingestEvent(event) {
|
|
104
|
+
const db = getDb();
|
|
105
|
+
const eventId = event.eventId ?? genId("evt");
|
|
106
|
+
const timestamp = event.timestamp ?? new Date().toISOString();
|
|
107
|
+
const fingerprint = event.fingerprint ?? computeFingerprint(event.toolName ?? "unknown", event.toolInputSummary);
|
|
108
|
+
const estimatedCost = event.estimatedCostUsd ?? estimateEventCost(event);
|
|
109
|
+
// Dedup: check for same fingerprint in same session within 5 minutes
|
|
110
|
+
const fiveMinAgo = new Date(Date.now() - 5 * 60 * 1000).toISOString();
|
|
111
|
+
const duplicate = db.prepare(`
|
|
112
|
+
SELECT event_id FROM unified_events
|
|
113
|
+
WHERE fingerprint = ? AND session_id = ? AND timestamp > ?
|
|
114
|
+
LIMIT 1
|
|
115
|
+
`).get(fingerprint, event.sessionId ?? "unknown", fiveMinAgo);
|
|
116
|
+
const isDuplicate = !!duplicate;
|
|
117
|
+
db.prepare(`
|
|
118
|
+
INSERT OR IGNORE INTO unified_events
|
|
119
|
+
(event_id, timestamp, surface, integration_path, session_id, trace_id, span_id,
|
|
120
|
+
company_id, user_id, tool_name, tool_input_summary, tool_output_summary,
|
|
121
|
+
latency_ms, token_in, token_out, estimated_cost_usd, cache_hit, success,
|
|
122
|
+
model_used, packet_id, entity_ids, artifact_ids, path_step_index, fingerprint, parent_span_id)
|
|
123
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
124
|
+
`).run(eventId, timestamp, event.surface ?? "unknown", event.integrationPath ?? "direct", event.sessionId ?? "unknown", event.traceId ?? null, event.spanId ?? null, event.companyId ?? null, event.userId ?? null, event.toolName ?? "unknown", event.toolInputSummary ?? null, event.toolOutputSummary ?? null, event.latencyMs ?? 0, event.tokenIn ?? 0, event.tokenOut ?? 0, estimatedCost, event.cacheHit ? 1 : 0, event.success !== false ? 1 : 0, event.modelUsed ?? null, event.packetId ?? null, JSON.stringify(event.entityIds ?? []), JSON.stringify(event.artifactIds ?? []), event.pathStepIndex ?? null, fingerprint, event.parentSpanId ?? null);
|
|
125
|
+
// Bounded eviction
|
|
126
|
+
eventCount++;
|
|
127
|
+
if (eventCount % 500 === 0) {
|
|
128
|
+
const total = db.prepare(`SELECT COUNT(*) as c FROM unified_events`).get()?.c ?? 0;
|
|
129
|
+
if (total > MAX_EVENTS) {
|
|
130
|
+
db.prepare(`DELETE FROM unified_events WHERE event_id IN (SELECT event_id FROM unified_events ORDER BY timestamp ASC LIMIT ?)`).run(total - MAX_EVENTS);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return { eventId, isDuplicate, estimatedCost };
|
|
134
|
+
}
|
|
135
|
+
// ── Path-specific ingestion helpers ──────────────────────────────────
|
|
136
|
+
/** Path A: Claude Code / local hook */
|
|
137
|
+
export function logHookEvent(data) {
|
|
138
|
+
return ingestEvent({
|
|
139
|
+
surface: "claude_code",
|
|
140
|
+
integrationPath: "local_hook",
|
|
141
|
+
sessionId: data.sessionId,
|
|
142
|
+
toolName: data.toolName,
|
|
143
|
+
toolInputSummary: data.toolInput?.slice(0, 500),
|
|
144
|
+
toolOutputSummary: data.toolOutput?.slice(0, 500),
|
|
145
|
+
latencyMs: data.durationMs ?? 0,
|
|
146
|
+
success: data.success ?? true,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
/** Path B: MCP proxy interception */
|
|
150
|
+
export function logMcpProxyEvent(data) {
|
|
151
|
+
return ingestEvent({
|
|
152
|
+
surface: "mcp_direct",
|
|
153
|
+
integrationPath: "mcp_proxy",
|
|
154
|
+
sessionId: data.sessionId,
|
|
155
|
+
toolName: data.toolName,
|
|
156
|
+
toolInputSummary: JSON.stringify(data.args).slice(0, 500),
|
|
157
|
+
toolOutputSummary: typeof data.result === "string" ? data.result.slice(0, 500) : JSON.stringify(data.result).slice(0, 500),
|
|
158
|
+
latencyMs: data.durationMs,
|
|
159
|
+
success: data.success,
|
|
160
|
+
modelUsed: data.modelUsed,
|
|
161
|
+
tokenIn: data.tokenIn,
|
|
162
|
+
tokenOut: data.tokenOut,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
/** Path C: OTel span */
|
|
166
|
+
export function logOtelSpan(data) {
|
|
167
|
+
const attrs = data.attributes ?? {};
|
|
168
|
+
return ingestEvent({
|
|
169
|
+
surface: "otel",
|
|
170
|
+
integrationPath: "otel_receiver",
|
|
171
|
+
sessionId: data.traceId, // Use trace as session for OTel
|
|
172
|
+
traceId: data.traceId,
|
|
173
|
+
spanId: data.spanId,
|
|
174
|
+
parentSpanId: data.parentSpanId,
|
|
175
|
+
toolName: data.toolName,
|
|
176
|
+
toolInputSummary: attrs["gen_ai.prompt"] ?? attrs["input"],
|
|
177
|
+
toolOutputSummary: attrs["gen_ai.completion"] ?? attrs["output"],
|
|
178
|
+
latencyMs: data.endTimeMs - data.startTimeMs,
|
|
179
|
+
tokenIn: attrs["gen_ai.usage.prompt_tokens"] ?? undefined,
|
|
180
|
+
tokenOut: attrs["gen_ai.usage.completion_tokens"] ?? undefined,
|
|
181
|
+
modelUsed: attrs["gen_ai.request.model"] ?? undefined,
|
|
182
|
+
success: attrs["error"] ? false : true,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
/** Path D: Framework wrapper (LangChain, CrewAI, etc.) */
|
|
186
|
+
export function logFrameworkEvent(data) {
|
|
187
|
+
return ingestEvent({
|
|
188
|
+
surface: "framework",
|
|
189
|
+
integrationPath: "framework_wrapper",
|
|
190
|
+
sessionId: data.sessionId,
|
|
191
|
+
toolName: data.toolName,
|
|
192
|
+
toolInputSummary: data.input?.slice(0, 500),
|
|
193
|
+
toolOutputSummary: data.output?.slice(0, 500),
|
|
194
|
+
latencyMs: data.durationMs,
|
|
195
|
+
modelUsed: data.modelUsed,
|
|
196
|
+
tokenIn: data.tokenIn,
|
|
197
|
+
tokenOut: data.tokenOut,
|
|
198
|
+
success: data.success ?? true,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
// ── Query functions for the pattern engine ────────────────────────────
|
|
202
|
+
export function getRecentEvents(sessionId, limit = 50) {
|
|
203
|
+
const db = getDb();
|
|
204
|
+
return db.prepare(`SELECT * FROM unified_events WHERE session_id = ? ORDER BY timestamp DESC LIMIT ?`).all(sessionId, limit);
|
|
205
|
+
}
|
|
206
|
+
export function getDuplicateRate(daysBack = 7) {
|
|
207
|
+
const db = getDb();
|
|
208
|
+
const since = new Date(Date.now() - daysBack * 86400000).toISOString();
|
|
209
|
+
const total = db.prepare(`SELECT COUNT(*) as c FROM unified_events WHERE timestamp >= ?`).get(since)?.c ?? 0;
|
|
210
|
+
// Count events with same fingerprint appearing 2+ times in same session
|
|
211
|
+
const duplicates = db.prepare(`
|
|
212
|
+
SELECT COUNT(*) as c FROM (
|
|
213
|
+
SELECT fingerprint, session_id, COUNT(*) as cnt
|
|
214
|
+
FROM unified_events WHERE timestamp >= ?
|
|
215
|
+
GROUP BY fingerprint, session_id HAVING cnt > 1
|
|
216
|
+
)
|
|
217
|
+
`).get(since)?.c ?? 0;
|
|
218
|
+
return { total, duplicates, rate: total > 0 ? Math.round((duplicates / total) * 100) : 0 };
|
|
219
|
+
}
|
|
220
|
+
export function getCostByModel(daysBack = 7) {
|
|
221
|
+
const db = getDb();
|
|
222
|
+
const since = new Date(Date.now() - daysBack * 86400000).toISOString();
|
|
223
|
+
return db.prepare(`
|
|
224
|
+
SELECT COALESCE(model_used, 'no_model') as model,
|
|
225
|
+
COUNT(*) as calls,
|
|
226
|
+
ROUND(SUM(estimated_cost_usd), 4) as totalCost,
|
|
227
|
+
CAST(AVG(latency_ms) AS INTEGER) as avgLatency
|
|
228
|
+
FROM unified_events WHERE timestamp >= ?
|
|
229
|
+
GROUP BY model ORDER BY totalCost DESC
|
|
230
|
+
`).all(since);
|
|
231
|
+
}
|
|
232
|
+
export function getCostBySurface(daysBack = 7) {
|
|
233
|
+
const db = getDb();
|
|
234
|
+
const since = new Date(Date.now() - daysBack * 86400000).toISOString();
|
|
235
|
+
return db.prepare(`
|
|
236
|
+
SELECT surface, COUNT(*) as calls, ROUND(SUM(estimated_cost_usd), 4) as totalCost
|
|
237
|
+
FROM unified_events WHERE timestamp >= ?
|
|
238
|
+
GROUP BY surface ORDER BY totalCost DESC
|
|
239
|
+
`).all(since);
|
|
240
|
+
}
|
|
241
|
+
export function getTopToolChains(daysBack = 7, chainLength = 3) {
|
|
242
|
+
const db = getDb();
|
|
243
|
+
const since = new Date(Date.now() - daysBack * 86400000).toISOString();
|
|
244
|
+
// Get all sessions with 3+ events
|
|
245
|
+
const sessions = db.prepare(`
|
|
246
|
+
SELECT session_id FROM unified_events WHERE timestamp >= ?
|
|
247
|
+
GROUP BY session_id HAVING COUNT(*) >= ? ORDER BY COUNT(*) DESC LIMIT 100
|
|
248
|
+
`).all(since, chainLength);
|
|
249
|
+
const chainCounts = new Map();
|
|
250
|
+
for (const { session_id } of sessions) {
|
|
251
|
+
const events = db.prepare(`
|
|
252
|
+
SELECT tool_name FROM unified_events
|
|
253
|
+
WHERE session_id = ? AND timestamp >= ?
|
|
254
|
+
ORDER BY timestamp ASC
|
|
255
|
+
`).all(session_id, since);
|
|
256
|
+
for (let i = 0; i <= events.length - chainLength; i++) {
|
|
257
|
+
const chain = events.slice(i, i + chainLength).map(e => e.tool_name).join(" → ");
|
|
258
|
+
chainCounts.set(chain, (chainCounts.get(chain) ?? 0) + 1);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return Array.from(chainCounts.entries())
|
|
262
|
+
.filter(([, count]) => count > 1)
|
|
263
|
+
.sort((a, b) => b[1] - a[1])
|
|
264
|
+
.slice(0, 10)
|
|
265
|
+
.map(([chain, count]) => ({ chain, count }));
|
|
266
|
+
}
|
|
267
|
+
//# sourceMappingURL=eventCollector.js.map
|