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.
Files changed (184) hide show
  1. package/README.md +8 -14
  2. package/dist/activation.js +59 -1
  3. package/dist/agent-daemon.js +281 -4
  4. package/dist/agent-loop.js +332 -7
  5. package/dist/analytics.js +236 -13
  6. package/dist/attribution.js +49 -1
  7. package/dist/audit.js +457 -2
  8. package/dist/auto-capture.js +138 -3
  9. package/dist/auto-orchestrator.js +325 -1
  10. package/dist/autoconfig.d.ts +50 -0
  11. package/dist/autoconfig.d.ts.map +1 -1
  12. package/dist/autoconfig.js +1115 -39
  13. package/dist/autoconfig.js.map +1 -1
  14. package/dist/buddy-runner.js +109 -1
  15. package/dist/buddy.js +564 -14
  16. package/dist/build-flags.js +15 -1
  17. package/dist/capabilities.js +183 -3
  18. package/dist/capture.js +56 -1
  19. package/dist/causality.js +148 -8
  20. package/dist/cli.js +281 -20
  21. package/dist/cloud/cli.js +541 -5
  22. package/dist/cloud/client.js +221 -1
  23. package/dist/cloud/crypto.js +85 -1
  24. package/dist/cloud/machine-id.js +113 -2
  25. package/dist/cloud/recovery.js +60 -1
  26. package/dist/cloud/sync-engine.js +543 -7
  27. package/dist/cloud-backup.js +579 -5
  28. package/dist/cloud-profile.js +138 -1
  29. package/dist/cloud-sync-entrypoint.js +47 -1
  30. package/dist/cloud-sync.js +309 -2
  31. package/dist/connectors/bridge-source.d.ts +46 -0
  32. package/dist/connectors/bridge-source.d.ts.map +1 -0
  33. package/dist/connectors/bridge-source.js +77 -0
  34. package/dist/connectors/bridge-source.js.map +1 -0
  35. package/dist/connectors/index.d.ts +24 -0
  36. package/dist/connectors/index.d.ts.map +1 -0
  37. package/dist/connectors/index.js +69 -0
  38. package/dist/connectors/index.js.map +1 -0
  39. package/dist/connectors/ingest.d.ts +16 -0
  40. package/dist/connectors/ingest.d.ts.map +1 -0
  41. package/dist/connectors/ingest.js +116 -0
  42. package/dist/connectors/ingest.js.map +1 -0
  43. package/dist/connectors/types.d.ts +99 -0
  44. package/dist/connectors/types.d.ts.map +1 -0
  45. package/dist/connectors/types.js +17 -0
  46. package/dist/connectors/types.js.map +1 -0
  47. package/dist/constellation.js +168 -12
  48. package/dist/content-signature.js +45 -1
  49. package/dist/context-build-budgeted.js +144 -4
  50. package/dist/context-ranking.js +69 -1
  51. package/dist/crypto.js +179 -1
  52. package/dist/daemon-write-endpoint.js +290 -1
  53. package/dist/daemon-writer.js +406 -2
  54. package/dist/database.js +1278 -53
  55. package/dist/deprecations.js +162 -2
  56. package/dist/design.js +141 -13
  57. package/dist/event-replication.js +112 -1
  58. package/dist/events-sse.js +43 -7
  59. package/dist/events.js +238 -6
  60. package/dist/failure-patterns.d.ts +107 -0
  61. package/dist/failure-patterns.d.ts.map +1 -1
  62. package/dist/failure-patterns.js +924 -43
  63. package/dist/failure-patterns.js.map +1 -1
  64. package/dist/federation.js +236 -12
  65. package/dist/goals.js +101 -13
  66. package/dist/golden.js +355 -3
  67. package/dist/handlers/agent.js +165 -4
  68. package/dist/handlers/alias-adapters.js +129 -1
  69. package/dist/handlers/aliases.js +171 -1
  70. package/dist/handlers/audit.js +87 -1
  71. package/dist/handlers/boundary.js +221 -1
  72. package/dist/handlers/capture.js +1114 -73
  73. package/dist/handlers/causality.js +119 -9
  74. package/dist/handlers/cloud.js +382 -85
  75. package/dist/handlers/companion.js +459 -28
  76. package/dist/handlers/datalake.js +187 -7
  77. package/dist/handlers/dispatch-context.js +22 -0
  78. package/dist/handlers/entity.js +256 -25
  79. package/dist/handlers/events.js +335 -16
  80. package/dist/handlers/failure.d.ts.map +1 -1
  81. package/dist/handlers/failure.js +408 -13
  82. package/dist/handlers/failure.js.map +1 -1
  83. package/dist/handlers/goals.js +296 -4
  84. package/dist/handlers/intelligence.js +681 -126
  85. package/dist/handlers/invoicing.js +70 -1
  86. package/dist/handlers/mcpclient.js +137 -6
  87. package/dist/handlers/orchestration.js +125 -40
  88. package/dist/handlers/output-schemas.js +24 -1
  89. package/dist/handlers/presence.js +99 -3
  90. package/dist/handlers/project.js +182 -28
  91. package/dist/handlers/prompts.js +157 -6
  92. package/dist/handlers/quest.js +224 -4
  93. package/dist/handlers/recall.js +237 -13
  94. package/dist/handlers/registry.js +167 -1
  95. package/dist/handlers/resources.js +288 -1
  96. package/dist/handlers/review.js +74 -11
  97. package/dist/handlers/run.js +498 -16
  98. package/dist/handlers/search.js +338 -15
  99. package/dist/handlers/session.js +643 -31
  100. package/dist/handlers/share.js +184 -8
  101. package/dist/handlers/shims.js +464 -1
  102. package/dist/handlers/skill.js +449 -67
  103. package/dist/handlers/survivors.js +120 -1
  104. package/dist/handlers/symbols.js +109 -8
  105. package/dist/handlers/syncops.js +302 -4
  106. package/dist/handlers/types.js +27 -1
  107. package/dist/harvest.js +191 -5
  108. package/dist/hours.js +156 -7
  109. package/dist/http-auth.js +321 -3
  110. package/dist/http-fast.js +1302 -22
  111. package/dist/icons.js +47 -1
  112. package/dist/importers.js +268 -1
  113. package/dist/index.js +840 -2
  114. package/dist/indexer.js +145 -4
  115. package/dist/intelligence.js +261 -31
  116. package/dist/internal-dispatch.js +212 -3
  117. package/dist/keyset.js +110 -1
  118. package/dist/knowledge-graph.js +176 -12
  119. package/dist/license.js +441 -2
  120. package/dist/logger.js +199 -2
  121. package/dist/maintenance.js +148 -2
  122. package/dist/mcp-client.js +262 -6
  123. package/dist/memory-artifacts.js +596 -32
  124. package/dist/migrate-prompt.js +124 -2
  125. package/dist/migrations.d.ts.map +1 -1
  126. package/dist/migrations.js +799 -42
  127. package/dist/migrations.js.map +1 -1
  128. package/dist/performance.js +228 -1
  129. package/dist/presence.js +140 -11
  130. package/dist/priority-embed.js +164 -5
  131. package/dist/providers/embedding-provider.js +196 -1
  132. package/dist/readonly-gate.js +29 -1
  133. package/dist/receipt.js +43 -1
  134. package/dist/rehydration.js +157 -9
  135. package/dist/reindex.js +88 -1
  136. package/dist/render-target.js +544 -21
  137. package/dist/render.js +280 -4
  138. package/dist/repl-guard.js +173 -1
  139. package/dist/replication-daemon-entrypoint.js +31 -1
  140. package/dist/replication-daemon.js +262 -2
  141. package/dist/rerank.js +142 -1
  142. package/dist/resilience.js +591 -1
  143. package/dist/reverse-bridge.js +360 -5
  144. package/dist/security.js +244 -1
  145. package/dist/session-seen.js +51 -3
  146. package/dist/setup.js +260 -1
  147. package/dist/skill-author.js +168 -5
  148. package/dist/spec-kit.js +191 -1
  149. package/dist/sqlite-busy.js +154 -1
  150. package/dist/statusline.js +315 -11
  151. package/dist/sub-agent.js +262 -13
  152. package/dist/summarizer.js +139 -13
  153. package/dist/symbols.js +283 -7
  154. package/dist/sync.js +359 -5
  155. package/dist/tasks-dispatch.js +84 -1
  156. package/dist/tasks.js +282 -1
  157. package/dist/token-budget.js +143 -1
  158. package/dist/tool-analytics.js +129 -7
  159. package/dist/tool-annotations.js +365 -1
  160. package/dist/tool-manifest-v2.json +1 -1
  161. package/dist/tool-manifest.json +1 -1
  162. package/dist/tool-profiles.js +75 -1
  163. package/dist/trace-harvest.js +244 -6
  164. package/dist/types.js +30 -1
  165. package/dist/ui-dashboard.js +50 -41
  166. package/dist/ulid.js +81 -1
  167. package/dist/usage-tracker.js +66 -1
  168. package/dist/validate.js +129 -1
  169. package/dist/vault.js +534 -1
  170. package/dist/vector-init.js +67 -1
  171. package/dist/vectors.js +184 -3
  172. package/dist/version-check.js +136 -4
  173. package/dist/visibility.js +155 -19
  174. package/dist/wyrm-cli.js +2845 -101
  175. package/dist/wyrm-cli.js.map +1 -1
  176. package/dist/wyrm-guard.d.ts.map +1 -1
  177. package/dist/wyrm-guard.js +475 -14
  178. package/dist/wyrm-guard.js.map +1 -1
  179. package/dist/wyrm-loop.js +150 -3
  180. package/dist/wyrm-manifest.json +1 -1
  181. package/dist/wyrm-statusline-daemon.js +11 -1
  182. package/dist/wyrm-statusline.js +56 -4
  183. package/dist/wyrm-ui.js +77 -9
  184. package/package.json +1 -1
package/dist/analytics.js CHANGED
@@ -1,7 +1,43 @@
1
- const S=3,p=15,h=50,A=3e4,N=90;class k{db;insertStmt;batchBuffer;flushInterval;constructor(_){this.db=_,this.batchBuffer=[],this.initTables(),this.insertStmt=this.db.prepare(`
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
- `),this.flushInterval=setInterval(()=>{this.flush()},3e4),this.flushInterval.unref&&this.flushInterval.unref()}initTables(){this.db.exec(`
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
- `)}record(_){this.batchBuffer.push(_),this.batchBuffer.length>=50&&this.flush()}flush(){if(this.batchBuffer.length===0)return;const _=this.batchBuffer.splice(0);this.db.transaction(n=>{for(const o of n)this.insertStmt.run({tool_name:o.tool,tokens_in:o.tokens_in,tokens_out:o.tokens_out,cached:o.cached?1:0,response_ms:o.ms,success:o.success!==!1?1:0,error_message:o.error??null,timestamp:o.timestamp})})(_),this.updateCostTracking(_)}updateCostTracking(_){const s=new Map;for(const t of _){const e=t.timestamp.slice(0,7),r=`${e}:${t.tool}`;let a=s.get(r);a||(a={tool:t.tool,period:e,calls:0,tokens_in:0,tokens_out:0,cached:0,total_ms:0,errors:0},s.set(r,a)),a.calls++,a.tokens_in+=t.tokens_in,a.tokens_out+=t.tokens_out,t.cached&&a.cached++,a.total_ms+=t.ms,t.success===!1&&a.errors++}const n=this.db.prepare(`
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
- `);this.db.transaction(t=>{for(const e of t.values()){const r=this.estimateCost(e.tokens_in,e.tokens_out);n.run({period:e.period,tool_name:e.tool,call_count:e.calls,total_tokens_in:e.tokens_in,total_tokens_out:e.tokens_out,cached_count:e.cached,avg_response_ms:e.calls>0?e.total_ms/e.calls:0,error_count:e.errors,estimated_cost_usd:r})}})(s)}dashboard(_=30){this.flush();const s=new Date;s.setDate(s.getDate()-_);const n=s.toISOString().slice(0,10),o=new Date().toISOString().slice(0,10),t=this.db.prepare(`
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:n}),e=this.db.prepare(`
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:n}),r=this.db.prepare(`
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:n}),a=this.estimateCost(t.total_tokens_in,t.total_tokens_out);return{period:{start:n,end:o},summary:{total_calls:t.total_calls,unique_tools:t.unique_tools,total_tokens_in:t.total_tokens_in,total_tokens_out:t.total_tokens_out,cache_hit_rate:t.cache_hit_rate,avg_response_ms:t.avg_response_ms,error_rate:t.error_rate,estimated_cost_usd:a},top_tools:e,daily:r}}toolBreakdown(_,s=30){this.flush();const n=new Date;n.setDate(n.getDate()-s);const o=n.toISOString().slice(0,10),t=this.db.prepare(`
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:_,start:o}),e=this.db.prepare(`
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:_,start:o});return{tool:_,total_calls:t.total_calls,avg_tokens_in:t.avg_tokens_in,avg_tokens_out:t.avg_tokens_out,avg_response_ms:t.avg_response_ms,cache_hit_rate:t.cache_hit_rate,error_rate:t.error_rate,daily:e}}costReport(_){this.flush();const s=_??new Date().toISOString().slice(0,7),n=this.db.prepare(`
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:s}),o=n.reduce((d,T)=>d+T.cost_usd,0),t=new Date,e=parseInt(s.slice(0,4),10),r=parseInt(s.slice(5,7),10)-1,a=new Date(e,r,1),l=new Date(e,r+1,0).getDate(),u=Math.max(1,Math.ceil((t.getTime()-a.getTime())/(1e3*60*60*24))),E=Math.min(u,l),i=l>0?Math.round(o/E*l*100)/100:o;return{period:s,tools:n,total_cost_usd:Math.round(o*100)/100,projected_monthly_usd:i}}cleanup(_=90){this.flush();const s=new Date;s.setDate(s.getDate()-_);const n=s.toISOString().slice(0,10),o=this.db.prepare(`
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:n}),t=this.db.prepare(`
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
- `);return{deleted:this.db.transaction(()=>{for(const c of o){const l=this.estimateCost(c.total_tokens_in,c.total_tokens_out);t.run({period:c.period,tool_name:c.tool_name,call_count:c.call_count,total_tokens_in:c.total_tokens_in,total_tokens_out:c.total_tokens_out,cached_count:c.cached_count,avg_response_ms:c.avg_response_ms,error_count:c.error_count,estimated_cost_usd:l})}return this.db.prepare(`
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:n}).changes})()}}shutdown(){clearInterval(this.flushInterval),this.flush()}estimateCost(_,s){const n=_/1e6*3,o=s/1e6*15;return Math.round((n+o)*1e6)/1e6}}export{k as WyrmAnalytics};
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
@@ -1 +1,49 @@
1
- const e="legacy";function o(t){const n=typeof t=="string"?t.trim():"";return n!==""?n:e}function i(t){const n=t.agent_id??null,r=t.run_id??null;return{actor:n??o(t.actor),agent_id:n,run_id:r}}export{e as LEGACY_ACTOR,o as readActor,i as resolveAttribution};
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