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.
Files changed (236) hide show
  1. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/CHANGELOG.md +64 -0
  2. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/PKG-INFO +5 -3
  3. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/README.md +4 -2
  4. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/docs/configuration.md +15 -0
  5. vtx_coding_agent-0.1.2/docs/extensions.md +200 -0
  6. vtx_coding_agent-0.1.2/examples/extensions/auto_commit.py +77 -0
  7. vtx_coding_agent-0.1.2/examples/extensions/hello.py +22 -0
  8. vtx_coding_agent-0.1.2/examples/extensions/log_tool_calls.py +55 -0
  9. vtx_coding_agent-0.1.2/examples/extensions/permission_gate.py +50 -0
  10. vtx_coding_agent-0.1.2/examples/extensions/tool_override.py +57 -0
  11. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/pyproject.toml +1 -1
  12. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/cli.py +24 -0
  13. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/config.py +39 -0
  14. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/defaults/config.yml +13 -1
  15. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/events.py +17 -1
  16. vtx_coding_agent-0.1.2/src/vtx/extensions.py +894 -0
  17. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/headless.py +13 -2
  18. vtx_coding_agent-0.1.2/src/vtx/llm/provider.yaml +644 -0
  19. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/providers/anthropic_sdk.py +1 -0
  20. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/providers/openai_sdk.py +24 -3
  21. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/sdk/anthropic.py +41 -0
  22. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/sdk/base.py +4 -0
  23. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/sdk/openai.py +64 -2
  24. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/loop.py +53 -0
  25. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/runtime.py +30 -0
  26. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/tools/__init__.py +31 -0
  27. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/tools/web.py +40 -4
  28. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/turn.py +76 -5
  29. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/app.py +40 -8
  30. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/commands/__init__.py +31 -0
  31. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/commands/settings.py +9 -4
  32. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/launch.py +9 -0
  33. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/version.py +1 -1
  34. vtx_coding_agent-0.1.2/tests/llm/test_thinking_wire_params.py +193 -0
  35. vtx_coding_agent-0.1.2/tests/test_extensions.py +488 -0
  36. vtx_coding_agent-0.1.2/tests/tools/test_web_expand_collapse.py +82 -0
  37. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/uv.lock +1 -1
  38. vtx_coding_agent-0.1.1/src/vtx/llm/provider.yaml +0 -280
  39. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/.agents/skills/vtx-release-publish/SKILL.md +0 -0
  40. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/.agents/skills/vtx-tmux-test/SKILL.md +0 -0
  41. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/.agents/skills/vtx-tmux-test/run-e2e-tests.sh +0 -0
  42. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/.agents/skills/vtx-tmux-test/setup-test-project.sh +0 -0
  43. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/.gitignore +0 -0
  44. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/.pre-commit-config.yaml +0 -0
  45. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/.python-version +0 -0
  46. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/AGENTS.md +0 -0
  47. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/LICENSE +0 -0
  48. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/docs/README.md +0 -0
  49. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/docs/architecture.md +0 -0
  50. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/docs/development.md +0 -0
  51. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/docs/e2e-test-coverage-review.md +0 -0
  52. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/docs/headless.md +0 -0
  53. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/docs/local-models.md +0 -0
  54. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/docs/permissions.md +0 -0
  55. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/docs/providers.md +0 -0
  56. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/docs/sessions.md +0 -0
  57. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/docs/skills.md +0 -0
  58. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/docs/storage-layout.md +0 -0
  59. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/docs/theming.md +0 -0
  60. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/docs/tools.md +0 -0
  61. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/scripts/show_themes.py +0 -0
  62. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/__init__.py +0 -0
  63. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/async_utils.py +0 -0
  64. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/builtin_skills/github/SKILL.md +0 -0
  65. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/builtin_skills/init/SKILL.md +0 -0
  66. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/builtin_skills/review/SKILL.md +0 -0
  67. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/builtin_skills/skill-builder/SKILL.md +0 -0
  68. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/context/__init__.py +0 -0
  69. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/context/_xml.py +0 -0
  70. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/context/agent_mds.py +0 -0
  71. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/context/git.py +0 -0
  72. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/context/loader.py +0 -0
  73. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/context/skills.py +0 -0
  74. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/core/__init__.py +0 -0
  75. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/core/compaction.py +0 -0
  76. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/core/errors.py +0 -0
  77. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/core/handoff.py +0 -0
  78. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/core/scratchpad.py +0 -0
  79. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/core/types.py +0 -0
  80. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/defaults/__init__.py +0 -0
  81. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/diff_display.py +0 -0
  82. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/gh_cli.py +0 -0
  83. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/git_branch.py +0 -0
  84. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/__init__.py +0 -0
  85. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/base.py +0 -0
  86. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/context_length.py +0 -0
  87. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/dynamic_models.py +0 -0
  88. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/model_fetcher.py +0 -0
  89. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/models.py +0 -0
  90. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/oauth/__init__.py +0 -0
  91. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/oauth/copilot.py +0 -0
  92. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/oauth/dynamic.py +0 -0
  93. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/oauth/openai.py +0 -0
  94. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/phase_parser.py +0 -0
  95. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/provider_catalog.py +0 -0
  96. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/providers/__init__.py +0 -0
  97. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/providers/mock.py +0 -0
  98. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/providers/sanitize.py +0 -0
  99. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/sdk/__init__.py +0 -0
  100. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/llm/tool_parser.py +0 -0
  101. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/notify.py +0 -0
  102. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/permissions.py +0 -0
  103. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/prompts/__init__.py +0 -0
  104. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/prompts/builder.py +0 -0
  105. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/prompts/env.py +0 -0
  106. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/prompts/identity.py +0 -0
  107. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/prompts/tooling.py +0 -0
  108. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/py.typed +0 -0
  109. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/session.py +0 -0
  110. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/sounds/completion.wav +0 -0
  111. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/sounds/error.wav +0 -0
  112. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/sounds/permission.wav +0 -0
  113. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/themes.py +0 -0
  114. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/tools/_read_image.py +0 -0
  115. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/tools/_tool_utils.py +0 -0
  116. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/tools/base.py +0 -0
  117. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/tools/bash.py +0 -0
  118. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/tools/edit.py +0 -0
  119. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/tools/find.py +0 -0
  120. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/tools/read.py +0 -0
  121. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/tools/skill.py +0 -0
  122. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/tools/write.py +0 -0
  123. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/tools_manager.py +0 -0
  124. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/__init__.py +0 -0
  125. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/agent_runner.py +0 -0
  126. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/app_protocol.py +0 -0
  127. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/autocomplete.py +0 -0
  128. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/blocks.py +0 -0
  129. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/chat.py +0 -0
  130. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/clipboard.py +0 -0
  131. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/commands/auth.py +0 -0
  132. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/commands/base.py +0 -0
  133. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/commands/models.py +0 -0
  134. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/commands/sessions.py +0 -0
  135. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/completion_ui.py +0 -0
  136. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/export.py +0 -0
  137. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/floating_list.py +0 -0
  138. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/formatting.py +0 -0
  139. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/input.py +0 -0
  140. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/latex.py +0 -0
  141. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/path_complete.py +0 -0
  142. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/prompt_history.py +0 -0
  143. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/queue_ui.py +0 -0
  144. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/selection_mode.py +0 -0
  145. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/session_ui.py +0 -0
  146. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/startup.py +0 -0
  147. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/styles.py +0 -0
  148. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/tool_output.py +0 -0
  149. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/tree.py +0 -0
  150. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/welcome.py +0 -0
  151. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/ui/widgets.py +0 -0
  152. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/src/vtx/update_check.py +0 -0
  153. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/conftest.py +0 -0
  154. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/context/test_agents.py +0 -0
  155. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/context/test_skills.py +0 -0
  156. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/llm/__init__.py +0 -0
  157. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/llm/test_anthropic_provider.py +0 -0
  158. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/llm/test_mock_provider.py +0 -0
  159. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/llm/test_openai_oauth.py +0 -0
  160. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/llm/test_tls_verify.py +0 -0
  161. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_agentic_loop.py +0 -0
  162. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_cli.py +0 -0
  163. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_cli_auth_flags.py +0 -0
  164. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_cli_provider_resolution.py +0 -0
  165. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_compaction.py +0 -0
  166. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_config_binaries.py +0 -0
  167. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_config_error_fallback.py +0 -0
  168. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_config_injection.py +0 -0
  169. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_config_migration.py +0 -0
  170. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_dynamic_auth.py +0 -0
  171. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_dynamic_models.py +0 -0
  172. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_errors.py +0 -0
  173. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_git_branch.py +0 -0
  174. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_handoff.py +0 -0
  175. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_handoff_link_interrupt.py +0 -0
  176. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_headless.py +0 -0
  177. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_launch_warnings.py +0 -0
  178. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_llm_lazy_imports.py +0 -0
  179. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_local_auth_config.py +0 -0
  180. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_model_provider_resolution.py +0 -0
  181. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_notifications_config.py +0 -0
  182. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_notify.py +0 -0
  183. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_openai_compat.py +0 -0
  184. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_permissions.py +0 -0
  185. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_phase_parser.py +0 -0
  186. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_prompts.py +0 -0
  187. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_runtime_switch_model.py +0 -0
  188. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_session_persistence.py +0 -0
  189. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_session_queries.py +0 -0
  190. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_session_resume.py +0 -0
  191. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_session_tree.py +0 -0
  192. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_system_prompt.py +0 -0
  193. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_system_prompt_git_context.py +0 -0
  194. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_themes.py +0 -0
  195. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_tool_parser.py +0 -0
  196. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_tools_manager.py +0 -0
  197. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_ui_notifications.py +0 -0
  198. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_update_check.py +0 -0
  199. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/test_update_notice_behavior.py +0 -0
  200. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/tools/test_bash_shell.py +0 -0
  201. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/tools/test_bash_truncation.py +0 -0
  202. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/tools/test_diff.py +0 -0
  203. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/tools/test_edit.py +0 -0
  204. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/tools/test_edit_display.py +0 -0
  205. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/tools/test_read.py +0 -0
  206. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/tools/test_read_image.py +0 -0
  207. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/tools/test_read_image_integration.py +0 -0
  208. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/tools/test_read_image_resize.py +0 -0
  209. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/tools/test_skill.py +0 -0
  210. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/tools/test_subprocess_cancellation.py +0 -0
  211. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/tools/test_write.py +0 -0
  212. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_app_approval_keys.py +0 -0
  213. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_autocomplete.py +0 -0
  214. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_completion_chrome.py +0 -0
  215. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_export.py +0 -0
  216. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_floating_list.py +0 -0
  217. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_info_bar_clicks.py +0 -0
  218. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_info_bar_permissions.py +0 -0
  219. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_input_approval_submit.py +0 -0
  220. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_input_cursor_theme.py +0 -0
  221. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_input_handoff.py +0 -0
  222. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_input_paste.py +0 -0
  223. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_input_shell_style.py +0 -0
  224. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_keybindings.py +0 -0
  225. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_latex.py +0 -0
  226. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_login_command.py +0 -0
  227. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_permission_selection_status.py +0 -0
  228. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_permissions_command.py +0 -0
  229. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_prompt_history.py +0 -0
  230. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_queue_editing.py +0 -0
  231. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_shell_command_detection.py +0 -0
  232. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_status_line.py +0 -0
  233. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_streaming_blocks.py +0 -0
  234. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_styles.py +0 -0
  235. {vtx_coding_agent-0.1.1 → vtx_coding_agent-0.1.2}/tests/ui/test_thinking_notifications_commands.py +0 -0
  236. {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.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 operates on a default system prompt of **under 270 tokens**. Including full tool descriptions and parameter schemas, the entire runtime environment consumes only **~1,000 tokens**.
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 small, Vtx leaves the model's context window open for what matters most: **your code, your project files, and your task context**.
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 operates on a default system prompt of **under 270 tokens**. Including full tool descriptions and parameter schemas, the entire runtime environment consumes only **~1,000 tokens**.
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 small, Vtx leaves the model's context window open for what matters most: **your code, your project files, and your task context**.
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.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"