refactor(mobile): set permissions on the app by name, without an open first
What and why
-
agent-device's
settings permissionacted only on the app its session was on. To set a permission before the first launch, the engine opened the app first just to put the session on it. That took:- a
sessionAppfield on the surface and on every slot binding; - a
sessions.listat worker start, to check that the warm-up's session survived; - a foreground open before
setPermissionand before the permissions a launch presets; - a retry that matched the refusal text
requires an active app in session.
- a
-
agent-device 0.21.21 takes an explicit
app(callstack/agent-device#3179, filed from this integration), and 0.21.25 resolves a display name there and refuses an app that isn't installed (callstack/agent-device#3386). This PR bumpsagent-deviceto 0.21.25 for that: on 0.21.22 a target pinned by display name (bundleId: 'Settings', as in the quickstart) would get its permission written for a client literally namedSettings.simctl privacyandpmneed only the id, not a running app. All of the above is gone:client.settings.update({ setting: 'permission', permission, state, ...this.selection(), ...(app === undefined ? {} : { app }) }) -
The permission call carries the worker's device selection. Before, the open in front of it bound the session to the device; without that open, a worker with no session (after
closeApp, or on a slot whose earlier worker retired) would send the change to the daemon's default device. -
device.setPermission()acts on the pinned app, as its docs say, not on whichever app the session last opened. -
The warm-up in
preparestill opens the pinned app, so a test that starts withoutapp.open()finds it on screen. Its other reason, that a timed-out open resets the daemon every slot shares, is gone since agent-device 0.21.21 (callstack/agent-device#3177), and the comments say so.
flowchart LR
subgraph main
A1[open app, foreground] --> B1[settings permission] --> C1[open --relaunch]
end
subgraph this branch
B2[settings permission --app] --> C2[open --relaunch]
end
Verified
Ran it locally: yes
- iOS 27.1 simulator (iPhone 17 Pro), a scratch project on the built packages, target
com.apple.Maps. The test runsdevice.closeApp(),device.setPermission('photos', 'grant'),app.open(),device.closeApp(),device.setPermission('photos', 'deny'). Commands agent-device received for the session, from itsevents.ndjson:
| main | this branch |
|---|---|
| boot, session_list, close, open, settings, open, close, open, settings | boot, close, settings, open, close, settings |
- The simulator's
TCC.dbhaskTCCServicePhotos | com.apple.Maps | 0from the branch's finaldeny. - On 0.21.25, the same simulator with the target pinned by display name (
app: { bundleId: 'Maps' }):device.setPermission('photos', 'grant')before the firstapp.open()wrotekTCCServicePhotos | com.apple.Maps | 2. - Not run locally: Android, and the mobile benchmark. This PR's mobile jobs run both.
Checklist
- Changeset added (
pnpm changeset) for any change to a published package, or the change is testbed, docs, or CI only. - Breaking change: title carries
!, the changeset body names what breaks and what replaces it, and the deprecation warning landed a window earlier (stability policy in CONTRIBUTING.md). - Docs updated in the same PR: the
docs/**/*.mdxpage for the behavior, andskills/e2e/if the skill describes it. - Contract change: emitted
.d.tsreviewed,tests/types/sdk-types.tsupdated; wire change edits the schema and both fixtures. - Engine contract change (
e2e/engine): changesets fore2e,@e2e-dev/web, and@e2e-dev/mobile.
DeviceLease loses the optional sessionApp it inherited from SlotBinding. It was never documented, and the lease-to-binding path already dropped it.
🤖 Generated with Claude Code