@yesvara/svara 0.1.0 → 0.1.1

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/index.mjs CHANGED
@@ -1,8 +1,9 @@
1
1
  import {
2
2
  Chunker,
3
3
  DocumentLoader,
4
+ SvaraDB,
4
5
  VectorRetriever
5
- } from "./chunk-FEA5KIJN.mjs";
6
+ } from "./chunk-WA3BIA3S.mjs";
6
7
  import {
7
8
  __esm,
8
9
  __export,
@@ -339,8 +340,8 @@ var SvaraApp = class {
339
340
  * .route('/chat', supportAgent.handler())
340
341
  * .route('/sales', salesAgent.handler());
341
342
  */
342
- route(path2, handler) {
343
- this.express.post(path2, handler);
343
+ route(path, handler) {
344
+ this.express.post(path, handler);
344
345
  return this;
345
346
  }
346
347
  /**
@@ -884,13 +885,17 @@ var SvaraAgent = class extends EventEmitter {
884
885
  verbose;
885
886
  channels = /* @__PURE__ */ new Map();
886
887
  knowledgeBase = null;
888
+ retriever = null;
889
+ // Store VectorRetriever for retrieveChunks access
887
890
  knowledgePaths = [];
888
891
  isStarted = false;
892
+ db;
889
893
  constructor(config) {
890
894
  super();
891
895
  this.name = config.name;
892
896
  this.maxIterations = config.maxIterations ?? 10;
893
897
  this.verbose = config.verbose ?? false;
898
+ this.db = new SvaraDB("./data/svara.db");
894
899
  this.systemPrompt = config.systemPrompt ?? `You are ${config.name}, a helpful and friendly AI assistant. Be concise and accurate.`;
895
900
  this.llmConfig = resolveConfig(config.model, {
896
901
  temperature: config.temperature,
@@ -1009,7 +1014,8 @@ var SvaraAgent = class extends EventEmitter {
1009
1014
  response: result.response,
1010
1015
  sessionId: result.sessionId,
1011
1016
  usage: result.usage,
1012
- toolsUsed: result.toolsUsed
1017
+ toolsUsed: result.toolsUsed,
1018
+ retrievedDocuments: result.retrievedDocuments || []
1013
1019
  });
1014
1020
  } catch (err) {
1015
1021
  const error = err;
@@ -1080,6 +1086,46 @@ var SvaraAgent = class extends EventEmitter {
1080
1086
  await this.knowledgeBase.load(arr);
1081
1087
  }
1082
1088
  }
1089
+ // ─── Internal: User & Session Tracking ───────────────────────────────────────
1090
+ async trackUserAndSession(userId, sessionId, channel = "api") {
1091
+ try {
1092
+ const existingUser = this.db.query(
1093
+ "SELECT id FROM svara_users WHERE id = ?",
1094
+ [userId]
1095
+ );
1096
+ if (existingUser.length === 0) {
1097
+ this.db.run(
1098
+ `INSERT INTO svara_users (id, display_name, first_seen, last_seen)
1099
+ VALUES (?, ?, unixepoch(), unixepoch())`,
1100
+ [userId, userId]
1101
+ );
1102
+ } else {
1103
+ this.db.run(
1104
+ "UPDATE svara_users SET last_seen = unixepoch() WHERE id = ?",
1105
+ [userId]
1106
+ );
1107
+ }
1108
+ const existingSession = this.db.query(
1109
+ "SELECT id FROM svara_sessions WHERE id = ?",
1110
+ [sessionId]
1111
+ );
1112
+ if (existingSession.length === 0) {
1113
+ this.db.run(
1114
+ `INSERT INTO svara_sessions (id, user_id, channel, created_at, updated_at)
1115
+ VALUES (?, ?, ?, unixepoch(), unixepoch())`,
1116
+ [sessionId, userId, channel]
1117
+ );
1118
+ } else {
1119
+ this.db.run(
1120
+ "UPDATE svara_sessions SET updated_at = unixepoch() WHERE id = ?",
1121
+ [sessionId]
1122
+ );
1123
+ }
1124
+ this.log("debug", `Tracked user ${userId} with session ${sessionId}`);
1125
+ } catch (error) {
1126
+ this.log("error", `Failed to track user: ${error.message}`);
1127
+ }
1128
+ }
1083
1129
  // ─── Internal: Agentic Loop ───────────────────────────────────────────────
1084
1130
  /**
1085
1131
  * Receives a raw incoming message from a channel and processes it.
@@ -1092,13 +1138,33 @@ var SvaraAgent = class extends EventEmitter {
1092
1138
  });
1093
1139
  }
1094
1140
  async run(message, options) {
1141
+ console.log(`
1142
+ [RUN START] kb=${!!this.knowledgeBase} ret=${!!this.retriever}`);
1095
1143
  const startTime = Date.now();
1096
1144
  const sessionId = options.sessionId ?? crypto.randomUUID();
1097
- this.emit("message:received", { message, sessionId, userId: options.userId });
1145
+ const userId = options.userId ?? "unknown";
1146
+ await this.trackUserAndSession(userId, sessionId);
1147
+ this.emit("message:received", { message, sessionId, userId });
1098
1148
  const history = await this.memory.getHistory(sessionId);
1099
1149
  let ragContext = "";
1100
- if (this.knowledgeBase) {
1150
+ let retrievedDocuments = [];
1151
+ if (this.knowledgeBase && this.retriever) {
1101
1152
  ragContext = await this.knowledgeBase.retrieve(message);
1153
+ try {
1154
+ console.log(`[DEBUG] Calling retrieveChunks for query: "${message}"`);
1155
+ const context = await this.retriever.retrieveChunks(message, 3);
1156
+ console.log(`[DEBUG] Retrieved ${context.chunks.length} chunks`);
1157
+ retrievedDocuments = context.chunks.map((item) => ({
1158
+ source: item.chunk?.source || "unknown",
1159
+ score: Math.round(item.score * 100) / 100,
1160
+ excerpt: item.chunk?.content?.substring(0, 150) || ""
1161
+ }));
1162
+ console.log(`[DEBUG] Mapped ${retrievedDocuments.length} documents`);
1163
+ } catch (e) {
1164
+ console.error(`[ERROR] RAG retrieval failed:`, e);
1165
+ }
1166
+ } else {
1167
+ console.log(`[DEBUG] No knowledgeBase (${!!this.knowledgeBase}) or retriever (${!!this.retriever})`);
1102
1168
  }
1103
1169
  const messages = this.context.buildMessages(
1104
1170
  this.systemPrompt,
@@ -1108,7 +1174,7 @@ var SvaraAgent = class extends EventEmitter {
1108
1174
  );
1109
1175
  const internalCtx = {
1110
1176
  sessionId,
1111
- userId: options.userId ?? "unknown",
1177
+ userId,
1112
1178
  agentName: this.name,
1113
1179
  history,
1114
1180
  metadata: options.metadata ?? {}
@@ -1165,7 +1231,8 @@ var SvaraAgent = class extends EventEmitter {
1165
1231
  toolsUsed: [...new Set(toolsUsed)],
1166
1232
  iterations,
1167
1233
  usage: totalUsage,
1168
- duration: Date.now() - startTime
1234
+ duration: Date.now() - startTime,
1235
+ retrievedDocuments: retrievedDocuments.length > 0 ? retrievedDocuments : void 0
1169
1236
  };
1170
1237
  this.emit("message:sent", { response: finalResponse, sessionId });
1171
1238
  return result;
@@ -1174,9 +1241,9 @@ var SvaraAgent = class extends EventEmitter {
1174
1241
  async initKnowledge(paths) {
1175
1242
  try {
1176
1243
  const { glob } = await import("glob");
1177
- const { VectorRetriever: VectorRetriever2 } = await import("./retriever-4QY667XF.mjs");
1178
- const retriever = new VectorRetriever2();
1179
- await retriever.init({ embeddings: { provider: "openai" } });
1244
+ const { VectorRetriever: VectorRetriever2 } = await import("./retriever-OTPBECGO.mjs");
1245
+ this.retriever = new VectorRetriever2(this.name, this.db);
1246
+ await this.retriever.init({ embeddings: { provider: "openai" } });
1180
1247
  const files = [];
1181
1248
  for (const pattern of paths) {
1182
1249
  const matches = await glob(pattern);
@@ -1186,16 +1253,16 @@ var SvaraAgent = class extends EventEmitter {
1186
1253
  console.warn(`[@yesvara/svara] No files found matching: ${paths.join(", ")}`);
1187
1254
  return;
1188
1255
  }
1189
- await retriever.addDocuments(files);
1256
+ await this.retriever.addDocuments(files);
1190
1257
  this.knowledgeBase = {
1191
1258
  load: async (p) => {
1192
1259
  const newFiles = [];
1193
1260
  for (const pattern of Array.isArray(p) ? p : [p]) {
1194
1261
  newFiles.push(...await glob(pattern));
1195
1262
  }
1196
- await retriever.addDocuments(newFiles);
1263
+ await this.retriever.addDocuments(newFiles);
1197
1264
  },
1198
- retrieve: (query, topK) => retriever.retrieve(query, topK)
1265
+ retrieve: (query, topK) => this.retriever.retrieve(query, topK)
1199
1266
  };
1200
1267
  this.log("info", `Knowledge base loaded: ${files.length} file(s).`);
1201
1268
  } catch (err) {
@@ -1257,231 +1324,6 @@ function createTool(definition) {
1257
1324
  };
1258
1325
  }
1259
1326
 
1260
- // src/database/sqlite.ts
1261
- import path from "path";
1262
- import fs from "fs";
1263
-
1264
- // src/database/schema.ts
1265
- var SCHEMA_VERSION = 1;
1266
- var CREATE_TABLES_SQL = `
1267
- -- Schema version tracking
1268
- CREATE TABLE IF NOT EXISTS svara_meta (
1269
- key TEXT PRIMARY KEY,
1270
- value TEXT NOT NULL
1271
- );
1272
-
1273
- -- Conversation history persistence
1274
- CREATE TABLE IF NOT EXISTS svara_messages (
1275
- id TEXT PRIMARY KEY,
1276
- session_id TEXT NOT NULL,
1277
- role TEXT NOT NULL CHECK(role IN ('user', 'assistant', 'system', 'tool')),
1278
- content TEXT NOT NULL,
1279
- tool_call_id TEXT,
1280
- created_at INTEGER NOT NULL DEFAULT (unixepoch())
1281
- );
1282
-
1283
- CREATE INDEX IF NOT EXISTS idx_messages_session
1284
- ON svara_messages (session_id, created_at);
1285
-
1286
- -- Session metadata
1287
- CREATE TABLE IF NOT EXISTS svara_sessions (
1288
- id TEXT PRIMARY KEY,
1289
- user_id TEXT,
1290
- channel TEXT NOT NULL,
1291
- created_at INTEGER NOT NULL DEFAULT (unixepoch()),
1292
- updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
1293
- metadata TEXT DEFAULT '{}'
1294
- );
1295
-
1296
- -- Vector store chunks for RAG
1297
- CREATE TABLE IF NOT EXISTS svara_chunks (
1298
- id TEXT PRIMARY KEY,
1299
- document_id TEXT NOT NULL,
1300
- content TEXT NOT NULL,
1301
- chunk_index INTEGER NOT NULL,
1302
- embedding BLOB, -- stored as binary float32 array
1303
- source TEXT NOT NULL,
1304
- metadata TEXT DEFAULT '{}',
1305
- created_at INTEGER NOT NULL DEFAULT (unixepoch())
1306
- );
1307
-
1308
- CREATE INDEX IF NOT EXISTS idx_chunks_document
1309
- ON svara_chunks (document_id);
1310
-
1311
- -- Document registry
1312
- CREATE TABLE IF NOT EXISTS svara_documents (
1313
- id TEXT PRIMARY KEY,
1314
- source TEXT NOT NULL UNIQUE,
1315
- type TEXT NOT NULL,
1316
- size INTEGER,
1317
- hash TEXT,
1318
- indexed_at INTEGER NOT NULL DEFAULT (unixepoch()),
1319
- metadata TEXT DEFAULT '{}'
1320
- );
1321
-
1322
- -- Key-value store for arbitrary agent state
1323
- CREATE TABLE IF NOT EXISTS svara_kv (
1324
- key TEXT PRIMARY KEY,
1325
- value TEXT NOT NULL,
1326
- expires_at INTEGER, -- unix timestamp, NULL = no expiry
1327
- updated_at INTEGER NOT NULL DEFAULT (unixepoch())
1328
- );
1329
- `;
1330
- var INSERT_META_SQL = `
1331
- INSERT OR REPLACE INTO svara_meta (key, value)
1332
- VALUES ('schema_version', ?), ('created_at', ?);
1333
- `;
1334
-
1335
- // src/database/sqlite.ts
1336
- var KVStore = class {
1337
- constructor(db) {
1338
- this.db = db;
1339
- }
1340
- db;
1341
- /** Set a key-value pair, with optional TTL in seconds. */
1342
- set(key, value, ttlSeconds) {
1343
- const expiresAt = ttlSeconds ? Math.floor(Date.now() / 1e3) + ttlSeconds : null;
1344
- this.db.prepare(`
1345
- INSERT OR REPLACE INTO svara_kv (key, value, expires_at, updated_at)
1346
- VALUES (?, ?, ?, unixepoch())
1347
- `).run(key, JSON.stringify(value), expiresAt);
1348
- }
1349
- /** Get a value by key. Returns undefined if not found or expired. */
1350
- get(key) {
1351
- const row = this.db.prepare(`
1352
- SELECT value, expires_at FROM svara_kv
1353
- WHERE key = ? AND (expires_at IS NULL OR expires_at > unixepoch())
1354
- `).get(key);
1355
- if (!row) return void 0;
1356
- return JSON.parse(row.value);
1357
- }
1358
- /** Delete a key. */
1359
- delete(key) {
1360
- this.db.prepare("DELETE FROM svara_kv WHERE key = ?").run(key);
1361
- }
1362
- /** Check if a key exists and is not expired. */
1363
- has(key) {
1364
- return this.get(key) !== void 0;
1365
- }
1366
- /** Get all keys matching a prefix. */
1367
- keys(prefix = "") {
1368
- const rows = this.db.prepare(`
1369
- SELECT key FROM svara_kv
1370
- WHERE key LIKE ? AND (expires_at IS NULL OR expires_at > unixepoch())
1371
- `).all(`${prefix}%`);
1372
- return rows.map((r) => r.key);
1373
- }
1374
- };
1375
- var SvaraDB = class {
1376
- db;
1377
- kv;
1378
- constructor(dbPath = ":memory:") {
1379
- if (dbPath !== ":memory:") {
1380
- fs.mkdirSync(path.dirname(path.resolve(dbPath)), { recursive: true });
1381
- }
1382
- this.db = this.openDatabase(dbPath);
1383
- this.configure();
1384
- this.migrate();
1385
- this.kv = new KVStore(this.db);
1386
- }
1387
- // ─── Query Helpers ────────────────────────────────────────────────────────
1388
- /**
1389
- * Run a SELECT and return all matching rows.
1390
- */
1391
- query(sql, params = []) {
1392
- return this.db.prepare(sql).all(...params);
1393
- }
1394
- /**
1395
- * Run a SELECT and return the first matching row.
1396
- */
1397
- queryOne(sql, params = []) {
1398
- return this.db.prepare(sql).get(...params);
1399
- }
1400
- /**
1401
- * Run an INSERT/UPDATE/DELETE. Returns affected row count.
1402
- */
1403
- run(sql, params = []) {
1404
- return this.db.prepare(sql).run(...params).changes;
1405
- }
1406
- /**
1407
- * Execute raw SQL (for DDL, migrations, etc.).
1408
- */
1409
- exec(sql) {
1410
- this.db.exec(sql);
1411
- }
1412
- /**
1413
- * Run multiple operations in a single transaction.
1414
- *
1415
- * @example
1416
- * db.transaction(() => {
1417
- * db.run('INSERT INTO orders ...', [...]);
1418
- * db.run('UPDATE inventory ...', [...]);
1419
- * });
1420
- */
1421
- transaction(fn) {
1422
- return this.db.transaction(fn)();
1423
- }
1424
- /**
1425
- * Close the database connection.
1426
- */
1427
- close() {
1428
- this.db.close();
1429
- }
1430
- // ─── Internal Message Storage ─────────────────────────────────────────────
1431
- saveMessage(params) {
1432
- this.db.prepare(`
1433
- INSERT OR REPLACE INTO svara_messages (id, session_id, role, content, tool_call_id)
1434
- VALUES (?, ?, ?, ?, ?)
1435
- `).run(
1436
- params.id,
1437
- params.sessionId,
1438
- params.role,
1439
- params.content,
1440
- params.toolCallId ?? null
1441
- );
1442
- }
1443
- getMessages(sessionId, limit = 50) {
1444
- return this.db.prepare(`
1445
- SELECT id, role, content, tool_call_id, created_at
1446
- FROM svara_messages
1447
- WHERE session_id = ?
1448
- ORDER BY created_at ASC
1449
- LIMIT ?
1450
- `).all(sessionId, limit);
1451
- }
1452
- clearSession(sessionId) {
1453
- this.db.prepare("DELETE FROM svara_messages WHERE session_id = ?").run(sessionId);
1454
- }
1455
- // ─── Private Setup ────────────────────────────────────────────────────────
1456
- openDatabase(dbPath) {
1457
- try {
1458
- const Database = __require("better-sqlite3");
1459
- return new Database(dbPath);
1460
- } catch {
1461
- throw new Error(
1462
- '[SvaraJS] Database requires the "better-sqlite3" package.\nRun: npm install better-sqlite3'
1463
- );
1464
- }
1465
- }
1466
- configure() {
1467
- this.db.pragma("journal_mode = WAL");
1468
- this.db.pragma("synchronous = NORMAL");
1469
- this.db.pragma("foreign_keys = ON");
1470
- }
1471
- migrate() {
1472
- this.db.exec(CREATE_TABLES_SQL);
1473
- const meta = this.db.prepare(
1474
- "SELECT value FROM svara_meta WHERE key = 'schema_version'"
1475
- ).get();
1476
- if (!meta) {
1477
- this.db.prepare(INSERT_META_SQL).run(
1478
- String(SCHEMA_VERSION),
1479
- (/* @__PURE__ */ new Date()).toISOString()
1480
- );
1481
- }
1482
- }
1483
- };
1484
-
1485
1327
  // src/index.ts
1486
1328
  init_web();
1487
1329
  init_telegram();
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VectorRetriever
3
- } from "./chunk-FEA5KIJN.mjs";
3
+ } from "./chunk-WA3BIA3S.mjs";
4
4
  import "./chunk-CIESM3BP.mjs";
5
5
  export {
6
6
  VectorRetriever
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yesvara/svara",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Build AI agents in 15 lines of code. Multi-channel, RAG-ready, production-grade.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -23,37 +23,46 @@
23
23
  }
24
24
  },
25
25
  "keywords": [
26
- "ai", "agent", "llm", "openai", "anthropic", "rag",
27
- "chatbot", "whatsapp", "telegram", "framework", "agentic"
26
+ "ai",
27
+ "agent",
28
+ "llm",
29
+ "openai",
30
+ "anthropic",
31
+ "rag",
32
+ "chatbot",
33
+ "whatsapp",
34
+ "telegram",
35
+ "framework",
36
+ "agentic"
28
37
  ],
29
38
  "author": "Yesvara Contributors",
30
39
  "license": "MIT",
31
40
  "repository": {
32
41
  "type": "git",
33
- "url": "https://github.com/yogiswara92/svara"
42
+ "url": "https://github.com/yogiswara92/svarajs"
34
43
  },
35
44
  "homepage": "https://svarajs.yesvara.com",
36
45
  "publishConfig": {
37
46
  "access": "public"
38
47
  },
39
48
  "dependencies": {
40
- "express": "^4.18.2",
41
- "better-sqlite3": "^9.4.3",
42
- "openai": "^4.47.1",
43
- "@anthropic-ai/sdk": "^0.24.3",
44
- "zod": "^3.23.8",
49
+ "@anthropic-ai/sdk": ">=0.20.0",
50
+ "better-sqlite3": "^9.6.0",
45
51
  "chalk": "^5.3.0",
46
52
  "commander": "^12.1.0",
47
53
  "dotenv": "^16.4.5",
54
+ "express": "^4.18.2",
48
55
  "glob": "^10.4.1",
49
- "pdf-parse": "^1.1.1",
56
+ "inquirer": "^9.3.2",
50
57
  "mammoth": "^1.7.2",
58
+ "openai": "^6.34.0",
51
59
  "ora": "^8.0.1",
52
- "inquirer": "^9.3.2"
60
+ "pdf-parse": "^1.1.1",
61
+ "zod": "^3.23.8"
53
62
  },
54
63
  "devDependencies": {
55
- "@types/express": "^4.17.21",
56
64
  "@types/better-sqlite3": "^7.6.10",
65
+ "@types/express": "^4.17.21",
57
66
  "@types/node": "^20.14.2",
58
67
  "@types/pdf-parse": "^1.1.4",
59
68
  "tsup": "^8.1.0",
@@ -61,12 +70,15 @@
61
70
  "vitest": "^1.6.0"
62
71
  },
63
72
  "peerDependencies": {
64
- "openai": ">=4.0.0",
65
73
  "@anthropic-ai/sdk": ">=0.20.0"
66
74
  },
67
75
  "peerDependenciesMeta": {
68
- "openai": { "optional": true },
69
- "@anthropic-ai/sdk": { "optional": true }
76
+ "openai": {
77
+ "optional": true
78
+ },
79
+ "@anthropic-ai/sdk": {
80
+ "optional": true
81
+ }
70
82
  },
71
83
  "engines": {
72
84
  "node": ">=18.0.0"
package/src/core/agent.ts CHANGED
@@ -53,6 +53,7 @@ import type {
53
53
  import { ConversationMemory } from '../memory/conversation.js';
54
54
  import { ContextBuilder } from '../memory/context.js';
55
55
  import { ToolRegistry } from '../tools/registry.js';
56
+ import { SvaraDB } from '../database/sqlite.js';
56
57
  import { ToolExecutor } from '../tools/executor.js';
57
58
  import type { Tool } from '../types.js';
58
59
 
@@ -169,8 +170,10 @@ export class SvaraAgent extends EventEmitter {
169
170
 
170
171
  private channels: Map<ChannelName, SvaraChannel> = new Map();
171
172
  private knowledgeBase: KnowledgeBase | null = null;
173
+ private retriever: any = null; // Store VectorRetriever for retrieveChunks access
172
174
  private knowledgePaths: string[] = [];
173
175
  private isStarted = false;
176
+ private db: SvaraDB;
174
177
 
175
178
  constructor(config: AgentConfig) {
176
179
  super();
@@ -178,6 +181,7 @@ export class SvaraAgent extends EventEmitter {
178
181
  this.name = config.name;
179
182
  this.maxIterations = config.maxIterations ?? 10;
180
183
  this.verbose = config.verbose ?? false;
184
+ this.db = new SvaraDB('./data/svara.db');
181
185
 
182
186
  this.systemPrompt = config.systemPrompt
183
187
  ?? `You are ${config.name}, a helpful and friendly AI assistant. Be concise and accurate.`;
@@ -324,6 +328,7 @@ export class SvaraAgent extends EventEmitter {
324
328
  sessionId: result.sessionId,
325
329
  usage: result.usage,
326
330
  toolsUsed: result.toolsUsed,
331
+ retrievedDocuments: result.retrievedDocuments || [],
327
332
  });
328
333
  } catch (err) {
329
334
  const error = err as Error;
@@ -405,6 +410,58 @@ export class SvaraAgent extends EventEmitter {
405
410
  }
406
411
  }
407
412
 
413
+ // ─── Internal: User & Session Tracking ───────────────────────────────────────
414
+
415
+ private async trackUserAndSession(userId: string, sessionId: string, channel = 'api'): Promise<void> {
416
+ try {
417
+ // Track user
418
+ const existingUser = this.db.query(
419
+ 'SELECT id FROM svara_users WHERE id = ?',
420
+ [userId]
421
+ ) as Array<{ id: string }>;
422
+
423
+ if (existingUser.length === 0) {
424
+ // New user
425
+ this.db.run(
426
+ `INSERT INTO svara_users (id, display_name, first_seen, last_seen)
427
+ VALUES (?, ?, unixepoch(), unixepoch())`,
428
+ [userId, userId]
429
+ );
430
+ } else {
431
+ // Update last_seen
432
+ this.db.run(
433
+ 'UPDATE svara_users SET last_seen = unixepoch() WHERE id = ?',
434
+ [userId]
435
+ );
436
+ }
437
+
438
+ // Track session
439
+ const existingSession = this.db.query(
440
+ 'SELECT id FROM svara_sessions WHERE id = ?',
441
+ [sessionId]
442
+ ) as Array<{ id: string }>;
443
+
444
+ if (existingSession.length === 0) {
445
+ // New session
446
+ this.db.run(
447
+ `INSERT INTO svara_sessions (id, user_id, channel, created_at, updated_at)
448
+ VALUES (?, ?, ?, unixepoch(), unixepoch())`,
449
+ [sessionId, userId, channel]
450
+ );
451
+ } else {
452
+ // Update updated_at
453
+ this.db.run(
454
+ 'UPDATE svara_sessions SET updated_at = unixepoch() WHERE id = ?',
455
+ [sessionId]
456
+ );
457
+ }
458
+
459
+ this.log('debug', `Tracked user ${userId} with session ${sessionId}`);
460
+ } catch (error) {
461
+ this.log('error', `Failed to track user: ${(error as Error).message}`);
462
+ }
463
+ }
464
+
408
465
  // ─── Internal: Agentic Loop ───────────────────────────────────────────────
409
466
 
410
467
  /**
@@ -419,18 +476,40 @@ export class SvaraAgent extends EventEmitter {
419
476
  }
420
477
 
421
478
  private async run(message: string, options: AgentRunOptions): Promise<AgentRunResult> {
479
+ console.log(`\n[RUN START] kb=${!!this.knowledgeBase} ret=${!!this.retriever}`);
422
480
  const startTime = Date.now();
423
481
  const sessionId = options.sessionId ?? crypto.randomUUID();
482
+ const userId = options.userId ?? 'unknown';
483
+
484
+ // Track user and session
485
+ await this.trackUserAndSession(userId, sessionId);
424
486
 
425
- this.emit('message:received', { message, sessionId, userId: options.userId });
487
+ this.emit('message:received', { message, sessionId, userId });
426
488
 
427
489
  // Build LLM message history
428
490
  const history = await this.memory.getHistory(sessionId);
429
491
 
430
492
  // RAG retrieval
431
493
  let ragContext = '';
432
- if (this.knowledgeBase) {
494
+ let retrievedDocuments: Array<{ source: string; score: number; excerpt: string }> = [];
495
+ if (this.knowledgeBase && this.retriever) {
433
496
  ragContext = await this.knowledgeBase.retrieve(message);
497
+ // Also retrieve chunks to get document metadata and scores
498
+ try {
499
+ console.log(`[DEBUG] Calling retrieveChunks for query: "${message}"`);
500
+ const context = await this.retriever.retrieveChunks(message, 3);
501
+ console.log(`[DEBUG] Retrieved ${context.chunks.length} chunks`);
502
+ retrievedDocuments = context.chunks.map((item: any) => ({
503
+ source: item.chunk?.source || 'unknown',
504
+ score: Math.round(item.score * 100) / 100,
505
+ excerpt: item.chunk?.content?.substring(0, 150) || '',
506
+ }));
507
+ console.log(`[DEBUG] Mapped ${retrievedDocuments.length} documents`);
508
+ } catch (e) {
509
+ console.error(`[ERROR] RAG retrieval failed:`, e);
510
+ }
511
+ } else {
512
+ console.log(`[DEBUG] No knowledgeBase (${!!this.knowledgeBase}) or retriever (${!!this.retriever})`);
434
513
  }
435
514
 
436
515
  const messages = this.context.buildMessages(
@@ -442,7 +521,7 @@ export class SvaraAgent extends EventEmitter {
442
521
 
443
522
  const internalCtx: InternalAgentContext = {
444
523
  sessionId,
445
- userId: options.userId ?? 'unknown',
524
+ userId,
446
525
  agentName: this.name,
447
526
  history,
448
527
  metadata: options.metadata ?? {},
@@ -521,6 +600,7 @@ export class SvaraAgent extends EventEmitter {
521
600
  iterations,
522
601
  usage: totalUsage,
523
602
  duration: Date.now() - startTime,
603
+ retrievedDocuments: retrievedDocuments.length > 0 ? retrievedDocuments : undefined,
524
604
  };
525
605
 
526
606
  this.emit('message:sent', { response: finalResponse, sessionId });
@@ -534,8 +614,9 @@ export class SvaraAgent extends EventEmitter {
534
614
  const { glob } = await import('glob');
535
615
  const { VectorRetriever } = await import('../rag/retriever.js');
536
616
 
537
- const retriever = new VectorRetriever();
538
- await retriever.init({ embeddings: { provider: 'openai' } });
617
+ // Create retriever with agent name for isolated RAG per agent
618
+ this.retriever = new VectorRetriever(this.name, this.db);
619
+ await this.retriever.init({ embeddings: { provider: 'openai' } });
539
620
 
540
621
  const files: string[] = [];
541
622
  for (const pattern of paths) {
@@ -548,16 +629,16 @@ export class SvaraAgent extends EventEmitter {
548
629
  return;
549
630
  }
550
631
 
551
- await retriever.addDocuments(files);
632
+ await this.retriever.addDocuments(files);
552
633
  this.knowledgeBase = {
553
634
  load: async (p) => {
554
635
  const newFiles: string[] = [];
555
636
  for (const pattern of (Array.isArray(p) ? p : [p])) {
556
637
  newFiles.push(...await glob(pattern));
557
638
  }
558
- await retriever.addDocuments(newFiles);
639
+ await this.retriever.addDocuments(newFiles);
559
640
  },
560
- retrieve: (query, topK) => retriever.retrieve(query, topK),
641
+ retrieve: (query, topK) => this.retriever.retrieve(query, topK),
561
642
  };
562
643
 
563
644
  this.log('info', `Knowledge base loaded: ${files.length} file(s).`);