codex-partner 0.0.3__tar.gz → 0.0.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (323) hide show
  1. {codex_partner-0.0.3 → codex_partner-0.0.5}/PKG-INFO +3 -3
  2. {codex_partner-0.0.3 → codex_partner-0.0.5}/README.md +2 -2
  3. {codex_partner-0.0.3 → codex_partner-0.0.5}/app.py +343 -37
  4. {codex_partner-0.0.3 → codex_partner-0.0.5}/codex_partner/__init__.py +1 -1
  5. codex_partner-0.0.5/codex_partner/activity_graph.py +473 -0
  6. {codex_partner-0.0.3 → codex_partner-0.0.5}/codex_partner/database.py +32 -0
  7. {codex_partner-0.0.3 → codex_partner-0.0.5}/codex_partner.egg-info/PKG-INFO +3 -3
  8. {codex_partner-0.0.3 → codex_partner-0.0.5}/codex_partner.egg-info/SOURCES.txt +71 -0
  9. {codex_partner-0.0.3 → codex_partner-0.0.5}/pyproject.toml +1 -1
  10. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/app.js +57 -11
  11. codex_partner-0.0.5/static/chat-worker.js +512 -0
  12. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/conversation.js +197 -42
  13. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/core.js +76 -8
  14. codex_partner-0.0.5/static/default-user-avatar.webp +0 -0
  15. codex_partner-0.0.5/static/exploration-tree.js +391 -0
  16. codex_partner-0.0.5/static/history-pagination.js +58 -0
  17. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/index.html +10 -6
  18. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/settings.js +1 -1
  19. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/styles.css +140 -11
  20. codex_partner-0.0.5/static/vendor/katex/LICENSE +21 -0
  21. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_AMS-Regular.ttf +0 -0
  22. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_AMS-Regular.woff +0 -0
  23. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  24. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  25. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  26. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  27. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  28. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  29. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  30. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  31. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff +0 -0
  32. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  33. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  34. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff +0 -0
  35. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  36. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Main-Bold.ttf +0 -0
  37. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Main-Bold.woff +0 -0
  38. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
  39. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  40. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff +0 -0
  41. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  42. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Main-Italic.ttf +0 -0
  43. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Main-Italic.woff +0 -0
  44. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
  45. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Main-Regular.ttf +0 -0
  46. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Main-Regular.woff +0 -0
  47. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
  48. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  49. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff +0 -0
  50. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  51. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Math-Italic.ttf +0 -0
  52. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Math-Italic.woff +0 -0
  53. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
  54. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  55. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff +0 -0
  56. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  57. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  58. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff +0 -0
  59. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  60. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  61. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff +0 -0
  62. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  63. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Script-Regular.ttf +0 -0
  64. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Script-Regular.woff +0 -0
  65. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
  66. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Size1-Regular.ttf +0 -0
  67. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Size1-Regular.woff +0 -0
  68. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  69. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Size2-Regular.ttf +0 -0
  70. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Size2-Regular.woff +0 -0
  71. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  72. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Size3-Regular.ttf +0 -0
  73. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Size3-Regular.woff +0 -0
  74. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  75. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Size4-Regular.ttf +0 -0
  76. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Size4-Regular.woff +0 -0
  77. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  78. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  79. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff +0 -0
  80. codex_partner-0.0.5/static/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  81. codex_partner-0.0.5/static/vendor/katex/katex.min.css +1 -0
  82. codex_partner-0.0.5/static/vendor/katex/katex.min.js +1 -0
  83. codex_partner-0.0.5/static/vendor/markdown-it/LICENSE +22 -0
  84. codex_partner-0.0.5/static/vendor/markdown-it/markdown-it.min.js +14 -0
  85. codex_partner-0.0.5/static/vendor/markdown-it-texmath/LICENSE +21 -0
  86. codex_partner-0.0.5/static/vendor/markdown-it-texmath/texmath.js +368 -0
  87. {codex_partner-0.0.3 → codex_partner-0.0.5}/tests/test_running_state.py +853 -17
  88. codex_partner-0.0.3/static/chat-worker.js +0 -244
  89. {codex_partner-0.0.3 → codex_partner-0.0.5}/codex_partner/__main__.py +0 -0
  90. {codex_partner-0.0.3 → codex_partner-0.0.5}/codex_partner/app_server.py +0 -0
  91. {codex_partner-0.0.3 → codex_partner-0.0.5}/codex_partner/cli.py +0 -0
  92. {codex_partner-0.0.3 → codex_partner-0.0.5}/codex_partner/commands.py +0 -0
  93. {codex_partner-0.0.3 → codex_partner-0.0.5}/codex_partner/discovery.py +0 -0
  94. {codex_partner-0.0.3 → codex_partner-0.0.5}/codex_partner/schemas.py +0 -0
  95. {codex_partner-0.0.3 → codex_partner-0.0.5}/codex_partner/ssh_auth.py +0 -0
  96. {codex_partner-0.0.3 → codex_partner-0.0.5}/codex_partner.egg-info/dependency_links.txt +0 -0
  97. {codex_partner-0.0.3 → codex_partner-0.0.5}/codex_partner.egg-info/entry_points.txt +0 -0
  98. {codex_partner-0.0.3 → codex_partner-0.0.5}/codex_partner.egg-info/requires.txt +0 -0
  99. {codex_partner-0.0.3 → codex_partner-0.0.5}/codex_partner.egg-info/top_level.txt +0 -0
  100. {codex_partner-0.0.3 → codex_partner-0.0.5}/setup.cfg +0 -0
  101. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/__init__.py +0 -0
  102. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/editor.js +0 -0
  103. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/mascot-dance.css +0 -0
  104. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/mascot-dance.js +0 -0
  105. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/LICENSE +0 -0
  106. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/ace.js +0 -0
  107. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/ext-modelist.js +0 -0
  108. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-abap.js +0 -0
  109. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-abc.js +0 -0
  110. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-actionscript.js +0 -0
  111. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-ada.js +0 -0
  112. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-alda.js +0 -0
  113. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-apache_conf.js +0 -0
  114. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-apex.js +0 -0
  115. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-applescript.js +0 -0
  116. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-aql.js +0 -0
  117. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-asciidoc.js +0 -0
  118. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-asl.js +0 -0
  119. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-assembly_arm32.js +0 -0
  120. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-assembly_x86.js +0 -0
  121. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-astro.js +0 -0
  122. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-autohotkey.js +0 -0
  123. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-basic.js +0 -0
  124. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-batchfile.js +0 -0
  125. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-bibtex.js +0 -0
  126. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-c9search.js +0 -0
  127. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-c_cpp.js +0 -0
  128. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-cedar.js +0 -0
  129. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-cedarschema.js +0 -0
  130. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-cirru.js +0 -0
  131. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-clojure.js +0 -0
  132. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-clue.js +0 -0
  133. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-cobol.js +0 -0
  134. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-coffee.js +0 -0
  135. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-coldfusion.js +0 -0
  136. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-crystal.js +0 -0
  137. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-csharp.js +0 -0
  138. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-csound_document.js +0 -0
  139. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-csound_orchestra.js +0 -0
  140. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-csound_score.js +0 -0
  141. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-csp.js +0 -0
  142. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-css.js +0 -0
  143. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-csv.js +0 -0
  144. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-curly.js +0 -0
  145. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-cuttlefish.js +0 -0
  146. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-d.js +0 -0
  147. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-dart.js +0 -0
  148. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-diff.js +0 -0
  149. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-django.js +0 -0
  150. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-dockerfile.js +0 -0
  151. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-dot.js +0 -0
  152. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-drools.js +0 -0
  153. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-edifact.js +0 -0
  154. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-eiffel.js +0 -0
  155. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-ejs.js +0 -0
  156. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-elixir.js +0 -0
  157. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-elm.js +0 -0
  158. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-erlang.js +0 -0
  159. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-flix.js +0 -0
  160. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-forth.js +0 -0
  161. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-fortran.js +0 -0
  162. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-fsharp.js +0 -0
  163. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-fsl.js +0 -0
  164. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-ftl.js +0 -0
  165. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-gcode.js +0 -0
  166. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-gherkin.js +0 -0
  167. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-gitignore.js +0 -0
  168. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-glsl.js +0 -0
  169. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-gobstones.js +0 -0
  170. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-golang.js +0 -0
  171. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-graphqlschema.js +0 -0
  172. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-groovy.js +0 -0
  173. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-groq.js +0 -0
  174. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-haml.js +0 -0
  175. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-handlebars.js +0 -0
  176. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-haskell.js +0 -0
  177. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-haskell_cabal.js +0 -0
  178. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-haxe.js +0 -0
  179. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-hjson.js +0 -0
  180. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-html.js +0 -0
  181. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-html_elixir.js +0 -0
  182. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-html_ruby.js +0 -0
  183. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-ini.js +0 -0
  184. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-io.js +0 -0
  185. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-ion.js +0 -0
  186. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-jack.js +0 -0
  187. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-jade.js +0 -0
  188. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-java.js +0 -0
  189. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-javascript.js +0 -0
  190. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-jexl.js +0 -0
  191. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-json.js +0 -0
  192. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-json5.js +0 -0
  193. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-jsoniq.js +0 -0
  194. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-jsp.js +0 -0
  195. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-jssm.js +0 -0
  196. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-jsx.js +0 -0
  197. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-julia.js +0 -0
  198. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-kotlin.js +0 -0
  199. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-latex.js +0 -0
  200. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-latte.js +0 -0
  201. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-less.js +0 -0
  202. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-liquid.js +0 -0
  203. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-lisp.js +0 -0
  204. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-livescript.js +0 -0
  205. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-logiql.js +0 -0
  206. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-logtalk.js +0 -0
  207. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-lsl.js +0 -0
  208. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-lua.js +0 -0
  209. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-luapage.js +0 -0
  210. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-lucene.js +0 -0
  211. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-makefile.js +0 -0
  212. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-mariadb.js +0 -0
  213. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-markdown.js +0 -0
  214. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-mask.js +0 -0
  215. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-matlab.js +0 -0
  216. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-maze.js +0 -0
  217. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-mediawiki.js +0 -0
  218. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-mel.js +0 -0
  219. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-mips.js +0 -0
  220. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-mixal.js +0 -0
  221. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-mushcode.js +0 -0
  222. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-mysql.js +0 -0
  223. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-nasal.js +0 -0
  224. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-nginx.js +0 -0
  225. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-nim.js +0 -0
  226. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-nix.js +0 -0
  227. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-nsis.js +0 -0
  228. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-nunjucks.js +0 -0
  229. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-objectivec.js +0 -0
  230. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-ocaml.js +0 -0
  231. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-odin.js +0 -0
  232. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-partiql.js +0 -0
  233. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-pascal.js +0 -0
  234. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-perl.js +0 -0
  235. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-pgsql.js +0 -0
  236. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-php.js +0 -0
  237. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-php_laravel_blade.js +0 -0
  238. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-pig.js +0 -0
  239. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-plain_text.js +0 -0
  240. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-plsql.js +0 -0
  241. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-powershell.js +0 -0
  242. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-praat.js +0 -0
  243. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-prisma.js +0 -0
  244. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-prolog.js +0 -0
  245. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-properties.js +0 -0
  246. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-protobuf.js +0 -0
  247. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-prql.js +0 -0
  248. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-puppet.js +0 -0
  249. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-python.js +0 -0
  250. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-qml.js +0 -0
  251. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-r.js +0 -0
  252. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-raku.js +0 -0
  253. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-razor.js +0 -0
  254. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-rdoc.js +0 -0
  255. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-red.js +0 -0
  256. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-redshift.js +0 -0
  257. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-rhtml.js +0 -0
  258. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-robot.js +0 -0
  259. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-rst.js +0 -0
  260. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-ruby.js +0 -0
  261. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-rust.js +0 -0
  262. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-sac.js +0 -0
  263. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-sass.js +0 -0
  264. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-scad.js +0 -0
  265. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-scala.js +0 -0
  266. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-scheme.js +0 -0
  267. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-scrypt.js +0 -0
  268. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-scss.js +0 -0
  269. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-sh.js +0 -0
  270. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-sjs.js +0 -0
  271. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-slim.js +0 -0
  272. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-smarty.js +0 -0
  273. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-smithy.js +0 -0
  274. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-snippets.js +0 -0
  275. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-soy_template.js +0 -0
  276. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-space.js +0 -0
  277. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-sparql.js +0 -0
  278. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-sql.js +0 -0
  279. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-sqlserver.js +0 -0
  280. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-stylus.js +0 -0
  281. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-svg.js +0 -0
  282. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-swift.js +0 -0
  283. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-tcl.js +0 -0
  284. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-terraform.js +0 -0
  285. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-tex.js +0 -0
  286. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-text.js +0 -0
  287. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-textile.js +0 -0
  288. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-toml.js +0 -0
  289. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-tsv.js +0 -0
  290. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-tsx.js +0 -0
  291. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-turtle.js +0 -0
  292. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-twig.js +0 -0
  293. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-typescript.js +0 -0
  294. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-vala.js +0 -0
  295. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-vbscript.js +0 -0
  296. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-velocity.js +0 -0
  297. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-verilog.js +0 -0
  298. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-vhdl.js +0 -0
  299. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-visualforce.js +0 -0
  300. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-vue.js +0 -0
  301. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-wollok.js +0 -0
  302. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-xml.js +0 -0
  303. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-xquery.js +0 -0
  304. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-yaml.js +0 -0
  305. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-zeek.js +0 -0
  306. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/mode-zig.js +0 -0
  307. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/ace/theme-tomorrow_night_eighties.js +0 -0
  308. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/i18next/LICENSE +0 -0
  309. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/i18next/i18next.min.js +0 -0
  310. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/toastui-editor/LICENSE +0 -0
  311. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/toastui-editor/toastui-editor-dark.css +0 -0
  312. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/toastui-editor/toastui-editor.css +0 -0
  313. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/toastui-editor/toastui-editor.js +0 -0
  314. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/toastui-editor/zh-cn.js +0 -0
  315. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/xterm/LICENSE +0 -0
  316. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/xterm/addons/fit.LICENSE +0 -0
  317. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/xterm/addons/fit.js +0 -0
  318. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/xterm/addons/unicode11.LICENSE +0 -0
  319. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/xterm/addons/unicode11.js +0 -0
  320. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/xterm/addons/web-links.LICENSE +0 -0
  321. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/xterm/addons/web-links.js +0 -0
  322. {codex_partner-0.0.3 → codex_partner-0.0.5}/static/vendor/xterm/xterm.css +0 -0
  323. {codex_partner-0.0.3 → codex_partner-0.0.5}/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
3
+ Version: 0.0.5
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.3 .
77
+ docker build -t codex-partner:0.0.5 .
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.3
86
+ codex-partner:0.0.5
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.3 .
52
+ docker build -t codex-partner:0.0.5 .
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.3
61
+ codex-partner:0.0.5
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
- return f"ssh:{host}:{provider_key}" if host else provider_key
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
- dashboard_pids = {
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=? WHERE id=?",
1480
- (goal.get("objective", ""), goal.get("status", "active"), int(goal.get("tokensUsed", 0) or 0), rollout_stamp(record), task_id),
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("SELECT id,status,retry_forever,retry_explicit,provider_id,model FROM tasks WHERE codex_session_id=?", (thread_id,))
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
- (title[:160], prompt, objective, workspace, model, provider_id, goal_status, int(goal.get("tokensUsed", 0) or 0),
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] = [goal.get("status", "active"), goal.get("tokensUsed", 0), now()]
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.insert(0, "goal=?")
2528
- values.insert(0, str(objective or ""))
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
- sets.append("updated_at=?"); args.append(now()); args.append(task_id)
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
- db.execute(f"UPDATE tasks SET {sets}, updated_at=? WHERE id=?", tuple(updates.values()) + (now(), task_id))
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
- if current["status"] == "running" or task_id in running or task_id in appserver_turn_tasks:
5564
- result = await steer_task_message(
5565
- task_id,
5566
- TaskMessageIn(message=row["body"], client_message_id=message_id, delivery="auto"),
5567
- )
5568
- if result.get("status") == "queued" and result.get("error"):
5569
- result = {**result, "dispatch_error": result["error"]}
5570
- return result
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
- return {"ok": True, "operation": operation, "thread_id": thread_id, "result": result}
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
- if task.get("ssh_host"):
5901
- fork_task_id, stamp = str(uuid.uuid4()), now()
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, f"{task['name']} 副本"[:160], task.get("prompt", ""), task.get("goal", ""), task["workspace"], "available",
5906
- int(task.get("yolo", 1)), int(task.get("max_retries", 3)), int(task.get("retry_forever", 0)), int(task.get("retry_explicit", 0)),
5907
- task.get("provider_id"), task.get("model", ""), task.get("context", ""), fork_id, task.get("goal_status", "none"), stamp, stamp, 0,
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["ssh_host"]),
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"), f"ssh {task['ssh_host']} codex thread/fork", stamp, stamp, 0, "Forked remote Codex thread", fork_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 = db.one("SELECT id,name,status FROM tasks WHERE id=?", (fork_task_id,))
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
- db.execute("UPDATE tasks SET goal='',goal_status='none',goal_tokens_used=0,retry_forever=0,retry_explicit=0,updated_at=? WHERE id=?", (now(), task["id"]))
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
- db.execute("UPDATE tasks SET goal=?,goal_status=?,goal_tokens_used=0,updated_at=? WHERE id=?", (objective, status, now(), task["id"]))
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)
@@ -1,4 +1,4 @@
1
1
  """Codex Partner application package."""
2
2
 
3
3
  APP_NAME = "Codex Partner"
4
- APP_VERSION = "0.0.3"
4
+ APP_VERSION = "0.0.5"