mantis-agent-sdk 2.2.0__tar.gz → 2.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/CHANGELOG.md +12 -0
  2. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/PKG-INFO +1 -1
  3. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/__init__.py +1 -1
  4. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/builtin_tools/fs.py +42 -0
  5. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/tui_fullscreen.py +81 -5
  6. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/pyproject.toml +1 -1
  7. mantis_agent_sdk-2.3.0/tests/test_write_guard.py +70 -0
  8. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/.gitignore +0 -0
  9. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/LICENSE +0 -0
  10. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/README.md +0 -0
  11. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/RELEASING.md +0 -0
  12. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/SEMVER.md +0 -0
  13. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/docs/api/client.md +0 -0
  14. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/docs/api/errors.md +0 -0
  15. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/docs/api/index.md +0 -0
  16. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/docs/api/messages.md +0 -0
  17. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/docs/api/options.md +0 -0
  18. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/docs/api/sessions.md +0 -0
  19. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/docs/api/tools.md +0 -0
  20. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/docs/development/plan-v1.md +0 -0
  21. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/docs/development/plan.md +0 -0
  22. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/docs/development/upstream-comparison.md +0 -0
  23. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/docs/examples/index.md +0 -0
  24. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/docs/getting-started/configuration.md +0 -0
  25. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/docs/getting-started/index.md +0 -0
  26. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/docs/getting-started/installation.md +0 -0
  27. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/docs/getting-started/local-setup.md +0 -0
  28. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/docs/getting-started/quickstart.md +0 -0
  29. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/docs/guides/budget.md +0 -0
  30. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/docs/guides/hooks.md +0 -0
  31. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/docs/guides/index.md +0 -0
  32. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/docs/guides/mcp.md +0 -0
  33. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/docs/guides/memory.md +0 -0
  34. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/docs/guides/models-and-backends.md +0 -0
  35. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/docs/guides/permissions.md +0 -0
  36. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/docs/guides/plugins.md +0 -0
  37. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/docs/guides/sessions.md +0 -0
  38. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/docs/guides/streaming.md +0 -0
  39. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/docs/guides/sub-agents.md +0 -0
  40. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/docs/guides/thinking.md +0 -0
  41. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/docs/guides/tools.md +0 -0
  42. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/docs/index.md +0 -0
  43. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/docs/internals/PARITY_ROADMAP.md +0 -0
  44. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/agent.py +0 -0
  45. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/anthropic_oauth.py +0 -0
  46. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/bench.py +0 -0
  47. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/budget.py +0 -0
  48. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/builtin_tools/__init__.py +0 -0
  49. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/builtin_tools/ask.py +0 -0
  50. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/builtin_tools/codenav.py +0 -0
  51. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/builtin_tools/memory_tool.py +0 -0
  52. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/builtin_tools/plan.py +0 -0
  53. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/builtin_tools/skill_tool.py +0 -0
  54. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/builtin_tools/todo.py +0 -0
  55. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/builtin_tools/web.py +0 -0
  56. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/capabilities.py +0 -0
  57. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/catalog.py +0 -0
  58. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/claude_compat.py +0 -0
  59. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/cli.py +0 -0
  60. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/clipboard.py +0 -0
  61. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/compact.py +0 -0
  62. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/compat_query.py +0 -0
  63. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/errors.py +0 -0
  64. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/events.py +0 -0
  65. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/examples/__init__.py +0 -0
  66. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/examples/fireworks_hosted.py +0 -0
  67. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/examples/max_budget_usd.py +0 -0
  68. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/examples/mcp_calculator.py +0 -0
  69. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/examples/mcp_filesystem.py +0 -0
  70. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/examples/multi_agent_research.py +0 -0
  71. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/examples/ollama_local.py +0 -0
  72. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/examples/quick_start.py +0 -0
  73. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/examples/quickstart.py +0 -0
  74. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/examples/research_agent.py +0 -0
  75. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/examples/stderr_callback_example.py +0 -0
  76. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/examples/streaming_mode_ipython.py +0 -0
  77. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/examples/streaming_render.py +0 -0
  78. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/examples/system_prompt.py +0 -0
  79. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/examples/tools_option.py +0 -0
  80. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/examples/vllm_self_hosted.py +0 -0
  81. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/examples/with_thinking.py +0 -0
  82. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/examples/with_tracing.py +0 -0
  83. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/hooks.py +0 -0
  84. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/http.py +0 -0
  85. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/mcp/__init__.py +0 -0
  86. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/mcp/client.py +0 -0
  87. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/mcp/server.py +0 -0
  88. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/mcp/transports/__init__.py +0 -0
  89. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/mcp/transports/base.py +0 -0
  90. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/mcp/transports/http.py +0 -0
  91. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/mcp/transports/in_process.py +0 -0
  92. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/mcp/transports/sse.py +0 -0
  93. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/mcp/transports/stdio.py +0 -0
  94. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/mcp/types.py +0 -0
  95. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/memory.py +0 -0
  96. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/memory_recall.py +0 -0
  97. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/paths.py +0 -0
  98. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/permissions.py +0 -0
  99. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/project_memory.py +0 -0
  100. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/providers/__init__.py +0 -0
  101. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/providers/anthropic_passthrough.py +0 -0
  102. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/providers/base.py +0 -0
  103. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/providers/llamacpp.py +0 -0
  104. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/providers/mock.py +0 -0
  105. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/providers/modal_provider.py +0 -0
  106. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/providers/ollama.py +0 -0
  107. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/providers/openai_compat.py +0 -0
  108. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/providers/tgi.py +0 -0
  109. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/query.py +0 -0
  110. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/response_format.py +0 -0
  111. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/retry.py +0 -0
  112. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/routing.py +0 -0
  113. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/session.py +0 -0
  114. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/session_tree.py +0 -0
  115. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/settings.py +0 -0
  116. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/setup_local.py +0 -0
  117. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/setup_local_llamacpp.py +0 -0
  118. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/setup_wizard.py +0 -0
  119. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/skills.py +0 -0
  120. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/streaming/__init__.py +0 -0
  121. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/streaming/executor.py +0 -0
  122. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/streaming/text_tool_parser.py +0 -0
  123. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/streaming/thinking_parser.py +0 -0
  124. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/subagent.py +0 -0
  125. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/system_reminder.py +0 -0
  126. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/tools.py +0 -0
  127. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/tracing.py +0 -0
  128. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/transcripts.py +0 -0
  129. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/tui.py +0 -0
  130. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/mantis_agent/types.py +0 -0
  131. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/__init__.py +0 -0
  132. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/conftest.py +0 -0
  133. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/public_api_surface.txt +0 -0
  134. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/run_verbatim_examples.py +0 -0
  135. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_anthropic_oauth.py +0 -0
  136. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_anthropic_passthrough.py +0 -0
  137. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_ask_user_question.py +0 -0
  138. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_backend_routing.py +0 -0
  139. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_bash_background.py +0 -0
  140. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_bash_hardening.py +0 -0
  141. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_capabilities.py +0 -0
  142. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_catalog.py +0 -0
  143. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_claude_examples_verbatim.py +0 -0
  144. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_claude_sdk_parity.py +0 -0
  145. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_clipboard.py +0 -0
  146. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_compaction.py +0 -0
  147. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_context_block.py +0 -0
  148. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_context_view.py +0 -0
  149. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_diff_command.py +0 -0
  150. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_docs_site.py +0 -0
  151. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_example_fireworks_hosted.py +0 -0
  152. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_example_vllm_self_hosted.py +0 -0
  153. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_examples_multi_backend.py +0 -0
  154. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_export_copy.py +0 -0
  155. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_file_mentions.py +0 -0
  156. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_grep_upgrades.py +0 -0
  157. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_hook_matchers.py +0 -0
  158. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_kimi_routing.py +0 -0
  159. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_lsp.py +0 -0
  160. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_lsp_multilang.py +0 -0
  161. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_lsp_symbols.py +0 -0
  162. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_mantis_agent_directory.py +0 -0
  163. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_mantis_agent_options.py +0 -0
  164. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_mcp_elicitation.py +0 -0
  165. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_mcp_resources_prompts.py +0 -0
  166. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_mcp_sampling.py +0 -0
  167. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_mcp_tools_bridging.py +0 -0
  168. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_memory_command.py +0 -0
  169. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_memory_recall.py +0 -0
  170. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_microcompaction.py +0 -0
  171. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_mid_stream_cancellation.py +0 -0
  172. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_modal_provider.py +0 -0
  173. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_model_fallback.py +0 -0
  174. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_model_setup_sources.py +0 -0
  175. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_multiedit_and_todo.py +0 -0
  176. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_multimodal_read.py +0 -0
  177. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_notebook_edit.py +0 -0
  178. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_notebook_read.py +0 -0
  179. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_permission_ask.py +0 -0
  180. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_permission_denials_in_result.py +0 -0
  181. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_permission_denials_surfaced.py +0 -0
  182. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_permission_updated_input.py +0 -0
  183. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_plan_mode.py +0 -0
  184. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_plugin_wiring.py +0 -0
  185. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_production_polish.py +0 -0
  186. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_project_memory.py +0 -0
  187. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_prompt_caching.py +0 -0
  188. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_public_api_surface.py +0 -0
  189. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_query_wrapper.py +0 -0
  190. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_real_kimi.py +0 -0
  191. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_real_ollama.py +0 -0
  192. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_recall_wiring.py +0 -0
  193. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_release_artifacts.py +0 -0
  194. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_response_format.py +0 -0
  195. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_run_loop_integration.py +0 -0
  196. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_runaway_guard.py +0 -0
  197. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_session_fork_resume.py +0 -0
  198. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_session_fresh_context.py +0 -0
  199. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_session_tree.py +0 -0
  200. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_setting_sources.py +0 -0
  201. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_setup_local.py +0 -0
  202. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_setup_local_llamacpp.py +0 -0
  203. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_skills_wiring.py +0 -0
  204. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_streaming_completions.py +0 -0
  205. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_streaming_mode.py +0 -0
  206. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_streaming_query.py +0 -0
  207. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_streaming_tool_dispatch.py +0 -0
  208. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_subagent_multi_agent.py +0 -0
  209. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_system_reminder.py +0 -0
  210. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_text_tool_call_salvage.py +0 -0
  211. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_thinking_render.py +0 -0
  212. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_thinking_variants.py +0 -0
  213. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_todo_reinjection.py +0 -0
  214. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_tool_permission_signal.py +0 -0
  215. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_tool_result_truncation.py +0 -0
  216. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_tools.py +0 -0
  217. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_tracing.py +0 -0
  218. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_tui_permission_modes.py +0 -0
  219. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_types_roundtrip.py +0 -0
  220. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_version.py +0 -0
  221. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_vim_mode.py +0 -0
  222. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_web_fetch.py +0 -0
  223. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_web_search_ddg.py +0 -0
  224. {mantis_agent_sdk-2.2.0 → mantis_agent_sdk-2.3.0}/tests/test_word_diff.py +0 -0
@@ -74,6 +74,18 @@ The full versioning policy is in [SEMVER.md](SEMVER.md).
74
74
  Three new public exports: `ResponseFormatError`,
75
75
  `normalize_response_format`, `translate_response_format`.
76
76
 
77
+ ## [2.3.0] — 2026-06-30
78
+
79
+ ### Added
80
+
81
+ - **Read-before-write guard** (Claude Code's readFileState). `write_file` now
82
+ refuses to clobber an existing file the tools haven't *seen* this session, or
83
+ one that changed on disk since it was read — so unseen or newer content is
84
+ never silently destroyed by a blind overwrite. The tools (`read_file`,
85
+ `write_file`, `edit_file`, `multi_edit`) track each file's mtime; new files and
86
+ read-then-write / write-then-overwrite flows pass freely, and the error tells
87
+ the model to read first (recoverable in one step).
88
+
77
89
  ## [2.2.0] — 2026-06-30
78
90
 
79
91
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mantis-agent-sdk
3
- Version: 2.2.0
3
+ Version: 2.3.0
4
4
  Summary: Drop-in open-source agent SDK. Multi-model, streaming, MCP, sub-agents.
5
5
  Project-URL: Homepage, https://github.com/teddyoweh/mantis-agent-sdk
6
6
  Project-URL: Repository, https://github.com/teddyoweh/mantis-agent-sdk
@@ -284,7 +284,7 @@ def _detect_version() -> str:
284
284
 
285
285
  return version("mantis-agent-sdk")
286
286
  except Exception: # pragma: no cover - extremely defensive
287
- return "2.2.0"
287
+ return "2.3.0"
288
288
 
289
289
 
290
290
  __version__ = _detect_version()
@@ -34,6 +34,43 @@ _MAX_LINE = 2000 # chars per line before truncation
34
34
  _MAX_MATCHES = 200 # grep/glob hits returned
35
35
 
36
36
 
37
+ # ---------------------------------------------------------------------------
38
+ # Read-before-write guard (Claude Code's readFileState). Tracks the mtime of
39
+ # every file a tool has *seen* (read or written) this process. write_file then
40
+ # refuses to clobber an existing file the tools haven't seen, or one changed on
41
+ # disk since — so unseen/newer content is never silently destroyed. New files
42
+ # pass freely.
43
+ # ---------------------------------------------------------------------------
44
+ _FILE_READS: dict[str, float] = {}
45
+
46
+
47
+ def _record_seen(p: Path) -> None:
48
+ try:
49
+ _FILE_READS[str(p.resolve())] = p.stat().st_mtime
50
+ except OSError:
51
+ pass
52
+
53
+
54
+ def _check_write_guard(p: Path) -> None:
55
+ if not p.exists() or not p.is_file():
56
+ return # new file — nothing to clobber
57
+ seen = _FILE_READS.get(str(p.resolve()))
58
+ if seen is None:
59
+ raise ValueError(
60
+ f"{p} already exists but hasn't been read this session. Read it first "
61
+ f"so you don't overwrite content you haven't seen (write_file replaces "
62
+ f"the ENTIRE file). Use edit_file for a targeted change."
63
+ )
64
+ try:
65
+ if p.stat().st_mtime > seen + 1e-6:
66
+ raise ValueError(
67
+ f"{p} was modified on disk since you last read it. Read it again "
68
+ f"before writing so you don't clobber the newer version."
69
+ )
70
+ except OSError:
71
+ pass
72
+
73
+
37
74
  def _truncate(text: str, limit: int = _MAX_OUTPUT) -> str:
38
75
  if len(text) <= limit:
39
76
  return text
@@ -423,6 +460,7 @@ async def read_file(path: str, offset: int = 1, limit: int = _MAX_READ_LINES) ->
423
460
  if p.is_dir():
424
461
  raise IsADirectoryError(f"{path} is a directory — use ls instead")
425
462
 
463
+ _record_seen(p) # the agent has now seen this file — write_file may touch it
426
464
  suffix = p.suffix.lower()
427
465
  if suffix in _IMAGE_READ_EXTS:
428
466
  import base64 # noqa: PLC0415
@@ -480,6 +518,7 @@ async def write_file(path: str, content: str) -> str:
480
518
  content = str(content)
481
519
 
482
520
  p = Path(path).expanduser()
521
+ _check_write_guard(p) # don't blind-overwrite an unseen / externally-changed file
483
522
  old = ""
484
523
  if p.exists() and p.is_file():
485
524
  old = await anyio.to_thread.run_sync(lambda: p.read_text("utf-8", "replace"))
@@ -489,6 +528,7 @@ async def write_file(path: str, content: str) -> str:
489
528
  p.write_text(content, "utf-8")
490
529
 
491
530
  await anyio.to_thread.run_sync(_write)
531
+ _record_seen(p) # we just wrote it — subsequent writes/edits are fine
492
532
  return _edit_summary("Wrote" if not old else "Updated", str(p), old, content)
493
533
 
494
534
 
@@ -522,6 +562,7 @@ async def edit_file(
522
562
  )
523
563
  updated = text.replace(old_string, new_string)
524
564
  await anyio.to_thread.run_sync(lambda: p.write_text(updated, "utf-8"))
565
+ _record_seen(p)
525
566
  return _edit_summary("Updated", str(p), text, updated)
526
567
 
527
568
 
@@ -565,6 +606,7 @@ async def multi_edit(path: str, edits: list[dict]) -> str:
565
606
  applied += 1
566
607
 
567
608
  await anyio.to_thread.run_sync(lambda: p.write_text(text, "utf-8"))
609
+ _record_seen(p)
568
610
  return _edit_summary("Updated", str(p), original, text)
569
611
 
570
612
 
@@ -716,12 +716,88 @@ async def run_fullscreen(tui: Any) -> int:
716
716
  # /models [partial] → picker overlay, pre-filtered if given.
717
717
  _open_model_picker(arg or "")
718
718
  return True
719
+ if cmd == "/disable":
720
+ from . import catalog # noqa: PLC0415
721
+
722
+ enabled = [p.id for p in catalog.CATALOG if catalog.is_enabled(p)]
723
+ pid = arg.strip().lower()
724
+ if not pid:
725
+ await _print(lambda e=enabled: tui.console.print(
726
+ "[ansibrightblack]usage: [white]/disable <provider>[/] · enabled: "
727
+ f"[white]{', '.join(e) or 'none'}[/][/]"))
728
+ return True
729
+ prov = catalog.BY_ID.get(pid)
730
+ if prov is None:
731
+ await _print(lambda: tui.console.print(
732
+ f"[ansibrightblack](unknown provider [white]{pid}[/] — try one of: "
733
+ f"{', '.join(p.id for p in catalog.CATALOG)})[/]"))
734
+ return True
735
+ removed = catalog.clear_key(pid)
736
+ if pid == "anthropic":
737
+ import os as _os # noqa: PLC0415
738
+ _os.environ.pop("ANTHROPIC_AUTH_TOKEN", None)
739
+ removed = True
740
+ state.pop("model_cache", None) # picker reflects the change
741
+ await _print(lambda r=removed, lbl=prov.label: tui.console.print(
742
+ f"[ansibrightblack]({'forgot ' + lbl if r else 'no saved key for ' + lbl})[/]"))
743
+ return True
744
+ if cmd == "/enable":
745
+ from . import catalog # noqa: PLC0415
746
+
747
+ pid = arg.strip().lower()
748
+ prov = catalog.BY_ID.get(pid)
749
+ if prov is None:
750
+ await _print(lambda: tui.console.print(
751
+ "[ansibrightblack]usage: [white]/enable <provider>[/] · providers: "
752
+ f"[white]{', '.join(p.id for p in catalog.CATALOG)}[/][/]"))
753
+ return True
754
+ # Reuse the picker's inline key-entry: prompt (masked) for the key,
755
+ # then validate + enable + switch to the provider's flagship model.
756
+ state["awaiting_key"] = {"provider_id": pid, "model": prov.models[0]}
757
+ input_buffer.reset()
758
+ await _announce(f"paste your {prov.api_key_env} to enable {pid} · enter to confirm · esc to cancel")
759
+ return True
760
+ if cmd == "/connect":
761
+ parts = arg.split()
762
+ if not parts or not parts[0].startswith(("http://", "https://")):
763
+ await _print(lambda: tui.console.print(
764
+ "[ansibrightblack]usage: [white]/connect <url> [model][/] — e.g. "
765
+ "[white]/connect http://localhost:8000/v1 qwen2.5-coder:7b[/][/]"))
766
+ return True
767
+ url = parts[0].rstrip("/")
768
+ model = parts[1] if len(parts) > 1 else tui.model
769
+ tui.backend, tui.model = url, model
770
+ tui.agent = tui._build_agent()
771
+ if tui.agent is not None and tui.agent.permissions is not None:
772
+ tui.agent.permissions.asker = _ask_permission
773
+ try:
774
+ from . import catalog # noqa: PLC0415
775
+ catalog.set_last_model(model, url)
776
+ except Exception: # noqa: BLE001
777
+ pass
778
+ state.pop("model_cache", None)
779
+ await _print(lambda u=url, m=model: tui.console.print(
780
+ f"[ansibrightblack](connected · [white]{m}[/] @ [white]{u}[/] · self-hosted)[/]"))
781
+ return True
719
782
  if cmd == "/help":
720
- await _print(lambda: tui.console.print(
721
- "\n[bold]commands[/] [white]/model[/] <id> · [white]/clear[/] · "
722
- "[white]/cwd[/] · [white]/exit[/]\n"
723
- "[ansibrightblack]@file to attach a path · shift+tab cycles mode · esc/Ctrl+C interrupts a "
724
- "running reply (Ctrl+C also quits when idle) · Ctrl+D quits[/]\n"))
783
+ def _help() -> None:
784
+ w, d = "white", "ansibrightblack"
785
+ tui.console.print("\n[bold]commands[/]")
786
+ tui.console.print(
787
+ f" [{d}]models[/] [{w}]/models[/] [{d}][filter][/] browse & pick (type to filter) · "
788
+ f"[{w}]/model[/] <id> switch")
789
+ tui.console.print(
790
+ f" [{d}] [/] [{w}]/enable[/] <provider> · [{w}]/disable[/] <provider> · "
791
+ f"[{w}]/connect[/] <url> [model] (self-host)")
792
+ tui.console.print(
793
+ f" [{d}]session[/] [{w}]/clear[/] · [{w}]/memory[/] · [{w}]/context[/] · "
794
+ f"[{w}]/copy[/] · [{w}]/export[/] · [{w}]/diff[/] · [{w}]/cwd[/]")
795
+ tui.console.print(
796
+ f" [{d}]quit[/] [{w}]/exit[/] (or Ctrl+D · Ctrl+C when idle)")
797
+ tui.console.print(
798
+ f" [{d}]keys[/] [{d}]@file to attach a path · shift+tab cycles mode · "
799
+ f"esc/Ctrl+C interrupts a running reply[/]\n")
800
+ await _print(_help)
725
801
  return True
726
802
  return False # unknown → treat as a normal prompt
727
803
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mantis-agent-sdk"
3
- version = "2.2.0"
3
+ version = "2.3.0"
4
4
  description = "Drop-in open-source agent SDK. Multi-model, streaming, MCP, sub-agents."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -0,0 +1,70 @@
1
+ """Read-before-write guard: write_file won't blind-clobber an unseen/stale file."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from pathlib import Path
7
+
8
+ import anyio
9
+ import pytest
10
+
11
+ import mantis_agent.builtin_tools.fs as fs
12
+ from mantis_agent.builtin_tools.fs import edit_file, read_file, write_file
13
+
14
+
15
+ @pytest.fixture(autouse=True)
16
+ def _clear_tracker():
17
+ fs._FILE_READS.clear()
18
+ yield
19
+ fs._FILE_READS.clear()
20
+
21
+
22
+ def test_new_file_writes_freely(tmp_path: Path) -> None:
23
+ p = tmp_path / "new.txt"
24
+ out = anyio.run(lambda: write_file.fn(path=str(p), content="hello"))
25
+ assert p.read_text() == "hello"
26
+ assert "Wrote" in out
27
+
28
+
29
+ def test_existing_unread_file_is_guarded(tmp_path: Path) -> None:
30
+ p = tmp_path / "exists.txt"
31
+ p.write_text("precious unseen content")
32
+ with pytest.raises(ValueError, match="hasn't been read"):
33
+ anyio.run(lambda: write_file.fn(path=str(p), content="clobbered"))
34
+ assert p.read_text() == "precious unseen content" # untouched
35
+
36
+
37
+ def test_read_then_write_allowed(tmp_path: Path) -> None:
38
+ p = tmp_path / "f.txt"
39
+ p.write_text("original")
40
+ anyio.run(lambda: read_file.fn(path=str(p))) # now seen
41
+ anyio.run(lambda: write_file.fn(path=str(p), content="new"))
42
+ assert p.read_text() == "new"
43
+
44
+
45
+ def test_write_then_overwrite_allowed(tmp_path: Path) -> None:
46
+ p = tmp_path / "f.txt"
47
+ anyio.run(lambda: write_file.fn(path=str(p), content="v1")) # creates + records
48
+ anyio.run(lambda: write_file.fn(path=str(p), content="v2")) # allowed
49
+ assert p.read_text() == "v2"
50
+
51
+
52
+ def test_modified_since_read_is_guarded(tmp_path: Path) -> None:
53
+ p = tmp_path / "f.txt"
54
+ p.write_text("v1")
55
+ anyio.run(lambda: read_file.fn(path=str(p)))
56
+ # simulate an external edit AFTER our read (bump mtime into the future)
57
+ future = os.stat(p).st_mtime + 100
58
+ os.utime(p, (future, future))
59
+ with pytest.raises(ValueError, match="modified on disk"):
60
+ anyio.run(lambda: write_file.fn(path=str(p), content="v2"))
61
+
62
+
63
+ def test_edit_records_so_write_is_allowed(tmp_path: Path) -> None:
64
+ p = tmp_path / "f.txt"
65
+ p.write_text("alpha beta")
66
+ anyio.run(lambda: read_file.fn(path=str(p)))
67
+ anyio.run(lambda: edit_file.fn(path=str(p), old_string="alpha", new_string="ALPHA"))
68
+ # edit updated the tracker's mtime → a following write_file doesn't false-trip
69
+ anyio.run(lambda: write_file.fn(path=str(p), content="rewritten"))
70
+ assert p.read_text() == "rewritten"