@@ -56,12 +56,16 @@ const MACOS_APP_LEASE_COMMANDS: ReadonlySet<string> = new Set([
5656 'lease_release' ,
5757] ) ;
5858
59+ /** Commands that can resolve a device when no session exists yet; a batch's steps inherit its platform. */
60+ const DEVICE_RESOLVING_COMMANDS : ReadonlySet < string > = new Set ( [ 'open' , 'batch' ] ) ;
61+
5962type MacOsAppLeaseRule =
6063 | 'command'
6164 | 'app'
6265 | 'surface'
6366 | 'capture'
6467 | 'host-path'
68+ | 'device'
6569 | 'backend'
6670 | 'process' ;
6771
@@ -90,6 +94,16 @@ const HOST_INPUT_KEYS = [
9094 'iosXctestEnvDir' ,
9195] as const ;
9296
97+ /** Flags that pick a device other than the leased app's own; a lease never takes a device selector. */
98+ const DEVICE_SELECTOR_KEYS = [
99+ 'device' ,
100+ 'udid' ,
101+ 'serial' ,
102+ 'target' ,
103+ 'iosSimulatorDeviceSet' ,
104+ 'androidDeviceAllowlist' ,
105+ ] as const ;
106+
93107/** The only screenshot path a remote client sends: the temp file `agent-device` names for it. */
94108const REMOTE_SCREENSHOT_PATH = / ^ \/ t m p \/ a g e n t - d e v i c e - s c r e e n s h o t - [ 0 - 9 ] + - [ a - z 0 - 9 ] + \. p n g $ / ;
95109
@@ -119,24 +133,46 @@ export function assertMacOsAppLeaseAdmitsRequest(
119133) : void {
120134 if ( lease . backend !== 'macos-app' ) return ;
121135 const key = parseMacOsAppLeaseKey ( lease . deviceKey ) ;
122- assertInvocation ( key , req . command , req . positionals ?? [ ] , {
123- ...req . input ,
124- ...req . flags ,
125- ...( req . runtime ?. launchUrl ? { launchUrl : req . runtime . launchUrl } : { } ) ,
126- ...( req . runtime ?. bundleUrl ? { bundleUrl : req . runtime . bundleUrl } : { } ) ,
127- } ) ;
136+ assertInvocation (
137+ key ,
138+ req . command ,
139+ req . positionals ?? [ ] ,
140+ withRuntimeInputs ( req . runtime , { ...req . input , ...req . flags } ) ,
141+ ) ;
142+ if ( DEVICE_RESOLVING_COMMANDS . has ( req . command ) && req . flags ?. platform !== 'macos' ) {
143+ throw macOsAppLeaseDenied (
144+ 'device' ,
145+ `A macos-app lease needs platform macos on ${ req . command } .` ,
146+ ) ;
147+ }
128148 if ( req . command === 'batch' ) {
129149 for ( const step of ( req . flags ?. batchSteps ?? [ ] ) as readonly Record < string , unknown > [ ] ) {
130150 const positionals = Array . isArray ( step . positionals ) ? step . positionals . map ( String ) : [ ] ;
131- assertInvocation ( key , normalizeBatchCommandName ( step . command ) , positionals , {
132- ...( step . input as Record < string , unknown > | undefined ) ,
133- ...( step . flags as Record < string , unknown > | undefined ) ,
134- } ) ;
151+ assertInvocation (
152+ key ,
153+ normalizeBatchCommandName ( step . command ) ,
154+ positionals ,
155+ withRuntimeInputs ( step . runtime as DaemonRequest [ 'runtime' ] , {
156+ ...( step . input as Record < string , unknown > | undefined ) ,
157+ ...( step . flags as Record < string , unknown > | undefined ) ,
158+ } ) ,
159+ ) ;
135160 }
136161 }
137162 if ( session ) assertSessionIsLeasedApp ( key , session ) ;
138163}
139164
165+ function withRuntimeInputs (
166+ runtime : DaemonRequest [ 'runtime' ] ,
167+ fields : Record < string , unknown > ,
168+ ) : Record < string , unknown > {
169+ return {
170+ ...fields ,
171+ ...( runtime ?. launchUrl ? { launchUrl : runtime . launchUrl } : { } ) ,
172+ ...( runtime ?. bundleUrl ? { bundleUrl : runtime . bundleUrl } : { } ) ,
173+ } ;
174+ }
175+
140176function assertInvocation (
141177 key : MacOsAppLeaseKey ,
142178 command : string ,
@@ -149,6 +185,7 @@ function assertInvocation(
149185 } ) ;
150186 }
151187 assertAppSurface ( fields ) ;
188+ assertNoDeviceSelector ( fields ) ;
152189 if ( command === 'screenshot' ) assertWindowCapture ( positionals , fields ) ;
153190 else assertNoHostInputs ( fields ) ;
154191 if ( command === 'open' ) assertOpensLeasedApp ( key , positionals ) ;
@@ -185,6 +222,18 @@ function assertNoHostInputs(fields: Readonly<Record<string, unknown>>): void {
185222 ) ;
186223}
187224
225+ function assertNoDeviceSelector ( fields : Readonly < Record < string , unknown > > ) : void {
226+ const named = DEVICE_SELECTOR_KEYS . find ( ( key ) => fields [ key ] !== undefined && fields [ key ] !== '' ) ;
227+ if ( ! named ) return ;
228+ throw macOsAppLeaseDenied (
229+ 'device' ,
230+ `A macos-app lease does not accept the ${ named } device selector.` ,
231+ {
232+ field : named ,
233+ } ,
234+ ) ;
235+ }
236+
188237function assertAppSurface ( fields : Readonly < Record < string , unknown > > ) : void {
189238 if ( fields . surface !== undefined && fields . surface !== 'app' ) {
190239 throw macOsAppLeaseDenied (
0 commit comments