Alwrity today's tasks workflow implementation plan.

This commit is contained in:
ajaysi
2025-09-06 15:28:05 +05:30
parent f82ada0361
commit ae42720c2a
25 changed files with 7836 additions and 46 deletions

View File

@@ -0,0 +1,426 @@
import {
TodayTask
} from '../types/workflow';
interface VerificationResult {
isCompleted: boolean;
confidence: number; // 0-1 scale
evidence: string[];
warnings: string[];
suggestions: string[];
}
interface VerificationRule {
id: string;
name: string;
description: string;
pillarId: string;
actionType: string;
verifier: (task: TodayTask, context?: any) => Promise<VerificationResult>;
weight: number; // Importance weight for confidence calculation
}
interface VerificationContext {
userId: string;
timestamp: Date;
platformData?: Record<string, any>;
userActivity?: Record<string, any>;
}
class TaskCompletionVerifier {
private verificationRules: Map<string, VerificationRule> = new Map();
private verificationHistory: Map<string, VerificationResult[]> = new Map();
constructor() {
this.initializeDefaultRules();
}
/**
* Verify if a task has been completed
*/
async verifyTaskCompletion(
task: TodayTask,
context?: VerificationContext
): Promise<VerificationResult> {
try {
const rule = this.verificationRules.get(`${task.pillarId}-${task.actionType}`);
if (!rule) {
// Fallback to basic verification
return this.basicVerification(task, context);
}
const result = await rule.verifier(task, context);
// Store verification history
this.storeVerificationResult(task.id, result);
return result;
} catch (error) {
console.error(`Verification failed for task ${task.id}:`, error);
return {
isCompleted: false,
confidence: 0,
evidence: [],
warnings: [`Verification failed: ${error instanceof Error ? error.message : 'Unknown error'}`],
suggestions: ['Try completing the task again or contact support']
};
}
}
/**
* Verify multiple tasks at once
*/
async verifyMultipleTasks(
tasks: TodayTask[],
context?: VerificationContext
): Promise<Map<string, VerificationResult>> {
const results = new Map<string, VerificationResult>();
// Verify tasks in parallel for better performance
const verificationPromises = tasks.map(async (task) => {
const result = await this.verifyTaskCompletion(task, context);
results.set(task.id, result);
});
await Promise.all(verificationPromises);
return results;
}
/**
* Get verification history for a task
*/
getVerificationHistory(taskId: string): VerificationResult[] {
return this.verificationHistory.get(taskId) || [];
}
/**
* Add custom verification rule
*/
addVerificationRule(rule: VerificationRule): void {
const key = `${rule.pillarId}-${rule.actionType}`;
this.verificationRules.set(key, rule);
}
/**
* Remove verification rule
*/
removeVerificationRule(pillarId: string, actionType: string): void {
const key = `${pillarId}-${actionType}`;
this.verificationRules.delete(key);
}
/**
* Get all verification rules
*/
getVerificationRules(): VerificationRule[] {
return Array.from(this.verificationRules.values());
}
/**
* Initialize default verification rules
*/
private initializeDefaultRules(): void {
// Plan pillar rules
this.addVerificationRule({
id: 'plan-navigate',
name: 'Content Planning Navigation',
description: 'Verify user navigated to content planning dashboard',
pillarId: 'plan',
actionType: 'navigate',
weight: 0.8,
verifier: async (task, context) => {
return this.verifyNavigation(task, context);
}
});
// Generate pillar rules
this.addVerificationRule({
id: 'generate-navigate',
name: 'Content Generation Navigation',
description: 'Verify user navigated to content generation tools',
pillarId: 'generate',
actionType: 'navigate',
weight: 0.8,
verifier: async (task, context) => {
return this.verifyNavigation(task, context);
}
});
// Publish pillar rules
this.addVerificationRule({
id: 'publish-navigate',
name: 'Content Publishing Navigation',
description: 'Verify user navigated to publishing tools',
pillarId: 'publish',
actionType: 'navigate',
weight: 0.8,
verifier: async (task, context) => {
return this.verifyNavigation(task, context);
}
});
// Analyze pillar rules
this.addVerificationRule({
id: 'analyze-navigate',
name: 'Analytics Navigation',
description: 'Verify user navigated to analytics dashboard',
pillarId: 'analyze',
actionType: 'navigate',
weight: 0.8,
verifier: async (task, context) => {
return this.verifyNavigation(task, context);
}
});
// Engage pillar rules
this.addVerificationRule({
id: 'engage-navigate',
name: 'Engagement Navigation',
description: 'Verify user navigated to engagement tools',
pillarId: 'engage',
actionType: 'navigate',
weight: 0.8,
verifier: async (task, context) => {
return this.verifyNavigation(task, context);
}
});
// Remarket pillar rules
this.addVerificationRule({
id: 'remarket-navigate',
name: 'Remarketing Navigation',
description: 'Verify user navigated to remarketing tools',
pillarId: 'remarket',
actionType: 'navigate',
weight: 0.8,
verifier: async (task, context) => {
return this.verifyNavigation(task, context);
}
});
}
/**
* Verify navigation-based tasks
*/
private async verifyNavigation(
task: TodayTask,
context?: VerificationContext
): Promise<VerificationResult> {
const evidence: string[] = [];
const warnings: string[] = [];
const suggestions: string[] = [];
let confidence = 0;
try {
// Check if user is currently on the target page
if (typeof window !== 'undefined' && task.actionUrl) {
const currentPath = window.location.pathname;
const targetPath = task.actionUrl;
if (currentPath === targetPath) {
evidence.push(`User is currently on target page: ${targetPath}`);
confidence += 0.4;
} else {
warnings.push(`User is not on target page. Current: ${currentPath}, Expected: ${targetPath}`);
suggestions.push('Navigate to the correct page to complete this task');
}
}
// Check user activity (if available)
if (context?.userActivity) {
const activity = context.userActivity;
const taskStartTime = task.startedAt?.getTime() || 0;
const recentActivity = Object.entries(activity)
.filter(([_, timestamp]) => typeof timestamp === 'number' && timestamp > taskStartTime);
if (recentActivity.length > 0) {
evidence.push(`User activity detected after task start: ${recentActivity.length} actions`);
confidence += 0.3;
} else {
warnings.push('No user activity detected after task start');
}
}
// Check platform data (if available)
if (context?.platformData) {
const platformData = context.platformData;
if (platformData.lastActivity && platformData.lastActivity > (task.startedAt?.getTime() || 0)) {
evidence.push('Platform activity detected after task start');
confidence += 0.3;
}
}
// Time-based verification
if (task.startedAt && task.completedAt) {
const timeSpent = task.completedAt.getTime() - task.startedAt.getTime();
const estimatedTime = task.estimatedTime * 60 * 1000; // Convert to milliseconds
if (timeSpent >= estimatedTime * 0.5) { // At least 50% of estimated time
evidence.push(`Task took ${Math.round(timeSpent / 60000)} minutes (estimated: ${task.estimatedTime} minutes)`);
confidence += 0.2;
} else {
warnings.push(`Task completed too quickly (${Math.round(timeSpent / 60000)} minutes vs ${task.estimatedTime} estimated)`);
}
}
// Cap confidence at 1.0
confidence = Math.min(confidence, 1.0);
return {
isCompleted: confidence >= 0.6, // Threshold for completion
confidence,
evidence,
warnings,
suggestions
};
} catch (error) {
return {
isCompleted: false,
confidence: 0,
evidence: [],
warnings: [`Navigation verification failed: ${error instanceof Error ? error.message : 'Unknown error'}`],
suggestions: ['Try navigating to the target page again']
};
}
}
/**
* Basic verification fallback
*/
private async basicVerification(
task: TodayTask,
context?: VerificationContext
): Promise<VerificationResult> {
const evidence: string[] = [];
const warnings: string[] = [];
const suggestions: string[] = [];
let confidence = 0;
// Check if task has completion timestamp
if (task.completedAt) {
evidence.push('Task has completion timestamp');
confidence += 0.5;
} else {
warnings.push('No completion timestamp found');
}
// Check if task was started
if (task.startedAt) {
evidence.push('Task was started');
confidence += 0.3;
} else {
warnings.push('No start timestamp found');
}
// Check time spent
if (task.startedAt && task.completedAt) {
const timeSpent = task.completedAt.getTime() - task.startedAt.getTime();
if (timeSpent > 0) {
evidence.push(`Task took ${Math.round(timeSpent / 60000)} minutes`);
confidence += 0.2;
}
}
return {
isCompleted: confidence >= 0.5,
confidence,
evidence,
warnings,
suggestions: suggestions.length > 0 ? suggestions : ['Complete the task to verify completion']
};
}
/**
* Store verification result in history
*/
private storeVerificationResult(taskId: string, result: VerificationResult): void {
const history = this.verificationHistory.get(taskId) || [];
history.push(result);
// Keep only last 10 verification results
if (history.length > 10) {
history.shift();
}
this.verificationHistory.set(taskId, history);
}
/**
* Get verification statistics
*/
getVerificationStats(): {
totalVerifications: number;
averageConfidence: number;
completionRate: number;
mostCommonWarnings: string[];
} {
const allResults = Array.from(this.verificationHistory.values()).flat();
if (allResults.length === 0) {
return {
totalVerifications: 0,
averageConfidence: 0,
completionRate: 0,
mostCommonWarnings: []
};
}
const totalVerifications = allResults.length;
const averageConfidence = allResults.reduce((sum, result) => sum + result.confidence, 0) / totalVerifications;
const completionRate = allResults.filter(result => result.isCompleted).length / totalVerifications;
// Count warning frequency
const warningCounts = new Map<string, number>();
allResults.forEach(result => {
result.warnings.forEach(warning => {
warningCounts.set(warning, (warningCounts.get(warning) || 0) + 1);
});
});
const mostCommonWarnings = Array.from(warningCounts.entries())
.sort((a, b) => b[1] - a[1])
.slice(0, 5)
.map(([warning]) => warning);
return {
totalVerifications,
averageConfidence,
completionRate,
mostCommonWarnings
};
}
/**
* Clear verification history
*/
clearVerificationHistory(): void {
this.verificationHistory.clear();
}
/**
* Export verification data
*/
exportVerificationData(): {
rules: VerificationRule[];
history: Record<string, VerificationResult[]>;
stats: {
totalVerifications: number;
averageConfidence: number;
completionRate: number;
mostCommonWarnings: string[];
};
} {
return {
rules: this.getVerificationRules(),
history: Object.fromEntries(this.verificationHistory),
stats: this.getVerificationStats()
};
}
}
// Export singleton instance
export const taskCompletionVerifier = new TaskCompletionVerifier();
export default TaskCompletionVerifier;

View File

@@ -0,0 +1,433 @@
import {
TodayTask,
DailyWorkflow,
WorkflowError
} from '../types/workflow';
interface DependencyGraph {
[taskId: string]: {
dependencies: string[];
dependents: string[];
status: 'ready' | 'blocked' | 'completed' | 'skipped';
};
}
interface DependencyValidationResult {
isValid: boolean;
errors: string[];
warnings: string[];
readyTasks: string[];
blockedTasks: string[];
}
class TaskDependencyManager {
private dependencyGraph: DependencyGraph = {};
/**
* Build dependency graph from workflow tasks
*/
buildDependencyGraph(workflow: DailyWorkflow): DependencyGraph {
this.dependencyGraph = {};
// Initialize all tasks in the graph
workflow.tasks.forEach(task => {
this.dependencyGraph[task.id] = {
dependencies: task.dependencies || [],
dependents: [],
status: this.getTaskStatus(task, workflow)
};
});
// Build dependent relationships
Object.keys(this.dependencyGraph).forEach(taskId => {
const task = this.dependencyGraph[taskId];
task.dependencies.forEach(depId => {
if (this.dependencyGraph[depId]) {
this.dependencyGraph[depId].dependents.push(taskId);
}
});
});
return this.dependencyGraph;
}
/**
* Validate dependency graph for issues
*/
validateDependencyGraph(workflow: DailyWorkflow): DependencyValidationResult {
const result: DependencyValidationResult = {
isValid: true,
errors: [],
warnings: [],
readyTasks: [],
blockedTasks: []
};
this.buildDependencyGraph(workflow);
// Check for circular dependencies
const circularDeps = this.detectCircularDependencies();
if (circularDeps.length > 0) {
result.isValid = false;
result.errors.push(`Circular dependencies detected: ${circularDeps.join(', ')}`);
}
// Check for missing dependencies
const missingDeps = this.detectMissingDependencies(workflow);
if (missingDeps.length > 0) {
result.isValid = false;
result.errors.push(`Missing dependencies: ${missingDeps.join(', ')}`);
}
// Check for orphaned tasks (tasks with no dependencies or dependents)
const orphanedTasks = this.detectOrphanedTasks();
if (orphanedTasks.length > 0) {
result.warnings.push(`Orphaned tasks detected: ${orphanedTasks.join(', ')}`);
}
// Categorize tasks by readiness
Object.keys(this.dependencyGraph).forEach(taskId => {
const task = this.dependencyGraph[taskId];
if (task.status === 'ready') {
result.readyTasks.push(taskId);
} else if (task.status === 'blocked') {
result.blockedTasks.push(taskId);
}
});
return result;
}
/**
* Get tasks that are ready to be executed
*/
getReadyTasks(workflow: DailyWorkflow): TodayTask[] {
this.buildDependencyGraph(workflow);
return workflow.tasks.filter(task => {
const graphTask = this.dependencyGraph[task.id];
return graphTask && graphTask.status === 'ready' && task.status === 'pending';
});
}
/**
* Get tasks that are blocked by dependencies
*/
getBlockedTasks(workflow: DailyWorkflow): TodayTask[] {
this.buildDependencyGraph(workflow);
return workflow.tasks.filter(task => {
const graphTask = this.dependencyGraph[task.id];
return graphTask && graphTask.status === 'blocked';
});
}
/**
* Get tasks that depend on a specific task
*/
getDependentTasks(taskId: string): string[] {
const task = this.dependencyGraph[taskId];
return task ? [...task.dependents] : [];
}
/**
* Get tasks that a specific task depends on
*/
getDependencyTasks(taskId: string): string[] {
const task = this.dependencyGraph[taskId];
return task ? [...task.dependencies] : [];
}
/**
* Check if a task can be executed (all dependencies satisfied)
*/
canExecuteTask(taskId: string, workflow: DailyWorkflow): boolean {
this.buildDependencyGraph(workflow);
const task = this.dependencyGraph[taskId];
if (!task) {
return false;
}
return task.status === 'ready';
}
/**
* Get the optimal execution order for tasks
*/
getOptimalExecutionOrder(workflow: DailyWorkflow): TodayTask[] {
this.buildDependencyGraph(workflow);
const visited = new Set<string>();
const visiting = new Set<string>();
const executionOrder: TodayTask[] = [];
const visit = (taskId: string) => {
if (visiting.has(taskId)) {
throw new WorkflowError({
code: 'CIRCULAR_DEPENDENCY',
message: `Circular dependency detected involving task ${taskId}`,
timestamp: new Date(),
recoverable: false
});
}
if (visited.has(taskId)) {
return;
}
visiting.add(taskId);
const task = this.dependencyGraph[taskId];
if (task) {
// Visit dependencies first
task.dependencies.forEach(depId => {
visit(depId);
});
}
visiting.delete(taskId);
visited.add(taskId);
// Add task to execution order
const workflowTask = workflow.tasks.find(t => t.id === taskId);
if (workflowTask) {
executionOrder.push(workflowTask);
}
};
// Visit all tasks
workflow.tasks.forEach(task => {
if (!visited.has(task.id)) {
visit(task.id);
}
});
return executionOrder;
}
/**
* Update task status in dependency graph
*/
updateTaskStatus(taskId: string, status: 'completed' | 'skipped' | 'in_progress'): void {
if (this.dependencyGraph[taskId]) {
// Update status of the task
this.dependencyGraph[taskId].status = status === 'in_progress' ? 'ready' : status;
// Update status of dependent tasks
this.updateDependentTasksStatus(taskId);
}
}
/**
* Get dependency chain for a task (all tasks that must be completed first)
*/
getDependencyChain(taskId: string): string[] {
const chain: string[] = [];
const visited = new Set<string>();
const buildChain = (currentTaskId: string) => {
if (visited.has(currentTaskId)) {
return;
}
visited.add(currentTaskId);
const task = this.dependencyGraph[currentTaskId];
if (task) {
task.dependencies.forEach(depId => {
buildChain(depId);
if (!chain.includes(depId)) {
chain.push(depId);
}
});
}
};
buildChain(taskId);
return chain;
}
/**
* Get impact of completing a task (what tasks become available)
*/
getCompletionImpact(taskId: string): string[] {
const impact: string[] = [];
const task = this.dependencyGraph[taskId];
if (task) {
task.dependents.forEach(dependentId => {
const dependent = this.dependencyGraph[dependentId];
if (dependent && dependent.status === 'blocked') {
// Check if all dependencies are now satisfied
const allDepsSatisfied = dependent.dependencies.every(depId => {
const depTask = this.dependencyGraph[depId];
return depTask && (depTask.status === 'completed' || depTask.status === 'skipped');
});
if (allDepsSatisfied) {
impact.push(dependentId);
}
}
});
}
return impact;
}
/**
* Detect circular dependencies in the graph
*/
private detectCircularDependencies(): string[] {
const visited = new Set<string>();
const visiting = new Set<string>();
const circular: string[] = [];
const visit = (taskId: string, path: string[] = []) => {
if (visiting.has(taskId)) {
const cycleStart = path.indexOf(taskId);
if (cycleStart !== -1) {
circular.push(...path.slice(cycleStart), taskId);
}
return;
}
if (visited.has(taskId)) {
return;
}
visiting.add(taskId);
const task = this.dependencyGraph[taskId];
if (task) {
task.dependencies.forEach(depId => {
visit(depId, [...path, taskId]);
});
}
visiting.delete(taskId);
visited.add(taskId);
};
Object.keys(this.dependencyGraph).forEach(taskId => {
if (!visited.has(taskId)) {
visit(taskId);
}
});
return [...new Set(circular)];
}
/**
* Detect missing dependencies
*/
private detectMissingDependencies(workflow: DailyWorkflow): string[] {
const missing: string[] = [];
const taskIds = new Set(workflow.tasks.map(t => t.id));
Object.keys(this.dependencyGraph).forEach(taskId => {
const task = this.dependencyGraph[taskId];
task.dependencies.forEach(depId => {
if (!taskIds.has(depId)) {
missing.push(`${taskId} -> ${depId}`);
}
});
});
return missing;
}
/**
* Detect orphaned tasks
*/
private detectOrphanedTasks(): string[] {
const orphaned: string[] = [];
Object.keys(this.dependencyGraph).forEach(taskId => {
const task = this.dependencyGraph[taskId];
if (task.dependencies.length === 0 && task.dependents.length === 0) {
orphaned.push(taskId);
}
});
return orphaned;
}
/**
* Update dependent tasks status when a dependency is completed
*/
private updateDependentTasksStatus(completedTaskId: string): void {
const task = this.dependencyGraph[completedTaskId];
if (!task) {
return;
}
task.dependents.forEach(dependentId => {
const dependent = this.dependencyGraph[dependentId];
if (dependent && dependent.status === 'blocked') {
// Check if all dependencies are now satisfied
const allDepsSatisfied = dependent.dependencies.every(depId => {
const depTask = this.dependencyGraph[depId];
return depTask && (depTask.status === 'completed' || depTask.status === 'skipped');
});
if (allDepsSatisfied) {
dependent.status = 'ready';
}
}
});
}
/**
* Get task status based on dependencies
*/
private getTaskStatus(task: TodayTask, workflow: DailyWorkflow): 'ready' | 'blocked' | 'completed' | 'skipped' {
if (task.status === 'completed' || task.status === 'skipped') {
return task.status;
}
if (!task.dependencies || task.dependencies.length === 0) {
return 'ready';
}
// Check if all dependencies are satisfied
const allDepsSatisfied = task.dependencies.every(depId => {
const depTask = workflow.tasks.find(t => t.id === depId);
return depTask && (depTask.status === 'completed' || depTask.status === 'skipped');
});
return allDepsSatisfied ? 'ready' : 'blocked';
}
/**
* Get dependency graph visualization data
*/
getDependencyGraphData(): {
nodes: Array<{ id: string; label: string; status: string }>;
edges: Array<{ from: string; to: string; type: string }>;
} {
const nodes = Object.keys(this.dependencyGraph).map(taskId => ({
id: taskId,
label: taskId,
status: this.dependencyGraph[taskId].status
}));
const edges: Array<{ from: string; to: string; type: string }> = [];
Object.keys(this.dependencyGraph).forEach(taskId => {
const task = this.dependencyGraph[taskId];
task.dependencies.forEach(depId => {
edges.push({
from: depId,
to: taskId,
type: 'dependency'
});
});
});
return { nodes, edges };
}
}
// Export singleton instance
export const taskDependencyManager = new TaskDependencyManager();
export default TaskDependencyManager;

View File

@@ -0,0 +1,469 @@
import {
TodayTask,
DailyWorkflow,
NavigationState,
WorkflowError
} from '../types/workflow';
interface NavigationConfig {
autoNavigate: boolean;
delayBeforeNavigation: number; // milliseconds
showNavigationConfirmation: boolean;
enableBackNavigation: boolean;
persistNavigationState: boolean;
}
interface NavigationEvent {
type: 'task_started' | 'task_completed' | 'task_skipped' | 'navigation_requested';
taskId: string;
workflowId: string;
timestamp: Date;
metadata?: Record<string, any>;
}
class TaskNavigationService {
private config: NavigationConfig;
private navigationHistory: NavigationEvent[] = [];
private currentNavigationState: NavigationState | null = null;
private navigationListeners: Array<(event: NavigationEvent) => void> = [];
constructor(config: NavigationConfig = {
autoNavigate: true,
delayBeforeNavigation: 2000,
showNavigationConfirmation: false,
enableBackNavigation: true,
persistNavigationState: true
}) {
this.config = config;
this.loadNavigationHistory();
}
/**
* Navigate to a specific task
*/
async navigateToTask(
task: TodayTask,
workflow: DailyWorkflow,
options: {
skipConfirmation?: boolean;
trackNavigation?: boolean;
} = {}
): Promise<boolean> {
try {
// Validate task and workflow
if (!this.validateTaskForNavigation(task, workflow)) {
throw new WorkflowError({
code: 'INVALID_NAVIGATION_TARGET',
message: `Cannot navigate to task ${task.id}`,
timestamp: new Date(),
recoverable: true,
suggestedAction: 'Check task dependencies and status'
});
}
// Show confirmation if required
if (this.config.showNavigationConfirmation && !options.skipConfirmation) {
const confirmed = await this.showNavigationConfirmation(task);
if (!confirmed) {
return false;
}
}
// Execute navigation based on action type
const navigationSuccess = await this.executeNavigation(task);
if (navigationSuccess) {
// Update navigation state
this.updateNavigationState(task, workflow);
// Track navigation event
if (options.trackNavigation !== false) {
this.trackNavigationEvent({
type: 'navigation_requested',
taskId: task.id,
workflowId: workflow.id,
timestamp: new Date(),
metadata: { actionType: task.actionType, actionUrl: task.actionUrl }
});
}
// Auto-navigate to next task if enabled
if (this.config.autoNavigate && task.status === 'completed') {
setTimeout(() => {
this.autoNavigateToNextTask(workflow);
}, this.config.delayBeforeNavigation);
}
}
return navigationSuccess;
} catch (error) {
console.error('Navigation failed:', error);
throw error;
}
}
/**
* Auto-navigate to the next available task
*/
async autoNavigateToNextTask(workflow: DailyWorkflow): Promise<TodayTask | null> {
try {
const nextTask = this.getNextAvailableTask(workflow);
if (nextTask) {
await this.navigateToTask(nextTask, workflow, {
skipConfirmation: true,
trackNavigation: true
});
return nextTask;
}
return null;
} catch (error) {
console.error('Auto-navigation failed:', error);
return null;
}
}
/**
* Navigate back to previous task
*/
async navigateBack(workflow: DailyWorkflow): Promise<TodayTask | null> {
if (!this.config.enableBackNavigation) {
throw new WorkflowError({
code: 'BACK_NAVIGATION_DISABLED',
message: 'Back navigation is disabled',
timestamp: new Date(),
recoverable: false
});
}
try {
const previousTask = this.getPreviousTask(workflow);
if (previousTask) {
await this.navigateToTask(previousTask, workflow, {
skipConfirmation: true,
trackNavigation: true
});
return previousTask;
}
return null;
} catch (error) {
console.error('Back navigation failed:', error);
throw error;
}
}
/**
* Get the next available task in the workflow
*/
getNextAvailableTask(workflow: DailyWorkflow): TodayTask | null {
const currentIndex = workflow.currentTaskIndex;
const remainingTasks = workflow.tasks.slice(currentIndex + 1);
// Find next task that's not completed and has dependencies satisfied
for (const task of remainingTasks) {
if (task.status === 'pending' && this.areDependenciesSatisfied(task, workflow)) {
return task;
}
}
return null;
}
/**
* Get the previous task in the workflow
*/
getPreviousTask(workflow: DailyWorkflow): TodayTask | null {
const currentIndex = workflow.currentTaskIndex;
if (currentIndex > 0) {
return workflow.tasks[currentIndex - 1];
}
return null;
}
/**
* Check if task dependencies are satisfied
*/
areDependenciesSatisfied(task: TodayTask, workflow: DailyWorkflow): boolean {
if (!task.dependencies || task.dependencies.length === 0) {
return true;
}
return task.dependencies.every(depId => {
const depTask = workflow.tasks.find(t => t.id === depId);
return depTask && depTask.status === 'completed';
});
}
/**
* Execute the actual navigation based on task action type
*/
private async executeNavigation(task: TodayTask): Promise<boolean> {
try {
switch (task.actionType) {
case 'navigate':
return await this.navigateToInternalPage(task);
case 'modal':
return await this.openModal(task);
case 'external':
return await this.navigateToExternalUrl(task);
default:
throw new WorkflowError({
code: 'UNKNOWN_ACTION_TYPE',
message: `Unknown action type: ${task.actionType}`,
timestamp: new Date(),
recoverable: true,
suggestedAction: 'Check task configuration'
});
}
} catch (error) {
console.error(`Navigation execution failed for task ${task.id}:`, error);
return false;
}
}
/**
* Navigate to internal ALwrity page
*/
private async navigateToInternalPage(task: TodayTask): Promise<boolean> {
if (!task.actionUrl) {
throw new WorkflowError({
code: 'MISSING_ACTION_URL',
message: `Task ${task.id} is missing action URL`,
timestamp: new Date(),
recoverable: true,
suggestedAction: 'Configure action URL for the task'
});
}
try {
// Use React Router navigation
if (typeof window !== 'undefined' && window.history) {
window.history.pushState(null, '', task.actionUrl);
// Dispatch custom event for React Router to handle
window.dispatchEvent(new PopStateEvent('popstate'));
return true;
}
return false;
} catch (error) {
console.error('Internal navigation failed:', error);
return false;
}
}
/**
* Open modal for task
*/
private async openModal(task: TodayTask): Promise<boolean> {
try {
// Dispatch custom event to open modal
const modalEvent = new CustomEvent('openTaskModal', {
detail: { task }
});
if (typeof window !== 'undefined') {
window.dispatchEvent(modalEvent);
return true;
}
return false;
} catch (error) {
console.error('Modal opening failed:', error);
return false;
}
}
/**
* Navigate to external URL
*/
private async navigateToExternalUrl(task: TodayTask): Promise<boolean> {
if (!task.actionUrl) {
throw new WorkflowError({
code: 'MISSING_ACTION_URL',
message: `Task ${task.id} is missing external URL`,
timestamp: new Date(),
recoverable: true,
suggestedAction: 'Configure external URL for the task'
});
}
try {
if (typeof window !== 'undefined') {
window.open(task.actionUrl, '_blank', 'noopener,noreferrer');
return true;
}
return false;
} catch (error) {
console.error('External navigation failed:', error);
return false;
}
}
/**
* Validate if task can be navigated to
*/
private validateTaskForNavigation(task: TodayTask, workflow: DailyWorkflow): boolean {
// Check if task exists in workflow
const workflowTask = workflow.tasks.find(t => t.id === task.id);
if (!workflowTask) {
return false;
}
// Check if task is enabled
if (!task.enabled) {
return false;
}
// Check dependencies
if (!this.areDependenciesSatisfied(task, workflow)) {
return false;
}
return true;
}
/**
* Show navigation confirmation dialog
*/
private async showNavigationConfirmation(task: TodayTask): Promise<boolean> {
return new Promise((resolve) => {
// In a real implementation, this would show a confirmation dialog
// For now, we'll use a simple confirm dialog
const confirmed = window.confirm(
`Navigate to: ${task.title}\n\n${task.description}\n\nContinue?`
);
resolve(confirmed);
});
}
/**
* Update navigation state
*/
private updateNavigationState(task: TodayTask, workflow: DailyWorkflow): void {
const currentIndex = workflow.tasks.findIndex(t => t.id === task.id);
const previousTask = currentIndex > 0 ? workflow.tasks[currentIndex - 1] : null;
const nextTask = currentIndex < workflow.tasks.length - 1 ? workflow.tasks[currentIndex + 1] : null;
this.currentNavigationState = {
currentTask: task,
previousTask,
nextTask,
canGoBack: currentIndex > 0,
canGoForward: currentIndex < workflow.tasks.length - 1
};
}
/**
* Track navigation event
*/
private trackNavigationEvent(event: NavigationEvent): void {
this.navigationHistory.push(event);
// Notify listeners
this.navigationListeners.forEach(listener => {
try {
listener(event);
} catch (error) {
console.error('Navigation listener error:', error);
}
});
// Persist navigation history
if (this.config.persistNavigationState) {
this.persistNavigationHistory();
}
}
/**
* Add navigation event listener
*/
addNavigationListener(listener: (event: NavigationEvent) => void): void {
this.navigationListeners.push(listener);
}
/**
* Remove navigation event listener
*/
removeNavigationListener(listener: (event: NavigationEvent) => void): void {
const index = this.navigationListeners.indexOf(listener);
if (index > -1) {
this.navigationListeners.splice(index, 1);
}
}
/**
* Get current navigation state
*/
getCurrentNavigationState(): NavigationState | null {
return this.currentNavigationState;
}
/**
* Get navigation history
*/
getNavigationHistory(): NavigationEvent[] {
return [...this.navigationHistory];
}
/**
* Clear navigation history
*/
clearNavigationHistory(): void {
this.navigationHistory = [];
this.persistNavigationHistory();
}
/**
* Persist navigation history to localStorage
*/
private persistNavigationHistory(): void {
try {
localStorage.setItem('task-navigation-history', JSON.stringify(this.navigationHistory));
} catch (error) {
console.warn('Failed to persist navigation history:', error);
}
}
/**
* Load navigation history from localStorage
*/
private loadNavigationHistory(): void {
try {
const stored = localStorage.getItem('task-navigation-history');
if (stored) {
this.navigationHistory = JSON.parse(stored).map((event: any) => ({
...event,
timestamp: new Date(event.timestamp)
}));
}
} catch (error) {
console.warn('Failed to load navigation history:', error);
}
}
/**
* Update navigation configuration
*/
updateConfig(newConfig: Partial<NavigationConfig>): void {
this.config = { ...this.config, ...newConfig };
}
/**
* Get current configuration
*/
getConfig(): NavigationConfig {
return { ...this.config };
}
}
// Export singleton instance
export const taskNavigationService = new TaskNavigationService();
export default TaskNavigationService;

View File

@@ -0,0 +1,611 @@
import {
TodayTask,
DailyWorkflow,
WorkflowProgress,
TaskCompletionData,
TaskGenerationContext,
WorkflowOrchestratorConfig,
NavigationState,
WorkflowError
} from '../types/workflow';
import { taskNavigationService } from './TaskNavigationService';
import { taskDependencyManager } from './TaskDependencyManager';
import { taskCompletionVerifier } from './TaskCompletionVerifier';
class TaskWorkflowOrchestrator {
private workflows: Map<string, DailyWorkflow> = new Map();
private config: WorkflowOrchestratorConfig;
constructor(config: WorkflowOrchestratorConfig = {
autoNavigate: true,
showProgress: true,
enableNotifications: true,
persistProgress: true,
allowTaskSkipping: true
}) {
this.config = config;
this.loadPersistedWorkflows();
}
/**
* Generate a new daily workflow for a user
*/
async generateDailyWorkflow(
userId: string,
date: string = new Date().toISOString().split('T')[0],
context?: TaskGenerationContext
): Promise<DailyWorkflow> {
try {
// Check if workflow already exists for this date
const existingWorkflow = this.getWorkflow(userId, date);
if (existingWorkflow) {
return existingWorkflow;
}
// Generate tasks based on context or default configuration
const tasks = await this.generateTasksForDate(userId, date, context);
// Create new workflow
const workflow: DailyWorkflow = {
id: `${userId}-${date}`,
date,
userId,
tasks,
currentTaskIndex: 0,
completedTasks: 0,
totalTasks: tasks.length,
workflowStatus: 'not_started',
totalEstimatedTime: tasks.reduce((sum, task) => sum + task.estimatedTime, 0),
actualTimeSpent: 0
};
// Save workflow
this.workflows.set(workflow.id, workflow);
this.persistWorkflow(workflow);
return workflow;
} catch (error) {
throw new WorkflowError({
code: 'WORKFLOW_GENERATION_FAILED',
message: `Failed to generate workflow for user ${userId} on ${date}`,
timestamp: new Date(),
recoverable: true,
suggestedAction: 'Retry workflow generation'
});
}
}
/**
* Get workflow for a specific user and date
*/
getWorkflow(userId: string, date: string): DailyWorkflow | null {
const workflowId = `${userId}-${date}`;
return this.workflows.get(workflowId) || null;
}
/**
* Start a workflow
*/
async startWorkflow(workflowId: string): Promise<DailyWorkflow> {
const workflow = this.workflows.get(workflowId);
if (!workflow) {
throw new WorkflowError({
code: 'WORKFLOW_NOT_FOUND',
message: `Workflow ${workflowId} not found`,
timestamp: new Date(),
recoverable: false
});
}
workflow.workflowStatus = 'in_progress';
workflow.startedAt = new Date();
// Mark first task as in progress
if (workflow.tasks.length > 0) {
workflow.tasks[0].status = 'in_progress';
workflow.tasks[0].startedAt = new Date();
}
this.persistWorkflow(workflow);
return workflow;
}
/**
* Complete a specific task
*/
async completeTask(
workflowId: string,
taskId: string,
completionData?: Partial<TaskCompletionData>
): Promise<WorkflowProgress> {
const workflow = this.workflows.get(workflowId);
if (!workflow) {
throw new WorkflowError({
code: 'WORKFLOW_NOT_FOUND',
message: `Workflow ${workflowId} not found`,
timestamp: new Date(),
recoverable: false
});
}
const task = workflow.tasks.find(t => t.id === taskId);
if (!task) {
throw new WorkflowError({
code: 'TASK_NOT_FOUND',
message: `Task ${taskId} not found in workflow ${workflowId}`,
timestamp: new Date(),
recoverable: false
});
}
// Verify task completion
await taskCompletionVerifier.verifyTaskCompletion(task, {
userId: workflow.userId,
timestamp: new Date()
});
// Mark task as completed
task.status = 'completed';
task.completedAt = new Date();
// Calculate time spent
if (task.startedAt) {
const timeSpent = Math.round((task.completedAt.getTime() - task.startedAt.getTime()) / (1000 * 60));
workflow.actualTimeSpent += timeSpent;
}
// Update dependency manager
taskDependencyManager.updateTaskStatus(taskId, 'completed');
// Update workflow progress
workflow.completedTasks++;
// Check if workflow is complete
if (workflow.completedTasks === workflow.totalTasks) {
workflow.workflowStatus = 'completed';
workflow.completedAt = new Date();
}
// Auto-navigate to next task if enabled
if (this.config.autoNavigate) {
const nextTask = taskDependencyManager.getReadyTasks(workflow)[0];
if (nextTask) {
setTimeout(async () => {
try {
await taskNavigationService.navigateToTask(nextTask, workflow);
} catch (error) {
console.warn('Auto-navigation failed:', error);
}
}, 2000); // 2 second delay
}
}
this.persistWorkflow(workflow);
return this.getWorkflowProgress(workflowId);
}
/**
* Skip a task
*/
async skipTask(workflowId: string, taskId: string): Promise<WorkflowProgress> {
const workflow = this.workflows.get(workflowId);
if (!workflow) {
throw new WorkflowError({
code: 'WORKFLOW_NOT_FOUND',
message: `Workflow ${workflowId} not found`,
timestamp: new Date(),
recoverable: false
});
}
const task = workflow.tasks.find(t => t.id === taskId);
if (!task) {
throw new WorkflowError({
code: 'TASK_NOT_FOUND',
message: `Task ${taskId} not found in workflow ${workflowId}`,
timestamp: new Date(),
recoverable: false
});
}
task.status = 'skipped';
workflow.completedTasks++;
this.persistWorkflow(workflow);
return this.getWorkflowProgress(workflowId);
}
/**
* Get current workflow progress
*/
getWorkflowProgress(workflowId: string): WorkflowProgress {
const workflow = this.workflows.get(workflowId);
if (!workflow) {
throw new WorkflowError({
code: 'WORKFLOW_NOT_FOUND',
message: `Workflow ${workflowId} not found`,
timestamp: new Date(),
recoverable: false
});
}
const currentTask = workflow.tasks[workflow.currentTaskIndex];
const nextTask = workflow.tasks[workflow.currentTaskIndex + 1];
const remainingTasks = workflow.tasks.slice(workflow.currentTaskIndex + 1);
const estimatedTimeRemaining = remainingTasks.reduce((sum, task) => sum + task.estimatedTime, 0);
return {
completedTasks: workflow.completedTasks,
totalTasks: workflow.totalTasks,
completionPercentage: Math.round((workflow.completedTasks / workflow.totalTasks) * 100),
currentTask,
nextTask,
estimatedTimeRemaining,
actualTimeSpent: workflow.actualTimeSpent
};
}
/**
* Get navigation state for current workflow
*/
getNavigationState(workflowId: string): NavigationState {
const workflow = this.workflows.get(workflowId);
if (!workflow) {
throw new WorkflowError({
code: 'WORKFLOW_NOT_FOUND',
message: `Workflow ${workflowId} not found`,
timestamp: new Date(),
recoverable: false
});
}
const currentTask = workflow.tasks[workflow.currentTaskIndex];
const previousTask = workflow.currentTaskIndex > 0 ? workflow.tasks[workflow.currentTaskIndex - 1] : null;
const nextTask = workflow.currentTaskIndex < workflow.tasks.length - 1 ? workflow.tasks[workflow.currentTaskIndex + 1] : null;
return {
currentTask,
previousTask,
nextTask,
canGoBack: workflow.currentTaskIndex > 0,
canGoForward: workflow.currentTaskIndex < workflow.tasks.length - 1
};
}
/**
* Move to next task in workflow
*/
async moveToNextTask(workflowId: string): Promise<TodayTask | null> {
const workflow = this.workflows.get(workflowId);
if (!workflow) {
throw new WorkflowError({
code: 'WORKFLOW_NOT_FOUND',
message: `Workflow ${workflowId} not found`,
timestamp: new Date(),
recoverable: false
});
}
if (workflow.currentTaskIndex < workflow.tasks.length - 1) {
workflow.currentTaskIndex++;
const nextTask = workflow.tasks[workflow.currentTaskIndex];
// Mark next task as in progress
if (nextTask.status === 'pending') {
nextTask.status = 'in_progress';
nextTask.startedAt = new Date();
}
this.persistWorkflow(workflow);
return nextTask;
}
return null;
}
/**
* Generate tasks for a specific date (enhanced with dependency management)
*/
private async generateTasksForDate(
userId: string,
date: string,
context?: TaskGenerationContext
): Promise<TodayTask[]> {
// This is a placeholder implementation
// In Phase 3, this will be replaced with AI-powered task generation
const defaultTasks: TodayTask[] = [
{
id: `${userId}-${date}-plan-1`,
pillarId: 'plan',
title: 'Review content strategy',
description: 'Check and update your content strategy for the week',
status: 'pending',
priority: 'high',
estimatedTime: 15,
actionType: 'navigate',
actionUrl: '/content-planning-dashboard',
enabled: true,
icon: 'Business',
color: '#4CAF50'
},
{
id: `${userId}-${date}-plan-2`,
pillarId: 'plan',
title: 'Update content calendar',
description: 'Review and update your content calendar',
status: 'pending',
priority: 'medium',
estimatedTime: 10,
dependencies: [`${userId}-${date}-plan-1`],
actionType: 'navigate',
actionUrl: '/content-planning-dashboard',
enabled: true,
icon: 'CalendarMonth',
color: '#4CAF50'
},
{
id: `${userId}-${date}-generate-1`,
pillarId: 'generate',
title: 'Create social media content',
description: 'Generate content for your social media platforms',
status: 'pending',
priority: 'high',
estimatedTime: 30,
dependencies: [`${userId}-${date}-plan-1`, `${userId}-${date}-plan-2`],
actionType: 'navigate',
actionUrl: '/facebook-writer',
enabled: true,
icon: 'AutoAwesome',
color: '#2196F3'
},
{
id: `${userId}-${date}-generate-2`,
pillarId: 'generate',
title: 'Create blog content',
description: 'Write blog posts for your website',
status: 'pending',
priority: 'medium',
estimatedTime: 45,
dependencies: [`${userId}-${date}-plan-1`],
actionType: 'navigate',
actionUrl: '/blog-writer',
enabled: true,
icon: 'Article',
color: '#2196F3'
},
{
id: `${userId}-${date}-publish-1`,
pillarId: 'publish',
title: 'Publish social media content',
description: 'Publish your created content to social media',
status: 'pending',
priority: 'medium',
estimatedTime: 10,
dependencies: [`${userId}-${date}-generate-1`],
actionType: 'navigate',
actionUrl: '/facebook-writer',
enabled: true,
icon: 'Publish',
color: '#FF9800'
},
{
id: `${userId}-${date}-publish-2`,
pillarId: 'publish',
title: 'Publish blog content',
description: 'Publish blog posts to your website',
status: 'pending',
priority: 'medium',
estimatedTime: 15,
dependencies: [`${userId}-${date}-generate-2`],
actionType: 'navigate',
actionUrl: '/blog-writer',
enabled: true,
icon: 'Publish',
color: '#FF9800'
},
{
id: `${userId}-${date}-analyze-1`,
pillarId: 'analyze',
title: 'Review content performance',
description: 'Analyze performance of published content',
status: 'pending',
priority: 'low',
estimatedTime: 20,
dependencies: [`${userId}-${date}-publish-1`, `${userId}-${date}-publish-2`],
actionType: 'navigate',
actionUrl: '/analytics-dashboard',
enabled: true,
icon: 'Analytics',
color: '#9C27B0'
},
{
id: `${userId}-${date}-engage-1`,
pillarId: 'engage',
title: 'Respond to comments',
description: 'Engage with comments on your content',
status: 'pending',
priority: 'low',
estimatedTime: 15,
dependencies: [`${userId}-${date}-publish-1`],
actionType: 'navigate',
actionUrl: '/engagement-dashboard',
enabled: true,
icon: 'ChatBubbleOutline',
color: '#E91E63'
},
// Engage pillar tasks
{
id: `${userId}-${date}-engage-1`,
pillarId: 'engage',
title: 'Reply to blog comment',
description: 'Respond to comments on your latest blog post',
status: 'pending',
priority: 'high',
estimatedTime: 10,
dependencies: [`${userId}-${date}-analyze-1`],
actionType: 'navigate',
actionUrl: '/engagement-dashboard',
enabled: true,
icon: 'Comment',
color: '#E91E63'
},
{
id: `${userId}-${date}-engage-2`,
pillarId: 'engage',
title: 'Respond to Twitter mention',
description: 'Reply to Twitter mentions and engage with followers',
status: 'pending',
priority: 'medium',
estimatedTime: 5,
dependencies: [`${userId}-${date}-engage-1`],
actionType: 'navigate',
actionUrl: '/engagement-dashboard',
enabled: true,
icon: 'Twitter',
color: '#1DA1F2'
},
// Remarket pillar tasks
{
id: `${userId}-${date}-remarket-1`,
pillarId: 'remarket',
title: 'Launch Retargeting Campaign',
description: 'Create and launch targeted remarketing campaigns',
status: 'pending',
priority: 'high',
estimatedTime: 35,
dependencies: [`${userId}-${date}-engage-2`],
actionType: 'navigate',
actionUrl: '/remarketing-dashboard',
enabled: true,
icon: 'Psychology',
color: '#00695C'
},
{
id: `${userId}-${date}-remarket-2`,
pillarId: 'remarket',
title: 'Lead Nurturing Sequence',
description: 'Set up automated lead nurturing workflows',
status: 'pending',
priority: 'medium',
estimatedTime: 30,
dependencies: [`${userId}-${date}-remarket-1`],
actionType: 'navigate',
actionUrl: '/lead-nurturing',
enabled: true,
icon: 'Refresh',
color: '#4CAF50'
}
];
// Validate dependencies and get optimal execution order
const tempWorkflow: DailyWorkflow = {
id: `${userId}-${date}`,
date,
userId,
tasks: defaultTasks,
currentTaskIndex: 0,
completedTasks: 0,
totalTasks: defaultTasks.length,
workflowStatus: 'not_started',
totalEstimatedTime: defaultTasks.reduce((sum, task) => sum + task.estimatedTime, 0),
actualTimeSpent: 0
};
// Validate dependency graph
const validation = taskDependencyManager.validateDependencyGraph(tempWorkflow);
if (!validation.isValid) {
console.warn('Dependency validation failed:', validation.errors);
// Return tasks without dependencies if validation fails
return defaultTasks.map(task => ({ ...task, dependencies: [] }));
}
// Get optimal execution order
const orderedTasks = taskDependencyManager.getOptimalExecutionOrder(tempWorkflow);
return orderedTasks;
}
/**
* Persist workflow to localStorage
*/
private persistWorkflow(workflow: DailyWorkflow): void {
if (this.config.persistProgress) {
try {
localStorage.setItem(`workflow-${workflow.id}`, JSON.stringify(workflow));
} catch (error) {
console.warn('Failed to persist workflow:', error);
}
}
}
/**
* Load persisted workflows from localStorage
*/
private loadPersistedWorkflows(): void {
if (this.config.persistProgress) {
try {
const keys = Object.keys(localStorage).filter(key => key.startsWith('workflow-'));
keys.forEach(key => {
const workflowData = localStorage.getItem(key);
if (workflowData) {
try {
const workflow = JSON.parse(workflowData) as DailyWorkflow;
// Ensure workflow has required properties
if (!workflow.id || !workflow.date || !workflow.userId) {
console.warn(`Invalid workflow data for key ${key}, skipping`);
return;
}
// Ensure tasks array exists and is valid
if (!workflow.tasks || !Array.isArray(workflow.tasks)) {
console.warn(`Invalid tasks array for workflow ${workflow.id}, initializing empty array`);
workflow.tasks = [];
}
// Convert date strings back to Date objects
if (workflow.startedAt) workflow.startedAt = new Date(workflow.startedAt);
if (workflow.completedAt) workflow.completedAt = new Date(workflow.completedAt);
// Process tasks with null checks
workflow.tasks.forEach(task => {
if (task && typeof task === 'object') {
if (task.startedAt) task.startedAt = new Date(task.startedAt);
if (task.completedAt) task.completedAt = new Date(task.completedAt);
}
});
this.workflows.set(workflow.id, workflow);
} catch (parseError) {
console.warn(`Failed to parse workflow data for key ${key}:`, parseError);
// Remove corrupted data
localStorage.removeItem(key);
}
}
});
} catch (error) {
console.warn('Failed to load persisted workflows:', error);
}
}
}
/**
* Clear completed workflows (cleanup)
*/
clearCompletedWorkflows(): void {
const completedWorkflows = Array.from(this.workflows.values())
.filter(workflow => workflow.workflowStatus === 'completed');
completedWorkflows.forEach(workflow => {
this.workflows.delete(workflow.id);
if (this.config.persistProgress) {
localStorage.removeItem(`workflow-${workflow.id}`);
}
});
}
}
// Export singleton instance
export const taskWorkflowOrchestrator = new TaskWorkflowOrchestrator();
export default TaskWorkflowOrchestrator;