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.contentnode = contentTree.nodeForObject(object)node.expandNote(true)}function saveAnyEdits(selection){previousSelection = selection.databaseObjectsselection.window.selectObjects([])selection.window.selectObjects(previousSelection)}const action = new PlugIn.Action(async function(selection, sender){try {saveAnyEdits(selection)tempTask = nullselectedItem = selection.databaseObjects[0]objectType = selectedItem.constructor.name// CHECK FOR NOTEif(selectedItem.note.length === 0){plugInTitle = action.labelthrow {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-TASKSshouldReplace = falseif(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 PROMPTitemProperties = {"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 TASKtempTask = new Task("Thinking…", selectedItem.ending)// QUERY THE ALMschema = LanguageModel.Schema.fromJSON(schemaJSON)session = new LanguageModel.Session()responseJSON = await session.respondWithSchema(prompt, schema)response = JSON.parse(responseJSON)console.log(JSON.stringify(response))// PROCESS RESPONSEdeleteObject(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 USERtaskCount = response.lengthtaskTasks = (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;})();