voidx 2.1.1__tar.gz → 2.2.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 (254) hide show
  1. {voidx-2.1.1 → voidx-2.2.0}/PKG-INFO +1 -1
  2. {voidx-2.1.1 → voidx-2.2.0}/pyproject.toml +2 -3
  3. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/__init__.py +1 -1
  4. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/agents.py +11 -11
  5. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/graph/core.py +30 -2
  6. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/graph/subagent.py +6 -24
  7. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/graph/tool_execution.py +46 -3
  8. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/graph/turn_mixin.py +16 -8
  9. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/intent_refinement.py +7 -5
  10. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/runtime_context.py +85 -9
  11. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/llm/catalog.py +7 -1
  12. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/llm/instruction.py +63 -14
  13. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/llm/usage.py +35 -0
  14. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/permission/rules.py +2 -0
  15. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/runtime/intent_classifier.py +3 -0
  16. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/runtime/ui.py +2 -0
  17. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/skills/__init__.py +12 -1
  18. {voidx-2.1.1/src/voidx/skills/bundled/superpowers → voidx-2.2.0/src/voidx/skills/bundled}/writing-design-docs/SKILL.md +1 -1
  19. voidx-2.2.0/src/voidx/skills/context.py +168 -0
  20. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/skills/policy.py +21 -2
  21. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/skills/registry.py +23 -2
  22. voidx-2.2.0/src/voidx/skills/runtime.py +252 -0
  23. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/skills/service.py +39 -7
  24. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/tools/base.py +1 -0
  25. voidx-2.2.0/src/voidx/tools/load_skills.py +203 -0
  26. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/tools/on_intent.py +1 -6
  27. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/tools/registry.py +8 -3
  28. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/tools/repomap.py +3 -0
  29. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/tools/search.py +4 -0
  30. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/tools/websearch.py +4 -1
  31. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/output/browse.py +38 -24
  32. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/output/capture.py +13 -4
  33. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/output/dock/__init__.py +3 -0
  34. voidx-2.2.0/src/voidx/ui/output/dock/agent_placeholder.py +21 -0
  35. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/output/dock/app.py +53 -6
  36. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/output/dock/nodes.py +8 -2
  37. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/output/dock/stream.py +7 -4
  38. voidx-2.2.0/src/voidx/ui/output/dock/todo.py +110 -0
  39. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/output/events/__init__.py +30 -75
  40. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/output/events/schema.py +10 -0
  41. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/output/tree.py +22 -0
  42. voidx-2.2.0/src/voidx/ui/tui/activity.py +64 -0
  43. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/tui/app.py +57 -0
  44. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/tui/panels.py +5 -2
  45. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/tui/parser.py +24 -0
  46. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/tui/renderer.py +360 -39
  47. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/tui/state.py +17 -0
  48. voidx-2.2.0/src/voidx/ui/tui/terminal_mixin.py +95 -0
  49. {voidx-2.1.1 → voidx-2.2.0}/src/voidx.egg-info/PKG-INFO +1 -1
  50. {voidx-2.1.1 → voidx-2.2.0}/src/voidx.egg-info/SOURCES.txt +15 -14
  51. voidx-2.2.0/tests/test_instruction_cache.py +88 -0
  52. voidx-2.2.0/tests/test_llm_catalog.py +46 -0
  53. {voidx-2.1.1 → voidx-2.2.0}/tests/test_llm_usage.py +28 -0
  54. {voidx-2.1.1 → voidx-2.2.0}/tests/test_npm_package.py +6 -6
  55. voidx-2.2.0/tests/test_output_browse.py +23 -0
  56. {voidx-2.1.1 → voidx-2.2.0}/tests/test_pure_tui.py +990 -4
  57. {voidx-2.1.1 → voidx-2.2.0}/tests/test_runtime_intent_classifier.py +25 -0
  58. voidx-2.2.0/tests/test_skills.py +890 -0
  59. {voidx-2.1.1 → voidx-2.2.0}/tests/test_ui_events.py +274 -4
  60. {voidx-2.1.1 → voidx-2.2.0}/tests/test_ui_gateway.py +58 -0
  61. voidx-2.1.1/src/voidx/skills/bundled/superpowers/writing-design-docs/templates/api-doc.md +0 -64
  62. voidx-2.1.1/src/voidx/skills/bundled/superpowers/writing-design-docs/templates/prd.md +0 -117
  63. voidx-2.1.1/src/voidx/skills/bundled/superpowers/writing-design-docs/templates/readme.md +0 -55
  64. voidx-2.1.1/src/voidx/skills/bundled/superpowers/writing-design-docs/templates/rfc.md +0 -38
  65. voidx-2.1.1/src/voidx/skills/bundled/superpowers/writing-design-docs/templates/tech-design.md +0 -68
  66. voidx-2.1.1/src/voidx/skills/runtime.py +0 -90
  67. voidx-2.1.1/src/voidx/tools/doc_template.py +0 -96
  68. voidx-2.1.1/src/voidx/ui/tui/terminal_mixin.py +0 -42
  69. voidx-2.1.1/tests/test_instruction_cache.py +0 -31
  70. voidx-2.1.1/tests/test_skills.py +0 -444
  71. {voidx-2.1.1 → voidx-2.2.0}/README.md +0 -0
  72. {voidx-2.1.1 → voidx-2.2.0}/setup.cfg +0 -0
  73. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/__init__.py +0 -0
  74. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/attachments.py +0 -0
  75. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/graph/__init__.py +0 -0
  76. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/graph/compaction.py +0 -0
  77. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/graph/contracts.py +0 -0
  78. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/graph/convergence.py +0 -0
  79. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/graph/permissions.py +0 -0
  80. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/graph/run_loop.py +0 -0
  81. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/graph/runtime.py +0 -0
  82. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/graph/session_mixin.py +0 -0
  83. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/graph/streaming.py +0 -0
  84. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/graph/title_mixin.py +0 -0
  85. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/graph/todo_events.py +0 -0
  86. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/graph/topology.py +0 -0
  87. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/graph/transcript_mixin.py +0 -0
  88. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/graph/wiring.py +0 -0
  89. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/message_rows.py +0 -0
  90. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/slash/__init__.py +0 -0
  91. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/slash/code_ide.py +0 -0
  92. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/slash/guide.py +0 -0
  93. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/slash/handler.py +0 -0
  94. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/slash/init.py +0 -0
  95. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/slash/lsp.py +0 -0
  96. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/slash/mcp.py +0 -0
  97. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/slash/model.py +0 -0
  98. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/slash/profile.py +0 -0
  99. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/slash/runtime.py +0 -0
  100. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/slash/session.py +0 -0
  101. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/slash/skills.py +0 -0
  102. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/state.py +0 -0
  103. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/task_state.py +0 -0
  104. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/tool_filters.py +0 -0
  105. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/agent/tool_messages.py +0 -0
  106. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/config/__init__.py +0 -0
  107. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/config/cli.py +0 -0
  108. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/config/enums.py +0 -0
  109. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/config/models.py +0 -0
  110. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/config/permissions.py +0 -0
  111. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/config/settings.py +0 -0
  112. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/config/settings_agent.py +0 -0
  113. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/config/settings_api_keys.py +0 -0
  114. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/config/settings_code_ide.py +0 -0
  115. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/config/settings_custom.py +0 -0
  116. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/config/settings_mcp.py +0 -0
  117. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/config/settings_permissions.py +0 -0
  118. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/config/settings_skills.py +0 -0
  119. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/config/settings_utils.py +0 -0
  120. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/config/settings_web.py +0 -0
  121. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/data/__init__.py +0 -0
  122. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/data/intent_classifier.json +0 -0
  123. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/diffing.py +0 -0
  124. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/llm/__init__.py +0 -0
  125. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/llm/compaction.py +0 -0
  126. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/llm/context.py +0 -0
  127. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/llm/message_markers.py +0 -0
  128. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/llm/provider.py +0 -0
  129. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/lsp/__init__.py +0 -0
  130. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/lsp/client.py +0 -0
  131. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/lsp/config.py +0 -0
  132. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/lsp/detector.py +0 -0
  133. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/lsp/detector_data.py +0 -0
  134. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/lsp/errors.py +0 -0
  135. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/lsp/manager.py +0 -0
  136. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/lsp/schema.py +0 -0
  137. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/lsp/service.py +0 -0
  138. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/main.py +0 -0
  139. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/mcp/__init__.py +0 -0
  140. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/mcp/client/__init__.py +0 -0
  141. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/mcp/client/base.py +0 -0
  142. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/mcp/client/errors.py +0 -0
  143. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/mcp/client/http_transport.py +0 -0
  144. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/mcp/client/sse_transport.py +0 -0
  145. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/mcp/client/stdio_transport.py +0 -0
  146. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/mcp/manager.py +0 -0
  147. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/mcp/schema.py +0 -0
  148. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/mcp/tool.py +0 -0
  149. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/mcp_servers/__init__.py +0 -0
  150. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/mcp_servers/web.py +0 -0
  151. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/memory/__init__.py +0 -0
  152. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/memory/context_frames.py +0 -0
  153. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/memory/model_profiles.py +0 -0
  154. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/memory/runtime_state.py +0 -0
  155. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/memory/session.py +0 -0
  156. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/memory/store.py +0 -0
  157. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/memory/transcript.py +0 -0
  158. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/permission/__init__.py +0 -0
  159. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/permission/context.py +0 -0
  160. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/permission/engine.py +0 -0
  161. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/permission/evaluate.py +0 -0
  162. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/permission/sandbox.py +0 -0
  163. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/permission/schema.py +0 -0
  164. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/permission/service.py +0 -0
  165. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/permission/wildcard.py +0 -0
  166. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/runtime/__init__.py +0 -0
  167. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/runtime/intent.py +0 -0
  168. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/runtime/task_state.py +0 -0
  169. {voidx-2.1.1/src/voidx/skills/bundled/superpowers → voidx-2.2.0/src/voidx/skills/bundled}/brainstorming/SKILL.md +0 -0
  170. {voidx-2.1.1/src/voidx/skills/bundled/superpowers → voidx-2.2.0/src/voidx/skills/bundled}/receiving-code-review/SKILL.md +0 -0
  171. {voidx-2.1.1/src/voidx/skills/bundled/superpowers → voidx-2.2.0/src/voidx/skills/bundled}/requesting-code-review/SKILL.md +0 -0
  172. {voidx-2.1.1/src/voidx/skills/bundled/superpowers → voidx-2.2.0/src/voidx/skills/bundled}/systematic-debugging/SKILL.md +0 -0
  173. {voidx-2.1.1/src/voidx/skills/bundled/superpowers → voidx-2.2.0/src/voidx/skills/bundled}/test-driven-development/SKILL.md +0 -0
  174. {voidx-2.1.1/src/voidx/skills/bundled/superpowers → voidx-2.2.0/src/voidx/skills/bundled}/verification-before-completion/SKILL.md +0 -0
  175. {voidx-2.1.1/src/voidx/skills/bundled/superpowers → voidx-2.2.0/src/voidx/skills/bundled}/writing-plans/SKILL.md +0 -0
  176. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/skills/schema.py +0 -0
  177. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/tools/__init__.py +0 -0
  178. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/tools/agent.py +0 -0
  179. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/tools/bash.py +0 -0
  180. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/tools/clarify.py +0 -0
  181. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/tools/file_ops.py +0 -0
  182. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/tools/git.py +0 -0
  183. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/tools/lsp.py +0 -0
  184. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/tools/plan_checkpoint.py +0 -0
  185. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/tools/task_status.py +0 -0
  186. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/tools/task_tracker.py +0 -0
  187. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/tools/todo.py +0 -0
  188. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/tools/web_content.py +0 -0
  189. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/tools/web_mcp.py +0 -0
  190. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/tools/webfetch.py +0 -0
  191. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/__init__.py +0 -0
  192. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/commands.py +0 -0
  193. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/frontend.py +0 -0
  194. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/gateway/__init__.py +0 -0
  195. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/gateway/bootstrap.py +0 -0
  196. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/gateway/server.py +0 -0
  197. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/gateway/session.py +0 -0
  198. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/output/__init__.py +0 -0
  199. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/output/agent_display.py +0 -0
  200. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/output/console/__init__.py +0 -0
  201. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/output/console/app.py +0 -0
  202. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/output/console/formatting.py +0 -0
  203. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/output/console/streaming.py +0 -0
  204. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/output/diff.py +0 -0
  205. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/output/dock/formatting.py +0 -0
  206. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/output/dock/nodes_permission.py +0 -0
  207. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/output/dock/nodes_startup.py +0 -0
  208. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/output/dock/nodes_status.py +0 -0
  209. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/output/dock/state.py +0 -0
  210. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/output/dock/status.py +0 -0
  211. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/output/types.py +0 -0
  212. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/protocol/__init__.py +0 -0
  213. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/protocol/commands.py +0 -0
  214. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/protocol/envelope.py +0 -0
  215. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/protocol/requests.py +0 -0
  216. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/protocol/schema.py +0 -0
  217. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/protocol/transcript.py +0 -0
  218. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/session.py +0 -0
  219. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/tools/__init__.py +0 -0
  220. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/tools/attachment_tokens.py +0 -0
  221. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/tools/clipboard_image.py +0 -0
  222. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/tools/clipboard_text.py +0 -0
  223. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/tools/code_ide.py +0 -0
  224. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/tools/file_picker.py +0 -0
  225. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/transcript.py +0 -0
  226. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/tui/__init__.py +0 -0
  227. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/tui/choice_mixin.py +0 -0
  228. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/tui/clipboard_mixin.py +0 -0
  229. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/tui/helpers.py +0 -0
  230. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/tui/input.py +0 -0
  231. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/tui/overlays.py +0 -0
  232. {voidx-2.1.1 → voidx-2.2.0}/src/voidx/ui/tui/text_prompt_mixin.py +0 -0
  233. {voidx-2.1.1 → voidx-2.2.0}/src/voidx.egg-info/dependency_links.txt +0 -0
  234. {voidx-2.1.1 → voidx-2.2.0}/src/voidx.egg-info/entry_points.txt +0 -0
  235. {voidx-2.1.1 → voidx-2.2.0}/src/voidx.egg-info/requires.txt +0 -0
  236. {voidx-2.1.1 → voidx-2.2.0}/src/voidx.egg-info/top_level.txt +0 -0
  237. {voidx-2.1.1 → voidx-2.2.0}/tests/test_clipboard_image.py +0 -0
  238. {voidx-2.1.1 → voidx-2.2.0}/tests/test_clipboard_text.py +0 -0
  239. {voidx-2.1.1 → voidx-2.2.0}/tests/test_code_ide.py +0 -0
  240. {voidx-2.1.1 → voidx-2.2.0}/tests/test_compaction.py +0 -0
  241. {voidx-2.1.1 → voidx-2.2.0}/tests/test_config.py +0 -0
  242. {voidx-2.1.1 → voidx-2.2.0}/tests/test_intent_classifier_phase_a.py +0 -0
  243. {voidx-2.1.1 → voidx-2.2.0}/tests/test_llm_provider.py +0 -0
  244. {voidx-2.1.1 → voidx-2.2.0}/tests/test_lsp.py +0 -0
  245. {voidx-2.1.1 → voidx-2.2.0}/tests/test_main.py +0 -0
  246. {voidx-2.1.1 → voidx-2.2.0}/tests/test_main_startup.py +0 -0
  247. {voidx-2.1.1 → voidx-2.2.0}/tests/test_mcp.py +0 -0
  248. {voidx-2.1.1 → voidx-2.2.0}/tests/test_runtime_ui.py +0 -0
  249. {voidx-2.1.1 → voidx-2.2.0}/tests/test_scrollback_flush.py +0 -0
  250. {voidx-2.1.1 → voidx-2.2.0}/tests/test_startup.py +0 -0
  251. {voidx-2.1.1 → voidx-2.2.0}/tests/test_tree_smoke.py +0 -0
  252. {voidx-2.1.1 → voidx-2.2.0}/tests/test_ui_diff.py +0 -0
  253. {voidx-2.1.1 → voidx-2.2.0}/tests/test_ui_frontend_protocol.py +0 -0
  254. {voidx-2.1.1 → voidx-2.2.0}/tests/test_ui_session_changes.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: voidx
3
- Version: 2.1.1
3
+ Version: 2.2.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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "voidx"
3
- version = "2.1.1"
3
+ version = "2.2.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"
@@ -44,8 +44,7 @@ where = ["src"]
44
44
 
45
45
  [tool.setuptools.package-data]
46
46
  "voidx.skills" = [
47
- "bundled/superpowers/*/SKILL.md",
48
- "bundled/superpowers/*/templates/*.md",
47
+ "bundled/*/SKILL.md",
49
48
  ]
50
49
  "voidx.data" = [
51
50
  "intent_classifier.json",
@@ -1,3 +1,3 @@
1
1
  """VoidX - A coding agent that quantifies everything."""
2
2
 
3
- __version__ = "2.1.1"
3
+ __version__ = "2.2.0"
@@ -50,12 +50,12 @@ BASE_SYSTEM_PROMPT = """You are voidx, a coding agent that lives in the terminal
50
50
 
51
51
  ## Workflow Skills
52
52
 
53
- - voidx may activate workflow skills such as systematic-debugging,
54
- test-driven-development, verification-before-completion,
55
- receiving-code-review, requesting-code-review, and writing-plans.
56
- - The Current Task State lists active workflow skills for this turn.
57
- - The Active Skills section contains the full instructions for active skills.
58
- - Follow active workflow skills before acting.
53
+ - voidx has a workflow skill system.
54
+ - Current Task State is the activation source for this turn's workflow skills.
55
+ - Skill Context messages contain bundled workflow skill bodies as a reference
56
+ library. Follow only skills listed as active in Current Task State, unless the
57
+ user explicitly references another skill.
58
+ - load_skills can return project/global skill bodies for the current turn.
59
59
  """
60
60
 
61
61
 
@@ -339,7 +339,7 @@ BUILTIN_AGENTS: dict[str, AgentDef] = {
339
339
  when_to_use="Default agent for all user interactions. Always use first.",
340
340
  tools=[
341
341
  "on_intent", "clarify", "plan_checkpoint",
342
- "read", "glob", "grep", "bash", "agent", "task_status", "todo",
342
+ "read", "glob", "grep", "bash", "agent", "task_status", "todo", "load_skills",
343
343
  "webfetch", "websearch", "repo_map",
344
344
  "lsp_diagnostics", "lsp_symbols", "lsp_definition", "lsp_references",
345
345
  "write", "edit", "apply_patch", "lsp_format",
@@ -358,7 +358,7 @@ BUILTIN_AGENTS: dict[str, AgentDef] = {
358
358
  "or answer 'how does X work' questions. Specify thoroughness: "
359
359
  "'quick' for basic, 'medium' for moderate, 'very thorough' for exhaustive.",
360
360
  tools=[
361
- "read", "glob", "grep", "webfetch", "websearch", "repo_map",
361
+ "read", "glob", "grep", "load_skills", "webfetch", "websearch", "repo_map",
362
362
  "lsp_diagnostics", "lsp_symbols", "lsp_definition", "lsp_references",
363
363
  ],
364
364
  can_write=False,
@@ -374,7 +374,7 @@ BUILTIN_AGENTS: dict[str, AgentDef] = {
374
374
  when_to_use="Use for design/architecture questions, before complex implementations, "
375
375
  "or when the user asks for a plan/approach/solution design.",
376
376
  tools=[
377
- "read", "glob", "grep", "webfetch", "websearch", "repo_map",
377
+ "read", "glob", "grep", "load_skills", "webfetch", "websearch", "repo_map",
378
378
  "lsp_diagnostics", "lsp_symbols", "lsp_definition", "lsp_references",
379
379
  ],
380
380
  can_write=False,
@@ -388,7 +388,7 @@ BUILTIN_AGENTS: dict[str, AgentDef] = {
388
388
  when_to_use="Use for all code writing, file editing, refactoring, bug fixing, "
389
389
  "and bash execution. Give complete, self-contained task descriptions.",
390
390
  tools=[
391
- "read", "write", "edit", "apply_patch", "glob", "grep", "bash", "todo", "repo_map",
391
+ "read", "write", "edit", "apply_patch", "glob", "grep", "bash", "todo", "load_skills", "repo_map",
392
392
  "lsp_diagnostics", "lsp_symbols", "lsp_definition", "lsp_references",
393
393
  "lsp_format",
394
394
  ],
@@ -404,7 +404,7 @@ BUILTIN_AGENTS: dict[str, AgentDef] = {
404
404
  when_to_use="ALWAYS invoke after implement finishes non-trivial work. "
405
405
  "Use to verify correctness before reporting completion to the user.",
406
406
  tools=[
407
- "read", "glob", "grep", "bash", "webfetch", "websearch", "repo_map",
407
+ "read", "glob", "grep", "bash", "load_skills", "webfetch", "websearch", "repo_map",
408
408
  "lsp_diagnostics", "lsp_symbols", "lsp_definition", "lsp_references",
409
409
  ],
410
410
  can_write=False,
@@ -59,6 +59,7 @@ from voidx.agent.graph.wiring import (
59
59
  )
60
60
  from voidx.agent.state import AgentState
61
61
  from voidx.agent.graph.streaming import extract_text, stream_llm as _stream_llm
62
+ from voidx.agent.graph.subagent import _task_intent_for_agent as _subagent_task_intent_for_agent
62
63
  from voidx.agent.graph.subagent import run_subagent as _run_subagent
63
64
  from voidx.agent.graph.title_mixin import GraphTitleMixin
64
65
  from voidx.agent.graph.tool_execution import GraphToolExecutionMixin
@@ -117,6 +118,20 @@ def _merge_skill_runs(*groups: list[SkillRunState | dict]) -> list[SkillRunState
117
118
  return list(merged.values())
118
119
 
119
120
 
121
+ def _skill_names(group: list[SkillRunState | dict]) -> list[str]:
122
+ names: list[str] = []
123
+ for item in group:
124
+ if isinstance(item, SkillRunState):
125
+ name = item.name
126
+ elif isinstance(item, dict):
127
+ name = item.get("name", "")
128
+ else:
129
+ name = ""
130
+ if isinstance(name, str) and name.strip():
131
+ names.append(name.strip())
132
+ return names
133
+
134
+
120
135
  class VoidXGraph(
121
136
  GraphTitleMixin,
122
137
  GraphRunLoopMixin,
@@ -147,6 +162,7 @@ class VoidXGraph(
147
162
 
148
163
  self._interaction_mode: InteractionMode = InteractionMode.AUTO
149
164
  self._debug: bool = False
165
+ self._instruction.set_debug(self._debug)
150
166
  ui.set_debug(self._debug)
151
167
 
152
168
  self._file_mtimes: dict[str, float] = {}
@@ -372,6 +388,16 @@ class VoidXGraph(
372
388
  self._next_agent_id += 1
373
389
  parent_tool_call_id = _current_parent_tool_call_id.get()
374
390
  started_at = time.monotonic()
391
+ interaction_mode = InteractionMode.PLAN.value if agent_def.name == "plan" else InteractionMode.AUTO.value
392
+ task_intent = _subagent_task_intent_for_agent(agent_def.name)
393
+ skill_runtime_context = await self._instruction.skill_context_for(
394
+ description,
395
+ agent=agent_def.name,
396
+ task_intent=task_intent,
397
+ interaction_mode=interaction_mode,
398
+ scope=description,
399
+ turn_count=getattr(getattr(self, "_task_run", None), "turn_count", 0),
400
+ )
375
401
 
376
402
  async def authorize(calls, agent_name: str):
377
403
  return await self._authorize_tool_calls(
@@ -403,8 +429,8 @@ class VoidXGraph(
403
429
  "session_id": session_id if self._session else None,
404
430
  "usage_stats": self._usage_stats,
405
431
  "lsp_manager": getattr(self, "_lsp_manager", None),
406
- "skill_selection": self._settings.get_skill_selection() if self._settings else None,
407
432
  "parent_tools": self.tools,
433
+ "skill_runtime_context": skill_runtime_context,
408
434
  }
409
435
  if self._current_tree and self._turn_node:
410
436
  kwargs.update({
@@ -435,6 +461,7 @@ class VoidXGraph(
435
461
 
436
462
  def set_debug(self, value: bool) -> None:
437
463
  self._debug = value
464
+ self._instruction.set_debug(value)
438
465
  ui.set_debug(value)
439
466
 
440
467
  def _apply_max_steps_override(self, agent_def: AgentDef) -> AgentDef:
@@ -477,6 +504,7 @@ class VoidXGraph(
477
504
  interaction_mode=interaction_mode,
478
505
  scope=pending_approval_scope(state.get("pending_approval")) or state.get("goal") or current_user_text,
479
506
  turn_count=state.get("goal_turn_count", 0),
507
+ exclude_names=_skill_names(state.get("skill_runs", []) or []),
480
508
  )
481
509
  skill_runs = _merge_skill_runs(
482
510
  _restored_skill_runs(getattr(self, "_task_run", None)),
@@ -497,7 +525,7 @@ class VoidXGraph(
497
525
  agent=agent_name,
498
526
  interaction_mode=interaction_mode,
499
527
  instructions=instructions,
500
- skill_instructions=skill_context.instructions,
528
+ skill_context_content=skill_context.content,
501
529
  skill_runs=skill_runs,
502
530
  active_skill_summaries=skill_context.active,
503
531
  summary=summary,
@@ -26,6 +26,7 @@ from voidx.agent.tool_messages import sanitize_tool_message_content
26
26
  from voidx.agent.tool_filters import filter_unavailable_lsp_tools
27
27
  from voidx.config import Config
28
28
  from voidx.llm.provider import create_chat_model, resolve_protocol
29
+ from voidx.llm.instruction import SkillRuntimeContext
29
30
  from voidx.llm.usage import (
30
31
  UsageStats,
31
32
  estimate_context_tokens,
@@ -33,9 +34,6 @@ from voidx.llm.usage import (
33
34
  extract_token_usage,
34
35
  )
35
36
  from voidx.memory.context_frames import save_context_frame_from_messages
36
- from voidx.skills.registry import SkillRegistry
37
- from voidx.skills.runtime import SkillRunState
38
- from voidx.skills.service import SkillService
39
37
  from voidx.tools.base import ToolContext
40
38
  from voidx.tools.registry import ToolRegistry
41
39
  from voidx.tools.task_tracker import TaskTracker
@@ -59,8 +57,8 @@ async def run_subagent(
59
57
  session_id: str | None = None,
60
58
  usage_stats: UsageStats | None = None,
61
59
  lsp_manager=None,
62
- skill_selection=None,
63
60
  parent_tools: ToolRegistry | None = None,
61
+ skill_runtime_context: SkillRuntimeContext | None = None,
64
62
  ) -> str:
65
63
  """Run a child agent. Child messages are appended to sub_messages
66
64
  (when provided) so the caller can place them after ToolMessages."""
@@ -140,16 +138,7 @@ async def run_subagent(
140
138
  interaction_mode = InteractionMode.PLAN.value if agent_def.name == "plan" else InteractionMode.AUTO.value
141
139
  mode_prompt = PLAN_MODE_APPEND if InteractionMode.parse(interaction_mode) == InteractionMode.PLAN else ""
142
140
  task_intent = _task_intent_for_agent(agent_def.name)
143
- skill_service = SkillService(
144
- SkillRegistry(config.workspace),
145
- selection=skill_selection,
146
- )
147
- skill_matches = skill_service.select(
148
- task_description,
149
- agent=agent_def.name,
150
- task_intent=task_intent,
151
- interaction_mode=interaction_mode,
152
- )
141
+ skills = skill_runtime_context or SkillRuntimeContext(instructions=[], active=[], content="", runs=[])
153
142
  context_cache = ContextCompilerCache()
154
143
  context, context_cache = RuntimeContextBuilder(
155
144
  config=context_config,
@@ -160,16 +149,9 @@ async def run_subagent(
160
149
  tool_contract=agent_def.tool_contract,
161
150
  agent=agent_def.name,
162
151
  interaction_mode=interaction_mode,
163
- skill_instructions=[skill_service.render_instruction(match.skill) for match in skill_matches],
164
- skill_runs=[
165
- SkillRunState.from_match(
166
- match,
167
- phase="design" if interaction_mode == InteractionMode.PLAN.value else task_intent,
168
- scope=task_description,
169
- )
170
- for match in skill_matches
171
- ],
172
- active_skill_summaries=[f"{match.name} ({match.reason})" for match in skill_matches],
152
+ skill_context_content=skills.content,
153
+ skill_runs=skills.runs,
154
+ active_skill_summaries=skills.active,
173
155
  current_user_text=task_description,
174
156
  task_intent=task_intent,
175
157
  session_date=datetime.now().astimezone().strftime("%Y-%m-%d %Z"),
@@ -14,6 +14,7 @@ from voidx.agent.graph.runtime import current_parent_tool_call_id, ui
14
14
  from voidx.agent.graph.todo_events import todo_updated_event
15
15
  from voidx.agent.task_state import ToolStatePatch
16
16
  from voidx.agent.tool_messages import sanitize_tool_message_content
17
+ from voidx.skills.runtime import SkillRunState
17
18
  from voidx.tools.base import ToolContext, UserInteraction, UserResponse
18
19
  from voidx.runtime.ui import (
19
20
  FileChangeAppended,
@@ -69,6 +70,7 @@ class GraphToolExecutionMixin:
69
70
  pending_approval=_dump_pending_approval(state.get("pending_approval")),
70
71
  goal=state.get("goal", ""),
71
72
  goal_turn_count=state.get("goal_turn_count", 0),
73
+ active_skill_names=_active_skill_names(state.get("skill_runs", []) or []),
72
74
  file_mtimes=self._file_mtimes,
73
75
  mcp_manager=getattr(self, "_mcp_manager", None),
74
76
  lsp_manager=getattr(self, "_lsp_manager", None),
@@ -287,7 +289,10 @@ class GraphToolExecutionMixin:
287
289
  ]
288
290
  deferred_msgs = [_deferred_message(tc, ctx.workspace) for tc in deferred_for_barrier]
289
291
 
290
- state_update = _state_update_from_executed_tools(executed)
292
+ state_update = _state_update_from_executed_tools(
293
+ executed,
294
+ current_skill_runs=state.get("skill_runs", []) or [],
295
+ )
291
296
  return {
292
297
  "messages": [item.message for item in executed] + extra + denied_msgs + deferred_msgs,
293
298
  **state_update,
@@ -306,8 +311,14 @@ class GraphToolExecutionMixin:
306
311
  return True
307
312
 
308
313
 
309
- def _state_update_from_executed_tools(executed: list[_ExecutedTool]) -> dict:
314
+ def _state_update_from_executed_tools(
315
+ executed: list[_ExecutedTool],
316
+ *,
317
+ current_skill_runs: object = (),
318
+ ) -> dict:
310
319
  update: dict = {}
320
+ merged_skill_runs = _merge_skill_runs_for_state(current_skill_runs)
321
+ skill_runs_changed = False
311
322
  for item in executed:
312
323
  metadata = getattr(item.result, "metadata", {}) or {}
313
324
  raw = metadata.get("state_patch")
@@ -325,12 +336,31 @@ def _state_update_from_executed_tools(executed: list[_ExecutedTool]) -> dict:
325
336
  elif field == "pending_approval":
326
337
  update["pending_approval"] = data.get(field)
327
338
  elif field == "skill_runs":
328
- update["skill_runs"] = patch.skill_runs
339
+ merged_skill_runs = _merge_skill_runs_for_state(
340
+ merged_skill_runs,
341
+ patch.skill_runs,
342
+ )
343
+ skill_runs_changed = True
329
344
  else:
330
345
  update[field] = data.get(field)
346
+ if skill_runs_changed:
347
+ update["skill_runs"] = merged_skill_runs
331
348
  return update
332
349
 
333
350
 
351
+ def _merge_skill_runs_for_state(*groups: object) -> list[SkillRunState]:
352
+ merged: dict[str, SkillRunState] = {}
353
+ for group in groups:
354
+ items = group.values() if isinstance(group, dict) else group or []
355
+ for item in items:
356
+ try:
357
+ run = item if isinstance(item, SkillRunState) else SkillRunState.model_validate(item)
358
+ except (TypeError, ValueError):
359
+ continue
360
+ merged[run.name] = run
361
+ return list(merged.values())
362
+
363
+
334
364
  def _parallel_subagent_limit(config) -> int:
335
365
  parallel = getattr(config, "parallel_subagents", None)
336
366
  if not bool(getattr(parallel, "enabled", False)):
@@ -430,3 +460,16 @@ def _dump_pending_approval(value: object | None) -> dict | None:
430
460
  if hasattr(value, "model_dump"):
431
461
  return value.model_dump(mode="json")
432
462
  return None
463
+
464
+
465
+ def _active_skill_names(value: object) -> list[str]:
466
+ names: list[str] = []
467
+ items = value.values() if isinstance(value, dict) else value or []
468
+ for item in items:
469
+ if isinstance(item, dict):
470
+ name = item.get("name")
471
+ else:
472
+ name = getattr(item, "name", None)
473
+ if isinstance(name, str) and name.strip():
474
+ names.append(name.strip())
475
+ return names
@@ -31,6 +31,8 @@ from voidx.runtime.ui import (
31
31
  InputSet,
32
32
  StatusFinished,
33
33
  StatusUpdated,
34
+ TodoCleared,
35
+ TodoCommitted,
34
36
  TurnStarted,
35
37
  WarningAppended,
36
38
  dock,
@@ -73,9 +75,7 @@ class GraphTurnMixin:
73
75
  display_text: str | None = None,
74
76
  ) -> None:
75
77
  t_turn_start = time.monotonic()
76
- t_turn_calls_start = self._usage_stats.total_calls
77
- t_turn_in_start = self._usage_stats.total_input_tokens
78
- t_turn_out_start = self._usage_stats.total_output_tokens
78
+ self._usage_stats.begin_turn()
79
79
  user_message_id: int | None = None
80
80
  try:
81
81
  session_tracker.begin_turn(self._workspace)
@@ -339,13 +339,11 @@ class GraphTurnMixin:
339
339
  scheduler = getattr(self, "_schedule_session_title_generation", None)
340
340
  if callable(scheduler):
341
341
  scheduler(self._session.id, title_source, title)
342
- await self._persist_transcript_snapshot()
343
-
344
342
  elapsed = time.monotonic() - t_turn_start
345
343
  stats = self._usage_stats
346
- turn_calls = stats.total_calls - t_turn_calls_start
347
- turn_in = stats.total_input_tokens - t_turn_in_start
348
- turn_out = stats.total_output_tokens - t_turn_out_start
344
+ turn_calls = stats.turn_calls
345
+ turn_in = stats.turn_input_tokens
346
+ turn_out = stats.turn_output_tokens
349
347
  from voidx.llm.usage import format_token_count
350
348
  dock.append_message(
351
349
  f"[dim]✻ {elapsed:.0f}s[/dim]"
@@ -361,6 +359,13 @@ class GraphTurnMixin:
361
359
  "\n".join(change_lines),
362
360
  markup=True,
363
361
  )
362
+ if via_events():
363
+ await ui_events.emit(TodoCommitted())
364
+ await ui_events.drain()
365
+ else:
366
+ dock.commit_todo_state()
367
+ if self._session:
368
+ await self._persist_transcript_snapshot()
364
369
  except (KeyboardInterrupt, asyncio.CancelledError):
365
370
  if self._session is not None and user_message_id is not None:
366
371
  await delete_messages_from(self._session.id, user_message_id)
@@ -378,6 +383,7 @@ class GraphTurnMixin:
378
383
  }
379
384
  raise
380
385
  finally:
386
+ self._usage_stats.end_turn()
381
387
  pending_guidance = getattr(self, "_pending_guidance", None)
382
388
  if pending_guidance is not None:
383
389
  if pending_guidance:
@@ -392,9 +398,11 @@ class GraphTurnMixin:
392
398
  await ui_events.emit(StatusFinished(status_id="turn:analyzing"))
393
399
  await ui_events.emit(StatusFinished(status_id="agent:-1:progress"))
394
400
  await ui_events.emit(StatusFinished(status_id="compaction"))
401
+ await ui_events.emit(TodoCleared())
395
402
  await ui_events.emit(InputSet(text="", hints=[]))
396
403
  await ui_events.drain()
397
404
  else:
405
+ dock.clear_todo_state()
398
406
  dock.set_input("", [])
399
407
 
400
408
 
@@ -72,7 +72,6 @@ def refine_intent(
72
72
  intent_refined=True,
73
73
  )
74
74
 
75
- service = _skill_service(config, settings)
76
75
  return OnIntentResult(
77
76
  confirmed_intent=confirmed,
78
77
  confidence=inp.confidence,
@@ -82,7 +81,6 @@ def refine_intent(
82
81
  available_tool_ids=available_tool_ids,
83
82
  needs_user_confirmation=needs_confirmation,
84
83
  state_patch=patch,
85
- skill_instructions=[service.render_instruction(match.skill) for match in matches],
86
84
  )
87
85
 
88
86
 
@@ -136,13 +134,14 @@ def _available_tools_for_intent(
136
134
  "lsp_definition",
137
135
  "lsp_references",
138
136
  "task_status",
137
+ "load_skills",
139
138
  }
140
139
  planning_tools = read_tools | {"agent", "todo", "bash"}
141
140
  implementation_tools = set(agent_tools)
142
141
  review_tools = read_tools | {"agent", "todo", "bash"}
143
142
 
144
143
  if intent in {TaskIntent.CHAT, TaskIntent.AMBIGUOUS}:
145
- desired = set()
144
+ desired = {"load_skills"}
146
145
  elif intent == TaskIntent.INSPECT:
147
146
  desired = read_tools
148
147
  elif intent == TaskIntent.DESIGN:
@@ -205,14 +204,17 @@ def _skill_matches(
205
204
  agent=ctx.agent,
206
205
  task_intent=intent.value,
207
206
  interaction_mode=ctx.interaction_mode,
207
+ scopes=("bundled",),
208
+ exclude_names=ctx.active_skill_names,
208
209
  )
209
210
  seen = {normalize_skill_name(match.name) for match in matches}
211
+ excluded = {normalize_skill_name(name) for name in ctx.active_skill_names}
210
212
  for name in inp.suggested_skills:
211
213
  normalized = normalize_skill_name(name)
212
- if normalized in seen:
214
+ if normalized in seen or normalized in excluded:
213
215
  continue
214
216
  skill = service.get(normalized)
215
- if skill is None or not service.is_enabled(skill):
217
+ if skill is None or skill.meta.scope != "bundled" or not service.is_enabled(skill):
216
218
  continue
217
219
  matches.append(SkillMatch(skill=skill, reason="suggested"))
218
220
  seen.add(normalized)
@@ -9,12 +9,18 @@ import json
9
9
  import platform
10
10
  from typing import Any, Iterable
11
11
 
12
- from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage
12
+ from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage, ToolMessage
13
13
  from pydantic import BaseModel, ConfigDict, Field
14
14
 
15
15
  from voidx.agent.message_rows import RowMessageCacheEntry
16
16
  from voidx.config import ApprovalReviewer, Config, UserProfile
17
17
  from voidx.runtime.intent import InteractionMode, TaskIntent, infer_task_intent
18
+ from voidx.skills.context import (
19
+ has_skill_tool_context,
20
+ is_skill_context_content,
21
+ skill_context_cache_key,
22
+ strip_skill_tool_context,
23
+ )
18
24
  from voidx.skills.runtime import SkillRunState
19
25
 
20
26
  _CONTEXT_MARKER = "VOIDX_RUNTIME_CONTEXT"
@@ -26,6 +32,9 @@ class ContextCompilerCache:
26
32
  stable_prefix_key: str = ""
27
33
  stable_system_content: str = ""
28
34
  stable_system_message: SystemMessage | None = None
35
+ skill_context_key: str = ""
36
+ skill_context_content: str = ""
37
+ skill_context_message: HumanMessage | None = None
29
38
  row_messages: dict[int, RowMessageCacheEntry] = dataclass_field(default_factory=dict)
30
39
 
31
40
 
@@ -68,11 +77,15 @@ class RuntimeContext(BaseModel):
68
77
 
69
78
  sections: list[ContextSection]
70
79
  task_sections: list[ContextSection] = Field(default_factory=list)
80
+ skill_context_content: str = ""
71
81
  system_content: str | None = None
72
82
  system_message: SystemMessage | None = Field(default=None, exclude=True)
83
+ skill_context_message: HumanMessage | None = Field(default=None, exclude=True)
73
84
 
74
85
  def section_names(self) -> list[str]:
75
86
  names = [section.name for section in self.sections]
87
+ if self.skill_context_content:
88
+ names.append("Skill Context")
76
89
  names.extend(section.name for section in self.task_sections)
77
90
  return names
78
91
 
@@ -97,6 +110,10 @@ class ContextCompiler:
97
110
  def compile_messages(self, messages: list[BaseMessage]) -> list[BaseMessage]:
98
111
  semantic_messages = raw_semantic_messages(messages)
99
112
  current_user_index = _last_user_index(semantic_messages)
113
+ semantic_messages = _strip_historical_tool_skill_context(
114
+ semantic_messages,
115
+ current_user_index,
116
+ )
100
117
 
101
118
  system_content = self.context.render_system()
102
119
  cached_system = self.context.system_message
@@ -113,11 +130,23 @@ class ContextCompiler:
113
130
  current = semantic_messages[current_user_index]
114
131
  semantic_messages[current_user_index] = _prepend_task_context(current, task_context)
115
132
 
116
- return [prefix, *semantic_messages]
133
+ skill_context_message = self._skill_context_message()
134
+ if skill_context_message is None:
135
+ return [prefix, *semantic_messages]
136
+ return [prefix, skill_context_message, *semantic_messages]
117
137
 
118
138
  def apply_to_messages(self, messages: list[BaseMessage]) -> None:
119
139
  messages[:] = self.compile_messages(messages)
120
140
 
141
+ def _skill_context_message(self) -> HumanMessage | None:
142
+ content = self.context.skill_context_content.strip()
143
+ if not content:
144
+ return None
145
+ cached = self.context.skill_context_message
146
+ if cached is not None and cached.content == content:
147
+ return cached
148
+ return HumanMessage(content=content)
149
+
121
150
 
122
151
  class RuntimeContextBuilder:
123
152
  def __init__(
@@ -133,7 +162,7 @@ class RuntimeContextBuilder:
133
162
  agent: str,
134
163
  interaction_mode: str | InteractionMode,
135
164
  instructions: Iterable[str] = (),
136
- skill_instructions: Iterable[str] = (),
165
+ skill_context_content: str = "",
137
166
  skill_runs: Iterable[SkillRunState] = (),
138
167
  active_skill_summaries: Iterable[str] = (),
139
168
  summary: str | None = None,
@@ -162,7 +191,7 @@ class RuntimeContextBuilder:
162
191
  self.agent = agent
163
192
  self.interaction_mode = InteractionMode.parse(interaction_mode)
164
193
  self.instructions = [item for item in instructions if item.strip()]
165
- self.skill_instructions = [item for item in skill_instructions if item.strip()]
194
+ self.skill_context_content = skill_context_content.strip()
166
195
  self.skill_runs = list(skill_runs)
167
196
  self.active_skill_summaries = [item for item in active_skill_summaries if item.strip()]
168
197
  self.summary = summary.strip() if summary else ""
@@ -192,6 +221,12 @@ class RuntimeContextBuilder:
192
221
  return RuntimeContext(
193
222
  sections=self._build_stable_sections(),
194
223
  task_sections=self._build_task_sections(),
224
+ skill_context_content=self.skill_context_content,
225
+ skill_context_message=(
226
+ HumanMessage(content=self.skill_context_content)
227
+ if self.skill_context_content
228
+ else None
229
+ ),
195
230
  )
196
231
 
197
232
  def build_incremental(
@@ -213,13 +248,38 @@ class RuntimeContextBuilder:
213
248
  cache.stable_system_content = system_content
214
249
  cache.stable_system_message = system_message
215
250
 
251
+ skill_context_content, skill_context_message = self._incremental_skill_context(cache)
252
+
216
253
  return RuntimeContext(
217
254
  sections=sections,
218
255
  task_sections=self._build_task_sections(),
256
+ skill_context_content=skill_context_content,
219
257
  system_content=system_content,
220
258
  system_message=system_message,
259
+ skill_context_message=skill_context_message,
221
260
  ), cache
222
261
 
262
+ def _incremental_skill_context(
263
+ self,
264
+ cache: ContextCompilerCache,
265
+ ) -> tuple[str, HumanMessage | None]:
266
+ content = self.skill_context_content.strip()
267
+ if not content:
268
+ cache.skill_context_key = ""
269
+ cache.skill_context_content = ""
270
+ cache.skill_context_message = None
271
+ return "", None
272
+
273
+ key = skill_context_cache_key(content)
274
+ if cache.skill_context_key == key and cache.skill_context_content:
275
+ return cache.skill_context_content, cache.skill_context_message
276
+
277
+ message = HumanMessage(content=content)
278
+ cache.skill_context_key = key
279
+ cache.skill_context_content = content
280
+ cache.skill_context_message = message
281
+ return content, message
282
+
223
283
  def _build_stable_sections(self) -> list[ContextSection]:
224
284
  sections = [
225
285
  ContextSection(name="Base System", content=self.base_system_prompt),
@@ -266,11 +326,6 @@ class RuntimeContextBuilder:
266
326
  ContextSection(name="Runtime State", content=_render_envelope(envelope)),
267
327
  ContextSection(name="Current DateTime", content=self.current_datetime),
268
328
  ]
269
- if self.skill_instructions:
270
- task_sections.append(ContextSection(
271
- name="Active Skills",
272
- content="\n\n".join(self.skill_instructions),
273
- ))
274
329
  task_sections.append(ContextSection(
275
330
  name="Current Task State",
276
331
  content=self._current_task_state(),
@@ -352,12 +407,33 @@ def raw_semantic_messages(messages: list[BaseMessage]) -> list[BaseMessage]:
352
407
  if isinstance(message, SystemMessage):
353
408
  continue
354
409
  if isinstance(message, HumanMessage):
410
+ if is_skill_context_content(message.content):
411
+ continue
355
412
  raw.append(_strip_turn_overlay(message))
356
413
  else:
357
414
  raw.append(message)
358
415
  return raw
359
416
 
360
417
 
418
+ def _strip_historical_tool_skill_context(
419
+ messages: list[BaseMessage],
420
+ current_user_index: int | None,
421
+ ) -> list[BaseMessage]:
422
+ cutoff = current_user_index if current_user_index is not None else len(messages)
423
+ stripped: list[BaseMessage] = []
424
+ for index, message in enumerate(messages):
425
+ if index < cutoff and isinstance(message, ToolMessage):
426
+ if not has_skill_tool_context(message.content):
427
+ stripped.append(message)
428
+ continue
429
+ content = strip_skill_tool_context(message.content)
430
+ if content != message.content:
431
+ stripped.append(message.model_copy(update={"content": content}))
432
+ continue
433
+ stripped.append(message)
434
+ return stripped
435
+
436
+
361
437
  def _strip_turn_overlay(message: HumanMessage) -> HumanMessage:
362
438
  content = message.content
363
439
  if isinstance(content, str):