voidx 2.3.0__tar.gz → 3.1.0__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 (344) hide show
  1. {voidx-2.3.0 → voidx-3.1.0}/PKG-INFO +4 -1
  2. {voidx-2.3.0 → voidx-3.1.0}/pyproject.toml +3 -2
  3. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/__init__.py +1 -1
  4. voidx-3.1.0/src/voidx/agent/agents.py +77 -0
  5. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/attachments.py +2 -1
  6. voidx-3.1.0/src/voidx/agent/goal_resolver.py +439 -0
  7. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/graph/compaction.py +10 -2
  8. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/graph/compaction_coordinator.py +75 -48
  9. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/graph/contracts.py +14 -8
  10. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/graph/convergence.py +7 -89
  11. voidx-3.1.0/src/voidx/agent/graph/core/__init__.py +9 -0
  12. voidx-3.1.0/src/voidx/agent/graph/core/_voidx_graph.py +461 -0
  13. voidx-3.1.0/src/voidx/agent/graph/core/helpers.py +126 -0
  14. voidx-3.1.0/src/voidx/agent/graph/core/llm.py +335 -0
  15. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/graph/permissions.py +55 -42
  16. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/graph/run_loop.py +30 -15
  17. voidx-3.1.0/src/voidx/agent/graph/runtime_guards.py +462 -0
  18. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/graph/session_runtime.py +35 -70
  19. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/graph/streaming.py +173 -9
  20. voidx-3.1.0/src/voidx/agent/graph/subagent.py +393 -0
  21. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/graph/title_mixin.py +2 -2
  22. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/graph/todo_events.py +1 -1
  23. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/graph/tool_execution.py +0 -1
  24. voidx-3.1.0/src/voidx/agent/graph/tool_executor/__init__.py +8 -0
  25. voidx-3.1.0/src/voidx/agent/graph/tool_executor/executor.py +353 -0
  26. voidx-3.1.0/src/voidx/agent/graph/tool_executor/guards.py +150 -0
  27. voidx-3.1.0/src/voidx/agent/graph/tool_executor/helpers.py +346 -0
  28. voidx-3.1.0/src/voidx/agent/graph/tool_executor/types.py +86 -0
  29. voidx-3.1.0/src/voidx/agent/graph/tool_executor/ui.py +143 -0
  30. voidx-3.1.0/src/voidx/agent/graph/tool_executor/workflow.py +314 -0
  31. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/graph/topology.py +11 -18
  32. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/graph/turn_mixin.py +0 -4
  33. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/graph/turn_runner.py +175 -121
  34. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/graph/wiring.py +2 -9
  35. voidx-3.1.0/src/voidx/agent/graph/workflow_utils.py +33 -0
  36. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/message_rows.py +1 -1
  37. voidx-3.1.0/src/voidx/agent/prompts.py +177 -0
  38. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/runtime_context.py +174 -276
  39. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/slash/code_ide.py +1 -2
  40. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/slash/handler.py +55 -20
  41. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/slash/host.py +9 -15
  42. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/slash/init.py +1 -1
  43. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/slash/model.py +7 -7
  44. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/slash/session.py +119 -5
  45. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/slash/skills.py +1 -2
  46. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/state.py +3 -16
  47. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/task_state.py +14 -10
  48. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/todo_state.py +82 -18
  49. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/tool_filters.py +2 -2
  50. voidx-3.1.0/src/voidx/agent/tool_result_storage.py +73 -0
  51. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/config/__init__.py +0 -2
  52. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/config/enums.py +1 -1
  53. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/config/models.py +9 -39
  54. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/config/settings.py +152 -37
  55. voidx-3.1.0/src/voidx/config/settings_agent.py +21 -0
  56. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/config/settings_api_keys.py +3 -11
  57. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/config/settings_code_ide.py +2 -4
  58. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/config/settings_custom.py +3 -21
  59. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/config/settings_mcp.py +23 -19
  60. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/config/settings_permissions.py +7 -7
  61. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/config/settings_skills.py +4 -2
  62. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/config/settings_update.py +3 -7
  63. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/config/settings_web.py +15 -10
  64. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/llm/catalog.py +6 -2
  65. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/llm/compaction.py +14 -0
  66. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/llm/instruction.py +15 -33
  67. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/llm/provider.py +93 -27
  68. voidx-3.1.0/src/voidx/llm/service.py +32 -0
  69. voidx-3.1.0/src/voidx/logging/__init__.py +6 -0
  70. voidx-3.1.0/src/voidx/logging/request_log.py +147 -0
  71. voidx-3.1.0/src/voidx/logging/tool_log.py +75 -0
  72. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/mcp/client/base.py +4 -2
  73. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/mcp/client/stdio_transport.py +3 -1
  74. voidx-3.1.0/src/voidx/memory/cleanup.py +122 -0
  75. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/memory/context_frames.py +80 -20
  76. voidx-3.1.0/src/voidx/memory/jsonl_store.py +188 -0
  77. voidx-3.1.0/src/voidx/memory/runtime_state.py +347 -0
  78. voidx-3.1.0/src/voidx/memory/service.py +99 -0
  79. voidx-3.1.0/src/voidx/memory/session.py +403 -0
  80. voidx-3.1.0/src/voidx/memory/store.py +247 -0
  81. voidx-3.1.0/src/voidx/memory/subagents.py +21 -0
  82. voidx-3.1.0/src/voidx/memory/transcript.py +495 -0
  83. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/permission/engine.py +4 -1
  84. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/permission/evaluate.py +1 -1
  85. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/permission/rules.py +47 -32
  86. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/permission/service.py +13 -2
  87. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/runtime/__init__.py +15 -12
  88. voidx-3.1.0/src/voidx/runtime/attachments.py +7 -0
  89. voidx-3.1.0/src/voidx/runtime/intent.py +46 -0
  90. voidx-3.1.0/src/voidx/runtime/reference_tokens.py +9 -0
  91. voidx-3.1.0/src/voidx/runtime/task_state.py +229 -0
  92. voidx-3.1.0/src/voidx/runtime/todo.py +9 -0
  93. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/runtime/ui.py +5 -2
  94. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/selfupdate.py +2 -1
  95. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/skills/context.py +0 -52
  96. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/skills/schema.py +1 -2
  97. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/skills/service.py +11 -1
  98. voidx-3.1.0/src/voidx/tools/agent.py +304 -0
  99. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/tools/base.py +44 -12
  100. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/tools/bash.py +63 -20
  101. voidx-3.1.0/src/voidx/tools/bash_router.py +1222 -0
  102. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/tools/clarify.py +20 -55
  103. voidx-3.1.0/src/voidx/tools/compact_context.py +53 -0
  104. voidx-3.1.0/src/voidx/tools/file_ops/__init__.py +19 -0
  105. voidx-3.1.0/src/voidx/tools/file_ops/edit_execute.py +538 -0
  106. voidx-3.1.0/src/voidx/tools/file_ops/edit_resolve.py +257 -0
  107. voidx-3.1.0/src/voidx/tools/file_ops/file.py +177 -0
  108. voidx-3.1.0/src/voidx/tools/file_ops/line.py +150 -0
  109. voidx-3.1.0/src/voidx/tools/file_ops/read.py +232 -0
  110. voidx-3.1.0/src/voidx/tools/file_ops/types.py +79 -0
  111. voidx-3.1.0/src/voidx/tools/file_ops/write.py +89 -0
  112. voidx-3.1.0/src/voidx/tools/file_state.py +353 -0
  113. voidx-3.1.0/src/voidx/tools/git.py +1335 -0
  114. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/tools/load_doc_template.py +7 -5
  115. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/tools/load_skills.py +5 -3
  116. voidx-3.1.0/src/voidx/tools/lsp.py +134 -0
  117. voidx-3.1.0/src/voidx/tools/plan_checkpoint.py +254 -0
  118. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/tools/registry.py +7 -14
  119. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/tools/repomap.py +26 -6
  120. voidx-3.1.0/src/voidx/tools/search.py +299 -0
  121. voidx-3.1.0/src/voidx/tools/service.py +32 -0
  122. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/tools/task_status.py +4 -2
  123. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/tools/task_tracker.py +6 -9
  124. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/tools/todo.py +3 -6
  125. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/tools/web_mcp.py +1 -0
  126. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/tools/webfetch.py +112 -15
  127. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/tools/websearch.py +4 -2
  128. voidx-3.1.0/src/voidx/tools/workflow.py +553 -0
  129. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/commands.py +8 -0
  130. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/output/agent_display.py +2 -2
  131. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/output/capture.py +3 -6
  132. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/output/console/app.py +48 -13
  133. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/output/console/formatting.py +2 -2
  134. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/output/console/streaming.py +6 -3
  135. voidx-3.1.0/src/voidx/ui/output/display_policy.py +137 -0
  136. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/output/dock/app.py +12 -6
  137. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/output/dock/formatting.py +38 -17
  138. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/output/dock/nodes.py +35 -21
  139. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/output/dock/stream.py +15 -58
  140. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/output/dock/todo.py +1 -5
  141. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/output/events/consumers.py +35 -19
  142. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/output/events/schema.py +9 -3
  143. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/output/tree.py +186 -18
  144. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/output/types.py +3 -3
  145. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/session.py +2 -8
  146. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/tools/clipboard_image.py +2 -7
  147. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/tools/file_picker.py +10 -6
  148. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/tools/skill_picker.py +8 -4
  149. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/transcript.py +1 -1
  150. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/tui/app.py +14 -2
  151. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/tui/choice_mixin.py +15 -4
  152. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/tui/helpers.py +1 -4
  153. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/tui/input.py +2 -2
  154. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/tui/overlays.py +4 -2
  155. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/tui/panels.py +2 -3
  156. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/tui/render_activity.py +12 -0
  157. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/tui/render_frame.py +161 -30
  158. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/tui/render_status.py +53 -13
  159. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/tui/state.py +22 -0
  160. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/workflow/__init__.py +10 -8
  161. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/workflow/auto_advance.py +3 -3
  162. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/workflow/context.py +4 -9
  163. voidx-3.1.0/src/voidx/workflow/dag.py +30 -0
  164. voidx-3.1.0/src/voidx/workflow/nodes.py +418 -0
  165. voidx-3.1.0/src/voidx/workflow/policy.py +82 -0
  166. voidx-3.1.0/src/voidx/workflow/reconcile.py +293 -0
  167. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/workflow/render.py +42 -28
  168. voidx-3.1.0/src/voidx/workflow/route.py +49 -0
  169. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/workflow/runtime.py +138 -135
  170. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/workflow/schema.py +49 -31
  171. voidx-3.1.0/src/voidx/workflow/service.py +143 -0
  172. voidx-3.1.0/src/voidx/workflow/types.py +101 -0
  173. {voidx-2.3.0 → voidx-3.1.0}/src/voidx.egg-info/PKG-INFO +4 -1
  174. {voidx-2.3.0 → voidx-3.1.0}/src/voidx.egg-info/SOURCES.txt +45 -47
  175. {voidx-2.3.0 → voidx-3.1.0}/src/voidx.egg-info/requires.txt +4 -0
  176. voidx-3.1.0/tests/test_install_sh.py +162 -0
  177. voidx-2.3.0/src/voidx/agent/agents.py +0 -492
  178. voidx-2.3.0/src/voidx/agent/graph/core.py +0 -808
  179. voidx-2.3.0/src/voidx/agent/graph/subagent.py +0 -284
  180. voidx-2.3.0/src/voidx/agent/graph/tool_executor.py +0 -640
  181. voidx-2.3.0/src/voidx/agent/intent_refinement.py +0 -299
  182. voidx-2.3.0/src/voidx/config/cli.py +0 -1
  183. voidx-2.3.0/src/voidx/config/settings_agent.py +0 -34
  184. voidx-2.3.0/src/voidx/data/intent_classifier.json +0 -1
  185. voidx-2.3.0/src/voidx/memory/runtime_state.py +0 -391
  186. voidx-2.3.0/src/voidx/memory/session.py +0 -298
  187. voidx-2.3.0/src/voidx/memory/store.py +0 -273
  188. voidx-2.3.0/src/voidx/memory/transcript.py +0 -132
  189. voidx-2.3.0/src/voidx/runtime/intent.py +0 -96
  190. voidx-2.3.0/src/voidx/runtime/intent_classifier.py +0 -265
  191. voidx-2.3.0/src/voidx/runtime/task_state.py +0 -376
  192. voidx-2.3.0/src/voidx/skills/policy.py +0 -43
  193. voidx-2.3.0/src/voidx/skills/runtime.py +0 -25
  194. voidx-2.3.0/src/voidx/tools/advance_workflow.py +0 -206
  195. voidx-2.3.0/src/voidx/tools/agent.py +0 -110
  196. voidx-2.3.0/src/voidx/tools/apply_patch.py +0 -423
  197. voidx-2.3.0/src/voidx/tools/file_ops.py +0 -184
  198. voidx-2.3.0/src/voidx/tools/file_state.py +0 -28
  199. voidx-2.3.0/src/voidx/tools/git.py +0 -644
  200. voidx-2.3.0/src/voidx/tools/lsp.py +0 -155
  201. voidx-2.3.0/src/voidx/tools/on_intent.py +0 -116
  202. voidx-2.3.0/src/voidx/tools/plan_checkpoint.py +0 -164
  203. voidx-2.3.0/src/voidx/tools/search.py +0 -155
  204. voidx-2.3.0/src/voidx/workflow/dag.py +0 -34
  205. voidx-2.3.0/src/voidx/workflow/nodes.py +0 -322
  206. voidx-2.3.0/src/voidx/workflow/policy.py +0 -147
  207. voidx-2.3.0/src/voidx/workflow/service.py +0 -197
  208. voidx-2.3.0/tests/test_auto_advance.py +0 -410
  209. voidx-2.3.0/tests/test_clipboard_image.py +0 -100
  210. voidx-2.3.0/tests/test_clipboard_text.py +0 -22
  211. voidx-2.3.0/tests/test_code_ide.py +0 -75
  212. voidx-2.3.0/tests/test_compaction.py +0 -793
  213. voidx-2.3.0/tests/test_config.py +0 -478
  214. voidx-2.3.0/tests/test_instruction_cache.py +0 -88
  215. voidx-2.3.0/tests/test_intent_classifier_phase_a.py +0 -159
  216. voidx-2.3.0/tests/test_llm_catalog.py +0 -46
  217. voidx-2.3.0/tests/test_llm_provider.py +0 -632
  218. voidx-2.3.0/tests/test_llm_usage.py +0 -212
  219. voidx-2.3.0/tests/test_lsp.py +0 -527
  220. voidx-2.3.0/tests/test_main.py +0 -77
  221. voidx-2.3.0/tests/test_main_startup.py +0 -129
  222. voidx-2.3.0/tests/test_mcp.py +0 -377
  223. voidx-2.3.0/tests/test_output_browse.py +0 -23
  224. voidx-2.3.0/tests/test_runtime_intent_classifier.py +0 -191
  225. voidx-2.3.0/tests/test_runtime_ui.py +0 -71
  226. voidx-2.3.0/tests/test_scrollback_flush.py +0 -151
  227. voidx-2.3.0/tests/test_selfupdate.py +0 -103
  228. voidx-2.3.0/tests/test_skills.py +0 -980
  229. voidx-2.3.0/tests/test_startup.py +0 -59
  230. voidx-2.3.0/tests/test_tree_smoke.py +0 -240
  231. voidx-2.3.0/tests/test_tui_frame_rendering.py +0 -472
  232. voidx-2.3.0/tests/test_tui_input_handling.py +0 -865
  233. voidx-2.3.0/tests/test_tui_output_tree.py +0 -301
  234. voidx-2.3.0/tests/test_tui_paste_handling.py +0 -280
  235. voidx-2.3.0/tests/test_tui_status_activity.py +0 -723
  236. voidx-2.3.0/tests/test_tui_terminal_panels.py +0 -710
  237. voidx-2.3.0/tests/test_ui_diff.py +0 -68
  238. voidx-2.3.0/tests/test_ui_events.py +0 -1077
  239. voidx-2.3.0/tests/test_ui_frontend_protocol.py +0 -150
  240. voidx-2.3.0/tests/test_ui_gateway.py +0 -366
  241. voidx-2.3.0/tests/test_ui_session_changes.py +0 -180
  242. {voidx-2.3.0 → voidx-3.1.0}/README.md +0 -0
  243. {voidx-2.3.0 → voidx-3.1.0}/setup.cfg +0 -0
  244. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/__init__.py +0 -0
  245. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/graph/__init__.py +0 -0
  246. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/graph/runtime.py +0 -0
  247. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/graph/session_mixin.py +0 -0
  248. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/graph/transcript_mixin.py +0 -0
  249. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/slash/__init__.py +0 -0
  250. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/slash/guide.py +0 -0
  251. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/slash/lsp.py +0 -0
  252. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/slash/mcp.py +0 -0
  253. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/slash/profile.py +0 -0
  254. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/slash/runtime.py +0 -0
  255. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/slash/upgrade.py +0 -0
  256. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/agent/tool_messages.py +0 -0
  257. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/config/permissions.py +0 -0
  258. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/config/settings_utils.py +0 -0
  259. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/data/__init__.py +0 -0
  260. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/data/templates/api-doc.md +0 -0
  261. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/data/templates/prd.md +0 -0
  262. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/data/templates/readme.md +0 -0
  263. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/data/templates/rfc.md +0 -0
  264. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/data/templates/tech-design.md +0 -0
  265. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/diffing.py +0 -0
  266. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/llm/__init__.py +0 -0
  267. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/llm/context.py +0 -0
  268. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/llm/message_markers.py +0 -0
  269. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/llm/usage.py +0 -0
  270. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/lsp/__init__.py +0 -0
  271. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/lsp/client.py +0 -0
  272. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/lsp/config.py +0 -0
  273. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/lsp/detector.py +0 -0
  274. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/lsp/detector_data.py +0 -0
  275. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/lsp/errors.py +0 -0
  276. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/lsp/manager.py +0 -0
  277. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/lsp/schema.py +0 -0
  278. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/lsp/service.py +0 -0
  279. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/main.py +0 -0
  280. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/mcp/__init__.py +0 -0
  281. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/mcp/client/__init__.py +0 -0
  282. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/mcp/client/errors.py +0 -0
  283. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/mcp/client/http_transport.py +0 -0
  284. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/mcp/client/sse_transport.py +0 -0
  285. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/mcp/manager.py +0 -0
  286. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/mcp/schema.py +0 -0
  287. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/mcp/tool.py +0 -0
  288. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/mcp_servers/__init__.py +0 -0
  289. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/mcp_servers/web.py +0 -0
  290. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/memory/__init__.py +0 -0
  291. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/memory/model_profiles.py +0 -0
  292. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/permission/__init__.py +0 -0
  293. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/permission/context.py +0 -0
  294. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/permission/sandbox.py +0 -0
  295. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/permission/schema.py +0 -0
  296. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/permission/wildcard.py +0 -0
  297. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/runtime/ui_port.py +0 -0
  298. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/skills/__init__.py +0 -0
  299. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/skills/references.py +0 -0
  300. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/skills/registry.py +0 -0
  301. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/tools/__init__.py +0 -0
  302. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/tools/web_content.py +0 -0
  303. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/__init__.py +0 -0
  304. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/frontend.py +0 -0
  305. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/gateway/__init__.py +0 -0
  306. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/gateway/bootstrap.py +0 -0
  307. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/gateway/server.py +0 -0
  308. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/gateway/session.py +0 -0
  309. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/output/__init__.py +0 -0
  310. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/output/browse.py +0 -0
  311. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/output/console/__init__.py +0 -0
  312. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/output/diff.py +0 -0
  313. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/output/dock/__init__.py +0 -0
  314. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/output/dock/agent_placeholder.py +0 -0
  315. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/output/dock/nodes_permission.py +0 -0
  316. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/output/dock/nodes_startup.py +0 -0
  317. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/output/dock/nodes_status.py +0 -0
  318. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/output/dock/state.py +0 -0
  319. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/output/dock/status.py +0 -0
  320. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/output/events/__init__.py +0 -0
  321. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/output/events/bus.py +0 -0
  322. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/protocol/__init__.py +0 -0
  323. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/protocol/commands.py +0 -0
  324. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/protocol/envelope.py +0 -0
  325. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/protocol/requests.py +0 -0
  326. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/protocol/schema.py +0 -0
  327. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/protocol/transcript.py +0 -0
  328. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/tools/__init__.py +0 -0
  329. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/tools/attachment_tokens.py +0 -0
  330. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/tools/clipboard_text.py +0 -0
  331. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/tools/code_ide.py +0 -0
  332. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/tui/__init__.py +0 -0
  333. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/tui/activity.py +0 -0
  334. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/tui/clipboard_mixin.py +0 -0
  335. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/tui/parser.py +0 -0
  336. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/tui/render_input.py +0 -0
  337. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/tui/render_todo.py +0 -0
  338. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/tui/renderer.py +0 -0
  339. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/tui/terminal_mixin.py +0 -0
  340. {voidx-2.3.0 → voidx-3.1.0}/src/voidx/ui/tui/text_prompt_mixin.py +0 -0
  341. {voidx-2.3.0 → voidx-3.1.0}/src/voidx.egg-info/dependency_links.txt +0 -0
  342. {voidx-2.3.0 → voidx-3.1.0}/src/voidx.egg-info/entry_points.txt +0 -0
  343. {voidx-2.3.0 → voidx-3.1.0}/src/voidx.egg-info/top_level.txt +0 -0
  344. {voidx-2.3.0 → voidx-3.1.0}/tests/test_npm_package.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: voidx
3
- Version: 2.3.0
3
+ Version: 3.1.0
4
4
  Summary: A coding agent that quantifies everything and solves with tools, not fuzzy prompts.
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -15,6 +15,9 @@ Requires-Dist: rich>=13.9.0
15
15
  Requires-Dist: tiktoken>=0.8.0
16
16
  Requires-Dist: httpx>=0.28.0
17
17
  Requires-Dist: websockets>=14
18
+ Requires-Dist: pathspec>=0.12
19
+ Provides-Extra: gemini
20
+ Requires-Dist: langchain-google-genai>=4.0.0; extra == "gemini"
18
21
  Provides-Extra: dev
19
22
  Requires-Dist: build>=1.2.0; extra == "dev"
20
23
  Requires-Dist: pytest>=8.0.0; extra == "dev"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "voidx"
3
- version = "2.3.0"
3
+ version = "3.1.0"
4
4
  description = "A coding agent that quantifies everything and solves with tools, not fuzzy prompts."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -16,12 +16,14 @@ dependencies = [
16
16
  "tiktoken>=0.8.0",
17
17
  "httpx>=0.28.0",
18
18
  "websockets>=14",
19
+ "pathspec>=0.12",
19
20
  ]
20
21
 
21
22
  [project.scripts]
22
23
  voidx = "voidx.main:cli"
23
24
 
24
25
  [project.optional-dependencies]
26
+ gemini = ["langchain-google-genai>=4.0.0"]
25
27
  dev = [
26
28
  "build>=1.2.0",
27
29
  "pytest>=8.0.0",
@@ -47,7 +49,6 @@ where = ["src"]
47
49
  "bundled/*/SKILL.md",
48
50
  ]
49
51
  "voidx.data" = [
50
- "intent_classifier.json",
51
52
  "templates/*.md",
52
53
  ]
53
54
 
@@ -1,3 +1,3 @@
1
1
  """VoidX - A coding agent that quantifies everything."""
2
2
 
3
- __version__ = "2.3.0"
3
+ __version__ = "3.1.0"
@@ -0,0 +1,77 @@
1
+ """Agent definitions — typed config and whenToUse descriptions.
2
+
3
+ voidx uses one agent identity:
4
+ voidx — primary identity, also used for isolated child runs
5
+
6
+ Runtime personas (coordinate/explore/plan/implement/review) are thinking-mode
7
+ labels, not AgentDef ids.
8
+
9
+ Tool visibility is controlled by the ToolRegistry and permission layer,
10
+ not by a static whitelist on AgentDef.
11
+ """
12
+
13
+
14
+ from __future__ import annotations
15
+
16
+ from pydantic import BaseModel
17
+
18
+ # ── agent definitions ─────────────────────────────────────────────────────
19
+
20
+ class AgentDef(BaseModel):
21
+ """An agent's complete definition — typed, no loose config."""
22
+ name: str
23
+ description: str
24
+ when_to_use: str
25
+ can_write: bool
26
+ can_delegate: bool # can it start child agents via the agent tool?
27
+ hidden: bool = False # hidden from user-facing lists?
28
+ model: str | None = None # None = inherit from parent
29
+
30
+ # ── built-in agents ────────────────────────────────────────────────────────
31
+
32
+ BUILTIN_AGENTS: dict[str, AgentDef] = {
33
+ "voidx": AgentDef(
34
+ name="voidx",
35
+ description="Primary agent. Understands intent, edits small scoped changes directly, "
36
+ "delegates broad work to specialists, reviews results.",
37
+ when_to_use="Default agent for all user interactions. Always use first.",
38
+ can_write=True,
39
+ can_delegate=True,
40
+ hidden=False,
41
+ ),
42
+ }
43
+
44
+
45
+ def get_agent(name: str) -> AgentDef | None:
46
+ return BUILTIN_AGENTS.get(name)
47
+
48
+
49
+ def get_visible_agents() -> list[AgentDef]:
50
+ return [a for a in BUILTIN_AGENTS.values() if not a.hidden]
51
+
52
+
53
+ def get_subagents() -> list[AgentDef]:
54
+ """Child-run identities voidx can delegate to."""
55
+ agent = get_agent("voidx")
56
+ return [child_run_agent_def(agent)] if agent is not None else []
57
+
58
+
59
+ def child_run_agent_def(agent: AgentDef) -> AgentDef:
60
+ """Return the child-run view of the public voidx identity."""
61
+ return agent.model_copy(update={
62
+ "name": "voidx",
63
+ "description": "Isolated child run of voidx that follows the supplied workflow route.",
64
+ "when_to_use": "Use for delegated child work that benefits from isolated context.",
65
+ "can_delegate": False,
66
+ })
67
+
68
+
69
+ def child_agent_descriptions_for_llm() -> str:
70
+ """Generate child-agent descriptions for the agent tool."""
71
+ lines = ["Available child agents:"]
72
+ for agent in get_subagents():
73
+ lines.append(
74
+ f"- {agent.name}: {agent.description}\n"
75
+ f" Write access: {agent.can_write}"
76
+ )
77
+ return "\n".join(lines)
@@ -10,9 +10,10 @@ from dataclasses import dataclass, field
10
10
  from pathlib import Path
11
11
  from typing import Any
12
12
 
13
+ from voidx.runtime.attachments import MAX_IMAGE_ATTACHMENT_BYTES
14
+
13
15
  IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".gif", ".webp"}
14
16
  MAX_TEXT_ATTACHMENT_BYTES = 200_000
15
- MAX_IMAGE_ATTACHMENT_BYTES = 5_000_000
16
17
  MAX_DIR_LISTING_ITEMS = 500
17
18
  _DIR_TREE_SKIP = {"__pycache__", ".git", ".hg", ".svn", "node_modules", ".venv", "venv", "dist", "build", ".pytest_cache", ".mypy_cache"}
18
19
  _ATTACHMENT_RE = re.compile(r'(?<!\S)(?:@(?:"([^"]+)"|(\S+))|\[image-([^\]]+)\])')
@@ -0,0 +1,439 @@
1
+ """Runtime-owned structured goal resolution for top-level turns."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import json
7
+ from typing import Any, Literal
8
+
9
+ from langchain_core.messages import AIMessage, BaseMessage, HumanMessage, SystemMessage
10
+ from pydantic import BaseModel, model_validator
11
+
12
+ from voidx.logging.request_log import log_llm_diagnostic, serialize_llm_message
13
+ from voidx.runtime.intent import InteractionMode, TaskIntent, _contains_any
14
+ from voidx.runtime.task_state import (
15
+ GoalResolution,
16
+ GoalSpec,
17
+ IntentResolution,
18
+ PlanResolution,
19
+ TaskState,
20
+ goal_type_from_join,
21
+ )
22
+ from voidx.workflow.dag import DEFAULT_WORKFLOW_DAG
23
+
24
+
25
+ GOAL_RESOLVER_TIMEOUT_SECONDS = 20
26
+ WorkflowName = Literal["brainstorm", "debug", "design", "feedback", "plan", "review", "tdd", "verify"]
27
+
28
+
29
+ class ResolverGoal(BaseModel):
30
+ intent: Literal["coding", "general"] = "general"
31
+ goal: str | None = None
32
+ workflow: WorkflowName | None = None
33
+ kind_hint: str | None = None
34
+
35
+ @model_validator(mode="after")
36
+ def _goal_and_workflow_are_bound(self) -> "ResolverGoal":
37
+ has_goal = bool(self.goal and self.goal.strip())
38
+ has_workflow = self.workflow is not None
39
+ if has_goal != has_workflow:
40
+ raise ValueError("goal and workflow must be set together")
41
+ if not has_goal:
42
+ self.goal = None
43
+ self.workflow = None
44
+ return self
45
+
46
+
47
+ def resolve_plan_mode(user_text: str, task_state: TaskState) -> GoalResolution:
48
+ """PLAN mode: construct result directly without LLM call."""
49
+ desc = (
50
+ task_state.current_goal.desc
51
+ if task_state.current_goal and task_state.current_goal.desc.strip()
52
+ else user_text
53
+ )
54
+ return GoalResolution(
55
+ intent=IntentResolution(type=TaskIntent.CODING),
56
+ goal=GoalSpec(desc=desc),
57
+ plan=PlanResolution(join="brainstorm", leave="brainstorm"),
58
+ )
59
+
60
+
61
+ def resolve_goal_mode(user_text: str, task_state: TaskState) -> GoalResolution:
62
+ """GOAL mode: construct result directly without LLM call.
63
+
64
+ The user must specify a goal to enter goal mode. Fixed entry from plan node;
65
+ plan will clarify via questions before planning.
66
+ """
67
+ goal = task_state.current_goal or GoalSpec(desc=user_text)
68
+ return GoalResolution(
69
+ intent=IntentResolution(type=TaskIntent.CODING),
70
+ goal=goal,
71
+ plan=PlanResolution(join="plan", leave=None),
72
+ )
73
+
74
+
75
+ async def resolve_goal_for_turn(
76
+ *,
77
+ model: Any | None,
78
+ user_text: str,
79
+ interaction_mode: str | InteractionMode | None,
80
+ task_state: TaskState,
81
+ log_diagnostic: bool = True,
82
+ ) -> GoalResolution:
83
+ del interaction_mode
84
+ fallback = GoalResolution(
85
+ intent=IntentResolution(type=TaskIntent.GENERAL),
86
+ goal=None,
87
+ plan=None,
88
+ )
89
+ fallback_reason = ""
90
+ fallback_error_type = ""
91
+ fallback_error = ""
92
+ if model is None:
93
+ fallback_reason = "model_unavailable"
94
+ normalized = _normalize_resolution(fallback, user_text, task_state)
95
+ _log_goal_resolver_decision(normalized, user_text, task_state, fallback_reason, fallback_error_type, fallback_error, enabled=log_diagnostic)
96
+ return normalized
97
+
98
+ structured = getattr(model, "with_structured_output", None)
99
+ if not callable(structured):
100
+ fallback_reason = "structured_output_unsupported"
101
+ normalized = _normalize_resolution(fallback, user_text, task_state)
102
+ _log_goal_resolver_decision(normalized, user_text, task_state, fallback_reason, fallback_error_type, fallback_error, enabled=log_diagnostic)
103
+ return normalized
104
+
105
+ resolver_goal: ResolverGoal | None
106
+ try:
107
+ runnable = structured(ResolverGoal)
108
+ resolver_messages = _resolver_messages_from_exchanges(user_text, task_state)
109
+ raw = await asyncio.wait_for(
110
+ runnable.ainvoke(resolver_messages),
111
+ timeout=GOAL_RESOLVER_TIMEOUT_SECONDS,
112
+ )
113
+ _log_goal_resolver_exchange(resolver_messages, raw=raw, enabled=log_diagnostic)
114
+ resolver_goal = _coerce_resolution(raw)
115
+ except Exception as exc:
116
+ fallback_reason = "structured_output_error"
117
+ fallback_error_type = type(exc).__name__
118
+ fallback_error = _truncate_error_text(str(exc))
119
+ if "resolver_messages" in locals():
120
+ _log_goal_resolver_exchange(
121
+ resolver_messages,
122
+ error_type=fallback_error_type,
123
+ error=fallback_error,
124
+ enabled=log_diagnostic,
125
+ )
126
+ resolver_goal = None
127
+
128
+ if resolver_goal is None:
129
+ fallback_reason = fallback_reason or "invalid_structured_output"
130
+ normalized = _normalize_resolution(fallback, user_text, task_state)
131
+ resolver_kind_hint = ""
132
+ else:
133
+ resolution = _to_goal_resolution(resolver_goal, task_state)
134
+ normalized = _normalize_resolution(resolution, user_text, task_state)
135
+ resolver_kind_hint = resolver_goal.kind_hint or ""
136
+ _log_goal_resolver_decision(
137
+ normalized,
138
+ user_text,
139
+ task_state,
140
+ fallback_reason,
141
+ fallback_error_type,
142
+ fallback_error,
143
+ resolver_kind_hint=resolver_kind_hint,
144
+ enabled=log_diagnostic,
145
+ )
146
+ return normalized
147
+
148
+
149
+ def _resolver_messages_from_exchanges(user_text: str, task_state: TaskState) -> list[BaseMessage]:
150
+ return [
151
+ SystemMessage(content=_resolver_system_prompt()),
152
+ HumanMessage(content=_resolver_request_markdown(user_text, task_state)),
153
+ ]
154
+
155
+
156
+ def _log_goal_resolver_decision(
157
+ resolution: GoalResolution,
158
+ user_text: str,
159
+ task_state: TaskState,
160
+ fallback_reason: str,
161
+ fallback_error_type: str,
162
+ fallback_error: str,
163
+ *,
164
+ resolver_kind_hint: str = "",
165
+ enabled: bool = True,
166
+ ) -> None:
167
+ goal = resolution.goal
168
+ plan = resolution.plan
169
+ log_llm_diagnostic(
170
+ "goal_resolver_decision",
171
+ enabled=enabled,
172
+ intent=resolution.intent.type.value,
173
+ goal_type=goal_type_from_join(plan.join if plan is not None else None),
174
+ goal_desc=goal.desc if goal is not None else "",
175
+ plan_join=plan.join if plan is not None else "",
176
+ plan_leave=plan.leave if plan is not None and plan.leave is not None else "",
177
+ resolver_kind_hint=resolver_kind_hint,
178
+ fallback_reason=fallback_reason,
179
+ fallback_error_type=fallback_error_type,
180
+ fallback_error=fallback_error,
181
+ active_workflows=_active_workflow_names(task_state),
182
+ user_text=user_text,
183
+ )
184
+
185
+
186
+ def _log_goal_resolver_exchange(
187
+ messages: list[BaseMessage],
188
+ *,
189
+ raw: object | None = None,
190
+ error_type: str = "",
191
+ error: str = "",
192
+ enabled: bool = True,
193
+ ) -> None:
194
+ response: dict[str, Any] = {}
195
+ if error_type or error:
196
+ response["error_type"] = error_type
197
+ response["error"] = error
198
+ else:
199
+ response["raw"] = _raw_response_for_log(raw)
200
+ log_llm_diagnostic(
201
+ "goal_resolver_exchange",
202
+ enabled=enabled,
203
+ request={"messages": [serialize_llm_message(message) for message in messages]},
204
+ response=response,
205
+ )
206
+
207
+
208
+ def _raw_response_for_log(value: object) -> object:
209
+ if isinstance(value, BaseModel):
210
+ return value.model_dump(mode="json")
211
+ if isinstance(value, AIMessage):
212
+ return {
213
+ "content": value.content,
214
+ "tool_calls": getattr(value, "tool_calls", None) or [],
215
+ "usage_metadata": getattr(value, "usage_metadata", None) or {},
216
+ }
217
+ if isinstance(value, (str, int, float, bool)) or value is None:
218
+ return value
219
+ if isinstance(value, (dict, list, tuple)):
220
+ return value
221
+ return repr(value)
222
+
223
+
224
+ def _truncate_error_text(value: str, limit: int = 2000) -> str:
225
+ text = " ".join(value.split())
226
+ if len(text) <= limit:
227
+ return text
228
+ return text[:limit].rstrip() + "..."
229
+
230
+
231
+ def _resolver_system_prompt() -> str:
232
+ return (
233
+ "Resolve this turn into intent, goal, workflow, and kind_hint.\n"
234
+ "Read the Markdown request. Return only structured data matching the schema at the end."
235
+ )
236
+
237
+
238
+ def _resolver_request_markdown(user_text: str, task_state: TaskState) -> str:
239
+ recent_content = _recent_exchanges_content(task_state)
240
+ active = ", ".join(_active_workflow_names(task_state)) or "none"
241
+ goal = task_state.current_goal.label if task_state.current_goal is not None else "none"
242
+ return "\n".join([
243
+ "# Goal Resolver Request",
244
+ "",
245
+ "## Current State",
246
+ "",
247
+ f"- intent: {task_state.current_intent.value}",
248
+ f"- goal: {goal}",
249
+ f"- active workflows: {active}",
250
+ "",
251
+ "## Recent Conversation Content",
252
+ "",
253
+ "```text",
254
+ recent_content,
255
+ "```",
256
+ "",
257
+ "## Current User Content",
258
+ "",
259
+ "```text",
260
+ user_text,
261
+ "```",
262
+ "",
263
+ "## Available Workflows",
264
+ "",
265
+ "- brainstorm: Confirm requirements and design, get user approval",
266
+ "- debug: Locate root cause and confirm fix direction",
267
+ "- design: Produce a structured document that passes the reader test",
268
+ "- feedback: Verify and implement valid review feedback",
269
+ "- plan: Produce an executable implementation plan, get user approval",
270
+ "- review: Initiate structured code review request and collect verdict",
271
+ "- tdd: Complete implementation via TDD cycle, all tests green",
272
+ "- verify: Prove changes reach expected state with reproducible evidence",
273
+ "",
274
+ "## Return Fields",
275
+ "",
276
+ '- intent: "coding" for codebase/workspace work; "general" for non-code conversation.',
277
+ "- goal: short user-language summary, or null when no workflow should start.",
278
+ "- workflow: workflow to start, or null. Must be set exactly when goal is set.",
279
+ "- kind_hint: optional semantic hint such as review/debug/feature/inspect. Advisory only; never overrides workflow.",
280
+ "",
281
+ "## ResolverGoal Schema",
282
+ "",
283
+ "- intent: 'coding' | 'general'",
284
+ "- goal: null or string (short summary of the user's request in their language, 1-2 sentences)",
285
+ "- workflow: null or one of [brainstorm, debug, design, feedback, plan, review, tdd, verify]",
286
+ "- kind_hint: null or string (non-authoritative semantic hint; not used for routing)",
287
+ ])
288
+
289
+
290
+ _ALLOWED_JOIN_NODES = {"debug", "brainstorm", "design", "plan", "tdd", "review", "feedback", "verify"}
291
+
292
+
293
+ def _coerce_resolution(value: object) -> ResolverGoal | None:
294
+ if isinstance(value, ResolverGoal):
295
+ return value
296
+ if isinstance(value, GoalResolution):
297
+ return _resolver_goal_from_goal_resolution(value)
298
+ if isinstance(value, AIMessage):
299
+ value = value.content
300
+ if isinstance(value, str):
301
+ try:
302
+ value = json.loads(value)
303
+ except json.JSONDecodeError:
304
+ return None
305
+ if isinstance(value, dict) and "parsed" in value:
306
+ return _coerce_resolution(value.get("parsed"))
307
+ if isinstance(value, dict):
308
+ if "workflow" not in value and ("plan" in value or isinstance(value.get("goal"), dict) or isinstance(value.get("intent"), dict)):
309
+ value = _legacy_dict_to_resolver_dict(value)
310
+ try:
311
+ return ResolverGoal.model_validate(value)
312
+ except ValueError:
313
+ return None
314
+ return None
315
+
316
+
317
+ def _to_goal_resolution(resolver: ResolverGoal, task_state: TaskState) -> GoalResolution:
318
+ del task_state
319
+ intent_type = TaskIntent(resolver.intent)
320
+ if resolver.goal is None or resolver.workflow is None:
321
+ return GoalResolution(intent=IntentResolution(type=intent_type), goal=None, plan=None)
322
+ return GoalResolution(
323
+ intent=IntentResolution(type=intent_type),
324
+ goal=GoalSpec(desc=resolver.goal),
325
+ plan=PlanResolution(join=resolver.workflow, leave=None),
326
+ )
327
+
328
+
329
+ def _resolver_goal_from_goal_resolution(resolution: GoalResolution) -> ResolverGoal | None:
330
+ goal = resolution.goal
331
+ plan = resolution.plan
332
+ return ResolverGoal(
333
+ intent=resolution.intent.type.value,
334
+ goal=goal.desc if goal is not None else None,
335
+ workflow=plan.join if plan is not None else None,
336
+ kind_hint=None,
337
+ )
338
+
339
+
340
+ def _legacy_dict_to_resolver_dict(value: dict) -> dict:
341
+ intent_value = value.get("intent")
342
+ intent = intent_value.get("type") if isinstance(intent_value, dict) else intent_value
343
+ goal_value = value.get("goal")
344
+ if isinstance(goal_value, dict):
345
+ goal = goal_value.get("desc")
346
+ kind_hint = goal_value.get("type")
347
+ else:
348
+ goal = goal_value
349
+ kind_hint = value.get("kind_hint")
350
+ plan_value = value.get("plan")
351
+ workflow = plan_value.get("join") if isinstance(plan_value, dict) else value.get("workflow")
352
+ return {
353
+ "intent": intent or "general",
354
+ "goal": goal,
355
+ "workflow": workflow,
356
+ "kind_hint": kind_hint,
357
+ }
358
+
359
+
360
+ def _normalize_resolution(
361
+ resolution: GoalResolution,
362
+ user_text: str,
363
+ task_state: TaskState,
364
+ ) -> GoalResolution:
365
+ plan = resolution.plan
366
+ if plan is not None:
367
+ if plan.join and plan.join not in _ALLOWED_JOIN_NODES:
368
+ plan = None
369
+ elif plan.leave and plan.leave not in DEFAULT_WORKFLOW_DAG.nodes:
370
+ plan = PlanResolution(join=plan.join, leave=None)
371
+
372
+ if resolution.intent.type == TaskIntent.GENERAL:
373
+ current_join = _current_active_join(task_state)
374
+ if current_join and task_state.current_goal is not None:
375
+ return GoalResolution(
376
+ intent=IntentResolution(type=TaskIntent.CODING),
377
+ goal=task_state.current_goal,
378
+ plan=PlanResolution(join=current_join, leave=None),
379
+ )
380
+ return GoalResolution(
381
+ intent=resolution.intent,
382
+ goal=None,
383
+ plan=None,
384
+ )
385
+
386
+ goal = resolution.goal
387
+ if goal is not None and (plan is None or not plan.join):
388
+ current_join = _current_active_join(task_state)
389
+ if current_join and task_state.current_goal is not None and _is_short_continuation(user_text):
390
+ return GoalResolution(
391
+ intent=resolution.intent,
392
+ goal=task_state.current_goal,
393
+ plan=PlanResolution(join=current_join, leave=None),
394
+ )
395
+ goal = None
396
+ plan = None
397
+
398
+ return GoalResolution(
399
+ intent=resolution.intent,
400
+ goal=goal,
401
+ plan=plan,
402
+ )
403
+
404
+
405
+ def _recent_exchanges_content(task_state: TaskState) -> str:
406
+ blocks: list[str] = []
407
+ for index, exchange in enumerate(task_state.recent_exchanges, start=1):
408
+ parts = [part for part in (exchange.user_text.strip(), exchange.assistant_text.strip()) if part]
409
+ if not parts:
410
+ continue
411
+ blocks.append(f"### Content {index}\n\n" + "\n\n".join(parts))
412
+ return "\n\n".join(blocks)
413
+
414
+
415
+ _SHORT_CONTINUATION_TEXTS = {"ok", "okay", "continue", "go on", "yes", "y", "改", "继续", "继续改", "好", "好的"}
416
+
417
+
418
+ def _is_short_continuation(user_text: str) -> bool:
419
+ text = user_text.strip().lower()
420
+ if text in _SHORT_CONTINUATION_TEXTS:
421
+ return True
422
+ return len(text) <= 8 and _contains_any(text, ("继续", "改", "ok"))
423
+
424
+
425
+ def _current_active_join(task_state: TaskState) -> str:
426
+ if task_state.workflow_route is not None and task_state.workflow_route.join:
427
+ return task_state.workflow_route.join
428
+ for name, run in task_state.workflow_runs.items():
429
+ if getattr(run.status, "value", run.status) == "active":
430
+ return name
431
+ return ""
432
+
433
+
434
+ def _active_workflow_names(task_state: TaskState) -> list[str]:
435
+ return [
436
+ name
437
+ for name, run in task_state.workflow_runs.items()
438
+ if getattr(run.status, "value", run.status) == "active"
439
+ ]
@@ -48,7 +48,7 @@ class GraphCompactionMixin:
48
48
  self._in_turn_compaction_count = count
49
49
  if count > 2:
50
50
  return None
51
- return await _compaction_component_for(self).compact_for_live_state(
51
+ result = await _compaction_component_for(self).compact_for_live_state(
52
52
  messages,
53
53
  force=True,
54
54
  ask=False,
@@ -56,6 +56,10 @@ class GraphCompactionMixin:
56
56
  run_compaction_agent=self._run_compaction_agent,
57
57
  persist_compaction=self._persist_compaction,
58
58
  )
59
+ if result is not None:
60
+ self._file_read_coverage.clear()
61
+ self._file_mtimes.clear()
62
+ return result
59
63
 
60
64
  async def _ask_compact(self: GraphCompactionHost, total_tokens: int) -> bool:
61
65
  return await _compaction_component_for(self).ask_compact(total_tokens)
@@ -64,11 +68,15 @@ class GraphCompactionMixin:
64
68
  await _compaction_component_for(self).persist_compaction(head_messages)
65
69
 
66
70
  async def _compact_session_history(self: GraphCompactionHost, *, force: bool = True) -> bool:
67
- return await _compaction_component_for(self).compact_session_history(
71
+ result = await _compaction_component_for(self).compact_session_history(
68
72
  force=force,
69
73
  run_compaction_agent=self._run_compaction_agent,
70
74
  persist_compaction=self._persist_compaction,
71
75
  )
76
+ if result:
77
+ self._file_read_coverage.clear()
78
+ self._file_mtimes.clear()
79
+ return result
72
80
 
73
81
  async def _run_compaction_agent(
74
82
  self: GraphCompactionHost,