@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.
- package/dist/db/embedding-store.d.ts +74 -0
- package/dist/db/embedding-store.d.ts.map +1 -0
- package/dist/db/embedding-store.js +177 -0
- package/dist/db/embedding-store.js.map +1 -0
- package/dist/db/health-snapshot-store.d.ts +33 -0
- package/dist/db/health-snapshot-store.d.ts.map +1 -0
- package/dist/db/health-snapshot-store.js +112 -0
- package/dist/db/health-snapshot-store.js.map +1 -0
- package/dist/db/lesson-store.d.ts +57 -0
- package/dist/db/lesson-store.d.ts.map +1 -0
- package/dist/db/lesson-store.js +217 -0
- package/dist/db/lesson-store.js.map +1 -0
- package/dist/db/migrate.d.ts.map +1 -1
- package/dist/db/migrate.js +256 -8
- package/dist/db/migrate.js.map +1 -1
- package/dist/db/schema.sqlite.d.ts +822 -47
- package/dist/db/schema.sqlite.d.ts.map +1 -1
- package/dist/db/schema.sqlite.js +79 -4
- package/dist/db/schema.sqlite.js.map +1 -1
- package/dist/db/session-summary-store.d.ts +45 -0
- package/dist/db/session-summary-store.d.ts.map +1 -0
- package/dist/db/session-summary-store.js +112 -0
- package/dist/db/session-summary-store.js.map +1 -0
- package/dist/db/sqlite-store.d.ts +19 -12
- package/dist/db/sqlite-store.d.ts.map +1 -1
- package/dist/db/sqlite-store.js +145 -44
- package/dist/db/sqlite-store.js.map +1 -1
- package/dist/db/tenant-scoped-store.d.ts +61 -0
- package/dist/db/tenant-scoped-store.d.ts.map +1 -0
- package/dist/db/tenant-scoped-store.js +94 -0
- package/dist/db/tenant-scoped-store.js.map +1 -0
- package/dist/index.d.ts +18 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +78 -5
- package/dist/index.js.map +1 -1
- package/dist/lib/alert-engine.d.ts +11 -0
- package/dist/lib/alert-engine.d.ts.map +1 -1
- package/dist/lib/alert-engine.js +65 -24
- package/dist/lib/alert-engine.js.map +1 -1
- package/dist/lib/analysis/cost-analysis.d.ts +20 -0
- package/dist/lib/analysis/cost-analysis.d.ts.map +1 -0
- package/dist/lib/analysis/cost-analysis.js +161 -0
- package/dist/lib/analysis/cost-analysis.js.map +1 -0
- package/dist/lib/analysis/error-patterns.d.ts +26 -0
- package/dist/lib/analysis/error-patterns.d.ts.map +1 -0
- package/dist/lib/analysis/error-patterns.js +158 -0
- package/dist/lib/analysis/error-patterns.js.map +1 -0
- package/dist/lib/analysis/index.d.ts +23 -0
- package/dist/lib/analysis/index.d.ts.map +1 -0
- package/dist/lib/analysis/index.js +144 -0
- package/dist/lib/analysis/index.js.map +1 -0
- package/dist/lib/analysis/performance-trends.d.ts +19 -0
- package/dist/lib/analysis/performance-trends.d.ts.map +1 -0
- package/dist/lib/analysis/performance-trends.js +121 -0
- package/dist/lib/analysis/performance-trends.js.map +1 -0
- package/dist/lib/analysis/tool-sequences.d.ts +19 -0
- package/dist/lib/analysis/tool-sequences.d.ts.map +1 -0
- package/dist/lib/analysis/tool-sequences.js +148 -0
- package/dist/lib/analysis/tool-sequences.js.map +1 -0
- package/dist/lib/context/index.d.ts +5 -0
- package/dist/lib/context/index.d.ts.map +1 -0
- package/dist/lib/context/index.js +5 -0
- package/dist/lib/context/index.js.map +1 -0
- package/dist/lib/context/retrieval.d.ts +60 -0
- package/dist/lib/context/retrieval.d.ts.map +1 -0
- package/dist/lib/context/retrieval.js +233 -0
- package/dist/lib/context/retrieval.js.map +1 -0
- package/dist/lib/embeddings/index.d.ts +31 -0
- package/dist/lib/embeddings/index.d.ts.map +1 -0
- package/dist/lib/embeddings/index.js +31 -0
- package/dist/lib/embeddings/index.js.map +1 -0
- package/dist/lib/embeddings/local.d.ts +15 -0
- package/dist/lib/embeddings/local.d.ts.map +1 -0
- package/dist/lib/embeddings/local.js +65 -0
- package/dist/lib/embeddings/local.js.map +1 -0
- package/dist/lib/embeddings/math.d.ts +13 -0
- package/dist/lib/embeddings/math.d.ts.map +1 -0
- package/dist/lib/embeddings/math.js +35 -0
- package/dist/lib/embeddings/math.js.map +1 -0
- package/dist/lib/embeddings/openai.d.ts +15 -0
- package/dist/lib/embeddings/openai.d.ts.map +1 -0
- package/dist/lib/embeddings/openai.js +58 -0
- package/dist/lib/embeddings/openai.js.map +1 -0
- package/dist/lib/embeddings/summarizer.d.ts +26 -0
- package/dist/lib/embeddings/summarizer.d.ts.map +1 -0
- package/dist/lib/embeddings/summarizer.js +181 -0
- package/dist/lib/embeddings/summarizer.js.map +1 -0
- package/dist/lib/embeddings/types.d.ts +17 -0
- package/dist/lib/embeddings/types.d.ts.map +1 -0
- package/dist/lib/embeddings/types.js +5 -0
- package/dist/lib/embeddings/types.js.map +1 -0
- package/dist/lib/embeddings/worker.d.ts +56 -0
- package/dist/lib/embeddings/worker.d.ts.map +1 -0
- package/dist/lib/embeddings/worker.js +109 -0
- package/dist/lib/embeddings/worker.js.map +1 -0
- package/dist/lib/health/computer.d.ts +28 -0
- package/dist/lib/health/computer.d.ts.map +1 -0
- package/dist/lib/health/computer.js +270 -0
- package/dist/lib/health/computer.js.map +1 -0
- package/dist/lib/optimization/classifier.d.ts +34 -0
- package/dist/lib/optimization/classifier.d.ts.map +1 -0
- package/dist/lib/optimization/classifier.js +108 -0
- package/dist/lib/optimization/classifier.js.map +1 -0
- package/dist/lib/optimization/engine.d.ts +24 -0
- package/dist/lib/optimization/engine.d.ts.map +1 -0
- package/dist/lib/optimization/engine.js +202 -0
- package/dist/lib/optimization/engine.js.map +1 -0
- package/dist/lib/optimization/index.d.ts +10 -0
- package/dist/lib/optimization/index.d.ts.map +1 -0
- package/dist/lib/optimization/index.js +9 -0
- package/dist/lib/optimization/index.js.map +1 -0
- package/dist/lib/sse.d.ts +1 -0
- package/dist/lib/sse.d.ts.map +1 -1
- package/dist/lib/sse.js +8 -1
- package/dist/lib/sse.js.map +1 -1
- package/dist/middleware/auth.d.ts +1 -0
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +2 -1
- package/dist/middleware/auth.js.map +1 -1
- package/dist/routes/agents.d.ts.map +1 -1
- package/dist/routes/agents.js +6 -3
- package/dist/routes/agents.js.map +1 -1
- package/dist/routes/alerts.d.ts.map +1 -1
- package/dist/routes/alerts.js +15 -7
- package/dist/routes/alerts.js.map +1 -1
- package/dist/routes/analytics.d.ts.map +1 -1
- package/dist/routes/analytics.js +16 -2
- package/dist/routes/analytics.js.map +1 -1
- package/dist/routes/api-keys.d.ts.map +1 -1
- package/dist/routes/api-keys.js +30 -5
- package/dist/routes/api-keys.js.map +1 -1
- package/dist/routes/context.d.ts +23 -0
- package/dist/routes/context.d.ts.map +1 -0
- package/dist/routes/context.js +52 -0
- package/dist/routes/context.js.map +1 -0
- package/dist/routes/events.d.ts +6 -1
- package/dist/routes/events.d.ts.map +1 -1
- package/dist/routes/events.js +61 -6
- package/dist/routes/events.js.map +1 -1
- package/dist/routes/health.d.ts +21 -0
- package/dist/routes/health.d.ts.map +1 -0
- package/dist/routes/health.js +142 -0
- package/dist/routes/health.js.map +1 -0
- package/dist/routes/ingest.d.ts +6 -0
- package/dist/routes/ingest.d.ts.map +1 -1
- package/dist/routes/ingest.js +23 -4
- package/dist/routes/ingest.js.map +1 -1
- package/dist/routes/lessons.d.ts +19 -0
- package/dist/routes/lessons.d.ts.map +1 -0
- package/dist/routes/lessons.js +164 -0
- package/dist/routes/lessons.js.map +1 -0
- package/dist/routes/optimize.d.ts +15 -0
- package/dist/routes/optimize.d.ts.map +1 -0
- package/dist/routes/optimize.js +55 -0
- package/dist/routes/optimize.js.map +1 -0
- package/dist/routes/recall.d.ts +22 -0
- package/dist/routes/recall.d.ts.map +1 -0
- package/dist/routes/recall.js +91 -0
- package/dist/routes/recall.js.map +1 -0
- package/dist/routes/reflect.d.ts +15 -0
- package/dist/routes/reflect.d.ts.map +1 -0
- package/dist/routes/reflect.js +54 -0
- package/dist/routes/reflect.js.map +1 -0
- package/dist/routes/sessions.d.ts.map +1 -1
- package/dist/routes/sessions.js +8 -6
- package/dist/routes/sessions.js.map +1 -1
- package/dist/routes/stats.d.ts.map +1 -1
- package/dist/routes/stats.js +3 -1
- package/dist/routes/stats.js.map +1 -1
- package/dist/routes/stream.d.ts +9 -2
- package/dist/routes/stream.d.ts.map +1 -1
- package/dist/routes/stream.js +55 -2
- package/dist/routes/stream.js.map +1 -1
- package/dist/routes/tenant-helper.d.ts +20 -0
- package/dist/routes/tenant-helper.d.ts.map +1 -0
- package/dist/routes/tenant-helper.js +35 -0
- package/dist/routes/tenant-helper.js.map +1 -0
- package/package.json +11 -11
- package/LICENSE +0 -21
- package/dist/__tests__/agents-stats.test.d.ts +0 -5
- package/dist/__tests__/agents-stats.test.d.ts.map +0 -1
- package/dist/__tests__/agents-stats.test.js +0 -134
- package/dist/__tests__/agents-stats.test.js.map +0 -1
- package/dist/__tests__/alerts.test.d.ts +0 -5
- package/dist/__tests__/alerts.test.d.ts.map +0 -1
- package/dist/__tests__/alerts.test.js +0 -245
- package/dist/__tests__/alerts.test.js.map +0 -1
- package/dist/__tests__/analytics.test.d.ts +0 -5
- package/dist/__tests__/analytics.test.d.ts.map +0 -1
- package/dist/__tests__/analytics.test.js +0 -218
- package/dist/__tests__/analytics.test.js.map +0 -1
- package/dist/__tests__/api-keys.test.d.ts +0 -5
- package/dist/__tests__/api-keys.test.d.ts.map +0 -1
- package/dist/__tests__/api-keys.test.js +0 -118
- package/dist/__tests__/api-keys.test.js.map +0 -1
- package/dist/__tests__/auth-no-db.test.d.ts +0 -2
- package/dist/__tests__/auth-no-db.test.d.ts.map +0 -1
- package/dist/__tests__/auth-no-db.test.js +0 -43
- package/dist/__tests__/auth-no-db.test.js.map +0 -1
- package/dist/__tests__/auth.test.d.ts +0 -5
- package/dist/__tests__/auth.test.d.ts.map +0 -1
- package/dist/__tests__/auth.test.js +0 -86
- package/dist/__tests__/auth.test.js.map +0 -1
- package/dist/__tests__/config.test.d.ts +0 -2
- package/dist/__tests__/config.test.d.ts.map +0 -1
- package/dist/__tests__/config.test.js +0 -37
- package/dist/__tests__/config.test.js.map +0 -1
- package/dist/__tests__/events-ingest.test.d.ts +0 -5
- package/dist/__tests__/events-ingest.test.d.ts.map +0 -1
- package/dist/__tests__/events-ingest.test.js +0 -248
- package/dist/__tests__/events-ingest.test.js.map +0 -1
- package/dist/__tests__/events-query.test.d.ts +0 -5
- package/dist/__tests__/events-query.test.d.ts.map +0 -1
- package/dist/__tests__/events-query.test.js +0 -205
- package/dist/__tests__/events-query.test.js.map +0 -1
- package/dist/__tests__/health.test.d.ts +0 -5
- package/dist/__tests__/health.test.d.ts.map +0 -1
- package/dist/__tests__/health.test.js +0 -40
- package/dist/__tests__/health.test.js.map +0 -1
- package/dist/__tests__/ingest.test.d.ts +0 -8
- package/dist/__tests__/ingest.test.d.ts.map +0 -1
- package/dist/__tests__/ingest.test.js +0 -469
- package/dist/__tests__/ingest.test.js.map +0 -1
- package/dist/__tests__/llm-tracking.test.d.ts +0 -10
- package/dist/__tests__/llm-tracking.test.d.ts.map +0 -1
- package/dist/__tests__/llm-tracking.test.js +0 -602
- package/dist/__tests__/llm-tracking.test.js.map +0 -1
- package/dist/__tests__/sessions.test.d.ts +0 -5
- package/dist/__tests__/sessions.test.d.ts.map +0 -1
- package/dist/__tests__/sessions.test.js +0 -176
- package/dist/__tests__/sessions.test.js.map +0 -1
- package/dist/__tests__/stream.test.d.ts +0 -5
- package/dist/__tests__/stream.test.d.ts.map +0 -1
- package/dist/__tests__/stream.test.js +0 -352
- package/dist/__tests__/stream.test.js.map +0 -1
- package/dist/__tests__/test-helpers.d.ts +0 -24
- package/dist/__tests__/test-helpers.d.ts.map +0 -1
- package/dist/__tests__/test-helpers.js +0 -45
- package/dist/__tests__/test-helpers.js.map +0 -1
- package/dist/db/__tests__/init.test.d.ts +0 -2
- package/dist/db/__tests__/init.test.d.ts.map +0 -1
- package/dist/db/__tests__/init.test.js +0 -73
- package/dist/db/__tests__/init.test.js.map +0 -1
- package/dist/db/__tests__/sqlite-store-read.test.d.ts +0 -2
- package/dist/db/__tests__/sqlite-store-read.test.d.ts.map +0 -1
- package/dist/db/__tests__/sqlite-store-read.test.js +0 -749
- package/dist/db/__tests__/sqlite-store-read.test.js.map +0 -1
- package/dist/db/__tests__/sqlite-store-write.test.d.ts +0 -2
- package/dist/db/__tests__/sqlite-store-write.test.d.ts.map +0 -1
- package/dist/db/__tests__/sqlite-store-write.test.js +0 -418
- package/dist/db/__tests__/sqlite-store-write.test.js.map +0 -1
- package/dist/lib/__tests__/alert-engine.test.d.ts +0 -5
- package/dist/lib/__tests__/alert-engine.test.d.ts.map +0 -1
- package/dist/lib/__tests__/alert-engine.test.js +0 -211
- package/dist/lib/__tests__/alert-engine.test.js.map +0 -1
- package/dist/lib/__tests__/retention.test.d.ts +0 -2
- package/dist/lib/__tests__/retention.test.d.ts.map +0 -1
- package/dist/lib/__tests__/retention.test.js +0 -238
- package/dist/lib/__tests__/retention.test.js.map +0 -1
package/dist/db/sqlite-store.js
CHANGED
|
@@ -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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
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
|
}
|