feat(launch_app): UWP/packaged-app support on Windows - #1544
Conversation
Adds `launch_uwp` module exposing two entry points for packaged-app
(Microsoft Store / UWP / MSIX) activation on Windows:
- `launch_uwp(aumid, args)` — calls
`IApplicationActivationManager::ActivateApplication`, returning the
**real** UWP process pid (the one whose `MainWindowHandle` the user
actually sees), not the stub-redirect pid that
`ShellExecuteExW("notepad")` returns on Win11.
- `resolve_aumid_by_name(display_name)` — enumerates `shell:AppsFolder`
via `IShellItem`/`IEnumShellItems` and matches a display name to its
AUMID. Result is cached for the lifetime of the driver process
(~200 ms cold enumeration on Win11). Strips an optional `.exe` suffix
so `"notepad.exe"` and `"notepad"` resolve identically.
`PKEY_AppUserModel_ID` is defined inline to avoid pulling in the
entire `Win32_Storage_EnhancedStorage` feature subtree just for one
constant; only `Win32_UI_Shell_PropertiesSystem` is added to the
windows-rs feature list (needed by `IShellItem2::GetString`).
Not wired into `LaunchAppTool` yet — that comes in a follow-up commit
so existing callers aren't surprised by behavior changes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires `crate::launch_uwp` into `LaunchAppTool::invoke`. Routing
precedence (most explicit signal wins):
1. `aumid` parameter — explicit AUMID, packaged-app path.
2. `bundle_id` containing `!` — treated as AUMID, packaged-app path
(Win32 PATH lookups never produce `!`, so this is a safe marker
of caller intent).
3. `name` with no `path` — first looked up against `shell:AppsFolder`
(the Start Menu's "all apps" index). On a hit, packaged path with
the resolved AUMID. On miss, falls through to `ShellExecuteExW`.
4. `path` or no match — existing `ShellExecuteExW` path (unchanged).
Win11 `launch_app {name: "notepad"}` now returns the real packaged
Notepad pid instead of the ~7 KB System32 stub pid (which exits within
milliseconds and is useless for `list_windows` / `get_window_state`).
Schema additions:
- `aumid` — optional explicit AUMID, cleaner than overloading
`bundle_id`. Takes precedence.
- `bundle_id` description updated to call out AUMID detection.
- `name` description updated to call out the AppsFolder lookup.
Response shape: `bundle_id` is now the AUMID actually used when the
packaged path was taken (so the caller can round-trip the same value
to relaunch), and `null` for plain Win32 launches.
Other changes:
- Fixes a pointer-coercion compile bug in `launch_uwp::enumerate_apps_folder`
(`Option<*mut u32>` needs an explicit `as *mut u32` cast — was
`Some(&mut fetched)` which doesn't unify to the right type).
- Parity example (`launch_app_parity.rs`) now exercises both the plain
`name` path and an explicit-AUMID path, and accepts either `null` or
an AUMID for `bundle_id` in the response.
- PARITY.md updated with the new fix list entries.
- mcp-tools.mdx — Windows-only callout describing the routing rules
plus an `aumid` arg-list entry.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (8)
📝 WalkthroughWalkthroughAdds Windows packaged-app (UWP/MSIX) launching by AUMID: new launch_uwp module (COM activation, AppsFolder name→AUMID resolution with cache), integrates into launch_app routing (path > aumid > name > bundle_id), and updates docs, parity notes, and examples for Win11 behavior. ChangesWindows UWP/MSIX launcher integration
Sequence Diagram(s)sequenceDiagram
participant Caller
participant LaunchAppTool
participant LaunchUWPModule
participant IApplicationActivationManager
participant ShellExecuteExW
Caller->>LaunchAppTool: invoke(target params)
LaunchAppTool->>LaunchUWPModule: resolve AUMID (aumid / bundle_id / name)
alt AUMID resolved
LaunchAppTool->>LaunchUWPModule: launch_uwp(aumid, args)
LaunchUWPModule->>IApplicationActivationManager: ActivateApplication(aumid, arguments)
IApplicationActivationManager-->>LaunchUWPModule: real packaged PID
LaunchUWPModule-->>LaunchAppTool: packaged PID
else no AUMID
LaunchAppTool->>ShellExecuteExW: ShellExecuteExW(target)
ShellExecuteExW-->>LaunchAppTool: stub/parent PID
end
LaunchAppTool-->>Caller: structured response (pid, running, bundle_id)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
libs/cua-driver-rs/crates/platform-windows/src/launch_uwp.rs (1)
182-195: 💤 Low valueConsider caching lowercase display names to avoid repeated allocations.
The prefix-match loop calls
entry.display_name.to_lowercase()on every entry per lookup. Sinceentriesis already cached, you could store a pre-lowercased display name alongside the original to avoid repeated allocations on each call. This is a minor optimization given the cache is small (~150–300 entries) and lookups are infrequent.🤖 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-windows/src/launch_uwp.rs` around lines 182 - 195, The loop repeatedly calls entry.display_name.to_lowercase(); modify the cached data structure (AppsFolderEntry) to include a precomputed lowercase_display_name (String) when entries are built, then change this function to compare against entry.lowercase_display_name.starts_with(query_stripped) instead of calling to_lowercase() each iteration; keep the existing selection logic (best, entries, aumid) unchanged and ensure any code that constructs AppsFolderEntry populates the new lowercase field.
🤖 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-windows/examples/launch_app_parity.rs`:
- Around line 109-127: The match currently treats any non-(Some(pid),
Some(bundle)) result as a benign skip; tighten it so only an explicit "not
installed/not found" tool error yields the skip. Update the match on (pid_aumid,
bundle_id_aumid) to: if pid exists always attempt cleanup (use the existing
std::process::Command::new("taskkill") cleanup regardless), if bundle_id_aumid
is None inspect the actual error value returned by the AUMID launch call and
only print the skip message when that error equals the explicit
not-installed/not-found sentinel (or its string contains "not installed"/"not
found"); otherwise treat the case as a test failure (panic/assert) so real
regressions surface. Ensure you reference pid_aumid, bundle_id_aumid, aumid and
keep the taskkill cleanup intact.
---
Nitpick comments:
In `@libs/cua-driver-rs/crates/platform-windows/src/launch_uwp.rs`:
- Around line 182-195: The loop repeatedly calls
entry.display_name.to_lowercase(); modify the cached data structure
(AppsFolderEntry) to include a precomputed lowercase_display_name (String) when
entries are built, then change this function to compare against
entry.lowercase_display_name.starts_with(query_stripped) instead of calling
to_lowercase() each iteration; keep the existing selection logic (best, entries,
aumid) unchanged and ensure any code that constructs AppsFolderEntry populates
the new lowercase field.
🪄 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: 3c4d7ed5-6d43-486a-9bc5-b4a0e16ac712
📒 Files selected for processing (7)
docs/content/docs/cua-driver/reference/mcp-tools.mdxlibs/cua-driver-rs/PARITY.mdlibs/cua-driver-rs/crates/platform-windows/Cargo.tomllibs/cua-driver-rs/crates/platform-windows/examples/launch_app_parity.rslibs/cua-driver-rs/crates/platform-windows/src/launch_uwp.rslibs/cua-driver-rs/crates/platform-windows/src/lib.rslibs/cua-driver-rs/crates/platform-windows/src/tools/impl_.rs
…tch (CodeRabbit) - launch_uwp: AppsFolderEntry now caches lowercase_display_name at construction; the name-lookup loop compares against the cached field instead of calling to_lowercase() per-iteration. - launch_app_parity example: tightened the match on (pid_aumid, bundle_id_aumid). Always run taskkill cleanup when pid exists; only print the "skip" message when the error explicitly indicates the AUMID is not installed; otherwise panic so real regressions surface.
|
Pushed CR fixups + docs regen: |
|
✅ Actions performedFull review triggered. |
Summary
Adds UWP / MSIX / Microsoft Store packaged-app launch support to
cua-driver-rs'slaunch_appMCP tool on Windows, fixing the Win11notepadproblem.Problem: On Win11, many built-in apps (Notepad, Calculator, Paint, …) ship as packaged apps. The legacy
notepad.exe/calc.exe/mspaint.exeinC:\Windows\System32\are now ~7 KB stubs that launch the packaged equivalent and exit within milliseconds.launch_app {name: "notepad"}was usingShellExecuteExW+GetProcessIdon the spawned handle, which returned the stub's pid — gone before any window registers, andlist_windows(pid)for that pid is always empty.Fix: Detect the packaged-app case and route through
IApplicationActivationManager::ActivateApplication— the Microsoft-canonical API for launching packaged apps from outside a packaged context. It returns the real UWP process pid via itspidout-parameter.Routing rules
Order of precedence — most explicit signal wins:
aumidparameter (new) — explicit App User Model ID, packaged-app path.bundle_idcontaining!— treated as AUMID, packaged-app path (Win32 PATH lookups never produce!, so this is a safe marker).namewith nopath— first looked up againstshell:AppsFolder(the Start Menu index, cached for the lifetime of the driver process). On a hit, packaged path with the resolved AUMID. On miss, falls through toShellExecuteExW's PATH search.pathor no-match — existingShellExecuteExWpath (unchanged behavior for plain Win32 apps).Implementation notes
crates/platform-windows/src/launch_uwp.rs(~290 LoC) with two entry points:launch_uwp(aumid, args)andresolve_aumid_by_name(display_name).shell:AppsFolderviaIShellItem/IEnumShellItems/IShellItem2. Cached for the process lifetime (~200 ms cold enumeration on Win11).PKEY_AppUserModel_IDis defined inline (just aPROPERTYKEYliteral) to avoid pulling the entireWin32_Storage_EnhancedStoragefeature subtree just for one constant. OnlyWin32_UI_Shell_PropertiesSystemis added to the windows-rs feature list (needed byIShellItem2::GetString).bundle_idis the AUMID actually used when the packaged path was taken (so callers can round-trip to relaunch), andnullfor plain Win32 launches.Commits
4c65562f— addlaunch_uwpmodule, no wiring (so existing callers aren't surprised by a behavior change in a single commit).b6a89b50— wire intoLaunchAppTool::invoke, update schema + description, update PARITY.md, add Windows-only callout to mcp-tools.mdx, extend parity example to cover the AUMID path.Test plan
cargo build --release --target x86_64-pc-windows-msvcis clean (that target is in the CD matrix; macOS dev host has no cross-toolchain so build verification happens in CI).cargo test -p platform-windows launch_uwp::tests --target x86_64-pc-windows-msvcpasses theis_aumidunit tests.cua-driver call launch_app '{"bundle_id":"Microsoft.WindowsNotepad_8wekyb3d8bbwe!App"}'returns a non-zero pid;Get-Process -Id <pid>showsNotepad; the responsebundle_idround-trips the AUMID.cua-driver call launch_app '{"name":"notepad"}'returns the real packaged-Notepad pid (not a stub pid);windowsarray is non-empty within the 5×200 ms retry budget.cua-driver call launch_app '{"name":"calc"}'returns a Calculator pid via AppsFolder shortest-prefix match.cua-driver call launch_app '{"name":"regedit.exe"}'still works via theShellExecuteExWfallback (no AppsFolder match for unpackaged Win32 apps).cua-driver call launch_app '{"path":"C:\\Windows\\System32\\mspaint.exe"}'still usesShellExecuteExW(explicitpathskips packaged routing).launch_app_parity.exepasses on both Win10 (stays onnullbundle_id) and Win11 (AUMIDbundle_id, explicit-AUMID round-trip).🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation