// ========================================== // TASK QUEUE SIDEBAR - Right Sidebar // ========================================== // Right-side slide-out toolbar for task queue and CLI execution management let isTaskQueueSidebarVisible = false; let taskQueueData = []; let cliQueueData = []; let currentQueueTab = 'tasks'; // 'tasks' | 'cli' let cliCategoryFilter = 'all'; // 'all' | 'user' | 'internal' | 'insight' // Update task queue data (for CLAUDE.md updates from explorer) let sidebarUpdateTasks = []; let isSidebarTaskRunning = {}; // Track running tasks by id /** * Initialize task queue sidebar */ function initTaskQueueSidebar() { // Create sidebar if not exists - check for container to handle partial creation var existingContainer = document.getElementById('taskQueueContainer'); if (existingContainer) { existingContainer.remove(); } if (!document.getElementById('taskQueueSidebar')) { const sidebarHtml = `
📋 Execution Queue 0
📋
No tasks in queue
Active workflow tasks will appear here
📋
`; const container = document.createElement('div'); container.id = 'taskQueueContainer'; container.innerHTML = sidebarHtml; document.body.appendChild(container); } updateTaskQueueData(); updateCliQueueData(); renderTaskQueueSidebar(); renderCliQueue(); updateTaskQueueBadge(); } /** * Toggle task queue sidebar visibility */ function toggleTaskQueueSidebar() { isTaskQueueSidebarVisible = !isTaskQueueSidebarVisible; const sidebar = document.getElementById('taskQueueSidebar'); const overlay = document.getElementById('taskQueueOverlay'); const toggle = document.getElementById('taskQueueToggle'); if (sidebar && overlay && toggle) { if (isTaskQueueSidebarVisible) { // Close notification sidebar if open if (isNotificationPanelVisible && typeof toggleNotifSidebar === 'function') { toggleNotifSidebar(); } sidebar.classList.add('open'); overlay.classList.add('show'); toggle.classList.add('hidden'); // Refresh data when opened updateTaskQueueData(); renderTaskQueueSidebar(); } else { sidebar.classList.remove('open'); overlay.classList.remove('show'); toggle.classList.remove('hidden'); } } } /** * Update task queue data from workflow data */ function updateTaskQueueData() { taskQueueData = []; // Safety check for global state if (typeof workflowData === 'undefined' || !workflowData) { console.warn('[TaskQueue] workflowData not initialized'); return; } // Collect tasks from active sessions var activeSessions = workflowData.activeSessions || []; activeSessions.forEach(session => { const sessionKey = `session-${session.session_id}`.replace(/[^a-zA-Z0-9-]/g, '-'); const sessionData = sessionDataStore[sessionKey] || session; const tasks = sessionData.tasks || []; tasks.forEach(task => { taskQueueData.push({ ...task, session_id: session.session_id, session_type: session.type || 'workflow', session_description: session.description || session.session_id }); }); }); // Also check lite task sessions if (typeof liteTaskDataStore === 'undefined' || !liteTaskDataStore) { return; } Object.keys(liteTaskDataStore).forEach(function(key) { const liteSession = liteTaskDataStore[key]; if (liteSession && liteSession.tasks) { liteSession.tasks.forEach(task => { taskQueueData.push({ ...task, session_id: liteSession.session_id || key, session_type: liteSession.type || 'lite', session_description: liteSession.description || key }); }); } }); // Sort: in_progress first, then pending, then by timestamp taskQueueData.sort((a, b) => { const statusOrder = { 'in_progress': 0, 'pending': 1, 'completed': 2, 'skipped': 3 }; const aOrder = statusOrder[a.status] ?? 99; const bOrder = statusOrder[b.status] ?? 99; if (aOrder !== bOrder) return aOrder - bOrder; return 0; }); } /** * Render task queue list in sidebar * Note: Named renderTaskQueueSidebar to avoid conflict with explorer.js renderTaskQueue */ function renderTaskQueueSidebar(filter) { filter = filter || 'all'; var contentEl = document.getElementById('taskQueueContent'); if (!contentEl) { console.warn('[TaskQueue] taskQueueContent element not found'); return; } let filteredTasks = taskQueueData; if (filter !== 'all') { filteredTasks = taskQueueData.filter(t => t.status === filter); } if (filteredTasks.length === 0) { contentEl.innerHTML = `
📋
${filter === 'all' ? 'No tasks in queue' : `No ${filter.replace('_', ' ')} tasks`}
Active workflow tasks will appear here
`; return; } contentEl.innerHTML = filteredTasks.map(task => { const statusIcon = { 'in_progress': '🔄', 'pending': '⏳', 'completed': '✅', 'skipped': '⏭️' }[task.status] || '📋'; const statusClass = task.status || 'pending'; const taskId = task.task_id || task.id || 'N/A'; const title = task.title || task.description || taskId; return `
${statusIcon}
${escapeHtml(title)} ${escapeHtml(taskId)}
${escapeHtml(task.session_id)} ${escapeHtml(task.session_type)}
${task.scope ? `
${escapeHtml(task.scope)}
` : ''}
`; }).join(''); } /** * Filter task queue */ function filterTaskQueue(filter) { // Update active filter button document.querySelectorAll('.task-filter-btn').forEach(btn => { btn.classList.toggle('active', btn.dataset.filter === filter); }); renderTaskQueueSidebar(filter); } /** * Open task from queue (navigate to task detail) */ function openTaskFromQueue(sessionId, taskId) { // Close sidebar toggleTaskQueueSidebar(); // Try to find and open the task const sessionKey = `session-${sessionId}`.replace(/[^a-zA-Z0-9-]/g, '-'); // Check if it's a lite task session if (liteTaskDataStore[sessionKey]) { if (typeof openTaskDrawerForLite === 'function') { currentSessionDetailKey = sessionKey; openTaskDrawerForLite(sessionId, taskId); } } else { // Regular workflow task if (typeof openTaskDrawer === 'function') { currentDrawerTasks = sessionDataStore[sessionKey]?.tasks || []; openTaskDrawer(taskId); } } } /** * Update task queue badge counts */ function updateTaskQueueBadge() { const inProgressCount = taskQueueData.filter(t => t.status === 'in_progress').length; const pendingCount = taskQueueData.filter(t => t.status === 'pending').length; const activeCount = inProgressCount + pendingCount; const countBadge = document.getElementById('taskQueueCountBadge'); const toggleBadge = document.getElementById('taskQueueToggleBadge'); if (countBadge) { countBadge.textContent = taskQueueData.length; countBadge.style.display = taskQueueData.length > 0 ? 'inline-flex' : 'none'; } if (toggleBadge) { toggleBadge.textContent = activeCount; toggleBadge.style.display = activeCount > 0 ? 'flex' : 'none'; // Highlight if there are in-progress tasks toggleBadge.classList.toggle('has-active', inProgressCount > 0); } } /** * Refresh task queue (called from external updates) */ function refreshTaskQueue() { updateTaskQueueData(); updateCliQueueData(); renderTaskQueueSidebar(); renderCliQueue(); updateTaskQueueBadge(); } /** * Switch between Tasks and CLI tabs */ function switchQueueTab(tab) { currentQueueTab = tab; // Update tab button states document.querySelectorAll('.task-queue-tab').forEach(btn => { btn.classList.toggle('active', btn.dataset.tab === tab); }); // Show/hide filters and content const taskFilters = document.getElementById('taskQueueFilters'); const cliFilters = document.getElementById('cliQueueFilters'); const taskContent = document.getElementById('taskQueueContent'); const cliContent = document.getElementById('cliQueueContent'); if (tab === 'tasks') { if (taskFilters) taskFilters.style.display = 'flex'; if (cliFilters) cliFilters.style.display = 'none'; if (taskContent) taskContent.style.display = 'block'; if (cliContent) cliContent.style.display = 'none'; } else { if (taskFilters) taskFilters.style.display = 'none'; if (cliFilters) cliFilters.style.display = 'flex'; if (taskContent) taskContent.style.display = 'none'; if (cliContent) cliContent.style.display = 'block'; // Refresh CLI data when switching to CLI tab updateCliQueueData(); renderCliQueue(); } } /** * Update CLI queue data from API */ async function updateCliQueueData() { try { // Fetch recent CLI executions with category info const response = await fetch(`/api/cli/history-native?path=${encodeURIComponent(projectPath)}&limit=20`); if (!response.ok) return; const data = await response.json(); cliQueueData = data.executions || []; } catch (err) { console.warn('[TaskQueue] Failed to load CLI queue:', err); cliQueueData = []; } } /** * Render CLI queue list */ function renderCliQueue() { const contentEl = document.getElementById('cliHistoryList'); if (!contentEl) return; // Filter by category let filtered = cliQueueData; if (cliCategoryFilter !== 'all') { filtered = cliQueueData.filter(exec => (exec.category || 'user') === cliCategoryFilter); } // Update tab badge const cliTabBadge = document.getElementById('cliTabBadge'); if (cliTabBadge) { cliTabBadge.textContent = cliQueueData.length; cliTabBadge.style.display = cliQueueData.length > 0 ? 'inline' : 'none'; } if (filtered.length === 0) { const emptyText = cliCategoryFilter === 'all' ? 'No CLI executions' : `No ${cliCategoryFilter} executions`; contentEl.innerHTML = `
${emptyText}
CLI tool executions will appear here
`; return; } contentEl.innerHTML = filtered.map(exec => { const category = exec.category || 'user'; const categoryIcon = { user: '🔵', internal: '🟢', insight: '🟣' }[category] || '⚪'; const statusIcon = exec.status === 'success' ? '✅' : exec.status === 'timeout' ? '⏰' : '❌'; const timeAgo = getCliTimeAgo(new Date(exec.updated_at || exec.timestamp)); const promptPreview = (exec.prompt_preview || '').substring(0, 60); return `
${categoryIcon} ${exec.tool.toUpperCase()} ${statusIcon} ${timeAgo}
${escapeHtml(promptPreview)}${promptPreview.length >= 60 ? '...' : ''}
#${exec.id.split('-')[0]} ${exec.turn_count > 1 ? `${exec.turn_count} turns` : ''} ${exec.hasNativeSession ? '📎' : ''}
`; }).join(''); } /** * Filter CLI queue by category */ function filterCliQueue(category) { cliCategoryFilter = category; // Update filter button states document.querySelectorAll('.cli-filter-btn').forEach(btn => { btn.classList.toggle('active', btn.dataset.filter === category); }); renderCliQueue(); } /** * Show CLI execution detail from queue */ function showCliExecutionFromQueue(executionId) { toggleTaskQueueSidebar(); // Use the showExecutionDetail function from cli-history.js if available if (typeof showExecutionDetail === 'function') { showExecutionDetail(executionId); } else { console.warn('[TaskQueue] showExecutionDetail not available'); } } /** * Helper to format time ago */ function getCliTimeAgo(date) { const seconds = Math.floor((new Date() - date) / 1000); if (seconds < 60) return 'now'; if (seconds < 3600) return `${Math.floor(seconds / 60)}m`; if (seconds < 86400) return `${Math.floor(seconds / 3600)}h`; return `${Math.floor(seconds / 86400)}d`; } // ========================================== // UPDATE TASK QUEUE - For CLAUDE.md Updates // ========================================== /** * Add update task to sidebar queue (called from explorer) */ function addUpdateTaskToSidebar(path, tool = 'gemini', strategy = 'single-layer') { const task = { id: Date.now(), path, tool, strategy, status: 'pending', // pending, running, completed, failed message: '', addedAt: new Date().toISOString() }; sidebarUpdateTasks.push(task); renderSidebarUpdateTasks(); updateCliTabBadge(); // Open sidebar and switch to CLI tab if not visible if (!isTaskQueueSidebarVisible) { toggleTaskQueueSidebar(); } switchQueueTab('cli'); } /** * Remove update task from queue */ function removeUpdateTask(taskId) { sidebarUpdateTasks = sidebarUpdateTasks.filter(t => t.id !== taskId); renderSidebarUpdateTasks(); updateCliTabBadge(); } /** * Clear completed/failed update tasks */ function clearCompletedUpdateTasks() { sidebarUpdateTasks = sidebarUpdateTasks.filter(t => t.status === 'pending' || t.status === 'running'); renderSidebarUpdateTasks(); updateCliTabBadge(); } /** * Update CLI tool for a specific task */ function updateSidebarTaskCliTool(taskId, tool) { const task = sidebarUpdateTasks.find(t => t.id === taskId); if (task && task.status === 'pending') { task.tool = tool; } } /** * Execute a single update task */ async function executeSidebarUpdateTask(taskId) { const task = sidebarUpdateTasks.find(t => t.id === taskId); if (!task || task.status !== 'pending') return; const folderName = task.path.split('/').pop() || task.path; // Update status to running task.status = 'running'; task.message = t('taskQueue.processing'); isSidebarTaskRunning[taskId] = true; renderSidebarUpdateTasks(); if (typeof addGlobalNotification === 'function') { addGlobalNotification('info', `Processing: ${folderName}`, `Strategy: ${task.strategy}, Tool: ${task.tool}`, 'Explorer'); } try { const response = await csrfFetch('/api/update-claude-md', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ path: task.path, tool: task.tool, strategy: task.strategy }) }); const result = await response.json(); if (result.success) { task.status = 'completed'; task.message = t('taskQueue.updated'); if (typeof addGlobalNotification === 'function') { addGlobalNotification('success', `Completed: ${folderName}`, result.message, 'Explorer'); } } else { task.status = 'failed'; task.message = result.error || t('taskQueue.failed'); if (typeof addGlobalNotification === 'function') { addGlobalNotification('error', `Failed: ${folderName}`, result.error || 'Unknown error', 'Explorer'); } } } catch (error) { task.status = 'failed'; task.message = error.message; if (typeof addGlobalNotification === 'function') { addGlobalNotification('error', `Error: ${folderName}`, error.message, 'Explorer'); } } finally { delete isSidebarTaskRunning[taskId]; renderSidebarUpdateTasks(); updateCliTabBadge(); // Refresh tree to show updated CLAUDE.md files if (typeof loadExplorerTree === 'function' && typeof explorerCurrentPath !== 'undefined') { loadExplorerTree(explorerCurrentPath); } } } /** * Stop/cancel a running update task (if possible) */ function stopSidebarUpdateTask(taskId) { // Currently just removes the task - actual cancellation would need AbortController const task = sidebarUpdateTasks.find(t => t.id === taskId); if (task && task.status === 'running') { task.status = 'failed'; task.message = 'Cancelled'; delete isSidebarTaskRunning[taskId]; renderSidebarUpdateTasks(); updateCliTabBadge(); } } /** * Render update task queue list */ function renderSidebarUpdateTasks() { const listEl = document.getElementById('updateTasksList'); if (!listEl) return; if (sidebarUpdateTasks.length === 0) { listEl.innerHTML = `
${t('taskQueue.noTasks')}

${t('taskQueue.noTasksHint')}

`; return; } listEl.innerHTML = sidebarUpdateTasks.map(task => { const folderName = task.path.split('/').pop() || task.path; const strategyIcon = task.strategy === 'multi-layer' ? '📂' : '📄'; const strategyLabel = task.strategy === 'multi-layer' ? t('taskQueue.withSubdirs') : t('taskQueue.currentOnly'); const statusIcon = { 'pending': '⏳', 'running': '🔄', 'completed': '✅', 'failed': '❌' }[task.status]; const isPending = task.status === 'pending'; const isRunning = task.status === 'running'; return `
${statusIcon} ${escapeHtml(folderName)} ${strategyIcon}
${isPending ? ` ` : ''} ${isRunning ? ` ` : ''}
${task.message ? `
${escapeHtml(task.message)}
` : ''}
`; }).join(''); } /** * Update CLI tab badge with pending update tasks count */ function updateCliTabBadge() { const pendingCount = sidebarUpdateTasks.filter(t => t.status === 'pending' || t.status === 'running').length; const cliTabBadge = document.getElementById('cliTabBadge'); if (cliTabBadge) { const totalCount = pendingCount + cliQueueData.length; cliTabBadge.textContent = totalCount; cliTabBadge.style.display = totalCount > 0 ? 'inline' : 'none'; } }