Skip to content

feat(apps): add app control tools and the App tab - #15

Merged
lauridskern merged 22 commits into
mainfrom
feat/device-app-tools
Oct 15, 2026
Merged

lauridskern merged 22 commits into
mainfrom
feat/device-app-tools

Conversation

@lauridskern

@lauridskern lauridskern commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Adds app control to Mobile Dev: MCP tools for Codex, and an App tab in the device popover.

  • App control tools: list, launch, stop, and install apps, open deep links, change app permissions, and send test push notifications on iOS simulators (simctl) and Android devices (adb). Panel-only tools read app icons, Android labels, declared URL schemes, and permission state. SimulatorDriver and AndroidDriver implement one AppDriver interface, so the tool layer validates the target once and drivers never branch on platform. Each driver also rejects permissions its platform can't set, so the tool layer has no platform checks.
  • App tab: the device popover splits into App and Device tabs. The App tab picks an installed app with its icon and launches it on selection. It can relaunch or stop the app, open URLs with declared-scheme and recent suggestions, and set permissions and test pushes in collapsible sections.
  • Device tab: controls are grouped under Display, Location, and Panel. Location shows the current place with city presets and custom coordinates, and text size is a stepper. The popover animates its height between tabs.
  • Fixes:
    • Panel inputs use small text at every width.
    • Android launcher activities are quoted, so inner classes like .Main$Launcher launch.
    • The server serves every earlier release's UI address, so tabs opened before an update keep working.
  • Shared helpers: the panel's element-bound stores use one elementStores helper, and App tab timings use measureUiOperation from src/ui/telemetry.ts. Android installs run through runAdb in commands.ts, which feat(settings): add clean status bar, network conditions, and per-app language #14's network throttle also uses.
  • Docs: the Mobile Dev skill documents the app control tools.

Release bumps are separate commits, up to 0.1.151.

Stack: #14 (clean status bar, network conditions, per-app language) builds on this branch.

Conflicts with main: only the version bump files (package.json, package-lock.json, .codex-plugin/plugin.json, src/shared/version.ts), because main gained two fixes after this branch started.

Telemetry

  • Traces: the app control tools use the existing sampled MCP traces.
  • Errors: unexpected simctl, adb, aapt2, or plutil failures report a fixed message under app_control.<action>, so bundle IDs, URLs, serials, and paths stay local. Expected outcomes, such as a missing app, count only through server.error.outcome, without a report.
  • Panel timings: the App tab records ui.app_tools.<action> round trips and counts ui.app_tools.failure. Results that arrive after a surface or device change are excluded.
  • Docs: docs/telemetry.md is updated.

Testing

  • tests/app-control.test.ts covers simctl and adb parsing, launch/permission/link/icon handling, and command errors. tests/device-settings.test.ts and tests/react-ui.test.ts cover the App and Device tabs.
  • npm test on this branch alone: the app control, device settings, and React UI tests pass. In a fresh worktree, 10 Baguette, serve-emu, and agent-device runtime tests fail because the untracked vendored binaries and runtime node_modules are missing there. With feat(settings): add clean status bar, network conditions, and per-app language #14 on top, in the main checkout, all 642 tests pass (1 skipped).

Add MCP tools that list, launch, stop, and install apps, open deep links,
change app permissions, and send test push notifications on iOS simulators
and Android devices through simctl and adb. Panel-only tools read app icons,
Android labels, declared URL schemes, and permission state.

Unexpected command failures reach Sentry with a fixed message so command
output with bundle IDs, URLs, and serials stays local.
Split the device popover into App and Device tabs. The App tab picks an
installed app with its icon, launches it on selection, relaunches or stops
it, opens URLs with declared-scheme and recent suggestions, and sets
permissions and test pushes.

Without a detected foreground app, the picker opens on the newest running
app, then the last app picked on that device.
Replace the single AppControl class, which branched on platform in every
method, with an AppDriver interface implemented by SimulatorDriver (simctl)
and AndroidDriver (adb). The tool layer validates the target once and picks
the driver; drivers never branch on platform.

simctl failures that the user can fix are declared at each call site
instead of through repeated catch, cast, and rethrow blocks. The app list
now puts the foreground app first among running apps, and permission
changes return the full list on both platforms. AppIcon becomes AppDetails
because it also carries the Android label.
Collapse the store's icon, label, and request-tracking structures into one
details map, model loading permissions as null instead of a separate flag,
and derive running state from the server's running list. One staleness
guard replaces five copies, actions share a single path that resolves the
selected app, and permissions and link suggestions update together.

The App tab component splits into picker, URL, permission, and push
sections.
The shadcn inputs used text-base below the md breakpoint to avoid iOS
Safari focus zoom. The panel is always narrower than md, so its fields
rendered at 16px next to 14px controls.
Codex keeps the resource address of side tabs and saved cards opened
before a plugin update. The server only listed addresses up to 0.1.57,
so a tab opened on the previous release showed "Couldn't display Mobile
Dev" after updating. Resource templates now serve the current panel,
workspace, recording, and comparison views for any earlier release.
Relaunch and Stop join the app picker as icon buttons, and the running state moves into its
subtitle. Open sits inside the URL field, and recent URLs get a clock chip ahead of the dashed
scheme templates. Permissions and the test notification collapse into rows with a summary, and
the open one is remembered across panels. Permission rows show every entry with an icon.
The Device tab groups controls under Display, Location and Panel in one right-hand column.
Text size becomes a stepper whose middle still opens the full list. Location shows the current
place with one-click city chips, and Custom opens the coordinate fields. Tabs gain icons.
Inner-class launcher activities such as .Main$Launcher were expanded by the device shell.
Expected app-control outcomes now reach server.error.outcome, as docs/telemetry.md describes.
… helpers

- Drivers reject permissions their platform cannot set, so the tool layer no longer branches.
- One adb runner in commands.ts, shared by installs and the network throttle.
- Element-bound stores and measured panel operations use shared helpers.
…ir device

- Drivers report the foreground and running apps together, so iOS lists launchctl once.
- A permission change that finishes after a device switch no longer marks the new device.
- The panel requests icons in batches the server accepts, so large app lists still get icons.
- Android SDK discovery is shared, and a missing aapt2 is looked up again later.
# Conflicts:
#	.codex-plugin/plugin.json
#	package-lock.json
#	package.json
#	src/shared/version.ts
@lauridskern
lauridskern merged commit acd0768 into main Oct 15, 2026
6 checks passed
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.

1 participant