multiagent-task-manager
Version:
A comprehensive multi-agent task management system for coordinating tasks between AI agents and human team members with intelligent recommendations and workload balancing
1,764 lines (1,426 loc) • 74.5 kB
JavaScript
#!/usr/bin/env node
const fs = require("fs");
const path = require("path");
const { v4: uuidv4 } = require("uuid");
/**
* Task Manager - Multi-Agent Task Management System
* A comprehensive package for managing tasks across multiple AI agents and humans
*/
class TaskManager {
constructor(options = {}) {
this.config = {
maxRecommendations: options.maxRecommendations || 3,
autoSave: options.autoSave !== false,
useCurrentDir:
options.useCurrentDir ||
process.env.TASK_MANAGER_USE_CURRENT_DIR === "true",
...options,
};
this.dataDir = options.dataDir || process.env.TASK_MANAGER_DATA_DIR || "./";
// Handle both legacy and new MCP directory structures
// Legacy: dataDir = "/some/path" → append "tasks-data"
// New MCP: dataDir = "/some/path/tasks-data" → use as-is
// Use precise check for tasks-data as the final directory component
const normalizedDir = path.normalize(this.dataDir);
const dirName = path.basename(normalizedDir);
if (dirName !== "tasks-data") {
// Special handling for "./" to avoid "./tasks-data" becoming "tasks-data"
if (this.dataDir === "./") {
this.dataDir = "./tasks-data";
} else {
this.dataDir = path.join(this.dataDir, "tasks-data");
}
}
this.trackerFile = path.join(this.dataDir, "task-tracker.json");
this.agentsFile = path.join(this.dataDir, "agents.json");
this.currentAgentId =
options.agentId || process.env.TASK_MANAGER_AGENT_ID || null;
this.scoring = {
priority: { critical: 10, high: 7, medium: 5, low: 2 },
dependency: { blocking: 8, dependent: 3, independent: 1 },
criticalPath: { onPath: 5, nearPath: 3, offPath: 1 },
risk: { high: 8, medium: 5, low: 2 },
phase: { active: 10, next: 7, future: 3, completed: 0 },
};
this.taskTracker = null;
this.agents = null;
this.init();
}
// ==================== INITIALIZATION ====================
init() {
this.ensureDataDirectory();
this.loadData();
}
ensureDataDirectory() {
if (!fs.existsSync(this.dataDir)) {
fs.mkdirSync(this.dataDir, { recursive: true });
this.createInitialFiles();
}
}
smartInit(options = {}) {
const useCurrentDir = options.useCurrentDir || this.config.useCurrentDir;
let targetDir =
options.dataDir || process.env.TASK_MANAGER_DATA_DIR || "./tasks-data";
// Detect if we're running in MCP mode (when current directory is unsafe)
const isMCPMode = !this.isDirectorySafe(process.cwd());
// Handle both legacy and new MCP directory structures for smartInit
// Use precise check for tasks-data as the final directory component
const normalizedTarget = path.normalize(targetDir);
const targetDirName = path.basename(normalizedTarget);
if (targetDirName !== "tasks-data") {
// Special handling for "./" to avoid "./tasks-data" becoming "tasks-data"
if (targetDir === "./") {
targetDir = "./tasks-data";
} else {
targetDir = path.join(targetDir, "tasks-data");
}
}
console.log(`🔍 Initializing TaskManager in: ${targetDir}`);
// Update paths if different from constructor
if (targetDir !== this.dataDir) {
this.dataDir = targetDir;
this.trackerFile = path.join(this.dataDir, "task-tracker.json");
this.agentsFile = path.join(this.dataDir, "agents.json");
}
const results = {
created: [],
existed: [],
updated: [],
};
// Create data directory if it doesn't exist
if (!fs.existsSync(this.dataDir)) {
fs.mkdirSync(this.dataDir, { recursive: true });
results.created.push(`Directory: ${this.dataDir}`);
console.log(`✅ Created directory: ${this.dataDir}`);
} else {
results.existed.push(`Directory: ${this.dataDir}`);
console.log(`📁 Directory exists: ${this.dataDir}`);
}
// Handle task tracker file
if (!fs.existsSync(this.trackerFile)) {
this.createTaskTracker();
results.created.push("task-tracker.json");
console.log(`✅ Created: task-tracker.json`);
} else {
results.existed.push("task-tracker.json");
console.log(`📄 Exists: task-tracker.json`);
}
// Handle agents file
if (!fs.existsSync(this.agentsFile)) {
this.createAgentsFile();
results.created.push("agents.json");
console.log(`✅ Created: agents.json`);
} else {
results.existed.push("agents.json");
console.log(`📄 Exists: agents.json`);
}
// Handle README in data directory
const readmePath = path.join(this.dataDir, "README.md");
if (!fs.existsSync(readmePath)) {
this.createDataReadme();
results.created.push("README.md");
console.log(`✅ Created: README.md`);
} else {
results.existed.push("README.md");
console.log(`📄 Exists: README.md`);
}
// Handle .env file (skip entirely in MCP mode)
if (!isMCPMode) {
const projectRoot = process.cwd();
const envPath = path.join(projectRoot, ".env");
const envExamplePath = path.join(__dirname, ".env.example");
// Check if we're in a safe directory before creating .env
const isProjectRootSafe = this.isDirectorySafe(projectRoot);
if (isProjectRootSafe) {
this.handleEnvFile(envPath, envExamplePath, results);
} else {
console.log(
`⚠️ Skipped .env creation: unsafe directory ${projectRoot}`,
);
results.existed.push(".env (skipped - unsafe directory)");
}
} else {
console.log(
`🔧 MCP Mode: Skipped .env creation (use environment variables in MCP config)`,
);
results.existed.push(".env (skipped - MCP mode)");
}
// Load data after ensuring files exist
this.loadData();
return results;
}
handleEnvFile(envPath, envExamplePath, results) {
const requiredEnvVars = [
"# TaskManager Configuration",
`TASK_MANAGER_AGENT_ID=${this.currentAgentId || "your-agent-id"}`,
`TASK_MANAGER_DATA_DIR=/.`,
`TASK_MANAGER_USE_CURRENT_DIR=${this.config.useCurrentDir}`,
];
if (fs.existsSync(envPath)) {
// Read existing .env file
const existingContent = fs.readFileSync(envPath, "utf8");
const lines = existingContent.split("\n");
// Check which TaskManager variables are missing
const missingVars = requiredEnvVars.filter((reqVar) => {
if (reqVar.startsWith("#")) return false; // Skip comments
const varName = reqVar.split("=")[0];
return !lines.some((line) => line.trim().startsWith(varName + "="));
});
if (missingVars.length > 0) {
// Append missing variables
const newContent =
existingContent.trimEnd() +
"\n\n# TaskManager Configuration\n" +
missingVars.filter((v) => !v.startsWith("#")).join("\n") +
"\n";
fs.writeFileSync(envPath, newContent);
results.updated.push(".env (appended TaskManager variables)");
console.log(
`🔄 Updated: .env (added ${missingVars.length} TaskManager variables)`,
);
} else {
results.existed.push(".env (TaskManager variables already present)");
console.log(
`📄 Exists: .env (TaskManager variables already configured)`,
);
}
} else {
// Create new .env file
const envContent = [
"# Project Environment Variables",
"",
...requiredEnvVars,
"",
].join("\n");
fs.writeFileSync(envPath, envContent);
results.created.push(".env");
console.log(`✅ Created: .env`);
}
}
createInitialFiles() {
this.createTaskTracker();
this.createAgentsFile();
this.createDataReadme();
console.log(`✅ Task Manager initialized in ${this.dataDir}`);
}
createTaskTracker() {
const initialTracker = {
project: {
name: "New Project",
code: "NEW-PROJ",
version: "1.0.0",
created: new Date().toISOString(),
updated: new Date().toISOString(),
status: "active",
description: "Project managed by Task Manager",
},
progress: {
total_tasks: 0,
completed: 0,
in_progress: 0,
todo: 0,
completion_percentage: 0,
},
current_state: {
active_phase: "phase-1",
active_tasks: [],
next_recommended_tasks: [],
blocking_issues: [],
latest_update: new Date().toISOString(),
recommendation_algorithm: {
version: "2.0.0",
max_recommendations: this.config.maxRecommendations,
},
},
phases: {},
tasks: {},
agents: {},
recommendation_history: [],
metrics: {
velocity: { tasks_per_day: 0, completion_rate: 0 },
quality: { bugs_found: 0, test_coverage: 0 },
performance: {},
},
};
fs.writeFileSync(this.trackerFile, JSON.stringify(initialTracker, null, 2));
}
createAgentsFile() {
const initialAgents = {
registry: {},
types: {
human: { icon: "👤", capabilities: ["all"] },
ai: { icon: "🤖", capabilities: ["code", "analysis", "documentation"] },
},
created: new Date().toISOString(),
updated: new Date().toISOString(),
};
fs.writeFileSync(this.agentsFile, JSON.stringify(initialAgents, null, 2));
}
createDataReadme() {
const readme = `# Multi-Agent Task Manager
Welcome to your TaskManager project! This directory contains all your project data and serves as the command center for multi-agent collaboration.
## 📁 Directory Structure
### Core Files
- \`task-tracker.json\`: Main project and task data
- \`agents.json\`: Agent registry and capabilities
- \`README.md\`: This comprehensive guide
This simple structure keeps everything organized in one place for easy management.
## 🚀 Quick Start
\`\`\`bash
# Set your agent identity (required)
export TASK_MANAGER_AGENT_ID=your-agent-id
# Or create a .env file with:
# TASK_MANAGER_AGENT_ID=your-agent-id
# TASK_MANAGER_DATA_DIR=./tasks-data
# Check your current tasks
npx multiagent-task-manager my-tasks
# Get AI recommendations for what to work on next
npx multiagent-task-manager my-recommendations
\`\`\`
## 👥 Agent Management
### Creating Agents
\`\`\`bash
# Create a human developer
npx multiagent-task-manager agents add
# This will prompt you interactively for:
# - Agent ID (e.g., dev-john)
# - Name (e.g., John Smith)
# - Type (human/ai)
# - Capabilities (e.g., frontend,backend,testing)
\`\`\`
### Viewing Agents
\`\`\`bash
# List all agents
npx multiagent-task-manager agents
# Get agent workload information
npx multiagent-task-manager workload dev-john
\`\`\`
## 📋 Task Management
### Creating Tasks
\`\`\`bash
# Create a task interactively
npx multiagent-task-manager create --interactive
# Create a task with command line options
npx multiagent-task-manager create --title "Fix login bug" --priority critical --assign dev-john
\`\`\`
### Assigning Tasks
\`\`\`bash
# Assign a task to a specific agent
npx multiagent-task-manager assign TASK-001 dev-john
# Get AI recommendations for task assignment
npx multiagent-task-manager recommend --agent dev-john
\`\`\`
### Working with Tasks
\`\`\`bash
# Start working on a task (agent-centric)
npx multiagent-task-manager start TASK-001
# Update task status
npx multiagent-task-manager update TASK-001 --status in-progress
# Mark task as completed (agent-centric)
npx multiagent-task-manager complete TASK-001
# Update task status to blocked
npx multiagent-task-manager update TASK-001 --status blocked
\`\`\`
### Task Queries
\`\`\`bash
# View all tasks
npx multiagent-task-manager list
# Filter tasks by status
npx multiagent-task-manager list --status in-progress
# Filter by agent
npx multiagent-task-manager list --agent dev-john
\`\`\`
## 🎯 Personal Workflow
### My Tasks & Recommendations
\`\`\`bash
# See your assigned tasks
npx multiagent-task-manager my-tasks
# Get AI recommendations for what to work on next
npx multiagent-task-manager my-recommendations
# See your completed tasks
npx multiagent-task-manager my-tasks --status completed
# Check tasks you can help with
npx multiagent-task-manager my-recommendations --include-team-tasks
\`\`\`
### Time Tracking
\`\`\`bash
# Start working on a task (tracks time automatically)
npx multiagent-task-manager start TASK-001
# Complete task (stops time tracking)
npx multiagent-task-manager complete TASK-001
# Self-assign and start working on an available task
npx multiagent-task-manager take TASK-001
# View agent workload (includes time data)
npx multiagent-task-manager workload dev-john
\`\`\`
## 📊 Project Overview
### Status & Reports
\`\`\`bash
# Get project overview
npx multiagent-task-manager status
# Export project data
npx multiagent-task-manager export
\`\`\`
### Analytics
\`\`\`bash
# Get recommendations for agents
npx multiagent-task-manager recommend --agent dev-john
# Check agent workloads
npx multiagent-task-manager workload
\`\`\`
## 🔧 Advanced Features
### Bulk Operations
\`\`\`bash
# Export current project data
npx multiagent-task-manager export
# Import/restore from exported data
# (Copy exported JSON to task-tracker.json and agents.json)
\`\`\`
### Notifications & Check-in
\`\`\`bash
# Agent check-in (shows notifications and status)
npx multiagent-task-manager check-in
# View notifications
npx multiagent-task-manager notifications
# Clear notifications
npx multiagent-task-manager clear-notifications
\`\`\`
## 🏗️ Project Structure
### Task States
- \`todo\` → Initial state for new tasks
- \`in-progress\` → Currently being worked on
- \`review\` → Waiting for review/approval
- \`blocked\` → Cannot proceed due to dependencies
- \`completed\` → Successfully finished
- \`cancelled\` → No longer needed
### Priority Levels
- \`critical\` 🔴: Drop everything and fix now
- \`high\` 🟠: Important and urgent, work on soon
- \`medium\` 🟡: Normal priority, schedule appropriately
- \`low\` 🟢: Nice to have, work on when time permits
### Task Types
- \`feature\`: New functionality
- \`bug\`: Fix existing issues
- \`maintenance\`: Code cleanup, refactoring
- \`documentation\`: Writing or updating docs
- \`testing\`: Creating or running tests
- \`deployment\`: Release and infrastructure
### Agent Types
- \`human\`: Human team members with full capabilities
- \`ai\`: AI agents with specific programmed capabilities
## ⚙️ Configuration
### Environment Variables
Create a \`.env\` file in your project root:
\`\`\`env
# Required: Your agent identifier
TASK_MANAGER_AGENT_ID=your-agent-id
# Optional: Custom data directory
TASK_MANAGER_DATA_DIR=./tasks-data
# Optional: Use current directory instead of ./tasks-data
TASK_MANAGER_USE_CURRENT_DIR=false
# Optional: Default task priority
TASK_MANAGER_DEFAULT_PRIORITY=medium
# Optional: Auto-save interval (minutes)
TASK_MANAGER_AUTO_SAVE=5
\`\`\`
### CLI Aliases
Add these to your shell profile for faster access:
\`\`\`bash
alias tm="npx multiagent-task-manager"
alias tmt="npx multiagent-task-manager my-tasks"
alias tmr="npx multiagent-task-manager my-recommendations"
alias tms="npx multiagent-task-manager status"
\`\`\`
## 🤖 AI Assistant Integration (MCP)
Use TaskManager as an AI assistant tool with enhanced directory management:
**Note**: The MCP server features intelligent directory resolution and creates an organized structure automatically. It includes comprehensive safety handling and project detection.
### Enhanced Directory Features
- **Project Root Detection**: Automatically finds your project root using markers like package.json, .git
- **Intelligent Structure**: Creates organized subdirectories (agents/, reports/, templates/, backups/)
- **Current Directory Smart Default**: Uses current directory with enhanced safety checks
- **Automatic Fallback**: Multiple fallback strategies for maximum reliability
- **System Directory Protection**: Comprehensive protection against unsafe directories
- **Legacy Data Migration**: Automatically migrates existing TaskManager data
\`\`\`bash
# Start MCP server in current directory (safe default behavior)
npx -y --package=multiagent-task-manager multiagent-task-manager-mcp
# Alternative: Install globally first
npm install -g multiagent-task-manager
multiagent-task-manager-mcp
# For local development/testing (before publishing)
node mcp-server.js
# Run in background
npx -y --package=multiagent-task-manager multiagent-task-manager-mcp &
\`\`\`
### Recommended Configuration
Add to your AI assistant configuration:
\`\`\`json
{
"mcpServers": {
"multiagent-task-manager": {
"command": "npx",
"args": ["-y", "--package=multiagent-task-manager", "multiagent-task-manager-mcp"],
"env": {
"TASK_MANAGER_AGENT_ID": "claude-assistant"
}
}
}
}
\`\`\`
**How it works:**
- Detects project root automatically (looks for package.json, .git, etc.)
- Creates enhanced directory structure with organized subdirectories
- Uses current directory with intelligent safety validation
- Multiple fallback strategies: project-based, user home, emergency locations
- Migrates existing legacy data automatically
- No manual directory configuration needed in most cases
### Custom Directory Configuration (Optional)
If you need to override the automatic directory handling:
\`\`\`json
{
"mcpServers": {
"multiagent-task-manager": {
"command": "npx",
"args": ["-y", "--package=multiagent-task-manager", "multiagent-task-manager-mcp"],
"env": {
"TASK_MANAGER_DATA_DIR": "~/Documents/TaskManager",
"TASK_MANAGER_AGENT_ID": "claude-assistant"
}
}
}
}
\`\`\`
**When to use custom configuration:**
- Working across multiple projects but want centralized task data
- Need tasks stored in a specific location for backup/sync
- Corporate environments with specific directory requirements
- Override automatic project detection for special workflows
## 📚 Example Workflows
### 🚀 Complete Workflow Example
Here's a step-by-step example of setting up a project and working with tasks:
\`\`\`bash
# 1. Initialize the project
npx multiagent-task-manager init
export TASK_MANAGER_AGENT_ID=dev-alice
# 2. Add team members
npx multiagent-task-manager agents add
# When prompted, enter:
# ID: dev-alice
# Name: Alice Johnson
# Type: human
# Capabilities: frontend,react,testing
npx multiagent-task-manager agents add
# When prompted, enter:
# ID: dev-bob
# Name: Bob Smith
# Type: human
# Capabilities: backend,nodejs,database
npx multiagent-task-manager agents add
# When prompted, enter:
# ID: ai-reviewer
# Name: Code Review AI
# Type: ai
# Capabilities: code-review,documentation
# 3. Create some tasks
npx multiagent-task-manager create --interactive
# When prompted, enter:
# Title: Implement user authentication
# Description: Add login/logout functionality with JWT
# Category: feature
# Priority: high
# Assignees: dev-bob
npx multiagent-task-manager create --interactive
# When prompted, enter:
# Title: Design login page UI
# Description: Create responsive login form with validation
# Category: design
# Priority: medium
# Assignees: dev-alice
npx multiagent-task-manager create --interactive
# When prompted, enter:
# Title: Write authentication tests
# Description: Unit and integration tests for auth system
# Category: testing
# Priority: medium
# Dependencies: TASK-001
# 4. Check project status
npx multiagent-task-manager status
npx multiagent-task-manager agents
# 5. Start working on tasks
export TASK_MANAGER_AGENT_ID=dev-alice
npx multiagent-task-manager my-tasks
npx multiagent-task-manager start TASK-002
# 6. Update task progress
npx multiagent-task-manager update TASK-002 --status in-progress
# 7. Complete the task
npx multiagent-task-manager complete TASK-002
# 8. Assign reviewer
npx multiagent-task-manager assign TASK-002 ai-reviewer
# 9. Check recommendations for next work
npx multiagent-task-manager my-recommendations
\`\`\`
### 🎯 Agent-Specific Task Management
\`\`\`bash
# Working as dev-bob
export TASK_MANAGER_AGENT_ID=dev-bob
# See my assigned tasks
npx multiagent-task-manager my-tasks
# Start working on authentication task
npx multiagent-task-manager start TASK-001
# Check my workload
npx multiagent-task-manager my-workload
# Update task status
npx multiagent-task-manager update TASK-001 --status in-progress
# Complete the task
npx multiagent-task-manager complete TASK-001
# Check notifications
npx multiagent-task-manager notifications
\`\`\`
### 🔄 Task Assignment and Transfer Example
\`\`\`bash
# List all tasks to see what needs assignment
npx multiagent-task-manager list
# Assign unassigned task to specific agent
npx multiagent-task-manager assign TASK-003 dev-alice
# Get AI recommendations for best assignment
npx multiagent-task-manager recommend --agent dev-bob
# Self-assign an available task
export TASK_MANAGER_AGENT_ID=dev-alice
npx multiagent-task-manager take TASK-004
# Check agent workloads before reassigning
npx multiagent-task-manager workload dev-alice
npx multiagent-task-manager workload dev-bob
# Transfer overloaded work
npx multiagent-task-manager assign TASK-005 dev-bob
\`\`\`
### 📋 Status Management Examples
\`\`\`bash
# Update task statuses
npx multiagent-task-manager update TASK-001 --status completed
npx multiagent-task-manager update TASK-002 --status in-progress
npx multiagent-task-manager update TASK-003 --status blocked
# Filter tasks by status
npx multiagent-task-manager list --status todo
npx multiagent-task-manager list --status in-progress
npx multiagent-task-manager list --status completed
# Check blocked tasks across the project
npx multiagent-task-manager list --status blocked
\`\`\`
### 🔍 Project Monitoring Example
\`\`\`bash
# Daily project overview
npx multiagent-task-manager status
npx multiagent-task-manager list --status in-progress
# Check team workloads
npx multiagent-task-manager workload dev-alice
npx multiagent-task-manager workload dev-bob
npx multiagent-task-manager workload ai-reviewer
# Export project data for reporting
npx multiagent-task-manager export > project-status.json
\`\`\`
### Daily Standup Preparation
\`\`\`bash
# Quick daily overview
npx multiagent-task-manager my-tasks --status completed,in-progress
npx multiagent-task-manager my-recommendations
npx multiagent-task-manager list --status blocked
\`\`\`
### Sprint Planning
\`\`\`bash
# Review backlog
npx multiagent-task-manager list --status todo
# Check agent availability
npx multiagent-task-manager agents
# Get recommendations for assignments
npx multiagent-task-manager recommend
\`\`\`
### Code Review Workflow
\`\`\`bash
# Start code review task
npx multiagent-task-manager start TASK-001
# Add review comments
npx multiagent-task-manager update TASK-001 --progress "Found 3 issues in authentication logic"
# Request changes or approve
npx multiagent-task-manager assign TASK-001 original-author
# OR
npx multiagent-task-manager complete TASK-001
\`\`\`
## 🆘 Help & Troubleshooting
\`\`\`bash
# Get help and see all commands
npx multiagent-task-manager
# View current configuration
cat .env
# Check agent status and notifications
npx multiagent-task-manager check-in
# Re-initialize if needed
npx multiagent-task-manager init
# Test MCP server with correct npx syntax
npx -y --package=multiagent-task-manager multiagent-task-manager-mcp --help
# Or install globally first
npm install -g multiagent-task-manager
multiagent-task-manager-mcp --help
# For local development
node mcp-server.js --help
\`\`\`
## 🔗 Related Resources
- **GitHub Repository**: [TaskManager Project](https://github.com/yourusername/multiagent-task-manager)
- **NPM Package**: \`npm install -g multiagent-task-manager\`
- **Documentation**: Full docs at project homepage
- **Issues & Support**: GitHub Issues page
---
## 📖 Quick Reference
### Common Command Patterns
\`\`\`bash
# Setup & Initialization
npx multiagent-task-manager init # Initialize project
export TASK_MANAGER_AGENT_ID=your-id # Set your identity
# Agent Management
npx multiagent-task-manager agents # List all agents
npx multiagent-task-manager agents add # Add new agent (interactive)
npx multiagent-task-manager workload agent-id # Check agent workload
# Task Creation & Management
npx multiagent-task-manager create --interactive # Create task (guided)
npx multiagent-task-manager list # List all tasks
npx multiagent-task-manager list --status todo # Filter by status
npx multiagent-task-manager list --agent agent-id # Filter by agent
# Task Assignment & Updates
npx multiagent-task-manager assign TASK-ID AGENT-ID # Assign task
npx multiagent-task-manager update TASK-ID --status STATUS # Update status
npx multiagent-task-manager take TASK-ID # Self-assign task
# Personal Workflow
npx multiagent-task-manager my-tasks # My assigned tasks
npx multiagent-task-manager start TASK-ID # Start working on task
npx multiagent-task-manager complete TASK-ID # Complete task
npx multiagent-task-manager my-recommendations # Get AI suggestions
# Project Overview
npx multiagent-task-manager status # Project summary
npx multiagent-task-manager recommend # Get recommendations
npx multiagent-task-manager export # Export project data
# Notifications & Check-in
npx multiagent-task-manager check-in # Agent status & notifications
npx multiagent-task-manager notifications # View notifications
npx multiagent-task-manager clear-notifications # Clear notifications
\`\`\`
### Status Values
- \`todo\` - Ready to start
- \`in-progress\` - Currently being worked on
- \`completed\` - Finished successfully
- \`blocked\` - Cannot proceed
### Priority Values
- \`critical\` - Drop everything
- \`high\` - Important and urgent
- \`medium\` - Normal priority
- \`low\` - When time permits
---
**Pro Tip**: Use \`npx multiagent-task-manager\` for the latest version, or install globally with \`npm install -g multiagent-task-manager\` and use \`task-manager\` command directly.
**MCP Usage**: Use \`npx -y --package=multiagent-task-manager multiagent-task-manager-mcp\` to run the MCP server with automatic directory safety handling, or install globally with \`npm install -g multiagent-task-manager\` first.
**Directory Safety**: The MCP server automatically handles read-only directories and system paths, falling back to safe locations when needed.
Happy task managing! 🚀
`;
fs.writeFileSync(path.join(this.dataDir, "README.md"), readme);
}
loadData() {
try {
if (fs.existsSync(this.trackerFile)) {
this.taskTracker = JSON.parse(
fs.readFileSync(this.trackerFile, "utf8"),
);
} else {
this.taskTracker = null;
}
if (fs.existsSync(this.agentsFile)) {
this.agents = JSON.parse(fs.readFileSync(this.agentsFile, "utf8"));
} else {
this.agents = null;
}
// Ensure data structures exist
if (!this.taskTracker) {
this.createInitialFiles();
}
if (!this.agents) {
this.createInitialFiles();
}
} catch (error) {
console.error("Error loading data:", error.message);
throw error;
}
}
saveData() {
if (!this.config.autoSave) return;
try {
this.taskTracker.project.updated = new Date().toISOString();
this.agents.updated = new Date().toISOString();
fs.writeFileSync(
this.trackerFile,
JSON.stringify(this.taskTracker, null, 2),
);
fs.writeFileSync(this.agentsFile, JSON.stringify(this.agents, null, 2));
} catch (error) {
console.error("Error saving data:", error.message);
throw error;
}
}
// ==================== AGENT MANAGEMENT ====================
addAgent(agentInfo) {
const agent = {
id: agentInfo.id || `agent-${Date.now()}`,
name: agentInfo.name,
type: agentInfo.type || "ai", // 'ai' or 'human'
capabilities: agentInfo.capabilities || [],
status: "active",
created: new Date().toISOString(),
updated: new Date().toISOString(),
workload: {
active_tasks: 0,
completed_tasks: 0,
total_score: 0,
},
...agentInfo,
};
this.agents.registry[agent.id] = agent;
this.saveData();
console.log(`✅ Agent ${agent.name} (${agent.id}) added successfully`);
return agent;
}
getAgent(agentId) {
if (!this.agents || !this.agents.registry) {
return null;
}
return this.agents.registry[agentId] || null;
}
listAgents() {
if (!this.agents || !this.agents.registry) {
return [];
}
return Object.values(this.agents.registry);
}
updateAgent(agentId, updates) {
if (!this.agents.registry[agentId]) {
throw new Error(`Agent ${agentId} not found`);
}
if (!this.agents || !this.agents.registry) {
throw new Error("Agents registry not initialized");
}
this.agents.registry[agentId] = {
...this.agents.registry[agentId],
...updates,
updated: new Date().toISOString(),
};
this.saveData();
return this.agents.registry[agentId];
}
removeAgent(agentId) {
if (!this.agents.registry[agentId]) {
throw new Error(`Agent ${agentId} not found`);
}
// Unassign from all tasks
if (this.taskTracker && this.taskTracker.tasks) {
Object.values(this.taskTracker.tasks).forEach((task) => {
if (task.assignees) {
task.assignees = task.assignees.filter((a) => a.id !== agentId);
}
});
}
if (this.agents && this.agents.registry) {
delete this.agents.registry[agentId];
}
this.saveData();
console.log(`✅ Agent ${agentId} removed successfully`);
}
// ==================== TASK MANAGEMENT ====================
createTask(taskData) {
const taskId = taskData.id || this.generateTaskId();
const task = {
id: taskId,
title: taskData.title,
category: taskData.category || "general",
phase: taskData.phase || this.taskTracker.current_state.active_phase,
status: taskData.status || "todo",
priority: taskData.priority || "medium",
assignees: this.normalizeAssignees(taskData.assignees || []),
created: new Date().toISOString(),
updated: new Date().toISOString(),
completed: null,
dependencies: taskData.dependencies || [],
blocks: taskData.blocks || [],
subtasks: taskData.subtasks || [],
files_affected: taskData.files_affected || [],
completion_criteria: taskData.completion_criteria || [],
description: taskData.description || "",
recommendation_score: 0,
risk_level: taskData.risk_level || "medium",
estimated_hours: taskData.estimated_hours || 0,
tags: taskData.tags || [],
};
if (!this.taskTracker) {
this.createInitialFiles();
this.loadData();
}
if (!this.taskTracker.tasks) {
this.taskTracker.tasks = {};
}
this.taskTracker.tasks[taskId] = task;
this.updateProgress();
this.updateAgentWorkloads();
this.saveData();
console.log(`✅ Task ${taskId} created: ${task.title}`);
return task;
}
updateTask(taskId, updates) {
if (!this.taskTracker.tasks[taskId]) {
throw new Error(`Task ${taskId} not found`);
}
const oldStatus = this.taskTracker.tasks[taskId].status;
this.taskTracker.tasks[taskId] = {
...this.taskTracker.tasks[taskId],
...updates,
updated: new Date().toISOString(),
};
// Handle status changes
if (updates.status && updates.status !== oldStatus) {
if (updates.status === "completed") {
this.taskTracker.tasks[taskId].completed = new Date().toISOString();
}
}
// Handle assignee changes
if (updates.assignees) {
this.taskTracker.tasks[taskId].assignees = this.normalizeAssignees(
updates.assignees,
);
}
this.updateProgress();
this.updateAgentWorkloads();
this.saveData();
console.log(`✅ Task ${taskId} updated`);
return this.taskTracker.tasks[taskId];
}
deleteTask(taskId) {
if (!this.taskTracker.tasks[taskId]) {
throw new Error(`Task ${taskId} not found`);
}
// Remove from dependencies and blocks
Object.values(this.taskTracker.tasks).forEach((task) => {
task.dependencies = task.dependencies.filter((dep) => dep !== taskId);
task.blocks = task.blocks.filter((block) => block !== taskId);
});
delete this.taskTracker.tasks[taskId];
this.updateProgress();
this.updateAgentWorkloads();
this.saveData();
console.log(`✅ Task ${taskId} deleted`);
}
getTask(taskId) {
return this.taskTracker.tasks[taskId] || null;
}
listTasks(filters = {}) {
let tasks = Object.values(this.taskTracker.tasks);
if (filters.agent) {
tasks = tasks.filter(
(task) =>
task.assignees && task.assignees.some((a) => a.id === filters.agent),
);
}
if (filters.status) {
tasks = tasks.filter((task) => task.status === filters.status);
}
if (filters.priority) {
tasks = tasks.filter((task) => task.priority === filters.priority);
}
if (filters.phase) {
tasks = tasks.filter((task) => task.phase === filters.phase);
}
return tasks;
}
// ==================== TASK ASSIGNMENT ====================
assignAgentToTask(taskId, agentInfo) {
const task = this.getTask(taskId);
if (!task) {
throw new Error(`Task ${taskId} not found`);
}
const normalizedAgent = this.normalizeAssignee(agentInfo);
// Check if agent is already assigned
const isAlreadyAssigned = task.assignees.some(
(a) => a.id === normalizedAgent.id,
);
if (isAlreadyAssigned) {
console.log(
`⚠️ Agent ${normalizedAgent.id} is already assigned to task ${taskId}`,
);
return task;
}
task.assignees.push(normalizedAgent);
task.updated = new Date().toISOString();
this.updateAgentWorkloads();
this.saveData();
// Notify the assigned agent
try {
this.notifyAssignment(taskId, this.currentAgentId);
} catch (error) {
// Notification failed, but assignment succeeded
console.log(`⚠️ Assignment notification failed: ${error.message}`);
}
console.log(`✅ Agent ${normalizedAgent.name} assigned to task ${taskId}`);
return task;
}
unassignAgentFromTask(taskId, agentId) {
const task = this.getTask(taskId);
if (!task) {
throw new Error(`Task ${taskId} not found`);
}
task.assignees = task.assignees.filter((a) => a.id !== agentId);
task.updated = new Date().toISOString();
this.updateAgentWorkloads();
this.saveData();
console.log(`✅ Agent ${agentId} unassigned from task ${taskId}`);
return task;
}
transferTask(taskId, fromAgentId, toAgentInfo) {
this.unassignAgentFromTask(taskId, fromAgentId);
return this.assignAgentToTask(taskId, toAgentInfo);
}
// ==================== RECOMMENDATIONS ====================
getRecommendationsForAgent(agentId, limit = null) {
const agent = this.getAgent(agentId);
if (!agent) {
throw new Error(`Agent ${agentId} not found`);
}
const eligibleTasks = this.getEligibleTasksForAgent(agentId);
const scoredTasks = eligibleTasks.map((task) => ({
...task,
recommendation_score: this.calculateTaskScore(task),
}));
// Sort by score (highest first)
scoredTasks.sort((a, b) => b.recommendation_score - a.recommendation_score);
const recommendations = scoredTasks.slice(
0,
limit || this.config.maxRecommendations,
);
// Update recommendation history
this.addRecommendationHistory(agentId, recommendations);
return recommendations.map((task) => ({
...task,
recommendation_reason: this.generateRecommendationReason(task),
}));
}
// ==================== AGENT-CENTRIC METHODS ====================
getCurrentAgent() {
if (!this.currentAgentId) {
throw new Error(
"No current agent set. Set TASK_MANAGER_AGENT_ID environment variable or pass agentId in constructor.",
);
}
return this.getAgent(this.currentAgentId);
}
setCurrentAgent(agentId) {
const agent = this.getAgent(agentId);
if (!agent) {
throw new Error(`Agent ${agentId} not found`);
}
this.currentAgentId = agentId;
return agent;
}
getMyTasks(filters = {}) {
if (!this.currentAgentId) {
throw new Error("No current agent set");
}
return this.listTasks({ ...filters, agent: this.currentAgentId });
}
getMyActiveTasks() {
return this.getMyTasks({ status: "in-progress" });
}
getMyTodoTasks() {
return this.getMyTasks({ status: "todo" });
}
getMyRecommendations(limit = null) {
if (!this.currentAgentId) {
throw new Error("No current agent set");
}
return this.getRecommendationsForAgent(this.currentAgentId, limit);
}
getMyWorkload() {
if (!this.currentAgentId) {
throw new Error("No current agent set");
}
return this.getAgentWorkload(this.currentAgentId);
}
startTask(taskId) {
const task = this.getTask(taskId);
if (!task) {
throw new Error(`Task ${taskId} not found`);
}
// Check if current agent is assigned to this task
const isAssigned =
task.assignees &&
task.assignees.some((a) => a.id === this.currentAgentId);
if (!isAssigned) {
throw new Error(`Current agent is not assigned to task ${taskId}`);
}
return this.updateTask(taskId, { status: "in-progress" });
}
completeTask(taskId) {
const task = this.getTask(taskId);
if (!task) {
throw new Error(`Task ${taskId} not found`);
}
// Check if current agent is assigned to this task
const isAssigned =
task.assignees &&
task.assignees.some((a) => a.id === this.currentAgentId);
if (!isAssigned) {
throw new Error(`Current agent is not assigned to task ${taskId}`);
}
return this.updateTask(taskId, { status: "completed" });
}
takeSelfAssignedTask(taskId) {
if (!this.currentAgentId) {
throw new Error("No current agent set");
}
const currentAgent = this.getCurrentAgent();
return this.assignAgentToTask(taskId, {
id: this.currentAgentId,
name: currentAgent.name,
type: currentAgent.type,
role: "primary",
});
}
checkIn() {
if (!this.currentAgentId) {
throw new Error("No current agent set");
}
const agent = this.getCurrentAgent();
const activeTasks = this.getMyActiveTasks();
const todoTasks = this.getMyTodoTasks();
const recommendations = this.getMyRecommendations(3);
return {
agent: agent,
status: {
active_tasks: activeTasks.length,
todo_tasks: todoTasks.length,
pending_recommendations: recommendations.length,
},
active_tasks: activeTasks,
todo_tasks: todoTasks,
recommendations: recommendations,
};
}
notifyAssignment(taskId, assignedByAgentId = null) {
const task = this.getTask(taskId);
if (!task) {
throw new Error(`Task ${taskId} not found`);
}
const assignedBy = assignedByAgentId
? this.getAgent(assignedByAgentId)
: null;
const notification = {
type: "task_assignment",
task_id: taskId,
task_title: task.title,
assigned_by: assignedBy ? assignedBy.name : "System",
assigned_at: new Date().toISOString(),
priority: task.priority,
message: `You have been assigned to task: ${task.title}`,
};
// Store notification for the assigned agents
task.assignees.forEach((assignee) => {
if (!this.taskTracker.notifications) {
this.taskTracker.notifications = {};
}
if (!this.taskTracker.notifications[assignee.id]) {
this.taskTracker.notifications[assignee.id] = [];
}
this.taskTracker.notifications[assignee.id].push(notification);
});
this.saveData();
return notification;
}
getMyNotifications() {
if (!this.currentAgentId) {
throw new Error("No current agent set");
}
if (
!this.taskTracker.notifications ||
!this.taskTracker.notifications[this.currentAgentId]
) {
return [];
}
return this.taskTracker.notifications[this.currentAgentId];
}
clearMyNotifications() {
if (!this.currentAgentId) {
throw new Error("No current agent set");
}
if (
this.taskTracker.notifications &&
this.taskTracker.notifications[this.currentAgentId]
) {
this.taskTracker.notifications[this.currentAgentId] = [];
this.saveData();
}
}
getEligibleTasksForAgent(agentId) {
const agent = this.getAgent(agentId);
if (!agent) return [];
return Object.values(this.taskTracker.tasks).filter((task) => {
// Must be todo or unassigned
if (task.status !== "todo" && task.status !== "blocked") return false;
// Check if agent is already assigned
const isAssigned =
task.assignees && task.assignees.some((a) => a.id === agentId);
if (isAssigned) return false;
// Check dependencies
const hasUnmetDependencies = task.dependencies.some((depId) => {
const depTask = this.taskTracker.tasks[depId];
return !depTask || depTask.status !== "completed";
});
if (hasUnmetDependencies) return false;
// Check agent capabilities (for AI agents)
if (
agent.type === "ai" &&
agent.capabilities &&
agent.capabilities.length > 0
) {
const taskRequiresCapability = this.getRequiredCapabilities(task);
if (taskRequiresCapability.length > 0) {
const hasRequiredCapability = taskRequiresCapability.some((cap) =>
agent.capabilities.includes(cap),
);
if (!hasRequiredCapability) return false;
}
}
return true;
});
}
getRequiredCapabilities(task) {
const capabilities = [];
if (task.category === "coding" || task.category === "feature") {
capabilities.push("coding");
}
if (task.category === "testing") {
capabilities.push("testing");
}
if (task.category === "documentation") {
capabilities.push("documentation");
}
if (task.category === "analysis") {
capabilities.push("analysis");
}
return capabilities;
}
calculateTaskScore(task) {
let score = 0;
// Priority score
score += this.scoring.priority[task.priority] || 0;
// Dependency score
const dependencyCount = task.blocks ? task.blocks.length : 0;
score +=
dependencyCount > 0
? this.scoring.dependency.blocking
: this.scoring.dependency.independent;
// Risk score
score += this.scoring.risk[task.risk_level] || 0;
// Phase score
const currentPhase = this.taskTracker.current_state.active_phase;
if (task.phase === currentPhase) {
score += this.scoring.phase.active;
} else {
score += this.scoring.phase.future;
}
return Math.round(score);
}
generateRecommendationReason(task) {
const reasons = [];
if (this.scoring.priority[task.priority] >= 7) {
reasons.push(`High priority (${task.priority})`);
}
if (task.blocks && task.blocks.length > 0) {
reasons.push(`Blocks ${task.blocks.length} other task(s)`);
}
if (task.phase === this.taskTracker.current_state.active_phase) {
reasons.push("In active phase");
}
if (task.risk_level === "high") {
reasons.push("High risk - needs attention");
}
return reasons.join(", ") || "Good fit for current workflow";
}
// ==================== HELPER METHODS ====================
normalizeAssignees(assignees) {
if (!Array.isArray(assignees)) {
assignees = [assignees];
}
return assignees.map((assignee) => this.normalizeAssignee(assignee));
}
normalizeAssignee(assignee) {
if (typeof assignee === "string") {
// Try to find agent by ID first
const agent = this.getAgent(assignee);
if (agent) {
return {
id: agent.id,
name: agent.name,
type: agent.type,
role: "primary",
assigned_date: new Date().toISOString(),
};
}
// Assume it's a human if not found in agents
return {
id: assignee,
name: assignee,
type: "human",
role: "primary",
assigned_date: new Date().toISOString(),
};
}
return {
id: assignee.id,
name: assignee.name || assignee.id,
type: assignee.type || "human",
role: assignee.role || "primary",
assigned_date: assignee.assigned_date || new Date().toISOString(),
};
}
generateTaskId() {
if (!this.taskTracker || !this.taskTracker.tasks) {
return "TASK-001";
}
const tasks = Object.keys(this.taskTracker.tasks);
if (tasks.length === 0) {
return "TASK-001";
}
const maxId = tasks.reduce((max, taskId) => {
const match = taskId.match(/TASK-(\d+)/);
if (match) {
const num = parseInt(match[1]);
return num > max ? num : max;
}
return max;
}, 0);
return `TASK-${String(maxId + 1).padStart(3, "0")}`;
}
updateProgress() {
const tasks = Object.values(this.taskTracker.tasks);
const total = tasks.length;
const completed = tasks.filter((t) => t.status === "completed").length;
const inProgress = tasks.filter((t) => t.status === "in-progress").length;
const todo = tasks.filter((t) => t.status === "todo").length;
this.taskTracker.progress = {
total_tasks: total,
completed: completed,
in_progress: inProgress,
todo: todo,
completion_percentage:
total > 0 ? Math.round((completed / total) * 100) : 0,
};
}
updateAgentWorkloads() {
if (!this.agents || !this.agents.registry) {
return;
}
// Reset all agent workloads
Object.values(this.agents.registry).forEach((agent) => {
agent.workload = {
active_tasks: 0,
completed_tasks: 0,
total_score: 0,
};
});
if (!this.taskTracker || !this.taskTracker.tasks) {
return;
}
// Calculate workloads from tasks
Object.values(this.taskTracker.tasks).forEach((task) => {
if (task.assignees) {
task.assignees.forEach((assignee) => {
const agent = this.agents.registry[assignee.id];
if (agent) {
if (task.status === "completed") {
agent.workload.completed_tasks++;
} else if (
task.status === "in-progress" ||
task.status === "todo"
) {
agent.workload.active_tasks++;
agent.workload.total_score += task.recommendation_score || 0;
}
}
});
}
});
}
addRecommendationHistory(agentId, recommendations) {
const historyEntry = {
agent_id: agentId,
date: new Date().toISOString(),
algorithm_version: "2.0.0",
recommendations: recommendations.map((task) => ({
task_id: task.id,
score: task.recommendation_score,
selected: false,
})),
rationale: `Generated ${recommendations.length} recommendations for agent ${agentId}`,
};
if (!this.taskTracker.recommendation_history) {
this.taskTracker.recommendation_history = [];
}
this.taskTracker.recommendation_history.push(historyEntry);
// Keep only last 50 entries
if (this.taskTracker.recommendation_history.length > 50) {
this.taskTracker.recommendation_history =
this.taskTracker.recommendation_history.slice(-50);
}
}
// ==================== REPORTING ====================
getProjectStatus() {
const agents = this.listAgents();
const tasks = Object.values(this.taskTracker.tasks);
return {
project: this.taskTracker.project,
progress: this.taskTracker.progress,
agents: {
total: agents.length,
active: agents.filter((a) => a.status === "active").length,
by_type: {
human: agents.filter((a) => a.type === "human").length,
ai: agents.filter((a) => a.type === "ai").length,
},
},
tasks: {
total: tasks.length,
by_status: {
todo: tasks.filter((t) => t.status === "todo").length,
in_progress: tasks.filter((t) => t.status === "in-progress").length,
completed: tasks.filter((t) => t.status === "completed").length,
blocked: tasks.filter((t) => t.status === "blocked").length,
},
by_priority: {
critical: tasks.filter((t) => t.priority === "critical").length,
high: tasks.filter((t) => t.priority === "high").length,
medium: tasks.filter((t) => t.priority === "medium").length,
low: tasks.filter((t) => t.priority === "low").length,
},
},
};
}
getAgentWorkload(agentId) {
const agent = this.getAgent(agentId);
if (!agent) {
throw new Error(`Agent ${agentId} not found`);
}
const tasks = this.listTasks({ agent: agentId });
return {
agent: agent,
workload: agent.workload,
tasks: {
active: tasks.filter(
(t) => t.status === "in-progress" || t.status === "todo",
),
completed: tasks.filter((t) => t.status === "completed"),
blocked: tasks.filter((t) => t.status === "blocked"),
},
};
}
// ==================== CLI INTERFACE ====================
static parseArgs(args) {
const parsed = { command: args