← 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);
});
}