voidx 3.4.0__tar.gz → 3.4.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (313) hide show
  1. voidx-3.4.1/LICENSE +21 -0
  2. {voidx-3.4.0 → voidx-3.4.1}/PKG-INFO +5 -1
  3. {voidx-3.4.0 → voidx-3.4.1}/pyproject.toml +2 -0
  4. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/__init__.py +1 -1
  5. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/graph/run_loop.py +1 -1
  6. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/graph/wiring.py +1 -1
  7. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/slash/handler.py +3 -0
  8. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/slash/model.py +41 -2
  9. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/config/models.py +5 -0
  10. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/config/settings.py +1 -1
  11. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/llm/provider.py +3 -1
  12. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/llm/service.py +2 -2
  13. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/file_ops/edit_execute.py +6 -0
  14. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/commands.py +1 -0
  15. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/output/dock/app.py +43 -3
  16. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/output/dock/formatting.py +19 -0
  17. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/tui/input.py +29 -4
  18. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/tui/parser.py +60 -22
  19. {voidx-3.4.0 → voidx-3.4.1}/src/voidx.egg-info/PKG-INFO +5 -1
  20. {voidx-3.4.0 → voidx-3.4.1}/src/voidx.egg-info/SOURCES.txt +1 -0
  21. {voidx-3.4.0 → voidx-3.4.1}/README.md +0 -0
  22. {voidx-3.4.0 → voidx-3.4.1}/setup.cfg +0 -0
  23. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/__init__.py +0 -0
  24. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/agents.py +0 -0
  25. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/attachments.py +0 -0
  26. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/goal_resolver.py +0 -0
  27. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/graph/__init__.py +0 -0
  28. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/graph/compaction.py +0 -0
  29. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/graph/compaction_coordinator.py +0 -0
  30. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/graph/contracts.py +0 -0
  31. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/graph/convergence.py +0 -0
  32. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/graph/core/__init__.py +0 -0
  33. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/graph/core/_voidx_graph.py +0 -0
  34. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/graph/core/helpers.py +0 -0
  35. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/graph/core/llm.py +0 -0
  36. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/graph/permissions.py +0 -0
  37. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/graph/runtime.py +0 -0
  38. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/graph/runtime_guards.py +0 -0
  39. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/graph/session_mixin.py +0 -0
  40. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/graph/session_runtime.py +0 -0
  41. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/graph/streaming.py +0 -0
  42. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/graph/subagent.py +0 -0
  43. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/graph/title_mixin.py +0 -0
  44. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/graph/todo_events.py +0 -0
  45. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/graph/tool_execution.py +0 -0
  46. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/graph/tool_executor/__init__.py +0 -0
  47. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/graph/tool_executor/executor.py +0 -0
  48. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/graph/tool_executor/guards.py +0 -0
  49. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/graph/tool_executor/helpers.py +0 -0
  50. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/graph/tool_executor/types.py +0 -0
  51. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/graph/tool_executor/ui.py +0 -0
  52. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/graph/tool_executor/workflow.py +0 -0
  53. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/graph/topology.py +0 -0
  54. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/graph/transcript_mixin.py +0 -0
  55. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/graph/turn_mixin.py +0 -0
  56. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/graph/turn_runner.py +0 -0
  57. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/graph/workflow_utils.py +0 -0
  58. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/message_rows.py +0 -0
  59. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/message_trimming.py +0 -0
  60. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/prompts.py +0 -0
  61. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/runtime_context.py +0 -0
  62. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/slash/__init__.py +0 -0
  63. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/slash/code_ide.py +0 -0
  64. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/slash/guide.py +0 -0
  65. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/slash/host.py +0 -0
  66. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/slash/init.py +0 -0
  67. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/slash/lsp.py +0 -0
  68. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/slash/mcp.py +0 -0
  69. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/slash/profile.py +0 -0
  70. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/slash/runtime.py +0 -0
  71. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/slash/session.py +0 -0
  72. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/slash/skills.py +0 -0
  73. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/slash/upgrade.py +0 -0
  74. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/state.py +0 -0
  75. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/task_state.py +0 -0
  76. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/todo_state.py +0 -0
  77. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/tool_call_ids.py +0 -0
  78. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/tool_exchange_sanitizer.py +0 -0
  79. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/tool_filters.py +0 -0
  80. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/tool_messages.py +0 -0
  81. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/agent/tool_result_storage.py +0 -0
  82. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/config/__init__.py +0 -0
  83. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/config/enums.py +0 -0
  84. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/config/permissions.py +0 -0
  85. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/config/settings_agent.py +0 -0
  86. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/config/settings_api_keys.py +0 -0
  87. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/config/settings_code_ide.py +0 -0
  88. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/config/settings_custom.py +0 -0
  89. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/config/settings_mcp.py +0 -0
  90. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/config/settings_permissions.py +0 -0
  91. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/config/settings_skills.py +0 -0
  92. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/config/settings_update.py +0 -0
  93. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/config/settings_utils.py +0 -0
  94. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/config/settings_web.py +0 -0
  95. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/data/__init__.py +0 -0
  96. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/data/templates/api-doc.md +0 -0
  97. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/data/templates/prd.md +0 -0
  98. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/data/templates/readme.md +0 -0
  99. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/data/templates/rfc.md +0 -0
  100. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/data/templates/tech-design.md +0 -0
  101. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/diffing.py +0 -0
  102. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/llm/__init__.py +0 -0
  103. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/llm/catalog.py +0 -0
  104. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/llm/compaction.py +0 -0
  105. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/llm/context.py +0 -0
  106. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/llm/instruction.py +0 -0
  107. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/llm/message_markers.py +0 -0
  108. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/llm/message_status.py +0 -0
  109. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/llm/usage.py +0 -0
  110. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/logging/__init__.py +0 -0
  111. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/logging/request_log.py +0 -0
  112. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/logging/tool_log.py +0 -0
  113. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/lsp/__init__.py +0 -0
  114. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/lsp/client.py +0 -0
  115. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/lsp/config.py +0 -0
  116. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/lsp/detector.py +0 -0
  117. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/lsp/detector_data.py +0 -0
  118. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/lsp/errors.py +0 -0
  119. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/lsp/manager.py +0 -0
  120. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/lsp/schema.py +0 -0
  121. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/lsp/service.py +0 -0
  122. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/main.py +0 -0
  123. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/mcp/__init__.py +0 -0
  124. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/mcp/client/__init__.py +0 -0
  125. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/mcp/client/base.py +0 -0
  126. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/mcp/client/errors.py +0 -0
  127. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/mcp/client/http_transport.py +0 -0
  128. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/mcp/client/sse_transport.py +0 -0
  129. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/mcp/client/stdio_transport.py +0 -0
  130. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/mcp/manager.py +0 -0
  131. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/mcp/schema.py +0 -0
  132. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/mcp/tool.py +0 -0
  133. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/mcp_servers/__init__.py +0 -0
  134. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/mcp_servers/web.py +0 -0
  135. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/memory/__init__.py +0 -0
  136. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/memory/cleanup.py +0 -0
  137. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/memory/context_frames.py +0 -0
  138. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/memory/jsonl_store.py +0 -0
  139. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/memory/model_profiles.py +0 -0
  140. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/memory/runtime_state.py +0 -0
  141. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/memory/service.py +0 -0
  142. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/memory/session.py +0 -0
  143. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/memory/store.py +0 -0
  144. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/memory/subagents.py +0 -0
  145. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/memory/transcript.py +0 -0
  146. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/permission/__init__.py +0 -0
  147. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/permission/context.py +0 -0
  148. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/permission/engine.py +0 -0
  149. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/permission/evaluate.py +0 -0
  150. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/permission/rules.py +0 -0
  151. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/permission/sandbox.py +0 -0
  152. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/permission/schema.py +0 -0
  153. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/permission/service.py +0 -0
  154. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/permission/wildcard.py +0 -0
  155. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/runtime/__init__.py +0 -0
  156. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/runtime/attachments.py +0 -0
  157. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/runtime/intent.py +0 -0
  158. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/runtime/reference_tokens.py +0 -0
  159. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/runtime/task_state.py +0 -0
  160. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/runtime/todo.py +0 -0
  161. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/runtime/ui.py +0 -0
  162. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/runtime/ui_port.py +0 -0
  163. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/selfupdate.py +0 -0
  164. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/skills/__init__.py +0 -0
  165. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/skills/context.py +0 -0
  166. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/skills/references.py +0 -0
  167. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/skills/registry.py +0 -0
  168. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/skills/schema.py +0 -0
  169. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/skills/service.py +0 -0
  170. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/__init__.py +0 -0
  171. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/agent.py +0 -0
  172. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/base.py +0 -0
  173. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/bash/__init__.py +0 -0
  174. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/bash/core.py +0 -0
  175. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/bash/hint/__init__.py +0 -0
  176. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/bash/hint/file.py +0 -0
  177. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/bash/hint/git.py +0 -0
  178. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/bash/hint/search.py +0 -0
  179. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/bash/router.py +0 -0
  180. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/bash/safety.py +0 -0
  181. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/bash/tool.py +0 -0
  182. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/clarify.py +0 -0
  183. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/compact_context.py +0 -0
  184. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/file_ops/__init__.py +0 -0
  185. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/file_ops/edit_resolve.py +0 -0
  186. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/file_ops/file.py +0 -0
  187. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/file_ops/read.py +0 -0
  188. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/file_ops/types.py +0 -0
  189. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/file_ops/write.py +0 -0
  190. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/file_state.py +0 -0
  191. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/git.py +0 -0
  192. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/load_doc_template.py +0 -0
  193. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/load_skills.py +0 -0
  194. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/lsp.py +0 -0
  195. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/plan_checkpoint.py +0 -0
  196. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/powershell/__init__.py +0 -0
  197. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/powershell/core.py +0 -0
  198. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/powershell/hint/__init__.py +0 -0
  199. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/powershell/hint/file.py +0 -0
  200. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/powershell/hint/search.py +0 -0
  201. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/powershell/router.py +0 -0
  202. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/powershell/safety.py +0 -0
  203. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/powershell/sandbox.py +0 -0
  204. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/powershell/tool.py +0 -0
  205. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/registry.py +0 -0
  206. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/search.py +0 -0
  207. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/service.py +0 -0
  208. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/shell/__init__.py +0 -0
  209. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/shell/common.py +0 -0
  210. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/shell/hint/__init__.py +0 -0
  211. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/shell/hint/git.py +0 -0
  212. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/skills.py +0 -0
  213. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/task_status.py +0 -0
  214. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/task_tracker.py +0 -0
  215. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/todo.py +0 -0
  216. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/web_content.py +0 -0
  217. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/web_mcp.py +0 -0
  218. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/webfetch.py +0 -0
  219. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/websearch.py +0 -0
  220. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/tools/workflow.py +0 -0
  221. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/__init__.py +0 -0
  222. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/frontend.py +0 -0
  223. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/gateway/__init__.py +0 -0
  224. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/gateway/adapter.py +0 -0
  225. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/gateway/bootstrap.py +0 -0
  226. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/gateway/diff_review.py +0 -0
  227. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/gateway/server.py +0 -0
  228. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/gateway/session.py +0 -0
  229. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/gateway/terminal.py +0 -0
  230. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/output/__init__.py +0 -0
  231. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/output/agent_display.py +0 -0
  232. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/output/browse.py +0 -0
  233. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/output/capture.py +0 -0
  234. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/output/console/__init__.py +0 -0
  235. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/output/console/app.py +0 -0
  236. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/output/console/formatting.py +0 -0
  237. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/output/console/streaming.py +0 -0
  238. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/output/diff.py +0 -0
  239. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/output/display_policy.py +0 -0
  240. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/output/dock/__init__.py +0 -0
  241. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/output/dock/agent_placeholder.py +0 -0
  242. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/output/dock/nodes.py +0 -0
  243. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/output/dock/nodes_checkpoint.py +0 -0
  244. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/output/dock/nodes_clarify.py +0 -0
  245. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/output/dock/nodes_permission.py +0 -0
  246. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/output/dock/nodes_startup.py +0 -0
  247. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/output/dock/nodes_status.py +0 -0
  248. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/output/dock/state.py +0 -0
  249. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/output/dock/status.py +0 -0
  250. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/output/dock/stream.py +0 -0
  251. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/output/dock/todo.py +0 -0
  252. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/output/events/__init__.py +0 -0
  253. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/output/events/bus.py +0 -0
  254. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/output/events/consumers.py +0 -0
  255. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/output/events/schema.py +0 -0
  256. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/output/tree.py +0 -0
  257. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/output/types.py +0 -0
  258. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/protocol/__init__.py +0 -0
  259. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/protocol/commands.py +0 -0
  260. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/protocol/envelope.py +0 -0
  261. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/protocol/requests.py +0 -0
  262. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/protocol/schema.py +0 -0
  263. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/protocol/transcript.py +0 -0
  264. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/protocol/v2/__init__.py +0 -0
  265. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/protocol/v2/envelope.py +0 -0
  266. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/protocol/v2/methods.py +0 -0
  267. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/protocol/v2/snapshot.py +0 -0
  268. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/protocol/v2/threads.py +0 -0
  269. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/session.py +0 -0
  270. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/tools/__init__.py +0 -0
  271. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/tools/attachment_tokens.py +0 -0
  272. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/tools/clipboard_image.py +0 -0
  273. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/tools/clipboard_text.py +0 -0
  274. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/tools/code_ide.py +0 -0
  275. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/tools/file_picker.py +0 -0
  276. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/tools/skill_picker.py +0 -0
  277. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/transcript.py +0 -0
  278. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/tui/__init__.py +0 -0
  279. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/tui/activity.py +0 -0
  280. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/tui/app.py +0 -0
  281. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/tui/choice_mixin.py +0 -0
  282. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/tui/clipboard_mixin.py +0 -0
  283. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/tui/helpers.py +0 -0
  284. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/tui/overlays.py +0 -0
  285. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/tui/panels.py +0 -0
  286. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/tui/render_activity.py +0 -0
  287. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/tui/render_frame.py +0 -0
  288. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/tui/render_input.py +0 -0
  289. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/tui/render_status.py +0 -0
  290. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/tui/render_todo.py +0 -0
  291. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/tui/renderer.py +0 -0
  292. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/tui/state.py +0 -0
  293. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/tui/terminal_mixin.py +0 -0
  294. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/ui/tui/text_prompt_mixin.py +0 -0
  295. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/workflow/__init__.py +0 -0
  296. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/workflow/auto_advance.py +0 -0
  297. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/workflow/context.py +0 -0
  298. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/workflow/dag.py +0 -0
  299. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/workflow/nodes.py +0 -0
  300. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/workflow/policy.py +0 -0
  301. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/workflow/reconcile.py +0 -0
  302. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/workflow/render.py +0 -0
  303. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/workflow/route.py +0 -0
  304. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/workflow/runtime.py +0 -0
  305. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/workflow/schema.py +0 -0
  306. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/workflow/service.py +0 -0
  307. {voidx-3.4.0 → voidx-3.4.1}/src/voidx/workflow/types.py +0 -0
  308. {voidx-3.4.0 → voidx-3.4.1}/src/voidx.egg-info/dependency_links.txt +0 -0
  309. {voidx-3.4.0 → voidx-3.4.1}/src/voidx.egg-info/entry_points.txt +0 -0
  310. {voidx-3.4.0 → voidx-3.4.1}/src/voidx.egg-info/requires.txt +0 -0
  311. {voidx-3.4.0 → voidx-3.4.1}/src/voidx.egg-info/top_level.txt +0 -0
  312. {voidx-3.4.0 → voidx-3.4.1}/tests/test_install_sh.py +0 -0
  313. {voidx-3.4.0 → voidx-3.4.1}/tests/test_npm_package.py +0 -0
voidx-3.4.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 chikhamx
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,9 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: voidx
3
- Version: 3.4.0
3
+ Version: 3.4.1
4
4
  Summary: A coding agent that quantifies everything and solves with tools, not fuzzy prompts.
5
+ Author: chikhamx
6
+ License: MIT
5
7
  Requires-Python: >=3.11
6
8
  Description-Content-Type: text/markdown
9
+ License-File: LICENSE
7
10
  Requires-Dist: langgraph>=0.3.0
8
11
  Requires-Dist: langchain>=0.3.0
9
12
  Requires-Dist: langchain-anthropic>=0.3.0
@@ -23,6 +26,7 @@ Provides-Extra: dev
23
26
  Requires-Dist: build>=1.2.0; extra == "dev"
24
27
  Requires-Dist: pytest>=8.0.0; extra == "dev"
25
28
  Requires-Dist: pytest-asyncio>=0.25.0; extra == "dev"
29
+ Dynamic: license-file
26
30
 
27
31
  # voidx
28
32
 
@@ -2,6 +2,8 @@
2
2
  name = "voidx"
3
3
  dynamic = ["version"]
4
4
  description = "A coding agent that quantifies everything and solves with tools, not fuzzy prompts."
5
+ license = {text = "MIT"}
6
+ authors = [{name = "chikhamx"}]
5
7
  readme = "README.md"
6
8
  requires-python = ">=3.11"
7
9
  dependencies = [
@@ -1,3 +1,3 @@
1
1
  """VoidX - A coding agent that quantifies everything."""
2
2
 
3
- __version__ = "3.4.0"
3
+ __version__ = "3.4.1"
@@ -169,7 +169,7 @@ class GraphRunLoopMixin(GraphTurnMixin, GraphSessionMixin, GraphTranscriptMixin)
169
169
  model=self.config.model.model,
170
170
  workspace=self._workspace,
171
171
  session_title=title,
172
- context_limit=get_context_limit(self.config.model.provider),
172
+ context_limit=get_context_limit(self.config.model.provider, self.config.model.protocol or "", self.config.model.context_window),
173
173
  reasoning_effort=self.config.model.reasoning_effort or "xhigh",
174
174
  permission_label=self._permission.status_label,
175
175
  sandbox_label=lambda: self._permission._sandbox_label(),
@@ -74,7 +74,7 @@ def build_permission_service(config: Config, *, notifier: Callable[[str], object
74
74
 
75
75
 
76
76
  def build_compaction_service(config: Config) -> tuple[UsageStats, CompactionService]:
77
- context_limit = get_context_limit(config.model.provider)
77
+ context_limit = get_context_limit(config.model.provider, config.model.protocol or "", config.model.context_window)
78
78
  return (
79
79
  UsageStats(context_limit=context_limit),
80
80
  CompactionService(
@@ -146,6 +146,9 @@ class SlashHandler(
146
146
  elif args == "reasoning" or args.startswith("reasoning "):
147
147
  target = args.removeprefix("reasoning").strip()
148
148
  await self._model_reasoning(target)
149
+ elif args == "ctx" or args.startswith("ctx "):
150
+ target = args.removeprefix("ctx").strip()
151
+ await self._model_ctx(target)
149
152
  elif args:
150
153
  await self._switch_model(args)
151
154
  else:
@@ -190,7 +190,7 @@ class SlashModelMixin:
190
190
  else:
191
191
  ui.print("[dim](none)[/dim]")
192
192
  ui.print()
193
- ui.print("[dim]Usage: /model list|new|reasoning|test|del|switch|<name>[/dim]")
193
+ ui.print("[dim]Usage: /model list|new|reasoning|ctx|test|del|switch|<name>[/dim]")
194
194
 
195
195
  async def _model_list(self) -> None:
196
196
  cfg = self.host.config
@@ -341,6 +341,45 @@ class SlashModelMixin:
341
341
 
342
342
  ui.print(f"Reasoning effort: [cyan]{new_effort}[/cyan] [green]✓[/green]")
343
343
 
344
+ async def _model_ctx(self, target: str) -> None:
345
+ choices_map: dict[str, int | None] = {
346
+ "128k": 128_000,
347
+ "256k": 256_000,
348
+ "384k": 384_000,
349
+ "512k": 512_000,
350
+ "1M": 1_000_000,
351
+ "Auto": None,
352
+ }
353
+
354
+ if target:
355
+ key = target.lower()
356
+ normalized = {c.lower(): (c, v) for c, v in choices_map.items()}
357
+ if key not in normalized:
358
+ ui.error(f"Invalid context window: '{target}'. Use: {', '.join(choices_map)}")
359
+ return
360
+ new_label, new_value = normalized[key]
361
+ else:
362
+ choices = list(choices_map)
363
+ idx = await _select_from_list(self.host.app, "Context window", choices)
364
+ if idx is None:
365
+ ui.print("[dim]Cancelled.[/dim]")
366
+ return
367
+ new_value = choices_map[choices[idx]]
368
+ new_label = choices[idx]
369
+
370
+ self.host.config.model.context_window = new_value
371
+ self._sync_context_limit()
372
+
373
+ settings = self.host.settings
374
+ if settings is not None:
375
+ if new_value is None:
376
+ settings._pop_setting("context_window")
377
+ else:
378
+ settings._set_setting("context_window", new_value)
379
+
380
+ display = "Auto (provider default)" if new_value is None else f"{new_label}"
381
+ ui.print(f"Context window: [cyan]{display}[/cyan] [green]✓[/green]")
382
+
344
383
  async def _switch_model(self, model_spec: str) -> None:
345
384
  from voidx.llm.service import create_chat_model
346
385
  from voidx.memory.service import update_session_model
@@ -412,7 +451,7 @@ class SlashModelMixin:
412
451
  def _sync_context_limit(self) -> None:
413
452
  from voidx.llm.service import get_context_limit
414
453
 
415
- limit = get_context_limit(self.host.config.model.provider)
454
+ limit = get_context_limit(self.host.config.model.provider, self.host.config.model.protocol or "", self.host.config.model.context_window)
416
455
  stats = self.host.usage_stats
417
456
  if stats is not None:
418
457
  stats.context_limit = limit
@@ -32,6 +32,11 @@ class ModelConfig(BaseModel):
32
32
  default="xhigh",
33
33
  description="Reasoning intensity: off, low, medium, high, xhigh, or None (provider default)",
34
34
  )
35
+ context_window: int | None = Field(
36
+ default=None,
37
+ ge=1,
38
+ description="Override context window size in tokens. None = auto-detect by provider.",
39
+ )
35
40
 
36
41
 
37
42
  class AgentConfig(BaseModel):
@@ -312,7 +312,7 @@ class Settings(
312
312
  base_url = None
313
313
  protocol = None
314
314
 
315
- cfg = ModelConfig(provider=provider, model=model, base_url=base_url)
315
+ cfg = ModelConfig(provider=provider, model=model, base_url=base_url, context_window=self._effective_data().get("context_window"))
316
316
  if protocol:
317
317
  cfg.protocol = protocol
318
318
  permission_mode = self.get_permission_mode()
@@ -584,8 +584,10 @@ def extract_thinking(chunk: AIMessageChunk, protocol: str) -> str:
584
584
 
585
585
  # ── context limits ────────────────────────────────────────────────────────
586
586
 
587
- def get_context_limit(provider: str, protocol: str = "") -> int:
587
+ def get_context_limit(provider: str, protocol: str = "", context_window: int | None = None) -> int:
588
588
  """Return context-window limit for *provider*. Falls back to *protocol* for unknown providers."""
589
+ if context_window is not None and context_window > 0:
590
+ return context_window
589
591
  limits: dict[str, int] = {
590
592
  PROTOCOL_DEEPSEEK: 1_000_000,
591
593
  "anthropic": 200_000,
@@ -21,8 +21,8 @@ def extract_thinking(chunk: AIMessageChunk, protocol: str) -> str:
21
21
  return provider.extract_thinking(chunk, protocol)
22
22
 
23
23
 
24
- def get_context_limit(provider_name: str, protocol: str = "") -> int:
25
- return provider.get_context_limit(provider_name, protocol)
24
+ def get_context_limit(provider_name: str, protocol: str = "", context_window: int | None = None) -> int:
25
+ return provider.get_context_limit(provider_name, protocol, context_window)
26
26
 
27
27
  __all__ = [
28
28
  "create_chat_model",
@@ -158,6 +158,12 @@ async def _execute_text_replace(
158
158
 
159
159
  lines = list(display.lines)
160
160
  new_lines = _split_edit_lines(new_string)
161
+ if (
162
+ start_line == end_line
163
+ and new_string in ("", "\n", " ")
164
+ and lines[start_line - 1] != ""
165
+ ):
166
+ new_lines = []
161
167
  lines[start_line - 1:end_line] = new_lines
162
168
 
163
169
  # Head-line dedup: if the first line of new_string exactly matches the
@@ -49,6 +49,7 @@ COMMANDS: list[tuple[str, str]] = [
49
49
  ("/mode goal", "Keep multi-step work scoped"),
50
50
  ("/mode plan", "Read-only plan mode"),
51
51
  ("/model", "Switch configured model"),
52
+ ("/model ctx", "Set context window size"),
52
53
  ("/model del", "Remove a profile"),
53
54
  ("/model list", "Show configured model details"),
54
55
  ("/model new", "Create or update a model profile"),
@@ -12,9 +12,12 @@ from rich.text import Text
12
12
 
13
13
  from voidx.ui.output.dock.formatting import (
14
14
  ANSI_LINE_PREFIX,
15
+ _ansi_line,
15
16
  _clean,
17
+ _markdown_lines,
16
18
  _strip_ansi_trailing_space,
17
19
  _text_from_line,
20
+ split_pasted_segments,
18
21
  )
19
22
  from voidx.ui.output.dock.nodes import DockNodeMixin
20
23
  from voidx.ui.output.dock.stream import DockStreamMixin
@@ -219,18 +222,55 @@ class BottomInputDock(DockStreamMixin, DockStatusMixin, DockNodeMixin):
219
222
  if self._tree.root.children:
220
223
  self._append_root_spacer()
221
224
  preview = _clean(text)
222
- lines = [_strip_ansi_trailing_space(line) for line in (preview.splitlines() or [preview])]
225
+ segments = split_pasted_segments(preview)
226
+ header, body_lines = self._render_turn_segments(segments)
223
227
  self._current_turn = self._tree.new_node(
224
228
  parent=self._tree.root,
225
229
  node_type="turn",
226
- header=f"[bold white]❯[/] {escape(lines[0])}",
227
- body_lines=[escape(line) for line in lines[1:]],
230
+ header=f"[bold white]❯[/] {header}",
231
+ body_lines=body_lines,
228
232
  collapsed=False,
229
233
  )
230
234
  self._mark_settled(self._current_turn)
231
235
  self.refresh()
232
236
  return self._current_turn
233
237
 
238
+ def _render_turn_segments(
239
+ self, segments: list[tuple[bool, str]]
240
+ ) -> tuple[str, list[str]]:
241
+ """Render (is_pasted, content) segments into (header, body_lines).
242
+
243
+ Non-pasted segments use escape() plain text (existing behavior).
244
+ Pasted segments use _markdown_lines() with _ansi_line() wrapping.
245
+ """
246
+ header = ""
247
+ body_lines: list[str] = []
248
+ width = self._markdown_width()
249
+ for index, (is_pasted, content) in enumerate(segments):
250
+ if is_pasted:
251
+ md_lines = [
252
+ _ansi_line(line)
253
+ for line in _markdown_lines(content, width)
254
+ ]
255
+ if index == 0 and md_lines:
256
+ header = md_lines[0]
257
+ body_lines.extend(md_lines[1:])
258
+ else:
259
+ body_lines.extend(md_lines)
260
+ else:
261
+ lines = [
262
+ _strip_ansi_trailing_space(line)
263
+ for line in ((content.splitlines() or [content]) if content else [])
264
+ ]
265
+ if index == 0 and lines:
266
+ header = escape(lines[0])
267
+ body_lines.extend(escape(line) for line in lines[1:])
268
+ else:
269
+ body_lines.extend(escape(line) for line in lines)
270
+ if not header and body_lines:
271
+ header = body_lines.pop(0)
272
+ return header, body_lines
273
+
234
274
  def ensure_agent(self) -> OutputNode:
235
275
  if self._current_agent is None:
236
276
  self._append_root_spacer()
@@ -92,6 +92,25 @@ def _strip_ansi_trailing_space(line: str) -> str:
92
92
  return buffer.getvalue()
93
93
 
94
94
 
95
+ _PASTED_RE = re.compile(r"<pasted>\n(.*?)\n</pasted>", re.DOTALL)
96
+
97
+
98
+ def split_pasted_segments(text: str) -> list[tuple[bool, str]]:
99
+ """Split text into (is_pasted, content) segments by <pasted> blocks."""
100
+ segments: list[tuple[bool, str]] = []
101
+ pos = 0
102
+ for match in _PASTED_RE.finditer(text):
103
+ if match.start() > pos:
104
+ segments.append((False, text[pos:match.start()]))
105
+ segments.append((True, match.group(1)))
106
+ pos = match.end()
107
+ if pos < len(text):
108
+ segments.append((False, text[pos:]))
109
+ if not segments:
110
+ segments.append((False, text))
111
+ return segments
112
+
113
+
95
114
  def short_value(value: object) -> str:
96
115
  text = str(value).replace("\n", "\\n")
97
116
  return text[:157] + "..." if len(text) > 160 else text
@@ -52,12 +52,15 @@ class _InputEditorMixin:
52
52
 
53
53
  def _register_text_paste(self, text: str) -> str:
54
54
  paste_id = self._paste_next_id
55
+ display = self._compute_text_paste_display(paste_id, text)
56
+ return self._register_paste_entry(kind="text", display=display, expanded=text)
57
+
58
+ @staticmethod
59
+ def _compute_text_paste_display(paste_id: int, text: str) -> str:
55
60
  line_count = len(text.split("\n"))
56
61
  if line_count > 1:
57
- display = f"[Pasted text #{paste_id} +{line_count - 1} lines]"
58
- else:
59
- display = f"[Pasted text #{paste_id} {len(text)} chars]"
60
- return self._register_paste_entry(kind="text", display=display, expanded=text)
62
+ return f"[Pasted text #{paste_id} +{line_count - 1} lines]"
63
+ return f"[Pasted text #{paste_id} {len(text)} chars]"
61
64
 
62
65
  def _register_image_paste(self, stem: str, size: int) -> str:
63
66
  from voidx.ui.tools.attachment_tokens import image_attachment_token_text
@@ -80,6 +83,8 @@ class _InputEditorMixin:
80
83
  display = str(entry.get("display", ""))
81
84
  expanded = str(entry.get("expanded", ""))
82
85
  if display:
86
+ if entry.get("kind") == "text":
87
+ expanded = f"<pasted>\n{expanded}\n</pasted>"
83
88
  result = result.replace(display, expanded)
84
89
  return result
85
90
 
@@ -343,3 +348,23 @@ class _InputEditorMixin:
343
348
  self._cursor_col = col + len(token)
344
349
  self._clear_attachment_suppression_on_edit()
345
350
  self._update_input_panels()
351
+
352
+ def _replace_token_in_editor(self, old_token: str, new_token: str) -> None:
353
+ """Replace a token string in the input with an updated version.
354
+
355
+ Used when merging split-batch pastes: the old token (e.g.
356
+ "[Pasted text #1 +3 lines]") is replaced in-place by the new
357
+ token (e.g. "[Pasted text #1 +4 lines]") with updated line count.
358
+ """
359
+ self._reset_ctrl_c()
360
+ line = self._current_line()
361
+ idx = line.rfind(old_token)
362
+ if idx == -1:
363
+ # Token not found on current line — fall back to inserting new
364
+ self._insert_text_token(new_token)
365
+ return
366
+ new_line = line[:idx] + new_token + line[idx + len(old_token):]
367
+ self._set_current_line(new_line)
368
+ self._cursor_col = idx + len(new_token)
369
+ self._clear_attachment_suppression_on_edit()
370
+ self._update_input_panels()
@@ -77,30 +77,41 @@ class _InputParserMixin:
77
77
  if mapped:
78
78
  return mapped.encode("utf-8")
79
79
  return ("\x00" + ch2).encode("utf-8")
80
- # Newline first char: try to drain a paste
81
- if ch in ("\r", "\n"):
82
- pasted = self._try_drain_win32_paste(ch)
83
- if pasted is not None:
84
- return pasted
85
- return ch.encode("utf-8")
80
+ # Any character could be the start of a paste. Probe for
81
+ # quickly-following chars; if they look like a paste (multiple
82
+ # newlines or a long run), wrap the whole thing as bracketed
83
+ # paste. This fixes the case where pasted text starts with a
84
+ # regular char (e.g. "line1\r\nline2") — previously only
85
+ # newline-first pastes triggered the drain, so the leading
86
+ # "line1" was inserted char-by-char while the rest became a
87
+ # partial [Pasted text] token.
88
+ pasted = self._try_drain_win32_paste(ch)
89
+ if pasted is not None:
90
+ return pasted
86
91
  return ch.encode("utf-8")
87
92
 
88
93
  return await asyncio.to_thread(_read)
89
94
 
90
95
  def _try_drain_win32_paste(self, first_char: str, timeout_ms: int = 20) -> bytes | None:
91
- """Drain remaining console input after a newline to detect a paste.
96
+ """Drain remaining console input to detect a paste.
92
97
 
93
- Windows Terminal injects pasted content character-by-character into the
94
- console input buffer. When the first character is ``\\r`` or ``\\n``,
95
- this method reads any quickly-following characters and, if they look
96
- like a paste (multiple newlines or a long run), wraps them in a
97
- bracketed-paste sequence so ``_process_paste`` handles them as a unit.
98
+ Windows Terminal injects pasted content character-by-character into
99
+ the console input buffer. This method reads any quickly-following
100
+ characters after ``first_char`` and, if they look like a paste
101
+ (multiple newlines or a long run), wraps them in a bracketed-paste
102
+ sequence so ``_process_paste`` handles them as a unit.
103
+
104
+ Works with ANY first character, not just newlines — pasted text
105
+ often starts with a regular character (e.g. "line1\\r\\nline2").
98
106
  Returns the bracketed-paste bytes, or ``None`` if this is not a paste.
99
107
  """
100
108
  import msvcrt
101
109
  import time
102
110
 
103
- if first_char not in ("\r", "\n"):
111
+ # Quick exit: if nothing follows immediately, this is a single
112
+ # keypress (normal typing), not a paste. Avoids entering the poll
113
+ # loop for every ordinary character.
114
+ if not msvcrt.kbhit():
104
115
  return None
105
116
 
106
117
  buffer = first_char
@@ -121,6 +132,11 @@ class _InputParserMixin:
121
132
  newline_count = buffer.count("\r") + buffer.count("\n")
122
133
  if newline_count >= 2 or len(buffer) > 8:
123
134
  return b"\x1b[200~" + buffer.encode("utf-8", errors="replace") + b"\x1b[201~"
135
+ # Not a paste: preserve any extra chars we consumed so they are
136
+ # processed on the next _process_input call instead of being lost.
137
+ extra = buffer[1:]
138
+ if extra:
139
+ self._pending_bytes = extra.encode("utf-8", errors="replace")
124
140
  return None
125
141
 
126
142
  async def _read_input_line(self) -> bytes:
@@ -231,16 +247,38 @@ class _InputParserMixin:
231
247
  return
232
248
  # Normalise line endings: \r\n → \n, \r → \n
233
249
  text = text.replace("\r\n", "\n").replace("\r", "\n")
234
- if len(text.split("\n")) > 3 or len(text) > 200:
250
+ # All pasted text collapses to a [Pasted text #N ...] token, regardless
251
+ # of length. This keeps the input box compact and clearly marks pasted
252
+ # content as a distinct unit.
253
+ #
254
+ # Merge with the previous paste if it happened within a short window.
255
+ # Windows Terminal may split a large paste across multiple drain
256
+ # batches (20ms timeout fires between batches), producing two
257
+ # bracketed-paste sequences. Without merging, the user sees two
258
+ # adjacent tokens for what was a single paste action.
259
+ import time
260
+
261
+ now = time.monotonic()
262
+ last = getattr(self, "_last_paste_time", None)
263
+ self._last_paste_time = now
264
+
265
+ if (
266
+ last is not None
267
+ and (now - last) < 0.2
268
+ and self._paste_entries
269
+ and self._paste_entries[-1].get("kind") == "text"
270
+ ):
271
+ # Append to the previous paste entry
272
+ entry = self._paste_entries[-1]
273
+ combined = str(entry["expanded"]) + text
274
+ old_display = str(entry["display"])
275
+ new_display = self._compute_text_paste_display(int(entry["id"]), combined)
276
+ entry["expanded"] = combined
277
+ entry["display"] = new_display
278
+ # Replace the old token in the editor with the updated one
279
+ self._replace_token_in_editor(old_display, new_display)
280
+ else:
235
281
  self._insert_text_token(self._register_text_paste(text))
236
- return
237
- # Split into lines and insert each, creating editor newlines
238
- lines = text.split("\n")
239
- for i, line in enumerate(lines):
240
- if i > 0:
241
- self._insert_newline()
242
- if line:
243
- self._insert_text(line)
244
282
 
245
283
  def _dispatch_key(self, data: bytes, offset: int) -> tuple[int, str | None]:
246
284
  """Parse a key sequence starting at offset. Returns (bytes_consumed, action)."""
@@ -1,9 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: voidx
3
- Version: 3.4.0
3
+ Version: 3.4.1
4
4
  Summary: A coding agent that quantifies everything and solves with tools, not fuzzy prompts.
5
+ Author: chikhamx
6
+ License: MIT
5
7
  Requires-Python: >=3.11
6
8
  Description-Content-Type: text/markdown
9
+ License-File: LICENSE
7
10
  Requires-Dist: langgraph>=0.3.0
8
11
  Requires-Dist: langchain>=0.3.0
9
12
  Requires-Dist: langchain-anthropic>=0.3.0
@@ -23,6 +26,7 @@ Provides-Extra: dev
23
26
  Requires-Dist: build>=1.2.0; extra == "dev"
24
27
  Requires-Dist: pytest>=8.0.0; extra == "dev"
25
28
  Requires-Dist: pytest-asyncio>=0.25.0; extra == "dev"
29
+ Dynamic: license-file
26
30
 
27
31
  # voidx
28
32
 
@@ -1,3 +1,4 @@
1
+ LICENSE
1
2
  README.md
2
3
  pyproject.toml
3
4
  src/voidx/__init__.py
File without changes
File without changes
File without changes
File without changes
File without changes