portacode 1.5.18.dev0__tar.gz → 1.5.19.dev1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/.gitignore +2 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/Makefile +9 -5
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/PKG-INFO +1 -1
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/_version.py +2 -2
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/cli.py +71 -5
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/codex_loopback_proxy.py +107 -1
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/client.py +10 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/codex_app_server.py +21 -3
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/handlers/__init__.py +2 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/handlers/codex_handlers.py +124 -5
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/handlers/file_handlers.py +54 -0
- portacode-1.5.19.dev1/portacode/connection/handlers/test_file_move_copy.py +81 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/terminal.py +2 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/data.py +16 -1
- portacode-1.5.19.dev1/portacode/github_credential.py +122 -0
- portacode-1.5.19.dev1/portacode/singleton.py +124 -0
- portacode-1.5.19.dev1/portacode/test_singleton.py +34 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/tests/test_codex_app_server.py +6 -1
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/tests/test_codex_handlers.py +65 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/tests/test_codex_loopback_proxy.py +66 -1
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode.egg-info/PKG-INFO +1 -1
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode.egg-info/SOURCES.txt +4 -3
- portacode-1.5.19.dev1/portacode.egg-info/entry_points.txt +3 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/setup.py +4 -3
- portacode-1.5.18.dev0/backup.sh +0 -42
- portacode-1.5.18.dev0/docker-compose.yaml +0 -292
- portacode-1.5.18.dev0/portacode.egg-info/entry_points.txt +0 -2
- portacode-1.5.18.dev0/restore.sh +0 -56
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/.claude/agents/communication-manager.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/.claude/settings.local.json +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/.gitmodules +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/LICENSE +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/MANIFEST.in +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/README.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/build_android.sh +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/connect.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/connect.sh +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/docs/cloudflared-domain-connect-containers-audit.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/docs/creative-team-brief-portacode.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/docs/device-health-alerting-plan.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/docs/devops-messaging-ab-tests.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/docs/homepage-dashboard-positioning-fixes.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/docs/images/device-transfer-button.png +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/docs/images/device-transfer-modal.png +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/docs/images/pair-device-button.png +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/docs/images/pairing-request.png +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/docs/images/student-workspace.png +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/docs/template-guide.html +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/README.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/simple_device/Dockerfile +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/simple_device/README.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/simple_device/data/device-01/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/simple_device/data/device-01/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/simple_device/docker-compose.yaml +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/Dockerfile +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/README.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-01/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-01/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-01/.local/share/portacode/run/gateway.pid +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-01/workspace/.gitignore +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-01/workspace/README.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-01/workspace/db.sqlite3 +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/__init__.cpython-311.pyc +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/settings.cpython-311.pyc +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/urls.cpython-311.pyc +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/wsgi.cpython-311.pyc +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-01/workspace/manage.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-01/workspace/requirements.txt +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-01/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/__init__.cpython-311.pyc +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/admin.cpython-311.pyc +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/apps.cpython-311.pyc +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/menu.cpython-311.pyc +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/models.cpython-311.pyc +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/urls.cpython-311.pyc +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/views.cpython-311.pyc +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/admin.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/apps.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/menu.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/migrations/__pycache__/__init__.cpython-311.pyc +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/models.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/tests.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/urls.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/views.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-02/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-02/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-02/workspace/README.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-02/workspace/manage.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-02/workspace/requirements.txt +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-02/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-02/workspace/treats/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-02/workspace/treats/admin.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-02/workspace/treats/apps.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-02/workspace/treats/menu.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-02/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-02/workspace/treats/models.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-02/workspace/treats/tests.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-02/workspace/treats/urls.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-02/workspace/treats/views.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-03/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-03/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-03/workspace/README.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-03/workspace/manage.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-03/workspace/requirements.txt +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-03/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-03/workspace/treats/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-03/workspace/treats/admin.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-03/workspace/treats/apps.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-03/workspace/treats/menu.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-03/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-03/workspace/treats/models.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-03/workspace/treats/tests.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-03/workspace/treats/urls.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-03/workspace/treats/views.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-04/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-04/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-04/workspace/README.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-04/workspace/manage.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-04/workspace/requirements.txt +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-04/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-04/workspace/treats/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-04/workspace/treats/admin.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-04/workspace/treats/apps.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-04/workspace/treats/menu.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-04/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-04/workspace/treats/models.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-04/workspace/treats/tests.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-04/workspace/treats/urls.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-04/workspace/treats/views.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-05/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-05/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-05/workspace/README.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-05/workspace/manage.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-05/workspace/requirements.txt +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-05/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-05/workspace/treats/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-05/workspace/treats/admin.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-05/workspace/treats/apps.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-05/workspace/treats/menu.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-05/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-05/workspace/treats/models.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-05/workspace/treats/tests.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-05/workspace/treats/urls.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-05/workspace/treats/views.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-06/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-06/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-06/workspace/README.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-06/workspace/manage.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-06/workspace/requirements.txt +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-06/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-06/workspace/treats/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-06/workspace/treats/admin.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-06/workspace/treats/apps.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-06/workspace/treats/menu.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-06/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-06/workspace/treats/models.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-06/workspace/treats/tests.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-06/workspace/treats/urls.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-06/workspace/treats/views.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-07/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-07/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-07/workspace/README.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-07/workspace/manage.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-07/workspace/requirements.txt +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-07/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-07/workspace/treats/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-07/workspace/treats/admin.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-07/workspace/treats/apps.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-07/workspace/treats/menu.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-07/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-07/workspace/treats/models.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-07/workspace/treats/tests.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-07/workspace/treats/urls.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-07/workspace/treats/views.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-08/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-08/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-08/workspace/README.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-08/workspace/manage.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-08/workspace/requirements.txt +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-08/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-08/workspace/treats/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-08/workspace/treats/admin.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-08/workspace/treats/apps.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-08/workspace/treats/menu.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-08/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-08/workspace/treats/models.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-08/workspace/treats/tests.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-08/workspace/treats/urls.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-08/workspace/treats/views.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-09/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-09/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-09/workspace/README.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-09/workspace/manage.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-09/workspace/requirements.txt +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-09/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-09/workspace/treats/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-09/workspace/treats/admin.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-09/workspace/treats/apps.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-09/workspace/treats/menu.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-09/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-09/workspace/treats/models.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-09/workspace/treats/tests.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-09/workspace/treats/urls.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-09/workspace/treats/views.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-10/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-10/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-10/workspace/README.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-10/workspace/manage.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-10/workspace/requirements.txt +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-10/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-10/workspace/treats/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-10/workspace/treats/admin.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-10/workspace/treats/apps.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-10/workspace/treats/menu.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-10/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-10/workspace/treats/models.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-10/workspace/treats/tests.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-10/workspace/treats/urls.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/data/student-10/workspace/treats/views.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/docker-compose.yaml +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/initial_content/README.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/initial_content/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/initial_content/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/initial_content/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/initial_content/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/initial_content/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/initial_content/manage.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/initial_content/requirements.txt +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/initial_content/templates/treats/home.html +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/initial_content/treats/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/initial_content/treats/admin.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/initial_content/treats/apps.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/initial_content/treats/menu.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/initial_content/treats/migrations/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/initial_content/treats/models.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/initial_content/treats/tests.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/initial_content/treats/urls.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/initial_content/treats/views.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/examples/workshop_fleet/instructions/WELCOME.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/README.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/__main__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/codex_prepare.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/codex_usage_limit.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/README.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/handlers/README.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/handlers/automation_v2_handlers.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/handlers/base.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/handlers/chunked_content.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/handlers/cloudflare_forwarding.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/handlers/cloudflare_tunnel.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/handlers/diff_handlers.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/handlers/file_transfer_handlers.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/handlers/project_aware_file_handlers.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/handlers/project_state/README.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/handlers/project_state/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/handlers/project_state/file_system_watcher.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/handlers/project_state/git_manager.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/handlers/project_state/handlers.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/handlers/project_state/manager.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/handlers/project_state/models.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/handlers/project_state/test_handlers.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/handlers/project_state/utils.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/handlers/project_state_handlers.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/handlers/proxmox_infra.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/handlers/registry.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/handlers/runtime_user.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/handlers/session.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/handlers/system_handlers.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/handlers/tab_factory.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/handlers/terminal_handlers.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/handlers/test_cloudflare_forwarding.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/handlers/test_proxmox_infra.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/handlers/test_runtime_user.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/handlers/update_handler.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/multiplex.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/test_webmin_proxy_config.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/webmin_proxy_config.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/exit_codes.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/keypair.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/link_capture/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/link_capture/__pycache__/__init__.cpython-311.pyc +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/link_capture/__pycache__/__init__.cpython-38.pyc +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/link_capture/bin/__pycache__/link_capture_wrapper.cpython-311.pyc +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/link_capture/bin/__pycache__/link_capture_wrapper.cpython-38.pyc +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/link_capture/bin/elinks +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/link_capture/bin/gio-open +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/link_capture/bin/gnome-open +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/link_capture/bin/gvfs-open +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/link_capture/bin/kde-open +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/link_capture/bin/kfmclient +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/link_capture/bin/link_capture_exec.sh +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/link_capture/bin/link_capture_wrapper.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/link_capture/bin/links +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/link_capture/bin/links2 +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/link_capture/bin/lynx +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/link_capture/bin/mate-open +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/link_capture/bin/netsurf +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/link_capture/bin/sensible-browser +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/link_capture/bin/w3m +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/link_capture/bin/x-www-browser +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/link_capture/bin/xdg-open +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/logging_categories.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/pairing.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/restart.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/service.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/static/js/test-ntp-clock.html +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/static/js/utils/ntp-clock.js +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/test_runtime_paths.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/test_service.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/test_terminal_exposed_services.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/tests/test_codex_prepare.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/tunneling/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/tunneling/cloudflared_login.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/tunneling/ensure_cloudflared.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/tunneling/ensure_pyyaml.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/tunneling/forwarding_state.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/tunneling/get_domain.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/tunneling/privileged.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/tunneling/service_install.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/tunneling/state.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/updater.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/utils/NTP_ARCHITECTURE.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/utils/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/utils/diff_apply.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/utils/diff_renderer.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/utils/ntp_clock.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/utils/runtime_paths.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode.egg-info/dependency_links.txt +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode.egg-info/requires.txt +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode.egg-info/top_level.txt +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode_for_school/.gitignore +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode_for_school/README.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode_for_school/persistent_workspace/Dockerfile +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode_for_school/persistent_workspace/README.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode_for_school/persistent_workspace/entrypoint.sh +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode_for_school/persistent_workspace/manage_classroom.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode_for_school/temporary_workspace/Dockerfile +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode_for_school/temporary_workspace/README.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode_for_school/temporary_workspace/docker-compose.yaml +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode_for_school/temporary_workspace/docs/device_transfere.png +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode_for_school/temporary_workspace/docs/pairing_approval.png +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode_for_school/temporary_workspace/entrypoint.sh +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/pyproject.toml +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/run_tests.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/scripts/diagnose_codex_gateway.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/setup.cfg +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/test.sh +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/test_modules/README.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/test_modules/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/test_modules/test_device_online.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/test_modules/test_file_operations.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/test_modules/test_git_status_ui.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/test_modules/test_login_flow.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/test_modules/test_navigate_testing_folder.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/test_modules/test_play_store_screenshots.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/test_modules/test_terminal_buffer_performance.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/test_modules/test_terminal_interaction.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/test_modules/test_terminal_loading_race_condition.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/test_modules/test_terminal_start.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/test_request_id.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/test_session_terminal_shutdown.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/testing_framework/.env.example +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/testing_framework/README.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/testing_framework/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/testing_framework/cli.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/testing_framework/core/__init__.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/testing_framework/core/base_test.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/testing_framework/core/cli_manager.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/testing_framework/core/hierarchical_runner.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/testing_framework/core/playwright_manager.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/testing_framework/core/runner.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/testing_framework/core/shared_cli_manager.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/testing_framework/core/test_discovery.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/testing_framework/requirements.txt +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/todo/UI_UX/opening_a_file_on_desktop_results_in_nothing.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/todo/UI_UX/server_occasionally_stops_communicating_with_all_devices.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/todo/agent_context_management.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/todo/django_server_time_sync.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/todo/issues/device_performance_degradation.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/todo/issues/git_data_not_captured_in_proxmox.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/todo/issues/indefinite_resource_loading.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/todo/issues/portacode_service_silently_down.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/todo/issues/premature_terminal_exit.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/todo/issues/project_cpu_hotspots.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/todo/issues/terminals_exit_upon_starting.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/todo/issues/websocket_client_silently_dead.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/todo/issues/wrong_item_classification_on_client_side.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/todo/smartphone_terminal_input_frustrations.md +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/tools/generate_play_store_assets.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/tools/pairing_tester.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/tools/run_screenshot_suite.sh +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/tools/test_python_ntp_clock.py +0 -0
- {portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/validate.sh +0 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Makefile for Python package release automation
|
|
2
2
|
|
|
3
|
-
.PHONY: release
|
|
3
|
+
.PHONY: release
|
|
4
|
+
|
|
5
|
+
PUSH_RELEASE_TAG ?= 1
|
|
4
6
|
|
|
5
7
|
release:
|
|
6
8
|
@echo "Usage: make release VERSION=x.y.z [or just 'make release' for date-based version]"
|
|
@@ -15,9 +17,11 @@ else
|
|
|
15
17
|
rm -rf dist/*
|
|
16
18
|
# Ensure we're on a clean state
|
|
17
19
|
git pull
|
|
18
|
-
# Create and push the tag
|
|
19
|
-
git tag -f v$(VERSION)
|
|
20
|
-
|
|
20
|
+
# Create and push the tag
|
|
21
|
+
git tag -f v$(VERSION)
|
|
22
|
+
ifeq ($(PUSH_RELEASE_TAG),1)
|
|
23
|
+
git push -f origin v$(VERSION)
|
|
24
|
+
endif
|
|
21
25
|
# Clean any previous builds
|
|
22
26
|
rm -rf build/ *.egg-info
|
|
23
27
|
# Build from the tagged state
|
|
@@ -26,4 +30,4 @@ else
|
|
|
26
30
|
endif
|
|
27
31
|
|
|
28
32
|
# Default target
|
|
29
|
-
release: release-real
|
|
33
|
+
release: release-real
|
|
@@ -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.19.dev1'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 5, 19, 'dev1')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -9,13 +9,15 @@ from pathlib import Path
|
|
|
9
9
|
import signal
|
|
10
10
|
import json
|
|
11
11
|
import socket
|
|
12
|
+
import time
|
|
12
13
|
from typing import Optional, Tuple
|
|
13
14
|
|
|
14
15
|
import click
|
|
15
16
|
import pyperclip
|
|
16
17
|
|
|
17
18
|
from . import __version__
|
|
18
|
-
from .data import get_pid_file, is_process_running
|
|
19
|
+
from .data import get_gateway_lock_file, get_pid_file, is_process_running
|
|
20
|
+
from .singleton import GatewayLock
|
|
19
21
|
from .keypair import (
|
|
20
22
|
get_or_create_keypair,
|
|
21
23
|
fingerprint_public_key,
|
|
@@ -64,6 +66,18 @@ def prepare_codex_command() -> None:
|
|
|
64
66
|
)
|
|
65
67
|
|
|
66
68
|
|
|
69
|
+
@cli.command("github-setup")
|
|
70
|
+
def github_setup_command() -> None:
|
|
71
|
+
"""Configure Git to request device-scoped GitHub credentials from Portacode."""
|
|
72
|
+
from .github_credential import configure_git
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
configure_git()
|
|
76
|
+
except (OSError, subprocess.CalledProcessError) as exc:
|
|
77
|
+
raise click.ClickException(f"Could not configure GitHub access: {exc}") from exc
|
|
78
|
+
click.echo(click.style("GitHub access is configured for this device.", fg="green"))
|
|
79
|
+
|
|
80
|
+
|
|
67
81
|
@cli.command()
|
|
68
82
|
@click.option("--gateway", "gateway", "-g", help="Gateway websocket URL (overrides env/ default)")
|
|
69
83
|
@click.option("--debug", "debug", is_flag=True, help="Enable debug logging")
|
|
@@ -164,8 +178,38 @@ def connect(
|
|
|
164
178
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
165
179
|
)
|
|
166
180
|
|
|
167
|
-
# 1. Ensure only a single connection per
|
|
181
|
+
# 1. Ensure only a single connection per host. The lifetime-held lock is
|
|
182
|
+
# atomic; the historical PID file is retained so rolling upgrades can
|
|
183
|
+
# interoperate with older Portacode versions.
|
|
168
184
|
pid_file = get_pid_file()
|
|
185
|
+
gateway_lock = GatewayLock(get_gateway_lock_file())
|
|
186
|
+
if not gateway_lock.acquire():
|
|
187
|
+
other_pid = gateway_lock.read_pid()
|
|
188
|
+
message = "Another portacode connection is active."
|
|
189
|
+
if other_pid:
|
|
190
|
+
message = f"Another portacode connection (PID {other_pid}) is active."
|
|
191
|
+
click.echo(click.style(message, fg="yellow"))
|
|
192
|
+
|
|
193
|
+
replace = non_interactive
|
|
194
|
+
if not non_interactive:
|
|
195
|
+
replace = click.confirm("Terminate the existing connection?", default=False)
|
|
196
|
+
if not replace:
|
|
197
|
+
click.echo("Aborting.")
|
|
198
|
+
sys.exit(1)
|
|
199
|
+
if not other_pid or not is_process_running(other_pid):
|
|
200
|
+
click.echo(click.style("Unable to identify the lock owner; aborting safely.", fg="red"))
|
|
201
|
+
sys.exit(1)
|
|
202
|
+
|
|
203
|
+
if non_interactive:
|
|
204
|
+
click.echo(click.style("Non-interactive mode: terminating the existing connection automatically.", fg="bright_black"))
|
|
205
|
+
_terminate_process(other_pid)
|
|
206
|
+
deadline = time.monotonic() + 5.0
|
|
207
|
+
while time.monotonic() < deadline and not gateway_lock.acquire():
|
|
208
|
+
time.sleep(0.05)
|
|
209
|
+
if not gateway_lock.held:
|
|
210
|
+
click.echo(click.style("Existing connection did not release the singleton lock; aborting.", fg="red"))
|
|
211
|
+
sys.exit(1)
|
|
212
|
+
|
|
169
213
|
if pid_file.exists():
|
|
170
214
|
try:
|
|
171
215
|
other_pid = int(pid_file.read_text())
|
|
@@ -191,13 +235,17 @@ def connect(
|
|
|
191
235
|
if non_interactive:
|
|
192
236
|
click.echo(click.style("Non-interactive mode: terminating the existing connection automatically.", fg="bright_black"))
|
|
193
237
|
_terminate_process(other_pid)
|
|
194
|
-
pid_file.unlink(missing_ok=True)
|
|
195
238
|
elif click.confirm("Terminate the existing connection?", default=False):
|
|
196
239
|
_terminate_process(other_pid)
|
|
197
|
-
pid_file.unlink(missing_ok=True)
|
|
198
240
|
else:
|
|
199
241
|
click.echo("Aborting.")
|
|
200
242
|
sys.exit(1)
|
|
243
|
+
_wait_for_process_exit(other_pid, timeout=5.0)
|
|
244
|
+
try:
|
|
245
|
+
if int(pid_file.read_text()) == other_pid:
|
|
246
|
+
pid_file.unlink(missing_ok=True)
|
|
247
|
+
except (FileNotFoundError, ValueError, OSError):
|
|
248
|
+
pass
|
|
201
249
|
else:
|
|
202
250
|
# Stale pidfile
|
|
203
251
|
pid_file.unlink(missing_ok=True)
|
|
@@ -408,9 +456,11 @@ def connect(
|
|
|
408
456
|
|
|
409
457
|
if exit_after_setup:
|
|
410
458
|
click.echo(click.style("Bootstrap steps completed. Exiting without starting a live connection.", fg="green"))
|
|
459
|
+
gateway_lock.release()
|
|
411
460
|
return
|
|
412
461
|
|
|
413
462
|
pid_file.write_text(str(os.getpid()))
|
|
463
|
+
gateway_lock.write_pid(os.getpid())
|
|
414
464
|
|
|
415
465
|
async def _main() -> None:
|
|
416
466
|
mgr = ConnectionManager(
|
|
@@ -424,7 +474,14 @@ def connect(
|
|
|
424
474
|
try:
|
|
425
475
|
asyncio.run(_main())
|
|
426
476
|
finally:
|
|
427
|
-
|
|
477
|
+
# An older client may have replaced this file while we were shutting
|
|
478
|
+
# down. Never delete a PID file that no longer belongs to us.
|
|
479
|
+
try:
|
|
480
|
+
if int(pid_file.read_text()) == os.getpid():
|
|
481
|
+
pid_file.unlink(missing_ok=True)
|
|
482
|
+
except (FileNotFoundError, ValueError, OSError):
|
|
483
|
+
pass
|
|
484
|
+
gateway_lock.release()
|
|
428
485
|
|
|
429
486
|
|
|
430
487
|
def _terminate_process(pid: int):
|
|
@@ -442,6 +499,15 @@ def _terminate_process(pid: int):
|
|
|
442
499
|
pass
|
|
443
500
|
|
|
444
501
|
|
|
502
|
+
def _wait_for_process_exit(pid: int, timeout: float) -> bool:
|
|
503
|
+
deadline = time.monotonic() + timeout
|
|
504
|
+
while time.monotonic() < deadline:
|
|
505
|
+
if not is_process_running(pid):
|
|
506
|
+
return True
|
|
507
|
+
time.sleep(0.05)
|
|
508
|
+
return not is_process_running(pid)
|
|
509
|
+
|
|
510
|
+
|
|
445
511
|
def _is_transient_proxmox_setup_failure(reply: object) -> bool:
|
|
446
512
|
if not isinstance(reply, dict):
|
|
447
513
|
return True
|
|
@@ -29,13 +29,106 @@ LOGGER = logging.getLogger(__name__)
|
|
|
29
29
|
CODEX_LOOPBACK_HOST = "127.0.0.1"
|
|
30
30
|
CODEX_LOOPBACK_PORT = 61789
|
|
31
31
|
DEFAULT_GATEWAY_URL = "https://codexapi.portacode.com/v1"
|
|
32
|
-
|
|
32
|
+
# Codex has already serialized local command output by the time it reaches this
|
|
33
|
+
# proxy. The ingress envelope therefore needs to be larger than the payload we
|
|
34
|
+
# are willing to send upstream, otherwise oversized tool output is rejected
|
|
35
|
+
# before ``sanitize_responses_request`` gets a chance to crop it.
|
|
36
|
+
MAX_REQUEST_BYTES = 32 * 1024 * 1024
|
|
37
|
+
MAX_FORWARD_REQUEST_BYTES = 10 * 1024 * 1024
|
|
38
|
+
MAX_TOOL_OUTPUT_BYTES = 64 * 1024
|
|
39
|
+
MAX_TOTAL_TOOL_OUTPUT_BYTES = 256 * 1024
|
|
33
40
|
ALLOWED_PATHS = {"/health", "/v1/models", "/v1/responses"}
|
|
34
41
|
MAX_CONCURRENT_UPSTREAM = 8
|
|
35
42
|
|
|
36
43
|
# Streaming read can idle between SSE chunks; connect/write stay bounded.
|
|
37
44
|
_UPSTREAM_TIMEOUT = httpx.Timeout(connect=20.0, read=600.0, write=60.0, pool=20.0)
|
|
38
45
|
|
|
46
|
+
_TOOL_OUTPUT_TYPES = frozenset(
|
|
47
|
+
{
|
|
48
|
+
"function_call_output",
|
|
49
|
+
"computer_call_output",
|
|
50
|
+
"custom_tool_call_output",
|
|
51
|
+
"local_shell_call_output",
|
|
52
|
+
"shell_call_output",
|
|
53
|
+
"command_execution_output",
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _truncate_utf8(value: str, limit: int = MAX_TOOL_OUTPUT_BYTES) -> tuple[str, int]:
|
|
59
|
+
"""Keep useful head/tail context while enforcing a UTF-8 byte ceiling."""
|
|
60
|
+
raw = value.encode("utf-8")
|
|
61
|
+
if len(raw) <= limit:
|
|
62
|
+
return value, 0
|
|
63
|
+
if limit <= 0:
|
|
64
|
+
return "", len(raw)
|
|
65
|
+
marker_budget = 160
|
|
66
|
+
content_budget = max(limit - marker_budget, 0)
|
|
67
|
+
head_size = content_budget * 3 // 4
|
|
68
|
+
tail_size = content_budget - head_size
|
|
69
|
+
head = raw[:head_size].decode("utf-8", errors="ignore")
|
|
70
|
+
tail = raw[-tail_size:].decode("utf-8", errors="ignore") if tail_size else ""
|
|
71
|
+
omitted = len(raw) - len(head.encode("utf-8")) - len(tail.encode("utf-8"))
|
|
72
|
+
marker = (
|
|
73
|
+
f"\n\n[Portacode truncated {omitted} bytes of oversized tool output. "
|
|
74
|
+
"Narrow the command or inspect a specific file.]\n\n"
|
|
75
|
+
)
|
|
76
|
+
if len(marker.encode("utf-8")) >= limit:
|
|
77
|
+
return marker.encode("utf-8")[:limit].decode("utf-8", errors="ignore"), len(raw)
|
|
78
|
+
result = f"{head}{marker}{tail}"
|
|
79
|
+
# The dynamic marker can exceed marker_budget by a few bytes.
|
|
80
|
+
while len(result.encode("utf-8")) > limit and head:
|
|
81
|
+
head = head[:-128]
|
|
82
|
+
result = f"{head}{marker}{tail}"
|
|
83
|
+
return result, omitted
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def sanitize_responses_request(body: bytes) -> tuple[bytes, int, int]:
|
|
87
|
+
"""Crop only recognized Responses API tool-result fields.
|
|
88
|
+
|
|
89
|
+
Unknown JSON shapes pass through untouched so Codex/API upgrades cannot be
|
|
90
|
+
silently corrupted. Returns (body, fields_changed, bytes_omitted).
|
|
91
|
+
"""
|
|
92
|
+
try:
|
|
93
|
+
payload = json.loads(body)
|
|
94
|
+
except (json.JSONDecodeError, UnicodeDecodeError):
|
|
95
|
+
return body, 0, 0
|
|
96
|
+
if not isinstance(payload, dict) or not isinstance(payload.get("input"), list):
|
|
97
|
+
return body, 0, 0
|
|
98
|
+
|
|
99
|
+
changed = 0
|
|
100
|
+
omitted_total = 0
|
|
101
|
+
retained_total = 0
|
|
102
|
+
for item in payload["input"]:
|
|
103
|
+
if not isinstance(item, dict) or item.get("type") not in _TOOL_OUTPUT_TYPES:
|
|
104
|
+
continue
|
|
105
|
+
for key in ("output", "content"):
|
|
106
|
+
value = item.get(key)
|
|
107
|
+
if isinstance(value, str):
|
|
108
|
+
remaining = max(MAX_TOTAL_TOOL_OUTPUT_BYTES - retained_total, 0)
|
|
109
|
+
cropped, omitted = _truncate_utf8(value, min(MAX_TOOL_OUTPUT_BYTES, remaining))
|
|
110
|
+
if omitted:
|
|
111
|
+
item[key] = cropped
|
|
112
|
+
changed += 1
|
|
113
|
+
omitted_total += omitted
|
|
114
|
+
retained_total += len(cropped.encode("utf-8"))
|
|
115
|
+
elif isinstance(value, list):
|
|
116
|
+
for part in value:
|
|
117
|
+
if not isinstance(part, dict) or not isinstance(part.get("text"), str):
|
|
118
|
+
continue
|
|
119
|
+
remaining = max(MAX_TOTAL_TOOL_OUTPUT_BYTES - retained_total, 0)
|
|
120
|
+
cropped, omitted = _truncate_utf8(
|
|
121
|
+
part["text"], min(MAX_TOOL_OUTPUT_BYTES, remaining)
|
|
122
|
+
)
|
|
123
|
+
if omitted:
|
|
124
|
+
part["text"] = cropped
|
|
125
|
+
changed += 1
|
|
126
|
+
omitted_total += omitted
|
|
127
|
+
retained_total += len(cropped.encode("utf-8"))
|
|
128
|
+
if not changed:
|
|
129
|
+
return body, 0, 0
|
|
130
|
+
return json.dumps(payload, ensure_ascii=False, separators=(",", ":")).encode("utf-8"), changed, omitted_total
|
|
131
|
+
|
|
39
132
|
FORWARD_RESPONSE_HEADERS = frozenset(
|
|
40
133
|
{
|
|
41
134
|
"content-type",
|
|
@@ -366,6 +459,19 @@ class CodexLoopbackProxy:
|
|
|
366
459
|
*,
|
|
367
460
|
req_id: str,
|
|
368
461
|
) -> None:
|
|
462
|
+
if method == "POST" and path == "/v1/responses" and body:
|
|
463
|
+
body, cropped_fields, omitted_bytes = sanitize_responses_request(body)
|
|
464
|
+
if cropped_fields:
|
|
465
|
+
LOGGER.warning(
|
|
466
|
+
"codex-proxy[%s] cropped %s oversized tool result field(s), omitting %s bytes",
|
|
467
|
+
req_id,
|
|
468
|
+
cropped_fields,
|
|
469
|
+
omitted_bytes,
|
|
470
|
+
)
|
|
471
|
+
if len(body) > MAX_FORWARD_REQUEST_BYTES:
|
|
472
|
+
raise ValueError(
|
|
473
|
+
"Request body remains too large after local tool-output truncation"
|
|
474
|
+
)
|
|
369
475
|
headers = self._signature_headers(method, path, body)
|
|
370
476
|
content_type = request_headers.get("content-type")
|
|
371
477
|
if content_type:
|
|
@@ -213,6 +213,16 @@ class ConnectionManager:
|
|
|
213
213
|
else:
|
|
214
214
|
print("Press Ctrl+C to close the connection.")
|
|
215
215
|
# Finished authentication flow.
|
|
216
|
+
try:
|
|
217
|
+
from portacode.github_credential import configure_git
|
|
218
|
+
|
|
219
|
+
await asyncio.to_thread(configure_git)
|
|
220
|
+
except Exception as exc:
|
|
221
|
+
logger.warning(
|
|
222
|
+
"Could not configure device-scoped GitHub credentials: %s",
|
|
223
|
+
LogCategory.CONNECTION,
|
|
224
|
+
exc,
|
|
225
|
+
)
|
|
216
226
|
await self._register_initial_project_paths()
|
|
217
227
|
|
|
218
228
|
async def _start_codex_loopback_proxy(self) -> None:
|
|
@@ -14,6 +14,12 @@ from typing import Any, Awaitable, Callable, Dict, List, Optional
|
|
|
14
14
|
LOGGER = logging.getLogger(__name__)
|
|
15
15
|
|
|
16
16
|
DEFAULT_CODEX_COMMAND = ["codex", "app-server", "--listen", "stdio://"]
|
|
17
|
+
# JSON-RPC is newline framed and command completion notifications may contain
|
|
18
|
+
# the aggregate output. This ingress envelope must be larger than the UI
|
|
19
|
+
# output limit so the handler can crop the notification instead of readline()
|
|
20
|
+
# dropping it before dispatch. It remains bounded to protect the agent from a
|
|
21
|
+
# runaway app-server process.
|
|
22
|
+
CODEX_STDIO_MESSAGE_LIMIT = 32 * 1024 * 1024
|
|
17
23
|
|
|
18
24
|
|
|
19
25
|
class CodexAppServerError(RuntimeError):
|
|
@@ -149,6 +155,7 @@ class CodexAppServer:
|
|
|
149
155
|
"stdout": asyncio.subprocess.PIPE,
|
|
150
156
|
"stderr": asyncio.subprocess.PIPE,
|
|
151
157
|
"env": child_env,
|
|
158
|
+
"limit": CODEX_STDIO_MESSAGE_LIMIT,
|
|
152
159
|
}
|
|
153
160
|
if spawn_cwd:
|
|
154
161
|
spawn_kwargs["cwd"] = spawn_cwd
|
|
@@ -292,7 +299,18 @@ class CodexAppServer:
|
|
|
292
299
|
assert self._proc is not None and self._proc.stdout is not None
|
|
293
300
|
try:
|
|
294
301
|
while True:
|
|
295
|
-
|
|
302
|
+
try:
|
|
303
|
+
line = await self._proc.stdout.readline()
|
|
304
|
+
except ValueError as exc:
|
|
305
|
+
# asyncio converts an over-limit newline frame to
|
|
306
|
+
# ValueError. Drop that one frame but keep consuming future
|
|
307
|
+
# notifications; the app-server process is still healthy.
|
|
308
|
+
LOGGER.error(
|
|
309
|
+
"Dropping Codex app-server JSON-RPC frame larger than %s bytes: %s",
|
|
310
|
+
CODEX_STDIO_MESSAGE_LIMIT,
|
|
311
|
+
exc,
|
|
312
|
+
)
|
|
313
|
+
continue
|
|
296
314
|
if not line:
|
|
297
315
|
break
|
|
298
316
|
line = line.decode("utf-8", errors="replace").strip()
|
|
@@ -309,8 +327,8 @@ class CodexAppServer:
|
|
|
309
327
|
except Exception:
|
|
310
328
|
LOGGER.exception("codex app-server read loop failed")
|
|
311
329
|
finally:
|
|
312
|
-
#
|
|
313
|
-
if not self._closed:
|
|
330
|
+
# A reader/protocol failure is not necessarily a process exit.
|
|
331
|
+
if not self._closed and self._proc is not None and self._proc.returncode is not None:
|
|
314
332
|
asyncio.create_task(self._on_reader_exit())
|
|
315
333
|
|
|
316
334
|
async def _stderr_loop(self) -> None:
|
|
@@ -25,6 +25,7 @@ from .file_handlers import (
|
|
|
25
25
|
FileCreateHandler,
|
|
26
26
|
FolderCreateHandler,
|
|
27
27
|
FileRenameHandler,
|
|
28
|
+
FileMoveCopyHandler,
|
|
28
29
|
FileSearchHandler,
|
|
29
30
|
ContentRequestHandler,
|
|
30
31
|
)
|
|
@@ -93,6 +94,7 @@ __all__ = [
|
|
|
93
94
|
"FileCreateHandler",
|
|
94
95
|
"FolderCreateHandler",
|
|
95
96
|
"FileRenameHandler",
|
|
97
|
+
"FileMoveCopyHandler",
|
|
96
98
|
"FileSearchHandler",
|
|
97
99
|
"ContentRequestHandler",
|
|
98
100
|
"FileApplyDiffHandler",
|
{portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/handlers/codex_handlers.py
RENAMED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
|
+
import copy
|
|
6
7
|
import logging
|
|
7
8
|
import mimetypes
|
|
8
9
|
from pathlib import Path
|
|
@@ -13,6 +14,11 @@ from portacode.connection.codex_app_server import CodexAppServer, CodexAppServer
|
|
|
13
14
|
from portacode.connection.handlers.base import AsyncHandler
|
|
14
15
|
|
|
15
16
|
LOGGER = logging.getLogger(__name__)
|
|
17
|
+
MAX_UI_COMMAND_OUTPUT_BYTES = 256 * 1024
|
|
18
|
+
MAX_LIVE_TEXT_BYTES = 512 * 1024
|
|
19
|
+
MAX_LIVE_ITEMS_PER_THREAD = 64
|
|
20
|
+
MAX_LIVE_SNAPSHOT_BYTES = 4 * 1024 * 1024
|
|
21
|
+
MAX_TRACKED_LIVE_THREADS = 64
|
|
16
22
|
|
|
17
23
|
_IMAGE_SUFFIXES = {
|
|
18
24
|
".png",
|
|
@@ -121,6 +127,9 @@ class CodexChatManager:
|
|
|
121
127
|
self._cwd_project: Dict[str, str] = {}
|
|
122
128
|
self._thread_active_turn: Dict[str, str] = {}
|
|
123
129
|
self._thread_sessions: Dict[str, set[str]] = {}
|
|
130
|
+
# Materialized in-progress items live with the device agent, not a
|
|
131
|
+
# browser websocket. New/reconnected tabs receive these on resume.
|
|
132
|
+
self._thread_live_items: Dict[str, Dict[str, Dict[str, Any]]] = {}
|
|
124
133
|
# Survives page reloads via codex_status so the IDE can restore UI.
|
|
125
134
|
self._prepare_running = False
|
|
126
135
|
self._prepare_step: Optional[str] = None
|
|
@@ -174,8 +183,16 @@ class CodexChatManager:
|
|
|
174
183
|
turn_id = params.get("turnId") or turn.get("id")
|
|
175
184
|
if thread_id and method == "turn/started" and turn_id:
|
|
176
185
|
self._thread_active_turn[str(thread_id)] = str(turn_id)
|
|
186
|
+
self._thread_live_items[str(thread_id)] = {}
|
|
187
|
+
while len(self._thread_live_items) > MAX_TRACKED_LIVE_THREADS:
|
|
188
|
+
oldest = next(iter(self._thread_live_items))
|
|
189
|
+
if oldest == str(thread_id) and len(self._thread_live_items) > 1:
|
|
190
|
+
oldest = next(key for key in self._thread_live_items if key != str(thread_id))
|
|
191
|
+
self._thread_live_items.pop(oldest, None)
|
|
177
192
|
elif thread_id and method == "turn/completed":
|
|
178
193
|
self._thread_active_turn.pop(str(thread_id), None)
|
|
194
|
+
if thread_id:
|
|
195
|
+
params = self._materialize_and_bound_notification(str(thread_id), method, params)
|
|
179
196
|
project_id = self._resolve_project_id(params)
|
|
180
197
|
if not project_id:
|
|
181
198
|
# Unresolved project_id would fan out to every open project tab.
|
|
@@ -195,6 +212,98 @@ class CodexChatManager:
|
|
|
195
212
|
explicit_sessions=list(self._thread_sessions.get(str(thread_id), set())) if thread_id else None,
|
|
196
213
|
)
|
|
197
214
|
|
|
215
|
+
@staticmethod
|
|
216
|
+
def _bounded_text(value: str, limit: int) -> str:
|
|
217
|
+
raw = value.encode("utf-8")
|
|
218
|
+
if len(raw) <= limit:
|
|
219
|
+
return value
|
|
220
|
+
marker = "\n[Portacode truncated oversized live output.]\n"
|
|
221
|
+
budget = max(limit - len(marker.encode("utf-8")), 0)
|
|
222
|
+
return raw[:budget].decode("utf-8", errors="ignore") + marker
|
|
223
|
+
|
|
224
|
+
def _materialize_and_bound_notification(
|
|
225
|
+
self, thread_id: str, method: str, params: Dict[str, Any]
|
|
226
|
+
) -> Dict[str, Any]:
|
|
227
|
+
"""Bound UI command output and retain a reconnectable live snapshot."""
|
|
228
|
+
params = copy.deepcopy(params)
|
|
229
|
+
raw = params.get("item") if isinstance(params.get("item"), dict) else params
|
|
230
|
+
item_id = raw.get("id") or params.get("itemId") or params.get("id")
|
|
231
|
+
if not item_id or not method.startswith("item/"):
|
|
232
|
+
return params
|
|
233
|
+
|
|
234
|
+
item_id = str(item_id)
|
|
235
|
+
items = self._thread_live_items.setdefault(thread_id, {})
|
|
236
|
+
item = items.setdefault(item_id, {"id": item_id})
|
|
237
|
+
incoming_type = raw.get("type")
|
|
238
|
+
if incoming_type:
|
|
239
|
+
item["type"] = incoming_type
|
|
240
|
+
elif "commandExecution" in method:
|
|
241
|
+
item["type"] = "commandExecution"
|
|
242
|
+
item.setdefault("role", "tool")
|
|
243
|
+
elif "reasoning" in method:
|
|
244
|
+
item["type"] = "reasoning"
|
|
245
|
+
item.setdefault("role", "reasoning")
|
|
246
|
+
elif "agentMessage" in method or "message/delta" in method:
|
|
247
|
+
item["type"] = "agentMessage"
|
|
248
|
+
item.setdefault("role", "assistant")
|
|
249
|
+
for key in ("role", "command", "status"):
|
|
250
|
+
if raw.get(key) is not None:
|
|
251
|
+
item[key] = copy.deepcopy(raw[key])
|
|
252
|
+
|
|
253
|
+
is_command = "commandExecution" in method or item.get("type") == "commandExecution"
|
|
254
|
+
text_key = None
|
|
255
|
+
incoming = ""
|
|
256
|
+
if method == "item/commandExecution/outputDelta":
|
|
257
|
+
text_key, incoming = "output", raw.get("delta") or raw.get("output") or ""
|
|
258
|
+
elif method in {"item/agentMessage/delta", "item/message/delta"}:
|
|
259
|
+
text_key, incoming = "text", raw.get("delta") or raw.get("text") or ""
|
|
260
|
+
elif method in {"item/reasoning/textDelta", "item/reasoning/summaryTextDelta"}:
|
|
261
|
+
text_key, incoming = "reasoning", raw.get("delta") or raw.get("text") or ""
|
|
262
|
+
elif method in {"item/started", "item/completed"}:
|
|
263
|
+
if is_command:
|
|
264
|
+
text_key = "output"
|
|
265
|
+
incoming = raw.get("output") or (raw.get("commandExecution") or {}).get("output") or raw.get("aggregatedOutput") or ""
|
|
266
|
+
else:
|
|
267
|
+
incoming = raw.get("text") or ""
|
|
268
|
+
text_key = "text" if incoming else None
|
|
269
|
+
|
|
270
|
+
if text_key and isinstance(incoming, str):
|
|
271
|
+
limit = MAX_UI_COMMAND_OUTPUT_BYTES if text_key == "output" else MAX_LIVE_TEXT_BYTES
|
|
272
|
+
if method in {"item/started", "item/completed"}:
|
|
273
|
+
combined = incoming
|
|
274
|
+
else:
|
|
275
|
+
combined = str(item.get(text_key) or "") + incoming
|
|
276
|
+
bounded = self._bounded_text(combined, limit)
|
|
277
|
+
item[text_key] = bounded
|
|
278
|
+
# Forward only the portion that fits for deltas; completed items
|
|
279
|
+
# carry a bounded aggregate so the browser cannot restore the blob.
|
|
280
|
+
if method.endswith("/outputDelta") or method.endswith("/delta") or method.endswith("textDelta") or method.endswith("summaryTextDelta"):
|
|
281
|
+
previous = combined[:-len(incoming)] if incoming else combined
|
|
282
|
+
allowed = bounded[len(previous):] if bounded.startswith(previous) else ""
|
|
283
|
+
if "delta" in raw:
|
|
284
|
+
raw["delta"] = allowed
|
|
285
|
+
elif text_key in raw:
|
|
286
|
+
raw[text_key] = allowed
|
|
287
|
+
elif text_key == "output":
|
|
288
|
+
raw["output"] = bounded
|
|
289
|
+
raw.pop("aggregatedOutput", None)
|
|
290
|
+
if isinstance(raw.get("commandExecution"), dict):
|
|
291
|
+
raw["commandExecution"]["output"] = bounded
|
|
292
|
+
while len(items) > MAX_LIVE_ITEMS_PER_THREAD:
|
|
293
|
+
oldest = next(iter(items))
|
|
294
|
+
if oldest == item_id and len(items) > 1:
|
|
295
|
+
oldest = next(key for key in items if key != item_id)
|
|
296
|
+
items.pop(oldest, None)
|
|
297
|
+
while sum(len(str(value).encode("utf-8")) for value in items.values()) > MAX_LIVE_SNAPSHOT_BYTES and len(items) > 1:
|
|
298
|
+
oldest = next(iter(items))
|
|
299
|
+
if oldest == item_id:
|
|
300
|
+
oldest = next(key for key in items if key != item_id)
|
|
301
|
+
items.pop(oldest, None)
|
|
302
|
+
return params
|
|
303
|
+
|
|
304
|
+
def live_items(self, thread_id: str) -> List[Dict[str, Any]]:
|
|
305
|
+
return copy.deepcopy(list(self._thread_live_items.get(str(thread_id), {}).values()))
|
|
306
|
+
|
|
198
307
|
def _remember_thread_from_params(self, params: Dict[str, Any]) -> None:
|
|
199
308
|
"""Opportunistically map threadId → project when cwd is already known."""
|
|
200
309
|
if not isinstance(params, dict):
|
|
@@ -356,6 +465,7 @@ async def _resume_thread_history(
|
|
|
356
465
|
*,
|
|
357
466
|
thread_id: str,
|
|
358
467
|
cwd: Optional[str],
|
|
468
|
+
already_active: bool = False,
|
|
359
469
|
) -> tuple[Dict[str, Any], list, Optional[Dict[str, Any]]]:
|
|
360
470
|
"""Resume a thread without hydrating the full rollout in one giant payload.
|
|
361
471
|
|
|
@@ -366,11 +476,12 @@ async def _resume_thread_history(
|
|
|
366
476
|
if cwd:
|
|
367
477
|
resume_params["cwd"] = cwd
|
|
368
478
|
|
|
369
|
-
resume_result: Dict[str, Any] = {}
|
|
479
|
+
resume_result: Dict[str, Any] = {"thread": {"id": thread_id}}
|
|
370
480
|
try:
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
481
|
+
if not already_active:
|
|
482
|
+
resume_result = await manager.bridge.call(
|
|
483
|
+
"thread/resume", resume_params, timeout=120.0
|
|
484
|
+
)
|
|
374
485
|
except CodexAppServerError as exc:
|
|
375
486
|
# Older Codex builds may not know excludeTurns — fall back carefully.
|
|
376
487
|
message = str(exc).lower()
|
|
@@ -639,15 +750,23 @@ class CodexThreadResumeHandler(CodexAsyncHandler):
|
|
|
639
750
|
manager.subscribe_thread(thread_id, message.get("source_client_session"))
|
|
640
751
|
|
|
641
752
|
resume_result, items, active_turn = await _resume_thread_history(
|
|
642
|
-
manager,
|
|
753
|
+
manager,
|
|
754
|
+
thread_id=thread_id,
|
|
755
|
+
cwd=cwd,
|
|
756
|
+
already_active=str(thread_id) in manager._thread_active_turn,
|
|
643
757
|
)
|
|
644
758
|
thread = _thread_from_result(resume_result)
|
|
759
|
+
if not active_turn:
|
|
760
|
+
known_turn_id = manager._thread_active_turn.get(str(thread_id))
|
|
761
|
+
if known_turn_id:
|
|
762
|
+
active_turn = {"id": known_turn_id, "status": "inProgress"}
|
|
645
763
|
|
|
646
764
|
payload = {
|
|
647
765
|
"event": "codex_thread_resumed",
|
|
648
766
|
"threadId": thread_id,
|
|
649
767
|
"thread": thread,
|
|
650
768
|
"items": items,
|
|
769
|
+
"liveItems": manager.live_items(thread_id),
|
|
651
770
|
}
|
|
652
771
|
if active_turn:
|
|
653
772
|
payload["activeTurn"] = active_turn
|
{portacode-1.5.18.dev0 → portacode-1.5.19.dev1}/portacode/connection/handlers/file_handlers.py
RENAMED
|
@@ -506,6 +506,60 @@ class FileRenameHandler(SyncHandler):
|
|
|
506
506
|
raise RuntimeError(f"Failed to rename: {e}")
|
|
507
507
|
|
|
508
508
|
|
|
509
|
+
class FileMoveCopyHandler(SyncHandler):
|
|
510
|
+
"""Move or copy a file/folder into an existing directory."""
|
|
511
|
+
|
|
512
|
+
@property
|
|
513
|
+
def command_name(self) -> str:
|
|
514
|
+
return "file_move_copy"
|
|
515
|
+
|
|
516
|
+
def execute(self, message: Dict[str, Any]) -> Dict[str, Any]:
|
|
517
|
+
source_raw = message.get("source_path")
|
|
518
|
+
destination_raw = message.get("destination_path")
|
|
519
|
+
operation = message.get("operation", "move")
|
|
520
|
+
if not source_raw or not destination_raw:
|
|
521
|
+
raise ValueError("source_path and destination_path are required")
|
|
522
|
+
if operation not in ("move", "copy"):
|
|
523
|
+
raise ValueError("operation must be 'move' or 'copy'")
|
|
524
|
+
|
|
525
|
+
source = Path(source_raw).resolve()
|
|
526
|
+
destination_dir = Path(destination_raw).resolve()
|
|
527
|
+
if not source.exists():
|
|
528
|
+
raise ValueError(f"Source does not exist: {source_raw}")
|
|
529
|
+
if not destination_dir.is_dir():
|
|
530
|
+
raise ValueError(f"Destination is not a directory: {destination_raw}")
|
|
531
|
+
|
|
532
|
+
target = destination_dir / source.name
|
|
533
|
+
if target.exists():
|
|
534
|
+
raise ValueError(f"An item named '{source.name}' already exists in the destination")
|
|
535
|
+
if source.parent == destination_dir:
|
|
536
|
+
raise ValueError("The item is already in that folder")
|
|
537
|
+
if source.is_dir() and (destination_dir == source or source in destination_dir.parents):
|
|
538
|
+
raise ValueError("A folder cannot be moved or copied into itself")
|
|
539
|
+
|
|
540
|
+
try:
|
|
541
|
+
if operation == "move":
|
|
542
|
+
shutil.move(str(source), str(target))
|
|
543
|
+
elif source.is_dir():
|
|
544
|
+
shutil.copytree(source, target, copy_function=shutil.copy2)
|
|
545
|
+
else:
|
|
546
|
+
shutil.copy2(source, target)
|
|
547
|
+
except PermissionError as exc:
|
|
548
|
+
raise RuntimeError(f"Permission denied: {exc}") from exc
|
|
549
|
+
except OSError as exc:
|
|
550
|
+
raise RuntimeError(f"Failed to {operation}: {exc}") from exc
|
|
551
|
+
|
|
552
|
+
return {
|
|
553
|
+
"event": "file_move_copy_response",
|
|
554
|
+
"source_path": str(source),
|
|
555
|
+
"destination_path": str(destination_dir),
|
|
556
|
+
"target_path": str(target),
|
|
557
|
+
"operation": operation,
|
|
558
|
+
"is_directory": target.is_dir(),
|
|
559
|
+
"success": True,
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
|
|
509
563
|
class FileSearchHandler(SyncHandler):
|
|
510
564
|
"""Handler for searching text within files under a root directory."""
|
|
511
565
|
|