modifying the default python environment
The console is a &quot;start-up&quot; script.

This doesn't seem to be possible with an add-on or a startup script,
but maybe I am doing something wrong.

Yes indeed, Maybe you are,... hard to tell, have some suggestions, won't go there.
Look at the source code.
Just as with most spaces in the blender UI the code is available. In the case of the python console  the UI in bl_ui/space_console.py and the  nuts and bolts of the python console in scripts/modules/console_python.py
A code snippet from which, where the convenience imports and variables are added.
    namespace[&quot;__builtins__&quot;] = sys.modules[&quot;builtins&quot;]
    namespace[&quot;bpy&quot;] = bpy

    # weak! - but highly convenient
    namespace[&quot;C&quot;] = bpy.context
    namespace[&quot;D&quot;] = bpy.data

    replace_help(namespace)

    console = InteractiveConsole(locals=namespace,
                                 filename=&quot;&lt;blender_console&gt;&quot;)

    console.push(&quot;from mathutils import *&quot;)
    console.push(&quot;from math import *&quot;)

An option (but a bad slippery-slope don't do it option) is to edit this to suit, however
a lesser evil could be to copy, rename and  put in our user scripts/modules folder.
&gt;&gt;&gt; bpy.utils.script_path_user()
'/home/batfinger/.config/blender/2.91/scripts'

For example sake have called it console_batpython.py
Purpose of the languages selector in the Python Console

and have one or a number of different options available via the menu.  Which does involve  having to select the language (... in this case python with different imports...) at least once per console
Add locals to a console.
Will find a number of run script in console addons, a couple of rippers that come to mind are by @CodemanX and @pinkvertex (the links escape me atm).
The main gist is using the module in conjunction with code as displayed in the script stub template. To run a script from the text editor and have it print to and available to inspect in the console.
Here is a script which when run adds locals to consoles in same screen.
import bpy
import bmesh
import console_python

for area in bpy.context.screen.areas:
    if area.type == 'CONSOLE':
        region = area.regions[-1]
        id = hash(region)
        con, stdout, stderr = console_python.get_console(id)
        # add to locals
        con.locals['bmesh'] = bmesh
        # update with locals from this script
        con.locals.update(locals())
        # push in some locals
        con.push(&quot;import itertools&quot;)
        con.push(&quot;foo = 3&quot;)

and in any console on the same screen as the text editor.
&gt;&gt;&gt; itertools
&lt;module 'itertools' (built-in)&gt;

&gt;&gt;&gt; bmesh
&lt;module 'bmesh' (built-in)&gt;

&gt;&gt;&gt; foo
3

Text Editor
The concept of the text editor is to import all requirements as if it were any other module on disk. We can however write an operator based on the Text Editor &gt; Templates &gt; Python &gt; External Script Stub
exec(compile(file.read(), filepath, 'exec'), global_namespace)

and replace file.read() with text_block.as_string() and add other members to global_namespace dictionary.