voidx 1.0.1__tar.gz → 2.0.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 (203) hide show
  1. {voidx-1.0.1 → voidx-2.0.0}/PKG-INFO +30 -6
  2. voidx-2.0.0/README.md +61 -0
  3. {voidx-1.0.1 → voidx-2.0.0}/pyproject.toml +6 -3
  4. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/__init__.py +1 -1
  5. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/agent/agents.py +28 -24
  6. voidx-2.0.0/src/voidx/agent/graph/__init__.py +5 -0
  7. {voidx-1.0.1/src/voidx/agent/graph_components → voidx-2.0.0/src/voidx/agent/graph}/compaction.py +112 -69
  8. voidx-2.0.0/src/voidx/agent/graph/contracts.py +141 -0
  9. voidx-1.0.1/src/voidx/agent/graph.py → voidx-2.0.0/src/voidx/agent/graph/core.py +90 -79
  10. {voidx-1.0.1/src/voidx/agent/graph_components → voidx-2.0.0/src/voidx/agent/graph}/permissions.py +17 -22
  11. {voidx-1.0.1/src/voidx/agent/graph_components → voidx-2.0.0/src/voidx/agent/graph}/run_loop.py +211 -126
  12. {voidx-1.0.1/src/voidx/agent/graph_components → voidx-2.0.0/src/voidx/agent/graph}/runtime.py +1 -1
  13. {voidx-1.0.1/src/voidx/agent/graph_components → voidx-2.0.0/src/voidx/agent/graph}/streaming.py +6 -7
  14. {voidx-1.0.1/src/voidx/agent/graph_components → voidx-2.0.0/src/voidx/agent/graph}/subagent.py +25 -16
  15. {voidx-1.0.1/src/voidx/agent/graph_components → voidx-2.0.0/src/voidx/agent/graph}/tool_execution.py +141 -23
  16. voidx-2.0.0/src/voidx/agent/intent_refinement.py +241 -0
  17. voidx-2.0.0/src/voidx/agent/message_rows.py +36 -0
  18. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/agent/runtime_context.py +46 -17
  19. voidx-2.0.0/src/voidx/agent/slash/__init__.py +5 -0
  20. {voidx-1.0.1/src/voidx/agent/slash_components → voidx-2.0.0/src/voidx/agent/slash}/code_ide.py +2 -2
  21. voidx-1.0.1/src/voidx/agent/slash.py → voidx-2.0.0/src/voidx/agent/slash/handler.py +124 -106
  22. {voidx-1.0.1/src/voidx/agent/slash_components → voidx-2.0.0/src/voidx/agent/slash}/lsp.py +1 -1
  23. {voidx-1.0.1/src/voidx/agent/slash_components → voidx-2.0.0/src/voidx/agent/slash}/mcp.py +74 -10
  24. {voidx-1.0.1/src/voidx/agent/slash_components → voidx-2.0.0/src/voidx/agent/slash}/model.py +52 -30
  25. {voidx-1.0.1/src/voidx/agent/slash_components → voidx-2.0.0/src/voidx/agent/slash}/runtime.py +3 -5
  26. {voidx-1.0.1/src/voidx/agent/slash_components → voidx-2.0.0/src/voidx/agent/slash}/skills.py +1 -1
  27. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/agent/state.py +16 -10
  28. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/agent/task_state.py +79 -50
  29. voidx-2.0.0/src/voidx/agent/tool_messages.py +42 -0
  30. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/config.py +53 -31
  31. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/llm/catalog.py +4 -4
  32. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/llm/compaction.py +33 -9
  33. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/llm/instruction.py +28 -1
  34. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/lsp/service.py +3 -0
  35. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/main.py +6 -19
  36. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/mcp/__init__.py +2 -1
  37. voidx-2.0.0/src/voidx/mcp/client.py +723 -0
  38. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/mcp/manager.py +104 -50
  39. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/mcp/schema.py +51 -3
  40. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/mcp/tool.py +7 -54
  41. voidx-2.0.0/src/voidx/memory/__init__.py +18 -0
  42. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/memory/context_frames.py +1 -2
  43. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/memory/model_profiles.py +36 -41
  44. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/memory/runtime_state.py +140 -31
  45. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/memory/session.py +46 -55
  46. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/memory/store.py +33 -9
  47. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/memory/transcript.py +1 -6
  48. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/permission/engine.py +168 -31
  49. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/permission/sandbox.py +27 -3
  50. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/permission/service.py +1 -1
  51. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/skills/__init__.py +5 -0
  52. voidx-2.0.0/src/voidx/skills/bundled/superpowers/brainstorming/SKILL.md +51 -0
  53. voidx-2.0.0/src/voidx/skills/bundled/superpowers/receiving-code-review/SKILL.md +60 -0
  54. voidx-2.0.0/src/voidx/skills/bundled/superpowers/requesting-code-review/SKILL.md +59 -0
  55. voidx-2.0.0/src/voidx/skills/bundled/superpowers/systematic-debugging/SKILL.md +64 -0
  56. voidx-2.0.0/src/voidx/skills/bundled/superpowers/test-driven-development/SKILL.md +61 -0
  57. voidx-2.0.0/src/voidx/skills/bundled/superpowers/verification-before-completion/SKILL.md +69 -0
  58. voidx-2.0.0/src/voidx/skills/bundled/superpowers/writing-design-docs/SKILL.md +87 -0
  59. voidx-2.0.0/src/voidx/skills/bundled/superpowers/writing-design-docs/templates/api-doc.md +64 -0
  60. voidx-2.0.0/src/voidx/skills/bundled/superpowers/writing-design-docs/templates/prd.md +117 -0
  61. voidx-2.0.0/src/voidx/skills/bundled/superpowers/writing-design-docs/templates/readme.md +55 -0
  62. voidx-2.0.0/src/voidx/skills/bundled/superpowers/writing-design-docs/templates/rfc.md +38 -0
  63. voidx-2.0.0/src/voidx/skills/bundled/superpowers/writing-design-docs/templates/tech-design.md +68 -0
  64. voidx-2.0.0/src/voidx/skills/bundled/superpowers/writing-plans/SKILL.md +54 -0
  65. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/skills/policy.py +7 -2
  66. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/skills/registry.py +50 -11
  67. voidx-2.0.0/src/voidx/skills/runtime.py +90 -0
  68. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/skills/service.py +37 -2
  69. voidx-2.0.0/src/voidx/tools/__init__.py +12 -0
  70. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/tools/base.py +64 -8
  71. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/tools/bash.py +61 -7
  72. voidx-2.0.0/src/voidx/tools/clarify.py +139 -0
  73. voidx-2.0.0/src/voidx/tools/doc_template.py +96 -0
  74. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/tools/file_ops.py +20 -5
  75. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/tools/lsp.py +4 -4
  76. voidx-2.0.0/src/voidx/tools/on_intent.py +122 -0
  77. voidx-2.0.0/src/voidx/tools/plan_checkpoint.py +161 -0
  78. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/tools/registry.py +19 -3
  79. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/tools/repomap.py +1 -13
  80. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/tools/search.py +6 -17
  81. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/tools/task_status.py +1 -1
  82. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/tools/task_tracker.py +34 -13
  83. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/tools/todo.py +1 -1
  84. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/tools/web_mcp.py +1 -1
  85. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/tools/webfetch.py +1 -0
  86. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/tools/websearch.py +1 -0
  87. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/ui/commands.py +1 -1
  88. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/ui/frontend.py +1 -1
  89. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/ui/gateway/session.py +2 -2
  90. voidx-2.0.0/src/voidx/ui/output/__init__.py +23 -0
  91. {voidx-1.0.1/src/voidx/ui → voidx-2.0.0/src/voidx/ui/output}/browse.py +4 -4
  92. {voidx-1.0.1/src/voidx/ui → voidx-2.0.0/src/voidx/ui/output}/capture.py +12 -12
  93. voidx-2.0.0/src/voidx/ui/output/console/__init__.py +5 -0
  94. voidx-1.0.1/src/voidx/ui/console.py → voidx-2.0.0/src/voidx/ui/output/console/app.py +18 -79
  95. {voidx-1.0.1/src/voidx/ui/console_components → voidx-2.0.0/src/voidx/ui/output/console}/formatting.py +6 -4
  96. {voidx-1.0.1/src/voidx/ui/console_components → voidx-2.0.0/src/voidx/ui/output/console}/streaming.py +12 -12
  97. {voidx-1.0.1/src/voidx/ui → voidx-2.0.0/src/voidx/ui/output}/diff.py +1 -1
  98. voidx-2.0.0/src/voidx/ui/output/dock/__init__.py +13 -0
  99. voidx-1.0.1/src/voidx/ui/dock.py → voidx-2.0.0/src/voidx/ui/output/dock/app.py +97 -10
  100. {voidx-1.0.1/src/voidx/ui/dock_components → voidx-2.0.0/src/voidx/ui/output/dock}/nodes.py +57 -22
  101. {voidx-1.0.1/src/voidx/ui/dock_components → voidx-2.0.0/src/voidx/ui/output/dock}/state.py +2 -0
  102. voidx-2.0.0/src/voidx/ui/output/events/__init__.py +409 -0
  103. {voidx-1.0.1/src/voidx/ui → voidx-2.0.0/src/voidx/ui/output}/tree.py +191 -18
  104. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/ui/protocol/envelope.py +1 -1
  105. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/ui/protocol/requests.py +1 -1
  106. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/ui/protocol/transcript.py +1 -1
  107. voidx-2.0.0/src/voidx/ui/session.py +347 -0
  108. voidx-2.0.0/src/voidx/ui/tools/__init__.py +24 -0
  109. {voidx-1.0.1/src/voidx/ui → voidx-2.0.0/src/voidx/ui/tools}/clipboard_image.py +32 -31
  110. {voidx-1.0.1/src/voidx/ui → voidx-2.0.0/src/voidx/ui/tools}/file_picker.py +50 -55
  111. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/ui/transcript.py +1 -1
  112. voidx-2.0.0/src/voidx/ui/tui/__init__.py +4 -0
  113. voidx-2.0.0/src/voidx/ui/tui/app.py +628 -0
  114. voidx-2.0.0/src/voidx/ui/tui/helpers.py +147 -0
  115. voidx-2.0.0/src/voidx/ui/tui/input.py +226 -0
  116. voidx-2.0.0/src/voidx/ui/tui/panels.py +215 -0
  117. voidx-2.0.0/src/voidx/ui/tui/parser.py +395 -0
  118. voidx-2.0.0/src/voidx/ui/tui/renderer.py +592 -0
  119. {voidx-1.0.1 → voidx-2.0.0}/src/voidx.egg-info/PKG-INFO +30 -6
  120. {voidx-1.0.1 → voidx-2.0.0}/src/voidx.egg-info/SOURCES.txt +65 -41
  121. {voidx-1.0.1 → voidx-2.0.0}/src/voidx.egg-info/requires.txt +1 -1
  122. {voidx-1.0.1 → voidx-2.0.0}/tests/test_clipboard_image.py +1 -1
  123. {voidx-1.0.1 → voidx-2.0.0}/tests/test_code_ide.py +1 -1
  124. voidx-2.0.0/tests/test_compaction.py +386 -0
  125. {voidx-1.0.1 → voidx-2.0.0}/tests/test_config.py +52 -18
  126. voidx-2.0.0/tests/test_main.py +77 -0
  127. {voidx-1.0.1 → voidx-2.0.0}/tests/test_mcp.py +107 -0
  128. voidx-2.0.0/tests/test_npm_package.py +327 -0
  129. voidx-2.0.0/tests/test_pure_tui.py +1816 -0
  130. voidx-2.0.0/tests/test_scrollback_flush.py +151 -0
  131. {voidx-1.0.1 → voidx-2.0.0}/tests/test_skills.py +157 -2
  132. {voidx-1.0.1 → voidx-2.0.0}/tests/test_startup.py +3 -3
  133. {voidx-1.0.1 → voidx-2.0.0}/tests/test_tree_smoke.py +1 -1
  134. {voidx-1.0.1 → voidx-2.0.0}/tests/test_ui_diff.py +1 -1
  135. {voidx-1.0.1 → voidx-2.0.0}/tests/test_ui_events.py +5 -6
  136. {voidx-1.0.1 → voidx-2.0.0}/tests/test_ui_frontend_protocol.py +2 -2
  137. {voidx-1.0.1 → voidx-2.0.0}/tests/test_ui_gateway.py +3 -3
  138. voidx-2.0.0/tests/test_ui_session_changes.py +95 -0
  139. voidx-1.0.1/README.md +0 -37
  140. voidx-1.0.1/src/voidx/agent/graph_components/__init__.py +0 -1
  141. voidx-1.0.1/src/voidx/agent/slash_components/__init__.py +0 -1
  142. voidx-1.0.1/src/voidx/mcp/client.py +0 -458
  143. voidx-1.0.1/src/voidx/skills/bundled/superpowers/receiving-code-review/SKILL.md +0 -30
  144. voidx-1.0.1/src/voidx/skills/bundled/superpowers/requesting-code-review/SKILL.md +0 -27
  145. voidx-1.0.1/src/voidx/skills/bundled/superpowers/systematic-debugging/SKILL.md +0 -36
  146. voidx-1.0.1/src/voidx/skills/bundled/superpowers/test-driven-development/SKILL.md +0 -33
  147. voidx-1.0.1/src/voidx/skills/bundled/superpowers/verification-before-completion/SKILL.md +0 -31
  148. voidx-1.0.1/src/voidx/skills/bundled/superpowers/writing-plans/SKILL.md +0 -27
  149. voidx-1.0.1/src/voidx/tools/__init__.py +0 -0
  150. voidx-1.0.1/src/voidx/ui/__init__.py +0 -0
  151. voidx-1.0.1/src/voidx/ui/console_components/__init__.py +0 -1
  152. voidx-1.0.1/src/voidx/ui/dock_components/__init__.py +0 -1
  153. voidx-1.0.1/src/voidx/ui/event_components/__init__.py +0 -1
  154. voidx-1.0.1/src/voidx/ui/events.py +0 -378
  155. voidx-1.0.1/src/voidx/ui/session_changes.py +0 -163
  156. voidx-1.0.1/src/voidx/ui/startup.py +0 -162
  157. voidx-1.0.1/src/voidx/ui/tui.py +0 -1603
  158. voidx-1.0.1/tests/test_main.py +0 -91
  159. voidx-1.0.1/tests/test_npm_package.py +0 -101
  160. voidx-1.0.1/tests/test_pure_tui.py +0 -742
  161. voidx-1.0.1/tests/test_ui_session_changes.py +0 -31
  162. {voidx-1.0.1 → voidx-2.0.0}/setup.cfg +0 -0
  163. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/agent/__init__.py +0 -0
  164. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/agent/attachments.py +0 -0
  165. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/agent/tool_filters.py +0 -0
  166. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/llm/__init__.py +0 -0
  167. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/llm/context.py +0 -0
  168. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/llm/provider.py +0 -0
  169. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/llm/usage.py +0 -0
  170. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/lsp/__init__.py +0 -0
  171. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/lsp/client.py +0 -0
  172. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/lsp/config.py +0 -0
  173. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/lsp/detector.py +0 -0
  174. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/lsp/errors.py +0 -0
  175. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/lsp/manager.py +0 -0
  176. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/lsp/schema.py +0 -0
  177. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/mcp_servers/__init__.py +0 -0
  178. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/mcp_servers/web.py +0 -0
  179. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/permission/__init__.py +0 -0
  180. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/permission/evaluate.py +0 -0
  181. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/permission/schema.py +0 -0
  182. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/permission/wildcard.py +0 -0
  183. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/skills/schema.py +0 -0
  184. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/tools/agent.py +0 -0
  185. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/tools/web_content.py +0 -0
  186. {voidx-1.0.1/src/voidx/memory → voidx-2.0.0/src/voidx/ui}/__init__.py +0 -0
  187. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/ui/gateway/__init__.py +0 -0
  188. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/ui/gateway/bootstrap.py +0 -0
  189. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/ui/gateway/server.py +0 -0
  190. {voidx-1.0.1/src/voidx/ui/dock_components → voidx-2.0.0/src/voidx/ui/output/dock}/formatting.py +0 -0
  191. {voidx-1.0.1/src/voidx/ui/event_components → voidx-2.0.0/src/voidx/ui/output/events}/schema.py +0 -0
  192. {voidx-1.0.1/src/voidx/ui → voidx-2.0.0/src/voidx/ui/output}/types.py +0 -0
  193. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/ui/protocol/__init__.py +0 -0
  194. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/ui/protocol/commands.py +0 -0
  195. {voidx-1.0.1 → voidx-2.0.0}/src/voidx/ui/protocol/schema.py +0 -0
  196. {voidx-1.0.1/src/voidx/ui → voidx-2.0.0/src/voidx/ui/tools}/attachment_tokens.py +0 -0
  197. {voidx-1.0.1/src/voidx/ui → voidx-2.0.0/src/voidx/ui/tools}/code_ide.py +0 -0
  198. {voidx-1.0.1 → voidx-2.0.0}/src/voidx.egg-info/dependency_links.txt +0 -0
  199. {voidx-1.0.1 → voidx-2.0.0}/src/voidx.egg-info/entry_points.txt +0 -0
  200. {voidx-1.0.1 → voidx-2.0.0}/src/voidx.egg-info/top_level.txt +0 -0
  201. {voidx-1.0.1 → voidx-2.0.0}/tests/test_llm_provider.py +0 -0
  202. {voidx-1.0.1 → voidx-2.0.0}/tests/test_llm_usage.py +0 -0
  203. {voidx-1.0.1 → voidx-2.0.0}/tests/test_lsp.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: voidx
3
- Version: 1.0.1
3
+ Version: 2.0.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
@@ -14,7 +14,7 @@ Requires-Dist: typer>=0.15.0
14
14
  Requires-Dist: rich>=13.9.0
15
15
  Requires-Dist: tiktoken>=0.8.0
16
16
  Requires-Dist: httpx>=0.28.0
17
- Requires-Dist: websockets>=16.0
17
+ Requires-Dist: websockets>=14
18
18
  Provides-Extra: dev
19
19
  Requires-Dist: build>=1.2.0; extra == "dev"
20
20
  Requires-Dist: pytest>=8.0.0; extra == "dev"
@@ -26,22 +26,46 @@ voidx is a terminal AI coding agent built in Python.
26
26
 
27
27
  ## Install
28
28
 
29
- Python users can install the canonical package from PyPI:
29
+ ### One-line install (no Python or npm required)
30
+
31
+ macOS / Linux:
32
+
33
+ ```bash
34
+ curl -fsSL https://raw.githubusercontent.com/chikhamx/voidx/main/scripts/install.sh | bash
35
+ ```
36
+
37
+ Windows (PowerShell):
38
+
39
+ ```powershell
40
+ irm https://raw.githubusercontent.com/chikhamx/voidx/main/scripts/install.ps1 | iex
41
+ ```
42
+
43
+ The installer downloads a standalone Python runtime and sets up voidx in an
44
+ isolated environment — nothing else is needed on your machine.
45
+
46
+ ### pip
30
47
 
31
48
  ```bash
32
49
  pip install voidx
33
50
  voidx
34
51
  ```
35
52
 
36
- Node users can install the npm launcher. The launcher requires Python 3.11+
37
- on the machine and installs the matching Python package into an isolated
38
- user-local virtual environment on first run:
53
+ ### npm
39
54
 
40
55
  ```bash
41
56
  npm install -g @chikhamx/voidx
42
57
  voidx
43
58
  ```
44
59
 
60
+ ### China / slow network
61
+
62
+ Set mirror environment variables before running any install method:
63
+
64
+ ```bash
65
+ export VOIDX_PYTHON_MIRROR=https://npmmirror.com/mirrors/python-standalone
66
+ export VOIDX_PIP_INDEX=https://pypi.tuna.tsinghua.edu.cn/simple
67
+ ```
68
+
45
69
  ## Useful Commands
46
70
 
47
71
  ```bash
voidx-2.0.0/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # voidx
2
+
3
+ voidx is a terminal AI coding agent built in Python.
4
+
5
+ ## Install
6
+
7
+ ### One-line install (no Python or npm required)
8
+
9
+ macOS / Linux:
10
+
11
+ ```bash
12
+ curl -fsSL https://raw.githubusercontent.com/chikhamx/voidx/main/scripts/install.sh | bash
13
+ ```
14
+
15
+ Windows (PowerShell):
16
+
17
+ ```powershell
18
+ irm https://raw.githubusercontent.com/chikhamx/voidx/main/scripts/install.ps1 | iex
19
+ ```
20
+
21
+ The installer downloads a standalone Python runtime and sets up voidx in an
22
+ isolated environment — nothing else is needed on your machine.
23
+
24
+ ### pip
25
+
26
+ ```bash
27
+ pip install voidx
28
+ voidx
29
+ ```
30
+
31
+ ### npm
32
+
33
+ ```bash
34
+ npm install -g @chikhamx/voidx
35
+ voidx
36
+ ```
37
+
38
+ ### China / slow network
39
+
40
+ Set mirror environment variables before running any install method:
41
+
42
+ ```bash
43
+ export VOIDX_PYTHON_MIRROR=https://npmmirror.com/mirrors/python-standalone
44
+ export VOIDX_PIP_INDEX=https://pypi.tuna.tsinghua.edu.cn/simple
45
+ ```
46
+
47
+ ## Useful Commands
48
+
49
+ ```bash
50
+ voidx version
51
+ voidx sessions
52
+ voidx -w /path/to/project
53
+ ```
54
+
55
+ ## Development
56
+
57
+ ```bash
58
+ .venv/bin/python -m pytest
59
+ .venv/bin/python scripts/package.py --format all --clean
60
+ npm --prefix npm run check
61
+ ```
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "voidx"
3
- version = "1.0.1"
3
+ version = "2.0.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"
@@ -15,7 +15,7 @@ dependencies = [
15
15
  "rich>=13.9.0",
16
16
  "tiktoken>=0.8.0",
17
17
  "httpx>=0.28.0",
18
- "websockets>=16.0",
18
+ "websockets>=14",
19
19
  ]
20
20
 
21
21
  [project.scripts]
@@ -43,7 +43,10 @@ build-backend = "setuptools.build_meta"
43
43
  where = ["src"]
44
44
 
45
45
  [tool.setuptools.package-data]
46
- "voidx.skills" = ["bundled/superpowers/*/SKILL.md"]
46
+ "voidx.skills" = [
47
+ "bundled/superpowers/*/SKILL.md",
48
+ "bundled/superpowers/*/templates/*.md",
49
+ ]
47
50
 
48
51
  [tool.pytest.ini_options]
49
52
  asyncio_mode = "auto"
@@ -1,3 +1,3 @@
1
1
  """VoidX - A coding agent that quantifies everything."""
2
2
 
3
- __version__ = "1.0.1"
3
+ __version__ = "2.0.0"
@@ -83,8 +83,8 @@ surgical edits directly when that is the shortest safe path.
83
83
  the user explicitly approves.
84
84
  - Fix/implement/modify → edit directly for small scoped changes, or delegate
85
85
  broad/isolated work to implement.
86
- - Ambiguous → continue with read-only investigation when useful. Ask one
87
- clarifying question before edits, unsafe bash, or implement delegation.
86
+ - Ambiguous → continue with read-only investigation when useful. Use clarify
87
+ for one structured question before edits, unsafe bash, or implement delegation.
88
88
 
89
89
  Words like "看看", "分析", "梳理", "有什么建议", "如何设计", "优化方案",
90
90
  "look at", "analyze", "suggest", and "proposal" do NOT imply permission
@@ -92,11 +92,15 @@ surgical edits directly when that is the shortest safe path.
92
92
  says to modify code.
93
93
 
94
94
  1. **Chat / explain** — just answer. No tools unless you need to look something up.
95
+ If Current Task State says intent is chat or ambiguous, but the user request
96
+ appears to require workspace action, call on_intent before other workspace tools.
95
97
 
96
98
  2. **Simple search** — grab read/glob/grep and find it yourself. Only send explore
97
99
  for broad searches across many files.
98
100
 
99
- 3. **Design / plan** — hand off to plan for architecture questions.
101
+ 3. **Design / plan** — hand off to plan for architecture questions. For
102
+ non-trivial implementation plans, call plan_checkpoint before changing files,
103
+ running write-capable commands, or delegating implement.
100
104
 
101
105
  4. **Code changes**
102
106
  - Small, local, or mechanical changes → read first, then call write/edit
@@ -109,9 +113,9 @@ surgical edits directly when that is the shortest safe path.
109
113
  reporting completion.
110
114
  - If review says FAIL or NEEDS_CHANGE → fix, review again.
111
115
 
112
- 5. **Unclear intent** — ask. One specific clarifying question is better than five
113
- assumptions. "When you say 'broken', do you mean it crashes, returns wrong data,
114
- or something else?"
116
+ 5. **Unclear intent** — ask through clarify. One specific clarifying question is
117
+ better than five assumptions. "When you say 'broken', do you mean it crashes,
118
+ returns wrong data, or something else?"
115
119
 
116
120
  ## Rules
117
121
 
@@ -119,6 +123,8 @@ surgical edits directly when that is the shortest safe path.
119
123
  - In plan mode, do not call write/edit/lsp_format, unsafe bash, or implement.
120
124
  - Ambiguous implementation intent is not enough for write/edit/lsp_format,
121
125
  unsafe bash, or implement delegation.
126
+ - Child agents do not interact with the user. If a child plan result needs user
127
+ approval or clarification, call plan_checkpoint or clarify yourself.
122
128
  - Don't tell the user "done" until changes are verified.
123
129
  - Child agents have isolated context — give them complete, self-contained briefs.
124
130
 
@@ -257,14 +263,12 @@ class AgentDef(BaseModel):
257
263
 
258
264
  @property
259
265
  def role_prompt(self) -> str:
260
- prompts = {
261
- "orchestrator": ORCHESTRATOR_PROMPT,
262
- "explore": EXPLORE_PROMPT,
263
- "plan": PLAN_PROMPT,
264
- "implement": IMPLEMENT_PROMPT,
265
- "review": REVIEW_PROMPT,
266
- }
267
- return prompts.get(self.name, "")
266
+ if self.name in PROMPTLESS_AGENTS:
267
+ return ""
268
+ try:
269
+ return ROLE_PROMPTS[self.name]
270
+ except KeyError as exc:
271
+ raise ValueError(f"No role prompt registered for agent: {self.name}") from exc
268
272
 
269
273
  @property
270
274
  def tool_contract(self) -> str:
@@ -284,9 +288,14 @@ class AgentDef(BaseModel):
284
288
  lines.append("- Constraint: this role must not start another child agent.")
285
289
  return "\n".join(lines)
286
290
 
287
- @property
288
- def prompt(self) -> str:
289
- return self.role_prompt
291
+ ROLE_PROMPTS = {
292
+ "orchestrator": ORCHESTRATOR_PROMPT,
293
+ "explore": EXPLORE_PROMPT,
294
+ "plan": PLAN_PROMPT,
295
+ "implement": IMPLEMENT_PROMPT,
296
+ "review": REVIEW_PROMPT,
297
+ }
298
+ PROMPTLESS_AGENTS = {"compaction", "title"}
290
299
 
291
300
 
292
301
  # ── built-in agents ────────────────────────────────────────────────────────
@@ -298,6 +307,7 @@ BUILTIN_AGENTS: dict[str, AgentDef] = {
298
307
  "delegates broad work to specialists, reviews results.",
299
308
  when_to_use="Default agent for all user interactions. Always use first.",
300
309
  tools=[
310
+ "on_intent", "clarify", "plan_checkpoint",
301
311
  "read", "glob", "grep", "bash", "agent", "task_status", "todo",
302
312
  "webfetch", "websearch", "repo_map",
303
313
  "lsp_diagnostics", "lsp_symbols", "lsp_definition", "lsp_references",
@@ -397,9 +407,7 @@ COMPACTION_PROMPT = """You are voidx compaction agent. Your job is to generate a
397
407
  structured summary of the conversation history to free context space.
398
408
 
399
409
  You have NO tools. Just read the conversation history below and output the
400
- summary in the exact format specified.
401
-
402
- """ + "Use template defined in CompactionService."
410
+ summary in the exact format specified."""
403
411
 
404
412
  TITLE_PROMPT = """You are voidx title agent. Generate a short, descriptive
405
413
  title (max 80 chars) for this conversation based on the first user message.
@@ -433,7 +441,3 @@ def child_agent_descriptions_for_llm() -> str:
433
441
  f" Write access: {agent.can_write}"
434
442
  )
435
443
  return "\n".join(lines)
436
-
437
-
438
- def subagent_descriptions_for_llm() -> str:
439
- return child_agent_descriptions_for_llm()
@@ -0,0 +1,5 @@
1
+ """Agent graph — LangGraph state machine with 5-agent system."""
2
+
3
+ from voidx.agent.graph.core import VoidXGraph
4
+
5
+ __all__ = ["VoidXGraph"]
@@ -2,28 +2,40 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from langchain_core.messages import AIMessage, HumanMessage, SystemMessage, ToolMessage
5
+ from typing import TYPE_CHECKING
6
6
 
7
- from voidx.agent.graph_components.runtime import console, ui
8
- from voidx.agent.graph_components.streaming import extract_text, stream_llm
7
+ from langchain_core.messages import AIMessage, SystemMessage
8
+
9
+ from voidx.agent.message_rows import messages_from_rows
10
+ from voidx.agent.graph.runtime import console, ui
11
+ from voidx.agent.graph.streaming import extract_text, stream_llm
12
+ from voidx.llm.compaction import COMPACTION_MAX_RETRIES, CompactionService
9
13
  from voidx.llm.provider import resolve_protocol
10
14
  from voidx.llm.usage import estimate_context_tokens, estimate_message_tokens, extract_token_usage
11
15
  from voidx.memory.context_frames import save_context_frame_from_messages
12
- from voidx.ui.console import StreamingRenderer
13
- from voidx.ui.dock import dock
14
- from voidx.ui.events import StatusFinished, StatusUpdated, ui_events, via_events
16
+ from voidx.ui.output.console import StreamingRenderer
17
+ from voidx.ui.output.dock import dock
18
+ from voidx.ui.output.events import StatusFinished, StatusUpdated, ui_events, via_events
19
+
20
+ if TYPE_CHECKING:
21
+ from voidx.agent.graph.contracts import GraphCompactionHost
15
22
 
16
23
 
17
24
  class GraphCompactionMixin:
18
25
  async def _maybe_compact(
19
- self,
26
+ self: GraphCompactionHost,
20
27
  messages: list,
21
- session_msgs: list,
28
+ session_msgs: list | None = None,
22
29
  *,
23
30
  force: bool = False,
24
31
  ask: bool = True,
25
32
  ) -> tuple[list | None, str | None]:
26
- """Check overflow and compact if needed."""
33
+ """Check overflow and compact if needed.
34
+
35
+ Returns the messages removed from the live context and the persisted
36
+ tail anchor id when one is available. Fallback truncation has no stable
37
+ tail anchor, so it returns None for the second value.
38
+ """
27
39
  total_tokens = estimate_context_tokens(messages, self.config.model.model)
28
40
  tokens = {"total": total_tokens, "input": total_tokens, "output": 0, "reasoning": 0}
29
41
 
@@ -73,56 +85,83 @@ class GraphCompactionMixin:
73
85
  detail=f"fallback truncation, keeping last {keep} messages",
74
86
  stage="compacting",
75
87
  ))
76
- await ui_events.emit(StatusFinished(
77
- status_id="compaction",
78
- label=f"Compaction fallback kept last {keep} messages",
79
- remove=False,
80
- ))
81
88
  else:
82
89
  ui.print(f"[dim]Aggressive truncation: keeping last {keep} messages[/dim]")
83
- # Remove old messages, keep system + last N
84
- system_msgs = [m for m in messages if isinstance(m, SystemMessage)]
85
- other_msgs = [m for m in messages if not isinstance(m, SystemMessage)]
86
- messages.clear()
87
- messages.extend(system_msgs)
88
- messages.extend(other_msgs[-keep:])
89
- return messages[:max(0, len(messages) - keep)], None
90
90
 
91
- # Run compaction agent
92
- try:
91
+ removed = _truncate_to_recent_messages(messages, keep)
92
+ # Generate a basic summary from the removed messages
93
+ fallback = CompactionService.fallback_summary(removed)
94
+ self._pending_summary = fallback
95
+ self._compaction_summary = fallback
96
+ self._compaction.compaction_count += 1
97
+
93
98
  if via_events():
94
- await ui_events.emit(StatusUpdated(
99
+ await ui_events.emit(StatusFinished(
95
100
  status_id="compaction",
96
- label="Compacting context",
97
- detail=f"summarizing {len(head_msgs)} old messages",
98
- stage="compacting",
101
+ label=f"Compaction fallback kept last {keep} messages (with extracted summary)",
102
+ remove=False,
99
103
  ))
100
- previous_summary = getattr(self, "_compaction_summary", "") or None
101
- summary = await self._run_compaction_agent(head_msgs, previous_summary)
102
- except Exception as e:
104
+ return removed, None
105
+
106
+ # Run compaction agent with retries
107
+ summary = None
108
+ previous_summary = getattr(self, "_compaction_summary", "") or None
109
+ last_error: Exception | None = None
110
+
111
+ for attempt in range(1, COMPACTION_MAX_RETRIES + 2): # 1 initial + N retries
112
+ try:
113
+ if via_events():
114
+ retry_label = f" (attempt {attempt})" if attempt > 1 else ""
115
+ await ui_events.emit(StatusUpdated(
116
+ status_id="compaction",
117
+ label="Compacting context",
118
+ detail=f"summarizing {len(head_msgs)} old messages{retry_label}",
119
+ stage="compacting",
120
+ ))
121
+ summary = await self._run_compaction_agent(head_msgs, previous_summary)
122
+ if summary:
123
+ break
124
+ except Exception as e:
125
+ last_error = e
126
+ if attempt <= COMPACTION_MAX_RETRIES:
127
+ if via_events():
128
+ await ui_events.emit(StatusUpdated(
129
+ status_id="compaction",
130
+ label="Compaction agent failed",
131
+ detail=f"{e}; retrying ({attempt}/{COMPACTION_MAX_RETRIES})",
132
+ stage="compacting",
133
+ ))
134
+ else:
135
+ ui.print(f"[dim]Compaction agent failed ({e}) — retrying ({attempt}/{COMPACTION_MAX_RETRIES})[/dim]")
136
+
137
+ if not summary:
138
+ # All retries exhausted — fallback truncation with basic summary
103
139
  if via_events():
140
+ err_detail = f"{last_error}; " if last_error else ""
104
141
  await ui_events.emit(StatusUpdated(
105
142
  status_id="compaction",
106
143
  label="Compaction agent failed",
107
- detail=f"{e}; falling back to truncation",
144
+ detail=f"{err_detail}falling back to truncation with extracted summary",
108
145
  stage="compacting",
109
146
  ))
110
147
  else:
111
- ui.print(f"[dim]Compaction agent failed ({e}) aggressive truncation[/dim]")
148
+ err_msg = f" ({last_error})" if last_error else ""
149
+ ui.print(f"[dim]Compaction agent failed{err_msg} — aggressive truncation with summary[/dim]")
112
150
  keep = min(6, len(messages))
113
- system_msgs = [m for m in messages if isinstance(m, SystemMessage)]
114
- other_msgs = [m for m in messages if not isinstance(m, SystemMessage)]
115
- messages.clear()
116
- messages.extend(system_msgs)
117
- messages.extend(other_msgs[-keep:])
151
+ removed = _truncate_to_recent_messages(messages, keep)
152
+ # Generate a basic summary from the removed messages
153
+ fallback = CompactionService.fallback_summary(head_msgs if head_msgs else removed)
154
+ self._pending_summary = fallback
155
+ self._compaction_summary = fallback
156
+ self._compaction.compaction_count += 1
118
157
  if via_events():
119
158
  await ui_events.emit(StatusFinished(
120
159
  status_id="compaction",
121
- label=f"Compaction fallback kept last {keep} messages",
160
+ label=f"Compaction fallback kept last {keep} messages (with extracted summary)",
122
161
  ok=False,
123
162
  remove=False,
124
163
  ))
125
- return messages[:max(0, len(messages) - keep)], None
164
+ return removed, None
126
165
 
127
166
  if summary:
128
167
  keep_from = len(head_msgs)
@@ -154,7 +193,7 @@ class GraphCompactionMixin:
154
193
 
155
194
  return head_msgs, tail_id
156
195
 
157
- async def _ask_compact(self, total_tokens: int) -> bool:
196
+ async def _ask_compact(self: GraphCompactionHost, total_tokens: int) -> bool:
158
197
  choices = [
159
198
  ("Compact", "compact", "Summarize older context and continue"),
160
199
  ("Skip once", "skip", "Continue without compacting this turn"),
@@ -167,7 +206,7 @@ class GraphCompactionMixin:
167
206
  ui.print(f" [yellow]Context is large ({total_tokens} tokens); compacting automatically.[/yellow]")
168
207
  return True
169
208
 
170
- async def _persist_compaction(self, head_messages: list) -> None:
209
+ async def _persist_compaction(self: GraphCompactionHost, head_messages: list) -> None:
171
210
  if getattr(self, "_session", None) is None:
172
211
  return
173
212
  if hasattr(self, "_persist_runtime_state"):
@@ -179,42 +218,33 @@ class GraphCompactionMixin:
179
218
 
180
219
  await delete_messages_through(self._session.id, last_message_id)
181
220
 
182
- async def _compact_session_history(self, *, force: bool = True) -> bool:
221
+ # Sync in-memory cache: drop compacted rows
222
+ cache = getattr(self, "_session_msg_cache", None)
223
+ if cache is not None:
224
+ self._session_msg_cache = [r for r in cache if r.id is not None and r.id > last_message_id]
225
+
226
+ async def _compact_session_history(self: GraphCompactionHost, *, force: bool = True) -> bool:
183
227
  if getattr(self, "_session", None) is None:
184
228
  ui.print("[dim]No active session to compact.[/dim]")
185
229
  return False
186
230
 
187
- from voidx.agent.attachments import parse_structured_content
188
- from voidx.memory.session import load_messages
189
-
190
- rows = await load_messages(self._session.id)
191
- messages = []
192
- for row in rows:
193
- msg_id = str(row.id) if row.id is not None else None
194
- if row.role == "system":
195
- messages.append(SystemMessage(content=row.content, id=msg_id))
196
- elif row.role == "user":
197
- messages.append(HumanMessage(
198
- content=parse_structured_content(row.content, row.content_format),
199
- id=msg_id,
200
- ))
201
- elif row.role == "assistant":
202
- messages.append(AIMessage(
203
- content=parse_structured_content(row.content, row.content_format),
204
- tool_calls=row.tool_calls or [],
205
- id=msg_id,
206
- ))
207
- elif row.role == "tool":
208
- messages.append(ToolMessage(
209
- content=row.content,
210
- tool_call_id=row.tool_call_id or "",
211
- id=msg_id,
212
- ))
231
+ cache = getattr(self, "_session_msg_cache", None)
232
+ if cache is not None:
233
+ rows = list(cache)
234
+ else:
235
+ from voidx.memory.session import load_messages
236
+ rows = await load_messages(self._session.id)
237
+
238
+ messages = messages_from_rows(rows)
213
239
 
214
240
  head, _tail_id = await self._maybe_compact(messages, rows, force=force, ask=False)
215
241
  return bool(head)
216
242
 
217
- async def _run_compaction_agent(self, head_messages: list, previous_summary: str | None) -> str | None:
243
+ async def _run_compaction_agent(
244
+ self: GraphCompactionHost,
245
+ head_messages: list,
246
+ previous_summary: str | None,
247
+ ) -> str | None:
218
248
  """Run the compaction agent to generate a structured summary."""
219
249
  from voidx.agent.agents import COMPACTION_PROMPT
220
250
 
@@ -266,3 +296,16 @@ def _max_persisted_message_id(messages: list) -> int | None:
266
296
  except (TypeError, ValueError):
267
297
  continue
268
298
  return max(ids) if ids else None
299
+
300
+
301
+ def _truncate_to_recent_messages(messages: list, keep: int) -> list:
302
+ original = list(messages)
303
+ system_msgs = [m for m in original if isinstance(m, SystemMessage)]
304
+ other_msgs = [m for m in original if not isinstance(m, SystemMessage)]
305
+ tail_msgs = other_msgs[-keep:] if keep > 0 else []
306
+ retained_ids = {id(m) for m in [*system_msgs, *tail_msgs]}
307
+ removed = [m for m in original if id(m) not in retained_ids]
308
+ messages.clear()
309
+ messages.extend(system_msgs)
310
+ messages.extend(tail_msgs)
311
+ return removed