voidx 3.4.0__tar.gz → 3.4.2__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.
- voidx-3.4.2/LICENSE +21 -0
- {voidx-3.4.0 → voidx-3.4.2}/PKG-INFO +5 -1
- {voidx-3.4.0 → voidx-3.4.2}/pyproject.toml +2 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/__init__.py +1 -1
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/attachments.py +14 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/graph/run_loop.py +1 -1
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/graph/wiring.py +1 -1
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/slash/handler.py +3 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/slash/model.py +41 -2
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/config/models.py +5 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/config/settings.py +1 -1
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/llm/provider.py +3 -1
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/llm/service.py +2 -2
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/file_ops/edit_execute.py +6 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/commands.py +1 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/output/dock/app.py +21 -5
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/output/dock/formatting.py +19 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/output/tree.py +40 -5
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/tui/input.py +29 -4
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/tui/panels.py +7 -1
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/tui/parser.py +66 -25
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx.egg-info/PKG-INFO +5 -1
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx.egg-info/SOURCES.txt +1 -0
- {voidx-3.4.0 → voidx-3.4.2}/README.md +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/setup.cfg +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/__init__.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/agents.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/goal_resolver.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/graph/__init__.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/graph/compaction.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/graph/compaction_coordinator.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/graph/contracts.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/graph/convergence.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/graph/core/__init__.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/graph/core/_voidx_graph.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/graph/core/helpers.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/graph/core/llm.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/graph/permissions.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/graph/runtime.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/graph/runtime_guards.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/graph/session_mixin.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/graph/session_runtime.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/graph/streaming.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/graph/subagent.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/graph/title_mixin.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/graph/todo_events.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/graph/tool_execution.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/graph/tool_executor/__init__.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/graph/tool_executor/executor.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/graph/tool_executor/guards.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/graph/tool_executor/helpers.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/graph/tool_executor/types.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/graph/tool_executor/ui.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/graph/tool_executor/workflow.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/graph/topology.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/graph/transcript_mixin.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/graph/turn_mixin.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/graph/turn_runner.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/graph/workflow_utils.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/message_rows.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/message_trimming.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/prompts.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/runtime_context.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/slash/__init__.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/slash/code_ide.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/slash/guide.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/slash/host.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/slash/init.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/slash/lsp.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/slash/mcp.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/slash/profile.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/slash/runtime.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/slash/session.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/slash/skills.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/slash/upgrade.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/state.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/task_state.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/todo_state.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/tool_call_ids.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/tool_exchange_sanitizer.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/tool_filters.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/tool_messages.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/agent/tool_result_storage.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/config/__init__.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/config/enums.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/config/permissions.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/config/settings_agent.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/config/settings_api_keys.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/config/settings_code_ide.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/config/settings_custom.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/config/settings_mcp.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/config/settings_permissions.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/config/settings_skills.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/config/settings_update.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/config/settings_utils.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/config/settings_web.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/data/__init__.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/data/templates/api-doc.md +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/data/templates/prd.md +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/data/templates/readme.md +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/data/templates/rfc.md +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/data/templates/tech-design.md +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/diffing.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/llm/__init__.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/llm/catalog.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/llm/compaction.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/llm/context.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/llm/instruction.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/llm/message_markers.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/llm/message_status.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/llm/usage.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/logging/__init__.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/logging/request_log.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/logging/tool_log.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/lsp/__init__.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/lsp/client.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/lsp/config.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/lsp/detector.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/lsp/detector_data.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/lsp/errors.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/lsp/manager.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/lsp/schema.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/lsp/service.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/main.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/mcp/__init__.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/mcp/client/__init__.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/mcp/client/base.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/mcp/client/errors.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/mcp/client/http_transport.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/mcp/client/sse_transport.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/mcp/client/stdio_transport.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/mcp/manager.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/mcp/schema.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/mcp/tool.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/mcp_servers/__init__.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/mcp_servers/web.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/memory/__init__.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/memory/cleanup.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/memory/context_frames.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/memory/jsonl_store.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/memory/model_profiles.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/memory/runtime_state.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/memory/service.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/memory/session.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/memory/store.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/memory/subagents.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/memory/transcript.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/permission/__init__.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/permission/context.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/permission/engine.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/permission/evaluate.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/permission/rules.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/permission/sandbox.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/permission/schema.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/permission/service.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/permission/wildcard.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/runtime/__init__.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/runtime/attachments.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/runtime/intent.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/runtime/reference_tokens.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/runtime/task_state.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/runtime/todo.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/runtime/ui.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/runtime/ui_port.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/selfupdate.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/skills/__init__.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/skills/context.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/skills/references.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/skills/registry.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/skills/schema.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/skills/service.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/__init__.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/agent.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/base.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/bash/__init__.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/bash/core.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/bash/hint/__init__.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/bash/hint/file.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/bash/hint/git.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/bash/hint/search.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/bash/router.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/bash/safety.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/bash/tool.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/clarify.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/compact_context.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/file_ops/__init__.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/file_ops/edit_resolve.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/file_ops/file.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/file_ops/read.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/file_ops/types.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/file_ops/write.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/file_state.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/git.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/load_doc_template.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/load_skills.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/lsp.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/plan_checkpoint.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/powershell/__init__.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/powershell/core.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/powershell/hint/__init__.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/powershell/hint/file.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/powershell/hint/search.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/powershell/router.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/powershell/safety.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/powershell/sandbox.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/powershell/tool.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/registry.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/search.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/service.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/shell/__init__.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/shell/common.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/shell/hint/__init__.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/shell/hint/git.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/skills.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/task_status.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/task_tracker.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/todo.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/web_content.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/web_mcp.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/webfetch.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/websearch.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/tools/workflow.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/__init__.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/frontend.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/gateway/__init__.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/gateway/adapter.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/gateway/bootstrap.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/gateway/diff_review.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/gateway/server.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/gateway/session.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/gateway/terminal.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/output/__init__.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/output/agent_display.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/output/browse.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/output/capture.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/output/console/__init__.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/output/console/app.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/output/console/formatting.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/output/console/streaming.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/output/diff.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/output/display_policy.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/output/dock/__init__.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/output/dock/agent_placeholder.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/output/dock/nodes.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/output/dock/nodes_checkpoint.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/output/dock/nodes_clarify.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/output/dock/nodes_permission.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/output/dock/nodes_startup.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/output/dock/nodes_status.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/output/dock/state.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/output/dock/status.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/output/dock/stream.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/output/dock/todo.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/output/events/__init__.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/output/events/bus.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/output/events/consumers.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/output/events/schema.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/output/types.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/protocol/__init__.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/protocol/commands.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/protocol/envelope.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/protocol/requests.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/protocol/schema.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/protocol/transcript.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/protocol/v2/__init__.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/protocol/v2/envelope.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/protocol/v2/methods.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/protocol/v2/snapshot.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/protocol/v2/threads.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/session.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/tools/__init__.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/tools/attachment_tokens.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/tools/clipboard_image.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/tools/clipboard_text.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/tools/code_ide.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/tools/file_picker.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/tools/skill_picker.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/transcript.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/tui/__init__.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/tui/activity.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/tui/app.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/tui/choice_mixin.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/tui/clipboard_mixin.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/tui/helpers.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/tui/overlays.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/tui/render_activity.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/tui/render_frame.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/tui/render_input.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/tui/render_status.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/tui/render_todo.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/tui/renderer.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/tui/state.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/tui/terminal_mixin.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/ui/tui/text_prompt_mixin.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/workflow/__init__.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/workflow/auto_advance.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/workflow/context.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/workflow/dag.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/workflow/nodes.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/workflow/policy.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/workflow/reconcile.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/workflow/render.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/workflow/route.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/workflow/runtime.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/workflow/schema.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/workflow/service.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx/workflow/types.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx.egg-info/dependency_links.txt +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx.egg-info/entry_points.txt +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx.egg-info/requires.txt +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/src/voidx.egg-info/top_level.txt +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/tests/test_install_sh.py +0 -0
- {voidx-3.4.0 → voidx-3.4.2}/tests/test_npm_package.py +0 -0
voidx-3.4.2/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.
|
|
3
|
+
Version: 3.4.2
|
|
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 = [
|
|
@@ -152,9 +152,23 @@ def is_image_path(path: Path | str) -> bool:
|
|
|
152
152
|
return Path(path).suffix.lower() in IMAGE_EXTENSIONS
|
|
153
153
|
|
|
154
154
|
|
|
155
|
+
_PASTED_RE = re.compile(r"<pasted>\n.*?\n</pasted>", re.DOTALL)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _pasted_spans(text: str) -> list[tuple[int, int]]:
|
|
159
|
+
"""Return (start, end) spans of all <pasted>...</pasted> blocks."""
|
|
160
|
+
return [(m.start(), m.end()) for m in _PASTED_RE.finditer(text)]
|
|
161
|
+
|
|
162
|
+
|
|
155
163
|
def _attachment_tokens(text: str) -> list[tuple[int, int, str]]:
|
|
156
164
|
tokens: list[tuple[int, int, str]] = []
|
|
165
|
+
excluded = _pasted_spans(text)
|
|
157
166
|
for match in _ATTACHMENT_RE.finditer(text):
|
|
167
|
+
# Skip @-references and [image-] tokens inside <pasted> blocks —
|
|
168
|
+
# pasted content is quoted material (e.g. code with decorators),
|
|
169
|
+
# not user-authored attachment references.
|
|
170
|
+
if any(start <= match.start() < end for start, end in excluded):
|
|
171
|
+
continue
|
|
158
172
|
image_stem = match.group(3)
|
|
159
173
|
if image_stem:
|
|
160
174
|
raw_path = f":image:{image_stem}"
|
|
@@ -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"),
|
|
@@ -7,13 +7,14 @@ from typing import Any, Callable, Sequence
|
|
|
7
7
|
|
|
8
8
|
from rich.console import Console, Group
|
|
9
9
|
from rich.live import Live
|
|
10
|
-
from rich.markup import escape
|
|
11
10
|
from rich.text import Text
|
|
12
11
|
|
|
12
|
+
from rich.markup import escape
|
|
13
|
+
|
|
13
14
|
from voidx.ui.output.dock.formatting import (
|
|
14
15
|
ANSI_LINE_PREFIX,
|
|
15
16
|
_clean,
|
|
16
|
-
|
|
17
|
+
_PASTED_RE,
|
|
17
18
|
_text_from_line,
|
|
18
19
|
)
|
|
19
20
|
from voidx.ui.output.dock.nodes import DockNodeMixin
|
|
@@ -219,18 +220,33 @@ class BottomInputDock(DockStreamMixin, DockStatusMixin, DockNodeMixin):
|
|
|
219
220
|
if self._tree.root.children:
|
|
220
221
|
self._append_root_spacer()
|
|
221
222
|
preview = _clean(text)
|
|
222
|
-
|
|
223
|
+
preview = _PASTED_RE.sub(r"\1", preview)
|
|
224
|
+
header, body_lines = self._render_turn_text(preview)
|
|
225
|
+
header = f"[bold white]❯[/] {header}" if header else "[bold white]❯[/]"
|
|
223
226
|
self._current_turn = self._tree.new_node(
|
|
224
227
|
parent=self._tree.root,
|
|
225
228
|
node_type="turn",
|
|
226
|
-
header=
|
|
227
|
-
body_lines=
|
|
229
|
+
header=header,
|
|
230
|
+
body_lines=body_lines,
|
|
228
231
|
collapsed=False,
|
|
229
232
|
)
|
|
230
233
|
self._mark_settled(self._current_turn)
|
|
231
234
|
self.refresh()
|
|
232
235
|
return self._current_turn
|
|
233
236
|
|
|
237
|
+
def _render_turn_text(self, text: str) -> tuple[str, list[str]]:
|
|
238
|
+
"""Render user turn text into (header, body_lines) as plain text.
|
|
239
|
+
|
|
240
|
+
<pasted> wrapper tags are stripped (content kept), then the entire
|
|
241
|
+
text is rendered as escaped plain text — no markdown formatting.
|
|
242
|
+
"""
|
|
243
|
+
lines = text.splitlines() or [text] if text else []
|
|
244
|
+
if not lines:
|
|
245
|
+
return "", []
|
|
246
|
+
header = escape(lines[0])
|
|
247
|
+
body_lines = [escape(line) for line in lines[1:]]
|
|
248
|
+
return header, body_lines
|
|
249
|
+
|
|
234
250
|
def ensure_agent(self) -> OutputNode:
|
|
235
251
|
if self._current_agent is None:
|
|
236
252
|
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
|
|
@@ -14,6 +14,27 @@ from rich.text import Text
|
|
|
14
14
|
USER_ROW_STYLE = "white on #3a3937"
|
|
15
15
|
TOOL_META_BRANCH = "[dim]└[/dim] "
|
|
16
16
|
|
|
17
|
+
# Must match voidx.ui.output.dock.formatting.ANSI_LINE_PREFIX.
|
|
18
|
+
# Duplicated here to avoid circular imports (formatting imports from dock,
|
|
19
|
+
# dock imports from tree).
|
|
20
|
+
_ANSI_LINE_PREFIX = "\x00voidx-ansi\x00"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _visible_len(line: str) -> int:
|
|
24
|
+
"""Compute visible cell length of a render line safely.
|
|
25
|
+
|
|
26
|
+
Lines may contain an ANSI_LINE_PREFIX marker separating rich-markup text
|
|
27
|
+
from ANSI-styled text (pasted segments). The ANSI portion can contain
|
|
28
|
+
arbitrary characters including ``[/]`` that would crash Text.from_markup,
|
|
29
|
+
so it is parsed with Text.from_ansi instead.
|
|
30
|
+
"""
|
|
31
|
+
marker = line.find(_ANSI_LINE_PREFIX)
|
|
32
|
+
if marker == -1:
|
|
33
|
+
return cell_len(Text.from_markup(line).plain)
|
|
34
|
+
text = Text.from_markup(line[:marker])
|
|
35
|
+
text.append_text(Text.from_ansi(line[marker + len(_ANSI_LINE_PREFIX):]))
|
|
36
|
+
return cell_len(text.plain)
|
|
37
|
+
|
|
17
38
|
|
|
18
39
|
@dataclass
|
|
19
40
|
class OutputNode:
|
|
@@ -617,15 +638,29 @@ class OutputTree:
|
|
|
617
638
|
|
|
618
639
|
|
|
619
640
|
def _full_width_row(line: str, width: int) -> str:
|
|
620
|
-
visible =
|
|
641
|
+
visible = _visible_len(line)
|
|
621
642
|
padding = " " * max(0, width - visible)
|
|
622
|
-
return
|
|
643
|
+
return _wrap_full_width(line, padding, USER_ROW_STYLE)
|
|
623
644
|
|
|
624
645
|
|
|
625
646
|
def _permission_row(line: str, width: int) -> str:
|
|
626
|
-
visible =
|
|
647
|
+
visible = _visible_len(line)
|
|
627
648
|
padding = " " * max(0, width - visible)
|
|
628
|
-
return
|
|
649
|
+
return _wrap_full_width(line, padding, "white on #3a3937")
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
def _wrap_full_width(line: str, padding: str, style: str) -> str:
|
|
653
|
+
"""Wrap a line with a background style and padding to full width.
|
|
654
|
+
|
|
655
|
+
When the line contains an ANSI_LINE_PREFIX marker, the closing tag and
|
|
656
|
+
padding must go *before* the marker so that _text_from_line parses them
|
|
657
|
+
with Text.from_markup (which understands tags) rather than leaving them
|
|
658
|
+
as literal text in the Text.from_ansi portion.
|
|
659
|
+
"""
|
|
660
|
+
marker = line.find(_ANSI_LINE_PREFIX)
|
|
661
|
+
if marker == -1:
|
|
662
|
+
return f"[{style}]{line}{padding}[/]"
|
|
663
|
+
return f"[{style}]{line[:marker]}{padding}[/]{line[marker:]}"
|
|
629
664
|
|
|
630
665
|
|
|
631
666
|
def _is_full_width_user_row(node: OutputNode) -> bool:
|
|
@@ -644,7 +679,7 @@ def _pad_diff_background_row(line: str, width: int) -> str:
|
|
|
644
679
|
marker = "[/on #4a0000]"
|
|
645
680
|
if not marker:
|
|
646
681
|
return line
|
|
647
|
-
visible =
|
|
682
|
+
visible = _visible_len(line)
|
|
648
683
|
padding = " " * max(0, width - visible)
|
|
649
684
|
return line.replace(marker, f"{padding}{marker}", 1)
|
|
650
685
|
|
|
@@ -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
|
-
|
|
58
|
-
|
|
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()
|
|
@@ -7,6 +7,7 @@ from typing import Any
|
|
|
7
7
|
|
|
8
8
|
from voidx.config import Settings
|
|
9
9
|
from voidx.skills.service import SkillRegistry, SkillService
|
|
10
|
+
from voidx.ui.output.dock.formatting import _PASTED_RE
|
|
10
11
|
from voidx.ui.tools.attachment_tokens import attachment_token_text
|
|
11
12
|
from voidx.ui.tools.file_picker import (
|
|
12
13
|
AttachmentToken,
|
|
@@ -37,7 +38,12 @@ class _PanelManagerMixin:
|
|
|
37
38
|
# ── submit ───────────────────────────────────────────────────────────
|
|
38
39
|
|
|
39
40
|
def _submit_text_prompt(self) -> None:
|
|
40
|
-
value = self._get_input_text()
|
|
41
|
+
value = self._expand_registered_tokens(self._get_input_text())
|
|
42
|
+
# Clarify answers are plain text — strip <pasted> wrapper tags that
|
|
43
|
+
# _expand_registered_tokens adds (those tags are only consumed by the
|
|
44
|
+
# main input path's split_pasted_segments in start_turn rendering).
|
|
45
|
+
value = _PASTED_RE.sub(r"\1", value)
|
|
46
|
+
self._paste_entries.clear()
|
|
41
47
|
self._text_queue.put_nowait(value)
|
|
42
48
|
|
|
43
49
|
def _cancel_text_prompt(self) -> None:
|
|
@@ -77,30 +77,45 @@ class _InputParserMixin:
|
|
|
77
77
|
if mapped:
|
|
78
78
|
return mapped.encode("utf-8")
|
|
79
79
|
return ("\x00" + ch2).encode("utf-8")
|
|
80
|
-
#
|
|
81
|
-
if
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
|
92
|
-
|
|
93
|
-
Windows Terminal injects pasted content character-by-character into
|
|
94
|
-
console input buffer.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
"""Drain remaining console input to detect a paste.
|
|
97
|
+
|
|
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
|
+
Returns the bracketed-paste bytes when the drained content looks
|
|
105
|
+
like a paste. Returns the raw bytes of ALL drained characters
|
|
106
|
+
(including ``first_char``) when they don't look like a paste —
|
|
107
|
+
this lets the caller process them in arrival order without
|
|
108
|
+
stashing extras into ``_pending_bytes``, which previously caused
|
|
109
|
+
IME/keyboard characters to be reordered across reads. Returns
|
|
110
|
+
``None`` when nothing follows ``first_char`` (a single keypress).
|
|
99
111
|
"""
|
|
100
112
|
import msvcrt
|
|
101
113
|
import time
|
|
102
114
|
|
|
103
|
-
if
|
|
115
|
+
# Quick exit: if nothing follows immediately, this is a single
|
|
116
|
+
# keypress (normal typing), not a paste. Avoids entering the poll
|
|
117
|
+
# loop for every ordinary character.
|
|
118
|
+
if not msvcrt.kbhit():
|
|
104
119
|
return None
|
|
105
120
|
|
|
106
121
|
buffer = first_char
|
|
@@ -121,7 +136,11 @@ class _InputParserMixin:
|
|
|
121
136
|
newline_count = buffer.count("\r") + buffer.count("\n")
|
|
122
137
|
if newline_count >= 2 or len(buffer) > 8:
|
|
123
138
|
return b"\x1b[200~" + buffer.encode("utf-8", errors="replace") + b"\x1b[201~"
|
|
124
|
-
return
|
|
139
|
+
# Not a paste: return all drained chars as raw bytes so the caller
|
|
140
|
+
# processes them in arrival order. Previously, only first_char was
|
|
141
|
+
# returned and the rest was stashed in _pending_bytes, which caused
|
|
142
|
+
# fast IME/keyboard input to be reordered across reads.
|
|
143
|
+
return buffer.encode("utf-8", errors="replace")
|
|
125
144
|
|
|
126
145
|
async def _read_input_line(self) -> bytes:
|
|
127
146
|
"""Fallback: read a line from stdin (not a tty)."""
|
|
@@ -231,16 +250,38 @@ class _InputParserMixin:
|
|
|
231
250
|
return
|
|
232
251
|
# Normalise line endings: \r\n → \n, \r → \n
|
|
233
252
|
text = text.replace("\r\n", "\n").replace("\r", "\n")
|
|
234
|
-
|
|
253
|
+
# All pasted text collapses to a [Pasted text #N ...] token, regardless
|
|
254
|
+
# of length. This keeps the input box compact and clearly marks pasted
|
|
255
|
+
# content as a distinct unit.
|
|
256
|
+
#
|
|
257
|
+
# Merge with the previous paste if it happened within a short window.
|
|
258
|
+
# Windows Terminal may split a large paste across multiple drain
|
|
259
|
+
# batches (20ms timeout fires between batches), producing two
|
|
260
|
+
# bracketed-paste sequences. Without merging, the user sees two
|
|
261
|
+
# adjacent tokens for what was a single paste action.
|
|
262
|
+
import time
|
|
263
|
+
|
|
264
|
+
now = time.monotonic()
|
|
265
|
+
last = getattr(self, "_last_paste_time", None)
|
|
266
|
+
self._last_paste_time = now
|
|
267
|
+
|
|
268
|
+
if (
|
|
269
|
+
last is not None
|
|
270
|
+
and (now - last) < 0.2
|
|
271
|
+
and self._paste_entries
|
|
272
|
+
and self._paste_entries[-1].get("kind") == "text"
|
|
273
|
+
):
|
|
274
|
+
# Append to the previous paste entry
|
|
275
|
+
entry = self._paste_entries[-1]
|
|
276
|
+
combined = str(entry["expanded"]) + text
|
|
277
|
+
old_display = str(entry["display"])
|
|
278
|
+
new_display = self._compute_text_paste_display(int(entry["id"]), combined)
|
|
279
|
+
entry["expanded"] = combined
|
|
280
|
+
entry["display"] = new_display
|
|
281
|
+
# Replace the old token in the editor with the updated one
|
|
282
|
+
self._replace_token_in_editor(old_display, new_display)
|
|
283
|
+
else:
|
|
235
284
|
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
285
|
|
|
245
286
|
def _dispatch_key(self, data: bytes, offset: int) -> tuple[int, str | None]:
|
|
246
287
|
"""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.
|
|
3
|
+
Version: 3.4.2
|
|
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
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|