@agentlensai/server 0.3.0 → 0.6.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.
Files changed (259) hide show
  1. package/dist/db/embedding-store.d.ts +74 -0
  2. package/dist/db/embedding-store.d.ts.map +1 -0
  3. package/dist/db/embedding-store.js +177 -0
  4. package/dist/db/embedding-store.js.map +1 -0
  5. package/dist/db/health-snapshot-store.d.ts +33 -0
  6. package/dist/db/health-snapshot-store.d.ts.map +1 -0
  7. package/dist/db/health-snapshot-store.js +112 -0
  8. package/dist/db/health-snapshot-store.js.map +1 -0
  9. package/dist/db/lesson-store.d.ts +57 -0
  10. package/dist/db/lesson-store.d.ts.map +1 -0
  11. package/dist/db/lesson-store.js +217 -0
  12. package/dist/db/lesson-store.js.map +1 -0
  13. package/dist/db/migrate.d.ts.map +1 -1
  14. package/dist/db/migrate.js +256 -8
  15. package/dist/db/migrate.js.map +1 -1
  16. package/dist/db/schema.sqlite.d.ts +822 -47
  17. package/dist/db/schema.sqlite.d.ts.map +1 -1
  18. package/dist/db/schema.sqlite.js +79 -4
  19. package/dist/db/schema.sqlite.js.map +1 -1
  20. package/dist/db/session-summary-store.d.ts +45 -0
  21. package/dist/db/session-summary-store.d.ts.map +1 -0
  22. package/dist/db/session-summary-store.js +112 -0
  23. package/dist/db/session-summary-store.js.map +1 -0
  24. package/dist/db/sqlite-store.d.ts +19 -12
  25. package/dist/db/sqlite-store.d.ts.map +1 -1
  26. package/dist/db/sqlite-store.js +145 -44
  27. package/dist/db/sqlite-store.js.map +1 -1
  28. package/dist/db/tenant-scoped-store.d.ts +61 -0
  29. package/dist/db/tenant-scoped-store.d.ts.map +1 -0
  30. package/dist/db/tenant-scoped-store.js +94 -0
  31. package/dist/db/tenant-scoped-store.js.map +1 -0
  32. package/dist/index.d.ts +18 -2
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +78 -5
  35. package/dist/index.js.map +1 -1
  36. package/dist/lib/alert-engine.d.ts +11 -0
  37. package/dist/lib/alert-engine.d.ts.map +1 -1
  38. package/dist/lib/alert-engine.js +65 -24
  39. package/dist/lib/alert-engine.js.map +1 -1
  40. package/dist/lib/analysis/cost-analysis.d.ts +20 -0
  41. package/dist/lib/analysis/cost-analysis.d.ts.map +1 -0
  42. package/dist/lib/analysis/cost-analysis.js +161 -0
  43. package/dist/lib/analysis/cost-analysis.js.map +1 -0
  44. package/dist/lib/analysis/error-patterns.d.ts +26 -0
  45. package/dist/lib/analysis/error-patterns.d.ts.map +1 -0
  46. package/dist/lib/analysis/error-patterns.js +158 -0
  47. package/dist/lib/analysis/error-patterns.js.map +1 -0
  48. package/dist/lib/analysis/index.d.ts +23 -0
  49. package/dist/lib/analysis/index.d.ts.map +1 -0
  50. package/dist/lib/analysis/index.js +144 -0
  51. package/dist/lib/analysis/index.js.map +1 -0
  52. package/dist/lib/analysis/performance-trends.d.ts +19 -0
  53. package/dist/lib/analysis/performance-trends.d.ts.map +1 -0
  54. package/dist/lib/analysis/performance-trends.js +121 -0
  55. package/dist/lib/analysis/performance-trends.js.map +1 -0
  56. package/dist/lib/analysis/tool-sequences.d.ts +19 -0
  57. package/dist/lib/analysis/tool-sequences.d.ts.map +1 -0
  58. package/dist/lib/analysis/tool-sequences.js +148 -0
  59. package/dist/lib/analysis/tool-sequences.js.map +1 -0
  60. package/dist/lib/context/index.d.ts +5 -0
  61. package/dist/lib/context/index.d.ts.map +1 -0
  62. package/dist/lib/context/index.js +5 -0
  63. package/dist/lib/context/index.js.map +1 -0
  64. package/dist/lib/context/retrieval.d.ts +60 -0
  65. package/dist/lib/context/retrieval.d.ts.map +1 -0
  66. package/dist/lib/context/retrieval.js +233 -0
  67. package/dist/lib/context/retrieval.js.map +1 -0
  68. package/dist/lib/embeddings/index.d.ts +31 -0
  69. package/dist/lib/embeddings/index.d.ts.map +1 -0
  70. package/dist/lib/embeddings/index.js +31 -0
  71. package/dist/lib/embeddings/index.js.map +1 -0
  72. package/dist/lib/embeddings/local.d.ts +15 -0
  73. package/dist/lib/embeddings/local.d.ts.map +1 -0
  74. package/dist/lib/embeddings/local.js +65 -0
  75. package/dist/lib/embeddings/local.js.map +1 -0
  76. package/dist/lib/embeddings/math.d.ts +13 -0
  77. package/dist/lib/embeddings/math.d.ts.map +1 -0
  78. package/dist/lib/embeddings/math.js +35 -0
  79. package/dist/lib/embeddings/math.js.map +1 -0
  80. package/dist/lib/embeddings/openai.d.ts +15 -0
  81. package/dist/lib/embeddings/openai.d.ts.map +1 -0
  82. package/dist/lib/embeddings/openai.js +58 -0
  83. package/dist/lib/embeddings/openai.js.map +1 -0
  84. package/dist/lib/embeddings/summarizer.d.ts +26 -0
  85. package/dist/lib/embeddings/summarizer.d.ts.map +1 -0
  86. package/dist/lib/embeddings/summarizer.js +181 -0
  87. package/dist/lib/embeddings/summarizer.js.map +1 -0
  88. package/dist/lib/embeddings/types.d.ts +17 -0
  89. package/dist/lib/embeddings/types.d.ts.map +1 -0
  90. package/dist/lib/embeddings/types.js +5 -0
  91. package/dist/lib/embeddings/types.js.map +1 -0
  92. package/dist/lib/embeddings/worker.d.ts +56 -0
  93. package/dist/lib/embeddings/worker.d.ts.map +1 -0
  94. package/dist/lib/embeddings/worker.js +109 -0
  95. package/dist/lib/embeddings/worker.js.map +1 -0
  96. package/dist/lib/health/computer.d.ts +28 -0
  97. package/dist/lib/health/computer.d.ts.map +1 -0
  98. package/dist/lib/health/computer.js +270 -0
  99. package/dist/lib/health/computer.js.map +1 -0
  100. package/dist/lib/optimization/classifier.d.ts +34 -0
  101. package/dist/lib/optimization/classifier.d.ts.map +1 -0
  102. package/dist/lib/optimization/classifier.js +108 -0
  103. package/dist/lib/optimization/classifier.js.map +1 -0
  104. package/dist/lib/optimization/engine.d.ts +24 -0
  105. package/dist/lib/optimization/engine.d.ts.map +1 -0
  106. package/dist/lib/optimization/engine.js +202 -0
  107. package/dist/lib/optimization/engine.js.map +1 -0
  108. package/dist/lib/optimization/index.d.ts +10 -0
  109. package/dist/lib/optimization/index.d.ts.map +1 -0
  110. package/dist/lib/optimization/index.js +9 -0
  111. package/dist/lib/optimization/index.js.map +1 -0
  112. package/dist/lib/sse.d.ts +1 -0
  113. package/dist/lib/sse.d.ts.map +1 -1
  114. package/dist/lib/sse.js +8 -1
  115. package/dist/lib/sse.js.map +1 -1
  116. package/dist/middleware/auth.d.ts +1 -0
  117. package/dist/middleware/auth.d.ts.map +1 -1
  118. package/dist/middleware/auth.js +2 -1
  119. package/dist/middleware/auth.js.map +1 -1
  120. package/dist/routes/agents.d.ts.map +1 -1
  121. package/dist/routes/agents.js +6 -3
  122. package/dist/routes/agents.js.map +1 -1
  123. package/dist/routes/alerts.d.ts.map +1 -1
  124. package/dist/routes/alerts.js +15 -7
  125. package/dist/routes/alerts.js.map +1 -1
  126. package/dist/routes/analytics.d.ts.map +1 -1
  127. package/dist/routes/analytics.js +16 -2
  128. package/dist/routes/analytics.js.map +1 -1
  129. package/dist/routes/api-keys.d.ts.map +1 -1
  130. package/dist/routes/api-keys.js +30 -5
  131. package/dist/routes/api-keys.js.map +1 -1
  132. package/dist/routes/context.d.ts +23 -0
  133. package/dist/routes/context.d.ts.map +1 -0
  134. package/dist/routes/context.js +52 -0
  135. package/dist/routes/context.js.map +1 -0
  136. package/dist/routes/events.d.ts +6 -1
  137. package/dist/routes/events.d.ts.map +1 -1
  138. package/dist/routes/events.js +61 -6
  139. package/dist/routes/events.js.map +1 -1
  140. package/dist/routes/health.d.ts +21 -0
  141. package/dist/routes/health.d.ts.map +1 -0
  142. package/dist/routes/health.js +142 -0
  143. package/dist/routes/health.js.map +1 -0
  144. package/dist/routes/ingest.d.ts +6 -0
  145. package/dist/routes/ingest.d.ts.map +1 -1
  146. package/dist/routes/ingest.js +23 -4
  147. package/dist/routes/ingest.js.map +1 -1
  148. package/dist/routes/lessons.d.ts +19 -0
  149. package/dist/routes/lessons.d.ts.map +1 -0
  150. package/dist/routes/lessons.js +164 -0
  151. package/dist/routes/lessons.js.map +1 -0
  152. package/dist/routes/optimize.d.ts +15 -0
  153. package/dist/routes/optimize.d.ts.map +1 -0
  154. package/dist/routes/optimize.js +55 -0
  155. package/dist/routes/optimize.js.map +1 -0
  156. package/dist/routes/recall.d.ts +22 -0
  157. package/dist/routes/recall.d.ts.map +1 -0
  158. package/dist/routes/recall.js +91 -0
  159. package/dist/routes/recall.js.map +1 -0
  160. package/dist/routes/reflect.d.ts +15 -0
  161. package/dist/routes/reflect.d.ts.map +1 -0
  162. package/dist/routes/reflect.js +54 -0
  163. package/dist/routes/reflect.js.map +1 -0
  164. package/dist/routes/sessions.d.ts.map +1 -1
  165. package/dist/routes/sessions.js +8 -6
  166. package/dist/routes/sessions.js.map +1 -1
  167. package/dist/routes/stats.d.ts.map +1 -1
  168. package/dist/routes/stats.js +3 -1
  169. package/dist/routes/stats.js.map +1 -1
  170. package/dist/routes/stream.d.ts +9 -2
  171. package/dist/routes/stream.d.ts.map +1 -1
  172. package/dist/routes/stream.js +55 -2
  173. package/dist/routes/stream.js.map +1 -1
  174. package/dist/routes/tenant-helper.d.ts +20 -0
  175. package/dist/routes/tenant-helper.d.ts.map +1 -0
  176. package/dist/routes/tenant-helper.js +35 -0
  177. package/dist/routes/tenant-helper.js.map +1 -0
  178. package/package.json +11 -11
  179. package/LICENSE +0 -21
  180. package/dist/__tests__/agents-stats.test.d.ts +0 -5
  181. package/dist/__tests__/agents-stats.test.d.ts.map +0 -1
  182. package/dist/__tests__/agents-stats.test.js +0 -134
  183. package/dist/__tests__/agents-stats.test.js.map +0 -1
  184. package/dist/__tests__/alerts.test.d.ts +0 -5
  185. package/dist/__tests__/alerts.test.d.ts.map +0 -1
  186. package/dist/__tests__/alerts.test.js +0 -245
  187. package/dist/__tests__/alerts.test.js.map +0 -1
  188. package/dist/__tests__/analytics.test.d.ts +0 -5
  189. package/dist/__tests__/analytics.test.d.ts.map +0 -1
  190. package/dist/__tests__/analytics.test.js +0 -218
  191. package/dist/__tests__/analytics.test.js.map +0 -1
  192. package/dist/__tests__/api-keys.test.d.ts +0 -5
  193. package/dist/__tests__/api-keys.test.d.ts.map +0 -1
  194. package/dist/__tests__/api-keys.test.js +0 -118
  195. package/dist/__tests__/api-keys.test.js.map +0 -1
  196. package/dist/__tests__/auth-no-db.test.d.ts +0 -2
  197. package/dist/__tests__/auth-no-db.test.d.ts.map +0 -1
  198. package/dist/__tests__/auth-no-db.test.js +0 -43
  199. package/dist/__tests__/auth-no-db.test.js.map +0 -1
  200. package/dist/__tests__/auth.test.d.ts +0 -5
  201. package/dist/__tests__/auth.test.d.ts.map +0 -1
  202. package/dist/__tests__/auth.test.js +0 -86
  203. package/dist/__tests__/auth.test.js.map +0 -1
  204. package/dist/__tests__/config.test.d.ts +0 -2
  205. package/dist/__tests__/config.test.d.ts.map +0 -1
  206. package/dist/__tests__/config.test.js +0 -37
  207. package/dist/__tests__/config.test.js.map +0 -1
  208. package/dist/__tests__/events-ingest.test.d.ts +0 -5
  209. package/dist/__tests__/events-ingest.test.d.ts.map +0 -1
  210. package/dist/__tests__/events-ingest.test.js +0 -248
  211. package/dist/__tests__/events-ingest.test.js.map +0 -1
  212. package/dist/__tests__/events-query.test.d.ts +0 -5
  213. package/dist/__tests__/events-query.test.d.ts.map +0 -1
  214. package/dist/__tests__/events-query.test.js +0 -205
  215. package/dist/__tests__/events-query.test.js.map +0 -1
  216. package/dist/__tests__/health.test.d.ts +0 -5
  217. package/dist/__tests__/health.test.d.ts.map +0 -1
  218. package/dist/__tests__/health.test.js +0 -40
  219. package/dist/__tests__/health.test.js.map +0 -1
  220. package/dist/__tests__/ingest.test.d.ts +0 -8
  221. package/dist/__tests__/ingest.test.d.ts.map +0 -1
  222. package/dist/__tests__/ingest.test.js +0 -469
  223. package/dist/__tests__/ingest.test.js.map +0 -1
  224. package/dist/__tests__/llm-tracking.test.d.ts +0 -10
  225. package/dist/__tests__/llm-tracking.test.d.ts.map +0 -1
  226. package/dist/__tests__/llm-tracking.test.js +0 -602
  227. package/dist/__tests__/llm-tracking.test.js.map +0 -1
  228. package/dist/__tests__/sessions.test.d.ts +0 -5
  229. package/dist/__tests__/sessions.test.d.ts.map +0 -1
  230. package/dist/__tests__/sessions.test.js +0 -176
  231. package/dist/__tests__/sessions.test.js.map +0 -1
  232. package/dist/__tests__/stream.test.d.ts +0 -5
  233. package/dist/__tests__/stream.test.d.ts.map +0 -1
  234. package/dist/__tests__/stream.test.js +0 -352
  235. package/dist/__tests__/stream.test.js.map +0 -1
  236. package/dist/__tests__/test-helpers.d.ts +0 -24
  237. package/dist/__tests__/test-helpers.d.ts.map +0 -1
  238. package/dist/__tests__/test-helpers.js +0 -45
  239. package/dist/__tests__/test-helpers.js.map +0 -1
  240. package/dist/db/__tests__/init.test.d.ts +0 -2
  241. package/dist/db/__tests__/init.test.d.ts.map +0 -1
  242. package/dist/db/__tests__/init.test.js +0 -73
  243. package/dist/db/__tests__/init.test.js.map +0 -1
  244. package/dist/db/__tests__/sqlite-store-read.test.d.ts +0 -2
  245. package/dist/db/__tests__/sqlite-store-read.test.d.ts.map +0 -1
  246. package/dist/db/__tests__/sqlite-store-read.test.js +0 -749
  247. package/dist/db/__tests__/sqlite-store-read.test.js.map +0 -1
  248. package/dist/db/__tests__/sqlite-store-write.test.d.ts +0 -2
  249. package/dist/db/__tests__/sqlite-store-write.test.d.ts.map +0 -1
  250. package/dist/db/__tests__/sqlite-store-write.test.js +0 -418
  251. package/dist/db/__tests__/sqlite-store-write.test.js.map +0 -1
  252. package/dist/lib/__tests__/alert-engine.test.d.ts +0 -5
  253. package/dist/lib/__tests__/alert-engine.test.d.ts.map +0 -1
  254. package/dist/lib/__tests__/alert-engine.test.js +0 -211
  255. package/dist/lib/__tests__/alert-engine.test.js.map +0 -1
  256. package/dist/lib/__tests__/retention.test.d.ts +0 -2
  257. package/dist/lib/__tests__/retention.test.d.ts.map +0 -1
  258. package/dist/lib/__tests__/retention.test.js +0 -238
  259. package/dist/lib/__tests__/retention.test.js.map +0 -1
@@ -34,6 +34,16 @@ export class SqliteEventStore {
34
34
  constructor(db) {
35
35
  this.db = db;
36
36
  }
37
+ /**
38
+ * Defense-in-depth: warn when a query method is called without tenantId.
39
+ * This helps catch missing tenant scoping in call chains.
40
+ */
41
+ warnIfNoTenant(method, tenantId) {
42
+ if (tenantId === undefined) {
43
+ console.warn(`[SqliteEventStore] ${method}() called without tenantId — query is unscoped. ` +
44
+ `Ensure tenant isolation is applied upstream (via TenantScopedStore).`);
45
+ }
46
+ }
37
47
  // ─── Events — Write ────────────────────────────────────────
38
48
  async insertEvents(eventList) {
39
49
  if (eventList.length === 0)
@@ -42,6 +52,7 @@ export class SqliteEventStore {
42
52
  this.db.transaction((tx) => {
43
53
  // CRITICAL 3: Validate hash chain before inserting
44
54
  const firstEvent = eventList[0];
55
+ const tenantId = firstEvent.tenantId ?? 'default';
45
56
  // Check if the first event already exists (idempotent re-insert)
46
57
  const existingFirst = tx
47
58
  .select({ id: events.id })
@@ -53,7 +64,7 @@ export class SqliteEventStore {
53
64
  const lastStoredEvent = tx
54
65
  .select({ hash: events.hash })
55
66
  .from(events)
56
- .where(eq(events.sessionId, firstEvent.sessionId))
67
+ .where(and(eq(events.sessionId, firstEvent.sessionId), eq(events.tenantId, tenantId)))
57
68
  .orderBy(desc(events.timestamp), desc(events.id))
58
69
  .limit(1)
59
70
  .get();
@@ -103,17 +114,18 @@ export class SqliteEventStore {
103
114
  metadata: JSON.stringify(event.metadata),
104
115
  prevHash: event.prevHash,
105
116
  hash: event.hash,
117
+ tenantId,
106
118
  })
107
119
  .onConflictDoNothing({ target: events.id })
108
120
  .run();
109
121
  // Handle session management
110
- this._handleSessionUpdate(tx, event);
122
+ this._handleSessionUpdate(tx, event, tenantId);
111
123
  // Handle agent auto-creation
112
- this._handleAgentUpsert(tx, event);
124
+ this._handleAgentUpsert(tx, event, tenantId);
113
125
  }
114
126
  });
115
127
  }
116
- _handleSessionUpdate(tx, event) {
128
+ _handleSessionUpdate(tx, event, tenantId) {
117
129
  if (event.eventType === 'session_started') {
118
130
  // Create new session
119
131
  const payload = event.payload;
@@ -132,9 +144,10 @@ export class SqliteEventStore {
132
144
  errorCount: 0,
133
145
  totalCostUsd: 0,
134
146
  tags: JSON.stringify(tags),
147
+ tenantId,
135
148
  })
136
149
  .onConflictDoUpdate({
137
- target: sessions.id,
150
+ target: [sessions.id, sessions.tenantId],
138
151
  set: {
139
152
  agentName: agentName ?? sql `coalesce(${sessions.agentName}, NULL)`,
140
153
  status: 'active',
@@ -157,8 +170,9 @@ export class SqliteEventStore {
157
170
  errorCount: 0,
158
171
  totalCostUsd: 0,
159
172
  tags: '[]',
173
+ tenantId,
160
174
  })
161
- .onConflictDoNothing({ target: sessions.id })
175
+ .onConflictDoNothing({ target: [sessions.id, sessions.tenantId] })
162
176
  .run();
163
177
  // Build incremental updates
164
178
  const isToolCall = event.eventType === 'tool_call';
@@ -186,7 +200,7 @@ export class SqliteEventStore {
186
200
  ? sql `${sessions.errorCount} + 1`
187
201
  : sessions.errorCount,
188
202
  })
189
- .where(eq(sessions.id, event.sessionId))
203
+ .where(and(eq(sessions.id, event.sessionId), eq(sessions.tenantId, tenantId)))
190
204
  .run();
191
205
  return;
192
206
  }
@@ -215,11 +229,12 @@ export class SqliteEventStore {
215
229
  ? sql `${sessions.totalOutputTokens} + ${llmOutputTokens}`
216
230
  : sessions.totalOutputTokens,
217
231
  })
218
- .where(eq(sessions.id, event.sessionId))
232
+ .where(and(eq(sessions.id, event.sessionId), eq(sessions.tenantId, tenantId)))
219
233
  .run();
220
234
  }
221
- _handleAgentUpsert(tx, event) {
235
+ _handleAgentUpsert(tx, event, tenantId) {
222
236
  // CRITICAL 4: Use INSERT ... ON CONFLICT DO UPDATE for agents
237
+ // Agents are scoped per tenant — use composite key (id + tenant_id)
223
238
  const payload = event.payload;
224
239
  const agentName = payload.agentName ?? event.agentId;
225
240
  tx.insert(agents)
@@ -229,9 +244,10 @@ export class SqliteEventStore {
229
244
  firstSeenAt: event.timestamp,
230
245
  lastSeenAt: event.timestamp,
231
246
  sessionCount: event.eventType === 'session_started' ? 1 : 0,
247
+ tenantId,
232
248
  })
233
249
  .onConflictDoUpdate({
234
- target: agents.id,
250
+ target: [agents.id, agents.tenantId],
235
251
  set: {
236
252
  lastSeenAt: event.timestamp,
237
253
  sessionCount: event.eventType === 'session_started'
@@ -243,10 +259,11 @@ export class SqliteEventStore {
243
259
  }
244
260
  // ─── Sessions — Write ──────────────────────────────────────
245
261
  async upsertSession(session) {
262
+ const tenantId = session.tenantId ?? 'default';
246
263
  const existing = this.db
247
264
  .select()
248
265
  .from(sessions)
249
- .where(eq(sessions.id, session.id))
266
+ .where(and(eq(sessions.id, session.id), eq(sessions.tenantId, tenantId)))
250
267
  .get();
251
268
  if (existing) {
252
269
  const updates = {};
@@ -280,7 +297,7 @@ export class SqliteEventStore {
280
297
  this.db
281
298
  .update(sessions)
282
299
  .set(updates)
283
- .where(eq(sessions.id, session.id))
300
+ .where(and(eq(sessions.id, session.id), eq(sessions.tenantId, tenantId)))
284
301
  .run();
285
302
  }
286
303
  }
@@ -302,16 +319,18 @@ export class SqliteEventStore {
302
319
  totalInputTokens: session.totalInputTokens ?? 0,
303
320
  totalOutputTokens: session.totalOutputTokens ?? 0,
304
321
  tags: JSON.stringify(session.tags ?? []),
322
+ tenantId,
305
323
  })
306
324
  .run();
307
325
  }
308
326
  }
309
327
  // ─── Agents — Write ────────────────────────────────────────
310
328
  async upsertAgent(agent) {
329
+ const tenantId = agent.tenantId ?? 'default';
311
330
  const existing = this.db
312
331
  .select()
313
332
  .from(agents)
314
- .where(eq(agents.id, agent.id))
333
+ .where(and(eq(agents.id, agent.id), eq(agents.tenantId, tenantId)))
315
334
  .get();
316
335
  if (existing) {
317
336
  const updates = {};
@@ -327,7 +346,7 @@ export class SqliteEventStore {
327
346
  this.db
328
347
  .update(agents)
329
348
  .set(updates)
330
- .where(eq(agents.id, agent.id))
349
+ .where(and(eq(agents.id, agent.id), eq(agents.tenantId, tenantId)))
331
350
  .run();
332
351
  }
333
352
  }
@@ -342,6 +361,7 @@ export class SqliteEventStore {
342
361
  firstSeenAt: agent.firstSeenAt ?? now,
343
362
  lastSeenAt: agent.lastSeenAt ?? now,
344
363
  sessionCount: agent.sessionCount ?? 0,
364
+ tenantId,
345
365
  })
346
366
  .run();
347
367
  }
@@ -367,28 +387,38 @@ export class SqliteEventStore {
367
387
  hasMore: offset + rows.length < total,
368
388
  };
369
389
  }
370
- async getEvent(id) {
390
+ async getEvent(id, tenantId) {
391
+ this.warnIfNoTenant('getEvent', tenantId);
392
+ const conditions = [eq(events.id, id)];
393
+ if (tenantId)
394
+ conditions.push(eq(events.tenantId, tenantId));
371
395
  const row = this.db
372
396
  .select()
373
397
  .from(events)
374
- .where(eq(events.id, id))
398
+ .where(and(...conditions))
375
399
  .get();
376
400
  return row ? this._mapEventRow(row) : null;
377
401
  }
378
- async getSessionTimeline(sessionId) {
402
+ async getSessionTimeline(sessionId, tenantId) {
403
+ const conditions = [eq(events.sessionId, sessionId)];
404
+ if (tenantId)
405
+ conditions.push(eq(events.tenantId, tenantId));
379
406
  const rows = this.db
380
407
  .select()
381
408
  .from(events)
382
- .where(eq(events.sessionId, sessionId))
409
+ .where(and(...conditions))
383
410
  .orderBy(asc(events.timestamp))
384
411
  .all();
385
412
  return rows.map(this._mapEventRow);
386
413
  }
387
- async getLastEventHash(sessionId) {
414
+ async getLastEventHash(sessionId, tenantId) {
415
+ const conditions = [eq(events.sessionId, sessionId)];
416
+ if (tenantId)
417
+ conditions.push(eq(events.tenantId, tenantId));
388
418
  const row = this.db
389
419
  .select({ hash: events.hash })
390
420
  .from(events)
391
- .where(eq(events.sessionId, sessionId))
421
+ .where(and(...conditions))
392
422
  .orderBy(desc(events.timestamp), desc(events.id))
393
423
  .limit(1)
394
424
  .get();
@@ -405,6 +435,7 @@ export class SqliteEventStore {
405
435
  }
406
436
  // ─── Sessions — Read ───────────────────────────────────────
407
437
  async querySessions(query) {
438
+ this.warnIfNoTenant('querySessions', query.tenantId);
408
439
  const limit = Math.min(query.limit ?? 50, 500);
409
440
  const offset = query.offset ?? 0;
410
441
  const conditions = this._buildSessionConditions(query);
@@ -426,33 +457,43 @@ export class SqliteEventStore {
426
457
  total: totalResult?.count ?? 0,
427
458
  };
428
459
  }
429
- async getSession(id) {
460
+ async getSession(id, tenantId) {
461
+ const conditions = [eq(sessions.id, id)];
462
+ if (tenantId)
463
+ conditions.push(eq(sessions.tenantId, tenantId));
430
464
  const row = this.db
431
465
  .select()
432
466
  .from(sessions)
433
- .where(eq(sessions.id, id))
467
+ .where(and(...conditions))
434
468
  .get();
435
469
  return row ? this._mapSessionRow(row) : null;
436
470
  }
437
471
  // ─── Agents — Read ─────────────────────────────────────────
438
- async listAgents() {
472
+ async listAgents(tenantId) {
473
+ this.warnIfNoTenant('listAgents', tenantId);
474
+ const conditions = tenantId ? [eq(agents.tenantId, tenantId)] : [];
439
475
  const rows = this.db
440
476
  .select()
441
477
  .from(agents)
478
+ .where(conditions.length > 0 ? and(...conditions) : undefined)
442
479
  .orderBy(desc(agents.lastSeenAt))
443
480
  .all();
444
481
  return rows.map(this._mapAgentRow);
445
482
  }
446
- async getAgent(id) {
483
+ async getAgent(id, tenantId) {
484
+ const conditions = [eq(agents.id, id)];
485
+ if (tenantId)
486
+ conditions.push(eq(agents.tenantId, tenantId));
447
487
  const row = this.db
448
488
  .select()
449
489
  .from(agents)
450
- .where(eq(agents.id, id))
490
+ .where(and(...conditions))
451
491
  .get();
452
492
  return row ? this._mapAgentRow(row) : null;
453
493
  }
454
494
  // ─── Analytics ─────────────────────────────────────────────
455
495
  async getAnalytics(params) {
496
+ this.warnIfNoTenant('getAnalytics', params.tenantId);
456
497
  // HIGH 5: Build the time-bucket format string for SQLite strftime
457
498
  const formatStr = params.granularity === 'hour'
458
499
  ? '%Y-%m-%dT%H:00:00Z'
@@ -482,6 +523,7 @@ export class SqliteEventStore {
482
523
  WHERE timestamp >= ${params.from}
483
524
  AND timestamp <= ${params.to}
484
525
  ${params.agentId ? sql `AND agent_id = ${params.agentId}` : sql ``}
526
+ ${params.tenantId ? sql `AND tenant_id = ${params.tenantId}` : sql ``}
485
527
  GROUP BY bucket
486
528
  ORDER BY bucket ASC
487
529
  `);
@@ -499,6 +541,7 @@ export class SqliteEventStore {
499
541
  WHERE timestamp >= ${params.from}
500
542
  AND timestamp <= ${params.to}
501
543
  ${params.agentId ? sql `AND agent_id = ${params.agentId}` : sql ``}
544
+ ${params.tenantId ? sql `AND tenant_id = ${params.tenantId}` : sql ``}
502
545
  `);
503
546
  return {
504
547
  buckets: bucketRows.map((row) => ({
@@ -536,11 +579,12 @@ export class SqliteEventStore {
536
579
  notifyChannels: JSON.stringify(rule.notifyChannels),
537
580
  createdAt: rule.createdAt,
538
581
  updatedAt: rule.updatedAt,
582
+ tenantId: rule.tenantId ?? 'default',
539
583
  })
540
584
  .run();
541
585
  }
542
586
  // HIGH 8: Check affected row count and throw NotFoundError when zero
543
- async updateAlertRule(id, updates) {
587
+ async updateAlertRule(id, updates, tenantId) {
544
588
  const setValues = {};
545
589
  if (updates.name !== undefined)
546
590
  setValues.name = updates.name;
@@ -558,12 +602,15 @@ export class SqliteEventStore {
558
602
  setValues.notifyChannels = JSON.stringify(updates.notifyChannels);
559
603
  if (updates.updatedAt !== undefined)
560
604
  setValues.updatedAt = updates.updatedAt;
605
+ const whereConditions = [eq(alertRules.id, id)];
606
+ if (tenantId)
607
+ whereConditions.push(eq(alertRules.tenantId, tenantId));
561
608
  if (Object.keys(setValues).length === 0) {
562
609
  // Even with no actual updates, verify the rule exists
563
610
  const existing = this.db
564
611
  .select({ id: alertRules.id })
565
612
  .from(alertRules)
566
- .where(eq(alertRules.id, id))
613
+ .where(and(...whereConditions))
567
614
  .get();
568
615
  if (!existing) {
569
616
  throw new NotFoundError(`Alert rule not found: ${id}`);
@@ -573,27 +620,39 @@ export class SqliteEventStore {
573
620
  const result = this.db
574
621
  .update(alertRules)
575
622
  .set(setValues)
576
- .where(eq(alertRules.id, id))
623
+ .where(and(...whereConditions))
577
624
  .run();
578
625
  if (result.changes === 0) {
579
626
  throw new NotFoundError(`Alert rule not found: ${id}`);
580
627
  }
581
628
  }
582
- async deleteAlertRule(id) {
583
- const result = this.db.delete(alertRules).where(eq(alertRules.id, id)).run();
629
+ async deleteAlertRule(id, tenantId) {
630
+ const whereConditions = [eq(alertRules.id, id)];
631
+ if (tenantId)
632
+ whereConditions.push(eq(alertRules.tenantId, tenantId));
633
+ const result = this.db.delete(alertRules).where(and(...whereConditions)).run();
584
634
  if (result.changes === 0) {
585
635
  throw new NotFoundError(`Alert rule not found: ${id}`);
586
636
  }
587
637
  }
588
- async listAlertRules() {
589
- const rows = this.db.select().from(alertRules).all();
638
+ async listAlertRules(tenantId) {
639
+ this.warnIfNoTenant('listAlertRules', tenantId);
640
+ const conditions = tenantId ? [eq(alertRules.tenantId, tenantId)] : [];
641
+ const rows = this.db
642
+ .select()
643
+ .from(alertRules)
644
+ .where(conditions.length > 0 ? and(...conditions) : undefined)
645
+ .all();
590
646
  return rows.map(this._mapAlertRuleRow);
591
647
  }
592
- async getAlertRule(id) {
648
+ async getAlertRule(id, tenantId) {
649
+ const conditions = [eq(alertRules.id, id)];
650
+ if (tenantId)
651
+ conditions.push(eq(alertRules.tenantId, tenantId));
593
652
  const row = this.db
594
653
  .select()
595
654
  .from(alertRules)
596
- .where(eq(alertRules.id, id))
655
+ .where(and(...conditions))
597
656
  .get();
598
657
  return row ? this._mapAlertRuleRow(row) : null;
599
658
  }
@@ -609,6 +668,7 @@ export class SqliteEventStore {
609
668
  currentValue: entry.currentValue,
610
669
  threshold: entry.threshold,
611
670
  message: entry.message,
671
+ tenantId: entry.tenantId ?? 'default',
612
672
  })
613
673
  .run();
614
674
  }
@@ -619,6 +679,9 @@ export class SqliteEventStore {
619
679
  if (opts?.ruleId) {
620
680
  conditions.push(eq(alertHistory.ruleId, opts.ruleId));
621
681
  }
682
+ if (opts?.tenantId) {
683
+ conditions.push(eq(alertHistory.tenantId, opts.tenantId));
684
+ }
622
685
  const rows = this.db
623
686
  .select()
624
687
  .from(alertHistory)
@@ -641,54 +704,82 @@ export class SqliteEventStore {
641
704
  currentValue: row.currentValue,
642
705
  threshold: row.threshold,
643
706
  message: row.message,
707
+ tenantId: row.tenantId,
644
708
  })),
645
709
  total: totalResult?.count ?? 0,
646
710
  };
647
711
  }
648
712
  // ─── Maintenance ───────────────────────────────────────────
649
- async applyRetention(olderThan) {
713
+ async applyRetention(olderThan, tenantId) {
714
+ // Build conditions — always filter by timestamp, optionally by tenant
715
+ const countConditions = [lte(events.timestamp, olderThan)];
716
+ if (tenantId)
717
+ countConditions.push(eq(events.tenantId, tenantId));
650
718
  // Count events to be deleted
651
719
  const countResult = this.db
652
720
  .select({ count: drizzleCount() })
653
721
  .from(events)
654
- .where(lte(events.timestamp, olderThan))
722
+ .where(and(...countConditions))
655
723
  .get();
656
724
  const deletedCount = countResult?.count ?? 0;
657
725
  if (deletedCount === 0)
658
726
  return { deletedCount: 0 };
659
727
  this.db.transaction((tx) => {
660
- // Delete old events
661
- tx.delete(events).where(lte(events.timestamp, olderThan)).run();
662
- // Clean up sessions with no remaining events
663
- tx.run(sql `
664
- DELETE FROM sessions
665
- WHERE id NOT IN (SELECT DISTINCT session_id FROM events)
666
- `);
728
+ if (tenantId) {
729
+ // Tenant-scoped retention: only delete this tenant's old events
730
+ tx.delete(events)
731
+ .where(and(lte(events.timestamp, olderThan), eq(events.tenantId, tenantId)))
732
+ .run();
733
+ // Clean up this tenant's sessions that have no remaining events
734
+ tx.run(sql `
735
+ DELETE FROM sessions
736
+ WHERE tenant_id = ${tenantId}
737
+ AND id NOT IN (
738
+ SELECT DISTINCT session_id FROM events WHERE tenant_id = ${tenantId}
739
+ )
740
+ `);
741
+ }
742
+ else {
743
+ // Global retention (system-level, not exposed through TenantScopedStore)
744
+ tx.delete(events).where(lte(events.timestamp, olderThan)).run();
745
+ tx.run(sql `
746
+ DELETE FROM sessions
747
+ WHERE id NOT IN (SELECT DISTINCT session_id FROM events)
748
+ `);
749
+ }
667
750
  });
668
751
  return { deletedCount };
669
752
  }
670
- async getStats() {
753
+ async getStats(tenantId) {
754
+ const eventConditions = tenantId ? [eq(events.tenantId, tenantId)] : [];
755
+ const sessionConditions = tenantId ? [eq(sessions.tenantId, tenantId)] : [];
756
+ const agentConditions = tenantId ? [eq(agents.tenantId, tenantId)] : [];
671
757
  const eventCount = this.db
672
758
  .select({ count: drizzleCount() })
673
759
  .from(events)
760
+ .where(eventConditions.length > 0 ? and(...eventConditions) : undefined)
674
761
  .get()?.count ?? 0;
675
762
  const sessionCount = this.db
676
763
  .select({ count: drizzleCount() })
677
764
  .from(sessions)
765
+ .where(sessionConditions.length > 0 ? and(...sessionConditions) : undefined)
678
766
  .get()?.count ?? 0;
679
767
  const agentCount = this.db
680
768
  .select({ count: drizzleCount() })
681
769
  .from(agents)
770
+ .where(agentConditions.length > 0 ? and(...agentConditions) : undefined)
682
771
  .get()?.count ?? 0;
683
772
  const oldest = this.db
684
773
  .select({ timestamp: events.timestamp })
685
774
  .from(events)
775
+ .where(eventConditions.length > 0 ? and(...eventConditions) : undefined)
686
776
  .orderBy(asc(events.timestamp))
687
777
  .limit(1)
688
778
  .get();
689
779
  const newest = this.db
690
780
  .select({ timestamp: events.timestamp })
691
781
  .from(events)
782
+ .where(eventConditions.length > 0 ? and(...eventConditions) : undefined)
692
783
  .orderBy(desc(events.timestamp))
693
784
  .limit(1)
694
785
  .get();
@@ -709,6 +800,9 @@ export class SqliteEventStore {
709
800
  // ─── Private Helpers ───────────────────────────────────────
710
801
  _buildEventConditions(query) {
711
802
  const conditions = [];
803
+ if (query.tenantId) {
804
+ conditions.push(eq(events.tenantId, query.tenantId));
805
+ }
712
806
  if (query.sessionId) {
713
807
  conditions.push(eq(events.sessionId, query.sessionId));
714
808
  }
@@ -750,6 +844,9 @@ export class SqliteEventStore {
750
844
  // HIGH 4: Use json_each() for exact tag matching with OR semantics
751
845
  _buildSessionConditions(query) {
752
846
  const conditions = [];
847
+ if (query.tenantId) {
848
+ conditions.push(eq(sessions.tenantId, query.tenantId));
849
+ }
753
850
  if (query.agentId) {
754
851
  conditions.push(eq(sessions.agentId, query.agentId));
755
852
  }
@@ -796,6 +893,7 @@ export class SqliteEventStore {
796
893
  metadata: safeJsonParse(row.metadata, {}),
797
894
  prevHash: row.prevHash,
798
895
  hash: row.hash,
896
+ tenantId: row.tenantId,
799
897
  };
800
898
  }
801
899
  _mapSessionRow(row) {
@@ -814,6 +912,7 @@ export class SqliteEventStore {
814
912
  totalInputTokens: row.totalInputTokens,
815
913
  totalOutputTokens: row.totalOutputTokens,
816
914
  tags: safeJsonParse(row.tags, []),
915
+ tenantId: row.tenantId,
817
916
  };
818
917
  }
819
918
  _mapAgentRow(row) {
@@ -824,6 +923,7 @@ export class SqliteEventStore {
824
923
  firstSeenAt: row.firstSeenAt,
825
924
  lastSeenAt: row.lastSeenAt,
826
925
  sessionCount: row.sessionCount,
926
+ tenantId: row.tenantId,
827
927
  };
828
928
  }
829
929
  _mapAlertRuleRow(row) {
@@ -838,6 +938,7 @@ export class SqliteEventStore {
838
938
  notifyChannels: safeJsonParse(row.notifyChannels, []),
839
939
  createdAt: row.createdAt,
840
940
  updatedAt: row.updatedAt,
941
+ tenantId: row.tenantId,
841
942
  };
842
943
  }
843
944
  }