Does Blender use a Python Virtual Environment?
Current state of Blender's Python integration

Blender has its own Python interpreter, modules and libraries and is therefore entirely separated from your system's Python installation(s). The interpreter is linked into Blender's binary. It does not create or use  virtual environments. The add-ons and commands from the Python console are executed by the same Python interpreter and therefore have access to the same modules and libraries. There is no isolation or separate dependency management for each individual add-on. Hence, add-on developers need to be careful not to install packages that could conflict with other add-ons. Currently Blender has no unified way to install the required dependencies through the Python API. This also raises the question how the user is notified that the add-on requires additional packages. Depending on the location of Blender's directory, the installation may require elevated privileges. 

Since the current design is not an optimal solution, alternatives are being discussed by the developers and the community (see T71420). Recent versions have solidified the separation of Blender's Python and the system's Python by ignoring the PYTHONHOME and PYTHONPATH environment variables, unless the --python-use-system-env command line flag is passed to Blender, which add-on developers cannot rely on. This implies that add-ons are no longer supposed to install dependencies to the user's site-packages  (see T76993), thus no --user flag should be supplied to pip when installing packages. Technically this is still possible, as detailed in the linked ticket, but it's apparently not the intended way.

What does this mean in practice?

Currently your add-on can install dependencies, but it has to:


Check if pip is available and install it if it isn't (only Windows comes with pip pre-installed, at the time of writing)
Install the required packages through subprocess.


An example for this can be found on my GitHub. As previously noted, the required approach can change in future versions. Add-on developers aren't (yet) forced to ask for the user's permission before downloading and installing packages. Every add-on developer is expected to honor the privacy and autonomy of the user. Independent of this common courtesy are the actual OS permissions. If Blender's directory is located in, e.g. C:\Program Files on Windows, the user will have to start Blender with administrative privileges for your pip install to succeed or install the packages manually. The same is the case on Linux and macOS. Therefore, the add-on developer should provide instructions for this use case. Alternatively you could go against Blender's current design and manually add the user's site-packages to the sys.path as described by MACHIN3 in T76993. This avoids the permission issue when used in combination with the --user flag, since Blender has the necessary permissions to install packages in that directory. 

Answering your questions


  Does Blender create a Python virtual environment for itself? [...]


No.


  [...] If so, how would I ensure I was working in the same one, if working outside the internal text editor / Python console?


The add-ons will use the same interpreter and thus will be able to load the same installed modules. This also applies to scripts that are supplied to Blender via the command line with the -P or --python argument.

If you want to run scripts that depend on bpy outside of Blender, then you're likely looking to build Blender as Python module.


  I wondered whether I should have activated an environment, and imported into that.


No, you shouldn't have, because that is not supported at the moment. Allowing completely separated virtual environments as venv implements it, would requires major changes to Blender's Python integration.


  [...] and i wondered what Blender's python/lib/python3.7/venv/ directory was doing


Blender includes all libraries that come with every Python installation and venv is one of them. It's not actually used by Blender. It should also be noted that the Python binary in python/bin is only there for convenience, e.g. if you want to install packages with pip. It isn't the actual Python interpret Blender uses. Blender's binary includes the Python interpreter, it's not a separate binary.

Conclusion

There is currently no standard way of installing and managing dependencies through Blender's API and every add-on developer is required to handle this on their own. Future versions of Blender will hopefully improve the process.