voidx 3.0.0__tar.gz → 3.1.1__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 (343) hide show
  1. {voidx-3.0.0 → voidx-3.1.1}/PKG-INFO +4 -1
  2. {voidx-3.0.0 → voidx-3.1.1}/pyproject.toml +3 -2
  3. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/__init__.py +1 -1
  4. voidx-3.1.1/src/voidx/agent/agents.py +77 -0
  5. voidx-3.1.1/src/voidx/agent/goal_resolver.py +443 -0
  6. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/compaction.py +28 -12
  7. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/compaction_coordinator.py +202 -25
  8. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/contracts.py +15 -5
  9. voidx-3.1.1/src/voidx/agent/graph/convergence.py +87 -0
  10. voidx-3.1.1/src/voidx/agent/graph/core/__init__.py +9 -0
  11. voidx-3.1.1/src/voidx/agent/graph/core/_voidx_graph.py +460 -0
  12. voidx-3.1.1/src/voidx/agent/graph/core/helpers.py +126 -0
  13. voidx-3.1.1/src/voidx/agent/graph/core/llm.py +439 -0
  14. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/permissions.py +2 -14
  15. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/run_loop.py +14 -17
  16. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/runtime_guards.py +20 -23
  17. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/session_runtime.py +15 -5
  18. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/streaming.py +217 -28
  19. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/subagent.py +55 -107
  20. voidx-3.1.1/src/voidx/agent/graph/tool_executor/__init__.py +8 -0
  21. voidx-3.1.1/src/voidx/agent/graph/tool_executor/executor.py +355 -0
  22. voidx-3.1.1/src/voidx/agent/graph/tool_executor/guards.py +150 -0
  23. voidx-3.1.1/src/voidx/agent/graph/tool_executor/helpers.py +347 -0
  24. voidx-3.1.1/src/voidx/agent/graph/tool_executor/types.py +86 -0
  25. voidx-3.1.1/src/voidx/agent/graph/tool_executor/ui.py +143 -0
  26. voidx-3.1.1/src/voidx/agent/graph/tool_executor/workflow.py +314 -0
  27. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/turn_runner.py +105 -21
  28. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/wiring.py +2 -0
  29. voidx-3.1.1/src/voidx/agent/graph/workflow_utils.py +33 -0
  30. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/message_rows.py +3 -0
  31. voidx-3.1.1/src/voidx/agent/prompts.py +177 -0
  32. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/runtime_context.py +111 -275
  33. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/slash/handler.py +46 -6
  34. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/task_state.py +5 -7
  35. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/todo_state.py +1 -0
  36. voidx-3.1.1/src/voidx/agent/tool_call_ids.py +33 -0
  37. voidx-3.1.1/src/voidx/agent/tool_exchange_sanitizer.py +158 -0
  38. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/config/models.py +11 -0
  39. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/llm/catalog.py +6 -2
  40. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/llm/compaction.py +144 -8
  41. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/llm/instruction.py +2 -14
  42. voidx-3.1.1/src/voidx/llm/message_status.py +12 -0
  43. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/llm/provider.py +66 -3
  44. voidx-3.1.1/src/voidx/logging/__init__.py +6 -0
  45. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/logging/request_log.py +31 -7
  46. voidx-3.1.1/src/voidx/logging/tool_log.py +75 -0
  47. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/mcp/client/base.py +4 -2
  48. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/mcp/client/stdio_transport.py +3 -1
  49. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/memory/jsonl_store.py +7 -5
  50. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/memory/runtime_state.py +2 -21
  51. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/memory/session.py +5 -0
  52. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/memory/store.py +6 -1
  53. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/permission/engine.py +4 -1
  54. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/permission/evaluate.py +1 -1
  55. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/permission/rules.py +19 -15
  56. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/permission/service.py +1 -1
  57. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/runtime/__init__.py +6 -9
  58. voidx-3.1.1/src/voidx/runtime/intent.py +46 -0
  59. voidx-3.1.1/src/voidx/runtime/task_state.py +229 -0
  60. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/runtime/ui.py +2 -2
  61. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/selfupdate.py +2 -1
  62. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/skills/context.py +0 -52
  63. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/skills/service.py +0 -2
  64. voidx-3.1.1/src/voidx/tools/agent.py +304 -0
  65. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/tools/base.py +79 -11
  66. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/tools/bash.py +59 -16
  67. voidx-3.1.1/src/voidx/tools/bash_router.py +1222 -0
  68. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/tools/clarify.py +11 -55
  69. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/tools/compact_context.py +1 -1
  70. voidx-3.1.1/src/voidx/tools/file_ops/__init__.py +19 -0
  71. voidx-3.1.1/src/voidx/tools/file_ops/edit_execute.py +559 -0
  72. voidx-3.1.1/src/voidx/tools/file_ops/edit_resolve.py +325 -0
  73. voidx-3.1.1/src/voidx/tools/file_ops/file.py +184 -0
  74. voidx-3.1.1/src/voidx/tools/file_ops/line.py +150 -0
  75. voidx-3.1.1/src/voidx/tools/file_ops/read.py +232 -0
  76. voidx-3.1.1/src/voidx/tools/file_ops/types.py +79 -0
  77. voidx-3.1.1/src/voidx/tools/file_ops/write.py +89 -0
  78. voidx-3.1.1/src/voidx/tools/file_state.py +353 -0
  79. voidx-3.1.1/src/voidx/tools/git.py +1335 -0
  80. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/tools/load_doc_template.py +6 -5
  81. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/tools/load_skills.py +4 -3
  82. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/tools/lsp.py +6 -3
  83. voidx-3.1.1/src/voidx/tools/plan_checkpoint.py +254 -0
  84. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/tools/registry.py +4 -4
  85. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/tools/repomap.py +25 -6
  86. voidx-3.1.1/src/voidx/tools/search.py +299 -0
  87. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/tools/task_status.py +1 -1
  88. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/tools/task_tracker.py +3 -6
  89. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/tools/todo.py +1 -1
  90. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/tools/webfetch.py +111 -15
  91. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/tools/websearch.py +3 -2
  92. voidx-3.1.1/src/voidx/tools/workflow.py +553 -0
  93. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/commands.py +3 -0
  94. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/capture.py +2 -5
  95. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/console/app.py +10 -7
  96. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/console/formatting.py +1 -1
  97. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/display_policy.py +13 -9
  98. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/dock/nodes.py +6 -6
  99. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/events/consumers.py +1 -3
  100. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/events/schema.py +0 -4
  101. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/types.py +1 -0
  102. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/session.py +1 -1
  103. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tools/clipboard_image.py +1 -6
  104. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tools/file_picker.py +10 -6
  105. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/choice_mixin.py +15 -4
  106. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/input.py +2 -2
  107. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/overlays.py +4 -2
  108. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/panels.py +1 -1
  109. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/render_activity.py +12 -0
  110. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/render_status.py +7 -10
  111. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/workflow/__init__.py +0 -4
  112. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/workflow/auto_advance.py +2 -2
  113. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/workflow/context.py +1 -4
  114. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/workflow/dag.py +4 -14
  115. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/workflow/nodes.py +71 -70
  116. voidx-3.1.1/src/voidx/workflow/policy.py +82 -0
  117. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/workflow/reconcile.py +47 -5
  118. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/workflow/render.py +0 -23
  119. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/workflow/runtime.py +2 -6
  120. voidx-3.1.1/src/voidx/workflow/service.py +143 -0
  121. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/workflow/types.py +1 -0
  122. {voidx-3.0.0 → voidx-3.1.1}/src/voidx.egg-info/PKG-INFO +4 -1
  123. {voidx-3.0.0 → voidx-3.1.1}/src/voidx.egg-info/SOURCES.txt +28 -49
  124. {voidx-3.0.0 → voidx-3.1.1}/src/voidx.egg-info/requires.txt +4 -0
  125. voidx-3.0.0/src/voidx/agent/agents.py +0 -266
  126. voidx-3.0.0/src/voidx/agent/goal_resolver.py +0 -228
  127. voidx-3.0.0/src/voidx/agent/graph/convergence.py +0 -170
  128. voidx-3.0.0/src/voidx/agent/graph/core.py +0 -954
  129. voidx-3.0.0/src/voidx/agent/graph/tool_executor.py +0 -1226
  130. voidx-3.0.0/src/voidx/config/cli.py +0 -1
  131. voidx-3.0.0/src/voidx/data/intent_classifier.json +0 -1
  132. voidx-3.0.0/src/voidx/logging/__init__.py +0 -1
  133. voidx-3.0.0/src/voidx/runtime/intent.py +0 -163
  134. voidx-3.0.0/src/voidx/runtime/intent_classifier.py +0 -262
  135. voidx-3.0.0/src/voidx/runtime/task_state.py +0 -338
  136. voidx-3.0.0/src/voidx/skills/policy.py +0 -46
  137. voidx-3.0.0/src/voidx/skills/runtime.py +0 -25
  138. voidx-3.0.0/src/voidx/tools/advance_workflow.py +0 -253
  139. voidx-3.0.0/src/voidx/tools/agent.py +0 -187
  140. voidx-3.0.0/src/voidx/tools/file_ops.py +0 -208
  141. voidx-3.0.0/src/voidx/tools/file_state.py +0 -134
  142. voidx-3.0.0/src/voidx/tools/git.py +0 -645
  143. voidx-3.0.0/src/voidx/tools/plan_checkpoint.py +0 -157
  144. voidx-3.0.0/src/voidx/tools/search.py +0 -157
  145. voidx-3.0.0/src/voidx/workflow/policy.py +0 -205
  146. voidx-3.0.0/src/voidx/workflow/service.py +0 -391
  147. voidx-3.0.0/tests/test_auto_advance.py +0 -410
  148. voidx-3.0.0/tests/test_clipboard_image.py +0 -100
  149. voidx-3.0.0/tests/test_clipboard_text.py +0 -22
  150. voidx-3.0.0/tests/test_code_ide.py +0 -75
  151. voidx-3.0.0/tests/test_compaction.py +0 -892
  152. voidx-3.0.0/tests/test_config.py +0 -695
  153. voidx-3.0.0/tests/test_dock_formatting.py +0 -48
  154. voidx-3.0.0/tests/test_goal_resolution_refactor.py +0 -128
  155. voidx-3.0.0/tests/test_instruction_cache.py +0 -88
  156. voidx-3.0.0/tests/test_intent_classifier_phase_a.py +0 -158
  157. voidx-3.0.0/tests/test_llm_catalog.py +0 -46
  158. voidx-3.0.0/tests/test_llm_provider.py +0 -632
  159. voidx-3.0.0/tests/test_llm_usage.py +0 -212
  160. voidx-3.0.0/tests/test_lsp.py +0 -559
  161. voidx-3.0.0/tests/test_main.py +0 -77
  162. voidx-3.0.0/tests/test_main_startup.py +0 -129
  163. voidx-3.0.0/tests/test_mcp.py +0 -377
  164. voidx-3.0.0/tests/test_module_boundaries.py +0 -242
  165. voidx-3.0.0/tests/test_output_browse.py +0 -23
  166. voidx-3.0.0/tests/test_runtime_intent_classifier.py +0 -131
  167. voidx-3.0.0/tests/test_runtime_ui.py +0 -71
  168. voidx-3.0.0/tests/test_scrollback_flush.py +0 -151
  169. voidx-3.0.0/tests/test_selfupdate.py +0 -103
  170. voidx-3.0.0/tests/test_skills.py +0 -1236
  171. voidx-3.0.0/tests/test_startup.py +0 -59
  172. voidx-3.0.0/tests/test_streaming_sanitize.py +0 -103
  173. voidx-3.0.0/tests/test_tree_smoke.py +0 -270
  174. voidx-3.0.0/tests/test_tui_frame_rendering.py +0 -555
  175. voidx-3.0.0/tests/test_tui_input_handling.py +0 -865
  176. voidx-3.0.0/tests/test_tui_output_tree.py +0 -750
  177. voidx-3.0.0/tests/test_tui_paste_handling.py +0 -280
  178. voidx-3.0.0/tests/test_tui_status_activity.py +0 -778
  179. voidx-3.0.0/tests/test_tui_terminal_panels.py +0 -776
  180. voidx-3.0.0/tests/test_ui_diff.py +0 -68
  181. voidx-3.0.0/tests/test_ui_events.py +0 -1118
  182. voidx-3.0.0/tests/test_ui_frontend_protocol.py +0 -150
  183. voidx-3.0.0/tests/test_ui_gateway.py +0 -366
  184. voidx-3.0.0/tests/test_ui_session_changes.py +0 -131
  185. voidx-3.0.0/tests/test_workflow_reconcile.py +0 -309
  186. {voidx-3.0.0 → voidx-3.1.1}/README.md +0 -0
  187. {voidx-3.0.0 → voidx-3.1.1}/setup.cfg +0 -0
  188. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/__init__.py +0 -0
  189. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/attachments.py +0 -0
  190. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/__init__.py +0 -0
  191. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/runtime.py +0 -0
  192. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/session_mixin.py +0 -0
  193. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/title_mixin.py +0 -0
  194. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/todo_events.py +0 -0
  195. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/tool_execution.py +0 -0
  196. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/topology.py +0 -0
  197. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/transcript_mixin.py +0 -0
  198. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/graph/turn_mixin.py +0 -0
  199. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/slash/__init__.py +0 -0
  200. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/slash/code_ide.py +0 -0
  201. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/slash/guide.py +0 -0
  202. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/slash/host.py +0 -0
  203. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/slash/init.py +0 -0
  204. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/slash/lsp.py +0 -0
  205. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/slash/mcp.py +0 -0
  206. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/slash/model.py +0 -0
  207. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/slash/profile.py +0 -0
  208. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/slash/runtime.py +0 -0
  209. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/slash/session.py +0 -0
  210. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/slash/skills.py +0 -0
  211. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/slash/upgrade.py +0 -0
  212. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/state.py +0 -0
  213. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/tool_filters.py +0 -0
  214. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/tool_messages.py +0 -0
  215. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/agent/tool_result_storage.py +0 -0
  216. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/config/__init__.py +0 -0
  217. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/config/enums.py +0 -0
  218. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/config/permissions.py +0 -0
  219. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/config/settings.py +0 -0
  220. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/config/settings_agent.py +0 -0
  221. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/config/settings_api_keys.py +0 -0
  222. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/config/settings_code_ide.py +0 -0
  223. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/config/settings_custom.py +0 -0
  224. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/config/settings_mcp.py +0 -0
  225. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/config/settings_permissions.py +0 -0
  226. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/config/settings_skills.py +0 -0
  227. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/config/settings_update.py +0 -0
  228. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/config/settings_utils.py +0 -0
  229. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/config/settings_web.py +0 -0
  230. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/data/__init__.py +0 -0
  231. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/data/templates/api-doc.md +0 -0
  232. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/data/templates/prd.md +0 -0
  233. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/data/templates/readme.md +0 -0
  234. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/data/templates/rfc.md +0 -0
  235. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/data/templates/tech-design.md +0 -0
  236. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/diffing.py +0 -0
  237. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/llm/__init__.py +0 -0
  238. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/llm/context.py +0 -0
  239. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/llm/message_markers.py +0 -0
  240. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/llm/service.py +0 -0
  241. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/llm/usage.py +0 -0
  242. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/lsp/__init__.py +0 -0
  243. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/lsp/client.py +0 -0
  244. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/lsp/config.py +0 -0
  245. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/lsp/detector.py +0 -0
  246. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/lsp/detector_data.py +0 -0
  247. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/lsp/errors.py +0 -0
  248. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/lsp/manager.py +0 -0
  249. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/lsp/schema.py +0 -0
  250. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/lsp/service.py +0 -0
  251. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/main.py +0 -0
  252. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/mcp/__init__.py +0 -0
  253. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/mcp/client/__init__.py +0 -0
  254. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/mcp/client/errors.py +0 -0
  255. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/mcp/client/http_transport.py +0 -0
  256. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/mcp/client/sse_transport.py +0 -0
  257. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/mcp/manager.py +0 -0
  258. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/mcp/schema.py +0 -0
  259. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/mcp/tool.py +0 -0
  260. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/mcp_servers/__init__.py +0 -0
  261. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/mcp_servers/web.py +0 -0
  262. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/memory/__init__.py +0 -0
  263. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/memory/cleanup.py +0 -0
  264. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/memory/context_frames.py +0 -0
  265. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/memory/model_profiles.py +0 -0
  266. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/memory/service.py +0 -0
  267. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/memory/subagents.py +0 -0
  268. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/memory/transcript.py +0 -0
  269. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/permission/__init__.py +0 -0
  270. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/permission/context.py +0 -0
  271. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/permission/sandbox.py +0 -0
  272. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/permission/schema.py +0 -0
  273. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/permission/wildcard.py +0 -0
  274. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/runtime/attachments.py +0 -0
  275. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/runtime/reference_tokens.py +0 -0
  276. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/runtime/todo.py +0 -0
  277. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/runtime/ui_port.py +0 -0
  278. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/skills/__init__.py +0 -0
  279. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/skills/references.py +0 -0
  280. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/skills/registry.py +0 -0
  281. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/skills/schema.py +0 -0
  282. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/tools/__init__.py +0 -0
  283. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/tools/service.py +0 -0
  284. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/tools/web_content.py +0 -0
  285. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/tools/web_mcp.py +0 -0
  286. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/__init__.py +0 -0
  287. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/frontend.py +0 -0
  288. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/gateway/__init__.py +0 -0
  289. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/gateway/bootstrap.py +0 -0
  290. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/gateway/server.py +0 -0
  291. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/gateway/session.py +0 -0
  292. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/__init__.py +0 -0
  293. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/agent_display.py +0 -0
  294. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/browse.py +0 -0
  295. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/console/__init__.py +0 -0
  296. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/console/streaming.py +0 -0
  297. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/diff.py +0 -0
  298. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/dock/__init__.py +0 -0
  299. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/dock/agent_placeholder.py +0 -0
  300. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/dock/app.py +0 -0
  301. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/dock/formatting.py +0 -0
  302. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/dock/nodes_permission.py +0 -0
  303. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/dock/nodes_startup.py +0 -0
  304. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/dock/nodes_status.py +0 -0
  305. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/dock/state.py +0 -0
  306. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/dock/status.py +0 -0
  307. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/dock/stream.py +0 -0
  308. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/dock/todo.py +0 -0
  309. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/events/__init__.py +0 -0
  310. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/events/bus.py +0 -0
  311. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/output/tree.py +0 -0
  312. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/protocol/__init__.py +0 -0
  313. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/protocol/commands.py +0 -0
  314. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/protocol/envelope.py +0 -0
  315. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/protocol/requests.py +0 -0
  316. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/protocol/schema.py +0 -0
  317. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/protocol/transcript.py +0 -0
  318. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tools/__init__.py +0 -0
  319. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tools/attachment_tokens.py +0 -0
  320. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tools/clipboard_text.py +0 -0
  321. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tools/code_ide.py +0 -0
  322. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tools/skill_picker.py +0 -0
  323. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/transcript.py +0 -0
  324. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/__init__.py +0 -0
  325. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/activity.py +0 -0
  326. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/app.py +0 -0
  327. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/clipboard_mixin.py +0 -0
  328. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/helpers.py +0 -0
  329. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/parser.py +0 -0
  330. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/render_frame.py +0 -0
  331. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/render_input.py +0 -0
  332. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/render_todo.py +0 -0
  333. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/renderer.py +0 -0
  334. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/state.py +0 -0
  335. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/terminal_mixin.py +0 -0
  336. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/ui/tui/text_prompt_mixin.py +0 -0
  337. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/workflow/route.py +0 -0
  338. {voidx-3.0.0 → voidx-3.1.1}/src/voidx/workflow/schema.py +0 -0
  339. {voidx-3.0.0 → voidx-3.1.1}/src/voidx.egg-info/dependency_links.txt +0 -0
  340. {voidx-3.0.0 → voidx-3.1.1}/src/voidx.egg-info/entry_points.txt +0 -0
  341. {voidx-3.0.0 → voidx-3.1.1}/src/voidx.egg-info/top_level.txt +0 -0
  342. {voidx-3.0.0 → voidx-3.1.1}/tests/test_install_sh.py +0 -0
  343. {voidx-3.0.0 → voidx-3.1.1}/tests/test_npm_package.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: voidx
3
- Version: 3.0.0
3
+ Version: 3.1.1
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 = "3.0.0"
3
+ version = "3.1.1"
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__ = "3.0.0"
3
+ __version__ = "3.1.1"
@@ -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)
@@ -0,0 +1,443 @@
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
+ "You are a goal resolver. Classify the user's current turn into intent, goal, workflow, and kind_hint.\n"
234
+ "\n"
235
+ "## Output Schema\n"
236
+ "\n"
237
+ "Return a JSON object matching this template:\n"
238
+ "\n"
239
+ "{\n"
240
+ ' "intent": "coding" or "general",\n'
241
+ ' "goal": null or "<short summary of the user\'s request in their language, 1-2 sentences>",\n'
242
+ ' "workflow": null or "<one of the workflows listed below>",\n'
243
+ ' "kind_hint": null or "<semantic hint: review | debug | feature | inspect | refactor | test | docs>"\n'
244
+ "}\n"
245
+ "\n"
246
+ "## Field Rules\n"
247
+ "\n"
248
+ '- **intent**: "coding" for codebase/workspace work; "general" for non-code conversation.\n'
249
+ "- **goal**: Short user-language summary when a workflow should start; null otherwise. Must be set exactly when workflow is set, and null exactly when workflow is null.\n"
250
+ "- **workflow**: The workflow to start, or null. Must be set exactly when goal is set.\n"
251
+ "- **kind_hint**: Optional semantic hint. Advisory only; never overrides workflow selection.\n"
252
+ "\n"
253
+ "## Available Workflows\n"
254
+ "\n"
255
+ "- brainstorm: Confirm requirements and design, get user approval\n"
256
+ "- debug: Locate root cause and confirm fix direction\n"
257
+ "- design: Produce a structured document that passes the reader test\n"
258
+ "- feedback: Verify and implement valid review feedback\n"
259
+ "- plan: Produce an executable implementation plan, get user approval\n"
260
+ "- review: Initiate structured code review request and collect verdict\n"
261
+ "- tdd: Complete implementation via TDD cycle, all tests green\n"
262
+ "- verify: Prove changes reach expected state with reproducible evidence\n"
263
+ )
264
+
265
+
266
+ def _resolver_request_markdown(user_text: str, task_state: TaskState) -> str:
267
+ recent_content = _recent_exchanges_content(task_state)
268
+ active = ", ".join(_active_workflow_names(task_state)) or "none"
269
+ goal = task_state.current_goal.label if task_state.current_goal is not None else "none"
270
+ sections = [
271
+ "# Context",
272
+ "",
273
+ f"- intent: {task_state.current_intent.value}",
274
+ f"- goal: {goal}",
275
+ f"- active workflows: {active}",
276
+ "",
277
+ "# Recent Conversation",
278
+ "",
279
+ recent_content,
280
+ "",
281
+ "# Current User Question",
282
+ "",
283
+ user_text,
284
+ ]
285
+ return "\n".join(sections)
286
+
287
+
288
+ _ALLOWED_JOIN_NODES = {"debug", "brainstorm", "design", "plan", "tdd", "review", "feedback", "verify"}
289
+
290
+
291
+ def _coerce_resolution(value: object) -> ResolverGoal | None:
292
+ if isinstance(value, ResolverGoal):
293
+ return value
294
+ if isinstance(value, GoalResolution):
295
+ return _resolver_goal_from_goal_resolution(value)
296
+ if isinstance(value, AIMessage):
297
+ value = value.content
298
+ if isinstance(value, str):
299
+ try:
300
+ value = json.loads(value)
301
+ except json.JSONDecodeError:
302
+ return None
303
+ if isinstance(value, dict) and "parsed" in value:
304
+ return _coerce_resolution(value.get("parsed"))
305
+ if isinstance(value, dict):
306
+ if "workflow" not in value and ("plan" in value or isinstance(value.get("goal"), dict) or isinstance(value.get("intent"), dict)):
307
+ value = _legacy_dict_to_resolver_dict(value)
308
+ try:
309
+ return ResolverGoal.model_validate(value)
310
+ except ValueError:
311
+ return None
312
+ return None
313
+
314
+
315
+ def _to_goal_resolution(resolver: ResolverGoal, task_state: TaskState) -> GoalResolution:
316
+ del task_state
317
+ intent_type = TaskIntent(resolver.intent)
318
+ if resolver.goal is None or resolver.workflow is None:
319
+ return GoalResolution(intent=IntentResolution(type=intent_type), goal=None, plan=None)
320
+ return GoalResolution(
321
+ intent=IntentResolution(type=intent_type),
322
+ goal=GoalSpec(desc=resolver.goal),
323
+ plan=PlanResolution(join=resolver.workflow, leave=None),
324
+ )
325
+
326
+
327
+ def _resolver_goal_from_goal_resolution(resolution: GoalResolution) -> ResolverGoal | None:
328
+ goal = resolution.goal
329
+ plan = resolution.plan
330
+ return ResolverGoal(
331
+ intent=resolution.intent.type.value,
332
+ goal=goal.desc if goal is not None else None,
333
+ workflow=plan.join if plan is not None else None,
334
+ kind_hint=None,
335
+ )
336
+
337
+
338
+ def _legacy_dict_to_resolver_dict(value: dict) -> dict:
339
+ intent_value = value.get("intent")
340
+ intent = intent_value.get("type") if isinstance(intent_value, dict) else intent_value
341
+ goal_value = value.get("goal")
342
+ if isinstance(goal_value, dict):
343
+ goal = goal_value.get("desc")
344
+ kind_hint = goal_value.get("type")
345
+ else:
346
+ goal = goal_value
347
+ kind_hint = value.get("kind_hint")
348
+ plan_value = value.get("plan")
349
+ workflow = plan_value.get("join") if isinstance(plan_value, dict) else value.get("workflow")
350
+ return {
351
+ "intent": intent or "general",
352
+ "goal": goal,
353
+ "workflow": workflow,
354
+ "kind_hint": kind_hint,
355
+ }
356
+
357
+
358
+ def _normalize_resolution(
359
+ resolution: GoalResolution,
360
+ user_text: str,
361
+ task_state: TaskState,
362
+ ) -> GoalResolution:
363
+ plan = resolution.plan
364
+ if plan is not None:
365
+ if plan.join and plan.join not in _ALLOWED_JOIN_NODES:
366
+ plan = None
367
+ elif plan.leave and plan.leave not in DEFAULT_WORKFLOW_DAG.nodes:
368
+ plan = PlanResolution(join=plan.join, leave=None)
369
+
370
+ if resolution.intent.type == TaskIntent.GENERAL:
371
+ current_join = _current_active_join(task_state)
372
+ if current_join and task_state.current_goal is not None:
373
+ return GoalResolution(
374
+ intent=IntentResolution(type=TaskIntent.CODING),
375
+ goal=task_state.current_goal,
376
+ plan=PlanResolution(join=current_join, leave=None),
377
+ )
378
+ return GoalResolution(
379
+ intent=resolution.intent,
380
+ goal=None,
381
+ plan=None,
382
+ )
383
+
384
+ goal = resolution.goal
385
+ if goal is not None and (plan is None or not plan.join):
386
+ current_join = _current_active_join(task_state)
387
+ if current_join and task_state.current_goal is not None and _is_short_continuation(user_text):
388
+ return GoalResolution(
389
+ intent=resolution.intent,
390
+ goal=task_state.current_goal,
391
+ plan=PlanResolution(join=current_join, leave=None),
392
+ )
393
+ goal = None
394
+ plan = None
395
+
396
+ return GoalResolution(
397
+ intent=resolution.intent,
398
+ goal=goal,
399
+ plan=plan,
400
+ )
401
+
402
+
403
+ def _recent_exchanges_content(task_state: TaskState) -> str:
404
+ blocks: list[str] = []
405
+ for index, exchange in enumerate(task_state.recent_exchanges, start=1):
406
+ lines: list[str] = [f"## Turn {index}", ""]
407
+ user_text = exchange.user_text.strip()
408
+ assistant_text = exchange.assistant_text.strip()
409
+ if user_text:
410
+ lines.append(f"**Human**: {user_text}")
411
+ if assistant_text:
412
+ lines.append(f"**Assistant**: {assistant_text}")
413
+ if len(lines) <= 2:
414
+ continue
415
+ blocks.append("\n".join(lines))
416
+ return "\n\n".join(blocks)
417
+
418
+
419
+ _SHORT_CONTINUATION_TEXTS = {"ok", "okay", "continue", "go on", "yes", "y", "改", "继续", "继续改", "好", "好的"}
420
+
421
+
422
+ def _is_short_continuation(user_text: str) -> bool:
423
+ text = user_text.strip().lower()
424
+ if text in _SHORT_CONTINUATION_TEXTS:
425
+ return True
426
+ return len(text) <= 8 and _contains_any(text, ("继续", "改", "ok"))
427
+
428
+
429
+ def _current_active_join(task_state: TaskState) -> str:
430
+ if task_state.workflow_route is not None and task_state.workflow_route.join:
431
+ return task_state.workflow_route.join
432
+ for name, run in task_state.workflow_runs.items():
433
+ if getattr(run.status, "value", run.status) == "active":
434
+ return name
435
+ return ""
436
+
437
+
438
+ def _active_workflow_names(task_state: TaskState) -> list[str]:
439
+ return [
440
+ name
441
+ for name, run in task_state.workflow_runs.items()
442
+ if getattr(run.status, "value", run.status) == "active"
443
+ ]
@@ -4,7 +4,11 @@ from __future__ import annotations
4
4
 
5
5
  from typing import TYPE_CHECKING
6
6
 
7
- from voidx.agent.graph.compaction_coordinator import CompactionResult, GraphCompactionCoordinator
7
+ from voidx.agent.graph.compaction_coordinator import (
8
+ CompactionResult,
9
+ GraphCompactionCoordinator,
10
+ PreflightCompactionResult,
11
+ )
8
12
 
9
13
  if TYPE_CHECKING:
10
14
  from voidx.agent.graph.contracts import GraphCompactionHost
@@ -30,32 +34,40 @@ class GraphCompactionMixin:
30
34
  *,
31
35
  force: bool = False,
32
36
  ask: bool = True,
37
+ preflight: bool = False,
33
38
  ) -> tuple[list | None, str | None]:
34
39
  return await _compaction_component_for(self).maybe_compact(
35
40
  messages,
36
41
  session_msgs,
37
42
  force=force,
38
43
  ask=ask,
44
+ preflight=preflight,
39
45
  run_compaction_agent=self._run_compaction_agent,
40
46
  persist_compaction=self._persist_compaction,
41
47
  )
42
48
 
43
- async def _in_turn_compact(
49
+ async def _preflight_compact_if_needed(
44
50
  self: GraphCompactionHost,
45
51
  messages: list,
46
- ) -> CompactionResult | None:
47
- count = getattr(self, "_in_turn_compaction_count", 0) + 1
48
- self._in_turn_compaction_count = count
49
- if count > 2:
50
- return None
51
- return await _compaction_component_for(self).compact_for_live_state(
52
+ session_msgs: list | None = None,
53
+ *,
54
+ force: bool = False,
55
+ reason: str = "threshold",
56
+ ask: bool = False,
57
+ ) -> tuple[CompactionResult | None, PreflightCompactionResult]:
58
+ result, preflight_result = await _compaction_component_for(self).preflight_compact_if_needed(
52
59
  messages,
53
- force=True,
54
- ask=False,
55
- include_summary_message=True,
60
+ session_msgs,
61
+ force=force,
62
+ reason=reason,
63
+ ask=ask,
56
64
  run_compaction_agent=self._run_compaction_agent,
57
65
  persist_compaction=self._persist_compaction,
58
66
  )
67
+ if result is not None:
68
+ self._file_read_coverage.clear()
69
+ self._file_mtimes.clear()
70
+ return result, preflight_result
59
71
 
60
72
  async def _ask_compact(self: GraphCompactionHost, total_tokens: int) -> bool:
61
73
  return await _compaction_component_for(self).ask_compact(total_tokens)
@@ -64,11 +76,15 @@ class GraphCompactionMixin:
64
76
  await _compaction_component_for(self).persist_compaction(head_messages)
65
77
 
66
78
  async def _compact_session_history(self: GraphCompactionHost, *, force: bool = True) -> bool:
67
- return await _compaction_component_for(self).compact_session_history(
79
+ result = await _compaction_component_for(self).compact_session_history(
68
80
  force=force,
69
81
  run_compaction_agent=self._run_compaction_agent,
70
82
  persist_compaction=self._persist_compaction,
71
83
  )
84
+ if result:
85
+ self._file_read_coverage.clear()
86
+ self._file_mtimes.clear()
87
+ return result
72
88
 
73
89
  async def _run_compaction_agent(
74
90
  self: GraphCompactionHost,