Skip to content

v2.20.0

Choose a tag to compare

@kiki830621 kiki830621 released this 15 Jul 22:45

Added

  • download_if_missing — opt-in best-effort fetch for a server-side-only attachment (#272). #238 taught save_attachment to detect a not-downloaded attachment (savable_reason:"not_downloaded") but the remedy stayed manual ("open the message in Mail"). This adds an opt-in boolean (default false, fully backward-compatible): when both tiers fail on the not_downloaded / generic -10000 path AND the caller set the flag, the tool nudges Mail to materialize the message — reading source of theMessage (the raw RFC 822; non-GUI, no viewer window), which on an IMAP account pulls the server-side content into the local store as a side effect — then poll-retries the save on a bounded budget (~30s, 2s interval). Best-effort and unverified by design (Phase-0 spike, direction of #238(a)): Mail's scripting dictionary exposes no per-attachment download verb (downloaded is read-only, the only command is save), so this relies on an undocumented, version-/account-dependent materialization side effect that may not fetch on every account (notably where the save simply errors). It fails honestly — a spent budget throws the not_downloaded guidance, never a false "saved" — and the gate is a strict conjunction (notDownloaded && code==-10000 && downloadIfMissing) so every non-opt-in caller's behavior is byte-identical. Detection stayed on the SQLite path; only the state-changing fetch trigger + retry use AppleScript (per r-must-direct-db). New AttachmentDownloadScriptBuilder (pure trigger-script builder + gate + DownloadRetryPolicy) and MailController.saveAttachmentRetryingForDownload; 13 unit tests (script text, gate, policy, deterministic retry loop via the injected script-runner seam). The live question — does source-read actually pull a not-downloaded attachment down? — is a pre-merge gate (no not-downloaded specimen was available to live-test; source is confirmed a valid message property via the sdef). Post-verify hardening (6-AI APPROVE, PR #273): the retry loop is bounded by a real wall-clock deadline (not just a sleep-count that ignored each save's ~1-2s IPC), aborts immediately with the honest cause on a definitive "not found" instead of polling the full budget, logs a fetch-trigger failure to stderr, and uses a precise hasPrefix success check; the flag is documented as a silent no-op on Exchange/EWS (no .emlx) and when the local index is unavailable.