← back to Designer Wallcoverings

DW-Agents/dw-agents/agent-task-orchestrator/sync-todos-to-orchestrator.js

260 lines

#!/usr/bin/env node
/**
 * Sync TodoWrite updates to Task Orchestrator
 * This script is called automatically whenever todos are updated
 */

const fs = require('fs');
const path = require('path');

const TASKS_DB_FILE = path.join(__dirname, 'tasks-database.json');
const MEMORY_FILE = path.join(__dirname, 'task-memory.json');

// Load existing data
function loadData() {
  try {
    const tasksData = fs.readFileSync(TASKS_DB_FILE, 'utf8');
    const memoryData = fs.readFileSync(MEMORY_FILE, 'utf8');
    return {
      tasks: JSON.parse(tasksData),
      memory: JSON.parse(memoryData)
    };
  } catch (error) {
    console.error('Error loading data:', error.message);
    return null;
  }
}

// Save data
function saveData(tasks, memory) {
  try {
    fs.writeFileSync(TASKS_DB_FILE, JSON.stringify(tasks, null, 2));
    fs.writeFileSync(MEMORY_FILE, JSON.stringify(memory, null, 2));
    return true;
  } catch (error) {
    console.error('Error saving data:', error.message);
    return false;
  }
}

// Generate task ID
function generateTaskId() {
  const timestamp = Date.now();
  const random = Math.random().toString(36).substr(2, 9);
  return `TASK-${timestamp}-${random}`;
}

// Agent URL mapping
const AGENT_URLS = {
  'purchasing-office': 'http://45.61.58.125:9880',
  'purchasing': 'http://45.61.58.125:9880',
  'legal': 'http://45.61.58.125:9878',
  'digital-samples': 'http://45.61.58.125:9879',
  'marketing': 'http://45.61.58.125:9881',
  'accounting': 'http://45.61.58.125:9882',
  'zendesk': 'http://45.61.58.125:9884',
  'needs-attention': 'http://45.61.58.125:9886',
  'server-uptime': 'http://45.61.58.125:9888',
  'task-orchestrator': 'http://45.61.58.125:9900',
  'master-hub': 'http://45.61.58.125:9893',
  'york': 'http://45.61.58.125:7234',
  'yorkupdater': 'http://45.61.58.125:7234'
};

// Detect URLs from task content
function detectUrl(taskContent) {
  const lowerContent = taskContent.toLowerCase();

  // Check for explicit URLs in content
  const urlMatch = taskContent.match(/(https?:\/\/[^\s]+)/);
  if (urlMatch) return urlMatch[1];

  // Check for agent names
  for (const [key, url] of Object.entries(AGENT_URLS)) {
    if (lowerContent.includes(key)) {
      return url;
    }
  }

  return null;
}

// Detect work progress from task content (e.g., "44/2000 products")
function detectWorkProgress(taskContent) {
  // Match patterns like "44/2000" or "44 of 2000" or "44 out of 2000"
  const progressMatch = taskContent.match(/(\d+)\s*(?:\/|of|out of)\s*(\d+)/i);
  if (progressMatch) {
    return {
      current: parseInt(progressMatch[1]),
      total: parseInt(progressMatch[2])
    };
  }
  return null;
}

// Main sync function
function syncTodos(todosJson) {
  const data = loadData();
  if (!data) return false;

  const todos = JSON.parse(todosJson);
  const now = new Date().toISOString();
  let updated = false;

  todos.forEach(todo => {
    // Find existing task by matching title
    const existingTask = data.tasks.tasks.find(t =>
      t.title === todo.content && t.status !== 'completed'
    );

    if (existingTask) {
      // Update existing task
      const oldStatus = existingTask.status;

      // Detect and add URL if not already present
      const detectedUrl = detectUrl(todo.content + ' ' + todo.activeForm);
      if (detectedUrl && !existingTask.metadata.url) {
        existingTask.metadata.url = detectedUrl;
        updated = true;
      }

      // Detect work progress from content (e.g., "44/2000 products")
      const workProgress = detectWorkProgress(todo.content + ' ' + todo.activeForm);
      if (workProgress) {
        existingTask.metadata.workProgress = workProgress;
        updated = true;
      }

      // Update progress based on action log
      const completedActions = existingTask.actionLog.filter(a => a.success).length;
      const totalActions = existingTask.actionLog.length;
      existingTask.metadata.progress = { completed: completedActions, total: totalActions };

      if (todo.status === 'in_progress' && oldStatus !== 'in_progress') {
        existingTask.status = 'in_progress';
        if (!existingTask.startedAt) {
          existingTask.startedAt = now;
        }
        existingTask.actionLog.push({
          timestamp: now,
          action: 'started',
          details: 'Task work started',
          success: true
        });
        updated = true;
      } else if (todo.status === 'completed' && oldStatus !== 'completed') {
        existingTask.status = 'completed';
        existingTask.completedAt = now;

        // Calculate duration
        if (existingTask.startedAt) {
          const start = new Date(existingTask.startedAt).getTime();
          const end = new Date(now).getTime();
          existingTask.actualDuration = Math.round((end - start) / 60000);
        }

        existingTask.actionLog.push({
          timestamp: now,
          action: 'completed',
          details: 'Task completed successfully',
          success: true
        });
        updated = true;

        // Update memory
        const agentName = existingTask.assignedAgent || 'system';
        if (!data.memory.agentPerformance[agentName]) {
          data.memory.agentPerformance[agentName] = {
            tasksCompleted: 0,
            averageDuration: 0,
            successRate: 1.0
          };
        }
        data.memory.agentPerformance[agentName].tasksCompleted++;

        if (existingTask.actualDuration) {
          const perf = data.memory.agentPerformance[agentName];
          const totalTasks = perf.tasksCompleted;
          const oldTotal = perf.averageDuration * (totalTasks - 1);
          perf.averageDuration = (oldTotal + existingTask.actualDuration) / totalTasks;
        }
      }
    } else if (todo.status === 'in_progress' || todo.status === 'pending') {
      // Create new task
      const newTask = {
        id: generateTaskId(),
        title: todo.content,
        description: todo.activeForm,
        status: todo.status === 'in_progress' ? 'in_progress' : 'pending',
        priority: 'medium',
        assignedAgent: 'claude-code',
        createdAt: now,
        assignedAt: now,
        autoGenerated: true,
        subtasks: [],
        tags: ['claude-todo', 'auto-created'],
        metadata: {
          source: 'TodoWrite',
          activeForm: todo.activeForm,
          url: detectUrl(todo.content + ' ' + todo.activeForm) || null,
          workProgress: detectWorkProgress(todo.content + ' ' + todo.activeForm) || null
        },
        actionLog: [
          {
            timestamp: now,
            action: 'created',
            details: `Auto-created from TodoWrite: ${todo.content}`,
            success: true
          },
          {
            timestamp: now,
            action: 'assigned',
            agent: 'claude-code',
            details: 'Auto-assigned to Claude Code',
            success: true
          }
        ]
      };

      if (todo.status === 'in_progress') {
        newTask.startedAt = now;
        newTask.actionLog.push({
          timestamp: now,
          action: 'started',
          details: 'Task work started',
          success: true
        });
      }

      data.tasks.tasks.push(newTask);
      data.memory.recentTasks.unshift(newTask.id);

      if (data.memory.recentTasks.length > 100) {
        data.memory.recentTasks = data.memory.recentTasks.slice(0, 100);
      }

      updated = true;
    }
  });

  if (updated) {
    data.memory.lastUpdated = now;
    saveData(data.tasks, data.memory);
    console.log('✅ Synced todos to Task Orchestrator');
  }

  return updated;
}

// Read todos from command line argument or stdin
if (process.argv[2]) {
  syncTodos(process.argv[2]);
} else {
  // Read from stdin
  let input = '';
  process.stdin.on('data', chunk => input += chunk);
  process.stdin.on('end', () => {
    if (input) syncTodos(input);
  });
}