portacode 1.5.13.dev3__tar.gz → 1.5.14.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.13.dev3 → portacode-1.5.14.dev0}/PKG-INFO +1 -1
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/_version.py +2 -2
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/codex_prepare.py +6 -1
- portacode-1.5.14.dev0/portacode/connection/codex_app_server.py +326 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/handlers/__init__.py +15 -0
- portacode-1.5.14.dev0/portacode/connection/handlers/codex_handlers.py +293 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/terminal.py +13 -0
- portacode-1.5.14.dev0/portacode/tests/test_codex_app_server.py +166 -0
- portacode-1.5.14.dev0/portacode/tests/test_codex_handlers.py +189 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode.egg-info/PKG-INFO +1 -1
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode.egg-info/SOURCES.txt +4 -2
- portacode-1.5.13.dev3/portacode/link_capture/__pycache__/__init__.cpython-311.pyc +0 -0
- portacode-1.5.13.dev3/portacode/link_capture/bin/__pycache__/link_capture_wrapper.cpython-311.pyc +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/.claude/agents/communication-manager.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/.claude/settings.local.json +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/.gitignore +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/.gitmodules +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/LICENSE +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/MANIFEST.in +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/Makefile +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/README.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/backup.sh +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/build_android.sh +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/connect.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/connect.sh +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/docker-compose.yaml +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/docs/cloudflared-domain-connect-containers-audit.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/docs/creative-team-brief-portacode.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/docs/device-health-alerting-plan.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/docs/devops-messaging-ab-tests.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/docs/homepage-dashboard-positioning-fixes.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/docs/images/device-transfer-button.png +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/docs/images/device-transfer-modal.png +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/docs/images/pair-device-button.png +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/docs/images/pairing-request.png +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/docs/images/student-workspace.png +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/docs/template-guide.html +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/README.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/simple_device/Dockerfile +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/simple_device/README.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/simple_device/data/device-01/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/simple_device/data/device-01/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/simple_device/docker-compose.yaml +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/Dockerfile +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/README.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/.local/share/portacode/run/gateway.pid +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/.gitignore +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/README.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/db.sqlite3 +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/__init__.cpython-311.pyc +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/settings.cpython-311.pyc +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/urls.cpython-311.pyc +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/wsgi.cpython-311.pyc +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/manage.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/requirements.txt +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/__init__.cpython-311.pyc +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/admin.cpython-311.pyc +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/apps.cpython-311.pyc +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/menu.cpython-311.pyc +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/models.cpython-311.pyc +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/urls.cpython-311.pyc +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/views.cpython-311.pyc +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/admin.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/apps.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/menu.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/migrations/__pycache__/__init__.cpython-311.pyc +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/models.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/tests.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/urls.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/views.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/workspace/README.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/workspace/manage.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/workspace/requirements.txt +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/admin.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/apps.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/menu.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/models.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/tests.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/urls.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/views.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/workspace/README.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/workspace/manage.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/workspace/requirements.txt +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/admin.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/apps.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/menu.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/models.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/tests.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/urls.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/views.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/workspace/README.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/workspace/manage.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/workspace/requirements.txt +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/admin.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/apps.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/menu.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/models.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/tests.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/urls.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/views.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/workspace/README.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/workspace/manage.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/workspace/requirements.txt +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/admin.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/apps.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/menu.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/models.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/tests.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/urls.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/views.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/workspace/README.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/workspace/manage.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/workspace/requirements.txt +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/admin.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/apps.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/menu.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/models.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/tests.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/urls.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/views.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/workspace/README.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/workspace/manage.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/workspace/requirements.txt +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/admin.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/apps.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/menu.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/models.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/tests.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/urls.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/views.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/workspace/README.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/workspace/manage.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/workspace/requirements.txt +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/admin.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/apps.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/menu.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/models.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/tests.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/urls.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/views.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/workspace/README.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/workspace/manage.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/workspace/requirements.txt +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/admin.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/apps.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/menu.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/models.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/tests.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/urls.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/views.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/workspace/README.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/workspace/manage.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/workspace/requirements.txt +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/admin.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/apps.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/menu.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/models.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/tests.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/urls.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/views.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/docker-compose.yaml +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/initial_content/README.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/initial_content/manage.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/initial_content/requirements.txt +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/initial_content/templates/treats/home.html +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/initial_content/treats/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/initial_content/treats/admin.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/initial_content/treats/apps.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/initial_content/treats/menu.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/initial_content/treats/migrations/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/initial_content/treats/models.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/initial_content/treats/tests.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/initial_content/treats/urls.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/initial_content/treats/views.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/examples/workshop_fleet/instructions/WELCOME.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/README.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/__main__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/cli.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/codex_loopback_proxy.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/README.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/client.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/handlers/README.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/handlers/automation_v2_handlers.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/handlers/base.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/handlers/chunked_content.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/handlers/cloudflare_forwarding.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/handlers/cloudflare_tunnel.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/handlers/diff_handlers.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/handlers/file_handlers.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/handlers/file_transfer_handlers.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/handlers/project_aware_file_handlers.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/handlers/project_state/README.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/handlers/project_state/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/handlers/project_state/file_system_watcher.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/handlers/project_state/git_manager.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/handlers/project_state/handlers.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/handlers/project_state/manager.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/handlers/project_state/models.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/handlers/project_state/test_handlers.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/handlers/project_state/utils.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/handlers/project_state_handlers.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/handlers/proxmox_infra.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/handlers/registry.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/handlers/runtime_user.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/handlers/session.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/handlers/system_handlers.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/handlers/tab_factory.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/handlers/terminal_handlers.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/handlers/test_cloudflare_forwarding.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/handlers/test_proxmox_infra.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/handlers/test_runtime_user.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/handlers/update_handler.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/multiplex.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/test_webmin_proxy_config.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/connection/webmin_proxy_config.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/data.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/exit_codes.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/keypair.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/link_capture/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/link_capture/bin/elinks +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/link_capture/bin/gio-open +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/link_capture/bin/gnome-open +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/link_capture/bin/gvfs-open +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/link_capture/bin/kde-open +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/link_capture/bin/kfmclient +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/link_capture/bin/link_capture_exec.sh +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/link_capture/bin/link_capture_wrapper.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/link_capture/bin/links +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/link_capture/bin/links2 +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/link_capture/bin/lynx +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/link_capture/bin/mate-open +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/link_capture/bin/netsurf +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/link_capture/bin/sensible-browser +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/link_capture/bin/w3m +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/link_capture/bin/x-www-browser +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/link_capture/bin/xdg-open +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/logging_categories.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/pairing.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/restart.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/service.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/static/js/test-ntp-clock.html +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/static/js/utils/ntp-clock.js +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/test_runtime_paths.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/test_service.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/test_terminal_exposed_services.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/tunneling/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/tunneling/cloudflared_login.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/tunneling/ensure_cloudflared.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/tunneling/ensure_pyyaml.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/tunneling/forwarding_state.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/tunneling/get_domain.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/tunneling/privileged.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/tunneling/service_install.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/tunneling/state.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/updater.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/utils/NTP_ARCHITECTURE.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/utils/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/utils/diff_apply.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/utils/diff_renderer.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/utils/ntp_clock.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode/utils/runtime_paths.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode.egg-info/dependency_links.txt +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode.egg-info/entry_points.txt +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode.egg-info/requires.txt +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode.egg-info/top_level.txt +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode_for_school/.gitignore +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode_for_school/README.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode_for_school/persistent_workspace/Dockerfile +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode_for_school/persistent_workspace/README.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode_for_school/persistent_workspace/entrypoint.sh +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode_for_school/persistent_workspace/manage_classroom.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode_for_school/temporary_workspace/Dockerfile +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode_for_school/temporary_workspace/README.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode_for_school/temporary_workspace/docker-compose.yaml +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode_for_school/temporary_workspace/docs/device_transfere.png +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode_for_school/temporary_workspace/docs/pairing_approval.png +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/portacode_for_school/temporary_workspace/entrypoint.sh +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/pyproject.toml +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/restore.sh +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/run_tests.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/scripts/diagnose_codex_gateway.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/setup.cfg +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/setup.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/test.sh +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/test_modules/README.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/test_modules/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/test_modules/test_device_online.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/test_modules/test_file_operations.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/test_modules/test_git_status_ui.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/test_modules/test_login_flow.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/test_modules/test_navigate_testing_folder.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/test_modules/test_play_store_screenshots.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/test_modules/test_terminal_buffer_performance.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/test_modules/test_terminal_interaction.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/test_modules/test_terminal_loading_race_condition.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/test_modules/test_terminal_start.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/test_request_id.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/test_session_terminal_shutdown.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/testing_framework/.env.example +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/testing_framework/README.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/testing_framework/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/testing_framework/cli.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/testing_framework/core/__init__.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/testing_framework/core/base_test.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/testing_framework/core/cli_manager.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/testing_framework/core/hierarchical_runner.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/testing_framework/core/playwright_manager.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/testing_framework/core/runner.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/testing_framework/core/shared_cli_manager.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/testing_framework/core/test_discovery.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/testing_framework/requirements.txt +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/todo/UI_UX/opening_a_file_on_desktop_results_in_nothing.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/todo/UI_UX/server_occasionally_stops_communicating_with_all_devices.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/todo/agent_context_management.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/todo/django_server_time_sync.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/todo/issues/device_performance_degradation.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/todo/issues/git_data_not_captured_in_proxmox.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/todo/issues/indefinite_resource_loading.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/todo/issues/portacode_service_silently_down.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/todo/issues/premature_terminal_exit.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/todo/issues/project_cpu_hotspots.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/todo/issues/terminals_exit_upon_starting.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/todo/issues/websocket_client_silently_dead.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/todo/issues/wrong_item_classification_on_client_side.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/todo/smartphone_terminal_input_frustrations.md +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/tools/generate_play_store_assets.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/tools/pairing_tester.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/tools/run_screenshot_suite.sh +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.dev0}/tools/test_python_ntp_clock.py +0 -0
- {portacode-1.5.13.dev3 → portacode-1.5.14.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.14.dev0'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 5, 14, 'dev0')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -67,9 +67,14 @@ def _sudo_prefix() -> list[str]:
|
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
def _authorize_sudo_if_needed() -> None:
|
|
70
|
-
"""
|
|
70
|
+
"""Use passwordless sudo when available before prompting interactively."""
|
|
71
71
|
if platform.system().lower() not in {"linux", "darwin"} or not _sudo_prefix():
|
|
72
72
|
return
|
|
73
|
+
non_interactive = subprocess.run(
|
|
74
|
+
["sudo", "-n", "true"], text=True, capture_output=True
|
|
75
|
+
)
|
|
76
|
+
if non_interactive.returncode == 0:
|
|
77
|
+
return
|
|
73
78
|
result = subprocess.run(["sudo", "-v"], text=True)
|
|
74
79
|
if result.returncode:
|
|
75
80
|
raise CodexPreparationError("Administrator authorization is required to prepare Codex.")
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
"""JSON-RPC bridge to the Codex CLI app-server over stdio."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
8
|
+
import shutil
|
|
9
|
+
import uuid
|
|
10
|
+
from typing import Any, Awaitable, Callable, Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
LOGGER = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
DEFAULT_CODEX_COMMAND = ["codex", "app-server", "--listen", "stdio://"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CodexAppServerError(RuntimeError):
|
|
18
|
+
"""Raised when the Codex app-server cannot be started or is unhealthy."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CodexAppServer:
|
|
22
|
+
"""Thin asyncio JSON-RPC 2.0 client for `codex app-server --listen stdio://`.
|
|
23
|
+
|
|
24
|
+
One instance per device agent is enough: the app-server can host multiple
|
|
25
|
+
threads/cwds, so we keep it alive for the lifetime of the connection and
|
|
26
|
+
lazily spawn it on the first command.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
command: Optional[List[str]] = None,
|
|
32
|
+
client_info: Optional[Dict[str, Any]] = None,
|
|
33
|
+
on_notification: Optional[Callable[[str, Dict[str, Any]], Awaitable[None]]] = None,
|
|
34
|
+
_process_factory: Optional[Callable[..., Awaitable[Any]]] = None,
|
|
35
|
+
) -> None:
|
|
36
|
+
self.command = command or list(DEFAULT_CODEX_COMMAND)
|
|
37
|
+
self.client_info = client_info or {"name": "portacode", "version": "1.5.13"}
|
|
38
|
+
self.on_notification = on_notification
|
|
39
|
+
self._process_factory = _process_factory or asyncio.create_subprocess_exec
|
|
40
|
+
|
|
41
|
+
self._proc: Optional[asyncio.subprocess.Process] = None
|
|
42
|
+
self._reader_task: Optional[asyncio.Task] = None
|
|
43
|
+
self._stderr_task: Optional[asyncio.Task] = None
|
|
44
|
+
self._next_id = 1
|
|
45
|
+
self._pending: Dict[int, asyncio.Future] = {}
|
|
46
|
+
self._lock = asyncio.Lock()
|
|
47
|
+
self._initialized = asyncio.Event()
|
|
48
|
+
self._closed = False
|
|
49
|
+
|
|
50
|
+
# ------------------------------------------------------------------
|
|
51
|
+
# Lifecycle
|
|
52
|
+
# ------------------------------------------------------------------
|
|
53
|
+
async def start(self) -> None:
|
|
54
|
+
"""Start the app-server if it is not already running."""
|
|
55
|
+
if self._closed:
|
|
56
|
+
raise CodexAppServerError("CodexAppServer has been closed")
|
|
57
|
+
async with self._lock:
|
|
58
|
+
if self._proc is not None and self._proc.returncode is None:
|
|
59
|
+
return
|
|
60
|
+
await self._spawn()
|
|
61
|
+
|
|
62
|
+
async def stop(self) -> None:
|
|
63
|
+
"""Stop the app-server and cancel background tasks."""
|
|
64
|
+
async with self._lock:
|
|
65
|
+
self._closed = True
|
|
66
|
+
await self._kill_locked()
|
|
67
|
+
|
|
68
|
+
async def _spawn(self) -> None:
|
|
69
|
+
"""Spawn the subprocess and perform the JSON-RPC initialize handshake."""
|
|
70
|
+
exe = shutil.which(self.command[0]) or self.command[0]
|
|
71
|
+
if not shutil.which(self.command[0]):
|
|
72
|
+
raise CodexAppServerError(
|
|
73
|
+
f"{self.command[0]} not found on PATH. Run `portacode prepare codex` on this device."
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
LOGGER.info("Spawning Codex app-server: %s", " ".join(self.command))
|
|
77
|
+
try:
|
|
78
|
+
self._proc = await self._process_factory(
|
|
79
|
+
*self.command,
|
|
80
|
+
stdin=asyncio.subprocess.PIPE,
|
|
81
|
+
stdout=asyncio.subprocess.PIPE,
|
|
82
|
+
stderr=asyncio.subprocess.PIPE,
|
|
83
|
+
env=None, # Inherit the environment where `prepare codex` set OPENAI_API_KEY
|
|
84
|
+
)
|
|
85
|
+
except Exception as exc:
|
|
86
|
+
raise CodexAppServerError(f"Failed to spawn codex app-server: {exc}") from exc
|
|
87
|
+
|
|
88
|
+
self._reader_task = asyncio.create_task(self._read_loop(), name="codex-app-server-stdout")
|
|
89
|
+
self._stderr_task = asyncio.create_task(self._stderr_loop(), name="codex-app-server-stderr")
|
|
90
|
+
|
|
91
|
+
await self._initialize()
|
|
92
|
+
|
|
93
|
+
async def _kill_locked(self) -> None:
|
|
94
|
+
"""Tear down the subprocess and reader tasks."""
|
|
95
|
+
for future in list(self._pending.values()):
|
|
96
|
+
if not future.done():
|
|
97
|
+
future.set_exception(CodexAppServerError("Codex app-server stopped"))
|
|
98
|
+
self._pending.clear()
|
|
99
|
+
|
|
100
|
+
if self._reader_task is not None:
|
|
101
|
+
self._reader_task.cancel()
|
|
102
|
+
self._reader_task = None
|
|
103
|
+
if self._stderr_task is not None:
|
|
104
|
+
self._stderr_task.cancel()
|
|
105
|
+
self._stderr_task = None
|
|
106
|
+
|
|
107
|
+
if self._proc is not None and self._proc.returncode is None:
|
|
108
|
+
try:
|
|
109
|
+
self._proc.terminate()
|
|
110
|
+
await asyncio.wait_for(self._proc.wait(), timeout=3.0)
|
|
111
|
+
except asyncio.TimeoutError:
|
|
112
|
+
self._proc.kill()
|
|
113
|
+
await self._proc.wait()
|
|
114
|
+
except ProcessLookupError:
|
|
115
|
+
pass
|
|
116
|
+
self._proc = None
|
|
117
|
+
self._initialized.clear()
|
|
118
|
+
|
|
119
|
+
# ------------------------------------------------------------------
|
|
120
|
+
# JSON-RPC I/O
|
|
121
|
+
# ------------------------------------------------------------------
|
|
122
|
+
async def _initialize(self) -> None:
|
|
123
|
+
"""Send the initialize handshake and wait for its response."""
|
|
124
|
+
response = await self._request(
|
|
125
|
+
"initialize",
|
|
126
|
+
{
|
|
127
|
+
"clientInfo": self.client_info,
|
|
128
|
+
"capabilities": {"streaming": True},
|
|
129
|
+
},
|
|
130
|
+
timeout=10.0,
|
|
131
|
+
)
|
|
132
|
+
LOGGER.info("Codex app-server initialized: %s", response.get("result", {}))
|
|
133
|
+
self._initialized.set()
|
|
134
|
+
|
|
135
|
+
async def _request(
|
|
136
|
+
self, method: str, params: Dict[str, Any], timeout: float = 60.0
|
|
137
|
+
) -> Dict[str, Any]:
|
|
138
|
+
"""Send a JSON-RPC request and return the response object."""
|
|
139
|
+
await self.start()
|
|
140
|
+
assert self._proc is not None and self._proc.stdin is not None
|
|
141
|
+
|
|
142
|
+
request_id = self._next_id
|
|
143
|
+
self._next_id += 1
|
|
144
|
+
message = {"jsonrpc": "2.0", "id": request_id, "method": method, "params": params}
|
|
145
|
+
payload = json.dumps(message, ensure_ascii=False)
|
|
146
|
+
|
|
147
|
+
loop = asyncio.get_running_loop()
|
|
148
|
+
future: asyncio.Future = loop.create_future()
|
|
149
|
+
self._pending[request_id] = future
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
self._proc.stdin.write((payload + "\n").encode("utf-8"))
|
|
153
|
+
await self._proc.stdin.drain()
|
|
154
|
+
except Exception as exc:
|
|
155
|
+
self._pending.pop(request_id, None)
|
|
156
|
+
raise CodexAppServerError(f"Failed to write to codex app-server: {exc}") from exc
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
return await asyncio.wait_for(future, timeout=timeout)
|
|
160
|
+
except asyncio.TimeoutError as exc:
|
|
161
|
+
self._pending.pop(request_id, None)
|
|
162
|
+
raise CodexAppServerError(f"codex app-server request timed out: {method}") from exc
|
|
163
|
+
|
|
164
|
+
async def _send_notification(self, method: str, params: Dict[str, Any]) -> None:
|
|
165
|
+
"""Fire a JSON-RPC notification (no response expected)."""
|
|
166
|
+
await self.start()
|
|
167
|
+
assert self._proc is not None and self._proc.stdin is not None
|
|
168
|
+
message = {"jsonrpc": "2.0", "method": method, "params": params}
|
|
169
|
+
payload = json.dumps(message, ensure_ascii=False)
|
|
170
|
+
self._proc.stdin.write((payload + "\n").encode("utf-8"))
|
|
171
|
+
await self._proc.stdin.drain()
|
|
172
|
+
|
|
173
|
+
async def _send_error_response(self, request_id: Any, code: int, message: str) -> None:
|
|
174
|
+
"""Reply to a server->client request with a JSON-RPC error."""
|
|
175
|
+
assert self._proc is not None and self._proc.stdin is not None
|
|
176
|
+
response = {"jsonrpc": "2.0", "id": request_id, "error": {"code": code, "message": message}}
|
|
177
|
+
payload = json.dumps(response, ensure_ascii=False)
|
|
178
|
+
self._proc.stdin.write((payload + "\n").encode("utf-8"))
|
|
179
|
+
await self._proc.stdin.drain()
|
|
180
|
+
|
|
181
|
+
# ------------------------------------------------------------------
|
|
182
|
+
# Reading loops
|
|
183
|
+
# ------------------------------------------------------------------
|
|
184
|
+
async def _read_loop(self) -> None:
|
|
185
|
+
"""Read newline-delimited JSON-RPC messages from app-server stdout."""
|
|
186
|
+
assert self._proc is not None and self._proc.stdout is not None
|
|
187
|
+
try:
|
|
188
|
+
while True:
|
|
189
|
+
line = await self._proc.stdout.readline()
|
|
190
|
+
if not line:
|
|
191
|
+
break
|
|
192
|
+
line = line.decode("utf-8", errors="replace").strip()
|
|
193
|
+
if not line:
|
|
194
|
+
continue
|
|
195
|
+
try:
|
|
196
|
+
message = json.loads(line)
|
|
197
|
+
except json.JSONDecodeError as exc:
|
|
198
|
+
LOGGER.warning("Ignoring non-JSON line from codex app-server: %r (%s)", line, exc)
|
|
199
|
+
continue
|
|
200
|
+
await self._dispatch_message(message)
|
|
201
|
+
except asyncio.CancelledError:
|
|
202
|
+
raise
|
|
203
|
+
except Exception:
|
|
204
|
+
LOGGER.exception("codex app-server read loop failed")
|
|
205
|
+
finally:
|
|
206
|
+
# Process exited; schedule a restart if not closed.
|
|
207
|
+
if not self._closed:
|
|
208
|
+
asyncio.create_task(self._on_reader_exit())
|
|
209
|
+
|
|
210
|
+
async def _stderr_loop(self) -> None:
|
|
211
|
+
"""Forward app-server stderr lines to the logger."""
|
|
212
|
+
assert self._proc is not None and self._proc.stderr is not None
|
|
213
|
+
try:
|
|
214
|
+
while True:
|
|
215
|
+
line = await self._proc.stderr.readline()
|
|
216
|
+
if not line:
|
|
217
|
+
break
|
|
218
|
+
text = line.decode("utf-8", errors="replace").rstrip()
|
|
219
|
+
if text:
|
|
220
|
+
LOGGER.debug("codex app-server stderr: %s", text)
|
|
221
|
+
except asyncio.CancelledError:
|
|
222
|
+
raise
|
|
223
|
+
except Exception:
|
|
224
|
+
LOGGER.exception("codex app-server stderr loop failed")
|
|
225
|
+
|
|
226
|
+
async def _dispatch_message(self, message: Dict[str, Any]) -> None:
|
|
227
|
+
"""Route a single JSON-RPC message to pending response, notification, or request handler."""
|
|
228
|
+
request_id = message.get("id")
|
|
229
|
+
has_result = "result" in message
|
|
230
|
+
has_error = "error" in message
|
|
231
|
+
method = message.get("method")
|
|
232
|
+
params = message.get("params", {})
|
|
233
|
+
|
|
234
|
+
if has_result or has_error:
|
|
235
|
+
if request_id is not None and request_id in self._pending:
|
|
236
|
+
future = self._pending.pop(request_id)
|
|
237
|
+
if not future.done():
|
|
238
|
+
future.set_result(message)
|
|
239
|
+
return
|
|
240
|
+
|
|
241
|
+
if request_id is not None and method is not None:
|
|
242
|
+
# The server sent us a request. We currently auto-decline/unsupported everything
|
|
243
|
+
# because the portacode config sets approval_policy="never" and danger-full-access.
|
|
244
|
+
LOGGER.debug("Declining codex app-server request: %s", method)
|
|
245
|
+
await self._send_error_response(request_id, -32601, f"Method not supported by portacode: {method}")
|
|
246
|
+
return
|
|
247
|
+
|
|
248
|
+
if method is not None:
|
|
249
|
+
# Notification from app-server.
|
|
250
|
+
LOGGER.debug("codex app-server notification: %s", method)
|
|
251
|
+
if self.on_notification is not None:
|
|
252
|
+
try:
|
|
253
|
+
await self.on_notification(method, params)
|
|
254
|
+
except Exception:
|
|
255
|
+
LOGGER.exception("codex notification handler failed for %s", method)
|
|
256
|
+
return
|
|
257
|
+
|
|
258
|
+
LOGGER.debug("Ignoring unexpected codex app-server message: %s", message)
|
|
259
|
+
|
|
260
|
+
async def _on_reader_exit(self) -> None:
|
|
261
|
+
"""Restart the app-server once after an unexpected exit."""
|
|
262
|
+
async with self._lock:
|
|
263
|
+
if self._closed or self._proc is None:
|
|
264
|
+
return
|
|
265
|
+
LOGGER.warning("Codex app-server process exited (code %s); restarting", self._proc.returncode)
|
|
266
|
+
await self._kill_locked()
|
|
267
|
+
try:
|
|
268
|
+
await self.start()
|
|
269
|
+
except Exception:
|
|
270
|
+
LOGGER.exception("Failed to restart codex app-server")
|
|
271
|
+
|
|
272
|
+
# ------------------------------------------------------------------
|
|
273
|
+
# Public API
|
|
274
|
+
# ------------------------------------------------------------------
|
|
275
|
+
async def call(
|
|
276
|
+
self, method: str, params: Optional[Dict[str, Any]] = None, timeout: float = 60.0
|
|
277
|
+
) -> Dict[str, Any]:
|
|
278
|
+
"""Call a Codex app-server method and return the `result` field."""
|
|
279
|
+
params = params or {}
|
|
280
|
+
response = await self._request(method, params, timeout)
|
|
281
|
+
if "error" in response:
|
|
282
|
+
error = response["error"]
|
|
283
|
+
raise CodexAppServerError(
|
|
284
|
+
f"codex app-server error ({method}): {error.get('message', error)}"
|
|
285
|
+
)
|
|
286
|
+
return response.get("result", {})
|
|
287
|
+
|
|
288
|
+
async def notify(self, method: str, params: Optional[Dict[str, Any]] = None) -> None:
|
|
289
|
+
"""Send a JSON-RPC notification."""
|
|
290
|
+
await self._send_notification(method, params or {})
|
|
291
|
+
|
|
292
|
+
async def healthy(self) -> bool:
|
|
293
|
+
"""Return True if the app-server is initialized and the process is alive."""
|
|
294
|
+
if self._proc is None or self._proc.returncode is not None:
|
|
295
|
+
return False
|
|
296
|
+
return self._initialized.is_set()
|
|
297
|
+
|
|
298
|
+
@staticmethod
|
|
299
|
+
def get_binary_path() -> Optional[str]:
|
|
300
|
+
return shutil.which("codex")
|
|
301
|
+
|
|
302
|
+
@staticmethod
|
|
303
|
+
def version() -> Optional[str]:
|
|
304
|
+
import subprocess
|
|
305
|
+
|
|
306
|
+
exe = shutil.which("codex")
|
|
307
|
+
if not exe:
|
|
308
|
+
return None
|
|
309
|
+
try:
|
|
310
|
+
result = subprocess.run(
|
|
311
|
+
[exe, "--version"], capture_output=True, text=True, timeout=10
|
|
312
|
+
)
|
|
313
|
+
return result.stdout.strip() or None
|
|
314
|
+
except Exception:
|
|
315
|
+
return None
|
|
316
|
+
|
|
317
|
+
def get_running_thread_id(self, turn_id: Optional[str] = None) -> Optional[str]:
|
|
318
|
+
"""Hook for the manager to resolve turn_id -> thread_id if needed."""
|
|
319
|
+
return None
|
|
320
|
+
|
|
321
|
+
def __del__(self) -> None:
|
|
322
|
+
if self._proc is not None and self._proc.returncode is None:
|
|
323
|
+
try:
|
|
324
|
+
self._proc.kill()
|
|
325
|
+
except Exception:
|
|
326
|
+
pass
|
|
@@ -61,6 +61,14 @@ from .automation_v2_handlers import (
|
|
|
61
61
|
AutomationV2StateHandler,
|
|
62
62
|
AutomationV2CancelHandler,
|
|
63
63
|
)
|
|
64
|
+
from .codex_handlers import (
|
|
65
|
+
CodexStatusHandler,
|
|
66
|
+
CodexThreadListHandler,
|
|
67
|
+
CodexThreadStartHandler,
|
|
68
|
+
CodexThreadResumeHandler,
|
|
69
|
+
CodexTurnStartHandler,
|
|
70
|
+
CodexTurnInterruptHandler,
|
|
71
|
+
)
|
|
64
72
|
|
|
65
73
|
__all__ = [
|
|
66
74
|
"BaseHandler",
|
|
@@ -112,4 +120,11 @@ __all__ = [
|
|
|
112
120
|
"AutomationV2StartHandler",
|
|
113
121
|
"AutomationV2StateHandler",
|
|
114
122
|
"AutomationV2CancelHandler",
|
|
123
|
+
# Codex chat handlers
|
|
124
|
+
"CodexStatusHandler",
|
|
125
|
+
"CodexThreadListHandler",
|
|
126
|
+
"CodexThreadStartHandler",
|
|
127
|
+
"CodexThreadResumeHandler",
|
|
128
|
+
"CodexTurnStartHandler",
|
|
129
|
+
"CodexTurnInterruptHandler",
|
|
115
130
|
]
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"""Device-side command handlers for the Codex chat UI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any, Dict, Optional
|
|
7
|
+
|
|
8
|
+
from portacode.connection.codex_app_server import CodexAppServer, CodexAppServerError
|
|
9
|
+
from portacode.connection.handlers.base import AsyncHandler
|
|
10
|
+
|
|
11
|
+
LOGGER = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class _SessionAwareSender:
|
|
15
|
+
"""Helper to push events to the browser sessions interested in a project."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, control_channel: Any, context: Dict[str, Any]) -> None:
|
|
18
|
+
self.control_channel = control_channel
|
|
19
|
+
self.context = context
|
|
20
|
+
|
|
21
|
+
async def send(self, payload: Dict[str, Any], project_id: Optional[str] = None) -> None:
|
|
22
|
+
client_session_manager = self.context.get("client_session_manager")
|
|
23
|
+
if client_session_manager and client_session_manager.has_interested_clients():
|
|
24
|
+
target_sessions = client_session_manager.get_target_sessions(project_id)
|
|
25
|
+
if target_sessions:
|
|
26
|
+
payload = dict(payload)
|
|
27
|
+
payload["client_sessions"] = target_sessions
|
|
28
|
+
reply_channel = client_session_manager.get_reply_channel_for_compatibility()
|
|
29
|
+
if reply_channel:
|
|
30
|
+
payload = dict(payload)
|
|
31
|
+
payload["reply_channel"] = reply_channel
|
|
32
|
+
|
|
33
|
+
await self.control_channel.send(payload)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class CodexChatManager:
|
|
37
|
+
"""Owns one CodexAppServer bridge and routes its events to Portacode client sessions."""
|
|
38
|
+
|
|
39
|
+
def __init__(self, control_channel: Any, context: Dict[str, Any]) -> None:
|
|
40
|
+
self.sender = _SessionAwareSender(control_channel, context)
|
|
41
|
+
self.bridge = CodexAppServer(on_notification=self._on_notification)
|
|
42
|
+
self._thread_project: Dict[str, str] = {}
|
|
43
|
+
self._cwd_project: Dict[str, str] = {}
|
|
44
|
+
|
|
45
|
+
async def _on_notification(self, method: str, params: Dict[str, Any]) -> None:
|
|
46
|
+
project_id = self._resolve_project_id(params)
|
|
47
|
+
event_payload = {
|
|
48
|
+
"event": "codex_event",
|
|
49
|
+
"notification": {"method": method, "params": params},
|
|
50
|
+
}
|
|
51
|
+
if project_id:
|
|
52
|
+
event_payload["project_id"] = project_id
|
|
53
|
+
await self.sender.send(event_payload, project_id=project_id)
|
|
54
|
+
|
|
55
|
+
def _resolve_project_id(self, params: Dict[str, Any]) -> Optional[str]:
|
|
56
|
+
# Most notifications contain a threadId. Try that first.
|
|
57
|
+
thread_id = params.get("threadId") or (params.get("thread") or {}).get("id")
|
|
58
|
+
if thread_id and thread_id in self._thread_project:
|
|
59
|
+
return self._thread_project[thread_id]
|
|
60
|
+
|
|
61
|
+
# Some notifications include cwd directly; fall back to the cwd map.
|
|
62
|
+
cwd = params.get("cwd") or (params.get("thread") or {}).get("cwd")
|
|
63
|
+
if cwd and cwd in self._cwd_project:
|
|
64
|
+
return self._cwd_project[cwd]
|
|
65
|
+
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
def record_thread(self, thread_id: str, cwd: str, project_id: str) -> None:
|
|
69
|
+
self._thread_project[thread_id] = project_id
|
|
70
|
+
self._cwd_project[cwd] = project_id
|
|
71
|
+
|
|
72
|
+
async def ensure_started(self) -> None:
|
|
73
|
+
await self.bridge.start()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# A single manager is stored per device agent context.
|
|
77
|
+
def _get_manager(handler: AsyncHandler) -> CodexChatManager:
|
|
78
|
+
context = handler.context
|
|
79
|
+
if "codex_manager" not in context:
|
|
80
|
+
context["codex_manager"] = CodexChatManager(handler.control_channel, context)
|
|
81
|
+
return context["codex_manager"]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _project_id(message: Dict[str, Any]) -> Optional[str]:
|
|
85
|
+
return message.get("project_id")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class CodexStatusHandler(AsyncHandler):
|
|
89
|
+
"""Report whether Codex is installed and the app-server is healthy."""
|
|
90
|
+
|
|
91
|
+
def command_name(self) -> str:
|
|
92
|
+
return "codex_status"
|
|
93
|
+
|
|
94
|
+
async def execute(self, message: Dict[str, Any]) -> Dict[str, Any]:
|
|
95
|
+
installed = bool(CodexAppServer.get_binary_path())
|
|
96
|
+
version = CodexAppServer.version()
|
|
97
|
+
ready = False
|
|
98
|
+
error_message: Optional[str] = None
|
|
99
|
+
|
|
100
|
+
if installed:
|
|
101
|
+
try:
|
|
102
|
+
manager = _get_manager(self)
|
|
103
|
+
await manager.ensure_started()
|
|
104
|
+
ready = await manager.bridge.healthy()
|
|
105
|
+
except Exception as exc:
|
|
106
|
+
LOGGER.debug("codex_status: app-server not healthy: %s", exc)
|
|
107
|
+
error_message = str(exc)
|
|
108
|
+
|
|
109
|
+
payload = {
|
|
110
|
+
"event": "codex_status",
|
|
111
|
+
"installed": installed,
|
|
112
|
+
"version": version,
|
|
113
|
+
"ready": ready,
|
|
114
|
+
}
|
|
115
|
+
if error_message:
|
|
116
|
+
payload["error_message"] = error_message
|
|
117
|
+
project_id = _project_id(message)
|
|
118
|
+
if project_id:
|
|
119
|
+
payload["project_id"] = project_id
|
|
120
|
+
return payload
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class CodexThreadListHandler(AsyncHandler):
|
|
124
|
+
"""List Codex threads for a given project cwd."""
|
|
125
|
+
|
|
126
|
+
def command_name(self) -> str:
|
|
127
|
+
return "codex_thread_list"
|
|
128
|
+
|
|
129
|
+
async def execute(self, message: Dict[str, Any]) -> Dict[str, Any]:
|
|
130
|
+
cwd = message.get("cwd")
|
|
131
|
+
if not cwd:
|
|
132
|
+
raise ValueError("cwd is required")
|
|
133
|
+
|
|
134
|
+
manager = _get_manager(self)
|
|
135
|
+
await manager.ensure_started()
|
|
136
|
+
|
|
137
|
+
params: Dict[str, Any] = {"cwd": cwd, "useStateDbOnly": True}
|
|
138
|
+
limit = message.get("limit")
|
|
139
|
+
cursor = message.get("cursor")
|
|
140
|
+
if limit is not None:
|
|
141
|
+
params["limit"] = limit
|
|
142
|
+
if cursor is not None:
|
|
143
|
+
params["cursor"] = cursor
|
|
144
|
+
|
|
145
|
+
result = await manager.bridge.call("thread/list", params)
|
|
146
|
+
payload = {
|
|
147
|
+
"event": "codex_thread_list",
|
|
148
|
+
"threads": result.get("threads", []),
|
|
149
|
+
"nextCursor": result.get("nextCursor"),
|
|
150
|
+
}
|
|
151
|
+
project_id = _project_id(message)
|
|
152
|
+
if project_id:
|
|
153
|
+
payload["project_id"] = project_id
|
|
154
|
+
return payload
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class CodexThreadStartHandler(AsyncHandler):
|
|
158
|
+
"""Start a new Codex thread in the project directory."""
|
|
159
|
+
|
|
160
|
+
def command_name(self) -> str:
|
|
161
|
+
return "codex_thread_start"
|
|
162
|
+
|
|
163
|
+
async def execute(self, message: Dict[str, Any]) -> Dict[str, Any]:
|
|
164
|
+
cwd = message.get("cwd")
|
|
165
|
+
project_id = _project_id(message)
|
|
166
|
+
if not cwd:
|
|
167
|
+
raise ValueError("cwd is required")
|
|
168
|
+
|
|
169
|
+
manager = _get_manager(self)
|
|
170
|
+
await manager.ensure_started()
|
|
171
|
+
|
|
172
|
+
params = {"cwd": cwd}
|
|
173
|
+
# Optional overrides from the UI (e.g. model picker later).
|
|
174
|
+
if message.get("model"):
|
|
175
|
+
params["model"] = message["model"]
|
|
176
|
+
if message.get("modelProvider"):
|
|
177
|
+
params["modelProvider"] = message["modelProvider"]
|
|
178
|
+
|
|
179
|
+
result = await manager.bridge.call("thread/start", params)
|
|
180
|
+
thread_id = result.get("id") or result.get("threadId")
|
|
181
|
+
if not thread_id:
|
|
182
|
+
raise CodexAppServerError("thread/start did not return a thread id")
|
|
183
|
+
|
|
184
|
+
manager.record_thread(thread_id, cwd, project_id or cwd)
|
|
185
|
+
|
|
186
|
+
payload = {
|
|
187
|
+
"event": "codex_thread_started",
|
|
188
|
+
"threadId": thread_id,
|
|
189
|
+
"thread": result,
|
|
190
|
+
}
|
|
191
|
+
if project_id:
|
|
192
|
+
payload["project_id"] = project_id
|
|
193
|
+
return payload
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class CodexThreadResumeHandler(AsyncHandler):
|
|
197
|
+
"""Resume a Codex thread and return its history."""
|
|
198
|
+
|
|
199
|
+
def command_name(self) -> str:
|
|
200
|
+
return "codex_thread_resume"
|
|
201
|
+
|
|
202
|
+
async def execute(self, message: Dict[str, Any]) -> Dict[str, Any]:
|
|
203
|
+
thread_id = message.get("threadId")
|
|
204
|
+
cwd = message.get("cwd")
|
|
205
|
+
project_id = _project_id(message)
|
|
206
|
+
if not thread_id:
|
|
207
|
+
raise ValueError("threadId is required")
|
|
208
|
+
|
|
209
|
+
manager = _get_manager(self)
|
|
210
|
+
await manager.ensure_started()
|
|
211
|
+
|
|
212
|
+
resume_params: Dict[str, Any] = {"threadId": thread_id}
|
|
213
|
+
if cwd:
|
|
214
|
+
resume_params["cwd"] = cwd
|
|
215
|
+
await manager.bridge.call("thread/resume", resume_params)
|
|
216
|
+
|
|
217
|
+
read_result = await manager.bridge.call("thread/read", {"threadId": thread_id})
|
|
218
|
+
items = read_result.get("items", read_result.get("messages", []))
|
|
219
|
+
|
|
220
|
+
if cwd and project_id:
|
|
221
|
+
manager.record_thread(thread_id, cwd, project_id)
|
|
222
|
+
|
|
223
|
+
payload = {
|
|
224
|
+
"event": "codex_thread_resumed",
|
|
225
|
+
"threadId": thread_id,
|
|
226
|
+
"items": items,
|
|
227
|
+
}
|
|
228
|
+
if project_id:
|
|
229
|
+
payload["project_id"] = project_id
|
|
230
|
+
return payload
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class CodexTurnStartHandler(AsyncHandler):
|
|
234
|
+
"""Send a user turn to a Codex thread."""
|
|
235
|
+
|
|
236
|
+
def command_name(self) -> str:
|
|
237
|
+
return "codex_turn_start"
|
|
238
|
+
|
|
239
|
+
async def execute(self, message: Dict[str, Any]) -> Dict[str, Any]:
|
|
240
|
+
thread_id = message.get("threadId")
|
|
241
|
+
text = message.get("text")
|
|
242
|
+
if not thread_id:
|
|
243
|
+
raise ValueError("threadId is required")
|
|
244
|
+
if text is None or text == "":
|
|
245
|
+
raise ValueError("text is required")
|
|
246
|
+
|
|
247
|
+
manager = _get_manager(self)
|
|
248
|
+
await manager.ensure_started()
|
|
249
|
+
|
|
250
|
+
params = {
|
|
251
|
+
"threadId": thread_id,
|
|
252
|
+
"input": [{"type": "text", "text": text}],
|
|
253
|
+
}
|
|
254
|
+
# Optional reasoning controls.
|
|
255
|
+
if message.get("effort"):
|
|
256
|
+
params["effort"] = message["effort"]
|
|
257
|
+
if message.get("summary"):
|
|
258
|
+
params["summary"] = message["summary"]
|
|
259
|
+
|
|
260
|
+
result = await manager.bridge.call("turn/start", params)
|
|
261
|
+
project_id = _project_id(message)
|
|
262
|
+
|
|
263
|
+
payload = {
|
|
264
|
+
"event": "codex_turn_started",
|
|
265
|
+
"threadId": thread_id,
|
|
266
|
+
"turn": result,
|
|
267
|
+
}
|
|
268
|
+
if project_id:
|
|
269
|
+
payload["project_id"] = project_id
|
|
270
|
+
return payload
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
class CodexTurnInterruptHandler(AsyncHandler):
|
|
274
|
+
"""Interrupt a running Codex turn."""
|
|
275
|
+
|
|
276
|
+
def command_name(self) -> str:
|
|
277
|
+
return "codex_turn_interrupt"
|
|
278
|
+
|
|
279
|
+
async def execute(self, message: Dict[str, Any]) -> Dict[str, Any]:
|
|
280
|
+
thread_id = message.get("threadId")
|
|
281
|
+
turn_id = message.get("turnId")
|
|
282
|
+
if not thread_id or not turn_id:
|
|
283
|
+
raise ValueError("threadId and turnId are required")
|
|
284
|
+
|
|
285
|
+
manager = _get_manager(self)
|
|
286
|
+
await manager.ensure_started()
|
|
287
|
+
await manager.bridge.call("turn/interrupt", {"threadId": thread_id, "turnId": turn_id})
|
|
288
|
+
|
|
289
|
+
project_id = _project_id(message)
|
|
290
|
+
payload = {"event": "codex_turn_interrupted", "threadId": thread_id, "turnId": turn_id}
|
|
291
|
+
if project_id:
|
|
292
|
+
payload["project_id"] = project_id
|
|
293
|
+
return payload
|