UNPKG

smart-thinking-mcp

Version:

Un serveur MCP avancé pour le raisonnement multi-dimensionnel, adaptatif et collaboratif

1,430 lines 68.4 kB
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ThoughtGraph = void 0;
const events_1 = require("events");
const openrouter_client_1 = require("./utils/openrouter-client"); // Import LLM utility
/**
 * Classe qui gère le graphe de pensées et ses opérations
 */
class ThoughtGraph {
    nodes = new Map();
    hyperlinks = new Map();
    sessionId;
    similarityEngine;
    qualityEvaluator;
    eventEmitter;
    constructor(sessionId, similarityEngine, qualityEvaluator) {
        this.sessionId = sessionId || this.generateUniqueId();
        this.similarityEngine = similarityEngine;
        this.qualityEvaluator = qualityEvaluator;
        this.eventEmitter = new events_1.EventEmitter();
        // Configurer les écouteurs d'événements
        this.setupEventListeners();
    }
    /**
     * Configure les écouteurs d'événements pour la vérification continue
     */
    setupEventListeners() {
        // Configurer ici des écouteurs si nécessaire
    }
    /**
     * Vérifie les calculs dans une pensée et les annote si nécessaire
     *
     * @param thoughtId L'identifiant de la pensée à vérifier
     * @param content Le contenu de la pensée
     */
    async checkForCalculationsAndVerify(thoughtId, content) {
        if (!this.qualityEvaluator)
            return;
        // Détecter si la pensée contient des calculs avec des expressions régulières simples
        const hasSimpleCalculations = /\d+\s*[\+\-\*\/]\s*\d+\s*=/.test(content);
        const hasComplexCalculations = /calcul\s*(?:complexe|avancé)?\s*:?\s*([^=]+)=\s*\d+/.test(content);
        if (hasSimpleCalculations || hasComplexCalculations) {
            console.error(`Smart-Thinking: Détection en temps réel de calculs dans la pensée ${thoughtId}, vérification...`);
            try {
                // Vérifier les calculs de manière asynchrone
                const verifiedCalculations = await this.qualityEvaluator.detectAndVerifyCalculations(content);
                if (verifiedCalculations.length > 0) {
                    // Mettre à jour le contenu de la pensée avec les annotations
                    const updatedContent = this.qualityEvaluator.annotateThoughtWithVerifications(content, verifiedCalculations);
                    // Mettre à jour la pensée sans déclencher à nouveau les vérifications
                    const thought = this.nodes.get(thoughtId);
                    if (thought) {
                        thought.content = updatedContent;
                        thought.metadata.calculationsVerified = true;
                        thought.metadata.lastUpdated = new Date();
                        thought.metadata.verificationTimestamp = new Date();
                    }
                    // Émettre un événement pour notifier que des calculs ont été vérifiés
                    this.eventEmitter.emit('calculations-verified', {
                        thoughtId,
                        verifiedCalculations,
                        updatedContent
                    });
                    // Journaliser le résultat de la vérification
                    console.error(`Smart-Thinking: ${verifiedCalculations.length} calcul(s) vérifié(s) dans la pensée ${thoughtId}`);
                }
            }
            catch (error) {
                console.error(`Smart-Thinking: Erreur lors de la vérification des calculs:`, error);
            }
        }
    }
    /**
     * Permet d'enregistrer un écouteur d'événement externe
     *
     * @param event Le nom de l'événement
     * @param listener La fonction de rappel à exécuter
     */
    on(event, listener) {
        this.eventEmitter.on(event, listener);
    }
    /**
     * Génère un identifiant unique
     */
    generateUniqueId() {
        return Date.now().toString(36) + Math.random().toString(36).substring(2);
    }
    /**
     * Ajoute une nouvelle pensée au graphe
     *
     * @param content Le contenu de la pensée
     * @param type Le type de pensée
     * @param connections Les connexions à d'autres pensées
     * @returns L'identifiant de la pensée ajoutée
     */
    addThought(content, type = 'regular', connections = []) {
        const id = this.generateUniqueId();
        const node = {
            id,
            content,
            type,
            timestamp: new Date(),
            connections: [...connections], // Ensure a copy is made
            metrics: {
                confidence: 0.5, // Valeur par défaut
                relevance: 0.5, // Valeur par défaut
                quality: 0.5 // Valeur par défaut
            },
            metadata: {
                sessionId: this.sessionId
            }
        };
        this.nodes.set(id, node);
        // Établir les connexions bidirectionnelles
        this.establishConnections(id, connections);
        // Émettre un événement pour notifier de l'ajout d'une pensée
        this.eventEmitter.emit('thought-added', id, node);
        // Vérifier automatiquement les calculs si nécessaire (async, but don't wait for it)
        this.checkForCalculationsAndVerify(id, content).catch(err => {
            console.error("Error during background calculation check:", err);
        });
        // Calculate metrics asynchronously after adding the thought (don't block return)
        this.updateMetricsForThought(id).catch(err => {
            console.error(`Error during background metric calculation for ${id}:`, err);
        });
        return id;
    }
    /**
     * Met à jour les métriques pour une pensée spécifique (maintenant asynchrone)
     * @param thoughtId L'ID de la pensée
     */
    async updateMetricsForThought(thoughtId) {
        if (!this.qualityEvaluator) {
            console.warn("QualityEvaluator not available for metric update.");
            return;
        }
        const thought = this.getThought(thoughtId);
        if (!thought)
            return;
        try {
            const metrics = await this.qualityEvaluator.evaluate(thoughtId, this);
            this.updateThoughtMetrics(thoughtId, metrics);
            this.eventEmitter.emit('metrics-updated', thoughtId, metrics);
        }
        catch (error) {
            console.error(`Failed to update metrics for thought ${thoughtId}:`, error);
        }
    }
    /**
     * Établit des connexions bidirectionnelles entre les pensées
     *
     * @param sourceId L'identifiant de la pensée source
     * @param connections Les connexions à établir
     */
    establishConnections(sourceId, connections) {
        for (const connection of connections) {
            const targetNode = this.nodes.get(connection.targetId);
            if (targetNode) {
                // Avoid adding duplicate reciprocal connections if one already exists
                const existingReciprocal = targetNode.connections.find(conn => conn.targetId === sourceId);
                if (!existingReciprocal) {
                    targetNode.connections.push({
                        targetId: sourceId,
                        type: this.getReciprocalConnectionType(connection.type),
                        strength: connection.strength,
                        description: connection.description,
                        // Transférer les attributs si présents
                        attributes: connection.attributes,
                        inferred: connection.inferred,
                        inferenceConfidence: connection.inferenceConfidence,
                        bidirectional: connection.bidirectional,
                        createdByStepId: connection.createdByStepId,
                        justification: connection.justification,
                        heuristicWeights: connection.heuristicWeights
                    });
                }
            }
        }
    }
    /**
     * Détermine le type de connexion réciproque
     *
     * @param type Le type de connexion original
     * @returns Le type de connexion réciproque
     */
    getReciprocalConnectionType(type) {
        // Mapping for reciprocal types
        const reciprocalMap = {
            supports: 'supports',
            contradicts: 'contradicts',
            refines: 'derives', // If A refines B, B derives from A
            derives: 'refines', // If A derives from B, B refines A
            branches: 'branches',
            associates: 'associates',
            exemplifies: 'generalizes', // If A exemplifies B, B generalizes A
            generalizes: 'exemplifies', // If A generalizes B, B exemplifies A
            compares: 'compares',
            contrasts: 'contrasts',
            questions: 'questions', // Questioning can be reciprocal
            extends: 'extended-by',
            analyzes: 'analyzed-by',
            synthesizes: 'component-of', // If A synthesizes B, B is a component of A
            applies: 'applied-by',
            evaluates: 'evaluated-by',
            cites: 'cited-by',
            'extended-by': 'extends',
            'analyzed-by': 'analyzes',
            'component-of': 'synthesizes',
            'applied-by': 'applies',
            'evaluated-by': 'evaluates',
            'cited-by': 'cites'
        };
        return reciprocalMap[type] || 'associates'; // Default to 'associates'
    }
    /**
     * Récupère une pensée par son identifiant
     *
     * @param id L'identifiant de la pensée
     * @returns La pensée ou undefined si non trouvée
     */
    getThought(id) {
        return this.nodes.get(id);
    }
    /**
     * Met à jour les métriques d'une pensée
     *
     * @param id L'identifiant de la pensée
     * @param metrics Les nouvelles métriques
     * @returns true si la mise à jour a réussi, false sinon
     */
    updateThoughtMetrics(id, metrics) {
        const thought = this.nodes.get(id);
        if (!thought)
            return false;
        // Ensure metrics object exists
        if (!thought.metrics) {
            thought.metrics = { confidence: 0.5, relevance: 0.5, quality: 0.5 };
        }
        thought.metrics = {
            ...thought.metrics,
            ...metrics
        };
        return true;
    }
    /**
     * Récupère toutes les pensées
     *
     * @param sessionId L'identifiant de session facultatif pour filtrer les pensées
     * @returns Un tableau de toutes les pensées (filtrées par session si sessionId est fourni)
     */
    getAllThoughts(sessionId) {
        const allNodes = Array.from(this.nodes.values());
        if (!sessionId) {
            // Maybe return only thoughts from the graph's default session? Or all?
            // For now, let's return all if no session specified, but log a warning.
            // console.warn("getAllThoughts called without sessionId, returning all thoughts.");
            return allNodes;
        }
        return allNodes.filter(node => node.metadata?.sessionId === sessionId);
    }
    /**
     * Récupère les pensées les plus récentes
     *
     * @param limit Le nombre maximum de pensées à récupérer
     * @param sessionId L'identifiant de session facultatif pour filtrer les pensées
     * @returns Un tableau des pensées les plus récentes (filtrées par session)
     */
    getRecentThoughts(limit = 5, sessionId) {
        const thoughts = this.getAllThoughts(sessionId); // Use the session-filtered list
        return thoughts
            .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())
            .slice(0, limit);
    }
    /**
     * Récupère les pensées connectées à une pensée spécifique
     *
     * @param thoughtId L'identifiant de la pensée
     * @returns Un tableau des pensées connectées
     */
    getConnectedThoughts(thoughtId) {
        const thought = this.nodes.get(thoughtId);
        if (!thought)
            return [];
        return thought.connections
            .map(conn => this.nodes.get(conn.targetId))
            .filter((node) => node !== undefined);
    }
    /**
     * Récupère les pensées les plus pertinentes pour un contexte donné
     *
     * @param context Le contexte pour lequel chercher des pensées pertinentes
     * @param limit Le nombre maximum de pensées à récupérer
     * @param sessionId L'identifiant de session facultatif pour filtrer les pensées
     * @returns Un tableau des pensées les plus pertinentes (filtrées par session)
     */
    async getRelevantThoughts(context, limit = 5, sessionId) {
        const allThoughts = this.getAllThoughts(sessionId); // Use the session-filtered list
        // Si pas de pensées ou pas de SimilarityEngine, utiliser l'algorithme de base
        if (allThoughts.length === 0 || !this.similarityEngine) {
            console.warn("SimilarityEngine not available or no thoughts in graph. Falling back to keyword relevance.");
            return this.getRelevantThoughtsWithKeywords(context, limit);
        }
        try {
            // Utiliser SimilarityEngine pour trouver les pensées similaires
            const thoughtTexts = allThoughts.map(thought => thought.content);
            const similarResults = await this.similarityEngine.findSimilarTexts(context, thoughtTexts, limit);
            // Convertir les résultats en pensées
            return similarResults.map(result => {
                const matchingThought = allThoughts.find(thought => thought.content === result.text);
                if (matchingThought) {
                    // Stocker le score de similarité dans les métadonnées pour référence future
                    matchingThought.metadata.similarityScore = result.score;
                }
                return matchingThought;
            }).filter(thought => thought !== undefined);
        }
        catch (error) {
            console.error('Erreur lors de la recherche de pensées pertinentes avec embeddings:', error);
            // En cas d'erreur, revenir à l'algorithme basé sur les mots-clés
            return this.getRelevantThoughtsWithKeywords(context, limit);
        }
    }
    /**
     * Implémentation de secours basée sur les mots-clés
     *
     * @param context Le contexte pour lequel chercher des pensées pertinentes
     * @param limit Le nombre maximum de pensées à récupérer
     * @returns Un tableau des pensées les plus pertinentes
     */
    getRelevantThoughtsWithKeywords(context, limit = 5) {
        // Une implémentation simple basée sur la correspondance de mots-clés
        const contextWords = context.toLowerCase().split(/\W+/).filter(word => word.length > 3);
        if (contextWords.length === 0)
            return []; // Avoid division by zero if context is empty
        return Array.from(this.nodes.values())
            .map(thought => {
            const thoughtWords = thought.content.toLowerCase().split(/\W+/).filter(word => word.length > 3);
            // Calculer un score simple basé sur le nombre de mots partagés
            const matchingWords = contextWords.filter(word => thoughtWords.includes(word));
            const score = matchingWords.length / contextWords.length; // Normalize by context length
            return {
                thought,
                score
            };
        })
            .filter(item => item.score > 0) // Only keep thoughts with some relevance
            .sort((a, b) => b.score - a.score)
            .slice(0, limit)
            .map(item => item.thought);
    }
    /**
     * Crée un hyperlien entre plusieurs pensées
     *
     * @param nodeIds Les identifiants des pensées à connecter
     * @param type Le type de connexion
     * @param label Une étiquette descriptive facultative
     * @param attributes Attributs sémantiques facultatifs
     * @param strength La force de la connexion (0 à 1)
     * @returns L'identifiant de l'hyperlien créé
     */
    createHyperlink(nodeIds, type, label, attributes, strength = 0.5) {
        // Vérifier que les nœuds existent
        if (nodeIds.some(id => !this.nodes.has(id))) {
            console.error('Certains nœuds spécifiés n\'existent pas');
            return '';
        }
        // Générer un ID unique pour l'hyperlien
        const id = `hl-${this.generateUniqueId()}`; // Prefix for clarity
        // Créer l'hyperlien
        const hyperlink = {
            id,
            nodeIds,
            type,
            label,
            attributes,
            strength,
            inferred: false,
            confidence: 1.0, // Non inféré, donc confiance maximale
            metadata: {
                createdAt: new Date(),
                sessionId: this.sessionId
            }
        };
        // Ajouter l'hyperlien à la collection
        this.hyperlinks.set(id, hyperlink);
        return id;
    }
    /**
     * Récupère un hyperlien par son identifiant
     *
     * @param id L'identifiant de l'hyperlien
     * @returns L'hyperlien ou undefined si non trouvé
     */
    getHyperlink(id) {
        return this.hyperlinks.get(id);
    }
    /**
     * Récupère tous les hyperliens
     *
     * @param sessionId L'identifiant de session facultatif pour filtrer les hyperliens
     * @returns Un tableau de tous les hyperliens (filtrés par session)
     */
    getAllHyperlinks(sessionId) {
        const allLinks = Array.from(this.hyperlinks.values());
        if (!sessionId) {
            // console.warn("getAllHyperlinks called without sessionId, returning all hyperlinks.");
            return allLinks;
        }
        return allLinks.filter(link => link.metadata?.sessionId === sessionId);
    }
    /**
     * Récupère les hyperliens impliquant une pensée spécifique
     *
     * @param thoughtId L'identifiant de la pensée
     * @param sessionId L'identifiant de session facultatif pour filtrer les hyperliens
     * @returns Un tableau des hyperliens impliquant cette pensée (filtrés par session)
     */
    getHyperlinksForThought(thoughtId, sessionId) {
        const thought = this.getThought(thoughtId);
        // Ensure the thought itself belongs to the requested session if provided
        if (!thought || (sessionId && thought.metadata?.sessionId !== sessionId)) {
            return [];
        }
        const allLinks = this.getAllHyperlinks(sessionId); // Get session-filtered links
        return allLinks.filter(hyperlink => hyperlink.nodeIds.includes(thoughtId));
    }
    /**
     * Infère des relations entre pensées basées sur l'analyse de contenu et de contexte
     *
     * @param confidenceThreshold Seuil de confiance minimum pour les relations inférées (0 à 1)
     * @returns Le nombre de nouvelles relations inférées
     */
    async inferRelations(confidenceThreshold = 0.7) {
        if (!this.similarityEngine) {
            console.error('SimilarityEngine non disponible pour l\'inférence de relations');
            return 0;
        }
        const thoughts = this.getAllThoughts();
        let newRelationsCount = 0;
        // Inférence basée sur la similarité sémantique
        const similarityCount = await this.inferRelationsBySimilarity(thoughts, confidenceThreshold);
        // Inférence basée sur la transitivité des relations
        const transitivityCount = this.inferRelationsByTransitivity(confidenceThreshold);
        // Inférence basée sur des patterns dans le graphe
        const patternsCount = this.inferRelationsByPatterns(confidenceThreshold);
        newRelationsCount = similarityCount + transitivityCount + patternsCount;
        return newRelationsCount;
    }
    /**
     * Infère des relations basées sur la similarité sémantique
     *
     * @param thoughts Les pensées à analyser
     * @param confidenceThreshold Seuil de confiance minimum
     * @returns Le nombre de nouvelles relations inférées
     */
    async inferRelationsBySimilarity(thoughts, confidenceThreshold) {
        if (thoughts.length < 2 || !this.similarityEngine)
            return 0;
        let newRelationsCount = 0;
        const thoughtTexts = thoughts.map(t => t.content);
        let vectors = [];
        try {
            vectors = await this.similarityEngine.generateVectors(thoughtTexts);
            if (vectors.length !== thoughts.length) {
                console.error('Mismatch between number of thoughts and vectors received.');
                return 0;
            }
        }
        catch (error) {
            console.error('Failed to compute vectors for similarity inference:', error);
            return 0;
        }
        // Calculer les similarités entre toutes les paires de pensées
        for (let i = 0; i < thoughts.length; i++) {
            const sourceThought = thoughts[i];
            const sourceVector = vectors[i];
            for (let j = i + 1; j < thoughts.length; j++) {
                const targetThought = thoughts[j];
                const targetVector = vectors[j];
                const similarityScore = this.similarityEngine.calculateCosineSimilarity(sourceVector, targetVector);
                if (similarityScore >= confidenceThreshold) {
                    // Éviter les doublons
                    const hasConnection = sourceThought.connections.some(conn => conn.targetId === targetThought.id);
                    const hasReciprocalConnection = targetThought.connections.some(conn => conn.targetId === sourceThought.id);
                    if (!hasConnection && !hasReciprocalConnection) {
                        // Déterminer le type de connexion en fonction du contexte
                        const connectionType = this.inferConnectionType(sourceThought, targetThought);
                        // Ajouter la nouvelle connexion
                        this.addInferredConnection(sourceThought.id, targetThought.id, connectionType, similarityScore // Use the calculated similarity as confidence
                        );
                        newRelationsCount++;
                    }
                }
            }
        }
        return newRelationsCount;
    }
    /**
     * Infère des relations basées sur la transitivité
     *
     * @param confidenceThreshold Seuil de confiance minimum
     * @returns Le nombre de nouvelles relations inférées
     */
    inferRelationsByTransitivity(confidenceThreshold) {
        let newRelationsCount = 0;
        const thoughts = this.getAllThoughts();
        // Règles de transitivité pour certains types de connexions
        const transitivityRules = {
            // Si A supporte B et B supporte C, alors A supporte C (transitivité affaiblie)
            'supports': [
                {
                    firstType: 'supports',
                    secondType: 'supports',
                    resultType: 'supports',
                    confidenceMultiplier: 0.8
                }
            ],
            // Si A contredit B et B supporte C, alors A contredit C (transitivité inversée)
            'contradicts': [
                {
                    firstType: 'contradicts',
                    secondType: 'supports',
                    resultType: 'contradicts',
                    confidenceMultiplier: 0.7
                }
            ],
            // Si A dérive de B et B dérive de C, alors A dérive de C
            'derives': [
                {
                    firstType: 'derives',
                    secondType: 'derives',
                    resultType: 'derives',
                    confidenceMultiplier: 0.9
                }
            ]
            // Autres règles...
        };
        // Appliquer les règles de transitivité
        for (const thought of thoughts) {
            for (const conn1 of thought.connections) {
                const secondThought = this.getThought(conn1.targetId);
                if (!secondThought)
                    continue;
                const rules = transitivityRules[conn1.type] || [];
                for (const rule of rules) {
                    if (conn1.type !== rule.firstType)
                        continue;
                    for (const conn2 of secondThought.connections) {
                        if (conn2.type !== rule.secondType)
                            continue;
                        const thirdThought = this.getThought(conn2.targetId);
                        if (!thirdThought || thirdThought.id === thought.id)
                            continue;
                        // Éviter les cycles et les doublons
                        const hasConnection = thought.connections.some(c => c.targetId === thirdThought.id);
                        const hasReciprocalConnection = thirdThought.connections.some(c => c.targetId === thought.id);
                        if (!hasConnection && !hasReciprocalConnection) {
                            // Calculer la confiance basée sur les connexions existantes
                            const confidence = conn1.strength * conn2.strength * rule.confidenceMultiplier;
                            if (confidence >= confidenceThreshold) {
                                this.addInferredConnection(thought.id, thirdThought.id, rule.resultType, confidence);
                                newRelationsCount++;
                            }
                        }
                    }
                }
            }
        }
        return newRelationsCount;
    }
    /**
     * Infère des relations basées sur des patterns dans le graphe
     *
     * @param confidenceThreshold Seuil de confiance minimum
     * @returns Le nombre de nouvelles relations inférées
     */
    inferRelationsByPatterns(confidenceThreshold) {
        let newRelationsCount = 0;
        // Détecter des clusters de pensées fortement connectées
        const clusters = this.detectClusters();
        // Pour chaque cluster, créer des hyperliens entre les membres
        for (const cluster of clusters) {
            if (cluster.nodeIds.length >= 3) {
                // Créer un hyperlien pour le groupe si la confiance est suffisante
                if (cluster.cohesion >= confidenceThreshold) {
                    // Check if hyperlink already exists for this cluster
                    const existingHyperlink = Array.from(this.hyperlinks.values()).find(hl => hl.nodeIds.length === cluster.nodeIds.length &&
                        hl.nodeIds.every(id => cluster.nodeIds.includes(id)));
                    if (!existingHyperlink) {
                        this.createHyperlink(cluster.nodeIds, 'associates', // Type par défaut, pourrait être affiné
                        `Cluster: ${cluster.label || 'Sans nom'}`, {
                            nature: 'associative',
                            scope: 'broad'
                        }, cluster.cohesion);
                        newRelationsCount++; // Count hyperlink creation
                    }
                }
                // Également, inférer des relations individuelles entre les membres du cluster
                for (let i = 0; i < cluster.nodeIds.length; i++) {
                    for (let j = i + 1; j < cluster.nodeIds.length; j++) {
                        const thought1 = this.getThought(cluster.nodeIds[i]);
                        const thought2 = this.getThought(cluster.nodeIds[j]);
                        if (!thought1 || !thought2)
                            continue;
                        // Vérifier si une connexion existe déjà dans les deux sens
                        const hasConnection = thought1.connections.some(conn => conn.targetId === thought2.id);
                        const hasReciprocalConnection = thought2.connections.some(conn => conn.targetId === thought1.id);
                        if (!hasConnection && !hasReciprocalConnection) {
                            // La confiance est basée sur la cohésion du cluster
                            const confidence = cluster.cohesion * 0.9; // Slightly lower confidence than cluster itself
                            if (confidence >= confidenceThreshold) {
                                this.addInferredConnection(thought1.id, thought2.id, 'associates', // Assume association within cluster
                                confidence);
                                newRelationsCount++;
                            }
                        }
                    }
                }
            }
        }
        return newRelationsCount;
    }
    /**
     * Détecte des clusters (groupes) de pensées fortement connectées
     *
     * @returns Un tableau de clusters détectés
     */
    detectClusters() {
        const clusters = [];
        // Algorithme simple de clustering basé sur la connectivité
        const thoughts = this.getAllThoughts();
        const visited = new Set();
        for (const thought of thoughts) {
            if (visited.has(thought.id))
                continue;
            // Parcourir le graphe à partir de cette pensée
            const cluster = this.exploreCommunity(thought.id, visited);
            // Seulement considérer les clusters avec au moins 2 nœuds
            if (cluster.nodeIds.length >= 2) {
                clusters.push(cluster);
            }
        }
        return clusters;
    }
    /**
     * Explore une communauté connectée à partir d'un nœud de départ
     *
     * @param startNodeId ID du nœud de départ
     * @param visited Ensemble des nœuds déjà visités
     * @returns Un cluster de nœuds connectés
     */
    exploreCommunity(startNodeId, visited) {
        const communityNodes = [];
        const queue = [startNodeId];
        const connectivityScores = [];
        const internalEdges = new Set(); // Track internal edges to calculate cohesion
        while (queue.length > 0) {
            const currentId = queue.shift();
            if (visited.has(currentId))
                continue;
            visited.add(currentId);
            communityNodes.push(currentId);
            const thought = this.getThought(currentId);
            if (!thought)
                continue;
            // Évaluer les connexions
            for (const conn of thought.connections) {
                // Check if the target is part of the potential community (already visited or in queue)
                const targetInCommunity = visited.has(conn.targetId) || queue.includes(conn.targetId);
                if (!visited.has(conn.targetId) && conn.strength > 0.5) { // Threshold for exploring
                    queue.push(conn.targetId);
                }
                // If the connection is internal to the community being explored
                if (targetInCommunity) {
                    const edgeId = [currentId, conn.targetId].sort().join('-'); // Unique ID for undirected edge
                    if (!internalEdges.has(edgeId)) {
                        connectivityScores.push(conn.strength);
                        internalEdges.add(edgeId);
                    }
                }
            }
        }
        // Calculer la cohésion du cluster basée sur la force moyenne des connexions internes
        const avgConnectivity = connectivityScores.length > 0
            ? connectivityScores.reduce((sum, score) => sum + score, 0) / connectivityScores.length
            : 0;
        // Basic labeling (e.g., based on most frequent keywords in the cluster) - can be enhanced
        let label = undefined;
        if (communityNodes.length > 0) {
            const clusterContent = communityNodes.map(id => this.getThought(id)?.content || "").join(" ");
            // Simple keyword extraction for label (replace with more sophisticated method if needed)
            const words = clusterContent.toLowerCase().split(/\W+/).filter(w => w.length > 4);
            const wordCounts = {};
            words.forEach(w => { wordCounts[w] = (wordCounts[w] || 0) + 1; });
            const sortedWords = Object.entries(wordCounts).sort((a, b) => b[1] - a[1]);
            if (sortedWords.length > 0) {
                label = sortedWords[0][0];
            }
        }
        return {
            nodeIds: communityNodes,
            label: label,
            cohesion: avgConnectivity
        };
    }
    /**
     * Infère le type de connexion approprié entre deux pensées
     *
     * @param sourceThought La pensée source
     * @param targetThought La pensée cible
     * @returns Le type de connexion inféré
     */
    inferConnectionType(sourceThought, targetThought) {
        // Analyse simple basée sur le contenu et le type des pensées
        const sourceContent = sourceThought.content.toLowerCase();
        const targetContent = targetThought.content.toLowerCase();
        const combinedContent = sourceContent + " " + targetContent; // Combine for easier marker checking
        // --- Enrichissement des marqueurs ---
        const contradictionMarkers = [
            'cependant', 'mais', 'toutefois', 'contrairement', 'oppose', 'inversement',
            'contredit', 'différent', 'désaccord', 'conteste', 'au contraire', 'réfute',
            'pourtant', 'malgré', 'bien que', 'alors que', 'pas d\'accord', 'faux', 'incorrect'
        ];
        const supportMarkers = [
            'confirme', 'soutient', 'renforce', 'valide', 'appuie', 'corrobore',
            'accord', 'similaire', 'également', 'aussi', 'de même', 'en effet',
            'prouve', 'démontre', 'illustre', 'comme', 'ainsi', 'par exemple' // Certains peuvent aussi être 'exemplifies'
        ];
        const derivationMarkers = [
            'donc', 'par conséquent', 'ainsi', 'résulte', 'implique', 'entraîne',
            'si...alors', 'parce que', 'car', 'puisque', 'étant donné', 'conclusion', 'synthèse'
        ];
        const exemplificationMarkers = [
            'par exemple', 'comme', 'illustre', 'notamment', 'tel que'
        ];
        const questionMarkers = [
            'pourquoi', 'comment', 'quel', 'quelle', 'qui', 'où', 'quand', '?', 'remet en question'
        ];
        const refinementMarkers = [
            'précise', 'détaille', 'améliore', 'corrige', 'en d\'autres termes', 'spécifiquement'
        ];
        // --- Logique d'inférence améliorée ---
        // Priorité aux contradictions
        if (contradictionMarkers.some((marker) => combinedContent.includes(marker))) { // Explicitly typed param
            return 'contradicts';
        }
        // Ensuite, les dérivations/applications (logique forte)
        if (derivationMarkers.some((marker) => combinedContent.includes(marker))) { // Explicitly typed param
            // Affiner si possible (ex: si source est conclusion/meta et cible est regular -> synthesizes)
            if (sourceThought.type === 'conclusion' && targetThought.type !== 'conclusion')
                return 'synthesizes';
            if (sourceThought.type === 'meta')
                return 'analyzes'; // ou evaluates
            // Si contient "si...alors", pourrait être 'applies' ou 'derives'
            if (combinedContent.includes('si') && combinedContent.includes('alors'))
                return 'applies';
            return 'derives'; // Ou 'supports' selon le contexte exact
        }
        // Ensuite, les exemples
        if (exemplificationMarkers.some((marker) => combinedContent.includes(marker))) { // Explicitly typed param
            // Vérifier la direction (source exemplifie cible ou l'inverse)
            if (exemplificationMarkers.some((marker) => sourceContent.includes(marker)))
                return 'exemplifies'; // Check marker in source
            if (exemplificationMarkers.some((marker) => targetContent.includes(marker)))
                return 'generalizes'; // Check marker in target
            return 'exemplifies'; // Par défaut
        }
        // Ensuite, les questions
        if (questionMarkers.some((marker) => combinedContent.includes(marker))) { // Explicitly typed param
            return 'questions';
        }
        // Ensuite, les raffinements/révisions
        if (refinementMarkers.some((marker) => combinedContent.includes(marker)) || sourceThought.type === 'revision') { // Explicitly typed param
            return 'refines';
        }
        // Ensuite, le support (logique plus faible ou association positive)
        if (supportMarkers.some((marker) => combinedContent.includes(marker))) { // Explicitly typed param
            return 'supports';
        }
        // Inférence basée sur les types de pensées
        if (sourceThought.type === 'conclusion' && targetThought.type !== 'conclusion') {
            return 'synthesizes';
        }
        if (sourceThought.type === 'hypothesis' && targetThought.type === 'regular') {
            return 'generalizes';
        }
        if (sourceThought.type === 'regular' && targetThought.type === 'hypothesis') {
            return 'exemplifies';
        }
        if (sourceThought.type === 'meta') {
            return 'analyzes';
        }
        // Removed redundant check for 'revision' here as it's handled earlier
        // Par défaut, type d'association générique
        return 'associates';
    }
    /**
     * Ajoute une connexion inférée entre deux pensées
     *
     * @param sourceId ID de la pensée source
     * @param targetId ID de la pensée cible
     * @param type Type de connexion
     * @param confidence Niveau de confiance dans l'inférence
     * @returns true si l'ajout a réussi, false sinon
     */
    addInferredConnection(sourceId, targetId, type, confidence) {
        const sourceThought = this.getThought(sourceId);
        const targetThought = this.getThought(targetId);
        if (!sourceThought || !targetThought) {
            return false;
        }
        // Créer la connexion avec marquage d'inférence
        const inferenceJustification = {
            summary: 'Connexion inférée automatiquement par SimilarityEngine',
            heuristics: [{
                    metric: 'similarity',
                    weight: confidence,
                    score: confidence,
                    rationale: 'Score de similarité calculé lors de l’inférence automatique'
                }],
            timestamp: new Date().toISOString()
        };
        const connection = {
            targetId,
            type,
            strength: confidence, // Use confidence as strength for inferred connections
            inferred: true,
            inferenceConfidence: confidence,
            attributes: {
                certainty: this.mapConfidenceToCertainty(confidence)
            },
            createdByStepId: 'auto-inference',
            justification: inferenceJustification,
            heuristicWeights: inferenceJustification.heuristics
        };
        // Ajouter la connexion à la pensée source
        sourceThought.connections.push(connection);
        // Créer une connexion réciproque si nécessaire
        if (this.shouldCreateReciprocalConnection(type)) {
            const reciprocalType = this.getReciprocalConnectionType(type);
            targetThought.connections.push({
                targetId: sourceId,
                type: reciprocalType,
                strength: confidence,
                inferred: true,
                inferenceConfidence: confidence,
                attributes: {
                    certainty: this.mapConfidenceToCertainty(confidence)
                },
                createdByStepId: 'auto-inference',
                justification: inferenceJustification,
                heuristicWeights: inferenceJustification.heuristics
            });
        }
        return true;
    }
    /**
     * Détermine si une connexion réciproque doit être créée
     *
     * @param type Le type de connexion
     * @returns true si une connexion réciproque doit être créée
     */
    shouldCreateReciprocalConnection(type) {
        // Certains types de connexion sont intrinsèquement bidirectionnels
        const bidirectionalTypes = [
            'associates', 'compares', 'contrasts', 'supports', 'contradicts' // Added supports/contradicts as often reciprocal
        ];
        return bidirectionalTypes.includes(type);
    }
    /**
     * Convertit un niveau de confiance en niveau de certitude
     *
     * @param confidence Le niveau de confiance (0 à 1)
     * @returns Le niveau de certitude correspondant
     */
    mapConfidenceToCertainty(confidence) {
        if (confidence >= 0.9)
            return 'definite';
        if (confidence >= 0.75)
            return 'high';
        if (confidence >= 0.5)
            return 'moderate';
        if (confidence >= 0.3)
            return 'low';
        return 'speculative';
    }
    /**
     * Enrichit une pensée existante avec des attributs sémantiques pour ses connexions
     *
     * @param thoughtId L'ID de la pensée à enrichir
     * @returns Le nombre de connexions enrichies
     */
    enrichThoughtConnections(thoughtId) {
        const thought = this.getThought(thoughtId);
        if (!thought)
            return 0;
        let enrichedCount = 0;
        for (const connection of thought.connections) {
            // Ignorer les connexions déjà enrichies ou inférées (qui ont déjà des attributs)
            if (connection.attributes || connection.inferred)
                continue;
            const targetThought = this.getThought(connection.targetId);
            if (!targetThought)
                continue;
            // Enrichir avec des attributs sémantiques inférés du contexte
            connection.attributes = this.inferConnectionAttributes(thought, targetThought, connection.type);
            enrichedCount++;
        }
        return enrichedCount;
    }
    /**
     * Infère des attributs sémantiques pour une connexion
     *
     * @param sourceThought La pensée source
     * @param targetThought La pensée cible
     * @param type Le type de connexion
     * @returns Les attributs sémantiques inférés
     */
    inferConnectionAttributes(sourceThought, targetThought, type) {
        const attributes = {};
        // Inférer la temporalité (inchangé)
        if (sourceThought.timestamp < targetThought.timestamp) {
            attributes.temporality = 'before';
        }
        else if (sourceThought.timestamp > targetThought.timestamp) {
            attributes.temporality = 'after';
        }
        else {
            attributes.temporality = 'concurrent';
        }
        // --- Inférence de Certitude ---
        const certaintyMarkersLow = ['peut-être', 'semble', 'possible', 'pourrait', 'probablement', 'suggère'];
        const certaintyMarkersHigh = ['certainement', 'clairement', 'évidemment', 'prouvé', 'démontré', 'sans aucun doute'];
        const combinedContentForCertainty = sourceThought.content.toLowerCase() + " " + targetThought.content.toLowerCase();
        if (certaintyMarkersHigh.some((marker) => combinedContentForCertainty.includes(marker))) { // Explicitly typed param
            attributes.certainty = 'high'; // Ou 'definite' si très fort
        }
        else if (certaintyMarkersLow.some((marker) => combinedContentForCertainty.includes(marker))) { // Explicitly typed param
            attributes.certainty = 'low'; // Ou 'speculative' si très faible
        }
        else {
            // Default basé sur la confiance de la connexion inférée (si disponible)
            // Ou 'moderate' par défaut
            attributes.certainty = 'moderate';
        }
        // Note: La confiance de la connexion elle-même (strength) pourrait aussi influencer la certitude.
        // Inférer la nature (légèrement affiné)
        switch (type) {
            case 'supports':
            case 'contradicts':
                attributes.nature = 'associative'; // Could be causal/correlational depending on content
                break;
            case 'derives':
                attributes.nature = combinedContentForCertainty.includes('parce que') || combinedContentForCertainty.includes('car') ? 'causal' : 'sequential';
                break;
            case 'refines':
            case 'generalizes':
            case 'exemplifies':
            case 'component-of':
            case 'synthesizes':
                attributes.nature = 'hierarchical';
                break;
            case 'branches':
                attributes.nature = 'sequential';
                break;
            case 'analyzes':
            case 'evaluates':
            case 'applies':
                attributes.nature = 'causal'; // Maintient l'hypothèse causale/applicative
                break;
            default:
                attributes.nature = 'associative';
        }
        // Inférer la directionnalité (inchangé)
        if (this.shouldCreateReciprocalConnection(type)) {
            attributes.directionality = 'bidirectional';
        }
        else {
            attributes.directionality = 'unidirectional';
        }
        // Inférer la portée (inchangé - heuristique simple)
        const sourceLength = sourceThought.content.length;
        const targetLength = targetThought.content.length;
        if (Math.abs(sourceLength - targetLength) < 50) {
            attributes.scope = 'specific';
        }
        else if (sourceLength > targetLength * 1.5 || targetLength > sourceLength * 1.5) {
            attributes.scope = 'broad'; // One thought is much broader/narrower
        }
        else {
            attributes.scope = 'partial';
        }
        return attributes;
    }
    /**
     * Suggère les prochaines étapes de raisonnement, potentiellement en utilisant le LLM.
     *
     * @param limit Le nombre maximum de suggestions
     * @param sessionId L'identifiant de session facultatif pour filtrer le contexte
     * @returns Une promesse résolvant vers un tableau de suggestions pour les prochaines étapes
     */
    async suggestNextSteps(limit = 3, sessionId) {
        const sessionThoughts = this.getAllThoughts(sessionId);
        // Si aucune pensée n'existe dans la session, retourner un tableau vide
        if (sessionThoughts.length === 0) {
            return [];
        }
        // Recueillir un contexte riche pour le LLM
        // 1. Obtenir la pensée initiale (probablement la première de la session)
        const initialThought = sessionThoughts.length > 0 ?
            sessionThoughts.reduce((earliest, current) => new Date(earliest.metadata?.timestamp || 0) <= new Date(current.metadata?.timestamp || 0) ? earliest : current, sessionThoughts[0]) : null;
        const initialThoughtSummary = initialThought ?
            `[${initialThought.type}] ${initialThought.content.substring(0, 150)}...` :
            'Non identifié';
        // 2. Identifier les nœuds clés
        // Hypothèses récentes
        const hypotheses = sessionThoughts.filter(t => t.type === 'hypothesis');
        const recentHypotheses = hypotheses
            .sort((a, b) => new Date(b.metadata?.timestamp || 0).getTime() - new Date(a.metadata?.timestamp || 0).getTime())
            .slice(0, 3);
        const hypothesesSummary = recentHypotheses.length > 0 ?
            recentHypotheses.map(h => h.content.substring(0, 100)).join('\n') :
            'Aucune hypothèse formulée';
        // Conclusions récentes
        const conclusions = sessionThoughts.filter(t => t.type === 'conclusion');
        const recentConclusions = conclusions
            .sort((a, b) => new Date(b.metadata?.timestamp || 0).getTime() - new Date(a.metadata?.timestamp || 0).getTime())
            .slice(0, 2);
        const conclusionsSummary = recentConclusions.length > 0 ?
            recentConclusions.map(c => c.content.substring(0, 100)).join('\n') :
            'Aucune conclusion formulée';
        // Questions ouvertes (pensées contenant des points d'interrogation ou des mots-clés de question)
        const questionKeywords = ['pourquoi', 'comment', 'quoi', 'qui', 'où', 'quand', 'quel', 'quelle'];
        const openQuestions = sessionThoughts.filter(t => t.content.includes('?') ||
            questionKeywords.some(keyword => t.content.toLowerCase().includes(keyword)));
        const recentQuestions = openQuestions
            .sort((a, b) => new Date(b.metadata?.timestamp || 0).getTime() - new Date(a.metadata?.timestamp || 0).getTime())
            .slice(0, 3);
        const questionsSummary = recentQuestions.length > 0 ?
            recentQuestions.map(q => q.content.substring(0, 100)).join('\n') :
            'Aucune question explicite identifiée';
        // 3. Identifier les contradictions et problèmes
        const contradictions = sessionThoughts.filter(thought => thought.connections.some(conn => {
            // Check if the connected thought also belongs to the session
            const targetThought = this.getThought(conn.targetId);
            return conn.type === 'contradicts' && targetThought?.metadata?.sessionId === sessionId;
        }));
        const contradictionsSummary = contradictions.length > 0 ?
            contradictions.map(c => {
                const contradictingConnections = c.connections.filter(conn => conn.type === 'contradicts');
                const contradictedThoughts = contradictingConnections.map(conn => this.getThought(conn.targetId));
                return `"${c.content.substring(0, 70)}..." contredit "${contradictedThoughts[0]?.content.substring(0, 70)}..."`;
            }).join('\n') :
            'Aucune contradiction explicite détectée';
        // 4. Résumer l'activité récente
        const recentThoughts = this.getRecentThoughts(5, sessionId);
        const thoughtTypes = recentThoughts.map(t => t.type);
        const uniqueTypes = [...new Set(thoughtTypes)];
        const recentActivitySummary = `Activité récente: ${uniqueTypes.join(', ')}`;
        // 5. Analyser la structure du graphe
        let graphStructureSummary = 'Structure indéterminée';
        if (sessionThoughts.length <= 3) {
            graphStructureSummary = 'Exploration initiale';
        }
        else {
            // Vérifier si la structure est principalement linéaire
            const isLinear = sessionThoughts.every(t => t.connections.length <= 2);
            // Vérifier s'il y a des clusters
            const hasClusters = this.detectClusters().length > 1;
            // Vérifier s'il y a beaucoup de connexions (structure en réseau)
            const avgConnections = sessionThoughts.reduce((sum, t) => sum + t.connections.length, 0) / sessionThoughts.length;
            if (isLinear) {
                graphStructureSummary = 'Progression principalement linéaire';
            }
            else if (hasClusters) {
                graphStructureSummary = 'Plusieurs clusters de pensées identifiés';
            }
            else if (avgConnections > 3) {
                graphStructureSummary = 'Structure en réseau avec nombreuses connexions';
            }
            else {
                graphStructureSummary = 'Structure mixte avec quelques branches';
            }
        }
        // 6. Construire un prompt utilisateur enrichi
        const userPrompt = `
Analyse de la Session de Raisonnement (ID: ${sessionId || 'session principale'}):
- Pensée Initiale/Objectif: ${initialThoughtSummary}
- Focus d'Activité Récente: ${recentActivitySummary}
- Nœuds Clés:
    - Hypothèses: ${hypothesesSummary}
    - Conclusions: ${conclusionsSummary}
    - Questions Ouvertes: ${questionsSummary}
- État du Graphe: ${graphStructureSummary} (${sessionThoughts.length} pensées au total)
- Problèmes Identifiés: ${contradictionsSummary}

En fonction de cet état, suggérez les ${limit} prochaines étapes concrètes pour faire avancer le processus de raisonnement. 
Concentrez-vous sur des étapes qui:
- Répondent aux questions ouvertes
- Testent les hypothèses formulées
- Résolvent les contradictions identifiées
- Approfondissent l'analyse des points importants
- Utilisent les outils disponibles (recherche web, exécution de code, etc.) quand c'est pertinent

Formatez la réponse comme un tableau JSON d'objets, chacun avec 'description' (string), 'type' (ThoughtType comme 'regular', 'hypothesis', 'meta', etc.), 'confidence' (nombre entre 0 et 1), et 'reasoning' (string expliquant pourquoi cette étape est suggérée).

Exemple: [{"description": "Vérifier l'affirmation X avec une recherche web", "type": "regular", "confidence": 0.9, "reasoning": "Cette affirmation manque de sources fiables"}, ...]
`;
        // 7. Améliorer le prompt système
        const systemPrompt = `Vous êtes un assistant IA expert guidant un processus de raisonnement complexe suivi dans un graphe de pensées. 
Votre tâche est d'analyser le résumé fourni de l'état actuel du raisonnement (incluant objectifs, nœuds clés, structure et problèmes) 
et de suggérer ${limit} prochaines étapes concrètes, logiques et pertinentes.

Les suggestions doivent aider l'utilisateur à faire progresser sa réflexion, explorer des possibilités, vérifier des informations, 
ou synthétiser des découvertes. Vos suggestions doivent être spécifiques et actionables, pas génériques.

Assurez-vous que votre sortie est UNIQUEMENT le tableau JSON demandé, correctement formaté et parsable.`;
        // 8. Appeler le LLM avec le contexte enrichi
        let llmSuggestions = null;
        try {
            // Réduire la température pour une sortie JSON plus fiable
            const llmResponse = await (0, openrouter_client_1.callInternalLlm)(systemPrompt, userPrompt, 2000);
            if (llmResponse) {
                // Améliorer la détection et l'analyse du JSON
                const jsonMatch = llmResponse.match(/\[\s*\{[\s\S]*\}\s*\]/);
                if (jsonMatch) {
                    const jsonString = jsonMatch[0];
                    try {
                        const parsedSuggestions = JSON.parse(jsonString);
                        if (Array.isArray(parsedSuggestions) && parsedSuggestions.length > 0) {
                            const validatedSuggestions = [];
                            for (const suggestion of parsedSuggestions) {
                                if (suggestion &&
                                    typeof suggestion.description === 'string' &&
                                    typeof suggestion.type === 'string') {
                                    // Valider et normaliser la structure
                                    validatedSuggestions.push({
                                        description: suggestion.description,
                                        type: suggestion.type,
                                        confidence: typeof suggestion.confidence === 'number' ?
                                            Math.min(Math.max(suggestion.confidence, 0), 1) : 0.8,
                                        reasoning: typeof suggestion.reasoning === 'string' ?
                                            suggestion.reasoning : "Suggéré par LLM basé sur l'état du graphe."
                                    });
                                    if (validatedSuggestions.length >= limit)
                                        break;
                                }
                            }
                            if (validatedSuggestions.length > 0) {
                                llmSuggestions = validatedSuggestions;
                            }
                        }
                    }
                    catch (parseError) {
                        console.warn(`Réponse LLM pour suggestion d'étapes suivantes n'était pas un JSON valide: ${llmResponse}`, parseError);
                        // Log the raw response for debugging
                        console.log('Réponse brute du LLM:', llmResponse);
                    }
                }
                else {
                    console.warn('Aucun JSON trouvé dans la réponse LLM:', llmResponse);
                }
            }
        }
        catch (llmError) {
            console.error('Erreur lors de l\'appel au LLM pour suggestion d\'étapes suivantes:', llmError);
        }
        if (llmSuggestions && llmSuggestions.length > 0) {
            return llmSuggestions;
        }
        // Fallback à la méthode heuristique si le LLM échoue
        return this.suggestNextStepsHeuristic(limit, sessionId);
    }
    /**
     * Méthode heuristique originale pour suggérer les prochaines étapes (filtrée par session).
     */
    suggestNextStepsHeuristic(limit = 3, sessionId) {
        // Use session-specific thoughts
        const sessionThoughts = this.getAllThoughts(sessionId);
        // Removed the initial check for sessionThoughts.length === 0
        const suggestions = [];
        // Use session-specific recent thoughts
        const recentThoughts = this.getRecentThoughts(3, sessionId);
        const recentContent = recentThoughts.map(t => t.content).join(' ');
        // Analyser le contenu récent pour détecter des besoins spécifiques
        const needsFactChecking = this.containsAny(recentContent.toLowerCase(), ['vérifier', 'confirmer', 'source', 'preuve', 'statistique', 'données', 'affirme', 'selon']);
        const needsCalculation = this.containsAny(recentContent.toLowerCase(), ['calculer', 'calcul', 'équation', 'résoudre', 'chiffre', 'formule', 'mathématique']);
        const needsExternalInfo = this.containsAny(recentContent.toLowerCase(), ['chercher', 'information', 'recherche', 'trouver', 'données', 'référence', 'source', 'actualité']);
        const containsUncertainty = this.containsAny(recentContent.toLowerCase(), ['peut-être', 'probablement', 'semble', 'possible', 'hypothèse', 'incertain', 'pourrait']);
        // Suggestions basées sur les besoins détectés
        if (needsFactChecking) {
            suggestions.push({
                description: "Vérifiez les informations avec une recherche web",
                type: 'regular',
                confidence: 0.9,
                reasoning: "Utilisez l'outil perplexity_search_web ou tavily-search pour confirmer les faits mentionnés"
            });
        }
        if (needsCalculation) {
            suggestions.push({
                description: "Exécutez du code pour effectuer les calculs nécessaires",
                type: 'regular',
                confidence: 0.85,
                reasoning: "Utilisez l'outil executePython ou executeJavaScript pour résoudre les calculs ou équations"
            });
        }
        if (needsExternalInfo) {
            suggestions.push({
                description: "Recherchez des informations supplémentaires en ligne",
                type: 'regular',
                confidence: 0.9,
                reasoning: "Utilisez les outils de recherche web pour enrichir votre analyse avec des données pertinentes"
            });
        }
        // Check for contradictions within the session
        const hasContradictions = sessionThoughts.some(thought => thought.connections.some(conn => {
            // Check if the connected thought also belongs to the session
            const targetThought = this.getThought(conn.targetId);
            return conn.type === 'contradicts' && targetThought?.metadata?.sessionId === sessionId;
        }));
        if (hasContradictions) {
            suggestions.push({
                description: "Résolvez les contradictions en consultant des sources fiables",
                type: 'meta',
                confidence: 0.85,
                reasoning: "Utilisez tavily-search ou perplexity_search_web pour vérifier quelle position est correcte"
            });
        }
        // Suggestion pour extraire du contenu web si des URL sont mentionnées
        const containsUrl = /https?:\/\/[^\s]+/.test(recentContent);
        if (containsUrl) {
            suggestions.push({
                description: "Extrayez et analysez le contenu des URL mentionnées",
                type: 'regular',
                confidence: 0.85,
                reasoning: "Utilisez l'outil tavily-extract pour analyser en profondeur le contenu des pages web"
            });
        }
        // Check for meta thoughts within the session
        const hasMeta = sessionThoughts.some(thought => thought.type === 'meta');
        if (sessionThoughts.length >= 5 && !hasMeta && suggestions.length < limit) {
            suggestions.push({
                description: "Faites une méta-réflexion sur votre approche dans cette session",
                type: 'meta',
                confidence: 0.7,
                reasoning: "La méta-cognition peut améliorer la qualité du raisonnement"
            });
        }
        // Check for hypothesis within the session
        const hasHypothesis = sessionThoughts.some(thought => thought.type === 'hypothesis');
        if (((sessionThoughts.length >= 3 && !hasHypothesis) || containsUncertainty) && suggestions.length < limit) {
            suggestions.push({
                description: "Formulez une hypothèse basée sur vos observations dans cette session",
                type: 'hypothesis',
                confidence: 0.75,
                reasoning: "Une hypothèse claire peut guider la suite de votre raisonnement"
            });
        }
        // Check for conclusion within the session
        const hasConclusion = sessionThoughts.some(thought => thought.type === 'conclusion');
        if (sessionThoughts.length >= 7 && !hasConclusion && suggestions.length < limit) {
            suggestions.push({
                description: "Rédigez une conclusion provisoire basée sur votre analyse dans cette session",
                type: 'conclusion',
                confidence: 0.8,
                reasoning: "Même provisoire, une conclusion peut aider à synthétiser votre réflexion"
            });
        }
        // Check for hyperlinks within the session
        const sessionHyperlinks = this.getAllHyperlinks(sessionId);
        if (sessionHyperlinks.length > 0 && suggestions.length < limit) {
            suggestions.push({
                description: "Explorez les relations complexes identifiées dans les clusters de pensées de cette session",
                type: 'meta',
                confidence: 0.8,
                reasoning: "L'analyse des relations multi-nœuds peut révéler des insights cachés"
            });
        }
        // Assurer de ne pas dépasser la limite
        return suggestions.slice(0, limit);
    }
    /**
     * Vérifie si un texte contient l'un des termes donnés
     *
     * @param text Le texte à vérifier
     * @param terms Les termes à rechercher
     * @returns true si le texte contient au moins un des termes, false sinon
     */
    containsAny(text, terms) {
        // Optimisation: créer une regex unique pour tous les termes
        const regex = new RegExp(`\\b(${terms.join('|')})\\b`, 'i');
        return regex.test(text);
    }
    /**
     * Met à jour le contenu d'une pensée existante
     *
     * @param id L'identifiant de la pensée à mettre à jour
     * @param newContent Le nouveau contenu de la pensée
     * @returns true si la mise à jour a réussi, false sinon
     */
    updateThoughtContent(id, newContent) {
        const thought = this.nodes.get(id);
        if (!thought)
            return false;
        const oldContent = thought.content;
        thought.content = newContent;
        thought.metadata.lastUpdated = new Date();
        // Reset verification status on content update? Maybe optional.
        // thought.metadata.isVerified = false;
        // thought.metadata.verificationSource = undefined;
        // thought.metadata.verificationTimestamp = undefined;
        // Émettre un événement pour notifier de la mise à jour d'une pensée
        this.eventEmitter.emit('thought-updated', id, thought, { oldContent });
        // Vérifier automatiquement les calculs si nécessaire (async, but don't wait)
        this.checkForCalculationsAndVerify(id, newContent).catch(err => {
            console.error("Error during background calculation check:", err);
        });
        // Recalculate metrics asynchronously (don't wait)
        this.updateMetricsForThought(id).catch(err => {
            console.error(`Error during background metric calculation for ${id}:`, err);
        });
        return true;
    }
    /**
     * Efface toutes les pensées du graphe
     */
    clear() {
        this.nodes.clear();
        this.hyperlinks.clear();
        // Peut-être aussi effacer les caches associés si nécessaire
    }
    /**
     * Exporte le graphe de pensées sous forme de JSON
     *
     * @returns Une représentation JSON du graphe
     */
    exportToJson() {
        // Convert Map values to array for JSON stringification
        return JSON.stringify(Array.from(this.nodes.values()));
    }
    /**
     * Exporte le graphe enrichi (nœuds et hyperliens) sous forme de JSON
     *
     * @returns Une représentation JSON du graphe enrichi
     */
    exportEnrichedGraph() {
        const exportData = {
            nodes: Array.from(this.nodes.values()),
            hyperlinks: Array.from(this.hyperlinks.values())
        };
        // Use replacer function to handle Date objects correctly
        return JSON.stringify(exportData, (key, value) => {
            if (value instanceof Date) {
                return value.toISOString();
            }
            return value;
        });
    }
    /**
     * Importe un graphe de pensées depuis un JSON
     *
     * @param json Le JSON à importer
     * @returns true si l'importation a réussi, false sinon
     */
    importFromJson(json) {
        try {
            const nodes = JSON.parse(json);
            this.clear();
            for (const node of nodes) {
                // Rehydrate Date objects
                if (node.timestamp)
                    node.timestamp = new Date(node.timestamp);
                if (node.metadata?.lastUpdated)
                    node.metadata.lastUpdated = new Date(node.metadata.lastUpdated);
                if (node.metadata?.verificationTimestamp)
                    node.metadata.verificationTimestamp = new Date(node.metadata.verificationTimestamp);
                this.nodes.set(node.id, node);
            }
            return true;
        }
        catch (error) {
            console.error('Erreur lors de l\'importation du graphe:', error);
            return false;
        }
    }
    /**
     * Importe un graphe enrichi depuis un JSON
     *
     * @param json Le JSON à importer
     * @returns true si l'importation a réussi, false sinon
     */
    importEnrichedGraph(json) {
        try {
            const data = JSON.parse(json);
            // Importer les nœuds
            if (Array.isArray(data.nodes)) {
                this.clear(); // Clear existing graph data
                for (const node of data.nodes) {
                    // Rehydrate Date objects
                    if (node.timestamp)
                        node.timestamp = new Date(node.timestamp);
                    if (node.metadata?.lastUpdated)
                        node.metadata.lastUpdated = new Date(node.metadata.lastUpdated);
                    if (node.metadata?.verificationTimestamp)
                        node.metadata.verificationTimestamp = new Date(node.metadata.verificationTimestamp);
                    if (node.metadata?.createdAt)
                        node.metadata.createdAt = new Date(node.metadata.createdAt); // For hyperlinks metadata
                    this.nodes.set(node.id, node);
                }
            }
            // Importer les hyperliens
            if (Array.isArray(data.hyperlinks)) {
                this.hyperlinks.clear();
                for (const hyperlink of data.hyperlinks) {
                    // Rehydrate Date objects in metadata
                    if (hyperlink.metadata?.createdAt)
                        hyperlink.metadata.createdAt = new Date(hyperlink.metadata.createdAt);
                    this.hyperlinks.set(hyperlink.id, hyperlink);
                }
            }
            return true;
        }
        catch (error) {
            console.error('Erreur lors de l\'importation du graphe enrichi:', error);
            return false;
        }
    }
}
exports.ThoughtGraph = ThoughtGraph;
//# sourceMappingURL=thought-graph.js.map