Plug-In: AFM • Intelligent Assist
REQUIRES: macOS 26, iOS 26, iPados 26, visionOS 26 • OmniFocus 4.8
This plug-in uses the on-device Apple Foundation Models to generate sub-tasks for the selected OmniFocus task or project. The selected item’s title and note serve as the prompt for the AFM.
Related Links: AFM and Omni Automation documentation • AFM Plug-In Collection

AFM • Intelligent Assist
/*{
"type": "action",
"targets": ["omnifocus"],
"author": "Otto Automator",
"identifier": "com.omni-automation.of.alm-assist",
"version": "1.3",
"description": "This plug-in uses the on-device Apple Foundation Model to generate sub-tasks for the selected OmniFocus task or project. The selected item’s title and note serve as the prompt for the AFM.",
"label": "AFM • Intelligent Assist",
"shortLabel": "AI Assist",
"paletteLabel": "AI Assist",
"image": "apple.intelligence"
}*/
(() => {
const schemaJSON = {
arrayOf: {
name: "task-schema",
properties: [
{
name: "name",
isOptional: false
},
{
name: "note",
isOptional: false
}
]
}
}
function expandNotes(object, window){
contentTree = window.content
node = contentTree.nodeForObject(object)
node.expandNote(true)
}
function saveAnyEdits(selection){
previousSelection = selection.databaseObjects
selection.window.selectObjects([])
selection.window.selectObjects(previousSelection)
}
const action = new PlugIn.Action(async function(selection, sender){
try {
saveAnyEdits(selection)
tempTask = null
selectedItem = selection.databaseObjects[0]
objectType = selectedItem.constructor.name
// CHECK FOR NOTE
if(selectedItem.note.length === 0){
plugInTitle = action.label
throw {
name: "Missing Note",
message: `The “${plugInTitle}” plug-in uses the note of the selected ${objectType} as a prompt for the on-device Apple Foundation Model. The selected ${objectType} has no note.`
}
}
// CHECK FOR EXISTING SUB-TASKS
shouldReplace = false
if(selectedItem.hasChildren > 0){
msg = `This ${objectType} has tasks. Should they be replaced or appended?`
alert = new Alert("Existing Items", msg)
alert.addOption("Replace")
alert.addOption("Append")
alert.addOption("Cancel")
buttonIndex = await alert.show()
if(buttonIndex === 0){
shouldReplace = true
} else if (buttonIndex === 2){
return "-128"
}
}
// CONSTRUCT PROMPT
itemProperties = {"task": selectedItem.name, "description": selectedItem.note}
prompt = "You're a GTD expert helping someone plan out a project in OmniFocus. Provide a list of steps for the following JSON task: " + JSON.stringify(itemProperties)
console.log("Prompt:", prompt)
console.log("Schema:", JSON.stringify(schemaJSON))
// ADD PLACEHOLDER TASK
tempTask = new Task("Thinking…", selectedItem.ending)
// QUERY THE ALM
schema = LanguageModel.Schema.fromJSON(schemaJSON)
session = new LanguageModel.Session()
responseJSON = await session.respondWithSchema(prompt, schema)
response = JSON.parse(responseJSON)
console.log(JSON.stringify(response))
// PROCESS RESPONSE
deleteObject(tempTask)
if(shouldReplace){
for (item of selectedItem.children){deleteObject(item)}
}
for (responseObj of response){
newTask = new Task(responseObj.name, selectedItem.ending)
newTask.note = responseObj.note
}
expandNotes(selectedItem, selection.window)
// ALERT USER
taskCount = response.length
taskTasks = (taskCount === 1) ? "task":"tasks"
message = `${taskCount} ${taskTasks} added to the selected ${objectType}.`
new Alert("Assist Completion", message).show()
}
catch(err){
if(tempTask){deleteObject(tempTask)}
if(!err.causedByUserCancelling || err.message !== "-128"){
console.error(err.name, err.message)
new Alert(err.name, err.message).show()
}
}
});
action.validate = function(selection, sender){
minOS26 = Device.current.operatingSystemVersion.atLeast(new Version("26"))
if(!minOS26){return false}
return (
selection.databaseObjects.length === 1 &&
selection.projects.length === 1 ||
selection.tasks.length === 1
)
};
return action;
})();