OmniFocus: No proper state of Availability in Automation

Hey there,

Maybe I took a wrong turn somewhere in discourse, slack or docs, but I’m really missing a proper way to check if a task is available in OmniFocus’ automation model.
The taskStatus enum mixes due info and blocking, so it doesn’t reflect what the UI shows as available.

I ended up writing a plugin to recreate the availability logic for automation.
Would love to see something like available == true. Just like how it works in perspective rules, alongside flagged == true. ❤️

/*{
    "author": "Teiki",
    "targets": ["omnifocus"],
    "type": "action",
    "identifier": "com.example.listavailability",
    "version": "0.1.0",
    "description": "List available tasks",
    "label": "List Available Tasks",
    "mediumLabel": "List Available",
    "longLabel": "List Available Tasks",
    "paletteLabel": "List Available"
}*/
(() => {
    var action = new PlugIn.Action(function() {
        console.log("=== Available Tasks (not dropped, not blocked, not completed) ===\n");

        // Get all tasks and filter
        var availableTasks = flattenedTasks.filter(function(task) {
            // Skip if this is a project's root task
            if (task.project && task === task.project.task) {
                return false;
            }
            
            var status = String(task.taskStatus);
            return !status.includes("Completed") && 
                   !status.includes("Dropped") &&
                   !status.includes("Blocked");
        });
        
        console.log("Found " + availableTasks.length + " available tasks:\n");
        
        // Filter for truly available tasks (no on-hold tags, not blocked by sequence)
        var filteredTasks = [];

        // First pass to determine which tasks to include
        availableTasks.forEach(function(task) {
            // Check if task has any on-hold tag
            var hasOnHoldTag = task.tags.some(function(tag) {
                return String(tag.status).includes("OnHold");
            });
            
            // Recursive function to check if blocked by sequential logic
            function isBlockedBySequentialLogic(item) {
                // Base case: no parent means not blocked
                if (!item.parent) {
                    return false;
                }
                
                // First check if the parent itself is blocked (go up the chain first)
                if (item.parent && item.parent.parent) {
                    if (isBlockedBySequentialLogic(item.parent)) {
                        return true;
                    }
                }
                
                // Now check if THIS item is blocked at its level
                // Only matters if the parent is sequential
                if (item.parent.sequential) {
                    var siblings = item.parent.children;
                    var itemIndex = siblings.indexOf(item);
                    
                    // Check if any prior sibling is incomplete
                    for (var i = 0; i < itemIndex; i++) {
                        var sibling = siblings[i];
                        var siblingStatus = String(sibling.taskStatus);
                        if (!siblingStatus.includes("Completed") && !siblingStatus.includes("Dropped")) {
                            return true;
                        }
                    }
                }
                
                return false;
            }
            
            var blockedBySequence = isBlockedBySequentialLogic(task);
            
            // Check if task has incomplete children
            var hasIncompleteChildren = false;
            if (task.hasChildren && task.children) {
                hasIncompleteChildren = task.children.some(function(child) {
                    var childStatus = String(child.taskStatus);
                    return !childStatus.includes("Completed") && !childStatus.includes("Dropped");
                });
            }
            
            // Only include tasks that are not on hold, not blocked by sequence, and don't have incomplete children
            if (!hasOnHoldTag && !blockedBySequence && !hasIncompleteChildren) {
                filteredTasks.push({
                    task: task,
                    hasOnHoldTag: hasOnHoldTag,
                    isPartOfSequence: task.parent && task.parent.sequential,
                    blockedBySequence: blockedBySequence
                });
            }
        });
        
        console.log("After filtering (no on-hold tags, not blocked by sequence): " + filteredTasks.length + " tasks\n");
        
        // Sort by creation date descending (newest first)
        filteredTasks.sort(function(a, b) {
            var dateA = a.task.added || new Date(0);
            var dateB = b.task.added || new Date(0);
            return dateB.getTime() - dateA.getTime();
        });
        
        // Print the filtered tasks
        filteredTasks.forEach(function(item, index) {
            var task = item.task;
            var isPartOfSequence = item.isPartOfSequence;
            
            console.log((index + 1) + ". " + task.name + " ; hasOnHoldTag: " + item.hasOnHoldTag + " ; isPartOfSequence: " + isPartOfSequence + " ; blockedBySequence: " + item.blockedBySequence);
        });
    });

    action.validate = function() {
        return true;
    };

    return action;
})();