voidx 3.0.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 (340) hide show
  1. {voidx-3.0.0 → voidx-3.1.0}/PKG-INFO +4 -1
  2. {voidx-3.0.0 → voidx-3.1.0}/pyproject.toml +3 -2
  3. {voidx-3.0.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-3.1.0/src/voidx/agent/goal_resolver.py +439 -0
  6. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/compaction.py +10 -2
  7. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/compaction_coordinator.py +8 -5
  8. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/contracts.py +2 -1
  9. voidx-3.1.0/src/voidx/agent/graph/convergence.py +87 -0
  10. voidx-3.1.0/src/voidx/agent/graph/core/__init__.py +9 -0
  11. voidx-3.1.0/src/voidx/agent/graph/core/_voidx_graph.py +461 -0
  12. voidx-3.1.0/src/voidx/agent/graph/core/helpers.py +126 -0
  13. voidx-3.1.0/src/voidx/agent/graph/core/llm.py +335 -0
  14. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/permissions.py +2 -14
  15. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/run_loop.py +14 -17
  16. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/runtime_guards.py +20 -23
  17. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/session_runtime.py +15 -5
  18. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/streaming.py +152 -2
  19. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/subagent.py +41 -96
  20. voidx-3.1.0/src/voidx/agent/graph/tool_executor/__init__.py +8 -0
  21. voidx-3.1.0/src/voidx/agent/graph/tool_executor/executor.py +353 -0
  22. voidx-3.1.0/src/voidx/agent/graph/tool_executor/guards.py +150 -0
  23. voidx-3.1.0/src/voidx/agent/graph/tool_executor/helpers.py +346 -0
  24. voidx-3.1.0/src/voidx/agent/graph/tool_executor/types.py +86 -0
  25. voidx-3.1.0/src/voidx/agent/graph/tool_executor/ui.py +143 -0
  26. voidx-3.1.0/src/voidx/agent/graph/tool_executor/workflow.py +314 -0
  27. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/turn_runner.py +95 -19
  28. voidx-3.1.0/src/voidx/agent/graph/workflow_utils.py +33 -0
  29. voidx-3.1.0/src/voidx/agent/prompts.py +177 -0
  30. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/runtime_context.py +111 -275
  31. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/slash/handler.py +46 -6
  32. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/task_state.py +5 -7
  33. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/todo_state.py +1 -0
  34. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/config/models.py +8 -0
  35. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/llm/catalog.py +6 -2
  36. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/llm/instruction.py +2 -14
  37. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/llm/provider.py +66 -3
  38. voidx-3.1.0/src/voidx/logging/__init__.py +6 -0
  39. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/logging/request_log.py +31 -7
  40. voidx-3.1.0/src/voidx/logging/tool_log.py +75 -0
  41. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/mcp/client/base.py +4 -2
  42. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/mcp/client/stdio_transport.py +3 -1
  43. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/memory/jsonl_store.py +7 -5
  44. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/memory/runtime_state.py +2 -21
  45. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/memory/store.py +6 -1
  46. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/permission/engine.py +4 -1
  47. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/permission/evaluate.py +1 -1
  48. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/permission/rules.py +19 -15
  49. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/permission/service.py +1 -1
  50. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/runtime/__init__.py +6 -9
  51. voidx-3.1.0/src/voidx/runtime/intent.py +46 -0
  52. voidx-3.1.0/src/voidx/runtime/task_state.py +229 -0
  53. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/runtime/ui.py +2 -2
  54. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/selfupdate.py +2 -1
  55. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/skills/context.py +0 -52
  56. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/skills/service.py +0 -2
  57. voidx-3.1.0/src/voidx/tools/agent.py +304 -0
  58. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/tools/base.py +37 -6
  59. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/tools/bash.py +59 -16
  60. voidx-3.1.0/src/voidx/tools/bash_router.py +1222 -0
  61. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/tools/clarify.py +11 -55
  62. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/tools/compact_context.py +1 -1
  63. voidx-3.1.0/src/voidx/tools/file_ops/__init__.py +19 -0
  64. voidx-3.1.0/src/voidx/tools/file_ops/edit_execute.py +538 -0
  65. voidx-3.1.0/src/voidx/tools/file_ops/edit_resolve.py +257 -0
  66. voidx-3.1.0/src/voidx/tools/file_ops/file.py +177 -0
  67. voidx-3.1.0/src/voidx/tools/file_ops/line.py +150 -0
  68. voidx-3.1.0/src/voidx/tools/file_ops/read.py +232 -0
  69. voidx-3.1.0/src/voidx/tools/file_ops/types.py +79 -0
  70. voidx-3.1.0/src/voidx/tools/file_ops/write.py +89 -0
  71. voidx-3.1.0/src/voidx/tools/file_state.py +353 -0
  72. voidx-3.1.0/src/voidx/tools/git.py +1335 -0
  73. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/tools/load_doc_template.py +6 -5
  74. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/tools/load_skills.py +4 -3
  75. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/tools/lsp.py +6 -3
  76. voidx-3.1.0/src/voidx/tools/plan_checkpoint.py +254 -0
  77. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/tools/registry.py +4 -4
  78. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/tools/repomap.py +25 -6
  79. voidx-3.1.0/src/voidx/tools/search.py +299 -0
  80. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/tools/task_status.py +1 -1
  81. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/tools/task_tracker.py +3 -6
  82. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/tools/todo.py +1 -1
  83. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/tools/webfetch.py +111 -15
  84. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/tools/websearch.py +3 -2
  85. voidx-3.1.0/src/voidx/tools/workflow.py +553 -0
  86. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/commands.py +3 -0
  87. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/capture.py +2 -5
  88. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/console/app.py +10 -7
  89. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/console/formatting.py +1 -1
  90. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/display_policy.py +13 -9
  91. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/dock/nodes.py +6 -6
  92. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/events/consumers.py +1 -3
  93. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/events/schema.py +0 -4
  94. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/types.py +1 -0
  95. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/session.py +1 -1
  96. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tools/clipboard_image.py +1 -6
  97. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tools/file_picker.py +10 -6
  98. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/choice_mixin.py +15 -4
  99. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/input.py +2 -2
  100. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/overlays.py +4 -2
  101. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/panels.py +1 -1
  102. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/render_activity.py +12 -0
  103. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/render_status.py +7 -10
  104. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/workflow/__init__.py +0 -4
  105. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/workflow/auto_advance.py +2 -2
  106. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/workflow/context.py +1 -4
  107. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/workflow/dag.py +4 -14
  108. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/workflow/nodes.py +71 -70
  109. voidx-3.1.0/src/voidx/workflow/policy.py +82 -0
  110. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/workflow/reconcile.py +47 -5
  111. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/workflow/runtime.py +2 -6
  112. voidx-3.1.0/src/voidx/workflow/service.py +143 -0
  113. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/workflow/types.py +1 -0
  114. {voidx-3.0.0 → voidx-3.1.0}/src/voidx.egg-info/PKG-INFO +4 -1
  115. {voidx-3.0.0 → voidx-3.1.0}/src/voidx.egg-info/SOURCES.txt +25 -49
  116. {voidx-3.0.0 → voidx-3.1.0}/src/voidx.egg-info/requires.txt +4 -0
  117. voidx-3.0.0/src/voidx/agent/agents.py +0 -266
  118. voidx-3.0.0/src/voidx/agent/goal_resolver.py +0 -228
  119. voidx-3.0.0/src/voidx/agent/graph/convergence.py +0 -170
  120. voidx-3.0.0/src/voidx/agent/graph/core.py +0 -954
  121. voidx-3.0.0/src/voidx/agent/graph/tool_executor.py +0 -1226
  122. voidx-3.0.0/src/voidx/config/cli.py +0 -1
  123. voidx-3.0.0/src/voidx/data/intent_classifier.json +0 -1
  124. voidx-3.0.0/src/voidx/logging/__init__.py +0 -1
  125. voidx-3.0.0/src/voidx/runtime/intent.py +0 -163
  126. voidx-3.0.0/src/voidx/runtime/intent_classifier.py +0 -262
  127. voidx-3.0.0/src/voidx/runtime/task_state.py +0 -338
  128. voidx-3.0.0/src/voidx/skills/policy.py +0 -46
  129. voidx-3.0.0/src/voidx/skills/runtime.py +0 -25
  130. voidx-3.0.0/src/voidx/tools/advance_workflow.py +0 -253
  131. voidx-3.0.0/src/voidx/tools/agent.py +0 -187
  132. voidx-3.0.0/src/voidx/tools/file_ops.py +0 -208
  133. voidx-3.0.0/src/voidx/tools/file_state.py +0 -134
  134. voidx-3.0.0/src/voidx/tools/git.py +0 -645
  135. voidx-3.0.0/src/voidx/tools/plan_checkpoint.py +0 -157
  136. voidx-3.0.0/src/voidx/tools/search.py +0 -157
  137. voidx-3.0.0/src/voidx/workflow/policy.py +0 -205
  138. voidx-3.0.0/src/voidx/workflow/service.py +0 -391
  139. voidx-3.0.0/tests/test_auto_advance.py +0 -410
  140. voidx-3.0.0/tests/test_clipboard_image.py +0 -100
  141. voidx-3.0.0/tests/test_clipboard_text.py +0 -22
  142. voidx-3.0.0/tests/test_code_ide.py +0 -75
  143. voidx-3.0.0/tests/test_compaction.py +0 -892
  144. voidx-3.0.0/tests/test_config.py +0 -695
  145. voidx-3.0.0/tests/test_dock_formatting.py +0 -48
  146. voidx-3.0.0/tests/test_goal_resolution_refactor.py +0 -128
  147. voidx-3.0.0/tests/test_instruction_cache.py +0 -88
  148. voidx-3.0.0/tests/test_intent_classifier_phase_a.py +0 -158
  149. voidx-3.0.0/tests/test_llm_catalog.py +0 -46
  150. voidx-3.0.0/tests/test_llm_provider.py +0 -632
  151. voidx-3.0.0/tests/test_llm_usage.py +0 -212
  152. voidx-3.0.0/tests/test_lsp.py +0 -559
  153. voidx-3.0.0/tests/test_main.py +0 -77
  154. voidx-3.0.0/tests/test_main_startup.py +0 -129
  155. voidx-3.0.0/tests/test_mcp.py +0 -377
  156. voidx-3.0.0/tests/test_module_boundaries.py +0 -242
  157. voidx-3.0.0/tests/test_output_browse.py +0 -23
  158. voidx-3.0.0/tests/test_runtime_intent_classifier.py +0 -131
  159. voidx-3.0.0/tests/test_runtime_ui.py +0 -71
  160. voidx-3.0.0/tests/test_scrollback_flush.py +0 -151
  161. voidx-3.0.0/tests/test_selfupdate.py +0 -103
  162. voidx-3.0.0/tests/test_skills.py +0 -1236
  163. voidx-3.0.0/tests/test_startup.py +0 -59
  164. voidx-3.0.0/tests/test_streaming_sanitize.py +0 -103
  165. voidx-3.0.0/tests/test_tree_smoke.py +0 -270
  166. voidx-3.0.0/tests/test_tui_frame_rendering.py +0 -555
  167. voidx-3.0.0/tests/test_tui_input_handling.py +0 -865
  168. voidx-3.0.0/tests/test_tui_output_tree.py +0 -750
  169. voidx-3.0.0/tests/test_tui_paste_handling.py +0 -280
  170. voidx-3.0.0/tests/test_tui_status_activity.py +0 -778
  171. voidx-3.0.0/tests/test_tui_terminal_panels.py +0 -776
  172. voidx-3.0.0/tests/test_ui_diff.py +0 -68
  173. voidx-3.0.0/tests/test_ui_events.py +0 -1118
  174. voidx-3.0.0/tests/test_ui_frontend_protocol.py +0 -150
  175. voidx-3.0.0/tests/test_ui_gateway.py +0 -366
  176. voidx-3.0.0/tests/test_ui_session_changes.py +0 -131
  177. voidx-3.0.0/tests/test_workflow_reconcile.py +0 -309
  178. {voidx-3.0.0 → voidx-3.1.0}/README.md +0 -0
  179. {voidx-3.0.0 → voidx-3.1.0}/setup.cfg +0 -0
  180. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/__init__.py +0 -0
  181. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/attachments.py +0 -0
  182. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/__init__.py +0 -0
  183. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/runtime.py +0 -0
  184. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/session_mixin.py +0 -0
  185. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/title_mixin.py +0 -0
  186. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/todo_events.py +0 -0
  187. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/tool_execution.py +0 -0
  188. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/topology.py +0 -0
  189. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/transcript_mixin.py +0 -0
  190. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/turn_mixin.py +0 -0
  191. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/graph/wiring.py +0 -0
  192. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/message_rows.py +0 -0
  193. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/slash/__init__.py +0 -0
  194. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/slash/code_ide.py +0 -0
  195. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/slash/guide.py +0 -0
  196. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/slash/host.py +0 -0
  197. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/slash/init.py +0 -0
  198. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/slash/lsp.py +0 -0
  199. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/slash/mcp.py +0 -0
  200. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/slash/model.py +0 -0
  201. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/slash/profile.py +0 -0
  202. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/slash/runtime.py +0 -0
  203. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/slash/session.py +0 -0
  204. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/slash/skills.py +0 -0
  205. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/slash/upgrade.py +0 -0
  206. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/state.py +0 -0
  207. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/tool_filters.py +0 -0
  208. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/tool_messages.py +0 -0
  209. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/agent/tool_result_storage.py +0 -0
  210. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/config/__init__.py +0 -0
  211. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/config/enums.py +0 -0
  212. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/config/permissions.py +0 -0
  213. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/config/settings.py +0 -0
  214. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/config/settings_agent.py +0 -0
  215. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/config/settings_api_keys.py +0 -0
  216. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/config/settings_code_ide.py +0 -0
  217. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/config/settings_custom.py +0 -0
  218. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/config/settings_mcp.py +0 -0
  219. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/config/settings_permissions.py +0 -0
  220. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/config/settings_skills.py +0 -0
  221. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/config/settings_update.py +0 -0
  222. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/config/settings_utils.py +0 -0
  223. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/config/settings_web.py +0 -0
  224. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/data/__init__.py +0 -0
  225. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/data/templates/api-doc.md +0 -0
  226. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/data/templates/prd.md +0 -0
  227. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/data/templates/readme.md +0 -0
  228. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/data/templates/rfc.md +0 -0
  229. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/data/templates/tech-design.md +0 -0
  230. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/diffing.py +0 -0
  231. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/llm/__init__.py +0 -0
  232. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/llm/compaction.py +0 -0
  233. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/llm/context.py +0 -0
  234. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/llm/message_markers.py +0 -0
  235. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/llm/service.py +0 -0
  236. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/llm/usage.py +0 -0
  237. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/lsp/__init__.py +0 -0
  238. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/lsp/client.py +0 -0
  239. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/lsp/config.py +0 -0
  240. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/lsp/detector.py +0 -0
  241. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/lsp/detector_data.py +0 -0
  242. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/lsp/errors.py +0 -0
  243. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/lsp/manager.py +0 -0
  244. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/lsp/schema.py +0 -0
  245. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/lsp/service.py +0 -0
  246. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/main.py +0 -0
  247. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/mcp/__init__.py +0 -0
  248. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/mcp/client/__init__.py +0 -0
  249. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/mcp/client/errors.py +0 -0
  250. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/mcp/client/http_transport.py +0 -0
  251. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/mcp/client/sse_transport.py +0 -0
  252. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/mcp/manager.py +0 -0
  253. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/mcp/schema.py +0 -0
  254. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/mcp/tool.py +0 -0
  255. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/mcp_servers/__init__.py +0 -0
  256. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/mcp_servers/web.py +0 -0
  257. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/memory/__init__.py +0 -0
  258. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/memory/cleanup.py +0 -0
  259. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/memory/context_frames.py +0 -0
  260. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/memory/model_profiles.py +0 -0
  261. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/memory/service.py +0 -0
  262. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/memory/session.py +0 -0
  263. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/memory/subagents.py +0 -0
  264. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/memory/transcript.py +0 -0
  265. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/permission/__init__.py +0 -0
  266. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/permission/context.py +0 -0
  267. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/permission/sandbox.py +0 -0
  268. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/permission/schema.py +0 -0
  269. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/permission/wildcard.py +0 -0
  270. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/runtime/attachments.py +0 -0
  271. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/runtime/reference_tokens.py +0 -0
  272. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/runtime/todo.py +0 -0
  273. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/runtime/ui_port.py +0 -0
  274. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/skills/__init__.py +0 -0
  275. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/skills/references.py +0 -0
  276. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/skills/registry.py +0 -0
  277. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/skills/schema.py +0 -0
  278. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/tools/__init__.py +0 -0
  279. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/tools/service.py +0 -0
  280. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/tools/web_content.py +0 -0
  281. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/tools/web_mcp.py +0 -0
  282. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/__init__.py +0 -0
  283. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/frontend.py +0 -0
  284. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/gateway/__init__.py +0 -0
  285. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/gateway/bootstrap.py +0 -0
  286. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/gateway/server.py +0 -0
  287. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/gateway/session.py +0 -0
  288. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/__init__.py +0 -0
  289. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/agent_display.py +0 -0
  290. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/browse.py +0 -0
  291. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/console/__init__.py +0 -0
  292. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/console/streaming.py +0 -0
  293. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/diff.py +0 -0
  294. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/dock/__init__.py +0 -0
  295. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/dock/agent_placeholder.py +0 -0
  296. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/dock/app.py +0 -0
  297. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/dock/formatting.py +0 -0
  298. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/dock/nodes_permission.py +0 -0
  299. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/dock/nodes_startup.py +0 -0
  300. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/dock/nodes_status.py +0 -0
  301. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/dock/state.py +0 -0
  302. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/dock/status.py +0 -0
  303. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/dock/stream.py +0 -0
  304. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/dock/todo.py +0 -0
  305. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/events/__init__.py +0 -0
  306. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/events/bus.py +0 -0
  307. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/output/tree.py +0 -0
  308. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/protocol/__init__.py +0 -0
  309. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/protocol/commands.py +0 -0
  310. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/protocol/envelope.py +0 -0
  311. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/protocol/requests.py +0 -0
  312. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/protocol/schema.py +0 -0
  313. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/protocol/transcript.py +0 -0
  314. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tools/__init__.py +0 -0
  315. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tools/attachment_tokens.py +0 -0
  316. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tools/clipboard_text.py +0 -0
  317. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tools/code_ide.py +0 -0
  318. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tools/skill_picker.py +0 -0
  319. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/transcript.py +0 -0
  320. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/__init__.py +0 -0
  321. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/activity.py +0 -0
  322. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/app.py +0 -0
  323. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/clipboard_mixin.py +0 -0
  324. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/helpers.py +0 -0
  325. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/parser.py +0 -0
  326. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/render_frame.py +0 -0
  327. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/render_input.py +0 -0
  328. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/render_todo.py +0 -0
  329. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/renderer.py +0 -0
  330. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/state.py +0 -0
  331. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/terminal_mixin.py +0 -0
  332. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/ui/tui/text_prompt_mixin.py +0 -0
  333. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/workflow/render.py +0 -0
  334. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/workflow/route.py +0 -0
  335. {voidx-3.0.0 → voidx-3.1.0}/src/voidx/workflow/schema.py +0 -0
  336. {voidx-3.0.0 → voidx-3.1.0}/src/voidx.egg-info/dependency_links.txt +0 -0
  337. {voidx-3.0.0 → voidx-3.1.0}/src/voidx.egg-info/entry_points.txt +0 -0
  338. {voidx-3.0.0 → voidx-3.1.0}/src/voidx.egg-info/top_level.txt +0 -0
  339. {voidx-3.0.0 → voidx-3.1.0}/tests/test_install_sh.py +0 -0
  340. {voidx-3.0.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: 3.0.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 = "3.0.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__ = "3.0.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)
@@ -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,
@@ -20,10 +20,10 @@ from voidx.llm.compaction import (
20
20
  CompactionService,
21
21
  )
22
22
  from voidx.llm.service import resolve_protocol
23
+ from voidx.logging.tool_log import log_tool_event
23
24
  from voidx.llm.usage import estimate_context_tokens, estimate_message_tokens, extract_token_usage
24
25
  from voidx.memory.service import save_context_frame_from_messages
25
26
  from voidx.runtime.ui import StatusFinished, StatusUpdated, StreamingRenderer
26
- from voidx.skills.service import is_skill_context_content
27
27
  from voidx.workflow.service import is_workflow_context_content
28
28
 
29
29
  if TYPE_CHECKING:
@@ -437,6 +437,10 @@ class GraphCompactionCoordinator:
437
437
  type(assistant_msg).__name__,
438
438
  _content_type_summary(getattr(assistant_msg, "content", None)),
439
439
  )
440
+ log_tool_event(
441
+ "compaction_empty_result",
442
+ message=f"Compaction agent returned empty text: message_type={type(assistant_msg).__name__} content_type={_content_type_summary(getattr(assistant_msg, 'content', None))}",
443
+ )
440
444
  return None
441
445
 
442
446
 
@@ -468,10 +472,9 @@ def _runtime_prefix(messages: list[BaseMessage]) -> list[BaseMessage]:
468
472
  continue
469
473
  prefix.append(message)
470
474
  continue
471
- if isinstance(message, HumanMessage) and (
472
- is_skill_context_content(message.content)
473
- or is_workflow_context_content(message.content)
474
- ):
475
+ # Back-compat only: sessions created before workflow runtime moved into
476
+ # the stable SystemMessage may still have persisted workflow prefixes.
477
+ if isinstance(message, HumanMessage) and is_workflow_context_content(message.content):
475
478
  prefix.append(message)
476
479
  continue
477
480
  break
@@ -78,7 +78,8 @@ class GraphToolExecutionHost(Protocol):
78
78
  _workspace: str
79
79
  _app: Any | None
80
80
  _debug: bool
81
- _file_mtimes: dict[str, float]
81
+ _file_mtimes: dict[str, dict[str, int]]
82
+ _file_read_coverage: dict[str, dict]
82
83
  _turn_node: Any | None
83
84
  _current_messages: list[BaseMessage] | None
84
85
  _permission: PermissionService