wyrm-mcp 7.3.2 → 7.3.3
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 +8 -14
- package/dist/activation.js +59 -1
- package/dist/agent-daemon.js +281 -4
- package/dist/agent-loop.js +332 -7
- package/dist/analytics.js +236 -13
- package/dist/attribution.js +49 -1
- package/dist/audit.js +457 -2
- package/dist/auto-capture.js +138 -3
- package/dist/auto-orchestrator.js +325 -1
- package/dist/autoconfig.d.ts +50 -0
- package/dist/autoconfig.d.ts.map +1 -1
- package/dist/autoconfig.js +1115 -39
- package/dist/autoconfig.js.map +1 -1
- package/dist/buddy-runner.js +109 -1
- package/dist/buddy.js +564 -14
- package/dist/build-flags.js +15 -1
- package/dist/capabilities.js +183 -3
- package/dist/capture.js +56 -1
- package/dist/causality.js +148 -8
- package/dist/cli.js +281 -20
- package/dist/cloud/cli.js +541 -5
- package/dist/cloud/client.js +221 -1
- package/dist/cloud/crypto.js +85 -1
- package/dist/cloud/machine-id.js +113 -2
- package/dist/cloud/recovery.js +60 -1
- package/dist/cloud/sync-engine.js +543 -7
- package/dist/cloud-backup.js +579 -5
- package/dist/cloud-profile.js +138 -1
- package/dist/cloud-sync-entrypoint.js +47 -1
- package/dist/cloud-sync.js +309 -2
- package/dist/connectors/bridge-source.d.ts +46 -0
- package/dist/connectors/bridge-source.d.ts.map +1 -0
- package/dist/connectors/bridge-source.js +77 -0
- package/dist/connectors/bridge-source.js.map +1 -0
- package/dist/connectors/index.d.ts +24 -0
- package/dist/connectors/index.d.ts.map +1 -0
- package/dist/connectors/index.js +69 -0
- package/dist/connectors/index.js.map +1 -0
- package/dist/connectors/ingest.d.ts +16 -0
- package/dist/connectors/ingest.d.ts.map +1 -0
- package/dist/connectors/ingest.js +116 -0
- package/dist/connectors/ingest.js.map +1 -0
- package/dist/connectors/types.d.ts +99 -0
- package/dist/connectors/types.d.ts.map +1 -0
- package/dist/connectors/types.js +17 -0
- package/dist/connectors/types.js.map +1 -0
- package/dist/constellation.js +168 -12
- package/dist/content-signature.js +45 -1
- package/dist/context-build-budgeted.js +144 -4
- package/dist/context-ranking.js +69 -1
- package/dist/crypto.js +179 -1
- package/dist/daemon-write-endpoint.js +290 -1
- package/dist/daemon-writer.js +406 -2
- package/dist/database.js +1278 -53
- package/dist/deprecations.js +162 -2
- package/dist/design.js +141 -13
- package/dist/event-replication.js +112 -1
- package/dist/events-sse.js +43 -7
- package/dist/events.js +238 -6
- package/dist/failure-patterns.d.ts +107 -0
- package/dist/failure-patterns.d.ts.map +1 -1
- package/dist/failure-patterns.js +924 -43
- package/dist/failure-patterns.js.map +1 -1
- package/dist/federation.js +236 -12
- package/dist/goals.js +101 -13
- package/dist/golden.js +355 -3
- package/dist/handlers/agent.js +165 -4
- package/dist/handlers/alias-adapters.js +129 -1
- package/dist/handlers/aliases.js +171 -1
- package/dist/handlers/audit.js +87 -1
- package/dist/handlers/boundary.js +221 -1
- package/dist/handlers/capture.js +1114 -73
- package/dist/handlers/causality.js +119 -9
- package/dist/handlers/cloud.js +382 -85
- package/dist/handlers/companion.js +459 -28
- package/dist/handlers/datalake.js +187 -7
- package/dist/handlers/dispatch-context.js +22 -0
- package/dist/handlers/entity.js +256 -25
- package/dist/handlers/events.js +335 -16
- package/dist/handlers/failure.d.ts.map +1 -1
- package/dist/handlers/failure.js +408 -13
- package/dist/handlers/failure.js.map +1 -1
- package/dist/handlers/goals.js +296 -4
- package/dist/handlers/intelligence.js +681 -126
- package/dist/handlers/invoicing.js +70 -1
- package/dist/handlers/mcpclient.js +137 -6
- package/dist/handlers/orchestration.js +125 -40
- package/dist/handlers/output-schemas.js +24 -1
- package/dist/handlers/presence.js +99 -3
- package/dist/handlers/project.js +182 -28
- package/dist/handlers/prompts.js +157 -6
- package/dist/handlers/quest.js +224 -4
- package/dist/handlers/recall.js +237 -13
- package/dist/handlers/registry.js +167 -1
- package/dist/handlers/resources.js +288 -1
- package/dist/handlers/review.js +74 -11
- package/dist/handlers/run.js +498 -16
- package/dist/handlers/search.js +338 -15
- package/dist/handlers/session.js +643 -31
- package/dist/handlers/share.js +184 -8
- package/dist/handlers/shims.js +464 -1
- package/dist/handlers/skill.js +449 -67
- package/dist/handlers/survivors.js +120 -1
- package/dist/handlers/symbols.js +109 -8
- package/dist/handlers/syncops.js +302 -4
- package/dist/handlers/types.js +27 -1
- package/dist/harvest.js +191 -5
- package/dist/hours.js +156 -7
- package/dist/http-auth.js +321 -3
- package/dist/http-fast.js +1302 -22
- package/dist/icons.js +47 -1
- package/dist/importers.js +268 -1
- package/dist/index.js +840 -2
- package/dist/indexer.js +145 -4
- package/dist/intelligence.js +261 -31
- package/dist/internal-dispatch.js +212 -3
- package/dist/keyset.js +110 -1
- package/dist/knowledge-graph.js +176 -12
- package/dist/license.js +441 -2
- package/dist/logger.js +199 -2
- package/dist/maintenance.js +148 -2
- package/dist/mcp-client.js +262 -6
- package/dist/memory-artifacts.js +596 -32
- package/dist/migrate-prompt.js +124 -2
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +799 -42
- package/dist/migrations.js.map +1 -1
- package/dist/performance.js +228 -1
- package/dist/presence.js +140 -11
- package/dist/priority-embed.js +164 -5
- package/dist/providers/embedding-provider.js +196 -1
- package/dist/readonly-gate.js +29 -1
- package/dist/receipt.js +43 -1
- package/dist/rehydration.js +157 -9
- package/dist/reindex.js +88 -1
- package/dist/render-target.js +544 -21
- package/dist/render.js +280 -4
- package/dist/repl-guard.js +173 -1
- package/dist/replication-daemon-entrypoint.js +31 -1
- package/dist/replication-daemon.js +262 -2
- package/dist/rerank.js +142 -1
- package/dist/resilience.js +591 -1
- package/dist/reverse-bridge.js +360 -5
- package/dist/security.js +244 -1
- package/dist/session-seen.js +51 -3
- package/dist/setup.js +260 -1
- package/dist/skill-author.js +168 -5
- package/dist/spec-kit.js +191 -1
- package/dist/sqlite-busy.js +154 -1
- package/dist/statusline.js +315 -11
- package/dist/sub-agent.js +262 -13
- package/dist/summarizer.js +139 -13
- package/dist/symbols.js +283 -7
- package/dist/sync.js +359 -5
- package/dist/tasks-dispatch.js +84 -1
- package/dist/tasks.js +282 -1
- package/dist/token-budget.js +143 -1
- package/dist/tool-analytics.js +129 -7
- package/dist/tool-annotations.js +365 -1
- package/dist/tool-manifest-v2.json +1 -1
- package/dist/tool-manifest.json +1 -1
- package/dist/tool-profiles.js +75 -1
- package/dist/trace-harvest.js +244 -6
- package/dist/types.js +30 -1
- package/dist/ui-dashboard.js +50 -41
- package/dist/ulid.js +81 -1
- package/dist/usage-tracker.js +66 -1
- package/dist/validate.js +129 -1
- package/dist/vault.js +534 -1
- package/dist/vector-init.js +67 -1
- package/dist/vectors.js +184 -3
- package/dist/version-check.js +136 -4
- package/dist/visibility.js +155 -19
- package/dist/wyrm-cli.js +2845 -101
- package/dist/wyrm-cli.js.map +1 -1
- package/dist/wyrm-guard.d.ts.map +1 -1
- package/dist/wyrm-guard.js +475 -14
- package/dist/wyrm-guard.js.map +1 -1
- package/dist/wyrm-loop.js +150 -3
- package/dist/wyrm-manifest.json +1 -1
- package/dist/wyrm-statusline-daemon.js +11 -1
- package/dist/wyrm-statusline.js +56 -4
- package/dist/wyrm-ui.js +77 -9
- package/package.json +1 -1
package/dist/analytics.js
CHANGED
|
@@ -1,7 +1,43 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Wyrm Analytics — Persistent Usage Tracking & Cost Monitoring
|
|
3
|
+
*
|
|
4
|
+
* @copyright 2026 Ghost Protocol (Pvt) Ltd.
|
|
5
|
+
* @license Proprietary — (c) 2026 Ghost Protocol (Pvt) Ltd. All rights reserved. See LICENSE.
|
|
6
|
+
* @module analytics
|
|
7
|
+
* @version 3.2.0
|
|
8
|
+
*/
|
|
9
|
+
// ==================== CONSTANTS ====================
|
|
10
|
+
/** Claude Sonnet pricing per 1M tokens */
|
|
11
|
+
const COST_PER_MILLION_INPUT = 3;
|
|
12
|
+
const COST_PER_MILLION_OUTPUT = 15;
|
|
13
|
+
const BUFFER_FLUSH_THRESHOLD = 50;
|
|
14
|
+
const FLUSH_INTERVAL_MS = 30_000;
|
|
15
|
+
const DEFAULT_RETAIN_DAYS = 90;
|
|
16
|
+
// ==================== ANALYTICS CLASS ====================
|
|
17
|
+
export class WyrmAnalytics {
|
|
18
|
+
db;
|
|
19
|
+
insertStmt;
|
|
20
|
+
batchBuffer;
|
|
21
|
+
flushInterval;
|
|
22
|
+
constructor(db) {
|
|
23
|
+
this.db = db;
|
|
24
|
+
this.batchBuffer = [];
|
|
25
|
+
this.initTables();
|
|
26
|
+
this.insertStmt = this.db.prepare(`
|
|
2
27
|
INSERT INTO usage_events (tool_name, tokens_in, tokens_out, cached, response_ms, success, error_message, timestamp)
|
|
3
28
|
VALUES (@tool_name, @tokens_in, @tokens_out, @cached, @response_ms, @success, @error_message, @timestamp)
|
|
4
|
-
`)
|
|
29
|
+
`);
|
|
30
|
+
this.flushInterval = setInterval(() => {
|
|
31
|
+
this.flush();
|
|
32
|
+
}, FLUSH_INTERVAL_MS);
|
|
33
|
+
// Don't let the timer keep the process alive
|
|
34
|
+
if (this.flushInterval.unref) {
|
|
35
|
+
this.flushInterval.unref();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// ==================== SCHEMA ====================
|
|
39
|
+
initTables() {
|
|
40
|
+
this.db.exec(`
|
|
5
41
|
CREATE TABLE IF NOT EXISTS usage_events (
|
|
6
42
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
7
43
|
tool_name TEXT NOT NULL,
|
|
@@ -32,7 +68,59 @@ const S=3,p=15,h=50,A=3e4,N=90;class k{db;insertStmt;batchBuffer;flushInterval;c
|
|
|
32
68
|
CREATE INDEX IF NOT EXISTS idx_usage_events_tool ON usage_events(tool_name);
|
|
33
69
|
CREATE INDEX IF NOT EXISTS idx_usage_events_ts ON usage_events(timestamp);
|
|
34
70
|
CREATE INDEX IF NOT EXISTS idx_cost_tracking_period ON cost_tracking(period);
|
|
35
|
-
`)
|
|
71
|
+
`);
|
|
72
|
+
}
|
|
73
|
+
// ==================== RECORDING ====================
|
|
74
|
+
/** Record a single tool usage event */
|
|
75
|
+
record(event) {
|
|
76
|
+
this.batchBuffer.push(event);
|
|
77
|
+
if (this.batchBuffer.length >= BUFFER_FLUSH_THRESHOLD) {
|
|
78
|
+
this.flush();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/** Flush buffered events to disk */
|
|
82
|
+
flush() {
|
|
83
|
+
if (this.batchBuffer.length === 0)
|
|
84
|
+
return;
|
|
85
|
+
const events = this.batchBuffer.splice(0);
|
|
86
|
+
const insertMany = this.db.transaction((batch) => {
|
|
87
|
+
for (const event of batch) {
|
|
88
|
+
this.insertStmt.run({
|
|
89
|
+
tool_name: event.tool,
|
|
90
|
+
tokens_in: event.tokens_in,
|
|
91
|
+
tokens_out: event.tokens_out,
|
|
92
|
+
cached: event.cached ? 1 : 0,
|
|
93
|
+
response_ms: event.ms,
|
|
94
|
+
success: event.success !== false ? 1 : 0,
|
|
95
|
+
error_message: event.error ?? null,
|
|
96
|
+
timestamp: event.timestamp,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
insertMany(events);
|
|
101
|
+
this.updateCostTracking(events);
|
|
102
|
+
}
|
|
103
|
+
/** Update cost_tracking aggregates from a batch of events */
|
|
104
|
+
updateCostTracking(events) {
|
|
105
|
+
const groups = new Map();
|
|
106
|
+
for (const event of events) {
|
|
107
|
+
const period = event.timestamp.slice(0, 7); // YYYY-MM
|
|
108
|
+
const key = `${period}:${event.tool}`;
|
|
109
|
+
let group = groups.get(key);
|
|
110
|
+
if (!group) {
|
|
111
|
+
group = { tool: event.tool, period, calls: 0, tokens_in: 0, tokens_out: 0, cached: 0, total_ms: 0, errors: 0 };
|
|
112
|
+
groups.set(key, group);
|
|
113
|
+
}
|
|
114
|
+
group.calls++;
|
|
115
|
+
group.tokens_in += event.tokens_in;
|
|
116
|
+
group.tokens_out += event.tokens_out;
|
|
117
|
+
if (event.cached)
|
|
118
|
+
group.cached++;
|
|
119
|
+
group.total_ms += event.ms;
|
|
120
|
+
if (event.success === false)
|
|
121
|
+
group.errors++;
|
|
122
|
+
}
|
|
123
|
+
const upsertStmt = this.db.prepare(`
|
|
36
124
|
INSERT INTO cost_tracking (period, tool_name, call_count, total_tokens_in, total_tokens_out, cached_count, avg_response_ms, error_count, estimated_cost_usd, updated_at)
|
|
37
125
|
VALUES (@period, @tool_name, @call_count, @total_tokens_in, @total_tokens_out, @cached_count, @avg_response_ms, @error_count, @estimated_cost_usd, datetime('now'))
|
|
38
126
|
ON CONFLICT(period, tool_name) DO UPDATE SET
|
|
@@ -44,7 +132,35 @@ const S=3,p=15,h=50,A=3e4,N=90;class k{db;insertStmt;batchBuffer;flushInterval;c
|
|
|
44
132
|
error_count = error_count + @error_count,
|
|
45
133
|
estimated_cost_usd = estimated_cost_usd + @estimated_cost_usd,
|
|
46
134
|
updated_at = datetime('now')
|
|
47
|
-
`);
|
|
135
|
+
`);
|
|
136
|
+
const upsertMany = this.db.transaction((entries) => {
|
|
137
|
+
for (const group of entries.values()) {
|
|
138
|
+
const cost = this.estimateCost(group.tokens_in, group.tokens_out);
|
|
139
|
+
upsertStmt.run({
|
|
140
|
+
period: group.period,
|
|
141
|
+
tool_name: group.tool,
|
|
142
|
+
call_count: group.calls,
|
|
143
|
+
total_tokens_in: group.tokens_in,
|
|
144
|
+
total_tokens_out: group.tokens_out,
|
|
145
|
+
cached_count: group.cached,
|
|
146
|
+
avg_response_ms: group.calls > 0 ? group.total_ms / group.calls : 0,
|
|
147
|
+
error_count: group.errors,
|
|
148
|
+
estimated_cost_usd: cost,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
upsertMany(groups);
|
|
153
|
+
}
|
|
154
|
+
// ==================== QUERIES ====================
|
|
155
|
+
/** Get dashboard summary for a time period */
|
|
156
|
+
dashboard(days = 30) {
|
|
157
|
+
// Ensure buffered data is included
|
|
158
|
+
this.flush();
|
|
159
|
+
const start = new Date();
|
|
160
|
+
start.setDate(start.getDate() - days);
|
|
161
|
+
const startStr = start.toISOString().slice(0, 10);
|
|
162
|
+
const endStr = new Date().toISOString().slice(0, 10);
|
|
163
|
+
const summary = this.db.prepare(`
|
|
48
164
|
SELECT
|
|
49
165
|
COUNT(*) AS total_calls,
|
|
50
166
|
COUNT(DISTINCT tool_name) AS unique_tools,
|
|
@@ -61,7 +177,8 @@ const S=3,p=15,h=50,A=3e4,N=90;class k{db;insertStmt;batchBuffer;flushInterval;c
|
|
|
61
177
|
END AS error_rate
|
|
62
178
|
FROM usage_events
|
|
63
179
|
WHERE timestamp >= @start
|
|
64
|
-
`).get({start:
|
|
180
|
+
`).get({ start: startStr });
|
|
181
|
+
const top_tools = this.db.prepare(`
|
|
65
182
|
SELECT
|
|
66
183
|
tool_name AS tool,
|
|
67
184
|
COUNT(*) AS calls,
|
|
@@ -71,7 +188,8 @@ const S=3,p=15,h=50,A=3e4,N=90;class k{db;insertStmt;batchBuffer;flushInterval;c
|
|
|
71
188
|
GROUP BY tool_name
|
|
72
189
|
ORDER BY calls DESC
|
|
73
190
|
LIMIT 10
|
|
74
|
-
`).all({start:
|
|
191
|
+
`).all({ start: startStr });
|
|
192
|
+
const daily = this.db.prepare(`
|
|
75
193
|
SELECT
|
|
76
194
|
DATE(timestamp) AS date,
|
|
77
195
|
COUNT(*) AS calls,
|
|
@@ -81,7 +199,31 @@ const S=3,p=15,h=50,A=3e4,N=90;class k{db;insertStmt;batchBuffer;flushInterval;c
|
|
|
81
199
|
WHERE timestamp >= @start
|
|
82
200
|
GROUP BY DATE(timestamp)
|
|
83
201
|
ORDER BY date ASC
|
|
84
|
-
`).all({start:
|
|
202
|
+
`).all({ start: startStr });
|
|
203
|
+
const estimated_cost_usd = this.estimateCost(summary.total_tokens_in, summary.total_tokens_out);
|
|
204
|
+
return {
|
|
205
|
+
period: { start: startStr, end: endStr },
|
|
206
|
+
summary: {
|
|
207
|
+
total_calls: summary.total_calls,
|
|
208
|
+
unique_tools: summary.unique_tools,
|
|
209
|
+
total_tokens_in: summary.total_tokens_in,
|
|
210
|
+
total_tokens_out: summary.total_tokens_out,
|
|
211
|
+
cache_hit_rate: summary.cache_hit_rate,
|
|
212
|
+
avg_response_ms: summary.avg_response_ms,
|
|
213
|
+
error_rate: summary.error_rate,
|
|
214
|
+
estimated_cost_usd,
|
|
215
|
+
},
|
|
216
|
+
top_tools,
|
|
217
|
+
daily,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
/** Get per-tool breakdown */
|
|
221
|
+
toolBreakdown(toolName, days = 30) {
|
|
222
|
+
this.flush();
|
|
223
|
+
const start = new Date();
|
|
224
|
+
start.setDate(start.getDate() - days);
|
|
225
|
+
const startStr = start.toISOString().slice(0, 10);
|
|
226
|
+
const stats = this.db.prepare(`
|
|
85
227
|
SELECT
|
|
86
228
|
COUNT(*) AS total_calls,
|
|
87
229
|
COALESCE(ROUND(AVG(tokens_in), 2), 0) AS avg_tokens_in,
|
|
@@ -97,7 +239,8 @@ const S=3,p=15,h=50,A=3e4,N=90;class k{db;insertStmt;batchBuffer;flushInterval;c
|
|
|
97
239
|
END AS error_rate
|
|
98
240
|
FROM usage_events
|
|
99
241
|
WHERE tool_name = @tool AND timestamp >= @start
|
|
100
|
-
`).get({tool:
|
|
242
|
+
`).get({ tool: toolName, start: startStr });
|
|
243
|
+
const daily = this.db.prepare(`
|
|
101
244
|
SELECT
|
|
102
245
|
DATE(timestamp) AS date,
|
|
103
246
|
COUNT(*) AS calls
|
|
@@ -105,7 +248,23 @@ const S=3,p=15,h=50,A=3e4,N=90;class k{db;insertStmt;batchBuffer;flushInterval;c
|
|
|
105
248
|
WHERE tool_name = @tool AND timestamp >= @start
|
|
106
249
|
GROUP BY DATE(timestamp)
|
|
107
250
|
ORDER BY date ASC
|
|
108
|
-
`).all({tool:
|
|
251
|
+
`).all({ tool: toolName, start: startStr });
|
|
252
|
+
return {
|
|
253
|
+
tool: toolName,
|
|
254
|
+
total_calls: stats.total_calls,
|
|
255
|
+
avg_tokens_in: stats.avg_tokens_in,
|
|
256
|
+
avg_tokens_out: stats.avg_tokens_out,
|
|
257
|
+
avg_response_ms: stats.avg_response_ms,
|
|
258
|
+
cache_hit_rate: stats.cache_hit_rate,
|
|
259
|
+
error_rate: stats.error_rate,
|
|
260
|
+
daily,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
/** Get cost estimate for a period */
|
|
264
|
+
costReport(period) {
|
|
265
|
+
this.flush();
|
|
266
|
+
const targetPeriod = period ?? new Date().toISOString().slice(0, 7);
|
|
267
|
+
const tools = this.db.prepare(`
|
|
109
268
|
SELECT
|
|
110
269
|
tool_name AS tool,
|
|
111
270
|
call_count AS calls,
|
|
@@ -115,7 +274,35 @@ const S=3,p=15,h=50,A=3e4,N=90;class k{db;insertStmt;batchBuffer;flushInterval;c
|
|
|
115
274
|
FROM cost_tracking
|
|
116
275
|
WHERE period = @period
|
|
117
276
|
ORDER BY estimated_cost_usd DESC
|
|
118
|
-
`).all({period:
|
|
277
|
+
`).all({ period: targetPeriod });
|
|
278
|
+
const total_cost_usd = tools.reduce((sum, t) => sum + t.cost_usd, 0);
|
|
279
|
+
// Project monthly cost based on days elapsed in the period
|
|
280
|
+
const now = new Date();
|
|
281
|
+
const periodYear = parseInt(targetPeriod.slice(0, 4), 10);
|
|
282
|
+
const periodMonth = parseInt(targetPeriod.slice(5, 7), 10) - 1;
|
|
283
|
+
const periodStart = new Date(periodYear, periodMonth, 1);
|
|
284
|
+
const periodEnd = new Date(periodYear, periodMonth + 1, 0); // last day of month
|
|
285
|
+
const totalDaysInMonth = periodEnd.getDate();
|
|
286
|
+
const elapsed = Math.max(1, Math.ceil((now.getTime() - periodStart.getTime()) / (1000 * 60 * 60 * 24)));
|
|
287
|
+
const daysToProject = Math.min(elapsed, totalDaysInMonth);
|
|
288
|
+
const projected_monthly_usd = totalDaysInMonth > 0
|
|
289
|
+
? Math.round(((total_cost_usd / daysToProject) * totalDaysInMonth) * 100) / 100
|
|
290
|
+
: total_cost_usd;
|
|
291
|
+
return {
|
|
292
|
+
period: targetPeriod,
|
|
293
|
+
tools,
|
|
294
|
+
total_cost_usd: Math.round(total_cost_usd * 100) / 100,
|
|
295
|
+
projected_monthly_usd,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
/** Clean up old events (retention policy) */
|
|
299
|
+
cleanup(retainDays = DEFAULT_RETAIN_DAYS) {
|
|
300
|
+
this.flush();
|
|
301
|
+
const cutoff = new Date();
|
|
302
|
+
cutoff.setDate(cutoff.getDate() - retainDays);
|
|
303
|
+
const cutoffStr = cutoff.toISOString().slice(0, 10);
|
|
304
|
+
// Aggregate old events into cost_tracking before deleting
|
|
305
|
+
const oldEvents = this.db.prepare(`
|
|
119
306
|
SELECT
|
|
120
307
|
STRFTIME('%Y-%m', timestamp) AS period,
|
|
121
308
|
tool_name,
|
|
@@ -128,7 +315,8 @@ const S=3,p=15,h=50,A=3e4,N=90;class k{db;insertStmt;batchBuffer;flushInterval;c
|
|
|
128
315
|
FROM usage_events
|
|
129
316
|
WHERE timestamp < @cutoff
|
|
130
317
|
GROUP BY STRFTIME('%Y-%m', timestamp), tool_name
|
|
131
|
-
`).all({cutoff:
|
|
318
|
+
`).all({ cutoff: cutoffStr });
|
|
319
|
+
const upsertStmt = this.db.prepare(`
|
|
132
320
|
INSERT INTO cost_tracking (period, tool_name, call_count, total_tokens_in, total_tokens_out, cached_count, avg_response_ms, error_count, estimated_cost_usd, updated_at)
|
|
133
321
|
VALUES (@period, @tool_name, @call_count, @total_tokens_in, @total_tokens_out, @cached_count, @avg_response_ms, @error_count, @estimated_cost_usd, datetime('now'))
|
|
134
322
|
ON CONFLICT(period, tool_name) DO UPDATE SET
|
|
@@ -140,6 +328,41 @@ const S=3,p=15,h=50,A=3e4,N=90;class k{db;insertStmt;batchBuffer;flushInterval;c
|
|
|
140
328
|
error_count = error_count + @error_count,
|
|
141
329
|
estimated_cost_usd = estimated_cost_usd + @estimated_cost_usd,
|
|
142
330
|
updated_at = datetime('now')
|
|
143
|
-
`);
|
|
331
|
+
`);
|
|
332
|
+
const cleanupTxn = this.db.transaction(() => {
|
|
333
|
+
for (const row of oldEvents) {
|
|
334
|
+
const cost = this.estimateCost(row.total_tokens_in, row.total_tokens_out);
|
|
335
|
+
upsertStmt.run({
|
|
336
|
+
period: row.period,
|
|
337
|
+
tool_name: row.tool_name,
|
|
338
|
+
call_count: row.call_count,
|
|
339
|
+
total_tokens_in: row.total_tokens_in,
|
|
340
|
+
total_tokens_out: row.total_tokens_out,
|
|
341
|
+
cached_count: row.cached_count,
|
|
342
|
+
avg_response_ms: row.avg_response_ms,
|
|
343
|
+
error_count: row.error_count,
|
|
344
|
+
estimated_cost_usd: cost,
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
const result = this.db.prepare(`
|
|
144
348
|
DELETE FROM usage_events WHERE timestamp < @cutoff
|
|
145
|
-
`).run({cutoff:
|
|
349
|
+
`).run({ cutoff: cutoffStr });
|
|
350
|
+
return result.changes;
|
|
351
|
+
});
|
|
352
|
+
const deleted = cleanupTxn();
|
|
353
|
+
return { deleted };
|
|
354
|
+
}
|
|
355
|
+
/** Shutdown: flush remaining buffer and clear timer */
|
|
356
|
+
shutdown() {
|
|
357
|
+
clearInterval(this.flushInterval);
|
|
358
|
+
this.flush();
|
|
359
|
+
}
|
|
360
|
+
// ==================== HELPERS ====================
|
|
361
|
+
/** Estimate USD cost from token counts (Claude Sonnet rates) */
|
|
362
|
+
estimateCost(tokensIn, tokensOut) {
|
|
363
|
+
const inputCost = (tokensIn / 1_000_000) * COST_PER_MILLION_INPUT;
|
|
364
|
+
const outputCost = (tokensOut / 1_000_000) * COST_PER_MILLION_OUTPUT;
|
|
365
|
+
return Math.round((inputCost + outputCost) * 1_000_000) / 1_000_000;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
//# sourceMappingURL=analytics.js.map
|
package/dist/attribution.js
CHANGED
|
@@ -1 +1,49 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Wyrm attribution read helpers (v7 F2, T008).
|
|
3
|
+
*
|
|
4
|
+
* @copyright 2026 Ghost Protocol (Pvt) Ltd.
|
|
5
|
+
* @license Proprietary — (c) 2026 Ghost Protocol (Pvt) Ltd. All rights reserved. See LICENSE.
|
|
6
|
+
*
|
|
7
|
+
* Migration 20 adds nullable `agent_id`/`run_id` attribution columns to 9
|
|
8
|
+
* tables. Every row written before v7 — and any v7 row written outside a fleet
|
|
9
|
+
* run — carries NULL attribution. Article VI: a v6.x DB must open under v7
|
|
10
|
+
* with 100% of its rows, and that history must stay legibly attributed, so at
|
|
11
|
+
* every READ SITE that surfaces attribution, NULL reads as actor='legacy'.
|
|
12
|
+
*
|
|
13
|
+
* Precedence when rendering "who did this" (the same order is documented in
|
|
14
|
+
* migration 20 next to the events.agent_id column):
|
|
15
|
+
* 1. agent_id — machine identity (v7 fleet writes)
|
|
16
|
+
* 2. actor — display identity (the existing 6.x column on
|
|
17
|
+
* events/audit_log; it is EXTENDED, never replaced)
|
|
18
|
+
* 3. LEGACY_ACTOR — 'legacy' (neither set: a pre-v7 row)
|
|
19
|
+
*
|
|
20
|
+
* Wire paths (eventsForPush, ingestRemoteEvent, replication) intentionally do
|
|
21
|
+
* NOT coalesce: NULL stays NULL in storage and on the wire so replication
|
|
22
|
+
* round-trips are byte-faithful. 'legacy' is a READ-TIME presentation value
|
|
23
|
+
* only — it is never written back to the database.
|
|
24
|
+
*/
|
|
25
|
+
/** What NULL attribution reads as at attribution-surfacing read sites. */
|
|
26
|
+
export const LEGACY_ACTOR = 'legacy';
|
|
27
|
+
/**
|
|
28
|
+
* Read a display actor from a nullable actor column.
|
|
29
|
+
* NULL/undefined/blank -> 'legacy'; anything else passes through trimmed.
|
|
30
|
+
*/
|
|
31
|
+
export function readActor(actor) {
|
|
32
|
+
const a = typeof actor === 'string' ? actor.trim() : '';
|
|
33
|
+
return a !== '' ? a : LEGACY_ACTOR;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the full attribution for one row using the documented precedence.
|
|
37
|
+
* Use this at any read site that surfaces who/which-run produced a row
|
|
38
|
+
* (failure_check verdicts, wyrm_stats view=failures, event displays).
|
|
39
|
+
*/
|
|
40
|
+
export function resolveAttribution(row) {
|
|
41
|
+
const agentId = row.agent_id ?? null;
|
|
42
|
+
const runId = row.run_id ?? null;
|
|
43
|
+
return {
|
|
44
|
+
actor: agentId ?? readActor(row.actor),
|
|
45
|
+
agent_id: agentId,
|
|
46
|
+
run_id: runId,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=attribution.js.map
|