Rune's log capture proved the theory wrong: AEDeterminePermissionToAutomate-
Target(askUserIfNeeded: true) returned -1743 in ~9ms — far too fast for any
real dialog to have been shown and answered — and the very next passive
status check still reported -1744 (not yet determined), meaning the OS
never actually recorded a decision despite the "denied" return. That's the
pre-flight API misbehaving, not a threading issue (both previous fixes
addressed threading and neither helped).
requestAccess() now attempts a real, harmless Apple Event (listAccounts)
via the same runHandler path mail_list_accounts/testConnection() already
use — sending an actual Apple Event is the standard, proven mechanism for
triggering macOS's first-time Automation consent prompt, and this path is
already confirmed working (it's what correctly reported "not authorized"
in the first live test).
Previous fix assumed being MainActor-isolated inside an async method was
equivalent to a classic synchronous AppKit call — it wasn't; Rune
confirmed live it still did nothing (no dialog, no state change, even
after a clean tccutil reset). AEDeterminePermissionToAutomateTarget is a
blocking, modal-dialog-presenting legacy API and needs to run from a
genuine DispatchQueue.main.async dispatch to correctly nest its own run
loop, not from inside a suspended Task continuation frame. Added extra
Log.mail checkpoints (call received / about to call AE / result) so the
next attempt has real data to diagnose from either way.
requestAccess() was dispatched onto the background queue used for
AppleScript execution, on the assumption that a blocking call needed to
be off-main. That's backwards for AEDeterminePermissionToAutomateTarget's
consent dialog — like this app's existing NSAlert.runModal() calls, it
needs to run on the main thread/run loop to actually display. Off-main,
it silently returned without ever showing a prompt or changing state.
Also added Log.mail diagnostics on both the status-only check and the
prompting call so a future report of "still doesn't work" has an actual
status code to look at instead of starting from scratch.
Settings previously only surfaced an error message after a failed Test
Connection attempt — there was no way to trigger the actual macOS
Automation permission dialog or see live grant/deny status, unlike
Calendar/Contacts/Reminders/Location.
AEDeterminePermissionToAutomateTarget (askUserIfNeeded: false/true) turns
out to provide exactly that: a non-prompting status check and an explicit
prompt-and-wait call, mirroring EKEventStore.authorizationStatus(for:)/
requestFullAccessToEvents(). Mail's Settings row now uses the same
personalDataRow component as Calendar/Contacts — live status badge plus
a real "Request Access" button — instead of a one-off Test-Connection-only
UI. Test Connection stays as a secondary functional check.
appleScriptError carried the raw NSDictionary from NSAppleScript's error
out-param, which isn't Sendable and got flagged once the type crossed an
async boundary. Extract just the two fields actually used (errorNumber,
errorMessage) into plain Int/String instead.
Lets the AI search Apple Mail, read a message, and save an attachment to
disk (e.g. to hand off to paperless_upload_document) — e.g. "find the
receipt from Elkjøp and add it to Paperless." Talks to Mail.app via
AppleScript/Apple Events rather than parsing its private on-disk store,
so no Full Disk Access or MIME parsing is needed; Mail's own attachment
save handles all decoding. Every mail_* tool call requires approval
(Deny/Allow Once/Allow for Session), mirroring the bash_execute and
Personal Data gate pattern.
Also fixes a pre-existing bug found while touching the adjacent
tool-activation condition: paperlessEnabled was missing from it, so
Paperless tools could fail to activate unless another integration was
also active.