Newer
Older
cortex-hub / frontend / src / features / agents / hooks / useAgentDrillDown.js
import { useState, useEffect, useCallback } from 'react';
import { 
    getAgents, 
    getSessionMessages, 
    fetchWithAuth, 
    updateAgentConfig, 
    getUserConfig, 
    clearSessionHistory, 
    getSessionTokenStatus, 
    getAgentTriggers, 
    createAgentTrigger, 
    deleteAgentTrigger, 
    getUserAccessibleNodes, 
    getSkills, 
    resetAgentMetrics, 
    getAgentCortexFiles, 
    getAgentCortexFile 
} from '../../../services/apiService';

export const useAgentDrillDown = (agentId) => {
    const [agent, setAgent] = useState(null);
    const [chatHistory, setChatHistory] = useState([]);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);
    const [overrideText, setOverrideText] = useState("");
    
    // UI State
    const [activeTab, setActiveTab] = useState('config');
    const [editConfig, setEditConfig] = useState(null);
    const [saving, setSaving] = useState(false);
    const [userConfig, setUserConfig] = useState(null);
    const [tokenUsage, setTokenUsage] = useState({ token_count: 0, token_limit: 0, percentage: 0 });
    const [tokenError, setTokenError] = useState(null);
    const [clearing, setClearing] = useState(false);
    const [triggers, setTriggers] = useState([]);
    const [newTriggerType, setNewTriggerType] = useState('cron');
    const [newCronValue, setNewCronValue] = useState('0 * * * *');
    const [newIntervalValue, setNewIntervalValue] = useState(600);
    const [newDefaultPrompt, setNewDefaultPrompt] = useState('');
    const [creatingTrigger, setCreatingTrigger] = useState(false);
    const [modalConfig, setModalConfig] = useState(null);
    const [nodes, setNodes] = useState([]);
    const [allSkills, setAllSkills] = useState([]);
    const [flippedCards, setFlippedCards] = useState({ runtime: false, tokens: false });

    // Evaluation Hub State
    const [cortexFiles, setCortexFiles] = useState([]);
    const [feedbackContent, setFeedbackContent] = useState("");
    const [rubricContent, setRubricContent] = useState("");
    const [coworkerContent, setCoworkerContent] = useState("");
    const [historyLog, setHistoryLog] = useState([]);
    const [savingGroundTruth, setSavingGroundTruth] = useState(false);
    const [selectedAuditId, setSelectedAuditId] = useState(null);

    // Monitoring & Timer States
    const [runningSeconds, setRunningSeconds] = useState(0);
    const [lastTotalConsumption, setLastTotalConsumption] = useState(null);
    const [previousStatus, setPreviousStatus] = useState('idle');
    const [currentAction, setCurrentAction] = useState(null);
    const [lastAction, setLastAction] = useState(null);
    const [lastActionDuration, setLastActionDuration] = useState(null);
    const [actionStartTime, setActionStartTime] = useState(0);

    const fetchData = useCallback(async () => {
        try {
            const allAgents = await getAgents();
            const found = allAgents.find(a => a.id === agentId);
            if (!found) throw new Error("Agent not found");
            setAgent(found);

            setEditConfig(prev => prev || {
                name: found.template?.name || "",
                system_prompt: found.template?.system_prompt_content || found.template?.system_prompt_path || "",
                max_loop_iterations: found.template?.max_loop_iterations || 20,
                mesh_node_id: found.mesh_node_id || "",
                provider_name: found.session?.provider_name || "",
                restrict_skills: found.session?.restrict_skills || false,
                allowed_skill_ids: found.session?.skills ? found.session.skills.map(s => s.id) : [],
                is_locked: found.session?.is_locked || false,
                auto_clear_history: found.session?.auto_clear_history || false,
                co_worker_quality_gate: found.template?.co_worker_quality_gate || false,
                rework_threshold: found.template?.rework_threshold || 80,
                max_rework_attempts: found.template?.max_rework_attempts || 3
            });

            if (found.session_id) {
                const historyResp = await getSessionMessages(found.session_id);
                const formatted = (historyResp.messages || []).map(m => ({
                    text: m.content,
                    isUser: m.sender === 'user',
                    reasoning: m.reasoning_content,
                    status: null,
                    sender: m.sender,
                    timestamp: m.created_at,
                    id: m.id,
                    tool_calls: m.tool_calls,
                    message_metadata: m.message_metadata
                }));
                setChatHistory(formatted);
                
                try {
                    const usage = await getSessionTokenStatus(found.session_id);
                    if (usage.error) {
                        setTokenError(usage.error);
                        setTokenUsage({ token_count: 0, token_limit: 0, percentage: 0 });
                    } else {
                        setTokenUsage(usage);
                        setTokenError(null);
                    }
                } catch(e) {
                    setTokenError(e.message);
                }
            }

            try {
                const tList = await getAgentTriggers(agentId);
                setTriggers(tList);
            } catch(e) {}

            const sid = found.session?.sync_workspace_id || found.session_id;
            const nodeId = found.mesh_node_id || "hub";
            
            if (sid) {
                try {
                    const cFilesListing = await getAgentCortexFiles(agentId, nodeId, sid);
                    const files = cFilesListing.files || [];
                    setCortexFiles(files);

                    const fileExists = (name) => files.some(f => f.name === name || f.path === `.cortex/${name}`);

                    if (fileExists("feedback.md")) {
                        try {
                            const feedback = await getAgentCortexFile(agentId, nodeId, sid, "feedback.md");
                            setFeedbackContent(feedback?.content || "");
                        } catch (e) {}
                    }

                    if (fileExists("rubric.md")) {
                        try {
                            const rubric = await getAgentCortexFile(agentId, nodeId, sid, "rubric.md");
                            setRubricContent(rubric?.content || "");
                        } catch (e) {}
                    }

                    try {
                        const coworker = await getAgentCortexFile(agentId, nodeId, sid, ".coworker.md");
                        setCoworkerContent(coworker?.content || "");
                    } catch (e) {}

                    if (fileExists("history.log")) {
                        try {
                            const logs = await getAgentCortexFile(agentId, nodeId, sid, "history.log");
                            if (logs?.content) {
                                try {
                                    const parsed = JSON.parse(logs.content);
                                    setHistoryLog(Array.isArray(parsed) ? parsed : []);
                                } catch (e) {
                                    setHistoryLog(logs.content.split('\n').filter(l => l.trim()).map(line => ({ message: line })));
                                }
                            }
                        } catch (e) {}
                    }
                } catch (e) {}
            }
        } catch (err) {
            setError(err.message);
        } finally {
            setLoading(false);
        }
    }, [agentId]);

    useEffect(() => {
        const loadConf = async () => {
            try {
                const [conf, nList, sList] = await Promise.all([
                    getUserConfig(),
                    getUserAccessibleNodes(),
                    getSkills()
                ]);
                setUserConfig(conf);
                setNodes(nList);
                setAllSkills(sList);
            } catch (e) {}
        };
        loadConf();
    }, []);

    useEffect(() => {
        fetchData();
        const interval = setInterval(fetchData, 2500);
        return () => clearInterval(interval);
    }, [agentId, fetchData]);

    useEffect(() => {
        let timer = null;
        const isRunning = agent?.status === 'active' || agent?.status === 'starting';
        
        if (isRunning) {
            timer = setInterval(() => {
                setRunningSeconds(s => s + 1);
            }, 1000);
        } else if (previousStatus === 'active' || previousStatus === 'starting') {
            setLastTotalConsumption(runningSeconds);
        }
        
        setPreviousStatus(agent?.status || 'idle');
        return () => { if (timer) clearInterval(timer); };
    }, [agent?.status, previousStatus, runningSeconds]);

    useEffect(() => {
        const isRunning = agent?.status === 'active' || agent?.status === 'starting';
        if (!isRunning) return;

        if (previousStatus !== 'active' && previousStatus !== 'starting') {
            setRunningSeconds(0);
            setLastTotalConsumption(null);
            setCurrentAction(null);
            setLastAction(null);
            setLastActionDuration(null);
            setActionStartTime(0);
        }

        const rawStatus = agent?.evaluation_status || 'Orchestrating task payload...';
        
        if (rawStatus !== (currentAction?.raw || '')) {
            const lowStatus = rawStatus.toLowerCase();
            const hasPrefix = lowStatus.includes('agent:') || lowStatus.includes('audit:') || lowStatus.includes('co-worker:');
            
            let cleanStatus = rawStatus;
            if (!hasPrefix) {
                cleanStatus = (lowStatus.includes('audit') || lowStatus.includes('worker') || lowStatus.includes('evaluat'))
                    ? `🛡️ Co-Worker Audit: ${rawStatus}` 
                    : `🤖 Main Agent: ${rawStatus}`;
            }

            if (currentAction) {
                setLastAction(currentAction);
                setLastActionDuration(runningSeconds - actionStartTime);
            }
            
            setCurrentAction({ display: cleanStatus, raw: rawStatus });
            setActionStartTime(runningSeconds);
        }
    }, [agent?.status, agent?.evaluation_status, previousStatus, currentAction, runningSeconds, actionStartTime]);

    const handleAction = async (targetStatus) => {
        try {
            await fetchWithAuth(`/agents/${agentId}/status`, {
                method: 'PATCH',
                body: { status: targetStatus }
            });
            fetchData();
        } catch (err) {
            setModalConfig({ title: 'Status Change Failed', message: err.message, type: 'error' });
        }
    };

    const handleClearHistory = () => {
        if (!agent?.session_id) return;
        setModalConfig({
            title: 'Confirm Memory Wipe',
            message: "Are you sure you want to clear the agent's memory? This cannot be undone.",
            type: 'error',
            confirmText: 'Clear Memory',
            confirmAction: async () => {
                try {
                    setClearing(true);
                    if (agent?.session?.is_locked && editConfig?.is_locked === false) {
                        await updateAgentConfig(agent.id, { 
                            is_locked: false,
                            mesh_node_id: agent.mesh_node_id || "hub" 
                        });
                    }
                    await clearSessionHistory(agent.session_id);
                    setChatHistory([]);
                    setSelectedAuditId(null);
                    fetchData();
                } catch (err) {
                    setModalConfig({ title: 'Clear Failed', message: err.message, type: 'error' });
                } finally {
                    setClearing(false);
                }
            }
        });
    };

    const handleSaveConfig = async () => {
        try {
            setSaving(true);
            const payload = {
                name: editConfig.name,
                system_prompt: editConfig.system_prompt,
                max_loop_iterations: parseInt(editConfig.max_loop_iterations, 10) || 20,
                mesh_node_id: editConfig.mesh_node_id,
                provider_name: editConfig.provider_name,
                restrict_skills: editConfig.restrict_skills,
                allowed_skill_ids: editConfig.allowed_skill_ids,
                is_locked: editConfig.is_locked,
                auto_clear_history: editConfig.auto_clear_history,
                co_worker_quality_gate: editConfig.co_worker_quality_gate,
                rework_threshold: parseInt(editConfig.rework_threshold, 10),
                max_rework_attempts: parseInt(editConfig.max_rework_attempts, 10)
            };
            
            try {
                await fetchWithAuth(`/agents/${agentId}/status`, { method: "PATCH", body: { status: "idle" } });
            } catch (e) {}
            
            await updateAgentConfig(agentId, payload);
            fetchData();
            setModalConfig({ title: 'Success', message: 'Configuration Saved Successfully!', type: 'success' });
        } catch (err) {
            setModalConfig({ title: 'Save Failed', message: err.message, type: 'error' });
        } finally {
            setSaving(false);
        }
    };

    const handleSaveGroundTruth = async () => {
        try {
            setSavingGroundTruth(true);
            const sid = agent.session?.sync_workspace_id || agent.session_id;
            const nodeId = agent.mesh_node_id || "hub";

            await fetchWithAuth(`/nodes/${nodeId}/fs/touch?X-User-ID=${userConfig?.id || 'agent_ui'}`, {
                method: "POST",
                body: { 
                    path: ".coworker.md",
                    content: coworkerContent,
                    is_dir: false,
                    session_id: sid
                }
            });
            setModalConfig({ title: 'Success', message: 'Auditor Guidelines synced to node workspace.', type: 'success' });
            fetchData();
        } catch (err) {
            setModalConfig({ title: 'Update Failed', message: err.message, type: 'error' });
        } finally {
            setSavingGroundTruth(false);
        }
    };

    const handleAddTrigger = async () => {
        try {
            setCreatingTrigger(true);
            const payload = { 
                trigger_type: newTriggerType,
                default_prompt: newDefaultPrompt
            };
            if (newTriggerType === 'cron') payload.cron_expression = newCronValue;
            if (newTriggerType === 'interval') payload.interval_seconds = parseInt(newIntervalValue) || 600;
            
            await createAgentTrigger(agentId, payload);
            setNewDefaultPrompt('');
            setNewCronValue('0 * * * *');
            setNewIntervalValue(600);
            fetchData();
        } catch (err) {
            setModalConfig({ title: 'Trigger Failed', message: err.message, type: 'error' });
        } finally {
            setCreatingTrigger(false);
        }
    };

    const handleDeleteTrigger = async (triggerId) => {
        try {
            await deleteAgentTrigger(triggerId);
            fetchData();
        } catch (err) {
            setModalConfig({ title: 'Delete Failed', message: err.message, type: 'error' });
        }
    };

    const handleFireTrigger = async (triggerPrompt) => {
        try {
            await fetchWithAuth(`/agents/${agentId}/run`, {
                method: 'POST',
                body: { prompt: triggerPrompt }
            });
            setModalConfig({ title: 'Success', message: 'Agent manual execution started successfully!', type: 'success' });
            fetchData();
        } catch (err) {
            setModalConfig({ title: 'Execution Failed', message: err.message, type: 'error' });
        }
    };

    const handleFireWebhook = async (token, triggerPrompt) => {
        try {
            await fetchWithAuth(`/agents/${agentId}/webhook?token=${token}`, {
                method: 'POST',
                body: { prompt: triggerPrompt || "Manual test from UI" }
            });
            setModalConfig({ title: 'Success', message: 'Webhook test trigger sent successfully!', type: 'success' });
            fetchData();
        } catch (err) {
            setModalConfig({ title: 'Webhook Failed', message: err.message, type: 'error' });
        }
    };

    const handleResetMetrics = async () => {
        setModalConfig({
            title: 'Confirm Reset Metrics',
            message: "Are you sure you want to reset all execution metrics for this agent? This cannot be undone.",
            type: 'error',
            confirmText: 'Reset Metrics',
            confirmAction: async () => {
                try {
                    setClearing(true);
                    await resetAgentMetrics(agentId);
                    fetchData();
                } catch (err) {
                    setModalConfig({ title: 'Reset Failed', message: err.message, type: 'error' });
                } finally {
                    setClearing(false);
                }
            }
        });
    };

    const handleInjectOverride = async (e) => {
        e.preventDefault();
        if (!overrideText.trim() || !agent?.session_id) return;
        
        try {
            await fetchWithAuth(`/agents/${agentId}/run`, {
                method: "POST",
                body: { prompt: overrideText }
            });
            setOverrideText("");
            fetchData();
        } catch (err) {
            setModalConfig({ title: 'Injection Failed', message: err.message, type: 'error' });
        }
    };

    return {
        agent, chatHistory, loading, error, activeTab, setActiveTab,
        editConfig, setEditConfig, saving, userConfig, tokenUsage, tokenError,
        clearing, triggers, newTriggerType, setNewTriggerType, newCronValue, setNewCronValue,
        newIntervalValue, setNewIntervalValue, newDefaultPrompt, setNewDefaultPrompt,
        creatingTrigger, modalConfig, setModalConfig, nodes, allSkills, flippedCards, setFlippedCards,
        cortexFiles, feedbackContent, rubricContent, coworkerContent, setCoworkerContent,
        historyLog, savingGroundTruth, selectedAuditId, setSelectedAuditId,
        runningSeconds, lastTotalConsumption, currentAction, lastAction, lastActionDuration,
        handleAction, handleClearHistory, handleSaveConfig, handleSaveGroundTruth, fetchData,
        handleAddTrigger, handleDeleteTrigger, handleFireTrigger, handleFireWebhook,
        handleResetMetrics, handleInjectOverride, overrideText, setOverrideText
    };
};