feat: add opted-in iMessage connector data bridge

Merged
TE
2d ago
+1638-46

The Connect iMessage UI needs an opt-in data path that runs with cmux.app’s Full Disk Access. This adds the in-process imessage operation to the app’s existing Rust JSON channel and a thin native bridge for connection status, permission steps and conversation metadata. It does not add a send operation or change the connector UI yet.

Connect sets the declared shared cmux setting imessage.enabled to true; Disconnect sets it to false. Its default is false. The app and daemon read that one setting, with no second consent file. imessage.automationSkipped remembers the optional permission skip and grants neither reads nor sends; Disconnect clears it too. Both settings stay out of the Settings page and palette and cannot be changed by agents. Status and Connect never open chat.db; every thread read requires persisted opt-in and Full Disk Access. The response contains only titles, handles, group membership and last activity, in the reader’s newest-first order. The existing cmux-imessage crate is unchanged.

The Rust call runs inside cmux.app, off the main thread. The daemon launch contract and detached spawn let the daemon outlive its launcher, so its FDA attribution is not relied on. The in-app reader uses the permission of the process the person grants. This source inspection is not a Mac experiment proving daemon inheritance. Full Disk Access and Messages Automation reuse the existing probes and opener; the unchanged platform calls move to AgentPermissions, and the connector adapter lives in AgentPane. The app contains forwarding methods only. Permission and settings changes invalidate the connector through event streams. Connection changes and permission prompts require a person’s gesture. No daemon, peer, socket or MCP operation is added.

Archive and DEV availability

This is step A of the existing FFI release sequence (as in #31945). After this source lands, app-ffi-release.yml builds and publishes cmux-app-ffi-<merge SHA>. Its repin job opens or updates the app-ffi-repin PR with the matching URL/checksum and runs check-app-ffi-pin.sh --verify-release. That pin must land before a DEV build can exercise the new operation. The existing pin check is advisory during this transition. The FFI pin drift is the designed source-first release step; the pin check is also red on base, and the automatic release/repin must land before DEV validation. The area owner reviewed and accepted this release sequence. An older archive returns an unknown-operation error; the Swift adapter returns unavailable safely. The UI PR will display that state.

Request

Leo’s words:

First PR: iMessage on by default. If access is missing, one "Grant Full Disk Access" step opens the right System Settings pane. Each outgoing send still needs your click.

well i mean like some sort of connect imessage type thing...

wait... does this mean chief can be in your imessage now...?

ya i mean arent there already things that ask for it? if u wanna integrate imessage, hey bud that's full disk access, end of story. it's an obvious opt in for a specific reason. add ur contacts, ur group chats, etc

(Leo, 2026-11-03 03:46Z)

The follow-on UI makes the connector visible by default on macOS. Connect remains the opt-in. Connectors stays behind the settled one-click Show more reveal (design/session-info.md); an unconnected iMessage row opens with that section so Connect needs no additional disclosure click.

Testing

Passed: fleet app compile fbdb49a471d4c1ce5be01b4c at 91548894ccf921ffc396a916e3765ec33911a203 (71 s), the standalone Rust FFI suite (run 41590112278), eight license-gate unit tests, release-input coverage, formatting and static architecture checks. Native AgentPaneIMessageTests and CloudStartCoreTests passed; the latter covers large Unicode JSON at three starting buffer capacities and unknown-operation failure.

Canonical settings export step 80ec2d0de419e61fbb47d448 regenerated the schema and MDM files. Both export filters passed every named test (4/4 and 6/6), but their XCTest subprocesses aborted with signal 6. A focused comparison at exact base 357d9514b9142b2202768f2b0d48e1399e70c9b4, step 2f614f7c8c01aedb19be57c3, reproduced the same abort. Base also reports its pre-existing stale inactive-tab-width placeholder, 48 in the schema versus 72 in source; this canonical export includes that correction. The bot-owned defaults snapshot is excluded. Head native step 08ea2dfd56c188b46e9ccde5 likewise ran all three IMessageSettingTests cases successfully, but returned the same XCTest abort. These overall native runs are not recorded as green. The test-host abort is tracked in #33262.

The final correction makes the shared permission access helper an instantiable Sendable value, with the platform bodies unchanged. The scoped package conventions check and pre-push rules pass. A base merge preserves both copies of the existing 48→72 schema correction and clears the autofix stale-input check in a simulated merge snapshot. The defaults guard requires the independent review receipt for declaration fingerprints; it does not read the bot-owned defaults snapshot.

At reviewed head 9196aca3, the inventory test InventoryContractTests.test_event_stream_drift_is_rejected fails identically on base bc55b1e3 (head run 41596037492, base run 41599644250; all 32 tests complete). The nightly shell guard also has the identical build_only failure on that base (head run 41596037828, base run 41599646262). The new operation still requires the source-first FFI release and repin described above; its pin drift is an explicit dependency of this PR. The app compile passed at published head c93621f0306d6f953dedb1bf1c3513003b44b80f (job 2d6cc10744ba9c1a0bdb9259, 64 s). The FFI suite passed at that head in run 41606808754 (exit 0; 3.843 s command; broker validated snapshot parent and tree).

At 594e1f8d9c85f74551d89e538546f53ef9247bf6, the Rust FFI suite passed in run 41631501343. App compile receipt 039f878ddb9fdfa2d01a48e1 failed on the unchanged base GroupJoinHold.swift public Observation import; merged #33680 fixes that exact error, and the foundation merges cleanly with the base carrying it. No DEV/TCC result is claimed yet. The full crate-license check reports an existing cmux-motion allowlist omission; no third-party license policy is changed here.

The settings declaration, generated exports, native permission adapter and Rust reducer stay together so the app and daemon share one opt-in from the first integration. Localization and the new SQLite dependency lock entries account for much of the diff; the UI is a separate PR.

Next

The separate connector UI PR supplies the walkthrough, contacts/group list and gallery states. After that lands and the DEV flow works, a Chief-only Rust MCP server will expose list_threads, read_thread, search and draft_reply. Drafting never sends. A person clicks a Send card before cmux_imessage::send::send runs. It is registered only while connected. That Chief PR needs independent review, failing tests compared by name against base, a fleet fresh-home launch and the area owner requested as reviewer.

Changelog

none

🤖 Generated with Codex

Agent session: agent session