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 documentationAFM Plug-In Collection

Project with added tasks
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; })();