@juspay/neurolink 9.87.4 → 9.88.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.
@@ -1271,6 +1271,86 @@ User message: "${userMessage}"`;
1271
1271
  return results;
1272
1272
  }
1273
1273
  }
1274
+ /**
1275
+ * List all sessions with metadata for CLI/API usage
1276
+ * Implements IConversationMemoryManager.listSessions
1277
+ * @param userId - Optional user identifier to filter sessions
1278
+ * @returns Array of session list items with metadata
1279
+ */
1280
+ async listSessions(userId) {
1281
+ await this.ensureInitialized();
1282
+ if (!this.redisClient) {
1283
+ return [];
1284
+ }
1285
+ const now = Date.now();
1286
+ const list = [];
1287
+ if (userId) {
1288
+ // List sessions for a specific user
1289
+ const sessionIds = await this.getUserSessions(userId);
1290
+ for (const sessionId of sessionIds) {
1291
+ const session = await this.getUserSessionObject(userId, sessionId);
1292
+ if (!session) {
1293
+ continue;
1294
+ }
1295
+ list.push({
1296
+ id: session.sessionId,
1297
+ title: session.title || session.sessionId,
1298
+ createdAt: session.createdAt,
1299
+ updatedAt: session.updatedAt,
1300
+ userId: session.userId,
1301
+ messageCount: session.messages.length,
1302
+ lastActive: this.formatTimeAgo(now - new Date(session.updatedAt).getTime()),
1303
+ });
1304
+ }
1305
+ }
1306
+ else {
1307
+ // List all sessions across all users by scanning Redis keys
1308
+ const keys = await scanKeys(this.redisClient, `${this.redisConfig.keyPrefix}*`);
1309
+ for (const key of keys) {
1310
+ // Skip user session index keys (they end with :sessions)
1311
+ if (key.endsWith(":sessions")) {
1312
+ continue;
1313
+ }
1314
+ const raw = await this.redisClient.get(key);
1315
+ if (!raw) {
1316
+ continue;
1317
+ }
1318
+ const session = deserializeConversation(raw);
1319
+ if (!session) {
1320
+ continue;
1321
+ }
1322
+ list.push({
1323
+ id: session.sessionId,
1324
+ title: session.title || session.sessionId,
1325
+ createdAt: session.createdAt,
1326
+ updatedAt: session.updatedAt,
1327
+ userId: session.userId,
1328
+ messageCount: session.messages.length,
1329
+ lastActive: this.formatTimeAgo(now - new Date(session.updatedAt).getTime()),
1330
+ });
1331
+ }
1332
+ }
1333
+ return list.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
1334
+ }
1335
+ /**
1336
+ * Format milliseconds into human-readable time ago string
1337
+ */
1338
+ formatTimeAgo(ms) {
1339
+ const seconds = Math.floor(ms / 1000);
1340
+ const minutes = Math.floor(seconds / 60);
1341
+ const hours = Math.floor(minutes / 60);
1342
+ const days = Math.floor(hours / 24);
1343
+ if (days > 0) {
1344
+ return `${days} day${days > 1 ? "s" : ""} ago`;
1345
+ }
1346
+ if (hours > 0) {
1347
+ return `${hours} hour${hours > 1 ? "s" : ""} ago`;
1348
+ }
1349
+ if (minutes > 0) {
1350
+ return `${minutes} minute${minutes > 1 ? "s" : ""} ago`;
1351
+ }
1352
+ return "just now";
1353
+ }
1274
1354
  /**
1275
1355
  * Clean up stale pending tool execution data
1276
1356
  * Removes data older than 5 minutes to prevent memory leaks
@@ -56,6 +56,12 @@ export declare const directAgentTools: {
56
56
  path: string;
57
57
  includeHidden: boolean;
58
58
  }, {
59
+ success: boolean;
60
+ error: string;
61
+ path?: undefined;
62
+ items?: undefined;
63
+ count?: undefined;
64
+ } | {
59
65
  success: boolean;
60
66
  path: string;
61
67
  items: {
@@ -14,6 +14,28 @@ function truncateOutput(output) {
14
14
  }
15
15
  return output;
16
16
  }
17
+ /**
18
+ * Contain a built-in file tool to the process working directory.
19
+ *
20
+ * Returns the resolved absolute path when it lies inside `process.cwd()`, or an
21
+ * `error` string otherwise. This is the sandbox for the default-enabled
22
+ * `readFile` / `writeFile` / `listDirectory` / `analyzeCSV` tools: without it an
23
+ * agent can pass an absolute path (`/etc/passwd`) or `../` traversal and reach
24
+ * arbitrary files. The previous guard (`!resolvedPath.startsWith(cwd) &&
25
+ * !path.isAbsolute(filePath)`) was always false for absolute paths, so it never
26
+ * fired. The `cwd + path.sep` suffix prevents a sibling-prefix bypass
27
+ * (`/home/app` vs `/home/app-evil`).
28
+ */
29
+ function resolveWithinCwd(filePath) {
30
+ const resolvedPath = path.resolve(filePath);
31
+ const cwd = path.resolve(process.cwd());
32
+ if (resolvedPath !== cwd && !resolvedPath.startsWith(cwd + path.sep)) {
33
+ return {
34
+ error: `Access denied: "${filePath}" resolves outside the working directory`,
35
+ };
36
+ }
37
+ return { path: resolvedPath };
38
+ }
17
39
  // Runtime Google Search tool creation - bypasses TypeScript strict typing
18
40
  function createGoogleSearchTools() {
19
41
  const searchTool = {};
@@ -71,15 +93,13 @@ export const directAgentTools = {
71
93
  }),
72
94
  execute: async ({ path: filePath }) => {
73
95
  try {
74
- // Security check - prevent reading outside current directory for relative paths
75
- const resolvedPath = path.resolve(filePath);
76
- const cwd = process.cwd();
77
- if (!resolvedPath.startsWith(cwd) && !path.isAbsolute(filePath)) {
78
- return {
79
- success: false,
80
- error: `Access denied: Cannot read files outside current directory`,
81
- };
96
+ // Sandbox: contain reads to the working directory (blocks absolute-path
97
+ // escape and ../ traversal).
98
+ const guard = resolveWithinCwd(filePath);
99
+ if ("error" in guard) {
100
+ return { success: false, error: guard.error };
82
101
  }
102
+ const resolvedPath = guard.path;
83
103
  const content = fs.readFileSync(resolvedPath, "utf-8");
84
104
  const stats = fs.statSync(resolvedPath);
85
105
  return {
@@ -113,7 +133,12 @@ export const directAgentTools = {
113
133
  }),
114
134
  execute: async ({ path: dirPath, includeHidden }) => {
115
135
  try {
116
- const resolvedPath = path.resolve(dirPath);
136
+ // Sandbox: contain directory listing to the working directory.
137
+ const guard = resolveWithinCwd(dirPath);
138
+ if ("error" in guard) {
139
+ return { success: false, error: guard.error };
140
+ }
141
+ const resolvedPath = guard.path;
117
142
  const items = fs.readdirSync(resolvedPath);
118
143
  const filteredItems = includeHidden
119
144
  ? items
@@ -229,15 +254,13 @@ export const directAgentTools = {
229
254
  }),
230
255
  execute: async ({ path: filePath, content, mode }) => {
231
256
  try {
232
- const resolvedPath = path.resolve(filePath);
233
- const cwd = process.cwd();
234
- // Security check
235
- if (!resolvedPath.startsWith(cwd) && !path.isAbsolute(filePath)) {
236
- return {
237
- success: false,
238
- error: `Access denied: Cannot write files outside current directory`,
239
- };
257
+ // Sandbox: contain writes to the working directory (blocks absolute-path
258
+ // escape and ../ traversal).
259
+ const guard = resolveWithinCwd(filePath);
260
+ if ("error" in guard) {
261
+ return { success: false, error: guard.error };
240
262
  }
263
+ const resolvedPath = guard.path;
241
264
  // Check if file exists for create mode
242
265
  if (mode === "create" && fs.existsSync(resolvedPath)) {
243
266
  return {
@@ -326,11 +349,12 @@ export const directAgentTools = {
326
349
  try {
327
350
  // Resolve file path
328
351
  logger.debug(`[analyzeCSV] Resolving file: ${filePath}`);
329
- const path = await import("path");
330
- // Resolve path (support both relative and absolute)
331
- const resolvedPath = path.isAbsolute(filePath)
332
- ? filePath
333
- : path.resolve(process.cwd(), filePath);
352
+ // Sandbox: contain CSV reads to the working directory.
353
+ const guard = resolveWithinCwd(filePath);
354
+ if ("error" in guard) {
355
+ return { success: false, error: guard.error };
356
+ }
357
+ const resolvedPath = guard.path;
334
358
  logger.debug(`[analyzeCSV] Resolved path: ${resolvedPath}`);
335
359
  // Parse CSV using streaming from disk (memory efficient)
336
360
  logger.info(`[analyzeCSV] Starting CSV parsing (max ${maxRows} rows)...`);
@@ -2,7 +2,7 @@
2
2
  * Conversation Memory Manager for NeuroLink
3
3
  * Handles in-memory conversation storage, session management, and context injection
4
4
  */
5
- import type { ChatMessage, ConversationMemoryConfig, ConversationMemoryStats, SessionMemory, StoreConversationTurnOptions, IConversationMemoryManager } from "../types/index.js";
5
+ import type { ChatMessage, ConversationMemoryConfig, ConversationMemoryStats, SessionListItem, SessionMemory, StoreConversationTurnOptions, IConversationMemoryManager } from "../types/index.js";
6
6
  export declare class ConversationMemoryManager implements IConversationMemoryManager {
7
7
  private sessions;
8
8
  config: ConversationMemoryConfig;
@@ -56,6 +56,16 @@ export declare class ConversationMemoryManager implements IConversationMemoryMan
56
56
  private createNewSession;
57
57
  private enforceSessionLimit;
58
58
  getStats(): Promise<ConversationMemoryStats>;
59
+ /**
60
+ * List all sessions with metadata
61
+ * @param userId - Optional user ID to filter sessions
62
+ * @returns Array of session list items with metadata
63
+ */
64
+ listSessions(userId?: string): Promise<SessionListItem[]>;
65
+ /**
66
+ * Format milliseconds into human-readable time ago string
67
+ */
68
+ private formatTimeAgo;
59
69
  clearSession(sessionId: string): Promise<boolean>;
60
70
  clearAllSessions(): Promise<void>;
61
71
  /**
@@ -292,6 +292,50 @@ export class ConversationMemoryManager {
292
292
  totalTurns,
293
293
  };
294
294
  }
295
+ /**
296
+ * List all sessions with metadata
297
+ * @param userId - Optional user ID to filter sessions
298
+ * @returns Array of session list items with metadata
299
+ */
300
+ async listSessions(userId) {
301
+ await this.ensureInitialized();
302
+ const sessions = Array.from(this.sessions.values());
303
+ const now = Date.now();
304
+ return sessions
305
+ .filter((session) => !userId || session.userId === userId)
306
+ .map((session) => {
307
+ const lastActive = this.formatTimeAgo(now - session.lastActivity);
308
+ return {
309
+ id: session.sessionId,
310
+ title: session.sessionId, // In-memory doesn't store title, use sessionId
311
+ createdAt: new Date(session.createdAt).toISOString(),
312
+ updatedAt: new Date(session.lastActivity).toISOString(),
313
+ userId: session.userId,
314
+ messageCount: session.messages.length,
315
+ lastActive,
316
+ };
317
+ })
318
+ .sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
319
+ }
320
+ /**
321
+ * Format milliseconds into human-readable time ago string
322
+ */
323
+ formatTimeAgo(ms) {
324
+ const seconds = Math.floor(ms / 1000);
325
+ const minutes = Math.floor(seconds / 60);
326
+ const hours = Math.floor(minutes / 60);
327
+ const days = Math.floor(hours / 24);
328
+ if (days > 0) {
329
+ return `${days} day${days > 1 ? "s" : ""} ago`;
330
+ }
331
+ if (hours > 0) {
332
+ return `${hours} hour${hours > 1 ? "s" : ""} ago`;
333
+ }
334
+ if (minutes > 0) {
335
+ return `${minutes} minute${minutes > 1 ? "s" : ""} ago`;
336
+ }
337
+ return "just now";
338
+ }
295
339
  async clearSession(sessionId) {
296
340
  return withSpan({
297
341
  name: "neurolink.memory.clear",
@@ -2,7 +2,7 @@
2
2
  * Redis Conversation Memory Manager for NeuroLink
3
3
  * Redis-based implementation of conversation storage with same interface as ConversationMemoryManager
4
4
  */
5
- import type { ChatMessage, ConversationMemoryConfig, ConversationMemoryStats, RedisConversationObject, RedisStorageConfig, SessionMemory, SessionMetadata, StoreConversationTurnOptions, AgenticLoopReportMetadata, IConversationMemoryManager } from "../types/index.js";
5
+ import type { ChatMessage, ConversationMemoryConfig, ConversationMemoryStats, RedisConversationObject, RedisStorageConfig, SessionListItem, SessionMemory, SessionMetadata, StoreConversationTurnOptions, AgenticLoopReportMetadata, IConversationMemoryManager } from "../types/index.js";
6
6
  /**
7
7
  * Redis-based implementation of the ConversationMemoryManager
8
8
  * Uses the same interface but stores data in Redis
@@ -175,6 +175,17 @@ export declare class RedisConversationMemoryManager implements IConversationMemo
175
175
  * @returns Array of session metadata objects
176
176
  */
177
177
  getUserAllSessionsHistory(userId: string): Promise<SessionMetadata[]>;
178
+ /**
179
+ * List all sessions with metadata for CLI/API usage
180
+ * Implements IConversationMemoryManager.listSessions
181
+ * @param userId - Optional user identifier to filter sessions
182
+ * @returns Array of session list items with metadata
183
+ */
184
+ listSessions(userId?: string): Promise<SessionListItem[]>;
185
+ /**
186
+ * Format milliseconds into human-readable time ago string
187
+ */
188
+ private formatTimeAgo;
178
189
  /**
179
190
  * Clean up stale pending tool execution data
180
191
  * Removes data older than 5 minutes to prevent memory leaks
@@ -1271,6 +1271,86 @@ User message: "${userMessage}"`;
1271
1271
  return results;
1272
1272
  }
1273
1273
  }
1274
+ /**
1275
+ * List all sessions with metadata for CLI/API usage
1276
+ * Implements IConversationMemoryManager.listSessions
1277
+ * @param userId - Optional user identifier to filter sessions
1278
+ * @returns Array of session list items with metadata
1279
+ */
1280
+ async listSessions(userId) {
1281
+ await this.ensureInitialized();
1282
+ if (!this.redisClient) {
1283
+ return [];
1284
+ }
1285
+ const now = Date.now();
1286
+ const list = [];
1287
+ if (userId) {
1288
+ // List sessions for a specific user
1289
+ const sessionIds = await this.getUserSessions(userId);
1290
+ for (const sessionId of sessionIds) {
1291
+ const session = await this.getUserSessionObject(userId, sessionId);
1292
+ if (!session) {
1293
+ continue;
1294
+ }
1295
+ list.push({
1296
+ id: session.sessionId,
1297
+ title: session.title || session.sessionId,
1298
+ createdAt: session.createdAt,
1299
+ updatedAt: session.updatedAt,
1300
+ userId: session.userId,
1301
+ messageCount: session.messages.length,
1302
+ lastActive: this.formatTimeAgo(now - new Date(session.updatedAt).getTime()),
1303
+ });
1304
+ }
1305
+ }
1306
+ else {
1307
+ // List all sessions across all users by scanning Redis keys
1308
+ const keys = await scanKeys(this.redisClient, `${this.redisConfig.keyPrefix}*`);
1309
+ for (const key of keys) {
1310
+ // Skip user session index keys (they end with :sessions)
1311
+ if (key.endsWith(":sessions")) {
1312
+ continue;
1313
+ }
1314
+ const raw = await this.redisClient.get(key);
1315
+ if (!raw) {
1316
+ continue;
1317
+ }
1318
+ const session = deserializeConversation(raw);
1319
+ if (!session) {
1320
+ continue;
1321
+ }
1322
+ list.push({
1323
+ id: session.sessionId,
1324
+ title: session.title || session.sessionId,
1325
+ createdAt: session.createdAt,
1326
+ updatedAt: session.updatedAt,
1327
+ userId: session.userId,
1328
+ messageCount: session.messages.length,
1329
+ lastActive: this.formatTimeAgo(now - new Date(session.updatedAt).getTime()),
1330
+ });
1331
+ }
1332
+ }
1333
+ return list.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
1334
+ }
1335
+ /**
1336
+ * Format milliseconds into human-readable time ago string
1337
+ */
1338
+ formatTimeAgo(ms) {
1339
+ const seconds = Math.floor(ms / 1000);
1340
+ const minutes = Math.floor(seconds / 60);
1341
+ const hours = Math.floor(minutes / 60);
1342
+ const days = Math.floor(hours / 24);
1343
+ if (days > 0) {
1344
+ return `${days} day${days > 1 ? "s" : ""} ago`;
1345
+ }
1346
+ if (hours > 0) {
1347
+ return `${hours} hour${hours > 1 ? "s" : ""} ago`;
1348
+ }
1349
+ if (minutes > 0) {
1350
+ return `${minutes} minute${minutes > 1 ? "s" : ""} ago`;
1351
+ }
1352
+ return "just now";
1353
+ }
1274
1354
  /**
1275
1355
  * Clean up stale pending tool execution data
1276
1356
  * Removes data older than 5 minutes to prevent memory leaks
@@ -14,6 +14,7 @@ import { MCPToolRegistry } from "./mcp/toolRegistry.js";
14
14
  import type { DynamicOptions } from "./types/index.js";
15
15
  import { SkillsManager } from "./skills/skillsManager.js";
16
16
  import { TaskManager } from "./tasks/taskManager.js";
17
+ import type { SessionExport, SessionListItem } from "./types/index.js";
17
18
  /**
18
19
  * Curator P2-4 dedup (concurrency-safe): native providers emit
19
20
  * `generation:end` on the shared SDK emitter. We attach a fresh
@@ -1631,6 +1632,32 @@ export declare class NeuroLink {
1631
1632
  * Clear all conversation history (public API)
1632
1633
  */
1633
1634
  clearAllConversations(): Promise<void>;
1635
+ /**
1636
+ * List all conversation sessions with metadata (public API)
1637
+ * @param userId - Optional user ID to filter sessions (required for Redis storage)
1638
+ * @returns Array of session list items with metadata
1639
+ */
1640
+ listSessions(userId?: string): Promise<SessionListItem[]>;
1641
+ /**
1642
+ * Export a single session with full history and metadata (public API)
1643
+ * @param sessionId - The session ID to export
1644
+ * @param options - Export options
1645
+ * @returns Session export object with full history
1646
+ */
1647
+ exportSession(sessionId: string, options?: {
1648
+ includeMetadata?: boolean;
1649
+ format?: "json" | "csv";
1650
+ }): Promise<SessionExport | null>;
1651
+ /**
1652
+ * Export all sessions for a user (public API)
1653
+ * @param userId - Optional user ID (required for Redis storage)
1654
+ * @param options - Export options
1655
+ * @returns Array of session exports
1656
+ */
1657
+ exportAllSessions(userId?: string, options?: {
1658
+ includeMetadata?: boolean;
1659
+ format?: "json" | "csv";
1660
+ }): Promise<SessionExport[]>;
1634
1661
  /**
1635
1662
  * Store tool executions in conversation memory if enabled and Redis is configured
1636
1663
  * @param sessionId - Session identifier
@@ -10504,6 +10504,139 @@ Current user's request: ${currentInput}`;
10504
10504
  this.lastCompactionMessageCount.clear();
10505
10505
  await this.conversationMemory.clearAllSessions();
10506
10506
  }
10507
+ /**
10508
+ * List all conversation sessions with metadata (public API)
10509
+ * @param userId - Optional user ID to filter sessions (required for Redis storage)
10510
+ * @returns Array of session list items with metadata
10511
+ */
10512
+ async listSessions(userId) {
10513
+ // First ensure memory is initialized
10514
+ const initId = `list-sessions-init-${Date.now()}`;
10515
+ await this.initializeConversationMemoryForGeneration(initId, Date.now(), process.hrtime.bigint());
10516
+ if (!this.conversationMemory) {
10517
+ throw new Error("Conversation memory is not enabled");
10518
+ }
10519
+ // Check if listSessions is available on the memory manager
10520
+ if (!this.conversationMemory.listSessions) {
10521
+ logger.warn("listSessions not available on current memory manager");
10522
+ return [];
10523
+ }
10524
+ const MEMORY_OPERATION_TIMEOUT = 30000; // 30 seconds
10525
+ try {
10526
+ const sessions = await withTimeout(this.conversationMemory.listSessions(userId), MEMORY_OPERATION_TIMEOUT, new Error("listSessions operation timed out after 30s"));
10527
+ logger.debug("Listed conversation sessions", {
10528
+ userId,
10529
+ sessionCount: sessions.length,
10530
+ });
10531
+ return sessions;
10532
+ }
10533
+ catch (error) {
10534
+ logger.error("Failed to list conversation sessions", {
10535
+ userId,
10536
+ error: error instanceof Error ? error.message : String(error),
10537
+ });
10538
+ return [];
10539
+ }
10540
+ }
10541
+ /**
10542
+ * Export a single session with full history and metadata (public API)
10543
+ * @param sessionId - The session ID to export
10544
+ * @param options - Export options
10545
+ * @returns Session export object with full history
10546
+ */
10547
+ async exportSession(sessionId, options = {}) {
10548
+ // First ensure memory is initialized
10549
+ const initId = `export-session-init-${Date.now()}`;
10550
+ await this.initializeConversationMemoryForGeneration(initId, Date.now(), process.hrtime.bigint());
10551
+ if (!this.conversationMemory) {
10552
+ throw new Error("Conversation memory is not enabled");
10553
+ }
10554
+ if (!sessionId || typeof sessionId !== "string") {
10555
+ throw new Error("Session ID must be a non-empty string");
10556
+ }
10557
+ const MEMORY_OPERATION_TIMEOUT = 30000; // 30 seconds
10558
+ try {
10559
+ const messages = await withTimeout(this.conversationMemory.buildContextMessages(sessionId), MEMORY_OPERATION_TIMEOUT, new Error("buildContextMessages operation timed out after 30s"));
10560
+ if (messages.length === 0) {
10561
+ logger.debug("No messages found for session export", { sessionId });
10562
+ return null;
10563
+ }
10564
+ const sessionResult = this.conversationMemory.getSession(sessionId);
10565
+ const session = await withTimeout(sessionResult instanceof Promise
10566
+ ? sessionResult
10567
+ : Promise.resolve(sessionResult), MEMORY_OPERATION_TIMEOUT, new Error("getSession operation timed out after 30s"));
10568
+ const now = new Date().toISOString();
10569
+ const exportData = {
10570
+ sessionId,
10571
+ title: sessionId, // Use sessionId as title if not available
10572
+ userId: session?.userId,
10573
+ createdAt: session?.createdAt
10574
+ ? new Date(session.createdAt).toISOString()
10575
+ : now,
10576
+ updatedAt: session?.lastActivity
10577
+ ? new Date(session.lastActivity).toISOString()
10578
+ : now,
10579
+ messages,
10580
+ };
10581
+ if (options.includeMetadata) {
10582
+ exportData.exportMetadata = {
10583
+ exportedAt: now,
10584
+ exportFormat: options.format || "json",
10585
+ };
10586
+ }
10587
+ logger.debug("Exported conversation session", {
10588
+ sessionId,
10589
+ messageCount: messages.length,
10590
+ });
10591
+ return exportData;
10592
+ }
10593
+ catch (error) {
10594
+ logger.error("Failed to export conversation session", {
10595
+ sessionId,
10596
+ error: error instanceof Error ? error.message : String(error),
10597
+ });
10598
+ return null;
10599
+ }
10600
+ }
10601
+ /**
10602
+ * Export all sessions for a user (public API)
10603
+ * @param userId - Optional user ID (required for Redis storage)
10604
+ * @param options - Export options
10605
+ * @returns Array of session exports
10606
+ */
10607
+ async exportAllSessions(userId, options = {}) {
10608
+ // First ensure memory is initialized
10609
+ const initId = `export-all-init-${Date.now()}`;
10610
+ await this.initializeConversationMemoryForGeneration(initId, Date.now(), process.hrtime.bigint());
10611
+ if (!this.conversationMemory) {
10612
+ throw new Error("Conversation memory is not enabled");
10613
+ }
10614
+ const MEMORY_OPERATION_TIMEOUT = 30000; // 30 seconds
10615
+ const EXPORT_SESSION_TIMEOUT = 60000; // 60 seconds for full export
10616
+ try {
10617
+ // Get all session IDs
10618
+ const sessions = await withTimeout(this.listSessions(userId), MEMORY_OPERATION_TIMEOUT, new Error("listSessions operation timed out after 30s"));
10619
+ const exports = [];
10620
+ for (const session of sessions) {
10621
+ const exportData = await withTimeout(this.exportSession(session.id, options), EXPORT_SESSION_TIMEOUT, new Error(`exportSession operation timed out after 60s for session ${session.id}`));
10622
+ if (exportData) {
10623
+ exports.push(exportData);
10624
+ }
10625
+ }
10626
+ logger.debug("Exported all conversation sessions", {
10627
+ userId,
10628
+ sessionCount: exports.length,
10629
+ });
10630
+ return exports;
10631
+ }
10632
+ catch (error) {
10633
+ logger.error("Failed to export all conversation sessions", {
10634
+ userId,
10635
+ error: error instanceof Error ? error.message : String(error),
10636
+ });
10637
+ return [];
10638
+ }
10639
+ }
10507
10640
  /**
10508
10641
  * Store tool executions in conversation memory if enabled and Redis is configured
10509
10642
  * @param sessionId - Session identifier
@@ -451,6 +451,42 @@ export type AgenticLoopReportMetadata = {
451
451
  endDate: string;
452
452
  };
453
453
  };
454
+ /**
455
+ * Session list item for CLI/API listing
456
+ * Extends SessionMetadata with additional display information
457
+ */
458
+ export type SessionListItem = SessionMetadata & {
459
+ /** User identifier associated with this session */
460
+ userId?: string;
461
+ /** Total number of messages in this session */
462
+ messageCount: number;
463
+ /** Human-readable time since last activity (e.g., "2 hours ago") */
464
+ lastActive?: string;
465
+ };
466
+ /**
467
+ * Complete session export format for backup/analytics
468
+ * Contains full session data including all messages
469
+ */
470
+ export type SessionExport = {
471
+ /** Session identifier */
472
+ sessionId: string;
473
+ /** Session title/description */
474
+ title?: string;
475
+ /** User identifier */
476
+ userId?: string;
477
+ /** When session was created (ISO 8601) */
478
+ createdAt: string;
479
+ /** When session was last updated (ISO 8601) */
480
+ updatedAt: string;
481
+ /** Complete message history */
482
+ messages: ChatMessage[];
483
+ /** Export metadata */
484
+ exportMetadata?: {
485
+ exportedAt: string;
486
+ exportFormat: "json" | "csv";
487
+ neuroLinkVersion?: string;
488
+ };
489
+ };
454
490
  /**
455
491
  * Base conversation metadata (shared fields across all conversation types)
456
492
  * Contains essential conversation information without heavy data arrays
@@ -3,7 +3,7 @@
3
3
  * Both ConversationMemoryManager and RedisConversationMemoryManager
4
4
  * should implement this type.
5
5
  */
6
- import type { ChatMessage, ConversationMemoryConfig, ConversationMemoryStats, SessionMemory, StoreConversationTurnOptions } from "./conversation.js";
6
+ import type { ChatMessage, ConversationMemoryConfig, ConversationMemoryStats, SessionListItem, SessionMemory, StoreConversationTurnOptions } from "./conversation.js";
7
7
  /**
8
8
  * Common type for all conversation memory manager implementations.
9
9
  * Provides a consistent API for storing, retrieving, and managing conversation history.
@@ -24,6 +24,10 @@ export type IConversationMemoryManager = {
24
24
  clearAllSessions(): Promise<void> | void;
25
25
  /** Get memory statistics */
26
26
  getStats(): Promise<ConversationMemoryStats> | ConversationMemoryStats;
27
+ /** List all sessions with metadata (optional - for session management) */
28
+ listSessions?(userId?: string): Promise<SessionListItem[]>;
29
+ /** List all sessions with metadata (optional - for session management) */
30
+ listSessions?(userId?: string): Promise<SessionListItem[]>;
27
31
  /** Get raw messages array for a session (no context filtering or summarization) */
28
32
  getSessionMessages(sessionId: string, userId?: string): Promise<ChatMessage[]>;
29
33
  /** Replace the entire messages array for a session */