portacode 1.5.17.dev5__tar.gz → 1.5.18.dev0__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.dev5 → portacode-1.5.18.dev0}/PKG-INFO +1 -1
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/_version.py +2 -2
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/codex_app_server.py +70 -11
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/codex_handlers.py +83 -10
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/project_state/git_manager.py +2 -0
- portacode-1.5.18.dev0/portacode/link_capture/__pycache__/__init__.cpython-38.pyc +0 -0
- portacode-1.5.18.dev0/portacode/link_capture/bin/__pycache__/link_capture_wrapper.cpython-311.pyc +0 -0
- portacode-1.5.18.dev0/portacode/link_capture/bin/__pycache__/link_capture_wrapper.cpython-38.pyc +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/tests/test_codex_app_server.py +30 -1
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/tests/test_codex_handlers.py +57 -0
- portacode-1.5.18.dev0/portacode/tests/test_codex_loopback_proxy.py +137 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode.egg-info/PKG-INFO +1 -1
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode.egg-info/SOURCES.txt +4 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/.claude/agents/communication-manager.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/.claude/settings.local.json +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/.gitignore +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/.gitmodules +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/LICENSE +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/MANIFEST.in +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/Makefile +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/README.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/backup.sh +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/build_android.sh +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/connect.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/connect.sh +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/docker-compose.yaml +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/docs/cloudflared-domain-connect-containers-audit.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/docs/creative-team-brief-portacode.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/docs/device-health-alerting-plan.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/docs/devops-messaging-ab-tests.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/docs/homepage-dashboard-positioning-fixes.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/docs/images/device-transfer-button.png +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/docs/images/device-transfer-modal.png +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/docs/images/pair-device-button.png +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/docs/images/pairing-request.png +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/docs/images/student-workspace.png +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/docs/template-guide.html +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/README.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/simple_device/Dockerfile +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/simple_device/README.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/simple_device/data/device-01/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/simple_device/data/device-01/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/simple_device/docker-compose.yaml +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/Dockerfile +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/README.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/.local/share/portacode/run/gateway.pid +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/.gitignore +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/README.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/db.sqlite3 +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/__init__.cpython-311.pyc +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/settings.cpython-311.pyc +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/urls.cpython-311.pyc +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/wsgi.cpython-311.pyc +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/manage.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/requirements.txt +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/__init__.cpython-311.pyc +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/admin.cpython-311.pyc +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/apps.cpython-311.pyc +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/menu.cpython-311.pyc +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/models.cpython-311.pyc +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/urls.cpython-311.pyc +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/views.cpython-311.pyc +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/admin.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/apps.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/menu.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/migrations/__pycache__/__init__.cpython-311.pyc +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/models.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/tests.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/urls.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/views.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/workspace/README.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/workspace/manage.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/workspace/requirements.txt +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/admin.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/apps.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/menu.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/models.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/tests.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/urls.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/views.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/workspace/README.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/workspace/manage.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/workspace/requirements.txt +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/admin.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/apps.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/menu.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/models.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/tests.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/urls.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/views.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/workspace/README.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/workspace/manage.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/workspace/requirements.txt +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/admin.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/apps.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/menu.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/models.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/tests.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/urls.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/views.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/workspace/README.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/workspace/manage.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/workspace/requirements.txt +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/admin.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/apps.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/menu.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/models.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/tests.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/urls.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/views.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/workspace/README.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/workspace/manage.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/workspace/requirements.txt +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/admin.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/apps.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/menu.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/models.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/tests.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/urls.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/views.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/workspace/README.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/workspace/manage.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/workspace/requirements.txt +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/admin.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/apps.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/menu.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/models.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/tests.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/urls.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/views.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/workspace/README.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/workspace/manage.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/workspace/requirements.txt +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/admin.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/apps.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/menu.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/models.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/tests.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/urls.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/views.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/workspace/README.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/workspace/manage.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/workspace/requirements.txt +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/admin.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/apps.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/menu.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/models.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/tests.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/urls.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/views.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/workspace/README.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/workspace/manage.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/workspace/requirements.txt +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/admin.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/apps.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/menu.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/models.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/tests.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/urls.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/views.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/docker-compose.yaml +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/initial_content/README.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/initial_content/manage.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/initial_content/requirements.txt +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/initial_content/templates/treats/home.html +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/initial_content/treats/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/initial_content/treats/admin.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/initial_content/treats/apps.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/initial_content/treats/menu.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/initial_content/treats/migrations/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/initial_content/treats/models.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/initial_content/treats/tests.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/initial_content/treats/urls.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/initial_content/treats/views.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/instructions/WELCOME.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/README.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/__main__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/cli.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/codex_loopback_proxy.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/codex_prepare.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/codex_usage_limit.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/README.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/client.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/README.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/automation_v2_handlers.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/base.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/chunked_content.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/cloudflare_forwarding.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/cloudflare_tunnel.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/diff_handlers.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/file_handlers.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/file_transfer_handlers.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/project_aware_file_handlers.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/project_state/README.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/project_state/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/project_state/file_system_watcher.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/project_state/handlers.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/project_state/manager.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/project_state/models.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/project_state/test_handlers.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/project_state/utils.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/project_state_handlers.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/proxmox_infra.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/registry.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/runtime_user.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/session.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/system_handlers.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/tab_factory.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/terminal_handlers.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/test_cloudflare_forwarding.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/test_proxmox_infra.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/test_runtime_user.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/update_handler.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/multiplex.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/terminal.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/test_webmin_proxy_config.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/webmin_proxy_config.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/data.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/exit_codes.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/keypair.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/__pycache__/__init__.cpython-311.pyc +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/bin/elinks +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/bin/gio-open +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/bin/gnome-open +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/bin/gvfs-open +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/bin/kde-open +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/bin/kfmclient +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/bin/link_capture_exec.sh +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/bin/link_capture_wrapper.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/bin/links +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/bin/links2 +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/bin/lynx +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/bin/mate-open +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/bin/netsurf +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/bin/sensible-browser +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/bin/w3m +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/bin/x-www-browser +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/bin/xdg-open +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/logging_categories.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/pairing.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/restart.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/service.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/static/js/test-ntp-clock.html +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/static/js/utils/ntp-clock.js +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/test_runtime_paths.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/test_service.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/test_terminal_exposed_services.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/tests/test_codex_prepare.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/tunneling/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/tunneling/cloudflared_login.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/tunneling/ensure_cloudflared.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/tunneling/ensure_pyyaml.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/tunneling/forwarding_state.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/tunneling/get_domain.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/tunneling/privileged.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/tunneling/service_install.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/tunneling/state.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/updater.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/utils/NTP_ARCHITECTURE.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/utils/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/utils/diff_apply.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/utils/diff_renderer.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/utils/ntp_clock.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/utils/runtime_paths.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode.egg-info/dependency_links.txt +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode.egg-info/entry_points.txt +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode.egg-info/requires.txt +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode.egg-info/top_level.txt +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode_for_school/.gitignore +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode_for_school/README.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode_for_school/persistent_workspace/Dockerfile +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode_for_school/persistent_workspace/README.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode_for_school/persistent_workspace/entrypoint.sh +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode_for_school/persistent_workspace/manage_classroom.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode_for_school/temporary_workspace/Dockerfile +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode_for_school/temporary_workspace/README.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode_for_school/temporary_workspace/docker-compose.yaml +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode_for_school/temporary_workspace/docs/device_transfere.png +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode_for_school/temporary_workspace/docs/pairing_approval.png +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode_for_school/temporary_workspace/entrypoint.sh +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/pyproject.toml +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/restore.sh +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/run_tests.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/scripts/diagnose_codex_gateway.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/setup.cfg +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/setup.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/test.sh +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/test_modules/README.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/test_modules/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/test_modules/test_device_online.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/test_modules/test_file_operations.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/test_modules/test_git_status_ui.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/test_modules/test_login_flow.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/test_modules/test_navigate_testing_folder.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/test_modules/test_play_store_screenshots.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/test_modules/test_terminal_buffer_performance.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/test_modules/test_terminal_interaction.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/test_modules/test_terminal_loading_race_condition.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/test_modules/test_terminal_start.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/test_request_id.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/test_session_terminal_shutdown.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/testing_framework/.env.example +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/testing_framework/README.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/testing_framework/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/testing_framework/cli.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/testing_framework/core/__init__.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/testing_framework/core/base_test.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/testing_framework/core/cli_manager.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/testing_framework/core/hierarchical_runner.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/testing_framework/core/playwright_manager.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/testing_framework/core/runner.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/testing_framework/core/shared_cli_manager.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/testing_framework/core/test_discovery.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/testing_framework/requirements.txt +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/todo/UI_UX/opening_a_file_on_desktop_results_in_nothing.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/todo/UI_UX/server_occasionally_stops_communicating_with_all_devices.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/todo/agent_context_management.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/todo/django_server_time_sync.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/todo/issues/device_performance_degradation.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/todo/issues/git_data_not_captured_in_proxmox.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/todo/issues/indefinite_resource_loading.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/todo/issues/portacode_service_silently_down.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/todo/issues/premature_terminal_exit.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/todo/issues/project_cpu_hotspots.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/todo/issues/terminals_exit_upon_starting.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/todo/issues/websocket_client_silently_dead.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/todo/issues/wrong_item_classification_on_client_side.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/todo/smartphone_terminal_input_frustrations.md +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/tools/generate_play_store_assets.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/tools/pairing_tester.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/tools/run_screenshot_suite.sh +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/tools/test_python_ntp_clock.py +0 -0
- {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/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.
|
|
22
|
-
__version_tuple__ = version_tuple = (1, 5,
|
|
21
|
+
__version__ = version = '1.5.18.dev0'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 5, 18, 'dev0')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import asyncio
|
|
6
6
|
import json
|
|
7
7
|
import logging
|
|
8
|
+
import os
|
|
8
9
|
import shutil
|
|
9
10
|
import uuid
|
|
10
11
|
from pathlib import Path
|
|
@@ -32,6 +33,7 @@ class CodexAppServer:
|
|
|
32
33
|
command: Optional[List[str]] = None,
|
|
33
34
|
client_info: Optional[Dict[str, Any]] = None,
|
|
34
35
|
on_notification: Optional[Callable[[str, Dict[str, Any]], Awaitable[None]]] = None,
|
|
36
|
+
on_unexpected_exit: Optional[Callable[[Optional[int]], Awaitable[None]]] = None,
|
|
35
37
|
_process_factory: Optional[Callable[..., Awaitable[Any]]] = None,
|
|
36
38
|
) -> None:
|
|
37
39
|
self.command = command or list(DEFAULT_CODEX_COMMAND)
|
|
@@ -43,6 +45,7 @@ class CodexAppServer:
|
|
|
43
45
|
client_info = {"name": "portacode", "version": _portacode_version}
|
|
44
46
|
self.client_info = client_info
|
|
45
47
|
self.on_notification = on_notification
|
|
48
|
+
self.on_unexpected_exit = on_unexpected_exit
|
|
46
49
|
self._process_factory = _process_factory or asyncio.create_subprocess_exec
|
|
47
50
|
|
|
48
51
|
self._proc: Optional[asyncio.subprocess.Process] = None
|
|
@@ -74,12 +77,6 @@ class CodexAppServer:
|
|
|
74
77
|
|
|
75
78
|
async def _spawn(self) -> None:
|
|
76
79
|
"""Spawn the subprocess and perform the JSON-RPC initialize handshake."""
|
|
77
|
-
exe = shutil.which(self.command[0]) or self.command[0]
|
|
78
|
-
if not shutil.which(self.command[0]):
|
|
79
|
-
raise CodexAppServerError(
|
|
80
|
-
f"{self.command[0]} not found on PATH. Run `portacode prepare codex` on this device."
|
|
81
|
-
)
|
|
82
|
-
|
|
83
80
|
# systemd services do not load shell profile env; merge /etc/portacode/codex.env.
|
|
84
81
|
from portacode.codex_prepare import build_codex_subprocess_env, ensure_codex_home
|
|
85
82
|
from portacode.connection.handlers.runtime_user import (
|
|
@@ -93,9 +90,16 @@ class CodexAppServer:
|
|
|
93
90
|
# (e.g. bishoy), not as the root agent process.
|
|
94
91
|
runtime_user = get_default_runtime_user()
|
|
95
92
|
runtime_home = get_runtime_user_home()
|
|
93
|
+
exe = self._resolve_command_path(self.command[0], runtime_home)
|
|
94
|
+
if not exe:
|
|
95
|
+
raise CodexAppServerError(
|
|
96
|
+
f"{self.command[0]} not found for runtime user {runtime_user}. "
|
|
97
|
+
"Run `portacode prepare codex` on this device."
|
|
98
|
+
)
|
|
96
99
|
child_env["HOME"] = runtime_home
|
|
97
100
|
child_env["USER"] = runtime_user
|
|
98
101
|
child_env["LOGNAME"] = runtime_user
|
|
102
|
+
self._prepend_executable_directory(child_env, exe)
|
|
99
103
|
try:
|
|
100
104
|
child_env["CODEX_HOME"] = str(ensure_codex_home())
|
|
101
105
|
except Exception:
|
|
@@ -123,7 +127,7 @@ class CodexAppServer:
|
|
|
123
127
|
"no_proxy",
|
|
124
128
|
]
|
|
125
129
|
spawn_cmd = wrap_argv_for_user(
|
|
126
|
-
self.command,
|
|
130
|
+
[exe, *self.command[1:]],
|
|
127
131
|
runtime_user,
|
|
128
132
|
preserve_env_names=preserve,
|
|
129
133
|
login=False,
|
|
@@ -382,6 +386,11 @@ class CodexAppServer:
|
|
|
382
386
|
)
|
|
383
387
|
return
|
|
384
388
|
LOGGER.warning("Codex app-server process exited (code %s); restarting", self._proc.returncode)
|
|
389
|
+
if self.on_unexpected_exit is not None:
|
|
390
|
+
try:
|
|
391
|
+
await self.on_unexpected_exit(self._proc.returncode)
|
|
392
|
+
except Exception:
|
|
393
|
+
LOGGER.exception("Codex app-server exit handler failed")
|
|
385
394
|
await self._kill_locked()
|
|
386
395
|
try:
|
|
387
396
|
await self.start()
|
|
@@ -415,19 +424,69 @@ class CodexAppServer:
|
|
|
415
424
|
return self._initialized.is_set()
|
|
416
425
|
|
|
417
426
|
@staticmethod
|
|
418
|
-
def get_binary_path() -> Optional[str]:
|
|
419
|
-
|
|
427
|
+
def get_binary_path(runtime_home: Optional[str] = None) -> Optional[str]:
|
|
428
|
+
"""Find Codex for the service's configured runtime user.
|
|
429
|
+
|
|
430
|
+
A systemd agent normally has a deliberately small PATH. That should
|
|
431
|
+
not hide a Codex CLI installed by the runtime user through nvm or a
|
|
432
|
+
local npm prefix, which is common on developer machines.
|
|
433
|
+
"""
|
|
434
|
+
from portacode.connection.handlers.runtime_user import get_runtime_user_home
|
|
435
|
+
|
|
436
|
+
return CodexAppServer._resolve_command_path(
|
|
437
|
+
"codex", runtime_home or get_runtime_user_home()
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
@staticmethod
|
|
441
|
+
def _resolve_command_path(command: str, runtime_home: Optional[str] = None) -> Optional[str]:
|
|
442
|
+
if not command:
|
|
443
|
+
return None
|
|
444
|
+
resolved = shutil.which(command)
|
|
445
|
+
if resolved:
|
|
446
|
+
return resolved
|
|
447
|
+
|
|
448
|
+
candidate = Path(command).expanduser()
|
|
449
|
+
if candidate.is_absolute() or candidate.parent != Path("."):
|
|
450
|
+
if candidate.is_file() and os.access(str(candidate), os.X_OK):
|
|
451
|
+
return str(candidate)
|
|
452
|
+
return None
|
|
453
|
+
if command not in {"codex", "codex.cmd"}:
|
|
454
|
+
return None
|
|
455
|
+
|
|
456
|
+
home = Path(runtime_home or Path.home()).expanduser()
|
|
457
|
+
candidates = [
|
|
458
|
+
home / ".local" / "bin" / command,
|
|
459
|
+
home / ".npm" / "bin" / command,
|
|
460
|
+
]
|
|
461
|
+
nvm_root = home / ".nvm" / "versions" / "node"
|
|
462
|
+
if nvm_root.is_dir():
|
|
463
|
+
candidates.extend(sorted(nvm_root.glob("*/bin/" + command), reverse=True))
|
|
464
|
+
for candidate in candidates:
|
|
465
|
+
if candidate.is_file() and os.access(str(candidate), os.X_OK):
|
|
466
|
+
return str(candidate)
|
|
467
|
+
return None
|
|
468
|
+
|
|
469
|
+
@staticmethod
|
|
470
|
+
def _prepend_executable_directory(env: Dict[str, str], executable: str) -> None:
|
|
471
|
+
"""Make the Node interpreter beside an npm launcher available to env."""
|
|
472
|
+
executable_dir = str(Path(executable).parent)
|
|
473
|
+
current_path = env.get("PATH") or os.defpath
|
|
474
|
+
entries = current_path.split(os.pathsep)
|
|
475
|
+
if executable_dir not in entries:
|
|
476
|
+
env["PATH"] = os.pathsep.join([executable_dir, current_path])
|
|
420
477
|
|
|
421
478
|
@staticmethod
|
|
422
479
|
def version() -> Optional[str]:
|
|
423
480
|
import subprocess
|
|
424
481
|
|
|
425
|
-
exe =
|
|
482
|
+
exe = CodexAppServer.get_binary_path()
|
|
426
483
|
if not exe:
|
|
427
484
|
return None
|
|
428
485
|
try:
|
|
486
|
+
env = dict(os.environ)
|
|
487
|
+
CodexAppServer._prepend_executable_directory(env, exe)
|
|
429
488
|
result = subprocess.run(
|
|
430
|
-
[exe, "--version"], capture_output=True, text=True, timeout=10
|
|
489
|
+
[exe, "--version"], capture_output=True, text=True, timeout=10, env=env
|
|
431
490
|
)
|
|
432
491
|
return result.stdout.strip() or None
|
|
433
492
|
except Exception:
|
{portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/codex_handlers.py
RENAMED
|
@@ -87,17 +87,23 @@ class _SessionAwareSender:
|
|
|
87
87
|
self.control_channel = control_channel
|
|
88
88
|
self.context = context
|
|
89
89
|
|
|
90
|
-
async def send(
|
|
90
|
+
async def send(
|
|
91
|
+
self,
|
|
92
|
+
payload: Dict[str, Any],
|
|
93
|
+
project_id: Optional[str] = None,
|
|
94
|
+
explicit_sessions: Optional[List[str]] = None,
|
|
95
|
+
) -> None:
|
|
96
|
+
target_sessions = set(explicit_sessions or [])
|
|
91
97
|
client_session_manager = self.context.get("client_session_manager")
|
|
92
98
|
if client_session_manager and client_session_manager.has_interested_clients():
|
|
93
|
-
target_sessions
|
|
94
|
-
if target_sessions:
|
|
95
|
-
payload = dict(payload)
|
|
96
|
-
payload["client_sessions"] = target_sessions
|
|
99
|
+
target_sessions.update(client_session_manager.get_target_sessions(project_id))
|
|
97
100
|
reply_channel = client_session_manager.get_reply_channel_for_compatibility()
|
|
98
101
|
if reply_channel:
|
|
99
102
|
payload = dict(payload)
|
|
100
103
|
payload["reply_channel"] = reply_channel
|
|
104
|
+
if target_sessions:
|
|
105
|
+
payload = dict(payload)
|
|
106
|
+
payload["client_sessions"] = sorted(target_sessions)
|
|
101
107
|
|
|
102
108
|
await self.control_channel.send(payload)
|
|
103
109
|
|
|
@@ -107,14 +113,46 @@ class CodexChatManager:
|
|
|
107
113
|
|
|
108
114
|
def __init__(self, control_channel: Any, context: Dict[str, Any]) -> None:
|
|
109
115
|
self.sender = _SessionAwareSender(control_channel, context)
|
|
110
|
-
self.bridge = CodexAppServer(
|
|
116
|
+
self.bridge = CodexAppServer(
|
|
117
|
+
on_notification=self._on_notification,
|
|
118
|
+
on_unexpected_exit=self._on_bridge_exit,
|
|
119
|
+
)
|
|
111
120
|
self._thread_project: Dict[str, str] = {}
|
|
112
121
|
self._cwd_project: Dict[str, str] = {}
|
|
122
|
+
self._thread_active_turn: Dict[str, str] = {}
|
|
123
|
+
self._thread_sessions: Dict[str, set[str]] = {}
|
|
113
124
|
# Survives page reloads via codex_status so the IDE can restore UI.
|
|
114
125
|
self._prepare_running = False
|
|
115
126
|
self._prepare_step: Optional[str] = None
|
|
116
127
|
self._prepare_error: Optional[str] = None
|
|
117
128
|
|
|
129
|
+
async def _on_bridge_exit(self, returncode: Optional[int]) -> None:
|
|
130
|
+
"""Turn an app-server crash into terminal events for waiting browsers."""
|
|
131
|
+
active = list(self._thread_active_turn.items())
|
|
132
|
+
self._thread_active_turn.clear()
|
|
133
|
+
for thread_id, turn_id in active:
|
|
134
|
+
project_id = self._thread_project.get(thread_id)
|
|
135
|
+
if not project_id:
|
|
136
|
+
continue
|
|
137
|
+
suffix = f" (exit code {returncode})" if returncode is not None else ""
|
|
138
|
+
params = {
|
|
139
|
+
"threadId": thread_id,
|
|
140
|
+
"turnId": turn_id,
|
|
141
|
+
"error": {
|
|
142
|
+
"message": f"The local Codex app-server stopped unexpectedly{suffix}.",
|
|
143
|
+
"additionalDetails": "Portacode is restarting it. Retry the message after it reconnects.",
|
|
144
|
+
},
|
|
145
|
+
}
|
|
146
|
+
await self.sender.send(
|
|
147
|
+
{
|
|
148
|
+
"event": "codex_event",
|
|
149
|
+
"notification": {"method": "error", "params": params},
|
|
150
|
+
"project_id": project_id,
|
|
151
|
+
},
|
|
152
|
+
project_id=project_id,
|
|
153
|
+
explicit_sessions=list(self._thread_sessions.get(thread_id, set())),
|
|
154
|
+
)
|
|
155
|
+
|
|
118
156
|
async def _on_notification(self, method: str, params: Dict[str, Any]) -> None:
|
|
119
157
|
# Attach usage-limit reset timestamp captured by the loopback proxy.
|
|
120
158
|
if method == "error" or (
|
|
@@ -131,6 +169,13 @@ class CodexChatManager:
|
|
|
131
169
|
params["turn"] = turn
|
|
132
170
|
|
|
133
171
|
self._remember_thread_from_params(params)
|
|
172
|
+
thread_id = params.get("threadId") or (params.get("thread") or {}).get("id")
|
|
173
|
+
turn = params.get("turn") if isinstance(params.get("turn"), dict) else {}
|
|
174
|
+
turn_id = params.get("turnId") or turn.get("id")
|
|
175
|
+
if thread_id and method == "turn/started" and turn_id:
|
|
176
|
+
self._thread_active_turn[str(thread_id)] = str(turn_id)
|
|
177
|
+
elif thread_id and method == "turn/completed":
|
|
178
|
+
self._thread_active_turn.pop(str(thread_id), None)
|
|
134
179
|
project_id = self._resolve_project_id(params)
|
|
135
180
|
if not project_id:
|
|
136
181
|
# Unresolved project_id would fan out to every open project tab.
|
|
@@ -144,7 +189,11 @@ class CodexChatManager:
|
|
|
144
189
|
"notification": {"method": method, "params": params},
|
|
145
190
|
"project_id": project_id,
|
|
146
191
|
}
|
|
147
|
-
await self.sender.send(
|
|
192
|
+
await self.sender.send(
|
|
193
|
+
event_payload,
|
|
194
|
+
project_id=project_id,
|
|
195
|
+
explicit_sessions=list(self._thread_sessions.get(str(thread_id), set())) if thread_id else None,
|
|
196
|
+
)
|
|
148
197
|
|
|
149
198
|
def _remember_thread_from_params(self, params: Dict[str, Any]) -> None:
|
|
150
199
|
"""Opportunistically map threadId → project when cwd is already known."""
|
|
@@ -175,6 +224,16 @@ class CodexChatManager:
|
|
|
175
224
|
if cwd and project_id:
|
|
176
225
|
self._cwd_project[cwd] = project_id
|
|
177
226
|
|
|
227
|
+
def subscribe_thread(self, thread_id: str, client_session: Optional[str]) -> None:
|
|
228
|
+
"""Route future thread events to a tab even before session discovery catches up."""
|
|
229
|
+
if not thread_id or not client_session:
|
|
230
|
+
return
|
|
231
|
+
sessions = self._thread_sessions.setdefault(str(thread_id), set())
|
|
232
|
+
sessions.add(str(client_session))
|
|
233
|
+
# Avoid an unbounded collection of stale channel names after many reconnects.
|
|
234
|
+
while len(sessions) > 32:
|
|
235
|
+
sessions.pop()
|
|
236
|
+
|
|
178
237
|
async def ensure_started(self) -> None:
|
|
179
238
|
await self.bridge.start()
|
|
180
239
|
|
|
@@ -544,6 +603,7 @@ class CodexThreadStartHandler(CodexAsyncHandler):
|
|
|
544
603
|
raise CodexAppServerError("thread/start did not return a thread id")
|
|
545
604
|
|
|
546
605
|
manager.record_thread(thread_id, cwd, project_id or cwd)
|
|
606
|
+
manager.subscribe_thread(thread_id, message.get("source_client_session"))
|
|
547
607
|
|
|
548
608
|
payload = {
|
|
549
609
|
"event": "codex_thread_started",
|
|
@@ -576,6 +636,7 @@ class CodexThreadResumeHandler(CodexAsyncHandler):
|
|
|
576
636
|
# those notifications to the requesting project from the outset.
|
|
577
637
|
if project_id:
|
|
578
638
|
manager.record_thread(thread_id, cwd, project_id)
|
|
639
|
+
manager.subscribe_thread(thread_id, message.get("source_client_session"))
|
|
579
640
|
|
|
580
641
|
resume_result, items, active_turn = await _resume_thread_history(
|
|
581
642
|
manager, thread_id=thread_id, cwd=cwd
|
|
@@ -590,6 +651,9 @@ class CodexThreadResumeHandler(CodexAsyncHandler):
|
|
|
590
651
|
}
|
|
591
652
|
if active_turn:
|
|
592
653
|
payload["activeTurn"] = active_turn
|
|
654
|
+
active_turn_id = active_turn.get("id") or active_turn.get("turnId")
|
|
655
|
+
if active_turn_id:
|
|
656
|
+
manager._thread_active_turn[str(thread_id)] = str(active_turn_id)
|
|
593
657
|
if project_id:
|
|
594
658
|
payload["project_id"] = project_id
|
|
595
659
|
return payload
|
|
@@ -623,6 +687,7 @@ class CodexTurnStartHandler(CodexAsyncHandler):
|
|
|
623
687
|
# so the browser cannot miss the event that clears its streaming state.
|
|
624
688
|
if project_id:
|
|
625
689
|
manager.record_thread(thread_id, cwd, project_id)
|
|
690
|
+
manager.subscribe_thread(thread_id, message.get("source_client_session"))
|
|
626
691
|
|
|
627
692
|
params = {
|
|
628
693
|
"threadId": thread_id,
|
|
@@ -637,11 +702,15 @@ class CodexTurnStartHandler(CodexAsyncHandler):
|
|
|
637
702
|
params["summary"] = message["summary"]
|
|
638
703
|
|
|
639
704
|
result = await manager.bridge.call("turn/start", params)
|
|
705
|
+
turn = _turn_from_result(result)
|
|
706
|
+
turn_id = turn.get("id") or turn.get("turnId")
|
|
707
|
+
if turn_id:
|
|
708
|
+
manager._thread_active_turn[str(thread_id)] = str(turn_id)
|
|
640
709
|
|
|
641
710
|
payload = {
|
|
642
711
|
"event": "codex_turn_started",
|
|
643
712
|
"threadId": thread_id,
|
|
644
|
-
"turn":
|
|
713
|
+
"turn": turn,
|
|
645
714
|
}
|
|
646
715
|
if project_id:
|
|
647
716
|
payload["project_id"] = project_id
|
|
@@ -658,12 +727,16 @@ class CodexTurnInterruptHandler(CodexAsyncHandler):
|
|
|
658
727
|
async def execute(self, message: Dict[str, Any]) -> Dict[str, Any]:
|
|
659
728
|
thread_id = message.get("threadId")
|
|
660
729
|
turn_id = message.get("turnId")
|
|
661
|
-
if not thread_id
|
|
662
|
-
raise ValueError("threadId
|
|
730
|
+
if not thread_id:
|
|
731
|
+
raise ValueError("threadId is required")
|
|
663
732
|
|
|
664
733
|
manager = _get_manager(self)
|
|
734
|
+
turn_id = turn_id or manager._thread_active_turn.get(str(thread_id))
|
|
735
|
+
if not turn_id:
|
|
736
|
+
raise ValueError("No active turn was found for this thread")
|
|
665
737
|
await manager.ensure_started()
|
|
666
738
|
await manager.bridge.call("turn/interrupt", {"threadId": thread_id, "turnId": turn_id})
|
|
739
|
+
manager._thread_active_turn.pop(str(thread_id), None)
|
|
667
740
|
|
|
668
741
|
project_id = _project_id(message)
|
|
669
742
|
payload = {"event": "codex_turn_interrupted", "threadId": thread_id, "turnId": turn_id}
|
portacode-1.5.18.dev0/portacode/link_capture/bin/__pycache__/link_capture_wrapper.cpython-311.pyc
ADDED
|
Binary file
|
portacode-1.5.18.dev0/portacode/link_capture/bin/__pycache__/link_capture_wrapper.cpython-38.pyc
ADDED
|
Binary file
|
|
@@ -193,11 +193,40 @@ async def test_spawn_runs_as_runtime_user(tmp_path, monkeypatch):
|
|
|
193
193
|
await bridge.start()
|
|
194
194
|
assert seen
|
|
195
195
|
assert seen[0][:5] == ("/usr/sbin/runuser", "-u", "bishoy", "--preserve-environment", "--")
|
|
196
|
-
assert "codex" in seen[0]
|
|
196
|
+
assert "/usr/bin/codex" in seen[0]
|
|
197
197
|
assert seen_kwargs[0].get("cwd") == str(home)
|
|
198
198
|
await bridge.stop()
|
|
199
199
|
|
|
200
200
|
|
|
201
|
+
@pytest.mark.asyncio
|
|
202
|
+
async def test_spawn_finds_nvm_codex_when_service_path_does_not(tmp_path, monkeypatch):
|
|
203
|
+
"""A systemd PATH must not hide a runtime user's nvm-installed Codex."""
|
|
204
|
+
home = tmp_path / "home"
|
|
205
|
+
codex_bin = home / ".nvm" / "versions" / "node" / "v24.4.1" / "bin"
|
|
206
|
+
codex_bin.mkdir(parents=True)
|
|
207
|
+
codex = codex_bin / "codex"
|
|
208
|
+
codex.write_text("#!/usr/bin/env node\n", encoding="utf-8")
|
|
209
|
+
codex.chmod(0o755)
|
|
210
|
+
monkeypatch.setenv("PATH", "/usr/bin:/bin")
|
|
211
|
+
monkeypatch.setattr(
|
|
212
|
+
"portacode.connection.handlers.runtime_user.get_default_runtime_user",
|
|
213
|
+
lambda message=None: "menas",
|
|
214
|
+
)
|
|
215
|
+
monkeypatch.setattr(
|
|
216
|
+
"portacode.connection.handlers.runtime_user.get_runtime_user_home",
|
|
217
|
+
lambda message=None: str(home),
|
|
218
|
+
)
|
|
219
|
+
monkeypatch.setattr("portacode.codex_prepare.ensure_codex_home", lambda: home / ".codex")
|
|
220
|
+
|
|
221
|
+
proc = FakeProcess([{"jsonrpc": "2.0", "id": 1, "result": {"protocolVersion": "v2"}}])
|
|
222
|
+
bridge = CodexAppServer(_process_factory=_factory_for(proc))
|
|
223
|
+
await bridge.start()
|
|
224
|
+
|
|
225
|
+
assert CodexAppServer.get_binary_path(str(home)) == str(codex)
|
|
226
|
+
assert proc.spawn_kwargs["env"]["PATH"].split(":")[0] == str(codex_bin)
|
|
227
|
+
await bridge.stop()
|
|
228
|
+
|
|
229
|
+
|
|
201
230
|
def test_build_codex_subprocess_env_reads_managed_file(tmp_path):
|
|
202
231
|
from portacode.codex_prepare import build_codex_subprocess_env
|
|
203
232
|
|
|
@@ -330,6 +330,47 @@ async def test_codex_turn_interrupt():
|
|
|
330
330
|
assert manager.bridge.calls[-1] == ("turn/interrupt", {"threadId": "th-1", "turnId": "turn-1"})
|
|
331
331
|
|
|
332
332
|
|
|
333
|
+
@pytest.mark.asyncio
|
|
334
|
+
async def test_codex_turn_interrupt_resolves_active_turn_when_browser_missed_id():
|
|
335
|
+
channel = DummyControlChannel()
|
|
336
|
+
context = _context()
|
|
337
|
+
manager = CodexChatManager(channel, context)
|
|
338
|
+
manager.bridge = FakeBridge()
|
|
339
|
+
manager._thread_active_turn["th-1"] = "turn-active"
|
|
340
|
+
context["codex_manager"] = manager
|
|
341
|
+
|
|
342
|
+
handler = CodexTurnInterruptHandler(channel, context)
|
|
343
|
+
await handler.handle(
|
|
344
|
+
{"cmd": "codex_turn_interrupt", "project_id": "p-1", "threadId": "th-1"},
|
|
345
|
+
reply_channel="rc-1",
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
assert manager.bridge.calls[-1] == (
|
|
349
|
+
"turn/interrupt",
|
|
350
|
+
{"threadId": "th-1", "turnId": "turn-active"},
|
|
351
|
+
)
|
|
352
|
+
assert "th-1" not in manager._thread_active_turn
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
@pytest.mark.asyncio
|
|
356
|
+
async def test_codex_bridge_exit_terminates_active_browser_turn():
|
|
357
|
+
channel = DummyControlChannel()
|
|
358
|
+
context = _context()
|
|
359
|
+
manager = CodexChatManager(channel, context)
|
|
360
|
+
manager.record_thread("th-1", "/tmp/proj", "p-1")
|
|
361
|
+
manager._thread_active_turn["th-1"] = "turn-1"
|
|
362
|
+
|
|
363
|
+
await manager._on_bridge_exit(137)
|
|
364
|
+
|
|
365
|
+
payload = channel.sent[-1]
|
|
366
|
+
assert payload["event"] == "codex_event"
|
|
367
|
+
assert payload["project_id"] == "p-1"
|
|
368
|
+
assert payload["notification"]["method"] == "error"
|
|
369
|
+
assert payload["notification"]["params"]["turnId"] == "turn-1"
|
|
370
|
+
assert "exit code 137" in payload["notification"]["params"]["error"]["message"]
|
|
371
|
+
assert not manager._thread_active_turn
|
|
372
|
+
|
|
373
|
+
|
|
333
374
|
@pytest.mark.asyncio
|
|
334
375
|
async def test_notification_forwarding_targets_project():
|
|
335
376
|
channel = DummyControlChannel()
|
|
@@ -346,6 +387,22 @@ async def test_notification_forwarding_targets_project():
|
|
|
346
387
|
assert payload["client_sessions"] == ["sess-1"]
|
|
347
388
|
|
|
348
389
|
|
|
390
|
+
@pytest.mark.asyncio
|
|
391
|
+
async def test_resumed_thread_events_include_new_tab_before_session_registry_updates():
|
|
392
|
+
channel = DummyControlChannel()
|
|
393
|
+
context = _context()
|
|
394
|
+
manager = CodexChatManager(channel, context)
|
|
395
|
+
manager.record_thread("th-1", "/tmp/proj", "p-1")
|
|
396
|
+
manager.subscribe_thread("th-1", "new-tab-session")
|
|
397
|
+
|
|
398
|
+
await manager._on_notification(
|
|
399
|
+
"item/agentMessage/delta",
|
|
400
|
+
{"threadId": "th-1", "delta": "live"},
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
assert channel.sent[-1]["client_sessions"] == ["new-tab-session", "sess-1"]
|
|
404
|
+
|
|
405
|
+
|
|
349
406
|
@pytest.mark.asyncio
|
|
350
407
|
async def test_notification_without_project_mapping_is_dropped():
|
|
351
408
|
channel = DummyControlChannel()
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Tests for the isolated Codex loopback proxy."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
import pytest
|
|
11
|
+
from cryptography.hazmat.primitives import serialization
|
|
12
|
+
from cryptography.hazmat.primitives.asymmetric import rsa
|
|
13
|
+
from cryptography.hazmat.primitives.serialization import Encoding, NoEncryption, PrivateFormat, PublicFormat
|
|
14
|
+
|
|
15
|
+
from portacode.codex_loopback_proxy import CodexLoopbackProxy
|
|
16
|
+
from portacode.keypair import KeyPair
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _write_keypair(tmp_path: Path) -> KeyPair:
|
|
20
|
+
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
|
21
|
+
priv_path = tmp_path / "id_portacode"
|
|
22
|
+
pub_path = tmp_path / "id_portacode.pub"
|
|
23
|
+
priv_path.write_bytes(
|
|
24
|
+
private_key.private_bytes(
|
|
25
|
+
encoding=Encoding.PEM,
|
|
26
|
+
format=PrivateFormat.TraditionalOpenSSL,
|
|
27
|
+
encryption_algorithm=NoEncryption(),
|
|
28
|
+
)
|
|
29
|
+
)
|
|
30
|
+
pub_path.write_bytes(
|
|
31
|
+
private_key.public_key().public_bytes(
|
|
32
|
+
Encoding.PEM, PublicFormat.SubjectPublicKeyInfo
|
|
33
|
+
)
|
|
34
|
+
)
|
|
35
|
+
return KeyPair(priv_path, pub_path)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class _FakeStreamResponse:
|
|
39
|
+
def __init__(self, status_code=200, headers=None, chunks=None, reason="OK"):
|
|
40
|
+
self.status_code = status_code
|
|
41
|
+
self.headers = httpx.Headers(headers or {"content-type": "text/event-stream"})
|
|
42
|
+
self.reason_phrase = reason
|
|
43
|
+
self._chunks = chunks or [b"data: hello\n\n"]
|
|
44
|
+
|
|
45
|
+
async def aread(self) -> bytes:
|
|
46
|
+
return b"".join(self._chunks)
|
|
47
|
+
|
|
48
|
+
async def aiter_raw(self):
|
|
49
|
+
for chunk in self._chunks:
|
|
50
|
+
yield chunk
|
|
51
|
+
|
|
52
|
+
async def __aenter__(self):
|
|
53
|
+
return self
|
|
54
|
+
|
|
55
|
+
async def __aexit__(self, exc_type, exc, tb):
|
|
56
|
+
return False
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@pytest.mark.asyncio
|
|
60
|
+
async def test_loopback_proxy_health_on_isolated_thread(tmp_path, monkeypatch):
|
|
61
|
+
keypair = _write_keypair(tmp_path)
|
|
62
|
+
# Bind an ephemeral port to avoid colliding with a live agent.
|
|
63
|
+
import portacode.codex_loopback_proxy as proxy_mod
|
|
64
|
+
|
|
65
|
+
monkeypatch.setattr(proxy_mod, "CODEX_LOOPBACK_PORT", 0)
|
|
66
|
+
|
|
67
|
+
proxy = CodexLoopbackProxy(keypair, gateway_url="https://example.test/v1")
|
|
68
|
+
# start_server with port 0 needs us to discover the port — patch after start.
|
|
69
|
+
await proxy.start()
|
|
70
|
+
assert proxy._server is not None
|
|
71
|
+
sockets = proxy._server.sockets or []
|
|
72
|
+
assert sockets
|
|
73
|
+
port = sockets[0].getsockname()[1]
|
|
74
|
+
|
|
75
|
+
async with httpx.AsyncClient() as client:
|
|
76
|
+
response = await client.get(f"http://127.0.0.1:{port}/health", timeout=5.0)
|
|
77
|
+
assert response.status_code == 200
|
|
78
|
+
body = response.json()
|
|
79
|
+
assert body["ok"] is True
|
|
80
|
+
assert body["service"] == "portacode-codex-loopback"
|
|
81
|
+
assert "active_requests" in body
|
|
82
|
+
|
|
83
|
+
await proxy.stop()
|
|
84
|
+
assert proxy._thread is None or not proxy._thread.is_alive()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@pytest.mark.asyncio
|
|
88
|
+
async def test_loopback_proxy_forwards_and_logs_errors(tmp_path, monkeypatch):
|
|
89
|
+
keypair = _write_keypair(tmp_path)
|
|
90
|
+
import portacode.codex_loopback_proxy as proxy_mod
|
|
91
|
+
|
|
92
|
+
monkeypatch.setattr(proxy_mod, "CODEX_LOOPBACK_PORT", 0)
|
|
93
|
+
|
|
94
|
+
proxy = CodexLoopbackProxy(keypair, gateway_url="https://example.test/v1")
|
|
95
|
+
await proxy.start()
|
|
96
|
+
port = proxy._server.sockets[0].getsockname()[1]
|
|
97
|
+
|
|
98
|
+
fake = _FakeStreamResponse(
|
|
99
|
+
status_code=429,
|
|
100
|
+
headers={"content-type": "application/json", "x-portacode-resets-at": "1700000000"},
|
|
101
|
+
chunks=[b'{"error":{"message":"quota"}}'],
|
|
102
|
+
reason="Too Many Requests",
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
class _Client:
|
|
106
|
+
def stream(self, method, url, content=None, headers=None):
|
|
107
|
+
assert method == "POST"
|
|
108
|
+
assert url.endswith("/device/responses")
|
|
109
|
+
assert "X-Portacode-Signature" in headers
|
|
110
|
+
return fake
|
|
111
|
+
|
|
112
|
+
async def aclose(self):
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
proxy._client = _Client()
|
|
116
|
+
|
|
117
|
+
async with httpx.AsyncClient() as client:
|
|
118
|
+
response = await client.post(
|
|
119
|
+
f"http://127.0.0.1:{port}/v1/responses",
|
|
120
|
+
content=b'{"model":"gpt-5","input":"hi"}',
|
|
121
|
+
headers={"content-type": "application/json"},
|
|
122
|
+
timeout=5.0,
|
|
123
|
+
)
|
|
124
|
+
assert response.status_code == 429
|
|
125
|
+
assert response.json()["error"]["message"] == "quota"
|
|
126
|
+
await proxy.stop()
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@pytest.mark.asyncio
|
|
130
|
+
async def test_keypair_caches_private_key(tmp_path):
|
|
131
|
+
keypair = _write_keypair(tmp_path)
|
|
132
|
+
first = keypair.sign_bytes(b"abc")
|
|
133
|
+
# Corrupt the on-disk key after first load — cached key should still work.
|
|
134
|
+
keypair.private_key_path.write_bytes(b"not-a-key")
|
|
135
|
+
second = keypair.sign_bytes(b"abc")
|
|
136
|
+
assert first == second
|
|
137
|
+
assert keypair.public_key_der_b64()
|
|
@@ -342,6 +342,7 @@ portacode/connection/handlers/project_state/test_handlers.py
|
|
|
342
342
|
portacode/connection/handlers/project_state/utils.py
|
|
343
343
|
portacode/link_capture/__init__.py
|
|
344
344
|
portacode/link_capture/__pycache__/__init__.cpython-311.pyc
|
|
345
|
+
portacode/link_capture/__pycache__/__init__.cpython-38.pyc
|
|
345
346
|
portacode/link_capture/bin/elinks
|
|
346
347
|
portacode/link_capture/bin/gio-open
|
|
347
348
|
portacode/link_capture/bin/gnome-open
|
|
@@ -359,10 +360,13 @@ portacode/link_capture/bin/sensible-browser
|
|
|
359
360
|
portacode/link_capture/bin/w3m
|
|
360
361
|
portacode/link_capture/bin/x-www-browser
|
|
361
362
|
portacode/link_capture/bin/xdg-open
|
|
363
|
+
portacode/link_capture/bin/__pycache__/link_capture_wrapper.cpython-311.pyc
|
|
364
|
+
portacode/link_capture/bin/__pycache__/link_capture_wrapper.cpython-38.pyc
|
|
362
365
|
portacode/static/js/test-ntp-clock.html
|
|
363
366
|
portacode/static/js/utils/ntp-clock.js
|
|
364
367
|
portacode/tests/test_codex_app_server.py
|
|
365
368
|
portacode/tests/test_codex_handlers.py
|
|
369
|
+
portacode/tests/test_codex_loopback_proxy.py
|
|
366
370
|
portacode/tests/test_codex_prepare.py
|
|
367
371
|
portacode/tunneling/__init__.py
|
|
368
372
|
portacode/tunneling/cloudflared_login.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|