@agentlensai/server 0.2.0 → 0.5.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/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 +250 -4
- package/dist/db/migrate.js.map +1 -1
- package/dist/db/schema.sqlite.d.ts +881 -55
- package/dist/db/schema.sqlite.d.ts.map +1 -1
- package/dist/db/schema.sqlite.js +82 -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 +25 -13
- package/dist/db/sqlite-store.d.ts.map +1 -1
- package/dist/db/sqlite-store.js +224 -47
- 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 +25 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +99 -5
- package/dist/index.js.map +1 -1
- package/dist/lib/alert-engine.d.ts +72 -0
- package/dist/lib/alert-engine.d.ts.map +1 -0
- package/dist/lib/alert-engine.js +318 -0
- package/dist/lib/alert-engine.js.map +1 -0
- 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 +158 -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 +155 -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 +118 -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 +145 -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 +56 -0
- package/dist/lib/context/retrieval.d.ts.map +1 -0
- package/dist/lib/context/retrieval.js +229 -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/event-bus.d.ts +48 -0
- package/dist/lib/event-bus.d.ts.map +1 -0
- package/dist/lib/event-bus.js +34 -0
- package/dist/lib/event-bus.js.map +1 -0
- package/dist/lib/retention.d.ts +1 -1
- package/dist/lib/retention.d.ts.map +1 -1
- package/dist/lib/sse.d.ts +22 -0
- package/dist/lib/sse.d.ts.map +1 -0
- package/dist/lib/sse.js +121 -0
- package/dist/lib/sse.js.map +1 -0
- 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 +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 +17 -0
- package/dist/routes/alerts.d.ts.map +1 -0
- package/dist/routes/alerts.js +141 -0
- package/dist/routes/alerts.js.map +1 -0
- package/dist/routes/analytics.d.ts +16 -0
- package/dist/routes/analytics.d.ts.map +1 -0
- package/dist/routes/analytics.js +307 -0
- package/dist/routes/analytics.js.map +1 -0
- 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/config.d.ts +5 -0
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/config.js +27 -7
- package/dist/routes/config.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 +7 -2
- package/dist/routes/events.d.ts.map +1 -1
- package/dist/routes/events.js +76 -8
- package/dist/routes/events.js.map +1 -1
- package/dist/routes/ingest.d.ts +36 -0
- package/dist/routes/ingest.d.ts.map +1 -0
- package/dist/routes/ingest.js +280 -0
- package/dist/routes/ingest.js.map +1 -0
- 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/recall.d.ts +20 -0
- package/dist/routes/recall.d.ts.map +1 -0
- package/dist/routes/recall.js +71 -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 +55 -0
- package/dist/routes/reflect.js.map +1 -0
- package/dist/routes/sessions.d.ts +1 -1
- package/dist/routes/sessions.d.ts.map +1 -1
- package/dist/routes/sessions.js +9 -7
- package/dist/routes/sessions.js.map +1 -1
- package/dist/routes/stats.d.ts +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 +23 -0
- package/dist/routes/stream.d.ts.map +1 -0
- package/dist/routes/stream.js +94 -0
- package/dist/routes/stream.js.map +1 -0
- 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 +23 -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__/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__/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__/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__/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
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
* - listAgents(), getAgent()
|
|
14
14
|
* - getAnalytics(), getStats()
|
|
15
15
|
*/
|
|
16
|
-
import { eq, and, gte, lte, inArray, desc, asc, sql,
|
|
17
|
-
import { computeEventHash } from '@
|
|
18
|
-
import { events, sessions, agents, alertRules } from './schema.sqlite.js';
|
|
16
|
+
import { eq, and, gte, lte, inArray, desc, asc, sql, count as drizzleCount } from 'drizzle-orm';
|
|
17
|
+
import { computeEventHash } from '@agentlensai/core';
|
|
18
|
+
import { events, sessions, agents, alertRules, alertHistory } from './schema.sqlite.js';
|
|
19
19
|
import { HashChainError, NotFoundError } from './errors.js';
|
|
20
20
|
// ─── Helpers ───────────────────────────────────────────────
|
|
21
21
|
/**
|
|
@@ -42,6 +42,7 @@ export class SqliteEventStore {
|
|
|
42
42
|
this.db.transaction((tx) => {
|
|
43
43
|
// CRITICAL 3: Validate hash chain before inserting
|
|
44
44
|
const firstEvent = eventList[0];
|
|
45
|
+
const tenantId = firstEvent.tenantId ?? 'default';
|
|
45
46
|
// Check if the first event already exists (idempotent re-insert)
|
|
46
47
|
const existingFirst = tx
|
|
47
48
|
.select({ id: events.id })
|
|
@@ -53,7 +54,7 @@ export class SqliteEventStore {
|
|
|
53
54
|
const lastStoredEvent = tx
|
|
54
55
|
.select({ hash: events.hash })
|
|
55
56
|
.from(events)
|
|
56
|
-
.where(eq(events.sessionId, firstEvent.sessionId))
|
|
57
|
+
.where(and(eq(events.sessionId, firstEvent.sessionId), eq(events.tenantId, tenantId)))
|
|
57
58
|
.orderBy(desc(events.timestamp), desc(events.id))
|
|
58
59
|
.limit(1)
|
|
59
60
|
.get();
|
|
@@ -103,17 +104,18 @@ export class SqliteEventStore {
|
|
|
103
104
|
metadata: JSON.stringify(event.metadata),
|
|
104
105
|
prevHash: event.prevHash,
|
|
105
106
|
hash: event.hash,
|
|
107
|
+
tenantId,
|
|
106
108
|
})
|
|
107
109
|
.onConflictDoNothing({ target: events.id })
|
|
108
110
|
.run();
|
|
109
111
|
// Handle session management
|
|
110
|
-
this._handleSessionUpdate(tx, event);
|
|
112
|
+
this._handleSessionUpdate(tx, event, tenantId);
|
|
111
113
|
// Handle agent auto-creation
|
|
112
|
-
this._handleAgentUpsert(tx, event);
|
|
114
|
+
this._handleAgentUpsert(tx, event, tenantId);
|
|
113
115
|
}
|
|
114
116
|
});
|
|
115
117
|
}
|
|
116
|
-
_handleSessionUpdate(tx, event) {
|
|
118
|
+
_handleSessionUpdate(tx, event, tenantId) {
|
|
117
119
|
if (event.eventType === 'session_started') {
|
|
118
120
|
// Create new session
|
|
119
121
|
const payload = event.payload;
|
|
@@ -132,9 +134,10 @@ export class SqliteEventStore {
|
|
|
132
134
|
errorCount: 0,
|
|
133
135
|
totalCostUsd: 0,
|
|
134
136
|
tags: JSON.stringify(tags),
|
|
137
|
+
tenantId,
|
|
135
138
|
})
|
|
136
139
|
.onConflictDoUpdate({
|
|
137
|
-
target: sessions.id,
|
|
140
|
+
target: [sessions.id, sessions.tenantId],
|
|
138
141
|
set: {
|
|
139
142
|
agentName: agentName ?? sql `coalesce(${sessions.agentName}, NULL)`,
|
|
140
143
|
status: 'active',
|
|
@@ -157,8 +160,9 @@ export class SqliteEventStore {
|
|
|
157
160
|
errorCount: 0,
|
|
158
161
|
totalCostUsd: 0,
|
|
159
162
|
tags: '[]',
|
|
163
|
+
tenantId,
|
|
160
164
|
})
|
|
161
|
-
.onConflictDoNothing({ target: sessions.id })
|
|
165
|
+
.onConflictDoNothing({ target: [sessions.id, sessions.tenantId] })
|
|
162
166
|
.run();
|
|
163
167
|
// Build incremental updates
|
|
164
168
|
const isToolCall = event.eventType === 'tool_call';
|
|
@@ -166,8 +170,13 @@ export class SqliteEventStore {
|
|
|
166
170
|
event.severity === 'critical' ||
|
|
167
171
|
event.eventType === 'tool_error';
|
|
168
172
|
const isCost = event.eventType === 'cost_tracked';
|
|
173
|
+
const isLlmResponse = event.eventType === 'llm_response';
|
|
169
174
|
const costPayload = event.payload;
|
|
170
175
|
const costUsd = isCost ? (Number(costPayload.costUsd) || 0) : 0;
|
|
176
|
+
const llmCostUsd = isLlmResponse ? (Number(costPayload.costUsd) || 0) : 0;
|
|
177
|
+
const llmUsage = isLlmResponse ? costPayload.usage : undefined;
|
|
178
|
+
const llmInputTokens = llmUsage ? (Number(llmUsage.inputTokens) || 0) : 0;
|
|
179
|
+
const llmOutputTokens = llmUsage ? (Number(llmUsage.outputTokens) || 0) : 0;
|
|
171
180
|
if (event.eventType === 'session_ended') {
|
|
172
181
|
const payload = event.payload;
|
|
173
182
|
const reason = payload.reason;
|
|
@@ -181,7 +190,7 @@ export class SqliteEventStore {
|
|
|
181
190
|
? sql `${sessions.errorCount} + 1`
|
|
182
191
|
: sessions.errorCount,
|
|
183
192
|
})
|
|
184
|
-
.where(eq(sessions.id, event.sessionId))
|
|
193
|
+
.where(and(eq(sessions.id, event.sessionId), eq(sessions.tenantId, tenantId)))
|
|
185
194
|
.run();
|
|
186
195
|
return;
|
|
187
196
|
}
|
|
@@ -197,13 +206,25 @@ export class SqliteEventStore {
|
|
|
197
206
|
: sessions.errorCount,
|
|
198
207
|
totalCostUsd: isCost
|
|
199
208
|
? sql `${sessions.totalCostUsd} + ${costUsd}`
|
|
200
|
-
:
|
|
209
|
+
: isLlmResponse
|
|
210
|
+
? sql `${sessions.totalCostUsd} + ${llmCostUsd}`
|
|
211
|
+
: sessions.totalCostUsd,
|
|
212
|
+
llmCallCount: isLlmResponse
|
|
213
|
+
? sql `${sessions.llmCallCount} + 1`
|
|
214
|
+
: sessions.llmCallCount,
|
|
215
|
+
totalInputTokens: isLlmResponse
|
|
216
|
+
? sql `${sessions.totalInputTokens} + ${llmInputTokens}`
|
|
217
|
+
: sessions.totalInputTokens,
|
|
218
|
+
totalOutputTokens: isLlmResponse
|
|
219
|
+
? sql `${sessions.totalOutputTokens} + ${llmOutputTokens}`
|
|
220
|
+
: sessions.totalOutputTokens,
|
|
201
221
|
})
|
|
202
|
-
.where(eq(sessions.id, event.sessionId))
|
|
222
|
+
.where(and(eq(sessions.id, event.sessionId), eq(sessions.tenantId, tenantId)))
|
|
203
223
|
.run();
|
|
204
224
|
}
|
|
205
|
-
_handleAgentUpsert(tx, event) {
|
|
225
|
+
_handleAgentUpsert(tx, event, tenantId) {
|
|
206
226
|
// CRITICAL 4: Use INSERT ... ON CONFLICT DO UPDATE for agents
|
|
227
|
+
// Agents are scoped per tenant — use composite key (id + tenant_id)
|
|
207
228
|
const payload = event.payload;
|
|
208
229
|
const agentName = payload.agentName ?? event.agentId;
|
|
209
230
|
tx.insert(agents)
|
|
@@ -213,9 +234,10 @@ export class SqliteEventStore {
|
|
|
213
234
|
firstSeenAt: event.timestamp,
|
|
214
235
|
lastSeenAt: event.timestamp,
|
|
215
236
|
sessionCount: event.eventType === 'session_started' ? 1 : 0,
|
|
237
|
+
tenantId,
|
|
216
238
|
})
|
|
217
239
|
.onConflictDoUpdate({
|
|
218
|
-
target: agents.id,
|
|
240
|
+
target: [agents.id, agents.tenantId],
|
|
219
241
|
set: {
|
|
220
242
|
lastSeenAt: event.timestamp,
|
|
221
243
|
sessionCount: event.eventType === 'session_started'
|
|
@@ -227,10 +249,11 @@ export class SqliteEventStore {
|
|
|
227
249
|
}
|
|
228
250
|
// ─── Sessions — Write ──────────────────────────────────────
|
|
229
251
|
async upsertSession(session) {
|
|
252
|
+
const tenantId = session.tenantId ?? 'default';
|
|
230
253
|
const existing = this.db
|
|
231
254
|
.select()
|
|
232
255
|
.from(sessions)
|
|
233
|
-
.where(eq(sessions.id, session.id))
|
|
256
|
+
.where(and(eq(sessions.id, session.id), eq(sessions.tenantId, tenantId)))
|
|
234
257
|
.get();
|
|
235
258
|
if (existing) {
|
|
236
259
|
const updates = {};
|
|
@@ -252,13 +275,19 @@ export class SqliteEventStore {
|
|
|
252
275
|
updates.errorCount = session.errorCount;
|
|
253
276
|
if (session.totalCostUsd !== undefined)
|
|
254
277
|
updates.totalCostUsd = session.totalCostUsd;
|
|
278
|
+
if (session.llmCallCount !== undefined)
|
|
279
|
+
updates.llmCallCount = session.llmCallCount;
|
|
280
|
+
if (session.totalInputTokens !== undefined)
|
|
281
|
+
updates.totalInputTokens = session.totalInputTokens;
|
|
282
|
+
if (session.totalOutputTokens !== undefined)
|
|
283
|
+
updates.totalOutputTokens = session.totalOutputTokens;
|
|
255
284
|
if (session.tags !== undefined)
|
|
256
285
|
updates.tags = JSON.stringify(session.tags);
|
|
257
286
|
if (Object.keys(updates).length > 0) {
|
|
258
287
|
this.db
|
|
259
288
|
.update(sessions)
|
|
260
289
|
.set(updates)
|
|
261
|
-
.where(eq(sessions.id, session.id))
|
|
290
|
+
.where(and(eq(sessions.id, session.id), eq(sessions.tenantId, tenantId)))
|
|
262
291
|
.run();
|
|
263
292
|
}
|
|
264
293
|
}
|
|
@@ -276,17 +305,22 @@ export class SqliteEventStore {
|
|
|
276
305
|
toolCallCount: session.toolCallCount ?? 0,
|
|
277
306
|
errorCount: session.errorCount ?? 0,
|
|
278
307
|
totalCostUsd: session.totalCostUsd ?? 0,
|
|
308
|
+
llmCallCount: session.llmCallCount ?? 0,
|
|
309
|
+
totalInputTokens: session.totalInputTokens ?? 0,
|
|
310
|
+
totalOutputTokens: session.totalOutputTokens ?? 0,
|
|
279
311
|
tags: JSON.stringify(session.tags ?? []),
|
|
312
|
+
tenantId,
|
|
280
313
|
})
|
|
281
314
|
.run();
|
|
282
315
|
}
|
|
283
316
|
}
|
|
284
317
|
// ─── Agents — Write ────────────────────────────────────────
|
|
285
318
|
async upsertAgent(agent) {
|
|
319
|
+
const tenantId = agent.tenantId ?? 'default';
|
|
286
320
|
const existing = this.db
|
|
287
321
|
.select()
|
|
288
322
|
.from(agents)
|
|
289
|
-
.where(eq(agents.id, agent.id))
|
|
323
|
+
.where(and(eq(agents.id, agent.id), eq(agents.tenantId, tenantId)))
|
|
290
324
|
.get();
|
|
291
325
|
if (existing) {
|
|
292
326
|
const updates = {};
|
|
@@ -302,7 +336,7 @@ export class SqliteEventStore {
|
|
|
302
336
|
this.db
|
|
303
337
|
.update(agents)
|
|
304
338
|
.set(updates)
|
|
305
|
-
.where(eq(agents.id, agent.id))
|
|
339
|
+
.where(and(eq(agents.id, agent.id), eq(agents.tenantId, tenantId)))
|
|
306
340
|
.run();
|
|
307
341
|
}
|
|
308
342
|
}
|
|
@@ -317,6 +351,7 @@ export class SqliteEventStore {
|
|
|
317
351
|
firstSeenAt: agent.firstSeenAt ?? now,
|
|
318
352
|
lastSeenAt: agent.lastSeenAt ?? now,
|
|
319
353
|
sessionCount: agent.sessionCount ?? 0,
|
|
354
|
+
tenantId,
|
|
320
355
|
})
|
|
321
356
|
.run();
|
|
322
357
|
}
|
|
@@ -342,23 +377,42 @@ export class SqliteEventStore {
|
|
|
342
377
|
hasMore: offset + rows.length < total,
|
|
343
378
|
};
|
|
344
379
|
}
|
|
345
|
-
async getEvent(id) {
|
|
380
|
+
async getEvent(id, tenantId) {
|
|
381
|
+
const conditions = [eq(events.id, id)];
|
|
382
|
+
if (tenantId)
|
|
383
|
+
conditions.push(eq(events.tenantId, tenantId));
|
|
346
384
|
const row = this.db
|
|
347
385
|
.select()
|
|
348
386
|
.from(events)
|
|
349
|
-
.where(
|
|
387
|
+
.where(and(...conditions))
|
|
350
388
|
.get();
|
|
351
389
|
return row ? this._mapEventRow(row) : null;
|
|
352
390
|
}
|
|
353
|
-
async getSessionTimeline(sessionId) {
|
|
391
|
+
async getSessionTimeline(sessionId, tenantId) {
|
|
392
|
+
const conditions = [eq(events.sessionId, sessionId)];
|
|
393
|
+
if (tenantId)
|
|
394
|
+
conditions.push(eq(events.tenantId, tenantId));
|
|
354
395
|
const rows = this.db
|
|
355
396
|
.select()
|
|
356
397
|
.from(events)
|
|
357
|
-
.where(
|
|
398
|
+
.where(and(...conditions))
|
|
358
399
|
.orderBy(asc(events.timestamp))
|
|
359
400
|
.all();
|
|
360
401
|
return rows.map(this._mapEventRow);
|
|
361
402
|
}
|
|
403
|
+
async getLastEventHash(sessionId, tenantId) {
|
|
404
|
+
const conditions = [eq(events.sessionId, sessionId)];
|
|
405
|
+
if (tenantId)
|
|
406
|
+
conditions.push(eq(events.tenantId, tenantId));
|
|
407
|
+
const row = this.db
|
|
408
|
+
.select({ hash: events.hash })
|
|
409
|
+
.from(events)
|
|
410
|
+
.where(and(...conditions))
|
|
411
|
+
.orderBy(desc(events.timestamp), desc(events.id))
|
|
412
|
+
.limit(1)
|
|
413
|
+
.get();
|
|
414
|
+
return row?.hash ?? null;
|
|
415
|
+
}
|
|
362
416
|
async countEvents(query) {
|
|
363
417
|
const conditions = this._buildEventConditions(query);
|
|
364
418
|
const result = this.db
|
|
@@ -391,28 +445,36 @@ export class SqliteEventStore {
|
|
|
391
445
|
total: totalResult?.count ?? 0,
|
|
392
446
|
};
|
|
393
447
|
}
|
|
394
|
-
async getSession(id) {
|
|
448
|
+
async getSession(id, tenantId) {
|
|
449
|
+
const conditions = [eq(sessions.id, id)];
|
|
450
|
+
if (tenantId)
|
|
451
|
+
conditions.push(eq(sessions.tenantId, tenantId));
|
|
395
452
|
const row = this.db
|
|
396
453
|
.select()
|
|
397
454
|
.from(sessions)
|
|
398
|
-
.where(
|
|
455
|
+
.where(and(...conditions))
|
|
399
456
|
.get();
|
|
400
457
|
return row ? this._mapSessionRow(row) : null;
|
|
401
458
|
}
|
|
402
459
|
// ─── Agents — Read ─────────────────────────────────────────
|
|
403
|
-
async listAgents() {
|
|
460
|
+
async listAgents(tenantId) {
|
|
461
|
+
const conditions = tenantId ? [eq(agents.tenantId, tenantId)] : [];
|
|
404
462
|
const rows = this.db
|
|
405
463
|
.select()
|
|
406
464
|
.from(agents)
|
|
465
|
+
.where(conditions.length > 0 ? and(...conditions) : undefined)
|
|
407
466
|
.orderBy(desc(agents.lastSeenAt))
|
|
408
467
|
.all();
|
|
409
468
|
return rows.map(this._mapAgentRow);
|
|
410
469
|
}
|
|
411
|
-
async getAgent(id) {
|
|
470
|
+
async getAgent(id, tenantId) {
|
|
471
|
+
const conditions = [eq(agents.id, id)];
|
|
472
|
+
if (tenantId)
|
|
473
|
+
conditions.push(eq(agents.tenantId, tenantId));
|
|
412
474
|
const row = this.db
|
|
413
475
|
.select()
|
|
414
476
|
.from(agents)
|
|
415
|
-
.where(
|
|
477
|
+
.where(and(...conditions))
|
|
416
478
|
.get();
|
|
417
479
|
return row ? this._mapAgentRow(row) : null;
|
|
418
480
|
}
|
|
@@ -447,6 +509,7 @@ export class SqliteEventStore {
|
|
|
447
509
|
WHERE timestamp >= ${params.from}
|
|
448
510
|
AND timestamp <= ${params.to}
|
|
449
511
|
${params.agentId ? sql `AND agent_id = ${params.agentId}` : sql ``}
|
|
512
|
+
${params.tenantId ? sql `AND tenant_id = ${params.tenantId}` : sql ``}
|
|
450
513
|
GROUP BY bucket
|
|
451
514
|
ORDER BY bucket ASC
|
|
452
515
|
`);
|
|
@@ -464,6 +527,7 @@ export class SqliteEventStore {
|
|
|
464
527
|
WHERE timestamp >= ${params.from}
|
|
465
528
|
AND timestamp <= ${params.to}
|
|
466
529
|
${params.agentId ? sql `AND agent_id = ${params.agentId}` : sql ``}
|
|
530
|
+
${params.tenantId ? sql `AND tenant_id = ${params.tenantId}` : sql ``}
|
|
467
531
|
`);
|
|
468
532
|
return {
|
|
469
533
|
buckets: bucketRows.map((row) => ({
|
|
@@ -501,11 +565,12 @@ export class SqliteEventStore {
|
|
|
501
565
|
notifyChannels: JSON.stringify(rule.notifyChannels),
|
|
502
566
|
createdAt: rule.createdAt,
|
|
503
567
|
updatedAt: rule.updatedAt,
|
|
568
|
+
tenantId: rule.tenantId ?? 'default',
|
|
504
569
|
})
|
|
505
570
|
.run();
|
|
506
571
|
}
|
|
507
572
|
// HIGH 8: Check affected row count and throw NotFoundError when zero
|
|
508
|
-
async updateAlertRule(id, updates) {
|
|
573
|
+
async updateAlertRule(id, updates, tenantId) {
|
|
509
574
|
const setValues = {};
|
|
510
575
|
if (updates.name !== undefined)
|
|
511
576
|
setValues.name = updates.name;
|
|
@@ -523,12 +588,15 @@ export class SqliteEventStore {
|
|
|
523
588
|
setValues.notifyChannels = JSON.stringify(updates.notifyChannels);
|
|
524
589
|
if (updates.updatedAt !== undefined)
|
|
525
590
|
setValues.updatedAt = updates.updatedAt;
|
|
591
|
+
const whereConditions = [eq(alertRules.id, id)];
|
|
592
|
+
if (tenantId)
|
|
593
|
+
whereConditions.push(eq(alertRules.tenantId, tenantId));
|
|
526
594
|
if (Object.keys(setValues).length === 0) {
|
|
527
595
|
// Even with no actual updates, verify the rule exists
|
|
528
596
|
const existing = this.db
|
|
529
597
|
.select({ id: alertRules.id })
|
|
530
598
|
.from(alertRules)
|
|
531
|
-
.where(
|
|
599
|
+
.where(and(...whereConditions))
|
|
532
600
|
.get();
|
|
533
601
|
if (!existing) {
|
|
534
602
|
throw new NotFoundError(`Alert rule not found: ${id}`);
|
|
@@ -538,74 +606,165 @@ export class SqliteEventStore {
|
|
|
538
606
|
const result = this.db
|
|
539
607
|
.update(alertRules)
|
|
540
608
|
.set(setValues)
|
|
541
|
-
.where(
|
|
609
|
+
.where(and(...whereConditions))
|
|
542
610
|
.run();
|
|
543
611
|
if (result.changes === 0) {
|
|
544
612
|
throw new NotFoundError(`Alert rule not found: ${id}`);
|
|
545
613
|
}
|
|
546
614
|
}
|
|
547
|
-
async deleteAlertRule(id) {
|
|
548
|
-
const
|
|
615
|
+
async deleteAlertRule(id, tenantId) {
|
|
616
|
+
const whereConditions = [eq(alertRules.id, id)];
|
|
617
|
+
if (tenantId)
|
|
618
|
+
whereConditions.push(eq(alertRules.tenantId, tenantId));
|
|
619
|
+
const result = this.db.delete(alertRules).where(and(...whereConditions)).run();
|
|
549
620
|
if (result.changes === 0) {
|
|
550
621
|
throw new NotFoundError(`Alert rule not found: ${id}`);
|
|
551
622
|
}
|
|
552
623
|
}
|
|
553
|
-
async listAlertRules() {
|
|
554
|
-
const
|
|
624
|
+
async listAlertRules(tenantId) {
|
|
625
|
+
const conditions = tenantId ? [eq(alertRules.tenantId, tenantId)] : [];
|
|
626
|
+
const rows = this.db
|
|
627
|
+
.select()
|
|
628
|
+
.from(alertRules)
|
|
629
|
+
.where(conditions.length > 0 ? and(...conditions) : undefined)
|
|
630
|
+
.all();
|
|
555
631
|
return rows.map(this._mapAlertRuleRow);
|
|
556
632
|
}
|
|
557
|
-
async getAlertRule(id) {
|
|
633
|
+
async getAlertRule(id, tenantId) {
|
|
634
|
+
const conditions = [eq(alertRules.id, id)];
|
|
635
|
+
if (tenantId)
|
|
636
|
+
conditions.push(eq(alertRules.tenantId, tenantId));
|
|
558
637
|
const row = this.db
|
|
559
638
|
.select()
|
|
560
639
|
.from(alertRules)
|
|
561
|
-
.where(
|
|
640
|
+
.where(and(...conditions))
|
|
562
641
|
.get();
|
|
563
642
|
return row ? this._mapAlertRuleRow(row) : null;
|
|
564
643
|
}
|
|
644
|
+
// ─── Alert History ─────────────────────────────────────────
|
|
645
|
+
async insertAlertHistory(entry) {
|
|
646
|
+
this.db
|
|
647
|
+
.insert(alertHistory)
|
|
648
|
+
.values({
|
|
649
|
+
id: entry.id,
|
|
650
|
+
ruleId: entry.ruleId,
|
|
651
|
+
triggeredAt: entry.triggeredAt,
|
|
652
|
+
resolvedAt: entry.resolvedAt ?? null,
|
|
653
|
+
currentValue: entry.currentValue,
|
|
654
|
+
threshold: entry.threshold,
|
|
655
|
+
message: entry.message,
|
|
656
|
+
tenantId: entry.tenantId ?? 'default',
|
|
657
|
+
})
|
|
658
|
+
.run();
|
|
659
|
+
}
|
|
660
|
+
async listAlertHistory(opts) {
|
|
661
|
+
const limit = Math.min(opts?.limit ?? 50, 500);
|
|
662
|
+
const offset = opts?.offset ?? 0;
|
|
663
|
+
const conditions = [];
|
|
664
|
+
if (opts?.ruleId) {
|
|
665
|
+
conditions.push(eq(alertHistory.ruleId, opts.ruleId));
|
|
666
|
+
}
|
|
667
|
+
if (opts?.tenantId) {
|
|
668
|
+
conditions.push(eq(alertHistory.tenantId, opts.tenantId));
|
|
669
|
+
}
|
|
670
|
+
const rows = this.db
|
|
671
|
+
.select()
|
|
672
|
+
.from(alertHistory)
|
|
673
|
+
.where(conditions.length > 0 ? and(...conditions) : undefined)
|
|
674
|
+
.orderBy(desc(alertHistory.triggeredAt))
|
|
675
|
+
.limit(limit)
|
|
676
|
+
.offset(offset)
|
|
677
|
+
.all();
|
|
678
|
+
const totalResult = this.db
|
|
679
|
+
.select({ count: drizzleCount() })
|
|
680
|
+
.from(alertHistory)
|
|
681
|
+
.where(conditions.length > 0 ? and(...conditions) : undefined)
|
|
682
|
+
.get();
|
|
683
|
+
return {
|
|
684
|
+
entries: rows.map((row) => ({
|
|
685
|
+
id: row.id,
|
|
686
|
+
ruleId: row.ruleId,
|
|
687
|
+
triggeredAt: row.triggeredAt,
|
|
688
|
+
resolvedAt: row.resolvedAt ?? undefined,
|
|
689
|
+
currentValue: row.currentValue,
|
|
690
|
+
threshold: row.threshold,
|
|
691
|
+
message: row.message,
|
|
692
|
+
tenantId: row.tenantId,
|
|
693
|
+
})),
|
|
694
|
+
total: totalResult?.count ?? 0,
|
|
695
|
+
};
|
|
696
|
+
}
|
|
565
697
|
// ─── Maintenance ───────────────────────────────────────────
|
|
566
|
-
async applyRetention(olderThan) {
|
|
698
|
+
async applyRetention(olderThan, tenantId) {
|
|
699
|
+
// Build conditions — always filter by timestamp, optionally by tenant
|
|
700
|
+
const countConditions = [lte(events.timestamp, olderThan)];
|
|
701
|
+
if (tenantId)
|
|
702
|
+
countConditions.push(eq(events.tenantId, tenantId));
|
|
567
703
|
// Count events to be deleted
|
|
568
704
|
const countResult = this.db
|
|
569
705
|
.select({ count: drizzleCount() })
|
|
570
706
|
.from(events)
|
|
571
|
-
.where(
|
|
707
|
+
.where(and(...countConditions))
|
|
572
708
|
.get();
|
|
573
709
|
const deletedCount = countResult?.count ?? 0;
|
|
574
710
|
if (deletedCount === 0)
|
|
575
711
|
return { deletedCount: 0 };
|
|
576
712
|
this.db.transaction((tx) => {
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
713
|
+
if (tenantId) {
|
|
714
|
+
// Tenant-scoped retention: only delete this tenant's old events
|
|
715
|
+
tx.delete(events)
|
|
716
|
+
.where(and(lte(events.timestamp, olderThan), eq(events.tenantId, tenantId)))
|
|
717
|
+
.run();
|
|
718
|
+
// Clean up this tenant's sessions that have no remaining events
|
|
719
|
+
tx.run(sql `
|
|
720
|
+
DELETE FROM sessions
|
|
721
|
+
WHERE tenant_id = ${tenantId}
|
|
722
|
+
AND id NOT IN (
|
|
723
|
+
SELECT DISTINCT session_id FROM events WHERE tenant_id = ${tenantId}
|
|
724
|
+
)
|
|
725
|
+
`);
|
|
726
|
+
}
|
|
727
|
+
else {
|
|
728
|
+
// Global retention (system-level, not exposed through TenantScopedStore)
|
|
729
|
+
tx.delete(events).where(lte(events.timestamp, olderThan)).run();
|
|
730
|
+
tx.run(sql `
|
|
731
|
+
DELETE FROM sessions
|
|
732
|
+
WHERE id NOT IN (SELECT DISTINCT session_id FROM events)
|
|
733
|
+
`);
|
|
734
|
+
}
|
|
584
735
|
});
|
|
585
736
|
return { deletedCount };
|
|
586
737
|
}
|
|
587
|
-
async getStats() {
|
|
738
|
+
async getStats(tenantId) {
|
|
739
|
+
const eventConditions = tenantId ? [eq(events.tenantId, tenantId)] : [];
|
|
740
|
+
const sessionConditions = tenantId ? [eq(sessions.tenantId, tenantId)] : [];
|
|
741
|
+
const agentConditions = tenantId ? [eq(agents.tenantId, tenantId)] : [];
|
|
588
742
|
const eventCount = this.db
|
|
589
743
|
.select({ count: drizzleCount() })
|
|
590
744
|
.from(events)
|
|
745
|
+
.where(eventConditions.length > 0 ? and(...eventConditions) : undefined)
|
|
591
746
|
.get()?.count ?? 0;
|
|
592
747
|
const sessionCount = this.db
|
|
593
748
|
.select({ count: drizzleCount() })
|
|
594
749
|
.from(sessions)
|
|
750
|
+
.where(sessionConditions.length > 0 ? and(...sessionConditions) : undefined)
|
|
595
751
|
.get()?.count ?? 0;
|
|
596
752
|
const agentCount = this.db
|
|
597
753
|
.select({ count: drizzleCount() })
|
|
598
754
|
.from(agents)
|
|
755
|
+
.where(agentConditions.length > 0 ? and(...agentConditions) : undefined)
|
|
599
756
|
.get()?.count ?? 0;
|
|
600
757
|
const oldest = this.db
|
|
601
758
|
.select({ timestamp: events.timestamp })
|
|
602
759
|
.from(events)
|
|
760
|
+
.where(eventConditions.length > 0 ? and(...eventConditions) : undefined)
|
|
603
761
|
.orderBy(asc(events.timestamp))
|
|
604
762
|
.limit(1)
|
|
605
763
|
.get();
|
|
606
764
|
const newest = this.db
|
|
607
765
|
.select({ timestamp: events.timestamp })
|
|
608
766
|
.from(events)
|
|
767
|
+
.where(eventConditions.length > 0 ? and(...eventConditions) : undefined)
|
|
609
768
|
.orderBy(desc(events.timestamp))
|
|
610
769
|
.limit(1)
|
|
611
770
|
.get();
|
|
@@ -626,6 +785,9 @@ export class SqliteEventStore {
|
|
|
626
785
|
// ─── Private Helpers ───────────────────────────────────────
|
|
627
786
|
_buildEventConditions(query) {
|
|
628
787
|
const conditions = [];
|
|
788
|
+
if (query.tenantId) {
|
|
789
|
+
conditions.push(eq(events.tenantId, query.tenantId));
|
|
790
|
+
}
|
|
629
791
|
if (query.sessionId) {
|
|
630
792
|
conditions.push(eq(events.sessionId, query.sessionId));
|
|
631
793
|
}
|
|
@@ -655,13 +817,21 @@ export class SqliteEventStore {
|
|
|
655
817
|
conditions.push(lte(events.timestamp, query.to));
|
|
656
818
|
}
|
|
657
819
|
if (query.search) {
|
|
658
|
-
|
|
820
|
+
// H1 fix: Escape LIKE wildcards to prevent wildcard abuse
|
|
821
|
+
const escaped = query.search
|
|
822
|
+
.replace(/\\/g, '\\\\')
|
|
823
|
+
.replace(/%/g, '\\%')
|
|
824
|
+
.replace(/_/g, '\\_');
|
|
825
|
+
conditions.push(sql `${events.payload} LIKE ${'%' + escaped + '%'} ESCAPE '\\'`);
|
|
659
826
|
}
|
|
660
827
|
return conditions;
|
|
661
828
|
}
|
|
662
829
|
// HIGH 4: Use json_each() for exact tag matching with OR semantics
|
|
663
830
|
_buildSessionConditions(query) {
|
|
664
831
|
const conditions = [];
|
|
832
|
+
if (query.tenantId) {
|
|
833
|
+
conditions.push(eq(sessions.tenantId, query.tenantId));
|
|
834
|
+
}
|
|
665
835
|
if (query.agentId) {
|
|
666
836
|
conditions.push(eq(sessions.agentId, query.agentId));
|
|
667
837
|
}
|
|
@@ -708,6 +878,7 @@ export class SqliteEventStore {
|
|
|
708
878
|
metadata: safeJsonParse(row.metadata, {}),
|
|
709
879
|
prevHash: row.prevHash,
|
|
710
880
|
hash: row.hash,
|
|
881
|
+
tenantId: row.tenantId,
|
|
711
882
|
};
|
|
712
883
|
}
|
|
713
884
|
_mapSessionRow(row) {
|
|
@@ -722,7 +893,11 @@ export class SqliteEventStore {
|
|
|
722
893
|
toolCallCount: row.toolCallCount,
|
|
723
894
|
errorCount: row.errorCount,
|
|
724
895
|
totalCostUsd: row.totalCostUsd,
|
|
896
|
+
llmCallCount: row.llmCallCount,
|
|
897
|
+
totalInputTokens: row.totalInputTokens,
|
|
898
|
+
totalOutputTokens: row.totalOutputTokens,
|
|
725
899
|
tags: safeJsonParse(row.tags, []),
|
|
900
|
+
tenantId: row.tenantId,
|
|
726
901
|
};
|
|
727
902
|
}
|
|
728
903
|
_mapAgentRow(row) {
|
|
@@ -733,6 +908,7 @@ export class SqliteEventStore {
|
|
|
733
908
|
firstSeenAt: row.firstSeenAt,
|
|
734
909
|
lastSeenAt: row.lastSeenAt,
|
|
735
910
|
sessionCount: row.sessionCount,
|
|
911
|
+
tenantId: row.tenantId,
|
|
736
912
|
};
|
|
737
913
|
}
|
|
738
914
|
_mapAlertRuleRow(row) {
|
|
@@ -747,6 +923,7 @@ export class SqliteEventStore {
|
|
|
747
923
|
notifyChannels: safeJsonParse(row.notifyChannels, []),
|
|
748
924
|
createdAt: row.createdAt,
|
|
749
925
|
updatedAt: row.updatedAt,
|
|
926
|
+
tenantId: row.tenantId,
|
|
750
927
|
};
|
|
751
928
|
}
|
|
752
929
|
}
|