codex-partner 0.0.3__tar.gz → 0.0.4__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.3 → codex_partner-0.0.4}/PKG-INFO +3 -3
- {codex_partner-0.0.3 → codex_partner-0.0.4}/README.md +2 -2
- {codex_partner-0.0.3 → codex_partner-0.0.4}/app.py +343 -37
- {codex_partner-0.0.3 → codex_partner-0.0.4}/codex_partner/__init__.py +1 -1
- codex_partner-0.0.4/codex_partner/activity_graph.py +472 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/codex_partner/database.py +32 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/codex_partner.egg-info/PKG-INFO +3 -3
- {codex_partner-0.0.3 → codex_partner-0.0.4}/codex_partner.egg-info/SOURCES.txt +71 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/pyproject.toml +1 -1
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/app.js +57 -11
- codex_partner-0.0.4/static/chat-worker.js +512 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/conversation.js +197 -42
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/core.js +76 -8
- codex_partner-0.0.4/static/default-user-avatar.webp +0 -0
- codex_partner-0.0.4/static/exploration-tree.js +391 -0
- codex_partner-0.0.4/static/history-pagination.js +58 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/index.html +10 -6
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/settings.js +1 -1
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/styles.css +140 -11
- codex_partner-0.0.4/static/vendor/katex/LICENSE +21 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_AMS-Regular.ttf +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_AMS-Regular.woff +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Main-Bold.ttf +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Main-Bold.woff +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Main-Italic.ttf +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Main-Italic.woff +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Main-Regular.ttf +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Main-Regular.woff +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Math-Italic.ttf +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Math-Italic.woff +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Script-Regular.ttf +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Script-Regular.woff +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Size1-Regular.ttf +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Size1-Regular.woff +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Size2-Regular.ttf +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Size2-Regular.woff +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Size3-Regular.ttf +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Size3-Regular.woff +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Size4-Regular.ttf +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Size4-Regular.woff +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff +0 -0
- codex_partner-0.0.4/static/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- codex_partner-0.0.4/static/vendor/katex/katex.min.css +1 -0
- codex_partner-0.0.4/static/vendor/katex/katex.min.js +1 -0
- codex_partner-0.0.4/static/vendor/markdown-it/LICENSE +22 -0
- codex_partner-0.0.4/static/vendor/markdown-it/markdown-it.min.js +14 -0
- codex_partner-0.0.4/static/vendor/markdown-it-texmath/LICENSE +21 -0
- codex_partner-0.0.4/static/vendor/markdown-it-texmath/texmath.js +368 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/tests/test_running_state.py +838 -17
- codex_partner-0.0.3/static/chat-worker.js +0 -244
- {codex_partner-0.0.3 → codex_partner-0.0.4}/codex_partner/__main__.py +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/codex_partner/app_server.py +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/codex_partner/cli.py +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/codex_partner/commands.py +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/codex_partner/discovery.py +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/codex_partner/schemas.py +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/codex_partner/ssh_auth.py +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/codex_partner.egg-info/dependency_links.txt +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/codex_partner.egg-info/entry_points.txt +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/codex_partner.egg-info/requires.txt +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/codex_partner.egg-info/top_level.txt +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/setup.cfg +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/__init__.py +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/editor.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/mascot-dance.css +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/mascot-dance.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/LICENSE +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/ace.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/ext-modelist.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-abap.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-abc.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-actionscript.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-ada.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-alda.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-apache_conf.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-apex.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-applescript.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-aql.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-asciidoc.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-asl.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-assembly_arm32.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-assembly_x86.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-astro.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-autohotkey.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-basic.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-batchfile.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-bibtex.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-c9search.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-c_cpp.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-cedar.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-cedarschema.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-cirru.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-clojure.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-clue.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-cobol.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-coffee.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-coldfusion.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-crystal.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-csharp.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-csound_document.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-csound_orchestra.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-csound_score.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-csp.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-css.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-csv.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-curly.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-cuttlefish.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-d.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-dart.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-diff.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-django.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-dockerfile.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-dot.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-drools.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-edifact.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-eiffel.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-ejs.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-elixir.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-elm.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-erlang.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-flix.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-forth.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-fortran.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-fsharp.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-fsl.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-ftl.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-gcode.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-gherkin.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-gitignore.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-glsl.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-gobstones.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-golang.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-graphqlschema.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-groovy.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-groq.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-haml.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-handlebars.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-haskell.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-haskell_cabal.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-haxe.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-hjson.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-html.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-html_elixir.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-html_ruby.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-ini.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-io.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-ion.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-jack.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-jade.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-java.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-javascript.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-jexl.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-json.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-json5.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-jsoniq.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-jsp.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-jssm.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-jsx.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-julia.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-kotlin.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-latex.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-latte.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-less.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-liquid.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-lisp.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-livescript.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-logiql.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-logtalk.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-lsl.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-lua.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-luapage.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-lucene.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-makefile.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-mariadb.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-markdown.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-mask.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-matlab.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-maze.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-mediawiki.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-mel.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-mips.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-mixal.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-mushcode.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-mysql.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-nasal.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-nginx.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-nim.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-nix.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-nsis.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-nunjucks.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-objectivec.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-ocaml.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-odin.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-partiql.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-pascal.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-perl.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-pgsql.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-php.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-php_laravel_blade.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-pig.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-plain_text.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-plsql.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-powershell.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-praat.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-prisma.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-prolog.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-properties.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-protobuf.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-prql.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-puppet.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-python.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-qml.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-r.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-raku.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-razor.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-rdoc.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-red.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-redshift.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-rhtml.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-robot.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-rst.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-ruby.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-rust.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-sac.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-sass.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-scad.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-scala.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-scheme.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-scrypt.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-scss.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-sh.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-sjs.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-slim.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-smarty.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-smithy.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-snippets.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-soy_template.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-space.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-sparql.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-sql.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-sqlserver.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-stylus.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-svg.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-swift.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-tcl.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-terraform.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-tex.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-text.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-textile.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-toml.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-tsv.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-tsx.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-turtle.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-twig.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-typescript.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-vala.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-vbscript.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-velocity.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-verilog.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-vhdl.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-visualforce.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-vue.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-wollok.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-xml.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-xquery.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-yaml.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-zeek.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/mode-zig.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/ace/theme-tomorrow_night_eighties.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/i18next/LICENSE +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/i18next/i18next.min.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/toastui-editor/LICENSE +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/toastui-editor/toastui-editor-dark.css +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/toastui-editor/toastui-editor.css +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/toastui-editor/toastui-editor.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/toastui-editor/zh-cn.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/xterm/LICENSE +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/xterm/addons/fit.LICENSE +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/xterm/addons/fit.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/xterm/addons/unicode11.LICENSE +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/xterm/addons/unicode11.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/xterm/addons/web-links.LICENSE +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/xterm/addons/web-links.js +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/static/vendor/xterm/xterm.css +0 -0
- {codex_partner-0.0.3 → codex_partner-0.0.4}/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.4
|
|
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
|
|
@@ -74,7 +74,7 @@ For access from other devices, set `CODEX_DASHBOARD_HOST=0.0.0.0`. Codex Partner
|
|
|
74
74
|
## Docker
|
|
75
75
|
|
|
76
76
|
```bash
|
|
77
|
-
docker build -t codex-partner:0.0.
|
|
77
|
+
docker build -t codex-partner:0.0.4 .
|
|
78
78
|
docker run -d --name codex-partner \
|
|
79
79
|
-p 8787:8787 \
|
|
80
80
|
--add-host=host.docker.internal:host-gateway \
|
|
@@ -83,7 +83,7 @@ docker run -d --name codex-partner \
|
|
|
83
83
|
-v codex-partner-data:/var/lib/codex-partner \
|
|
84
84
|
-v codex-home:/home/codex/.codex \
|
|
85
85
|
-v codex-workspace:/workspace \
|
|
86
|
-
codex-partner:0.0.
|
|
86
|
+
codex-partner:0.0.4
|
|
87
87
|
docker exec -it codex-partner codex login
|
|
88
88
|
```
|
|
89
89
|
|
|
@@ -49,7 +49,7 @@ For access from other devices, set `CODEX_DASHBOARD_HOST=0.0.0.0`. Codex Partner
|
|
|
49
49
|
## Docker
|
|
50
50
|
|
|
51
51
|
```bash
|
|
52
|
-
docker build -t codex-partner:0.0.
|
|
52
|
+
docker build -t codex-partner:0.0.4 .
|
|
53
53
|
docker run -d --name codex-partner \
|
|
54
54
|
-p 8787:8787 \
|
|
55
55
|
--add-host=host.docker.internal:host-gateway \
|
|
@@ -58,7 +58,7 @@ docker run -d --name codex-partner \
|
|
|
58
58
|
-v codex-partner-data:/var/lib/codex-partner \
|
|
59
59
|
-v codex-home:/home/codex/.codex \
|
|
60
60
|
-v codex-workspace:/workspace \
|
|
61
|
-
codex-partner:0.0.
|
|
61
|
+
codex-partner:0.0.4
|
|
62
62
|
docker exec -it codex-partner codex login
|
|
63
63
|
```
|
|
64
64
|
|
|
@@ -47,6 +47,7 @@ import pexpect
|
|
|
47
47
|
from PIL import Image, ImageOps, ImageSequence, UnidentifiedImageError
|
|
48
48
|
|
|
49
49
|
from codex_partner import APP_NAME, APP_VERSION
|
|
50
|
+
from codex_partner.activity_graph import ActivityGraphStore, PROJECTION_VERSION, project_events, semantic_event
|
|
50
51
|
from codex_partner.commands import SLASH_ALIASES, SLASH_COMMAND_BY_NAME, SLASH_COMMANDS, parse_slash_command
|
|
51
52
|
from codex_partner.database import Database
|
|
52
53
|
from codex_partner.app_server import AppServerClient
|
|
@@ -375,6 +376,9 @@ def canonicalize_task_thread_id(task_id: str, thread_id: str) -> str:
|
|
|
375
376
|
connection.execute("UPDATE sessions SET task_id=? WHERE task_id=?", (thread_id, task_id))
|
|
376
377
|
connection.execute("UPDATE events SET task_id=? WHERE task_id=?", (thread_id, task_id))
|
|
377
378
|
connection.execute("UPDATE task_messages SET task_id=? WHERE task_id=?", (thread_id, task_id))
|
|
379
|
+
connection.execute("UPDATE activity_graphs SET task_id=? WHERE task_id=?", (thread_id, task_id))
|
|
380
|
+
connection.execute("UPDATE activity_nodes SET task_id=? WHERE task_id=?", (thread_id, task_id))
|
|
381
|
+
connection.execute("UPDATE activity_graph_seen SET task_id=? WHERE task_id=?", (thread_id, task_id))
|
|
378
382
|
connection.commit()
|
|
379
383
|
task_id_aliases[task_id] = thread_id
|
|
380
384
|
if task_id in running:
|
|
@@ -433,6 +437,7 @@ def is_provider_failure(exc: Exception) -> bool:
|
|
|
433
437
|
|
|
434
438
|
|
|
435
439
|
db = Database(DB_PATH, now)
|
|
440
|
+
activity_graph_store = ActivityGraphStore(db)
|
|
436
441
|
|
|
437
442
|
|
|
438
443
|
def reconcile_task_session_ids() -> None:
|
|
@@ -510,6 +515,7 @@ async def lifespan(_: FastAPI):
|
|
|
510
515
|
app_shutting_down = False
|
|
511
516
|
observer_task: Optional[asyncio.Task] = None
|
|
512
517
|
inactive_trash_task: Optional[asyncio.Task] = None
|
|
518
|
+
activity_graph_task: Optional[asyncio.Task] = None
|
|
513
519
|
reconcile_task_session_ids()
|
|
514
520
|
try:
|
|
515
521
|
await sync_native_threads()
|
|
@@ -531,6 +537,8 @@ async def lifespan(_: FastAPI):
|
|
|
531
537
|
except Exception:
|
|
532
538
|
# Retention cleanup is best effort and must never block the dashboard.
|
|
533
539
|
pass
|
|
540
|
+
seed_activity_graph_builds()
|
|
541
|
+
activity_graph_task = asyncio.create_task(activity_graph_worker_loop())
|
|
534
542
|
if AUTO_RESUME:
|
|
535
543
|
await asyncio.sleep(0)
|
|
536
544
|
for row in db.all("SELECT id FROM tasks WHERE status='queued' ORDER BY updated_at"):
|
|
@@ -565,6 +573,9 @@ async def lifespan(_: FastAPI):
|
|
|
565
573
|
if inactive_trash_task:
|
|
566
574
|
inactive_trash_task.cancel()
|
|
567
575
|
await asyncio.gather(inactive_trash_task, return_exceptions=True)
|
|
576
|
+
if activity_graph_task:
|
|
577
|
+
activity_graph_task.cancel()
|
|
578
|
+
await asyncio.gather(activity_graph_task, return_exceptions=True)
|
|
568
579
|
workers = list(task_workers.values())
|
|
569
580
|
for worker in workers:
|
|
570
581
|
worker.cancel()
|
|
@@ -588,6 +599,10 @@ overview_client_users: dict[WebSocket, str] = {}
|
|
|
588
599
|
task_queues: dict[str, asyncio.Queue[str]] = {}
|
|
589
600
|
task_workers: dict[str, asyncio.Task] = {}
|
|
590
601
|
task_message_locks: dict[str, asyncio.Lock] = {}
|
|
602
|
+
activity_graph_queue: asyncio.Queue[str] = asyncio.Queue()
|
|
603
|
+
activity_graph_pending: set[str] = set()
|
|
604
|
+
activity_graph_locks: dict[str, tuple[asyncio.AbstractEventLoop, asyncio.Lock]] = {}
|
|
605
|
+
activity_graph_backlog: dict[str, list[dict[str, Any]]] = {}
|
|
591
606
|
app_servers: dict[str, "AppServerClient"] = {}
|
|
592
607
|
app_thread_bindings: dict[str, tuple[str, str, str]] = {}
|
|
593
608
|
turn_waiters: dict[str, asyncio.Future] = {}
|
|
@@ -616,7 +631,23 @@ native_import_attempt_at = 0.0
|
|
|
616
631
|
def appserver_key(provider: Optional[dict], task: Optional[dict] = None) -> str:
|
|
617
632
|
provider_key = (provider or {}).get("id", "default")
|
|
618
633
|
host = (task or {}).get("ssh_host") or ""
|
|
619
|
-
|
|
634
|
+
base = f"ssh:{host}:{provider_key}" if host else provider_key
|
|
635
|
+
return f"{base}:task:{task['id']}" if task and task.get("id") else base
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
async def close_task_appserver(provider: Optional[dict], task: Optional[dict], client: Optional[AppServerClient]) -> None:
|
|
639
|
+
if not task or not client:
|
|
640
|
+
return
|
|
641
|
+
async with appserver_lock:
|
|
642
|
+
keys = [key for key, candidate in app_servers.items() if candidate is client]
|
|
643
|
+
if not keys:
|
|
644
|
+
return
|
|
645
|
+
for key in keys:
|
|
646
|
+
app_servers.pop(key, None)
|
|
647
|
+
for task_id, binding in list(app_thread_bindings.items()):
|
|
648
|
+
if binding and binding[0] in keys:
|
|
649
|
+
app_thread_bindings.pop(task_id, None)
|
|
650
|
+
await client.close()
|
|
620
651
|
|
|
621
652
|
|
|
622
653
|
async def appserver_for(provider: Optional[dict], task: Optional[dict] = None) -> AppServerClient:
|
|
@@ -785,6 +816,21 @@ def native_rollout_rows() -> list[dict[str, str]]:
|
|
|
785
816
|
return [{"thread_id": str(row[0]), "path": str(row[1])} for row in rows]
|
|
786
817
|
|
|
787
818
|
|
|
819
|
+
def native_rollout_path(thread_id: str) -> str:
|
|
820
|
+
"""Return one thread's rollout path without scanning every native thread."""
|
|
821
|
+
thread_id = safe_thread_id(thread_id)
|
|
822
|
+
path = CODEX_HOME / "state_5.sqlite"
|
|
823
|
+
if not thread_id or not path.is_file():
|
|
824
|
+
return ""
|
|
825
|
+
try:
|
|
826
|
+
conn = sqlite3.connect(f"file:{path}?mode=ro", uri=True)
|
|
827
|
+
row = conn.execute("SELECT rollout_path FROM threads WHERE id=?", (thread_id,)).fetchone()
|
|
828
|
+
conn.close()
|
|
829
|
+
except sqlite3.Error:
|
|
830
|
+
return ""
|
|
831
|
+
return str(row[0]) if row and row[0] else ""
|
|
832
|
+
|
|
833
|
+
|
|
788
834
|
def rollout_record_phase(record: dict) -> str:
|
|
789
835
|
record_type = str(record.get("type") or "")
|
|
790
836
|
payload = record.get("payload") or {}
|
|
@@ -1139,13 +1185,35 @@ def rollout_writer_pids(path: str, refresh: bool = False) -> set[int]:
|
|
|
1139
1185
|
return set(writers)
|
|
1140
1186
|
|
|
1141
1187
|
|
|
1188
|
+
def process_tree_pids(roots: set[int]) -> set[int]:
|
|
1189
|
+
"""Return roots and their Linux descendants, tolerating process exits."""
|
|
1190
|
+
found = {int(pid) for pid in roots if pid}
|
|
1191
|
+
pending = list(found)
|
|
1192
|
+
while pending:
|
|
1193
|
+
pid = pending.pop()
|
|
1194
|
+
try:
|
|
1195
|
+
children = Path(f"/proc/{pid}/task/{pid}/children").read_text(encoding="ascii").split()
|
|
1196
|
+
except OSError:
|
|
1197
|
+
continue
|
|
1198
|
+
for value in children:
|
|
1199
|
+
try:
|
|
1200
|
+
child = int(value)
|
|
1201
|
+
except ValueError:
|
|
1202
|
+
continue
|
|
1203
|
+
if child not in found:
|
|
1204
|
+
found.add(child)
|
|
1205
|
+
pending.append(child)
|
|
1206
|
+
return found
|
|
1207
|
+
|
|
1208
|
+
|
|
1142
1209
|
def native_rollout_writer_pids(path: str, refresh: bool = False) -> set[int]:
|
|
1143
1210
|
"""Return writers outside this dashboard's app-server children."""
|
|
1144
|
-
|
|
1211
|
+
dashboard_roots = {
|
|
1145
1212
|
client.process.pid
|
|
1146
1213
|
for client in app_servers.values()
|
|
1147
1214
|
if client.process and client.process.returncode is None
|
|
1148
1215
|
}
|
|
1216
|
+
dashboard_pids = process_tree_pids(dashboard_roots)
|
|
1149
1217
|
return rollout_writer_pids(path, refresh=refresh) - dashboard_pids - {os.getpid()}
|
|
1150
1218
|
|
|
1151
1219
|
|
|
@@ -1336,6 +1404,12 @@ async def apply_external_turn_boundary(
|
|
|
1336
1404
|
if boundary == "task_started":
|
|
1337
1405
|
stamp = rollout_stamp(record, "started_at")
|
|
1338
1406
|
task = task_or_404(task_id)
|
|
1407
|
+
tracked = (external_turn_sets.get(task_id) or {}).get(turn_id) if turn_id else None
|
|
1408
|
+
if tracked:
|
|
1409
|
+
if path:
|
|
1410
|
+
tracked["path"] = path
|
|
1411
|
+
persist_external_task_status(task_id)
|
|
1412
|
+
return
|
|
1339
1413
|
dashboard_turn_id = str(appserver_turn_ids.get(task_id) or "")
|
|
1340
1414
|
persisted_dashboard_turn = task.get("execution_source") == "dashboard" and str(task.get("execution_turn_id") or "") == turn_id
|
|
1341
1415
|
persisted_dashboard_turn = persisted_dashboard_turn or has_persisted_dashboard_turn(task_id, turn_id)
|
|
@@ -1475,9 +1549,11 @@ async def process_native_rollout_record(task_id: str, thread_id: str, record: di
|
|
|
1475
1549
|
return
|
|
1476
1550
|
if record.get("type") == "event_msg" and payload_type == "thread_goal_updated":
|
|
1477
1551
|
goal = record_payload.get("goal") or {}
|
|
1552
|
+
stamp = rollout_stamp(record)
|
|
1478
1553
|
db.execute(
|
|
1479
|
-
"UPDATE tasks SET goal=?,goal_status=?,goal_tokens_used=?,updated_at=?
|
|
1480
|
-
|
|
1554
|
+
"UPDATE tasks SET goal=?,goal_status=?,goal_tokens_used=?,goal_revision=goal_revision+1,goal_updated_at=?,updated_at=? "
|
|
1555
|
+
"WHERE id=? AND COALESCE(goal_updated_at,'')<=?",
|
|
1556
|
+
(goal.get("objective", ""), goal.get("status", "active"), int(goal.get("tokensUsed", 0) or 0), stamp, stamp, task_id, stamp),
|
|
1481
1557
|
)
|
|
1482
1558
|
await broadcast_task(task_id, {"type": "task_status", "task": task_or_404(task_id), "source": {"kind": "external_goal"}})
|
|
1483
1559
|
return
|
|
@@ -1582,6 +1658,29 @@ async def refresh_native_rollouts() -> None:
|
|
|
1582
1658
|
rollout_writer_cache.pop(os.path.realpath(path), None)
|
|
1583
1659
|
|
|
1584
1660
|
|
|
1661
|
+
async def refresh_live_external_turn(task_id: str) -> bool:
|
|
1662
|
+
"""Synchronously close the rollout-observer gap before starting a web turn."""
|
|
1663
|
+
if external_turns.get(task_id):
|
|
1664
|
+
return True
|
|
1665
|
+
task = task_or_404(task_id)
|
|
1666
|
+
if task.get("ssh_host"):
|
|
1667
|
+
return False
|
|
1668
|
+
thread_id = latest_codex_session(task)
|
|
1669
|
+
if not thread_id:
|
|
1670
|
+
return False
|
|
1671
|
+
path = await asyncio.to_thread(native_rollout_path, thread_id)
|
|
1672
|
+
if not path:
|
|
1673
|
+
return False
|
|
1674
|
+
_offset, boundary, phase = await asyncio.to_thread(inspect_rollout_boundary, path)
|
|
1675
|
+
if not boundary or str((boundary.get("payload") or {}).get("type") or "") != "task_started":
|
|
1676
|
+
return False
|
|
1677
|
+
writers = await asyncio.to_thread(native_rollout_writer_pids, path, True)
|
|
1678
|
+
if not writers:
|
|
1679
|
+
return False
|
|
1680
|
+
await apply_external_turn_boundary(task_id, thread_id, boundary, phase, initial=True, path=path)
|
|
1681
|
+
return bool(external_turns.get(task_id))
|
|
1682
|
+
|
|
1683
|
+
|
|
1585
1684
|
async def native_rollout_watch_loop() -> None:
|
|
1586
1685
|
while True:
|
|
1587
1686
|
try:
|
|
@@ -2324,7 +2423,11 @@ async def sync_native_threads() -> dict:
|
|
|
2324
2423
|
archived = bool(thread.get("archived") or native_settings.get("archived"))
|
|
2325
2424
|
memory_mode = str(native_settings.get("memory_mode") or "enabled")
|
|
2326
2425
|
provider_id = providers.get(model_provider)
|
|
2327
|
-
existing = db.one(
|
|
2426
|
+
existing = db.one(
|
|
2427
|
+
"SELECT id,name,name_revision,goal,goal_status,goal_tokens_used,goal_revision,status,"
|
|
2428
|
+
"retry_forever,retry_explicit,provider_id,model FROM tasks WHERE codex_session_id=?",
|
|
2429
|
+
(thread_id,),
|
|
2430
|
+
)
|
|
2328
2431
|
if existing:
|
|
2329
2432
|
# Imported native threads may be edited from the dashboard. Do
|
|
2330
2433
|
# not let the periodic native index sync erase those dashboard
|
|
@@ -2337,11 +2440,16 @@ async def sync_native_threads() -> dict:
|
|
|
2337
2440
|
existing_full = db.one("SELECT trashed FROM tasks WHERE id=?", (existing["id"],)) or {}
|
|
2338
2441
|
status = "trashed" if existing_full.get("trashed") else (existing["status"] if existing["status"] in {"running", "retrying", "queued", "stopped"} else ("archived" if archived else "available"))
|
|
2339
2442
|
retry_forever = int(existing.get("retry_forever", 0)) if existing.get("retry_explicit") else 0
|
|
2443
|
+
if int(existing.get("goal_revision") or 0) > 0:
|
|
2444
|
+
objective = existing.get("goal", "")
|
|
2445
|
+
goal_status = existing.get("goal_status", "none")
|
|
2446
|
+
goal["tokensUsed"] = existing.get("goal_tokens_used", 0)
|
|
2447
|
+
synced_title = existing.get("name") if int(existing.get("name_revision") or 0) > 0 else title[:160]
|
|
2340
2448
|
db.execute(
|
|
2341
2449
|
"UPDATE tasks SET name=?,prompt=?,goal=?,workspace=?,model=?,provider_id=?,goal_status=?,goal_tokens_used=?,"
|
|
2342
2450
|
"retry_forever=?,yolo=?,native=1,archived=?,memory_mode=?,status=?,updated_at=?,"
|
|
2343
2451
|
"last_interaction_at=CASE WHEN COALESCE(last_interaction_at,'')<? THEN ? ELSE last_interaction_at END WHERE id=?",
|
|
2344
|
-
(
|
|
2452
|
+
(synced_title, prompt, objective, workspace, model, provider_id, goal_status, int(goal.get("tokensUsed", 0) or 0),
|
|
2345
2453
|
retry_forever, int(yolo), int(archived), memory_mode, status, updated_at, updated_at, updated_at, existing["id"]),
|
|
2346
2454
|
)
|
|
2347
2455
|
persist_native_thread_model(thread_id, model)
|
|
@@ -2521,11 +2629,27 @@ async def handle_appserver_notification(server_key: str, message: dict) -> None:
|
|
|
2521
2629
|
goal = params.get("goal") or {}
|
|
2522
2630
|
payload = {"type": "goal_updated", "goal": goal, "thread_id": thread_id}
|
|
2523
2631
|
objective = goal.get("objective")
|
|
2632
|
+
current_goal = task_or_404(task_id)
|
|
2633
|
+
stale_objective = (
|
|
2634
|
+
objective is not None
|
|
2635
|
+
and int(current_goal.get("goal_revision") or 0) > 0
|
|
2636
|
+
and str(objective or "") != str(current_goal.get("goal") or "")
|
|
2637
|
+
)
|
|
2638
|
+
if stale_objective:
|
|
2639
|
+
# A running turn may report the objective it started with after a
|
|
2640
|
+
# dashboard edit. Keep the newer durable revision authoritative.
|
|
2641
|
+
payload["ignored_stale"] = True
|
|
2642
|
+
objective = None
|
|
2643
|
+
stamp = now()
|
|
2524
2644
|
fields = ["goal_status=?", "goal_tokens_used=?", "updated_at=?"]
|
|
2525
|
-
values: list[Any] = [
|
|
2645
|
+
values: list[Any] = [
|
|
2646
|
+
current_goal.get("goal_status", "active") if stale_objective else goal.get("status", "active"),
|
|
2647
|
+
current_goal.get("goal_tokens_used", 0) if stale_objective else goal.get("tokensUsed", 0),
|
|
2648
|
+
stamp,
|
|
2649
|
+
]
|
|
2526
2650
|
if objective is not None:
|
|
2527
|
-
fields
|
|
2528
|
-
values
|
|
2651
|
+
fields[0:0] = ["goal=?", "goal_updated_at=?"]
|
|
2652
|
+
values[0:0] = [str(objective or ""), stamp]
|
|
2529
2653
|
values.append(task_id)
|
|
2530
2654
|
db.execute(
|
|
2531
2655
|
f"UPDATE tasks SET {','.join(fields)} WHERE id=?",
|
|
@@ -2936,6 +3060,11 @@ async def supervise_appserver_turn(
|
|
|
2936
3060
|
await broadcast_task(task["id"], {"type": "message", "message_id": message_id, "status": "failed", "error": "Stopped by user" if stopped else error, "session_id": session_id})
|
|
2937
3061
|
await broadcast_task(task["id"], {"type": "session", "session_id": session_id, "status": "stopped" if stopped else "failed", "error": error})
|
|
2938
3062
|
finally:
|
|
3063
|
+
if thread_id:
|
|
3064
|
+
try:
|
|
3065
|
+
await asyncio.wait_for(client.request("thread/unsubscribe", {"threadId": thread_id}), timeout=3)
|
|
3066
|
+
except Exception:
|
|
3067
|
+
pass
|
|
2939
3068
|
if thread_id and turn_waiters.get(thread_id) is waiter:
|
|
2940
3069
|
turn_waiters.pop(thread_id, None)
|
|
2941
3070
|
if turn_id and appserver_turn_ids.get(task["id"]) == turn_id:
|
|
@@ -2951,6 +3080,7 @@ async def supervise_appserver_turn(
|
|
|
2951
3080
|
# normal success path. The worker will wait if another owner is still
|
|
2952
3081
|
# active and dispatch queued rows as soon as this turn is idle.
|
|
2953
3082
|
schedule_task_drain(task["id"])
|
|
3083
|
+
await close_task_appserver(provider, task, client)
|
|
2954
3084
|
await drain_task_messages(task["id"])
|
|
2955
3085
|
|
|
2956
3086
|
|
|
@@ -3703,6 +3833,7 @@ async def broadcast_profile(username: str, profile: Optional[dict] = None) -> No
|
|
|
3703
3833
|
async def broadcast_task(task_id: str, payload: dict) -> None:
|
|
3704
3834
|
"""Fan out one canonical task event to every browser attached to the task."""
|
|
3705
3835
|
overview_source = payload
|
|
3836
|
+
schedule_activity_graph_event(task_id, overview_source)
|
|
3706
3837
|
if payload.get("type") == "task_status":
|
|
3707
3838
|
task = payload.get("task") or {}
|
|
3708
3839
|
mutable_fields = {
|
|
@@ -4573,10 +4704,15 @@ async def patch_task(task_id: str, payload: TaskPatch, _: Any = Depends(auth)):
|
|
|
4573
4704
|
continue
|
|
4574
4705
|
sets.append(f"{key}=?")
|
|
4575
4706
|
args.append(int(value) if key in {"yolo", "retry_forever"} else value)
|
|
4707
|
+
stamp = now()
|
|
4576
4708
|
if "retry_forever" in values:
|
|
4577
4709
|
sets.append("retry_explicit=?")
|
|
4578
4710
|
args.append(1)
|
|
4579
|
-
|
|
4711
|
+
if "goal" in values:
|
|
4712
|
+
sets.append("goal_revision=goal_revision+1")
|
|
4713
|
+
sets.append("goal_updated_at=?")
|
|
4714
|
+
args.append(stamp)
|
|
4715
|
+
sets.append("updated_at=?"); args.append(stamp); args.append(task_id)
|
|
4580
4716
|
db.execute(f"UPDATE tasks SET {','.join(sets)} WHERE id=?", tuple(args))
|
|
4581
4717
|
result = task_or_404(task_id)
|
|
4582
4718
|
if "model" in values:
|
|
@@ -4634,7 +4770,10 @@ async def patch_goal(task_id: str, payload: GoalPatch, _: Any = Depends(auth)):
|
|
|
4634
4770
|
updates["goal_status"] = remote_goal.get("status") or payload.status
|
|
4635
4771
|
if updates:
|
|
4636
4772
|
sets = ",".join(f"{key}=?" for key in updates)
|
|
4637
|
-
|
|
4773
|
+
stamp = now()
|
|
4774
|
+
revision_update = ",goal_revision=goal_revision+1,goal_updated_at=?" if payload.objective is not None else ""
|
|
4775
|
+
revision_args = (stamp,) if payload.objective is not None else ()
|
|
4776
|
+
db.execute(f"UPDATE tasks SET {sets}{revision_update}, updated_at=? WHERE id=?", tuple(updates.values()) + revision_args + (stamp, task_id))
|
|
4638
4777
|
result = task_or_404(task_id)
|
|
4639
4778
|
await broadcast_task(task_id, {"type": "task_status", "task": result, "source": {"kind": "goal"}})
|
|
4640
4779
|
await broadcast_overview(task_id, {"kind": "goal"})
|
|
@@ -4808,6 +4947,153 @@ async def native_timeline_events(task: dict) -> tuple[list[dict], list[dict]]:
|
|
|
4808
4947
|
return merged, metrics
|
|
4809
4948
|
|
|
4810
4949
|
|
|
4950
|
+
def schedule_activity_graph_build(task_id: str) -> None:
|
|
4951
|
+
if not task_id or task_id in activity_graph_pending:
|
|
4952
|
+
return
|
|
4953
|
+
activity_graph_pending.add(task_id)
|
|
4954
|
+
activity_graph_queue.put_nowait(task_id)
|
|
4955
|
+
|
|
4956
|
+
|
|
4957
|
+
def seed_activity_graph_builds() -> None:
|
|
4958
|
+
rows = db.all(
|
|
4959
|
+
"SELECT t.id FROM tasks t LEFT JOIN activity_graphs g ON g.task_id=t.id "
|
|
4960
|
+
"WHERE t.trashed=0 AND (g.task_id IS NULL OR g.status!='ready' OR g.projection_version!=?) "
|
|
4961
|
+
"ORDER BY CASE WHEN t.status IN ('running','retrying','queued') THEN 0 ELSE 1 END, t.updated_at DESC",
|
|
4962
|
+
(PROJECTION_VERSION,),
|
|
4963
|
+
)
|
|
4964
|
+
for row in rows:
|
|
4965
|
+
schedule_activity_graph_build(row["id"])
|
|
4966
|
+
|
|
4967
|
+
|
|
4968
|
+
async def activity_events_for_task(task: dict) -> list[dict]:
|
|
4969
|
+
if task.get("native"):
|
|
4970
|
+
events, _ = await native_timeline_events(task)
|
|
4971
|
+
return events
|
|
4972
|
+
return await asyncio.to_thread(
|
|
4973
|
+
db.all,
|
|
4974
|
+
"SELECT * FROM events WHERE task_id=? ORDER BY id",
|
|
4975
|
+
(task["id"],),
|
|
4976
|
+
)
|
|
4977
|
+
|
|
4978
|
+
|
|
4979
|
+
def activity_graph_lock(task_id: str) -> asyncio.Lock:
|
|
4980
|
+
loop = asyncio.get_running_loop()
|
|
4981
|
+
cached = activity_graph_locks.get(task_id)
|
|
4982
|
+
if not cached or cached[0] is not loop:
|
|
4983
|
+
cached = (loop, asyncio.Lock())
|
|
4984
|
+
activity_graph_locks[task_id] = cached
|
|
4985
|
+
return cached[1]
|
|
4986
|
+
|
|
4987
|
+
|
|
4988
|
+
async def apply_activity_graph_event(task_id: str, event: dict[str, Any]) -> None:
|
|
4989
|
+
lock = activity_graph_lock(task_id)
|
|
4990
|
+
async with lock:
|
|
4991
|
+
snapshot = await asyncio.to_thread(activity_graph_store.snapshot, task_id)
|
|
4992
|
+
if snapshot.get("status") != "ready" or int(snapshot.get("projection_version") or 0) != PROJECTION_VERSION:
|
|
4993
|
+
activity_graph_backlog.setdefault(task_id, []).append(event)
|
|
4994
|
+
schedule_activity_graph_build(task_id)
|
|
4995
|
+
return
|
|
4996
|
+
task = task_summary(task_id)
|
|
4997
|
+
if not task:
|
|
4998
|
+
return
|
|
4999
|
+
patch = await asyncio.to_thread(
|
|
5000
|
+
activity_graph_store.apply_event,
|
|
5001
|
+
task_id,
|
|
5002
|
+
event,
|
|
5003
|
+
task.get("status") in {"running", "retrying", "queued"},
|
|
5004
|
+
str(task.get("status") or ""),
|
|
5005
|
+
now(),
|
|
5006
|
+
)
|
|
5007
|
+
if patch and patch.get("upsert_nodes"):
|
|
5008
|
+
await broadcast_task(task_id, {"type": "activity_map_patch", **patch})
|
|
5009
|
+
|
|
5010
|
+
|
|
5011
|
+
async def apply_activity_graph_status(task_id: str, task: dict[str, Any]) -> None:
|
|
5012
|
+
lock = activity_graph_lock(task_id)
|
|
5013
|
+
async with lock:
|
|
5014
|
+
patch = await asyncio.to_thread(
|
|
5015
|
+
activity_graph_store.apply_status,
|
|
5016
|
+
task_id,
|
|
5017
|
+
task.get("status") in {"running", "retrying", "queued"},
|
|
5018
|
+
str(task.get("status") or ""),
|
|
5019
|
+
now(),
|
|
5020
|
+
)
|
|
5021
|
+
if patch:
|
|
5022
|
+
await broadcast_task(task_id, {"type": "activity_map_patch", **patch})
|
|
5023
|
+
|
|
5024
|
+
|
|
5025
|
+
def schedule_activity_graph_event(task_id: str, payload: dict[str, Any]) -> None:
|
|
5026
|
+
if payload.get("type") in {"task_status", "task_patch"}:
|
|
5027
|
+
task = payload.get("task") or payload.get("patch") or {}
|
|
5028
|
+
if task.get("status"):
|
|
5029
|
+
asyncio.create_task(apply_activity_graph_status(task_id, task))
|
|
5030
|
+
return
|
|
5031
|
+
if payload.get("type") != "event":
|
|
5032
|
+
return
|
|
5033
|
+
event = {
|
|
5034
|
+
"id": payload.get("id", ""),
|
|
5035
|
+
"session_id": payload.get("session_id", ""),
|
|
5036
|
+
"ts": payload.get("ts") or now(),
|
|
5037
|
+
"stream": payload.get("stream") or "app-server",
|
|
5038
|
+
"payload": payload.get("payload") or {},
|
|
5039
|
+
}
|
|
5040
|
+
if not semantic_event(event):
|
|
5041
|
+
return
|
|
5042
|
+
asyncio.create_task(apply_activity_graph_event(task_id, event))
|
|
5043
|
+
|
|
5044
|
+
|
|
5045
|
+
async def build_activity_graph(task_id: str) -> None:
|
|
5046
|
+
task = task_summary(task_id)
|
|
5047
|
+
if not task:
|
|
5048
|
+
return
|
|
5049
|
+
stamp = now()
|
|
5050
|
+
await asyncio.to_thread(activity_graph_store.mark_building, task_id, stamp)
|
|
5051
|
+
try:
|
|
5052
|
+
events = await activity_events_for_task(task)
|
|
5053
|
+
nodes, seen = await asyncio.to_thread(
|
|
5054
|
+
project_events,
|
|
5055
|
+
events,
|
|
5056
|
+
task_running=task.get("status") in {"running", "retrying", "queued"},
|
|
5057
|
+
task_status=str(task.get("status") or ""),
|
|
5058
|
+
finalize=True,
|
|
5059
|
+
)
|
|
5060
|
+
revision = await asyncio.to_thread(activity_graph_store.replace, task_id, nodes, seen, len(events), now())
|
|
5061
|
+
await broadcast_task(
|
|
5062
|
+
task_id,
|
|
5063
|
+
{"type": "activity_map_ready", "revision": revision, "node_count": len(nodes), "event_count": len(events)},
|
|
5064
|
+
)
|
|
5065
|
+
backlog = activity_graph_backlog.pop(task_id, [])
|
|
5066
|
+
for event in backlog:
|
|
5067
|
+
await apply_activity_graph_event(task_id, event)
|
|
5068
|
+
except asyncio.CancelledError:
|
|
5069
|
+
raise
|
|
5070
|
+
except Exception as exc:
|
|
5071
|
+
db.execute(
|
|
5072
|
+
"UPDATE activity_graphs SET status='failed',error=?,updated_at=? WHERE task_id=?",
|
|
5073
|
+
(str(exc)[:1000], now(), task_id),
|
|
5074
|
+
)
|
|
5075
|
+
await broadcast_task(task_id, {"type": "activity_map_failed", "error": str(exc)[:500]})
|
|
5076
|
+
|
|
5077
|
+
|
|
5078
|
+
async def activity_graph_worker_loop() -> None:
|
|
5079
|
+
while True:
|
|
5080
|
+
task_id = await activity_graph_queue.get()
|
|
5081
|
+
try:
|
|
5082
|
+
await build_activity_graph(task_id)
|
|
5083
|
+
finally:
|
|
5084
|
+
activity_graph_pending.discard(task_id)
|
|
5085
|
+
activity_graph_queue.task_done()
|
|
5086
|
+
|
|
5087
|
+
|
|
5088
|
+
@app.get("/api/tasks/{task_id}/activity-map")
|
|
5089
|
+
async def task_activity_map(task_id: str, _: Any = Depends(auth)):
|
|
5090
|
+
task = task_or_404(task_id)
|
|
5091
|
+
snapshot = await asyncio.to_thread(activity_graph_store.snapshot, task["id"])
|
|
5092
|
+
if snapshot.get("status") != "ready" or int(snapshot.get("projection_version") or 0) != PROJECTION_VERSION:
|
|
5093
|
+
schedule_activity_graph_build(task["id"])
|
|
5094
|
+
return snapshot
|
|
5095
|
+
|
|
5096
|
+
|
|
4811
5097
|
@app.get("/api/tasks/{task_id}/timeline")
|
|
4812
5098
|
async def task_timeline(task_id: str, before: str = "", limit: int = 160, _: Any = Depends(auth)):
|
|
4813
5099
|
"""Return one newest-first cursor page, rendered oldest-to-newest by clients."""
|
|
@@ -5301,6 +5587,10 @@ async def launch(
|
|
|
5301
5587
|
await require_ssh_connection(task["ssh_host"], codex=True)
|
|
5302
5588
|
else:
|
|
5303
5589
|
require_codex()
|
|
5590
|
+
if await refresh_live_external_turn(task_id):
|
|
5591
|
+
persist_external_task_status(task_id, dashboard_active=False)
|
|
5592
|
+
raise HTTPException(409, "Task is already running in a terminal Codex client")
|
|
5593
|
+
task = task_or_404(task_id)
|
|
5304
5594
|
if external_turns.get(task_id):
|
|
5305
5595
|
persist_external_task_status(task_id, dashboard_active=False)
|
|
5306
5596
|
raise HTTPException(409, "Task is already running in a terminal Codex client")
|
|
@@ -5560,14 +5850,24 @@ async def dispatch_task_message(task_id: str, message_id: str, _: Any = Depends(
|
|
|
5560
5850
|
if row["status"] != "queued":
|
|
5561
5851
|
return row
|
|
5562
5852
|
current = task_or_404(task_id)
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
5853
|
+
task_busy = current["status"] in {"running", "retrying", "queued"} or task_id in running or task_id in appserver_turn_tasks or task_id in external_turns
|
|
5854
|
+
if task_busy:
|
|
5855
|
+
if dashboard_owns_task(task_id):
|
|
5856
|
+
try:
|
|
5857
|
+
result = await steer_task_message(
|
|
5858
|
+
task_id,
|
|
5859
|
+
TaskMessageIn(message=row["body"], client_message_id=message_id, delivery="auto"),
|
|
5860
|
+
)
|
|
5861
|
+
if result.get("status") == "queued" and result.get("error"):
|
|
5862
|
+
result = {**result, "dispatch_error": result["error"]}
|
|
5863
|
+
return result
|
|
5864
|
+
except HTTPException as exc:
|
|
5865
|
+
if exc.status_code != 409:
|
|
5866
|
+
raise
|
|
5867
|
+
db.execute("UPDATE task_messages SET status='queued', started_at=NULL, error='' WHERE id=?", (message_id,))
|
|
5868
|
+
schedule_task_drain(task_id)
|
|
5869
|
+
queued = db.one("SELECT * FROM task_messages WHERE id=?", (message_id,)) or row
|
|
5870
|
+
return {**queued, "waiting_for_turn": True, "execution_source": current.get("execution_source") or ("terminal" if task_id in external_turns else "dashboard")}
|
|
5571
5871
|
|
|
5572
5872
|
stamp = now()
|
|
5573
5873
|
db.execute("UPDATE task_messages SET status='dispatching', started_at=?, error='' WHERE id=?", (stamp, message_id))
|
|
@@ -5876,8 +6176,10 @@ async def run_thread_operation(task: dict, payload: OperationIn) -> dict:
|
|
|
5876
6176
|
if not name:
|
|
5877
6177
|
raise HTTPException(400, "rename requires a name in operation prompt or arguments")
|
|
5878
6178
|
result = await client.request("thread/name/set", {"threadId": thread_id, "name": name[:160]})
|
|
5879
|
-
db.execute("UPDATE tasks SET name=?,updated_at=? WHERE id=?", (name[:160], now(), task["id"]))
|
|
5880
|
-
|
|
6179
|
+
db.execute("UPDATE tasks SET name=?,name_revision=name_revision+1,updated_at=? WHERE id=?", (name[:160], now(), task["id"]))
|
|
6180
|
+
renamed = task_or_404(task["id"])
|
|
6181
|
+
await broadcast_task(task["id"], {"type": "task_status", "task": renamed, "source": {"kind": "thread", "operation": operation}})
|
|
6182
|
+
return {"ok": True, "operation": operation, "thread_id": thread_id, "result": result, "task": renamed}
|
|
5881
6183
|
if operation == "compact":
|
|
5882
6184
|
result = await client.request("thread/compact/start", {"threadId": thread_id})
|
|
5883
6185
|
return {"ok": True, "operation": operation, "thread_id": thread_id, "result": result}
|
|
@@ -5897,26 +6199,28 @@ async def run_thread_operation(task: dict, payload: OperationIn) -> dict:
|
|
|
5897
6199
|
fork_id = str((result.get("thread") or {}).get("id") or "")
|
|
5898
6200
|
if not fork_id:
|
|
5899
6201
|
raise HTTPException(502, "Codex did not return the forked thread id")
|
|
5900
|
-
|
|
5901
|
-
|
|
6202
|
+
forked_task = db.one("SELECT * FROM tasks WHERE codex_session_id=?", (fork_id,))
|
|
6203
|
+
if not forked_task:
|
|
6204
|
+
fork_task_id = fork_id if not db.one("SELECT id FROM tasks WHERE id=?", (fork_id,)) else str(uuid.uuid4())
|
|
6205
|
+
stamp = now()
|
|
6206
|
+
fork_name = f"{task['name']} (fork)"[:160]
|
|
5902
6207
|
db.execute(
|
|
5903
|
-
"INSERT INTO tasks (id,name,prompt,goal,workspace,status,yolo,max_retries,retry_forever,retry_explicit,provider_id,model,context,codex_session_id,goal_status,created_at,updated_at,native,reasoning_effort,service_tier,personality,collaboration_mode,permission_profile,ssh_host) "
|
|
5904
|
-
"VALUES (
|
|
5905
|
-
(fork_task_id,
|
|
5906
|
-
int(task.get("yolo", 1)), int(task.get("max_retries", 3)),
|
|
5907
|
-
task.get("provider_id"), task.get("model", ""), task.get("context", ""), fork_id, task.get("goal_status", "none"),
|
|
6208
|
+
"INSERT INTO tasks (id,name,prompt,goal,workspace,status,yolo,max_retries,retry_forever,retry_explicit,provider_id,model,context,codex_session_id,goal_status,goal_tokens_used,goal_revision,goal_updated_at,created_at,updated_at,last_interaction_at,native,reasoning_effort,service_tier,personality,collaboration_mode,permission_profile,ssh_host,name_revision) "
|
|
6209
|
+
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
|
6210
|
+
(fork_task_id, fork_name, task.get("prompt", ""), task.get("goal", ""), task["workspace"], "available",
|
|
6211
|
+
int(task.get("yolo", 1)), int(task.get("max_retries", 3)), 0, 0,
|
|
6212
|
+
task.get("provider_id"), task.get("model", ""), task.get("context", ""), fork_id, task.get("goal_status", "none"),
|
|
6213
|
+
int(task.get("goal_tokens_used", 0)), 1 if task.get("goal") else 0, stamp if task.get("goal") else "", stamp, stamp, stamp,
|
|
6214
|
+
0 if task.get("ssh_host") else 1,
|
|
5908
6215
|
task.get("reasoning_effort", ""), task.get("service_tier", ""), task.get("personality", ""), task.get("collaboration_mode", "default"),
|
|
5909
|
-
task.get("permission_profile", ""), task
|
|
6216
|
+
task.get("permission_profile", ""), task.get("ssh_host", ""), 1),
|
|
5910
6217
|
)
|
|
5911
6218
|
db.execute(
|
|
5912
6219
|
"INSERT INTO sessions (id,task_id,status,attempt,provider_id,command,started_at,finished_at,exit_code,summary,codex_session_id) VALUES (?,?,?,?,?,?,?,?,?,?,?)",
|
|
5913
|
-
(str(uuid.uuid4()), fork_task_id, "imported", 0, task.get("provider_id"),
|
|
6220
|
+
(str(uuid.uuid4()), fork_task_id, "imported", 0, task.get("provider_id"), "codex app-server thread/fork", stamp, stamp, 0, "Forked Codex thread", fork_id),
|
|
5914
6221
|
)
|
|
5915
|
-
forked_task =
|
|
6222
|
+
forked_task = task_or_404(fork_task_id)
|
|
5916
6223
|
await broadcast_overview(fork_task_id, {"type": "created", "forked": True})
|
|
5917
|
-
else:
|
|
5918
|
-
await sync_native_threads()
|
|
5919
|
-
forked_task = db.one("SELECT id,name,status FROM tasks WHERE codex_session_id=?", (fork_id,))
|
|
5920
6224
|
return {"ok": True, "operation": operation, "thread_id": thread_id, "fork_thread_id": fork_id, "task": forked_task}
|
|
5921
6225
|
|
|
5922
6226
|
|
|
@@ -6012,7 +6316,8 @@ async def execute_slash_command(task: dict, command: str, arg_text: str, args: l
|
|
|
6012
6316
|
if first == "clear":
|
|
6013
6317
|
if client and thread_id:
|
|
6014
6318
|
await client.request("thread/goal/clear", {"threadId": thread_id})
|
|
6015
|
-
|
|
6319
|
+
stamp = now()
|
|
6320
|
+
db.execute("UPDATE tasks SET goal='',goal_status='none',goal_tokens_used=0,goal_revision=goal_revision+1,goal_updated_at=?,retry_forever=0,retry_explicit=0,updated_at=? WHERE id=?", (stamp, stamp, task["id"]))
|
|
6016
6321
|
return result_message(command, "Goal 已清除。")
|
|
6017
6322
|
if first == "budget":
|
|
6018
6323
|
if len(args) != 2 or not args[1].isdigit():
|
|
@@ -6045,7 +6350,8 @@ async def execute_slash_command(task: dict, command: str, arg_text: str, args: l
|
|
|
6045
6350
|
if client and thread_id:
|
|
6046
6351
|
goal = (await client.request("thread/goal/set", {"threadId": thread_id, "objective": objective, "status": status})).get("goal") or {}
|
|
6047
6352
|
status = goal.get("status", status)
|
|
6048
|
-
|
|
6353
|
+
stamp = now()
|
|
6354
|
+
db.execute("UPDATE tasks SET goal=?,goal_status=?,goal_tokens_used=0,goal_revision=goal_revision+1,goal_updated_at=?,updated_at=? WHERE id=?", (objective, status, stamp, stamp, task["id"]))
|
|
6049
6355
|
return result_message(command, f"Goal 已设置:\n\n{objective}\n\n状态:`{status}`。", goal={"objective": objective, "status": status})
|
|
6050
6356
|
|
|
6051
6357
|
if command == "status":
|
|
@@ -6457,7 +6763,7 @@ async def codex_operation(task_id: str, payload: OperationIn, _: Any = Depends(a
|
|
|
6457
6763
|
return await run_thread_operation(task, payload)
|
|
6458
6764
|
if payload.operation in {"memory-enable", "memory-disable"}:
|
|
6459
6765
|
return await run_thread_operation(task, payload)
|
|
6460
|
-
if task_id in running or task_id in appserver_turn_tasks or task["status"] == "running":
|
|
6766
|
+
if payload.operation not in {"rename", "fork"} and (task_id in running or task_id in appserver_turn_tasks or task["status"] == "running"):
|
|
6461
6767
|
raise HTTPException(409, "Task is already running")
|
|
6462
6768
|
if task.get("ssh_host"):
|
|
6463
6769
|
await require_ssh_connection(task["ssh_host"], codex=True)
|