voidx 3.2.2__tar.gz → 3.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {voidx-3.2.2 → voidx-3.3.0}/PKG-INFO +1 -1
- {voidx-3.2.2 → voidx-3.3.0}/pyproject.toml +1 -1
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/__init__.py +1 -1
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/graph/compaction_coordinator.py +6 -3
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/graph/core/_voidx_graph.py +2 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/graph/subagent.py +60 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/prompts.py +7 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/permission/rules.py +8 -1
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/skills/registry.py +41 -1
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/agent.py +8 -13
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/bash/core.py +0 -5
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/bash/hint/file.py +2 -2
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/bash/hint/git.py +4 -8
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/bash/hint/search.py +6 -6
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/bash/tool.py +8 -4
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/clarify.py +4 -1
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/compact_context.py +4 -1
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/file_ops/edit_execute.py +22 -23
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/file_ops/edit_resolve.py +8 -8
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/file_ops/file.py +7 -4
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/file_ops/read.py +5 -2
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/file_ops/write.py +4 -4
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/file_state.py +12 -4
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/git.py +25 -15
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/load_doc_template.py +4 -1
- voidx-3.3.0/src/voidx/tools/load_skills.py +12 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/lsp.py +10 -4
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/plan_checkpoint.py +4 -1
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/registry.py +3 -3
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/search.py +14 -8
- voidx-3.3.0/src/voidx/tools/skills.py +252 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/task_status.py +17 -4
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/todo.py +22 -23
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/webfetch.py +4 -1
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/websearch.py +4 -1
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/workflow.py +4 -1
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/output/console/formatting.py +5 -1
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/output/diff.py +1 -1
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/output/dock/__init__.py +6 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/output/dock/app.py +3 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/output/dock/formatting.py +2 -2
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/output/dock/nodes.py +5 -3
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/output/dock/nodes_checkpoint.py +1 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/output/dock/nodes_permission.py +2 -3
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/output/dock/status.py +35 -16
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/output/events/consumers.py +219 -18
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/output/events/schema.py +1 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/output/tree.py +16 -6
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/tui/render_activity.py +28 -2
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx.egg-info/PKG-INFO +1 -1
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx.egg-info/SOURCES.txt +1 -0
- voidx-3.2.2/src/voidx/tools/load_skills.py +0 -206
- {voidx-3.2.2 → voidx-3.3.0}/README.md +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/setup.cfg +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/__init__.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/agents.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/attachments.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/goal_resolver.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/graph/__init__.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/graph/compaction.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/graph/contracts.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/graph/convergence.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/graph/core/__init__.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/graph/core/helpers.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/graph/core/llm.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/graph/permissions.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/graph/run_loop.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/graph/runtime.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/graph/runtime_guards.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/graph/session_mixin.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/graph/session_runtime.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/graph/streaming.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/graph/title_mixin.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/graph/todo_events.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/graph/tool_execution.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/graph/tool_executor/__init__.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/graph/tool_executor/executor.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/graph/tool_executor/guards.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/graph/tool_executor/helpers.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/graph/tool_executor/types.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/graph/tool_executor/ui.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/graph/tool_executor/workflow.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/graph/topology.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/graph/transcript_mixin.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/graph/turn_mixin.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/graph/turn_runner.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/graph/wiring.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/graph/workflow_utils.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/message_rows.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/runtime_context.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/slash/__init__.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/slash/code_ide.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/slash/guide.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/slash/handler.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/slash/host.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/slash/init.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/slash/lsp.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/slash/mcp.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/slash/model.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/slash/profile.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/slash/runtime.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/slash/session.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/slash/skills.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/slash/upgrade.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/state.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/task_state.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/todo_state.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/tool_call_ids.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/tool_exchange_sanitizer.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/tool_filters.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/tool_messages.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/agent/tool_result_storage.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/config/__init__.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/config/enums.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/config/models.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/config/permissions.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/config/settings.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/config/settings_agent.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/config/settings_api_keys.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/config/settings_code_ide.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/config/settings_custom.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/config/settings_mcp.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/config/settings_permissions.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/config/settings_skills.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/config/settings_update.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/config/settings_utils.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/config/settings_web.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/data/__init__.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/data/templates/api-doc.md +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/data/templates/prd.md +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/data/templates/readme.md +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/data/templates/rfc.md +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/data/templates/tech-design.md +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/diffing.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/llm/__init__.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/llm/catalog.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/llm/compaction.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/llm/context.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/llm/instruction.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/llm/message_markers.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/llm/message_status.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/llm/provider.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/llm/service.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/llm/usage.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/logging/__init__.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/logging/request_log.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/logging/tool_log.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/lsp/__init__.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/lsp/client.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/lsp/config.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/lsp/detector.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/lsp/detector_data.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/lsp/errors.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/lsp/manager.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/lsp/schema.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/lsp/service.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/main.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/mcp/__init__.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/mcp/client/__init__.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/mcp/client/base.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/mcp/client/errors.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/mcp/client/http_transport.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/mcp/client/sse_transport.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/mcp/client/stdio_transport.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/mcp/manager.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/mcp/schema.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/mcp/tool.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/mcp_servers/__init__.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/mcp_servers/web.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/memory/__init__.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/memory/cleanup.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/memory/context_frames.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/memory/jsonl_store.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/memory/model_profiles.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/memory/runtime_state.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/memory/service.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/memory/session.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/memory/store.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/memory/subagents.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/memory/transcript.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/permission/__init__.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/permission/context.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/permission/engine.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/permission/evaluate.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/permission/sandbox.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/permission/schema.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/permission/service.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/permission/wildcard.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/runtime/__init__.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/runtime/attachments.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/runtime/intent.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/runtime/reference_tokens.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/runtime/task_state.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/runtime/todo.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/runtime/ui.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/runtime/ui_port.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/selfupdate.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/skills/__init__.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/skills/context.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/skills/references.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/skills/schema.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/skills/service.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/__init__.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/base.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/bash/__init__.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/bash/hint/__init__.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/bash/router.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/bash/safety.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/file_ops/__init__.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/file_ops/types.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/service.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/task_tracker.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/web_content.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/tools/web_mcp.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/__init__.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/commands.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/frontend.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/gateway/__init__.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/gateway/bootstrap.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/gateway/server.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/gateway/session.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/output/__init__.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/output/agent_display.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/output/browse.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/output/capture.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/output/console/__init__.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/output/console/app.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/output/console/streaming.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/output/display_policy.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/output/dock/agent_placeholder.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/output/dock/nodes_startup.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/output/dock/nodes_status.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/output/dock/state.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/output/dock/stream.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/output/dock/todo.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/output/events/__init__.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/output/events/bus.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/output/types.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/protocol/__init__.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/protocol/commands.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/protocol/envelope.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/protocol/requests.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/protocol/schema.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/protocol/transcript.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/session.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/tools/__init__.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/tools/attachment_tokens.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/tools/clipboard_image.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/tools/clipboard_text.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/tools/code_ide.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/tools/file_picker.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/tools/skill_picker.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/transcript.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/tui/__init__.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/tui/activity.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/tui/app.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/tui/choice_mixin.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/tui/clipboard_mixin.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/tui/helpers.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/tui/input.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/tui/overlays.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/tui/panels.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/tui/parser.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/tui/render_frame.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/tui/render_input.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/tui/render_status.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/tui/render_todo.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/tui/renderer.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/tui/state.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/tui/terminal_mixin.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/ui/tui/text_prompt_mixin.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/workflow/__init__.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/workflow/auto_advance.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/workflow/context.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/workflow/dag.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/workflow/nodes.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/workflow/policy.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/workflow/reconcile.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/workflow/render.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/workflow/route.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/workflow/runtime.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/workflow/schema.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/workflow/service.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx/workflow/types.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx.egg-info/dependency_links.txt +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx.egg-info/entry_points.txt +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx.egg-info/requires.txt +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/src/voidx.egg-info/top_level.txt +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/tests/test_install_sh.py +0 -0
- {voidx-3.2.2 → voidx-3.3.0}/tests/test_npm_package.py +0 -0
|
@@ -189,7 +189,7 @@ class GraphCompactionCoordinator:
|
|
|
189
189
|
if host._ui.via_events():
|
|
190
190
|
await host._ui.events.emit(StatusUpdated(
|
|
191
191
|
status_id="compaction",
|
|
192
|
-
label="Compacting
|
|
192
|
+
label="Compacting",
|
|
193
193
|
detail=_compaction_status_detail(total_tokens, force=force, preflight=preflight),
|
|
194
194
|
stage="compacting",
|
|
195
195
|
display="record_only",
|
|
@@ -198,7 +198,7 @@ class GraphCompactionCoordinator:
|
|
|
198
198
|
host._ui.ui.print(
|
|
199
199
|
"[yellow]Context overflow — compacting...[/yellow]"
|
|
200
200
|
if not force
|
|
201
|
-
else "[yellow]Compacting
|
|
201
|
+
else "[yellow]Compacting...[/yellow]"
|
|
202
202
|
)
|
|
203
203
|
|
|
204
204
|
runtime_prefix = _runtime_prefix(messages)
|
|
@@ -251,9 +251,10 @@ class GraphCompactionCoordinator:
|
|
|
251
251
|
retry_label = f" (attempt {attempt})" if attempt > 1 else ""
|
|
252
252
|
await host._ui.events.emit(StatusUpdated(
|
|
253
253
|
status_id="compaction",
|
|
254
|
-
label="Compacting
|
|
254
|
+
label="Compacting",
|
|
255
255
|
detail=f"summarizing {len(head_msgs)} old messages{retry_label}",
|
|
256
256
|
stage="compacting",
|
|
257
|
+
display="record_only",
|
|
257
258
|
))
|
|
258
259
|
summary = await run_agent(head_msgs, previous_summary)
|
|
259
260
|
if summary:
|
|
@@ -270,6 +271,7 @@ class GraphCompactionCoordinator:
|
|
|
270
271
|
label="Compaction agent failed",
|
|
271
272
|
detail=f"{e}; retrying ({attempt}/{COMPACTION_MAX_RETRIES})",
|
|
272
273
|
stage="compacting",
|
|
274
|
+
display="record_only",
|
|
273
275
|
))
|
|
274
276
|
else:
|
|
275
277
|
host._ui.ui.print(f"[dim]Compaction agent failed ({e}) — retrying ({attempt}/{COMPACTION_MAX_RETRIES})[/dim]")
|
|
@@ -288,6 +290,7 @@ class GraphCompactionCoordinator:
|
|
|
288
290
|
label="Compaction agent failed",
|
|
289
291
|
detail=f"{failure_detail}; using extracted summary",
|
|
290
292
|
stage="compacting",
|
|
293
|
+
display="record_only",
|
|
291
294
|
))
|
|
292
295
|
else:
|
|
293
296
|
err_msg = f" ({failure_detail})"
|
|
@@ -399,6 +399,7 @@ class VoidXGraph(
|
|
|
399
399
|
|
|
400
400
|
ok = False
|
|
401
401
|
run_metadata: dict[str, object] = {}
|
|
402
|
+
result = ""
|
|
402
403
|
try:
|
|
403
404
|
kwargs = {
|
|
404
405
|
"sub_messages": sub_buffer,
|
|
@@ -439,6 +440,7 @@ class VoidXGraph(
|
|
|
439
440
|
ok=ok,
|
|
440
441
|
elapsed=time.monotonic() - started_at,
|
|
441
442
|
finish_reason=str(run_metadata.get("finish_reason") or ("final_answer" if ok else "error")),
|
|
443
|
+
summary=result if ok else "",
|
|
442
444
|
))
|
|
443
445
|
if self._session:
|
|
444
446
|
await append_subagent_event(session_id, agent_run_id, {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
|
+
import re
|
|
6
7
|
import time
|
|
7
8
|
|
|
8
9
|
from langchain_core.messages import AIMessage, HumanMessage, ToolMessage
|
|
@@ -43,6 +44,7 @@ from voidx.runtime.ui_port import AgentUiPort, runtime_ui_port
|
|
|
43
44
|
|
|
44
45
|
|
|
45
46
|
_SAFETY_STEP_LIMIT = 50
|
|
47
|
+
_RESULT_CONTRACT_RETRY_LIMIT = 2
|
|
46
48
|
|
|
47
49
|
async def run_subagent(
|
|
48
50
|
agent_def: AgentDef,
|
|
@@ -105,6 +107,8 @@ async def run_subagent(
|
|
|
105
107
|
)
|
|
106
108
|
guard_state = RuntimeGuardState(wall_clock=WallClockGuardState.for_subagent())
|
|
107
109
|
pending_guard_guidance: list[str] = []
|
|
110
|
+
contract_retry_count = 0
|
|
111
|
+
has_successful_tool_work = False
|
|
108
112
|
|
|
109
113
|
context, context_cache = RuntimeContextBuilder(
|
|
110
114
|
config=context_config,
|
|
@@ -212,6 +216,18 @@ async def run_subagent(
|
|
|
212
216
|
|
|
213
217
|
if not assistant_msg.tool_calls:
|
|
214
218
|
text = extract_text(assistant_msg)
|
|
219
|
+
if has_successful_tool_work and not _satisfies_result_contract(text, result_contract):
|
|
220
|
+
if contract_retry_count < _RESULT_CONTRACT_RETRY_LIMIT:
|
|
221
|
+
contract_retry_count += 1
|
|
222
|
+
step -= 1
|
|
223
|
+
guidance = _result_contract_retry_message(result_contract)
|
|
224
|
+
messages.append(HumanMessage(content=guidance))
|
|
225
|
+
continue
|
|
226
|
+
if tracker:
|
|
227
|
+
tracker.update(task_id, last_output=text[:200])
|
|
228
|
+
tracker.finish(task_id, "completed")
|
|
229
|
+
mark_finished("contract_unsatisfied")
|
|
230
|
+
return text
|
|
215
231
|
if tracker:
|
|
216
232
|
tracker.update(task_id, last_output=text[:200])
|
|
217
233
|
tracker.finish(task_id, "completed")
|
|
@@ -318,6 +334,7 @@ async def run_subagent(
|
|
|
318
334
|
if metadata.get("runtime_guard"):
|
|
319
335
|
continue
|
|
320
336
|
if result_ok(item["result"]):
|
|
337
|
+
has_successful_tool_work = True
|
|
321
338
|
guard_state.tool_failures.record_success(item["tool_call"])
|
|
322
339
|
continue
|
|
323
340
|
key = build_failure_key(item["tool_call"], item["result"])
|
|
@@ -391,3 +408,46 @@ def _task_payload(task_description: str, result_contract) -> str:
|
|
|
391
408
|
"Return the final answer using this contract."
|
|
392
409
|
)
|
|
393
410
|
return "\n\n".join(parts)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def _result_contract_fields(result_contract) -> list[str]:
|
|
414
|
+
result_format = str(getattr(result_contract, "format", "") or "")
|
|
415
|
+
fields: list[str] = []
|
|
416
|
+
for raw_part in result_format.split(","):
|
|
417
|
+
part = raw_part.strip()
|
|
418
|
+
if not part:
|
|
419
|
+
continue
|
|
420
|
+
match = re.match(r"([A-Za-z_][A-Za-z0-9_]*)", part)
|
|
421
|
+
if match:
|
|
422
|
+
fields.append(match.group(1))
|
|
423
|
+
return fields
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def _satisfies_result_contract(text: str, result_contract) -> bool:
|
|
427
|
+
fields = _result_contract_fields(result_contract)
|
|
428
|
+
if not fields:
|
|
429
|
+
return True
|
|
430
|
+
if not text.strip():
|
|
431
|
+
return False
|
|
432
|
+
|
|
433
|
+
matched = [
|
|
434
|
+
field
|
|
435
|
+
for field in fields
|
|
436
|
+
if re.search(rf"(?im)^\s*(?:[-*]\s*)?{re.escape(field)}\s*[:=]", text)
|
|
437
|
+
]
|
|
438
|
+
required = 1 if len(fields) == 1 else 2
|
|
439
|
+
if fields[0] not in matched:
|
|
440
|
+
return False
|
|
441
|
+
return len(matched) >= required
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def _result_contract_retry_message(result_contract) -> str:
|
|
445
|
+
schema_name = str(getattr(result_contract, "schema_name", "") or "agent_result")
|
|
446
|
+
result_format = str(getattr(result_contract, "format", "") or "").strip()
|
|
447
|
+
return (
|
|
448
|
+
"Your previous response did not satisfy the child-agent result contract. "
|
|
449
|
+
"Do not return raw tool output or code snippets as the final answer.\n"
|
|
450
|
+
"Summarize the completed delegated task using the required contract:\n"
|
|
451
|
+
f"- schema_name: {schema_name}\n"
|
|
452
|
+
f"- format: {result_format}"
|
|
453
|
+
)
|
|
@@ -116,6 +116,13 @@ BASE_SYSTEM = BaseSystemPrompt(
|
|
|
116
116
|
PromptRule(detail="Assess before acting — evaluate what's already known and what's still needed."),
|
|
117
117
|
PromptRule(detail="Stay aligned with the user's actual goal."),
|
|
118
118
|
PromptRule(detail="Pick the smallest next action that makes progress toward the goal."),
|
|
119
|
+
PromptRule(
|
|
120
|
+
detail=(
|
|
121
|
+
"Delegate to child agents only for parallel independent tasks or when the user "
|
|
122
|
+
"explicitly asks. Do not delegate single-file reads, simple searches, or "
|
|
123
|
+
"straightforward tasks you can do directly."
|
|
124
|
+
),
|
|
125
|
+
),
|
|
119
126
|
PromptRule(detail="skill can return project/global skill bodies for the current turn."),
|
|
120
127
|
PromptRule(
|
|
121
128
|
detail=(
|
|
@@ -38,6 +38,7 @@ BASIC_RULES: Ruleset = [
|
|
|
38
38
|
Rule(permission="compact", pattern="*", action="allow"),
|
|
39
39
|
Rule(permission="task_status", pattern="*", action="allow"),
|
|
40
40
|
Rule(permission="skill", pattern="*", action="allow"),
|
|
41
|
+
Rule(permission="skill", pattern="create", action="ask"),
|
|
41
42
|
Rule(permission="lsp", pattern="*", action="allow"),
|
|
42
43
|
Rule(permission="agent", pattern="voidx", action="allow"),
|
|
43
44
|
Rule(permission="edit", pattern="*", action="ask"),
|
|
@@ -118,6 +119,8 @@ def build_pattern(tool: str, args: dict) -> str:
|
|
|
118
119
|
return "voidx"
|
|
119
120
|
if tool == "git":
|
|
120
121
|
return "read" if _is_read_only_git_tool_command(args) else "write"
|
|
122
|
+
if tool == "skill":
|
|
123
|
+
return "create" if args.get("op") == "create" else "*"
|
|
121
124
|
return "*"
|
|
122
125
|
|
|
123
126
|
|
|
@@ -359,10 +362,14 @@ def _is_read_only_git_ref_command(subcommand: str, args: list[str]) -> bool:
|
|
|
359
362
|
def capability_for_tool(tool: str, args: dict) -> PermissionCapability:
|
|
360
363
|
if tool in {
|
|
361
364
|
"read", "glob", "grep", "webfetch", "websearch", "todo", "task_status",
|
|
362
|
-
"
|
|
365
|
+
"workflow", "compact",
|
|
363
366
|
"lsp",
|
|
364
367
|
}:
|
|
365
368
|
return PermissionCapability.READ_TOOLS
|
|
369
|
+
if tool == "skill":
|
|
370
|
+
if args.get("op") == "create":
|
|
371
|
+
return PermissionCapability.FILE_WRITE
|
|
372
|
+
return PermissionCapability.READ_TOOLS
|
|
366
373
|
if tool in {"file", "write", "replace"}:
|
|
367
374
|
return PermissionCapability.FILE_WRITE
|
|
368
375
|
if tool == "bash":
|
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import re
|
|
5
6
|
from dataclasses import dataclass
|
|
6
7
|
from pathlib import Path
|
|
7
|
-
from typing import Any
|
|
8
|
+
from typing import Any, Literal
|
|
8
9
|
|
|
9
10
|
from voidx.skills.schema import SkillDefinition, SkillMeta, SkillScope
|
|
10
11
|
|
|
11
12
|
SKILL_FILENAME = "SKILL.md"
|
|
12
13
|
DEFAULT_BUNDLED_DIR = Path(__file__).resolve().parent / "bundled"
|
|
14
|
+
SKILL_NAME_RE = re.compile(r"^(?=.{1,64}$)[a-z0-9]([a-z0-9-]*[a-z0-9])?$")
|
|
13
15
|
|
|
14
16
|
|
|
15
17
|
@dataclass
|
|
@@ -72,6 +74,44 @@ class SkillRegistry:
|
|
|
72
74
|
self._cache = None
|
|
73
75
|
self._cache_signature = None
|
|
74
76
|
|
|
77
|
+
def create_skill(
|
|
78
|
+
self,
|
|
79
|
+
name: str,
|
|
80
|
+
description: str,
|
|
81
|
+
body: str,
|
|
82
|
+
*,
|
|
83
|
+
scope: Literal["project", "global"] = "project",
|
|
84
|
+
) -> Path | None:
|
|
85
|
+
"""Create a SKILL.md file. Returns the path, or None if it already exists.
|
|
86
|
+
|
|
87
|
+
Security: name is validated against SKILL_NAME_RE on the first line.
|
|
88
|
+
This is the sole path-escape defense since sandbox skips the check
|
|
89
|
+
(skill create has no file_path arg). Must not rely on tool-layer validation.
|
|
90
|
+
"""
|
|
91
|
+
if not SKILL_NAME_RE.match(name):
|
|
92
|
+
raise ValueError(
|
|
93
|
+
f"Invalid skill name '{name}': must be 1-64 chars, lowercase "
|
|
94
|
+
f"alphanumeric with hyphens, not starting/ending with a hyphen."
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
root = self.project_dir if scope == "project" else self.global_dir
|
|
98
|
+
path = root / name / SKILL_FILENAME
|
|
99
|
+
if path.exists():
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
103
|
+
frontmatter = (
|
|
104
|
+
"---\n"
|
|
105
|
+
f"name: {name}\n"
|
|
106
|
+
f"description: {description}\n"
|
|
107
|
+
"enabled: true\n"
|
|
108
|
+
"---\n\n"
|
|
109
|
+
)
|
|
110
|
+
path.write_text(frontmatter + body, encoding="utf-8")
|
|
111
|
+
self.invalidate()
|
|
112
|
+
return path
|
|
113
|
+
|
|
114
|
+
|
|
75
115
|
def get(self, name: str) -> SkillDefinition | None:
|
|
76
116
|
target = normalize_skill_name(name)
|
|
77
117
|
for skill in self.discover():
|
|
@@ -127,15 +127,8 @@ _RESULT_PRESETS: dict[str, AgentResultContract] = {
|
|
|
127
127
|
class AgentTool(BaseTool):
|
|
128
128
|
id = "agent"
|
|
129
129
|
description = (
|
|
130
|
-
"Start an isolated child agent for a delegated task.
|
|
131
|
-
"
|
|
132
|
-
"explicitly asks for a child agent. Do not use for single-file reads, "
|
|
133
|
-
"simple searches, or straightforward tasks you can do directly. Each "
|
|
134
|
-
"call must include mode, task, and one concrete target. Use "
|
|
135
|
-
"success_criteria for implement and feedback modes, and result_preset "
|
|
136
|
-
"when the child output shape should be explicit. "
|
|
137
|
-
"The child agent receives your task description and runtime context, "
|
|
138
|
-
"but not caller conversation history."
|
|
130
|
+
"Start an isolated child agent for a delegated task. The child receives "
|
|
131
|
+
"the task brief and runtime context, but not caller conversation history."
|
|
139
132
|
)
|
|
140
133
|
|
|
141
134
|
def __init__(
|
|
@@ -196,19 +189,21 @@ class AgentTool(BaseTool):
|
|
|
196
189
|
|
|
197
190
|
if self._agent_resolver is None:
|
|
198
191
|
return ToolResult(
|
|
199
|
-
output=f"Child agent execution not available. Task: {normalized.description[:200]}"
|
|
192
|
+
output=f"Child agent execution not available. Task: {normalized.description[:200]}",
|
|
193
|
+
metadata={"error": True, "reason": "no_resolver"},
|
|
200
194
|
)
|
|
201
195
|
|
|
202
196
|
agent_def = self._agent_resolver(requested_agent) if self._agent_resolver else None
|
|
203
197
|
if not agent_def:
|
|
204
198
|
available = self._available_agents
|
|
205
|
-
return ToolResult(output=f"Unknown child agent: {inp.agent}. Available: {available}")
|
|
199
|
+
return ToolResult(output=f"Unknown child agent: {inp.agent}. Available: {available}", metadata={"error": True, "reason": "unknown_agent"})
|
|
206
200
|
|
|
207
201
|
agent_def_name = str(getattr(agent_def, "name", inp.agent))
|
|
208
202
|
|
|
209
203
|
if not self._run_child_agent:
|
|
210
204
|
return ToolResult(
|
|
211
|
-
output=f"Child agent execution not available. Task: {normalized.description[:200]}"
|
|
205
|
+
output=f"Child agent execution not available. Task: {normalized.description[:200]}",
|
|
206
|
+
metadata={"error": True, "reason": "no_runner"},
|
|
212
207
|
)
|
|
213
208
|
|
|
214
209
|
try:
|
|
@@ -235,7 +230,7 @@ class AgentTool(BaseTool):
|
|
|
235
230
|
except Exception as exc:
|
|
236
231
|
return ToolResult(
|
|
237
232
|
output=f"Child agent '{agent_def_name}' failed: {exc}",
|
|
238
|
-
metadata={"agent": agent_def_name, "error": str(exc)},
|
|
233
|
+
metadata={"agent": agent_def_name, "error": True, "reason": "exception", "detail": str(exc)[:200]},
|
|
239
234
|
)
|
|
240
235
|
|
|
241
236
|
|
|
@@ -81,11 +81,6 @@ def _strip_cd_prefix(command: str) -> str:
|
|
|
81
81
|
return remainder
|
|
82
82
|
|
|
83
83
|
|
|
84
|
-
_UNHINTABLE_GIT_SUBCOMMANDS = frozenset({
|
|
85
|
-
"push", "pull", "merge", "rebase", "cherry-pick",
|
|
86
|
-
"reset", "checkout", "fetch", "clone", "init",
|
|
87
|
-
"submodule", "filter-branch", "bisect",
|
|
88
|
-
})
|
|
89
84
|
|
|
90
85
|
_GIT_GLOBAL_OPTIONS_WITH_VALUE = frozenset({
|
|
91
86
|
"-C", "-c", "--git-dir", "--work-tree", "--namespace", "--exec-path",
|
|
@@ -132,7 +132,7 @@ def _hint_write_echo(stripped: str, words: list[str]) -> RouteHint | None:
|
|
|
132
132
|
)
|
|
133
133
|
return RouteHint(
|
|
134
134
|
tool_id="file", ui_label="→ file",
|
|
135
|
-
llm_hint=f'Prefer file(file_path="{path}", op="create") then
|
|
135
|
+
llm_hint=f'Prefer file(file_path="{path}", op="create") then write(file_path="{path}", op="append", new_string="{content}") for file tracking and diff output.',
|
|
136
136
|
)
|
|
137
137
|
|
|
138
138
|
|
|
@@ -180,7 +180,7 @@ def _hint_write_heredoc(stripped: str) -> RouteHint | None:
|
|
|
180
180
|
)
|
|
181
181
|
return RouteHint(
|
|
182
182
|
tool_id="file", ui_label="→ file",
|
|
183
|
-
llm_hint=f'Prefer file(file_path="{path}", op="create") then
|
|
183
|
+
llm_hint=f'Prefer file(file_path="{path}", op="create") then write(file_path="{path}", op="append", new_string="{content}") for file tracking and diff output.',
|
|
184
184
|
)
|
|
185
185
|
|
|
186
186
|
|
|
@@ -5,20 +5,16 @@ from __future__ import annotations
|
|
|
5
5
|
from voidx.tools.bash.core import (
|
|
6
6
|
RouteHint,
|
|
7
7
|
_git_subcommand,
|
|
8
|
-
_UNHINTABLE_GIT_SUBCOMMANDS,
|
|
9
8
|
)
|
|
10
9
|
|
|
11
10
|
|
|
12
11
|
def _hint_git(stripped: str, words: list[str]) -> RouteHint | None:
|
|
13
|
-
subcommand,
|
|
12
|
+
subcommand, _ = _git_subcommand(words)
|
|
14
13
|
if not subcommand:
|
|
15
14
|
return None
|
|
16
|
-
if subcommand in _UNHINTABLE_GIT_SUBCOMMANDS:
|
|
17
|
-
return None
|
|
18
15
|
git_args = stripped[len("git"):].strip()
|
|
19
|
-
|
|
20
|
-
return None
|
|
16
|
+
llm_hint = f"Prefer git tool with args={git_args!r} for structured output."
|
|
21
17
|
return RouteHint(
|
|
22
18
|
tool_id="git", ui_label="→ git",
|
|
23
|
-
llm_hint=
|
|
24
|
-
)
|
|
19
|
+
llm_hint=llm_hint,
|
|
20
|
+
)
|
|
@@ -233,16 +233,16 @@ def _hint_sed(words: list[str]) -> RouteHint | None:
|
|
|
233
233
|
line_no = int(line_prefix)
|
|
234
234
|
return RouteHint(
|
|
235
235
|
tool_id="replace", ui_label="→ replace",
|
|
236
|
-
llm_hint=f'Prefer replace(file_path="{path}", start_no={line_no}, end_no={line_no},
|
|
236
|
+
llm_hint=f'Prefer replace(file_path="{path}", start_no={line_no}, end_no={line_no}, start_anchor="{old_text}", end_anchor="{old_text}", new_string="{new_text}").',
|
|
237
237
|
)
|
|
238
238
|
if is_global:
|
|
239
239
|
return RouteHint(
|
|
240
240
|
tool_id="replace", ui_label="→ replace",
|
|
241
|
-
llm_hint=f'For global substitution: first read {path} to locate lines, then use replace(file_path, start_no, end_no,
|
|
241
|
+
llm_hint=f'For global substitution: first read {path} to locate lines, then use replace(file_path, start_no, end_no, start_anchor="{old_text}", end_anchor="{old_text}", new_string="{new_text}").',
|
|
242
242
|
)
|
|
243
243
|
return RouteHint(
|
|
244
244
|
tool_id="replace", ui_label="→ replace",
|
|
245
|
-
llm_hint=f'Prefer replace(file_path="{path}", start_no, end_no,
|
|
245
|
+
llm_hint=f'Prefer replace(file_path="{path}", start_no, end_no, start_anchor="{old_text}", end_anchor="{old_text}", new_string="{new_text}").',
|
|
246
246
|
)
|
|
247
247
|
|
|
248
248
|
m = _SED_RANGE_DELETE.match(script)
|
|
@@ -250,7 +250,7 @@ def _hint_sed(words: list[str]) -> RouteHint | None:
|
|
|
250
250
|
start, end = int(m.group(1)), int(m.group(2))
|
|
251
251
|
return RouteHint(
|
|
252
252
|
tool_id="replace", ui_label="→ replace",
|
|
253
|
-
llm_hint=f'For line range deletion: first read {path} to see lines {start}-{end}, then use replace(file_path="{path}", start_no={start}, end_no={end},
|
|
253
|
+
llm_hint=f'For line range deletion: first read {path} to see lines {start}-{end}, then use replace(file_path="{path}", start_no={start}, end_no={end}, start_anchor="...", end_anchor="...", new_string="").',
|
|
254
254
|
)
|
|
255
255
|
|
|
256
256
|
m = _SED_LINE_DELETE.match(script)
|
|
@@ -258,14 +258,14 @@ def _hint_sed(words: list[str]) -> RouteHint | None:
|
|
|
258
258
|
line_no = int(m.group(1))
|
|
259
259
|
return RouteHint(
|
|
260
260
|
tool_id="replace", ui_label="→ replace",
|
|
261
|
-
llm_hint=f'For single line deletion: first read {path} to see line {line_no}, then use replace(file_path="{path}", start_no={line_no}, end_no={line_no},
|
|
261
|
+
llm_hint=f'For single line deletion: first read {path} to see line {line_no}, then use replace(file_path="{path}", start_no={line_no}, end_no={line_no}, start_anchor="...", end_anchor="...", new_string="").',
|
|
262
262
|
)
|
|
263
263
|
m = _SED_PATTERN_DELETE.match(script)
|
|
264
264
|
if m:
|
|
265
265
|
pat = m.group(1)
|
|
266
266
|
return RouteHint(
|
|
267
267
|
tool_id="replace", ui_label="→ replace",
|
|
268
|
-
llm_hint=f'For pattern-based deletion: first grep "{pat}" {path} to locate lines, then use replace(
|
|
268
|
+
llm_hint=f'For pattern-based deletion: first grep "{pat}" {path} to locate matching lines, then use replace(file_path="{path}", start_no, end_no, start_anchor, end_anchor, new_string="") with the matched line numbers and content.',
|
|
269
269
|
)
|
|
270
270
|
|
|
271
271
|
return None
|
|
@@ -27,7 +27,10 @@ class BashTool(BaseTool):
|
|
|
27
27
|
return model_to_json_schema(BashInput)
|
|
28
28
|
|
|
29
29
|
async def execute(self, args: dict, ctx: ToolContext) -> ToolResult:
|
|
30
|
-
|
|
30
|
+
try:
|
|
31
|
+
inp = BashInput.model_validate(args)
|
|
32
|
+
except Exception as exc:
|
|
33
|
+
return ToolResult(output=f"Invalid arguments: {exc}", metadata={"error": True})
|
|
31
34
|
|
|
32
35
|
blocked = _check_command(inp.command)
|
|
33
36
|
if blocked:
|
|
@@ -35,7 +38,7 @@ class BashTool(BaseTool):
|
|
|
35
38
|
return ToolResult(
|
|
36
39
|
output=json.dumps(payload, ensure_ascii=False),
|
|
37
40
|
display=blocked,
|
|
38
|
-
metadata={"command": inp.command, "blocked": True},
|
|
41
|
+
metadata={"command": inp.command, "blocked": True, "error": True},
|
|
39
42
|
)
|
|
40
43
|
|
|
41
44
|
blocked = _sandbox_denial(inp.command, ctx)
|
|
@@ -44,7 +47,7 @@ class BashTool(BaseTool):
|
|
|
44
47
|
return ToolResult(
|
|
45
48
|
output=json.dumps(payload, ensure_ascii=False),
|
|
46
49
|
display=blocked,
|
|
47
|
-
metadata={"command": inp.command, "blocked": True},
|
|
50
|
+
metadata={"command": inp.command, "blocked": True, "error": True},
|
|
48
51
|
)
|
|
49
52
|
|
|
50
53
|
hint = try_hint(inp.command)
|
|
@@ -83,7 +86,7 @@ class BashTool(BaseTool):
|
|
|
83
86
|
return ToolResult(
|
|
84
87
|
output=json.dumps(payload, ensure_ascii=False),
|
|
85
88
|
display=display,
|
|
86
|
-
metadata={"command": inp.command, "exit_code": -1, "timeout": True},
|
|
89
|
+
metadata={"command": inp.command, "exit_code": -1, "timeout": True, "error": True},
|
|
87
90
|
)
|
|
88
91
|
|
|
89
92
|
stdout_text = stdout.decode("utf-8", errors="replace") if stdout else ""
|
|
@@ -117,6 +120,7 @@ class BashTool(BaseTool):
|
|
|
117
120
|
"command": inp.command,
|
|
118
121
|
"exit_code": exit_code,
|
|
119
122
|
"ok": exit_code == 0,
|
|
123
|
+
**({"error": True} if exit_code != 0 else {}),
|
|
120
124
|
},
|
|
121
125
|
)
|
|
122
126
|
|
|
@@ -45,7 +45,10 @@ class ClarifyTool(BaseTool):
|
|
|
45
45
|
return model_to_json_schema(ClarifyInput)
|
|
46
46
|
|
|
47
47
|
async def execute(self, args: dict, ctx: ToolContext) -> ToolResult:
|
|
48
|
-
|
|
48
|
+
try:
|
|
49
|
+
inp = ClarifyInput.model_validate(args)
|
|
50
|
+
except Exception as exc:
|
|
51
|
+
return ToolResult(output=f"Invalid arguments: {exc}", metadata={"error": True})
|
|
49
52
|
if ctx.interact is None:
|
|
50
53
|
return ToolResult(
|
|
51
54
|
title="clarify: unavailable",
|
|
@@ -37,7 +37,10 @@ class CompactContextTool(BaseTool):
|
|
|
37
37
|
return model_to_json_schema(CompactContextInput)
|
|
38
38
|
|
|
39
39
|
async def execute(self, args: dict, ctx: ToolContext) -> ToolResult:
|
|
40
|
-
|
|
40
|
+
try:
|
|
41
|
+
inp = CompactContextInput.model_validate(args)
|
|
42
|
+
except Exception as exc:
|
|
43
|
+
return ToolResult(output=f"Invalid arguments: {exc}", metadata={"error": True})
|
|
41
44
|
summary = inp.summary.strip()
|
|
42
45
|
tail_anchor_id = inp.tail_anchor_id.strip()
|
|
43
46
|
return ToolResult(
|
|
@@ -25,7 +25,7 @@ from .types import DisplayLines, ResolvedEdit
|
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
class FileReplaceInput(BaseModel):
|
|
28
|
-
file_path: str = Field(description="
|
|
28
|
+
file_path: str = Field(description="Absolute or relative path to the file")
|
|
29
29
|
start_no: int = Field(
|
|
30
30
|
ge=1,
|
|
31
31
|
description=(
|
|
@@ -40,26 +40,22 @@ class FileReplaceInput(BaseModel):
|
|
|
40
40
|
"Use the line number from the latest read output."
|
|
41
41
|
),
|
|
42
42
|
)
|
|
43
|
-
|
|
43
|
+
start_anchor: str = Field(
|
|
44
44
|
description=(
|
|
45
|
-
"
|
|
46
|
-
"Use an empty string only
|
|
47
|
-
"Aim for a distinctive snippet."
|
|
45
|
+
"Content anchor on the first line to replace — a substring "
|
|
46
|
+
"expected anywhere on that line. Use an empty string only "
|
|
47
|
+
"when the first line is empty. Aim for a distinctive snippet."
|
|
48
48
|
),
|
|
49
49
|
)
|
|
50
|
-
|
|
50
|
+
end_anchor: str = Field(
|
|
51
51
|
description=(
|
|
52
|
-
"
|
|
53
|
-
"Use an empty string only
|
|
54
|
-
"Aim for a distinctive snippet."
|
|
52
|
+
"Content anchor on the last line to replace — a substring "
|
|
53
|
+
"expected anywhere on that line. Use an empty string only "
|
|
54
|
+
"when the last line is empty. Aim for a distinctive snippet."
|
|
55
55
|
),
|
|
56
56
|
)
|
|
57
57
|
new_string: str = Field(
|
|
58
|
-
description=
|
|
59
|
-
"Replacement content. May contain any number of lines. "
|
|
60
|
-
"A trailing newline does not add an extra blank line; "
|
|
61
|
-
"start with a newline only when an intentional blank first line is desired."
|
|
62
|
-
),
|
|
58
|
+
description="Replacement content. May contain any number of lines.",
|
|
63
59
|
)
|
|
64
60
|
|
|
65
61
|
@model_validator(mode="after")
|
|
@@ -74,7 +70,7 @@ class FileReplaceTool(BaseTool):
|
|
|
74
70
|
description = (
|
|
75
71
|
"Replace whole lines in a file. "
|
|
76
72
|
"Provide the exact start_no/end_no from the latest read output, "
|
|
77
|
-
"plus
|
|
73
|
+
"plus start_anchor/end_anchor substrings from the first and last lines. "
|
|
78
74
|
"Read the target lines first."
|
|
79
75
|
)
|
|
80
76
|
|
|
@@ -82,14 +78,17 @@ class FileReplaceTool(BaseTool):
|
|
|
82
78
|
return model_to_json_schema(FileReplaceInput)
|
|
83
79
|
|
|
84
80
|
async def execute(self, args: dict, ctx: ToolContext) -> ToolResult:
|
|
85
|
-
|
|
81
|
+
try:
|
|
82
|
+
inp = FileReplaceInput.model_validate(args)
|
|
83
|
+
except Exception as exc:
|
|
84
|
+
return ToolResult(output=f"Invalid arguments: {exc}", metadata={"error": True})
|
|
86
85
|
return await _execute_text_replace(
|
|
87
86
|
ctx,
|
|
88
87
|
file_path=inp.file_path,
|
|
89
88
|
start_no=inp.start_no,
|
|
90
89
|
end_no=inp.end_no,
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
start_anchor=inp.start_anchor,
|
|
91
|
+
end_anchor=inp.end_anchor,
|
|
93
92
|
new_string=inp.new_string,
|
|
94
93
|
tool_name=self.id,
|
|
95
94
|
)
|
|
@@ -114,8 +113,8 @@ async def _execute_text_replace(
|
|
|
114
113
|
file_path: str,
|
|
115
114
|
start_no: int,
|
|
116
115
|
end_no: int,
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
start_anchor: str,
|
|
117
|
+
end_anchor: str,
|
|
119
118
|
new_string: str,
|
|
120
119
|
tool_name: str,
|
|
121
120
|
) -> ToolResult:
|
|
@@ -126,12 +125,12 @@ async def _execute_text_replace(
|
|
|
126
125
|
|
|
127
126
|
original = path.read_text(encoding="utf-8", errors="replace")
|
|
128
127
|
display = _split_display_lines(original)
|
|
129
|
-
match = _find_text_segment(display.lines, start_no, end_no,
|
|
128
|
+
match = _find_text_segment(display.lines, start_no, end_no, start_anchor, end_anchor)
|
|
130
129
|
if isinstance(match, str):
|
|
131
130
|
return ToolResult(output=match, metadata={"error": True})
|
|
132
131
|
|
|
133
132
|
_, _, start_line, end_line = match
|
|
134
|
-
coverage_error = check_read_coverage(ctx, path, start_line, end_line)
|
|
133
|
+
coverage_error = check_read_coverage(ctx, path, start_line, end_line, display_path=file_path)
|
|
135
134
|
if coverage_error:
|
|
136
135
|
return ToolResult(output=f"Edit 0: {coverage_error}", metadata={"error": True})
|
|
137
136
|
|
|
@@ -222,7 +221,7 @@ async def _apply_resolved_edits(
|
|
|
222
221
|
continue
|
|
223
222
|
if edit.operation == "insert" and edit.start_line == total_lines and edit.end_line == total_lines:
|
|
224
223
|
continue
|
|
225
|
-
coverage_error = check_read_coverage(ctx, path, edit.start_line, edit.end_line)
|
|
224
|
+
coverage_error = check_read_coverage(ctx, path, edit.start_line, edit.end_line, display_path=file_path)
|
|
226
225
|
if coverage_error:
|
|
227
226
|
return ToolResult(output=f"Edit {i}: {coverage_error}", metadata={"error": True})
|
|
228
227
|
|