vtx-coding-agent 0.1.1__tar.gz → 0.1.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.
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/CHANGELOG.md +64 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/PKG-INFO +5 -3
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/README.md +4 -2
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/docs/configuration.md +15 -0
- vtx_coding_agent-0.1.2/docs/extensions.md +200 -0
- vtx_coding_agent-0.1.2/examples/extensions/auto_commit.py +77 -0
- vtx_coding_agent-0.1.2/examples/extensions/hello.py +22 -0
- vtx_coding_agent-0.1.2/examples/extensions/log_tool_calls.py +55 -0
- vtx_coding_agent-0.1.2/examples/extensions/permission_gate.py +50 -0
- vtx_coding_agent-0.1.2/examples/extensions/tool_override.py +57 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/pyproject.toml +1 -1
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/cli.py +24 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/config.py +39 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/defaults/config.yml +13 -1
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/events.py +17 -1
- vtx_coding_agent-0.1.2/src/vtx/extensions.py +894 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/headless.py +13 -2
- vtx_coding_agent-0.1.2/src/vtx/llm/provider.yaml +644 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/providers/anthropic_sdk.py +1 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/providers/openai_sdk.py +24 -3
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/sdk/anthropic.py +41 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/sdk/base.py +4 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/sdk/openai.py +64 -2
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/loop.py +53 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/runtime.py +30 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/tools/__init__.py +31 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/tools/web.py +40 -4
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/turn.py +76 -5
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/app.py +40 -8
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/commands/__init__.py +31 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/commands/settings.py +9 -4
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/launch.py +9 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/version.py +1 -1
- vtx_coding_agent-0.1.2/tests/llm/test_thinking_wire_params.py +193 -0
- vtx_coding_agent-0.1.2/tests/test_extensions.py +488 -0
- vtx_coding_agent-0.1.2/tests/tools/test_web_expand_collapse.py +82 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/uv.lock +1 -1
- vtx_coding_agent-0.1.1/src/vtx/llm/provider.yaml +0 -280
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/.agents/skills/vtx-release-publish/SKILL.md +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/.agents/skills/vtx-tmux-test/SKILL.md +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/.agents/skills/vtx-tmux-test/run-e2e-tests.sh +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/.agents/skills/vtx-tmux-test/setup-test-project.sh +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/.gitignore +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/.pre-commit-config.yaml +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/.python-version +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/AGENTS.md +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/LICENSE +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/docs/README.md +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/docs/architecture.md +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/docs/development.md +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/docs/e2e-test-coverage-review.md +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/docs/headless.md +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/docs/local-models.md +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/docs/permissions.md +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/docs/providers.md +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/docs/sessions.md +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/docs/skills.md +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/docs/storage-layout.md +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/docs/theming.md +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/docs/tools.md +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/scripts/show_themes.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/__init__.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/async_utils.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/builtin_skills/github/SKILL.md +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/builtin_skills/init/SKILL.md +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/builtin_skills/review/SKILL.md +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/builtin_skills/skill-builder/SKILL.md +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/context/__init__.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/context/_xml.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/context/agent_mds.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/context/git.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/context/loader.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/context/skills.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/core/__init__.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/core/compaction.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/core/errors.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/core/handoff.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/core/scratchpad.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/core/types.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/defaults/__init__.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/diff_display.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/gh_cli.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/git_branch.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/__init__.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/base.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/context_length.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/dynamic_models.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/model_fetcher.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/models.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/oauth/__init__.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/oauth/copilot.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/oauth/dynamic.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/oauth/openai.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/phase_parser.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/provider_catalog.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/providers/__init__.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/providers/mock.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/providers/sanitize.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/sdk/__init__.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/tool_parser.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/notify.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/permissions.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/prompts/__init__.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/prompts/builder.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/prompts/env.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/prompts/identity.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/prompts/tooling.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/py.typed +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/session.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/sounds/completion.wav +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/sounds/error.wav +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/sounds/permission.wav +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/themes.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/tools/_read_image.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/tools/_tool_utils.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/tools/base.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/tools/bash.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/tools/edit.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/tools/find.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/tools/read.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/tools/skill.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/tools/write.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/tools_manager.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/__init__.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/agent_runner.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/app_protocol.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/autocomplete.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/blocks.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/chat.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/clipboard.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/commands/auth.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/commands/base.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/commands/models.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/commands/sessions.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/completion_ui.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/export.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/floating_list.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/formatting.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/input.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/latex.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/path_complete.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/prompt_history.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/queue_ui.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/selection_mode.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/session_ui.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/startup.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/styles.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/tool_output.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/tree.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/welcome.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/widgets.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/update_check.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/conftest.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/context/test_agents.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/context/test_skills.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/llm/__init__.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/llm/test_anthropic_provider.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/llm/test_mock_provider.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/llm/test_openai_oauth.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/llm/test_tls_verify.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_agentic_loop.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_cli.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_cli_auth_flags.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_cli_provider_resolution.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_compaction.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_config_binaries.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_config_error_fallback.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_config_injection.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_config_migration.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_dynamic_auth.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_dynamic_models.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_errors.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_git_branch.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_handoff.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_handoff_link_interrupt.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_headless.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_launch_warnings.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_llm_lazy_imports.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_local_auth_config.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_model_provider_resolution.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_notifications_config.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_notify.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_openai_compat.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_permissions.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_phase_parser.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_prompts.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_runtime_switch_model.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_session_persistence.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_session_queries.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_session_resume.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_session_tree.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_system_prompt.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_system_prompt_git_context.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_themes.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_tool_parser.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_tools_manager.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_ui_notifications.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_update_check.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_update_notice_behavior.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/tools/test_bash_shell.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/tools/test_bash_truncation.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/tools/test_diff.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/tools/test_edit.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/tools/test_edit_display.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/tools/test_read.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/tools/test_read_image.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/tools/test_read_image_integration.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/tools/test_read_image_resize.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/tools/test_skill.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/tools/test_subprocess_cancellation.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/tools/test_write.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_app_approval_keys.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_autocomplete.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_completion_chrome.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_export.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_floating_list.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_info_bar_clicks.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_info_bar_permissions.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_input_approval_submit.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_input_cursor_theme.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_input_handoff.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_input_paste.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_input_shell_style.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_keybindings.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_latex.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_login_command.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_permission_selection_status.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_permissions_command.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_prompt_history.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_queue_editing.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_shell_command_detection.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_status_line.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_streaming_blocks.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_styles.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_thinking_notifications_commands.py +0 -0
- {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_tool_output_expansion.py +0 -0
|
@@ -4,6 +4,70 @@ All notable changes to Vtx are documented in this file. The format is based on
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
|
|
5
5
|
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.1.2] - 2026-06-16 — Extension System
|
|
8
|
+
|
|
9
|
+
Adds a first-class extension API that lets users add new LLM-callable tools,
|
|
10
|
+
intercept and modify tool calls, react to lifecycle events, and register new
|
|
11
|
+
slash commands — all without forking vtx. Modeled on the pi agent's extension
|
|
12
|
+
hooks, but native to vtx's Python stack.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
#### Extension system
|
|
17
|
+
- `vtx.extensions` module: `ExtensionAPI`, `EventBus`, `ExtensionTool`,
|
|
18
|
+
`ExtensionCommand`, file/package loader, and discovery.
|
|
19
|
+
- Auto-discovery from `<cwd>/.vtx/extensions/*.py` and
|
|
20
|
+
`~/.vtx/agent/extensions/*.py` (with package-style `__init__.py` support).
|
|
21
|
+
Project-local extensions take precedence over global ones.
|
|
22
|
+
- New `extensions:` list in `config.yml` for user-configured extension paths.
|
|
23
|
+
- New `--extension PATH` (repeatable) and `--no-extensions` CLI flags.
|
|
24
|
+
- Events: `session_start`, `session_end`, `agent_start`, `agent_end`,
|
|
25
|
+
`turn_start`, `turn_end`, `tool_call`, `tool_result`, `compaction_start`,
|
|
26
|
+
`compaction_end`.
|
|
27
|
+
- Blocking semantics: a `tool_call` handler can return `{"block": True,
|
|
28
|
+
"reason": "..."}` to deny the call, or `{"args": {...}}` to rewrite the
|
|
29
|
+
arguments before execution. A `tool_result` handler can return
|
|
30
|
+
`{"output": "..."}` to rewrite the text the LLM sees. Modifications are
|
|
31
|
+
chained across handlers.
|
|
32
|
+
- Tool override: an extension that registers a tool with the same name as a
|
|
33
|
+
built-in (e.g. `read`, `bash`) replaces it. The extension version's
|
|
34
|
+
description and parameter schema are what the LLM sees.
|
|
35
|
+
- Sync-only `session_start` / `session_end` emit so startup and shutdown can
|
|
36
|
+
fire handlers without spinning up an extra event loop.
|
|
37
|
+
- Handler exceptions are caught and logged to stderr; they never crash the
|
|
38
|
+
agent loop.
|
|
39
|
+
- Example extensions under `examples/extensions/`: `hello.py`,
|
|
40
|
+
`permission_gate.py`, `auto_commit.py`, `tool_override.py`,
|
|
41
|
+
`log_tool_calls.py`.
|
|
42
|
+
- Full reference: [docs/extensions.md](docs/extensions.md).
|
|
43
|
+
|
|
44
|
+
#### LLM providers
|
|
45
|
+
- 31 new gateways in `src/vtx/llm/provider.yaml`, taking the catalog from 18
|
|
46
|
+
to 49 entries. All require authentication (`api_key_env` set on every
|
|
47
|
+
entry; ollama is the only key-less provider, via `api_key_optional: true`
|
|
48
|
+
for local use). All fetch their model list dynamically from the
|
|
49
|
+
provider's `/models` endpoint with a 10-minute parser cooldown.
|
|
50
|
+
- OpenAI-compatible: AIHubMix (`AIHUBMIX_API_KEY`, custom `APP-Code` header),
|
|
51
|
+
Apertis, Baseten, Berget AI, Blackbox AI, Cline (custom `/ai/cline/models`
|
|
52
|
+
endpoint), Chutes AI, Cortecs, Crof, Dialagram, Dinference, Friendli,
|
|
53
|
+
HicapAI, Jiekou, Knox, LightningAI, LLMGateway, MegaNova, Moark,
|
|
54
|
+
ModelScope, MoonshotAI, NanoGPT, Pollinations AI, Routing.run, Seraphyn,
|
|
55
|
+
Sherlock (CloudFerro), Vercel AI, Zenmux, Clarifai.
|
|
56
|
+
- Anthropic-compatible: FastRouter.
|
|
57
|
+
- All entries appear automatically in `/login` and the model picker — no
|
|
58
|
+
other code changes needed because `provider.yaml` is the single source of
|
|
59
|
+
truth.
|
|
60
|
+
|
|
61
|
+
#### Internal
|
|
62
|
+
- Config schema bumped to v7 with a v6 → v7 migration that initializes
|
|
63
|
+
`extensions: []` and tolerates the first-pass dict form.
|
|
64
|
+
- `Loop` and `_TurnRunner` now accept an optional `EventBus`; events are
|
|
65
|
+
fired at the right points without changing existing event payload shape.
|
|
66
|
+
- `tools.get_tools_with_extensions(default_names, extension_tools)` merges
|
|
67
|
+
built-ins and extension tools with extension names winning.
|
|
68
|
+
- `commands.__init__` routes `/<name>` to extension-registered commands after
|
|
69
|
+
built-ins get a chance to handle them.
|
|
70
|
+
|
|
7
71
|
## [0.1.1] - 2026-06-15 — Initial Public Release
|
|
8
72
|
|
|
9
73
|
The first public release of Vtx, a minimalist, developer-first coding agent
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vtx-coding-agent
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Minimalist coding agent harness with a Textual TUI and headless CLI. <1k-token system prompt, 18+ LLM providers, AGENTS.md + skills context, session tree, prompt/auto permissions.
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Requires-Python: >=3.12
|
|
@@ -39,9 +39,9 @@ Description-Content-Type: text/markdown
|
|
|
39
39
|
|
|
40
40
|
**Vtx** is a minimalist, developer-first coding agent harness that delivers maximum capability with minimum overhead.
|
|
41
41
|
|
|
42
|
-
Unlike heavy agentic frameworks that load thousands of hidden tokens, Vtx
|
|
42
|
+
Unlike heavy agentic frameworks that load thousands of hidden tokens, Vtx is transparent about its footprint. The Vtx-authored base prompt is roughly **2,000 tokens**, and the full runtime (base + tool guidelines + env block) is around **~2,200 tokens**. Composed prompts in real projects typically land in the **2,000–3,500 token** range once `AGENTS.md` and skill descriptions are attached.
|
|
43
43
|
|
|
44
|
-
By keeping the core prompt
|
|
44
|
+
By keeping the core prompt lean, Vtx leaves the model's context window open for what matters most: **your code, your project files, and your task context**.
|
|
45
45
|
|
|
46
46
|
---
|
|
47
47
|
|
|
@@ -53,6 +53,7 @@ By keeping the core prompt small, Vtx leaves the model's context window open for
|
|
|
53
53
|
- **Flexible Model Support**: Compatible with Hosted APIs (OpenAI, Anthropic, Azure, DeepSeek, ZhiPu) as well as unauthenticated local endpoints (Ollama, llama-server).
|
|
54
54
|
- **Collapsible Thinking Blocks**: TUI elegantly collapses finalized thinking chains to keep your workspace readable.
|
|
55
55
|
- **Secure Sandboxed Control**: Supports both `prompt` (confirmation before mutating changes) and `auto` permission modes.
|
|
56
|
+
- **Self-Extensible**: Drop a Python file in `~/.vtx/agent/extensions/` to add tools, intercept tool calls, register slash commands, and react to lifecycle events. See [docs/extensions.md](docs/extensions.md).
|
|
56
57
|
|
|
57
58
|
---
|
|
58
59
|
|
|
@@ -245,6 +246,7 @@ For deeper information, consult the topic-specific files in the [`docs/`](docs/)
|
|
|
245
246
|
- [docs/permissions.md](docs/permissions.md) — Safe-command lists and user approval heuristics.
|
|
246
247
|
- [docs/sessions.md](docs/sessions.md) — Session JSONL format, history files, handoff guides, and compaction.
|
|
247
248
|
- [docs/skills.md](docs/skills.md) — Authoring custom Skills, argument parsing, and command mapping.
|
|
249
|
+
- [docs/extensions.md](docs/extensions.md) — Python extension API: add tools, intercept tool calls, register slash commands.
|
|
248
250
|
- [docs/theming.md](docs/theming.md) — Catalog of the 24+ built-in themes and color tokens.
|
|
249
251
|
- [docs/headless.md](docs/headless.md) — Non-interactive execution, piped input streams, and exit codes.
|
|
250
252
|
- [docs/storage-layout.md](docs/storage-layout.md) — Complete directory mapping of files on disk.
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
|
|
18
18
|
**Vtx** is a minimalist, developer-first coding agent harness that delivers maximum capability with minimum overhead.
|
|
19
19
|
|
|
20
|
-
Unlike heavy agentic frameworks that load thousands of hidden tokens, Vtx
|
|
20
|
+
Unlike heavy agentic frameworks that load thousands of hidden tokens, Vtx is transparent about its footprint. The Vtx-authored base prompt is roughly **2,000 tokens**, and the full runtime (base + tool guidelines + env block) is around **~2,200 tokens**. Composed prompts in real projects typically land in the **2,000–3,500 token** range once `AGENTS.md` and skill descriptions are attached.
|
|
21
21
|
|
|
22
|
-
By keeping the core prompt
|
|
22
|
+
By keeping the core prompt lean, Vtx leaves the model's context window open for what matters most: **your code, your project files, and your task context**.
|
|
23
23
|
|
|
24
24
|
---
|
|
25
25
|
|
|
@@ -31,6 +31,7 @@ By keeping the core prompt small, Vtx leaves the model's context window open for
|
|
|
31
31
|
- **Flexible Model Support**: Compatible with Hosted APIs (OpenAI, Anthropic, Azure, DeepSeek, ZhiPu) as well as unauthenticated local endpoints (Ollama, llama-server).
|
|
32
32
|
- **Collapsible Thinking Blocks**: TUI elegantly collapses finalized thinking chains to keep your workspace readable.
|
|
33
33
|
- **Secure Sandboxed Control**: Supports both `prompt` (confirmation before mutating changes) and `auto` permission modes.
|
|
34
|
+
- **Self-Extensible**: Drop a Python file in `~/.vtx/agent/extensions/` to add tools, intercept tool calls, register slash commands, and react to lifecycle events. See [docs/extensions.md](docs/extensions.md).
|
|
34
35
|
|
|
35
36
|
---
|
|
36
37
|
|
|
@@ -223,6 +224,7 @@ For deeper information, consult the topic-specific files in the [`docs/`](docs/)
|
|
|
223
224
|
- [docs/permissions.md](docs/permissions.md) — Safe-command lists and user approval heuristics.
|
|
224
225
|
- [docs/sessions.md](docs/sessions.md) — Session JSONL format, history files, handoff guides, and compaction.
|
|
225
226
|
- [docs/skills.md](docs/skills.md) — Authoring custom Skills, argument parsing, and command mapping.
|
|
227
|
+
- [docs/extensions.md](docs/extensions.md) — Python extension API: add tools, intercept tool calls, register slash commands.
|
|
226
228
|
- [docs/theming.md](docs/theming.md) — Catalog of the 24+ built-in themes and color tokens.
|
|
227
229
|
- [docs/headless.md](docs/headless.md) — Non-interactive execution, piped input streams, and exit codes.
|
|
228
230
|
- [docs/storage-layout.md](docs/storage-layout.md) — Complete directory mapping of files on disk.
|
|
@@ -252,6 +252,20 @@ When true, the welcome panel on launch lists keyboard shortcuts. Set to `false`
|
|
|
252
252
|
|
|
253
253
|
List of models hidden from the `/model` picker. Use a provider name to hide all its models (`"github-copilot"`) or `"provider:model"` to hide a single model (`"github-copilot:gpt-5.5-copilot"`). Hidden models stay usable via config defaults or `--model` / session resume — they just don't show up in the picker.
|
|
254
254
|
|
|
255
|
+
## `extensions`
|
|
256
|
+
|
|
257
|
+
### `extensions` (list of paths)
|
|
258
|
+
|
|
259
|
+
Paths to Python extension files or package directories. Each entry is loaded at startup in addition to the auto-discovered paths in `<cwd>/.vtx/extensions/` and `~/.vtx/agent/extensions/`.
|
|
260
|
+
|
|
261
|
+
```yaml
|
|
262
|
+
extensions:
|
|
263
|
+
- ~/.vtx/extensions/permission_gate.py
|
|
264
|
+
- ./tools/vtx-extensions/audit-logger/
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Pass `--no-extensions` on the CLI to skip auto-discovery (the explicit list still loads). See [extensions.md](extensions.md) for the full extension API.
|
|
268
|
+
|
|
255
269
|
## `permissions`
|
|
256
270
|
|
|
257
271
|
### `permissions.mode`
|
|
@@ -334,6 +348,7 @@ Vtx auto-migrates your config when you upgrade. The current `config_version` is
|
|
|
334
348
|
| v3 → v4 | Added `llm.auth.openai_compat` and `llm.auth.anthropic_compat` (defaulted to `"auto"`). |
|
|
335
349
|
| v4 → v5 | Added `notifications.volume` (defaulted to `0.5`). |
|
|
336
350
|
| v5 → v6 | Promoted `llm.system_prompt` to a dict with `git_context` and `content`. The default `content` is now sourced from `vtx.prompts.identity` so users can override it via config. |
|
|
351
|
+
| v6 → v7 | Added top-level `extensions:` (list of paths). Default is `[]`. Auto-discovered paths in `.vtx/extensions/` and `~/.vtx/agent/extensions/` still load unless `--no-extensions` is passed. |
|
|
337
352
|
|
|
338
353
|
The earlier Vtx releases stored the config under `~/.vtx/`. v0.3.11 added a migration that moves `config.yml`, `sessions/`, `auth/` files, and `models/` into `~/.vtx/`. The old path is no longer read. See [`src/vtx/config.py`](../src/vtx/config.py) for the migration code.
|
|
339
354
|
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# Extensions
|
|
2
|
+
|
|
3
|
+
Vtx ships with a built-in extension system so you can add new tools,
|
|
4
|
+
intercept tool calls, react to lifecycle events, and register new
|
|
5
|
+
slash commands without forking the codebase. This page is a quick
|
|
6
|
+
start; see the module docstring in `vtx/extensions.py` for the full
|
|
7
|
+
API.
|
|
8
|
+
|
|
9
|
+
## Quick start
|
|
10
|
+
|
|
11
|
+
Create a file at `~/.vtx/agent/extensions/hello.py` (or
|
|
12
|
+
`.vtx/extensions/hello.py` in a project):
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
from vtx.extensions import AGENT_START, AGENT_END, SESSION_START
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def register(api):
|
|
19
|
+
@api.on(SESSION_START)
|
|
20
|
+
def on_start(event, payload):
|
|
21
|
+
api.notify("hello extension loaded")
|
|
22
|
+
|
|
23
|
+
@api.on(AGENT_END)
|
|
24
|
+
def on_done(event, payload):
|
|
25
|
+
api.notify(f"agent ended: {payload.get('stop_reason')}")
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Drop in the file, restart vtx, and you'll see the notifications. The
|
|
29
|
+
extension is a plain Python module — no compilation, no manifest
|
|
30
|
+
file, no separate package format.
|
|
31
|
+
|
|
32
|
+
## What extensions can do
|
|
33
|
+
|
|
34
|
+
| API call | Effect |
|
|
35
|
+
|---------------------------------|--------|
|
|
36
|
+
| `api.on(event, handler)` | Subscribe to a lifecycle event |
|
|
37
|
+
| `api.register_tool(name, ...)` | Add (or override) an LLM-callable tool |
|
|
38
|
+
| `api.register_command(name, ...)` | Add a `/name` slash command |
|
|
39
|
+
| `api.notify(message, level)` | Print a line to stderr / chat log |
|
|
40
|
+
| `api.cwd`, `api.session_file`, `api.config_dir` | Read-only session context |
|
|
41
|
+
|
|
42
|
+
## Discovery order
|
|
43
|
+
|
|
44
|
+
Extensions are loaded from these locations, in this order. Project-local
|
|
45
|
+
extensions take precedence over global ones, so you can override a
|
|
46
|
+
shared extension on a per-project basis.
|
|
47
|
+
|
|
48
|
+
1. `<cwd>/.vtx/extensions/*.py` and `*/__init__.py`
|
|
49
|
+
2. `~/.vtx/agent/extensions/*.py` and `*/__init__.py`
|
|
50
|
+
3. `extensions:` list in `~/.vtx/config.yml`
|
|
51
|
+
4. `--extension PATH` (repeatable) on the CLI
|
|
52
|
+
|
|
53
|
+
To disable auto-discovery while still allowing explicit paths, pass
|
|
54
|
+
`--no-extensions`.
|
|
55
|
+
|
|
56
|
+
## Events
|
|
57
|
+
|
|
58
|
+
The full event surface:
|
|
59
|
+
|
|
60
|
+
| Event | Fires when... | Blocking? |
|
|
61
|
+
|-------------------|----------------------------------------|-----------|
|
|
62
|
+
| `session_start` | TUI / headless run begins | no |
|
|
63
|
+
| `session_end` | TUI / headless run ends | no |
|
|
64
|
+
| `agent_start` | User submitted a prompt | no |
|
|
65
|
+
| `agent_end` | Agent finished a turn | no |
|
|
66
|
+
| `turn_start` | A new turn within the agent loop | no |
|
|
67
|
+
| `turn_end` | A turn completed | no |
|
|
68
|
+
| `tool_call` | Right before a tool executes | **yes** |
|
|
69
|
+
| `tool_result` | Right after a tool returns | **yes** |
|
|
70
|
+
| `compaction_start`| Conversation overflow triggered | no |
|
|
71
|
+
| `compaction_end` | Overflow summary written | no |
|
|
72
|
+
|
|
73
|
+
`session_start` and `session_end` are emitted synchronously because
|
|
74
|
+
they fire before / after the asyncio loop. Use a sync handler for
|
|
75
|
+
those events; for everything else, async handlers are preferred.
|
|
76
|
+
|
|
77
|
+
## Blocking / modifying tool calls
|
|
78
|
+
|
|
79
|
+
The `tool_call` and `tool_result` events accept handler return values:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from vtx.extensions import TOOL_CALL, TOOL_RESULT
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def register(api):
|
|
86
|
+
@api.on(TOOL_CALL)
|
|
87
|
+
def gate(event, payload):
|
|
88
|
+
# Block dangerous commands
|
|
89
|
+
if payload["tool_name"] == "bash":
|
|
90
|
+
cmd = (payload.get("args") or {}).get("command", "")
|
|
91
|
+
if "rm -rf" in cmd:
|
|
92
|
+
return {"block": True, "reason": "rm -rf is not allowed"}
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
@api.on(TOOL_RESULT)
|
|
96
|
+
def redact(event, payload):
|
|
97
|
+
# Rewrite the text the LLM sees
|
|
98
|
+
if payload["tool_name"] == "bash":
|
|
99
|
+
text = " ".join(
|
|
100
|
+
c.text for c in payload["result"].content if hasattr(c, "text")
|
|
101
|
+
)
|
|
102
|
+
if "SECRET" in text:
|
|
103
|
+
return {"output": text.replace("SECRET", "[REDACTED]")}
|
|
104
|
+
return None
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Handler return values are processed in registration order, and later
|
|
108
|
+
handlers see earlier handlers' modifications. The first handler that
|
|
109
|
+
returns `{"block": True, ...}` short-circuits the call and the tool
|
|
110
|
+
never runs.
|
|
111
|
+
|
|
112
|
+
## Registering a custom tool
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
def register(api):
|
|
116
|
+
api.register_tool(
|
|
117
|
+
name="greet",
|
|
118
|
+
description="Greet someone by name",
|
|
119
|
+
parameters={
|
|
120
|
+
"type": "object",
|
|
121
|
+
"properties": {
|
|
122
|
+
"name": {"type": "string", "description": "Who to greet"}
|
|
123
|
+
},
|
|
124
|
+
"required": ["name"],
|
|
125
|
+
},
|
|
126
|
+
execute=lambda args, ctx: {
|
|
127
|
+
"success": True,
|
|
128
|
+
"result": f"Hello, {args['name']}!",
|
|
129
|
+
},
|
|
130
|
+
)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The `parameters` argument is a JSON Schema object — the same shape
|
|
134
|
+
LLM providers accept, so you can write `{"type": "array", "items": ...}`,
|
|
135
|
+
`{"type": "string", "enum": [...]}`, etc. vtx generates a pydantic
|
|
136
|
+
model from the schema, so the same parameters you write here are
|
|
137
|
+
what the LLM sees in its system prompt.
|
|
138
|
+
|
|
139
|
+
The `execute` callback may be sync or async. Return a `ToolResult`
|
|
140
|
+
(or any dict with `success`, `result`, `ui_summary`, `ui_details`,
|
|
141
|
+
`file_changes` keys).
|
|
142
|
+
|
|
143
|
+
### Overriding a built-in tool
|
|
144
|
+
|
|
145
|
+
If an extension registers a tool with the same name as a built-in
|
|
146
|
+
(`read`, `bash`, `web_search`, etc.), the extension version wins.
|
|
147
|
+
The original tool is not silently shadowed — the LLM sees your
|
|
148
|
+
description and your parameter schema instead. Use this for
|
|
149
|
+
auditing, sandboxing, or wrapping a built-in with a different
|
|
150
|
+
backend.
|
|
151
|
+
|
|
152
|
+
## Registering a custom slash command
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
def register(api):
|
|
156
|
+
api.register_command(
|
|
157
|
+
name="hello",
|
|
158
|
+
description="Say hello",
|
|
159
|
+
handler=lambda args: f"hi {args or 'world'}",
|
|
160
|
+
)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
The handler receives the argument string (everything after `/hello`)
|
|
164
|
+
and may return a string or a `CommandOutcome(output=..., success=...)`.
|
|
165
|
+
Extension commands are looked up after the built-ins, so they can
|
|
166
|
+
shadow built-in commands if you really want to.
|
|
167
|
+
|
|
168
|
+
## Examples
|
|
169
|
+
|
|
170
|
+
See `examples/extensions/` in the source tree:
|
|
171
|
+
|
|
172
|
+
- `hello.py` — minimal lifecycle notifications
|
|
173
|
+
- `permission_gate.py` — block destructive `bash` commands
|
|
174
|
+
- `auto_commit.py` — git commit at the end of every successful turn
|
|
175
|
+
- `tool_override.py` — audit log for every `read` call
|
|
176
|
+
- `log_tool_calls.py` — JSONL log of every tool call + result
|
|
177
|
+
|
|
178
|
+
## Permissions and trust
|
|
179
|
+
|
|
180
|
+
Extensions run in-process with the same permissions as the vtx
|
|
181
|
+
binary. They can read and write any file you can, call out to the
|
|
182
|
+
network, and execute subprocesses. Don't load extensions from
|
|
183
|
+
sources you don't trust.
|
|
184
|
+
|
|
185
|
+
Vtx does not currently sandbox extensions. If you need stronger
|
|
186
|
+
boundaries, run vtx inside a container or VM and treat the
|
|
187
|
+
extension file as part of the image.
|
|
188
|
+
|
|
189
|
+
## Debugging
|
|
190
|
+
|
|
191
|
+
A bad extension should never crash the agent. If `register(api)`
|
|
192
|
+
raises, vtx logs the error to stderr and skips the extension. If
|
|
193
|
+
an event handler raises, the bus swallows the exception and
|
|
194
|
+
prints a traceback to stderr; the rest of the handlers still
|
|
195
|
+
fire and the agent loop continues.
|
|
196
|
+
|
|
197
|
+
To see which extensions vtx loaded, pass `--no-extensions` and
|
|
198
|
+
then load each one explicitly with `--extension PATH` until you
|
|
199
|
+
find the one that breaks. Extension errors are also logged at
|
|
200
|
+
launch time as `LaunchWarning` entries in the TUI.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Auto-commit at the end of every successful agent run.
|
|
2
|
+
|
|
3
|
+
On ``agent_end`` with ``stop_reason == "stop"``, this extension stages
|
|
4
|
+
all changes in the current working directory and creates a single commit
|
|
5
|
+
attributed to the extension. If the working tree is clean or there is
|
|
6
|
+
no git repo, the extension does nothing.
|
|
7
|
+
|
|
8
|
+
Skip-turn-ending events (interrupted, error, length) are ignored so we
|
|
9
|
+
do not commit partial work the user might want to inspect or revert.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import asyncio
|
|
15
|
+
import shutil
|
|
16
|
+
|
|
17
|
+
from vtx.extensions import AGENT_END
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
async def _run_git(*args: str, cwd: str) -> tuple[int, str, str]:
|
|
21
|
+
"""Run a git command, return (returncode, stdout, stderr)."""
|
|
22
|
+
proc = await asyncio.create_subprocess_exec(
|
|
23
|
+
"git", *args, cwd=cwd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
|
24
|
+
)
|
|
25
|
+
stdout, stderr = await proc.communicate()
|
|
26
|
+
return (
|
|
27
|
+
proc.returncode or 0,
|
|
28
|
+
stdout.decode("utf-8", errors="replace").strip(),
|
|
29
|
+
stderr.decode("utf-8", errors="replace").strip(),
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def register(api):
|
|
34
|
+
if shutil.which("git") is None:
|
|
35
|
+
api.notify("git not on PATH; auto_commit is a no-op", level="warning")
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
@api.on(AGENT_END)
|
|
39
|
+
def _commit(event, payload):
|
|
40
|
+
stop = payload.get("stop_reason", "stop")
|
|
41
|
+
if stop != "stop":
|
|
42
|
+
return None
|
|
43
|
+
# We are in a sync handler so we cannot await directly. We schedule
|
|
44
|
+
# the commit on the running loop (or, if no loop is running, do
|
|
45
|
+
# nothing — there is no way to run async work from a sync event
|
|
46
|
+
# fired at process exit).
|
|
47
|
+
try:
|
|
48
|
+
loop = asyncio.get_running_loop()
|
|
49
|
+
except RuntimeError:
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
async def _do_commit() -> None:
|
|
53
|
+
cwd = api.cwd
|
|
54
|
+
code, _, _ = await _run_git("rev-parse", "--is-inside-work-tree", cwd=cwd)
|
|
55
|
+
if code != 0:
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
code, status, _ = await _run_git("status", "--porcelain", cwd=cwd)
|
|
59
|
+
if code != 0 or not status:
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
code, _, err = await _run_git("add", "-A", cwd=cwd)
|
|
63
|
+
if code != 0:
|
|
64
|
+
api.notify(f"git add failed: {err}", level="error")
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
summary = "\n".join(status.splitlines()[:8])
|
|
68
|
+
msg = f"vtx auto-commit: {len(status.splitlines())} file(s)\n\n{summary}"
|
|
69
|
+
code, _, err = await _run_git("commit", "-m", msg, "--no-verify", cwd=cwd)
|
|
70
|
+
if code == 0:
|
|
71
|
+
api.notify("auto-committed working-tree changes")
|
|
72
|
+
else:
|
|
73
|
+
# commit can fail benignly (e.g. pre-commit hook rejected).
|
|
74
|
+
api.notify(f"commit skipped: {err[:120]}", level="warning")
|
|
75
|
+
|
|
76
|
+
loop.create_task(_do_commit()) # noqa: RUF006
|
|
77
|
+
return None
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Minimal extension example. Logs a line on agent_end.
|
|
2
|
+
|
|
3
|
+
Drop this file into ``~/.vtx/agent/extensions/hello.py`` (or any
|
|
4
|
+
``.vtx/extensions/hello.py``) and the next vtx run will print
|
|
5
|
+
``hello extension loaded`` on startup, plus ``agent ended: stop``
|
|
6
|
+
when the agent finishes a turn.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from vtx.extensions import AGENT_END, SESSION_START
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def register(api):
|
|
13
|
+
api.notify("loaded", level="info")
|
|
14
|
+
|
|
15
|
+
@api.on(SESSION_START)
|
|
16
|
+
def _start(event, payload):
|
|
17
|
+
api.notify("session starting")
|
|
18
|
+
|
|
19
|
+
@api.on(AGENT_END)
|
|
20
|
+
def _done(event, payload):
|
|
21
|
+
stop = payload.get("stop_reason", "unknown")
|
|
22
|
+
api.notify(f"agent ended: {stop}")
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Log every tool call to a JSONL file in the agent dir.
|
|
2
|
+
|
|
3
|
+
This is the simplest possible observability extension: a single
|
|
4
|
+
``tool_call`` handler that appends a structured line for every LLM
|
|
5
|
+
tool invocation. Useful for post-hoc debugging, sharing sessions
|
|
6
|
+
with collaborators, or feeding tool-use data into a local eval.
|
|
7
|
+
|
|
8
|
+
The log lives at ``~/.vtx/agent/tool-calls.log``. Delete it or rotate
|
|
9
|
+
it yourself; the extension does not manage retention.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
from datetime import UTC, datetime
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
from vtx.extensions import TOOL_CALL, TOOL_RESULT
|
|
19
|
+
|
|
20
|
+
_LOG_PATH = Path.home() / ".vtx" / "agent" / "tool-calls.log"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _log(record: dict) -> None:
|
|
24
|
+
_LOG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
25
|
+
with _LOG_PATH.open("a", encoding="utf-8") as f:
|
|
26
|
+
f.write(json.dumps(record) + "\n")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def register(api):
|
|
30
|
+
@api.on(TOOL_CALL)
|
|
31
|
+
def _on_call(event, payload):
|
|
32
|
+
_log(
|
|
33
|
+
{
|
|
34
|
+
"ts": datetime.now(UTC).isoformat(),
|
|
35
|
+
"event": "call",
|
|
36
|
+
"tool": payload.get("tool_name"),
|
|
37
|
+
"tool_call_id": payload.get("tool_call_id"),
|
|
38
|
+
"args": payload.get("args"),
|
|
39
|
+
}
|
|
40
|
+
)
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
@api.on(TOOL_RESULT)
|
|
44
|
+
def _on_result(event, payload):
|
|
45
|
+
result = payload.get("result")
|
|
46
|
+
_log(
|
|
47
|
+
{
|
|
48
|
+
"ts": datetime.now(UTC).isoformat(),
|
|
49
|
+
"event": "result",
|
|
50
|
+
"tool": payload.get("tool_name"),
|
|
51
|
+
"tool_call_id": payload.get("tool_call_id"),
|
|
52
|
+
"is_error": getattr(result, "is_error", None),
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
return None
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Block destructive bash commands before they run.
|
|
2
|
+
|
|
3
|
+
Subscribes to the ``tool_call`` event and returns ``{"block": True, ...}``
|
|
4
|
+
if the LLM tries to call ``bash`` with ``rm -rf``, ``sudo``, or
|
|
5
|
+
``dd of=/dev/`` style destructive patterns. The LLM sees the block
|
|
6
|
+
reason in the tool result and can try a safer alternative.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
|
|
13
|
+
from vtx.extensions import TOOL_CALL
|
|
14
|
+
|
|
15
|
+
# Conservative patterns; tune to taste. Each pattern matches anywhere
|
|
16
|
+
# in the command (we do not try to tokenize shell — that is its own
|
|
17
|
+
# rabbit hole).
|
|
18
|
+
_DESTRUCTIVE_PATTERNS: tuple[re.Pattern[str], ...] = (
|
|
19
|
+
re.compile(r"\brm\s+(-[a-zA-Z]*[rR][fF][a-zA-Z]*|--recursive)\b"),
|
|
20
|
+
re.compile(r"\brm\s+-[a-zA-Z]*[fF][rR][a-zA-Z]*\b"),
|
|
21
|
+
re.compile(r"(\b|^)sudo\b"),
|
|
22
|
+
re.compile(r"\bdd\s+.*\bof=/dev/(sd|hd|nvme|vd)"),
|
|
23
|
+
re.compile(r":\(\)\s*\{.*:\|:.*\}"), # fork bomb
|
|
24
|
+
re.compile(r"\bchmod\s+(-R\s+)?(0?[0-7]{3,4})\b\s+/"), # chmod 000 /
|
|
25
|
+
re.compile(r"\bmkfs(\.[a-z0-9]+)?\s+/dev/"),
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
_BLOCK_REASON = (
|
|
29
|
+
"Blocked by permission_gate extension: this command matches a destructive pattern. "
|
|
30
|
+
"If you really need it, ask the user to run it manually or remove the extension."
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _is_destructive(command: str) -> bool:
|
|
35
|
+
return any(p.search(command) for p in _DESTRUCTIVE_PATTERNS)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def register(api):
|
|
39
|
+
@api.on(TOOL_CALL)
|
|
40
|
+
def _gate(event, payload):
|
|
41
|
+
if payload.get("tool_name") != "bash":
|
|
42
|
+
return None
|
|
43
|
+
args = payload.get("args") or {}
|
|
44
|
+
command = args.get("command") or ""
|
|
45
|
+
if not isinstance(command, str) or not command.strip():
|
|
46
|
+
return None
|
|
47
|
+
if _is_destructive(command):
|
|
48
|
+
api.notify(f"blocked destructive bash: {command[:80]!r}", level="warning")
|
|
49
|
+
return {"block": True, "reason": _BLOCK_REASON}
|
|
50
|
+
return None
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Override the built-in ``read`` tool to log file access.
|
|
2
|
+
|
|
3
|
+
Demonstrates tool override: an extension can register a tool with the
|
|
4
|
+
same name as a built-in and the extension version wins. The original
|
|
5
|
+
behavior is preserved (we just wrap it with a log line on entry and
|
|
6
|
+
exit). Use the override pattern when you need to add auditing, access
|
|
7
|
+
control, or sandboxing to a built-in without forking vtx.
|
|
8
|
+
|
|
9
|
+
Because the built-in ``read`` is referenced via the ``tools_by_name``
|
|
10
|
+
mapping at registration time, this example does not call the original
|
|
11
|
+
implementation directly; it re-implements the same parameter contract
|
|
12
|
+
(``path``, ``offset``, ``limit``) and reads the file itself. To delegate
|
|
13
|
+
to the original, the extension would import ``vtx.tools.ReadTool`` and
|
|
14
|
+
call it with the validated params.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
from datetime import UTC, datetime
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
from vtx.extensions import TOOL_CALL, TOOL_RESULT
|
|
24
|
+
|
|
25
|
+
_LOG_PATH = Path.home() / ".vtx" / "agent" / "read-access.log"
|
|
26
|
+
_MAX_BYTES = 50 * 1024 # Mirror ReadTool's truncation ceiling
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _log_line(path: str, *, action: str, blocked: bool = False) -> None:
|
|
30
|
+
_LOG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
31
|
+
entry = {
|
|
32
|
+
"ts": datetime.now(UTC).isoformat(),
|
|
33
|
+
"action": action,
|
|
34
|
+
"path": path,
|
|
35
|
+
"blocked": blocked,
|
|
36
|
+
}
|
|
37
|
+
with _LOG_PATH.open("a", encoding="utf-8") as f:
|
|
38
|
+
f.write(json.dumps(entry) + "\n")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def register(api):
|
|
42
|
+
@api.on(TOOL_CALL)
|
|
43
|
+
def _audit_call(event, payload):
|
|
44
|
+
if payload.get("tool_name") != "read":
|
|
45
|
+
return None
|
|
46
|
+
args = payload.get("args") or {}
|
|
47
|
+
path = args.get("path", "")
|
|
48
|
+
_log_line(path, action="call")
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
@api.on(TOOL_RESULT)
|
|
52
|
+
def _audit_result(event, payload):
|
|
53
|
+
if payload.get("tool_name") != "read":
|
|
54
|
+
return None
|
|
55
|
+
args = payload.get("args") or {}
|
|
56
|
+
_log_line(args.get("path", ""), action="result")
|
|
57
|
+
return None
|
|
@@ -14,7 +14,7 @@ default = true
|
|
|
14
14
|
|
|
15
15
|
[project]
|
|
16
16
|
name = "vtx-coding-agent"
|
|
17
|
-
version = "0.1.
|
|
17
|
+
version = "0.1.2"
|
|
18
18
|
description = "Minimalist coding agent harness with a Textual TUI and headless CLI. <1k-token system prompt, 18+ LLM providers, AGENTS.md + skills context, session tree, prompt/auto permissions."
|
|
19
19
|
readme = "README.md"
|
|
20
20
|
requires-python = ">=3.12"
|