Skip to main content
Glama

think-session-manager

Manages structured thinking sessions by listing, searching, retrieving detailed analysis, or resuming previous sessions for complex problem-solving.

Instructions

Manage thinking sessions: list with pagination/filtering, search by content, get detailed analysis, or resume previous sessions

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
actionYes
sessionIdNo
limitNo
offsetNo
queryNo
strategyNo
includeTopicsNo
sortByNotimestamp
completionStatusNo
minScoreNo

Implementation Reference

  • The handler function that implements the core logic of the 'think-session-manager' tool. It processes actions ('list', 'get', 'resume', 'search') by calling methods on the shared sessionStorage instance (ThinkingSessionStorage), handling pagination, filtering, searching, and resuming sessions.
    async (args) => {
      try {
        switch (args.action) {
          case "list":
            const listOptions = {
              limit: args.limit,
              offset: args.offset,
              strategy: args.strategy,
              includeTopics: args.includeTopics,
              sortBy: args.sortBy
            };
            
            const listResult = await sessionStorage.listSessions(listOptions);
            
            return {
              content: [{
                type: "text",
                text: JSON.stringify({
                  action: "list",
                  total: listResult.total,
                  offset: listResult.offset,
                  limit: listResult.limit,
                  hasMore: listResult.hasMore,
                  sessions: listResult.sessions.map(s => ({
                    sessionId: s.id.split('-').slice(-2).join('-'), // Extract timestamp portion  
                    description: s.sessionPurpose || s.topics?.[0]?.term + ' analysis' || 'General thinking session',
                    strategy: s.strategy,
                    quality: s.qualityRating ? Object.values(s.qualityRating).filter(v => typeof v === 'number').reduce((sum, val, _, arr) => sum + val / arr.length, 0).toFixed(1) : null,
                    duration: s.automaticMetrics?.duration || null,
                    thoughtCount: s.thoughtCount,
                    completion: s.completion,
                    complexity: s.complexity,
                    age: s.age
                  }))
                }, null, 2)
              }]
            };
    
          case "get":
            if (!args.sessionId) {
              throw new Error("sessionId required for 'get' action");
            }
            
            // Handle both full session ID and shortened version
            let fullSessionId = args.sessionId;
            if (!args.sessionId.includes('session-')) {
              // If it's a shortened ID, we need to find the full ID
              const allSessions = await sessionStorage.listSessions({ limit: 1000 });
              const matchingSession = allSessions.sessions.find(s => 
                s.id.split('-').slice(-2).join('-') === args.sessionId
              );
              if (matchingSession) {
                fullSessionId = matchingSession.id;
              }
            }
            
            const session = await sessionStorage.getSession(fullSessionId);
            
            if (!session) {
              throw new Error(`Session not found: ${args.sessionId}`);
            }
            
            const sessionAnalysis = {
              id: session.id,
              strategy: session.strategy,
              timestamp: session.timestamp,
              thoughtHistory: session.thoughtHistory.map(t => ({
                thoughtNumber: t.thoughtNumber,
                absoluteNumber: t.absoluteNumber,
                sequenceNumber: t.sequenceNumber,
                thought: t.thought.substring(0, 150) + (t.thought.length > 150 ? '...' : ''),
                stage: t.currentStage,
                hasActions: t.plannedActions ? t.plannedActions.length > 0 : false,
                hasResults: t.actionResults ? t.actionResults.length > 0 : false
              })),
              branches: Object.keys(session.branches),
              topics: sessionStorage.extractTopics(session.thoughtHistory),
              complexity: sessionStorage.calculateComplexity(session.thoughtHistory, session.branches),
              completion: sessionStorage.getCompletionStatus(session.thoughtHistory),
              summary: {
                totalThoughts: session.thoughtHistory.length,
                totalBranches: Object.keys(session.branches).length,
                finalStage: session.thoughtHistory[session.thoughtHistory.length - 1]?.currentStage,
                completed: !session.thoughtHistory[session.thoughtHistory.length - 1]?.nextThoughtNeeded
              }
            };
            
            return {
              content: [{
                type: "text",
                text: JSON.stringify({
                  action: "get",
                  ...sessionAnalysis
                }, null, 2)
              }]
            };
    
          case "search":
            if (!args.query) {
              throw new Error("query required for 'search' action");
            }
            
            const searchOptions = {
              limit: args.limit,
              offset: args.offset,
              minScore: args.minScore,
              strategy: args.strategy,
              completionStatus: args.completionStatus
            };
            
            const searchResult = await sessionStorage.searchSessions(args.query, searchOptions);
            
            return {
              content: [{
                type: "text",
                text: JSON.stringify({
                  action: "search",
                  query: args.query,
                  ...searchResult,
                  sessions: searchResult.sessions.map(s => ({
                    sessionId: s.id.split('-').slice(-2).join('-'), // Extract timestamp portion for shorter ID
                    description: s.sessionPurpose || s.thoughtHistory[0]?.thought.substring(0, 100) + '...' || 'No description available',
                    strategy: s.strategy,
                    quality: s.qualityRating ? Object.values(s.qualityRating).filter(v => typeof v === 'number').reduce((sum, val, _, arr) => sum + val / arr.length, 0).toFixed(1) : null,
                    duration: s.automaticMetrics?.duration || null,
                    completion: s.completion,
                    searchScore: s.searchScore.toFixed(2),
                    matchedTerms: s.matchedTerms,
                    age: `${Math.round((Date.now() - new Date(s.timestamp)) / (1000 * 60))}m ago`
                  }))
                }, null, 2)
              }]
            };
    
          case "resume":
            if (!args.sessionId) {
              throw new Error("sessionId required for 'resume' action");
            }
            
            // Handle both full session ID and shortened version
            let fullResumeSessionId = args.sessionId;
            if (!args.sessionId.includes('session-')) {
              // If it's a shortened ID, we need to find the full ID
              const allSessions = await sessionStorage.listSessions({ limit: 1000 });
              const matchingSession = allSessions.sessions.find(s => 
                s.id.split('-').slice(-2).join('-') === args.sessionId
              );
              if (matchingSession) {
                fullResumeSessionId = matchingSession.id;
              }
            }
            
            const resumeSession = await sessionStorage.getSession(fullResumeSessionId);
            
            if (!resumeSession) {
              throw new Error(`Session not found: ${args.sessionId}`);
            }
            
            // Restore session state
            thinkingServer.thoughtHistory = resumeSession.thoughtHistory;
            thinkingServer.branches = resumeSession.branches;
            thinkingServer.sessionId = resumeSession.id;
            thinkingServer.strategy = resumeSession.strategy;
            thinkingServer.stageManager = new StageManager(resumeSession.strategy);
            thinkingServer.isInitialized = true;
            
            // Reconstruct dual numbering from history
            const lastThought = resumeSession.thoughtHistory[resumeSession.thoughtHistory.length - 1];
            thinkingServer.absoluteThoughtNumber = lastThought?.absoluteNumber || 0;
            thinkingServer.sequenceThoughtNumber = lastThought?.sequenceNumber || 0;
            
            // Set stage to last known stage
            if (lastThought?.currentStage) {
              thinkingServer.stageManager.currentStage = lastThought.currentStage;
            }
            
            console.error(`📚 Resumed session: ${resumeSession.id} (${resumeSession.strategy})`);
            
            // Build semantic response for current state
            const semanticResponse = thinkingServer.parameterRouter.buildSemanticResponse(
              thinkingServer.strategy,
              thinkingServer.stageManager.getCurrentStage(),
              thinkingServer.thoughtHistory,
              thinkingServer.sessionId
            );
            
            return {
              content: [{
                type: "text",
                text: JSON.stringify({
                  action: "resume",
                  message: `Resumed session: ${resumeSession.id}`,
                  strategy: resumeSession.strategy,
                  thoughtCount: resumeSession.thoughtHistory.length,
                  currentState: semanticResponse.currentState,
                  absoluteThoughtNumber: thinkingServer.absoluteThoughtNumber,
                  sequenceThoughtNumber: thinkingServer.sequenceThoughtNumber,
                  availableActions: semanticResponse.availableActions,
                  lastThought: lastThought?.thought?.substring(0, 200) + (lastThought?.thought?.length > 200 ? '...' : '')
                }, null, 2)
              }]
            };
    
          default:
            throw new Error(`Unknown action: ${args.action}`);
        }
      } catch (error) {
        console.error(`ERROR: Session manager action '${args.action}' failed:`, error);
        throw error;
      }
    }
  • Zod schema for input validation of the think-session-manager tool, defining parameters for different actions like listing, searching, and filtering sessions.
      action: z.enum(["list", "get", "resume", "search"]),
      sessionId: z.string().optional(),
      limit: z.number().optional().default(10),
      offset: z.number().optional().default(0),
      query: z.string().optional(),
      strategy: z.string().optional(),
      includeTopics: z.boolean().optional().default(false),
      sortBy: z.enum(["timestamp", "thoughtCount", "complexity", "quality", "duration"]).optional().default("timestamp"),
      completionStatus: z.enum(["empty", "started", "in-progress", "completed"]).optional(),
      minScore: z.number().optional().default(0.1)
    },
  • index.js:1861-2087 (registration)
    MCP server tool registration for 'think-session-manager', including name, description, schema, and handler reference.
      "think-session-manager",
      "Manage thinking sessions: list with pagination/filtering, search by content, get detailed analysis, or resume previous sessions",
      {
        action: z.enum(["list", "get", "resume", "search"]),
        sessionId: z.string().optional(),
        limit: z.number().optional().default(10),
        offset: z.number().optional().default(0),
        query: z.string().optional(),
        strategy: z.string().optional(),
        includeTopics: z.boolean().optional().default(false),
        sortBy: z.enum(["timestamp", "thoughtCount", "complexity", "quality", "duration"]).optional().default("timestamp"),
        completionStatus: z.enum(["empty", "started", "in-progress", "completed"]).optional(),
        minScore: z.number().optional().default(0.1)
      },
      async (args) => {
        try {
          switch (args.action) {
            case "list":
              const listOptions = {
                limit: args.limit,
                offset: args.offset,
                strategy: args.strategy,
                includeTopics: args.includeTopics,
                sortBy: args.sortBy
              };
              
              const listResult = await sessionStorage.listSessions(listOptions);
              
              return {
                content: [{
                  type: "text",
                  text: JSON.stringify({
                    action: "list",
                    total: listResult.total,
                    offset: listResult.offset,
                    limit: listResult.limit,
                    hasMore: listResult.hasMore,
                    sessions: listResult.sessions.map(s => ({
                      sessionId: s.id.split('-').slice(-2).join('-'), // Extract timestamp portion  
                      description: s.sessionPurpose || s.topics?.[0]?.term + ' analysis' || 'General thinking session',
                      strategy: s.strategy,
                      quality: s.qualityRating ? Object.values(s.qualityRating).filter(v => typeof v === 'number').reduce((sum, val, _, arr) => sum + val / arr.length, 0).toFixed(1) : null,
                      duration: s.automaticMetrics?.duration || null,
                      thoughtCount: s.thoughtCount,
                      completion: s.completion,
                      complexity: s.complexity,
                      age: s.age
                    }))
                  }, null, 2)
                }]
              };
    
            case "get":
              if (!args.sessionId) {
                throw new Error("sessionId required for 'get' action");
              }
              
              // Handle both full session ID and shortened version
              let fullSessionId = args.sessionId;
              if (!args.sessionId.includes('session-')) {
                // If it's a shortened ID, we need to find the full ID
                const allSessions = await sessionStorage.listSessions({ limit: 1000 });
                const matchingSession = allSessions.sessions.find(s => 
                  s.id.split('-').slice(-2).join('-') === args.sessionId
                );
                if (matchingSession) {
                  fullSessionId = matchingSession.id;
                }
              }
              
              const session = await sessionStorage.getSession(fullSessionId);
              
              if (!session) {
                throw new Error(`Session not found: ${args.sessionId}`);
              }
              
              const sessionAnalysis = {
                id: session.id,
                strategy: session.strategy,
                timestamp: session.timestamp,
                thoughtHistory: session.thoughtHistory.map(t => ({
                  thoughtNumber: t.thoughtNumber,
                  absoluteNumber: t.absoluteNumber,
                  sequenceNumber: t.sequenceNumber,
                  thought: t.thought.substring(0, 150) + (t.thought.length > 150 ? '...' : ''),
                  stage: t.currentStage,
                  hasActions: t.plannedActions ? t.plannedActions.length > 0 : false,
                  hasResults: t.actionResults ? t.actionResults.length > 0 : false
                })),
                branches: Object.keys(session.branches),
                topics: sessionStorage.extractTopics(session.thoughtHistory),
                complexity: sessionStorage.calculateComplexity(session.thoughtHistory, session.branches),
                completion: sessionStorage.getCompletionStatus(session.thoughtHistory),
                summary: {
                  totalThoughts: session.thoughtHistory.length,
                  totalBranches: Object.keys(session.branches).length,
                  finalStage: session.thoughtHistory[session.thoughtHistory.length - 1]?.currentStage,
                  completed: !session.thoughtHistory[session.thoughtHistory.length - 1]?.nextThoughtNeeded
                }
              };
              
              return {
                content: [{
                  type: "text",
                  text: JSON.stringify({
                    action: "get",
                    ...sessionAnalysis
                  }, null, 2)
                }]
              };
    
            case "search":
              if (!args.query) {
                throw new Error("query required for 'search' action");
              }
              
              const searchOptions = {
                limit: args.limit,
                offset: args.offset,
                minScore: args.minScore,
                strategy: args.strategy,
                completionStatus: args.completionStatus
              };
              
              const searchResult = await sessionStorage.searchSessions(args.query, searchOptions);
              
              return {
                content: [{
                  type: "text",
                  text: JSON.stringify({
                    action: "search",
                    query: args.query,
                    ...searchResult,
                    sessions: searchResult.sessions.map(s => ({
                      sessionId: s.id.split('-').slice(-2).join('-'), // Extract timestamp portion for shorter ID
                      description: s.sessionPurpose || s.thoughtHistory[0]?.thought.substring(0, 100) + '...' || 'No description available',
                      strategy: s.strategy,
                      quality: s.qualityRating ? Object.values(s.qualityRating).filter(v => typeof v === 'number').reduce((sum, val, _, arr) => sum + val / arr.length, 0).toFixed(1) : null,
                      duration: s.automaticMetrics?.duration || null,
                      completion: s.completion,
                      searchScore: s.searchScore.toFixed(2),
                      matchedTerms: s.matchedTerms,
                      age: `${Math.round((Date.now() - new Date(s.timestamp)) / (1000 * 60))}m ago`
                    }))
                  }, null, 2)
                }]
              };
    
            case "resume":
              if (!args.sessionId) {
                throw new Error("sessionId required for 'resume' action");
              }
              
              // Handle both full session ID and shortened version
              let fullResumeSessionId = args.sessionId;
              if (!args.sessionId.includes('session-')) {
                // If it's a shortened ID, we need to find the full ID
                const allSessions = await sessionStorage.listSessions({ limit: 1000 });
                const matchingSession = allSessions.sessions.find(s => 
                  s.id.split('-').slice(-2).join('-') === args.sessionId
                );
                if (matchingSession) {
                  fullResumeSessionId = matchingSession.id;
                }
              }
              
              const resumeSession = await sessionStorage.getSession(fullResumeSessionId);
              
              if (!resumeSession) {
                throw new Error(`Session not found: ${args.sessionId}`);
              }
              
              // Restore session state
              thinkingServer.thoughtHistory = resumeSession.thoughtHistory;
              thinkingServer.branches = resumeSession.branches;
              thinkingServer.sessionId = resumeSession.id;
              thinkingServer.strategy = resumeSession.strategy;
              thinkingServer.stageManager = new StageManager(resumeSession.strategy);
              thinkingServer.isInitialized = true;
              
              // Reconstruct dual numbering from history
              const lastThought = resumeSession.thoughtHistory[resumeSession.thoughtHistory.length - 1];
              thinkingServer.absoluteThoughtNumber = lastThought?.absoluteNumber || 0;
              thinkingServer.sequenceThoughtNumber = lastThought?.sequenceNumber || 0;
              
              // Set stage to last known stage
              if (lastThought?.currentStage) {
                thinkingServer.stageManager.currentStage = lastThought.currentStage;
              }
              
              console.error(`📚 Resumed session: ${resumeSession.id} (${resumeSession.strategy})`);
              
              // Build semantic response for current state
              const semanticResponse = thinkingServer.parameterRouter.buildSemanticResponse(
                thinkingServer.strategy,
                thinkingServer.stageManager.getCurrentStage(),
                thinkingServer.thoughtHistory,
                thinkingServer.sessionId
              );
              
              return {
                content: [{
                  type: "text",
                  text: JSON.stringify({
                    action: "resume",
                    message: `Resumed session: ${resumeSession.id}`,
                    strategy: resumeSession.strategy,
                    thoughtCount: resumeSession.thoughtHistory.length,
                    currentState: semanticResponse.currentState,
                    absoluteThoughtNumber: thinkingServer.absoluteThoughtNumber,
                    sequenceThoughtNumber: thinkingServer.sequenceThoughtNumber,
                    availableActions: semanticResponse.availableActions,
                    lastThought: lastThought?.thought?.substring(0, 200) + (lastThought?.thought?.length > 200 ? '...' : '')
                  }, null, 2)
                }]
              };
    
            default:
              throw new Error(`Unknown action: ${args.action}`);
          }
        } catch (error) {
          console.error(`ERROR: Session manager action '${args.action}' failed:`, error);
          throw error;
        }
      }
    );
  • ThinkingSessionStorage class providing helper methods for session persistence, listing, searching, analysis (topics, complexity, completion status), and resumption. Used by the tool handler for all session operations.
    class ThinkingSessionStorage {
        constructor(storagePath) {
            this.storagePath = storagePath;
        }
    
        calculateAutomaticMetrics(thoughtHistory, sessionStartTime, hasActions) {
            const sessionEndTime = new Date().toISOString();
            const durationMs = new Date(sessionEndTime) - new Date(sessionStartTime);
            const durationMinutes = Math.round(durationMs / (1000 * 60));
            
            // Calculate iteration ratio (revisions / total thoughts)
            const revisionCount = thoughtHistory.filter(t => t.isRevision).length;
            const iterationRatio = thoughtHistory.length > 0 ? revisionCount / thoughtHistory.length : 0;
            
            return {
                duration: durationMinutes,
                iterationRatio: Math.round(iterationRatio * 100) / 100, // Round to 2 decimals
                toolIntegration: hasActions
            };
        }
    
        async saveSession(sessionId, thoughtHistory, branches, sessionPurpose = null, qualityRating = null, sessionStartTime = null) {
            const sessionDir = path.join(this.storagePath, sessionId);
            await fs.ensureDir(sessionDir);
            
            // Calculate automatic metrics
            const hasActions = thoughtHistory.some(t => t.plannedActions || t.actionResults);
            const automaticMetrics = sessionStartTime ? 
                this.calculateAutomaticMetrics(thoughtHistory, sessionStartTime, hasActions) : 
                null;
            
            const sessionData = {
                id: sessionId,
                timestamp: new Date().toISOString(),
                strategy: sessionId.split('-')[0], // Extract strategy from sessionId
                sessionPurpose,
                qualityRating,
                automaticMetrics,
                thoughtHistory,
                branches
            };
            
            const filePath = path.join(sessionDir, 'session.json');
            await fs.writeJson(filePath, sessionData, { spaces: 2 });
            
            return filePath;
        }
    
        // Extract key topics and terms from thought content
        extractTopics(thoughtHistory) {
            const allText = thoughtHistory.map(t => t.thought).join(' ');
            
            // Simple topic extraction - remove common words and get meaningful terms
            const stopWords = new Set(['the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by', 'from', 'up', 'about', 'into', 'through', 'during', 'before', 'after', 'above', 'below', 'between', 'among', 'this', 'that', 'these', 'those', 'i', 'you', 'he', 'she', 'it', 'we', 'they', 'me', 'him', 'her', 'us', 'them', 'my', 'your', 'his', 'her', 'its', 'our', 'their', 'am', 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'could', 'should', 'may', 'might', 'must', 'can', 'shall']);
            
            const words = allText.toLowerCase()
                .replace(/[^\w\s]/g, ' ')
                .split(/\s+/)
                .filter(word => word.length > 2 && !stopWords.has(word));
            
            // Count word frequency
            const wordCounts = {};
            words.forEach(word => {
                wordCounts[word] = (wordCounts[word] || 0) + 1;
            });
            
            // Get top terms
            const topTerms = Object.entries(wordCounts)
                .sort(([,a], [,b]) => b - a)
                .slice(0, 8)
                .map(([word, count]) => ({ term: word, frequency: count }));
            
            return topTerms;
        }
    
        // Calculate session complexity based on branching, actions, and thought depth
        calculateComplexity(thoughtHistory, branches) {
            const thoughtComplexity = thoughtHistory.length;
            const branchComplexity = Object.keys(branches).length * 2;
            const actionComplexity = thoughtHistory.filter(t => t.plannedActions || t.actionResults).length;
            
            const totalComplexity = thoughtComplexity + branchComplexity + actionComplexity;
            
            if (totalComplexity < 5) return 'simple';
            if (totalComplexity < 15) return 'moderate';
            if (totalComplexity < 30) return 'complex';
            return 'very-complex';
        }
    
        // Determine completion status
        getCompletionStatus(thoughtHistory) {
            if (thoughtHistory.length === 0) return 'empty';
            
            const lastThought = thoughtHistory[thoughtHistory.length - 1];
            const hasConclusion = lastThought.finalAnswer || !lastThought.nextThoughtNeeded;
            const hasSignificantProgress = thoughtHistory.length >= 3;
            
            if (hasConclusion && hasSignificantProgress) return 'completed';
            if (hasSignificantProgress) return 'in-progress';
            return 'started';
        }
    
        // Search sessions by content with term scoring
        async searchSessions(query, options = {}) {
            const {
                limit = 10,
                offset = 0,
                minScore = 0.1,
                strategy = null,
                completionStatus = null
            } = options;
            
            const sessions = await this.getAllSessions();
            const queryTerms = query.toLowerCase().split(/\s+/).filter(term => term.length > 2);
            
            const scoredSessions = sessions.map(session => {
                const sessionText = session.thoughtHistory.map(t => t.thought).join(' ').toLowerCase();
                
                // Calculate relevance score
                let score = 0;
                queryTerms.forEach(term => {
                    const regex = new RegExp(term, 'gi');
                    const matches = sessionText.match(regex) || [];
                    score += matches.length;
                });
                
                // Normalize by session length
                const normalizedScore = score / Math.max(sessionText.length / 1000, 1);
                
                return {
                    ...session,
                    searchScore: normalizedScore,
                    matchedTerms: queryTerms.filter(term => sessionText.includes(term))
                };
            });
            
            // Filter and sort by relevance
            let filteredSessions = scoredSessions
                .filter(s => s.searchScore >= minScore)
                .filter(s => !strategy || s.strategy === strategy)
                .filter(s => !completionStatus || s.completion === completionStatus)
                .sort((a, b) => b.searchScore - a.searchScore);
            
            const total = filteredSessions.length;
            filteredSessions = filteredSessions.slice(offset, offset + limit);
            
            return {
                sessions: filteredSessions,
                total,
                offset,
                limit,
                hasMore: offset + limit < total
            };
        }
    
        async listSessions(options = {}) {
            const {
                limit = 10,
                offset = 0,
                strategy = null,
                includeTopics = false,
                sortBy = 'timestamp' // timestamp, thoughtCount, complexity
            } = options;
            
            await fs.ensureDir(this.storagePath);
            const dirs = await fs.readdir(this.storagePath);
            
            const sessions = [];
            for (const dir of dirs) {
                const sessionPath = path.join(this.storagePath, dir, 'session.json');
                if (await fs.pathExists(sessionPath)) {
                    try {
                        const sessionData = await fs.readJson(sessionPath);
                        
                        const sessionInfo = {
                            id: sessionData.id,
                            timestamp: sessionData.timestamp,
                            strategy: sessionData.strategy || "unknown",
                            sessionPurpose: sessionData.sessionPurpose,
                            qualityRating: sessionData.qualityRating,
                            automaticMetrics: sessionData.automaticMetrics,
                            thoughtCount: sessionData.thoughtHistory.length,
                            branchCount: Object.keys(sessionData.branches).length,
                            completion: this.getCompletionStatus(sessionData.thoughtHistory),
                            complexity: this.calculateComplexity(sessionData.thoughtHistory, sessionData.branches),
                            age: `${Math.round((Date.now() - new Date(sessionData.timestamp)) / (1000 * 60))} minutes ago`
                        };
                        
                        if (includeTopics) {
                            sessionInfo.topics = this.extractTopics(sessionData.thoughtHistory);
                        }
                        
                        sessions.push(sessionInfo);
                    } catch (error) {
                        console.error(`Error reading session ${dir}:`, error);
                    }
                }
            }
            
            // Filter by strategy if specified
            const filteredSessions = strategy ? 
                sessions.filter(s => s.strategy === strategy) : 
                sessions;
            
            // Sort sessions
            filteredSessions.sort((a, b) => {
                switch (sortBy) {
                    case 'thoughtCount':
                        return b.thoughtCount - a.thoughtCount;
                    case 'complexity':
                        const complexityOrder = { 'simple': 1, 'moderate': 2, 'complex': 3, 'very-complex': 4 };
                        return complexityOrder[b.complexity] - complexityOrder[a.complexity];
                    case 'quality':
                        // Calculate average quality score
                        const getAvgQuality = (session) => {
                            if (!session.qualityRating) return 0;
                            const ratings = Object.values(session.qualityRating).filter(v => typeof v === 'number');
                            return ratings.length > 0 ? ratings.reduce((sum, val) => sum + val, 0) / ratings.length : 0;
                        };
                        return getAvgQuality(b) - getAvgQuality(a);
                    case 'duration':
                        const getDuration = (session) => session.automaticMetrics?.duration || 0;
                        return getDuration(a) - getDuration(b); // Shorter duration first (more efficient)
                    case 'timestamp':
                    default:
                        return new Date(b.timestamp) - new Date(a.timestamp);
                }
            });
            
            const total = filteredSessions.length;
            const paginatedSessions = filteredSessions.slice(offset, offset + limit);
            
            return {
                sessions: paginatedSessions,
                total,
                offset,
                limit,
                hasMore: offset + limit < total
            };
        }
    
        async getAllSessions() {
            await fs.ensureDir(this.storagePath);
            const dirs = await fs.readdir(this.storagePath);
            
            const sessions = [];
            for (const dir of dirs) {
                const sessionPath = path.join(this.storagePath, dir, 'session.json');
                if (await fs.pathExists(sessionPath)) {
                    try {
                        const sessionData = await fs.readJson(sessionPath);
                        sessions.push({
                            ...sessionData,
                            completion: this.getCompletionStatus(sessionData.thoughtHistory),
                            complexity: this.calculateComplexity(sessionData.thoughtHistory, sessionData.branches)
                        });
                    } catch (error) {
                        console.error(`Error reading session ${dir}:`, error);
                    }
                }
            }
            
            return sessions;
        }
    
        async getSession(sessionId) {
            const sessionPath = path.join(this.storagePath, sessionId, 'session.json');
            if (await fs.pathExists(sessionPath)) {
                return await fs.readJson(sessionPath);
            }
            return null;
        }
    }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aaronsb/think-strategies'

If you have feedback or need assistance with the MCP directory API, please join our Discord server