codex-partner 0.0.5__tar.gz → 0.0.7__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.
- {codex_partner-0.0.5 → codex_partner-0.0.7}/PKG-INFO +58 -5
- {codex_partner-0.0.5 → codex_partner-0.0.7}/README.md +54 -4
- {codex_partner-0.0.5 → codex_partner-0.0.7}/app.py +222 -22
- {codex_partner-0.0.5 → codex_partner-0.0.7}/codex_partner/__init__.py +1 -1
- {codex_partner-0.0.5 → codex_partner-0.0.7}/codex_partner/app_server.py +4 -1
- {codex_partner-0.0.5 → codex_partner-0.0.7}/codex_partner/database.py +5 -4
- {codex_partner-0.0.5 → codex_partner-0.0.7}/codex_partner/discovery.py +4 -2
- codex_partner-0.0.7/codex_partner/platform_support.py +74 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/codex_partner/schemas.py +1 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/codex_partner/ssh_auth.py +6 -1
- {codex_partner-0.0.5 → codex_partner-0.0.7}/codex_partner.egg-info/PKG-INFO +58 -5
- {codex_partner-0.0.5 → codex_partner-0.0.7}/codex_partner.egg-info/SOURCES.txt +2 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/codex_partner.egg-info/requires.txt +3 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/pyproject.toml +4 -1
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/app.js +27 -4
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/core.js +16 -5
- codex_partner-0.0.7/tests/test_platform_support.py +62 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/tests/test_running_state.py +114 -25
- {codex_partner-0.0.5 → codex_partner-0.0.7}/codex_partner/__main__.py +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/codex_partner/activity_graph.py +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/codex_partner/cli.py +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/codex_partner/commands.py +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/codex_partner.egg-info/dependency_links.txt +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/codex_partner.egg-info/entry_points.txt +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/codex_partner.egg-info/top_level.txt +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/setup.cfg +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/__init__.py +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/chat-worker.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/conversation.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/default-user-avatar.webp +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/editor.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/exploration-tree.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/history-pagination.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/index.html +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/mascot-dance.css +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/mascot-dance.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/settings.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/styles.css +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/LICENSE +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/ace.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/ext-modelist.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-abap.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-abc.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-actionscript.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-ada.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-alda.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-apache_conf.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-apex.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-applescript.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-aql.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-asciidoc.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-asl.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-assembly_arm32.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-assembly_x86.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-astro.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-autohotkey.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-basic.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-batchfile.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-bibtex.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-c9search.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-c_cpp.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-cedar.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-cedarschema.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-cirru.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-clojure.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-clue.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-cobol.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-coffee.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-coldfusion.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-crystal.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-csharp.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-csound_document.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-csound_orchestra.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-csound_score.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-csp.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-css.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-csv.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-curly.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-cuttlefish.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-d.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-dart.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-diff.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-django.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-dockerfile.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-dot.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-drools.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-edifact.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-eiffel.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-ejs.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-elixir.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-elm.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-erlang.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-flix.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-forth.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-fortran.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-fsharp.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-fsl.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-ftl.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-gcode.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-gherkin.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-gitignore.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-glsl.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-gobstones.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-golang.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-graphqlschema.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-groovy.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-groq.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-haml.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-handlebars.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-haskell.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-haskell_cabal.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-haxe.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-hjson.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-html.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-html_elixir.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-html_ruby.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-ini.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-io.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-ion.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-jack.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-jade.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-java.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-javascript.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-jexl.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-json.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-json5.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-jsoniq.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-jsp.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-jssm.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-jsx.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-julia.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-kotlin.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-latex.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-latte.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-less.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-liquid.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-lisp.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-livescript.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-logiql.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-logtalk.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-lsl.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-lua.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-luapage.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-lucene.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-makefile.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-mariadb.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-markdown.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-mask.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-matlab.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-maze.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-mediawiki.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-mel.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-mips.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-mixal.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-mushcode.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-mysql.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-nasal.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-nginx.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-nim.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-nix.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-nsis.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-nunjucks.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-objectivec.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-ocaml.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-odin.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-partiql.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-pascal.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-perl.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-pgsql.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-php.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-php_laravel_blade.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-pig.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-plain_text.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-plsql.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-powershell.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-praat.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-prisma.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-prolog.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-properties.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-protobuf.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-prql.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-puppet.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-python.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-qml.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-r.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-raku.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-razor.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-rdoc.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-red.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-redshift.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-rhtml.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-robot.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-rst.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-ruby.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-rust.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-sac.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-sass.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-scad.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-scala.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-scheme.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-scrypt.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-scss.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-sh.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-sjs.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-slim.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-smarty.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-smithy.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-snippets.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-soy_template.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-space.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-sparql.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-sql.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-sqlserver.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-stylus.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-svg.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-swift.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-tcl.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-terraform.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-tex.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-text.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-textile.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-toml.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-tsv.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-tsx.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-turtle.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-twig.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-typescript.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-vala.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-vbscript.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-velocity.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-verilog.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-vhdl.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-visualforce.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-vue.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-wollok.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-xml.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-xquery.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-yaml.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-zeek.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/mode-zig.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/ace/theme-tomorrow_night_eighties.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/i18next/LICENSE +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/i18next/i18next.min.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/LICENSE +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_AMS-Regular.ttf +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_AMS-Regular.woff +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Main-Bold.ttf +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Main-Bold.woff +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Main-Italic.ttf +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Main-Italic.woff +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Main-Regular.ttf +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Main-Regular.woff +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Math-Italic.ttf +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Math-Italic.woff +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Script-Regular.ttf +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Script-Regular.woff +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Size1-Regular.ttf +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Size1-Regular.woff +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Size2-Regular.ttf +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Size2-Regular.woff +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Size3-Regular.ttf +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Size3-Regular.woff +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Size4-Regular.ttf +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Size4-Regular.woff +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/katex.min.css +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/katex/katex.min.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/markdown-it/LICENSE +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/markdown-it/markdown-it.min.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/markdown-it-texmath/LICENSE +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/markdown-it-texmath/texmath.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/toastui-editor/LICENSE +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/toastui-editor/toastui-editor-dark.css +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/toastui-editor/toastui-editor.css +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/toastui-editor/toastui-editor.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/toastui-editor/zh-cn.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/xterm/LICENSE +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/xterm/addons/fit.LICENSE +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/xterm/addons/fit.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/xterm/addons/unicode11.LICENSE +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/xterm/addons/unicode11.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/xterm/addons/web-links.LICENSE +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/xterm/addons/web-links.js +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/xterm/xterm.css +0 -0
- {codex_partner-0.0.5 → codex_partner-0.0.7}/static/vendor/xterm/xterm.js +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codex-partner
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.7
|
|
4
4
|
Summary: A self-hosted web interface for managing persistent Codex tasks.
|
|
5
5
|
Project-URL: Homepage, https://github.com/froststeam/Codex-Partner
|
|
6
6
|
Project-URL: Repository, https://github.com/froststeam/Codex-Partner.git
|
|
@@ -9,6 +9,8 @@ Keywords: codex,agent,task-management,self-hosted
|
|
|
9
9
|
Classifier: Development Status :: 3 - Alpha
|
|
10
10
|
Classifier: Environment :: Web Environment
|
|
11
11
|
Classifier: Framework :: FastAPI
|
|
12
|
+
Classifier: Operating System :: MacOS
|
|
13
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
12
14
|
Classifier: Operating System :: POSIX :: Linux
|
|
13
15
|
Classifier: Programming Language :: Python :: 3
|
|
14
16
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
@@ -20,6 +22,7 @@ Requires-Dist: uvicorn[standard]>=0.27
|
|
|
20
22
|
Requires-Dist: tomli>=2.0; python_version < "3.11"
|
|
21
23
|
Requires-Dist: PyYAML>=6.0
|
|
22
24
|
Requires-Dist: pexpect>=4.8; platform_system != "Windows"
|
|
25
|
+
Requires-Dist: pywinpty>=2.0.13; platform_system == "Windows"
|
|
23
26
|
Requires-Dist: python-dotenv>=1.0
|
|
24
27
|
Requires-Dist: Pillow>=10.0
|
|
25
28
|
|
|
@@ -45,6 +48,19 @@ Codex Partner is a self-hosted control panel for long-running Codex work. It kee
|
|
|
45
48
|
- Model and API provider management with health checks and failover
|
|
46
49
|
- Codex memories, Skills, archive, recycle bin, and SSH workspace support
|
|
47
50
|
- Default YOLO mode for newly created sessions
|
|
51
|
+
- Safe terminal-to-web takeover: normal web sends ask before stopping a terminal turn; `Alt+Enter` keeps the message queued without interruption
|
|
52
|
+
|
|
53
|
+
## Platform support
|
|
54
|
+
|
|
55
|
+
Codex Partner runs natively on Linux, macOS, and Windows with Python 3.10 or newer.
|
|
56
|
+
|
|
57
|
+
| Platform | Codex sessions | Workspace tools | Browser terminal | Background service |
|
|
58
|
+
| --- | --- | --- | --- | --- |
|
|
59
|
+
| Linux | Yes | Yes | POSIX PTY | systemd user service |
|
|
60
|
+
| macOS | Yes | Yes | zsh/bash PTY | Run directly or use launchd |
|
|
61
|
+
| Windows 10/11 | Yes | Yes | PowerShell through ConPTY | Run directly or use Task Scheduler |
|
|
62
|
+
|
|
63
|
+
Windows terminal support uses `pywinpty`, which is installed automatically. Key-based remote SSH works when the OpenSSH client is installed. Browser password prompts for remote SSH are not available on native Windows; configure an SSH key first.
|
|
48
64
|
|
|
49
65
|
## Install
|
|
50
66
|
|
|
@@ -69,12 +85,49 @@ codex-partner
|
|
|
69
85
|
|
|
70
86
|
Open <http://127.0.0.1:8787>.
|
|
71
87
|
|
|
72
|
-
For access from other devices, set `CODEX_DASHBOARD_HOST=0.0.0.0
|
|
88
|
+
Linux keeps SSH browser authentication enabled by default. macOS and Windows default to unauthenticated loopback access because desktop installations normally do not run an SSH server. For access from other devices, set `CODEX_DASHBOARD_HOST=0.0.0.0`, configure `CODEX_DASHBOARD_AUTH=ssh`, and put HTTPS in front of the service. When authentication is handled by a reverse proxy, explicitly set `CODEX_DASHBOARD_AUTH=none`; Codex Partner rejects a non-loopback bind with an implicit unauthenticated default.
|
|
89
|
+
|
|
90
|
+
### macOS
|
|
91
|
+
|
|
92
|
+
Install Python and Node.js with Homebrew if they are not already available, then install and authenticate Codex:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
brew install python node
|
|
96
|
+
npm install --global @openai/codex
|
|
97
|
+
codex login
|
|
98
|
+
python3 -m pip install --user --upgrade codex-partner
|
|
99
|
+
python3 -m codex_partner
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Open <http://127.0.0.1:8787>. Application data defaults to `~/Library/Application Support/CodexPartner`; native Codex state remains in `~/.codex`.
|
|
103
|
+
|
|
104
|
+
### Windows
|
|
105
|
+
|
|
106
|
+
Run these commands in PowerShell. Install Python and Node.js first if they are not already available:
|
|
107
|
+
|
|
108
|
+
```powershell
|
|
109
|
+
winget install --id Python.Python.3.12 -e
|
|
110
|
+
winget install --id OpenJS.NodeJS.LTS -e
|
|
111
|
+
npm install --global @openai/codex
|
|
112
|
+
codex login
|
|
113
|
+
py -m pip install --user --upgrade codex-partner
|
|
114
|
+
py -m codex_partner
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Open <http://127.0.0.1:8787>. Application data defaults to `%LOCALAPPDATA%\CodexPartner`; native Codex state remains in `%USERPROFILE%\.codex`. If `codex` is not found after installing Node.js, close and reopen PowerShell so its `PATH` is refreshed.
|
|
118
|
+
|
|
119
|
+
For local desktop use, keep the default `127.0.0.1` bind address. To set options for one PowerShell session:
|
|
120
|
+
|
|
121
|
+
```powershell
|
|
122
|
+
$env:CODEX_DASHBOARD_PORT = "8787"
|
|
123
|
+
$env:CODEX_DASHBOARD_AUTH = "none"
|
|
124
|
+
py -m codex_partner
|
|
125
|
+
```
|
|
73
126
|
|
|
74
127
|
## Docker
|
|
75
128
|
|
|
76
129
|
```bash
|
|
77
|
-
docker build -t codex-partner:0.0.
|
|
130
|
+
docker build -t codex-partner:0.0.7 .
|
|
78
131
|
docker run -d --name codex-partner \
|
|
79
132
|
-p 8787:8787 \
|
|
80
133
|
--add-host=host.docker.internal:host-gateway \
|
|
@@ -83,11 +136,11 @@ docker run -d --name codex-partner \
|
|
|
83
136
|
-v codex-partner-data:/var/lib/codex-partner \
|
|
84
137
|
-v codex-home:/home/codex/.codex \
|
|
85
138
|
-v codex-workspace:/workspace \
|
|
86
|
-
codex-partner:0.0.
|
|
139
|
+
codex-partner:0.0.7
|
|
87
140
|
docker exec -it codex-partner codex login
|
|
88
141
|
```
|
|
89
142
|
|
|
90
|
-
## systemd
|
|
143
|
+
## Linux systemd
|
|
91
144
|
|
|
92
145
|
After completing the source installation above, stop the foreground process and install the user service:
|
|
93
146
|
|
|
@@ -20,6 +20,19 @@ Codex Partner is a self-hosted control panel for long-running Codex work. It kee
|
|
|
20
20
|
- Model and API provider management with health checks and failover
|
|
21
21
|
- Codex memories, Skills, archive, recycle bin, and SSH workspace support
|
|
22
22
|
- Default YOLO mode for newly created sessions
|
|
23
|
+
- Safe terminal-to-web takeover: normal web sends ask before stopping a terminal turn; `Alt+Enter` keeps the message queued without interruption
|
|
24
|
+
|
|
25
|
+
## Platform support
|
|
26
|
+
|
|
27
|
+
Codex Partner runs natively on Linux, macOS, and Windows with Python 3.10 or newer.
|
|
28
|
+
|
|
29
|
+
| Platform | Codex sessions | Workspace tools | Browser terminal | Background service |
|
|
30
|
+
| --- | --- | --- | --- | --- |
|
|
31
|
+
| Linux | Yes | Yes | POSIX PTY | systemd user service |
|
|
32
|
+
| macOS | Yes | Yes | zsh/bash PTY | Run directly or use launchd |
|
|
33
|
+
| Windows 10/11 | Yes | Yes | PowerShell through ConPTY | Run directly or use Task Scheduler |
|
|
34
|
+
|
|
35
|
+
Windows terminal support uses `pywinpty`, which is installed automatically. Key-based remote SSH works when the OpenSSH client is installed. Browser password prompts for remote SSH are not available on native Windows; configure an SSH key first.
|
|
23
36
|
|
|
24
37
|
## Install
|
|
25
38
|
|
|
@@ -44,12 +57,49 @@ codex-partner
|
|
|
44
57
|
|
|
45
58
|
Open <http://127.0.0.1:8787>.
|
|
46
59
|
|
|
47
|
-
For access from other devices, set `CODEX_DASHBOARD_HOST=0.0.0.0
|
|
60
|
+
Linux keeps SSH browser authentication enabled by default. macOS and Windows default to unauthenticated loopback access because desktop installations normally do not run an SSH server. For access from other devices, set `CODEX_DASHBOARD_HOST=0.0.0.0`, configure `CODEX_DASHBOARD_AUTH=ssh`, and put HTTPS in front of the service. When authentication is handled by a reverse proxy, explicitly set `CODEX_DASHBOARD_AUTH=none`; Codex Partner rejects a non-loopback bind with an implicit unauthenticated default.
|
|
61
|
+
|
|
62
|
+
### macOS
|
|
63
|
+
|
|
64
|
+
Install Python and Node.js with Homebrew if they are not already available, then install and authenticate Codex:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
brew install python node
|
|
68
|
+
npm install --global @openai/codex
|
|
69
|
+
codex login
|
|
70
|
+
python3 -m pip install --user --upgrade codex-partner
|
|
71
|
+
python3 -m codex_partner
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Open <http://127.0.0.1:8787>. Application data defaults to `~/Library/Application Support/CodexPartner`; native Codex state remains in `~/.codex`.
|
|
75
|
+
|
|
76
|
+
### Windows
|
|
77
|
+
|
|
78
|
+
Run these commands in PowerShell. Install Python and Node.js first if they are not already available:
|
|
79
|
+
|
|
80
|
+
```powershell
|
|
81
|
+
winget install --id Python.Python.3.12 -e
|
|
82
|
+
winget install --id OpenJS.NodeJS.LTS -e
|
|
83
|
+
npm install --global @openai/codex
|
|
84
|
+
codex login
|
|
85
|
+
py -m pip install --user --upgrade codex-partner
|
|
86
|
+
py -m codex_partner
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Open <http://127.0.0.1:8787>. Application data defaults to `%LOCALAPPDATA%\CodexPartner`; native Codex state remains in `%USERPROFILE%\.codex`. If `codex` is not found after installing Node.js, close and reopen PowerShell so its `PATH` is refreshed.
|
|
90
|
+
|
|
91
|
+
For local desktop use, keep the default `127.0.0.1` bind address. To set options for one PowerShell session:
|
|
92
|
+
|
|
93
|
+
```powershell
|
|
94
|
+
$env:CODEX_DASHBOARD_PORT = "8787"
|
|
95
|
+
$env:CODEX_DASHBOARD_AUTH = "none"
|
|
96
|
+
py -m codex_partner
|
|
97
|
+
```
|
|
48
98
|
|
|
49
99
|
## Docker
|
|
50
100
|
|
|
51
101
|
```bash
|
|
52
|
-
docker build -t codex-partner:0.0.
|
|
102
|
+
docker build -t codex-partner:0.0.7 .
|
|
53
103
|
docker run -d --name codex-partner \
|
|
54
104
|
-p 8787:8787 \
|
|
55
105
|
--add-host=host.docker.internal:host-gateway \
|
|
@@ -58,11 +108,11 @@ docker run -d --name codex-partner \
|
|
|
58
108
|
-v codex-partner-data:/var/lib/codex-partner \
|
|
59
109
|
-v codex-home:/home/codex/.codex \
|
|
60
110
|
-v codex-workspace:/workspace \
|
|
61
|
-
codex-partner:0.0.
|
|
111
|
+
codex-partner:0.0.7
|
|
62
112
|
docker exec -it codex-partner codex login
|
|
63
113
|
```
|
|
64
114
|
|
|
65
|
-
## systemd
|
|
115
|
+
## Linux systemd
|
|
66
116
|
|
|
67
117
|
After completing the source installation above, stop the foreground process and install the user service:
|
|
68
118
|
|
|
@@ -7,7 +7,6 @@ import binascii
|
|
|
7
7
|
import codecs
|
|
8
8
|
import errno
|
|
9
9
|
import getpass
|
|
10
|
-
import fcntl
|
|
11
10
|
import glob
|
|
12
11
|
import hashlib
|
|
13
12
|
import io
|
|
@@ -16,7 +15,6 @@ import json
|
|
|
16
15
|
import mimetypes
|
|
17
16
|
import os
|
|
18
17
|
import posixpath
|
|
19
|
-
import pty
|
|
20
18
|
import platform
|
|
21
19
|
import re
|
|
22
20
|
import signal
|
|
@@ -27,7 +25,6 @@ import sqlite3
|
|
|
27
25
|
import socket
|
|
28
26
|
import struct
|
|
29
27
|
import subprocess
|
|
30
|
-
import termios
|
|
31
28
|
import time
|
|
32
29
|
import uuid
|
|
33
30
|
import urllib.error
|
|
@@ -43,7 +40,20 @@ from fastapi.responses import FileResponse, JSONResponse, StreamingResponse
|
|
|
43
40
|
from starlette.middleware.gzip import GZipMiddleware
|
|
44
41
|
from dotenv import load_dotenv
|
|
45
42
|
import yaml
|
|
46
|
-
|
|
43
|
+
try:
|
|
44
|
+
import fcntl
|
|
45
|
+
import pty
|
|
46
|
+
import termios
|
|
47
|
+
except ImportError: # Native Windows has no POSIX PTY APIs.
|
|
48
|
+
fcntl = pty = termios = None
|
|
49
|
+
try:
|
|
50
|
+
import pexpect
|
|
51
|
+
except ImportError: # Password-driven SSH prompts are optional on Windows.
|
|
52
|
+
pexpect = None
|
|
53
|
+
try:
|
|
54
|
+
from winpty import PtyProcess as WindowsPtyProcess
|
|
55
|
+
except ImportError: # Installed through the Windows-only pywinpty dependency.
|
|
56
|
+
WindowsPtyProcess = None
|
|
47
57
|
from PIL import Image, ImageOps, ImageSequence, UnidentifiedImageError
|
|
48
58
|
|
|
49
59
|
from codex_partner import APP_NAME, APP_VERSION
|
|
@@ -59,6 +69,14 @@ from codex_partner.discovery import (
|
|
|
59
69
|
official_candidates,
|
|
60
70
|
vscode_candidates,
|
|
61
71
|
)
|
|
72
|
+
from codex_partner.platform_support import (
|
|
73
|
+
IS_WINDOWS,
|
|
74
|
+
default_auth_mode,
|
|
75
|
+
default_data_dir,
|
|
76
|
+
local_shell_command,
|
|
77
|
+
prepare_subprocess_command,
|
|
78
|
+
validate_bind_auth,
|
|
79
|
+
)
|
|
62
80
|
from codex_partner.schemas import (
|
|
63
81
|
ApprovalResolveIn,
|
|
64
82
|
AvatarIn,
|
|
@@ -91,12 +109,12 @@ except ModuleNotFoundError: # Python 3.10
|
|
|
91
109
|
ROOT = Path(__file__).resolve().parent
|
|
92
110
|
load_dotenv(ROOT / ".env", override=False)
|
|
93
111
|
STATIC = ROOT / "static"
|
|
94
|
-
DATA_DIR =
|
|
112
|
+
DATA_DIR = default_data_dir(ROOT)
|
|
95
113
|
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
|
96
114
|
DB_PATH = DATA_DIR / "dashboard.sqlite3"
|
|
97
115
|
CODEX_HOME = Path(os.getenv("CODEX_HOME", str(Path.home() / ".codex"))).expanduser().resolve()
|
|
98
116
|
CODEX_MEMORY_DIR = CODEX_HOME / "memories"
|
|
99
|
-
AUTH_MODE = os.getenv("CODEX_DASHBOARD_AUTH",
|
|
117
|
+
AUTH_MODE = os.getenv("CODEX_DASHBOARD_AUTH", default_auth_mode()).strip().lower()
|
|
100
118
|
if AUTH_MODE not in {"ssh", "none"}:
|
|
101
119
|
raise RuntimeError("CODEX_DASHBOARD_AUTH must be 'ssh' or 'none'")
|
|
102
120
|
AUTH_COOKIE = "codex_partner_session"
|
|
@@ -128,6 +146,7 @@ def configured_port(name: str, default: int) -> int:
|
|
|
128
146
|
|
|
129
147
|
|
|
130
148
|
DASHBOARD_HOST = os.getenv("CODEX_DASHBOARD_HOST", "127.0.0.1").strip() or "127.0.0.1"
|
|
149
|
+
validate_bind_auth(DASHBOARD_HOST, AUTH_MODE, "CODEX_DASHBOARD_AUTH" in os.environ)
|
|
131
150
|
DASHBOARD_PORT = configured_port("CODEX_DASHBOARD_PORT", 8787)
|
|
132
151
|
AUTH_SSH_PORT = configured_port("CODEX_DASHBOARD_AUTH_SSH_PORT", 22)
|
|
133
152
|
SSH_BIN = shutil.which("ssh") or "ssh"
|
|
@@ -135,7 +154,12 @@ SSH_CONFIG = Path.home() / ".ssh/config"
|
|
|
135
154
|
SSH_FIXED_HOST = os.getenv("CODEX_DASHBOARD_SSH_HOST", "").strip()
|
|
136
155
|
SSH_FIXED_USER = os.getenv("CODEX_DASHBOARD_SSH_USER", "").strip()
|
|
137
156
|
SSH_FIXED_PORT = configured_port("CODEX_DASHBOARD_SSH_PORT", 22)
|
|
138
|
-
|
|
157
|
+
if os.getenv("XDG_RUNTIME_DIR"):
|
|
158
|
+
SSH_RUNTIME_DIR = Path(os.environ["XDG_RUNTIME_DIR"]) / "codex-dashboard-ssh"
|
|
159
|
+
elif hasattr(os, "getuid") and platform.system() == "Linux":
|
|
160
|
+
SSH_RUNTIME_DIR = Path(f"/run/user/{os.getuid()}") / "codex-dashboard-ssh"
|
|
161
|
+
else:
|
|
162
|
+
SSH_RUNTIME_DIR = DATA_DIR / "ssh-runtime"
|
|
139
163
|
try:
|
|
140
164
|
SSH_RUNTIME_DIR.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
141
165
|
SSH_RUNTIME_DIR.chmod(0o700)
|
|
@@ -207,7 +231,7 @@ def codex_management_snapshot() -> dict[str, Any]:
|
|
|
207
231
|
if CODEX_AVAILABLE:
|
|
208
232
|
try:
|
|
209
233
|
result = subprocess.run(
|
|
210
|
-
[CODEX_BIN, "--version"],
|
|
234
|
+
prepare_subprocess_command([CODEX_BIN, "--version"]),
|
|
211
235
|
stdin=subprocess.DEVNULL,
|
|
212
236
|
stdout=subprocess.PIPE,
|
|
213
237
|
stderr=subprocess.STDOUT,
|
|
@@ -1154,8 +1178,23 @@ def rollout_writer_pids(path: str, refresh: bool = False) -> set[int]:
|
|
|
1154
1178
|
try:
|
|
1155
1179
|
processes = os.scandir("/proc")
|
|
1156
1180
|
except OSError:
|
|
1181
|
+
lsof = shutil.which("lsof")
|
|
1182
|
+
if lsof:
|
|
1183
|
+
try:
|
|
1184
|
+
result = subprocess.run(
|
|
1185
|
+
[lsof, "-t", "--", target],
|
|
1186
|
+
capture_output=True,
|
|
1187
|
+
text=True,
|
|
1188
|
+
encoding="utf-8",
|
|
1189
|
+
errors="replace",
|
|
1190
|
+
timeout=2,
|
|
1191
|
+
check=False,
|
|
1192
|
+
)
|
|
1193
|
+
writers = {int(value) for value in result.stdout.splitlines() if value.isdigit()}
|
|
1194
|
+
except (OSError, subprocess.SubprocessError, ValueError):
|
|
1195
|
+
pass
|
|
1157
1196
|
rollout_writer_cache[target] = (stamp, writers)
|
|
1158
|
-
return writers
|
|
1197
|
+
return set(writers)
|
|
1159
1198
|
with processes:
|
|
1160
1199
|
for process in processes:
|
|
1161
1200
|
if not process.name.isdigit():
|
|
@@ -1232,11 +1271,13 @@ def interrupt_rollout_writers(path: str) -> set[int]:
|
|
|
1232
1271
|
writers = native_rollout_writer_pids(path, refresh=True)
|
|
1233
1272
|
for pid in writers:
|
|
1234
1273
|
try:
|
|
1235
|
-
|
|
1236
|
-
if
|
|
1237
|
-
|
|
1274
|
+
command_path = Path(f"/proc/{pid}/cmdline")
|
|
1275
|
+
if command_path.is_file():
|
|
1276
|
+
command = command_path.read_bytes().replace(b"\0", b" ")
|
|
1277
|
+
if b"app-server" in command:
|
|
1278
|
+
continue
|
|
1238
1279
|
os.kill(pid, signal.SIGINT)
|
|
1239
|
-
except OSError:
|
|
1280
|
+
except (OSError, ValueError):
|
|
1240
1281
|
continue
|
|
1241
1282
|
return writers
|
|
1242
1283
|
|
|
@@ -1826,7 +1867,7 @@ def ssh_config_aliases(path: Path = SSH_CONFIG, seen: Optional[set[Path]] = None
|
|
|
1826
1867
|
return []
|
|
1827
1868
|
for line in lines:
|
|
1828
1869
|
try:
|
|
1829
|
-
parts = shlex.split(line, comments=True, posix=
|
|
1870
|
+
parts = shlex.split(line, comments=True, posix=not IS_WINDOWS)
|
|
1830
1871
|
except ValueError:
|
|
1831
1872
|
continue
|
|
1832
1873
|
if not parts:
|
|
@@ -1995,6 +2036,8 @@ exit 1
|
|
|
1995
2036
|
|
|
1996
2037
|
|
|
1997
2038
|
def password_ssh_connect(host: str, password: str) -> tuple[bool, str]:
|
|
2039
|
+
if pexpect is None:
|
|
2040
|
+
return False, "SSH password authentication is unavailable on this platform"
|
|
1998
2041
|
args = [
|
|
1999
2042
|
*ssh_options(host, batch=False),
|
|
2000
2043
|
"-o", "ConnectTimeout=10",
|
|
@@ -3613,7 +3656,7 @@ def appserver_turn_inputs(task: dict, message: str) -> list[dict[str, Any]]:
|
|
|
3613
3656
|
continue
|
|
3614
3657
|
seen.add((kind, path))
|
|
3615
3658
|
if kind == "mention":
|
|
3616
|
-
inputs.append({"type": "mention", "name":
|
|
3659
|
+
inputs.append({"type": "mention", "name": Path(path).name, "path": path})
|
|
3617
3660
|
else:
|
|
3618
3661
|
item: dict[str, Any] = {"type": kind, "path": path}
|
|
3619
3662
|
if kind == "localImage":
|
|
@@ -3867,6 +3910,13 @@ async def close_terminal_session(terminal_id: str) -> None:
|
|
|
3867
3910
|
if not session:
|
|
3868
3911
|
return
|
|
3869
3912
|
session["closed"] = True
|
|
3913
|
+
windows_pty = session.get("windows_pty")
|
|
3914
|
+
if windows_pty is not None:
|
|
3915
|
+
try:
|
|
3916
|
+
await asyncio.to_thread(lambda: windows_pty.close(force=True))
|
|
3917
|
+
except Exception:
|
|
3918
|
+
pass
|
|
3919
|
+
return
|
|
3870
3920
|
fd, pid = session.get("fd"), session.get("pid")
|
|
3871
3921
|
loop = session.get("loop")
|
|
3872
3922
|
if loop and fd is not None:
|
|
@@ -3905,6 +3955,81 @@ def set_terminal_size(fd: int, cols: int, rows: int) -> None:
|
|
|
3905
3955
|
fcntl.ioctl(fd, termios.TIOCSWINSZ, struct.pack("HHHH", rows, cols, 0, 0))
|
|
3906
3956
|
|
|
3907
3957
|
|
|
3958
|
+
async def windows_terminal_socket(websocket: WebSocket, task_id: str, cwd: str, ssh_host: str) -> None:
|
|
3959
|
+
if WindowsPtyProcess is None:
|
|
3960
|
+
await websocket.send_json({"type": "error", "message": "Windows terminal support requires pywinpty"})
|
|
3961
|
+
await websocket.close(code=1011)
|
|
3962
|
+
return
|
|
3963
|
+
env = os.environ.copy()
|
|
3964
|
+
env.update({"TERM": "xterm-256color", "COLORTERM": "truecolor"})
|
|
3965
|
+
if ssh_host:
|
|
3966
|
+
remote_command = f"cd -- {shlex.quote(cwd)} && exec \"${{SHELL:-/bin/sh}}\" -il"
|
|
3967
|
+
command = [*ssh_options(ssh_host, batch=True, tty=True), ssh_destination(ssh_host), remote_command]
|
|
3968
|
+
shell_label = f"ssh:{ssh_host}"
|
|
3969
|
+
else:
|
|
3970
|
+
command, shell_label = local_shell_command()
|
|
3971
|
+
try:
|
|
3972
|
+
spawn_cwd = cwd if not ssh_host else str(DEFAULT_WORKSPACE)
|
|
3973
|
+
process = await asyncio.to_thread(WindowsPtyProcess.spawn, command, cwd=spawn_cwd, env=env)
|
|
3974
|
+
except Exception as exc:
|
|
3975
|
+
await websocket.send_json({"type": "error", "message": f"terminal start failed: {exc}"})
|
|
3976
|
+
await websocket.close(code=1011)
|
|
3977
|
+
return
|
|
3978
|
+
|
|
3979
|
+
terminal_id = str(uuid.uuid4())
|
|
3980
|
+
session = {"windows_pty": process, "task_id": task_id, "closed": False}
|
|
3981
|
+
terminal_sessions[terminal_id] = session
|
|
3982
|
+
await websocket.send_json({"type": "ready", "terminal_id": terminal_id, "cwd": cwd, "shell": shell_label})
|
|
3983
|
+
|
|
3984
|
+
async def pump_output() -> None:
|
|
3985
|
+
while not session["closed"]:
|
|
3986
|
+
try:
|
|
3987
|
+
data = await asyncio.to_thread(process.read, 65536)
|
|
3988
|
+
except (EOFError, OSError):
|
|
3989
|
+
data = ""
|
|
3990
|
+
if data:
|
|
3991
|
+
await websocket.send_json({"type": "output", "data": data})
|
|
3992
|
+
continue
|
|
3993
|
+
await websocket.send_json({"type": "exit", "code": getattr(process, "exitstatus", None)})
|
|
3994
|
+
return
|
|
3995
|
+
|
|
3996
|
+
async def receive_input() -> None:
|
|
3997
|
+
while True:
|
|
3998
|
+
incoming = await websocket.receive_json()
|
|
3999
|
+
kind = incoming.get("type")
|
|
4000
|
+
if kind == "input":
|
|
4001
|
+
value = incoming.get("data", incoming.get("input", ""))
|
|
4002
|
+
if isinstance(value, str) and value:
|
|
4003
|
+
await asyncio.to_thread(process.write, value)
|
|
4004
|
+
elif kind == "resize":
|
|
4005
|
+
try:
|
|
4006
|
+
cols = max(20, min(int(incoming.get("cols", 120)), 400))
|
|
4007
|
+
rows = max(4, min(int(incoming.get("rows", 32)), 160))
|
|
4008
|
+
await asyncio.to_thread(process.setwinsize, rows, cols)
|
|
4009
|
+
except (OSError, ValueError, TypeError):
|
|
4010
|
+
continue
|
|
4011
|
+
elif kind == "ping":
|
|
4012
|
+
await websocket.send_json({"type": "pong"})
|
|
4013
|
+
elif kind == "close":
|
|
4014
|
+
return
|
|
4015
|
+
|
|
4016
|
+
pump_task = asyncio.create_task(pump_output())
|
|
4017
|
+
receive_task = asyncio.create_task(receive_input())
|
|
4018
|
+
try:
|
|
4019
|
+
_done, pending = await asyncio.wait({pump_task, receive_task}, return_when=asyncio.FIRST_COMPLETED)
|
|
4020
|
+
for waiter in pending:
|
|
4021
|
+
waiter.cancel()
|
|
4022
|
+
await asyncio.gather(*pending, return_exceptions=True)
|
|
4023
|
+
except WebSocketDisconnect:
|
|
4024
|
+
pass
|
|
4025
|
+
finally:
|
|
4026
|
+
for waiter in (pump_task, receive_task):
|
|
4027
|
+
if not waiter.done():
|
|
4028
|
+
waiter.cancel()
|
|
4029
|
+
await asyncio.gather(pump_task, receive_task, return_exceptions=True)
|
|
4030
|
+
await close_terminal_session(terminal_id)
|
|
4031
|
+
|
|
4032
|
+
|
|
3908
4033
|
@app.websocket("/ws/terminal/{task_id}")
|
|
3909
4034
|
async def terminal_socket(websocket: WebSocket, task_id: str):
|
|
3910
4035
|
if not websocket_auth_session(websocket):
|
|
@@ -3921,11 +4046,17 @@ async def terminal_socket(websocket: WebSocket, task_id: str):
|
|
|
3921
4046
|
await websocket.close(code=4428, reason="SSH login required")
|
|
3922
4047
|
return
|
|
3923
4048
|
await websocket.accept()
|
|
3924
|
-
terminal_id = str(uuid.uuid4())
|
|
3925
4049
|
cwd = task["workspace"] if ssh_host else (task["workspace"] if Path(task["workspace"]).is_dir() else str(DEFAULT_WORKSPACE))
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
4050
|
+
if IS_WINDOWS:
|
|
4051
|
+
await windows_terminal_socket(websocket, task_id, cwd, ssh_host)
|
|
4052
|
+
return
|
|
4053
|
+
terminal_id = str(uuid.uuid4())
|
|
4054
|
+
shell_command, local_shell = local_shell_command()
|
|
4055
|
+
shell = f"ssh:{ssh_host}" if ssh_host else local_shell
|
|
4056
|
+
if pty is None or fcntl is None or termios is None:
|
|
4057
|
+
await websocket.send_json({"type": "error", "message": "PTY support is unavailable on this platform"})
|
|
4058
|
+
await websocket.close(code=1011)
|
|
4059
|
+
return
|
|
3929
4060
|
pid, fd = pty.fork()
|
|
3930
4061
|
if pid == 0:
|
|
3931
4062
|
try:
|
|
@@ -3937,7 +4068,7 @@ async def terminal_socket(websocket: WebSocket, task_id: str):
|
|
|
3937
4068
|
os.execvpe(command[0], command, env)
|
|
3938
4069
|
else:
|
|
3939
4070
|
os.chdir(cwd)
|
|
3940
|
-
os.execvpe(
|
|
4071
|
+
os.execvpe(shell_command[0], shell_command, env)
|
|
3941
4072
|
except Exception as exc:
|
|
3942
4073
|
os.write(2, f"terminal start failed: {exc}\n".encode())
|
|
3943
4074
|
os._exit(127)
|
|
@@ -4176,6 +4307,61 @@ async def enqueue_task_message(task_id: str, body: str, client_message_id: Optio
|
|
|
4176
4307
|
return db.one("SELECT * FROM task_messages WHERE id=?", (message_id,)) or {"id": message_id, "status": "queued"}
|
|
4177
4308
|
|
|
4178
4309
|
|
|
4310
|
+
async def terminal_turn_active(task_id: str) -> bool:
|
|
4311
|
+
"""Refresh and report a terminal-owned turn without confusing dashboard work."""
|
|
4312
|
+
await refresh_live_external_turn(task_id)
|
|
4313
|
+
return bool(refresh_external_primary(task_id)) and not dashboard_owns_task(task_id)
|
|
4314
|
+
|
|
4315
|
+
|
|
4316
|
+
async def interrupt_browser_terminal(task_id: str) -> None:
|
|
4317
|
+
"""Send Ctrl+C to the foreground process in every matching browser terminal."""
|
|
4318
|
+
matching = [session for session in terminal_sessions.values() if session.get("task_id") == task_id and not session.get("closed")]
|
|
4319
|
+
for session in matching:
|
|
4320
|
+
try:
|
|
4321
|
+
if process := session.get("windows_pty"):
|
|
4322
|
+
await asyncio.to_thread(process.write, "\x03")
|
|
4323
|
+
elif session.get("fd") is not None:
|
|
4324
|
+
os.write(session["fd"], b"\x03")
|
|
4325
|
+
except (EOFError, OSError):
|
|
4326
|
+
continue
|
|
4327
|
+
|
|
4328
|
+
|
|
4329
|
+
async def stop_terminal_for_web_takeover(task_id: str) -> bool:
|
|
4330
|
+
"""Stop terminal-owned turns without letting Goal auto-resume race the web message."""
|
|
4331
|
+
await refresh_live_external_turn(task_id)
|
|
4332
|
+
tracked = list((external_turn_sets.get(task_id) or {}).values())
|
|
4333
|
+
if not tracked:
|
|
4334
|
+
return False
|
|
4335
|
+
await interrupt_browser_terminal(task_id)
|
|
4336
|
+
paths = {str(turn.get("path") or "") for turn in tracked} - {""}
|
|
4337
|
+
for path in paths:
|
|
4338
|
+
await asyncio.to_thread(interrupt_rollout_writers, path)
|
|
4339
|
+
clear_external_turns(task_id)
|
|
4340
|
+
stamp = now()
|
|
4341
|
+
for external in tracked:
|
|
4342
|
+
session_id = str(external.get("session_id") or "")
|
|
4343
|
+
if not session_id:
|
|
4344
|
+
continue
|
|
4345
|
+
db.execute(
|
|
4346
|
+
"UPDATE sessions SET status='stopped',finished_at=COALESCE(finished_at,?),exit_code=COALESCE(exit_code,130),summary='Terminal turn stopped for web takeover' WHERE id=?",
|
|
4347
|
+
(stamp, session_id),
|
|
4348
|
+
)
|
|
4349
|
+
payload = {"type": "turn_aborted", "status": "stopped", "reason": "Web session took over the terminal turn"}
|
|
4350
|
+
db.execute(
|
|
4351
|
+
"INSERT INTO events (session_id,ts,stream,payload) VALUES (?,?,?,?)",
|
|
4352
|
+
(session_id, stamp, "rollout", json.dumps(payload, ensure_ascii=False)),
|
|
4353
|
+
)
|
|
4354
|
+
await broadcast_task(task_id, {"type": "event", "session_id": session_id, "stream": "rollout", "payload": payload, "ts": stamp})
|
|
4355
|
+
db.execute(
|
|
4356
|
+
"UPDATE tasks SET status='stopped',active_session_id=NULL,execution_source='',execution_turn_id='',run_mode='',last_error='',updated_at=? WHERE id=?",
|
|
4357
|
+
(stamp, task_id),
|
|
4358
|
+
)
|
|
4359
|
+
result = task_or_404(task_id)
|
|
4360
|
+
await broadcast_task(task_id, {"type": "task_status", "task": result, "source": {"kind": "web_takeover"}})
|
|
4361
|
+
await broadcast_overview(task_id, {"kind": "web_takeover"})
|
|
4362
|
+
return True
|
|
4363
|
+
|
|
4364
|
+
|
|
4179
4365
|
def command_event_session(task: dict) -> str:
|
|
4180
4366
|
if task.get("active_session_id") and db.one("SELECT id FROM sessions WHERE id=?", (task["active_session_id"],)):
|
|
4181
4367
|
return task["active_session_id"]
|
|
@@ -4473,7 +4659,7 @@ async def install_codex(force: bool = False, _: Any = Depends(auth)):
|
|
|
4473
4659
|
env["PATH"] = os.pathsep.join((str(Path(npm).parent), str(extra_bin), env.get("PATH", "")))
|
|
4474
4660
|
try:
|
|
4475
4661
|
process = await asyncio.create_subprocess_exec(
|
|
4476
|
-
*command,
|
|
4662
|
+
*prepare_subprocess_command(command),
|
|
4477
4663
|
stdout=asyncio.subprocess.PIPE,
|
|
4478
4664
|
stderr=asyncio.subprocess.STDOUT,
|
|
4479
4665
|
env=env,
|
|
@@ -5655,6 +5841,8 @@ async def supervise(task: dict, session_id: str, providers: list[Optional[dict]]
|
|
|
5655
5841
|
cwd = None
|
|
5656
5842
|
env.pop("OPENAI_API_KEY", None)
|
|
5657
5843
|
env.pop("OPENAI_BASE_URL", None)
|
|
5844
|
+
else:
|
|
5845
|
+
spawn_cmd = prepare_subprocess_command(spawn_cmd)
|
|
5658
5846
|
process = await asyncio.create_subprocess_exec(*spawn_cmd, cwd=cwd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT, env=env)
|
|
5659
5847
|
async with running_lock:
|
|
5660
5848
|
running[task_id] = process
|
|
@@ -5802,6 +5990,16 @@ async def clear_task_messages(task_id: str, _: Any = Depends(auth)):
|
|
|
5802
5990
|
|
|
5803
5991
|
@app.post("/api/tasks/{task_id}/messages")
|
|
5804
5992
|
async def post_task_message(task_id: str, payload: TaskMessageIn, _: Any = Depends(auth)):
|
|
5993
|
+
if payload.delivery != "queue" and await terminal_turn_active(task_id):
|
|
5994
|
+
if not payload.takeover_terminal:
|
|
5995
|
+
raise HTTPException(
|
|
5996
|
+
409,
|
|
5997
|
+
detail={
|
|
5998
|
+
"code": "terminal_turn_active",
|
|
5999
|
+
"message": "Codex 正在终端中运行。是否停止终端 turn 并由网页接管?",
|
|
6000
|
+
},
|
|
6001
|
+
)
|
|
6002
|
+
await stop_terminal_for_web_takeover(task_id)
|
|
5805
6003
|
return await enqueue_task_message(task_id, payload.message, payload.client_message_id, payload.delivery)
|
|
5806
6004
|
|
|
5807
6005
|
|
|
@@ -6249,7 +6447,7 @@ def git_diff_summary(task: dict) -> str:
|
|
|
6249
6447
|
sections = []
|
|
6250
6448
|
for index, command in enumerate(commands):
|
|
6251
6449
|
try:
|
|
6252
|
-
completed = ssh_capture(task["ssh_host"], shlex.join(command), 12) if task.get("ssh_host") else subprocess.run(command, capture_output=True, text=True, timeout=8, check=False)
|
|
6450
|
+
completed = ssh_capture(task["ssh_host"], shlex.join(command), 12) if task.get("ssh_host") else subprocess.run(prepare_subprocess_command(command), capture_output=True, text=True, timeout=8, check=False)
|
|
6253
6451
|
except (OSError, subprocess.TimeoutExpired) as exc:
|
|
6254
6452
|
return f"无法读取 Git 变更:{exc}"
|
|
6255
6453
|
output = (completed.stdout or completed.stderr).strip()
|
|
@@ -6805,6 +7003,8 @@ async def supervise_operation(task: dict, session_id: str, cmd: list[str]) -> No
|
|
|
6805
7003
|
connection = await require_ssh_connection(task["ssh_host"], codex=True)
|
|
6806
7004
|
spawn_cmd = [*ssh_options(task["ssh_host"], batch=True), ssh_destination(task["ssh_host"]), shlex.join([connection["codex_bin"], *cmd[1:]])]
|
|
6807
7005
|
cwd = None
|
|
7006
|
+
else:
|
|
7007
|
+
spawn_cmd = prepare_subprocess_command(spawn_cmd)
|
|
6808
7008
|
process = await asyncio.create_subprocess_exec(*spawn_cmd, cwd=cwd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT, env=os.environ.copy())
|
|
6809
7009
|
async with running_lock:
|
|
6810
7010
|
running[task_id] = process
|
|
@@ -4,6 +4,8 @@ import asyncio
|
|
|
4
4
|
import json
|
|
5
5
|
from typing import Any, Awaitable, Callable, Optional
|
|
6
6
|
|
|
7
|
+
from .platform_support import prepare_subprocess_command
|
|
8
|
+
|
|
7
9
|
|
|
8
10
|
NotificationHandler = Callable[[str, dict], Awaitable[None]]
|
|
9
11
|
|
|
@@ -55,8 +57,9 @@ class AppServerClient:
|
|
|
55
57
|
await asyncio.wait_for(self.process.wait(), 1)
|
|
56
58
|
except Exception:
|
|
57
59
|
pass
|
|
60
|
+
command = prepare_subprocess_command(self.command)
|
|
58
61
|
self.process = await asyncio.create_subprocess_exec(
|
|
59
|
-
*
|
|
62
|
+
*command,
|
|
60
63
|
stdin=asyncio.subprocess.PIPE,
|
|
61
64
|
stdout=asyncio.subprocess.PIPE,
|
|
62
65
|
stderr=asyncio.subprocess.DEVNULL,
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import sqlite3
|
|
4
4
|
import threading
|
|
5
|
+
from contextlib import closing
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
from typing import Callable, Optional
|
|
7
8
|
|
|
@@ -27,7 +28,7 @@ class Database:
|
|
|
27
28
|
return connection
|
|
28
29
|
|
|
29
30
|
def initialize(self) -> None:
|
|
30
|
-
with self.lock, self.connect() as connection:
|
|
31
|
+
with self.lock, closing(self.connect()) as connection:
|
|
31
32
|
connection.executescript(
|
|
32
33
|
"""
|
|
33
34
|
CREATE TABLE IF NOT EXISTS tasks (
|
|
@@ -262,15 +263,15 @@ class Database:
|
|
|
262
263
|
)
|
|
263
264
|
|
|
264
265
|
def one(self, sql: str, args: tuple = ()) -> Optional[dict]:
|
|
265
|
-
with self.lock, self.connect() as connection:
|
|
266
|
+
with self.lock, closing(self.connect()) as connection:
|
|
266
267
|
row = connection.execute(sql, args).fetchone()
|
|
267
268
|
return dict(row) if row else None
|
|
268
269
|
|
|
269
270
|
def all(self, sql: str, args: tuple = ()) -> list[dict]:
|
|
270
|
-
with self.lock, self.connect() as connection:
|
|
271
|
+
with self.lock, closing(self.connect()) as connection:
|
|
271
272
|
return [dict(row) for row in connection.execute(sql, args).fetchall()]
|
|
272
273
|
|
|
273
274
|
def execute(self, sql: str, args: tuple = ()) -> None:
|
|
274
|
-
with self.lock, self.connect() as connection:
|
|
275
|
+
with self.lock, closing(self.connect()) as connection:
|
|
275
276
|
connection.execute(sql, args)
|
|
276
277
|
connection.commit()
|