@devflow-tools/server 0.8.10 → 0.9.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 +30 -19
- package/dist/app.module.d.ts.map +1 -1
- package/dist/app.module.js +1 -7
- package/dist/app.module.js.map +1 -1
- package/dist/database/database.module.d.ts.map +1 -1
- package/dist/database/database.module.js +4 -2
- package/dist/database/database.module.js.map +1 -1
- package/dist/knowledge/knowledge.service.d.ts +1 -0
- package/dist/knowledge/knowledge.service.d.ts.map +1 -1
- package/dist/knowledge/knowledge.service.js +7 -2
- package/dist/knowledge/knowledge.service.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/memory/memory.service.d.ts +1 -0
- package/dist/memory/memory.service.d.ts.map +1 -1
- package/dist/memory/memory.service.js +22 -4
- package/dist/memory/memory.service.js.map +1 -1
- package/dist/telemetry/auto-checker.d.ts +23 -0
- package/dist/telemetry/auto-checker.d.ts.map +1 -0
- package/dist/telemetry/auto-checker.js +121 -0
- package/dist/telemetry/auto-checker.js.map +1 -0
- package/dist/telemetry/telemetry.controller.d.ts +101 -43
- package/dist/telemetry/telemetry.controller.d.ts.map +1 -1
- package/dist/telemetry/telemetry.controller.js +198 -9
- package/dist/telemetry/telemetry.controller.js.map +1 -1
- package/dist/telemetry/telemetry.service.d.ts +137 -84
- package/dist/telemetry/telemetry.service.d.ts.map +1 -1
- package/dist/telemetry/telemetry.service.js +439 -455
- package/dist/telemetry/telemetry.service.js.map +1 -1
- package/dist/workflow/workflow.controller.d.ts +4 -4
- package/dist/workflow/workflow.service.d.ts +6 -4
- package/dist/workflow/workflow.service.d.ts.map +1 -1
- package/dist/workflow/workflow.service.js +46 -4
- package/dist/workflow/workflow.service.js.map +1 -1
- package/package.json +11 -11
|
@@ -12,66 +12,109 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
|
12
12
|
};
|
|
13
13
|
import { Inject, Injectable, NotFoundException, Optional } from "@nestjs/common";
|
|
14
14
|
import { TelemetryEngine } from "@devflow-tools/telemetry";
|
|
15
|
+
import { DevFlowDatabase } from "@devflow-tools/database";
|
|
15
16
|
import { ErrorCode } from "../common/error-codes.js";
|
|
17
|
+
import { AutoChecker } from './auto-checker.js';
|
|
16
18
|
let TelemetryService = class TelemetryService {
|
|
17
19
|
constructor(database) {
|
|
18
|
-
this.engine = new TelemetryEngine();
|
|
20
|
+
this.engine = new TelemetryEngine(); // Legacy in-memory store — real data goes through this.db
|
|
19
21
|
this.db = database ?? null;
|
|
20
22
|
}
|
|
21
23
|
async recordEvent(event) {
|
|
22
|
-
|
|
24
|
+
if (!this.db)
|
|
25
|
+
return;
|
|
26
|
+
this.db.insertEvent?.({
|
|
27
|
+
runId: event.runId,
|
|
28
|
+
kind: event.kind,
|
|
29
|
+
timestamp: event.timestamp,
|
|
30
|
+
duration: event.duration,
|
|
31
|
+
toolName: event.data?.toolName,
|
|
32
|
+
pluginName: event.data?.mcpToolName,
|
|
33
|
+
metadata: event.data,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
// ---- Sessions ----
|
|
37
|
+
async createSession(params) {
|
|
38
|
+
if (!this.db)
|
|
39
|
+
return;
|
|
40
|
+
this.db.insertSession({
|
|
41
|
+
id: params.id,
|
|
42
|
+
projectRoot: params.projectRoot,
|
|
43
|
+
label: params.label,
|
|
44
|
+
startedAt: params.startedAt,
|
|
45
|
+
});
|
|
23
46
|
}
|
|
47
|
+
async getSession(id) {
|
|
48
|
+
if (!this.db)
|
|
49
|
+
throw new NotFoundException({ error: { code: ErrorCode.RUN_NOT_FOUND, message: `Session "${id}" not found`, details: {} } });
|
|
50
|
+
const session = this.db.getSession(id);
|
|
51
|
+
if (!session)
|
|
52
|
+
throw new NotFoundException({ error: { code: ErrorCode.RUN_NOT_FOUND, message: `Session "${id}" not found`, details: {} } });
|
|
53
|
+
return session;
|
|
54
|
+
}
|
|
55
|
+
async listSessions(limit, offset, projectRoot) {
|
|
56
|
+
if (!this.db)
|
|
57
|
+
return [];
|
|
58
|
+
return this.db.listSessions(limit, offset, projectRoot);
|
|
59
|
+
}
|
|
60
|
+
async getSessionEvents(sessionId) {
|
|
61
|
+
if (!this.db)
|
|
62
|
+
return [];
|
|
63
|
+
return this.db.listToolCallEventsBySession?.(sessionId) ?? [];
|
|
64
|
+
}
|
|
65
|
+
async closeSession(id, finishedAt) {
|
|
66
|
+
if (!this.db)
|
|
67
|
+
return;
|
|
68
|
+
this.db.closeSession(id, finishedAt);
|
|
69
|
+
}
|
|
70
|
+
async createSkillExecution(params) {
|
|
71
|
+
if (!this.db)
|
|
72
|
+
return;
|
|
73
|
+
this.db.insertSkillExecution({
|
|
74
|
+
executionId: params.executionId,
|
|
75
|
+
sessionId: params.sessionId,
|
|
76
|
+
skillName: params.skillName,
|
|
77
|
+
startedAt: params.startedAt,
|
|
78
|
+
status: 'running',
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
// ---- Runs (now backed by sessions) ----
|
|
24
82
|
async listRuns(limit, offset, source) {
|
|
25
|
-
const runs = Array.from(this.engine.runs.values());
|
|
26
|
-
let filtered = runs;
|
|
27
|
-
if (source) {
|
|
28
|
-
filtered = runs.filter((r) => r.meta?.source === source);
|
|
29
|
-
}
|
|
30
|
-
const engineRuns = filtered.slice(offset, offset + limit).map((r) => ({
|
|
31
|
-
id: r.meta?.runId ?? "", agent: r.meta?.agent ?? "", mode: r.meta?.mode ?? "",
|
|
32
|
-
source: r.meta?.source ?? "unknown", status: r.status, startedAt: r.startedAt, tokenUsed: r.tokenUsed,
|
|
33
|
-
}));
|
|
34
|
-
// Also include skill_executions from the database (hook telemetry)
|
|
35
83
|
if (this.db) {
|
|
36
84
|
try {
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
id:
|
|
85
|
+
const sessions = this.db.listSessions(limit, offset);
|
|
86
|
+
return sessions.map((s) => ({
|
|
87
|
+
id: s.id,
|
|
40
88
|
agent: "claude-code",
|
|
41
89
|
mode: "hook",
|
|
42
90
|
source: "hook",
|
|
43
|
-
status:
|
|
44
|
-
startedAt:
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
mcpToolCalls: e.mcpToolCalls,
|
|
49
|
-
mcpComplianceRate: e.mcpComplianceRate,
|
|
91
|
+
status: s.status,
|
|
92
|
+
startedAt: s.startedAt,
|
|
93
|
+
totalToolCalls: s.totalToolCalls,
|
|
94
|
+
mcpToolCalls: s.mcpToolCalls,
|
|
95
|
+
mcpComplianceRate: s.mcpToolCalls > 0 ? Math.round(s.mcpToolCalls / s.totalToolCalls * 100) / 100 : 0,
|
|
50
96
|
}));
|
|
51
|
-
// Merge: db runs first (most recent), then engine runs, dedup by id
|
|
52
|
-
const seen = new Set();
|
|
53
|
-
const merged = [...dbRuns, ...engineRuns].filter((r) => {
|
|
54
|
-
if (seen.has(r.id))
|
|
55
|
-
return false;
|
|
56
|
-
seen.add(r.id);
|
|
57
|
-
return true;
|
|
58
|
-
});
|
|
59
|
-
return merged.slice(offset, offset + limit);
|
|
60
|
-
}
|
|
61
|
-
catch {
|
|
62
|
-
// DB not available — fall back to engine runs only
|
|
63
97
|
}
|
|
98
|
+
catch { }
|
|
64
99
|
}
|
|
65
|
-
return
|
|
100
|
+
return [];
|
|
66
101
|
}
|
|
67
102
|
async getRun(id) {
|
|
68
|
-
//
|
|
69
|
-
const run = this.engine.runs.get(id);
|
|
70
|
-
if (run)
|
|
71
|
-
return run;
|
|
72
|
-
// 2. Fall back to database (skill_executions)
|
|
103
|
+
// Try session first, then skill execution, then engine
|
|
73
104
|
if (this.db) {
|
|
74
105
|
try {
|
|
106
|
+
const s = this.db.getSession(id);
|
|
107
|
+
if (s) {
|
|
108
|
+
return {
|
|
109
|
+
meta: { runId: s.id, agent: "claude-code", mode: "hook", source: "hook" },
|
|
110
|
+
status: s.status,
|
|
111
|
+
startedAt: s.startedAt,
|
|
112
|
+
finishedAt: s.finishedAt,
|
|
113
|
+
totalToolCalls: s.totalToolCalls,
|
|
114
|
+
mcpToolCalls: s.mcpToolCalls,
|
|
115
|
+
steps: [],
|
|
116
|
+
};
|
|
117
|
+
}
|
|
75
118
|
const exec = this.db.getSkillExecution(id);
|
|
76
119
|
if (exec) {
|
|
77
120
|
return {
|
|
@@ -79,411 +122,326 @@ let TelemetryService = class TelemetryService {
|
|
|
79
122
|
status: exec.status,
|
|
80
123
|
startedAt: exec.startedAt,
|
|
81
124
|
finishedAt: exec.finishedAt,
|
|
82
|
-
tokenUsed: exec.totalTokens,
|
|
83
|
-
steps: [],
|
|
84
|
-
// Extra fields from skill_executions
|
|
85
125
|
skillName: exec.skillName,
|
|
86
126
|
totalToolCalls: exec.totalToolCalls,
|
|
87
127
|
mcpToolCalls: exec.mcpToolCalls,
|
|
88
|
-
directToolCalls: exec.directToolCalls,
|
|
89
128
|
mcpComplianceRate: exec.mcpComplianceRate,
|
|
129
|
+
steps: [],
|
|
90
130
|
};
|
|
91
131
|
}
|
|
92
132
|
}
|
|
93
|
-
catch {
|
|
133
|
+
catch { }
|
|
94
134
|
}
|
|
135
|
+
const run = this.engine.runs.get(id);
|
|
136
|
+
if (run)
|
|
137
|
+
return run;
|
|
95
138
|
throw new NotFoundException({ error: { code: ErrorCode.RUN_NOT_FOUND, message: `Run "${id}" not found`, details: {} } });
|
|
96
139
|
}
|
|
97
140
|
async getTimeline(from, to) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
141
|
+
if (!this.db)
|
|
142
|
+
return [];
|
|
143
|
+
try {
|
|
144
|
+
const sessions = this.db.listSessions(500, 0);
|
|
145
|
+
return sessions
|
|
146
|
+
.filter((s) => {
|
|
147
|
+
if (from && s.startedAt < Number(from))
|
|
148
|
+
return false;
|
|
149
|
+
if (to && s.startedAt > Number(to))
|
|
150
|
+
return false;
|
|
151
|
+
return true;
|
|
152
|
+
})
|
|
153
|
+
.map((s) => ({
|
|
154
|
+
runId: s.id,
|
|
155
|
+
stepId: s.id,
|
|
156
|
+
type: "session",
|
|
157
|
+
duration: s.durationMs,
|
|
158
|
+
timestamp: s.startedAt,
|
|
159
|
+
}));
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
111
164
|
}
|
|
112
165
|
async getRunEvents(runId) {
|
|
113
|
-
//
|
|
114
|
-
const engineEvents = await this.engine.listEvents(runId);
|
|
115
|
-
if (engineEvents && engineEvents.length > 0)
|
|
116
|
-
return engineEvents;
|
|
117
|
-
// 2. Fall back to database (tool_call_events)
|
|
166
|
+
// Try session events first
|
|
118
167
|
if (this.db) {
|
|
119
168
|
try {
|
|
120
|
-
const
|
|
121
|
-
if (
|
|
122
|
-
return
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
type: e.isMcpTool ? "mcp_tool" : e.toolType === "subagent" ? "subagent" : "tool_use",
|
|
127
|
-
timestamp: e.timestamp,
|
|
128
|
-
duration: e.duration,
|
|
129
|
-
toolName: e.toolName,
|
|
130
|
-
toolType: e.toolType,
|
|
131
|
-
isMcpTool: e.isMcpTool,
|
|
132
|
-
mcpToolName: e.mcpToolName,
|
|
133
|
-
mcpEnforced: e.mcpEnforced,
|
|
134
|
-
mcpFallback: e.mcpFallback,
|
|
135
|
-
tokensUsed: e.tokensUsed,
|
|
136
|
-
blocked: e.blocked,
|
|
137
|
-
blockReason: e.blockReason,
|
|
138
|
-
error: e.error,
|
|
139
|
-
output: e.output,
|
|
140
|
-
input: e.input,
|
|
141
|
-
}));
|
|
142
|
-
}
|
|
169
|
+
const events = this.db.listToolCallEventsBySession(runId);
|
|
170
|
+
if (events && events.length > 0)
|
|
171
|
+
return events;
|
|
172
|
+
const execEvents = this.db.listToolCallEvents(runId);
|
|
173
|
+
if (execEvents && execEvents.length > 0)
|
|
174
|
+
return execEvents;
|
|
143
175
|
}
|
|
144
|
-
catch {
|
|
176
|
+
catch { }
|
|
145
177
|
}
|
|
178
|
+
const engineEvents = await this.engine.listEvents(runId);
|
|
146
179
|
return engineEvents ?? [];
|
|
147
180
|
}
|
|
148
181
|
async getRunStats(runId) {
|
|
149
|
-
// 1. Try in-memory engine first
|
|
150
|
-
const engineStats = await this.engine.getRunStats(runId);
|
|
151
|
-
if (engineStats && engineStats.totalEvents > 0)
|
|
152
|
-
return engineStats;
|
|
153
|
-
// 2. Fall back to database
|
|
154
182
|
if (this.db) {
|
|
155
183
|
try {
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
if (exec) {
|
|
159
|
-
const sumTokens = events?.reduce((s, e) => s + (e.tokensUsed ?? 0), 0) ?? 0;
|
|
184
|
+
const events = this.db.listToolCallEventsBySession(runId) ?? this.db.listToolCallEvents(runId) ?? [];
|
|
185
|
+
if (events.length > 0) {
|
|
160
186
|
return {
|
|
161
|
-
totalEvents: events
|
|
187
|
+
totalEvents: events.length,
|
|
162
188
|
llmCalls: 0,
|
|
163
|
-
totalTokens:
|
|
164
|
-
toolUses:
|
|
189
|
+
totalTokens: 0,
|
|
190
|
+
toolUses: events.length,
|
|
165
191
|
fileReads: 0,
|
|
166
192
|
fileWrites: 0,
|
|
167
193
|
bashCommands: 0,
|
|
168
|
-
errors: events
|
|
169
|
-
totalDuration:
|
|
170
|
-
mcpToolCalls:
|
|
171
|
-
directToolCalls:
|
|
172
|
-
mcpComplianceRate:
|
|
194
|
+
errors: events.filter((e) => e.error).length,
|
|
195
|
+
totalDuration: 0,
|
|
196
|
+
mcpToolCalls: events.filter((e) => e.isMcpTool).length,
|
|
197
|
+
directToolCalls: events.filter((e) => !e.isMcpTool).length,
|
|
198
|
+
mcpComplianceRate: events.length > 0
|
|
199
|
+
? events.filter((e) => e.isMcpTool).length / events.length : 0,
|
|
173
200
|
};
|
|
174
201
|
}
|
|
175
202
|
}
|
|
176
|
-
catch {
|
|
203
|
+
catch { }
|
|
177
204
|
}
|
|
178
|
-
return
|
|
205
|
+
return this.engine.getRunStats(runId);
|
|
179
206
|
}
|
|
180
207
|
async endRun(runId, result) {
|
|
181
208
|
await this.engine.endRun(runId, result);
|
|
182
209
|
}
|
|
183
|
-
|
|
184
|
-
const allEvents = this.collectAllEvents();
|
|
185
|
-
const cutoff = periodDays != null
|
|
186
|
-
? Date.now() - periodDays * 24 * 60 * 60 * 1000
|
|
187
|
-
: undefined;
|
|
188
|
-
const feedbackEvents = allEvents.filter((e) => {
|
|
189
|
-
if (e.data?.toolName !== "context_feedback")
|
|
190
|
-
return false;
|
|
191
|
-
if (projectRoot && e.data?.toolInput?.projectRoot !== projectRoot)
|
|
192
|
-
return false;
|
|
193
|
-
if (cutoff != null && (e.timestamp ?? 0) < cutoff)
|
|
194
|
-
return false;
|
|
195
|
-
return true;
|
|
196
|
-
});
|
|
197
|
-
const total = feedbackEvents.length;
|
|
198
|
-
const hits = feedbackEvents.filter((e) => e.data?.toolInput?.feedback === "hit").length;
|
|
199
|
-
const partials = feedbackEvents.filter((e) => e.data?.toolInput?.feedback === "partial").length;
|
|
200
|
-
const misses = feedbackEvents.filter((e) => e.data?.toolInput?.feedback === "miss").length;
|
|
201
|
-
return {
|
|
202
|
-
total,
|
|
203
|
-
hits,
|
|
204
|
-
partials,
|
|
205
|
-
misses,
|
|
206
|
-
hitRate: total > 0 ? hits / total : 0,
|
|
207
|
-
partialRate: total > 0 ? partials / total : 0,
|
|
208
|
-
missRate: total > 0 ? misses / total : 0,
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
collectAllEvents() {
|
|
212
|
-
const eventsMap = this.engine.events;
|
|
213
|
-
if (!eventsMap)
|
|
214
|
-
return [];
|
|
215
|
-
const all = [];
|
|
216
|
-
for (const events of eventsMap.values()) {
|
|
217
|
-
if (Array.isArray(events))
|
|
218
|
-
all.push(...events);
|
|
219
|
-
}
|
|
220
|
-
return all;
|
|
221
|
-
}
|
|
210
|
+
// ---- Telemetry Stats (from sessions + tool_call_events) ----
|
|
222
211
|
async getStats(periodDays = 7) {
|
|
223
|
-
|
|
212
|
+
if (!this.db)
|
|
213
|
+
return this.emptyStats();
|
|
224
214
|
const now = Date.now();
|
|
225
215
|
const cutoff = now - periodDays * 24 * 60 * 60 * 1000;
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
.map(([date, d]) => ({ date, ...d }))
|
|
261
|
-
.sort((a, b) => a.date.localeCompare(b.date)),
|
|
262
|
-
byWorkflow: this.buildWorkflowBreakdown(periodRuns, now),
|
|
263
|
-
byPlugin: this.buildPluginBreakdown(periodRuns),
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
buildWorkflowBreakdown(periodRuns, now) {
|
|
267
|
-
const byWorkflow = {};
|
|
268
|
-
for (const r of periodRuns) {
|
|
269
|
-
const wf = r.workflowName ?? r.meta?.workflowName ?? "unknown";
|
|
270
|
-
if (!byWorkflow[wf])
|
|
271
|
-
byWorkflow[wf] = { runs: 0, completed: 0, failed: 0, avgDuration: 0 };
|
|
272
|
-
byWorkflow[wf].runs++;
|
|
273
|
-
if (r.status === "completed")
|
|
274
|
-
byWorkflow[wf].completed++;
|
|
275
|
-
else
|
|
276
|
-
byWorkflow[wf].failed++;
|
|
277
|
-
byWorkflow[wf].avgDuration += ((r.finishedAt ?? now) - r.startedAt);
|
|
278
|
-
}
|
|
279
|
-
for (const wf of Object.keys(byWorkflow)) {
|
|
280
|
-
byWorkflow[wf].avgDuration = byWorkflow[wf].runs > 0
|
|
281
|
-
? Math.round(byWorkflow[wf].avgDuration / byWorkflow[wf].runs) : 0;
|
|
216
|
+
try {
|
|
217
|
+
const sessions = this.db.listSessions(1000, 0);
|
|
218
|
+
const periodSessions = sessions.filter((s) => s.startedAt >= cutoff);
|
|
219
|
+
const days = {};
|
|
220
|
+
let totalToolCalls = 0;
|
|
221
|
+
for (const s of periodSessions) {
|
|
222
|
+
const day = new Date(s.startedAt).toISOString().slice(0, 10);
|
|
223
|
+
if (!days[day])
|
|
224
|
+
days[day] = { runs: 0 };
|
|
225
|
+
days[day].runs++;
|
|
226
|
+
totalToolCalls += s.totalToolCalls ?? 0;
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
period: {
|
|
230
|
+
from: new Date(cutoff).toISOString().slice(0, 10),
|
|
231
|
+
to: new Date(now).toISOString().slice(0, 10),
|
|
232
|
+
},
|
|
233
|
+
summary: {
|
|
234
|
+
totalRuns: periodSessions.length,
|
|
235
|
+
completedRuns: periodSessions.filter((s) => s.status === 'completed').length,
|
|
236
|
+
failedRuns: periodSessions.filter((s) => s.status === 'failed').length,
|
|
237
|
+
totalTokens: 0,
|
|
238
|
+
totalLlmCalls: 0,
|
|
239
|
+
totalToolUses: totalToolCalls,
|
|
240
|
+
avgRunDuration: periodSessions.length > 0
|
|
241
|
+
? Math.round(periodSessions.reduce((sum, s) => sum + (s.durationMs ?? 0), 0) / periodSessions.length)
|
|
242
|
+
: 0,
|
|
243
|
+
},
|
|
244
|
+
byDay: Object.entries(days)
|
|
245
|
+
.map(([date, d]) => ({ date, tokens: 0, ...d }))
|
|
246
|
+
.sort((a, b) => a.date.localeCompare(b.date)),
|
|
247
|
+
byWorkflow: [],
|
|
248
|
+
byPlugin: [],
|
|
249
|
+
};
|
|
282
250
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
runs: d.runs,
|
|
286
|
-
successRate: d.runs > 0 ? d.completed / d.runs : 0,
|
|
287
|
-
avgDuration: d.avgDuration,
|
|
288
|
-
}));
|
|
289
|
-
}
|
|
290
|
-
buildPluginBreakdown(periodRuns) {
|
|
291
|
-
const byPlugin = {};
|
|
292
|
-
for (const r of periodRuns) {
|
|
293
|
-
const pl = r.pluginName ?? r.meta?.pluginName ?? "none";
|
|
294
|
-
if (!byPlugin[pl])
|
|
295
|
-
byPlugin[pl] = { runs: 0, completed: 0, failed: 0 };
|
|
296
|
-
byPlugin[pl].runs++;
|
|
297
|
-
if (r.status === "completed")
|
|
298
|
-
byPlugin[pl].completed++;
|
|
299
|
-
else
|
|
300
|
-
byPlugin[pl].failed++;
|
|
251
|
+
catch {
|
|
252
|
+
return this.emptyStats();
|
|
301
253
|
}
|
|
302
|
-
return Object.entries(byPlugin).map(([name, d]) => ({
|
|
303
|
-
name,
|
|
304
|
-
runs: d.runs,
|
|
305
|
-
successRate: d.runs > 0 ? d.completed / d.runs : 0,
|
|
306
|
-
}));
|
|
307
254
|
}
|
|
308
|
-
|
|
309
|
-
const runs = Array.from(this.engine.runs.values());
|
|
310
|
-
const now = Date.now();
|
|
311
|
-
const totalRuns = runs.length;
|
|
312
|
-
const successfulRuns = runs.filter((r) => r.status === "completed").length;
|
|
313
|
-
const successRate = totalRuns > 0 ? successfulRuns / totalRuns : 0;
|
|
314
|
-
const totalTokens = runs.reduce((sum, r) => sum + (r.tokenUsed ?? 0), 0);
|
|
315
|
-
const avgTokenUsage = totalRuns > 0 ? totalTokens / totalRuns : 0;
|
|
316
|
-
const totalDuration = runs.reduce((sum, r) => {
|
|
317
|
-
const start = r.startedAt ?? 0;
|
|
318
|
-
const end = r.finishedAt ?? now;
|
|
319
|
-
return sum + (end - start);
|
|
320
|
-
}, 0);
|
|
321
|
-
const avgDuration = totalRuns > 0 ? totalDuration / totalRuns : 0;
|
|
322
|
-
// Feedback hit rate: combine run-level feedback with context_feedback telemetry events
|
|
323
|
-
const runFeedbackHits = runs.filter((r) => r.relevanceFeedback === "hit" || r.meta?.relevanceFeedback === "hit").length;
|
|
324
|
-
const runFeedbackTotal = runs.filter((r) => r.relevanceFeedback !== undefined || r.meta?.relevanceFeedback !== undefined).length;
|
|
325
|
-
const telemetryFeedback = await this.getFeedbackStats();
|
|
326
|
-
const combinedHits = runFeedbackHits + telemetryFeedback.hits;
|
|
327
|
-
const combinedTotal = runFeedbackTotal + telemetryFeedback.total;
|
|
328
|
-
const feedbackHitRate = combinedTotal > 0 ? combinedHits / combinedTotal : 0;
|
|
255
|
+
emptyStats() {
|
|
329
256
|
return {
|
|
330
|
-
period:
|
|
331
|
-
totalRuns,
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
avgDuration,
|
|
336
|
-
feedbackHitRate,
|
|
337
|
-
trend: "stable",
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
async getExperimentalRoiSummary() {
|
|
341
|
-
return {
|
|
342
|
-
totalScenarios: 0,
|
|
343
|
-
avgTimeSavedRate: null,
|
|
344
|
-
avgTokenReductionRate: null,
|
|
345
|
-
avgFirstPassLift: null,
|
|
346
|
-
avgReworkReductionRate: null,
|
|
347
|
-
conclusion: "No experimental data available yet",
|
|
257
|
+
period: { from: '', to: '' },
|
|
258
|
+
summary: { totalRuns: 0, completedRuns: 0, failedRuns: 0, totalTokens: 0, totalLlmCalls: 0, totalToolUses: 0, avgRunDuration: 0 },
|
|
259
|
+
byDay: [],
|
|
260
|
+
byWorkflow: [],
|
|
261
|
+
byPlugin: [],
|
|
348
262
|
};
|
|
349
263
|
}
|
|
264
|
+
// ---- ROI Summary (from sessions + tool_call_events) ----
|
|
350
265
|
async getRoiSummary(periodDays = 7) {
|
|
351
|
-
|
|
266
|
+
if (!this.db)
|
|
267
|
+
return this.emptyRoiSummary();
|
|
352
268
|
const now = Date.now();
|
|
353
269
|
const cutoff = now - periodDays * 24 * 60 * 60 * 1000;
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
failureBreakdown[cat] = (failureBreakdown[cat] ?? 0) + 1;
|
|
270
|
+
try {
|
|
271
|
+
const sessions = this.db.listSessions(1000, 0);
|
|
272
|
+
const periodSessions = sessions.filter((s) => s.startedAt >= cutoff);
|
|
273
|
+
const totalRuns = periodSessions.length;
|
|
274
|
+
const completedRuns = periodSessions.filter((s) => s.status === 'completed').length;
|
|
275
|
+
const failedRuns = periodSessions.filter((s) => s.status === 'failed').length;
|
|
276
|
+
const avgDuration = totalRuns > 0
|
|
277
|
+
? Math.round(periodSessions.reduce((sum, s) => sum + (s.durationMs ?? 0), 0) / totalRuns)
|
|
278
|
+
: 0;
|
|
279
|
+
// Daily breakdown
|
|
280
|
+
const days = {};
|
|
281
|
+
for (const s of periodSessions) {
|
|
282
|
+
const day = new Date(s.startedAt).toISOString().slice(0, 10);
|
|
283
|
+
if (!days[day])
|
|
284
|
+
days[day] = { runs: 0, failures: 0 };
|
|
285
|
+
days[day].runs++;
|
|
286
|
+
if (s.status === 'failed')
|
|
287
|
+
days[day].failures++;
|
|
288
|
+
}
|
|
289
|
+
// Batch fetch all events for all period sessions
|
|
290
|
+
const allEventGroups = this.db.listToolCallEventsBySessions(periodSessions.map((s) => s.id));
|
|
291
|
+
// Failure breakdown from tool_call_events errors
|
|
292
|
+
const failureBreakdown = {
|
|
293
|
+
tool_error: 0,
|
|
294
|
+
mcp_unavailable: 0,
|
|
295
|
+
unknown: 0,
|
|
296
|
+
};
|
|
297
|
+
const byPlugin = {};
|
|
298
|
+
for (const s of periodSessions) {
|
|
299
|
+
const events = (allEventGroups[s.id] ?? []);
|
|
300
|
+
for (const e of events) {
|
|
301
|
+
if (e.error) {
|
|
302
|
+
const err = e.error.toLowerCase();
|
|
303
|
+
if (err.includes('mcp') || err.includes('unavailable') || err.includes('not found'))
|
|
304
|
+
failureBreakdown.mcp_unavailable++;
|
|
305
|
+
else if (err.includes('tool') || err.includes('command') || err.includes('error'))
|
|
306
|
+
failureBreakdown.tool_error++;
|
|
307
|
+
else
|
|
308
|
+
failureBreakdown.unknown++;
|
|
394
309
|
}
|
|
395
|
-
|
|
396
|
-
|
|
310
|
+
if (e.mcpToolName) {
|
|
311
|
+
const prefix = e.mcpToolName.split('_')[0];
|
|
312
|
+
if (prefix)
|
|
313
|
+
byPlugin[prefix] = (byPlugin[prefix] ?? 0) + 1;
|
|
397
314
|
}
|
|
398
315
|
}
|
|
399
|
-
else {
|
|
400
|
-
failureBreakdown.unknown++;
|
|
401
|
-
}
|
|
402
316
|
}
|
|
317
|
+
return {
|
|
318
|
+
period: {
|
|
319
|
+
from: new Date(cutoff).toISOString().slice(0, 10),
|
|
320
|
+
to: new Date(now).toISOString().slice(0, 10),
|
|
321
|
+
},
|
|
322
|
+
overview: {
|
|
323
|
+
totalRuns,
|
|
324
|
+
completedRuns,
|
|
325
|
+
failedRuns,
|
|
326
|
+
avgDuration,
|
|
327
|
+
firstPassSuccessRate: totalRuns > 0 ? Math.round((completedRuns / totalRuns) * 10000) / 100 : 0,
|
|
328
|
+
},
|
|
329
|
+
efficiency: {
|
|
330
|
+
avgTimePerTask: avgDuration,
|
|
331
|
+
},
|
|
332
|
+
failures: {
|
|
333
|
+
total: failedRuns,
|
|
334
|
+
breakdown: failureBreakdown,
|
|
335
|
+
},
|
|
336
|
+
workflows: [],
|
|
337
|
+
plugins: Object.entries(byPlugin).map(([name, runs]) => {
|
|
338
|
+
const pluginSessions = periodSessions.filter((s) => {
|
|
339
|
+
const evts = (allEventGroups[s.id] ?? []);
|
|
340
|
+
return evts.some((e) => e.mcpToolName?.startsWith(name + '_'));
|
|
341
|
+
});
|
|
342
|
+
const completed = pluginSessions.filter((s) => s.status === 'completed').length;
|
|
343
|
+
const successRate = pluginSessions.length > 0
|
|
344
|
+
? Math.round((completed / pluginSessions.length) * 10000) / 100
|
|
345
|
+
: 0;
|
|
346
|
+
return { name, runs, successRate };
|
|
347
|
+
}),
|
|
348
|
+
byDay: Object.entries(days)
|
|
349
|
+
.map(([date, d]) => ({ date, ...d, tokens: 0 }))
|
|
350
|
+
.sort((a, b) => a.date.localeCompare(b.date)),
|
|
351
|
+
};
|
|
403
352
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
for (const r of periodRuns) {
|
|
407
|
-
const day = new Date(r.startedAt).toISOString().slice(0, 10);
|
|
408
|
-
if (!days[day])
|
|
409
|
-
days[day] = { runs: 0, tokens: 0, failures: 0 };
|
|
410
|
-
days[day].runs++;
|
|
411
|
-
days[day].tokens += r.tokenUsed ?? 0;
|
|
412
|
-
if (r.status !== "completed")
|
|
413
|
-
days[day].failures++;
|
|
353
|
+
catch {
|
|
354
|
+
return this.emptyRoiSummary();
|
|
414
355
|
}
|
|
415
|
-
return {
|
|
416
|
-
period: {
|
|
417
|
-
from: new Date(cutoff).toISOString().slice(0, 10),
|
|
418
|
-
to: new Date(now).toISOString().slice(0, 10),
|
|
419
|
-
},
|
|
420
|
-
overview: {
|
|
421
|
-
totalRuns, completedRuns, failedRuns, avgDuration, avgTokens,
|
|
422
|
-
firstPassSuccessRate, feedbackHitRate,
|
|
423
|
-
},
|
|
424
|
-
efficiency: {
|
|
425
|
-
totalTokensSaved: 0, // needs baseline comparison data
|
|
426
|
-
avgTimePerTask,
|
|
427
|
-
avgRetryCount,
|
|
428
|
-
},
|
|
429
|
-
failures: {
|
|
430
|
-
total: failedRuns,
|
|
431
|
-
breakdown: failureBreakdown,
|
|
432
|
-
},
|
|
433
|
-
workflows: this.buildWorkflowBreakdown(periodRuns, now),
|
|
434
|
-
plugins: this.buildPluginBreakdown(periodRuns),
|
|
435
|
-
byDay: Object.entries(days)
|
|
436
|
-
.map(([date, d]) => ({ date, ...d }))
|
|
437
|
-
.sort((a, b) => a.date.localeCompare(b.date)),
|
|
438
|
-
};
|
|
439
356
|
}
|
|
440
|
-
|
|
441
|
-
const runs = Array.from(this.engine.runs.values());
|
|
442
|
-
const pluginRuns = runs.filter((r) => {
|
|
443
|
-
const name = r.pluginName ?? r.meta?.pluginName;
|
|
444
|
-
if (name !== pluginName)
|
|
445
|
-
return false;
|
|
446
|
-
if (projectRoot && r.meta?.projectRoot !== projectRoot)
|
|
447
|
-
return false;
|
|
448
|
-
return true;
|
|
449
|
-
});
|
|
450
|
-
const total = pluginRuns.length;
|
|
451
|
-
const successful = pluginRuns.filter((r) => r.status === "completed").length;
|
|
452
|
-
const startedAts = pluginRuns.map((r) => r.startedAt).filter((t) => typeof t === "number");
|
|
357
|
+
emptyRoiSummary() {
|
|
453
358
|
return {
|
|
454
|
-
|
|
455
|
-
totalRuns:
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
359
|
+
period: { from: '', to: '' },
|
|
360
|
+
overview: { totalRuns: 0, completedRuns: 0, failedRuns: 0, avgDuration: 0, firstPassSuccessRate: 0 },
|
|
361
|
+
efficiency: { avgTimePerTask: 0 },
|
|
362
|
+
failures: { total: 0, breakdown: { tool_error: 0, mcp_unavailable: 0, unknown: 0 } },
|
|
363
|
+
workflows: [],
|
|
364
|
+
plugins: [],
|
|
365
|
+
byDay: [],
|
|
460
366
|
};
|
|
461
367
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
368
|
+
// ---- Skill Execution Tracking ----
|
|
369
|
+
async recordExecutionStart(params) {
|
|
370
|
+
if (!this.db)
|
|
371
|
+
return;
|
|
372
|
+
this.db.insertSkillExecution({
|
|
373
|
+
executionId: params.executionId,
|
|
374
|
+
sessionId: params.sessionId,
|
|
375
|
+
skillName: params.skillName,
|
|
376
|
+
startedAt: params.startedAt,
|
|
377
|
+
status: 'running',
|
|
467
378
|
});
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
379
|
+
}
|
|
380
|
+
async recordToolCall(event) {
|
|
381
|
+
if (!this.db)
|
|
382
|
+
return;
|
|
383
|
+
try {
|
|
384
|
+
this.db.insertToolCallEvent({
|
|
385
|
+
eventId: event.eventId,
|
|
386
|
+
executionId: event.executionId,
|
|
387
|
+
sessionId: event.sessionId,
|
|
388
|
+
timestamp: event.timestamp,
|
|
389
|
+
toolName: event.toolName,
|
|
390
|
+
toolType: event.toolType,
|
|
391
|
+
isMcpTool: event.isMcpTool,
|
|
392
|
+
mcpToolName: event.mcpToolName,
|
|
393
|
+
mcpEnforced: event.mcpEnforced,
|
|
394
|
+
mcpFallback: event.mcpFallback,
|
|
395
|
+
kind: event.kind ?? 'tool_use',
|
|
396
|
+
input: event.input,
|
|
397
|
+
duration: event.duration,
|
|
398
|
+
parentToolCallId: event.parentToolCallId,
|
|
399
|
+
subagentId: event.subagentId,
|
|
400
|
+
error: event.error,
|
|
401
|
+
blocked: event.blocked,
|
|
402
|
+
blockReason: event.blockReason,
|
|
403
|
+
});
|
|
475
404
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
405
|
+
catch { }
|
|
406
|
+
}
|
|
407
|
+
async updateToolCallOutput(eventId, output, completedAt, duration) {
|
|
408
|
+
if (!this.db)
|
|
409
|
+
return;
|
|
410
|
+
try {
|
|
411
|
+
this.db.updateToolCallEvent(eventId, { output: JSON.stringify(output), duration });
|
|
412
|
+
}
|
|
413
|
+
catch { }
|
|
414
|
+
}
|
|
415
|
+
async recordExecutionComplete(params) {
|
|
416
|
+
if (!this.db)
|
|
417
|
+
return;
|
|
418
|
+
this.db.updateSkillExecution(params.executionId, {
|
|
419
|
+
status: params.status,
|
|
420
|
+
finishedAt: params.finishedAt,
|
|
421
|
+
totalDuration: params.summary.totalDuration,
|
|
422
|
+
mcpComplianceRate: params.summary.mcpComplianceRate,
|
|
423
|
+
totalToolCalls: params.summary.totalToolCalls,
|
|
424
|
+
mcpToolCalls: params.summary.mcpToolCalls,
|
|
425
|
+
directToolCalls: params.summary.directToolCalls,
|
|
426
|
+
subagentCount: params.summary.subagentCount,
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
async getExecutions(limit, skillName) {
|
|
430
|
+
if (!this.db)
|
|
431
|
+
return [];
|
|
432
|
+
return this.db.listSkillExecutions(limit, skillName);
|
|
486
433
|
}
|
|
434
|
+
async getExecutionEvents(executionId) {
|
|
435
|
+
if (!this.db)
|
|
436
|
+
return [];
|
|
437
|
+
return this.db.listToolCallEvents(executionId);
|
|
438
|
+
}
|
|
439
|
+
async getMcpCompliance() {
|
|
440
|
+
if (!this.db)
|
|
441
|
+
return { overall: 0, bySkill: {}, missedTools: [] };
|
|
442
|
+
return this.db.getMcpCompliance();
|
|
443
|
+
}
|
|
444
|
+
// ---- Failure classification (unchanged) ----
|
|
487
445
|
async classifyFailure(runId) {
|
|
488
446
|
const run = await this.getRun(runId).catch(() => null);
|
|
489
447
|
if (!run || run.status !== "failed")
|
|
@@ -542,6 +500,13 @@ let TelemetryService = class TelemetryService {
|
|
|
542
500
|
};
|
|
543
501
|
}
|
|
544
502
|
async cleanup(olderThanDays) {
|
|
503
|
+
if (!this.db)
|
|
504
|
+
return 0;
|
|
505
|
+
try {
|
|
506
|
+
this.db.cleanupStaleRecords?.();
|
|
507
|
+
}
|
|
508
|
+
catch { }
|
|
509
|
+
// Also clean up legacy engine runs
|
|
545
510
|
const runs = this.engine.runs;
|
|
546
511
|
const cutoff = Date.now() - olderThanDays * 24 * 60 * 60 * 1000;
|
|
547
512
|
let removed = 0;
|
|
@@ -553,99 +518,118 @@ let TelemetryService = class TelemetryService {
|
|
|
553
518
|
}
|
|
554
519
|
return removed;
|
|
555
520
|
}
|
|
556
|
-
//
|
|
557
|
-
async
|
|
558
|
-
if (!this.db)
|
|
559
|
-
return; // Graceful: no database = skip silently
|
|
560
|
-
this.db.insertSkillExecution({
|
|
561
|
-
executionId: params.executionId,
|
|
562
|
-
skillName: params.skillName,
|
|
563
|
-
startedAt: params.startedAt,
|
|
564
|
-
status: 'running',
|
|
565
|
-
});
|
|
566
|
-
}
|
|
567
|
-
async recordToolCall(event) {
|
|
521
|
+
// @Interval(5 * 60 * 1000) — uncomment when @nestjs/schedule is available
|
|
522
|
+
async cleanupStaleRecords() {
|
|
568
523
|
if (!this.db)
|
|
569
524
|
return;
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
if (!existing) {
|
|
573
|
-
this.db.insertSkillExecution({
|
|
574
|
-
executionId: event.executionId,
|
|
575
|
-
skillName: event.mcpToolName ?? event.toolName,
|
|
576
|
-
startedAt: event.timestamp,
|
|
577
|
-
status: 'running',
|
|
578
|
-
});
|
|
525
|
+
try {
|
|
526
|
+
this.db.cleanupStaleRecords?.();
|
|
579
527
|
}
|
|
580
|
-
|
|
581
|
-
eventId: event.eventId,
|
|
582
|
-
executionId: event.executionId,
|
|
583
|
-
timestamp: event.timestamp,
|
|
584
|
-
toolName: event.toolName,
|
|
585
|
-
toolType: event.toolType,
|
|
586
|
-
isMcpTool: event.isMcpTool,
|
|
587
|
-
mcpToolName: event.mcpToolName,
|
|
588
|
-
mcpEnforced: event.mcpEnforced,
|
|
589
|
-
mcpFallback: event.mcpFallback,
|
|
590
|
-
input: event.input,
|
|
591
|
-
tokensUsed: event.tokensUsed,
|
|
592
|
-
duration: event.duration,
|
|
593
|
-
parentToolCallId: event.parentToolCallId,
|
|
594
|
-
subagentId: event.subagentId,
|
|
595
|
-
error: event.error,
|
|
596
|
-
blocked: event.blocked,
|
|
597
|
-
blockReason: event.blockReason,
|
|
598
|
-
});
|
|
528
|
+
catch { /* best-effort */ }
|
|
599
529
|
}
|
|
600
|
-
|
|
530
|
+
// ---- Stub methods for external callers that reference old API ----
|
|
531
|
+
// ---- Tool Metrics ----
|
|
532
|
+
async recordToolMetric(metric) {
|
|
601
533
|
if (!this.db)
|
|
602
534
|
return;
|
|
603
|
-
this.db.
|
|
535
|
+
this.db.insertToolMetric({ ...metric, createdAt: Date.now() });
|
|
604
536
|
}
|
|
605
|
-
async
|
|
537
|
+
async getToolMetrics(sessionId, toolName, limit = 100, offset = 0) {
|
|
538
|
+
if (!this.db)
|
|
539
|
+
return [];
|
|
540
|
+
return this.db.getToolMetrics(sessionId, toolName, limit, offset);
|
|
541
|
+
}
|
|
542
|
+
async getToolMetricsSummary(days = 7) {
|
|
543
|
+
if (!this.db)
|
|
544
|
+
return [];
|
|
545
|
+
return this.db.getToolMetricsSummary(days);
|
|
546
|
+
}
|
|
547
|
+
// ---- Accuracy Queries ----
|
|
548
|
+
async recordAccuracyQuery(params) {
|
|
606
549
|
if (!this.db)
|
|
607
550
|
return;
|
|
608
|
-
|
|
609
|
-
const events = this.db.listToolCallEvents(params.executionId);
|
|
610
|
-
const totalToolCalls = events.length;
|
|
611
|
-
const mcpToolCalls = events.filter((e) => e.isMcpTool).length;
|
|
612
|
-
const directToolCalls = events.filter((e) => !e.isMcpTool).length;
|
|
613
|
-
const subagentCount = events.filter((e) => e.toolType === 'subagent').length;
|
|
614
|
-
this.db.updateSkillExecution(params.executionId, {
|
|
615
|
-
status: params.status,
|
|
616
|
-
finishedAt: params.finishedAt,
|
|
617
|
-
totalDuration: params.summary.totalDuration,
|
|
618
|
-
totalTokens: params.summary.totalTokens,
|
|
619
|
-
mcpComplianceRate: totalToolCalls > 0
|
|
620
|
-
? Math.round(mcpToolCalls / totalToolCalls * 10000) / 100
|
|
621
|
-
: 0,
|
|
622
|
-
totalToolCalls,
|
|
623
|
-
mcpToolCalls,
|
|
624
|
-
directToolCalls,
|
|
625
|
-
subagentCount,
|
|
626
|
-
});
|
|
551
|
+
this.db.insertAccuracyQuery({ ...params, createdAt: Date.now() });
|
|
627
552
|
}
|
|
628
|
-
async
|
|
553
|
+
async getAccuracyQueries(options) {
|
|
629
554
|
if (!this.db)
|
|
630
555
|
return [];
|
|
631
|
-
return this.db.
|
|
556
|
+
return this.db.getAccuracyQueries(options);
|
|
632
557
|
}
|
|
633
|
-
async
|
|
558
|
+
async updateAccuracyFeedback(id, feedback) {
|
|
559
|
+
if (!this.db)
|
|
560
|
+
return;
|
|
561
|
+
this.db.updateAccuracyFeedback(id, feedback);
|
|
562
|
+
}
|
|
563
|
+
async getAccuracyStats(engine, since) {
|
|
564
|
+
if (!this.db)
|
|
565
|
+
return { totalQueries: 0, annotatedQueries: 0, hitRate: null };
|
|
566
|
+
return this.db.getAccuracyStats(engine, since);
|
|
567
|
+
}
|
|
568
|
+
// ---- Auto Checker ----
|
|
569
|
+
async runAutoCheck() {
|
|
634
570
|
if (!this.db)
|
|
635
571
|
return [];
|
|
636
|
-
|
|
572
|
+
const checker = new AutoChecker(this.db);
|
|
573
|
+
return checker.checkAll();
|
|
637
574
|
}
|
|
638
|
-
|
|
575
|
+
// ---- Stub methods ----
|
|
576
|
+
async getFeedbackStats(_projectRoot, _periodDays) {
|
|
577
|
+
return { total: 0, hits: 0, partials: 0, misses: 0, hitRate: 0, partialRate: 0, missRate: 0 };
|
|
578
|
+
}
|
|
579
|
+
async getOpsRoiSummary() {
|
|
580
|
+
return {
|
|
581
|
+
period: "all-time",
|
|
582
|
+
totalRuns: 0,
|
|
583
|
+
successfulRuns: 0,
|
|
584
|
+
successRate: 0,
|
|
585
|
+
avgDuration: 0,
|
|
586
|
+
trend: "stable",
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
async getExperimentalRoiSummary() {
|
|
590
|
+
return {
|
|
591
|
+
totalScenarios: 0,
|
|
592
|
+
avgTimeSavedRate: null,
|
|
593
|
+
avgTokenReductionRate: null,
|
|
594
|
+
avgFirstPassLift: null,
|
|
595
|
+
avgReworkReductionRate: null,
|
|
596
|
+
conclusion: "No experimental data available yet",
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
async getPluginEffectiveness(pluginName, _projectRoot) {
|
|
639
600
|
if (!this.db)
|
|
640
|
-
return {
|
|
641
|
-
|
|
601
|
+
return { name: pluginName, totalRuns: 0, successRate: 0, lastUsedAt: null };
|
|
602
|
+
const sessions = this.db.listSessions(1000, 0);
|
|
603
|
+
let useCount = 0;
|
|
604
|
+
for (const s of sessions) {
|
|
605
|
+
const events = this.db.listToolCallEventsBySession(s.id);
|
|
606
|
+
for (const e of events) {
|
|
607
|
+
if (e.mcpToolName?.startsWith(pluginName + '_'))
|
|
608
|
+
useCount++;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
return {
|
|
612
|
+
name: pluginName,
|
|
613
|
+
totalRuns: useCount,
|
|
614
|
+
successRate: 0,
|
|
615
|
+
lastUsedAt: null,
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
async getPluginUsageStats(pluginName) {
|
|
619
|
+
const eff = await this.getPluginEffectiveness(pluginName);
|
|
620
|
+
return {
|
|
621
|
+
activeProjects: 0,
|
|
622
|
+
totalRuns: eff.totalRuns,
|
|
623
|
+
successfulRuns: 0,
|
|
624
|
+
lastUsedAt: eff.lastUsedAt ?? undefined,
|
|
625
|
+
};
|
|
642
626
|
}
|
|
643
627
|
};
|
|
644
628
|
TelemetryService = __decorate([
|
|
645
629
|
Injectable(),
|
|
646
630
|
__param(0, Optional()),
|
|
647
631
|
__param(0, Inject('DATABASE')),
|
|
648
|
-
__metadata("design:paramtypes", [
|
|
632
|
+
__metadata("design:paramtypes", [DevFlowDatabase])
|
|
649
633
|
], TelemetryService);
|
|
650
634
|
export { TelemetryService };
|
|
651
635
|
//# sourceMappingURL=telemetry.service.js.map
|