portacode 1.5.17.dev1__tar.gz → 1.5.17.dev3__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.
Files changed (439) hide show
  1. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/PKG-INFO +1 -1
  2. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/_version.py +2 -2
  3. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/codex_prepare.py +86 -15
  4. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/codex_app_server.py +3 -1
  5. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/handlers/project_state/git_manager.py +128 -178
  6. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/handlers/runtime_user.py +51 -6
  7. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/handlers/test_runtime_user.py +15 -4
  8. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/tests/test_codex_app_server.py +10 -2
  9. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/tests/test_codex_prepare.py +31 -1
  10. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode.egg-info/PKG-INFO +1 -1
  11. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/.claude/agents/communication-manager.md +0 -0
  12. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/.claude/settings.local.json +0 -0
  13. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/.gitignore +0 -0
  14. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/.gitmodules +0 -0
  15. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/LICENSE +0 -0
  16. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/MANIFEST.in +0 -0
  17. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/Makefile +0 -0
  18. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/README.md +0 -0
  19. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/backup.sh +0 -0
  20. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/build_android.sh +0 -0
  21. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/connect.py +0 -0
  22. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/connect.sh +0 -0
  23. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/docker-compose.yaml +0 -0
  24. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/docs/cloudflared-domain-connect-containers-audit.md +0 -0
  25. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/docs/creative-team-brief-portacode.md +0 -0
  26. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/docs/device-health-alerting-plan.md +0 -0
  27. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/docs/devops-messaging-ab-tests.md +0 -0
  28. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/docs/homepage-dashboard-positioning-fixes.md +0 -0
  29. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/docs/images/device-transfer-button.png +0 -0
  30. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/docs/images/device-transfer-modal.png +0 -0
  31. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/docs/images/pair-device-button.png +0 -0
  32. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/docs/images/pairing-request.png +0 -0
  33. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/docs/images/student-workspace.png +0 -0
  34. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/docs/template-guide.html +0 -0
  35. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/README.md +0 -0
  36. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/simple_device/Dockerfile +0 -0
  37. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/simple_device/README.md +0 -0
  38. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/simple_device/data/device-01/.local/share/portacode/keys/id_portacode +0 -0
  39. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/simple_device/data/device-01/.local/share/portacode/keys/id_portacode.pub +0 -0
  40. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/simple_device/docker-compose.yaml +0 -0
  41. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/Dockerfile +0 -0
  42. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/README.md +0 -0
  43. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-01/.local/share/portacode/keys/id_portacode +0 -0
  44. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-01/.local/share/portacode/keys/id_portacode.pub +0 -0
  45. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-01/.local/share/portacode/run/gateway.pid +0 -0
  46. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-01/workspace/.gitignore +0 -0
  47. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-01/workspace/README.md +0 -0
  48. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-01/workspace/db.sqlite3 +0 -0
  49. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__init__.py +0 -0
  50. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/__init__.cpython-311.pyc +0 -0
  51. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/settings.cpython-311.pyc +0 -0
  52. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/urls.cpython-311.pyc +0 -0
  53. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/wsgi.cpython-311.pyc +0 -0
  54. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/asgi.py +0 -0
  55. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/settings.py +0 -0
  56. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/urls.py +0 -0
  57. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/wsgi.py +0 -0
  58. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-01/workspace/manage.py +0 -0
  59. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-01/workspace/requirements.txt +0 -0
  60. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-01/workspace/templates/treats/home.html +0 -0
  61. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-01/workspace/treats/__init__.py +0 -0
  62. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/__init__.cpython-311.pyc +0 -0
  63. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/admin.cpython-311.pyc +0 -0
  64. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/apps.cpython-311.pyc +0 -0
  65. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/menu.cpython-311.pyc +0 -0
  66. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/models.cpython-311.pyc +0 -0
  67. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/urls.cpython-311.pyc +0 -0
  68. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/views.cpython-311.pyc +0 -0
  69. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-01/workspace/treats/admin.py +0 -0
  70. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-01/workspace/treats/apps.py +0 -0
  71. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-01/workspace/treats/menu.py +0 -0
  72. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-01/workspace/treats/migrations/__init__.py +0 -0
  73. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-01/workspace/treats/migrations/__pycache__/__init__.cpython-311.pyc +0 -0
  74. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-01/workspace/treats/models.py +0 -0
  75. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-01/workspace/treats/tests.py +0 -0
  76. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-01/workspace/treats/urls.py +0 -0
  77. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-01/workspace/treats/views.py +0 -0
  78. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-02/.local/share/portacode/keys/id_portacode +0 -0
  79. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-02/.local/share/portacode/keys/id_portacode.pub +0 -0
  80. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-02/workspace/README.md +0 -0
  81. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/__init__.py +0 -0
  82. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/asgi.py +0 -0
  83. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/settings.py +0 -0
  84. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/urls.py +0 -0
  85. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/wsgi.py +0 -0
  86. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-02/workspace/manage.py +0 -0
  87. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-02/workspace/requirements.txt +0 -0
  88. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-02/workspace/templates/treats/home.html +0 -0
  89. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-02/workspace/treats/__init__.py +0 -0
  90. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-02/workspace/treats/admin.py +0 -0
  91. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-02/workspace/treats/apps.py +0 -0
  92. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-02/workspace/treats/menu.py +0 -0
  93. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-02/workspace/treats/migrations/__init__.py +0 -0
  94. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-02/workspace/treats/models.py +0 -0
  95. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-02/workspace/treats/tests.py +0 -0
  96. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-02/workspace/treats/urls.py +0 -0
  97. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-02/workspace/treats/views.py +0 -0
  98. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-03/.local/share/portacode/keys/id_portacode +0 -0
  99. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-03/.local/share/portacode/keys/id_portacode.pub +0 -0
  100. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-03/workspace/README.md +0 -0
  101. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/__init__.py +0 -0
  102. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/asgi.py +0 -0
  103. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/settings.py +0 -0
  104. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/urls.py +0 -0
  105. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/wsgi.py +0 -0
  106. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-03/workspace/manage.py +0 -0
  107. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-03/workspace/requirements.txt +0 -0
  108. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-03/workspace/templates/treats/home.html +0 -0
  109. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-03/workspace/treats/__init__.py +0 -0
  110. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-03/workspace/treats/admin.py +0 -0
  111. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-03/workspace/treats/apps.py +0 -0
  112. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-03/workspace/treats/menu.py +0 -0
  113. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-03/workspace/treats/migrations/__init__.py +0 -0
  114. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-03/workspace/treats/models.py +0 -0
  115. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-03/workspace/treats/tests.py +0 -0
  116. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-03/workspace/treats/urls.py +0 -0
  117. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-03/workspace/treats/views.py +0 -0
  118. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-04/.local/share/portacode/keys/id_portacode +0 -0
  119. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-04/.local/share/portacode/keys/id_portacode.pub +0 -0
  120. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-04/workspace/README.md +0 -0
  121. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/__init__.py +0 -0
  122. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/asgi.py +0 -0
  123. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/settings.py +0 -0
  124. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/urls.py +0 -0
  125. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/wsgi.py +0 -0
  126. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-04/workspace/manage.py +0 -0
  127. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-04/workspace/requirements.txt +0 -0
  128. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-04/workspace/templates/treats/home.html +0 -0
  129. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-04/workspace/treats/__init__.py +0 -0
  130. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-04/workspace/treats/admin.py +0 -0
  131. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-04/workspace/treats/apps.py +0 -0
  132. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-04/workspace/treats/menu.py +0 -0
  133. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-04/workspace/treats/migrations/__init__.py +0 -0
  134. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-04/workspace/treats/models.py +0 -0
  135. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-04/workspace/treats/tests.py +0 -0
  136. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-04/workspace/treats/urls.py +0 -0
  137. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-04/workspace/treats/views.py +0 -0
  138. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-05/.local/share/portacode/keys/id_portacode +0 -0
  139. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-05/.local/share/portacode/keys/id_portacode.pub +0 -0
  140. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-05/workspace/README.md +0 -0
  141. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/__init__.py +0 -0
  142. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/asgi.py +0 -0
  143. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/settings.py +0 -0
  144. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/urls.py +0 -0
  145. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/wsgi.py +0 -0
  146. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-05/workspace/manage.py +0 -0
  147. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-05/workspace/requirements.txt +0 -0
  148. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-05/workspace/templates/treats/home.html +0 -0
  149. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-05/workspace/treats/__init__.py +0 -0
  150. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-05/workspace/treats/admin.py +0 -0
  151. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-05/workspace/treats/apps.py +0 -0
  152. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-05/workspace/treats/menu.py +0 -0
  153. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-05/workspace/treats/migrations/__init__.py +0 -0
  154. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-05/workspace/treats/models.py +0 -0
  155. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-05/workspace/treats/tests.py +0 -0
  156. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-05/workspace/treats/urls.py +0 -0
  157. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-05/workspace/treats/views.py +0 -0
  158. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-06/.local/share/portacode/keys/id_portacode +0 -0
  159. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-06/.local/share/portacode/keys/id_portacode.pub +0 -0
  160. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-06/workspace/README.md +0 -0
  161. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/__init__.py +0 -0
  162. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/asgi.py +0 -0
  163. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/settings.py +0 -0
  164. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/urls.py +0 -0
  165. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/wsgi.py +0 -0
  166. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-06/workspace/manage.py +0 -0
  167. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-06/workspace/requirements.txt +0 -0
  168. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-06/workspace/templates/treats/home.html +0 -0
  169. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-06/workspace/treats/__init__.py +0 -0
  170. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-06/workspace/treats/admin.py +0 -0
  171. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-06/workspace/treats/apps.py +0 -0
  172. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-06/workspace/treats/menu.py +0 -0
  173. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-06/workspace/treats/migrations/__init__.py +0 -0
  174. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-06/workspace/treats/models.py +0 -0
  175. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-06/workspace/treats/tests.py +0 -0
  176. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-06/workspace/treats/urls.py +0 -0
  177. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-06/workspace/treats/views.py +0 -0
  178. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-07/.local/share/portacode/keys/id_portacode +0 -0
  179. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-07/.local/share/portacode/keys/id_portacode.pub +0 -0
  180. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-07/workspace/README.md +0 -0
  181. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/__init__.py +0 -0
  182. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/asgi.py +0 -0
  183. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/settings.py +0 -0
  184. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/urls.py +0 -0
  185. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/wsgi.py +0 -0
  186. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-07/workspace/manage.py +0 -0
  187. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-07/workspace/requirements.txt +0 -0
  188. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-07/workspace/templates/treats/home.html +0 -0
  189. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-07/workspace/treats/__init__.py +0 -0
  190. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-07/workspace/treats/admin.py +0 -0
  191. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-07/workspace/treats/apps.py +0 -0
  192. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-07/workspace/treats/menu.py +0 -0
  193. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-07/workspace/treats/migrations/__init__.py +0 -0
  194. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-07/workspace/treats/models.py +0 -0
  195. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-07/workspace/treats/tests.py +0 -0
  196. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-07/workspace/treats/urls.py +0 -0
  197. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-07/workspace/treats/views.py +0 -0
  198. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-08/.local/share/portacode/keys/id_portacode +0 -0
  199. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-08/.local/share/portacode/keys/id_portacode.pub +0 -0
  200. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-08/workspace/README.md +0 -0
  201. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/__init__.py +0 -0
  202. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/asgi.py +0 -0
  203. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/settings.py +0 -0
  204. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/urls.py +0 -0
  205. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/wsgi.py +0 -0
  206. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-08/workspace/manage.py +0 -0
  207. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-08/workspace/requirements.txt +0 -0
  208. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-08/workspace/templates/treats/home.html +0 -0
  209. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-08/workspace/treats/__init__.py +0 -0
  210. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-08/workspace/treats/admin.py +0 -0
  211. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-08/workspace/treats/apps.py +0 -0
  212. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-08/workspace/treats/menu.py +0 -0
  213. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-08/workspace/treats/migrations/__init__.py +0 -0
  214. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-08/workspace/treats/models.py +0 -0
  215. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-08/workspace/treats/tests.py +0 -0
  216. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-08/workspace/treats/urls.py +0 -0
  217. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-08/workspace/treats/views.py +0 -0
  218. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-09/.local/share/portacode/keys/id_portacode +0 -0
  219. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-09/.local/share/portacode/keys/id_portacode.pub +0 -0
  220. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-09/workspace/README.md +0 -0
  221. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/__init__.py +0 -0
  222. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/asgi.py +0 -0
  223. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/settings.py +0 -0
  224. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/urls.py +0 -0
  225. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/wsgi.py +0 -0
  226. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-09/workspace/manage.py +0 -0
  227. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-09/workspace/requirements.txt +0 -0
  228. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-09/workspace/templates/treats/home.html +0 -0
  229. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-09/workspace/treats/__init__.py +0 -0
  230. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-09/workspace/treats/admin.py +0 -0
  231. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-09/workspace/treats/apps.py +0 -0
  232. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-09/workspace/treats/menu.py +0 -0
  233. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-09/workspace/treats/migrations/__init__.py +0 -0
  234. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-09/workspace/treats/models.py +0 -0
  235. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-09/workspace/treats/tests.py +0 -0
  236. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-09/workspace/treats/urls.py +0 -0
  237. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-09/workspace/treats/views.py +0 -0
  238. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-10/.local/share/portacode/keys/id_portacode +0 -0
  239. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-10/.local/share/portacode/keys/id_portacode.pub +0 -0
  240. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-10/workspace/README.md +0 -0
  241. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/__init__.py +0 -0
  242. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/asgi.py +0 -0
  243. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/settings.py +0 -0
  244. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/urls.py +0 -0
  245. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/wsgi.py +0 -0
  246. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-10/workspace/manage.py +0 -0
  247. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-10/workspace/requirements.txt +0 -0
  248. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-10/workspace/templates/treats/home.html +0 -0
  249. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-10/workspace/treats/__init__.py +0 -0
  250. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-10/workspace/treats/admin.py +0 -0
  251. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-10/workspace/treats/apps.py +0 -0
  252. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-10/workspace/treats/menu.py +0 -0
  253. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-10/workspace/treats/migrations/__init__.py +0 -0
  254. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-10/workspace/treats/models.py +0 -0
  255. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-10/workspace/treats/tests.py +0 -0
  256. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-10/workspace/treats/urls.py +0 -0
  257. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/data/student-10/workspace/treats/views.py +0 -0
  258. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/docker-compose.yaml +0 -0
  259. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/initial_content/README.md +0 -0
  260. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/initial_content/galactic_bakeshop/__init__.py +0 -0
  261. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/initial_content/galactic_bakeshop/asgi.py +0 -0
  262. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/initial_content/galactic_bakeshop/settings.py +0 -0
  263. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/initial_content/galactic_bakeshop/urls.py +0 -0
  264. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/initial_content/galactic_bakeshop/wsgi.py +0 -0
  265. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/initial_content/manage.py +0 -0
  266. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/initial_content/requirements.txt +0 -0
  267. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/initial_content/templates/treats/home.html +0 -0
  268. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/initial_content/treats/__init__.py +0 -0
  269. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/initial_content/treats/admin.py +0 -0
  270. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/initial_content/treats/apps.py +0 -0
  271. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/initial_content/treats/menu.py +0 -0
  272. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/initial_content/treats/migrations/__init__.py +0 -0
  273. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/initial_content/treats/models.py +0 -0
  274. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/initial_content/treats/tests.py +0 -0
  275. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/initial_content/treats/urls.py +0 -0
  276. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/initial_content/treats/views.py +0 -0
  277. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/examples/workshop_fleet/instructions/WELCOME.md +0 -0
  278. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/README.md +0 -0
  279. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/__init__.py +0 -0
  280. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/__main__.py +0 -0
  281. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/cli.py +0 -0
  282. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/codex_loopback_proxy.py +0 -0
  283. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/codex_usage_limit.py +0 -0
  284. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/README.md +0 -0
  285. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/__init__.py +0 -0
  286. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/client.py +0 -0
  287. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/handlers/README.md +0 -0
  288. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md +0 -0
  289. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/handlers/__init__.py +0 -0
  290. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/handlers/automation_v2_handlers.py +0 -0
  291. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/handlers/base.py +0 -0
  292. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/handlers/chunked_content.py +0 -0
  293. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/handlers/cloudflare_forwarding.py +0 -0
  294. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/handlers/cloudflare_tunnel.py +0 -0
  295. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/handlers/codex_handlers.py +0 -0
  296. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/handlers/diff_handlers.py +0 -0
  297. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/handlers/file_handlers.py +0 -0
  298. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/handlers/file_transfer_handlers.py +0 -0
  299. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/handlers/project_aware_file_handlers.py +0 -0
  300. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/handlers/project_state/README.md +0 -0
  301. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/handlers/project_state/__init__.py +0 -0
  302. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/handlers/project_state/file_system_watcher.py +0 -0
  303. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/handlers/project_state/handlers.py +0 -0
  304. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/handlers/project_state/manager.py +0 -0
  305. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/handlers/project_state/models.py +0 -0
  306. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/handlers/project_state/test_handlers.py +0 -0
  307. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/handlers/project_state/utils.py +0 -0
  308. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/handlers/project_state_handlers.py +0 -0
  309. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/handlers/proxmox_infra.py +0 -0
  310. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/handlers/registry.py +0 -0
  311. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/handlers/session.py +0 -0
  312. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/handlers/system_handlers.py +0 -0
  313. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/handlers/tab_factory.py +0 -0
  314. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/handlers/terminal_handlers.py +0 -0
  315. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/handlers/test_cloudflare_forwarding.py +0 -0
  316. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/handlers/test_proxmox_infra.py +0 -0
  317. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/handlers/update_handler.py +0 -0
  318. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/multiplex.py +0 -0
  319. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/terminal.py +0 -0
  320. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/test_webmin_proxy_config.py +0 -0
  321. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/connection/webmin_proxy_config.py +0 -0
  322. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/data.py +0 -0
  323. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/exit_codes.py +0 -0
  324. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/keypair.py +0 -0
  325. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/link_capture/__init__.py +0 -0
  326. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/link_capture/__pycache__/__init__.cpython-311.pyc +0 -0
  327. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/link_capture/bin/elinks +0 -0
  328. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/link_capture/bin/gio-open +0 -0
  329. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/link_capture/bin/gnome-open +0 -0
  330. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/link_capture/bin/gvfs-open +0 -0
  331. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/link_capture/bin/kde-open +0 -0
  332. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/link_capture/bin/kfmclient +0 -0
  333. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/link_capture/bin/link_capture_exec.sh +0 -0
  334. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/link_capture/bin/link_capture_wrapper.py +0 -0
  335. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/link_capture/bin/links +0 -0
  336. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/link_capture/bin/links2 +0 -0
  337. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/link_capture/bin/lynx +0 -0
  338. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/link_capture/bin/mate-open +0 -0
  339. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/link_capture/bin/netsurf +0 -0
  340. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/link_capture/bin/sensible-browser +0 -0
  341. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/link_capture/bin/w3m +0 -0
  342. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/link_capture/bin/x-www-browser +0 -0
  343. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/link_capture/bin/xdg-open +0 -0
  344. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/logging_categories.py +0 -0
  345. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/pairing.py +0 -0
  346. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/restart.py +0 -0
  347. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/service.py +0 -0
  348. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/static/js/test-ntp-clock.html +0 -0
  349. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/static/js/utils/ntp-clock.js +0 -0
  350. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/test_runtime_paths.py +0 -0
  351. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/test_service.py +0 -0
  352. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/test_terminal_exposed_services.py +0 -0
  353. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/tests/test_codex_handlers.py +0 -0
  354. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/tunneling/__init__.py +0 -0
  355. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/tunneling/cloudflared_login.py +0 -0
  356. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/tunneling/ensure_cloudflared.py +0 -0
  357. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/tunneling/ensure_pyyaml.py +0 -0
  358. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/tunneling/forwarding_state.py +0 -0
  359. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/tunneling/get_domain.py +0 -0
  360. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/tunneling/privileged.py +0 -0
  361. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/tunneling/service_install.py +0 -0
  362. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/tunneling/state.py +0 -0
  363. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/updater.py +0 -0
  364. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/utils/NTP_ARCHITECTURE.md +0 -0
  365. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/utils/__init__.py +0 -0
  366. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/utils/diff_apply.py +0 -0
  367. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/utils/diff_renderer.py +0 -0
  368. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/utils/ntp_clock.py +0 -0
  369. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode/utils/runtime_paths.py +0 -0
  370. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode.egg-info/SOURCES.txt +0 -0
  371. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode.egg-info/dependency_links.txt +0 -0
  372. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode.egg-info/entry_points.txt +0 -0
  373. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode.egg-info/requires.txt +0 -0
  374. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode.egg-info/top_level.txt +0 -0
  375. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode_for_school/.gitignore +0 -0
  376. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode_for_school/README.md +0 -0
  377. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode_for_school/persistent_workspace/Dockerfile +0 -0
  378. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode_for_school/persistent_workspace/README.md +0 -0
  379. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode_for_school/persistent_workspace/entrypoint.sh +0 -0
  380. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode_for_school/persistent_workspace/manage_classroom.py +0 -0
  381. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode_for_school/temporary_workspace/Dockerfile +0 -0
  382. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode_for_school/temporary_workspace/README.md +0 -0
  383. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode_for_school/temporary_workspace/docker-compose.yaml +0 -0
  384. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode_for_school/temporary_workspace/docs/device_transfere.png +0 -0
  385. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode_for_school/temporary_workspace/docs/pairing_approval.png +0 -0
  386. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/portacode_for_school/temporary_workspace/entrypoint.sh +0 -0
  387. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/pyproject.toml +0 -0
  388. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/restore.sh +0 -0
  389. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/run_tests.py +0 -0
  390. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/scripts/diagnose_codex_gateway.py +0 -0
  391. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/setup.cfg +0 -0
  392. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/setup.py +0 -0
  393. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/test.sh +0 -0
  394. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/test_modules/README.md +0 -0
  395. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/test_modules/__init__.py +0 -0
  396. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/test_modules/test_device_online.py +0 -0
  397. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/test_modules/test_file_operations.py +0 -0
  398. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/test_modules/test_git_status_ui.py +0 -0
  399. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/test_modules/test_login_flow.py +0 -0
  400. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/test_modules/test_navigate_testing_folder.py +0 -0
  401. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/test_modules/test_play_store_screenshots.py +0 -0
  402. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/test_modules/test_terminal_buffer_performance.py +0 -0
  403. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/test_modules/test_terminal_interaction.py +0 -0
  404. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/test_modules/test_terminal_loading_race_condition.py +0 -0
  405. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/test_modules/test_terminal_start.py +0 -0
  406. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/test_request_id.py +0 -0
  407. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/test_session_terminal_shutdown.py +0 -0
  408. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/testing_framework/.env.example +0 -0
  409. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/testing_framework/README.md +0 -0
  410. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/testing_framework/__init__.py +0 -0
  411. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/testing_framework/cli.py +0 -0
  412. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/testing_framework/core/__init__.py +0 -0
  413. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/testing_framework/core/base_test.py +0 -0
  414. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/testing_framework/core/cli_manager.py +0 -0
  415. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/testing_framework/core/hierarchical_runner.py +0 -0
  416. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/testing_framework/core/playwright_manager.py +0 -0
  417. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/testing_framework/core/runner.py +0 -0
  418. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/testing_framework/core/shared_cli_manager.py +0 -0
  419. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/testing_framework/core/test_discovery.py +0 -0
  420. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/testing_framework/requirements.txt +0 -0
  421. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/todo/UI_UX/opening_a_file_on_desktop_results_in_nothing.md +0 -0
  422. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/todo/UI_UX/server_occasionally_stops_communicating_with_all_devices.md +0 -0
  423. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/todo/agent_context_management.md +0 -0
  424. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/todo/django_server_time_sync.md +0 -0
  425. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/todo/issues/device_performance_degradation.md +0 -0
  426. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/todo/issues/git_data_not_captured_in_proxmox.md +0 -0
  427. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/todo/issues/indefinite_resource_loading.md +0 -0
  428. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/todo/issues/portacode_service_silently_down.md +0 -0
  429. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/todo/issues/premature_terminal_exit.md +0 -0
  430. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/todo/issues/project_cpu_hotspots.md +0 -0
  431. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/todo/issues/terminals_exit_upon_starting.md +0 -0
  432. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/todo/issues/websocket_client_silently_dead.md +0 -0
  433. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/todo/issues/wrong_item_classification_on_client_side.md +0 -0
  434. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/todo/smartphone_terminal_input_frustrations.md +0 -0
  435. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/tools/generate_play_store_assets.py +0 -0
  436. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/tools/pairing_tester.py +0 -0
  437. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/tools/run_screenshot_suite.sh +0 -0
  438. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/tools/test_python_ntp_clock.py +0 -0
  439. {portacode-1.5.17.dev1 → portacode-1.5.17.dev3}/validate.sh +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: portacode
3
- Version: 1.5.17.dev1
3
+ Version: 1.5.17.dev3
4
4
  Summary: Portacode CLI client and SDK
5
5
  Home-page: https://github.com/portacode/portacode
6
6
  Author: Meena Erian
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '1.5.17.dev1'
22
- __version_tuple__ = version_tuple = (1, 5, 17, 'dev1')
21
+ __version__ = version = '1.5.17.dev3'
22
+ __version_tuple__ = version_tuple = (1, 5, 17, 'dev3')
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import json
6
+ import logging
6
7
  import os
7
8
  import platform
8
9
  import re
@@ -17,6 +18,8 @@ from typing import Callable, Dict, Iterable, Mapping, MutableMapping, Optional
17
18
 
18
19
  from .codex_loopback_proxy import CODEX_LOOPBACK_HOST, CODEX_LOOPBACK_PORT
19
20
 
21
+ LOGGER = logging.getLogger(__name__)
22
+
20
23
  # Do not pin `model` here — let the installed Codex CLI default apply, and let
21
24
  # the Portacode chat UI override via thread/start when the device supports it.
22
25
  CODEX_CONFIG = '''model_provider = "portacode_proxy"
@@ -24,11 +27,17 @@ approval_policy = "never"
24
27
  sandbox_mode = "danger-full-access"
25
28
  cli_auth_credentials_store = "file"
26
29
 
30
+ # Force all model traffic through the local Portacode device proxy.
31
+ # Newer Codex builds prefer WebSockets to api.openai.com; that bypasses our
32
+ # proxy and treats OPENAI_API_KEY=portacode-local as a real OpenAI key (401).
33
+ openai_base_url = "http://127.0.0.1:61789/v1"
34
+
27
35
  [model_providers.portacode_proxy]
28
36
  name = "Portacode Device Proxy"
29
37
  base_url = "http://127.0.0.1:61789/v1"
30
38
  wire_api = "responses"
31
39
  env_key = "OPENAI_API_KEY"
40
+ supports_websockets = false
32
41
  '''
33
42
 
34
43
  LOCAL_SENTINEL = "portacode-local"
@@ -145,14 +154,72 @@ def _install_codex() -> None:
145
154
  raise CodexPreparationError("Codex CLI installation completed but codex is not on PATH.")
146
155
 
147
156
 
148
- def _write_config() -> Path:
149
- config_path = Path.home() / ".codex" / "config.toml"
150
- config_path.parent.mkdir(parents=True, exist_ok=True)
151
- config_path.write_text(CODEX_CONFIG, encoding="utf-8")
152
- config_path.chmod(0o600)
157
+ def _extract_project_trust_blocks(existing: str) -> str:
158
+ """Keep Codex ``[projects."..."] trust_level`` stanzas when rewriting config."""
159
+ blocks: list[str] = []
160
+ current: list[str] = []
161
+ in_projects = False
162
+ for line in existing.splitlines():
163
+ stripped = line.strip()
164
+ if stripped.startswith("[") and stripped.endswith("]"):
165
+ if in_projects and current:
166
+ blocks.append("\n".join(current).rstrip())
167
+ current = []
168
+ in_projects = stripped.startswith("[projects.")
169
+ if in_projects:
170
+ current = [line]
171
+ continue
172
+ if in_projects:
173
+ current.append(line)
174
+ if in_projects and current:
175
+ blocks.append("\n".join(current).rstrip())
176
+ if not blocks:
177
+ return ""
178
+ return "\n\n" + "\n\n".join(blocks) + "\n"
179
+
180
+
181
+ def write_codex_config(codex_home: Optional[Path] = None) -> Path:
182
+ """Write the managed Codex config into the shared CODEX_HOME.
183
+
184
+ Always refreshes the Portacode proxy provider so runtime-user homes
185
+ (e.g. ``/home/bishoy/.codex``) cannot keep a stale default that sends
186
+ traffic to ``api.openai.com`` with the local sentinel key.
187
+ Preserves existing ``[projects.*]`` trust entries.
188
+ """
189
+ home = Path(codex_home) if codex_home is not None else resolve_codex_home()
190
+ home.mkdir(parents=True, exist_ok=True)
191
+ config_path = home / "config.toml"
192
+ existing = ""
193
+ try:
194
+ if config_path.is_file():
195
+ existing = config_path.read_text(encoding="utf-8")
196
+ except OSError:
197
+ existing = ""
198
+ body = CODEX_CONFIG.rstrip() + "\n" + _extract_project_trust_blocks(existing)
199
+ config_path.write_text(body, encoding="utf-8")
200
+ try:
201
+ config_path.chmod(0o600)
202
+ except OSError:
203
+ pass
204
+ try:
205
+ from portacode.connection.handlers.runtime_user import (
206
+ chown_path_if_possible,
207
+ get_default_runtime_user,
208
+ )
209
+
210
+ owner = get_default_runtime_user()
211
+ chown_path_if_possible(home, owner)
212
+ chown_path_if_possible(config_path, owner)
213
+ except Exception:
214
+ pass
153
215
  return config_path
154
216
 
155
217
 
218
+ def _write_config() -> Path:
219
+ """Backward-compatible alias used by ``prepare_codex``."""
220
+ return write_codex_config()
221
+
222
+
156
223
  def read_codex_env_file(path: Optional[Path] = None) -> Dict[str, str]:
157
224
  """Parse KEY=VALUE lines from the managed Codex env file."""
158
225
  path = path or CODEX_ENV_PATH
@@ -256,7 +323,6 @@ def _persist_codex_home_env(codex_home: Path) -> None:
256
323
 
257
324
  def ensure_codex_home() -> Path:
258
325
  """Create CODEX_HOME and seed it from /root/.codex sessions when needed."""
259
- import pwd
260
326
  import shutil
261
327
 
262
328
  codex_home = resolve_codex_home()
@@ -293,18 +359,23 @@ def ensure_codex_home() -> Path:
293
359
  shutil.copy2(src, dest)
294
360
  except OSError:
295
361
  pass
362
+ # Only chown newly created paths — full-tree chown on every spawn made
363
+ # Codex app-server initialize race the 10s handshake timeout.
296
364
  try:
297
- from portacode.connection.handlers.runtime_user import get_default_runtime_user
298
-
299
- user = get_default_runtime_user()
300
- pw_entry = pwd.getpwnam(user)
301
- for path in [codex_home, *codex_home.rglob("*")]:
302
- try:
303
- os.chown(path, pw_entry.pw_uid, pw_entry.pw_gid)
304
- except OSError:
305
- pass
365
+ from portacode.connection.handlers.runtime_user import (
366
+ chown_path_if_possible,
367
+ get_default_runtime_user,
368
+ )
369
+
370
+ owner = get_default_runtime_user()
371
+ chown_path_if_possible(codex_home, owner)
372
+ chown_path_if_possible(sessions_dir, owner)
306
373
  except Exception:
307
374
  pass
375
+ try:
376
+ write_codex_config(codex_home)
377
+ except Exception:
378
+ LOGGER.debug("Could not refresh managed Codex config.toml", exc_info=True)
308
379
  try:
309
380
  _persist_codex_home_env(codex_home)
310
381
  except Exception:
@@ -191,13 +191,15 @@ class CodexAppServer:
191
191
  Uses `_request_on_running_proc` so we do not re-enter `start()` while
192
192
  `start()` already holds `_lock` (asyncio.Lock is not reentrant).
193
193
  """
194
+ # Backfill / first boot can exceed a few seconds after CODEX_HOME moves
195
+ # or session indexes are rebuilt.
194
196
  response = await self._request_on_running_proc(
195
197
  "initialize",
196
198
  {
197
199
  "clientInfo": self.client_info,
198
200
  "capabilities": {"streaming": True},
199
201
  },
200
- timeout=10.0,
202
+ timeout=60.0,
201
203
  )
202
204
  LOGGER.info("Codex app-server initialized: %s", response.get("result", {}))
203
205
  # Required by current Codex app-server clients so post-init notifications
@@ -752,186 +752,132 @@ class GitManager:
752
752
  return None
753
753
 
754
754
 
755
+ def _parse_porcelain_entries(self) -> list[tuple[str, str, str]]:
756
+ """Parse ``git status --porcelain -z`` into (xy, path, orig_path) tuples.
757
+
758
+ Avoids GitPython ``index.diff`` / ``git diff --raw -z``, which can raise
759
+ uncaught IndexError in GitPython's stdout pump thread on some repos.
760
+ """
761
+ try:
762
+ raw = self.repo.git.status(porcelain=True, z=True)
763
+ except Exception as exc:
764
+ logger.debug("git status --porcelain -z failed: %s", exc)
765
+ return []
766
+ if not raw:
767
+ return []
768
+ # git -z separates records with NUL; rename records are "XY\\0path\\0orig\\0"
769
+ # or for porcelain v1 renames: "R new\\0old\\0" depending on version.
770
+ parts = raw.split("\0")
771
+ entries: list[tuple[str, str, str]] = []
772
+ i = 0
773
+ while i < len(parts):
774
+ record = parts[i]
775
+ if not record:
776
+ i += 1
777
+ continue
778
+ if len(record) < 3:
779
+ i += 1
780
+ continue
781
+ xy = record[:2]
782
+ path = record[3:]
783
+ orig = ""
784
+ # Rename/copy may place the other path in the next NUL field.
785
+ if xy[0] in {"R", "C"} and i + 1 < len(parts) and parts[i + 1]:
786
+ # Some git versions emit "XY score\\tnewpath" then old path next.
787
+ if "\t" in path:
788
+ path = path.split("\t", 1)[-1]
789
+ orig = parts[i + 1]
790
+ i += 2
791
+ else:
792
+ i += 1
793
+ if path:
794
+ entries.append((xy, path, orig))
795
+ return entries
796
+
755
797
  def get_detailed_status(self) -> GitDetailedStatus:
756
- """Get detailed Git status with file hashes using GitPython APIs."""
798
+ """Get detailed Git status with file hashes via porcelain (no index.diff)."""
757
799
  if not self.is_git_repo or not self.repo:
758
800
  return GitDetailedStatus()
759
-
801
+
760
802
  try:
761
803
  detailed_status = GitDetailedStatus()
762
804
  detailed_status.head_commit_hash = self.get_head_commit_hash()
763
-
764
- # Get all changed files using GitPython's index diff
765
- # Get staged changes (index vs HEAD)
766
- # Handle case where repository has no commits (no HEAD)
767
- try:
768
- staged_files = self.repo.index.diff("HEAD")
769
- except Exception as e:
770
- logger.debug("🔍 [TRACE] No HEAD found (likely no commits), using staged-only detection: %s", e)
771
- # When no HEAD exists, we need to get staged files differently
772
- staged_file_names = []
773
- try:
774
- staged_output = self.repo.git.diff('--cached', '--name-only')
775
- staged_file_names = staged_output.splitlines() if staged_output.strip() else []
776
- except Exception:
777
- staged_file_names = []
778
- logger.debug("🔍 [TRACE] Found %d staged files in no-HEAD repo: %s", len(staged_file_names), staged_file_names)
779
-
780
- # Create staged file changes manually for no-HEAD repos
781
- for file_repo_path in staged_file_names:
782
- file_abs_path = os.path.join(self.project_path, file_repo_path)
783
- file_name = os.path.basename(file_repo_path)
784
- content_hash = self._compute_file_hash(file_abs_path) if os.path.exists(file_abs_path) else None
785
-
786
- # For staged files in no-HEAD repo, they are all "added" (new files)
787
- diff_details = None
788
-
789
- change = GitFileChange(
790
- file_repo_path=file_repo_path,
791
- file_name=file_name,
792
- file_abs_path=file_abs_path,
793
- change_type='added',
794
- content_hash=content_hash,
795
- is_staged=True,
796
- diff_details=diff_details
797
- )
798
- logger.debug("🔍 [TRACE] Created staged change for no-HEAD repo: %s (added)", file_name)
799
- detailed_status.staged_changes.append(change)
800
-
801
- # Skip the normal staged files loop since we handled it above
802
- staged_files = []
803
- # Get git status --porcelain for accurate change types (GitPython diff can be buggy)
804
- try:
805
- porcelain_status = self.repo.git.status(porcelain=True).strip()
806
- porcelain_map = {}
807
- if porcelain_status:
808
- for line in porcelain_status.split('\n'):
809
- if len(line) >= 3:
810
- index_status = line[0]
811
- file_path = line[3:]
812
- porcelain_map[file_path] = index_status
813
- except Exception:
814
- porcelain_map = {}
815
-
816
- for diff_item in staged_files:
817
- file_repo_path = diff_item.a_path or diff_item.b_path
805
+
806
+ for xy, file_repo_path, _orig in self._parse_porcelain_entries():
807
+ index_status = xy[0] if xy else " "
808
+ work_status = xy[1] if len(xy) > 1 else " "
818
809
  file_abs_path = os.path.join(self.project_path, file_repo_path)
819
810
  file_name = os.path.basename(file_repo_path)
820
-
821
- # Determine change type - use porcelain status for accuracy, fall back to GitPython
822
- porcelain_status = porcelain_map.get(file_repo_path, '')
823
- if porcelain_status == 'A':
824
- change_type = 'untracked'
825
- elif porcelain_status == 'M':
826
- change_type = 'modified'
827
- elif porcelain_status == 'D':
828
- change_type = 'deleted'
829
- else:
830
- # Fall back to GitPython detection
831
- if diff_item.deleted_file:
832
- change_type = 'deleted'
833
- elif diff_item.new_file:
834
- change_type = 'untracked'
835
- else:
836
- change_type = 'modified'
837
-
838
- # Set content hash and diff details based on change type
839
- if change_type == 'deleted':
811
+
812
+ # Untracked
813
+ if index_status == "?" and work_status == "?":
814
+ content_hash = (
815
+ self._compute_file_hash(file_abs_path)
816
+ if os.path.exists(file_abs_path)
817
+ else None
818
+ )
819
+ detailed_status.untracked_files.append(
820
+ GitFileChange(
821
+ file_repo_path=file_repo_path,
822
+ file_name=file_name,
823
+ file_abs_path=file_abs_path,
824
+ change_type="untracked",
825
+ content_hash=content_hash,
826
+ is_staged=False,
827
+ diff_details=None,
828
+ )
829
+ )
830
+ continue
831
+
832
+ # Staged (index column)
833
+ if index_status not in {" ", "?", "!"}:
834
+ # Preserve prior UI mapping: staged "A" was labeled untracked.
835
+ change_type = (
836
+ "untracked"
837
+ if index_status == "A"
838
+ else self._get_change_type(index_status)
839
+ )
840
840
  content_hash = None
841
- diff_details = None # No diff for deleted files
842
- elif change_type == 'untracked':
843
- content_hash = self._compute_file_hash(file_abs_path) if os.path.exists(file_abs_path) else None
844
- # For new files, compare empty content vs current staged content
845
- diff_details = None
846
- else: # modified
847
- content_hash = self._compute_file_hash(file_abs_path) if os.path.exists(file_abs_path) else None
848
- # Compare HEAD content vs staged content
849
- diff_details = None
850
-
851
- change = GitFileChange(
852
- file_repo_path=file_repo_path,
853
- file_name=file_name,
854
- file_abs_path=file_abs_path,
855
- change_type=change_type,
856
- content_hash=content_hash,
857
- is_staged=True,
858
- diff_details=diff_details
859
- )
860
- logger.debug("Created staged change for: %s (%s)", file_name, change_type)
861
- detailed_status.staged_changes.append(change)
862
-
863
- # Get unstaged changes (working tree vs index)
864
- try:
865
- unstaged_files = self.repo.index.diff(None)
866
- except Exception as e:
867
- logger.debug("🔍 [TRACE] Error getting unstaged files: %s", e)
868
- unstaged_files = []
869
- for diff_item in unstaged_files:
870
- file_repo_path = diff_item.a_path or diff_item.b_path
871
- file_abs_path = os.path.join(self.project_path, file_repo_path)
872
- file_name = os.path.basename(file_repo_path)
873
-
874
- # Determine change type - stick to git's native types
875
- if diff_item.deleted_file:
876
- change_type = 'deleted'
841
+ if change_type != "deleted" and os.path.exists(file_abs_path):
842
+ content_hash = self._compute_file_hash(file_abs_path)
843
+ detailed_status.staged_changes.append(
844
+ GitFileChange(
845
+ file_repo_path=file_repo_path,
846
+ file_name=file_name,
847
+ file_abs_path=file_abs_path,
848
+ change_type=change_type,
849
+ content_hash=content_hash,
850
+ is_staged=True,
851
+ diff_details=None,
852
+ )
853
+ )
854
+
855
+ # Unstaged (worktree column)
856
+ if work_status not in {" ", "?", "!"}:
857
+ change_type = self._get_change_type(work_status)
877
858
  content_hash = None
878
- diff_details = None # No diff for deleted files
879
- elif diff_item.new_file:
880
- change_type = 'added'
881
- content_hash = self._compute_file_hash(file_abs_path) if os.path.exists(file_abs_path) else None
882
- # For new files, compare empty content vs current working content
883
- diff_details = None
884
- else:
885
- change_type = 'modified'
886
- content_hash = self._compute_file_hash(file_abs_path) if os.path.exists(file_abs_path) else None
887
- # Compare staged/index content vs working content
888
- diff_details = None
889
-
890
- change = GitFileChange(
891
- file_repo_path=file_repo_path,
892
- file_name=file_name,
893
- file_abs_path=file_abs_path,
894
- change_type=change_type,
895
- content_hash=content_hash,
896
- is_staged=False,
897
- diff_details=diff_details
898
- )
899
- logger.debug("Created unstaged change for: %s (%s)", file_name, change_type)
900
- detailed_status.unstaged_changes.append(change)
901
-
902
- # Get untracked files
903
- try:
904
- untracked_files = self.repo.untracked_files
905
- logger.debug("🔍 [TRACE] Processing %d untracked files: %s", len(untracked_files), untracked_files)
906
- except Exception as e:
907
- logger.debug("🔍 [TRACE] Error getting untracked files: %s", e)
908
- untracked_files = []
909
- for file_repo_path in untracked_files:
910
- file_abs_path = os.path.join(self.project_path, file_repo_path)
911
- file_name = os.path.basename(file_repo_path)
912
- content_hash = self._compute_file_hash(file_abs_path) if os.path.exists(file_abs_path) else None
913
-
914
- # For untracked files, compare empty content vs current file content
915
- diff_details = None
916
-
917
- change = GitFileChange(
918
- file_repo_path=file_repo_path,
919
- file_name=file_name,
920
- file_abs_path=file_abs_path,
921
- change_type='untracked',
922
- content_hash=content_hash,
923
- is_staged=False,
924
- diff_details=diff_details
925
- )
926
- logger.debug("🔍 [TRACE] Created untracked change for: %s", file_name)
927
- detailed_status.untracked_files.append(change)
928
-
929
- logger.debug("🔍 [TRACE] Returning detailed_status with %d staged, %d unstaged, %d untracked",
930
- len(detailed_status.staged_changes),
931
- len(detailed_status.unstaged_changes),
932
- len(detailed_status.untracked_files))
859
+ if change_type != "deleted" and os.path.exists(file_abs_path):
860
+ content_hash = self._compute_file_hash(file_abs_path)
861
+ detailed_status.unstaged_changes.append(
862
+ GitFileChange(
863
+ file_repo_path=file_repo_path,
864
+ file_name=file_name,
865
+ file_abs_path=file_abs_path,
866
+ change_type=change_type,
867
+ content_hash=content_hash,
868
+ is_staged=False,
869
+ diff_details=None,
870
+ )
871
+ )
872
+
873
+ logger.debug(
874
+ "Returning detailed_status with %d staged, %d unstaged, %d untracked",
875
+ len(detailed_status.staged_changes),
876
+ len(detailed_status.unstaged_changes),
877
+ len(detailed_status.untracked_files),
878
+ )
933
879
  return detailed_status
934
-
880
+
935
881
  except Exception as e:
936
882
  logger.error("Error getting detailed Git status: %s", e)
937
883
  return GitDetailedStatus()
@@ -1342,18 +1288,22 @@ class GitManager:
1342
1288
  except Exception:
1343
1289
  has_head = False
1344
1290
 
1345
- if not has_head:
1346
- # For the first commit, check if anything is staged
1291
+ # Prefer porcelain / diff --cached over index.diff (GitPython pump-thread bugs).
1292
+ try:
1293
+ staged_output = self.repo.git.diff("--cached", "--name-only")
1294
+ has_staged = bool(staged_output.strip())
1295
+ except Exception:
1296
+ has_staged = False
1297
+ if not has_staged and not has_head:
1298
+ # First commit with only "git add" of new files still shows in status.
1347
1299
  try:
1348
- staged_output = self.repo.git.diff('--cached', '--name-only')
1349
- has_staged = bool(staged_output.strip())
1300
+ has_staged = any(
1301
+ xy[0] not in {" ", "?", "!"}
1302
+ for xy, _path, _orig in self._parse_porcelain_entries()
1303
+ )
1350
1304
  except Exception:
1351
1305
  has_staged = False
1352
- else:
1353
- # Check if there are staged changes to commit
1354
- staged_changes = self.repo.index.diff("HEAD")
1355
- has_staged = len(staged_changes) > 0
1356
-
1306
+
1357
1307
  if not has_staged:
1358
1308
  raise RuntimeError("No staged changes to commit")
1359
1309
 
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import os
4
4
  import pwd
5
5
  import shlex
6
+ import shutil
6
7
  import stat
7
8
  from pathlib import Path
8
9
  from typing import Iterable, List, Optional, Sequence
@@ -69,22 +70,23 @@ def wrap_argv_for_user(
69
70
  """Wrap argv to run as ``user`` when the agent is root.
70
71
 
71
72
  ``login=True`` (default) matches terminal/git helpers with ``sudo -i``.
72
- Long-lived daemons (e.g. Codex app-server) should pass ``login=False`` and
73
- an explicit ``preserve_env_names`` list so managed env vars survive.
73
+ Long-lived daemons (e.g. Codex app-server) must pass ``login=False`` so we
74
+ use ``runuser``/``setpriv`` instead of ``sudo`` many hosts enable
75
+ ``Defaults use_pty``, which breaks JSON-RPC over piped stdio.
74
76
  """
75
77
  argv_list = list(argv)
76
78
  if not should_switch_user(user):
77
79
  return argv_list
78
80
 
81
+ if not login:
82
+ return _wrap_daemon_argv_for_user(argv_list, user, cwd=cwd)
83
+
79
84
  preserved = [
80
85
  str(name or "").strip()
81
86
  for name in (preserve_env_names or [])
82
87
  if str(name or "").strip()
83
88
  ]
84
- sudo_prefix = ["sudo", "-H"]
85
- if login:
86
- sudo_prefix.append("-i")
87
- sudo_prefix.extend(["-u", user])
89
+ sudo_prefix = ["sudo", "-H", "-i", "-u", user]
88
90
  if preserved:
89
91
  sudo_prefix.append(f"--preserve-env={','.join(preserved)}")
90
92
  sudo_prefix.append("--")
@@ -100,6 +102,49 @@ def wrap_argv_for_user(
100
102
  return [*sudo_prefix, shell_path, "-lc", command]
101
103
 
102
104
 
105
+ def _wrap_daemon_argv_for_user(
106
+ argv_list: List[str],
107
+ user: str,
108
+ *,
109
+ cwd: Optional[str] = None,
110
+ ) -> List[str]:
111
+ """Run a long-lived subprocess as ``user`` without allocating a PTY."""
112
+ if cwd:
113
+ shell_path = _resolve_shell_for_wrapped_argv(user, argv_list[0] if argv_list else "/bin/sh")
114
+ exec_command = " ".join(shlex.quote(arg) for arg in argv_list)
115
+ command = f"cd {shlex.quote(cwd)} && exec {exec_command}"
116
+ inner = [shell_path, "-lc", command]
117
+ else:
118
+ inner = list(argv_list)
119
+
120
+ runuser = shutil.which("runuser")
121
+ if runuser:
122
+ # --preserve-environment keeps OPENAI_API_KEY / CODEX_HOME from the
123
+ # parent env= mapping passed to create_subprocess_exec.
124
+ return [runuser, "-u", user, "--preserve-environment", "--", *inner]
125
+
126
+ setpriv = shutil.which("setpriv")
127
+ if setpriv:
128
+ try:
129
+ pw_entry = pwd.getpwnam(user)
130
+ except KeyError:
131
+ pw_entry = None
132
+ if pw_entry is not None:
133
+ return [
134
+ setpriv,
135
+ f"--reuid={pw_entry.pw_uid}",
136
+ f"--regid={pw_entry.pw_gid}",
137
+ "--init-groups",
138
+ "--",
139
+ *inner,
140
+ ]
141
+
142
+ # Last resort: sudo without login shell. Prefer -n (non-interactive).
143
+ # Hosts with Defaults use_pty may still break stdio — callers should prefer
144
+ # runuser on Linux agents.
145
+ return ["sudo", "-n", "-H", "-u", user, "--", *inner]
146
+
147
+
103
148
  def ensure_parent_dirs(path: str | Path, owner_user: Optional[str] = None) -> None:
104
149
  target = Path(path).parent
105
150
  missing: List[Path] = []
@@ -75,8 +75,9 @@ class RuntimeUserTests(TestCase):
75
75
  self.assertEqual(wrapped[5:8], ["--", "/bin/bash", "-lc"])
76
76
  self.assertIn("cd /home/meena/.openclaw && exec /bin/bash --login", wrapped[8])
77
77
 
78
+ @patch("portacode.connection.handlers.runtime_user.shutil.which", side_effect=lambda name: "/usr/sbin/runuser" if name == "runuser" else None)
78
79
  @patch("portacode.connection.handlers.runtime_user.os.geteuid", return_value=0)
79
- def test_wrap_argv_for_user_non_login_preserves_env(self, _mock_euid):
80
+ def test_wrap_argv_for_user_non_login_uses_runuser(self, _mock_euid, _mock_which):
80
81
  wrapped = wrap_argv_for_user(
81
82
  ["codex", "app-server"],
82
83
  "meena",
@@ -84,10 +85,20 @@ class RuntimeUserTests(TestCase):
84
85
  login=False,
85
86
  )
86
87
  self.assertEqual(
87
- wrapped[:6],
88
- ["sudo", "-H", "-u", "meena", "--preserve-env=OPENAI_API_KEY,CODEX_HOME", "--"],
88
+ wrapped,
89
+ ["/usr/sbin/runuser", "-u", "meena", "--preserve-environment", "--", "codex", "app-server"],
89
90
  )
90
- self.assertEqual(wrapped[6:], ["codex", "app-server"])
91
+
92
+ @patch("portacode.connection.handlers.runtime_user.shutil.which", return_value=None)
93
+ @patch("portacode.connection.handlers.runtime_user.os.geteuid", return_value=0)
94
+ def test_wrap_argv_for_user_non_login_falls_back_to_sudo_n(self, _mock_euid, _mock_which):
95
+ wrapped = wrap_argv_for_user(
96
+ ["codex", "app-server"],
97
+ "meena",
98
+ login=False,
99
+ )
100
+ self.assertEqual(wrapped[:5], ["sudo", "-n", "-H", "-u", "meena"])
101
+ self.assertEqual(wrapped[5:], ["--", "codex", "app-server"])
91
102
 
92
103
  @patch("portacode.connection.handlers.runtime_user.os.geteuid", return_value=0)
93
104
  @patch("portacode.connection.handlers.runtime_user.pwd.getpwnam")