Skip to content

parity(launch_app): port Swift #1492 bundle-ID + locale fallbacks to Rust - #1523

Open
f-trycua wants to merge 2 commits into
mainfrom
parity/launch-app-bundle-id-locale-fallbacks
Open

parity(launch_app): port Swift #1492 bundle-ID + locale fallbacks to Rust#1523
f-trycua wants to merge 2 commits into
mainfrom
parity/launch-app-bundle-id-locale-fallbacks

Conversation

@f-trycua

@f-trycua f-trycua commented May 16, 2026

Copy link
Copy Markdown
Collaborator

Summary

Ports Dylan's just-merged Swift fix from PR #1492 (3b5c372d) to the Rust port. launch_app on macOS now accepts the same three input shapes the Swift binary does — bundle identifiers as name, locale-specific display names, and case-insensitive variants — not just the exact on-disk bundle filename.

The Rust resolver apps::locate_app_by_name mirrors Swift AppLauncher.locate(bundleId:name:) with the same three-pass chain:

  1. Filesystem <name>.app lookup in the canonical roots (/Applications, /System/Applications[/Utilities], ~/Applications, ~/Applications/Chrome Apps.localized). Fast, locale-independent.
  2. LaunchServices bundle-ID lookup via NSWorkspace.URLForApplicationWithBundleIdentifier: (objc2). Lets name="com.apple.calculator" resolve without switching to the bundle_id parameter.
  3. Fuzzy scan, case-insensitive:
    • Locale-aware localizedName from NSRunningApplication (covers e.g. 計算機 on JP macOS for Calculator).
    • CFBundleDisplayNameCFBundleName from each candidate bundle's Info.plist (via plutil -extract, matching the existing scan_installed_apps pattern in this file).
    • Bundle URL stem (filename minus .app).

When a match resolves to a bundle ID, launch_app_by_name and launch_with_urls_by_name delegate to the bundle-ID launch path (open -g -b) — unambiguous, avoids a second LaunchServices lookup inside open. Falls back to open <path> when no bundle ID is recovered, and finally to a raw open -g -a <name> if the resolver misses (preserves the pre-fix behavior for unusual installs LaunchServices already knows about).

Scope

Only crates/platform-macos/ and the parity test/doc files. Linux and Windows backends are untouched — bundle ID / locale display name is a macOS concept (the Windows port already documents name as a path/bundle alias).

Before / After

# Before this PR (Rust binary)
launch_app name="com.apple.calculator" → "Could not locate app"
launch_app name="CALCULATOR"           → "Could not locate app"

# After this PR (Rust binary)
launch_app name="com.apple.calculator" → pid=…, bundle_id=com.apple.calculator
launch_app name="CALCULATOR"           → pid=…, bundle_id=com.apple.calculator
launch_app name="Calculator"           → pid=…, (regression guard)
launch_app name="no_such_app_xyzzy"    → MCP error

Files touched

  • libs/cua-driver-rs/crates/platform-macos/src/apps.rs — new locate_app_by_name + ResolvedApp and supporting helpers (url_for_application_with_bundle_identifier, find_running_app_by_localized_name, read_bundle_metadata, app_search_roots).
  • libs/cua-driver-rs/crates/platform-macos/src/tools/launch_app.rslaunch_with_urls_by_name now runs the resolver first; raw open -g -a remains as a fallback.
  • libs/cua-driver-rs/tests/integration/test_api_parity.py — three new parametrized parity tests:
    • test_mcp_launch_app_by_name_accepts_bundle_id
    • test_mcp_launch_app_by_name_case_insensitive
    • test_mcp_launch_app_unknown_name_raises_error
  • libs/cua-driver-rs/PARITY.mdlaunch_app macOS row flipped from OPENVERIFIED with cross-reference to Swift AppLauncher.locate (PR cua-driver: fix #1481 app name resolution — bundle ID + locale fallbacks #1492). Notes block ported.
  • libs/cua-driver-rs/Cargo.lock — workspace version bumps (cua-driver 0.1.2→0.1.3, cursor-overlay, mcp-server, focus-monitor-win) refreshed during build; matches the version numbers already committed in Cargo.toml files since commit c1731734.

Test plan

  • cargo build -p platform-macos --release clean (no new warnings).
  • cargo build --release (full workspace) clean.
  • All 5 launch_app parity tests pass on the Rust binary:
    • test_mcp_launch_app_by_bundle_id
    • test_mcp_launch_app_by_name
    • test_mcp_launch_app_by_name_accepts_bundle_id (new)
    • test_mcp_launch_app_by_name_case_insensitive (new)
    • test_mcp_launch_app_unknown_name_raises_error (new)
  • Full Rust parity suite still green (114/114).
  • Re-run the new tests against a freshly-built Swift binary (local Swift binary at ~/cua/libs/cua-driver/.build/release/cua-driver is from before 3b5c372d, so test_mcp_launch_app_by_name_accepts_bundle_id currently fails on it pre-rebuild — expected, the Swift fix already landed on main).
  • CI to run both binaries via ./run_tests.sh --parity once Swift binary in CI cache rebuilds against 3b5c372d.

References

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Enhanced macOS application launching with improved name resolution for better app discovery and reliability
  • Documentation

    • Updated platform compatibility documentation for the application launcher
  • Tests

    • Added integration tests covering app name resolution, case-insensitive matching, and error handling

Review Change Stack

…Rust

Mirror `AppLauncher.locate(bundleId:name:)` from PR #1492 in the
Rust port so `launch_app` on macOS accepts bundle identifiers as `name`,
locale-specific display names, and case-insensitive variants — not just
the exact on-disk bundle filename.

The new `apps::locate_app_by_name` runs the same three passes as Swift:

  1. Filesystem `<name>.app` lookup in the canonical roots.
  2. LaunchServices bundle-ID lookup via
     `NSWorkspace.URLForApplicationWithBundleIdentifier:` (objc2).
  3. Fuzzy scan, case-insensitive: locale-aware `localizedName` from
     `NSRunningApplication`, then `CFBundleDisplayName` /
     `CFBundleName` / stem from each candidate bundle's Info.plist.

When a match resolves to a bundle ID, `launch_app_by_name` and
`launch_with_urls_by_name` now delegate to the bundle-ID launch path
(unambiguous, avoids a second LaunchServices lookup inside `open`).
Resolver miss falls back to the previous raw `open -g -a <name>`
behavior to preserve existing inputs LaunchServices already knows
about. Linux and Windows backends are untouched — bundle ID / locale
display name is a macOS concept.

Verified end-to-end against the Rust binary:
  launch_app name="com.apple.calculator" → bundle_id=com.apple.calculator
  launch_app name="CALCULATOR"           → bundle_id=com.apple.calculator
  launch_app name="Calculator"           → unchanged (regression guard)
  launch_app name="no_such_app_xyzzy"    → MCP error

Adds three parametrized parity tests
(`test_mcp_launch_app_by_name_accepts_bundle_id`,
`test_mcp_launch_app_by_name_case_insensitive`,
`test_mcp_launch_app_unknown_name_raises_error`) and flips the
`launch_app` macOS row in PARITY.md to VERIFIED with a cross-reference
to the Swift implementation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented May 16, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Ignored Ignored Preview May 17, 2026 11:10pm

Request Review

@coderabbitai

coderabbitai Bot commented May 16, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 094db121-5897-4779-b91f-a3ea0233569c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR enhances macOS launch_app by implementing a three-pass name resolver that accepts bundle IDs, performs case-insensitive matching, and integrates it into the launcher functions. The changes include a new app resolver, launcher refactors, integration tests, and parity documentation updates.

Changes

macOS launch_app name resolution with three-pass resolver

Layer / File(s) Summary
App name resolver contract and implementation
libs/cua-driver-rs/crates/platform-macos/src/apps.rs
Introduces ResolvedApp struct and locate_app_by_name() function implementing a three-pass, case-insensitive strategy: filesystem .app lookup, LaunchServices bundle-ID lookup via NSWorkspace, then fuzzy matching using running-app localized names and Info.plist metadata. Adds helpers for search roots, plutil plist extraction, and localized-name matching.
Launcher integration with resolver
libs/cua-driver-rs/crates/platform-macos/src/apps.rs, libs/cua-driver-rs/crates/platform-macos/src/tools/launch_app.rs
Refactors launch_app_by_name() to resolve the name first, preferring bundle-ID launch when available, otherwise launching via resolved path and re-identifying the running process. Updates launch_with_urls_by_name() to resolve the app name and delegate appropriately, with improved error messaging.
Integration tests for name-resolution fallbacks
libs/cua-driver-rs/tests/integration/test_api_parity.py
Adds three MCP integration tests verifying that launch_app accepts bundle-ID-shaped names, matches case-insensitively, and raises an error when name matches no installed app across all resolver passes.
Parity audit documentation
libs/cua-driver-rs/PARITY.md
Documents launch_app parity status with cross-platform tool mapping, marks macOS as VERIFIED, describes the three-pass name resolution strategy with fallback order, and lists verified test cases covering bundle-ID names, case-insensitive matching, and error behavior.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • trycua/cua#1492: Both PRs implement the same macOS app name resolution change—a multi-pass resolver that accepts bundle IDs and does locale/case-insensitive fallbacks—so this Rust locate_app_by_name implementation corresponds directly to the retrieved PR's Swift AppLauncher.locate(bundleId:name:) behavior and related tests.

Poem

🐰 A rabbit's app resolver hops through three passes,
First filesystem, then LaunchServices caches,
Fuzzy matching localized names as it dashes—
Bundle IDs or paths, whatever app it catches! 📱✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'parity(launch_app): port Swift #1492 bundle-ID + locale fallbacks to Rust' accurately and concisely describes the main change: porting a Swift feature with bundle-ID and locale fallback support to the Rust macOS backend for launch_app.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch parity/launch-app-bundle-id-locale-fallbacks

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@libs/cua-driver-rs/crates/platform-macos/src/apps.rs`:
- Around line 127-129: The equality check allows matching when both
app.bundle_id and resolved.bundle_id are None, causing false PID matches; update
the condition so bundle IDs only count as equal when both are Some and equal.
Replace the current bundle-id comparison (app.bundle_id.as_deref() ==
resolved.bundle_id.as_deref()) with a guarded check such as matching both Some
values and comparing them (e.g., if let (Some(a), Some(b)) =
(app.bundle_id.as_deref(), resolved.bundle_id.as_deref()) && a == b), keeping
the existing display-name case-insensitive check
(app.name.eq_ignore_ascii_case(&resolved.display_name)).

In `@libs/cua-driver-rs/PARITY.md`:
- Around line 471-476: The fenced code block containing the launch_app examples
is missing a language specifier and triggers markdownlint MD040; edit the block
that starts with the triple backticks before the lines beginning "launch_app
name=" and add a language token such as text (e.g., ```text) so the block
becomes fenced with a language and satisfies the linter while preserving the
four example lines.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 02e8ef59-0d93-44aa-a225-9c305408d3ad

📥 Commits

Reviewing files that changed from the base of the PR and between 3b5c372 and 15015c7.

⛔ Files ignored due to path filters (1)
  • libs/cua-driver-rs/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • libs/cua-driver-rs/PARITY.md
  • libs/cua-driver-rs/crates/platform-macos/src/apps.rs
  • libs/cua-driver-rs/crates/platform-macos/src/tools/launch_app.rs
  • libs/cua-driver-rs/tests/integration/test_api_parity.py

Comment on lines +127 to +129
if app.name.eq_ignore_ascii_case(&resolved.display_name)
|| app.bundle_id.as_deref() == resolved.bundle_id.as_deref()
{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard None bundle-ID equality to avoid false PID matches.

Line 128 can match unrelated running apps when both sides are None, so the function may return the wrong PID.

🔧 Proposed fix
-            if app.name.eq_ignore_ascii_case(&resolved.display_name)
-                || app.bundle_id.as_deref() == resolved.bundle_id.as_deref()
+            if app.name.eq_ignore_ascii_case(&resolved.display_name)
+                || resolved
+                    .bundle_id
+                    .as_deref()
+                    .is_some_and(|bid| app.bundle_id.as_deref() == Some(bid))
             {
                 return Ok(app.pid);
             }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libs/cua-driver-rs/crates/platform-macos/src/apps.rs` around lines 127 - 129,
The equality check allows matching when both app.bundle_id and
resolved.bundle_id are None, causing false PID matches; update the condition so
bundle IDs only count as equal when both are Some and equal. Replace the current
bundle-id comparison (app.bundle_id.as_deref() == resolved.bundle_id.as_deref())
with a guarded check such as matching both Some values and comparing them (e.g.,
if let (Some(a), Some(b)) = (app.bundle_id.as_deref(),
resolved.bundle_id.as_deref()) && a == b), keeping the existing display-name
case-insensitive check (app.name.eq_ignore_ascii_case(&resolved.display_name)).

Comment thread libs/cua-driver-rs/PARITY.md Outdated
Comment on lines +471 to +476
```
launch_app name="com.apple.calculator" → pid=…, bundle_id=com.apple.calculator
launch_app name="CALCULATOR" → pid=…, bundle_id=com.apple.calculator
launch_app name="Calculator" → pid=…, (regression guard)
launch_app name="no_such_app_xyzzy" → MCP error
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a language to the fenced code block.

This block is currently untyped and triggers markdownlint MD040.

📝 Proposed fix
-```
+```text
 launch_app name="com.apple.calculator" → pid=…, bundle_id=com.apple.calculator
 launch_app name="CALCULATOR"           → pid=…, bundle_id=com.apple.calculator
 launch_app name="Calculator"           → pid=…, (regression guard)
 launch_app name="no_such_app_xyzzy"    → MCP error
</details>

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.22.1)</summary>

[warning] 471-471: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @libs/cua-driver-rs/PARITY.md around lines 471 - 476, The fenced code block
containing the launch_app examples is missing a language specifier and triggers
markdownlint MD040; edit the block that starts with the triple backticks before
the lines beginning "launch_app name=" and add a language token such as text
(e.g., ```text) so the block becomes fenced with a language and satisfies the
linter while preserving the four example lines.


</details>

<!-- fingerprinting:phantom:poseidon:hawk -->

<!-- This is an auto-generated comment by CodeRabbit -->

CR #1523:
- apps.rs:127-129: comparing two `None` bundle_ids would silently match an
  unrelated running app, returning the wrong pid for the launch we just
  performed. Require both-Some equality.
- PARITY.md:471: add `text` language tag to the launch_app example fence
  to satisfy markdownlint MD040.
@hippoley

Copy link
Copy Markdown
Contributor

Implemented in PR #1576. Ported Swift AppLauncher.locate(name:) Pass 3 to locate_by_name() in crates/platform-macos/src/apps/mod.rs:

  • 3a localizedName from running NSRunningApplication instances (locale-aware, covers JP/CN display names)
  • 3b CFBundleDisplayName > CFBundleName > bundle stem scan across canonical roots, case-insensitive

Pass 1 (exact filename) and Pass 2 (bundle-id-as-name) are unchanged. Three new integration tests added to test_api_parity.py.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants