codex-partner 0.0.6__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.6 → codex_partner-0.0.7}/PKG-INFO +4 -3
- {codex_partner-0.0.6 → codex_partner-0.0.7}/README.md +3 -2
- {codex_partner-0.0.6 → codex_partner-0.0.7}/app.py +71 -4
- {codex_partner-0.0.6 → codex_partner-0.0.7}/codex_partner/__init__.py +1 -1
- {codex_partner-0.0.6 → codex_partner-0.0.7}/codex_partner/schemas.py +1 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/codex_partner.egg-info/PKG-INFO +4 -3
- {codex_partner-0.0.6 → codex_partner-0.0.7}/pyproject.toml +1 -1
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/app.js +27 -4
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/core.js +16 -5
- {codex_partner-0.0.6 → codex_partner-0.0.7}/tests/test_running_state.py +83 -4
- {codex_partner-0.0.6 → codex_partner-0.0.7}/codex_partner/__main__.py +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/codex_partner/activity_graph.py +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/codex_partner/app_server.py +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/codex_partner/cli.py +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/codex_partner/commands.py +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/codex_partner/database.py +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/codex_partner/discovery.py +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/codex_partner/platform_support.py +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/codex_partner/ssh_auth.py +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/codex_partner.egg-info/SOURCES.txt +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/codex_partner.egg-info/dependency_links.txt +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/codex_partner.egg-info/entry_points.txt +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/codex_partner.egg-info/requires.txt +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/codex_partner.egg-info/top_level.txt +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/setup.cfg +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/__init__.py +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/chat-worker.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/conversation.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/default-user-avatar.webp +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/editor.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/exploration-tree.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/history-pagination.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/index.html +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/mascot-dance.css +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/mascot-dance.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/settings.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/styles.css +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/LICENSE +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/ace.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/ext-modelist.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-abap.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-abc.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-actionscript.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-ada.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-alda.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-apache_conf.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-apex.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-applescript.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-aql.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-asciidoc.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-asl.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-assembly_arm32.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-assembly_x86.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-astro.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-autohotkey.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-basic.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-batchfile.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-bibtex.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-c9search.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-c_cpp.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-cedar.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-cedarschema.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-cirru.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-clojure.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-clue.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-cobol.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-coffee.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-coldfusion.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-crystal.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-csharp.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-csound_document.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-csound_orchestra.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-csound_score.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-csp.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-css.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-csv.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-curly.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-cuttlefish.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-d.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-dart.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-diff.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-django.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-dockerfile.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-dot.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-drools.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-edifact.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-eiffel.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-ejs.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-elixir.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-elm.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-erlang.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-flix.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-forth.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-fortran.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-fsharp.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-fsl.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-ftl.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-gcode.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-gherkin.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-gitignore.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-glsl.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-gobstones.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-golang.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-graphqlschema.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-groovy.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-groq.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-haml.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-handlebars.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-haskell.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-haskell_cabal.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-haxe.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-hjson.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-html.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-html_elixir.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-html_ruby.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-ini.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-io.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-ion.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-jack.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-jade.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-java.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-javascript.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-jexl.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-json.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-json5.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-jsoniq.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-jsp.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-jssm.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-jsx.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-julia.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-kotlin.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-latex.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-latte.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-less.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-liquid.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-lisp.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-livescript.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-logiql.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-logtalk.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-lsl.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-lua.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-luapage.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-lucene.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-makefile.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-mariadb.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-markdown.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-mask.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-matlab.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-maze.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-mediawiki.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-mel.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-mips.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-mixal.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-mushcode.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-mysql.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-nasal.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-nginx.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-nim.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-nix.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-nsis.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-nunjucks.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-objectivec.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-ocaml.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-odin.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-partiql.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-pascal.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-perl.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-pgsql.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-php.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-php_laravel_blade.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-pig.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-plain_text.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-plsql.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-powershell.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-praat.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-prisma.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-prolog.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-properties.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-protobuf.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-prql.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-puppet.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-python.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-qml.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-r.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-raku.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-razor.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-rdoc.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-red.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-redshift.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-rhtml.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-robot.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-rst.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-ruby.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-rust.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-sac.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-sass.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-scad.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-scala.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-scheme.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-scrypt.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-scss.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-sh.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-sjs.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-slim.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-smarty.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-smithy.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-snippets.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-soy_template.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-space.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-sparql.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-sql.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-sqlserver.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-stylus.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-svg.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-swift.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-tcl.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-terraform.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-tex.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-text.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-textile.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-toml.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-tsv.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-tsx.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-turtle.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-twig.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-typescript.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-vala.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-vbscript.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-velocity.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-verilog.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-vhdl.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-visualforce.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-vue.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-wollok.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-xml.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-xquery.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-yaml.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-zeek.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/mode-zig.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/ace/theme-tomorrow_night_eighties.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/i18next/LICENSE +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/i18next/i18next.min.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/LICENSE +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_AMS-Regular.ttf +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_AMS-Regular.woff +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Main-Bold.ttf +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Main-Bold.woff +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Main-Italic.ttf +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Main-Italic.woff +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Main-Regular.ttf +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Main-Regular.woff +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Math-Italic.ttf +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Math-Italic.woff +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Script-Regular.ttf +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Script-Regular.woff +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Size1-Regular.ttf +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Size1-Regular.woff +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Size2-Regular.ttf +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Size2-Regular.woff +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Size3-Regular.ttf +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Size3-Regular.woff +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Size4-Regular.ttf +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Size4-Regular.woff +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/katex.min.css +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/katex/katex.min.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/markdown-it/LICENSE +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/markdown-it/markdown-it.min.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/markdown-it-texmath/LICENSE +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/markdown-it-texmath/texmath.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/toastui-editor/LICENSE +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/toastui-editor/toastui-editor-dark.css +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/toastui-editor/toastui-editor.css +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/toastui-editor/toastui-editor.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/toastui-editor/zh-cn.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/xterm/LICENSE +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/xterm/addons/fit.LICENSE +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/xterm/addons/fit.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/xterm/addons/unicode11.LICENSE +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/xterm/addons/unicode11.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/xterm/addons/web-links.LICENSE +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/xterm/addons/web-links.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/xterm/xterm.css +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/static/vendor/xterm/xterm.js +0 -0
- {codex_partner-0.0.6 → codex_partner-0.0.7}/tests/test_platform_support.py +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
|
|
@@ -48,6 +48,7 @@ Codex Partner is a self-hosted control panel for long-running Codex work. It kee
|
|
|
48
48
|
- Model and API provider management with health checks and failover
|
|
49
49
|
- Codex memories, Skills, archive, recycle bin, and SSH workspace support
|
|
50
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
|
|
51
52
|
|
|
52
53
|
## Platform support
|
|
53
54
|
|
|
@@ -126,7 +127,7 @@ py -m codex_partner
|
|
|
126
127
|
## Docker
|
|
127
128
|
|
|
128
129
|
```bash
|
|
129
|
-
docker build -t codex-partner:0.0.
|
|
130
|
+
docker build -t codex-partner:0.0.7 .
|
|
130
131
|
docker run -d --name codex-partner \
|
|
131
132
|
-p 8787:8787 \
|
|
132
133
|
--add-host=host.docker.internal:host-gateway \
|
|
@@ -135,7 +136,7 @@ docker run -d --name codex-partner \
|
|
|
135
136
|
-v codex-partner-data:/var/lib/codex-partner \
|
|
136
137
|
-v codex-home:/home/codex/.codex \
|
|
137
138
|
-v codex-workspace:/workspace \
|
|
138
|
-
codex-partner:0.0.
|
|
139
|
+
codex-partner:0.0.7
|
|
139
140
|
docker exec -it codex-partner codex login
|
|
140
141
|
```
|
|
141
142
|
|
|
@@ -20,6 +20,7 @@ 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
|
|
23
24
|
|
|
24
25
|
## Platform support
|
|
25
26
|
|
|
@@ -98,7 +99,7 @@ py -m codex_partner
|
|
|
98
99
|
## Docker
|
|
99
100
|
|
|
100
101
|
```bash
|
|
101
|
-
docker build -t codex-partner:0.0.
|
|
102
|
+
docker build -t codex-partner:0.0.7 .
|
|
102
103
|
docker run -d --name codex-partner \
|
|
103
104
|
-p 8787:8787 \
|
|
104
105
|
--add-host=host.docker.internal:host-gateway \
|
|
@@ -107,7 +108,7 @@ docker run -d --name codex-partner \
|
|
|
107
108
|
-v codex-partner-data:/var/lib/codex-partner \
|
|
108
109
|
-v codex-home:/home/codex/.codex \
|
|
109
110
|
-v codex-workspace:/workspace \
|
|
110
|
-
codex-partner:0.0.
|
|
111
|
+
codex-partner:0.0.7
|
|
111
112
|
docker exec -it codex-partner codex login
|
|
112
113
|
```
|
|
113
114
|
|
|
@@ -1271,11 +1271,13 @@ def interrupt_rollout_writers(path: str) -> set[int]:
|
|
|
1271
1271
|
writers = native_rollout_writer_pids(path, refresh=True)
|
|
1272
1272
|
for pid in writers:
|
|
1273
1273
|
try:
|
|
1274
|
-
|
|
1275
|
-
if
|
|
1276
|
-
|
|
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
|
|
1277
1279
|
os.kill(pid, signal.SIGINT)
|
|
1278
|
-
except OSError:
|
|
1280
|
+
except (OSError, ValueError):
|
|
1279
1281
|
continue
|
|
1280
1282
|
return writers
|
|
1281
1283
|
|
|
@@ -4305,6 +4307,61 @@ async def enqueue_task_message(task_id: str, body: str, client_message_id: Optio
|
|
|
4305
4307
|
return db.one("SELECT * FROM task_messages WHERE id=?", (message_id,)) or {"id": message_id, "status": "queued"}
|
|
4306
4308
|
|
|
4307
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
|
+
|
|
4308
4365
|
def command_event_session(task: dict) -> str:
|
|
4309
4366
|
if task.get("active_session_id") and db.one("SELECT id FROM sessions WHERE id=?", (task["active_session_id"],)):
|
|
4310
4367
|
return task["active_session_id"]
|
|
@@ -5933,6 +5990,16 @@ async def clear_task_messages(task_id: str, _: Any = Depends(auth)):
|
|
|
5933
5990
|
|
|
5934
5991
|
@app.post("/api/tasks/{task_id}/messages")
|
|
5935
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)
|
|
5936
6003
|
return await enqueue_task_message(task_id, payload.message, payload.client_message_id, payload.delivery)
|
|
5937
6004
|
|
|
5938
6005
|
|
|
@@ -121,6 +121,7 @@ class TaskMessageIn(BaseModel):
|
|
|
121
121
|
message: str = Field(min_length=1, max_length=20000)
|
|
122
122
|
client_message_id: Optional[str] = None
|
|
123
123
|
delivery: Literal["auto", "queue"] = "auto"
|
|
124
|
+
takeover_terminal: bool = False
|
|
124
125
|
|
|
125
126
|
|
|
126
127
|
class TaskMessagePatch(BaseModel):
|
|
@@ -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
|
|
@@ -48,6 +48,7 @@ Codex Partner is a self-hosted control panel for long-running Codex work. It kee
|
|
|
48
48
|
- Model and API provider management with health checks and failover
|
|
49
49
|
- Codex memories, Skills, archive, recycle bin, and SSH workspace support
|
|
50
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
|
|
51
52
|
|
|
52
53
|
## Platform support
|
|
53
54
|
|
|
@@ -126,7 +127,7 @@ py -m codex_partner
|
|
|
126
127
|
## Docker
|
|
127
128
|
|
|
128
129
|
```bash
|
|
129
|
-
docker build -t codex-partner:0.0.
|
|
130
|
+
docker build -t codex-partner:0.0.7 .
|
|
130
131
|
docker run -d --name codex-partner \
|
|
131
132
|
-p 8787:8787 \
|
|
132
133
|
--add-host=host.docker.internal:host-gateway \
|
|
@@ -135,7 +136,7 @@ docker run -d --name codex-partner \
|
|
|
135
136
|
-v codex-partner-data:/var/lib/codex-partner \
|
|
136
137
|
-v codex-home:/home/codex/.codex \
|
|
137
138
|
-v codex-workspace:/workspace \
|
|
138
|
-
codex-partner:0.0.
|
|
139
|
+
codex-partner:0.0.7
|
|
139
140
|
docker exec -it codex-partner codex login
|
|
140
141
|
```
|
|
141
142
|
|
|
@@ -270,6 +270,8 @@ async function submitMessage(mode = "codex") {
|
|
|
270
270
|
} catch (error) { input.value = message; resizeComposerInput(input); renderCommandPalette(); toast(error.message); }
|
|
271
271
|
return;
|
|
272
272
|
}
|
|
273
|
+
const draftMessage = message;
|
|
274
|
+
const draftAttachments = [...pendingAttachments];
|
|
273
275
|
if (pendingAttachments.length) {
|
|
274
276
|
message += pendingAttachments.map((file, index) => {
|
|
275
277
|
const prefix = message || index ? "\n\n" : "";
|
|
@@ -277,15 +279,36 @@ async function submitMessage(mode = "codex") {
|
|
|
277
279
|
return `${prefix}[${uiLabel("attachments")}:${file.name}]\n\n\`\`\`\n${file.content}\n\`\`\``;
|
|
278
280
|
}).join("");
|
|
279
281
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
+
let takeoverTerminal = false;
|
|
283
|
+
const terminalActive = Boolean(state.selectedTask?.external_running) && state.selectedTask?.execution_source === "terminal";
|
|
284
|
+
if (mode === "codex" && terminalActive) {
|
|
285
|
+
takeoverTerminal = await appConfirm(uiLabel("terminalTakeoverConfirm"), { danger: true, confirmLabel: uiLabel("terminalTakeoverAction") });
|
|
286
|
+
if (!takeoverTerminal) return;
|
|
287
|
+
}
|
|
288
|
+
// Dashboard-owned turns stay FIFO. A terminal-owned turn requires an
|
|
289
|
+
// explicit takeover confirmation; Alt+Enter always remains queue-only.
|
|
282
290
|
const clientMessageId = crypto.randomUUID ? crypto.randomUUID() : String(Date.now());
|
|
283
|
-
const delivery = mode === "queue" || activeTurn ? "queue" : "auto";
|
|
291
|
+
const delivery = mode === "queue" || (activeTurn && !takeoverTerminal) ? "queue" : "auto";
|
|
284
292
|
const optimisticStatus = delivery === "queue" ? "queued" : "sending";
|
|
285
293
|
const optimistic = { id: clientMessageId, body: message, status: optimisticStatus, created_at: new Date().toISOString(), _optimistic: true };
|
|
286
294
|
upsertTaskMessage(optimistic); state.historyIndex = -1; input.value = ""; resizeComposerInput(input); pendingAttachments = []; $("#composer-context").textContent = ""; $("#attach-file").value = ""; $("#command-palette").hidden = true; renderGoalBar(); scheduleRenderChat(); input.focus();
|
|
287
295
|
try {
|
|
288
|
-
|
|
296
|
+
let delivered;
|
|
297
|
+
try {
|
|
298
|
+
delivered = await api(`/tasks/${taskId}/messages`, { method: "POST", body: JSON.stringify({ message, client_message_id: clientMessageId, delivery, takeover_terminal: takeoverTerminal }) });
|
|
299
|
+
} catch (error) {
|
|
300
|
+
if (mode !== "codex" || error.code !== "terminal_turn_active" || takeoverTerminal) throw error;
|
|
301
|
+
const confirmed = await appConfirm(uiLabel("terminalTakeoverConfirm"), { danger: true, confirmLabel: uiLabel("terminalTakeoverAction") });
|
|
302
|
+
if (!confirmed) {
|
|
303
|
+
if (state.selectedId === taskId) {
|
|
304
|
+
removeTaskMessage(clientMessageId); input.value = draftMessage; pendingAttachments = draftAttachments;
|
|
305
|
+
resizeComposerInput(input); renderComposerAttachments(); renderGoalBar(); scheduleRenderChat(); input.focus();
|
|
306
|
+
}
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
takeoverTerminal = true;
|
|
310
|
+
delivered = await api(`/tasks/${taskId}/messages`, { method: "POST", body: JSON.stringify({ message, client_message_id: clientMessageId, delivery: "auto", takeover_terminal: true }) });
|
|
311
|
+
}
|
|
289
312
|
if (state.selectedId === taskId) { upsertTaskMessage(delivered); renderQueuedMessages(); refreshComposerHistory(); renderGoalBar(); scheduleRenderChat(); }
|
|
290
313
|
if (activeTurn && delivered.status === "queued") toast(uiLabel("queuedAfterTurn"));
|
|
291
314
|
} catch (error) {
|
|
@@ -371,6 +371,8 @@ const UI_LABELS = {
|
|
|
371
371
|
approvalSubmit: { zh: "提交选择", en: "Submit", fr: "Envoyer", ja: "送信", ko: "제출" },
|
|
372
372
|
approvalResolved: { zh: "授权选择已执行", en: "Approval choice applied", fr: "Choix appliqué", ja: "承認を反映しました", ko: "승인 선택을 적용했습니다" },
|
|
373
373
|
queuedAfterTurn: { zh: "已加入队列,当前 turn 结束后自动发射", en: "Queued and will run after the current turn", fr: "Ajouté à la file et exécuté après le tour actuel", ja: "キューに追加し、現在のターン完了後に自動実行します", ko: "대기열에 추가되었으며 현재 turn 종료 후 자동 실행됩니다" },
|
|
374
|
+
terminalTakeoverConfirm: { zh: "Codex 正在终端中运行。停止终端 turn,并由网页发送这条消息?", en: "Codex is running in a terminal. Stop that turn and send this message from the web?", fr: "Codex s’exécute dans un terminal. Arrêter ce tour et envoyer ce message depuis le Web ?", ja: "Codex はターミナルで実行中です。ターミナルのターンを停止し、このメッセージを Web から送信しますか?", ko: "Codex가 터미널에서 실행 중입니다. 터미널 turn을 중지하고 웹에서 이 메시지를 보낼까요?" },
|
|
375
|
+
terminalTakeoverAction: { zh: "停止终端并发送", en: "Stop terminal and send", fr: "Arrêter et envoyer", ja: "停止して送信", ko: "터미널 중지 후 전송" },
|
|
374
376
|
clearQueue: { zh: "清空所有排队消息", en: "Clear all queued messages", fr: "Vider tous les messages en attente", ja: "待機メッセージをすべて消去", ko: "대기 메시지 모두 지우기" },
|
|
375
377
|
dispatchNow: { zh: "立即执行这条消息", en: "Run this message now", fr: "Exécuter ce message maintenant", ja: "このメッセージを今すぐ実行", ko: "이 메시지 지금 실행" },
|
|
376
378
|
editQueuedAction: { zh: "编辑排队消息", en: "Edit queued message", fr: "Modifier le message en attente", ja: "待機メッセージを編集", ko: "대기 메시지 편집" },
|
|
@@ -677,18 +679,27 @@ async function api(path, options = {}, canPrompt = true) {
|
|
|
677
679
|
if (response.status === 401 && canPrompt) {
|
|
678
680
|
if (await requestSSHLogin()) return api(path, options, false);
|
|
679
681
|
}
|
|
680
|
-
if (!response.ok)
|
|
682
|
+
if (!response.ok) {
|
|
683
|
+
const detail = await responseErrorDetail(response);
|
|
684
|
+
const error = Error(detail.message);
|
|
685
|
+
error.code = detail.code || "";
|
|
686
|
+
error.detail = detail.detail;
|
|
687
|
+
throw error;
|
|
688
|
+
}
|
|
681
689
|
return response.json();
|
|
682
690
|
}
|
|
683
|
-
async function
|
|
691
|
+
async function responseErrorDetail(response) {
|
|
684
692
|
const text = await response.text().catch(() => "");
|
|
685
693
|
try {
|
|
686
694
|
const payload = JSON.parse(text);
|
|
687
|
-
if (payload?.detail) return String(payload.detail);
|
|
695
|
+
if (payload?.detail && typeof payload.detail === "object") return { message: String(payload.detail.message || response.statusText || "Request failed"), code: String(payload.detail.code || ""), detail: payload.detail };
|
|
696
|
+
if (payload?.detail) return { message: String(payload.detail), code: "", detail: payload.detail };
|
|
688
697
|
} catch (_) { /* Non-JSON server and proxy errors are expected here. */ }
|
|
689
698
|
const plain = text.trim();
|
|
690
|
-
|
|
691
|
-
|
|
699
|
+
return { message: plain && !plain.startsWith("<") && plain.length <= 500 ? plain : `${response.status} ${response.statusText || "Request failed"}`.trim(), code: "", detail: null };
|
|
700
|
+
}
|
|
701
|
+
async function responseErrorMessage(response) {
|
|
702
|
+
return (await responseErrorDetail(response)).message;
|
|
692
703
|
}
|
|
693
704
|
function toast(message) { const node = $("#toast"); node.textContent = message; node.classList.add("show"); setTimeout(() => node.classList.remove("show"), 2600); }
|
|
694
705
|
async function copyText(value) {
|
|
@@ -1646,6 +1646,83 @@ process.stdout.write(JSON.stringify(browserMessages));
|
|
|
1646
1646
|
|
|
1647
1647
|
asyncio.run(exercise())
|
|
1648
1648
|
|
|
1649
|
+
def test_terminal_message_requires_takeover_confirmation_before_queueing(self):
|
|
1650
|
+
task_id = "terminal-confirm-thread"
|
|
1651
|
+
self.make_task(task_id, "running")
|
|
1652
|
+
self.app.register_external_turn(task_id, {"turn_id": "terminal-turn", "path": "/tmp/terminal-turn.jsonl"})
|
|
1653
|
+
try:
|
|
1654
|
+
with mock.patch.object(self.app, "refresh_live_external_turn", new=mock.AsyncMock(return_value=False)):
|
|
1655
|
+
with self.assertRaises(self.app.HTTPException) as raised:
|
|
1656
|
+
asyncio.run(self.app.post_task_message(
|
|
1657
|
+
task_id,
|
|
1658
|
+
self.app.TaskMessageIn(message="take over", client_message_id="terminal-confirm-message"),
|
|
1659
|
+
None,
|
|
1660
|
+
))
|
|
1661
|
+
self.assertEqual(409, raised.exception.status_code)
|
|
1662
|
+
self.assertEqual("terminal_turn_active", raised.exception.detail["code"])
|
|
1663
|
+
self.assertIsNone(self.app.db.one("SELECT id FROM task_messages WHERE id='terminal-confirm-message'"))
|
|
1664
|
+
finally:
|
|
1665
|
+
self.app.clear_external_turns(task_id)
|
|
1666
|
+
self.app.db.execute("DELETE FROM tasks WHERE id=?", (task_id,))
|
|
1667
|
+
|
|
1668
|
+
def test_confirmed_terminal_takeover_stops_terminal_then_queues_message(self):
|
|
1669
|
+
task_id = "terminal-takeover-thread"
|
|
1670
|
+
self.make_task(task_id, "running")
|
|
1671
|
+
self.app.register_external_turn(task_id, {"turn_id": "terminal-turn", "path": "/tmp/terminal-turn.jsonl"})
|
|
1672
|
+
try:
|
|
1673
|
+
with mock.patch.object(self.app, "refresh_live_external_turn", new=mock.AsyncMock(return_value=False)), \
|
|
1674
|
+
mock.patch.object(self.app, "stop_terminal_for_web_takeover", new=mock.AsyncMock(return_value=True)) as stop, \
|
|
1675
|
+
mock.patch.object(self.app, "schedule_task_drain") as drain:
|
|
1676
|
+
result = asyncio.run(self.app.post_task_message(
|
|
1677
|
+
task_id,
|
|
1678
|
+
self.app.TaskMessageIn(
|
|
1679
|
+
message="take over",
|
|
1680
|
+
client_message_id="terminal-takeover-message",
|
|
1681
|
+
takeover_terminal=True,
|
|
1682
|
+
),
|
|
1683
|
+
None,
|
|
1684
|
+
))
|
|
1685
|
+
stop.assert_awaited_once_with(task_id)
|
|
1686
|
+
drain.assert_called_once_with(task_id)
|
|
1687
|
+
self.assertEqual("queued", result["status"])
|
|
1688
|
+
finally:
|
|
1689
|
+
self.app.clear_external_turns(task_id)
|
|
1690
|
+
self.app.db.execute("DELETE FROM task_messages WHERE task_id=?", (task_id,))
|
|
1691
|
+
self.app.db.execute("DELETE FROM tasks WHERE id=?", (task_id,))
|
|
1692
|
+
|
|
1693
|
+
def test_terminal_takeover_clears_external_owner_without_auto_resume_race(self):
|
|
1694
|
+
task_id = "terminal-takeover-stop-thread"
|
|
1695
|
+
session_id = "external-terminal-takeover-session"
|
|
1696
|
+
self.make_task(task_id, "running")
|
|
1697
|
+
self.app.db.execute(
|
|
1698
|
+
"INSERT INTO sessions (id,task_id,status,attempt,command,started_at,codex_session_id) VALUES (?,?,?,?,?,?,?)",
|
|
1699
|
+
(session_id, task_id, "running", 1, "codex resume", self.app.now(), task_id),
|
|
1700
|
+
)
|
|
1701
|
+
self.app.register_external_turn(task_id, {
|
|
1702
|
+
"turn_id": "terminal-turn",
|
|
1703
|
+
"session_id": session_id,
|
|
1704
|
+
"path": "/tmp/terminal-takeover.jsonl",
|
|
1705
|
+
"started_at": self.app.now(),
|
|
1706
|
+
})
|
|
1707
|
+
try:
|
|
1708
|
+
with mock.patch.object(self.app, "refresh_live_external_turn", new=mock.AsyncMock(return_value=False)), \
|
|
1709
|
+
mock.patch.object(self.app, "interrupt_browser_terminal", new=mock.AsyncMock()) as browser_interrupt, \
|
|
1710
|
+
mock.patch.object(self.app, "interrupt_rollout_writers", return_value={900}) as writer_interrupt, \
|
|
1711
|
+
mock.patch.object(self.app, "schedule_task_drain") as drain:
|
|
1712
|
+
stopped = asyncio.run(self.app.stop_terminal_for_web_takeover(task_id))
|
|
1713
|
+
self.assertTrue(stopped)
|
|
1714
|
+
browser_interrupt.assert_awaited_once_with(task_id)
|
|
1715
|
+
writer_interrupt.assert_called_once_with("/tmp/terminal-takeover.jsonl")
|
|
1716
|
+
drain.assert_not_called()
|
|
1717
|
+
self.assertNotIn(task_id, self.app.external_turns)
|
|
1718
|
+
self.assertEqual("stopped", self.app.task_or_404(task_id)["status"])
|
|
1719
|
+
self.assertEqual("stopped", self.app.db.one("SELECT status FROM sessions WHERE id=?", (session_id,))["status"])
|
|
1720
|
+
event = self.app.db.one("SELECT payload FROM events WHERE session_id=? ORDER BY id DESC LIMIT 1", (session_id,))
|
|
1721
|
+
self.assertEqual("turn_aborted", json.loads(event["payload"])["type"])
|
|
1722
|
+
finally:
|
|
1723
|
+
self.app.clear_external_turns(task_id)
|
|
1724
|
+
self.app.db.execute("DELETE FROM tasks WHERE id=?", (task_id,))
|
|
1725
|
+
|
|
1649
1726
|
def test_binary_dump_is_rejected_before_queueing(self):
|
|
1650
1727
|
task_id = "binary-dump-thread"
|
|
1651
1728
|
self.make_task(task_id, "available")
|
|
@@ -2885,7 +2962,7 @@ process.stdout.write(JSON.stringify(browserMessages));
|
|
|
2885
2962
|
self.assertNotIn('id="stop-generation"', html)
|
|
2886
2963
|
self.assertIn('$("#composer-model-select")', conversation_js)
|
|
2887
2964
|
self.assertIn('当前 API ·', (static / "core.js").read_text(encoding="utf-8"))
|
|
2888
|
-
self.assertIn('const delivery = mode === "queue" || activeTurn ? "queue" : "auto"', app_js)
|
|
2965
|
+
self.assertIn('const delivery = mode === "queue" || (activeTurn && !takeoverTerminal) ? "queue" : "auto"', app_js)
|
|
2889
2966
|
self.assertIn('function toggleGoalRun()', app_js)
|
|
2890
2967
|
self.assertIn('id="composer-model-select"', html)
|
|
2891
2968
|
self.assertIn('id="composer-effort-select"', html)
|
|
@@ -3104,15 +3181,17 @@ process.stdout.write(JSON.stringify(browserMessages));
|
|
|
3104
3181
|
static = Path(__file__).resolve().parents[1] / "static"
|
|
3105
3182
|
app_js = (static / "app.js").read_text(encoding="utf-8")
|
|
3106
3183
|
conversation_js = (static / "conversation.js").read_text(encoding="utf-8")
|
|
3184
|
+
core_js = (static / "core.js").read_text(encoding="utf-8")
|
|
3107
3185
|
self.assertIn("button.dataset.queueDispatch", app_js)
|
|
3108
3186
|
self.assertIn('button.dataset.dispatching === "1"', app_js)
|
|
3109
3187
|
self.assertIn("result.dispatch_error", app_js)
|
|
3110
|
-
self.assertIn('const delivery = mode === "queue" || activeTurn ? "queue" : "auto"', app_js)
|
|
3188
|
+
self.assertIn('const delivery = mode === "queue" || (activeTurn && !takeoverTerminal) ? "queue" : "auto"', app_js)
|
|
3111
3189
|
self.assertIn('toast(uiLabel("queuedAfterTurn"))', app_js)
|
|
3112
|
-
self.
|
|
3190
|
+
self.assertIn('uiLabel("terminalTakeoverConfirm")', app_js)
|
|
3191
|
+
self.assertIn('takeover_terminal: true', app_js)
|
|
3192
|
+
self.assertIn('error.code = detail.code', core_js)
|
|
3113
3193
|
self.assertIn("data-queue-dispatch", conversation_js)
|
|
3114
3194
|
self.assertIn("queued-error", conversation_js)
|
|
3115
|
-
core_js = (static / "core.js").read_text(encoding="utf-8")
|
|
3116
3195
|
self.assertIn("function taskIsRunningGroup", core_js)
|
|
3117
3196
|
self.assertIn('["running", "retrying", "queued"].includes', core_js)
|
|
3118
3197
|
self.assertIn("taskSortName(a).localeCompare", core_js)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|