portacode 1.5.17.dev0__tar.gz → 1.5.17.dev1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/PKG-INFO +1 -1
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/_version.py +2 -2
- portacode-1.5.17.dev1/portacode/codex_loopback_proxy.py +468 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/codex_prepare.py +154 -1
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/client.py +24 -3
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/codex_app_server.py +52 -3
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/handlers/codex_handlers.py +121 -17
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/handlers/runtime_user.py +31 -4
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/handlers/test_runtime_user.py +14 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/keypair.py +28 -10
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/tests/test_codex_app_server.py +53 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/tests/test_codex_handlers.py +62 -14
- portacode-1.5.17.dev1/portacode/tests/test_codex_prepare.py +54 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode.egg-info/PKG-INFO +1 -1
- portacode-1.5.17.dev0/portacode/codex_loopback_proxy.py +0 -248
- portacode-1.5.17.dev0/portacode/tests/test_codex_prepare.py +0 -29
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/.claude/agents/communication-manager.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/.claude/settings.local.json +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/.gitignore +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/.gitmodules +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/LICENSE +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/MANIFEST.in +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/Makefile +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/README.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/backup.sh +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/build_android.sh +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/connect.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/connect.sh +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/docker-compose.yaml +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/docs/cloudflared-domain-connect-containers-audit.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/docs/creative-team-brief-portacode.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/docs/device-health-alerting-plan.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/docs/devops-messaging-ab-tests.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/docs/homepage-dashboard-positioning-fixes.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/docs/images/device-transfer-button.png +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/docs/images/device-transfer-modal.png +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/docs/images/pair-device-button.png +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/docs/images/pairing-request.png +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/docs/images/student-workspace.png +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/docs/template-guide.html +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/README.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/simple_device/Dockerfile +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/simple_device/README.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/simple_device/data/device-01/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/simple_device/data/device-01/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/simple_device/docker-compose.yaml +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/Dockerfile +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/README.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-01/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-01/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-01/.local/share/portacode/run/gateway.pid +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-01/workspace/.gitignore +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-01/workspace/README.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-01/workspace/db.sqlite3 +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/__init__.cpython-311.pyc +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/settings.cpython-311.pyc +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/urls.cpython-311.pyc +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/wsgi.cpython-311.pyc +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-01/workspace/manage.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-01/workspace/requirements.txt +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-01/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/__init__.cpython-311.pyc +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/admin.cpython-311.pyc +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/apps.cpython-311.pyc +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/menu.cpython-311.pyc +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/models.cpython-311.pyc +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/urls.cpython-311.pyc +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/views.cpython-311.pyc +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/admin.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/apps.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/menu.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/migrations/__pycache__/__init__.cpython-311.pyc +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/models.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/tests.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/urls.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/views.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-02/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-02/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-02/workspace/README.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-02/workspace/manage.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-02/workspace/requirements.txt +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-02/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-02/workspace/treats/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-02/workspace/treats/admin.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-02/workspace/treats/apps.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-02/workspace/treats/menu.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-02/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-02/workspace/treats/models.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-02/workspace/treats/tests.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-02/workspace/treats/urls.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-02/workspace/treats/views.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-03/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-03/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-03/workspace/README.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-03/workspace/manage.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-03/workspace/requirements.txt +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-03/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-03/workspace/treats/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-03/workspace/treats/admin.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-03/workspace/treats/apps.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-03/workspace/treats/menu.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-03/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-03/workspace/treats/models.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-03/workspace/treats/tests.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-03/workspace/treats/urls.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-03/workspace/treats/views.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-04/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-04/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-04/workspace/README.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-04/workspace/manage.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-04/workspace/requirements.txt +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-04/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-04/workspace/treats/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-04/workspace/treats/admin.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-04/workspace/treats/apps.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-04/workspace/treats/menu.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-04/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-04/workspace/treats/models.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-04/workspace/treats/tests.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-04/workspace/treats/urls.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-04/workspace/treats/views.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-05/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-05/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-05/workspace/README.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-05/workspace/manage.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-05/workspace/requirements.txt +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-05/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-05/workspace/treats/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-05/workspace/treats/admin.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-05/workspace/treats/apps.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-05/workspace/treats/menu.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-05/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-05/workspace/treats/models.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-05/workspace/treats/tests.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-05/workspace/treats/urls.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-05/workspace/treats/views.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-06/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-06/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-06/workspace/README.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-06/workspace/manage.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-06/workspace/requirements.txt +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-06/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-06/workspace/treats/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-06/workspace/treats/admin.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-06/workspace/treats/apps.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-06/workspace/treats/menu.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-06/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-06/workspace/treats/models.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-06/workspace/treats/tests.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-06/workspace/treats/urls.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-06/workspace/treats/views.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-07/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-07/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-07/workspace/README.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-07/workspace/manage.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-07/workspace/requirements.txt +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-07/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-07/workspace/treats/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-07/workspace/treats/admin.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-07/workspace/treats/apps.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-07/workspace/treats/menu.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-07/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-07/workspace/treats/models.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-07/workspace/treats/tests.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-07/workspace/treats/urls.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-07/workspace/treats/views.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-08/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-08/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-08/workspace/README.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-08/workspace/manage.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-08/workspace/requirements.txt +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-08/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-08/workspace/treats/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-08/workspace/treats/admin.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-08/workspace/treats/apps.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-08/workspace/treats/menu.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-08/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-08/workspace/treats/models.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-08/workspace/treats/tests.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-08/workspace/treats/urls.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-08/workspace/treats/views.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-09/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-09/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-09/workspace/README.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-09/workspace/manage.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-09/workspace/requirements.txt +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-09/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-09/workspace/treats/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-09/workspace/treats/admin.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-09/workspace/treats/apps.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-09/workspace/treats/menu.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-09/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-09/workspace/treats/models.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-09/workspace/treats/tests.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-09/workspace/treats/urls.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-09/workspace/treats/views.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-10/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-10/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-10/workspace/README.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-10/workspace/manage.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-10/workspace/requirements.txt +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-10/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-10/workspace/treats/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-10/workspace/treats/admin.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-10/workspace/treats/apps.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-10/workspace/treats/menu.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-10/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-10/workspace/treats/models.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-10/workspace/treats/tests.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-10/workspace/treats/urls.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/data/student-10/workspace/treats/views.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/docker-compose.yaml +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/initial_content/README.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/initial_content/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/initial_content/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/initial_content/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/initial_content/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/initial_content/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/initial_content/manage.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/initial_content/requirements.txt +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/initial_content/templates/treats/home.html +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/initial_content/treats/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/initial_content/treats/admin.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/initial_content/treats/apps.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/initial_content/treats/menu.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/initial_content/treats/migrations/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/initial_content/treats/models.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/initial_content/treats/tests.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/initial_content/treats/urls.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/initial_content/treats/views.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/examples/workshop_fleet/instructions/WELCOME.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/README.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/__main__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/cli.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/codex_usage_limit.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/README.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/handlers/README.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/handlers/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/handlers/automation_v2_handlers.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/handlers/base.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/handlers/chunked_content.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/handlers/cloudflare_forwarding.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/handlers/cloudflare_tunnel.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/handlers/diff_handlers.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/handlers/file_handlers.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/handlers/file_transfer_handlers.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/handlers/project_aware_file_handlers.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/handlers/project_state/README.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/handlers/project_state/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/handlers/project_state/file_system_watcher.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/handlers/project_state/git_manager.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/handlers/project_state/handlers.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/handlers/project_state/manager.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/handlers/project_state/models.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/handlers/project_state/test_handlers.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/handlers/project_state/utils.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/handlers/project_state_handlers.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/handlers/proxmox_infra.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/handlers/registry.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/handlers/session.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/handlers/system_handlers.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/handlers/tab_factory.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/handlers/terminal_handlers.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/handlers/test_cloudflare_forwarding.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/handlers/test_proxmox_infra.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/handlers/update_handler.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/multiplex.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/terminal.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/test_webmin_proxy_config.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/connection/webmin_proxy_config.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/data.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/exit_codes.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/link_capture/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/link_capture/__pycache__/__init__.cpython-311.pyc +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/link_capture/bin/elinks +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/link_capture/bin/gio-open +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/link_capture/bin/gnome-open +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/link_capture/bin/gvfs-open +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/link_capture/bin/kde-open +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/link_capture/bin/kfmclient +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/link_capture/bin/link_capture_exec.sh +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/link_capture/bin/link_capture_wrapper.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/link_capture/bin/links +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/link_capture/bin/links2 +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/link_capture/bin/lynx +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/link_capture/bin/mate-open +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/link_capture/bin/netsurf +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/link_capture/bin/sensible-browser +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/link_capture/bin/w3m +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/link_capture/bin/x-www-browser +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/link_capture/bin/xdg-open +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/logging_categories.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/pairing.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/restart.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/service.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/static/js/test-ntp-clock.html +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/static/js/utils/ntp-clock.js +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/test_runtime_paths.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/test_service.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/test_terminal_exposed_services.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/tunneling/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/tunneling/cloudflared_login.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/tunneling/ensure_cloudflared.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/tunneling/ensure_pyyaml.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/tunneling/forwarding_state.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/tunneling/get_domain.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/tunneling/privileged.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/tunneling/service_install.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/tunneling/state.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/updater.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/utils/NTP_ARCHITECTURE.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/utils/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/utils/diff_apply.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/utils/diff_renderer.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/utils/ntp_clock.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode/utils/runtime_paths.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode.egg-info/SOURCES.txt +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode.egg-info/dependency_links.txt +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode.egg-info/entry_points.txt +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode.egg-info/requires.txt +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode.egg-info/top_level.txt +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode_for_school/.gitignore +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode_for_school/README.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode_for_school/persistent_workspace/Dockerfile +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode_for_school/persistent_workspace/README.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode_for_school/persistent_workspace/entrypoint.sh +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode_for_school/persistent_workspace/manage_classroom.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode_for_school/temporary_workspace/Dockerfile +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode_for_school/temporary_workspace/README.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode_for_school/temporary_workspace/docker-compose.yaml +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode_for_school/temporary_workspace/docs/device_transfere.png +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode_for_school/temporary_workspace/docs/pairing_approval.png +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/portacode_for_school/temporary_workspace/entrypoint.sh +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/pyproject.toml +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/restore.sh +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/run_tests.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/scripts/diagnose_codex_gateway.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/setup.cfg +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/setup.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/test.sh +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/test_modules/README.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/test_modules/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/test_modules/test_device_online.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/test_modules/test_file_operations.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/test_modules/test_git_status_ui.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/test_modules/test_login_flow.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/test_modules/test_navigate_testing_folder.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/test_modules/test_play_store_screenshots.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/test_modules/test_terminal_buffer_performance.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/test_modules/test_terminal_interaction.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/test_modules/test_terminal_loading_race_condition.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/test_modules/test_terminal_start.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/test_request_id.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/test_session_terminal_shutdown.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/testing_framework/.env.example +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/testing_framework/README.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/testing_framework/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/testing_framework/cli.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/testing_framework/core/__init__.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/testing_framework/core/base_test.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/testing_framework/core/cli_manager.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/testing_framework/core/hierarchical_runner.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/testing_framework/core/playwright_manager.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/testing_framework/core/runner.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/testing_framework/core/shared_cli_manager.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/testing_framework/core/test_discovery.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/testing_framework/requirements.txt +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/todo/UI_UX/opening_a_file_on_desktop_results_in_nothing.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/todo/UI_UX/server_occasionally_stops_communicating_with_all_devices.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/todo/agent_context_management.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/todo/django_server_time_sync.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/todo/issues/device_performance_degradation.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/todo/issues/git_data_not_captured_in_proxmox.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/todo/issues/indefinite_resource_loading.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/todo/issues/portacode_service_silently_down.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/todo/issues/premature_terminal_exit.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/todo/issues/project_cpu_hotspots.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/todo/issues/terminals_exit_upon_starting.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/todo/issues/websocket_client_silently_dead.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/todo/issues/wrong_item_classification_on_client_side.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/todo/smartphone_terminal_input_frustrations.md +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/tools/generate_play_store_assets.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/tools/pairing_tester.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/tools/run_screenshot_suite.sh +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/tools/test_python_ntp_clock.py +0 -0
- {portacode-1.5.17.dev0 → portacode-1.5.17.dev1}/validate.sh +0 -0
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '1.5.17.
|
|
22
|
-
__version_tuple__ = version_tuple = (1, 5, 17, '
|
|
21
|
+
__version__ = version = '1.5.17.dev1'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 5, 17, 'dev1')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
"""Loopback-only Codex Responses proxy authenticated by the device key.
|
|
2
|
+
|
|
3
|
+
Runs on a dedicated thread + event loop so long-lived SSE streams and crypto
|
|
4
|
+
never starve the Portacode websocket / terminal asyncio loop.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import base64
|
|
11
|
+
import hashlib
|
|
12
|
+
import json
|
|
13
|
+
import logging
|
|
14
|
+
import os
|
|
15
|
+
import secrets
|
|
16
|
+
import threading
|
|
17
|
+
import time
|
|
18
|
+
import uuid
|
|
19
|
+
from concurrent.futures import Future
|
|
20
|
+
from typing import Dict, Optional, Tuple
|
|
21
|
+
|
|
22
|
+
import httpx
|
|
23
|
+
|
|
24
|
+
from .codex_usage_limit import note_usage_limit_resets_at
|
|
25
|
+
from .keypair import KeyPair
|
|
26
|
+
|
|
27
|
+
LOGGER = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
CODEX_LOOPBACK_HOST = "127.0.0.1"
|
|
30
|
+
CODEX_LOOPBACK_PORT = 61789
|
|
31
|
+
DEFAULT_GATEWAY_URL = "https://codexapi.portacode.com/v1"
|
|
32
|
+
MAX_REQUEST_BYTES = 10 * 1024 * 1024
|
|
33
|
+
ALLOWED_PATHS = {"/health", "/v1/models", "/v1/responses"}
|
|
34
|
+
MAX_CONCURRENT_UPSTREAM = 8
|
|
35
|
+
|
|
36
|
+
# Streaming read can idle between SSE chunks; connect/write stay bounded.
|
|
37
|
+
_UPSTREAM_TIMEOUT = httpx.Timeout(connect=20.0, read=600.0, write=60.0, pool=20.0)
|
|
38
|
+
|
|
39
|
+
FORWARD_RESPONSE_HEADERS = frozenset(
|
|
40
|
+
{
|
|
41
|
+
"content-type",
|
|
42
|
+
"cache-control",
|
|
43
|
+
"x-codex-active-limit",
|
|
44
|
+
"x-codex-primary-reset-at",
|
|
45
|
+
"x-codex-secondary-reset-at",
|
|
46
|
+
"x-codex-primary-used-percent",
|
|
47
|
+
"x-codex-secondary-used-percent",
|
|
48
|
+
"x-codex-primary-window-minutes",
|
|
49
|
+
"x-codex-secondary-window-minutes",
|
|
50
|
+
"x-codex-promo-message",
|
|
51
|
+
"x-codex-credits-available",
|
|
52
|
+
"x-codex-credits-balance",
|
|
53
|
+
"x-portacode-resets-at",
|
|
54
|
+
"x-request-id",
|
|
55
|
+
"cf-ray",
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class CodexLoopbackProxy:
|
|
61
|
+
"""Expose the narrow OpenAI-compatible surface that Codex CLI needs locally."""
|
|
62
|
+
|
|
63
|
+
def __init__(self, keypair: KeyPair, gateway_url: Optional[str] = None) -> None:
|
|
64
|
+
self.keypair = keypair
|
|
65
|
+
self.gateway_url = (
|
|
66
|
+
gateway_url
|
|
67
|
+
or os.getenv("PORTACODE_CODEX_DEVICE_GATEWAY_URL")
|
|
68
|
+
or DEFAULT_GATEWAY_URL
|
|
69
|
+
).rstrip("/")
|
|
70
|
+
self._server: Optional[asyncio.AbstractServer] = None
|
|
71
|
+
self._client: Optional[httpx.AsyncClient] = None
|
|
72
|
+
self._upstream_sem: Optional[asyncio.Semaphore] = None
|
|
73
|
+
self._loop: Optional[asyncio.AbstractEventLoop] = None
|
|
74
|
+
self._thread: Optional[threading.Thread] = None
|
|
75
|
+
self._stop_event: Optional[threading.Event] = None
|
|
76
|
+
self._active_requests = 0
|
|
77
|
+
self._active_lock = threading.Lock()
|
|
78
|
+
|
|
79
|
+
async def start(self) -> None:
|
|
80
|
+
"""Start the proxy thread (idempotent). Awaitable from the agent loop."""
|
|
81
|
+
if self._thread is not None and self._thread.is_alive() and self._server is not None:
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
ready: Future = Future()
|
|
85
|
+
stop_event = threading.Event()
|
|
86
|
+
self._stop_event = stop_event
|
|
87
|
+
|
|
88
|
+
def _thread_main() -> None:
|
|
89
|
+
loop = asyncio.new_event_loop()
|
|
90
|
+
asyncio.set_event_loop(loop)
|
|
91
|
+
self._loop = loop
|
|
92
|
+
try:
|
|
93
|
+
loop.run_until_complete(self._async_start())
|
|
94
|
+
ready.set_result(True)
|
|
95
|
+
while not stop_event.is_set():
|
|
96
|
+
loop.run_forever()
|
|
97
|
+
if stop_event.is_set():
|
|
98
|
+
break
|
|
99
|
+
except Exception as exc:
|
|
100
|
+
if not ready.done():
|
|
101
|
+
ready.set_exception(exc)
|
|
102
|
+
else:
|
|
103
|
+
LOGGER.exception("Codex loopback proxy thread crashed")
|
|
104
|
+
finally:
|
|
105
|
+
try:
|
|
106
|
+
loop.run_until_complete(self._async_stop())
|
|
107
|
+
except Exception:
|
|
108
|
+
LOGGER.exception("Codex loopback proxy cleanup failed")
|
|
109
|
+
try:
|
|
110
|
+
loop.close()
|
|
111
|
+
except Exception:
|
|
112
|
+
pass
|
|
113
|
+
self._loop = None
|
|
114
|
+
|
|
115
|
+
self._thread = threading.Thread(
|
|
116
|
+
target=_thread_main,
|
|
117
|
+
name="portacode-codex-loopback",
|
|
118
|
+
daemon=True,
|
|
119
|
+
)
|
|
120
|
+
self._thread.start()
|
|
121
|
+
await asyncio.wrap_future(ready)
|
|
122
|
+
|
|
123
|
+
async def stop(self) -> None:
|
|
124
|
+
thread = self._thread
|
|
125
|
+
loop = self._loop
|
|
126
|
+
stop_event = self._stop_event
|
|
127
|
+
if thread is None:
|
|
128
|
+
return
|
|
129
|
+
if stop_event is not None:
|
|
130
|
+
stop_event.set()
|
|
131
|
+
if loop is not None and loop.is_running():
|
|
132
|
+
loop.call_soon_threadsafe(loop.stop)
|
|
133
|
+
# Prefer to_thread when available; keep a 3.8-safe fallback.
|
|
134
|
+
try:
|
|
135
|
+
await asyncio.to_thread(thread.join, 5.0)
|
|
136
|
+
except AttributeError:
|
|
137
|
+
loop = asyncio.get_running_loop()
|
|
138
|
+
await loop.run_in_executor(None, thread.join, 5.0)
|
|
139
|
+
self._thread = None
|
|
140
|
+
self._stop_event = None
|
|
141
|
+
|
|
142
|
+
async def _async_start(self) -> None:
|
|
143
|
+
if self._server is not None:
|
|
144
|
+
return
|
|
145
|
+
self._upstream_sem = asyncio.Semaphore(MAX_CONCURRENT_UPSTREAM)
|
|
146
|
+
self._client = httpx.AsyncClient(
|
|
147
|
+
timeout=_UPSTREAM_TIMEOUT,
|
|
148
|
+
follow_redirects=False,
|
|
149
|
+
http2=False,
|
|
150
|
+
)
|
|
151
|
+
try:
|
|
152
|
+
self._server = await asyncio.start_server(
|
|
153
|
+
self._handle_client,
|
|
154
|
+
host=CODEX_LOOPBACK_HOST,
|
|
155
|
+
port=CODEX_LOOPBACK_PORT,
|
|
156
|
+
limit=MAX_REQUEST_BYTES + 16 * 1024,
|
|
157
|
+
reuse_address=True,
|
|
158
|
+
)
|
|
159
|
+
except OSError:
|
|
160
|
+
LOGGER.exception(
|
|
161
|
+
"Failed to bind Codex loopback proxy on %s:%s",
|
|
162
|
+
CODEX_LOOPBACK_HOST,
|
|
163
|
+
CODEX_LOOPBACK_PORT,
|
|
164
|
+
)
|
|
165
|
+
await self._async_stop()
|
|
166
|
+
raise
|
|
167
|
+
LOGGER.info(
|
|
168
|
+
"Codex loopback proxy listening on http://%s:%s/v1 (isolated thread, gateway=%s)",
|
|
169
|
+
CODEX_LOOPBACK_HOST,
|
|
170
|
+
CODEX_LOOPBACK_PORT,
|
|
171
|
+
self.gateway_url,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
async def _async_stop(self) -> None:
|
|
175
|
+
server = self._server
|
|
176
|
+
self._server = None
|
|
177
|
+
if server is not None:
|
|
178
|
+
server.close()
|
|
179
|
+
try:
|
|
180
|
+
await server.wait_closed()
|
|
181
|
+
except Exception:
|
|
182
|
+
LOGGER.debug("wait_closed for loopback server failed", exc_info=True)
|
|
183
|
+
client = self._client
|
|
184
|
+
self._client = None
|
|
185
|
+
if client is not None:
|
|
186
|
+
try:
|
|
187
|
+
await client.aclose()
|
|
188
|
+
except Exception:
|
|
189
|
+
LOGGER.debug("Closing upstream httpx client failed", exc_info=True)
|
|
190
|
+
|
|
191
|
+
def _signature_headers(self, method: str, path: str, body: bytes) -> Dict[str, str]:
|
|
192
|
+
timestamp = str(int(time.time()))
|
|
193
|
+
nonce = secrets.token_urlsafe(24)
|
|
194
|
+
body_hash = hashlib.sha256(body).hexdigest()
|
|
195
|
+
canonical = f"{method}\n{path}\n{timestamp}\n{nonce}\n{body_hash}".encode("ascii")
|
|
196
|
+
signature = base64.b64encode(self.keypair.sign_bytes(canonical)).decode("ascii")
|
|
197
|
+
return {
|
|
198
|
+
"X-Portacode-Device-Key": self.keypair.public_key_der_b64(),
|
|
199
|
+
"X-Portacode-Timestamp": timestamp,
|
|
200
|
+
"X-Portacode-Nonce": nonce,
|
|
201
|
+
"X-Portacode-Signature": signature,
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
def _bump_active(self, delta: int) -> int:
|
|
205
|
+
with self._active_lock:
|
|
206
|
+
self._active_requests = max(0, self._active_requests + delta)
|
|
207
|
+
return self._active_requests
|
|
208
|
+
|
|
209
|
+
async def _handle_client(
|
|
210
|
+
self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter
|
|
211
|
+
) -> None:
|
|
212
|
+
req_id = uuid.uuid4().hex[:10]
|
|
213
|
+
peer = writer.get_extra_info("peername")
|
|
214
|
+
started = time.monotonic()
|
|
215
|
+
active = self._bump_active(1)
|
|
216
|
+
method = path = "-"
|
|
217
|
+
try:
|
|
218
|
+
method, path, headers, body = await self._read_request(reader)
|
|
219
|
+
LOGGER.info(
|
|
220
|
+
"codex-proxy[%s] %s %s bytes=%s peer=%s active=%s",
|
|
221
|
+
req_id,
|
|
222
|
+
method,
|
|
223
|
+
path,
|
|
224
|
+
len(body),
|
|
225
|
+
peer,
|
|
226
|
+
active,
|
|
227
|
+
)
|
|
228
|
+
if method == "GET" and path == "/health":
|
|
229
|
+
payload = {
|
|
230
|
+
"ok": True,
|
|
231
|
+
"service": "portacode-codex-loopback",
|
|
232
|
+
"active_requests": active,
|
|
233
|
+
"gateway": self.gateway_url,
|
|
234
|
+
}
|
|
235
|
+
await self._write_json(
|
|
236
|
+
writer, 200, json.dumps(payload, separators=(",", ":")).encode("utf-8")
|
|
237
|
+
)
|
|
238
|
+
return
|
|
239
|
+
if method not in {"GET", "POST"} or path not in ALLOWED_PATHS:
|
|
240
|
+
await self._write_error(writer, 404, "Unsupported local Codex endpoint")
|
|
241
|
+
return
|
|
242
|
+
if method == "GET" and path != "/v1/models":
|
|
243
|
+
await self._write_error(writer, 405, "Method not allowed")
|
|
244
|
+
return
|
|
245
|
+
if method == "POST" and path != "/v1/responses":
|
|
246
|
+
await self._write_error(writer, 405, "Method not allowed")
|
|
247
|
+
return
|
|
248
|
+
await self._forward(writer, method, path, headers, body, req_id=req_id)
|
|
249
|
+
except asyncio.TimeoutError:
|
|
250
|
+
LOGGER.error(
|
|
251
|
+
"codex-proxy[%s] timed out after %.1fs method=%s path=%s",
|
|
252
|
+
req_id,
|
|
253
|
+
time.monotonic() - started,
|
|
254
|
+
method,
|
|
255
|
+
path,
|
|
256
|
+
)
|
|
257
|
+
try:
|
|
258
|
+
await self._write_error(writer, 504, "Portacode Codex proxy timed out talking to gateway")
|
|
259
|
+
except Exception:
|
|
260
|
+
pass
|
|
261
|
+
except ValueError as exc:
|
|
262
|
+
LOGGER.warning("codex-proxy[%s] bad request: %s", req_id, exc)
|
|
263
|
+
try:
|
|
264
|
+
await self._write_error(writer, 400, str(exc))
|
|
265
|
+
except Exception:
|
|
266
|
+
pass
|
|
267
|
+
except Exception:
|
|
268
|
+
LOGGER.exception(
|
|
269
|
+
"codex-proxy[%s] request failed after %.1fs method=%s path=%s",
|
|
270
|
+
req_id,
|
|
271
|
+
time.monotonic() - started,
|
|
272
|
+
method,
|
|
273
|
+
path,
|
|
274
|
+
)
|
|
275
|
+
try:
|
|
276
|
+
await self._write_error(writer, 502, "Portacode Codex proxy request failed")
|
|
277
|
+
except Exception:
|
|
278
|
+
pass
|
|
279
|
+
finally:
|
|
280
|
+
remaining = self._bump_active(-1)
|
|
281
|
+
LOGGER.info(
|
|
282
|
+
"codex-proxy[%s] done in %.1fs active=%s",
|
|
283
|
+
req_id,
|
|
284
|
+
time.monotonic() - started,
|
|
285
|
+
remaining,
|
|
286
|
+
)
|
|
287
|
+
try:
|
|
288
|
+
writer.close()
|
|
289
|
+
await asyncio.wait_for(writer.wait_closed(), timeout=2.0)
|
|
290
|
+
except Exception:
|
|
291
|
+
pass
|
|
292
|
+
|
|
293
|
+
async def _read_request(
|
|
294
|
+
self, reader: asyncio.StreamReader
|
|
295
|
+
) -> Tuple[str, str, Dict[str, str], bytes]:
|
|
296
|
+
request_line = (await asyncio.wait_for(reader.readline(), timeout=30.0)).decode("latin-1").strip()
|
|
297
|
+
parts = request_line.split(" ")
|
|
298
|
+
if len(parts) != 3:
|
|
299
|
+
raise ValueError("Malformed HTTP request")
|
|
300
|
+
method, target, _version = parts
|
|
301
|
+
path = target.split("?", 1)[0]
|
|
302
|
+
headers: Dict[str, str] = {}
|
|
303
|
+
while True:
|
|
304
|
+
line = await asyncio.wait_for(reader.readline(), timeout=30.0)
|
|
305
|
+
if line in {b"\r\n", b"\n", b""}:
|
|
306
|
+
break
|
|
307
|
+
name, separator, value = line.decode("latin-1").partition(":")
|
|
308
|
+
if not separator:
|
|
309
|
+
raise ValueError("Malformed HTTP header")
|
|
310
|
+
headers[name.strip().lower()] = value.strip()
|
|
311
|
+
if headers.get("transfer-encoding", "").lower() == "chunked":
|
|
312
|
+
raise ValueError("Chunked request bodies are not supported")
|
|
313
|
+
length = int(headers.get("content-length", "0"))
|
|
314
|
+
if length < 0 or length > MAX_REQUEST_BYTES:
|
|
315
|
+
raise ValueError("Request body exceeds the local Codex proxy limit")
|
|
316
|
+
body = (
|
|
317
|
+
await asyncio.wait_for(reader.readexactly(length), timeout=60.0) if length else b""
|
|
318
|
+
)
|
|
319
|
+
return method.upper(), path, headers, body
|
|
320
|
+
|
|
321
|
+
def _collect_response_headers(self, response: httpx.Response) -> Dict[str, str]:
|
|
322
|
+
out: Dict[str, str] = {
|
|
323
|
+
"Content-Type": response.headers.get("content-type", "application/json"),
|
|
324
|
+
"Cache-Control": "no-store",
|
|
325
|
+
"Connection": "close",
|
|
326
|
+
}
|
|
327
|
+
for name, value in response.headers.items():
|
|
328
|
+
key = name.lower()
|
|
329
|
+
if key in FORWARD_RESPONSE_HEADERS and key not in {"content-type", "cache-control"}:
|
|
330
|
+
out[name] = value
|
|
331
|
+
return out
|
|
332
|
+
|
|
333
|
+
def _note_resets_from_error(self, response: httpx.Response, body: bytes) -> None:
|
|
334
|
+
resets_at = None
|
|
335
|
+
header_val = response.headers.get("x-portacode-resets-at") or response.headers.get(
|
|
336
|
+
"x-codex-primary-reset-at"
|
|
337
|
+
)
|
|
338
|
+
if header_val:
|
|
339
|
+
try:
|
|
340
|
+
resets_at = int(header_val)
|
|
341
|
+
except ValueError:
|
|
342
|
+
resets_at = None
|
|
343
|
+
if resets_at is None and body:
|
|
344
|
+
try:
|
|
345
|
+
payload = json.loads(body.decode("utf-8", errors="replace"))
|
|
346
|
+
except json.JSONDecodeError:
|
|
347
|
+
payload = None
|
|
348
|
+
if isinstance(payload, dict):
|
|
349
|
+
err = payload.get("error") if isinstance(payload.get("error"), dict) else {}
|
|
350
|
+
for key in ("resets_at", "resetsAt"):
|
|
351
|
+
if key in err:
|
|
352
|
+
try:
|
|
353
|
+
resets_at = int(err[key])
|
|
354
|
+
break
|
|
355
|
+
except (TypeError, ValueError):
|
|
356
|
+
pass
|
|
357
|
+
note_usage_limit_resets_at(resets_at)
|
|
358
|
+
|
|
359
|
+
async def _forward(
|
|
360
|
+
self,
|
|
361
|
+
writer: asyncio.StreamWriter,
|
|
362
|
+
method: str,
|
|
363
|
+
path: str,
|
|
364
|
+
request_headers: Dict[str, str],
|
|
365
|
+
body: bytes,
|
|
366
|
+
*,
|
|
367
|
+
req_id: str,
|
|
368
|
+
) -> None:
|
|
369
|
+
headers = self._signature_headers(method, path, body)
|
|
370
|
+
content_type = request_headers.get("content-type")
|
|
371
|
+
if content_type:
|
|
372
|
+
headers["Content-Type"] = content_type
|
|
373
|
+
gateway_path = path[3:] if path.startswith("/v1") else path
|
|
374
|
+
upstream_url = f"{self.gateway_url}/device{gateway_path}"
|
|
375
|
+
client = self._client
|
|
376
|
+
if client is None:
|
|
377
|
+
raise RuntimeError("Upstream HTTP client is not started")
|
|
378
|
+
sem = self._upstream_sem
|
|
379
|
+
if sem is None:
|
|
380
|
+
raise RuntimeError("Upstream semaphore is not started")
|
|
381
|
+
|
|
382
|
+
async with sem:
|
|
383
|
+
LOGGER.info(
|
|
384
|
+
"codex-proxy[%s] upstream %s %s",
|
|
385
|
+
req_id,
|
|
386
|
+
method,
|
|
387
|
+
upstream_url,
|
|
388
|
+
)
|
|
389
|
+
try:
|
|
390
|
+
async with client.stream(
|
|
391
|
+
method,
|
|
392
|
+
upstream_url,
|
|
393
|
+
content=body,
|
|
394
|
+
headers=headers,
|
|
395
|
+
) as response:
|
|
396
|
+
response_headers = self._collect_response_headers(response)
|
|
397
|
+
status_text = response.reason_phrase or "OK"
|
|
398
|
+
LOGGER.info(
|
|
399
|
+
"codex-proxy[%s] upstream status=%s content-type=%s",
|
|
400
|
+
req_id,
|
|
401
|
+
response.status_code,
|
|
402
|
+
response.headers.get("content-type", ""),
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
if response.status_code >= 400:
|
|
406
|
+
err_body = await response.aread()
|
|
407
|
+
self._note_resets_from_error(response, err_body)
|
|
408
|
+
LOGGER.warning(
|
|
409
|
+
"codex-proxy[%s] upstream error status=%s body_prefix=%r",
|
|
410
|
+
req_id,
|
|
411
|
+
response.status_code,
|
|
412
|
+
err_body[:300],
|
|
413
|
+
)
|
|
414
|
+
response_headers["Content-Length"] = str(len(err_body))
|
|
415
|
+
writer.write(
|
|
416
|
+
f"HTTP/1.1 {response.status_code} {status_text}\r\n".encode("ascii")
|
|
417
|
+
)
|
|
418
|
+
for name, value in response_headers.items():
|
|
419
|
+
writer.write(f"{name}: {value}\r\n".encode("latin-1"))
|
|
420
|
+
writer.write(b"\r\n")
|
|
421
|
+
writer.write(err_body)
|
|
422
|
+
await writer.drain()
|
|
423
|
+
return
|
|
424
|
+
|
|
425
|
+
if response.headers.get("content-length"):
|
|
426
|
+
response_headers["Content-Length"] = response.headers["content-length"]
|
|
427
|
+
writer.write(
|
|
428
|
+
f"HTTP/1.1 {response.status_code} {status_text}\r\n".encode("ascii")
|
|
429
|
+
)
|
|
430
|
+
for name, value in response_headers.items():
|
|
431
|
+
writer.write(f"{name}: {value}\r\n".encode("latin-1"))
|
|
432
|
+
writer.write(b"\r\n")
|
|
433
|
+
bytes_out = 0
|
|
434
|
+
async for chunk in response.aiter_raw():
|
|
435
|
+
if not chunk:
|
|
436
|
+
continue
|
|
437
|
+
bytes_out += len(chunk)
|
|
438
|
+
writer.write(chunk)
|
|
439
|
+
await writer.drain()
|
|
440
|
+
LOGGER.info(
|
|
441
|
+
"codex-proxy[%s] streamed %s bytes to client",
|
|
442
|
+
req_id,
|
|
443
|
+
bytes_out,
|
|
444
|
+
)
|
|
445
|
+
except httpx.TimeoutException as exc:
|
|
446
|
+
LOGGER.error("codex-proxy[%s] upstream timeout: %s", req_id, exc)
|
|
447
|
+
raise asyncio.TimeoutError(str(exc)) from exc
|
|
448
|
+
except httpx.HTTPError as exc:
|
|
449
|
+
LOGGER.error("codex-proxy[%s] upstream http error: %s", req_id, exc)
|
|
450
|
+
raise
|
|
451
|
+
|
|
452
|
+
async def _write_error(self, writer: asyncio.StreamWriter, status: int, message: str) -> None:
|
|
453
|
+
body = ('{"error":{"message":"' + message.replace('"', "'") + '"}}').encode("utf-8")
|
|
454
|
+
await self._write_json(writer, status, body)
|
|
455
|
+
|
|
456
|
+
async def _write_json(self, writer: asyncio.StreamWriter, status: int, body: bytes) -> None:
|
|
457
|
+
reason = "OK" if 200 <= status < 300 else "Error"
|
|
458
|
+
writer.write(
|
|
459
|
+
(
|
|
460
|
+
f"HTTP/1.1 {status} {reason}\r\n"
|
|
461
|
+
"Content-Type: application/json\r\n"
|
|
462
|
+
"Cache-Control: no-store\r\n"
|
|
463
|
+
f"Content-Length: {len(body)}\r\n"
|
|
464
|
+
"Connection: close\r\n\r\n"
|
|
465
|
+
).encode("ascii")
|
|
466
|
+
+ body
|
|
467
|
+
)
|
|
468
|
+
await writer.drain()
|
|
@@ -170,6 +170,148 @@ def read_codex_env_file(path: Optional[Path] = None) -> Dict[str, str]:
|
|
|
170
170
|
return values
|
|
171
171
|
|
|
172
172
|
|
|
173
|
+
def _runtime_user_home() -> Path:
|
|
174
|
+
try:
|
|
175
|
+
from portacode.connection.handlers.runtime_user import get_runtime_user_home
|
|
176
|
+
|
|
177
|
+
return Path(get_runtime_user_home())
|
|
178
|
+
except Exception:
|
|
179
|
+
return Path.home()
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def resolve_codex_home() -> Path:
|
|
183
|
+
"""Return the Codex state directory shared by app-server and interactive CLI.
|
|
184
|
+
|
|
185
|
+
Web chat and ``codex resume`` must use the same store. When the Portacode
|
|
186
|
+
agent runs as root it previously defaulted to ``/root/.codex`` while IDE
|
|
187
|
+
terminals (as the workspace user) used ``$HOME/.codex`` — so the CLI could
|
|
188
|
+
not see web-created chats.
|
|
189
|
+
"""
|
|
190
|
+
preferred = _runtime_user_home() / ".codex"
|
|
191
|
+
explicit = (os.environ.get("CODEX_HOME") or "").strip()
|
|
192
|
+
if not explicit:
|
|
193
|
+
return preferred
|
|
194
|
+
|
|
195
|
+
path = Path(explicit).expanduser()
|
|
196
|
+
# Remap stale root homes so CLI (runtime user) and app-server stay aligned.
|
|
197
|
+
try:
|
|
198
|
+
from portacode.connection.handlers.runtime_user import get_default_runtime_user
|
|
199
|
+
|
|
200
|
+
runtime_user = get_default_runtime_user()
|
|
201
|
+
except Exception:
|
|
202
|
+
runtime_user = ""
|
|
203
|
+
if (
|
|
204
|
+
hasattr(os, "geteuid")
|
|
205
|
+
and os.geteuid() == 0
|
|
206
|
+
and runtime_user
|
|
207
|
+
and runtime_user != "root"
|
|
208
|
+
and (str(path) == "/root/.codex" or str(path).startswith("/root/.codex/"))
|
|
209
|
+
):
|
|
210
|
+
return preferred
|
|
211
|
+
return path
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _persist_codex_home_env(codex_home: Path) -> None:
|
|
215
|
+
"""Ensure interactive shells (profile.d) pick up the shared CODEX_HOME."""
|
|
216
|
+
if platform.system().lower() != "linux":
|
|
217
|
+
return
|
|
218
|
+
path = Path(CODEX_ENV_PATH)
|
|
219
|
+
desired = f"CODEX_HOME={codex_home}"
|
|
220
|
+
try:
|
|
221
|
+
existing = path.read_text(encoding="utf-8") if path.is_file() else ""
|
|
222
|
+
except OSError:
|
|
223
|
+
return
|
|
224
|
+
lines = existing.splitlines()
|
|
225
|
+
out: list[str] = []
|
|
226
|
+
found = False
|
|
227
|
+
for line in lines:
|
|
228
|
+
if line.startswith("CODEX_HOME="):
|
|
229
|
+
if not found:
|
|
230
|
+
out.append(desired)
|
|
231
|
+
found = True
|
|
232
|
+
continue
|
|
233
|
+
out.append(line)
|
|
234
|
+
if not found:
|
|
235
|
+
if out and out[-1] != "":
|
|
236
|
+
out.append("")
|
|
237
|
+
out.append(desired)
|
|
238
|
+
body = "\n".join(out).rstrip() + "\n"
|
|
239
|
+
if body == existing:
|
|
240
|
+
return
|
|
241
|
+
script = (
|
|
242
|
+
f"install -d -m 755 /etc/portacode && "
|
|
243
|
+
f"printf '%s' {shlex.quote(body)} > {shlex.quote(str(path))} && "
|
|
244
|
+
f"chmod 644 {shlex.quote(str(path))}"
|
|
245
|
+
)
|
|
246
|
+
try:
|
|
247
|
+
_run([*_sudo_prefix(), "sh", "-c", script])
|
|
248
|
+
except Exception:
|
|
249
|
+
try:
|
|
250
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
251
|
+
path.write_text(body, encoding="utf-8")
|
|
252
|
+
path.chmod(0o644)
|
|
253
|
+
except OSError:
|
|
254
|
+
pass
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def ensure_codex_home() -> Path:
|
|
258
|
+
"""Create CODEX_HOME and seed it from /root/.codex sessions when needed."""
|
|
259
|
+
import pwd
|
|
260
|
+
import shutil
|
|
261
|
+
|
|
262
|
+
codex_home = resolve_codex_home()
|
|
263
|
+
codex_home.mkdir(parents=True, exist_ok=True)
|
|
264
|
+
sessions_dir = codex_home / "sessions"
|
|
265
|
+
sessions_dir.mkdir(parents=True, exist_ok=True)
|
|
266
|
+
|
|
267
|
+
root_codex = Path("/root/.codex")
|
|
268
|
+
root_sessions = root_codex / "sessions"
|
|
269
|
+
if (
|
|
270
|
+
hasattr(os, "geteuid")
|
|
271
|
+
and os.geteuid() == 0
|
|
272
|
+
and root_sessions.is_dir()
|
|
273
|
+
and codex_home.resolve() != root_codex.resolve()
|
|
274
|
+
):
|
|
275
|
+
for src in root_sessions.rglob("*.jsonl"):
|
|
276
|
+
try:
|
|
277
|
+
rel = src.relative_to(root_sessions)
|
|
278
|
+
except ValueError:
|
|
279
|
+
continue
|
|
280
|
+
dest = sessions_dir / rel
|
|
281
|
+
if dest.exists():
|
|
282
|
+
continue
|
|
283
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
284
|
+
try:
|
|
285
|
+
shutil.copy2(src, dest)
|
|
286
|
+
except OSError:
|
|
287
|
+
continue
|
|
288
|
+
for name in ("config.toml", "auth.json"):
|
|
289
|
+
src = root_codex / name
|
|
290
|
+
dest = codex_home / name
|
|
291
|
+
if src.is_file() and not dest.exists():
|
|
292
|
+
try:
|
|
293
|
+
shutil.copy2(src, dest)
|
|
294
|
+
except OSError:
|
|
295
|
+
pass
|
|
296
|
+
try:
|
|
297
|
+
from portacode.connection.handlers.runtime_user import get_default_runtime_user
|
|
298
|
+
|
|
299
|
+
user = get_default_runtime_user()
|
|
300
|
+
pw_entry = pwd.getpwnam(user)
|
|
301
|
+
for path in [codex_home, *codex_home.rglob("*")]:
|
|
302
|
+
try:
|
|
303
|
+
os.chown(path, pw_entry.pw_uid, pw_entry.pw_gid)
|
|
304
|
+
except OSError:
|
|
305
|
+
pass
|
|
306
|
+
except Exception:
|
|
307
|
+
pass
|
|
308
|
+
try:
|
|
309
|
+
_persist_codex_home_env(codex_home)
|
|
310
|
+
except Exception:
|
|
311
|
+
pass
|
|
312
|
+
return codex_home
|
|
313
|
+
|
|
314
|
+
|
|
173
315
|
def apply_codex_env_to_mapping(
|
|
174
316
|
env: MutableMapping[str, str],
|
|
175
317
|
path: Optional[Path] = None,
|
|
@@ -187,6 +329,9 @@ def apply_codex_env_to_mapping(
|
|
|
187
329
|
# Always guarantee the local proxy sentinel unless a non-empty value exists.
|
|
188
330
|
if not (env.get(OPENAI_API_KEY_ENV) or "").strip():
|
|
189
331
|
env[OPENAI_API_KEY_ENV] = file_values.get(OPENAI_API_KEY_ENV) or LOCAL_SENTINEL
|
|
332
|
+
# Keep interactive CLI and app-server on the same session store.
|
|
333
|
+
if not (env.get("CODEX_HOME") or "").strip():
|
|
334
|
+
env["CODEX_HOME"] = str(resolve_codex_home())
|
|
190
335
|
|
|
191
336
|
|
|
192
337
|
def build_codex_subprocess_env(
|
|
@@ -196,6 +341,10 @@ def build_codex_subprocess_env(
|
|
|
196
341
|
"""Environment for Codex CLI / app-server subprocesses."""
|
|
197
342
|
env = dict(base or os.environ)
|
|
198
343
|
apply_codex_env_to_mapping(env, path=path)
|
|
344
|
+
try:
|
|
345
|
+
env["CODEX_HOME"] = str(ensure_codex_home())
|
|
346
|
+
except Exception:
|
|
347
|
+
env.setdefault("CODEX_HOME", str(resolve_codex_home()))
|
|
199
348
|
return env
|
|
200
349
|
|
|
201
350
|
|
|
@@ -241,9 +390,13 @@ def _set_local_sentinel() -> None:
|
|
|
241
390
|
if system == "linux":
|
|
242
391
|
# Portacode terminals load this directly, including sessions started by
|
|
243
392
|
# a long-running agent that pre-dates the prepare command.
|
|
393
|
+
codex_home = str(resolve_codex_home())
|
|
244
394
|
managed_setup = (
|
|
245
395
|
"install -d -m 755 /etc/portacode && "
|
|
246
|
-
f"printf '%s\\n'
|
|
396
|
+
f"printf '%s\\n' "
|
|
397
|
+
f"'{OPENAI_API_KEY_ENV}={LOCAL_SENTINEL}' "
|
|
398
|
+
f"'CODEX_HOME={codex_home}' "
|
|
399
|
+
f"> {CODEX_ENV_PATH} && "
|
|
247
400
|
f"chmod 644 {CODEX_ENV_PATH} && "
|
|
248
401
|
"printf '%s\\n' '# Managed by portacode prepare codex.' "
|
|
249
402
|
f"'if [ -r {CODEX_ENV_PATH} ]; then' ' set -a' "
|