From c383c881e564f954416df6de249f68a52994bcb9 Mon Sep 17 00:00:00 2001 From: mrdulasolutions Date: Wed, 13 May 2026 17:19:14 -0400 Subject: [PATCH 1/2] fix(sidecar/prepare-node): ad-hoc sign bundled node so `tauri dev` works MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `npm run tauri:dev` was unusable on macOS 14.4+ (and stricter on 15+): the bundled aos-mail-node binary spawned and was immediately SIGKILL'd by `amfid` (Apple Mobile File Integrity), so the sidecar died on launch with `WARN sidecar terminated: signal: Some(9)`. Tauri's shell plugin then logged `sidecar stdin write failed: Broken pipe` and the app sat broken at the splash. Root cause: prepare-node-binary.mjs explicitly strips the Node Foundation signature (`codesign --remove-signature`) with a comment that "Tauri will re-sign with our Developer ID at bundle time." That's true for `tauri build` (via tauri-action's keychain dance), but in `tauri dev` nothing re-signs — the binary stays unsigned, and modern macOS refuses to execute unsigned arm64 Mach-O binaries. Fix: after the strip, add a `codesign --sign - --force` ad-hoc signing step. Ad-hoc signatures satisfy `amfid` for local execution and don't interfere with tauri-action's later `codesign --force` with the real Developer ID cert in production builds — `--force` replaces whatever signature is there. Verified locally: `aos-mail-node --version` now returns v24.14.0 (exit 0) instead of dying with exit 137. The running `tauri dev` session picked up the new binary on its next file-change rebuild and the sidecar booted cleanly: `sidecar: ready, listening on stdin`. Co-Authored-By: Claude Opus 4.7 --- sidecar/scripts/prepare-node-binary.mjs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/sidecar/scripts/prepare-node-binary.mjs b/sidecar/scripts/prepare-node-binary.mjs index 9c45cbc..a59f935 100644 --- a/sidecar/scripts/prepare-node-binary.mjs +++ b/sidecar/scripts/prepare-node-binary.mjs @@ -109,13 +109,21 @@ function main() { copyFileSync(sysNode, dest); } - // Strip whatever signature came from Node Foundation; Tauri will re-sign - // with our Developer ID at bundle time. + // Strip Node Foundation's signature, then ad-hoc sign so the binary can + // actually execute. Background: on macOS 14.4+ (and 15+ more strictly), + // `amfid` SIGKILLs unsigned arm64 Mach-O binaries the moment they exec. + // The original Node Foundation signature would work for execution but + // doesn't match our final .app's signing chain, so production builds + // need it stripped. Replacing it with an ad-hoc signature (`--sign -`) + // is the only way to have the binary be both executable *now* (for + // `tauri dev`) and clean-slate for tauri-action's later `codesign + // --force` re-sign with the Developer ID cert. try { execSync(`codesign --remove-signature "${dest}"`, { stdio: "ignore" }); } catch { // already unsigned — fine } + execSync(`codesign --sign - --force --timestamp=none "${dest}"`, { stdio: "ignore" }); execSync(`chmod +x "${dest}"`); const size = (statSync(dest).size / 1024 / 1024).toFixed(1); From 1d73cc2e8c17c21628f437b312b4e4d81aee31e3 Mon Sep 17 00:00:00 2001 From: mrdulasolutions Date: Wed, 13 May 2026 17:40:25 -0400 Subject: [PATCH 2/2] feat(settings): consolidate AI Models picker into Agent Tools MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User asked to move the AI Models section from General to "under the authentication for anthropic in agent tools." Doing so deduplicates a problem that had been growing: General had its own 8-feature model picker, Agent Tools had a 6-feature one (analysis/drafts/refinement/ summary/archiveReady/senderLookup), and changing the same feature in both wrote the same modelConfig key with whichever value got saved last — confusing. Single picker now lives in Agent Tools → AI Models card, directly below the Authentication card that holds the Anthropic API key. Covers all 9 features: analysis, drafts, refinement, calendaring (new in this card), summary, archiveReady, senderLookup, agentDrafter (new), agentChat (new). The General tab's AI Models section is removed. A placeholder comment is left so a future reader doesn't reintroduce it. The Updates card immediately follows, so the General tab now flows straight from Notifications → Updates. Co-Authored-By: Claude Opus 4.7 --- src/renderer/components/SettingsPanel.tsx | 153 +++------------------- 1 file changed, 17 insertions(+), 136 deletions(-) diff --git a/src/renderer/components/SettingsPanel.tsx b/src/renderer/components/SettingsPanel.tsx index 4158374..c6e14e0 100644 --- a/src/renderer/components/SettingsPanel.tsx +++ b/src/renderer/components/SettingsPanel.tsx @@ -1325,142 +1325,8 @@ export function SettingsPanel({ onClose, initialTab }: SettingsPanelProps) { )} - {/* AI Models */} -
-
-

AI Models

-

- Choose which Claude model to use for each feature. Haiku is fastest and - cheapest, Opus is most capable. -

-
-
- {[ - { - key: "analysis" as const, - label: "Email Analysis", - description: "Triaging which emails need replies", - }, - { - key: "drafts" as const, - label: "Draft Generation", - description: "Writing reply drafts", - }, - { - key: "refinement" as const, - label: "Draft Refinement", - description: "Improving drafts based on feedback", - }, - { - key: "calendaring" as const, - label: "Scheduling Detection", - description: "Identifying calendar-related emails", - }, - { - key: "archiveReady" as const, - label: "Archive-Ready Analysis", - description: "Detecting completed conversations", - }, - { - key: "senderLookup" as const, - label: "Sender Lookup", - description: "Web search for sender info", - anthropicOnly: true, - }, - { - key: "agentDrafter" as const, - label: "Agent Drafter", - description: "Background auto-draft generation", - }, - { - key: "agentChat" as const, - label: "Agent Chat", - description: "Interactive agent sidebar conversations", - }, - ].map(({ key, label, description, ...rest }) => { - // Mirror the Agents tab picker shape: full Anthropic - // model list + OpenRouter free models, auto-save on - // change. anthropicOnly features (Sender Lookup uses - // Anthropic web_search; no OpenRouter equivalent) keep - // the Anthropic-only chip and hide the OpenRouter - // optgroup. The sidecar's resolveModelFor() accepts - // both legacy tier names ("haiku") and concrete model - // ids ("claude-haiku-4-5-…", "deepseek/…:free"), so - // either value space round-trips correctly. - const anthropicOnly = - "anthropicOnly" in rest ? rest.anthropicOnly : false; - const isNonClaude = - anthropicOnly && !modelConfig[key].startsWith("claude-"); - return ( -
-
-

- {label} - {anthropicOnly && ( - - Anthropic-only - - )} -

-

- {description} -

- {isNonClaude && ( -

- Configured model is not a Claude model — this feature will fail - until a Claude model is selected. -

- )} -
- -
- ); - })} -
-

- Picking a non-Anthropic model routes that feature through OpenRouter — make - sure an OpenRouter API key is configured in Agent Tools → AI Models, otherwise - the call will surface a clear “OpenRouter API key required” error. - Sender Lookup is the exception: it uses Anthropic’s web_search tool, - which has no OpenRouter equivalent, so the picker only offers Claude models - for that feature. -

-
+ {/* AI Models lives in Agent Tools → AI Models now — single + source of truth, directly under the Anthropic auth section. */} {/* Updates */}
@@ -2292,6 +2158,11 @@ export function SettingsPanel({ onClose, initialTab }: SettingsPanelProps) { description: "Iterating on a draft from your feedback (falls back to Draft Generation)", }, + { + key: "calendaring" as const, + label: "Scheduling Detection", + description: "Identifying calendar-related emails", + }, { key: "summary" as const, label: "Thread Summary", @@ -2308,6 +2179,16 @@ export function SettingsPanel({ onClose, initialTab }: SettingsPanelProps) { description: "Web-search-backed sender profiles", anthropicOnly: true, }, + { + key: "agentDrafter" as const, + label: "Agent Drafter", + description: "Background auto-draft generation", + }, + { + key: "agentChat" as const, + label: "Agent Chat", + description: "Interactive agent sidebar conversations", + }, ] as const ).map(({ key, label, description, ...rest }) => { const anthropicOnly = "anthropicOnly" in rest ? rest.anthropicOnly : false;