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